嵌入式分享合集17

最近又犯了迷糊好好补补基础把 uint =多少byte都忘了 

一、STM32待机唤醒实验

本文我们来学习下STM32的待机唤醒功能。要实现的功能是:系统运行时 D1 指示灯闪烁,5 秒后进入待机模式,D1 指示灯熄灭,同时串口 printf输出相关提示信息,可通过 K_UP 按键实现唤醒。学习本内容可以参考《STM32F10x中文参考手册》-4 电源控制器(PWR)章节。

STM32 低功耗模式介绍

    很多单片机具有低功耗模式,比如 MSP430、STM8L等。我们的STM32也不例外,相关文章:STM32低功耗模式。默认情况下,系统复位或上电复位后,微控制器进入运行模式。在运行模式下,HCLK 为 CPU 提供时钟,并执行程序代码。当 CPU 不需继续运行(例如等待外部事件)时,可以利用多种低功耗模式来节省功耗。用户需要根据最低电源消耗、最快速启动时间和可用的唤醒源等条件,选定一个最佳的低功耗模式。

    当然在运行模式下,也可以通过如下方式降低功耗:

(1)降低系统时钟速度

(2)不使用 APBx 和 AHB 外设时,将对应的外设时钟关闭

    STM32 提供了 3 种低功耗模式,以达到不同层次的降低功耗的目的,这三

种模式如下:

(1)睡眠模式( CM3 内核停止工作,外设仍在运行)

(2)停止模式(所有时钟都停止)

(3)待机模式( 1.8 V 内核电源关闭)

    这三种模式所需的功耗是逐级递减,也就是说待机模式功耗是最低的。三种低功耗模式汇总表如下图所示:

    我们仅对 STM32 的待机模式进行介绍,其他 2 种模式可以参考《STM32F10x 中文参考手册》-4电源控制器(PWR)章节,里面有详细的介绍。

(1)待机模式

    在睡眠模式中,仅关闭了内核时钟,内核停止运行,但其片上外设, CM3 核心的外设全都照常运行。在停止模式中,进一步关闭了其它所有的时钟,于是所有的外设都停止了工作,但由于其 1.8V 区域的部分电源没有关闭,还保留了内核的寄存器、内存的信息,所以从停止模式唤醒,并重新开启时钟后,还可以从上次停止处继续执行代码。在待机模式中, 它除了关闭所有的时钟, 还把 1.8V 区域的电源也完全关闭了,也就是说,从待机模式唤醒后,由于没有之前代码的运行记录,只能对芯片复位,重新检测 BOOT 条件,从头开始执行程序。低功耗开发相关文章:STM32低功耗开发时,需要注意的GPIO配置问题

    那么我们如何进入待机模式呢?其实很简单,只要按下图所示待机模式进入与退出步骤的步骤执行就可以了。

    上图还列出了退出待机模式的操作,当检测到外部复位(NRST 引脚)、

IWDG 复位、 WKUP 引脚上升沿、 RTC 闹钟事件的上升沿时,微控制器退出待机模式。本文我们是通过 WKUP 引脚(PA0)上升沿来退出待机模式,当然也可以直接通过芯片复位管脚 NRST退出。

    从待机模式唤醒后,除了电源控制/状态寄存器(PWR_CSR),所有的寄存器豆被复位,程序将按照复位(启动引脚采样、复位向量已获取等)后的方式重新执行。电源控制/状态寄存器(PWR_CSR)将会指示内核由待机状态退出。

    在进入待机模式后,除了复位引脚以及被设置为防侵入或校准输出时的

TAMPER (PC13)引脚和被使能的唤醒引脚( WK_UP 脚(PA0)),其他的 IO 引脚都将处于高阻态。

    由于篇幅限制,本文并没有对待机模式相关寄存器进行介绍,大家可以参考《STM32F10x 中文参考手册》-4 电源控制器(PWR)章节,里面有详细的讲解。如果看不懂的可以暂时放下,因为我们使用的是库函数开发。

待机模式配置步骤

    接下来我们介绍下如何使用库函数进入和退出待机模式。这个也是在编写程序中必须要了解的。

    具体步骤如下:(电源管理相关库函数在 stm32f10x_pwr.c和 stm32f10x_pwr.h 文件中)

(1)使能电源时钟

    因为低功耗模式是通过 STM32 电源(PWR)系统进行管理的,所以需要使能电源时钟,调用的库函数为:

RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能 PWR 外设时钟

(2)设置 WK_UP 引脚为唤醒源

    待机唤醒方式有很多种,我们选择 WK_UP 引脚(PA0)上升沿来退出待机模式。在库函数中,设置使能 WK_UP 用于唤醒 CPU 待机模式的函数是:

PWR_WakeUpPinCmd(ENABLE);

    因为按键 K_UP 连接在PA0 管脚上,并且是高电平有效,这样一来就可以使用 K_UP按键来退出待机模式。

(3)进入待机模式

    进入待机模式, 首先要设置 SLEEPDEEP 位 ( 详见 《 Cortex M3 权威指南(中文)》 , chpt13 Cortex-M3 的其它特性--电源管理章节) ,接着我们通过 PWR_CR设置 PDDS 位,使得 CPU 进入深度睡眠时进入待机模式,最后执行 WFI 指令开始进入待机模式,并等待 WK_UP 中断的到来。整个操作可以通过一个库函数完成,如下:

PWR_EnterSTANDBYMode();//进入待机模式

    通常在进入待机模式前,我们会清除唤醒标志,以等待下次进入。清除唤醒标志库函数为:

PWR_ClearFlag(PWR_FLAG_WU);//清除 Wake-up 标志

    以上几步全部配置好后,我们就可以正常进入待机模式了,并且可以通过按键 K_UP或者复位按键唤醒。

    特别提醒下,如果学到 RTC 实时时钟实验的时候,需要进入待机模式,如果使能了 RTC 闹钟中断的时候,进入待机模式前,必须按如下操作处理:

1.禁止 RTC 中断( ALRAIE、 ALRBIE、 WUTIE、 TAMPIE 和 TSIE 等)。

2.清零对应中断标志位。

3.清除 PWR 唤醒(WUF)标志(通过设置 PWR_CR 的 CWUF 位实现)。

4.重新使能 RTC 对应中断。

5.进入低功耗模式。

    本实验使用到硬件资源如下:

(1)D1 指示灯

(2)串口 1

(3)K_UP 按键

    D1指示灯、K_UP 按键、串口 1 电路在前面章节都介绍过,这里不多说。D1指示灯用来提示系统正常运行,K_UP 按键用来唤醒待机模式,串口 1 用来输出提示信息。

    所要实现的功能是:系统运行时 D1 指示灯闪烁,5 秒后进入待机模式,D1 指示灯熄灭,同时串口 printf 输出相关提示信息,通过 K_UP 按键实现唤醒。

    程序框架如下:

(1)配置进入与退出待机模式

(2)编写主函数

    前面介绍待机模式配置步骤时,就已经讲解如何配置。下面我们打开“待机唤醒实验”工程,在 APP 工程组中可以看到添加了wkup.c文件(里面包含了待机模式驱动程序),在 StdPeriph_Driver 工程组中添加了 stm32f10x_pwr.c 库文件。电源系统管理相关操作的库函数都放在stm32f10x_pwr.c 和 stm32f10x_pwr.h 文件中,所以使用到电源系统管理就必须加入 stm32f10x_pwr.c 文件,同时还要包含对应的头文件路径。

    这里我们分析几个重要函数,其他部分程序大家可以打开工程查看。

待机模式配置函数

    要让系统进入待机模式,我们必须对它进行配置。进入待机模式代码如下:

/***************************************************************** 函 数 名 : Enter_Standby_Mode* 函数功能 : 进入待机模式* 输 入 : 无* 输 出 : 无*****************************************************************/void Enter_Standby_Mode(void){
   
     RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);//使能 PWR 外设时钟  PWR_ClearFlag(PWR_FLAG_WU);//清除 Wake-up 标志  PWR_WakeUpPinCmd(ENABLE);//使能唤醒管脚 使能或者失能唤醒管脚功能  PWR_EnterSTANDBYMode();//进入待机模式}

   该函数首先使能电源PWR时钟,然后清除唤醒标志位,并使能 WK_UP管脚为唤醒方式,最后进入待机模式。这一过程在前面步骤介绍中已经提了。

主函数

    配置待机模式后,我们就可以编写主函数,代码如下:

/***************************************************************** 函 数 名 : main* 函数功能 : 主函数* 输 入 : 无* 输 出 : 无*****************************************************************/int main(){
   
     SysTick_Init(72);  NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //中断优先级分组 分2 组  LED_Init();  USART1_Init(9600);  while(1)  {
   
       printf("time: 5\r\n");    led1=0;    delay_ms(1000); //隔 1 秒显示计数    printf("time: 4\r\n");    led1=1;    delay_ms(1000);    printf("time: 3\r\n");    led1=0;    delay_ms(1000);    printf("time: 2\r\n");    led1=1;    delay_ms(1000);    printf("time: 1\r\n");    led1=0;    delay_ms(1000);    printf("进入系统待机模式\r\n");    Enter_Standby_Mode();  }}

    主函数实现的功能很简单,首先调用之前编写好的硬件初始化函数,包括

SysTick 系统时钟,中断分组,LED 初始化等。然后进入 while 循环,每间隔一秒让 printf 输出一个信息,同时指示灯状态发生变化。倒计 5 秒钟后,调用函数 Enter_Standby_Mode进入待机模式,此时指示灯熄灭。

    将工程程序编译后下载到开发板内,可以看到系统运行时 D1 指示灯不断闪烁,5 秒钟后进入待机模式,此时 D1 指示灯熄灭。当按下 K_UP 按键或复位按键时,待机模式被唤醒,系统重新运行,同时串口打印提示信息。如果想在串口调试助手上看到输出信息,可以打开“串口调试助手”,首先勾选下标号 1 DTR 框,然后再取消勾选。这是因为此串口助手启动时会把系统复位住, 通过 DTR 状态切换下即可。然后设置好波特率等参数后,串口助手上即会收到 printf发送过来的信息。(串口助手上先勾选下标号1 DTR框,然后再取消勾选)如下图所示。

    实验说明:下载待机唤醒实验程序后,若使用普中 ARM 仿真器下载其他的程序会出现报警,这是因为处于低功耗模式时,所有外设时钟都已关闭,所以需要在下载程序前先复位下系统。whaosoft aiot http://143ai.com

二、C语言基础

 C语言是单片机开发中的必备基础知识,本文列举了部分STM32学习中比较常见的一些C语言基础知识。

1 位操作

    下面我们先讲解几种位操作符,然后讲解位操作使用技巧。C语言支持以下六种位操作:

    下面,重点讲解一下位操作在单片机开发中的一些实用技巧。

1.1 在不改变其他位的值的状况下,对某几个位进行设值

    这个场景在单片机开发中经常使用,方法就是我们先对需要设置的位用&操作符进行清零操作,然后用 | 操作符设值。

    比如,我要改变GPIOA的状态,可以先对寄存器的值进行&清零操作:

    然后再与需要设置的值进行|或运算:

1.2 移位操作提高代码的可读性

    移位操作在单片机开发中非常重要,下面是delay_init函数的一行代码:

SysTick->CTRL |= 1 << 1;

    这个操作就是将CTRL寄存器的第1位(从0开始算起)设置为1,为什么要通过左移而不是直接设置一个固定的值呢?

    其实这是为了提高代码的可读性以及可重用性。这行代码可以很直观明了的知道,是将第1位设置为1。如果写成:

SysTick->CTRL |= 0X0002;

    这个虽然也能实现同样的效果,但是可读性稍差,而且修改也比较麻烦。

1.3 ~按位取反操作使用技巧

    按位取反在设置寄存器的时候经常被使用,常用于清除某一个/某几个位。下面是delay_us函数的一行代码:

SysTick->CTRL &= ~(1 << 0) ;    /* 关闭SYSTICK */

    该代码可以解读为:仅设置CTRL寄存器的第0位(最低位)为0,其他位的值保持不变。

    同样我们也不使用按位取反,将代码写成:

SysTick->CTRL &= 0XFFFFFFFE;        /* 关闭SYSTICK */

    可见,前者的可读性及可维护性都要比后者好很多。

1.4 ^按位异或操作使用技巧

    该功能非常适合用于控制某个位翻转,常见的应用场景就是控制LED闪烁,如下:

GPIOB->ODR ^= 1 << 5;

    执行一次该代码,就会使PB5的输出状态翻转一次,如果我们的LED接在PB5上,就可以看到LED闪烁了。

2 define宏定义

    define是C语言中的预处理命令,它用于宏定义(定义的是常量),可以提高源代码的可读性,为编程提供方便。常见的格式:

    “标识符”为所定义的宏名。“字符串”可以是常数、表达式、格式串等。例如:

    定义标识符HSE_VALUE的值为8000000,数字后的U表示unsigned的意思。至于define宏定义的其他一些知识,比如宏定义带参数,这里就不多讲解了。

3 ifdef条件编译

    单片机程序开发过程中,经常会遇到一种情况,当满足某条件时对一组语句进行编译,而当条件不满足时则编译另一组语句。

    条件编译命令最常见的形式为:

#ifdef 标识符    程序段1#else    程序段2#endif

    它的作用是:当标识符已经被定义过(一般是用#define命令定义),则对程序段1进行编译,否则编译程序段2。

    其中#else部分也可以没有,即:

  #ifdef    程序段1    #endif

    条件编译在HAL库里面是用得很多,在stm32mp1xx_hal_conf.h这个头文件中经常会看到这样的语句:

#if !defined  (HSE_VALUE)      #define HSE_VALUE       24000000U    #endif

    如果没有定义HSE_VALUE这个宏,则定义HSE_VALUE宏,并且HSE_VALUE的值为24000000U。条件编译也是C语言的基础知识吧。

    这里提一下,24000000U中的U表示无符号整型,常见的,UL表示无符号长整型,F表示浮点型。

    这里加了U以后,系统编译时就不进行类型检查,直接以U的形式把值赋给某个对应的内存,如果超出定义变量的范围,则截取。

4 extern变量申明

    C语言中extern可以置于变量或者函数前,以表示变量或者函数的定义在别的文件中,提示编译器遇到此变量和函数时在其他模块中寻找其定义。

    这里面要注意,对于extern申明变量可以多次,但定义只有一次。在我们的代码中你会看到看到这样的语句:

extern uint16_t g_usart_rx_sta;

    这个语句是申明g_usart_rx_sta变量在其他文件中已经定义了,在这里要使用到。

    所以,你肯定可以找到在某个地方有变量定义的语句:

 uint16_t g_usart_rx_sta;

    extern的使用比较简单,但是也会经常用到,需要掌握。

5 typedef类型别名

    typedef用于为现有类型创建一个新的名字,或称为类型别名,用来简化变量的定义。typedef在HAL库用得最多的就是定义结构体的类型别名和枚举类型了。

struct _GPIO    {        __IO uint32_t CRL;        __IO uint32_t CRH;        …    };

    定义了一个结构体GPIO,这样我们定义结构体变量的方式为:

struct  _GPIO  gpiox;       /* 定义结构体变量gpiox */

    但这样很繁琐,HAL库中有很多这样的结构体变量需要定义。

    这里我们可以为结体定义一个别名GPIO_TypeDef,这样我们就可以在其他地方通过别名GPIO_TypeDef来定义结构体变量了,方法如下:

typedef struct    {            __IO uint32_t CRL;            __IO uint32_t CRH;            …    } GPIO_TypeDef;

    Typedef为结构体定义一个别名GPIO_TypeDef,这样我们可以通过GPIO_TypeDef来定义结构体变量:延庆川北小区45孙老师 收卖废品破烂垃圾炒股 废品孙 再回收

GPIO_TypeDef gpiox;

    这里的GPIO_TypeDef就跟struct _GPIO是等同的作用了,但是GPIO_TypeDef使用起来方便很多。

猜你喜欢

转载自blog.csdn.net/qq_29788741/article/details/125899217