在使用串口接收其他设备的数据时,应该针对数据的特点,譬如单字节与多字节、数据量大小、速度等,采用不同的接收方式。下面针对接收一帧含有多个字节的不定长数据接收方式进行讨论。
1、第一种方法:采用标志位(比如0X0D,0X0A)结束法
非常常见的一种接收方式,正点原子的例程便是采用的这种方式,以回车键作为一次数据结束的标志。缺点是在有些情况下会导致数据丢失(可能返回数据中0x0d、0a本身为有效数据)。所以,这种方法适合约定协议的数据帧,也就是说发送数据的设备也必须以相应的约定字节作为一次数据的结束。
void USART1_IRQHandler(void) //串口中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS
OSIntExit();
#endif
}
- 其中,采用标志字节(USART_RX_STA)同时作为接收状态指示与接收字节数量指示。接收到的数据存放在定义的数组中,要注意一次数据的最大长度不能超过前面定义的数组大小。
- 每接收到一个字节就要进入一次USART_IT_RXNE中断,对于波特率较高或还要再中断中进行其他操作的情况可能会出现问题。
2、第二种方法:在开启RXNE中断的基础上使能IDLE中断
通过在上述的基础上增加一条语句,实现对不定长的数据进行接收。避免了像对0X0D、0X0A这些结束标志位的使用,非常方便。
USART_ITConfig(USART2,USART_IT_RXNE, ENABLE);//开启串口接受中断USART_ITConfig(USART2,USART_IT_IDLE,ENABLE); //开启IDLE中断
- IDLE中断触发条件:接受数据后出现一个byte的高电平(空闲)状态,就会触发空闲中断.并不是空闲就会一直中断,应该是上升沿(停止位)后一个byte。缺点是无法人为控制空闲后多久进入中断,只能是很死板的一个byte后。在某些情形下,我们希望被作为一次接收的数据会被视为两次数据,分为两次接收。
- 本质上还是需要每接收到一个字节,就要进入一次RXNE中断,进入中断太频繁的问题依然存在。
- 注意:关于清除标志位:
- 在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断;但IDLE中断要通过“先读SR寄存器,再读DR寄存器”清除(即USART_GetITStatus()后USART_ReceiveData())。只是调用USART_ClearITPendingBit、USART_ClearFlag之类的方法是清除不了IDLE中断标志的,只能通过读出来的方式来消除。
- USART_FLAG_RXNE/ RXNE flag可以通过读DR寄存器进行清位,即调用USART_ReceiveData();也可以通过USART_ClearFlag()来清除。
- TC flag/TC pending bit 除了分别通过USART_ClearFlag()/USART_ClearITPendingBit()清除,也可以通过通过“先读SR寄存器,再写DR寄存器”清除,即USART_GetITStatus()再USART_SendData()。
- TXE flag/TXE Pending bit只能通过写DR寄存器清除,即USART_SendData()。
3、将串口配置为中断IDLE模式且使能DMA接收
配置成空闲中断IDLE模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。
void uart2_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART2,GPIOA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
//USART2_TX GPIOA.2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.2
//USART2_RX GPIOA.3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.3
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure); //初始化VIC寄存器
//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(USART2, &USART_InitStructure); //初始化串口2
// USART_ITConfig(USART2,USART_IT_RXNE, ENABLE); //*不开启串口接受中断*
USART_ITConfig(USART2,USART_IT_IDLE,ENABLE); //开启IDLE中断
USART_Cmd(USART2, ENABLE); //使能串口2
DMA_DeInit(DMA1_Channel6); //将DMA的通道5寄存器重设为缺省值 串口2对应的是DMA通道6
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART2->DR; //DMA外设usart基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART2_RX_BUF; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存
DMA_InitStructure.DMA_BufferSize = USART2_REC_LEN; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel6, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道
USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE); //使能串口2 DMA接收
DMA_Cmd(DMA1_Channel6, ENABLE);
}
避免了频繁进入RXNE中断,DMA在后台把数据默默地搬运到你指定的缓冲区里面。当整帧数据发送完毕之后串口才会产生一次中断,此时可以利用DMA_GetCurrDataCounter()函数计算出本次的数据接受长度,从而进行数据处理。
注意:DMA是不受cpu控制的,一旦设置好后就会自动搬运数据,在debug过程中它导致的赋值操作不会在断点停下。
4、时间间隔判断法
采用Xms连续接收的方式判断一次数据是否接收完成。如果两个数据之间的时间间隔超过Xms,则可以认为这两个数据不属于同一条消息了。
仍然采用使能USART_IT_RXNE中断的方法;在判断函数中设置一个临时指针temp指向缓存区数据指示变量USART_RX_STA,延时Xms后,如果接收没有结束,缓存区指针USART_RX_STA会后移,而临时指针仍然在原位,通过判断延时后的两者是否还相等来判断是否接收完成了。如果延时Xms后两个指示变量的值仍然相等,说明一帧数据接收完毕。此处的X的值根据实际情况选择。
缺点:可以避免0x0d,0x0a结束一次接收导致的数据丢失和IDLE中断触发死板的问题,但这种方法只适用于接收数据有明显时间间隔的情况,不适用于RX端收到数据频繁且随机的情况。
在使用串口接收其他设备的数据时,应该针对数据的特点,譬如单字节与多字节、数据量大小、速度等,采用不同的接收方式。下面针对接收一帧含有多个字节的不定长数据接收方式进行讨论。
1、第一种方法:采用标志位(比如0X0D,0X0A)结束法
非常常见的一种接收方式,正点原子的例程便是采用的这种方式,以回车键作为一次数据结束的标志。缺点是在有些情况下会导致数据丢失(可能返回数据中0x0d、0a本身为有效数据)。所以,这种方法适合约定协议的数据帧,也就是说发送数据的设备也必须以相应的约定字节作为一次数据的结束。
void USART1_IRQHandler(void) //串口中断服务程序
{
u8 Res;
#if SYSTEM_SUPPORT_OS
OSIntEnter();
#endif
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾)
{
Res =USART_ReceiveData(USART1); //读取接收到的数据
if((USART_RX_STA&0x8000)==0)//接收未完成
{
if(USART_RX_STA&0x4000)//接收到了0x0d
{
if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始
else USART_RX_STA|=0x8000; //接收完成了
}
else //还没收到0X0D
{
if(Res==0x0d)USART_RX_STA|=0x4000;
else
{
USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ;
USART_RX_STA++;
if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收
}
}
}
}
#if SYSTEM_SUPPORT_OS
OSIntExit();
#endif
}
- 其中,采用标志字节(USART_RX_STA)同时作为接收状态指示与接收字节数量指示。接收到的数据存放在定义的数组中,要注意一次数据的最大长度不能超过前面定义的数组大小。
- 每接收到一个字节就要进入一次USART_IT_RXNE中断,对于波特率较高或还要再中断中进行其他操作的情况可能会出现问题。
2、第二种方法:在开启RXNE中断的基础上使能IDLE中断
通过在上述的基础上增加一条语句,实现对不定长的数据进行接收。避免了像对0X0D、0X0A这些结束标志位的使用,非常方便。
USART_ITConfig(USART2,USART_IT_RXNE, ENABLE);//开启串口接受中断USART_ITConfig(USART2,USART_IT_IDLE,ENABLE); //开启IDLE中断
- IDLE中断触发条件:接受数据后出现一个byte的高电平(空闲)状态,就会触发空闲中断.并不是空闲就会一直中断,应该是上升沿(停止位)后一个byte。缺点是无法人为控制空闲后多久进入中断,只能是很死板的一个byte后。在某些情形下,我们希望被作为一次接收的数据会被视为两次数据,分为两次接收。
- 本质上还是需要每接收到一个字节,就要进入一次RXNE中断,进入中断太频繁的问题依然存在。
- 注意:关于清除标志位:
- 在中断函数里面,需要把对应的位清0,否则会影响下一次数据的接收。比如RXNE接收数据中断,只要把接收到的一个字节读出来,就会清除这个中断;但IDLE中断要通过“先读SR寄存器,再读DR寄存器”清除(即USART_GetITStatus()后USART_ReceiveData())。只是调用USART_ClearITPendingBit、USART_ClearFlag之类的方法是清除不了IDLE中断标志的,只能通过读出来的方式来消除。
- USART_FLAG_RXNE/ RXNE flag可以通过读DR寄存器进行清位,即调用USART_ReceiveData();也可以通过USART_ClearFlag()来清除。
- TC flag/TC pending bit 除了分别通过USART_ClearFlag()/USART_ClearITPendingBit()清除,也可以通过通过“先读SR寄存器,再写DR寄存器”清除,即USART_GetITStatus()再USART_SendData()。
- TXE flag/TXE Pending bit只能通过写DR寄存器清除,即USART_SendData()。
3、将串口配置为中断IDLE模式且使能DMA接收
配置成空闲中断IDLE模式且使能DMA接收,并同时设置接收缓冲区和初始化DMA。
void uart2_init(u32 bound){
//GPIO端口设置
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); //使能USART2,GPIOA时钟
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //使能DMA传输
//USART2_TX GPIOA.2
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.2
//USART2_RX GPIOA.3
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure);//初始化GPIOA.3
//Usart1 NVIC 配置
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure); //初始化VIC寄存器
//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(USART2, &USART_InitStructure); //初始化串口2
// USART_ITConfig(USART2,USART_IT_RXNE, ENABLE); //*不开启串口接受中断*
USART_ITConfig(USART2,USART_IT_IDLE,ENABLE); //开启IDLE中断
USART_Cmd(USART2, ENABLE); //使能串口2
DMA_DeInit(DMA1_Channel6); //将DMA的通道5寄存器重设为缺省值 串口2对应的是DMA通道6
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART2->DR; //DMA外设usart基地址
DMA_InitStructure.DMA_MemoryBaseAddr = (u32)USART2_RX_BUF; //DMA内存基地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC; //数据传输方向,从外设读取发送到内存
DMA_InitStructure.DMA_BufferSize = USART2_REC_LEN; //DMA通道的DMA缓存的大小
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //外设地址寄存器不变
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //内存地址寄存器递增
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //宽度为8位
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //数据宽度为8位
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal; //工作在正常缓存模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Medium; //DMA通道 x拥有中优先级
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; //DMA通道x没有设置为内存到内存传输
DMA_Init(DMA1_Channel6, &DMA_InitStructure); //根据DMA_InitStruct中指定的参数初始化DMA的通道
USART_DMACmd(USART2,USART_DMAReq_Rx,ENABLE); //使能串口2 DMA接收
DMA_Cmd(DMA1_Channel6, ENABLE);
}
避免了频繁进入RXNE中断,DMA在后台把数据默默地搬运到你指定的缓冲区里面。当整帧数据发送完毕之后串口才会产生一次中断,此时可以利用DMA_GetCurrDataCounter()函数计算出本次的数据接受长度,从而进行数据处理。
注意:DMA是不受cpu控制的,一旦设置好后就会自动搬运数据,在debug过程中它导致的赋值操作不会在断点停下。
4、时间间隔判断法
采用Xms连续接收的方式判断一次数据是否接收完成。如果两个数据之间的时间间隔超过Xms,则可以认为这两个数据不属于同一条消息了。
仍然采用使能USART_IT_RXNE中断的方法;在判断函数中设置一个临时指针temp指向缓存区数据指示变量USART_RX_STA,延时Xms后,如果接收没有结束,缓存区指针USART_RX_STA会后移,而临时指针仍然在原位,通过判断延时后的两者是否还相等来判断是否接收完成了。如果延时Xms后两个指示变量的值仍然相等,说明一帧数据接收完毕。此处的X的值根据实际情况选择。
缺点:可以避免0x0d,0x0a结束一次接收导致的数据丢失和IDLE中断触发死板的问题,但这种方法只适用于接收数据有明显时间间隔的情况,不适用于RX端收到数据频繁且随机的情况。
举报