风子 发表于 2015-11-8 14:49:20

【NUCLEO-L476RG开发】LPUART唤醒停止模式STOP Mode 2

本帖最后由 风子 于 2015-11-8 16:16 编辑

LPUART 即 LOW POWER UART,是STM32L4系列专为低功耗设计的低功耗外设之一,既然玩低功耗的板子,当然不能少了捣鼓低功耗功能,这次就玩玩LPUART。
同时,在上一篇帖子【NUCLEO-L476RG LL库开发】STM32【LL库】开发使用指南中初步介绍了 STM32 LL库以及它的独立使用方法,这个帖子顺便演示一下可能是更通用(实用)的使用方法:LL库和HAL库混合使用。毕竟HAL库使用CUBEMX,非常方便,而独立使用LL库还需要自己建立工程,况且LL库本来就是和HAL库集成在一起的,相互配合,优势互补,最大限度的兼顾用户编程使用方便和MCU性能功耗,我想这才是ST设计这个库的初衷。不过这不是这个帖子的重点,不讲,只在代码中演示。
实验目的是测试LPUART的唤醒Stop mode 2功能,要做的工作如下:
1.硬件,nucleo的虚拟串口默认接的是USART2,要使用LPUART,需要重新接线
2.软件,配置好LPUART,并且重定向printf函数,便于输出需要的信息
3.配置进入Stop mode 2的准备工作
4.进入Stop mode 2
5.使用LPUART唤醒MCU
6.输出一些过程中观察需要的调试信息
大概就有这么些内容,这个实验是参考了STM32L4Cube里面的一个例子,由于水平有限,这也只是个人实验笔记,可能有些地方写得不好,大家自己去看官方库里面的例程,位于STM32Cube_FW_L4_V1.1.0\Projects\STM32L476RG-Nucleo\Examples_LL\LPUART\LPUART_WakeUpFromStop2文件夹中。下面是具体操作:
1.板载ST-LINK有虚拟串口功能,并且引出了TX,RX引脚,只需要把他们和LPUART1的RX,TX引脚连起来即可,注意板子上的TX,RX是针对ST-LINK端说的,所以连接时和LPUART的TX,RX反过来连接,如图
注意RX,TX上连了其他USART的时候千万不要同时使用USART2,本来使用其他串口的时候应该断开板子上SB13,SB14,闲麻烦就不断开了,别使用USART2就好。
2.1配置LPUART1,这个使用Cubemx来完成,比较简单
打开LPUART1,配置PC0,PC1为LPUART1_RX,LPUART1_TX,打开RCC 外部低速时钟LSE
系统时钟选HSI经PLL倍频,80M,LPUART时钟选择LSE,这里不能使用PCLK,因为进入stop mode 2后PLL,系统时钟会被关闭,相应外设不能工作,所以此处只能选择HSI或者LSE,如果用HSI,上一步可以不必打开LSE。
LPUART1参数:波特率9600,使用LSE时钟波特率最高只能9600,其他默认。
打开LPUART的中断。
配置好后就可以直接生成工程了,我使用的是MDK5.16a,生成MDKv5工程。
2.2,重定向printf到串口LPUART1
printf函数非常方便,所以用它来输出调试信息,打开上一步生成的工程,在合适的地方加入代码:#ifdef__GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
HAL_UART_Transmit(&hlpuart1, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}//main中
        printf("This is a test!\n");

最后一句是放在主函数中,用来测试下printf函数能不能正常输出信息
看起来一切正常,可以下一步了。
2.3先来输出一些信息,供后面比较,输出系统时钟信息:
在前面配置中,我们使用了PLL倍频,得到80M的系统时钟,而在进入stop mode 2后PLL会被关闭,重新唤醒之后只能使用HSI或者MSI作为系统时钟,PLL需要重新使能才能使用,就像系统刚复位的时候一样,所以进入停止模式之前,应该先设置好唤醒之后的系统时钟,这是准备工作之一,
下面的函数用于获取当前系统时钟设置:void HAL_RCC_GetClockConfig(RCC_ClkInitTypeDef*RCC_ClkInitStruct, uint32_t *pFLatency)
获得时钟信息后用printf函数输出:
现在的时钟是PLL,和之前的配置相符。
3.进入停止模式2之前准备工作
为了使MCU能正常进入停止模式,且能正常被唤醒,有些工作必须做好,不然就睡死过去醒不来了。
3.1打开PWR时钟,设置系统唤醒后时钟为MSILL_APB1_GRP1_EnableClock(LL_APB1_GRP1_PERIPH_PWR);
LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_MSI);
3.2开中断,在HAL_UART_MspInit中加入代码:/* USER CODE BEGIN LPUART1_MspInit 1 */
LL_LPUART_SetWKUPType(LPUART1, LL_LPUART_WAKEUP_ON_RXNE);//设置唤醒方式为RXNE
LL_LPUART_EnableIT_RXNE(LPUART1);//打开RXNE中断
LL_LPUART_EnableIT_WKUP(LPUART1);//打开wakeup中断

/* USER CODE END LPUART1_MspInit 1 */
3.3检查LPUART的一些状态位,这个我就直接复制了例程中的一个函数,稍加修改__STATIC_INLINE void PrepareLPUARTToStopMode(void)
{
/* Empty RX Fifo before entering Stop Mode 2 (Otherwise, characters already present in FIFO
   will lead to immediate wake up */
while (LL_LPUART_IsActiveFlag_RXNE(LPUART1))
{
    /* Read Received character. RXNE flag is cleared by reading of RDR register */
    ubReceivedChar=LL_LPUART_ReceiveData8(LPUART1);
}

/* Clear OVERRUN flag */
LL_LPUART_ClearFlag_ORE(LPUART1);

/* Make sure that no LPUART transfer is on-going */
while(LL_LPUART_IsActiveFlag_BUSY(LPUART1) == 1)
{
}
/* Make sure that LPUART is ready to receive */   
while(LL_LPUART_IsActiveFlag_REACK(LPUART1) == 0)
{
}

/* About to enter stop mode: switch off LED */
LL_GPIO_ResetOutputPin(GPIOA, LL_GPIO_PIN_5);

/* Configure LPUART1 transfer interrupts : */
/* Clear WUF flag and enable the UART Wake Up from stop mode Interrupt */
LL_LPUART_ClearFlag_WKUP(LPUART1);
LL_LPUART_EnableIT_WKUP(LPUART1);

/* Enable Wake Up From Stop */
LL_LPUART_EnableInStopMode(LPUART1);
}分析一下这个函数都干了些什么(其实代码里有注释):
a.不断读取接收数据,知道RXNE为0,确保已经没有数据发进来
b.清除OVERRUN标志,确保所有当前工作完成,进入可以接收的状态c.清除WUF标志位,开WKUP中断(上一步自己做了),使能wakeup功能4.一切准备工作就绪,MCU可以进入停止模式了,PrepareLPUARTToStopMode();

HAL_PWREx_EnterSTOP2Mode(PWR_STOPENTRY_WFI);不过还有一个问题,还没写中断函数呢,MCU醒来后不知道干什么啊,人早上醒来知道洗脸刷牙,机器可不知道,所以得告诉它,写中断服务函数。
剩下的更新在一楼,完整工程见附件:

风子 发表于 2015-11-8 14:49:41

本帖最后由 风子 于 2015-11-8 15:31 编辑

继续更新:
为方便观察,进入停止模式之前先让LED闪烁3秒钟,同时输出一些信息。接着写中断函数


void LPUART1_IRQHandler(void)
{
/* USER CODE BEGIN LPUART1_IRQn 0 */
if(LL_LPUART_IsActiveFlag_WKUP(LPUART1) && LL_LPUART_IsEnabledIT_WKUP(LPUART1))
{
    /* Configure LPUART1 transfer interrupts : */
    /* Disable the UART Wake UP from stop mode Interrupt */
    LL_LPUART_DisableIT_WKUP(LPUART1);

    /* WUF flag clearing */
    LL_LPUART_ClearFlag_WKUP(LPUART1);
      
    ubReceivedChar=LL_LPUART_ReceiveData8(LPUART1);
}

/* USER CODE END LPUART1_IRQn 0 */
HAL_UART_IRQHandler(&hlpuart1);
/* USER CODE BEGIN LPUART1_IRQn 1 */

/* USER CODE END LPUART1_IRQn 1 */
}

当有中断发生,先检查中断源是不是WKUP中断,并且是否使能该中断,如果不是,则忽略掉。



      检查确定是WKUP中断,则关闭WKUP中断,清除相应标志位,避免重复进中断,最后将接收到的数据读出来存放,以便后用。
中断结束,MCU已经被唤醒,会回到进入停止模式的地方继续执行后面的代码,后面也不用做什么事,就是把收到的字符发送回串口助手,这样我们就可以知道MCU已经成功被唤醒了,当然,唤醒后直接把LED打开,观察更方便代码也更简单。同时发送系统时钟的信息和进入停止模式之前的信息做个比较,









可以清楚的看到,我们发送到MCU的字符'a'成功发送回来,进入停止模式前系统时钟是PLL,而唤醒之后变成了MSI,如果要继续使用PLL,需要重新配置使能,如果不影响其他地方,可以简单的再调用一次cube生成的时钟配置函数:
printf("System clock reconfiguration\n\r");
      SystemClock_Config();
displayclkinfo();
最后,把唤醒后的时钟改为HSI,测试一下效果:
LL_RCC_SetClkAfterWakeFromStop(LL_RCC_STOP_WAKEUPCLOCK_HSI);

此时唤醒后系统时钟就是HSI。

风子 发表于 2015-11-8 14:50:00

再占板凳

aabird 发表于 2015-11-8 15:36:28

还行吧。挺好的。我都不知道该如何表达了

风子 发表于 2015-11-8 15:39:07

aabird 发表于 2015-11-8 15:36
还行吧。挺好的。我都不知道该如何表达了

呵呵:):)

dsjsjf 发表于 2015-11-8 17:35:06

很详细,谢谢分享

Paderboy 发表于 2015-11-8 19:05:46

多谢分享。。。:loveliness:

DAHAIGE-355079 发表于 2015-11-8 20:30:57

帮顶            

kingsings 发表于 2015-11-8 22:57:18

写的很详细

风子 发表于 2015-11-9 08:58:30

kingsings 发表于 2015-11-8 22:57
写的很详细

谢谢支持
页: [1] 2 3
查看完整版本: 【NUCLEO-L476RG开发】LPUART唤醒停止模式STOP Mode 2