串口空闲中断+DMA的原理:
串口在接收数据过程中,不会触发中断,当触发串口空闲中断时,说明这个时候串口没有在收发数据了,可以视为收发完一帧数据。在这个过程中,DMA将串口接收数据寄存器的值直接“搬运”到指定的内存位置(可以是自定义的串口接收缓冲数组)。采用这种方式,可以减少中断次数,节约CPU资源。对比一下,如果采用串口接收中断,那么串口在接收到一个字节就触发一次中断,数据量大时,CPU要频繁处理中断,而采用串口空闲中断+DMA时,只需要触发一次中断。
下面给出代码。
bsp_usart.h文件编写函数声明以及引用外部变量。
#ifndef _BSP_USART_H
#define _BSP_USART_H
#include "gd32f10x.h"
/* 在main.c文件中定义的变量 */
extern uint8_t recv_buffer[1024];
extern uint16_t recv_count;
extern uint8_t recv_complete_flag;
/* 函数声明 */
void bsp_init_usart(void);
void usart_config(void);
void dma_config(void);
#endif
bsp_usart.c文件中编写串口及DMA初始化配置以及相关中断服务函数的代码(此处将中断服务函数直接定义到对应的外设初始化文件里了,可以自己选择位置,只需要保证中断服务函数能访问到相应变量)。
#include "bsp_usart.h"
/* 初始化串口 */
void bsp_init_usart(void)
{
usart_config();
dma_config();
}
/* 配置串口 */
void usart_config(void)
{
/* 使能IO口时钟 */
rcu_periph_clock_enable(RCU_GPIOA);
/* 使能串口时钟 */
rcu_periph_clock_enable(RCU_USART0);
/* 初始化TX脚 */
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_9);
/* 初始化RX脚 */
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_10);
/* 串口参数配置 */
usart_deinit(USART0);
usart_baudrate_set(USART0, 115200U); /* 波特率115200 */
usart_word_length_set(USART0, USART_WL_8BIT); /* 8位数据位 */
usart_stop_bit_set(USART0, USART_STB_1BIT); /* 1位停止位 */
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);
/* 使能串口空闲中断 */
nvic_irq_enable(USART0_IRQn, 2, 2);
usart_interrupt_enable(USART0, USART_INT_IDLE);
/* 使能串口DMA接收 */
usart_dma_receive_config(USART0, USART_RECEIVE_DMA_ENABLE);
}
/* 配置DMA */
void dma_config(void)
{
/* 定义DMA参数结构体变量 */
dma_parameter_struct dma_init_struct;
/* 使能相应DMA时钟 */
rcu_periph_clock_enable(RCU_DMA0);
/* 复位对应DMA通道 */
dma_deinit(DMA0, DMA_CH4);
/* 复位DMA参数结构体变量 */
dma_struct_para_init(&dma_init_struct);
/* 配置DMA结构体变量参数 */
dma_init_struct.direction = DMA_PERIPHERAL_TO_MEMORY; /* 外设到内存 */
dma_init_struct.memory_addr = (uint32_t)recv_buffer; /* 内存地址:接收数组缓冲的地址 */
dma_init_struct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; /* 内存地址自增 */
dma_init_struct.memory_width = DMA_MEMORY_WIDTH_8BIT; /* 内存数据宽度8b */
dma_init_struct.number = 1024; /* 接收长度 此处可以通过宏定义设置值的大小,方便修改*/
dma_init_struct.periph_addr = (uint32_t)&USART_DATA(USART0); /* 外设地址:串口接收数据寄存器 */
dma_init_struct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; /* 外设地址不自增 */
dma_init_struct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT; /* 外设数据宽度8b */
dma_init_struct.priority = DMA_PRIORITY_HIGH; /* 优先级:高 */
/* 初始化DMA */
dma_init(DMA0,DMA_CH4,&dma_init_struct);
/* 不使用循环模式 */
dma_circulation_disable(DMA0, DMA_CH4);
/* 不使用内存到内存模式 */
dma_memory_to_memory_disable(DMA0, DMA_CH4);
/* 使能对应DMA通道 */
dma_channel_enable(DMA0, DMA_CH4);
/* 使能相应中断 */
nvic_irq_enable(DMA0_Channel4_IRQn, 0, 0);
dma_interrupt_enable(DMA0,DMA_CH4,DMA_INT_FTF);
}
/* 串口中断服务函数 */
void USART0_IRQHandler(void)
{
if(usart_interrupt_flag_get(USART0, USART_INT_FLAG_IDLE) != RESET)
{
usart_data_receive(USART0); /* 必须读寄存器清中断标志位 */
recv_complete_flag = 1; /* 接收完成标志置1 */
recv_count = 1024 - dma_transfer_number_get(DMA0,DMA_CH4); /* DMA设置的Number值-读取的DMA剩余值=接收数据个数 */
recv_buffer[recv_count] = '\0'; /* 接收缓冲末尾赋值‘\0’ */
dma_config(); /* 重启DMA,不重启会导致DMA计数不正确 */
}
}
/* DMA中断服务函数 */
void DMA0_Channel4_IRQHandler(void)
{
if(dma_interrupt_flag_get(DMA0, DMA_CH4, DMA_INT_FLAG_FTF) != RESET)
{
dma_interrupt_flag_clear(DMA0, DMA_CH4, DMA_INT_FLAG_FTF);
}
}
当触发串口空闲中断时,在中断服务函数里面将接收完成标志位置位,主程序查询到这个标志位置位后,即可对数据进行相应的处理。
#include "gd32f10x.h"
#include "systick.h"
#include <stdio.h>
#include <string.h>
#include "main.h"
#include "bsp_usart.h"
uint8_t recv_buffer[1024];
uint16_t recv_count;
uint8_t recv_complete_flag;
int main(void)
{
nvic_priority_group_set(NVIC_PRIGROUP_PRE2_SUB2);
/* configure systick */
systick_config();
bsp_init_usart();
while(1)
{
/* 等待数据传输完成 INTERRUPT */
if(recv_complete_flag) // 数据接收完成
{
recv_complete_flag = RESET; // 等待下次接收
printf("recv_count:%d ",recv_count);
printf("Interrupt recv:%s\r\n",recv_buffer); // 打印接收的数据
memset(recv_buffer,0,sizeof(recv_buffer)); // 清空数组
recv_count = 0;
}
}
}
/* retarget the C library printf function to the USART */
int fputc(int ch, FILE *f)
{
usart_data_transmit(USART0, (uint8_t)ch);
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
return ch;
}
main函数里查询接收完成标志位的值,查询到置位时打印数据。