打印
[其他ST产品]

STM32f4 串口DMA配置与使用(含代码)

[复制链接]
1377|57
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
这一篇可和AD那篇一起stm32外部AD——ADC08060 驱动_居安士的博客-CSDN博客

搜索stm32代码大部分的DMA配置都和内部AD相连,没有单独实现串口DMA的,对于外部AD或者其他应用需要单独启用串口DMA的应用,本篇或许可以给大家以参考

当我们计算的串口波特率 无法满足数据传输速度的时候,我们需要将串口设置为DMA发送(不占用CPU资源),但是这样也不够,这个时候我们需要采用抽帧的方法进行发送,接下来会依次进行详解。
串口DMA配置方法

在配置串口DMA之前,我们要先看一下自己的串口是usart1还是usart2

不同的usart对应着不同的DMA,以及不同的数据流



使用特权

评论回复
沙发
个百zz分点个|  楼主 | 2023-9-30 23:42 | 只看该作者
usart2对应着DMA1通道4的数据流5和数据流6

usart1对应着DMA2通道4的数据流5和数据流7

使用特权

评论回复
板凳
个百zz分点个|  楼主 | 2023-9-30 23:42 | 只看该作者

使用特权

评论回复
地板
个百zz分点个|  楼主 | 2023-9-30 23:42 | 只看该作者
为了方便大家,我把需要修改的地方备注出来(备注乱码忽略),大家直接对应修改即可!!赞赞

使用特权

评论回复
5
个百zz分点个|  楼主 | 2023-9-30 23:43 | 只看该作者
下面是usart2的
//-----------------DMA1配置-------------------------------------
void uart2_init(int bps)      
{
                GPIO_InitTypeDef                                         GPIO_InitStructure;
                USART_InitTypeDef                                 USART_InitStructure;
                NVIC_InitTypeDef                                         NVIC_InitStructure;
               
                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //修改
                RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2,ENABLE);//修改
         
                GPIO_PinAFConfig(GPIOA,GPIO_PinSource2,GPIO_AF_USART2); //GPIOA2¸修改
                GPIO_PinAFConfig(GPIOA,GPIO_PinSource3,GPIO_AF_USART2); //GPIOA3¸修改
               
                //USART2¶Ë¿ÚÅäÖÃ
                GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_3; //修改
                GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; //¸´Óù¦ÄÜ
                GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;        //ËÙ¶È50MHz
                GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //ÍÆÍ츴ÓÃÊä³ö
                GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; //ÉÏÀ­
                GPIO_Init(GPIOA,&GPIO_InitStructure); //修改

                //USART2³õʼ»¯ÉèÖÃ
                USART_InitStructure.USART_BaudRate =bps; //²¨ÌØÂÊÉèÖÃ
                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_CTS; //ÎÞÓ²¼þÊý¾ÝÁ÷¿ØÖÆ
                USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;        //ÊÕ·¢Ä£Ê½
                USART_Init(USART2, &USART_InitStructure); //³õʼ»¯´®¿Ú2
               
                USART_Cmd(USART2, ENABLE); //修改
                USART_DMACmd(USART2,USART_DMAReq_Tx,ENABLE);
                USART_ClearFlag(USART2, USART_FLAG_TC);
                USART_ITConfig(USART2, USART_IT_RXNE, DISABLE); //修改
               
                //Usart2 NVIC ÅäÖÃ
                NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//ÉèÖÃϵͳÖжÏÓÅÏȼ¶·Ö×é0
                NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;//´®¿Ú1ÖжÏͨµÀ
                NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=3;//ÇÀÕ¼ÓÅÏȼ¶3
                NVIC_InitStructure.NVIC_IRQChannelSubPriority =3;        //×ÓÓÅÏȼ¶3
                NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;        //IRQͨµÀʹÄÜ
                NVIC_Init(&NVIC_InitStructure);        //¸ù¾ÝÖ¸¶¨µÄ²ÎÊý³õʼ»¯VIC¼Ä´æÆ÷¡¢
}
void usart2_dma_Config(uint32_t * usart2_dma_buffer, int byteLen)
{
                DMA_InitTypeDef                  DMA_InitStructure;
       
                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //修改
                DMA_DeInit(DMA1_Stream6);
                while (DMA_GetCmdStatus(DMA1_Stream6) != DISABLE){} //µÈ´ýDMA¿ÉÅäÖÃ
               
                /* ÅäÖÃ DMA Stream */
                DMA_InitStructure.DMA_Channel = DMA_Channel_4; //ͨµÀÑ¡Ôñ
                DMA_InitStructure.DMA_PeripheralBaseAddr =((u32)&USART2->DR); //DMAÍâÉèµØÖ·
                DMA_InitStructure.DMA_Memory0BaseAddr = (u32)usart2_dma_buffer; //DMA ´æ´¢Æ÷0µØÖ·
                DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //´æ´¢Æ÷µ½ÍâÉèģʽ
                DMA_InitStructure.DMA_BufferSize =byteLen; //Êý¾Ý´«ÊäÁ¿
                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_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
                DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
                DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //´æ´¢Æ÷Í»·¢µ¥´Î´«Êä
                DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //ÍâÉèÍ»·¢µ¥´Î´«Êä
                DMA_Init(DMA1_Stream6, &DMA_InitStructure); //修改
                DMA_Cmd(DMA1_Stream6, ENABLE);
}
void usart2_dma_ConfigHalfWord(uint16_t * usart2_dma_buffer, int byteLen)
{
                DMA_InitTypeDef                    DMA_InitStructure;
       
                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //修改
                DMA_DeInit(DMA1_Stream6);
                while (DMA_GetCmdStatus(DMA1_Stream6) != DISABLE){} //修改
               
                /* ÅäÖÃ DMA Stream */
                DMA_InitStructure.DMA_Channel = DMA_Channel_4; //ͨµÀÑ¡Ôñ
                DMA_InitStructure.DMA_PeripheralBaseAddr =((u32)&USART2->DR); //DMAÍâÉèµØÖ·
                DMA_InitStructure.DMA_Memory0BaseAddr = (u32        )usart2_dma_buffer; //DMA ´æ´¢Æ÷0µØÖ·
                DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //´æ´¢Æ÷µ½ÍâÉèģʽ
                DMA_InitStructure.DMA_BufferSize =byteLen; //Êý¾Ý´«ÊäÁ¿
                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_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
                DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
                DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //´æ´¢Æ÷Í»·¢µ¥´Î´«Êä
                DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //ÍâÉèÍ»·¢µ¥´Î´«Êä
                DMA_Init(DMA1_Stream6, &DMA_InitStructure); //修改
                DMA_Cmd(DMA1_Stream6, ENABLE);
}
void usart2_dma_ConfigByte(uint8_t * usart2_dma_buffer, int byteLen)
{
                DMA_InitTypeDef  DMA_InitStructure;
       
                RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1,ENABLE); //修改
                DMA_DeInit(DMA1_Stream6);
                while (DMA_GetCmdStatus(DMA1_Stream6) != DISABLE){} //修改
               
                /* ÅäÖÃ DMA Stream */
                DMA_InitStructure.DMA_Channel = DMA_Channel_4; //ͨµÀÑ¡Ôñ
                DMA_InitStructure.DMA_PeripheralBaseAddr =((u32)&USART2->DR); //修改
                DMA_InitStructure.DMA_Memory0BaseAddr = (u32        )usart2_dma_buffer; //修改
                DMA_InitStructure.DMA_DIR = DMA_DIR_MemoryToPeripheral; //´æ´¢Æ÷µ½ÍâÉèģʽ
                DMA_InitStructure.DMA_BufferSize =byteLen; //Êý¾Ý´«ÊäÁ¿
                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_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;         
                DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full;
                DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; //´æ´¢Æ÷Í»·¢µ¥´Î´«Êä
                DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; //ÍâÉèÍ»·¢µ¥´Î´«Êä
                DMA_Init(DMA1_Stream6, &DMA_InitStructure); //修改
                //DMA_SetCurrDataCounter(DMA1_Stream6, byteLen);
                DMA_Cmd(DMA1_Stream6, ENABLE);

使用特权

评论回复
6
个百zz分点个|  楼主 | 2023-9-30 23:43 | 只看该作者
我们除了需要修改串口号,IO口号,还要注意修改一个是数据流(RX和TX要注意区分,我这里是TX) 另一个是DMA内存号

使用特权

评论回复
7
个百zz分点个|  楼主 | 2023-9-30 23:43 | 只看该作者
.h文件别忘了加:


#include "sys.h"
#include <stm32f4xx.h>

void uart2_init(int bps);
void usart2_dma_Config(uint32_t * usart2_dma_buffer, int byteLen);
void usart2_dma_ConfigHalfWord(uint16_t * usart2_dma_buffer, int byteLen);
void usart2_dma_ConfigByte(uint8_t * usart2_dma_buffer, int byteLen);

使用特权

评论回复
8
个百zz分点个|  楼主 | 2023-9-30 23:43 | 只看该作者
DMA串口发送
我们在配置里面,已经将串口DMA发送的代码写过了,就是函数:usart2_dma_ConfigByte(uint8_t * usart2_dma_buffer, int byteLen)

我们是不可以把AD输出的input直接用串口输出的,这样会掉帧,所以所有的数据我们都先放进一个缓冲区,再从缓冲区里面读取。

之后我们只需要调用函数 usart2_dma_ConfigByte(数据缓冲区名称,一次传输的数据个数);

使用特权

评论回复
9
个百zz分点个|  楼主 | 2023-9-30 23:43 | 只看该作者
当我们这样写之后,会发现依然漏掉数据,这是因为仅仅串口DMA发送,只是不占用CPU资源,但是速度上不会有很大提高,我们需要使用“抽帧”方法 :

即计数存多幅图的图像,之后只输出一幅图图像,保留足够的时间给串口进行传输

在这里我们需要设计两个计数器:一个是像素计数器(计数输出一幅图的所有像素个数);另一个是帧计数器(计数输出图的个数)

假如我一幅图是552个像素,那么像素计数器=552的时候,帧计数器应该+1

再利用AD的上升沿作为像素计数器加一的标志

使用特权

评论回复
10
个百zz分点个|  楼主 | 2023-9-30 23:43 | 只看该作者
总结如下:每来一个AD上升沿,我们进行一次中断,将AD输出数据input存一次缓冲区,像素计数器+1;当像素计数器=552的时候,帧计数器+1,当帧计数器=n时(n为多少帧发一次,n越大越慢,可以进行几次尝试,选择最小的n值),启动串口DMA发送

使用特权

评论回复
11
个百zz分点个|  楼主 | 2023-9-30 23:44 | 只看该作者
void TIM4_IRQHandler(void)
{
        int i=0;
        uint8_t frameBufCopy[1024];//缓冲区
        if(TIM_GetITStatus(TIM4,TIM_IT_Update)==SET) //中断
        {

                  frameBuf[pixleInd]=input;
               
                        pixleInd++;     //像素计数器  
                  if(pixleInd>=552)
                        {
                                 pixleInd=0;
                                 frameCount++;//帧计数器
                        }
                        if(frameCount>=100)
                        {
                                frameCount=0;
                               
                           memcpy(frameBufCopy,frameBuf,552);
               usart2_dma_ConfigByte(frameBufCopy,552);//串口发送
                        }

               
        }
        TIM_ClearITPendingBit(TIM4,TIM_IT_Update);  //
//}

}

使用特权

评论回复
12
个百zz分点个|  楼主 | 2023-9-30 23:44 | 只看该作者
我们要注意以下几点

(1)DMA发送的缓冲区和计数器需要定义成全局变量

使用特权

评论回复
13
个百zz分点个|  楼主 | 2023-9-30 23:44 | 只看该作者
extern uint8_t frameBuf[1024];  
extern u16 pixleInd;           
extern u16 frameCount;

使用特权

评论回复
14
个百zz分点个|  楼主 | 2023-9-30 23:44 | 只看该作者
(2)串口DMA发送前拷贝数据

在串口发送的时候,AD还在不断覆盖缓冲区,需要把将要发送的数据拷贝出来,放进一个单独的缓冲区,在这里,使用了memcpy函数

void *memcpy(void*dest, const void *src, size_t n);

使用特权

评论回复
15
个百zz分点个|  楼主 | 2023-9-30 23:44 | 只看该作者
它用来做内存拷贝,你可以拿它拷贝任何数据类型的对象,可以指定拷贝的数据长度;

它由src指向地址为起始地址的连续n个字节的数据复制到以destin指向地址为起始地址的空间内。

使用特权

评论回复
16
个百zz分点个|  楼主 | 2023-9-30 23:44 | 只看该作者
我这里的数据是用眼睛去看,由于视觉残留,少图没关系,如果大家对应数据完整性要求特别高, 而数据本身又是M级别的速率,就不要使用串口,应该使用网口

使用特权

评论回复
17
jackcat| | 2023-10-5 09:19 | 只看该作者
在DMA传输完成时配置和处理DMA中断。DMA传输完成后,您可以执行相应的处理操作,如更新缓冲区指针、发送/接收完成的回调等

使用特权

评论回复
18
jimmhu| | 2023-10-5 10:26 | 只看该作者
接收缓冲区的长度不超过设定的最大长度。如果接收的数据不是连续的,可以关闭串口空闲中断,使用DMA传输完成中断来接收定长数据。

使用特权

评论回复
19
eefas| | 2023-10-5 10:40 | 只看该作者
在进行DMA串口传输之前,需要对DMA通道进行初始化和配置,包括数据传输的基地址、目标地址、传输大小、传输模式等参数。同时还需要设置中断请求和错误处理机制以确保数据传输的可靠性和稳定性。

使用特权

评论回复
20
primojones| | 2023-10-5 11:24 | 只看该作者
DMA传输过程中需要关注数据的传输状态,及时处理可能出现的错误或异常情况

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

50

主题

631

帖子

0

粉丝