今天讲一下串口通信,这个也是嵌入式中非常常用的,讲到通信那么就一定有通信协议,这样在通信的双方才能够互相识别对方发送的数据,通信协议呢就是双方或多方约定好数据传送的格式,然后按照指定的格式打包和解析,再接收或者传送处理后的数据就行了。
既然是约定的,那么格式就没限制了,只要双方能够按照这种协议进行通信就行了,很多时候我们都是使用自定义协议来完成通信,这里以一种简单的AT指令的形式来讲一下串口如何接收一帧完整的数据。
指令格式:”AT+CMD=XXXX\r\n”
解析方式(仅供参考,使用的正则表达式):
//传送内容为无符号整型sscanf(aRxBuf,"%*[^=]=%u\r\n", &tmpVal);
//传送内容为字符串(若正文含有’\r’或’\n’时存在缺陷,会导致解析内容丢失,可使用strstr();函数方案来完成解析)sscanf(aRxBuf, "%*[^=]=%[^\r\n]",tmpBuf);
这种协议有固定的头和尾,其数据的长度是不定的,我们只能通过头和尾来进行解析,还是以STM32单片机为例,但是解析的方法是通用的,STM32单片机串口每产生一次接收完成中断,便收到了一个字节的数据(仅限单字节接收的方式),我们就可以通过匹配的方式来完成帧数据的接收,其代码如下:
/*接收缓冲区长度*/#define RX_BUF_LENGTH 250U
/*接收协议公共变量*/typedef struct { volatile uint8_t step; /*switch 语句跳转条件*/ volatile uint32_t aRxBufIndex; /*接收数据缓冲区索引*/ uint8_t aRxBuf[RX_BUF_LENGTH];}_ptcCom_t;
_ptcCom_t ch_usart1;
/** @brief 串口数据帧解析* @param p:帧接收控制* c:串口产生接收中断后收到的字节数据* @retval none*/static void HAL_USART1_RxCpltCallback(_ptcCom_t *p,const uint8_t c){ switch(p->step) { case 0: p->aRxBufIndex = 0; if(c == 'A') { p->step++; p->aRxBuf[p->aRxBufIndex++] = c; } break; case 1: if(c == 'T') { p->step++; p->aRxBuf[p->aRxBufIndex++] = c; } else p->step = 0; break; case 2: p->aRxBuf[p->aRxBufIndex++] = c; //if index already in tail if(p->aRxBufIndex >= (sizeof(p->aRxBuf) / sizeof(*(p->aRxBuf)))) { /*如果接收缓冲区马上溢出了,但是还未收到数据的尾,可能数据传输过程中出错或丢失,则认为此数据无效,将其丢弃*/ if(!((p->aRxBuf[p->aRxBufIndex - 1] == '\n') && (p->aRxBuf[p->aRxBufIndex - 2] == '\r'))) { //overflow p->step = 0; break; } } /*如果收到了"\r\n",则认为收到一帧完整数据*/ else if(!((p->aRxBuf[p->aRxBufIndex - 1] == '\n') && (p->aRxBuf[p->aRxBufIndex - 2] == '\r'))) { break; }
#if(OPT_DBG_INF > 0U) p->aRxBuf[p->aRxBufIndex] = '\0'; PRINTF("usart1:%d,%s\n",p->aRxBufIndex,p->aRxBuf); #endif /* 这里可将数据存入FIFO,或者直接使用; do something */ //NOTICE:no 'break' here; default: p->step = 0; break; }}
这种方式最终得到的一帧完整的数据存放在相应的aRxBuf中,我们可以创建一个FIFO,将这帧数据存放到FIFO中,或者直接使用这帧数据都是可以的,至此一帧完整的数据就收到了,如果你的通信协议是16进制传输的,此方法同样适用。
扩展:对于STM32单片机来说,可以利用DMA的空闲中断来接收一帧完整的数据,在以后的文章中,可能会讲解此种方法。
后台回复“串口数据帧”五个字可获取本文源码。
评论区
登录后即可参与讨论
立即登录