stm32LL库串口空闲中断+DMA接收
俺用的STM32F407VGT6,cubemx生成的代码。
cubemx的串口配置
这里就按默认参数配就行,主要是要打开后面的DMA
在这里点添加就完事了,如果只用串口接收,那只开RX就行,我把TX也开了是为了方便以后拓展功能。
然后把串口的全局中断开了,好像到这里就完事了,这个是俺很久以前配的,可能有遗漏,不管那么多了。
配完了之后就是生成代码了,这里选用了LL库,之前用HAL库,拿DAC生成个正弦波都有一段会抽一下,换了LL库之后就没问题了。
然后打开工程,首先来到usart.c,可以看到,cubemx还是很贴心的帮我们生成好了代码,但是就是没帮我们初始化,所谓万事俱备只欠东风。
在这里把代码贴上来,需要注意的是这里的DMAmode得改成循环模式,不然数据接收到一次之后,你后面的数据再怎么变它都不会刷新。。。
LL_DMA_SetChannelSelection(DMA2, LL_DMA_STREAM_2, LL_DMA_CHANNEL_4);
LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_2, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
LL_DMA_SetStreamPriorityLevel(DMA2, LL_DMA_STREAM_2, LL_DMA_PRIORITY_HIGH);
LL_DMA_SetMode(DMA2, LL_DMA_STREAM_2, LL_DMA_MODE_CIRCULAR);
LL_DMA_SetPeriphIncMode(DMA2, LL_DMA_STREAM_2, LL_DMA_PERIPH_NOINCREMENT);
LL_DMA_SetMemoryIncMode(DMA2, LL_DMA_STREAM_2, LL_DMA_MEMORY_INCREMENT);
LL_DMA_SetPeriphSize(DMA2, LL_DMA_STREAM_2, LL_DMA_PDATAALIGN_BYTE);
LL_DMA_SetMemorySize(DMA2, LL_DMA_STREAM_2, LL_DMA_MDATAALIGN_BYTE);
LL_DMA_DisableFifoMode(DMA2, LL_DMA_STREAM_2);
对了,顺便附带一下LL库实现printf的操作,弄个这个函数就完事了
int fputc(int ch, FILE *f)
{
LL_USART_TransmitData8(USART1,ch);
while(!LL_USART_IsActiveFlag_TC(USART1))
{
}
return ch;
}
咳咳,言归正传,下面来到stm32f4xx_it.c,加入这个函数,也就是空闲中断的回调函数。
void USART_RxIdleCallback(void)
{
uint8_t i;
if(LL_USART_IsActiveFlag_IDLE(USART1))
{
//先清标志位
LL_USART_ClearFlag_IDLE(USART1);
if(UartRxDmaBuf[2] == 0xEE)
{
NVIC_SystemReset();
}
//先停止UART流DMA,暂停接收
LL_DMA_DisableStream(DMA2, LL_DMA_STREAM_2);
/* 3.搬移数据进行其他处理 */
for(i=0; i<8; i++)
{
UartRxBuf[i] = UartRxDmaBuf[i];
}
UartRxflag = 1; //标志已经成功接收到一包等待处理
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_2, 8);
/* 4.开启新的一次DMA接收 */
LL_DMA_EnableStream(DMA2, LL_DMA_STREAM_2);
}
}
中间有个串口发送特定数据reset单片机的操作,不需要的可以去掉。
然后就是改写串口中断处理函数啦,把之前写的函数往里面一丢,然后再清一下标志位,就完事了。
USART_RxIdleCallback();
LL_USART_ClearFlag_RXNE(USART1);
额额,这里的DMA中断处理函数应该是没有用到,我反正是没开DMA的中断,但是不知道不写会不会有啥影响,也贴出来吧,个人感觉不写是不会有问题的,如果拿这篇文章做验证的话可以先不写。
OK,事已至此,木已成舟,一切准备工作皆已就绪,相当于导弹造好了,就差摁按钮了。
首先先搞个变量出来接收DMA的数据,对了,忘记说了,这玩意迁移数据好像没多大意义,两个好像是一起刷新的,算了不管了,反正F4内存大,也不差这点空间。
uint8_t UartRxBuf[8]; //迁移的上位机数据
uint8_t UartRxDmaBuf[8]; //上位机传输的原始数据
uint8_t UartRxflag = 0; //数据待处理标志
然后在主函数里加上这些初始化的东西就大功告成啦,记得放在串口初始化函数后面哈,因为串口初始化函数里面还有着一些DMA初始化的操作,所以要先完成。
LL_DMA_SetPeriphAddress(DMA2, LL_DMA_STREAM_2, (uint32_t)(&USART1->DR));
LL_DMA_SetMemoryAddress(DMA2, LL_DMA_STREAM_2, (uint32_t)UartRxDmaBuf);
LL_DMA_SetDataLength(DMA2, LL_DMA_STREAM_2, 8);
LL_USART_EnableIT_RXNE(USART1); //使能串口1的中断
LL_USART_EnableIT_IDLE(USART1); //使能串口1的空闲中断
LL_USART_EnableDMAReq_RX(USART1);
LL_DMA_EnableStream(DMA2,LL_DMA_STREAM_2);
额额,稍微解释一下,第一个是设置外设的地址,因为之前在串口初始化里已经将DMA设置为外设到内存了,就是下面那段代码,在串口初始化里面可以找到,这里就不用再写一次了。
LL_DMA_SetDataTransferDirection(DMA2, LL_DMA_STREAM_2, LL_DMA_DIRECTION_PERIPH_TO_MEMORY);
第二个是设置内存地址,就不用多说了。
第三个是传输的长度,之前的数组设置成多大的这里参数就给多少。
这里讲一下,F4和F1两者LL库的区别,之前在网上查,F1的初始化函数,都不是设置STREAM,而是设置CHANNEL,我想是因为F4有DMA控制器的原因,在F4上多了个这么玩意(请忽视我原谅色的护眼背景)
而F1应该就是直接CHANNEL就接到仲裁器了吧,手头上没有F1的参考手册,姑且就发挥主观能动性臆测一下。
好了,行文至此,就此搁笔。
具体的还是需要花时间去参透,只有理解了其如何运行,才能得心应手地来配置。
这波,这波是在大气层。