最近遇到一个小问题,感觉很有意思便记下来分享一下 ,顺便也为日后类似的问题提供点思路:
使用stm32串口发送数据 ,串口是单线半双工模式,要求数据发送前串口变成发送模式,发送完后立即变回接收模式,发送操作使用DMA来发送,程序开了串口中断和DMA中断,串口中断主要是用于接收与解析数据,DMA中断是想用于操作串口收发模式切换,发现DMA发送时最后一个数据老是发不出来。
经过分析定位发现是因为DMA将传输完成后,串口其实还没有将所有的问题发送出去(至少还有一个字节没发出去),然后修改操串口收发模式切换时间点将该问题解决,具体为:串口在DMA传输前由接收模式变成发送模式,在DMA传输完成中断中开启串口发送完成中断,在串口发送完成中断中将串口工作模式从发送模式变回接收模式,问题完解决,相关配置代码如下:
串口配置:
void Usart_Init( void )
{
USART_InitTypeDef USART_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//GPIO clock enable
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // USART1时钟使能
//USART1_TX
GPIO_InitStructure.GPIO_Pin = USART1_TX_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_Init(USART1_TXRX_PORT, &GPIO_InitStructure);
//USART1_RX
GPIO_InitStructure.GPIO_Pin = USART1_RX_PIN;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; // 浮空输入
GPIO_Init(USART1_TXRX_PORT, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = 115200; // 串口波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;// 字长为8位数据格式
USART_InitStructure.USART_StopBits = USART_StopBits_1; // 一个停止位
USART_InitStructure.USART_Parity = USART_Parity_No; // 无奇偶校验位
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;// 无硬件数据流控制
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; // 收发模式
USART_Init(USART1, &USART_InitStructure); // 初始化串口1
USART_HalfDuplexCmd(USART1, ENABLE); // 使能变双工
DMA_Usart_Init();
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn; // USART1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ;// 抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // IRQ通道使能
NVIC_Init(&NVIC_InitStructure); // 根据指定的参数初始化NVIC寄存器
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 开启串口1接收中断
USART_Cmd(USART1, ENABLE); // 使能串口1
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE); // 开启串口USART1的DMA发送
}
DMA配置:
void DMA_Usart_Init(void)
{
DMA_InitTypeDef DMA_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1, ENABLE); //开启DMA1时钟
DMA_Cmd(DMA1_Channel4, DISABLE);//关闭DMA1的通道4 Usart1_TX对应通道4
DMA_DeInit(DMA1_Channel4);//恢复缺省值
DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART1->DR);//设置串口发送数据寄存器
DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)DMATXBUFF1;//设置发送缓冲区首地址
DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;//设置外设位目标,内存缓冲区->外设寄存器
DMA_InitStructure.DMA_BufferSize = 10;//需要发送的字节数,这里可以设置为0,因为在实际要发送时还会重新设置
DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;//外设地址不做增加调整,调整不调整是DMA自动实现的
DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;//内存缓冲区地址增加调整
DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;//外设数据宽度8位,1个字节
DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;//内存数据宽度8位,1个字节
DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;//单次传输模式
DMA_InitStructure.DMA_Priority = DMA_Priority_Low;//优先级配置
DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;//关闭内存到内存的DMA模式
DMA_Init(DMA1_Channel4, &DMA_InitStructure);//写入配置
NVIC_InitStructure.NVIC_IRQChannel = DMA1_Channel4_IRQn;//DMA1中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1 ;//抢占优先级2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //子优先级1
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道使能
NVIC_Init(&NVIC_InitStructure); //根据指定的参数初始化NVIC寄存器
DMA_ClearFlag(DMA1_FLAG_GL4);//清除DMA所有标志 GL4:通道4全局标志 TC4:传输完成 HT4:传输过半 TE4:传输错误
DMA_Cmd(DMA1_Channel4, DISABLE);//关闭DMA1的通道4
DMA_ITConfig(DMA1_Channel4, DMA_IT_TC, ENABLE);//开启发送DMA通道中断
}
串口中断:
void USART1_IRQHandler(void)
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) // 接收数据寄存器非空标志
{
if(RxNum1 > (RXBUFFSIZE1 - 1))
{
RxNum1 = 0;
}
RXBUFF1[RxNum1++] = USART_ReceiveData(USART1);
}
else if(USART_GetITStatus(USART1, USART_IT_TC) != RESET)// 发送完成标志
{
USART_ITConfig(USART1, USART_IT_TC, DISABLE);
USART_ClearFlag(USART1,USART_FLAG_TC);
USART1_RX_EN_TX_DIS();
}
else
{
}
}
DMA中断:
void DMA1_Channel4_IRQHandler(void)
{
if(DMA_GetITStatus(DMA1_FLAG_TC4) != RESET) // DMA1通道4发送完成标志
{
Usart1TxBusy = 0;
DMA_Cmd(DMA1_Channel4, DISABLE); // 关闭DMA1的通道4
DMA_ClearFlag(DMA1_FLAG_GL4); // 清除DMA1的通道4的所有中断标志,因为只有发送完成标志,所以直接全部清除
USART_ClearFlag(USART1,USART_FLAG_TC); // 在关中断前先清中断标志,防止是串口上一个数据发送时的中断标志
USART_ITConfig(USART1, USART_IT_TC, ENABLE);// DMA1传输完成后使能Usart1发送完成中断
}
}