Browse Source

合并主线修改

master
Lizongdi 2 weeks ago
parent
commit
406b1a8a90
  1. 4
      bspMCU/My_print.c
  2. 9
      bspMCU/l_uart.c
  3. 5
      library/common/rd_mempool.c
  4. 15
      peripheral/include/ymodem.h
  5. 156
      peripheral/ry_sy.c
  6. 232
      peripheral/ymodem.c

4
bspMCU/My_print.c

@ -37,8 +37,8 @@ int USART_Send(char *_pBuffer, uint32_t _iSize)
int iRet = HAL_UART_Transmit(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize, 100); int iRet = HAL_UART_Transmit(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize, 100);
HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_RESET); HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_RESET);
// int iRet = HAL_UART_Transmit_DMA(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize); // int iRet = HAL_UART_Transmit_DMA(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize);
if (iRet != HAL_OK) return RD_FAILURE;
return iRet; return (int)_iSize;
} }
/*int Dev01check(char *_pBuffer, uint32_t _iSize) /*int Dev01check(char *_pBuffer, uint32_t _iSize)

9
bspMCU/l_uart.c

@ -24,7 +24,8 @@ int COM1_Send(char *_pBuffer, uint32_t _iSize)
HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_SET); HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_SET);
int iRet = HAL_UART_Transmit(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize, 100); int iRet = HAL_UART_Transmit(ptUartUserData->m_uart, (uint8_t *)_pBuffer, _iSize, 100);
HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_RESET); HAL_GPIO_WritePin(RS485_2_DIR_GPIO_Port, RS485_2_DIR_Pin,GPIO_PIN_RESET);
return iRet; if (iRet != HAL_OK) return RD_FAILURE;
return (int)_iSize;
} }
int COM0_Send(char *_pBuffer, uint32_t _iSize) int COM0_Send(char *_pBuffer, uint32_t _iSize)
@ -45,7 +46,11 @@ static int l_com_new(lua_State *L)
if (id == 1 || id == 2) if (id == 1 || id == 2)
{ {
TUartUserData *ptUartUserData = UART_userdata_init(LUACOM_ID, baudrate, 0); TUartUserData *ptUartUserData = UART_userdata_init(LUACOM_ID, baudrate, 0);
if (NULL == ptUartUserData) lua_print("com is already used\n"); if (NULL == ptUartUserData)
{
lua_print("com is already used\n");
return g_ptCom1;
}
g_ptCom1 = rd_ComCreate(NULL, NULL, COM1_Send, ptUartUserData->m_buf_size, ptUartUserData); g_ptCom1 = rd_ComCreate(NULL, NULL, COM1_Send, ptUartUserData->m_buf_size, ptUartUserData);
UART_IT_init(g_ptCom1); UART_IT_init(g_ptCom1);
u->m_ptComCtrl = g_ptCom1; u->m_ptComCtrl = g_ptCom1;

5
library/common/rd_mempool.c

@ -307,6 +307,11 @@ WEAK void* Rd_MemPoolMalloc(size_t size)
WEAK void Rd_MemPoolFree(void* ptr) WEAK void Rd_MemPoolFree(void* ptr)
{ {
if (ptr == NULL)
{
return;
}
if (!is_valid_ptr(ptr)) if (!is_valid_ptr(ptr))
{ {
log_e("[%p] MemPool Free Error: Invalid Pointer %p", __builtin_return_address(0), ptr); log_e("[%p] MemPool Free Error: Invalid Pointer %p", __builtin_return_address(0), ptr);

15
peripheral/include/ymodem.h

@ -64,6 +64,21 @@ enum rym_code
#define RYM_MAX_ERRORS ((size_t)5) #define RYM_MAX_ERRORS ((size_t)5)
#endif #endif
/* 发送方向: 收到 NAK / 等应答超时时, 重发同一包的最大次数.
* 注意 YMODEM 里 NAK 的语义是"这一包没收好, 请重发", 不是失败退出;
* 接收端(尤其是刚建完文件/还没就绪时) 头一两次回 NAK 是正常现象. */
#ifndef RYM_TX_RETRY_MAX
#define RYM_TX_RETRY_MAX 10
#endif
/* 发送方向数据块的大小选择:
* 1 = 数据阶段一律用 1K(STX) 块 (标准 YMODEM 发送端行为, lrzsz 的 sz 默认如此).
* 0 = 按本块实际字节数选择 SOH(128B)/STX(1K).
* 对接某些终端(如 Xshell)时它们只接受 STX 数据块, 收到 SOH 数据块会一直回 NAK. */
#ifndef RYM_TX_BLOCK_1K
#define RYM_TX_BLOCK_1K 1
#endif
enum rym_stage enum rym_stage
{ {
RYM_STAGE_NONE = 0, RYM_STAGE_NONE = 0,

156
peripheral/ry_sy.c

@ -8,6 +8,7 @@
#include "ymodem.h" #include "ymodem.h"
#include "rd_stdio.h" #include "rd_stdio.h"
#include "ringbuffer.h"
#include <stdlib.h> #include <stdlib.h>
#ifndef YMODEM_PATH_MAX #ifndef YMODEM_PATH_MAX
@ -18,31 +19,68 @@ struct custom_ctx
{ {
struct rym_ctx parent; struct rym_ctx parent;
void *fp; /* Rd_Fopen 返回的文件句柄 */ void *fp; /* Rd_Fopen 返回的文件句柄 */
int flen; /* 剩余待接收字节数 */ int flen; /* 剩余待接收字节数, <0 表示对端未给长度 */
int expect_index; /* 下一包应有的序号, 0 表示还没收到数据包 */
char fpath[YMODEM_PATH_MAX]; char fpath[YMODEM_PATH_MAX];
}; };
/* ═══════════════════════════════════════════════════════════════════════ * /* ═══════════════════════════════════════════════════════════════════════ *
* TComCtrl → rym_device_t 桥接 * * TComCtrl → rym_device_t 桥接 *
* 读: rd_ComRead (内部自带信号量等待, 无协议回调时直接返回原始字节) * * 读: 直接掏接收环形缓冲区 (见 serial_rym_read, 不走 rd_ComRead) *
* 写: rd_ComSend * * 写: rd_ComSend (底层回调必须按"实际字节数"返回) *
* 无需额外维护信号量 * * 无需额外维护信号量 *
* ═══════════════════════════════════════════════════════════════════════ */ * ═══════════════════════════════════════════════════════════════════════ */
/*
* YMODEM 是原始二进制流, 不能借用 rd_ComRead:
* 1) m_pfCheck == NULL 时 rd_ComRead 内部 iPackLen 恒为 1, 每次只返回 1 字节,
* 再叠加 ymodem 层的 Rd_Delay(1)=1ms, 吞吐被压到 ~1KB/s (115200 下慢 11 倍);
* 2) rd_ComRead 在环空时执行 Rd_SemWait (osWaitForever) 永久阻塞,
* 使 ymodem 层所有基于 Rd_GetTime() 的超时变成死代码, 对端一停发就永久卡死.
* 这里一次取走环里已到达的全部字节; 无数据返回 0, 由上层用
* Rd_GetTime()/Rd_Delay(1) 轮询控制节奏与超时退出.
*/
static size_t serial_rym_read(void *opaque, uint8_t *buf, size_t len) static size_t serial_rym_read(void *opaque, uint8_t *buf, size_t len)
{ {
TComCtrl *ptCom = (TComCtrl *)opaque; TComCtrl *ptCom = (TComCtrl *)opaque;
if (ptCom == NULL || len == 0) return 0; size_t got = 0;
if (ptCom == NULL || ptCom->m_stComBuf.m_ptRecv == NULL || buf == NULL || len == 0)
return 0;
while (got < len)
{
char ch;
if (rd_RingbufferGetchar(ptCom->m_stComBuf.m_ptRecv, &ch) != 1)
break;
buf[got++] = (uint8_t)ch;
}
int ret = rd_ComRead(ptCom, (char *)buf, (uint32_t)len); return got;
return (ret > 0) ? (size_t)ret : 0;
} }
static size_t serial_rym_write(void *opaque, const uint8_t *buf, size_t len) static size_t serial_rym_write(void *opaque, const uint8_t *buf, size_t len)
{ {
TComCtrl *ptCom = (TComCtrl *)opaque; TComCtrl *ptCom = (TComCtrl *)opaque;
if (ptCom == NULL) return 0; int ret;
return (size_t)rd_ComSend(ptCom, (char *)buf, (uint32_t)len);
if (ptCom == NULL || buf == NULL || len == 0) return 0;
/* 返回实际写出的字节数; 失败返回 0 交给 ymodem 层超时处理,
* 不能把负数负值强转成 size_t 大数 */
ret = rd_ComSend(ptCom, (char *)buf, (uint32_t)len);
if (ret <= 0)
{
/* 发送失败在 ymodem 层只会表现为"对端不回话", 很难定位, 所以直接报出来:
* ret=-2(RD_NULL): TComCtrl 为空或 m_pfSend 未接
* ret=-1(RD_FAILURE): 底层写失败 (上位机侧常见于 COM 口没打开成功/
* 被别的程序占用, 例如 Xshell 还开着)
* ret=0: 一个字莂也没写出去 */
log_e("ymodem TX: rd_ComSend failed (ret=%d, len=%u) -> 字节没发出去",
ret, (unsigned)len);
return 0;
}
return (size_t)ret;
} }
static void ymodem_build_device(TComCtrl *ptCom, rym_device_t *dev) static void ymodem_build_device(TComCtrl *ptCom, rym_device_t *dev)
@ -53,6 +91,50 @@ static void ymodem_build_device(TComCtrl *ptCom, rym_device_t *dev)
dev->opaque = ptCom; dev->opaque = ptCom;
} }
/*
* 会话开始前把接收环里的残留字节丢掉.
* 否则上一条命令/上一次会话遗留的 'C' (接收端催包) 或 ACK 会顶到前面,
* 使 ymodem 的"发送一个包 → 读一个应答"序列整体错位:
* 表现就是传输跑了一半报 -118 (RYM_ERR_ACK), 或者会话结束时收不到正确应答.
*
* 同时必须把 m_sem_Recv 的计数也清零:
* 接收路径 (rd_ComRecvProc) 每收一批数据就 Rd_SemPost(m_sem_Recv),
* 而 rd_ComRead 的约定是"先等信号量、再读环".
* serial_rym_read 绕过 rd_ComRead 直接掏环, 信号量只进不出, 计数会顶到上限;
* 不清零的话, 会话结束后 c:read() 会"信号量立刻等到、环里却没数据",
* 阻塞语义被破坏 (空读/假就绪).
*/
static void ymodem_flush_rx(TComCtrl *ptCom)
{
char ch;
int guard;
if (ptCom == NULL)
return;
if (ptCom->m_stComBuf.m_ptRecv != NULL)
{
while (rd_RingbufferGetchar(ptCom->m_stComBuf.m_ptRecv, &ch) == 1)
{
/* 丢弃 */
}
}
/* 把残留的"有数据"信号也吃掉, 恢复"sem>0 ⇒ 环里有数据"的约定.
* 计数有上限(init+1), 正常情况下几次就空; guard 只防异常实现死循环.
*
* 用 0 ms = 纯试探, 不等. 前提是 Rd_SemWaitTimeout 真的遵守超时:
* FreeRTOS: osSemaphoreAcquire(sem, 0) ✓ 非阻塞
* Win32 : pthread_cond_timedwait 到期时间=当前 ✓ 立即返回
* Linux : rd_thread.c 的 WEAK 兜底恒返回 -1 ✓ 不会阻, 但也清不掉计数
* (Win32 曾把超时丢掉转成阻塞等待, 导致上位机 ry 在这里挂死, 已修) */
for (guard = 0; guard < 1024; guard++)
{
if (Rd_SemWaitTimeout(&ptCom->m_stComBuf.m_sem_Recv, 0) != 0)
break;
}
}
/* ═══════════════════════════════════════════════════════════════════════ * /* ═══════════════════════════════════════════════════════════════════════ *
* YMODEM 接收回调 (文件写入) * * YMODEM 接收回调 (文件写入) *
* ═══════════════════════════════════════════════════════════════════════ */ * ═══════════════════════════════════════════════════════════════════════ */
@ -83,27 +165,52 @@ static enum rym_code _rym_recv_begin(struct rym_ctx *ctx, uint8_t *buf, size_t l
if (cctx->flen == 0) if (cctx->flen == 0)
cctx->flen = -1; cctx->flen = -1;
cctx->expect_index = 0; /* 新文件, 序号重新开始 */
log_i("ymodem RX: file '%s', declared size=%d", cctx->fpath, cctx->flen);
return RYM_CODE_ACK; return RYM_CODE_ACK;
} }
static enum rym_code _rym_recv_data(struct rym_ctx *ctx, uint8_t *buf, size_t len) static enum rym_code _rym_recv_data(struct rym_ctx *ctx, uint8_t *buf, size_t len)
{ {
struct custom_ctx *cctx = (struct custom_ctx *)ctx; struct custom_ctx *cctx = (struct custom_ctx *)ctx;
uint8_t index = ctx->buf[1]; /* 本包序号 */
ssize_t ret;
if (cctx->fp == NULL) if (cctx->fp == NULL)
return RYM_CODE_CAN; return RYM_CODE_CAN;
if (cctx->flen == -1) /* 序号去重: 对端重传的包只能回 ACK, 绝不能再写一次.
* 否则每次重传都会把同一段数据追加到文件里, 文件会变成
* "原内容重复 N 遍" (实测一个 5 字节的文件被撑到了 1490 字节) */
if (cctx->expect_index > 0 && cctx->expect_index <= 0xFF)
{
if (index == (uint8_t)(cctx->expect_index - 1))
return RYM_CODE_ACK; /* 重复包: 已经写过, 只确认 */
if (index != (uint8_t)cctx->expect_index)
return RYM_CODE_NAK; /* 跳包/乱序: 不写也不确认, 等对端重传 */
}
if (cctx->flen < 0)
{ {
Rd_Mwrite(buf, len, cctx->fp); ret = Rd_Mwrite(buf, len, cctx->fp); /* 对端没给长度, 来多少写多少 */
} }
else else
{ {
int wlen = (int)(len > (size_t)cctx->flen ? (size_t)cctx->flen : len); /* 严格按包头声明的长度截断, flen 不会变成负数后被当成"无限写" */
Rd_Mwrite(buf, wlen, cctx->fp); int wlen = ((int)len > cctx->flen) ? cctx->flen : (int)len;
cctx->flen -= wlen; cctx->flen -= wlen;
ret = Rd_Mwrite(buf, (size_t)wlen, cctx->fp);
}
if (ret < 0)
{
log_e("ymodem RX data: write failed (ret=%ld), aborting", (long)ret);
return RYM_CODE_CAN; /* 写入失败: 中止会话 */
} }
cctx->expect_index = (int)index + 1; /* 记录下一包应有的序号 */
return RYM_CODE_ACK; return RYM_CODE_ACK;
} }
@ -149,7 +256,10 @@ static enum rym_code _rym_send_begin(struct rym_ctx *ctx, uint8_t *buf, size_t l
RD_MEMSET(buf, 0, len); RD_MEMSET(buf, 0, len);
const char *fname = _get_path_lastname(cctx->fpath); const char *fname = _get_path_lastname(cctx->fpath);
/* YMODEM 包头第二字段必须是十进制文件长度字符串 (CRC/Checksum 模式由握手字节 C/NAK 协商);
* 这里写 "STX" 之类的标识符是非法的, 接收端会把文件长度解析成 0 */
RD_SPRINTF((char *)buf, "%s%c%ld", fname, '\0', file_size); RD_SPRINTF((char *)buf, "%s%c%ld", fname, '\0', file_size);
log_i("ymodem TX: file '%s' declared size=%ld byte(s)", fname, file_size);
return RYM_CODE_SOH; return RYM_CODE_SOH;
} }
@ -173,7 +283,15 @@ static enum rym_code _rym_send_data(struct rym_ctx *ctx, uint8_t *buf, size_t le
ctx->stage = RYM_STAGE_FINISHING; ctx->stage = RYM_STAGE_FINISHING;
} }
#if RYM_TX_BLOCK_1K
/* 数据阶段一律用 1K(STX) 块 + 0x1A 填满, 这是标准 YMODEM 发送端的行为
* (lrzsz 的 sz 默认就是 1K 块). 部分接收端(含 Xshell)只接受 STX 数据块,
* 收到 SOH 数据块会一直回 NAK; 文件尾不足 1K 时靠包头里的长度截断. */
return RYM_CODE_STX;
#else
/* 按本块实际字节数选择 128B/1K 块 (旧行为) */
return (read_size > 128) ? RYM_CODE_STX : RYM_CODE_SOH; return (read_size > 128) ? RYM_CODE_STX : RYM_CODE_SOH;
#endif
} }
static enum rym_code _rym_send_end(struct rym_ctx *ctx, uint8_t *buf, size_t len) static enum rym_code _rym_send_end(struct rym_ctx *ctx, uint8_t *buf, size_t len)
@ -205,13 +323,18 @@ int rym_download_file(TComCtrl *ptCom, const char *file_path)
return RD_FAILURE; return RD_FAILURE;
} }
ctx->fp = NULL; ctx->fp = NULL;
RD_STRNCPY(ctx->fpath, file_path, YMODEM_PATH_MAX); RD_STRNCPY(ctx->fpath, file_path, YMODEM_PATH_MAX - 1);
ymodem_build_device(ptCom, &dev); ymodem_build_device(ptCom, &dev);
ymodem_flush_rx(ptCom);
res = rym_recv_on_device(&ctx->parent, &dev, res = rym_recv_on_device(&ctx->parent, &dev,
_rym_recv_begin, _rym_recv_data, _rym_recv_end, _rym_recv_begin, _rym_recv_data, _rym_recv_end,
10); 10);
/* 会话期间 ISR 一直在 post m_sem_Recv 而没人 acquire,
* 结束时再清一次, 保证后续 rd_ComRead 的阻塞语义正常 */
ymodem_flush_rx(ptCom);
if (ctx->fp != NULL) if (ctx->fp != NULL)
Rd_Mclose(ctx->fp); Rd_Mclose(ctx->fp);
RD_FREE(ctx); RD_FREE(ctx);
@ -230,13 +353,18 @@ int rym_upload_file(TComCtrl *ptCom, const char *file_path)
return RD_FAILURE; return RD_FAILURE;
} }
ctx->fp = NULL; ctx->fp = NULL;
RD_STRNCPY(ctx->fpath, file_path, YMODEM_PATH_MAX); RD_STRNCPY(ctx->fpath, file_path, YMODEM_PATH_MAX - 1);
ymodem_build_device(ptCom, &dev); ymodem_build_device(ptCom, &dev);
ymodem_flush_rx(ptCom);
res = rym_send_on_device(&ctx->parent, &dev, res = rym_send_on_device(&ctx->parent, &dev,
_rym_send_begin, _rym_send_data, _rym_send_end, _rym_send_begin, _rym_send_data, _rym_send_end,
10); 10);
/* 会话期间 ISR 一直在 post m_sem_Recv 而没人 acquire,
* 结束时再清一次, 保证后续 rd_ComRead 的阻塞语义正常 */
ymodem_flush_rx(ptCom);
if (ctx->fp != NULL) if (ctx->fp != NULL)
Rd_Mclose(ctx->fp); Rd_Mclose(ctx->fp);
RD_FREE(ctx); RD_FREE(ctx);

232
peripheral/ymodem.c

@ -109,9 +109,16 @@ static ssize_t _rym_read_data(struct rym_ctx *ctx, size_t len)
while (Rd_GetTime() - start < RYM_WAIT_CHR_MS) while (Rd_GetTime() - start < RYM_WAIT_CHR_MS)
{ {
size_t n = ctx->dev.read(ctx->dev.opaque, buf + readlen, len - readlen); size_t n = ctx->dev.read(ctx->dev.opaque, buf + readlen, len - readlen);
if (n > 0)
{
readlen += n; readlen += n;
if (readlen >= len) if (readlen >= len)
return readlen; return readlen;
/* 还有数据就继续搬, 不要在这里白等 1ms */
continue;
}
/* 只有真的没数据可读时才让出 CPU */
Rd_Delay(1); Rd_Delay(1);
} }
return readlen; return readlen;
@ -142,13 +149,29 @@ static int _rym_send_packet(struct rym_ctx *ctx, enum rym_code code, uint8_t ind
ctx->buf[packetlen - 2] = (uint8_t)(send_crc >> 8); ctx->buf[packetlen - 2] = (uint8_t)(send_crc >> 8);
ctx->buf[packetlen - 1] = (uint8_t)send_crc & 0xff; ctx->buf[packetlen - 1] = (uint8_t)send_crc & 0xff;
/* DEBUG: 发包前确认 CRC + 序号正确 */
char hex_buf[16];
RD_SNPRINTF(hex_buf, sizeof(hex_buf), "%02X:%02X crc=%04X", index, index_inv, send_crc);
log_d("ymodem TX pkt: %s [%uB]", hex_buf, (unsigned)packetlen);
size_t writelen = 0; size_t writelen = 0;
do uint32_t retry = 0;
while (writelen < packetlen)
{ {
size_t n = ctx->dev.write(ctx->dev.opaque, ctx->buf + writelen, packetlen - writelen); size_t n = ctx->dev.write(ctx->dev.opaque, ctx->buf + writelen, packetlen - writelen);
if (n == 0)
{
/* 底层写回调返回 0 (未写出任何字节) 时不能原地死循环:
* 比如它误把 HAL_OK(0) 当作"已写字节数"返回 */
if (++retry >= (uint32_t)(RYM_WAIT_CHR_MS / 10u))
return RD_FAILURE;
Rd_Delay(1);
continue;
}
writelen += n; writelen += n;
retry = 0;
} }
while (writelen < packetlen);
return RD_SUCCESS; return RD_SUCCESS;
} }
@ -160,13 +183,9 @@ static void _rym_putchar(struct rym_ctx *ctx, uint8_t code)
static uint8_t _rym_getchar(struct rym_ctx *ctx) static uint8_t _rym_getchar(struct rym_ctx *ctx)
{ {
uint8_t c; /* 带超时的等应答: 不能无限等 (对端无响应时旧实现会把 lua 线程永久卡住),
/* rd_ComRead 内部阻塞等待数据, 此处只需重试即可 */ * 超时返回 RYM_CODE_NONE, 由调用方按"应答不对"上报 -RYM_ERR_ACK */
while (ctx->dev.read(ctx->dev.opaque, &c, 1) != 1) return (uint8_t)_rym_read_code(ctx, RYM_WAIT_PKG_MS);
{
Rd_Delay(1);
}
return c;
} }
static int _rym_do_handshake(struct rym_ctx *ctx, int tm_sec) static int _rym_do_handshake(struct rym_ctx *ctx, int tm_sec)
@ -175,12 +194,13 @@ static int _rym_do_handshake(struct rym_ctx *ctx, int tm_sec)
size_t i; size_t i;
uint16_t recv_crc, cal_crc; uint16_t recv_crc, cal_crc;
size_t data_sz = 0; size_t data_sz = 0;
uint32_t start, deadline;
ctx->stage = RYM_STAGE_ESTABLISHING; ctx->stage = RYM_STAGE_ESTABLISHING;
log_i("ymodem RX handshake: start polling, %d rounds x %u ms", tm_sec, (unsigned)RYM_CHD_INTV_MS);
for (i = 0; i < (size_t)tm_sec; i++) for (i = 0; i < (size_t)tm_sec; i++)
{ {
_rym_putchar(ctx, RYM_CODE_C); _rym_putchar(ctx, RYM_CODE_C);
log_i("ymodem RX handshake: sent 'C' #%u, waiting header...", (unsigned)(i + 1));
code = _rym_read_code(ctx, RYM_CHD_INTV_MS); code = _rym_read_code(ctx, RYM_CHD_INTV_MS);
if (code == RYM_CODE_SOH) if (code == RYM_CODE_SOH)
{ {
@ -192,33 +212,51 @@ static int _rym_do_handshake(struct rym_ctx *ctx, int tm_sec)
data_sz = _RYM_STX_PKG_SZ; data_sz = _RYM_STX_PKG_SZ;
break; break;
} }
else if (code != RYM_CODE_NONE)
{
/* 有字节但不是包头: 说明链路通, 问题在协议/对端 */
log_w("ymodem RX handshake: got 0x%02X while waiting header", (unsigned)code);
}
} }
if (i == (size_t)tm_sec) if (i == (size_t)tm_sec)
return -RYM_ERR_TMO;
/* 接收剩余数据 */
i = 0;
start = Rd_GetTime();
deadline = start + 100;
while (Rd_GetTime() <= deadline && i < (data_sz - 1))
{ {
i += _rym_read_data(ctx, data_sz - 1); log_e("ymodem RX handshake: no packet header in %d rounds x %u ms (一个字节都没收到 -> 查端口/接线)",
Rd_Delay(5); tm_sec, (unsigned)RYM_CHD_INTV_MS);
return -RYM_ERR_TMO;
} }
/* 接收剩余数据: 必须一次读完.
* _rym_read_data 每次都是从 ctx->buf + 1 的偏移 0 开始写, 分多次调用会互相覆盖,
* 而外层用 i 累加长度会误判为"已收齐"; 原来的 100ms 窗口也和
* _rym_read_data 内部的 RYM_WAIT_CHR_MS 超时自相矛盾 (根本来不及). */
i = _rym_read_data(ctx, data_sz - 1);
if (i != (data_sz - 1)) if (i != (data_sz - 1))
{
log_e("ymodem RX HANDSHAKE: header short read %u/%u", (unsigned)i, (unsigned)(data_sz - 1));
return -RYM_ERR_DSZ; return -RYM_ERR_DSZ;
}
if (ctx->buf[1] != 0 || ctx->buf[2] != 0xFF) if (ctx->buf[1] != 0 || ctx->buf[2] != 0xFF)
{
log_e("ymodem RX HANDSHAKE: bad header seq 0x%02X/0x%02X", ctx->buf[1], ctx->buf[2]);
return -RYM_ERR_SEQ; return -RYM_ERR_SEQ;
}
recv_crc = (uint16_t)(*(ctx->buf + data_sz - 2) << 8) | *(ctx->buf + data_sz - 1); recv_crc = (uint16_t)(*(ctx->buf + data_sz - 2) << 8) | *(ctx->buf + data_sz - 1);
cal_crc = CRC16(ctx->buf + 3, data_sz - 5); cal_crc = CRC16(ctx->buf + 3, data_sz - 5);
if (recv_crc != cal_crc) if (recv_crc != cal_crc)
{
log_e("ymodem RX HANDSHAKE: header crc mismatch recv=0x%04X calc=0x%04X", recv_crc, cal_crc);
return -RYM_ERR_CRC; return -RYM_ERR_CRC;
}
if (ctx->on_begin && ctx->on_begin(ctx, ctx->buf + 3, data_sz - 5) != RYM_CODE_ACK) if (ctx->on_begin && ctx->on_begin(ctx, ctx->buf + 3, data_sz - 5) != RYM_CODE_ACK)
{
log_e("ymodem RX HANDSHAKE: create/open file failed");
return -RYM_ERR_CAN; return -RYM_ERR_CAN;
}
log_i("ymodem RX: handshake done");
return RD_SUCCESS; return RD_SUCCESS;
} }
@ -228,33 +266,81 @@ static int _rym_do_send_handshake(struct rym_ctx *ctx, int tm_sec)
enum rym_code code; enum rym_code code;
size_t i; size_t i;
size_t data_sz; size_t data_sz;
size_t retry;
uint8_t getc_ack; uint8_t getc_ack;
ctx->stage = RYM_STAGE_ESTABLISHING; ctx->stage = RYM_STAGE_ESTABLISHING;
data_sz = _RYM_SOH_PKG_SZ; data_sz = _RYM_SOH_PKG_SZ;
log_i("ymodem TX handshake: waiting for receiver's 'C' (%d rounds x %u ms)...",
tm_sec, (unsigned)RYM_CHD_INTV_MS);
for (i = 0; i < (size_t)tm_sec; i++) for (i = 0; i < (size_t)tm_sec; i++)
{ {
code = _rym_read_code(ctx, RYM_CHD_INTV_MS); code = _rym_read_code(ctx, RYM_CHD_INTV_MS);
if (code == RYM_CODE_C) if (code == RYM_CODE_C)
break; break;
if (code != RYM_CODE_NONE)
{
/* 收到别的字节也报出来: 区分"链路完全没数据"与"有数据但不是 'C'" */
log_w("ymodem TX handshake: got 0x%02X while waiting 'C'", (unsigned)code);
}
} }
if (i == (size_t)tm_sec) if (i == (size_t)tm_sec)
{
log_e("ymodem TX handshake: no 'C' in %d rounds x %u ms (一个字节都没收到 -> 查端口/接线)",
tm_sec, (unsigned)RYM_CHD_INTV_MS);
return -RYM_ERR_TMO; return -RYM_ERR_TMO;
}
if (ctx->on_begin && ctx->on_begin(ctx, ctx->buf + 3, data_sz - 5) != RYM_CODE_SOH) if (ctx->on_begin && ctx->on_begin(ctx, ctx->buf + 3, data_sz - 5) != RYM_CODE_SOH)
{
log_e("ymodem TX handshake: build file header failed");
return -RYM_ERR_CODE; return -RYM_ERR_CODE;
}
/* 发包头. 对端可能还没准备好(比如正在建文件)而回 NAK 或干脆不回,
* 这两种都是"重发", 不能当失败退出 */
for (retry = 0; ; retry++)
{
_rym_send_packet(ctx, RYM_CODE_SOH, 0); _rym_send_packet(ctx, RYM_CODE_SOH, 0);
/* 接收端在等包头期间会反复发 'C', 环里可能残留多个;
* 先跳过它们, 否则后面读到的"应答"会整体错位 */
do
{
getc_ack = _rym_getchar(ctx); getc_ack = _rym_getchar(ctx);
if (getc_ack != RYM_CODE_ACK) }
while (getc_ack == RYM_CODE_C);
if (getc_ack == RYM_CODE_ACK)
break;
if (getc_ack == RYM_CODE_CAN)
{
log_e("ymodem TX handshake: receiver cancelled (CAN)");
return -RYM_ERR_CAN;
}
if (retry + 1 >= (size_t)RYM_TX_RETRY_MAX)
{
log_e("ymodem TX HANDSHAKE: header not ACKed after %u tries, last=0x%02X (0x00=timeout)",
(unsigned)RYM_TX_RETRY_MAX, getc_ack);
return -RYM_ERR_ACK; return -RYM_ERR_ACK;
}
log_w("ymodem TX handshake: header %s -> resend (%u/%u)",
(getc_ack == RYM_CODE_NAK) ? "NAKed" : "no reply",
(unsigned)(retry + 1), (unsigned)RYM_TX_RETRY_MAX);
}
getc_ack = _rym_getchar(ctx); getc_ack = _rym_getchar(ctx);
if (getc_ack != RYM_CODE_C) if (getc_ack != RYM_CODE_C)
{
log_e("ymodem TX HANDSHAKE: no 'C' after header ACK, got=0x%02X (0x00=timeout)", getc_ack);
return -RYM_ERR_ACK; return -RYM_ERR_ACK;
}
log_i("ymodem TX: handshake done");
ctx->stage = RYM_STAGE_ESTABLISHED; ctx->stage = RYM_STAGE_ESTABLISHED;
return RD_SUCCESS; return RD_SUCCESS;
} }
@ -266,10 +352,16 @@ static int _rym_trans_data(struct rym_ctx *ctx, size_t data_sz, enum rym_code *c
size_t i = _rym_read_data(ctx, tsz); size_t i = _rym_read_data(ctx, tsz);
if (i != tsz) if (i != tsz)
{
log_e("ymodem RX data: short read %u/%u", (unsigned)i, (unsigned)tsz);
return -RYM_ERR_DSZ; return -RYM_ERR_DSZ;
}
if ((ctx->buf[1] + ctx->buf[2]) != 0xFF) if ((ctx->buf[1] + ctx->buf[2]) != 0xFF)
{
log_e("ymodem RX data: bad seq 0x%02X/0x%02X", ctx->buf[1], ctx->buf[2]);
return -RYM_ERR_SEQ; return -RYM_ERR_SEQ;
}
if (ctx->stage == RYM_STAGE_ESTABLISHED && ctx->buf[1] == 0x00) if (ctx->stage == RYM_STAGE_ESTABLISHED && ctx->buf[1] == 0x00)
{ {
@ -281,7 +373,10 @@ static int _rym_trans_data(struct rym_ctx *ctx, size_t data_sz, enum rym_code *c
recv_crc = (uint16_t)(*(ctx->buf + tsz - 1) << 8) | *(ctx->buf + tsz); recv_crc = (uint16_t)(*(ctx->buf + tsz - 1) << 8) | *(ctx->buf + tsz);
if (recv_crc != CRC16(ctx->buf + 3, data_sz)) if (recv_crc != CRC16(ctx->buf + 3, data_sz))
{
log_e("ymodem RX data: crc mismatch recv=0x%04X", recv_crc);
return -RYM_ERR_CRC; return -RYM_ERR_CRC;
}
if (ctx->on_data) if (ctx->on_data)
*code = ctx->on_data(ctx, ctx->buf + 3, data_sz); *code = ctx->on_data(ctx, ctx->buf + 3, data_sz);
@ -314,9 +409,12 @@ static int _rym_do_trans(struct rym_ctx *ctx)
data_sz = 1024; data_sz = 1024;
break; break;
case RYM_CODE_EOT: case RYM_CODE_EOT:
log_i("ymodem RX: EOT received, data phase done");
return RD_SUCCESS; return RD_SUCCESS;
default: default:
errors++; errors++;
log_w("ymodem RX: unexpected code 0x%02X (error %u/%u)",
(unsigned)code, (unsigned)errors, (unsigned)RYM_MAX_ERRORS);
if (errors > RYM_MAX_ERRORS) if (errors > RYM_MAX_ERRORS)
return -RYM_ERR_CODE; return -RYM_ERR_CODE;
_rym_putchar(ctx, RYM_CODE_NAK); _rym_putchar(ctx, RYM_CODE_NAK);
@ -327,6 +425,8 @@ static int _rym_do_trans(struct rym_ctx *ctx)
if (err != RD_SUCCESS) if (err != RD_SUCCESS)
{ {
errors++; errors++;
log_e("ymodem RX: block error %d (error %u/%u)",
err, (unsigned)errors, (unsigned)RYM_MAX_ERRORS);
if (errors > RYM_MAX_ERRORS) if (errors > RYM_MAX_ERRORS)
return err; return err;
_rym_putchar(ctx, RYM_CODE_NAK); _rym_putchar(ctx, RYM_CODE_NAK);
@ -356,23 +456,64 @@ static int _rym_do_send_trans(struct rym_ctx *ctx)
uint32_t index = 1; uint32_t index = 1;
uint8_t getc_ack; uint8_t getc_ack;
size_t data_sz = _RYM_STX_PKG_SZ; size_t data_sz = _RYM_STX_PKG_SZ;
size_t retry;
while (1) while (1)
{ {
if (!ctx->on_data) if (!ctx->on_data)
return -RYM_ERR_CODE; return -RYM_ERR_CODE;
/* 注意: 一个块只读一次文件. 下面重传时必须直接复用 ctx->buf,
* 不能再调 on_data, 否则每重传一次就往后多吃一段数据, 文件就乱了 */
code = ctx->on_data(ctx, ctx->buf + 3, data_sz - 5); code = ctx->on_data(ctx, ctx->buf + 3, data_sz - 5);
log_i("ymodem TX data: block %u sending (code=0x%02X%s)",
(unsigned)(uint8_t)index, (unsigned)code,
(ctx->stage == RYM_STAGE_FINISHING) ? ", last block" : "");
for (retry = 0; ; retry++)
{
_rym_send_packet(ctx, code, index); _rym_send_packet(ctx, code, index);
index++;
/* 接收端偶尔会在数据阶段夹发 'C' 催包, 直接跳过, 否则应答会整体错位 */
do
{
getc_ack = _rym_getchar(ctx); getc_ack = _rym_getchar(ctx);
if (getc_ack != RYM_CODE_ACK) if (getc_ack == RYM_CODE_C)
log_w("ymodem TX data: skip stray 'C' while waiting ACK of block %u",
(unsigned)(uint8_t)index);
}
while (getc_ack == RYM_CODE_C);
if (getc_ack == RYM_CODE_ACK)
break;
if (getc_ack == RYM_CODE_CAN)
{
log_e("ymodem TX data: receiver cancelled (CAN) at block %u", (unsigned)(uint8_t)index);
return -RYM_ERR_CAN;
}
/* NAK / 超时 = "这一块没收好, 请重发" (复用同一块数据) */
if (retry + 1 >= (size_t)RYM_TX_RETRY_MAX)
{
log_e("ymodem TX DATA PHASE: block %u not ACKed after %u tries, last=0x%02X (0x00=timeout, 0x15=NAK, 0x18=CAN)",
(unsigned)(uint8_t)index, (unsigned)RYM_TX_RETRY_MAX, getc_ack);
return -RYM_ERR_ACK; return -RYM_ERR_ACK;
}
log_w("ymodem TX data: block %u %s -> resend (%u/%u)",
(unsigned)(uint8_t)index, (getc_ack == RYM_CODE_NAK) ? "NAKed" : "no reply",
(unsigned)(retry + 1), (unsigned)RYM_TX_RETRY_MAX);
}
index++;
if (ctx->stage == RYM_STAGE_FINISHING) if (ctx->stage == RYM_STAGE_FINISHING)
{
log_i("ymodem TX: all data blocks sent (%u block(s))", (unsigned)index - 1);
break; break;
} }
}
return RD_SUCCESS; return RD_SUCCESS;
} }
@ -391,7 +532,10 @@ static int _rym_do_fin(struct rym_ctx *ctx)
_rym_putchar(ctx, RYM_CODE_NAK); _rym_putchar(ctx, RYM_CODE_NAK);
code = _rym_read_code(ctx, RYM_WAIT_PKG_MS); code = _rym_read_code(ctx, RYM_WAIT_PKG_MS);
if (code != RYM_CODE_EOT) if (code != RYM_CODE_EOT)
{
log_e("ymodem RX FIN phase: expected EOT, got 0x%02X (0x00=timeout)", (unsigned)code);
return -RYM_ERR_CODE; return -RYM_ERR_CODE;
}
_rym_putchar(ctx, RYM_CODE_ACK); _rym_putchar(ctx, RYM_CODE_ACK);
_rym_putchar(ctx, RYM_CODE_C); _rym_putchar(ctx, RYM_CODE_C);
@ -425,6 +569,7 @@ static int _rym_do_fin(struct rym_ctx *ctx)
ctx->stage = RYM_STAGE_FINISHED; ctx->stage = RYM_STAGE_FINISHED;
_rym_putchar(ctx, RYM_CODE_ACK); _rym_putchar(ctx, RYM_CODE_ACK);
log_i("ymodem RX: session finished");
return RD_SUCCESS; return RD_SUCCESS;
} }
@ -432,28 +577,55 @@ static int _rym_do_send_fin(struct rym_ctx *ctx)
{ {
size_t data_sz; size_t data_sz;
uint8_t index = 0; uint8_t index = 0;
size_t retry;
uint8_t getc_ack; uint8_t getc_ack;
data_sz = _RYM_SOH_PKG_SZ; data_sz = _RYM_SOH_PKG_SZ;
/* EOT 阶段: 对端回 NAK 表示"请再发一次 EOT", 直接回 ACK 表示收到,
* 不回(超时)也重发; 三种情况都不能当失败退出 (有些接收端要两三次才确认) */
for (retry = 0; ; retry++)
{
_rym_putchar(ctx, RYM_CODE_EOT); _rym_putchar(ctx, RYM_CODE_EOT);
getc_ack = _rym_getchar(ctx); getc_ack = _rym_getchar(ctx);
if (getc_ack != RYM_CODE_NAK)
return -RYM_ERR_ACK;
_rym_putchar(ctx, RYM_CODE_EOT); if (getc_ack == RYM_CODE_ACK)
getc_ack = _rym_getchar(ctx); break;
if (getc_ack != RYM_CODE_ACK)
return -RYM_ERR_ACK;
getc_ack = _rym_getchar(ctx); if (getc_ack == RYM_CODE_CAN)
if (getc_ack != RYM_CODE_C) {
log_e("ymodem TX fin: receiver cancelled (CAN)");
return -RYM_ERR_CAN;
}
if (retry + 1 >= (size_t)RYM_TX_RETRY_MAX)
{
log_e("ymodem TX FIN PHASE: EOT not ACKed after %u tries, last=0x%02X (0x00=timeout, 0x06=ACK, 0x15=NAK)",
(unsigned)RYM_TX_RETRY_MAX, getc_ack);
return -RYM_ERR_ACK; return -RYM_ERR_ACK;
}
log_w("ymodem TX fin: EOT %s -> resend (%u/%u)",
(getc_ack == RYM_CODE_NAK) ? "NAKed" : "no reply",
(unsigned)(retry + 1), (unsigned)RYM_TX_RETRY_MAX);
}
/* 等接收端的 'C' (表示准备接收下一个文件).
* 不把这一步当致命错误: 数据已经全部发完并被 ACK, 部分接收端不发 'C'
* 而是直接等结束帧; 因为这个地方返回 -118 会误报"传输失败"并且结束帧发不出去,
* 接收端就会一直等下去 (表现就是"不会自动停止") */
(void)_rym_getchar(ctx);
if (ctx->on_end && ctx->on_end(ctx, ctx->buf + 3, data_sz - 5) != RYM_CODE_SOH) if (ctx->on_end && ctx->on_end(ctx, ctx->buf + 3, data_sz - 5) != RYM_CODE_SOH)
return -RYM_ERR_CODE; return -RYM_ERR_CODE;
_rym_send_packet(ctx, RYM_CODE_SOH, index); _rym_send_packet(ctx, RYM_CODE_SOH, index);
/* 结束帧的 ACK 只做尽力而为的清理: 拿到了就不会残留到下一次会话,
* 拿不到 (超时/对端已收工) 也不影响本次传输结果 */
(void)_rym_getchar(ctx);
log_i("ymodem TX: session finished");
ctx->stage = RYM_STAGE_FINISHED; ctx->stage = RYM_STAGE_FINISHED;
return RD_SUCCESS; return RD_SUCCESS;
} }

Loading…
Cancel
Save