[AT32F403/403A]

第九章 AT32F403A基于V2库串口 dma接收不定长数据

[复制链接]
894|17
手机看帖
扫描二维码
随时随地手机跟帖
远芳侵古道|  楼主 | 2023-1-31 00:30 | 显示全部楼层 |阅读模式
概述
         本文主要是使用AT32F403A开发板,基于V2库串口1使用dma来传输接收的数据功能,并且使用串口空闲中断(IDLE)来接收不定长的数据,同时把上一章的printf加入,都是使用串口1来做。

         串口工具使用的Atlink-ez自带的串口功能,开发板硬件已经默认接到mcu的串口1上。win10以下的系统需要安装虚拟串口驱动才能正确识别到com口。

         工程建立、调试工具配置在前面章节有详细介绍。



硬件
        硬件方面使用的是雅特力官方发布的AT32F403A开发板,板子上的芯片是AT32F403AVGT7的型号,开发板上面还板载了一个atlink-ez的仿真器,atlink-ez还有一个串口的功能,硬件上是接到了MCU的串口1上。这个atlink-ez也可以掰下来使用。

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:31 | 显示全部楼层
   如下图是开发板pcb图(左边的就是atlink-ez):
1729963d7f0d870362.png

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:32 | 显示全部楼层
DMA
        直接存储器访问(DMA)控制器,主要是用于数据搬运,请求产生后,自动把数据按设定的方式进行搬运,不需要内核来管理,从而增强系统性能并减少处理器的中断生成。

         AT32F403A包含 2 个 DMA 控制器。每个控制器各有 7 个 DMA 通道,每个通道管理来自于外设对存储器访问的请求。默认情况下不同的通道是固定对应的外设,AT32的DMA支持弹性映射,弹性dma这个下一章单独讲解如何使用。   

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:34 | 显示全部楼层
默认外设和dma通道关系:
9268563d7f182aac39.png

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:34 | 显示全部楼层
        从图可以看出,串口1的收发对应的DMA通道是DMA1的通道5和通道4,所以下面是使用这两个通道来对串口1的数据进行搬运。

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:35 | 显示全部楼层
软件
        流程
        软件上主流程是串口1使用DMA1通道5来搬运串口1收到的数据到接收缓存中,由于dma的搬运的数据长度是必须先设定好的,接收时是不能判定外面有多少数据过来的,所以使用串口1空闲中断(IDLE)来判定接收完毕,从通道的剩余长度来计算出串口收到的数据长度从而达到接收不定长度的功能,然后把收到的数据复制到发送缓存里面,同时重新设定DMA1通道5的配置,以继续接收下一包数据;收到一帧数据后,通过DMA1 通道4把发送缓存里面的数据通过DMA1 通道4搬运到串口1的发送寄存器,串口1就可以把数据给发送出来。

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:36 | 显示全部楼层
初始化
        初始化部分包括gpio的初始化、串口的初始化、DMA的初始化。

        串口1使用默认的IO,PA9、PA10,PA9推挽输出模式,PA10输入上拉模式。

        DMA的主要参数就是地址,传输方向,数据长度,数据宽度,循环模式,下面是dma的结构体。

        typedef struct
{
  uint32_t                               peripheral_base_addr;    /*!< base addrress for peripheral */
  uint32_t                               memory_base_addr;        /*!< base addrress for memory */
  dma_dir_type                           direction;               /*!< dma transmit direction, peripheral as source or as destnation  */
  uint16_t                               buffer_size;             /*!< counter to transfer */
  confirm_state                          peripheral_inc_enable;   /*!< periphera address increment after one transmit */
  confirm_state                          memory_inc_enable;       /*!< memory address increment after one transmit */
  dma_peripheral_data_size_type          peripheral_data_width;   /*!< peripheral data width for transmit */
  dma_memory_data_size_type              memory_data_width;       /*!< memory data width for transmit */
  confirm_state                          loop_mode_enable;        /*!< when circular mode enable, buffer size will reload if count to 0 */
  dma_priority_level_type                priority;                /*!< dma priority can choose from very high, high, dedium or low */
} dma_init_type;

        peripheral_base_addr:外设基地址,一般指外设的数据寄存器地址。

        memory_base_addr:内存基地址,指的是我们定义存储数据的地址.

        direction:传输方向,支持外设到内存,内存到外设,内存到内存。

        buffer_size:dma传输的数据个数。

        peripheral_inc_enable:外设地址自加。

        memory_inc_enable:内存地址自加。

        peripheral_data_width:外设数据的宽度。8/16/32bit

        memory_data_width:内存数据的宽度。8/16/32bit

        loop_mode_enable:循环模式。开启后当传输完设定的数据个数之后,自动又开始传输,循环

        priority:优先级

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:36 | 显示全部楼层
通道4是串口1的发送通道,所以需要设定peripheral_base_addr是串口1的数据寄存器地址,memory_base_addr就是我们定义的txbuf的地址,direction是内存到外设模式,buffer_size就是要发送的数据长度,peripheral_inc_enable不使能,memory_inc_enable使能不然就只发送一个相同的数据了,peripheral_data_width和memory_data_width都是8bit,loop_mode_enable关闭,priority中等优先级即可。

        通道5是串口1的接收通道,所以需要设定peripheral_base_addr是串口1的数据寄存器地址,memory_base_addr就是我们定义的rxbuf的地址,direction是外设到内存模式,buffer_size就是接收的数据长度,peripheral_inc_enable不使能,memory_inc_enable使能不然就只把数据一直搬运到rxbuf的第一个字节。数据都覆盖掉了,peripheral_data_width和memory_data_width都是8bit,loop_mode_enable关闭,priority中等优先级即可。

        开始的时候对通道4的长度和地址先不用设定,需要使用的时候,再设定这两个参数;使能串口1的DMA接收以及使能DMA1通道5,因为我们的主流程是收到数据后再发送出来的,所以接收要先开启。对通道的重新配置需要先关闭通道,然后再设置参数。

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:36 | 显示全部楼层
初始化代码:
/*
*串口1  DMA配置函数
*IO:PA9/PA10
*blound: 波特率
*数据位 8,停止位 1,无校验
*DMA1,通道4/5
*/


void usart1_dma_init(u32 bound)
{
        gpio_init_type gpio_init_struct;
        dma_init_type dma_init_struct;
   
       
        /*Enable the UART Clock*/
        crm_periph_clock_enable(CRM_GPIOA_PERIPH_CLOCK, TRUE);                //开启GPIOA的时钟
        crm_periph_clock_enable(CRM_USART1_PERIPH_CLOCK, TRUE);                //开启USART1的时钟
        crm_periph_clock_enable(CRM_DMA1_PERIPH_CLOCK, TRUE);                //开启DMA1的时钟

        gpio_default_para_init(&gpio_init_struct);
        /* Configure the UART1 TX pin */
        gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;        //较大电流推动/吸入能力
    gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;                                //推挽输出
    gpio_init_struct.gpio_mode = GPIO_MODE_MUX;                                                                //复用
    gpio_init_struct.gpio_pins = GPIO_PINS_9;                                                                //PA9
    gpio_init_struct.gpio_pull = GPIO_PULL_NONE;                                                        //无上下拉
    gpio_init(GPIOA, &gpio_init_struct);

        /* Configure the UART1 RX pin */
    gpio_init_struct.gpio_drive_strength = GPIO_DRIVE_STRENGTH_STRONGER;        //较大电流推动/吸入能力
    gpio_init_struct.gpio_out_type  = GPIO_OUTPUT_PUSH_PULL;                                //推挽输出(输入模式,无效)
    gpio_init_struct.gpio_mode = GPIO_MODE_INPUT;                                                        //输入模式
    gpio_init_struct.gpio_pins = GPIO_PINS_10;                                                                //PA10
    gpio_init_struct.gpio_pull = GPIO_PULL_UP;                                                                //上拉
    gpio_init(GPIOA, &gpio_init_struct);
       
        dma_reset(DMA1_CHANNEL4);
        dma_default_para_init(&dma_init_struct);  
    dma_init_struct.buffer_size = 0;                                                               //内存大小
    dma_init_struct.direction = DMA_DIR_MEMORY_TO_PERIPHERAL;                   //外设地址为目的地址
    dma_init_struct.memory_base_addr = (uint32_t)0;                             //内存地址
    dma_init_struct.memory_data_width = DMA_MEMORY_DATA_WIDTH_BYTE;             //内存数据的宽度
    dma_init_struct.memory_inc_enable = TRUE;                                   //内存地址递增打开
    dma_init_struct.peripheral_base_addr = (uint32_t)&USART1->dt;               //外设地址
    dma_init_struct.peripheral_data_width = DMA_PERIPHERAL_DATA_WIDTH_BYTE;     //外设数据的宽度
    dma_init_struct.peripheral_inc_enable = FALSE;                              //外设地址递增关闭
    dma_init_struct.priority = DMA_PRIORITY_MEDIUM;                             //中等优先级
    dma_init_struct.loop_mode_enable = FALSE;                                                                        //不循环
        dma_init(DMA1_CHANNEL4, &dma_init_struct);
       
        dma_reset(DMA1_CHANNEL5);
        dma_init_struct.peripheral_base_addr=(uint32_t)&USART1->dt;                 //外设地址
    dma_init_struct.memory_base_addr=(uint32_t)Muartnum[0].Uartrxbuf;           //内存地址
    dma_init_struct.direction=DMA_DIR_PERIPHERAL_TO_MEMORY;                     //外设地址为源地址
    dma_init_struct.buffer_size=USART_REC_LEN;
        dma_init(DMA1_CHANNEL5, &dma_init_struct);
       
        usart_dma_receiver_enable(USART1,TRUE);                                                //使能串口dma接收
        dma_channel_enable(DMA1_CHANNEL5, TRUE);                                        //使能通道5
       
        nvic_irq_enable(USART1_IRQn, 0, 0);                                                      //使能串口1中断,优先级0,次优先级0
       
        /*Configure UART param*/
    usart_init(USART1, bound, USART_DATA_8BITS, USART_STOP_1_BIT);                //波特率,8数据位,1停止位
    usart_hardware_flow_control_set(USART1,USART_HARDWARE_FLOW_NONE);        //无硬件流操作
    usart_parity_selection_config(USART1,USART_PARITY_NONE);                        //无校验
    usart_transmitter_enable(USART1, TRUE);                                                                //使能发送
    usart_receiver_enable(USART1, TRUE);                                                                //使能接收
       
        usart_interrupt_enable(USART1, USART_IDLE_INT, TRUE);                                //使能串口空闲中断
    usart_enable(USART1, TRUE);                                                                                        //使能串口
       
}

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:36 | 显示全部楼层
     串口1空闲中断(IDLE)中清中断状态,获取dma搬运的数据也就是收到的数据长度了,再把数据复制到发送缓存中,然后重新设定DMA1通道5,接收下一帧数据。这里中断就不需要使用RDBF中断来接收数据,使用RDBF中断来接收数据,每个字节都需要进入一次中断,使用dma后就减少了中断的响应。

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:38 | 显示全部楼层
  中断服务函数:
/*
*串口1中断服务函数
*
*
*/

void USART1_IRQHandler(void)
{       
        uint8_t clear;
        if(usart_flag_get(USART1, USART_IDLEF_FLAG) != RESET)               // USART1总线空闲
        {
                clear=USART1->sts;                                              // USART1清除空闲中断标志位
                clear=USART1->dt;                                                                                                // USART1清除空闲中断标志位
                clear&=0;
                Muartnum[0].Uartrxsta = 1;                                                                                // USART1接收完成标志位
                Muartnum[0].Uartrxcut=USART_REC_LEN-dma_data_number_get(DMA1_CHANNEL5);        //获取DMA通道5收到的数据长度
                Muartnum[0].Uarttxcut=Muartnum[0].Uartrxcut;
                memcpy(Muartnum[0].Uarttxbuf,Muartnum[0].Uartrxbuf,Muartnum[0].Uarttxcut);                //数据复制到txbuf里面
                usartdmarecv(Muartnum[0].Uartrxbuf,USART_REC_LEN);                                //重新设定dma,接收下一包数据
        }
               
}

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:38 | 显示全部楼层
   DMA1通道5设置函数:(重新使能通道)
/**
*        串口1的DMA接收设置函数
*         data:接收数据的buf地址
*        len:设定接收的最大数据长度
*/

void usartdmarecv(u8 *data,u16 len)
{

    dma_flag_clear(DMA1_FDT5_FLAG);                 //清标志  
    dma_channel_enable(DMA1_CHANNEL5, FALSE);       //关闭USART1 DMA 接收
    usart_dma_receiver_enable(USART1,FALSE);        //关闭通道5
    DMA1_CHANNEL5->dtcnt=len;                       //接收的数据长度
    DMA1_CHANNEL5->maddr=(uint32_t)data;            //存放数据buf地址
    usart_dma_receiver_enable(USART1,TRUE);         //开启USART1 DMA 接收
    dma_channel_enable(DMA1_CHANNEL5, TRUE);        //开启通道5(开始接收)       

}

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:39 | 显示全部楼层
DMA1通道5设置函数:(重新使能通道)
/**
*        串口1的DMA接收设置函数
*         data:接收数据的buf地址
*        len:设定接收的最大数据长度
*/

void usartdmarecv(u8 *data,u16 len)
{

    dma_flag_clear(DMA1_FDT5_FLAG);                 //清标志  
    dma_channel_enable(DMA1_CHANNEL5, FALSE);       //关闭USART1 DMA 接收
    usart_dma_receiver_enable(USART1,FALSE);        //关闭通道5
    DMA1_CHANNEL5->dtcnt=len;                       //接收的数据长度
    DMA1_CHANNEL5->maddr=(uint32_t)data;            //存放数据buf地址
    usart_dma_receiver_enable(USART1,TRUE);         //开启USART1 DMA 接收
    dma_channel_enable(DMA1_CHANNEL5, TRUE);        //开启通道5(开始接收)       

}

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:40 | 显示全部楼层
  DMA1通道4发送函数:(设置dma长度和内存地址)
/**
*        串口1的DMA循环发送函数
*   data:要发送的buf地址
*        len:数据长度
*/

void usartdmasend(u8 *data,u16 len)
{

    DMA1_CHANNEL4->dtcnt=len;                           //发送的数据长度
    DMA1_CHANNEL4->maddr=(uint32_t)data;                //数据buf地址
    usart_dma_transmitter_enable(USART1,TRUE);          //开启USART1 DMA 发送
    dma_channel_enable(DMA1_CHANNEL4, TRUE);            //开启通道4(开始发送)
    while(dma_flag_get(DMA1_FDT4_FLAG)==RESET );        //等待传输完成
    dma_flag_clear(DMA1_FDT4_FLAG);                     //清标志
    dma_channel_enable(DMA1_CHANNEL4, FALSE);           //关闭通道4
    usart_dma_transmitter_enable(USART1,FALSE);         //关闭USART1 DMA 发送
      
}

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:40 | 显示全部楼层
测试
        通过串口助手往板子发送数据,正常的话会收到相同的数据,经过测试,功能正常。需要注意的是,串口的缓存设置的512的字节长度,DMA1 通道5的长度也设置的512字节,所以发给板子的最大长度不要超512字节。

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:44 | 显示全部楼层
  测试代码:     

2960663d7f3fe6ae51.png

使用特权

评论回复
远芳侵古道|  楼主 | 2023-1-31 00:45 | 显示全部楼层
    测试结果: 198363d7f4109ee52.png

使用特权

评论回复
tzd_311| | 2023-3-25 16:43 | 显示全部楼层
远芳侵古道 发表于 2023-1-31 00:40
测试
        通过串口助手往板子发送数据,正常的话会收到相同的数据,经过测试,功能正常。需要注意的是 ...

我在AT32F415上试过DMA串口接收,发现一个问题,例如当接收缓存设为512字节时,DMA传送次数也是设置512次时,每次用串口助手连续发送512个字节,引起DMA传输完成中断后,串口再也无法接收了。我在DMA中断中做如下处理:清中断标志位,禁用DMA通道,重设接收缓存区,再重新配置DMA通道参数,重新开户DMA接收。

使用特权

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

本版积分规则

62

主题

735

帖子

0

粉丝