0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FreeRTOS串口DMA收发不定长数据

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-09-26 09:08 次阅读

基础知识点

DMA

DMA(Direct Memory Access),即直接内存存储,在一些数据的传输中,如串口、SPI等,采用DMA方式,传输过程不需要CPU参与,可用让CPU有更多的时间处理其他的事情。

STM32F4的DMA通道选择如下:

pYYBAGMwZCmADMBdAAOMBqmQXto194.png

接下来的程序思路如下:

pYYBAGMwZDSAJNdMAADbHRWS2Ks224.png

编程要点

DMA发送

串口DMA发送配置

由于是发送不定长的数据,先不需要配置发送的长度,在每次的发送时,再配置。

//=======================================
//串口DMA发送配置
//=======================================
void dma_uart_tx_init()
{
    DMA_InitTypeDef  DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
    
    DMA_DeInit(Uart_Tx_DMAStream);//使用----->DMA2_Stream7
    while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}//等待DMA可配置 

    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道选择
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //目的:DMA外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)SendBuff;              //源:DMA存储器0地址
    DMA_InitStructure.DMA_DIR                = DMA_DIR_MemoryToPeripheral; //方向:存储器到外设模式
    //DMA_InitStructure.DMA_BufferSize       = BUF_SIZE;                   //长度:数据传输量(先不配置)
    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优先级:中等优先级
    DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 
    DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小
    DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存储器单次传输
    DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外设单次传输
    DMA_Init(Uart_Tx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
    
    //中断配置
    DMA_ITConfig(Uart_Tx_DMAStream,DMA_IT_TC,ENABLE);  //配置DMA发送完成后产生中断
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = DMA2_Stream7_IRQn;//
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=7;//抢占优先级8
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;       //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
    
    USART_DMACmd(USART1,USART_DMAReq_Tx,ENABLE);  //使能串口1的DMA发送
    DMA_Cmd (Uart_Tx_DMAStream,DISABLE);//先不要使能DMA!           
}

DMA发送完成中断

DMA发送完成后,触发DMA发送完成中断,这里可用释放自定义的DMA发送完成信号量,表明下次的DMA传输可用进行。

//=======================================
//DMA发送完成中断服务程序
//=======================================
void DMA2_Stream7_IRQHandler(void)
{
    BaseType_t xHigherPriorityTaskWoken;
    //printf("ooooooorn");
    if(DMA_GetITStatus(Uart_Tx_DMAStream,DMA_IT_TCIF7)!= RESET) //检查DMA传输完成中断 DMA_IT_TCIF7
    {
        DMA_ClearITPendingBit(Uart_Tx_DMAStream,DMA_IT_TCIF7); 
        //printf("dma tx okrn");
        if(uartDMATCSemaphore!=NULL)
        {
            //释放二值信号量
            xSemaphoreGiveFromISR(uartDMATCSemaphore,&xHigherPriorityTaskWoken);    //释放DMA传输完成二值信号量
        }
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
    }
}
 

DMA发送函数接口

//=======================================
//串口DMA发送函数
//======================================= 
void uart_DMA_send(u8 *str,u16 ndtr)
{
    u8 i;
    u8 *p=str;
    
    while(xSemaphoreTake(uartDMATCSemaphore,2)!=pdTRUE);//获取信号量,等待DMA发送可用
    
    DMA_Cmd(Uart_Tx_DMAStream, DISABLE);                      //关闭DMA传输 
    while (DMA_GetCmdStatus(Uart_Tx_DMAStream) != DISABLE){}    //确保DMA可以被设置  
    DMA_SetCurrDataCounter(Uart_Tx_DMAStream,ndtr);          //数据传输量 
    for(i=0;i;i++)>

DMA接收

串口DMA接收配置

需要配置一个接收地址和一个接收长度,用于DMA接收数据的暂存。

//=======================================
//串口DMA接收配置
//=======================================
void dma_uart_rx_init()
{
    DMA_InitTypeDef  DMA_InitStructure;
    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA2,ENABLE);//DMA2时钟使能 
    
    DMA_DeInit(Uart_Rx_DMAStream);//使用----->DMA2_Stream5
    while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}//等待DMA可配置 

    /* 配置 DMA Stream */
    DMA_InitStructure.DMA_Channel            = DMA_Channel_4;              //通道选择
    DMA_InitStructure.DMA_PeripheralBaseAddr = (u32)&USART1->DR;           //源:DMA外设地址
    DMA_InitStructure.DMA_Memory0BaseAddr    = (u32)ReceiveBuff;           //目的:DMA存储器0地址
    DMA_InitStructure.DMA_DIR                = DMA_DIR_PeripheralToMemory; //方向:外设到存储器模式
    DMA_InitStructure.DMA_BufferSize         = BUF_SIZE;                   //长度:数据传输量
    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优先级:中等优先级
    DMA_InitStructure.DMA_FIFOMode           = DMA_FIFOMode_Disable;       //FIFO模式 
    DMA_InitStructure.DMA_FIFOThreshold      = DMA_FIFOThreshold_Full;     //FIFO大小
    DMA_InitStructure.DMA_MemoryBurst        = DMA_MemoryBurst_Single;     //存储器单次传输
    DMA_InitStructure.DMA_PeripheralBurst    = DMA_PeripheralBurst_Single; //外设单次传输
    DMA_Init(Uart_Rx_DMAStream, &DMA_InitStructure);//初始化DMA Stream
    
    USART_DMACmd(USART1,USART_DMAReq_Rx,ENABLE);  //使能串口1的DMA接收
    DMA_Cmd (Uart_Rx_DMAStream,ENABLE);//使能          
}

串口空闲中断

串口空闲中断的作用与上一篇介绍的一样,都是在发送完一串字符后被触发,这次由于使用了DMA接收,所以接收的数据在DMA缓冲区,且接收的数据长度可用根DMA接收通道的总长度与剩余长度的差值来计算,将接收的数据复制出来使用即可,同时释放自定义的串口空闲信号量,以便其它任务可用及时获取串口接收到的数据。

//=======================================
//串口1空闲中断服务程序,用于DMA接收
//=======================================
void USART1_IRQHandler(void)                    
{
    uint8_t data;//接收数据暂存变量
    BaseType_t xHigherPriorityTaskWoken;
    
    if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET)//空闲中断
    {
        data = USART1->SR;
        data = USART1->DR;
        
        DMA_Cmd(Uart_Rx_DMAStream,DISABLE);//关闭DMA接收
        while (DMA_GetCmdStatus(Uart_Rx_DMAStream) != DISABLE){}    //确保DMA可以被设置 
        rx_cnt = BUF_SIZE - DMA_GetCurrDataCounter(Uart_Rx_DMAStream);//得到真正接收数据个数  
        DMA_SetCurrDataCounter(Uart_Rx_DMAStream,BUF_SIZE);//重新设置接收数据个数    
        //printf("rx_cnt:%drn",rx_cnt);
        memcpy(rxbuf,ReceiveBuff,rx_cnt);//先复制出来,防止下次的数据来了之后将其覆盖
        DMA_ClearFlag(Uart_Rx_DMAStream,DMA_FLAG_TCIF5 | DMA_FLAG_FEIF5 | DMA_FLAG_DMEIF5 | DMA_FLAG_TEIF5 | DMA_FLAG_HTIF5);//这里的各种标志还没搞懂
        DMA_Cmd(Uart_Rx_DMAStream,ENABLE); //开启DMA接收
            
        if(uartRxIDLESemaphore!=NULL)
        {
            //printf("nnnnnnnrn");
            //释放二值信号量
            xSemaphoreGiveFromISR(uartRxIDLESemaphore,&xHigherPriorityTaskWoken);//释放串口空闲中断二值信号量
        }
        portYIELD_FROM_ISR(xHigherPriorityTaskWoken);//如果需要的话进行一次任务切换
    }
} 

串口配置与测试任务

串口配置

基础的GPIO配置,以及串口空闲中断配置,并调用上面的串口DMA发送与接收配置。

//=======================================
//串口配置
//=======================================
void uart_init(u32 bound)
{
    //GPIO端口设置
    GPIO_InitTypeDef GPIO_InitStructure;
    USART_InitTypeDef USART_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;

    RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA,ENABLE); //使能GPIOA时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1,ENABLE);//使能USART1时钟

    //串口1对应引脚复用映射
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource9,GPIO_AF_USART1); //GPIOA9复用为USART1
    GPIO_PinAFConfig(GPIOA,GPIO_PinSource10,GPIO_AF_USART1); //GPIOA10复用为USART1

    //USART1端口配置
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10; //GPIOA9与GPIOA10
    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); //初始化PA9,PA10

    //USART1 初始化设置
    USART_InitStructure.USART_BaudRate            = bound;                         //波特率设置
    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_None;//无硬件数据流控制
    USART_InitStructure.USART_Mode                = USART_Mode_Rx | USART_Mode_Tx; //收发模式
    USART_Init(USART1, &USART_InitStructure); //初始化串口1

    //DMA Config
    dma_uart_tx_init();//串口DMA发送配置
    dma_uart_rx_init();//串口DMA接收配置
    
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口空闲中断
    //Usart1 NVIC 配置
    NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;       //串口1中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=6; //抢占优先级8
    NVIC_InitStructure.NVIC_IRQChannelSubPriority =0;       //子优先级0
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;         //IRQ通道使能
    NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化VIC寄存器
    
    USART_Cmd(USART1, ENABLE);  //使能串口1 
}

测试任务

创建DMA发送完成信号量和串口空闲信号量,并先释放DMA发送完成信号量,用于第一次DMA发送时获取信号量。然后测试两条DMA发送不定长字符串,最后测试DMA接收不定长字符串。

//打印任务函数(测试任务)
void print_task(void *pvParameters)
{
    //创建二值信号量
    uartDMATCSemaphore = xSemaphoreCreateBinary();
    uartRxIDLESemaphore = xSemaphoreCreateBinary();
    xSemaphoreGive(uartDMATCSemaphore);
    
    u8 str1[]="ma nong ai xue xirn";
    uart_DMA_send(str1,sizeof(str1));
    
    u8 str2[]="xxpcb.github.iorn";
    uart_DMA_send(str2,sizeof(str2));
    
    BaseType_t err = pdFALSE;
    while(1)
    {
        err=xSemaphoreTake(uartRxIDLESemaphore,5);  //获取信号量
        if(err==pdTRUE)                         //获取信号量成功
        {  
            uart_DMA_send("receive:",sizeof("receive:"));
            uart_DMA_send(rxbuf,rx_cnt);
            uart_DMA_send("rn",sizeof("rn"));
            
            rx_cnt=0;
        }
    }
}

实验结果

通过串口助手,可以先接收到DMA发送的两个字符串(第一条hello是测试串口的,不是DMA发的),然后通过串口调试助手发送两次nice to meet you,测试DMA接收。

hello
ma nong ai xue xi
xxpcb.github.io
receive:nice to meet you
receive:nice to meet you

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 串口
    +关注

    关注

    14

    文章

    1483

    浏览量

    74523
  • dma
    dma
    +关注

    关注

    3

    文章

    535

    浏览量

    99047
  • RTOS
    +关注

    关注

    20

    文章

    776

    浏览量

    118798
  • FreeRTOS
    +关注

    关注

    12

    文章

    473

    浏览量

    61349
收藏 人收藏

    评论

    相关推荐

    FreeRTOS串口中断接收不定长数据与二值信号量的使用

    FreeRTOS例程,使用串口中断接收不定长数据,以及二值信号量的使用
    的头像 发表于 09-26 09:02 3513次阅读
    <b class='flag-5'>FreeRTOS</b><b class='flag-5'>串口</b>中断接收<b class='flag-5'>不定长</b>的<b class='flag-5'>数据</b>与二值信号量的使用

    stm32串口怎么用DMA接收不定长数据?

    stm32串口怎么用DMA接收不定长数据,求大神点播1. 网上查到,空闲中断+DMA可以接收不定长
    发表于 08-21 09:21

    DMA如何接收不定长数据

    STM32串口DMA如何接收不定长数据呢?
    发表于 12-21 07:39

    STM32F103 串口 +DMA中断实现数据收发 精选资料分享

    01 STM32F103 串口DMA + 空闲中断 实现不定长数据收发02文章目录1. 代码讲解1.1 uart_
    发表于 08-13 08:39

    不定长数据接收的原理是什么?怎么实现串口数据不定长接收?

    不定长数据接收的原理是什么?怎么实现串口数据不定长接收?
    发表于 11-16 08:11

    STM32HAL库串口空闲中断+DMA是如何去实现串口接收不定长数据

    STM32HAL库串口空闲中断+DMA是如何去实现串口接收不定长数据的?如何使用CubeMX去配置STM32呢?
    发表于 11-16 09:01

    请问串口DMA+环形缓冲区如何实现不定长度的数据收发

    请问串口DMA+环形缓冲区如何实现不定长度的数据收发
    发表于 12-08 06:13

    串口DMA+中断的形式如何接收不定长数据

    串口DMA+中断的形式如何接收不定长数据
    发表于 12-08 06:07

    如何去实现stm32f405串口DMA+空闲中断不定长数据收发代码

    如何去实现stm32f405串口DMA+空闲中断不定长数据收发代码?
    发表于 12-08 07:36

    HAL库的DMA+CobeMx方式下的不定长收发

    STM32L051双串口DMA方式不定长收发HAL库的DMA+CobeMx方式下的不定长
    发表于 01-20 06:25

    请问STM32 DMA串口接收不定长数据的过程是怎样的?

    请问STM32 DMA串口接收不定长数据的过程是怎样的?
    发表于 02-18 08:00

    STM32 DMA串口接收不定长数据

    STM32 DMA串口接收不定长数据
    发表于 12-24 18:50 40次下载
    STM32  <b class='flag-5'>DMA</b><b class='flag-5'>串口</b>接收<b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    STM32之串口DMA接收不定长数据

    目录STM32之串口DMA接收不定长数据引言DMA简介什么是DMA在STM32的
    发表于 12-24 19:03 30次下载
    STM32之<b class='flag-5'>串口</b><b class='flag-5'>DMA</b>接收<b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    STM32F429 标准库 串口完成中断+DMA 接收不定长数据

    之前写过STM32F103的串口+DMA不定长数据接收驱动,近期在设计STM32F429系列单片机标准库函数 串口+DAM 接收
    发表于 12-24 19:03 21次下载
    STM32F429 标准库  <b class='flag-5'>串口</b>完成中断+<b class='flag-5'>DMA</b>  接收<b class='flag-5'>不定长</b><b class='flag-5'>数据</b>

    STM32CubeMX之串口接收不定长数据

    基本串口通信通常只能接收到定长数据,无法稳定接收不定长数据,本章介绍利用STM32单片机的IDLE空闲中断,接收
    的头像 发表于 05-11 09:59 2261次阅读
    STM32CubeMX之<b class='flag-5'>串口</b>接收<b class='flag-5'>不定长</b><b class='flag-5'>数据</b>