前言
回顾下之前的章节:
第一章节中我们描述了整个框架的核心设计思路以及主要的文件架构
第二章节中我们基于一个简单的定时器OS实现了串口的数据打印,并完成了通用crc模块的设计和测试
第三章节中我们给出了真随机数和伪随机数的概念和代码示例,并在架构上对接口进行了重构
第四章节中我们回顾了FMC的基本知识,并给出了示例,后面我们将在设计IAP的时候再次使用到FMC
第五章节中我们使用ADC和DMA搭建了一个通用的采样框架,并通过串口给出了采样的数据示例
第六章节中我们总结了DAC的基本使用方法,并通过DAC生成了任意频率的正弦波,三角波和方波
第七章节中我们总结下时钟的概念,并给出了获取系统中各模块的时钟频率的代码
本文我们介绍如何通过串口的DMA来实现串口数据的收发。
由于之前我们已经使用了串口1(STM中是USART1,GD中是USART0),本文中以串口2作为示例(STM中是USART2,GD中是USART1)。
串口DMA设计
前文中我们已经讲过了DMA的概念和原理:
DMA即直接存储器访问控制器,DMA提供了一种硬件的方式在外设和存储器之间或者存储器和存储器之间传输数据,而无需CPU的介入,避免了CPU多次进入中断进行大规模的数据拷贝,最终提高整体的系统性能。
简单而言,DMA相当于是外请(DMA硬件)的搬运工(数据拷贝),节约宝贵的CPU资源。
DMA一般需要配置的内容包括:
IO配置(时钟)
DMA参数配置(拷贝的方向,内容,地址,通道,模式和数量等)
中断(使能)配置
DMA的配置需要注意通道的匹配:
这里我们就不赘叙,直接给出串口DMA的设计思路:
简单一句话理解:把DMA缓存当做二级缓存!
串口发送和接收的DMA流程
串口发送和接收的DMA流程(手册中的):
注意的几个点手册中有描述,我在图中也给出来了,这几个坑描述如下:
DMA的几个标志位(传输完成,半传输完成等)需要软件自己清除。
DMA的配置在使能的情况下不可写,需要先失能才能写。
串口的空闲中断需要先读状态寄存器,再读数据寄存器才能清!
串口基本配置代码
代码中包括:
时钟配置,GPIO配置,串口参数配置,收发使能,DMA使能,使能空闲中断
GD32代码:
void uart2_init(uint32_t baudrate)
{
uint32_t com = USART1;
usart_deinit(com);
rcu_periph_clock_enable(RCU_USART1);
rcu_periph_clock_enable(RCU_GPIOA);
///< USART1_TX PA.2
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_2);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_2);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2);
///< USART1_RX PA.3
gpio_af_set(GPIOA, GPIO_AF_7, GPIO_PIN_3);
gpio_mode_set(GPIOA, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_3);
gpio_output_options_set(GPIOA, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_3);
usart_baudrate_set(com, baudrate);
usart_word_length_set(com, USART_WL_8BIT);
usart_stop_bit_set(com, USART_STB_1BIT);
usart_parity_config(com, USART_PM_NONE);
usart_hardware_flow_rts_config(com, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(com, USART_CTS_DISABLE);
usart_enable(com);
usart_receive_config(com, USART_RECEIVE_ENABLE);
usart_transmit_config(com, USART_TRANSMIT_ENABLE);
usart_dma_receive_config(com, USART_DENR_ENABLE);
usart_dma_transmit_config(com, USART_DENT_ENABLE);
usart_flag_clear(com, USART_FLAG_TC);
usart_interrupt_enable(com, USART_INT_IDLE);
nvic_irq_enable(USART1_IRQn, 3, 3);
}
STM32代码:
void uart2_init(uint32_t baudrate)
{
USART_InitTypeDef USART_InitStructure;
USART_TypeDef* USART = USART2;
USART_DeInit(USART);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
///< USART1_TX PA.2
gpio_init(GPIOA, GPIO_Mode_AF_PP, GPIO_Speed_50MHz, GPIO_Pin_2); ///< USART2_TX PA.2
gpio_init(GPIOA, GPIO_Mode_IN_FLOATING, GPIO_Speed_50MHz, GPIO_Pin_3); ///< USART2_RX PA.3
USART_StructInit(&USART_InitStructure);
USART_InitStructure.USART_BaudRate = baudrate; ///< 波特率
USART_Init(USART, &USART_InitStructure);
USART_Cmd(USART, ENABLE); ///< 使能串口
USART_DMACmd(USART, USART_DMAReq_Tx, ENABLE);
USART_DMACmd(USART, USART_DMAReq_Rx, ENABLE);
USART_ClearFlag(USART, USART_FLAG_TC);
USART_ITConfig(USART, USART_IT_IDLE, ENABLE);
nvic_irq_enable(USART2_IRQn, 3, 3);
}
串口DMA发送代码
uart2_dma_tx_init:串口发送DMA初始化
uart2_dma_send_ascii:DMA发送数据,严格按照手册中流程
【注】GD32 DMA是DMA0的通道6,STM DMA是DMA1的通道7。
GD32代码:
void uart2_dma_tx_init(uint32_t addr, uint32_t number)
{
dma_single_data_parameter_struct dma_parameter;
/* enable DMA1 */
rcu_periph_clock_enable(RCU_DMA0);
/* 发送 dm0 channel6(USART1 tx) */
dma_deinit(DMA0, DMA_CH6);
dma_parameter.direction = DMA_MEMORY_TO_PERIPH;
dma_parameter.periph_addr = (uint32_t)(&USART_DATA(USART1));
dma_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_parameter.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_parameter.memory0_addr = addr;
dma_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_parameter.number = number;
dma_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_single_data_mode_init(DMA0, DMA_CH6, &dma_parameter);
/* configure DMA mode */
dma_channel_subperipheral_select(DMA0, DMA_CH6, DMA_SUBPERI4);
}
void uart2_dma_send_ascii(uint8_t *buff, uint32_t count)
{
usart_flag_clear(USART1, USART_FLAG_TC);
dma_channel_disable(DMA0, DMA_CH6);
//dma_flag_clear(DMA0, DMA_CH6, DMA_CHINTF_RESET_VALUE);
dma_flag_clear(DMA0, DMA_CH6, DMA_FLAG_FTF);
dma_memory_address_config(DMA0, DMA_CH6, DMA_MEMORY_0, (uint32_t)buff);
dma_transfer_number_config(DMA0, DMA_CH6, count);
dma_channel_enable(DMA0, DMA_CH6);
while (usart_flag_get(USART1, USART_FLAG_TC)!=RESET);
}
void uart2_dma_send_string(char *str)
{
uart2_dma_send_ascii((uint8_t *)str, strlen(str));
}
STM32代码:
void uart2_dma_tx_init(uint32_t addr, uint32_t number)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel7);
/* 发送 dma1 channel7(USART2 tx) */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = addr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;
DMA_InitStructure.DMA_BufferSize = number;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel7, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel7, ENABLE);
}
void uart2_dma_send_ascii(uint8_t *buff, uint32_t count)
{
USART_ClearFlag(USART2, USART_FLAG_TC);
DMA_Cmd(DMA1_Channel7, DISABLE);
DMA_ClearFlag(DMA1_FLAG_GL7);
DMA1_Channel7->CMAR = (uint32_t)buff;
DMA1_Channel7->CNDTR = count;
DMA_Cmd(DMA1_Channel7, ENABLE);
while (USART_GetFlagStatus(USART2, USART_FLAG_TC)!=RESET);
}
void uart2_dma_send_string(char *str)
{
uart2_dma_send_ascii((uint8_t *)str, strlen(str));
}
串口DMA接收代码
uart2_dma_rx_init:串口接收DMA初始化
uart2_dma_rx_get_count:获取DMA接收数据长度,并置位DMA,在串口空闲中断中调用
【注】GD32 DMA是DMA0的通道5,STM DMA是DMA1的通道6。
GD32代码:
void uart2_dma_rx_init(uint32_t addr, uint32_t number)
{
dma_single_data_parameter_struct dma_parameter;
/* enable DMA1 */
rcu_periph_clock_enable(RCU_DMA0);
/* 接收 dm0 channel5(USART1 rx) */
dma_deinit(DMA0, DMA_CH5);
dma_parameter.direction = DMA_PERIPH_TO_MEMORY;
dma_parameter.periph_addr = (uint32_t)(&USART_DATA(USART1));
dma_parameter.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
dma_parameter.periph_memory_width = DMA_PERIPH_WIDTH_8BIT;
dma_parameter.memory0_addr = addr;
dma_parameter.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
dma_parameter.number = number;
dma_parameter.priority = DMA_PRIORITY_ULTRA_HIGH;
dma_parameter.circular_mode = DMA_CIRCULAR_MODE_DISABLE;
dma_single_data_mode_init(DMA0, DMA_CH5, &dma_parameter);
/* configure DMA mode */
dma_channel_subperipheral_select(DMA0, DMA_CH5, DMA_SUBPERI4);
dma_channel_enable(DMA0, DMA_CH5);
}
uint32_t uart2_dma_rx_get_count(uint32_t dma_rx_count_max)
{
uint32_t receive_len = dma_rx_count_max - dma_transfer_number_get(DMA0, DMA_CH5);
if (receive_len != 0)
{
dma_channel_disable(DMA0, DMA_CH5);
dma_transfer_number_config(DMA0, DMA_CH5, dma_rx_count_max);
dma_flag_clear(DMA0, DMA_CH5, DMA_FLAG_FTF);
dma_channel_enable(DMA0, DMA_CH5);
}
return receive_len;
}
void USART1_IRQHandler(void)
{
uint8_t temp;
if (usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE) != RESET)
{
// 软件先读USART_STAT0,再读USART_DATA可清除该位。
temp = USART_STAT0(USART1);
temp = USART_DATA(USART1);
temp = temp;
process_uart2_dma_rx_data();
}
}
STM32代码:
void uart2_dma_rx_init(uint32_t addr, uint32_t number)
{
DMA_InitTypeDef DMA_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE);
DMA_DeInit(DMA1_Channel6);
/* 发送 dma1 channel6(USART2 rx) */
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART2->DR);
DMA_InitStructure.DMA_MemoryBaseAddr = addr;
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;
DMA_InitStructure.DMA_BufferSize = number;
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_MemoryDataSize = DMA_PeripheralDataSize_Byte;
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;
DMA_InitStructure.DMA_Priority = DMA_Priority_High;
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;
DMA_Init(DMA1_Channel6, &DMA_InitStructure);
DMA_Cmd(DMA1_Channel6, ENABLE);
}
uint32_t uart2_dma_rx_get_count(uint32_t dma_rx_count_max)
{
uint32_t receive_len = dma_rx_count_max - DMA_GetCurrDataCounter(DMA1_Channel6);
if (receive_len != 0)
{
DMA_Cmd(DMA1_Channel6, DISABLE);
DMA_ClearFlag(DMA1_FLAG_GL6);
DMA_SetCurrDataCounter(DMA1_Channel6, dma_rx_count_max);
DMA_Cmd(DMA1_Channel6, ENABLE);
}
return receive_len;
}
void USART2_IRQHandler(void)
{
uint8_t temp;
if (USART_GetITStatus(USART2, USART_IT_IDLE) != RESET)
{
// 软件先读USART_SR,再读USART_DR可清除该位。
temp = USART2->SR;
temp = USART2->DR;
temp = temp;
process_uart2_dma_rx_data();
}
}
process_uart2_dma_rx_data:处理数据主函数
void process_uart2_dma_rx_data(void)
{
uint32_t receive_len = uart2_dma_rx_get_count(UART_DATA_DMA_RX_COUNT);
uint32_t index;
if (receive_len != 0)
{
printf("receivelen = %d\r\n", receive_len);
for (index = 0; index < receive_len; index++)
{
printf("%02X ", uart_dma_rx[index]);
}
printf("\r\n");
}
}
串口DMA发送例行结果展示
COM3接串口2
MCU发送数据为:Hello, My Name is Cortex M !\r\n
串口DMA接收例行结果展示
COM9接串口1,COM3接串口2
COM3循环发数据给MCU,MCU通过串口1打印给电脑
【注:STM32的串口DMA没找到串口头子,没测试】
--EOF--
例行求粉,谢谢!