STM32从停止模式唤醒后程序执行缓慢及串口乱码解决方法

测试STM32单片机低功耗模式时,遇到一个问题。当单片机从停止模式被唤醒后,LED指示灯闪烁变慢,同时串口通信出现乱码。
程序如下:


//将串口接收口做为中断唤醒
void EXIT_UART_Init(void)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    NVIC_InitTypeDef NVIC_InitStructure;
    EXTI_InitTypeDef EXTI_InitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);

    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource10);

    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_10MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);

    NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x00;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);

    EXTI_InitStructure.EXTI_Line = EXTI_Line10;
    EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
    EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
    EXTI_InitStructure.EXTI_LineCmd = ENABLE;
    EXTI_Init(&EXTI_InitStructure);

}
void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line10) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line10);
	}
}
//进入停止模式   任意外部中断唤醒  WKUP不能唤醒  
void enter_stop_mode(void)
{
    EXIT_UART_Init();											//RX引脚配置为外部中断
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); 		//开电源管理时钟
    PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);		//进入停机模式
}

进入停止模式时,首先将串口接收引脚PA10设置为外部中断,然后配置电源时钟,下来进入停止模式。在主程序中通过一个按键控制程序进入停止模式,然后给串口发送任意数据可以唤醒停止模式。
当程序从停机模式被唤醒后,发现LED闪烁频率变慢。
正常情况下LED指示灯波形为:
在这里插入图片描述
LED高低电平时长为51ms。
当从停机模式唤醒后,LED波形为:
在这里插入图片描述
从停机模式被唤醒后,LED高低电平时长变为450ms左右,和正常情况下为1:9的关系。为什么会这样呢?看到STM32中文参考手册里面有这样一句话:
在这里插入图片描述
退出停止模式时,HSI 被选为系统时钟。
在这里插入图片描述
HSI时钟为8MHz,而开发板的时钟是由外部晶振8MHz倍频到72MHz,刚好相差了9倍。所以从停机模式被唤醒后,LED的翻转速度为正常的1/9。
于是代码修改为从停机模式唤醒后,重新初始化一次时钟。

void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line10) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line10);
		SystemInit();						//重新设置时钟
	}
}

这样从停止模式恢复后,LED指示灯正常。但是又发现新的问题,串口通信出现了乱码。
在这里插入图片描述
主程序中隔3s发送一次数据,用来提示程序运行情况。当从停机模式唤醒后,会出现一次串口乱码情况。会不会是停止模式唤醒是只重新设置了系统时钟,没有重新设置串口引起的。那么在中断中在增加一个串口初始化看看。

void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line10) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line10);
		SystemInit();						//重新设置时钟
		uart_init(9600);					//重新初始化串口  
	}
}

增加串口初始化后,依然会出现乱码。那是什么原因造成的?会不会是在进入停止模式后,串口的寄存器某个位发生了改变? 在初始化串口前,先复位一次串口试试。

void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line10) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line10);
		SystemInit();						            //重新设置时钟
		USART_DeInit(USART1);				//复位串口
		uart_init(9600);					           //重新初始化串口  
	}
}

先将串口复位后,在初始化串口,未出现乱码情况。
在这里插入图片描述
为了验证这个现象,在调试模式看看串口寄存器的值是不是发生了变化,正常情况下串口寄存器值:
在这里插入图片描述
还未初始化串口时,串口1寄存器的值 SR寄存器为0xc0,其他寄存器全部为0。
在这里插入图片描述
串口初始化结束后,BRR寄存器值变为0x1D4C,CR1寄存器值变为0x202C。
然后全速运行一会程序,进入停止模式,再次唤醒后观察串口1寄存器的值。
在这里插入图片描述

当从停止模式唤醒后,SR寄存器值变为了0xFA,DR寄存器的值变成了0x55。
可以看出,串口的状态寄存器和数据寄存器发生了改变。所以在初始化串口之前要重新复位状态寄存器,和数据寄存器。那么在停机模式唤醒时不用复位串口,只需复位串口状态寄存器和数据寄存器就行。于是代码修改如下:

void EXTI15_10_IRQHandler(void)
{
    if(EXTI_GetITStatus(EXTI_Line10) != RESET)
    {
        EXTI_ClearITPendingBit(EXTI_Line10);
		SystemInit();						//重新设置时钟
		USART1->SR = 0x00C0;                //复位状态寄存器
		USART1->DR = 0x00;                  //复位数据寄存器	
		RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);	//使能USART1,GPIOA时钟
	}
}

中断中唤醒时,先重置系统时钟,然后复位串口状态寄存器和数据寄存器,然后重新初始化串口和GPIOA口时钟。
测试结果如下:
在这里插入图片描述
通过串口数据可以看出,串口未出现复位。同时和刚才直接复位串口相比,接收的数据中多了一行 退出停机模式提示。说明刚才直接复位串口的方法,会丢失一组数据。
主程序代码如下:

//进入停止模式   任意外部中断唤醒  WKUP不能唤醒  停止模式唤醒后系统 默认为 HSI 需要重新设置时钟
void enter_stop_mode(void)
{
    EXIT_UART_Init();											//RX引脚配置为外部中断
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE); 		//开电源管理时钟
    PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);		//进入停机模式
}
int main(void)
{
    u8 i = 0, j = 0;
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    delay_init();       										//延时函数初始化
    LED_Init();         										//初始化与LED连接的硬件接口
    KEY_Init();
    uart_init(9600);

    LED = 1;
    delay_ms(500);
    printf("low power test! \r\n\r\n");
    while(1)
    {
        i =  KEY_Scan(1);
        switch(i)
        {
        case 0:
            break;
        case 1:
            printf("进入停机模式\r\n\r\n");
            enter_stop_mode();									//唤醒后 接着下一行语句执行
            printf("退出停机模式\r\n\r\n");              		//唤醒后执行当前语句
            break;
        case 2:
            printf("进入待机模式\r\n\r\n");				
            Sys_Enter_Standby();						 		//唤醒后从程序开始位置执行
            printf("退出待机模式\r\n\r\n");				 		//执行不到这块
            break;
        case 3:
            printf("进入睡眠模式 中断唤醒 \r\n\r\n");
            sleep_mode_wfi();									//唤醒后接着下一条语句执行
            printf("退出睡眠模式 中断唤醒 \r\n\r\n");     		//唤醒后执行当前语句
            break;
        case 4:
            printf("进入睡眠模式 事件唤醒 \r\n\r\n");
            sleep_mode_wfe();									//唤醒后接着下一条语句执行
            printf("退出睡眠模式 事件唤醒 \r\n\r\n");				//唤醒后执行当前语句
            break;
        }

        j++;
       
		if(j>199)
		{
			  j = 0;
			printf("low power test running!\r\n");
		}

	   if(j % 5==0)
        {       
            LED = !LED;
        }
        delay_ms(10);

    }
}

总结:在使用低功耗停止模式时,从停止模式唤醒时,需要重新配置系统时钟,外设重新配置时最好先复位,然后再配置。

发布了76 篇原创文章 · 获赞 30 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_20222919/article/details/100260197
今日推荐