YMODEM协议介绍与高效高可移植非阻塞版本实现

原创 嵌入式Lee 2024-05-21 08:02

一. 前言

YMODEM类似于XMODED-1K模式(实际在SecureCRT等实现中会混合使用128包长模式),固定使用crc校验。和XMODEM的主要区别是,他会在正式传输文件前先使用ID0的包进行文件信息交互,文件传输完成之后使用ID0的包来结束整个传输。可以连续传输多个文件。

YMODEM还有个变种即YMODEM-g,流式无ACK,一般很少使用。

xmodem实现参考 《https://mp.weixin.qq.com/s/QSYKxND3DTyQZ0JtnSqkzQ XMODEM协议介绍与高效高可移植非阻塞版本实现》

Xmodemymodem协议参考xymodem.pdf(http://www.blunk-electronic.de/train-z/pdf/xymodem.pdf)

二. 协议介绍

基于Xmodem-1K,所以包格式和Xmodem一样。但是Ymodem在正式传输文件之前有一个包ID0的交互用于传输文件信息,中间可以传输多个文件,所有文件传输完成之后用一个ID0的包交互结束。一个文件的内容传输过程就对应Xmodem的传输,一个文件传输之后有两个EOT,接收方对第一个EOT进行NAK,对第二个进行ACK。详见传输过程示意。

2.1传输过程示意

整体上看流程为:

1.启动0 ID包交互

2.文件传输

3.文件传输

....

4.结束0 ID包交互

文件传输即对应Xmodem的过程,但是最后传输多了一个EOT-NAK的过程。

0 ID包的内容见下一节

如下图所示,有背景部分是ymodem相对xmodem增加,图示只描述正常流程,为了简洁不包括取消,NAK重发等过程。

2.2 格式

包格式参考前言的文档,这里不再赘述,只描述ydmodem不一样的0 ID包。

注意启动包,结束包都是ID0,所以数据传输从包ID1开始,但是绕到255之后还是会从0继续,而不是从1开始。所以不能以包ID0来判断是否为传输数据的包还是启动结束包。

2.2.1开始0 ID

按照文档的介绍格式为如下,即包ID0, 1024字节包长即STX,但是实际应用一般可以配置所以也可以SOH128字节包长。

文档规定只需要文件名以空字符结束,如下所示文件名x.xxx长度为5字节加上结束字符6字节,所以后面填充1024或者128-6字节的0. 最后是2字节的CRC校验值,大端存储。

固定使用CRC校验,不使用checksum

STX|SOH 00 FF "x.xxx" NUL[1024|128-6] (CRCCRC)

实际在应用中一般会扩展文件长度,时间,属性等信息,比如以SecureCRT为例,抓取格式如下

STX|SOH 00 FF "x.xxx" NULLEN0x20TIME0x20ATT” (CRCCRC)

即长度LEN和时间戳TIME和ATT属性之间以空格分割。

而文件名后和ATT之后以空字符0结束。

即文件名x.xxxNUL结束符之后增加了10进制的文件长度信息LEN,如下图的14113字节,

然后空格0x20之后是8进制UTC时间戳,

如下图8进制数14 620 606 052转为十进制为1715670058

https://tool.lu/timestamp/换算,对应时间戳为2024-05-14 15:00:58和文件修改时间一致。

然后空格0x20之后应该是属性,这里是一个固定的字符0. 最后全部填充0x00.

开始0 ID包也可能是128长包格式,比如SecureCRT除了第一个文件使用1024的启动0 DI包,后续文件都使用128包长。

2.2.2结束0 ID

按照文档描述格式为

STX|SOH 00 FF NUL[1024|128] CRC CRC

SecureCRT为如下,即长度固定为128字节,即SOH头,

然后文件名是空为0x00

然后文件长度为0x30即字符0 后面加0x20空格

时间戳也是0x30即字符0后面加0x20空格

属性为0x30即字符0后面加0x00结束。

实际就是文件名为NUL,文件长度为0, 时间戳为0,属性为0

三. 实现

使用状态机的方式实现,非阻塞。

状态机示意如下

接收状态机

发送状态机

代码实现如下

ymodem.c

#include #include "ymodem.h"
/* 符号定义 */#define SOH 0x01#define STX 0x02#define EOT 0x04#define ACK 0x06#define NAK 0x15#define CAN 0x18#define CTRLZ 0x1A
/** * crc计算参考https://www.iar.com/knowledge/support/technical-notes/general/checksum-generation/*/static const uint16_t t[] = { 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7, 0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,};
static uint16_t crc_nibble_rom(uint16_t sum, uint8_t* p, uint32_t len){ while (len--) { // hi nibble sum = t[(sum>>12)^(*p >> 4)]^(sum<<4); // lo nibble sum = t[(sum>>12)^(*p++ & 0xF)]^(sum<<4); } return sum;}
/** * \fn ymodem_check * \paran[in] crc 0使用checksum 1使用crc * \paran[in] buf 待计算数据 * \paran[in] sz 待计算数据长度 * \retval 0 校验正确 * \retval 其他值校验错误*/static int ymodem_check(uint16_t crc, uint8_t *buf, uint32_t sz){ if (crc != 0) { uint16_t crc = crc_nibble_rom(0, buf, sz); uint16_t tcrc = ((uint16_t)(buf[sz]) << 8) + (uint16_t)(buf[sz + 1]); if (crc == tcrc) { return 0; } } else { uint8_t cks = 0; for (uint32_t i = 0; i < sz; i++) { cks += buf[i]; } if (cks == buf[sz]) { return 0; } } return -1;}
#if YMODEM_RX_ENABLEstatic ymodem_rx_cfg_st* s_rx_cfg_pst = 0; /* 接口指针,ymodem_rx_init初始化 */
static unsigned int ymodem_atoi(uint8_t* str, uint32_t len){ unsigned int i = 0U; while(len--) { if ((*str >= '0') && (*str <= '9')) { i = i * 10U + (unsigned int)(*str++ - '0'); } else { break; } } return i;}
static uint32_t ymodem_get_len(uint8_t* str,uint32_t len){ uint8_t* p = str; if(*p== 0) { /* 无效设置最大值 */ return 0xFFFFFFFF; }
while(len--) { /* 略去前面的文件名 */ if(*p++ == 0) { break; } }
if(len >= 2) { return ymodem_atoi(p,len); } else { /* 后面整数至少也要2个字符,一个数字一个NULL,否则无效设置最大值 */ return 0xFFFFFFFF; }}
/** * \fn ymodem_rx * 接收处理, 在此之前必须要先调用xymodem_rx_init初始化. * 返回XYMODEM_RX_ERR_NEED_CONTINUE则需要继续重复调用,直到返回其他值. * \return \ref xymodem_rx_err_e*/ymodem_rx_err_e ymodem_rx(void){ ymodem_rx_err_e res = YMODEM_RX_ERR_NEED_CONTINUE; uint8_t tmp = NAK; uint32_t t; uint8_t* buf; uint16_t len; uint32_t getlen;#if YMODEM_CHECK_PARAM if(s_rx_cfg_pst == (ymodem_rx_cfg_st*)0) { return YMODEM_RX_ERR_UNINIT; } if((s_rx_cfg_pst->io_read == 0) || \ (s_rx_cfg_pst->io_write == 0) || \ (s_rx_cfg_pst->mem_write == 0) || \ (s_rx_cfg_pst->mem_start == 0) || \ (s_rx_cfg_pst->mem_done == 0) || \ (s_rx_cfg_pst->buffer == 0) || \ (s_rx_cfg_pst->getms == 0)) { return YMODEM_RX_ERR_UNINIT; }#endif t = s_rx_cfg_pst->getms(); switch(s_rx_cfg_pst->state.state_e) { case YMODEM_STATE_RX_IDLE: s_rx_cfg_pst->state.getlen_u32 = 0; s_rx_cfg_pst->state.pnum_u8 = 0; s_rx_cfg_pst->xferlen = 0; s_rx_cfg_pst->totallen = 0; /* YMODEM使用'C' 启动, 使用CRC16 */ tmp = 'C'; s_rx_cfg_pst->io_write(&tmp,1); /** * 等待接收P0的头 */ s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_P0_HEAD; s_rx_cfg_pst->state.ms_u32 = t; /* 状态切换更新时间 */ break; case YMODEM_STATE_RX_WAIT_P0_HEAD: /* 这里初始化,接收多个文件时,新的文件从0开始,已经接收长度也重新开始计算 */ s_rx_cfg_pst->state.pnum_u8 = 0; s_rx_cfg_pst->xferlen = 0;
buf = s_rx_cfg_pst->buffer; buf[0] = 0; /* 必须初始化,否则加入未收到数据, 后面超时判断要判断该值,则判断的可能是上一帧的值,导入不进入超时判断 */ if(0 != s_rx_cfg_pst->io_read(buf,1)) { /* 读到头,切换状态到读数据 */ if((buf[0] == SOH) || (buf[0] == STX)) { s_rx_cfg_pst->plen = (buf[0] == SOH) ? (uint16_t)128 : (uint16_t)1024; /* 根据头决定接收包长 */ s_rx_cfg_pst->state.ms_u32 = t; s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_P0_DATA; s_rx_cfg_pst->state.getlen_u32 = 1; s_rx_cfg_pst->is_start = 1; } else { /* 其他数据忽略 */ } } else { /* 没有读到数据 */ }
if((buf[0] != SOH) && (buf[0] != STX)) { /* 未收到头,进行超时判断 */ if((t - s_rx_cfg_pst->state.ms_u32) >= s_rx_cfg_pst->ack_timeout) { s_rx_cfg_pst->state.ms_u32 = t; /* 更新时间以便下一次继续判断超时 */ /* 如果是传输第一个文件时,即刚开始超时未收到响应,继续重复发送启动字符,如果超过了设置的超时时间则退出 * 只有第一次is_start=0时,才等待start_timeout超时. */ if(s_rx_cfg_pst->is_start == 0) { if(s_rx_cfg_pst->start_timeout <= 1) { /* 超时start_timeout次后退出 */ res = YMODEM_RX_ERR_WAIT_P0_HEAD_TO; } else { /* 回到IDLE重新发送启动 */ s_rx_cfg_pst->start_timeout--; s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_IDLE; } } else { /* 非最开始启动时,超时即退出 */ res = YMODEM_RX_ERR_WAIT_P0_HEAD_TO; } } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_RX_WAIT_P0_DATA: buf = s_rx_cfg_pst->buffer; len = s_rx_cfg_pst->plen + 3 + 2; /* 总共需要接收的长度 */ getlen = s_rx_cfg_pst->io_read(buf+s_rx_cfg_pst->state.getlen_u32, len - s_rx_cfg_pst->state.getlen_u32); /* 尝试接收剩余需要接收的长度 */ s_rx_cfg_pst->state.getlen_u32 += getlen; if(s_rx_cfg_pst->state.getlen_u32 >= len) { /* 接收完,准备判断合法性 */ if(((uint8_t)0 == buf[1]) && ((buf[1] + buf[2]) == (uint8_t)255)) { /* 校验正确 */ if(0 == ymodem_check(1, buf+3, s_rx_cfg_pst->plen)) { s_rx_cfg_pst->state.pnum_u8 = buf[1]; s_rx_cfg_pst->state.ms_u32 = t; if(buf[3] == 0) { /* 传输结束 回ACK */ tmp = ACK; s_rx_cfg_pst->io_write(&tmp,1); res = YMODEM_RX_ERR_NONE; } else { /* 文件开始 解析文件大小 */ s_rx_cfg_pst->totallen = ymodem_get_len(&(buf[3]),s_rx_cfg_pst->plen);
/* 调用启动接口 */ uint8_t* fname = &(buf[3]); if(0 == s_rx_cfg_pst->mem_start(s_rx_cfg_pst->addr,&fname,0)) { /* 回ACK */ tmp = ACK; s_rx_cfg_pst->io_write(&tmp,1);
/* 回'C' */ tmp = 'C'; s_rx_cfg_pst->io_write(&tmp,1); s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_DATA_HEAD; s_rx_cfg_pst->state.ms_u32 = t; } else { /* 不成功回取消 */ for(int i=0;i<10;i++) { tmp = CAN; s_rx_cfg_pst->io_write(&tmp,1); } res = YMODEM_RX_ERR_START; } } } else { /* NAK让对方重发 @todo需要flush输入? */ tmp = NAK; s_rx_cfg_pst->io_write(&tmp,1); res = YMODEM_RX_ERR_WAIT_P0_DATA_TO; } } } else { }
/* 超时处理 */ if((t - s_rx_cfg_pst->state.ms_u32) >= s_rx_cfg_pst->packet_timeout) { /* 超时,NAK让对方重发 */ tmp = NAK; s_rx_cfg_pst->io_write(&tmp,1); res = YMODEM_RX_ERR_WAIT_P0_DATA_TO; } else { /* 未超时继续等待*/ } break; case YMODEM_STATE_RX_WAIT_DATA_HEAD: buf = s_rx_cfg_pst->buffer; buf[0] = 0; /* 必须初始化,否则加入未收到数据, 后面超时判断要判断该值,则判断的可能是上一帧的值,导入不进入超时判断 */ if(0 != s_rx_cfg_pst->io_read(buf,1)) { /* 读到头,切换状态到读数据 */ if((buf[0] == SOH) || (buf[0] == STX)) { s_rx_cfg_pst->plen = (buf[0] == SOH) ? (uint16_t)128 : (uint16_t)1024; /* 根据头决定接收包长 */ s_rx_cfg_pst->state.ms_u32 = t; s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_DATA_DATA; s_rx_cfg_pst->state.getlen_u32 = 1; } else if(buf[0] == EOT) { /* NAK等待下一个EOT */ tmp = NAK; s_rx_cfg_pst->io_write(&tmp,1); s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_EOT; } else if(buf[0] == CAN) { /* 提前取消 */ res = YMODEM_RX_ERR_CAN; s_rx_cfg_pst->mem_done(); } else { /* 其他数据忽略 */ } } else { /* 没有读到数据 */ }
if((buf[0] != SOH) && (buf[0] != STX)) { /* 未收到头,进行超时判断 */ if((t - s_rx_cfg_pst->state.ms_u32) >= s_rx_cfg_pst->ack_timeout) { s_rx_cfg_pst->state.ms_u32 = t; /* 更新时间以便下一次继续判断超时 */ res = YMODEM_RX_ERR_WAIT_DATA_HEAD_TO; } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_RX_WAIT_DATA_DATA: buf = s_rx_cfg_pst->buffer; len = s_rx_cfg_pst->plen + 3 + 2; /* 总共需要接收的长度 */ getlen = s_rx_cfg_pst->io_read(buf+s_rx_cfg_pst->state.getlen_u32, len - s_rx_cfg_pst->state.getlen_u32); /* 尝试接收剩余需要接收的长度 */ s_rx_cfg_pst->state.getlen_u32 += getlen; if(s_rx_cfg_pst->state.getlen_u32 >= len) { /* 接收完,准备判断合法性 */ if(((uint8_t)((s_rx_cfg_pst->state.pnum_u8+(uint8_t)1)) == buf[1]) && ((buf[1] + buf[2]) == (uint8_t)255)) { /* 校验正确 */ if(0 == ymodem_check(1, buf+3, s_rx_cfg_pst->plen)) { s_rx_cfg_pst->state.pnum_u8 = buf[1]; s_rx_cfg_pst->state.ms_u32 = t; /* 调用写接口 */ if(s_rx_cfg_pst->xferlen >= s_rx_cfg_pst->totallen) { /* 上一次已经接收了足够的数据,本次无需再写入, 取消 */ for(int i= 0; i<10; i++) { tmp = CAN; s_rx_cfg_pst->io_write(&tmp,1); } res = YMODEM_RX_ERR_NONE; } else { /* 本次可能是最后一包,有可能有填充,如果超过长度,则只接收指定长度 */ if((s_rx_cfg_pst->xferlen + s_rx_cfg_pst->plen) > s_rx_cfg_pst->totallen) { s_rx_cfg_pst->mem_write(s_rx_cfg_pst->addr,&(buf[3]),s_rx_cfg_pst->totallen - s_rx_cfg_pst->xferlen); s_rx_cfg_pst->xferlen += s_rx_cfg_pst->totallen - s_rx_cfg_pst->xferlen; } else { s_rx_cfg_pst->mem_write(s_rx_cfg_pst->addr,&(buf[3]),s_rx_cfg_pst->plen); s_rx_cfg_pst->xferlen += s_rx_cfg_pst->plen; } s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_DATA_HEAD; tmp = ACK; s_rx_cfg_pst->io_write(&tmp,1); s_rx_cfg_pst->state.ms_u32 = t; } } } } else { }
/* 未接收完判断超时 */ if((t - s_rx_cfg_pst->state.ms_u32) >= s_rx_cfg_pst->packet_timeout) { /* 超时退出 */ for(int i= 0; i<10; i++) { tmp = CAN; s_rx_cfg_pst->io_write(&tmp,1); } res = YMODEM_RX_ERR_WAIT_DATA_DATA_TO; } else { /* 未超时继续等待*/ } break; case YMODEM_STATE_RX_WAIT_EOT: buf = s_rx_cfg_pst->buffer; buf[0] = 0; /* 必须初始化,否则加入未收到数据, 后面超时判断要判断该值,则判断的可能是上一帧的值,导入不进入超时判断 */ if(0 != s_rx_cfg_pst->io_read(buf,1)) { /* 读到头,切换状态到读数据 */ if((buf[0] == EOT)) { tmp = ACK; s_rx_cfg_pst->io_write(&tmp,1);
tmp = 'C'; s_rx_cfg_pst->io_write(&tmp,1);
s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_WAIT_P0_HEAD; s_rx_cfg_pst->state.ms_u32 = t; /* 切换状态更新时间 */
s_rx_cfg_pst->mem_done(); } else { /* 其他数据忽略 */ } } else { /* 没有读到数据 */ }
if((buf[0] != EOT)) { /* 未收到头,进行超时判断 */ if((t - s_rx_cfg_pst->state.ms_u32) >= s_rx_cfg_pst->ack_timeout) { /* 超时退出 */ res = YMODEM_RX_ERR_WAIT_EOT_TO; } else { /* 未超时继续等待*/ } } break; default: return YMODEM_RX_ERR_UNINIT; break; } return res;}
/** * \fn ymodem_rx_init * 接收初始化 * \param[in] cfg \ref ymodem_rx_cfg_st 配置信息 * \retval 0 成功 * \retval 其他值 失败*/int ymodem_rx_init(ymodem_rx_cfg_st* cfg){#if YMODEM_CHECK_PARAM if(cfg == (ymodem_rx_cfg_st*)0) { return -1; }#endif s_rx_cfg_pst = cfg; s_rx_cfg_pst->state.state_e = YMODEM_STATE_RX_IDLE; return 0;}
#endif
#if YMODEM_TX_ENABLEstatic ymodem_tx_cfg_st* s_tx_cfg_pst = 0; /* 接口指针,ymodem_tx_init初始化 */
/** * \fn ymodem_tx * 发送处理, 在此之前必须要先调用xymodem_tx_init初始化. * 返回XYMODEM_TX_ERR_NEED_CONTINUE则需要继续重复调用,直到返回其他值. * \return \ref xymodem_tx_err_e*/ymodem_tx_err_e ymodem_tx(void){ ymodem_tx_err_e res = YMODEM_TX_ERR_NEED_CONTINUE; uint8_t tmp = 0; uint32_t t; uint8_t* buf;#if YMODEM_CHECK_PARAM if(s_tx_cfg_pst == (ymodem_tx_cfg_st*)0) { return YMODEM_TX_ERR_UNINIT; } if((s_tx_cfg_pst->io_read == 0) || \ (s_tx_cfg_pst->io_write == 0) || \ (s_tx_cfg_pst->mem_read == 0) || \ (s_tx_cfg_pst->mem_start == 0) || \ (s_tx_cfg_pst->mem_done == 0) || \ (s_tx_cfg_pst->buffer == 0) || \ (s_tx_cfg_pst->getms == 0)) { return YMODEM_TX_ERR_UNINIT; }#endif t = s_tx_cfg_pst->getms(); switch(s_tx_cfg_pst->state.state_e) { case YMODEM_STATE_TX_IDLE:
break; case YMODEM_STATE_TX_WAIT_P0_START: /* 这里初始化,发多个文件时,新的文件包从ID0开始 */ s_tx_cfg_pst->state.pnum_u8 = 0; if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { /* 读到头,切换状态到读数据 */ if(tmp == 'C') { uint8_t* str; uint32_t strlen; s_tx_cfg_pst->state.ms_u32 = t; s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_P0_ACK; s_tx_cfg_pst->is_start = 1; buf = s_tx_cfg_pst->buffer; uint16_t crc; if(0 == s_tx_cfg_pst->mem_start(0,&str,&strlen)) { /* 填充P0包 */ memset(buf,0,s_tx_cfg_pst->plen+5); buf[0] = (s_tx_cfg_pst->plen == 128) ? SOH : STX; buf[1] = 0; buf[2] = 0xFF; memcpy(&(buf[3]),str,strlen);
crc = crc_nibble_rom(0,&(buf[3]),s_tx_cfg_pst->plen); buf[s_tx_cfg_pst->plen+4] = crc&0xFF; buf[s_tx_cfg_pst->plen+3] = (crc>>8)&0xFF;
s_tx_cfg_pst->io_write(buf,s_tx_cfg_pst->plen+5);
s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_P0_ACK; s_tx_cfg_pst->state.ms_u32 = t; } else { /* 发送结束P0包 */ memset(buf,0,128+5); buf[0] = SOH; buf[1] = 0; buf[2] = 0xFF; buf[3] = 0; buf[4] = 0x30; buf[5] = 0x20; buf[6] = 0x30; buf[7] = 0x20; buf[8] = 0x30; crc = crc_nibble_rom(0,&(buf[3]),128); buf[128+4] = crc&0xFF; buf[128+3] = (crc>>8)&0xFF; s_tx_cfg_pst->io_write(buf,s_tx_cfg_pst->plen+5); s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_P0_DONE_ACK; s_tx_cfg_pst->state.ms_u32 = t; } } else if(tmp == CAN) { /* 取消 */ res = YMODEM_TX_ERR_CAN; } else {
} } else { /* 没有读到数据 */ }
if(tmp != 'C') { /* 未收到头,进行超时判断 */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { s_tx_cfg_pst->state.ms_u32 = t; /* 更新时间以便下一次继续判断超时 */ /* 如果是传输第一个文件时,即刚开始超时未收到启动'C',继续等待,如果超过了设置的超时时间则退出 * 只有第一次is_start=0时,才等待start_timeout超时. */ if(s_tx_cfg_pst->is_start == 0) { if(s_tx_cfg_pst->start_timeout <= 1) { /* 超时start_timeout次后退出 */ res = YMODEM_TX_EER_WAIT_P0_START_TO; } else { /* 递减超时次数,继续等待 */ s_tx_cfg_pst->start_timeout--; } } else { /* 非最开始启动时,超时即退出 */ res = YMODEM_TX_EER_WAIT_P0_START_TO; } } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_TX_WAIT_P0_ACK: if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { /* 读到头,切换状态到读数据 */ if(tmp == ACK) { s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_DATA_START; s_tx_cfg_pst->state.ms_u32 = t; } } if(tmp != ACK) { /* 未收到ACK */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { res = YMODEM_TX_EER_WAIT_P0_ACK_TO; } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_TX_WAIT_DATA_START: buf = s_tx_cfg_pst->buffer; if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { /* 读到头,切换状态到发送数据 */ if(tmp == 'C') { uint32_t rdlen; uint16_t crc; /* 发送一包数据 */ s_tx_cfg_pst->state.pnum_u8++; buf[0] = (s_tx_cfg_pst->plen == 128) ? SOH : STX; buf[1] = s_tx_cfg_pst->state.pnum_u8; buf[2] = ~buf[1]; rdlen = s_tx_cfg_pst->mem_read(s_tx_cfg_pst->addr,&(buf[3]),s_tx_cfg_pst->plen);
if(rdlen > 0) { s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_DAT_ACK; if(rdlenplen) { memset(&(buf[3+rdlen]),CTRLZ,s_tx_cfg_pst->plen-rdlen); } crc = crc_nibble_rom(0,&(buf[3]),s_tx_cfg_pst->plen); buf[s_tx_cfg_pst->plen+4] = crc&0xFF; buf[s_tx_cfg_pst->plen+3] = (crc>>8)&0xFF; s_tx_cfg_pst->io_write(buf,s_tx_cfg_pst->plen+5); } else { /* 未读到数据可能是读失败胡总和读完,发EOT */ s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_EOT_NACK; tmp = EOT; s_tx_cfg_pst->io_write(&tmp,1); } s_tx_cfg_pst->state.ms_u32 = t; } } if(tmp != ACK) { /* 未收到'C' */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { res = YMODEM_TX_EER_WAIT_DATA_START_TO; } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_TX_WAIT_DAT_ACK: buf = s_tx_cfg_pst->buffer; if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { /* 读到ACK,继续发送数据 */ if(tmp == ACK) { uint32_t rdlen; uint16_t crc; /* 发送一包数据 */ s_tx_cfg_pst->state.pnum_u8++; buf[0] = (s_tx_cfg_pst->plen == 128) ? SOH : STX; buf[1] = s_tx_cfg_pst->state.pnum_u8; buf[2] = ~buf[1]; rdlen = s_tx_cfg_pst->mem_read(s_tx_cfg_pst->addr,&(buf[3]),s_tx_cfg_pst->plen);
if(rdlen > 0) { if(rdlenplen) { memset(&(buf[3+rdlen]),CTRLZ,s_tx_cfg_pst->plen-rdlen); } crc = crc_nibble_rom(0,&(buf[3]),s_tx_cfg_pst->plen); buf[s_tx_cfg_pst->plen+4] = crc&0xFF; buf[s_tx_cfg_pst->plen+3] = (crc>>8)&0xFF; s_tx_cfg_pst->io_write(buf,s_tx_cfg_pst->plen+5); } else { /* 未读到数据可能是读失败或者读完,发EOT */ s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_EOT_NACK; tmp = EOT; s_tx_cfg_pst->io_write(&tmp,1); } s_tx_cfg_pst->state.ms_u32 = t; } } if(tmp != ACK) { /* 未收到ACK */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { res = YMODEM_TX_EER_WAIT_DATA_ACK_TO; } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_TX_WAIT_EOT_NACK: if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { if(tmp == NAK) { /* 读到NAK 发送EOT继续等待ACK */ s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_EOT_ACK; s_tx_cfg_pst->state.ms_u32 = t; tmp = EOT; s_tx_cfg_pst->io_write(&tmp,1); } } if(tmp != NAK) { /* 未收到NAK */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { /* 超时返回错误 */ res = YMODEM_TX_EER_WAIT_EOT_NACK_TO; } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_TX_WAIT_EOT_ACK: if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { if(tmp == ACK) { /* 读到ACK 等待下一个启动'C' */ s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_P0_START; s_tx_cfg_pst->state.ms_u32 = t; } } if(tmp != ACK) { /* 未收到ACK */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { /* 超时返回错误 */ res = YMODEM_TX_EER_WAIT_EOT_ACK_TO; } else { /* 未超时继续等待*/ } } break; case YMODEM_STATE_TX_WAIT_P0_DONE_ACK: if(0 != s_tx_cfg_pst->io_read(&tmp,1)) { if(tmp == ACK) { /* 读到ACK 正常结束 */ s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_DATA_START; res = YMODEM_TX_ERR_NONE; } } if(tmp != ACK) { /* 未收到ACK */ if((t - s_tx_cfg_pst->state.ms_u32) >= s_tx_cfg_pst->ack_timeout) { /* 超时返回错误 */ res = YMODEM_TX_EER_WAIT_P0_DONE_ACK_TO; } else { /* 未超时继续等待*/ } } break; default: return YMODEM_TX_ERR_UNINIT; break; } return res;}
/** * \fn ymodem_tx_init * 发送初始化 * \param[in] cfg \ref ymodem_tx_cfg_st 配置信息 * \retval 0 成功 * \retval 其他值 失败*/int ymodem_tx_init(ymodem_tx_cfg_st* cfg){#if YMODEM_CHECK_PARAM if(cfg == (ymodem_tx_cfg_st*)0) { return -1; }#endif s_tx_cfg_pst = cfg; s_tx_cfg_pst->state.state_e = YMODEM_STATE_TX_WAIT_P0_START; return 0;}
#endif

ymodem.h

#ifndef YMODEM_H#define YMODEM_H
#ifdef __cplusplusextern "C" {#endif
#include
#define YMODEM_CHECK_PARAM 1
#define YMODEM_RX_ENABLE 1 /**< 配置为1使能RX相关实现, 配置为0不使能 */#define YMODEM_TX_ENABLE 1 /**< 配置为1使能TX相关实现, 配置为0不使能 */
typedef uint32_t (*ymodem_io_read_pf)(uint8_t* buffer, uint32_t len); /**< 通讯接收接口,返回实际读到的长度返回0表示未读到 */typedef uint32_t (*ymodem_io_write_pf)(uint8_t* buffer, uint32_t len); /**< 通讯发送接口 */typedef int (*ymodem_mem_start_pf)(uint32_t addr, uint8_t** name, uint32_t* len); /**< 启动文件传输回调接口,返回非0表示失败 */typedef int (*ymodem_mem_done_pf)(void); /**< 结束文件传输回调接口 */typedef uint32_t (*ymodem_getms_pf)(void); /**< 获取mS时间戳 */
#if YMODEM_RX_ENABLEtypedef uint32_t (*ymodem_mem_write_pf)(uint32_t addr, uint8_t* buffer, uint32_t len); /**< 写存储接口,接收才需要 */
/** * \enum ymodem_rx_state_e * 接收状态枚举*/typedef enum{ YMODEM_STATE_RX_IDLE = 0, /**< 初始状态 */
YMODEM_STATE_RX_WAIT_P0_HEAD = 1, /**< 等待接收P0包的头 */ YMODEM_STATE_RX_WAIT_P0_DATA = 2, /**< 等待接收P0包的数据 */
YMODEM_STATE_RX_WAIT_DATA_HEAD = 3, /**< 等待接收数据包的头 */ YMODEM_STATE_RX_WAIT_DATA_DATA = 4, /**< 等待接收数据包的数据 */
YMODEM_STATE_RX_WAIT_EOT = 5, /**< 等待接收第二个EOT 数据传完发送端发两个EOT,接收端对第一个NAK,对第二个ACK */} ymodem_rx_state_e;
/** * \enum ymodem_rx_err_e * 接收错误枚举*/typedef enum{ YMODEM_RX_ERR_NEED_CONTINUE = 0, /**< 需要继续处理 */ YMODEM_RX_ERR_NONE, /**< 成功结束无错误 */ YMODEM_RX_ERR_UNINIT, /**< 未初始化 */ YMODEM_RX_ERR_WAIT_P0_HEAD_TO, /**< 等待接收P0的包头超时 */ YMODEM_RX_ERR_WAIT_P0_DATA_TO, /**< 等待接收P0包的数据超时 */ YMODEM_RX_ERR_WAIT_DATA_HEAD_TO, /**< 等待接收数据包的头超时 */ YMODEM_RX_ERR_WAIT_DATA_DATA_TO, /**< 等待接收数据包的数据超时 */ YMODEM_RX_ERR_WAIT_EOT_TO, /**< 等待ID第二个EOT超时 */
YMODEM_RX_ERR_CAN, /**< 发送方取消 */ YMODEM_RX_ERR_START, /**< 启动文件传输回调接口返回失败 */} ymodem_rx_err_e;
/** * \struct ymodem_rx_state_st * 接收状态机结构体*/typedef struct{ ymodem_rx_state_e state_e; /**< 状态机的状态 */ uint32_t getlen_u32; /**< 本包接收到的数据个数 */ uint8_t pnum_u8; /**< 上一包的包ID */ uint32_t ms_u32; /**< 上一个状态mS时间戳 */ } ymodem_rx_state_st;
/** * \struct ymodem_rx_cfg_st * 接收参数配置结构体 * * 设计思想是模块本身除非必要,尽量不使用全局(静态)变量,这样模块不使用时不占用对应的RAM资源. * 需要的资源全部由调用者提供,即本结构体提供. * */typedef struct{ /* 以下需要用实例提供 */ ymodem_io_read_pf io_read; /**< 通讯接收接口 */ ymodem_io_read_pf io_write; /**< 通讯发送接口 */ ymodem_mem_start_pf mem_start; /**< 启动回调接口 */ ymodem_mem_done_pf mem_done; /**< 停止回调接口 */ ymodem_mem_write_pf mem_write; /**< 写存储接口 */ ymodem_getms_pf getms; /**< 获取mS时间戳接口 */ uint32_t start_timeout; /**< 等待启动超时次数,单位每次超时为ack_timeout */ uint32_t packet_timeout; /**< 等待数据包超时时间mS */ uint32_t ack_timeout; /**< 等待响应(头)超时时间mS*/ uint8_t* buffer; /**< 包缓冲区,用户提供,1024字节包需要1024+5 */ uint32_t addr; /**< 存储地址 */ uint32_t totallen; /**< 传输长度 */ uint32_t xferlen; /**< 已经传输长度 */
/* 以下调用rx_init接口初始化 */ ymodem_rx_state_st state; /**< 状态机 */
/* 以下内部使用无需初始化 */ uint16_t plen; /**< 包长:128或1024 */ uint8_t is_start; /**< 是否已经启动标志,第一次启动时等待start_timeout次超时时间 */} ymodem_rx_cfg_st;
/** * \fn ymodem_rx * 接收处理, 在此之前必须要先调用ymodem_rx_init初始化. * 返回YMODEM_RX_ERR_NEED_CONTINUE则需要继续重复调用,直到返回其他值. * \return \ref ymodem_rx_err_e*/ymodem_rx_err_e ymodem_rx(void);
/** * \fn ymodem_rx_init * 接收初始化 * \param[in] cfg \ref ymodem_rx_cfg_st 配置信息 * \retval 0 成功 * \retval 其他值 失败*/int ymodem_rx_init(ymodem_rx_cfg_st* cfg);
#endif
#if YMODEM_TX_ENABLE
/** * \enum ymodem_tx_state_e * 发送状态枚举*/typedef enum{ YMODEM_STATE_TX_IDLE = 0, /**< 初始状态 */
YMODEM_STATE_TX_WAIT_P0_START = 1, /**< 等待发送P0之前的'C' */ YMODEM_STATE_TX_WAIT_P0_ACK = 2, /**< 等待发送P0之后的ACK */ YMODEM_STATE_TX_WAIT_DATA_START = 3, /**< 等待发送DATA之前的'C' */ YMODEM_STATE_TX_WAIT_DAT_ACK = 4, /**< 等待发送数据之后的ACK */ YMODEM_STATE_TX_WAIT_EOT_NACK = 5, /**< 等待发送EOT之后的NACK */ YMODEM_STATE_TX_WAIT_EOT_ACK = 6, /**< 等待发送EOT之后的ACK */
YMODEM_STATE_TX_WAIT_P0_DONE_ACK = 7, /**< 等待发送结束P0之后的ACK */
} ymodem_tx_state_e;
/** * \enum ymodem_tx_err_e * 发送错误枚举*/typedef enum{ YMODEM_TX_ERR_NEED_CONTINUE = 0, /**< 需要继续处理 */ YMODEM_TX_ERR_NONE, /**< 成功结束无错误 */ YMODEM_TX_ERR_UNINIT, /**< 未初始化 */
YMODEM_TX_EER_WAIT_P0_START_TO, YMODEM_TX_EER_WAIT_P0_ACK_TO, YMODEM_TX_EER_WAIT_DATA_START_TO, YMODEM_TX_EER_WAIT_DATA_ACK_TO, YMODEM_TX_EER_WAIT_EOT_NACK_TO, YMODEM_TX_EER_WAIT_EOT_ACK_TO, YMODEM_TX_EER_WAIT_P0_DONE_ACK_TO,
YMODEM_TX_ERR_CAN, /**< 接收方取消 */ YMODEM_TX_ERR_START, /**< 启动文件传输回调接口返回失败 */} ymodem_tx_err_e;
/** * \struct ymodem_tx_state_st * 发送状态机结构体*/typedef struct{ ymodem_tx_state_e state_e; /**< 状态机的状态 */ uint8_t pnum_u8; /**< 上一包的包ID */ uint32_t ms_u32; /**< 上一个状态mS时间戳 */ } ymodem_tx_state_st;
/** * \struct ymodem_tx_cfg_st * 发送参数配置结构体 * * 设计思想是模块本身除非必要,尽量不使用全局(静态)变量,这样模块不使用时不占用对应的RAM资源. * 需要的资源全部由调用者提供,即本结构体提供. * */typedef struct{ /* 以下需要用实例提供 */ ymodem_io_read_pf io_read; /**< 通讯接收接口 */ ymodem_io_read_pf io_write; /**< 通讯发送接口 */ ymodem_mem_start_pf mem_start; /**< 启动回调接口 */ ymodem_mem_done_pf mem_done; /**< 停止回调接口 */ ymodem_mem_write_pf mem_read; /**< 读存储接口 */ ymodem_getms_pf getms; /**< 获取mS时间戳接口 */ uint32_t start_timeout; /**< 等待启动超时次数,单位每次超时为ack_timeout */ uint32_t packet_timeout; /**< 等待数据包超时时间mS */ uint32_t ack_timeout; /**< 等待响应(头)超时时间mS*/ uint8_t* buffer; /**< 包缓冲区,用户提供,1024字节包需要1024+5 */ uint32_t addr; /**< 存储地址 */ uint16_t plen; /**< 包长:128或1024 */ /* 以下调用rx_init接口初始化 */ ymodem_tx_state_st state; /**< 状态机 */
/* 以下内部使用无需初始化 */ uint8_t is_start; /**< 是否已经启动标志,第一次启动时等待start_timeout次超时时间 */} ymodem_tx_cfg_st;

typedef uint32_t (*ymodem_mem_read_pf)(uint32_t addr, uint8_t* buffer, uint32_t len); /**< 读存储接口,发送才需要 */
/** * \fn ymodem_tx * 接收处理, 在此之前必须要先调用ymodem_tx_init初始化. * 返回YMODEM_TX_ERR_NEED_CONTINUE则需要继续重复调用,直到返回其他值. * \return \ref ymodem_tx_err_e*/ymodem_tx_err_e ymodem_tx(void);
/** * \fn ymodem_tx_init * 发送初始化 * \param[in] cfg \ref ymodem_tx_cfg_st 配置信息 * \retval 0 成功 * \retval 其他值 失败*/int ymodem_tx_init(ymodem_tx_cfg_st* cfg);
#endif

#ifdef __cplusplus}#endif
#endif

四. 测试

测试以SecureCRT进行,有一些需要注意的地方

1.结束0 ID包总以128包长格式进行

2.最后数据不足1024,会拆分成128包传输,比如14113长的文件,前面按照1024字节传输,最后余801字节,会转为7128长包,最后的一个128包会填充,如下所示

3.0 ID结束包总是以128包传输。

4.第一个文件以1024包传输,后面的文件会以128包长传输,导致速率下降,如下所示

115200波特率串口第一个文件1024包传输可以稳定达到9kB/s,后面128包传输只能达到4kB/S

Shell实现参考《https://mp.weixin.qq.com/s/XLmbJn0SKoDT1aLdxHDrbg 一个超级精简高可移植的shell命令行C实现》

测试发送

uint8_t sfile_name[2][64];
static int tx_file_open_flag = 0;static uint32_t tx_file_num = 0;
static void syfilefunc(uint8_t* param);  { (uint8_t*)"syfile",       syfilefunc,       (uint8_t*)"syfile"},
void syfilefunc(uint8_t* param){  (void)param;  int plen = 0;  int res;  int num = 0;  ymodem_tx_cfg_st cfg=  {    .buffer = rxtx_buf,    .getms = getms,    .io_read = io_read,    .io_write = io_write,    .start_timeout = 60,    .packet_timeout = 2000,    .ack_timeout = 2000,    .plen = 1024,    .mem_start = tx_file_start,    .mem_read = tx_file_read,    .mem_done = tx_file_done,  };
  memset(sfile_name,0,sizeof(sfile_name));  tx_file_num = 0;  num = sscanf((const char*)param, "%*s %d %s %s", &plen, sfile_name[0],sfile_name[1]);  if((num == 2) || (num == 3))  {    cfg.plen = plen;    ymodem_tx_init(&cfg);    while((res = ymodem_tx()) == YMODEM_TX_ERR_NEED_CONTINUE);
    xprintf("\r\nres:%d\r\n",res);  }}

static uint32_t getms(void){  return os_boot_time32();}

static uint32_t io_read(uint8_t* buffer, uint32_t len){  return fifo_cdc_rx_read(buffer, len);}
static uint32_t io_write(uint8_t* buffer, uint32_t len){  fifo_cdc_tx_write(buffer, len);  return len;}
static uint8_t sfile_name_len_att[128];/* 获取文件名 */static int  tx_file_start(uint32_t addr, uint8_t** name, uint32_t* len){  (void)addr;  (void)len;  uint32_t flen;  uint32_t fnamelen;  int res = 0;  if(tx_file_open_flag != 0)  {     lfs_file_close(&lfs, &tx_file);    tx_file_open_flag = 0;  }  if(tx_file_num >= sizeof(sfile_name)/sizeof(sfile_name[0]))  {    return -1;  }
  if(0 == (res = lfs_file_open(&lfs, &tx_file, (const char*)sfile_name[tx_file_num], LFS_O_RDONLY)))  {    flen =  lfs_file_size(&lfs, &tx_file);    fnamelen = strlen((const char*)sfile_name[tx_file_num]);    memcpy(sfile_name_len_att,sfile_name[tx_file_num],fnamelen);    sfile_name_len_att[fnamelen]=0;    *len = fnamelen + 1 + snprintf((char*)(&sfile_name_len_att[fnamelen+1]),sizeof(sfile_name_len_att)-(fnamelen+1),"%ld %o %d",flen,1715670058,0);    *name = sfile_name_len_att;    tx_file_open_flag = 1;
  }  tx_file_num++;  return res;}

static uint32_t tx_file_read(uint32_t addr, uint8_t* buffer, uint32_t len){  (void)addr;  return lfs_file_read(&lfs, &tx_file, buffer, len);}
static int  tx_file_done(void){  if(0 != tx_file_open_flag)  {    tx_file_open_flag = 0;    return lfs_file_close(&lfs, &tx_file);  }  return 0;}

添加以上命令行实现,测试发送两个文件

测试接收

static void ryfilefunc(uint8_t* param);
  { (uint8_t*)"ryfile",       ryfilefunc,       (uint8_t*)"ryfile"},

void ryfilefunc(uint8_t* param){  (void)param;  int res = 0;
  ymodem_rx_cfg_st cfg=  {    .buffer = rxtx_buf,    .getms = getms,    .io_read = io_read,    .io_write = io_write,    .start_timeout = 60,    .packet_timeout = 2000,    .ack_timeout = 1000,    .mem_start = rx_file_start,    .mem_write = rx_file_write,    .mem_done = rx_file_done,  };
  ymodem_rx_init(&cfg);  while((res = ymodem_rx()) == YMODEM_RX_ERR_NEED_CONTINUE);
  xprintf("\r\nres:%d\r\n",res);}

static int  rx_file_start(uint32_t addr, uint8_t** name, uint32_t* len){  (void)addr;  (void)len;  int res = 0;  if(rx_file_open_flag == 0)  {    if(0 == (res = lfs_file_open(&lfs, &rx_file, (const char*)(*name), LFS_O_RDWR | LFS_O_CREAT)))    {      rx_file_open_flag = 1;    }  }
  return res;}
static uint32_t  rx_file_write(uint32_t addr, uint8_t* buffer, uint32_t len){  (void)addr;  return lfs_file_write(&lfs, &rx_file, buffer, len);}
static int  rx_file_done(void){  if(0 != rx_file_open_flag)  {    rx_file_open_flag = 0;    return lfs_file_close(&lfs, &rx_file);  }  return 0;}

实现以上命令行,测试接收两个文件

.总结

XMODEMYMODEM本身没有标准化,只有前言部分贴出的设计者写的一个文档xymodem.pdf,但是描述太罗嗦,逻辑性不强,介绍的也不是很详细,并且对于异常处理等也规范不细。所以不同的实现或扩展导致了一些差异,使用中最好是配合上位机工具进行调试,比如我们使用SecureCRT进行测试。

XMODEMYMODEM本身设计上不能保证可靠性,因为使用了单字节包,单字节包没法确认其正确性,只有带头和CRC的包才能确认其正确性(依赖CRC的检错能力)。所以XMODEMYMODEM只适合于简单场景,可靠性要求高,容易丢数据,误码,通讯不稳定等场景不适用(否则需要考虑复杂的重发,错误检测等机制,太麻烦)。

我们上述实现基于状态机实现,非阻塞,只考虑了正常传输的情况,也没有进行过多异常情况的处理,因为其本身设计就不可靠,异常处理无详细规范等,所以过多考虑本身也没太大意义。

代码仅作演示,不保证可靠性和充分测试,如果移植可能,需要实际进行调试修改。



评论
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-12 10:13 31浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 109浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 110浏览
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 82浏览
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 68浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 83浏览
  • 全球智能电视时代来临这年头若是消费者想随意地从各个通路中选购电视时,不难发现目前市场上的产品都已是具有智能联网功能的智能电视了,可以宣告智能电视的普及时代已到临!Google从2021年开始大力推广Google TV(即原Android TV的升级版),其他各大品牌商也都跟进推出搭载Google TV操作系统的机种,除了Google TV外,LG、Samsung、Panasonic等大厂牌也开发出自家的智能电视平台,可以看出各家业者都一致地看好这块大饼。智能电视的Wi-Fi连线怎么消失了?智能电
    百佳泰测试实验室 2024-12-12 17:33 45浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 86浏览
  • 本文介绍瑞芯微RK3588主板/开发板Android12系统下,APK签名文件生成方法。触觉智能EVB3588开发板演示,搭载了瑞芯微RK3588芯片,该开发板是核心板加底板设计,音视频接口、通信接口等各类接口一应俱全,可帮助企业提高产品开发效率,缩短上市时间,降低成本和设计风险。工具准备下载Keytool-ImportKeyPair工具在源码:build/target/product/security/系统初始签名文件目录中,将以下三个文件拷贝出来:platform.pem;platform.
    Industio_触觉智能 2024-12-12 10:27 43浏览
  • 应用环境与极具挑战性的测试需求在服务器制造领域里,系统整合测试(System Integration Test;SIT)是确保产品质量和性能的关键步骤。随着服务器系统的复杂性不断提升,包括:多种硬件组件、操作系统、虚拟化平台以及各种应用程序和服务的整合,服务器制造商面临着更有挑战性的测试需求。这些挑战主要体现在以下五个方面:1. 硬件和软件的高度整合:现代服务器通常包括多个处理器、内存模块、储存设备和网络接口。这些硬件组件必须与操作系统及应用软件无缝整合。SIT测试可以帮助制造商确保这些不同组件
    百佳泰测试实验室 2024-12-12 17:45 39浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 74浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦