lemonHe

主要关注FPGA信号处理和数字图像处理技术,欢迎交流 邮箱:heliminlemon@163.com

浅谈FPGA串口通信数据解析的两种方式

1
阅读(3151)

前面博文中提到过,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,则将接收数组中的一帧数据转移,用于数据的解析,同时,开始下一帧数据的接收。

当时写这个串口解析部分的程序,得到高手的指点,并且花了两个多星期,其实基本思路比较简单,使用中断进行数据接收,在解析函数中根据协议进行接收,接收完后再根据协议进行解析。

两种方式中,对于简单的工程应用,可以使用方式一;而对于比较复杂的协议,有时涉及到上百种控制,并且不同的数据包字节数可能不一样时,使用方式二会显得非常适合。

重点是协议,制订一份好的协议非常非常重要。