stm32f1系列标准库使用串口3进行DMA收消息时会死机的问题解决

使用这个芯片的串口3进行dma接收的时候发现一个问题。

现象:一旦接收到数据,便会一直进入串口3的中断回调handle函数(我使用的空闲中断,理论上不应该这样),主函数一条都不会执行。

发现过程:在USART3_IRQHandler里面设置串口打印步骤,如下图

图中的printf就是用来打印函数步骤,当收到一个1,串口界面就会显示如下r

如上,一直在中断里面循环,直到我的看门狗生效复位(喂狗函数在主程序,所以我推测循环进入中断后根本不会执行主函数)

后来我查阅了很多,加了一个步骤后就解决了。也就是如下的关键代码
       temp = USART3->SR;
       temp = USART3->DR;

当我设置一个temp读取串口3的这两个寄存器后,串口回调就正常了。具体原因我也不清楚,希望懂的大佬可以回答一下。


以下是串口3dma收发源码

宏定义

#define USART3_DMA_RX_BUFFER_MAX_LENGTH        (255)
#define USART3_DMA_TX_BUFFER_MAX_LENGTH        (255)
uint8_t USART3_DMA_RX_Buffer[USART3_DMA_RX_BUFFER_MAX_LENGTH];
uint8_t USART3_DMA_TX_Buffer[USART3_DMA_TX_BUFFER_MAX_LENGTH];
#define  DEBUG_USART_IRQ                USART3_IRQn
#define  DEBUG_USART_IRQHandler         USART3_IRQHandler


USART_STR MB_USART3;   

//该变量对应的结构体
typedef struct USART_STR
{
	unsigned char receCount;  //接收长度
	unsigned char mscomm_buffer[256];  //接收缓存区
	unsigned char rec_end_flag;  //接收完成标志
} USART_STR;

1、串口初始化以及中断初始化

void USART3_Configuration(void);

void USART3_Configuration(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	USART_InitTypeDef USART_InitStructure;
	NVIC_InitTypeDef NVIC_InitStructure;
	
	// config USART3 clock
	RCC_APB1PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB1Periph_USART3, ENABLE);  // 使能 GPIOB 和 USART3 的时钟
 
	// Configure USART3 Tx (PB.10) as alternate function push-pull 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                             // 设置 GPIOB 的 Pin 10
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;                        // 设置 GPIOB 的模式为复用推挽输出
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;                      // 设置 GPIOB 的输出速度为 50MHz
	GPIO_Init(GPIOB, &GPIO_InitStructure);                                 // 使用上述配置初始化 GPIOB
	
	// Configure USART3 Rx (PB.11) as input floating 
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11;                            // 设置 GPIOB 的 Pin 11
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;                 // 设置 GPIOB 的模式为浮空输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);                                // 使用上述配置初始化 GPIOB
	
	// USART3 mode config
	USART_InitStructure.USART_BaudRate = 115200;                                // 设置 USART 的波特率为 115200
	USART_InitStructure.USART_WordLength = USART_WordLength_8b;                 // 设置 USART 的数据位长度为 8 位
	USART_InitStructure.USART_StopBits = USART_StopBits_1;                      // 设置 USART 的停止位为 1 位
	USART_InitStructure.USART_Parity = USART_Parity_No;                          // 设置 USART 的奇偶校验位为无校验
	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None; // 设置 USART 的硬件流控制为无流控制
	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;             // 设置 USART 的工作模式为同时支持接收和发送
	USART_Init(USART3, &USART_InitStructure);                                   // 使用上述配置初始化 USART3 控制器
		
	
	
  
	/* 嵌套向量中断控制器组选择 */
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

	/* 配置串口的 NVIC设置*/
	NVIC_InitStructure.NVIC_IRQChannel = DEBUG_USART_IRQ;                         // 设置调试串口的中断通道
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;                     // 设置中断抢占优先级为 1
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                             // 设置中断子优先级为 0
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                                // 使能中断通道
	NVIC_Init(&NVIC_InitStructure);                                                // 使用上述配置初始化中断控制器
	
	//CPU的小缺陷:串口配置好,如果直接Send,则第1个字节发送不出去如下语句解决第1个字节无法正确发送出去的问题
	//USART_ClearFlag(UART4, USART_FLAG_TC);     				//清发送完成标志,Transmission Complete flag  	
	//USART_ITConfig(UART4, USART_IT_RXNE, ENABLE);
	//USART_Cmd(UART4, ENABLE);
	
	USART_Cmd(USART3, ENABLE);
	USART_ClearFlag(USART3, USART_FLAG_TC); //清除发送完成标志	
	while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);//等待空闲帧发送完成后再清零发送完成标志(警告:如果不使能USART_Mode_Tx,会导致单片机在这里死机)
	USART_ClearFlag(USART3, USART_FLAG_TC);	//清除发送完成标志
 
	USART_ITConfig(USART3, USART_IT_RXNE, DISABLE);     // 禁用接收中断
	USART_ITConfig(USART3, USART_IT_TXE, DISABLE);      // 禁用发送中断
	USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);      // 打开接收空闲中断
	USART_ITConfig(USART3, USART_IT_TC, ENABLE);        // 打开发送完成中断
	USART_DMACmd(USART3, USART_DMAReq_Tx, ENABLE);      // 使能发送DMA请求
	USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE);      // 使能接收DMA请求
	
//错误:
//1、以下情况初始化STM32F013VC单片机串口3,则单片机会死机
//	USART_ClearFlag(USART3, USART_FLAG_TC); //清除发送完成标志	
//	while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);	//等待空闲帧发送完成后再清零发送完成标志(警告:如果不使能USART_Mode_Tx,会导致单片机在这里死机)
//	USART_ClearFlag(USART3, USART_FLAG_TC);	//清除发送完成标志
//	USART_Cmd(USART1, ENABLE);
//正确:
//2、以下情况初始化STM32F013VC单片机串口3,则正常运行
//	USART_Cmd(USART3, ENABLE);
//	USART_ClearFlag(USART3, USART_FLAG_TC); //清除发送完成标志	
//	while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);	//等待空闲帧发送完成后再清零发送完成标志(警告:如果不使能USART_Mode_Tx,会导致单片机在这里死机)
//	USART_ClearFlag(USART3, USART_FLAG_TC);	//清除发送完成标志
//总结:
//3、因此USART_Cmd(USART3, ENABLE);必须放在while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);前面执行
}

 2、DMA初始化

2.1、TX初始化
 

void USART3_DMA_Tx_Configuration(void)
{
	DMA_InitTypeDef  DMA_InitStructure;
	
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);						//DMA1时钟使能
	DMA_DeInit(DMA1_Channel2);
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&USART3->DR;		//DMA外设地址
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART3_DMA_TX_Buffer;	//发送缓存指针
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralDST;						//传输方向
    DMA_InitStructure.DMA_BufferSize = USART3_DMA_TX_BUFFER_MAX_LENGTH;		//传输长度
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;		//外设递增
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; 				//内存递增
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;	//外设数据宽度:BYTE
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;			//内存数据宽度:BYTE
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//循环模式:否//(注:DMA_Mode_Normal为正常模式,DMA_Mode_Circular为循环模式)
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh; 				//优先级:高
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable; 							//内存:内存(都)
	DMA_Init(DMA1_Channel2 , &DMA_InitStructure);							//初始化DMA1_Channel2
	DMA_ClearFlag(DMA1_FLAG_GL2);
	DMA_Cmd(DMA1_Channel2 , DISABLE); 										//开启DMA传输
}

2.2 RX初始化
 

//USART3  RX DMA初始化程序
void USART3_DMA_Rx_Configuration(void)
{
	DMA_InitTypeDef  DMA_InitStructure;
 
	
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);						//DMA1时钟使能
    DMA_DeInit(DMA1_Channel3);  											// DMA1 通道3 复位为默认值
	DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&USART3->DR);		// DMA 的外设基地址为 USART3 的数据寄存器地址。
    DMA_InitStructure.DMA_MemoryBaseAddr = (uint32_t)USART3_DMA_RX_Buffer;	//接收缓存指针    
    DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralSRC;          			//设置数据传输方向为从外设到存储器。          
    DMA_InitStructure.DMA_BufferSize = USART3_DMA_RX_BUFFER_MAX_LENGTH;		//缓冲大小
    DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;        //分别设置外设地址和存储器地址不自增。
    DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                	//分别设置外设地址和存储器地址不自增。
    DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; //设置外设数据和存储器数据的大小为字节。
    DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;        	//设置外设数据和存储器数据的大小为字节。
    DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;							//(注:DMA_Mode_Normal为正常模式,DMA_Mode_Circular为循环模式)                           
    DMA_InitStructure.DMA_Priority = DMA_Priority_VeryHigh;                 //设置 DMA 的优先级为非常高。
    DMA_InitStructure.DMA_M2M = DMA_M2M_Disable;                            //禁用存储器到存储器的传输。
	
    DMA_Init(DMA1_Channel3, &DMA_InitStructure);            				//初始化 DMA1 通道3
    DMA_ClearFlag(DMA1_FLAG_GL3);                              				//清除 DMA 标志位
    DMA_Cmd(DMA1_Channel3 , ENABLE);  										//使能 DMA1 通道3
}

3、串口3DMA发送
 

//USART3  启动DMA发送初始化程序
void USART3_DMA_Begin_Send(uint8_t *send_buffer , uint16_t nSendBytes)
{		
	if (nSendBytes < USART3_DMA_TX_BUFFER_MAX_LENGTH)
	{
		memcpy(USART3_DMA_TX_Buffer , send_buffer , nSendBytes);
		DMA_Cmd(DMA1_Channel2 , DISABLE);                    				//关闭DMA传输
		DMA_SetCurrDataCounter(DMA1_Channel2 , nSendBytes);  				//数据传输量
		DMA_Cmd(DMA1_Channel2 , ENABLE);               						//开启DMA传输
	}
}

串口3中断回调函数
 

void USART3_IRQHandler(void)
{
	int temp = 0;
    if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) //接收处理
    {				

		temp = USART3->SR;
		temp = USART3->DR;//读数值,防止死机
        // 清除空闲中断标志位
        USART_ClearITPendingBit(USART3, USART_IT_IDLE);
        // 关闭 DMA1 通道3
        DMA_Cmd(DMA1_Channel3, DISABLE);        		

        // 获取接收到的数据长度
        uint16_t receivedLength = USART3_DMA_RX_BUFFER_MAX_LENGTH - DMA_GetCurrDataCounter(DMA1_Channel3);        
        if (receivedLength > 0)
        {
            // 执行接收数据的处理操作
            memcpy(MB_USART3.mscomm_buffer, USART3_DMA_RX_Buffer, receivedLength);
            MB_USART3.receCount = receivedLength;            
            //printf("U3Res: ->%s<-\r\n", MB_USART3.mscomm_buffer);
			MB_USART3.rec_end_flag = 1;


        }
        // 重置 DMA1 通道3 的数据计数器
        DMA_SetCurrDataCounter(DMA1_Channel3, USART3_DMA_RX_BUFFER_MAX_LENGTH);
        // 启用 DMA1 通道3
        DMA_Cmd(DMA1_Channel3, ENABLE);
    }else if (USART_GetITStatus(USART3,USART_IT_TC)!= RESET) //发送处理
	{
		USART_ClearITPendingBit(USART3, USART_IT_TC);				
		DMA_ClearFlag(DMA1_FLAG_GL2 | DMA1_FLAG_TC2 | DMA1_FLAG_HT2 | DMA1_FLAG_TE2);
		DMA_SetCurrDataCounter(DMA1_Channel2 , 0);
	}else if(USART_GetITStatus(USART3, USART_IT_PE | USART_IT_FE | USART_IT_NE |USART_IT_ORE) != RESET)//出错清除
	{
		USART_ClearITPendingBit(USART3, USART_IT_PE | USART_IT_FE | USART_IT_NE | USART_IT_ORE);
		//读SR后读DR清除ORE   以前以为清除中断就可以了,芯片资料要求需要读数据寄存器的值,不然出错后也会死机
		temp = USART3->SR;
		temp = USART3->DR;
	}
	temp=temp;//避免编译器警告

}

主程序示范

main.c

extern USART_STR MB_USART3;

int main()
{


	USART3_Configuration();
	USART3_DMA_Tx_Configuration();
	USART3_DMA_Rx_Configuration();

    while(1)
    {
        if(MB_USART3.rec_end_flag == 1)//如果接收到了标志位
		{
			USART3_DMA_Begin_Send("Rec-OK\r\n\0",strlen("Rec-OK\r\n\0")+1);//串口3返回确认
			printf("MAIN_PUSH:->%s<-\r\n",MB_USART3.mscomm_buffer);//将数据打印给串口1
			MB_USART3.rec_end_flag = 0;  //清除标志位
			
		}
    }

}

代码中涉及到的pribtf为串口1输出调试重定义后的写法。如报错可注释。

  • 7
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
下面是一个使用STM32F407标准库,通过USART2和DMA进行串口数据接的示例代码。 首先,需要初始化USART2和DMA。以下是初始化USART2和DMA的函数: ```c void USART2_DMA_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; DMA_InitTypeDef DMA_InitStructure; // 使能GPIOA时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); // 使能USART2时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE); // 使能DMA1时钟 RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); // 配置PA2为USART2的TX引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 配置PA3为USART2的RX引脚 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOA, &GPIO_InitStructure); // 将PA2和PA3连接到USART2 GPIO_PinAFConfig(GPIOA, GPIO_PinSource2, GPIO_AF_USART2); GPIO_PinAFConfig(GPIOA, GPIO_PinSource3, GPIO_AF_USART2); // 配置USART2 USART_InitStructure.USART_BaudRate = 115200; USART_InitStructure.USART_WordLength = USART_WordLength_8b; 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(USART2, &USART_InitStructure); // 配置DMA1_Stream5 DMA_DeInit(DMA1_Stream5); DMA_InitStructure.DMA_Channel = DMA_Channel_4; DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory; DMA_InitStructure.DMA_Mode = DMA_Mode_Circular; DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable; DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable; DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte; DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte; DMA_InitStructure.DMA_BufferSize = 100; DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)&(USART2->DR); DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)usart2_rx_buffer; DMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_Full; DMA_InitStructure.DMA_Priority = DMA_Priority_High; DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Enable; DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single; DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single; DMA_Init(DMA1_Stream5, &DMA_InitStructure); // 使能USART2和DMA1_Stream5 USART_Cmd(USART2, ENABLE); DMA_Cmd(DMA1_Stream5, ENABLE); // 使能USART2的DMA USART_DMACmd(USART2, USART_DMAReq_Rx, ENABLE); } ``` 在上述代码中,通过GPIO_Init()函数配置USART2的TX和RX引脚,然后通过USART_Init()函数配置USART2。使用DMA_Init()函数初始化DMA1_Stream5,并将其连接到USART2的DR寄存器。最后,使用USART_DMACmd()函数启用USART2的DMA。 接下来,需要设置一个DMA完成中断(Transfer Complete interrupt)。以下是设置DMA完成中断的函数: ```c void DMA1_Stream5_IRQHandler(void) { if (DMA_GetITStatus(DMA1_Stream5, DMA_IT_TCIF5) != RESET) { DMA_ClearITPendingBit(DMA1_Stream5, DMA_IT_TCIF5); DMA_Cmd(DMA1_Stream5, DISABLE); DMA_SetCurrDataCounter(DMA1_Stream5, 100); DMA_Cmd(DMA1_Stream5, ENABLE); } } ``` 在上述代码中,使用DMA_GetITStatus()函数检查DMA完成中断是否已被触发。如果中断被触发,使用DMA_ClearITPendingBit()函数清除中断标志,并使用DMA_Cmd()函数禁用DMA1_Stream5。最后,使用DMA_SetCurrDataCounter()函数将DMA1_Stream5的数据计数器重置为100,并使用DMA_Cmd()函数重新启动DMA1_Stream5。 现在,可以使用以下代码在主函数中调用上述函数来初始化USART2和DMA: ```c int main(void) { USART2_DMA_Init(); while (1) { } } ``` 最后,可以使用以下代码从usart2_rx_buffer数组中读取接到的数据: ```c uint8_t usart2_rx_buffer[100]; uint8_t usart2_rx_buffer_index = 0; void USART2_IRQHandler(void) { if (USART_GetITStatus(USART2, USART_IT_RXNE) != RESET) { usart2_rx_buffer[usart2_rx_buffer_index++] = USART_ReceiveData(USART2); if (usart2_rx_buffer_index >= 100) { usart2_rx_buffer_index = 0; } } } ``` 在上述代码中,使用USART_GetITStatus()函数检查USART2是否已接到数据。如果有数据可用,使用USART_ReceiveData()函数将数据读取到usart2_rx_buffer数组中。最后,检查usart2_rx_buffer_index是否超过了数组的大小,如果是,则将其重置为0。 这是一个简单的使用STM32F407标准库通过USART2和DMA进行串口数据接的示例。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值