STM32串口如何代码实现更稳定的接收消息

来源:互联网 发布:网络推广总结 编辑:程序博客网 时间:2024/05/22 03:34

在 《STM32串口向世界问好》介绍过如何发送消息,那么又如何接收消息呢?

也很简单,只需要配置好串口接收,配置好中断,并在串口中断函数里面进行数据接收就可以了。通用配置代码如下:

/**  * @brief  初始化IO 串口1  * @param  bound:波特率  * @retval None  */void USART1_Debug_Init(u32 bound){    //GPIO端口设置    GPIO_InitTypeDef GPIO_InitStructure;    USART_InitTypeDef USART_InitStructure;    NVIC_InitTypeDef NVIC_InitStructure;    assert_param(bound >0 && bound <= 256000);    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);    USART_DeInit(USART1);  //复位串口1    //USART1_TX   PA.9    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //PA.9    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;   //复用推挽输出    GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化PA9    //USART1_RX   PA.10    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;//浮空输入    GPIO_Init(GPIOA, &GPIO_InitStructure);  //初始化PA10    //USART 初始化设置    USART_InitStructure.USART_BaudRate = bound;    USART_InitStructure.USART_WordLength = USART_WordLength_8b;//字长为8位数据格式    USART_InitStructure.USART_StopBits = USART_StopBits_1;//一个停止位    USART_InitStructure.USART_Parity = USART_Parity_No;//无奇偶校验位    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;    USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; //收发模式    USART_Init(USART1, &USART_InitStructure); //初始化串口    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ;    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;    NVIC_Init(&NVIC_InitStructure);     USART_ClearFlag(USART1, USART_FLAG_TC);//防止第一个数据被覆盖    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//开启中断    USART_Cmd(USART1, ENABLE);                    //使能串口}

中断处理接收函数为:

 void USART1_IRQHandler(void){    u8 res;    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断 有数据为 1 SET    {        res = (u8)USART_ReceiveData(USART1);        res = res ;    }}

如果此时需要判断当接收的数据为1时点亮LED1,当接收数据为2时熄灭LED1则可在中断里作如下处理:

 void USART1_IRQHandler(void){    u8 res;    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断 有数据为 1 SET    {        res = (u8)USART_ReceiveData(USART1);         if(0x01 == res)        {            LED1 = ON ;         }        if(0x02 == res)        {            LED1 = OFF ;         }    }}

但这种接收控制方法是不够稳定与灵活的,比如在传输的过程中受到干扰,0x01 变成 0x02,则就会出现错误的控制。又比如我要接收一串数据并进行处理,这样就不好控制了。

这时我们就要想着制定一套通信协议来方便通信。

在此介绍一种简单通信协议,是我在设计一款无人机数据链通信时用到的一开源协议:MAVLink,另外加上CRC校验,进一步保证接收数据的可靠性。

其通信数据格式如下:

STM32串口如何代码实现更稳定的接收消息

红色部分代表起始帧 STX 为 0xFE ; LEN表示要发送的数据长度(PAYLOAD长度);SEQ表示数据的序列号,循环从0至255发送(可以检测是否丢包,并可能过此来判断信号强度);SYS是用来表示区分同一网络中不同飞行器号的,即系统ID;COMP代表组件ID,表示飞行器上各个组成部分,如飞控单元,GPS等;MSG则代表消息ID,即要发送不同控制命令ID;PAYLOAD表示此命令的内容;最后两字节是自动生的的CRC校验码 。

从上图也可以看出PAYLOAD有效长度可为0至255字节(因为LEN来表示,它们都是无符号8位数据类型),所以一条消息长度最小为8字节,最大为263字节。

至此一简单通信协议就介绍过了,说的有点多。下面就是如何对其解析,话不多说直接代码说明:

#define MavlinkLenMin  8#define MavlinkLenMax  263#define STX      0xFE//MAVLINK HEAD#define Add_STX  0x00#define Add_LEN  0x01#define Add_SEQ  0x02#define Add_SYS  0x03#define Add_COMP 0x04#define Add_MSG  0x05#define Add_PAYLOAD  0x06//PAYLOAD start from 0x06typedef enum {BEEN_RECEIVED = 0, RECEIVING = !BEEN_RECEIVED} Receive_Status;typedef struct{    boolean Get ;    u16 Len ;    u8 Cache[MavlinkLenMax];}MAVLink_Data_Struct , * MAVLink_Data_Struct_p ; MAVLink_Data_Struct Msg_Rev ; void Msg_Recv_Data_Analyse_Irq(u8 data){    if(RECEIVING == Msg_Rev.Get){        Msg_Rev.Cache[Msg_Rev.Len++] = data;        if(STX != Msg_Rev.Cache[Add_STX]){            Msg_Rev.Len = 0 ;        }        if(((u16)Msg_Rev.Cache[Add_LEN] + MavlinkLenMin)==Msg_Rev.Len){            Msg_Rev.Get = BEEN_RECEIVED ;        }    }}

可在串口中断接收函数里调用此函数用作协议数据接收解析

void USART1_IRQHandler(void){    u8 res;    if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断 有数据为 1 SET    {        res = (u8)USART_ReceiveData(USART1);        Msg_Recv_Data_Analyse_Irq(res);    }}

当一条消息接收完成后,Msg_Rev.Get的状态就会被设置成BEEN_RECEIVED ,这时就可在相关函数中对此条消息进行处理。

另外为了消息的更可靠,还可加入CRC校验,如下函数就是一简单通用的CRC16校验码生成函数:

u16 crc_chk_value(u8 *data_value){    u16 crc_value = 0xFFFF;         u16 length = (uint16_t)data_value[1] + 6;    u16  i;    while(length--)    {        crc_value ^= *data_value++;        for(i=0;i<8;i++)        {            if(crc_value & 0x0001)                crc_value = (crc_value >>1) ^ 0xa001;            else                crc_value=crc_value >> 1;        }    }    return(crc_value);}

如上述的对LED灯控制,我们可以作如下简单设计,设定发送操控数据的设备SYS ID为1,假定组件串口1的ID为1,消息ID也为1,另外发送的数据长度也为1,则解析控制函数如:

void Msg_Control_Process(void){    u16 checksum;    if(BEEN_RECEIVED == Msg_Rev.Get){        checksum = crc_chk_value(Msg_Rev.Cache);        if( (Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6 + 1] == (checksum & 0xFF)) &&                (Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6 + 2] == ((checksum >> 8) & 0xFF)) ){            if( (0x01 == Msg_Rev.Cache[Add_SYS]) && (0x01 == Msg_Rev.Cache[Add_COMP]) ){                if(0x01 == Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6]){                    LED1 = ON ;                }                if(0x02 ==  Msg_Rev.Cache[Msg_Rev.Cache[Add_LEN] + 6]){                    LED1 = OFF ;                }            }        }        Msg_Rev.Get = RECEIVING ;        Msg_Rev.Len = 0;    }}


此函数可在主轮询里调用,当中断里正常接收到一串消息后,就可以根据条件判断及加处控制处理,处理完成后再继续接收。因加入了CRC校验及消息和组件ID检测等,数据可靠性增加,当然软件通信可靠性增强一般是通过增加冗余来实现,此也不例外。

稍微复杂的控制用此比较好,上面的例程用此只是作简单原理性说明,有点大材小用的感觉 。

另在此设计中,你会发现,当接收完一条消息,处理完成后才接收下一条。这样,当处理过程较费时间,并且消息在不断的快速发送时,就容易引起丢包现象 ,所以以上设计并不是很好的。那么这个又如何解决呢?

待我研究下,下篇将会作详细介绍 。


0 0
原创粉丝点击