本文是GD开发板串口外设搭配DMA的一些基本配置操作。
1. 查阅用户手册可知,GD32F303有5个串口,其中前四个可以使用DMA搬运数据。
关于有哪些,可以查看用户手册中DMA请求映射表;所有的手册都大同小异,找到就知道对应哪个dma通道了。
2. 下面是串口以及串口DMA配置部分代码:
使用双通道切换模式,接收dma配置部分一定要开启循环模式。
void Uart_DMA_Init()
{
/*********************UART1 PA2 PA3*********************************************/
#if 1
rcu_periph_clock_enable(RCU_USART1); //开启USART1时钟
rcu_periph_clock_enable(RCU_GPIOA); //开启GPIOA时钟
gpio_init(GPIOA, GPIO_MODE_AF_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_2); //PA2(TX)配置为复用推挽输出
gpio_init(GPIOA, GPIO_MODE_IN_FLOATING, GPIO_OSPEED_50MHZ, GPIO_PIN_3); //PA3(RX)配置为浮空输入
usart_deinit(USART1);
usart_baudrate_set(USART1, 115200);
usart_word_length_set(USART1, USART_WL_8BIT);
usart_stop_bit_set(USART1, USART_STB_1BIT);
usart_parity_config(USART1, USART_PM_NONE);
usart_hardware_flow_rts_config(USART1, USART_RTS_DISABLE);
usart_hardware_flow_cts_config(USART1, USART_CTS_DISABLE);
usart_transmit_config(USART1, USART_TRANSMIT_ENABLE);
usart_receive_config(USART1, USART_RECEIVE_ENABLE);
/* USART DMA 发送和接收使能 */
usart_dma_transmit_config(USART1, USART_DENT_ENABLE|USART_DENR_ENABLE);
nvic_irq_enable(USART1_IRQn,1,0);
// usart_interrupt_enable(USART1,USART_INT_RBNE);
usart_interrupt_enable(USART1,USART_INT_IDLE); //使能空闲中断
usart_interrupt_flag_clear(USART1,USART_INT_FLAG_IDLE);//清除读数据缓冲区非空中断标志
usart_enable(USART1); //使能USART1
#endif
//Uart1 RX DMA0, DMA_CH5
#if 1
/* initialize DMA0 channe5(USART1 RX) */
rcu_periph_clock_enable(RCU_DMA0); //开启DMA时钟
dma_deinit(DMA0, DMA_CH5);
DMA_InitStruct.direction = DMA_PERIPHERAL_TO_MEMORY; //外设到内存
DMA_InitStruct.periph_addr = (uint32_t)(&USART_DATA(USART1));
DMA_InitStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE; //外设增量
DMA_InitStruct.periph_width = DMA_MEMORY_WIDTH_8BIT;
DMA_InitStruct.memory_addr = (uint32_t)uart1_1_rev_buf;
DMA_InitStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE; //内存增量
DMA_InitStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
DMA_InitStruct.number = sizeof(uart1_1_rev_buf);;
DMA_InitStruct.priority = DMA_PRIORITY_MEDIUM;
dma_init(DMA0, DMA_CH5, &DMA_InitStruct);
/* DMA循环模式配置,不使用循环模式 */
dma_circulation_enable(DMA0, DMA_CH5);
dma_memory_to_memory_disable(DMA0,DMA_CH5);
dma_channel_enable(DMA0, DMA_CH5);
dma_interrupt_enable(DMA0,DMA_CH5,DMA_INT_FTF|DMA_INT_ERR); //开启DMA传输完成中断标志
/***开启DMA中断**/
nvic_irq_enable(DMA0_Channel5_IRQn,1,0);
//Uart1 TX 4G DMA0, DMA_CH6
dma_deinit(DMA0, DMA_CH6); //USART1_TX
/* initialize DMA0 channe6(Usart1_TX) */
DMA_InitStruct.direction = DMA_MEMORY_TO_PERIPHERAL;
DMA_InitStruct.memory_addr = 0;
DMA_InitStruct.memory_inc = DMA_MEMORY_INCREASE_ENABLE;
DMA_InitStruct.memory_width = DMA_MEMORY_WIDTH_8BIT;
DMA_InitStruct.number = 0;
DMA_InitStruct.periph_addr = (uint32_t)(&USART_DATA(USART1));
DMA_InitStruct.periph_inc = DMA_PERIPH_INCREASE_DISABLE;
DMA_InitStruct.periph_width = DMA_PERIPHERAL_WIDTH_8BIT;
DMA_InitStruct.priority = DMA_PRIORITY_MEDIUM;
dma_init(DMA0, DMA_CH6, &DMA_InitStruct);
dma_circulation_disable(DMA0, DMA_CH6);
dma_channel_disable(DMA0, DMA_CH6);
dma_interrupt_enable(DMA0,DMA_CH6,DMA_INT_FTF|DMA_INT_ERR); //开启DMA传输完
//成中断标志
/***开启DMA中断**/
nvic_irq_enable(DMA0_Channel6_IRQn,1,0);
#endif
}
3. 使用串口空闲中断与DMA的协同工作机制传输最合适。
一开始通过PC端串口助手测试发现串口dma接收回调函数打印不出我发给mcu的数据,但是dma的内存基地址我打印了出来数据,,后来结合串口空闲中断,使用dma快速搬运数据,再通过串口空闲中断将数据接收出来。
dma的传输完成中断是 当你的基地址内存接满了,它才会进入dma接收完成中断服务函数中。所以刚开始我通过串口助手发的简单的少量数据压根没灌满内存地址的大小。
以下是dma中断以及串口空闲中断的服务函数。使用的是双通道,单通道不切换通道即可。同时也有dma发送函数封装。
/* 串口1中断服务程序 双通道 */
void USART1_IRQHandler(void)
{
if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE) != RESET) //接收数据空闲
{
usart_interrupt_flag_clear(USART1,USART_INT_FLAG_IDLE); /* 清除空闲中断标志位 */
usart_data_receive(USART1); /* 清除接收完成标志位 */
/*
双缓冲区模式时,需要将切换缓冲区放置在前面,处理接收数据放置在后面
*/
//切换接收缓冲区
dma_channel_disable(DMA0, DMA_CH5); /* 关闭DMA传输 */
uint16_t recv_len = NET_BUF_SIZE - dma_transfer_number_get(DMA0,DMA_CH5); //获取实际接收数据字节数
//DMA_CH5PADDR
if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
{
Uart1Rev_CallBack(uart1_1_rev_buf, recv_len); //RX
dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_2_rev_buf);
}
else
{
Uart1Rev_CallBack(uart1_2_rev_buf, recv_len); //RX
dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_1_rev_buf);
}
dma_transfer_number_config(DMA0,DMA_CH5,NET_BUF_SIZE);
dma_channel_enable(DMA0, DMA_CH5); /* 开启DMA传输 */
//数据处理接收
后面我将数据接收处理移到了上面的切换缓存通道的同时一起接收数据了。。。下面是刚开始写的,都可以实现。。但是下面的接收要注意上面的地址以及被更改了,所以接受的数据是上一个的缓存区
// if(NULL != Uart1Rev_CallBack && recv_len > 0)
// {
// if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
// {
// Uart1Rev_CallBack(uart1_2_rev_buf, recv_len); //RX // 当前DMA指向uart1_1,说明上一帧在uart1_2,,因为上面已经切换了地址。程序到这里就对应也已经改变了。
// }
// else
// {
// Uart1Rev_CallBack(uart1_1_rev_buf, recv_len); //RX
// }
// }
}
}
///* 串口1中断服务程序 单通道的接收 */
//void USART1_IRQHandler(void)
//{
// if(usart_interrupt_flag_get(USART1, USART_INT_FLAG_IDLE) != RESET) //接收数据空闲
// {
// usart_interrupt_flag_clear(USART1,USART_INT_FLAG_IDLE); /* 清除空闲中断标志位 */
// usart_data_receive(USART1); /* 清除接收完成标志位 */
// uint16_t recv_len = NET_BUF_SIZE - dma_transfer_number_get(DMA0,DMA_CH5);
// if(NULL != Uart1Rev_CallBack && recv_len > 0)
// {
// Uart1Rev_CallBack(uart1_1_rev_buf, recv_len); //RX
// }
// dma_channel_disable(DMA0, DMA_CH5); /* 关闭DMA传输 */
// /* 重新设置DMA传输 */
// dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_1_rev_buf);
// dma_transfer_number_config(DMA0,DMA_CH5,NET_BUF_SIZE);
// dma_channel_enable(DMA0, DMA_CH5); /* 开启DMA传输 */
// }
//}
//DMA的中断函数/
void DMA0_Channel5_IRQHandler(void) //UART1 RX
{
if(dma_interrupt_flag_get(DMA0,DMA_CH5,DMA_INT_FLAG_FTF) != RESET)
{
dma_interrupt_flag_clear(DMA0,DMA_CH5,DMA_INT_FLAG_FTF); //清除传输完成标识位
if(NULL != Uart1Rev_CallBack)
{
if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
{
Uart1Rev_CallBack(uart1_1_rev_buf,NET_BUF_SIZE);
}
else{
Uart1Rev_CallBack(uart1_2_rev_buf,NET_BUF_SIZE);
}
}
//切换缓存区
if(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf)
{
dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_2_rev_buf);
}
else
{
dma_memory_address_config(DMA0,DMA_CH5,(uint32_t)uart1_1_rev_buf);
}
dma_transfer_number_config(DMA0,DMA_CH5,NET_BUF_SIZE);
dma_channel_enable(DMA0, DMA_CH5); /* 开启DMA传输 */
}
if(dma_interrupt_flag_get(DMA0,DMA_CH5,DMA_INT_FLAG_ERR) != RESET)
{
dma_interrupt_flag_clear(DMA0,DMA_CH5,DMA_INT_FLAG_ERR);
}
}
/* uart1 tx 开始DMA传送 */
void UART1_DMA_Transmit(const uint8_t *data, uint16_t len)
{
dma_channel_disable(DMA0, DMA_CH6); // 先禁用通道,防止在配置时发生冲突
dma_memory_address_config(DMA0, DMA_CH6, (uint32_t)data); // 配置发送数据的内存地址
dma_transfer_number_config(DMA0, DMA_CH6, len); // 配置传输数据量
dma_channel_enable(DMA0, DMA_CH6); // 启用DMA通道6开始发送
}
下面是串口 接受/发送 的回调函数:
void MCU_Uart_RevCallBack_Hook(MCU_Uart_Type uart, void (*UartRev_CallBack)(uint8_t *data, uint16_t len))
{
switch(uart)
{
case Prot_Uart1:
Uart1Rev_CallBack = UartRev_CallBack;
break;
case Prot_Uart2:
Uart2Rev_CallBack = UartRev_CallBack;
break;
case Prot_Uart3:
Uart3Rev_CallBack = UartRev_CallBack;
break;
case Prot_Uart5:
Uart5Rev_CallBack = UartRev_CallBack;
break;
case Prot_Uart7:
Uart7Rev_CallBack = UartRev_CallBack;
break;
default:
break;
}
}
void MCU_Uart_Send(MCU_Uart_Type uart, uint8_t *data, uint16_t len)
{
switch(uart)
{
case Prot_Uart1:
UART1_DMA_Transmit(data, len); //启动DMA开始发送
break;
case Prot_Uart2://开启中断发送
UART2_DMA_Transmit(data, len); //启动DMA开始发送
break;
case Prot_Uart3://开启中断发送
UART3_DMA_Transmit(data, len); //启动DMA开始发送
break;
// case Prot_Uart5://开启中断发送
// UART5_DMA_Transmit(data, len); //启动DMA开始发送
// break;
case Prot_Uart7://开启中断发送
UART0_DMA_Transmit(data,len);//启动DMA开始发送
break;
default:
break;
}
}
以下是测试:使用串口助手给mcu发送数据。
/*
串口DEBUG接收中断
*/
#if 1
static void DEBUGRevData(uint8_t* data, uint16_t len)
{
static uint16_t data_len = 0; // 当前接收的数据长度
uint16_t i = 0;
#if 1//串口dma切换通道接收
// 十六进制格式化输出
printf("[UART] Received %d bytes:\n", len);
for(uint16_t i=0; i<len; i++) {
printf("%02X ", data[i]);
if((i+1)%16 == 0) printf("\n");
}
printf("\n---------------------------------------------\n");
printf("uart11_rev_buf %d byte = %s\n",len,uart1_1_rev_buf);
printf("uart22_rev_buf %d byte = %s\n",len,uart1_2_rev_buf);
/* 双缓冲状态显示 */
// 获取当前DMA存储器地址
printf("[DMA Buffer Status]\n");
printf("Active Buffer: %s\n",
(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf) ? "Buffer2 (processing)" : "Buffer1 (processing)");
printf("Buffer1 Ready: %s\n",
(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_2_rev_buf) ? "Yes" : "No");
printf("Buffer2 Ready: %s\n",
(DMA_CHMADDR(DMA0,DMA_CH5) == (uint32_t)uart1_1_rev_buf) ? "Yes" : "No");
#endif
// for (i = 0; i < len; i++)
// {
// usartDebudg_recv_buf[data_len++] = data[i]; // 将接收到的数据存储到缓冲区
// // 检查是否接收到换行符
// if (data[i] == '\n')
// {
// // 将接收到的数据以字符串形式输出
// usartDebudg_recv_buf[data_len - 1] = '\0'; // 添加字符串结束符
// printf("Received Data: %s\n", usartDebudg_recv_buf);
//
// printf("uart11_rev_buf = %s\n",uart1_1_rev_buf);
// printf("uart22_rev_buf = %s\n",uart1_2_rev_buf);
// // 清空接收缓冲区
// data_len = 0;
// memset(usartDebudg_recv_buf, 0, NET_BUF_SIZE);
// }
// }
}
#endif
/*
串口1 DMA发送完成中断
*/
#if 1
static void NetTranCompleteCallback(void)
{
printf("\n");
tx_busy= 0;
printf("tx_busy = %d\n",tx_busy);
}
#endif
void CallBack_init(void)
{
XYMCU_Uart_DMATranCallBack_Hook(Prot_Uart7,NetTranCompleteCallback); //串口dma发送完成回调
XYMCU_Uart_RevCallBack_Hook(Prot_Uart1,DEBUGRevData); //串口空闲回调
}
int main()
{
//由于测试的功能较多同时我使用的并不是裸机测试,是rtos中测试,所有主函数测试函数可能不通,只是一个测试流程和方向。具体需要自己修改可能。。更主要的是外设的配置部分
systick_config(); // 初始化systick计时器
Uart_DMA_Init()
CallBack_init();
uint16_t tx_busy = 1;
while(1)
{
//串口dma发送 这里是测试dma发送功能,当使用串口发出去后,回调函数会立马tx_busy = 0。
tx_busy = 1;
printf("tx_busy = %d\n",tx_busy);
MCU_Uart_Send(Prot_Uart7,(uint8_t *)tx_buff,sizeof(tx_buff));
delay(1000);
}
}