众所周知串口收/发数据是以字节为单位的位传输通信协议。
当串口接收数据按固定数据长度接收;则可能会由于传输过程中出现丢数据,发送端少发数据或多发数据导致接收错位无法正确获取数据。
为了解决数据接收错位,我们在接收数据时以串口通信最小单位一个字节为单位接收数据并插如到环形队列中;取数据时进入环形队列查询,若符合接收数据则取数据,否则退出查询。接收插入数据和查询取数互为独立,相互不影响。
具体操作详解如下:
1、创建环形队列:
创建环形队列,如图1;内容包含一个头部指针head,用于出队后移动位置指针;一个尾部指针tail,用与入队后移动位置指针;一个数组ringBuf用于存放数据;
环形队列存数据以最简单的数组实现。
队列入队,通过尾指针tail插入数据并将指针tail加一,然后判断是否到数组尾部或追上头部指针head,若到尾部,则将tail指向数组头部(即,tail= 0),形成环。若追上头部指针head,则丢弃早期数据,将头部指针head加一。
队列出队,通过头部指针head找帧头,没找到指针head加一继续找,若找帧头,则继续取完余部数据,然后再判定数据完整性,同理取数据时先判断头部指针head是否追上尾部指针tail,若追上(即head == tail)则数据据为空,直接退出查询,每取一个数据则头部指针head加一并判是否到数组尾部,若到尾部则指向数组头部(即,head=0),形成环。
若出现丢帧,请将存储数组ringBuf设大些。
1.1、环形队列结构体定义如下:
// 一帧数据长度(7字节)
#define FRAME_LEN (7)
// 缓冲区大小 [单帧数据长8字节,队列深度为1000帧]
#define BUFFER_MAX (FRAME_LEN * 1000)
//定义一个结构体
typedef struct{
unsigned char head; // 缓冲区头部位置
unsigned char tail; // 缓冲区尾部位置
unsigned char ringBuf[BUFFER_MAX]; // 缓冲区数组
}ringBuffer_t;
1.2、环形队列创建及初始化
// 创建全局环形队列缓冲区
static RingQueue_t RingQ;
void RingQueue_init()
{
RingQ.head = 0;
RingQ.tail = 0;
memset(RingQ.tingBuf, 0, BUFFER_MAX);
}
1.3、入队列
void RingQueue_Push(unsigned char data)
{
// 将数据写入队列(从尾部追加数据)
RingQ.ringBuf[RingQ.tail] = data & 0xFF;
// 偏移尾指针,并判断是否到数组尾部
if( ++RingQ.tail >= BUFFER_MAX)
{
//大于数组最大长度,则尾指针归零
RingQ.tail = 0;
}
// 如果尾部节点追到头部节点,则修改头部节点偏移位置丢弃早期数据
if(RingQ.tail == RingQ.head)
{
// 偏移头指针,并判断是否到数组尾部
if( ++RingQ.head >= BUFFER_MAX)
{
// 大于数组最大长度,则头指针归零
RingQ.head = 0;
}
}
}
1.4、出队列
BOOL RingQueue_Poll(unsigned char * pData)
{
// 如果头指针和尾指针相等,则表示缓冲区为空
if(RingQ.tail == RingQ.head)
{
return FALSE;
}
else
{
// 如果缓冲区非空,则从头部取数据并偏移头节点指针
*pData = RingQ.ringBuf[RingQ.head];
// 偏移头指针,并判断是否到数组尾部
if(++RingQ.head >= BUFFER_MAX)
{
// 大于数组最大长度,则头指针归零
RingQ.head = 0;
}
}
return TRUE;
}
2、SYS/BIOS,下串口配置
串口配置初始化只给步骤,具体初始化实现请查阅相关芯片数据手册编写实现。
2.1、串口初始化
void UART_Init(void)
{
// 配置 UART2 参数
// 波特率 115200 数据位 8 停止位 1 无校验位
UARTConfigSetExpClk(0x01D0C000, BAUD115200);
// 设置FIFO级别
// 接收FIFO触发电平为1Byte,清空发送/接收FIFO,FIFO使能
UARTFIFOLevelSet(0x01D0C000, 0x0000000F);
// 使能UART和接收中断,禁用发送中断
UARTIntEnable(0x01D0C000, (0x00000004 | 0x00000001 | 0));
}
2.2、注册串口中断
/*
* ======== Main Entry Point ========
*/
int main()
{
Hwi_Handle hwi;
Hwi_Params kwiuart;
Error_Block eb;
Delay(100000);//100ms
RingQueue_init(); //环形缓冲区初始化
UART_Init ();//UART初始化
Delay(100000);//100ms
// 动态创建硬件中断
Error_init(&eb);
Hwi_Params_init(&kwiuart);
kwiuart.eventId = 46; //uart1:46; uart2:69;
kwiuart.arg = 1;
kwiuart.maskSetting = Hwi_MaskingOption_SELF;
kwiuart.enableInt = TRUE;
hwi = Hwi_create(C674X_MASK_INT7, UART_Isr, &kwiuart, &eb);
if (hwi == NULL)
System_abort("Hwi1 create failed");
/* Start the BIOS 6 Scheduler */
BIOS_start ();
}
2.3、中断服务函数
char txArray[] = "UART Application......\n\r";
void UART_Isr(UArg Arg)
{
static unsigned int length = sizeof(txArray);
static unsigned int count = 0;
unsigned char rxData = 0;
unsigned int int_id = 0;
// 确定中断源
int_id = UARTIntStatus(0x01D0C000);
// 清除 UART2 系统中断
IntEventClear(46);
// 发送中断
if(0x00000001 == int_id)
{
if(0 < length)
{
// 写一个字节到 THR
UARTCharPutNonBlocking(0x01D0C000, txArray[count]);
length--;
count++;
}
if(0 == length)
{
// 禁用发送中断
UARTIntDisable(0x01D0C000, 00000002);
}
}
// 接收中断
if(0x00000002 == int_id)
{
rxData = UARTCharGetNonBlocking(0x01D0C000);
UARTCharPutNonBlocking(0x01D0C000, rxData);
//将数据写到环形队列中去
RingQueue_Push(rxData);
}
// 接收错误
if(0x00000003 == int_id)
{
while(UARTRxErrorGet(0x01D0C000))
{
// 从 RBR 读一个字节
UARTCharGetNonBlocking(0x01D0C000);
}
}
return;
}
2.4、从队列查询取数据帧
2.4.1、数据帧定义
typedef struct{
unsigned char headH; // 帧头高位
unsigned char headL; // 帧头低位
unsigned char data1; // 数据1
unsigned char data2; // 数据2
unsigned char data3; // 数据3
unsigned char SumVer; // 和校验(取低八位有效)
unsigned char End; // 帧尾
}UART_DATA_t;
2.4.2、从队列查询读取一帧数据
BOOL Read_One_Frame_Data(unsigned char * pData, unsigned int len)
{
int i=0;
unsigned int CRC = 0;
unsigned char RxData[7] = {0};
//============= 查找帧头 ===============
do{
// 找帧头高字节
if(FALSE == RingQueue_Poll(&RxData[0])) // 取帧头高字节
{
return FALSE;
}
if(0xAA == RxData[0] )//找到高字节再找低字节
{
if(FALSE == RingQueue_Poll(&RxData[1])) // 取帧头低字节
{
return FALSE;
}
}
}while(0xAA != RxData[0] && 0x55 != RxData[1] ); // 判断帧头高字节
//============= 提取数据 ===============
for(i=2; i< len; i++)
{
if(FALSE == RingQueue_Poll( &RxData[i]) )
{
return FALSE;
}
}
//============= 校验帧尾 ===============
if(0xEE != RxData[len-1])
{
return FALSE; //判断帧的完整性,若不完整直接退出。
}
//已经获取到完整的一帧数据
//============= 计算校验 ===============
for(i=0; i< len-2; i++)
{
CRC = (CRC + RxData[i]) & 0xFF;
}
//============= 拷贝数据 ===============
if(0xAA == RxData[0] && 0x55 == RxData[1] && CRC == RxData[len-2] && 0xEE == RxData[len-1])
{
memcpy(&pData[0], &RxData[0] , len); //正确获取数据
return TRUE;
}
else
{
return FALSE; //获取数据错误
}
}
2.4.3、任务线程读取数据
void RingQueue_read_Task()
{
int state = 0;
UART_DATA_t rData;
// 3、读环形队列
state = Read_One_Frame_Data(&rData, 7);
if(FALSE != state)
{
printf("read data: %02X %02X %02X %02X %02X %02X %02X %02X \n", \
rData.headH, rData.headL, rData.data1, rData.data2, rData.data3, rData.SumVer, rData.End);
}
}