浅谈FPGA串口通信数据解析的两种方式
1赞前面博文中提到过,FPGA串口通信要想应用在实现的工业现场,需要一整套完整的协议,来确保数据传输的可靠性和系统的稳定性。基于协议,进行串口指令解析是控制的关键,对于串口指令解析,有两种方式:逻辑解析和软硬核(我用的Altera的嵌入式软核NIOS)解析。
使用逻辑进行解析,往往使用逻辑进行数据收发,此处可参看小梅哥《FPGA数字系统设计教程》,其核心在于数据接收部分的设计,也即是数据帧接收状态机的设计。状态机大致包括以下状态:帧头、字节数、数据、帧尾。默认情况下,状态机处于帧头状态,如果检测到帧头,则进入下一状态,否则,状态机仍然处于帧头,依此循环,直至检测完一个数据帧,此时产生一个高信号,同时将检测到的数据帧锁存,用于解析指令状态,同时,开始下一数据帧的接收。
使用Nios进行解析,通常使用Altera自带的Uart核来完成数据收发,此部分请参看我的博文《基于nios的串口通信uart设计》,地址如下:http://blog.chinaaet.com/helimin/p/5100018309
具体协议参看我的博文《FPGA串口通信及数据解析》,地址如下:
http://blog.chinaaet.com/helimin/p/5100051178
使用中断的方式,每接收到1个字节,进行一次中断。接收到数据后,进行解析,思路如下:
(1) 定义一个指令缓存数组recBuf,每次进入中断,就将接收到的1字节数据存至数组,同时数组尾部索引recTailPointor自加1。
void uart0_isr(void * context,alt_u32 id) { rxdata0 = IORD_ALTERA_AVALON_UART_RXDATA(UART_0_BASE); recBuf[recTailPointor] = rxdata0;//获取数据 recTailPointor += 1; }
(2) 进入串口解析函数,在里面进行如下操作:调用当前字节数计算函数,计算放入数组中的字节数
inline alt_u8 getRecLen(void) { alt_u8 lureclen = 0; if(recHeadPointor <= recTailPointor){ lureclen = recTailPointor - recHeadPointor; } else{ //如果recHeadPointor > recTailPointor,说明recBuf[256]已经写到最后,又从头重写。 lureclen = 256 - (recHeadPointor - recTailPointor); } return lureclen; }
(3) 在串口解析函数判断放入数组中字节数是否大于0,如果大于0则判断数组中第一个元素是否是帧头,如果是帧头,再根据第二个元素(字节素)来进行数据接收,直到接收完一帧数据。此时将接收数据索引的头指针移至尾部。
alt_u8 UartAnalyze(void) { alt_u8 i = 0; alt_u8 luRecLen = 0; alt_u8 lutmp = 0; luRecLen = getRecLen(); //判断放入串口数据接收数组中的数据字节数 if(luRecLen > 0){ if(recBuf[recHeadPointor] == 0xcc){ while(1){ //一直等到接收完一帧指令跳出此循环 if(uartOverTimeFlag == 0){ luRecLen = getRecLen(); if(luRecLen >= 1){ if(luRecLen > (recBuf[recHeadPointor+1] + 3)){ break;//如果接收完一帧,则跳出循环,继续向下执行 }//if(luRecLen > (recBuf[recHeadPointor+1] + 3)) }//if(luRecLen >= 1) } else{//if(uartOverTimeFlag == 0) recHeadPointor = recTailPointor;//头移到尾处。 recTime = 0; uartOverTimeFlag = 0; return ERROR_OVERTIME; } }//while(1) //一帧数据长度足够则将数据转到uartCMDBuf[]中。 for(i=0;i<=luRecLen;i++){ lutmp = recHeadPointor + i; uartCMDBuf[i] = recBuf[lutmp]; } recHeadPointor = recTailPointor;//头移到尾处。 recTime = 0; uartOverTimeFlag = 0; if(Get_DRC(uartCMDBuf,uartCMDBuf[1]) == uartCMDBuf[uartCMDBuf[1]+1]){ return CMD_CORRECT; } else{//if(Get_DRC(uartCMDBuf,uartCMDBuf[1]) == uartCMDBuf[uartCMDBuf[1]+1]) return ERROR_DRC_NO_PASS; } } else{//if(recBuf[recHeadPointor] == 0xcc)&& if(recBuf[recHeadPointor] == 0xf0) recHeadPointor = recTailPointor;//头移到尾处。 recTime = 0; uartOverTimeFlag = 0; return ERROR_SHAKE_HANDS; } } else{//luRecLen=0说明没有串口数据。 recHeadPointor = recTailPointor;//头移到尾处。 recTime = 0; uartOverTimeFlag = 0; return 0; } }
在数据接收时,为了使得系统更稳定,使用定时器进行超时判断,如若一段时间指令都没有接收完毕,则放弃此次接收。
一帧数据接收完毕后,进行CRC循环校验、帧尾检测,如果都ok,则将接收数组中的一帧数据转移,用于数据的解析,同时,开始下一帧数据的接收。
当时写这个串口解析部分的程序,得到高手的指点,并且花了两个多星期,其实基本思路比较简单,使用中断进行数据接收,在解析函数中根据协议进行接收,接收完后再根据协议进行解析。
两种方式中,对于简单的工程应用,可以使用方式一;而对于比较复杂的协议,有时涉及到上百种控制,并且不同的数据包字节数可能不一样时,使用方式二会显得非常适合。
重点是协议,制订一份好的协议非常非常重要。