串口是MCU最常用的外设资源之一是市场上许多传感器或模块的控制方法都使用串口,这必须面临一个非常严重的问题:如果合理使用串口,可以尽可能介绍数据丢失和异常分析。
串口数据分析的方法有很多。最简单的方法是定义数组,然后将串口接收到的数据填充到数组中,然后进行分析,如下:
void USART3_IRQHandler(void) //串口3中断服务程序 { u8 Res=0; Res = USART_ReceiveData(USART3); Uart3_Buf[First_Int] = Res; ////将接收到的字符串存储在缓存中 First_Int ; ////缓存指针向后移动 if(First_Int >= Buf3_Max) //如果缓存满了,缓存指针指向缓存的第一个地址 { First_Int = 0; } } ///找字符串 //返回:1 已找到 0 未找到 u8 Find(char *a) { if(strstr(Uart3_Buf, a)!=NULL) { return 1; } else { return 0; } } //发送AT指令 //*b:字符串需要发送 //*a:找出是否返回的字符串 //wait_time:发送的次数 //interval_time:每次等待时间 u8 UART3_Send_AT_Command(char *b,char *a,u8 wait_time,u32 interval_time) { u8 i; i = 0; while(i < wait_time) //如果找不到 继续再次发出指令 然后找到目标字符串 { UART3_Send_Command(b);//串口2发送 b 字符串 他会自动发送\r\n 相当于发送指令 delay_ms(interval_time); //等一段时间 传50的话就是 50*20ms = 1秒 if(Find(a)) ///找到需要回答的字符串 a { return 1; } i ; } return 0; } /* 检测GSM能否正常使用? */ int check_status(void) { int ret; ret = UART3_Send_AT_Command("AT","OK",3.50)//测试通信是否成功 if(ret == 0) { return -1; } ret = UART3_Send_AT_Command("AT CPIN?","READY",3.50);//是否插入查询卡? if(ret == 0) { return -2; } ret = UART3_Send_AT_Command("AT CREG?","0,1",3,50);查询卡是否注册到网 if(ret == 0) { return -3; } return 1; } /*************************************************************** 发送短信 注:当然,您可以返回其他值,来确定到底是哪一步发送指令出现失败了。 ****************************************************************/ int send_text_message(char *content,char *phone_num) { u8 ret; char end_char[2]; char String_Phone[21]; end_char[0] = 0x1A;//结束字符 end_char[1] = '\0'; sprintf(String_Phone,"AT CMGS=\"%s\"",phone_num); ///字符串拼接函数(库函数) ret = UART3_Send_AT_Command("AT CMGF=1","OK",3,50);//配置为TEXT模式 if(ret == 0) { return -5; } ret = UART3_Send_AT_Command(String_Phone,">",3,50);///输入收信人的电话号码 if(ret == 0) { return -7; } UART3_SendString(content); //发送提示内容 ret = UART3_Send_AT_Command_End(end_char,"OK",3,250);//发送结束符,等待返回ok,等待5S发送一次,因为短信发送成功需要很长时间 if(ret == 0) { return -8; } return 1; }
上述代码段取自GPRS模块的AT显然,类似于此,定义一个相对较大的数据并发送指令串口分析AT在这种情况下,等待回复和分析的方法就足够了,但对大多数人来说,MCU特别是嵌入式MCU,一般资源非常有限。一段时间前,我收到了一个与我们合作的项目MCU工程师用的MCU RAM总共有128个字节,堆栈只分配了16个字节,已经到了谨慎使用一个字节和一个字节的地步。此时,如果定义一个相对较大的数组,显然是不可取的。一段时间前,我收到了一个与我们合作的项目MCU工程师使用的MCU RAM总共有128个字节,堆栈只分配了16个字节,已经到了仔细使用一个字节和一个字节的地步。此时,定义一个相对较大的数组显然是不可取的。在定义一个小数组时,如何确保数据尽可能不丢失(当然,这是相对的)?数据结构中的内容-队列需要在这里拉。在之前的博文中提到了队列的学习和说明:
这里使用环形队列(顺序队列),其基本思想与队列相同。需要注意的是,重要的思维方式,代码只是帮助理解:
1.首先,定义一个结构,当然,你也可以直接像我一样队列中的例子显然会节省更多的空间:
#define UART_DATA_MAX 20 typedef struct { uint8_t tail; /* 队列尾,入队时需要自加 */ uint8_t head; /* 队列头,出队时需要自减 */ uint8_t uart_length; /* 保存当前队列有效数据的长度 */ uint8_t recv_buf[UART_DATA_MAX]; /* 数据缓冲区 */ }uart_ring_buffer_t;
2.然后初始化这个队列:
static uart_ring_buffer_t ring_buffer; void uart_ring_buffer_init(void) { ring_buffer.head = 0; ring_buffer.tail = 0; ring_buffer.uart_length = 0; memset(ring_buffer.recv_buf, 0, sizeof(ring_buffer.recv_buf)); }
3.下一步是读写队列:
/* 读队列 */ bool read_uart_ring_buffer(uint8_t *data) { if(ring_buffer.uart_length == 0) { return false; } *data = ring_buffer.recv_buf[ring_buffer.head]; ring_buffer.recv_buf[ring_buffer.head] = 0; ring_buffer.head = (ring_buffer.head ) % UART_DATA_MAX; /* 防止数组溢出 */ ring_buffer.uart_length--; return true; } /* 写在队列里 */ bool write_uart_ring_buffer(uint8_t data) { if(ring_buffer.uart_length >= UART_DATA_MAX) { return false; } ring_buffer.recv_buf[ring_buffer.tail] = data; ring_buffer.tail = (ring_buffer.tail ) % UART_DATA_MAX; /* 防止数组溢出 */ ring_buffer.uart_length ; return true; } /* 获取当前队列有效数据(未处理数据)的长度 */ uint8_t get_uart_ring_buffer(void) { return ring_buffer.uart_length; }
4.数据协议分析(我的串口数据协议格式是AA 03 命令码(1byte) 有效数据(1byte) (算术和前两个字节):
/* 串口0中断服务函数 */ uint8_t ready_rx_flag = 0,rx_complete_cmd_flag = 0; void UART0_Handler(void) { uint32_t iWK = 0 ; uint8_t tWK = 0 ; uint8_t data_check_result = 0; __read_hw_reg32(UART0_IIR , iWK); iWK &= 0x0F; if((iWK != 0x04) && (iWK != 0x04) && (iWK != 0x0c)) return; __read_hw_reg32(UART0_RBR , tWK);
write_uart_ring_buffer(tWK);
}
int main(void)
{
uint8_t check_sum = 0;
uint8_t handler_buf[5] = {0};
uart_init();
uart_ring_buffer_init();
while(1)
{
if(read_uart_ring_buffer(&data)) /* 串口数据接收后处理 */
{
switch(recv_flag)
{
case 0:
if(data == 0xAA)
{
handler_buf[0] = data;
recv_flag = 1;
}
else
{
recv_flag = 0;
}
break;
case 1:
if(data == 0x03)
{
handler_buf[1] = data;
recv_flag = 2;
}
else
{
recv_flag = 0;
}
break;
case 2:
handler_buf[2] = data;
recv_flag = 3;
break;
case 3:
handler_buf[3] = data;
recv_flag = 4;
break;
case 4:
handler_buf[4] = data;
check_sum = (handler_buf[2] + handler_buf[3]) & 0x0FF;
if(check_sum == handler_buf[4])
{
uart_data_handle(handler_buf,5); /* 数据处理 */
recv_flag = 0;
memset(handler_buf, 0, sizeof(handler_buf));
}
break;
}
}
}
}
事实上,这样处理要保证数据不丢失,还得保证处理器的处理速度足够快才行,也就是要保证,数据解析的速度跟上数据接收的速度。如果你的MCU处理速度不够快,也可以定义一个相对较大的队列长度,这样,来不及处理的数据可以先暂时保存在这个队列里面,以空间换取时间。