GD32F30x系列的USART支持DMA功能,但不是所有的都支持,如手册中说明了UART4:
这里使用USART的DMA功能+串口空闲中断进行数据的收发,可以与上一节的串口接收中断收发对比,DMA的原理以及说明这里就不再赘述了,可自行查找资料了解一下。
1、先创建一个usart_dma.c和usart_dma.h文件,并放到对应的文件夹中,如下图所示:
并将创建好的文件添加到工程中,以及将gd32f30x_dma.c库文件添加到keil项目工程中,如下图所示:
2、实现串口的初始化以及DMA收发功能—这里以USART2为例
//usart_dma.c文件
#include "usart_dma.h"
//<<<<<<<<<<<<<<<<宏定义<<<<<<<<<<<<<<<<
#define USART_TX_RX_BUFF_LEN 256//DMA缓存数据大小
#define USART_DMA_IDLE 0//DMA状态空闲
#define USART_DMA_BUSY 1//DMA状态忙
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//<<<<<<<<<<<<<<<<结构定义<<<<<<<<<<<<<<<<
#pragma pack(1)
typedef struct
{
uint8_t dma_tx_buf[USART_TX_RX_BUFF_LEN];//缓存DMA要发送的数据
uint8_t dma_rx_buf[USART_TX_RX_BUFF_LEN];//缓存DMA接收到的数据
uint8_t dma_tx_state;//DMA发送状态
uint8_t dma_rx_state;//DMA接收状态
}USART_DataInfo;
#pragma pack()
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
USART_DataInfo g_usart_DataInfo;
/*
串口初始化,使用DMA模式
*/
void gd32_usart_dma_init(void)
{
usart_deinit(USART2);
usart_disable(USART2);
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_USART2);
/* connect port to USARTx_Tx */
gpio_init(GPIOB, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
/* connect port to USARTx_Rx */
gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_50MHZ, GPIO_PIN_11);
nvic_irq_enable(USART2_IRQn,1,0);
usart_interrupt_enable(USART2,USART_INT_IDLE); //空闲中断
//usart_interrupt_enable(USART2,USART_INT_RBNE);//接收中断
usart_baudrate_set(USART2,38400);//波特率设置
usart_parity_config(USART2,USART_PM_NONE);//校验位设置
usart_word_length_set(USART2,USART_WL_8BIT);//数据位设置
usart_stop_bit_set(USART2,USART_STB_1BIT);//停止位设置
usart_hardware_flow_rts_config(USART2, USART_RTS_DISABLE);//
usart_hardware_flow_cts_config(USART2, USART_CTS_DISABLE);//硬件流控设置
usart_data_first_config(USART2,USART_MSBF_LSB);//发送模式--LSB
usart_transmit_config(USART2,USART_TRANSMIT_ENABLE);//发送使能
usart_receive_config(USART2,USART_RECEIVE_ENABLE);//接收使能
usart_enable(USART2);//使能串口
gd32_usart_dma_tx_init();//DMA发送初始化
gd32_usart_dma_rx_init();//DMA接收初始化
}
/*
USART2 DMA发送初始化
*/
void gd32_usart_dma_tx_init(void)
{
dma_parameter_struct dma_init_struct;
/* enable DMA1 */
rcu_periph_clock_enable(RCU_DMA0);
/* deinitialize DMA channel(USART tx) */
dma_deinit(DMA0, DMA_CH1);
dma_init_struct.periph_addr = (uint32_t)(&USART_DATA(USART2));
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_init_struct.memory_addr = NULL;
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_init_struct.number = 0;
dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL;
dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init(DMA0, DMA_CH1, &dma_init_struct);
//configure DMA mode
dma_circulation_disable(DMA0, DMA_CH1);
dma_memory_to_memory_disable(DMA0, DMA_CH1);
usart_dma_transmit_config(USART2, USART_DENT_ENABLE);
nvic_irq_enable(DMA0_Channel1_IRQn,2,0);
dma_interrupt_enable(DMA0, DMA_CH1, DMA_INT_FTF|DMA_INT_ERR);
g_usart_DataInfo.dma_tx_state = USART_DMA_IDLE;
return;
}
/*
USART2 DMA接收初始化
*/
void gd32_usart_dma_rx_init(void)
{
dma_parameter_struct dma_parameter;
/* enable DMA0 */
rcu_periph_clock_enable(RCU_DMA0);
/* 接收 dm0 channel5(USART1 rx) */
dma_deinit(DMA0,DMA_CH2);
dma_parameter.periph_addr = (uint32_t)(&USART_DATA(USART2));
dma_parameter.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
dma_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_parameter.memory_addr = (uint32_t)&g_usart_DataInfo.dma_rx_buf[0];
dma_parameter.memory_width = DMA_MEMORY_WIDTH_8BIT;
dma_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_parameter.number = USART_TX_RX_BUFF_LEN;
dma_parameter.direction = DMA_PERIPHERAL_TO_MEMORY;
dma_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_init(DMA0, DMA_CH2, &dma_parameter);
/* configure DMA mode */
dma_channel_enable(DMA0, DMA_CH2);
dma_circulation_disable(DMA0, DMA_CH2);
dma_memory_to_memory_disable(DMA0, DMA_CH2);
usart_dma_receive_config(USART2, USART_DENR_ENABLE);
nvic_irq_enable(DMA0_Channel2_IRQn, 2, 1);
dma_interrupt_enable(DMA0, DMA_CH2, DMA_INT_FTF|DMA_INT_ERR);
g_usart_DataInfo.dma_rx_state = USART_DMA_BUSY;
}
/*
USART2 DMA发送数据
*/
void gd32_usart_dma_send(uint8_t *p_buff,uint32_t data_len)
{
dma_channel_enum dma_channel;
if(data_len >= USART_TX_RX_BUFF_LEN || USART_DMA_BUSY == g_usart_DataInfo.dma_tx_state)
return;
memcpy(g_usart_DataInfo.dma_tx_buf, p_buff, data_len);
g_usart_DataInfo.dma_tx_state = USART_DMA_BUSY;
dma_channel_disable(DMA0, DMA_CH1);
dma_memory_address_config(DMA0, DMA_CH1,(uint32_t)&g_usart_DataInfo.dma_tx_buf[0]);
dma_transfer_number_config(DMA0, DMA_CH1, data_len);
dma_channel_enable(DMA0, DMA_CH1);
}
/*
USART2 获取DMA接收数据
*/
uint32_t gd32_usart_dma_get_recv_data(uint8_t *recv_buff,uint32_t data_len)
{
uint32_t recv_len=0;
if(data_len >= USART_TX_RX_BUFF_LEN || USART_DMA_BUSY == g_usart_DataInfo.dma_rx_state)
{
return 0;
}
recv_len = USART_TX_RX_BUFF_LEN - dma_transfer_number_get(DMA0, DMA_CH2);
if(recv_len > 0)
{
if(recv_len> data_len)
{
recv_len = data_len;
}
memcpy(recv_buff, g_usart_DataInfo.dma_rx_buf, recv_len);
}
gd32_usart_dma_rx_init();
return recv_len;
}
/*
串口接收中断函数
*/
void USART2_IRQHandler(void)
{
if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_ERR_ORERR) != RESET
||usart_interrupt_flag_get(USART2,USART_INT_FLAG_ERR_NERR) != RESET
||usart_interrupt_flag_get(USART2,USART_INT_FLAG_ERR_FERR) != RESET)
{
usart_interrupt_flag_clear(USART2,USART_INT_FLAG_ERR_ORERR);
usart_interrupt_flag_clear(USART2,USART_INT_FLAG_ERR_NERR);
usart_interrupt_flag_clear(USART2,USART_INT_FLAG_ERR_FERR);
return;
}
if(usart_interrupt_flag_get(USART2,USART_INT_FLAG_IDLE) != RESET
&& RESET != usart_flag_get(USART2, USART_FLAG_IDLE))
{
usart_interrupt_flag_clear(USART2,USART_INT_FLAG_IDLE);
usart_flag_clear(USART2,USART_FLAG_IDLE);
usart_data_receive(USART2);
usart_interrupt_disable(USART2, USART_INT_IDLE);
dma_channel_disable(DMA0, DMA_CH2);
g_usart_DataInfo.dma_rx_state = USART_DMA_IDLE;
}
}
/*
USART2 -- DMA发送完成中断
*/
void DMA0_Channel1_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA0, DMA_CH1, DMA_INTF_ERRIF) != RESET)
{
dma_interrupt_flag_clear(DMA0, DMA_CH1, DMA_INTF_ERRIF);
}
if(dma_interrupt_flag_get(DMA0, DMA_CH1, DMA_INT_FLAG_FTF) != RESET)
{
dma_interrupt_flag_clear(DMA0, DMA_CH1, DMA_INT_FLAG_FTF);
g_usart_DataInfo.dma_tx_state = USART_DMA_IDLE;
}
}
/*
USART2 -- DMA接收完成中断
*/
void DMA0_Channel2_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA0, DMA_CH2, DMA_INTF_ERRIF) != RESET)
{
dma_interrupt_flag_clear(DMA0, DMA_CH2, DMA_INTF_ERRIF);
}
if(dma_interrupt_flag_get(DMA0, DMA_CH2, DMA_INTF_FTFIF) != RESET)
{
dma_interrupt_flag_clear(DMA0, DMA_CH2, DMA_INTF_FTFIF);
dma_channel_disable(DMA0, DMA_CH2);
}
}
//usart_dma.h文件
#ifndef __USART_DMA_H__
#define __USART_DMA_H__
#include "gd32f30x.h"
#include "stdio.h"
#include "string.h"
void gd32_usart_dma_init(void);
void gd32_usart_dma_tx_init(void);
void gd32_usart_dma_rx_init(void);
void gd32_usart_dma_send(uint8_t *p_buff,uint32_t data_len);
uint32_t gd32_usart_dma_get_recv_data(uint8_t *recv_buff,uint32_t data_len);
#endif
3、在main.c文件的main函数中调用串口DMA模式的初始化,并循环调用串口DMA发送,这里循环1S发送字符“1234567890”,连接好硬件板和电脑后,编译下载程序到硬件板中,点击调试运行,如下图所示:
4、点击运行程序,连接好电脑和硬件板的串口,使用串口调试工具查看当前硬件板发送的数据,如下图所示:
串口助手每隔1S接收一条1234567890字符,说明程序运行成功;
5,使用串口调试助手发送0x55、0xAA、0x00、0xFF数据到硬件板,设置断点,查看接收到的数据是否正常,如下图所示:
程序接收到的数据与串口助手发送的数据一致,程序运行正常。
这里我们可以看到使用串口连续发送4个字节的数据,在串口空闲中断中就能够查看到DMA缓存的4个字节的数据,只需要进一次中断即可,如果是串口接收则每接收一个字节需要中断一次,手动将数据缓存,因此使用DMA模式在串口收发频繁的情况下能够大大的节省CPU的开销。
注意:我这里没有调用gd32_usart_dma_get_recv_data(uint8_t *recv_buff,uint32_t data_len)去获取DMA的数据,所以接收完一帧数据后就把DMA关闭了,如果要继续接收的话需要把DMA的数据提取出来重新开启DMA接收才可以,这里就不做处理了。
6、这里在次测试一下使用DMA功能后,用串口连续发送200个字节的数据,查看会占用CPU多少的时间来处理,如下入所示:
这里可以查看到使用DMA发送数据之前当前的systick为6006,使用DMA发送数据完成后,系统当前的systick仍然是6006,并且串口也接收到硬件板发送过来的数据,说明使用DMA发送数据基本上是不占用CPU的时间的。
与之前的串口中断接收缓存和串口发送相比,在数据量大的情况下DMA模式能够大大的节省CPU的开销。