GD32F103串口DMA收发
GD32F103串口DMA收发
这两年做嵌入式开发太难了,芯片缺货导致方案不断改改改,之前一直没用过国产MCU,从去年开始也渐渐了解了一下国产MCU。原理大同小异,资料相对来说确实少一点,生态也没那么完善。 这个系列打算把项目过程中的一些经验分享给大家,也作为自己的一个成长记录。如果能给路过的各位带来切实收获,请各位不吝点赞,给作者资以鼓励;如果文章中有错误,也请各位不吝指正,共同学习,共同进步!
串口DMA收发
废话不多说,直接上干货,我用的MCU是GD32F103CBT6,串口使用USART0,对应的GPIO是PA9和PA10。
1.串口IO初始化
代码如下:
static void usart_config(uint32_t baudval){rcu_periph_clock_enable(RCU_GPIOA);//enable GPIO clock, PA9/PA10rcu_periph_clock_enable(RCU_AF); rcu_periph_clock_enable(RCU_USART0);//enable USART clock gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);//PA9--TX0 gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);//PA10--RX0 //USART0: 115200 usart_deinit(USART0); usart_baudrate_set(USART0, baudval); usart_word_length_set(USART0, USART_WL_8BIT); usart_stop_bit_set(USART0, USART_STB_1BIT); usart_parity_config(USART0, USART_PM_NONE); usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE); usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE); usart_receive_config(USART0, USART_RECEIVE_ENABLE); usart_transmit_config(USART0, USART_TRANSMIT_ENABLE); usart_enable(USART0);}
此处串口波特率采用传参方式在初始化串口时传入,实际使用时我用的串口波特率为115200。
2.DMA初始化
关于DMA通道映射详见UserManual 9.4.9 DMA请求映射及DMA各通道请求表。此处一定要仔细,不能搞错,我们使用的是USART0,由表中可知USART_TX对应的是Channel 3,USART_RX对应的是Channel 4。
代码如下:
uint8_t rxbuffer[2]= {'o','k'};uint8_t txbuffer[10] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a};#define USART0_DATA_ADDRESS ((uint32_t)&USART_DATA(USART0))static void usart_dma_config(void){ dma_parameter_struct dma_init_struct; /* enable DMA0 clock */ rcu_periph_clock_enable(RCU_DMA0); /* deinitialize DMA channel3(USART0 tx) */ dma_deinit(DMA0, DMA_CH3); dma_struct_para_init(&dma_init_struct); dma_init_struct.direction = DMA_MEMORY_TO_PERIPHERAL; dma_init_struct.memory_addr = NULL; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init_struct.number = ARRAYNUM(txbuffer); dma_init_struct.periph_addr = USART0_DATA_ADDRESS; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA0, DMA_CH3, &dma_init_struct); /* deinitialize DMA channel4 (USART0 rx) */ dma_deinit(DMA0, DMA_CH4); dma_struct_para_init(&dma_init_struct); dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; dma_init_struct.memory_addr = (uint32_t)rxbuffer; dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; dma_init_struct.number = ARRAYNUM(rxbuffer);; dma_init_struct.periph_addr = USART0_DATA_ADDRESS; dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; dma_init_struct.memory_width = DMA_PERIPHERAL_WIDTH_8BIT; dma_init_struct.priority = DMA_PRIORITY_ULTRA_HIGH; dma_init(DMA0, DMA_CH4, &dma_init_struct); /* configure DMA mode */ dma_circulation_disable(DMA0, DMA_CH3); dma_memory_to_memory_disable(DMA0, DMA_CH3); dma_circulation_enable(DMA0, DMA_CH4); dma_memory_to_memory_disable(DMA0, DMA_CH4); /* USART DMA0 enable for reception */ usart_dma_receive_config(USART0, USART_DENR_ENABLE); /* enable DMA0 channel4 transfer complete interrupt */ dma_interrupt_enable(DMA0, DMA_CH4, DMA_INT_FTF); /* enable DMA0 channel4 */ dma_channel_enable(DMA0, DMA_CH4); /* USART DMA0 enable for transmission */ usart_dma_transmit_config(USART0, USART_DENT_ENABLE); /* enable DMA0 channel3 transfer complete interrupt */// dma_interrupt_enable(DMA0, DMA_CH3, DMA_INT_FTF);// /* disable DMA0 channel3 */// dma_channel_disable(DMA0, DMA_CH3);}
3. 中断配置
串口接收采用中断方式,所以需要配置中断。
串口发送需要在主函数里根据项目实际需求进行发送,故不采用中断方式。
代码如下:
static void nvic_config(void){nvic_priority_group_set(NVIC_PRIGROUP_PRE3_SUB1); nvic_irq_enable(DMA0_Channel4_IRQn, 0, 1);}
4. 串口初始化
代码如下:
void bsp_usart_init() { usart_config(BAUDRATE); nvic_config(); usart_dma_config();}
5. 串口发送函数和printf支持实现
代码如下:
void usart0_dma_send(uint8_t *buffer,uint16_t len){dma_channel_disable(DMA0, DMA_CH3);dma_memory_address_config(DMA0, DMA_CH3,(uint32_t)buffer);//设置要发送数据的内存地址dma_transfer_number_config(DMA0, DMA_CH3, len);//一共发送多少个数据dma_channel_enable(DMA0, DMA_CH3);}int fputc(int ch, FILE *f){ usart_data_transmit(USART0, (uint8_t)ch); while(RESET == usart_flag_get(USART0, USART_FLAG_TBE)); return ch;}
6. 头文件中的宏定义和函数声明如下
代码如下:
#define BAUDRATE115200U //#define BAUDRATE9600U #define ARRAYNUM(arr_name) (uint32_t)(sizeof(arr_name) / sizeof(*(arr_name))) void bsp_usart_init(void); void usart0_dma_send(uint8_t *buffer,uint16_t len); int fputc(int ch, FILE *f);
7. 中断处理
串口接收中断里只需要清对应标志位即可。
void DMA0_Channel4_IRQHandler(void){ if(dma_interrupt_flag_get(DMA0, DMA_CH4, DMA_INT_FLAG_FTF)){ dma_interrupt_flag_clear(DMA0, DMA_CH4, DMA_INT_FLAG_G); }}
8. 主函数
主函数里我们采用定时器+计数方式实现1s发送一次txbuffer的数据,当上位机发送数据给串口,数据通过DMA直接存到rxbuffer中。
extern uint8_t txbuffer[10];extern uint8_t rxbuffer[2];int main(void) { bsp_usart_init(); SysTick_Init();bsp_wdg_init(); volatile uint8_t usartSend_taskTime = 0;for(;;) { if(g_u8_taskTimer >= 10) {g_u8_taskTimer = g_u8_taskTimer - 10;bsp_wdg_feed();task_10msCycle();usartSend_taskTime++;if(usartSend_taskTime >= 100){usart0_dma_send(txbuffer, (uint16_t)ARRAYNUM(txbuffer));//printf("\n\r%s\n\r", rxbuffer);usartSend_taskTime -=100;}} }}
9. 执行结果
rxbuffer和txbuffer中初始数据如下:
uint8_t rxbuffer[2]= {'o','k'};uint8_t txbuffer[10] = {0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,0x0a};
上位机接收的串口数据:
rxbuff初始数据:
上位机发送字符"lk"后rxbuffer也随之改变:
至此,整个串口DMA收发测试成功。
总结
使用DMA进行数据传输的好处毋庸置疑,可以大大减少CPU的负担,CPU效率也会大大提升。
由上面步骤可知,串口用DMA收发主要分以下几步:
1)串口初始化
2)DMA初始化
3)中断配置
4)发送和接收数据