Xmodem-1K 协议介绍
Xmodem-1K 是 Xmodem 协议的扩展版本,主要特点是使用 1024字节(1K)的数据块 进行传输,以提高传输效率。以下是其传输机制、优点和缺点的详细介绍。
---
传输机制
1. 初始化传输
- 接收方发送 NAK 信号,请求发送方开始传输。
- 如果支持 Xmodem-1K,接收方会发送字符 `'C'`,表示要求使用 **CRC 校验** 和 **1024字节数据块**。
2. 数据包结构
- 每个数据包包含以下部分:
- 起始字符:`SOH`(表示128字节数据块)或 `STX`(表示1024字节数据块)。
- 块编号:1字节,从1开始递增。
- 块编号补码:1字节,块编号的按位取反。
- 数据:1024字节的有效数据(不足部分用填充字节补全)。
- 校验和:2字节的 CRC 校验值。
3. 数据传输
- 发送方按顺序发送数据块。
- 接收方收到数据块后,检查块编号、CRC 校验和数据完整性。
- 如果数据正确,接收方发送 ACK,请求下一个数据块。
- 如果数据错误,接收方发送 NAK,要求重传当前数据块。
4. 结束传输
- 发送方发送 EOT信号,表示传输结束。
- 接收方回复 ACK,确认传输完成。
优点
1. 传输效率高
- 1024字节的数据块减少了协议开销(如起始字符、块编号、校验和等)的比例,适合传输大文件。
2. 适合高速通信
- 在稳定的通信环境中,Xmodem-1K 可以充分利用带宽,缩短传输时间。
3. 支持 CRC 校验
- 使用 2 字节的 CRC 校验,比标准 Xmodem 的 1 字节校验和更可靠,能检测更多类型的错误。
---
缺点
1. 不适合不稳定环境
- 数据块较大,如果在噪声较大的通信链路中传输,出错概率增加,重传的数据量也更大。
2. 兼容性有限
- 需要通信双方都支持 Xmodem-1K 协议,早期设备可能仅支持标准 Xmodem(128字节)。
3. 内存需求较高
- 接收方需要缓存整个 1024 字节的数据块,对资源有限的嵌入式系统可能不友好。
4. 小文件传输效率低
- 对于小文件,1024 字节的数据块可能导致填充字节过多,反而降低效率。
MCU初始化代码
来源于deepSeek生成,仅供参考
1.串口初始化
hhd32F107系列的单片机初始化函数,调用的库函数和下面有差异,但原理一致的,辛苦小伙伴们自己替换一下。
void USART_Init(void) {
// 配置USART的波特率、数据位、停止位等
// 例如:USART_InitTypeDef USART_InitStruct;
// USART_InitStruct.BaudRate = 115200;
// USART_InitStruct.WordLength = USART_WordLength_8b;
// USART_InitStruct.StopBits = USART_StopBits_1;
// USART_InitStruct.Parity = USART_Parity_No;
// USART_InitStruct.Mode = USART_Mode_Rx | USART_Mode_Tx;
// USART_Init(USART1, &USART_InitStruct);
// 启用接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
// 启用USART
USART_Cmd(USART1, ENABLE);
// 初始化环形缓冲区
RingBuffer_Init(&rx_buffer);
}
2.环形缓冲区实现
环形缓冲区用于在中断服务函数中存储接收到的数据。
#define BUFFER_SIZE 1024
typedef struct {
uint8_t buffer[BUFFER_SIZE];
uint16_t head;
uint16_t tail;
} RingBuffer;
RingBuffer rx_buffer;
void RingBuffer_Init(RingBuffer *rb) {
rb->head = 0;
rb->tail = 0;
}
uint8_t RingBuffer_IsFull(RingBuffer *rb) {
return ((rb->head + 1) % BUFFER_SIZE) == rb->tail;
}
uint8_t RingBuffer_IsEmpty(RingBuffer *rb) {
return rb->head == rb->tail;
}
void RingBuffer_Push(RingBuffer *rb, uint8_t data) {
if (!RingBuffer_IsFull(rb)) {
rb->buffer[rb->head] = data;
rb->head = (rb->head + 1) % BUFFER_SIZE;
}
}
uint8_t RingBuffer_Pop(RingBuffer *rb) {
uint8_t data = 0;
if (!RingBuffer_IsEmpty(rb)) {
data = rb->buffer[rb->tail];
rb->tail = (rb->tail + 1) % BUFFER_SIZE;
}
return data;
}
3.中断服务函数
在中断服务函数中,将接收到的数据存入环形缓冲区。
void USART1_IRQHandler(void) {
if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) {
uint8_t data = USART_ReceiveData(USART1);
RingBuffer_Push(&rx_buffer, data);
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
}
}
4.移植Xmodem协议代码
代码说明
-
数据包结构:
-
数据包大小为1029字节(
STX + 包序号 + 包序号反码 + 1024字节数据 + 2字节CRC
)。 -
通过检查包序号和包序号反码来验证数据包的完整性。
-
使用CRC-16校验数据包的有效性。
-
-
超时机制:
-
使用
HAL_GetTick()
获取当前时间(假设使用HAL库)。 -
如果在规定时间内没有收到数据,则发送NAK并重新等待数据包。
-
-
环形缓冲区:
-
使用你定义的
RingBuffer
来存储串口接收到的数据。 -
在中断服务函数中,数据被推入环形缓冲区;在主循环中,数据从环形缓冲区中弹出。
-
-
ACK/NAK响应:
-
根据Xmodem协议规范,发送ACK或NAK响应。
-
#include <stdint.h>
#include <string.h>
#define XMODEM_PACKET_SIZE 1024 // Xmodem数据包大小
#define XMODEM_HEADER_SIZE 3 // 数据包头大小(STX + 包序号 + 包序号反码)
#define XMODEM_CRC_SIZE 2 // CRC校验大小
#define XMODEM_PACKET_FULL (XMODEM_HEADER_SIZE + XMODEM_PACKET_SIZE + XMODEM_CRC_SIZE)
#define STX 0x02 // 1024字节数据包起始标志
#define EOT 0x04 // 传输结束标志
#define ACK 0x06 // 确认响应
#define NAK 0x15 // 否定响应
#define XMODEM_TIMEOUT_MS 1000 // 超时时间(1秒)
// 环形缓冲区操作函数(使用你定义的RingBuffer)
extern RingBuffer rx_buffer;
extern uint8_t RingBuffer_Pop(RingBuffer *rb);
extern uint8_t RingBuffer_IsEmpty(RingBuffer *rb);
// 串口发送函数
void USART_SendByte(uint8_t data) {
USART_SendData(USART1, data);
while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
}
// 计算CRC16校验
uint16_t crc16(uint8_t *data, uint16_t len) {
uint16_t crc = 0;
while (len--) {
crc = crc ^ ((uint16_t)*data++ << 8);
for (uint8_t i = 0; i < 8; i++) {
if (crc & 0x8000)
crc = (crc << 1) ^ 0x1021;
else
crc <<= 1;
}
}
return crc;
}
// 超时检测函数
uint8_t xmodem_timeout(uint32_t start_time, uint32_t timeout_ms) {
uint32_t current_time = HAL_GetTick(); // 获取当前时间(假设使用HAL库)
return (current_time - start_time) >= timeout_ms;
}
// Xmodem接收函数
int xmodem_receive(uint8_t *dest, uint32_t dest_size) {
uint8_t packet[XMODEM_PACKET_FULL];
uint8_t packet_number = 1; // 当前包序号
uint32_t total_size = 0; // 已接收数据总大小
uint32_t start_time;
// 发送NAK启动传输
USART_SendByte(NAK);
while (1) {
// 等待数据包起始字节
uint8_t start_byte;
start_time = HAL_GetTick(); // 记录起始时间
while (RingBuffer_IsEmpty(&rx_buffer)) {
if (xmodem_timeout(start_time, XMODEM_TIMEOUT_MS)) {
USART_SendByte(NAK); // 超时,发送NAK
start_time = HAL_GetTick(); // 重置超时计时
}
}
start_byte = RingBuffer_Pop(&rx_buffer);
if (start_byte == EOT) {
// 收到EOT,发送ACK并结束传输
USART_SendByte(ACK);
return total_size; // 返回接收到的总数据大小
} else if (start_byte == STX) {
// 接收完整数据包
start_time = HAL_GetTick(); // 记录起始时间
for (uint16_t i = 0; i < XMODEM_PACKET_FULL; i++) {
while (RingBuffer_IsEmpty(&rx_buffer)) {
if (xmodem_timeout(start_time, XMODEM_TIMEOUT_MS)) {
USART_SendByte(NAK); // 超时,发送NAK
goto next_packet; // 跳转到下一个数据包
}
}
packet[i] = RingBuffer_Pop(&rx_buffer);
}
// 检查包序号和包序号反码
uint8_t pkt_num = packet[0];
uint8_t pkt_num_inv = packet[1];
if (pkt_num + pkt_num_inv != 0xFF || pkt_num != packet_number) {
USART_SendByte(NAK); // 包序号错误,发送NAK
continue;
}
// 计算CRC校验
uint16_t crc_received = (packet[XMODEM_PACKET_FULL - 2] << 8) | packet[XMODEM_PACKET_FULL - 1];
uint16_t crc_calculated = crc16(&packet[XMODEM_HEADER_SIZE], XMODEM_PACKET_SIZE);
if (crc_received != crc_calculated) {
USART_SendByte(NAK); // CRC校验失败,发送NAK
continue;
}
// 数据包有效,拷贝数据到目标缓冲区
if (total_size + XMODEM_PACKET_SIZE <= dest_size) {
memcpy(dest + total_size, &packet[XMODEM_HEADER_SIZE], XMODEM_PACKET_SIZE);
total_size += XMODEM_PACKET_SIZE;
}
// 发送ACK确认
USART_SendByte(ACK);
packet_number++; // 更新包序号
} else {
// 无效起始字节,发送NAK
USART_SendByte(NAK);
}
next_packet:
continue; // 跳转到下一个数据包
}
}
5.主函数
#define MAX_FILE_SIZE 1024 * 100 // 假设最大接收100KB数据
int main(void) {
uint8_t file_buffer[MAX_FILE_SIZE]; // 接收文件缓冲区
uint32_t file_size = 0;
// 初始化系统时钟、串口等
SystemInit();
USART_Init();
// 接收文件
file_size = xmodem_receive(file_buffer, MAX_FILE_SIZE);
// 处理接收到的文件数据
if (file_size > 0) {
// 文件接收成功,处理数据
} else {
// 文件接收失败
}
while (1) {
// 主循环
}
}
注意事项
-
确保串口的波特率、数据位、停止位等参数与发送端一致。
-
如果数据包较大,目标缓冲区
dest
需要足够大以存储所有数据。 -
在实际应用中,可能需要根据硬件平台调整超时时间和定时器实现。
使用SecureCRT给单片机发送固件
1. 准备工作
硬件连接
-
确保单片机(HHD32F107)与 PC 通过串口连接(如 USB 转串口模块)。
-
确认串口的波特率、数据位、停止位和校验位等参数与 SecureCRT 配置一致。
软件环境
-
SecureCRT:用于发送文件和与单片机通信。
-
单片机程序:已实现 Xmodem 协议接收功能(基于前面提供的代码)。
2. SecureCRT 配置
2.1 打开 SecureCRT 并创建串口会话
-
打开 SecureCRT。
-
点击 File -> Connect,在弹出的窗口中选择 Serial 协议。
-
配置串口参数:
-
Port:选择正确的串口号(如 COM3)。
-
Baud rate:设置为与单片机一致的波特率(如 115200)。
-
Data bits:8。
-
Parity:None。
-
Stop bits:1。
-
Flow control:None。
-
-
点击 Connect,连接到串口。
2.2 配置 Xmodem 文件传输
-
在 SecureCRT 中,点击 Transfer -> Send Xmodem。
-
选择要发送的文件(如
firmware.bin
)。 -
SecureCRT 会通过 Xmodem 协议将文件发送到单片机。
3. 单片机程序流程
3.1 单片机初始化
-
单片机启动后,初始化串口(波特率、中断等)。
-
初始化环形缓冲区(用于存储接收到的数据)。
-
等待 SecureCRT 发送数据。
3.2 Xmodem 接收流程
-
发送 NAK 启动传输:
-
单片机上电后,主动发送一个
NAK
(0x15
)信号,表示准备好接收数据。 -
SecureCRT 收到
NAK
后,开始发送文件。
-
-
接收数据包:
-
SecureCRT 发送一个 Xmodem 数据包(1029 字节,包括
STX
、包序号、数据、CRC 等)。 -
单片机通过串口中断接收数据,并将其存储到环形缓冲区中。
-
单片机从环形缓冲区中读取数据,并解析数据包。
-
-
校验数据包:
-
检查包序号和包序号反码是否正确。
-
计算 CRC 校验值,并与接收到的 CRC 值进行比较。
-
如果校验通过,发送
ACK
(0x06
)确认;否则发送NAK
请求重传。
-
-
接收完成:
-
当 SecureCRT 发送完所有数据后,会发送一个
EOT
(0x04
)信号,表示传输结束。 -
单片机收到
EOT
后,发送ACK
确认,并结束接收流程。
-
-
存储数据:
-
单片机将接收到的有效数据存储到 Flash 或 RAM 中。
-