目录
概述
本文主要是使用AT32F403A开发板,基于V2库串口1使用dma来传输接收的数据功能,并且配合串口空闲中断(IDLE)来接收不定长的数据,同时把上一章的printf加入,都是使用串口1来做。
串口工具使用的Atlink-ez自带的串口功能。
工程建立、调试工具配置在前面章节有详细介绍。
硬件
硬件方面使用的是参考官方AT32F437 SURF板子而设计的一个AT32F403A开发板,板子上的芯片是AT32F403AVGT7的型号,开发板上面还板载了一个atlink-ez的仿真器,atlink-ez除了可以在线仿真和下载之外还有一个串口的功能,硬件上是通过跳线帽接到了MCU的串口1,pa9/10上面。
如下图是开发板pcb图,以及硬件资源。(左边上角的就是atlink-ez,用usb线接到pc即可):
如下是实物图:
本章的主要内容是使用串口1的dma进行数据的收发,相关原理图部分(jp1的1和2,3和4,分别用跳线帽接起来):
DMA
直接存储器访问(DMA)控制器,主要是用于数据搬运,请求产生后,自动把数据按设定的方式进行搬运,不需要内核来管理,从而增强系统性能并减少处理器的中断生成。
AT32F403A包含 2 个 DMA 控制器。每个控制器各有 7 个 DMA 通道,每个通道管理来自于外设对存储器访问的请求。默认情况下不同的通道是固定对应的外设,AT32的DMA支持弹性映射,弹性dma这个下一章单独讲解如何使用。
默认外设和dma通道关系:
从图可以看出,串口1的收发对应的DMA通道是DMA1的通道5和通道4,所以下面是使用这两个通道来对串口1的数据进行搬运。
软件
流程
软件上主流程是串口1使用DMA1通道5来搬运串口1收到的数据到接收缓存中,由于dma的搬运的数据长度是必须先设定好的,接收时是不能判定外面有多少数据过来的,所以使用串口1空闲中断(IDLE)来判定接收完毕,从通道的剩余长度来计算出串口收到的数据长度从而达到接收不定长度的功能,然后把收到的数据复制到发送缓存里面,同时重新设定DMA1通道5的配置,以继续接收下一包数据;收到一帧数据后,通过DMA1 通道4把发送缓存里面的数据通过DMA1 通道4搬运到串口1的发送寄存器,串口1就可以把数据给发送出来。
初始化
初始化部分包括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:优先级
通道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,因为我们的主流程是收到数据后再发送出来的,所以接收要先开启。对通道的重新配置需要先关闭通道,然后再设置参数。
初始化代码:
/*
*串口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); //使能串口
}
串口1空闲中断(IDLE)中清中断状态,获取dma搬运的数据也就是收到的数据长度了,再把数据复制到发送缓存中,然后重新设定DMA1通道5,接收下一帧数据。这里中断就不需要使用RDBF中断来接收数据,使用RDBF中断来接收数据,每个字节都需要进入一次中断,使用dma后就减少了中断的响应。
中断服务函数:
/*
*串口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,接收下一包数据
}
}
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(开始接收)
}
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 发送
}
测试
通过串口助手往板子发送数据,正常的话会收到相同的数据,经过测试,功能正常。需要注意的是,串口的缓存设置的512的字节长度,DMA1 通道5的长度也设置的512字节,所以发给板子的最大长度不要超512字节。
测试代码:
测试结果:
最后
有问题的可以加QQ群交流,同时相关代码上传到QQ群中。