STM32串口空闲中断
在STM32中,我们经常需要用串口接收数据,除了串口用于代码调试printf基础功能之外,我们更多需要使用串口中断来接收数据,但是如此的话频繁进入串口中断,然后处理,效率还是太低,老板(CPU)也不高兴,于是召集各片内外设召开了技术讨论会
DMA:可以使用我来接收串口数据,比如使用USART1的话利用DMA2就可以
老板:比较关键的一点就是不知道何时数据接收完毕,因为将DMA接收数据长度不固定了,那应该怎么办呢?
正在大家冥思苦想的时候,STM32的串口站起来了,生气的说到:
STM32串口:看来你们对我的认识还是不够啊,我还支持串口空闲中断呢,你们只需要对我进行简单的串口空闲中断配置,那么当接受到数据以后,首次进入空闲,我就会产生空闲中断,然后程序就会跳到串口中断服务函数中去
大伙一听,好像是这么个理儿,老板也很高兴了,确定这种方案之后吩咐大家开始进行STM32串口空闲中断不定长数据接收配置,结果如下:
空闲中断配置代码
usart.h:
#ifndef __USART_H
#define __USART_H
#include "stm32f4xx.h"
#include "stdio.h"
#define USART1_REC_LEN 100
extern u8 USART_RX_BUF[USART1_REC_LEN];
extern u8 USART1_REC_CNT;
extern u8 USART1_RX_FLAG;
void USART1_Init(u32 bound);
#endif
usart.c:
/* Includes ------------------------------------------------------------------*/
#include "usart.h"
/******************************Code area************************************/
u8 USART_RX_BUF[USART1_REC_LEN];
u8 USART1_REC_CNT = 0;
u8 USART1_RX_FLAG = 0;
/*******************************************************************************
* @Name : USART1_Init
* @brief : USART1初始化配置
* @Input : bound: 串口波特率
* @Output : None
* @retval : None
*******************************************************************************/
void USART1_Init(u32 bound)
{
GPIO_InitTypeDef GPIO_InitStructure;
USART_InitTypeDef USART_InitStructure;
DMA_InitTypeDef DMA_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA | RCC_AHB1Periph_DMA2,ENABLE); //使能GPIOA时钟 & DMA1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE); //使能USART1时钟
GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //PA9复用为USART1
GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //PA10复用为USART1
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF; //复用功能
GPIO_InitStructure.GPIO_OType=GPIO_OType_PP; //推挽复用输出
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_9 | GPIO_Pin_10; //选择USART1对应的管脚:PA9->USART1_TX,PA10->USART1_RX
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_100MHz; //端口速度100MHz
GPIO_Init(GPIOA,&GPIO_InitStructure); //GPIO端口初始化
USART_InitStructure.USART_BaudRate=bound; //设置串口波特率:115200
USART_InitStructure.USART_HardwareFlowControl=USART_HardwareFlowControl_None; //设置有无硬件流控制:无
USART_InitStructure.USART_Mode=USART_Mode_Rx | USART_Mode_Tx; //设置模式:接收&发送
USART_InitStructure.USART_Parity=USART_Parity_No; //设置有无奇偶校验:无
USART_InitStructure.USART_StopBits=USART_StopBits_1; //设置停止位:1位
USART_InitStructure.USART_WordLength=USART_WordLength_8b; //设置数据位:8位
USART_Init(USART1,&USART_InitStructure); //USART1初始化
DMA_DeInit(DMA2_Stream2); //将DMA2_Stream2寄存器取消初始化为其默认复位值
while(DMA_GetCmdStatus(DMA2_Stream2) != DISABLE); //等待DMA可配置
DMA_InitStructure.DMA_BufferSize = USART1_REC_LEN; //设置DMA数据传输量
DMA_InitStructure.DMA_Channel = DMA_Channel_4; //设置DMA数据传输通道
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; //设置数据传输方向
DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable; //设置是否开启FIFO模式
DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; //设置FIFO阈值,可以为FIFO的1/4、1/2、3/4、1倍,没有开启FIFO时可不关心
DMA_InitStructure.DMA_Memory0BaseAddr = (u32)USART_RX_BUF; //设置存放DMA传输数据的内存基地址
DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //设置存储器突发传输配置,可设置单次突发传输、4个节拍增量、8个节拍增量、16个节拍增量突发传输模式
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; //设置内存的数据长度是为字节存储、半字存储还是字存储
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; //设置传输数据时内存地址是否递增
DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; //设置DMA模式,可设置普通模式或循环模式,循环模式会让DMA接收一帧数据之后再回到初址
DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)(&(USART1->DR)); //设置DMA传输的外设基地址
DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //设置外设突发传输配置,可设置单次突发传输、4个节拍增量、8个节拍增量、16个节拍增量突发传输模式
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //设置外设的数据长度是为字节传输、半字传输还是字传输
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; //设置传输数据的时候外设的地址是不变还是递增
DMA_InitStructure.DMA_Priority = DMA_Priority_High; //设置DMA通道的优先级,有低、中、高、超高四种优先级可选
DMA_Init(DMA2_Stream2,&DMA_InitStructure); //DMA2 for USART1_RX初始化
DMA_Cmd(DMA2_Stream2,ENABLE); //使能DMA传输
NVIC_EnableIRQ(USART1_IRQn); //使能USART1中断通道
USART_Cmd(USART1,ENABLE); //USART1使能
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //若总线空闲,产生中断
USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE); //USART1's DMA接收&发送使能
USART_ClearFlag(USART1,USART_FLAG_TC); //清除发送完成标志
}
/*******************************************************************************
* @Name : USART1_IRQHandler
* @brief : USART1中断服务函数
* @Input : None
* @Output : None
* @retval : None
*******************************************************************************/
void USART1_IRQHandler(void)
{
u8 i;
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //接收中断(接收到的数据必须是0x0D 0x0A结尾)
{
i = USART1->SR; //软件序列清零(读入USART_SR寄存器,然后读入USART_DR寄存器)
i = USART1->DR;
//USART_ReceiveData(USART1); // Clear IDLE interrupt flag bit(注释掉上面i相关代码换成此句效果一样)
//(X) USART_ClearITPendingBit(USART1,USART_IT_IDLE); //清除发送完成标志(可查看参考手册712页或者USART_ClearITPendingBit函数说明(stm32f4xx_usart.c源文件1437~1441行))
USART1_REC_CNT = USART1_REC_LEN-DMA_GetCurrDataCounter(DMA2_Stream2); //算出接本帧数据长度
/***********帧数据处理函数************/
printf ("The Length:%d\r\n",USART1_REC_CNT);
/*************************************/
USART1_RX_FLAG = 1; //接收数据标志置位
DMA_Cmd(DMA2_Stream2,DISABLE); //失能DMA传输
while(DMA_GetCmdStatus(DMA2_Stream2) != DISABLE); //等待DMA可配置
DMA_SetCurrDataCounter(DMA2_Stream2,USART1_REC_LEN); //重新设置接收数据的个数
DMA_Cmd(DMA2_Stream2,ENABLE); //使能DMA传输
USART_ITConfig(USART1,USART_IT_IDLE,ENABLE); //若总线空闲,产生中断
}
}
/*******************************************************************************
* @Name : fputc
* @brief : 内部调用函数,注意要勾选OPTIONS中的USE Micro LIB选项
* 由于printf执行是调用fputc函数,所以相当于重新定义printf函数
* ,支持printf()发送数据。
* @Input : None
* @Output : None
* @retval : None
*******************************************************************************/
int fputc(int ch,FILE *f)
{
USART_SendData(USART1,(u8)ch);
while(USART_GetFlagStatus(USART1,USART_FLAG_TXE)==RESET); //获取串口状态
return ch;
}
/*******************************************************************************
* @Name : fgetc
* @brief : 内部调用函数,注意要勾选OPTIONS中的USE Micro LIB选项
* 由于scanf执行是调用fgetc函数,所以相当于重新定义scanf函数
* ,支持printf()发送数据。
* @Input : None
* @Output : None
* @retval : None
*******************************************************************************/
int fgetc(FILE *f)
{
while(USART_GetFlagStatus(USART1,USART_FLAG_RXNE) == RESET); //获取串口状态
return (USART_ReceiveData(USART1));
}
下载之后使用SSCOM串口调试器和Keil Debug测试:
点击Keil调试器Run(F5)之后发送数据结果如下:
上面结果只是简单测试,提交给老板之后,新的问题:如果我们发送数据的频率过快,在下一帧数据到来之前本帧数据未来得及处理怎么办呢?