使用STM32自带DMA传输数据,可以减轻CPU负担,只需设置一些参数即可发送想要发送的数据,以下是STM32F407VE芯片测试过的部分代码,可实现DMA串口收发数据。下图来自STM32官网的手册,RM0009.pdf
发送数据逻辑图:
接收数据逻辑图
下面是使用STM32 HAL库进行配置,大致实现思路都是一样的,先开启串口初始化(开启DMA传输),相应的DMA初始化,然后设置好传输地址,传输字节个数,然后启动使能
uint8_t u8txbuff[1024]; //发送缓冲区
uint8_t u8rxbuff[1024]; //接收缓冲区
//串口1初始化
//m_u32BaudRate:设置波特率
void usart1_init(uint32_t m_u32BaudRate)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
//设置GPIOA和串口时钟
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_USART1_CLK_ENABLE();
//设置串口GPIO
GPIO_InitStruct.Pin = GPIO_PIN_9|GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF7_USART1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
//设置串口USART1参数
huart1.Instance = USART1;
huart1.Init.BaudRate = m_u32BaudRate; //ÉèÖò¨ÌØÂÊ
huart1.Init.WordLength = UART_WORDLENGTH_8B; //Êý¾Ýλ£º8¸ö
huart1.Init.StopBits = UART_STOPBITS_1; //ֹͣλ
huart1.Init.Parity = UART_PARITY_NONE; //ÎÞÆæżУÑé
huart1.Init.Mode = UART_MODE_TX_RX; //ÊÕ·¢Ä£Ê½
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE; //ÎÞÓ²¼þ¿ØÖÆÁ÷
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK)
while(1);
//设置串口中断和中断优先级
HAL_NVIC_SetPriority(USART1_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(USART1_IRQn);
//DMA时钟设置
__HAL_RCC_DMA2_CLK_ENABLE();
//启动DMA传输 PA10 RX
hdma_usart1_tx.Instance = DMA2_Stream7; //Êý¾ÝÁ÷7
hdma_usart1_tx.Init.Channel = DMA_CHANNEL_4; //ͨµÀ4
hdma_usart1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; //ÄÚ´æµ½ÍâÉè
hdma_usart1_tx.Init.PeriphInc = DMA_PINC_DISABLE; //ÍâÉèµØÖ·²»Ôö³¤
hdma_usart1_tx.Init.MemInc = DMA_MINC_ENABLE; //ÄÚ´æµØÖ·Ôö³¤
hdma_usart1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //×Ö½Ú¶ÔÆë
hdma_usart1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //×Ö½Ú¶ÔÆë
hdma_usart1_tx.Init.Mode = DMA_NORMAL; //Õý³£Ä£Ê½
hdma_usart1_tx.Init.Priority = DMA_PRIORITY_HIGH; //´«ÊäµÈ¼¶¸ß
hdma_usart1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; //FIFOģʽ²»Ê¹ÓÃ
if (HAL_DMA_Init(&hdma_usart1_tx) != HAL_OK)
while(1);
//PA9 TX
hdma_usart1_rx.Instance = DMA2_Stream2; //DMA2数据流
hdma_usart1_rx.Init.Channel = DMA_CHANNEL_4; //通道4
hdma_usart1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; //外设到内存
hdma_usart1_rx.Init.PeriphInc = DMA_PINC_DISABLE; //外设地址不增加
hdma_usart1_rx.Init.MemInc = DMA_MINC_ENABLE; //内存地址增加
hdma_usart1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; //字节对齐
hdma_usart1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; //×Ö½Ú¶ÔÆë
hdma_usart1_rx.Init.Mode = DMA_CIRCULAR; //Ñ»·Ä£Ê½
hdma_usart1_rx.Init.Priority = DMA_PRIORITY_MEDIUM; //´«ÊäµÈ¼¶ÖеÈ
hdma_usart1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; //FIFOģʽ²»Ê¹ÓÃ
if (HAL_DMA_Init(&hdma_usart1_rx) != HAL_OK)
while(1);
//设置接收完成中断
__HAL_USART_ENABLE_IT(&huart1,USART_IT_TC); //设置发送完成中断
__HAL_USART_CLEAR_FLAG(&huart1, UART_FLAG_TC);//清除发送完成标志位
//设置DMA接收
SET_BIT(huart1.Instance->CR3, USART_CR3_DMAT);//串口启用DMA接收数据
HAL_DMA_Start(&hdma_usart1_tx,(uint32_t)I_USART.u8txBuffer,(uint32_t)&USART1->DR,0);//设定传输地址,传输源地址,传送字节个数
//设置DMA发送
SET_BIT(huart1.Instance->CR3, USART_CR3_DMAR);//串口启用DMA发送数据
HAL_DMA_Start(&hdma_usart1_rx,(uint32_t)&USART1->DR,(uint32_t)I_USART.u8rxBuffer,BUFFSIZE); //设定传输地址,传输源地址,传送字节个数
}
这个需要开启串口发送完成中断,当DMA往串口传送全部的数据完成后,串口会产生发送完成中断。
//串口2发送中断
void USART2_IRQHandler(void)
{
if((USART2->SR & UART_FLAG_TC)!=RESET)
{
//当DMA传输数据完成以后,就会进入一次这个中断
USART2->SR = ~(UART_FLAG_TC);
u8txmode = SEND_FINISH//此处设置:发送完成标志
}
}
每当调用这个函数就会,要求DMA发送一段数据给USART的数据寄存器当中,接下来等待进入串口发送中断后,就可以判定DMA传送数据到串口已经完成发送。
//入口参数
//u16txlen: 发送数据长度
void USART2_TxdSend(uint16_t u16txlen)
{
__HAL_DMA_DISABLE(&hdma_usart2_tx); //暂停DMA
__HAL_UART_CLEAR_FLAG(&I_huart2, UART_FLAG_TC); //清除发送中断
__HAL_DMA_GET_COUNTER(&hdma_usart2_tx) = u16txlen; //设置发送数据长度
__HAL_DMA_ENABLE(&hdma_usart2_tx); //启动DMA
}
开启定时器是希望周期性的去查询DMA传送了多少的数据到内存,也就是说接收了多少个字节。接收状态时,超过一定时间后,DMA传送字节个数没有变化(也就是说DMA_CNDTR寄存器没有变化)就可以判定一帧数据已经接收完成了。
void Timer4_Init(void)
{
TIM_MasterConfigTypeDef sMasterConfig = {0};
//01-定时器4时钟
__HAL_RCC_TIM4_CLK_ENABLE();
//02-定时器4
I_htim4.Instance = TIM4; //TIM4
I_htim4.Init.Prescaler = 35; //35分频
I_htim4.Init.CounterMode = TIM_COUNTERMODE_UP; //向上计数
I_htim4.Init.Period = 10000; //重装载值 0 ->> 10000
I_htim4.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1; //时钟源不分频
I_htim4.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE; //启动重装载值
if (HAL_TIM_Base_Init(&I_htim4) != HAL_OK)
while(1);
//03-设置中断优先级
HAL_NVIC_SetPriority(TIM4_IRQn, 3, 0);
HAL_NVIC_EnableIRQ(TIM4_IRQn);
//05-设置中断源
sMasterConfig.MasterOutputTrigger = TIM_TRGO_UPDATE; //更新中断
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&I_htim4, &sMasterConfig) != HAL_OK)
while(1);
//06-清除中断标志位
__HAL_TIM_CLEAR_FLAG(&I_htim4,TIM_FLAG_UPDATE);
//07-打开定时器中断
HAL_TIM_Base_Start_IT(&I_htim4);
}
DMA的DMA_CNDTR寄存器每当传输一个数据时,这个寄存器会自动减1,即接收一个数据。当串口处于接收状态时,使用定时器中断1ms周期查询DMA的DMA_CNDTR寄存器变化。
假定BUFFMAX为缓冲区长度:
①当BUFFMAX等于DMA_CNDTR寄存器值,没有输入接收(空闲状态)
②当BUFFMAX不等于DMA_CNDTR寄存器值,有新的数据接收(接收状态)
③当DMA_CNDTR寄存器超过20ms没有变化,判定接收数据完成。
#define RECE_FINISH 0 //接收数据完成标志
#define RECE_ING 1 //正在就收数据
#define RECE_ILDE 2 //空闲状态
uint16_t u16rxp; //现在接收数据长度
uint16_t u16timer; //接收计时器
uint8_t u8rxmode; //接收状态
//定时器4(每1毫秒进入一次中断)
void TIM4_IRQHandler(void)
{
//获取定时器更新
if(__HAL_TIM_GET_FLAG(&I_htim4,TIM_FLAG_UPDATE) != RESET)
{
//清除定时器中断
__HAL_TIM_CLEAR_FLAG(&I_htim4,TIM_FLAG_UPDATE);
if(u8rxmode != RECE_FINISH)
{
if(u16rxp != (BUFFMAX - DMA1_Channel6->CNDTR))
{//查询接收数据有变化
u16timer =0;
u16rxp = (BUFFMAX - DMA1_Channel6->CNDTR); //当前已经获取的数据长度
}
if(u16timer < 20 )
u16timer ++; //接收数据计数
if(u16timer ==20)
{//超过一定时间,DMA没传过任何数据,则认为完成
u16timer =0;
u8rxmode = RECE_FINISH;
__HAL_DMA_DISABLE(&hdma_usart2_rx); //暂停DMA2
DMA1_Channel6->CNDTR = BUFFMAX; //写入缓冲区长度
__HAL_DMA_ENABLE(&hdma_usart2_rx); //启动DMA2
}
}
}
}
当往usart2串口发送数据,数据会发送回来,速度取决于对每一帧数据接收超时判断。
#include
extern uint16_t u16rxp;
extern uint8_t u8txbuff;
extern uint8_t u8rxbuff;
extern uint8_t u8rxmode;
int main(void)
{
uart2_init(115200); //波特率设置 115200
Timer4_Init(); //开启定时器
while(1)
{
if(u8rxmode==RECE_FINISH) //判定接收完成
{
memcpy(u8txbuff,u8rxbuff,u16rxp); //将接收的数据放入发送缓冲区
USART2_TxdSend(u16rxp); //发送数据
u8rxmode=RECE_ILDE;//进入空闲状态
}
}
}