GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)

前言 之前写了GD32F103调试小记(二)之USART(接收中断、接收空闲中断+DMA、发送DMA)一文。这次我们来看看GD32F303的USART是如何配置的,结合这两篇文章,相信大家GD32的USART配置流程会十分熟悉。
DMA 能大大减轻CPU负荷,数据的搬运工,建议场景合适的情况下,都去配置DMA功能。
USART 一种串行通信协议,网上资料很多,这里不多说。
各模块程序编写 在配置前,请确保你已经有一个GD32F303包含其对应标准库的Keil工程。
1、时钟配置
GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

  • 上图说明了GD32F303所拥有的外设时钟挂载在哪个总线上。这里我们使用USART2,挂载在APB1总线上。
  • 这里我们开启各GPIO端口时钟、GPIO引脚复用功能时钟、DMA时钟和USART2的时钟。
void SystemClock_Reconfig(void) { /* Enable all peripherals clocks you need*/ rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_GPIOA); rcu_periph_clock_enable(RCU_GPIOB); rcu_periph_clock_enable(RCU_GPIOC); rcu_periph_clock_enable(RCU_GPIOD); rcu_periph_clock_enable(RCU_DMA0); rcu_periph_clock_enable(RCU_USART2); }

2、配置GPIO
GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

  • 这里我用到了PC10和PC11作为USART2的TX与RX,代码配置如下:
// USART port and pins definition #define USART2_PORTGPIOC #define USART2_TX_PINGPIO_PIN_10 #define USART2_RX_PINGPIO_PIN_11void GPIO_Init(void) { /* 使用SW下载,不使用JTAG下载,管脚用作其它功能 */ gpio_pin_remap_config(GPIO_SWJ_SWDPENABLE_REMAP, ENABLE); /* demo board USARTx I/O */ gpio_pin_remap_config(GPIO_USART2_PARTIAL_REMAP, ENABLE); gpio_init(USART2_PORT, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ,USART2_TX_PIN); gpio_init(USART2_PORT, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ,USART2_RX_PIN); }

3、配置DMA
GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

  • 上图可知,USART2_TX和UASRT2_RX对应DMA0_CH1和DMA0_CH2。
  • 记住DMA传输的方向,TX对应的DMA通道是从我们内存中定义的变量搬运到USART2的发送寄存器中,而RX对应的DMA通道是从USART2的接收寄存器搬运到我们内存里定义的变量。
  • DMA是个搬运工,如果是单个字节的发送与接收,单凭USART自带的接收/发送完成标志位也可以做到。所以如果在USART上用到了DMA一定是多个字节数据的传输。这里用USARTx_RX_BY_IDLE_DMA做一个宏的区分。
  • 这里TX对应的通道初始化完成后先关闭掉,而RX对应的通道是初始化完成后可以先开启。至于为什么,容我先卖个关子,在下面我会详细解释。
/* USARTx Receiving Mode */ #define USARTx_RX_BY_IDLE_DMA 1//1:使用接收空闲中断+DMA0:仅使用接收中断/* USARTx ADrawbuffer definition */ #define USART2_TX_SIZE 20 #define USART2_RX_SIZE 20uint8_t USART2TX_Buffer[USART2_TX_SIZE] = {0x11,0x12,0x13,0x14,0x15,0x16,0x17,0x18}; uint8_t USART2RX_Buffer[USART2_RX_SIZE] = {0}; void DMA_Init(void) { dma_parameter_struct dma_init_Usart2_TX = {0}; dma_parameter_struct dma_init_Usart2_RX = {0}; /* deinitialize DMA channel */ dma_deinit(DMA0, DMA_CH1); //USART2_TX dma_deinit(DMA0, DMA_CH2); //USART2_RX /* initialize DMA0 channel1(Usart2_TX) */ dma_init_Usart2_TX.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_Usart2_TX.memory_addr = (uint32_t)USART2TX_Buffer; dma_init_Usart2_TX.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_Usart2_TX.memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init_Usart2_TX.number = (uint32_t)USART2_TX_SIZE; dma_init_Usart2_TX.periph_addr = (uint32_t)(&USART_DATA(USART2)); dma_init_Usart2_TX.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_Usart2_TX.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; dma_init_Usart2_TX.priority = DMA_PRIORITY_LOW; dma_init(DMA0, DMA_CH1, &dma_init_Usart2_TX); dma_circulation_disable(DMA0, DMA_CH1); /* initialize DMA0 channel2(Usart2_RX) */ dma_init_Usart2_RX.direction = DMA_PERIPHERAL_TO_MEMORY; dma_init_Usart2_RX.memory_addr = (uint32_t)USART2RX_Buffer; dma_init_Usart2_RX.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_Usart2_RX.memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init_Usart2_RX.number = (uint32_t)USART2_RX_SIZE; dma_init_Usart2_RX.periph_addr = (uint32_t)(&USART_DATA(USART2)); dma_init_Usart2_RX.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_Usart2_RX.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; dma_init_Usart2_RX.priority = DMA_PRIORITY_LOW; dma_init(DMA0, DMA_CH2, &dma_init_Usart2_RX); dma_circulation_disable(DMA0, DMA_CH2); dma_memory_to_memory_disable(DMA0,DMA_CH1); //USART2_TX dma_memory_to_memory_disable(DMA0,DMA_CH2); //USART2_RX /* enable all DMA channels you need */ dma_channel_disable(DMA0,DMA_CH1); //USART2_TX dma_channel_enable(DMA0,DMA_CH2); //USART2_RX }

4、配置USART2
  • 波特率38400,数据位8位,停止位1位,无校验位,不使用硬件流控制,接收/发送使能,发送DMA使能。接收DMA是否使能由USARTx_RX_BY_IDLE_DMA这个宏决定。
void USARTx_Init(void) { /* USART2 configure */ usart_deinit(USART2); usart_baudrate_set(USART2, 38400U); usart_word_length_set(USART2, USART_WL_8BIT); usart_stop_bit_set(USART2, USART_STB_1BIT); usart_parity_config(USART2, USART_PM_NONE); usart_hardware_flow_rts_config(USART2, USART_RTS_DISABLE); usart_hardware_flow_cts_config(USART2, USART_CTS_DISABLE); usart_receive_config(USART2, USART_RECEIVE_ENABLE); usart_transmit_config(USART2, USART_TRANSMIT_ENABLE); usart_enable(USART2); /* config USARTx_TX transmit by DMA */ usart_dma_transmit_config(USART2,USART_DENT_ENABLE); #if USARTx_RX_BY_IDLE_DMA usart_dma_receive_config(USART2,USART_DENR_ENABLE); #endif }

5、配置中断优先级组与USART2中断
GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

  • 仔细阅读F303应用手册中关于USART的中断描述,我们可以理解RBNE代表每从RX脚上接收到一帧的数据,就置1,我们能从USART接收寄存器读取该数据。IDLEF代表当RX接收完多帧数据后,检测到线上空闲就置1,而USART接收寄存器每次只能接收单个帧数据,意味着多帧数据里除了最后一帧数据,前面的都被覆盖(忽略)掉了,所以必须配合DMA的数据传输功能才能保证多帧数据的接收完整。我想看到这里,大家也应该理解我这里为什么用USARTx_RX_BY_IDLE_DMA这个宏作区分了吧。
  • 设置整个系统中断优先级组。这里根据USARTx_RX_BY_IDLE_DMA这个宏决定仅开接收中断还是仅开接收空闲中断。
void NVIC_Init(void) { /* USART2 IRQ set */ nvic_irq_enable(USART2_IRQn, 2, 0); #if USARTx_RX_BY_IDLE_DMA usart_interrupt_enable(USART2, USART_INT_IDLE); #else usart_interrupt_enable(USART2, USART_INT_RBNE); #endif }

  • 编写中断服务函数。
  • 如果USARTx_RX_BY_IDLE_DMA这个宏为真,即选择使用接收空闲中断+DMA的方式接收数据。我们通过读相应的数据寄存器去清这个标志位(手册描述的清位方式),再重新置为对应的DMA接收通道。程序运行到此处时,DMA通道已经获取了一个或多个字节的数据(看你发了多少字节的数据),关闭再开启则是让DMA的内存buffer再次从头开始(比如我们定义的USART1RX_Buffer数组是USART1_RX的内存buff,在接受了3个字节的数据后,从USART1RX_Buffer[0]移到了USART1RX_Buffer[3],复位DMA后又将从USART1RX_Buffer[0]开始)。这样只要保证最大字节数不被覆盖,在有标志位置1后及时处理,就能完美接收不定长的数据,且程序被中断的次数最少。
  • 如果USARTx_RX_BY_IDLE_DMA这个宏为假,即选择仅使用接收中断。那么每接收到一个字节的数据就会产生一次中断,再将每个字节的内容从设备数据寄存器搬运到我们自己定义的内存变量里。
void USART2_IRQHandler(void) { #if USARTx_RX_BY_IDLE_DMA if(RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_IDLE)) { Module.USART2_RX_OK=1; /* clear USART_INT_FLAG_IDLE */ usart_data_receive(USART2); /* disable USART2_RX DMA_Channel */ dma_channel_disable(DMA0, DMA_CH2); /* reset DMA_Channel CNT */ dma_transfer_number_config(DMA0, DMA_CH2, USART2_RX_SIZE); /* enable USART2_RX DMA_Channel */ dma_channel_enable(DMA0, DMA_CH2); } #else if(RESET != usart_interrupt_flag_get(USART2, USART_INT_FLAG_RBNE)) { usart_interrupt_flag_clear(USART2,USART_INT_FLAG_RBNE); Module.USART2_RX_OK=1; USART2RX_Buffer[0] = usart_data_receive(USART2); } #endif }

6、配置发送DMA
GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
文章图片

  • 如果理解了上述手册中对DMA的描述,这里也应该明白了,为什么TX对应初始化完的DMA通道初始化完后不能直接使能,而RX对应的DMA通道初始化完后能直接使能。还是为了保证初始化完后避免误发数据。
  • 根据上述对DMA的描述,我们对USART发送DMA的底层函数编写如下:
void Usartx_Transmit_DMA(uint32_t usart_periph,uint8_t* data_buffer,uint8_t length) { if(usart_periph==USART2) { /* Channel disable */ dma_channel_disable(DMA0, DMA_CH1); dma_memory_address_config(DMA0, DMA_CH1,(uint32_t)data_buffer); dma_transfer_number_config(DMA0,DMA_CH1,length); /* enable DMA channel to start send */ dma_channel_enable(DMA0, DMA_CH1); } }

7、主函数部分
  • 主函数程序逻辑如下:
int main(void) { SystemTick_Init(); SystemClock_Reconfig(); GPIO_Init(); DMA_Init(); USARTx_Init(); NVIC_Init(); while(1) { //Usartx_Transmit_DMA(USART2,USART2TX_Buffer,USART2_RX_SIZE); Usartx_Transmit_DMA(USART2,(uint8*)"hello,MCU World!",20); delay_ms(1000); } }

  • 使用Usartx_Transmit_DMA(USART2,USART2TX_Buffer,USART2_RX_SIZE);
    GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
    文章图片
  • 使用Usartx_Transmit_DMA(USART2,(uint8*)“hello,MCU World!”,20);
    GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
    文章图片
  • 修改while(1)里的内容。
while(1) { if(Module.USART2_RX_OK) { Module.USART2_RX_OK = 0; Usartx_Transmit_DMA(USART2,USART2RX_Buffer,USART2_RX_SIZE); } }

  • 显示效果如下:
    GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)
    文章图片
    8、总结
    至此,GD32F303与GD32F103关于USART的调试小记已全部完成。不止是GD的,任何与之类似的USART模块都可以以此流程去配置操作。
【GD32|GD32F303调试小记(一)之USART(接收中断、接收空闲中断+DMA、发送DMA)】!!!本文为欢喜6666在CSDN原创发布,复制或转载请注明出处 :)!!!

    推荐阅读