STM32单片机串口空闲中断接收不定长数据
在使用单片机的串口通信功能时,常用的接收数据方法是通过固定的字节数来判断一帧数是否发送完成,或者是通过固定的结束标志位来表示一帧数据发送完成。但是有时候会遇到发送的数据长度不固定,也没有固定的结束标志,对于这样的数据通常的做法是每隔一段时间查看一下接收数据的长度是否发生了变化,如果指定的一段时间内接收数据长度没有发生变化,就认为是一帧数据发送完成。在STM32单片机中串口提供了一个更好用的功能,就是空闲中断功能。也就是说当一帧数据发送结束后,就会产生一个空闲中断。这样就可以利用这个空闲中断来判断一帧数据接收是否完成。
关于串口空闲检测可以在STM32参考手册上找到相关介绍
通过这个图可以看出来,当第一组数据Data1、Data2、Data3、Data4发送结束后,总线就会处于空闲状态,这时就会产生一个空闲中断。
当Data1、Data2、Data3、Data4每一个数据到来时串口产生的中断为 RXNE:读数据寄存器非空中断。具体的相关介绍可以在状态寄存器(USART_SR)中查看。
也就是每接收一个字节,串口会产生一个RXNE中断,当一帧数据发送完成就产生一个IDLE中断。
这样就可以在串口每个RXNE中断来临后将数据先存储起来,然后在IDLE中断到来后说明数据接收结束。这时候就可以去处理接收到的数据了。
下面就通过代码来说明一下如何使用串口空闲中断来接收不定长数据
首先初始化串口
void uart2_init( u16 baud )
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIO_InitStructure );
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init( &NVIC_InitStructure );
USART_InitStructure.USART_BaudRate = baud;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init( USART2, &USART_InitStructure );
USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断
USART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断
USART_Cmd( USART2, ENABLE ); //使能串口2
}
与常规的串口初始化唯一不同的就是多了一行空闲中断初始化代码
USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断
下来在中断中根据中断类型来处理数据
void USART2_IRQHandler( void )
{
u8 tem = 0;
if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断 接收到一个字节产生一次中断
{
tem = USART_ReceiveData( USART2 ); //读取数据,可以自动将中断标志位RXNE清零
rec_buff[uart2_rec_cnt++] = tem; //存储接收到的数据
}
if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET )//空闲中断 接收到一帧数据 产生一次中断
{
tem = USART2->SR; //读取SR寄存器
tem = USART2->DR; //读取DR寄存器 (先读USART_SR,然后读USART_DR可以清除空闲中断标志位IDLE)
copy_data( rec_buff, uart2_rec_cnt ); //备份数据
receiveOK_flag = 1; //接收完成标志位置位
uart2_rec_cnt = 0; //接收数据长度清零
}
}
当串口是RXNE中断时,就直接将数据存储到数组中,当IDLE中断到来后就将接收到的数据存储起来,然后置位接收一帧数据成功标志。这样主程序在检测到接收数据成功标志后,就可以去处理数据了。
这样通过IDLE中断,就可以轻松接收到不定长的数据了,不论数据长度是多少,只要是一帧数据结束,IDLE中断就可以检测到。
完整代码如下
#ifndef __UART2_H
#define __UART2_H
#include "sys.h"
#define UART2_REC_LEN 20 //串口缓存区长度
void uart2_init( u16 baud );
void uartDMA_Init( void );
void myDMA_Enable( DMA_Channel_TypeDef*DMA_CHx );
void uart2_Send( u8 *buf, u16 len );
void copy_data( u8 *buf, u16 len );
#endif
//串口2空闲中断,接收不定长数据
#include "uart2.h"
u8 rec_buff[UART2_REC_LEN] = {0};
u16 uart2_rec_cnt = 0; //串口接收数据长度
u8 data_backup[UART2_REC_LEN] = {0}; //数据备份
u16 dataLen_backup = 0; //长度备份
_Bool receiveOK_flag = 0; //接收完成标志位
/*
空闲中断是什么意思呢?
指的是当总线接收数据时,一旦数据流断了,此时总线没有接收传输,处于空闲状态,IDLE就会置1,产生空闲中断;又有数据发送时,IDLE位就会置0;
注意:置1之后它不会自动清0,也不会因为状态位是1而一直产生中断,它只有0跳变到1时才会产生,也可以理解为上升沿触发。
所以,为确保下次空闲中断正常进行,需要在中断服务函数发送任意数据来清除标志位。
*/
void uart2_init( u16 baud )
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );
RCC_APB1PeriphClockCmd( RCC_APB1Periph_USART2, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽复用模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIO_InitStructure );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入模式
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init( GPIOA, &GPIO_InitStructure );
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x02;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init( &NVIC_InitStructure );
USART_InitStructure.USART_BaudRate = baud;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_Init( USART2, &USART_InitStructure );
USART_ITConfig( USART2, USART_IT_IDLE, ENABLE ); //使能串口空闲中断
USART_ITConfig( USART2, USART_IT_RXNE, ENABLE ); //使能串口RXNE接收中断
USART_Cmd( USART2, ENABLE ); //使能串口2
}
//发送len个字节
//buf:发送区首地址
//len:发送的字节数
void uart2_Send( u8 *buf, u16 len )
{
u16 t;
for( t = 0; t < len; t++ ) //循环发送数据
{
while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
USART_SendData( USART2, buf[t] );
}
while( USART_GetFlagStatus( USART2, USART_FLAG_TC ) == RESET );
}
//备份接收到的数据
void copy_data( u8 *buf, u16 len )
{
u16 t;
dataLen_backup = len; //保存数据长度
for( t = 0; t < len; t++ )
{
data_backup[t] = buf[t]; //备份接收到的数据,防止在处理数据过程中接收到新数据,将旧数据覆盖掉。
}
}
//利用空闲中断接收串口不定长数据
//RXNE中断和IDLE中断的区别?
//当接收到1个字节,就会产生RXNE中断,当接收到一帧数据,就会产生IDLE中断。比如给单片机一次性发送了8个字节,就会产生8次RXNE中断,1次IDLE中断。
void USART2_IRQHandler( void )
{
u8 tem = 0;
if( USART_GetITStatus( USART2, USART_IT_RXNE ) != RESET ) //接收中断 接收到一个字节产生一次中断
{
tem = USART_ReceiveData( USART2 ); //读取数据,可以自动将中断标志位RXNE清零
rec_buff[uart2_rec_cnt++] = tem; //存储接收到的数据
}
if( USART_GetITStatus( USART2, USART_IT_IDLE ) != RESET ) //空闲中断 接收到一帧数据 产生一次中断
{
tem = USART2->SR; //读取SR寄存器
tem = USART2->DR; //读取DR寄存器 (先读USART_SR,然后读USART_DR可以清除空闲中断标志位IDLE)
//uart2_Send( rec_buff, uart2_rec_cnt ); //一帧数据接收完毕,将接收到的数据发送出去
copy_data( rec_buff, uart2_rec_cnt ); //备份数据
receiveOK_flag = 1; //接收完成标志位置位
uart2_rec_cnt = 0; //接收数据长度清零
}
}
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "uart2.h"
#include "string.h"
extern u8 data_backup[UART2_REC_LEN]; //数据备份
extern u16 dataLen_backup; //长度备份
extern _Bool receiveOK_flag; //接收完成标志位
int main( void )
{
u8 j = 0;
NVIC_PriorityGroupConfig( NVIC_PriorityGroup_2 );
delay_init(); //延时函数初始化
LED_Init(); //初始化与LED连接的硬件接口
uart2_init( 9600 );
while( 1 )
{
if( receiveOK_flag ) //一帧数据接收完成后开始处理数据
{
receiveOK_flag = 0;
uart2_Send( data_backup, dataLen_backup ); //发送数据
memset( data_backup, 0, sizeof( data_backup ) ); //清空备份数组
}
j++;
if( j > 50 )
{
j = 0;
LED = !LED;
}
delay_ms( 10 );
}
}
测试效果如下
只要发送的数据长度不超过接收缓存区的长度,发送的数据都可以准确的接收到。
完整工程下载地址 https://download.csdn.net/download/qq_20222919/12926272
上一篇: CSAPP实验-datalab
推荐阅读