USART - 空闲中断接收
STM32 的 USART 收发, 以前只知道用RENE(非空中断)处理接收, 自从发现IDLE(空闲中断) 这词后, 眼前一亮, 老司机自然明白能省多少中断工夫!!
使用时,注意以下两点,很重要:
1. IDLE的中断清理方式.
- 开始以为住SR写0就能清理中断, 调试时寄存器确实清0了, 但程序上却不断进中断! (你用IDLE就会发现这问题了)
- 这个费了一个白天,直至网上发现前人的留言:
- "因为IDLE被搞成了一个帧,而不是一个事件,这个帧不读掉是清除不了中断标志的,这个真的很坑啊。"
- 方法如下:
- u32 temp;
- temp=USART1->SR;
- temp=USART1->DR;
- 就是这样, 这两句顺序还不能错, 前人不说, 撞墙也想不出来这前因后果!!
2. DMA 设置
- 平时用DMA一般就:PtoM, MtoP, 就是数据长度是已知道的; 而用IDLE(空闲中断), 最方便的就是接收不定长数据.
- 所以NDTR(传输数量)要设得比实际数量要大(注意单位:你的字长),如200. 大多少没关系.
- 中断中, 要重新设置NDTR. 切记,切记,切记. 另外: DMA是在关闭时方能修改!!
代码如下, 主要三个函数, DMA初始化, USART初始化, 中断函数,
// DMA初始化
//******************************************************************************
void DMA_Init(void)
{
RCC->AHB1ENR |= 0x1<<22 ; // 使能DMA2时钟
DMA2_Stream1 ->CR = 0; // EN [0] 禁止数据流 ,才能写寄存器
DMA2_Stream1 ->PAR = (u32)USART1 +0x04; // 外设地址 ADC1->DR
DMA2_Stream1 ->M0AR = (u32)&RxBuf; // 存储器地址 内部SDRAM的变量
DMA2_Stream1 ->NDTR = 60; // 一次传输数量
DMA2_Stream1 ->FCR = 0x21; // 复位值 FIFO所有配置失效
DMA2_Stream1 ->CR |= 0<< 6; // DIR [7:6] 传输方向 0_PtM, 1_Mtp, 2_MtM
DMA2_Stream1 ->CR |= 1<< 8; // CIRC [8] 循环模式 0_禁止 1_使能
DMA2_Stream1 ->CR |= 0<< 9; // PINC [9] 外设递增 0_外设地址指针固定 1_递增
DMA2_Stream1 ->CR |= 1<<10; // MINC [10] 存储器递增 0_存储器指针固定 1_递增
DMA2_Stream1 ->CR |= 0<<11; // PSIZE [12:11] 外设数据大小 0_8位 1_16位 10_32位
DMA2_Stream1 ->CR |= 0<<13; // MSIZE [14:13] 存储器数据大小 0_8位 1_16位 10_32位
DMA2_Stream1 ->CR |= 1<<16; // PL [17:16] 优先级 0_低 1_中 2_高 3_最高
DMA2_Stream1 ->CR |= 5<<25; // CHSEL [27:25] 通道选择
DMA2_Stream1 ->CR |= 1<<0; // EN [0] 使能数据流, 使能后DMA大部份寄存器都不能写
}
// USART初始化
//******************************************************************************
void USART_Init()
{
float T;
u16 M,F;
RCC->APB2ENR |= (u32)1<<4; // 使能 USART时钟
RCC->AHB1ENR |= (u32)1<<0; // 使能 GPIOA时钟
vSys_GPIOSet (DEBUG_TX_GPIOx, DEBUG_TX_PINx, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_OSPEED_50M , GPIO_PUPD_UP , DEBUG_USART_AFx); // TX, GPIO
vSys_GPIOSet (DEBUG_RX_GPIOx, DEBUG_RX_PINx, GPIO_MODE_AF, GPIO_OTYPE_PP, GPIO_OSPEED_50M , GPIO_PUPD_UP , DEBUG_USART_AFx); // RX, GPIO
T=(float)(DEBUG_USART_CLK *1000000)/(DEBUG_USART_BRR *16); // 得到USARTDIV,OVER8设置为0
M=T; // 得到整数部分
F=(T-M)*16; // 得到小数部分,OVER8设置为0
M<<=4;
M=M+F;
USART1 ->BRR = M; // 设置波特率因子
USART1 ->CR1 = 0; // 清0
USART1 ->CR1 |= 1<<2; // 接收
USART1 ->CR1 |= 1<<3; // 发送
USART1 ->CR1 |= 1<<4; // 使能接收缓冲区非空中断
USART1 ->CR1 |= 0<<10; // 校验
USART1 ->CR1 |= 0<<12; // 字长
USART1 ->CR1 |= 0<<15; // 过采样 0=16
USART1 ->CR1 |= 1<<13; // 使能
vSys_NVICSet(DEBUG_USART_IRQN,9); // 我自己的NVIC设置函数, 改成你自己需要的
DMA_Init ();
USART1 ->CR3 = 1<<6; // 使能DMA接收
}
// 中断函数
//******************************************************************************
void USART1_IRQHandler(void)
{
static u32 temp;
temp =USART1->SR ;
if((temp&0x10)) // 检查IDLE中断标志
{
vDelay_ms (20); // 等一会, 速度匹配问题, 暂未能解决
p(RxBuf );
temp=USART1 ->SR; // !!! 这两句很重要, 作用是清理IDLE中断, 顺序不参错!!
temp=USART1->DR; // !!!
DMA2_Stream1 -> CR &=~(1<<0); // 失能 DMA, 关闭方能修改参数
DMA2_Stream1 ->NDTR = 60; // 重新设置DMA的传输数目, 必须大于要传输的数目,否则超出部分会履盖开始部份
DMA2_Stream1 -> CR |= 1<<0; // 使能 DMA
}
}
来源:https://blog.csdn.net/zhouml_msn/article/details/99697809