![[DSP] YMODEM协议与实现](http://pic.xiahunao.cn/yaotu/[DSP] YMODEM协议与实现)
1.协议简介及控制字符1.1 协议简介YMODEM XMODEM-1K的扩展在XMODEM的基础上增加了文件名/长度包块0、1K打包STX以及批量文件传输功能。核心思想很简单就是停等协议Stop-and-Wait——发完一包数据等对方ACK再发下一包数据收到NAK就重发。简单、可靠、就是比较慢。1.2 控制字符字符值发起者含义SOH0x01发送方后面跟128字节数据包STX0x02发送方后面跟1024字节数据包EOT0x04发送方传输结束ACK0x06接收方确认包正常接收NAK0x15接收方否认要求重传CAN0x18双方取消传输C0x43接收方握手请求2. 帧格式2.1 数据帧seq包序号从0开始块0文件名包数据包从1开始~seq序号取反校验用。要求seq~seq 0xFFdata有效载荷不满填0x1ACRC16CCITT多项式0x1021处置0只算data部分不含包头/CRC2.2 文件名包文件包与数据包格式一样只不过data段放的内容是文件名和文件大小。文件名后跟一个\0间隔文件大小后也跟一个\0间隔不够128字节补0x00。2.3 单字节控制帧3. 传输时序接收方发送C启动会话指定 CRC16 校验发送方收到C发送SOH 00 FF的块 0 包携带文件名、文件大小信息128 字节接收方收到块 0 包校验成功接收方回复ACK(0x06)接收方再次发送C通知发送方可以开始传输文件有效数据发送方收到该C发送STX 01 FE块 11024 字节 Ymodem‑1K 数据接收方收到数据块校验成功则接收方回复ACK校验失败则接收方回复NAK触发发送方重传本数据块发送方继续依次发送块 2、块 3……每发送一个数据包等待接收方回复ACK文件全部发送完成发送方发送第一个EOT(0x04)接收方收到第一个EOT接收方回复NAK(0x15)发送方收到NAK重发第二个EOT接收方收到第二个EOT接收方回复ACK当前单个文件传输完毕接收方发送C批量模式询问发送方是否还有待传输的下一个文件发送方收到C若无更多文件发送方发送结束包SOH 00 FF128 字节全 0接收方收到结束包接收方回复ACK整个 Ymodem 会话彻底结束4. 代码实现代码要分为协议层和硬件层协议层主要包括crc校验函数、读取函数、解析第0包函数、接收状态机、发送逻辑等硬件层包括字符发送、字符接收、延时函数以及一些对外接口函数等。4.1 硬件层putc 字符发送函数static void u0_putc(unsigned char c) { UARTCharPut(SOC_UART_0_REGS, c); }getc 字符提取函数static int u0_getc(unsigned char *c, unsigned int timeout_ms) { int us_ticks 0; while (1) { int ch UARTCharGetNonBlocking(SOC_UART_0_REGS); if (ch 0) { *c (unsigned char)ch; return 1; } _delay_us(20); if (us_ticks 50) { /* 50 * 20us 1ms */ us_ticks 0; if (timeout_ms 0) return 0; timeout_ms--; } } }delay函数使用定时器实现_delay_msstatic void u0_delay(unsigned int ms) { unsigned int i; for (i 0; i ms; i) _delay_ms(1); }接收端口int uart0_ymodem_recv(unsigned char *buf, unsigned int size) { return ymodem_receive(u0_putc, u0_getc, u0_delay, buf, size); }发送端口int uart0_ymodem_send(ymodem_read_fn read_fn, void *arg, unsigned int datalen, const char *filename) { return ymodem_send(u0_putc, u0_getc, u0_delay, read_fn, arg, datalen, filename); }4.2 协议层crc校验函数static unsigned short crc16_ccitt(const unsigned char *buf, int len) { unsigned short crc 0; while (len-- 0) { crc ^ (unsigned short)(*buf) 8; int i; for (i 0; i 8; i) crc (crc 0x8000) ? (crc 1) ^ CRC_POLY : (crc 1); } return crc; }读取函数static int read_body(int (*getc)(unsigned char *, unsigned int), unsigned char header, unsigned char *data_buf, int *pkt_seq) { int pkt_size (header SOH) ? PKT_128 : PKT_1024; unsigned char raw[PKT_1024 2]; unsigned char seq, seq_inv; int i; if (!getc(seq, 2000)) return -2; if (!getc(seq_inv, 2000)) return -2; if ((unsigned char)(seq seq_inv) ! 0xFF) return -1; *pkt_seq seq; for (i 0; i pkt_size 2; i) if (!getc(raw[i], 2000)) return -2; unsigned short rx_crc (raw[pkt_size] 8) | raw[pkt_size 1]; unsigned short calc_crc crc16_ccitt(raw, pkt_size); if (rx_crc ! calc_crc) { g_ym_crc_fail; g_ym_last_rx rx_crc; g_ym_last_calc calc_crc; int j; for (j 0; j 16 j pkt_size; j) g_ym_dbg_pkt[j] raw[j]; return -1; } g_ym_crc_pass; memcpy(data_buf, raw, pkt_size); /* 整包拷贝含填充 */ return pkt_size; }解析块0static void parse_block0(const unsigned char *data, int len, unsigned long *filesize) { int i 0; while (i len data[i] ! 0) i; /* 跳过 filename */ if (i len) return; i; /* 跳过 \0 */ unsigned long v 0; int got 0; while (i len data[i] 0 data[i] 9) { v v * 10 (data[i] - 0); i; got 1; } if (got) *filesize v; }接收状态机函数typedef enum { YMODEM_STATE_HANDSHAKE, YMODEM_STATE_WAIT_HEADER, YMODEM_STATE_READ_PACKET, YMODEM_STATE_PROCESS_PACKET, YMODEM_STATE_WAIT_EOT, YMODEM_STATE_DONE, YMODEM_STATE_ABORT, YMODEM_STATE_ERROR } ymodem_state_t; int ymodem_receive(void (*putc)(unsigned char), int (*getc)(unsigned char *, unsigned int), void (*delay)(unsigned int), unsigned char *buf, unsigned int size) { ymodem_state_t state YMODEM_STATE_HANDSHAKE; unsigned char header 0; unsigned char packet[PKT_1024]; int pkt_seq 0; int rc 0; int retry 0; unsigned int total 0; unsigned int expected_seq 0; unsigned long filesize 0; int have_block0 0; (void)delay; if (putc 0 || getc 0 || buf 0 || size 0) { return -1; } while (state ! YMODEM_STATE_DONE state ! YMODEM_STATE_ABORT state ! YMODEM_STATE_ERROR) { switch (state) { case YMODEM_STATE_HANDSHAKE: { int started 0; int i; for (i 0; i 60; i) { putc(C); if (getc(header, 1000)) { if (header SOH || header STX) { started 1; state YMODEM_STATE_READ_PACKET; break; } if (header CAN) { state YMODEM_STATE_ABORT; break; } if (header EOT) { putc(ACK); state YMODEM_STATE_DONE; break; } } } if (!started state YMODEM_STATE_HANDSHAKE) { state YMODEM_STATE_ERROR; } break; } case YMODEM_STATE_WAIT_HEADER: { if (!getc(header, 3000)) { retry; if (retry 10) { state YMODEM_STATE_ERROR; } else { putc(NAK); } break; } retry 0; if (header SOH || header STX) { state YMODEM_STATE_READ_PACKET; } else if (header EOT) { /* * 发送端第一次发 EOT。 * 按常见 YMODEM 流程回复 NAK * 等待发送端再次发送 EOT。 */ putc(NAK); state YMODEM_STATE_WAIT_EOT; } else if (header CAN) { state YMODEM_STATE_ABORT; } else { putc(NAK); } break; } case YMODEM_STATE_READ_PACKET: { rc read_body(getc, header, packet, pkt_seq); if (rc 0) { retry; if (retry 10) { state YMODEM_STATE_ERROR; } else { putc(NAK); state YMODEM_STATE_WAIT_HEADER; } } else { retry 0; state YMODEM_STATE_PROCESS_PACKET; } break; } case YMODEM_STATE_PROCESS_PACKET: { /* * 第0包文件信息包 * * 只有在传输刚开始时序号0才表示块0。 * 当 expected_seq 达到256以后 * 序号0是正常的数据包不能误认为块0。 */ if (pkt_seq 0 expected_seq 0) { parse_block0(packet, rc, filesize); if (filesize 0) { have_block0 1; if (filesize size) { putc(CAN); putc(CAN); state YMODEM_STATE_ERROR; break; } } expected_seq 1; putc(ACK); putc(C); state YMODEM_STATE_WAIT_HEADER; break; } /* * 如果发送端没有发送块0 * 则允许第一个数据包直接从序号1开始。 */ if (expected_seq 0 pkt_seq 1) { expected_seq 1; } /* * 新数据包。 */ if (pkt_seq (int)(expected_seq 0xFF)) { unsigned int copy_len (unsigned int)rc; /* * 已知真实文件长度时 * 最后一个包只保存有效数据 * 不保存0x1A填充。 */ if (have_block0) { if ((unsigned long)total filesize) { copy_len 0; } else if ((unsigned long)copy_len filesize - total) { copy_len (unsigned int) (filesize - total); } } else { /* * 没有块0时不知道真实文件长度 * 不能允许写出缓冲区。 */ if (copy_len size - total) { putc(CAN); putc(CAN); state YMODEM_STATE_ERROR; break; } } if (copy_len 0) { memcpy(buf[total], packet, copy_len); total copy_len; } expected_seq; putc(ACK); state YMODEM_STATE_WAIT_HEADER; break; } /* * 块0重复发送。 * * 可能是接收端之前发送的 ACK 或 C 丢失。 */ if (pkt_seq 0 expected_seq 1 total 0) { putc(ACK); putc(C); state YMODEM_STATE_WAIT_HEADER; break; } /* * 重复数据包。 * * 只回复ACK不能再次保存。 */ if (pkt_seq (int)((expected_seq - 1) 0xFF)) { putc(ACK); state YMODEM_STATE_WAIT_HEADER; break; } /* * 其他情况视为乱序包。 */ putc(NAK); state YMODEM_STATE_WAIT_HEADER; break; } case YMODEM_STATE_WAIT_EOT: { /* * 等待发送端的第二个 EOT。 */ if (getc(header, 2000) header EOT) { putc(ACK); if (!have_block0) { while (total 0 buf[total - 1] 0x1A) { total--; } } state YMODEM_STATE_DONE; } else { retry; if (retry 10) { state YMODEM_STATE_ERROR; } else { state YMODEM_STATE_WAIT_HEADER; } } break; } default: state YMODEM_STATE_ERROR; break; } } if (state YMODEM_STATE_DONE) { return (int)total; } if (state YMODEM_STATE_ABORT) { return 0; } return -1; }发送逻辑函数int ymodem_send(void (*putc)(unsigned char), int (*getc)(unsigned char *, unsigned int), void (*delay)(unsigned int), ymodem_read_fn read_fn, void *read_arg, unsigned int datalen, const char *filename) { unsigned char pkt[PKT_1024 5]; unsigned char c; unsigned int seq, offset; int i, retry; /* ---- 1. 等接收方发 C ---- */ for (retry 0; retry 60; retry) { if (getc(c, 1000)) { if (c C) break; if (c CAN) return 0; } } if (retry 60) return -1; /* ---- 2. 发块0文件名 文件大小SOH 128字节包 ---- */ memset(pkt, 0, PKT_128 5); pkt[0] SOH; pkt[1] 0x00; pkt[2] 0xFF; { int n 0; const char *s filename; while (*s n 60) pkt[3 n] (unsigned char)*s; pkt[3 n] 0; /* 十进制文件大小 */ { char tmp[12]; int t 0; unsigned int v datalen; if (v 0) tmp[t] 0; while (v t 11) { tmp[t] 0 (v % 10); v / 10; } while (t) pkt[3 n] (unsigned char)tmp[--t]; pkt[3 n] 0; } { unsigned short crc crc16_ccitt(pkt[3], PKT_128); pkt[3 PKT_128] (unsigned char)(crc 8); pkt[3 PKT_128 1] (unsigned char)crc; } } for (retry 0; retry 10; retry) { for (i 0; i PKT_128 5; i) putc(pkt[i]); if (getc(c, 3000)) { if (c ACK) break; if (c CAN) return 0; } } if (retry 10) return -1; /* ---- 3. 等 C接收方请求数据 ---- */ for (retry 0; retry 10; retry) { if (getc(c, 3000)) { if (c C) break; if (c CAN) return 0; } } if (retry 10) return -1; /* ---- 4. 发数据包STX 1024字节 ---- */ seq 1; offset 0; while (offset datalen) { unsigned int chunk datalen - offset; if (chunk PKT_1024) chunk PKT_1024; pkt[0] STX; pkt[1] (unsigned char)(seq 0xFF); pkt[2] (unsigned char)(~seq 0xFF); /* 流式读取只从数据源取本包需要的 chunk 字节 */ if (read_fn(read_arg, offset, pkt[3], chunk) ! (int)chunk) { putc(CAN); putc(CAN); /* 读数据失败通知对方取消 */ return -1; } for (i chunk; i PKT_1024; i) pkt[3 i] 0x1A; /* 0x1A 填充 */ { unsigned short crc crc16_ccitt(pkt[3], PKT_1024); pkt[3 PKT_1024] (unsigned char)(crc 8); pkt[3 PKT_1024 1] (unsigned char)crc; } for (retry 0; retry 10; retry) { for (i 0; i PKT_1024 5; i) putc(pkt[i]); if (getc(c, 3000)) { if (c ACK) break; if (c CAN) return 0; } } if (retry 10) return -1; offset chunk; seq; } /* ---- 5. EOT 双握手 ---- */ for (retry 0; retry 10; retry) { putc(EOT); if (getc(c, 3000)) { if (c NAK || c ACK) break; /* 第一个EOT标准是NAK有的实现直接ACK */ if (c CAN) return 0; } } putc(EOT); for (retry 0; retry 10; retry) { if (getc(c, 3000)) { if (c ACK) break; if (c CAN) return 0; } } return (int)datalen; }5. 实验结果传输成功