KT6368A 芯片是一款支持蓝牙双模的纯数据芯片,蓝牙5.1版本。芯片的亮点在超小尺寸,超级价格。以及简单明了的透传和串口AT控制功能。大大降低了嵌入蓝牙在其它产品的开发难度和成本。
同时支持 SPP 和 BLE 。但是只能任选其中一个协议使用。
备注:这款芯片最大的特点,就是便宜,使用简单,生产简单。无其他,便宜才是王道。
请注意,一旦蓝牙被连接之后,芯片自动进入透传模式。不再识别AT指令。所以AT指令只能用于,未连接状态下面使用 。
引脚说明
引脚编号 |
名称 |
I/O 类型 |
功能 |
其他功能 |
1 |
VBAT |
P |
LDO 电源 |
- |
1 |
VDDIO |
P |
IO 电源 3.3V |
- |
2 |
PA9 |
I/O |
GPIO(上拉) |
长按复位; ADC8:ADC 通道 8; |
3 |
VSS |
P |
GND(地) |
- |
4 |
BT_RF |
- |
射频天线 |
- |
5 |
BTOSCI |
I |
BTOSCI |
- |
6 |
BTOSCO |
O |
BTOSCO |
- |
7 |
USB0DM |
I/O |
GPIO(下拉) |
IIC_SDA_A:IIC 数据线 (A); ADC11:ADC 通道 11; UART1_RXD:UART1 数据输入(D); |
8 |
USB0DP |
I/O |
GPIO(下拉) |
IIC_SCL_A:IIC 时钟线 (A); ADC10:ADC 通道 10; UART1_TXD:UART1 数据输出(D); |
串口命令
KT6368A 使用 AT 串口指令进行设置,具体通讯命令请查看用户手册;
上位机实现
假设我们要通过上位机发送一个 .bin 文件到 MCU
数据发送过程中需要解决的问题
数据完整性:防止数据丢失或乱序。
数据准确性:确保接收的数据与发送的数据一致,避免传输过程中数据损坏。
传输控制:支持数据分片、确认和重传机制,避免传输中断导致的失败。
文件校验:在文件传输完成后,使用校验(如 CRC 或 MD5)确保文件内容无误。
解决方案
由于 .bin 文件通常比较大,蓝牙传输(尤其是通过 UART 的模块)无法一次发送整个文件,因此需要分片传输。具体步骤如下:
1.文件分片
- 将 .bin 文件按照固定大小(如 512 字节/1024 字节)分割为多个数据包。
- 每个数据包包含以下信息:
- 包头:标识数据包起始,如固定值 0xFFAA。
- 包编号:用于标识每个数据包,防止乱序。
- 数据长度:本包中数据部分的字节数。
- 数据内容:文件的实际数据片段。
- 校验值:如 CRC16 或简单的和校验,用于确保本包内容的完整性。
- 包尾:标识数据包结束,如固定值 0xFFBB。
2.确认与重传机制
- 每发送一个包,上位机等待 MCU 的确认(ACK)。
- 如果接收到 ACK,则发送下一个数据包。
- 如果超时未收到 ACK,或者收到 NACK,则重发当前数据包。
3.文件完整性校验
- 文件发送完成后,发送整个文件的校验值(如 CRC32、MD5),让 MCU 对接收到的文件进行校验,确保完整性。
- 如果校验失败,则重新发送。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72
| import time import struct from bleak import BleakClient
DEVICE_ADDRESS = "XX:XX:XX:XX:XX:XX" SERVICE_UUID = "your-service-uuid" CHAR_UUID = "your-char-uuid"
CHUNK_SIZE = 512
def calc_crc16(data: bytes) -> int: crc = 0xFFFF for byte in data: crc ^= byte for _ in range(8): if crc & 1: crc = (crc >> 1) ^ 0xA001 else: crc >>= 1 return crc & 0xFFFF
async def send_file(file_path): async with BleakClient(DEVICE_ADDRESS) as client: print("Connected to Bluetooth device")
with open(file_path, "rb") as f: file_data = f.read()
total_size = len(file_data) print(f"File size: {total_size} bytes")
num_chunks = (total_size + CHUNK_SIZE - 1) // CHUNK_SIZE for i in range(num_chunks): start = i * CHUNK_SIZE end = min(start + CHUNK_SIZE, total_size) chunk = file_data[start:end]
chunk_len = len(chunk) crc = calc_crc16(chunk) packet = struct.pack("<HHH", 0x55AA, i, chunk_len) + chunk + struct.pack("<H", crc) + struct.pack("<H", 0xFFBB)
await client.write_gatt_char(CHAR_UUID, packet) print(f"Sent chunk {i + 1}/{num_chunks}")
ack = await client.read_gatt_char(CHAR_UUID) if ack != b"ACK": print(f"Error: No ACK for chunk {i + 1}. Retrying...") i -= 1
file_crc = calc_crc16(file_data) packet = struct.pack("<I", file_crc) + struct.pack("<H", 0xFFBB) await client.write_gatt_char(CHAR_UUID, packet) print("File transfer complete. Waiting for MCU confirmation...")
response = await client.read_gatt_char(CHAR_UUID) if response == b"SUCCESS": print("File transfer successful!") else: print("File transfer failed. MCU reported an error.")
|
MCU 实现
蓝牙模块的 GATT 服务接收这些包后,通过 UART 将每个数据包发送给 MCU。
MCU 的 UART 接口逐字节接收数据,并根据协议(如分片协议、CRC 校验等)处理。
MCU 对每个接收到的数据包进行校验,确认无误后返回 ACK(通过 UART 发送到蓝牙模块)。
蓝牙模块再通过 GATT 将 ACK 发送给上位机。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include "stm32f1xx_hal.h" #include <string.h> #include <stdbool.h>
#define UART_BUFFER_SIZE 1024 #define FLASH_START_ADDRESS 0x08020000
uint8_t uart_rx_buffer[UART_BUFFER_SIZE]; uint8_t file_buffer[UART_BUFFER_SIZE]; uint32_t received_crc = 0; uint32_t file_size = 0; uint32_t current_address = FLASH_START_ADDRESS;
uint16_t calc_crc16(uint8_t *data, uint32_t length) { uint16_t crc = 0xFFFF; for (uint32_t i = 0; i < length; i++) { crc ^= data[i]; for (uint8_t j = 0; j < 8; j++) { if (crc & 1) crc = (crc >> 1) ^ 0xA001; else crc >>= 1; } } return crc; }
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { uint16_t packet_header = (uart_rx_buffer[1] << 8) | uart_rx_buffer[0]; uint16_t chunk_index = (uart_rx_buffer[3] << 8) | uart_rx_buffer[2]; uint16_t chunk_len = (uart_rx_buffer[5] << 8) | uart_rx_buffer[4]; uint16_t received_crc = (uart_rx_buffer[chunk_len + 6] << 8) | uart_rx_buffer[chunk_len + 7]; uint16_t packet_tail = (uart_rx_buffer[chunk_len + 8] << 8) | uart_rx_buffer[chunk_len + 9];
if (packet_header == 0x55AA && packet_tail == 0xFFBB) { uint16_t calculated_crc = calc_crc16(&uart_rx_buffer[6], chunk_len); if (calculated_crc == received_crc) { memcpy(file_buffer, &uart_rx_buffer[6], chunk_len); HAL_UART_Transmit(&huart1, (uint8_t *)"ACK", 3, 100); } else { HAL_UART_Transmit(&huart1, (uint8_t *)"NACK", 4, 100); } } else { HAL_UART_Transmit(&huart1, (uint8_t *)"NACK", 4, 100); }
HAL_UART_Receive_IT(&huart1, uart_rx_buffer, UART_BUFFER_SIZE); } }
|
示例电路
示例代码
https://github.com/TooUpper/SensorDrive