当前位置:   article > 正文

STM32入门笔记_stm32 bkp

stm32 bkp

师承江科协,STM32入门学习笔记。有错指出,无限进步。

一、BKP 备份寄存器

 1.BKP简介

2.BKP基本结构

STM32F103C8T6 是中容量型,所以只有20个字节,也就是下图中的数据寄存器只有DR1~DR10。

侵入检测:保护BKP的数据安全,可以设置高低电平触发,当TEMPER引脚来一个上升沿或者下降沿时,就会触发侵入检测,自动清除BKP的数据。

时钟输出:BKP就位于“后备区域”(下图灰色与橙色部分),后备区域: 没有VDD供电时,可以由VBAT备用电池来供电维持。所以可以用BKP来输出RCT_Second 秒脉冲、RCT_Overflow 校准时钟、RCT_Alarm 闹钟脉冲。

3.BKP相关库函数

  1. void BKP_DeInit(void); // 重定义,恢复缺省配置,可用于BKP清零
  2. void BKP_TamperPinLevelConfig(uint16_t BKP_TamperPinLevel); // 侵入检测选择高低电平触发
  3. void BKP_TamperPinCmd(FunctionalState NewState); // 是否开启侵入检测
  4. void BKP_ITConfig(FunctionalState NewState); // 开启中断
  5. void BKP_RTCOutputConfig(uint16_t BKP_RTCOutputSource); // RTC时钟输出,输出RTC校准时钟、RTC闹钟脉冲或者秒脉冲
  6. void BKP_SetRTCCalibrationValue(uint8_t CalibrationValue); // 写入RTC校准值(写入RTC校准寄存器)
  7. void BKP_WriteBackupRegister(uint16_t BKP_DR, uint16_t Data); // 写入BKP
  8. uint16_t BKP_ReadBackupRegister(uint16_t BKP_DR); // 读取BKP
  9. FlagStatus BKP_GetFlagStatus(void);
  10. void BKP_ClearFlag(void);
  11. ITStatus BKP_GetITStatus(void);
  12. void BKP_ClearITPendingBit(void);
  13. PWR_BackupAccessCmd(ENABLE); // BKP访问使能

4.BKP读写实例

4.1 前情提要

1. 开启PWR、BKP 时钟

2. 开启PWR对BKP的访问使能

4.2 程序代码

  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "KEY.h"
  5. uint16_t Write[2]={1234,5678};
  6. uint16_t Read[2];
  7. uint8_t KeyNum;
  8. int main(void)
  9. {
  10. OLED_Init();
  11. Key_Init();
  12. OLED_ShowString(1,1,"W: ");
  13. OLED_ShowString(2,1,"R: ");
  14. OLED_ShowNum(1,3,Write[0],4);
  15. OLED_ShowNum(1,8,Write[1],4);
  16. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
  17. RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
  18. PWR_BackupAccessCmd(ENABLE); // BKP访问使能
  19. // BKP_DeInit(); 重定义,恢复缺省配置,可用于BKP清零
  20. while(1)
  21. {
  22. KeyNum = Key_Num();
  23. if(KeyNum == 1) // 当按键按下
  24. {
  25. Write[0]++; // 写入值加1
  26. Write[1]++;
  27. BKP_WriteBackupRegister(BKP_DR1,Write[0]); // 写入BKP
  28. BKP_WriteBackupRegister(BKP_DR2,Write[1]);
  29. OLED_ShowNum(1,3,Write[0],4); // 显示写入的数组
  30. OLED_ShowNum(1,8,Write[1],4);
  31. }
  32. Read[0]=BKP_ReadBackupRegister(BKP_DR1); // 读取BKP放入数组
  33. Read[1]=BKP_ReadBackupRegister(BKP_DR2);
  34. OLED_ShowNum(2,3,Read[0],4); // 显示读取到的数组
  35. OLED_ShowNum(2,8,Read[1],4);
  36. }
  37. }

 4.3 现象分析

1. 按下按键,写和读在原数组上++显示。说明BKP基本的读写功能正常;

2. 按下复位键 / VDD 断电重启 ,OLED正常显示原来的数组 ;VBAT口断电重启,数据为0。说明BKP的数据清除决定权归VBAT管。(BKP_DeInit(); 这个函数也可以让BKP清零) 


二、RTC 实时时钟

1.RCT简介

此处需要先了解 “时间戳” 是什么 ?

时间戳与实际时间日期的转换关系是怎么样的 ?

由 time 函数库来解决中间复杂的数学与润平年等计算,那么time函数库使用方法是 ?


2.RCT的基本结构

 1. RTCCLK :RCC时钟树的三种来源:HSE:主晶振8M/128  、LSE :32.768kHZ、LSI:40kHZ。 主要用的是LSE,因为只有它在后备区,且它的频率正好为2的15次方,可以用15位的寄存器溢出来输出1hz的计数频率。

3. RCT相关库函数

  1. void RCC_LSEConfig(uint8_t RCC_LSE); // 外部低速时钟LSE配置
  2. void RCC_LSICmd(FunctionalState NewState); // 内部低速时钟LSI配置
  3. void RCC_RTCCLKConfig(uint32_t RCC_RTCCLKSource); // 相当于结构图中的数据选择器,选择RTCCLK的来源
  4. void RCC_RTCCLKCmd(FunctionalState NewState); // RTCCLK使能
  5. FlagStatus RCC_GetFlagStatus(uint8_t RCC_FLAG);
  6. void RCC_ClearFlag(void);
  7. ITStatus RCC_GetITStatus(uint8_t RCC_IT);
  8. void RCC_ClearITPendingBit(uint8_t RCC_IT);
  9. //------------------------------------------------------------------------//
  10. void RTC_ITConfig(uint16_t RTC_IT, FunctionalState NewState); // 配置中断输出
  11. void RTC_EnterConfigMode(void); // 进入配置模式,进入配置模式之后,CNF位置1。才能配置RCT相关的寄存器
  12. void RTC_ExitConfigMode(void); // 退出配置模式,CNF位置0。
  13. uint32_t RTC_GetCounter(void); // 读取计数器,也就是读取时钟
  14. void RTC_SetCounter(uint32_t CounterValue); // 设置计数器的值,设置时间
  15. void RTC_SetPrescaler(uint32_t PrescalerValue); // 写入预分频器中的重装寄存器PRL,配置分频系数
  16. void RTC_SetAlarm(uint32_t AlarmValue); // 设置闹钟值,ALR的值
  17. uint32_t RTC_GetDivider(void); // 读取预分频器中DIV余数寄存器,获得更精细的时间值(ms,us...)
  18. void RTC_WaitForLastTask(void); // 等待操作:等待上一步操作结束,RTOFF=1
  19. void RTC_WaitForSynchro(void); // 等待操作:等待同步
  20. FlagStatus RTC_GetFlagStatus(uint16_t RTC_FLAG);
  21. void RTC_ClearFlag(uint16_t RTC_FLAG);
  22. ITStatus RTC_GetITStatus(uint16_t RTC_IT);
  23. void RTC_ClearITPendingBit(uint16_t RTC_IT);

4.RCT 实例

4.1 前情提要

4.1.1 编写MyRTC函数

1. 开启PWR、BKP 时钟

2. 开启PWR对BKP的访问使能

 3. 开启LSE时钟(一般情况下,为了省电LSE默认是关闭的,需要手动开启),等待启动完成;

4. 数据选择器:指定LSE时钟作为RTCCLK,cmd使能时钟

5. 等待操作:等待同步(RTCCLK与APB主线的频率不同,为了不出现bug,故用之),等待上一步操作完成(防止时序乱套)

6. 设置PRL 重装寄存器的值,功能类似于TIM中的ARR。作用是把输入RCT频率变成 1hz输出。

写RTC的寄存器之前和之后最好是都加一行“等待上一步操作”。

这里不要多写俩条进入配置模式和退出配置模式是因为函数已经写过了

7. 直接输出秒脉冲 / 设置CNT AIR去触发中断(达到闹钟的效果)

4.1.2 STM32中的Time库函数

C语言的time库函数功能如下:

STM32中也用time库函数但有一些不同:

1. STM32是离线设备,并不知道系统时钟,获取时钟函数无效;

2.  STM32是离线设备,所以在它看来localtime和gmtime是一样的,所以就用一个localtime来表示0时区的时间,其他时区需要手动加偏移。

4.2 程序代码

MyRTC.c
 

  1. #include "stm32f10x.h" // Device header
  2. #include <time.h>
  3. uint16_t MyRTC_Time[6] = {2023,1,1,23,55,59};
  4. uint16_t MyRTC_Time[6];
  5. void MyRTC_SetTime(void);
  6. void MyRTC_Init(void)
  7. {
  8. // 1.开启时钟
  9. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE);
  10. RCC_APB1PeriphClockCmd(RCC_APB1Periph_BKP,ENABLE);
  11. PWR_BackupAccessCmd(ENABLE); // BKP访问使能
  12. if(BKP_ReadBackupRegister(BKP_DR1) != 0xA5A5) // 利用BKP来防止RTC复位重置,这样每次VDD断电或者按下重置后,因为BKP_DR1的是不会变的,所以RTC也会保持
  13. {
  14. // 2. 选择时钟源
  15. RCC_LSEConfig(RCC_LSE_ON); // 外部低速时钟LSE配置
  16. while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) != SET); // 等待开启
  17. // 3. 数据选择器:指定LSE时钟作为RTCCLK
  18. RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);
  19. RCC_RTCCLKCmd(ENABLE);
  20. // 4. 等待同步,等待上一步完成
  21. RTC_WaitForSynchro();
  22. RTC_WaitForLastTask(); // 这里没有上一步,但是保险还是加上
  23. // 5.PRL重装寄存器
  24. RTC_SetPrescaler(32768-1); // LSE频率是32768,从0开始算,所以分频系数-1
  25. RTC_WaitForLastTask();
  26. MyRTC_SetTime();
  27. BKP_WriteBackupRegister(BKP_DR1,0xA5A5); // 给BKP_DR1赋值,相当于给个标志位
  28. }
  29. }
  30. // 设置时间日期写入到RTC中
  31. void MyRTC_SetTime(void)
  32. {
  33. time_t time_cnt; // 秒计数器
  34. struct tm time_date; // 时间日期
  35. time_date.tm_year = MyRTC_Time[0]-1900; // 把自己设置的时间日期传给结构体。从1900开始加
  36. time_date.tm_mon = MyRTC_Time[1]-1;
  37. time_date.tm_wday = MyRTC_Time[2];
  38. time_date.tm_hour = MyRTC_Time[3];
  39. time_date.tm_min = MyRTC_Time[4];
  40. time_date.tm_sec = MyRTC_Time[5];
  41. time_cnt = mktime(&time_date) - 8*60*60; // 把时间日期 转换成 时间戳
  42. RTC_SetCounter(time_cnt); // 时间戳设置成计数器CNT的值
  43. RTC_WaitForLastTask();
  44. }
  45. // 将RTC中的值读取出来
  46. void MyRTC_ReadTime(void)
  47. {
  48. time_t time_cnt; // 秒计数器
  49. struct tm time_date; // 时间日期
  50. time_cnt=RTC_GetCounter() + 8*60*60; // 加上8个小时的偏移,得到北京时间
  51. time_date = * localtime(&time_cnt);
  52. MyRTC_Time[0]= time_date.tm_year + 1900 ; // 转换完的时间日期结构体传给自己设置的数组。从1900开始加
  53. MyRTC_Time[1] = time_date.tm_mon + 1;
  54. MyRTC_Time[2]= time_date.tm_wday ;
  55. MyRTC_Time[3]= time_date.tm_hour;
  56. MyRTC_Time[4]= time_date.tm_min ;
  57. MyRTC_Time[5]= time_date.tm_sec ;
  58. }

 main.c

  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "MyRTC.h"
  5. int main(void)
  6. {
  7. OLED_Init();
  8. MyRTC_Init();
  9. OLED_ShowString(1, 1, "Date:XXXX-XX-XX");
  10. OLED_ShowString(2, 1, "Time:XX:XX:XX");
  11. OLED_ShowString(3, 1, "CNT :");
  12. OLED_ShowString(4, 1, "DIV :");
  13. while (1)
  14. {
  15. MyRTC_ReadTime();
  16. OLED_ShowNum(1, 6, MyRTC_Time[0], 4);
  17. OLED_ShowNum(1, 11, MyRTC_Time[1], 2);
  18. OLED_ShowNum(1, 14, MyRTC_Time[2], 2);
  19. OLED_ShowNum(2, 6, MyRTC_Time[3], 2);
  20. OLED_ShowNum(2, 9, MyRTC_Time[4], 2);
  21. OLED_ShowNum(2, 12, MyRTC_Time[5], 2);
  22. OLED_ShowNum(3, 6, RTC_GetCounter(), 10);
  23. OLED_ShowNum(4, 6, RTC_GetDivider(), 10);
  24. // OLED_ShowNum(4, 6, (32767-RTC_GetDivider())/32767 * 99 , 10); // 32767~0线性转换成0~999。1秒从0计到999=计一个数1ms。
  25. }
  26. }

 4.3 现象分析

1. LSE晶振有可能不起振,那就得把时钟源设置LSI (40Khz)然后把分频系数改成40000-1;

2. 用DIV实现毫秒计时原理:秒计时:RTC时钟频率为32.768Khz --> DIV在从32767自减到0算一个周期 --> 一个周期结束,CNT就自增1 ,DIV被重装为32767 --> 用时1秒;

毫秒计时:DIV :32767~0线性转换为 0~999 ;一秒计数1000,那么计一个数用时1ms。

(重装设为1,是不是每1ms,CNT++)


三、PWR 电源控制

1. PWR简介

PWR 作用:*1. 通过硬件设计管理STM32内部电源供电;(暂不使用仅了解)

*2. 作为一个可编程电压检测器:监控VDD的电压,当不在安全阈值范围内时,触发一些紧急操作;(暂不使用仅了解)

3.低功耗模式:见第三小节。

* 上电复位和掉电复位:VDD输入的时候低于某一个范围时,就会触发复位信号。HOLD住不让动。(详见手册)

*可编程电压监控器:VDD输入的时候低于某一个范围时,PVD就输出上升沿或者下降沿,触发外部中断EXTI,在中断函数里执行一些东西提醒用户电压过低,但程序还是会运行。阈值范围会比上面俩个高一些。阈值范围可以通过代码设定。(详见手册)


2. PWR基本结构

下图是STM32F103系列的内部电源框图。

需要了解是有哪些供电区域?谁在哪个区域?

1. VDDA供电区域:模拟信号的供电,VREF-和VREF+是参考电压,在引脚多的系列会单独引出。STM32F103C8T6中,在内部已经分别接入了VSSA和VDDA。

2. VDD供电区域:主要了解哪一些是1.8V低供电区的,哪一些是3.3V供电区的,电压调节器的作用是:将3.3V减压供1.8V区域使用,也相当于1.8V区域的开关。

3. 后备供电区域:低电压检测器的作用:开关。VDD有电就接入,VDD没电就接VBAT


3. 低功耗模式

设置STM32处于低功耗模式:

1. 关键在于开启低功耗(待机)的时候,要关闭(关闭时钟或者关闭电源)哪一些硬件?

关闭硬件有俩种方法:关闭时钟或者关闭电源。关闭时钟数据还在,只是先不运行。关闭电源,啥也没有了。

2. 保留哪一些硬件?

3. 还有如何去唤醒?

通过某些中断或者某些事件。

三种模式的区别在于:关闭的硬件数量不一样。关闭越多硬件,唤醒就越麻烦。

名称解释: WFI(Wait For Interrupt)等待中断 有中断来我才醒来

WFE(Wait For Event)等待事件 有中断事件来我才醒来

俩个都是内核指令,对应的也有函数。调用函数即可。

×××位:某个寄存器的某个位,也对应的函数。调用函数即可。

表格分析:

睡眠模式: 只把CPU的时钟关了,程序暂停运行。芯片不运行功耗降低。(脑关,手脚还在动)

  • 执行完WFI/WFE指令后,STM32进入睡眠模式,程序暂停运行,唤醒后程序从暂停的地方继续运行。
  • 在睡眠模式下,所有的I/O引脚都保持它们在运行模式时的状态。(比如运行时给LED高电平,进入睡眠模式,还是会保持高电平输出)

停机模式:SLEEPDEEP位=1   PDDS=0   LPDS=0电压调节器开启   LPDS=1电压调节器处于低功耗模式。 把1.8V区域的时钟、HSI和HSE都关了。只有外部中断EXTI才能唤醒。(脑关,手脚也不动了)

  • 执行完WFI/WFE指令后,STM32进入停止模式,程序暂停运行,唤醒后程序从暂停的地方继续运行。
  • 在停止模式下,所有的I/O引脚都保持它们在运行模式时的状态。
  • 1.8V供电区域的所有时钟都被停止,PLL、HSI和HSE被禁止,SRAM和寄存器内容被保留下来。当一个中断或唤醒事件导致退出停止模式时,HSI被选为系统时钟。(所以唤醒后得手动把原来的系统时钟启动。)
  • 因为只是关了时钟,所以唤醒后,存储器的数据还在

待机模式:SLEEPDEEP位=1   PDDS=1 。 把1.8V区域的时钟和电源、HSI和HSE都关了。LSI和LSE没关是因为要给RTC和IWDG唤醒用。只有指定的那四个信号才能唤醒 。(直接回家了)

  • 执行完WFI/WFE指令后,STM32进入待机模式,唤醒后程序从头开始运行
  • 整个1.8V供电区域被断电,PLL、HSI和HSE也被断电,SRAM和寄存器内容丢失,只有备份的寄存器和待机电路维持供电
  • 在待机模式下,所有的I/O引脚变为高阻态(浮空输入)
  • WKUP引脚的上升沿、RTC闹钟事件的上升沿、NRST引脚上外部复位、IWDG复位退出待机模式
  • 关了电源,所以唤醒后,程序从头开始,,存储器的数据从0开始

上图为要怎样配置寄存器中的位来选择三种模式。配置完这些对应的寄存器后,调用WFI或者WFE就会进入相应的模式。

4. PWR实例

4.1 前情提要

1. 单片机的主频率哪来的 ?

答:单片机是数字电路,一切工作都要遵循时间顺序来进行。所以就有晶振(或者芯片内部的RC振荡器,精度差一点):提供稳定的时钟信号。(比如晶振是4kHz的,说明它一秒会产生4000个振荡周期)。晶振进来经过 RCC(Reset and Clock Control复位和时钟控制)。RCC的作用如下:

名称解释:

 SYSCLK: 系统时钟

AHB APB1 APB2 : Advanced High-performance Bus,即高级高性能总线。Advanced Peripheral Bus,即高级外设总线。Advanced Peripheral Bus2,即高级外设总线2。

HSE / HSI / LSI / LSE: 内部时钟信号由芯片内部的RC振荡器产生,精度差一点。外部时钟信号由晶振产生,精度高。

PLL:锁相环 倍频输出。倍频可选择为2~16倍,但是其输出频率最大不得超过72MHz。

4.2 程序代码

1. 修改主频

  1. /* System_stm32f10x.c中提供了俩个外部可调用的函数和一个变量。
  2. ———————————————————————————————————————————————————————————————————————
  3. - SystemInit(): 配置时钟树RCC
  4. - SystemCoreClock : 获取当前的Sysclk主频
  5. - SystemCoreClockUpdate(): 在程序执行期间,每当核心时钟发生变化时,必须调用Updates the
  6. variable SystemCoreClock函数以更新SystemCoreClock变量。
  7. */
  8. /* 解除下面哪一个宏定义,便可改变主频为相应的频率
  9. ——————————————————————————————————————————————————
  10. #if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
  11. /* #define SYSCLK_FREQ_HSE HSE_VALUE */
  12. #define SYSCLK_FREQ_24MHz 24000000
  13. #else
  14. /* #define SYSCLK_FREQ_HSE HSE_VALUE */
  15. /* #define SYSCLK_FREQ_24MHz 24000000 */
  16. /* #define SYSCLK_FREQ_36MHz 36000000 */
  17. /* #define SYSCLK_FREQ_48MHz 48000000 */
  18. /* #define SYSCLK_FREQ_56MHz 56000000 */
  19. #define SYSCLK_FREQ_72MHz 72000000
  20. #endif
  21. */
  22. // main.c
  23. #include "stm32f10x.h" // Device header
  24. #include "Delay.h"
  25. #include "OLED.h"
  26. int main(void)
  27. {
  28. OLED_Init();
  29. OLED_ShowString(1,1,"SYSCLKS:");
  30. OLED_ShowNum(1,8,SystemCoreClock,8);
  31. while(1)
  32. {
  33. OLED_ShowString(2,1,"Running");
  34. Delay_ms(500);
  35. OLED_ShowString(2,1," ");
  36. Delay_ms(500);
  37. }
  38. }

2. 串口作为下位机接收数据+睡眠模式

  1. // main.c
  2. #include "stm32f10x.h" // Device header
  3. #include "Delay.h"
  4. #include "OLED.h"
  5. #include "Serial.h"
  6. uint8_t RxData;
  7. int main(void)
  8. {
  9. OLED_Init();
  10. OLED_ShowString(1, 1, "RxData:");
  11. Serial_Init();
  12. while (1)
  13. {
  14. if (Serial_GetRxFlag() == 1)
  15. {
  16. RxData = Serial_GetRxData();
  17. Serial_SendByte(RxData);
  18. OLED_ShowHexNum(1, 8, RxData, 2);
  19. }
  20. OLED_ShowString(2, 1, "Running");
  21. Delay_ms(100);
  22. OLED_ShowString(2, 1, " ");
  23. Delay_ms(100);
  24. __WFI();
  25. }
  26. }

3. 对射式红外计数器+停机模式

  1. /*
  2. void PWR_DeInit(void); // 恢复缺省配置
  3. void PWR_BackupAccessCmd(FunctionalState NewState); // 开启允许BKP访问
  4. void PWR_PVDCmd(FunctionalState NewState); // 使能PVD
  5. void PWR_PVDLevelConfig(uint32_t PWR_PVDLevel); // 设置PVD阈值范围
  6. void PWR_WakeUpPinCmd(FunctionalState NewState); // 使能WakeUp引脚
  7. void PWR_EnterSTOPMode(uint32_t PWR_Regulator, uint8_t PWR_STOPEntry); // 进入停机模式
  8. void PWR_EnterSTANDBYMode(void); // 进入待机模式
  9. FlagStatus PWR_GetFlagStatus(uint32_t PWR_FLAG);
  10. void PWR_ClearFlag(uint32_t PWR_FLAG);
  11. */
  12. // mian.c
  13. #include "stm32f10x.h" // Device header
  14. #include "Delay.h"
  15. #include "OLED.h"
  16. #include "CountSensor.h"
  17. int main(void)
  18. {
  19. OLED_Init();
  20. CountSensor_Init();
  21. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);
  22. OLED_ShowString(1, 1, "Count:");
  23. while (1)
  24. {
  25. OLED_ShowNum(1, 7, CountSensor_Get(), 5);
  26. OLED_ShowString(2, 1, "Running");
  27. Delay_ms(100);
  28. OLED_ShowString(2, 1, " ");
  29. Delay_ms(100);
  30. PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI);
  31. SystemInit();
  32. }
  33. }

4. 实时时钟+待机模式

  1. // main.c
  2. #include "stm32f10x.h" // Device header
  3. #include "Delay.h"
  4. #include "OLED.h"
  5. #include "MyRTC.h"
  6. int main(void)
  7. {
  8. OLED_Init();
  9. MyRTC_Init();
  10. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR,ENABLE); // 进入待机模式需要用到PWR;虽然RCT开了,但为了减少代码耦合性。此处单独加上
  11. OLED_ShowString(1, 1, "CNT:"); // 计数器值
  12. OLED_ShowString(2, 1, "ALR:"); // 设置的闹钟值
  13. OLED_ShowString(3, 1, "ALRF:"); // 闹钟标志位
  14. // PWR_WakeUpPinCmd(ENABLE); // 用Wakeup引脚接入高电平的时候,也会唤醒待机模式
  15. uint32_t AlarmValue = RTC_GetCounter()+10; // 自定义AlarmValue闹钟值(10s)
  16. RTC_SetAlarm(AlarmValue); // 设置闹钟值函数
  17. OLED_ShowNum(2,5,AlarmValue,10); // 显示闹钟值
  18. while (1)
  19. {
  20. OLED_ShowNum(1,5,RTC_GetCounter(),10);
  21. OLED_ShowNum(3,6, RTC_GetFlagStatus(RTC_IT_ALR),2); // 闹钟标志位,触发置1
  22. OLED_ShowString(4, 1, "Running");
  23. Delay_ms(100);
  24. OLED_ShowString(4, 1, " ");
  25. Delay_ms(100);
  26. OLED_ShowString(4, 1, "StandBy");
  27. Delay_ms(2000);
  28. OLED_ShowString(4, 1, " ");
  29. Delay_ms(100);
  30. OLED_Clear(); // 模拟在进入待机前,把能关的外设也关了。
  31. PWR_EnterSTANDBYMode(); // 开启待机模式,唤醒后会从头开始执行。此条代码后代码将不会执行
  32. }
  33. }

 4.3 现象分析

1. 修改主频

OLED屏显示的“Running”闪烁周期随主频变化而变化。Delay函数里是以72MHz来写的。

2. 串口作为下位机接收数据+睡眠模式

运行过程:运行完一次停在_WFI()这里。当电脑发一个数据过来,触发了中断,进入中断函数把Serial_GetRxFlag() == 1,“Running”闪烁一下,又加入睡眠模式。如此往复。

3. 对射式红外计数器+停机模式

运行过程:  PWR_EnterSTOPMode(PWR_Regulator_ON, PWR_STOPEntry_WFI)一开始停机在这个函数中的_WFI()这里。计数器触发外部中断,会把唤醒停机模式,退出停机模式需要手动切换HSE72MHz主频。进入外部中断函数,计数,OLED显示,再次进入停机模式。如此往复。(第一次烧录是不需要按住复位键的,第二次烧录因为单片机已经进入待机模式,所以存储器都是不运行状态,需要用复位键唤醒的一瞬间把代码烧进去)。

4. 实时时钟+待机模式

烧录工程:编译--> 按住复位键 --> 下载。(第一次烧录是不需要按住复位键的,第二次烧录因为单片机已经进入待机模式,所以存储器都是关电状态,需要用复位键唤醒的一瞬间把代码烧进去)。

运行过程: 从头开始OLED显示,清屏。遇到PWR_EnterSTANDBYMode()中的_WFI()这里停止。当10秒过后,RTC产生alarm闹钟事件,Flag位置1,OLED显示,清屏。如此往复。

四、WDG 看门狗

1. WDG简介

  • IWDG:就是能帮你自动复位的东西。
  • 独立看门狗WDG:独立:指用的“LSI”时钟,与sysclk不相关。主线崩了,还是可以正常执行。精度低:它只有一个下限,即超过多少秒没有喂狗,它就会复位。
  • 窗口看门狗WWDG:窗口:用的APB1时钟不是独立的,有上限和下限,有一个"喂狗"时间范围,早晚都不行。

2. 看门狗的基本结构

2.1 独立看门狗IWDG

WDG原理:看门狗实际上就是一个递减计数器,当递减到0时就会产生IWDG复位。所以要持续的“喂狗”(就是及时在计数器递减到0前,手动重装载。)这样就不会触发复位。

  • 独立看门狗框图:类比 “时机单元(分频器、计数器,重装器)” 。
  • IWDG键寄存器: 控制那几个寄存器在特定的时候才写入,提高抗干扰性。比如:只有键寄存器写入0xCCCC时,看门狗才会启动;写入0xAAAA时,重装载寄存器才工作。

注:这些时间是按照40kHz时钟给出。实际上,MCU内部的RC频率会在30kHE到60kHz之间变化。

  • 超时时间:设置独立看门狗的下限(多少秒不喂狗就会复位)
  • 公式说明:LSI为40kHz,RL即重装寄存器范围0~4096;

2.2 窗口看门狗WWDG

  • WWDG框图:下限:看门狗控制寄存器 WDGA开启置1 当递减至T6=0时 复位;上限:看门狗配置寄存器 写入上限值 当计数值超过上限窗口值(T>W)时,如果进行喂狗  复位;

  • WWDG工作特性 :T[6 : 0]==0x40 时 T6最高位还是1 此时有一个中断EWI,可以在这一刻利用中断触发一些操作(数据紧急备份,用户提醒,及时喂狗等)T[6 : 0]==0x3F就复位看

  • 超时时间:设置独立看门狗的下限
  • 窗口时间:上限时间
  • 公式说明:4096是因为刚进入有一个4096的分频器

2.3  IWDG 和WWDG 对比

3. 相关库函数

  1. /* IWDG */
  2. void IWDG_WriteAccessCmd(uint16_t IWDG_WriteAccess); // IWDG寄存器:解除和开启写保护(PR和RLR)
  3. void IWDG_SetPrescaler(uint8_t IWDG_Prescaler); // 预分频器:选择预分频系数
  4. void IWDG_SetReload(uint16_t Reload); // 设置重装载值:RLR重装寄存器
  5. void IWDG_ReloadCounter(void); // 将RLR的值重装到计数器
  6. void IWDG_Enable(void); // 开启独立看门狗
  7. FlagStatus IWDG_GetFlagStatus(uint16_t IWDG_FLAG); // 获取标志位:SR状态寄存器
  8. /* WWDG */
  9. void WWDG_DeInit(void); // 恢复缺省配置
  10. void WWDG_SetPrescaler(uint32_t WWDG_Prescaler); // 预分频器:设置预分频值
  11. void WWDG_SetWindowValue(uint8_t WindowValue); // 设置窗口值:CFR寄存器 设置窗口寄存器上限
  12. void WWDG_EnableIT(void); // 使能中断
  13. void WWDG_SetCounter(uint8_t Counter); // 设置计数器值:看门狗控制寄存器CR
  14. void WWDG_Enable(uint8_t Counter); // 启动看门狗
  15. FlagStatus WWDG_GetFlagStatus(void);
  16. void WWDG_ClearFlag(void);

4. WDG 实例

4.1 前情提要

独立看门狗的时钟LSI会在调用时自动打开,无需手动开启。

4.2 程序代码

1. 独立看门狗IWDG

  1. // mainc.
  2. #include "stm32f10x.h" // Device header
  3. #include "Delay.h"
  4. #include "OLED.h"
  5. #include "Key.h"
  6. int main(void)
  7. {
  8. OLED_Init();
  9. Key_Init();
  10. OLED_ShowString(1,1,"IWDG TEST ");
  11. if(RCC_GetFlagStatus(RCC_FLAG_IWDGRST)== SET ) // 判断是不是看门狗导致的复位
  12. {
  13. OLED_ShowString(2,1,"IWDGRST "); // 如果是的话,闪烁显示IWDGRST
  14. Delay_ms(500);
  15. OLED_ShowString(2,1," ");
  16. Delay_ms(10);
  17. RCC_ClearFlag(); // 标志位要手动清除,不然会一直进if
  18. }
  19. else // 如果是上电或者按键导致的复位
  20. {
  21. OLED_ShowString(4,1,"NormalRST "); // 如果是的话,闪烁显示NormalRST
  22. Delay_ms(500);
  23. OLED_ShowString(4,1," ");
  24. Delay_ms(10);
  25. }
  26. /*
  27. 超时时间 T = 0.025ms * PR预分频系数 * (RL+1);
  28. ——————————————————————————————————————————————++
  29. 所以 当 T = 1S = 1000ms 时
  30. PR = 16;
  31. RL = 2499;
  32. */
  33. IWDG_WriteAccessCmd(IWDG_WriteAccess_Enable); // 解除和开启写保护(PR和RLR)
  34. IWDG_SetPrescaler(IWDG_Prescaler_16); // 预分频器:选择预分频系数
  35. IWDG_SetReload(2499); // 设置重装载值:RLR重装寄存器
  36. IWDG_ReloadCounter(); // 初始化:第一次喂狗,把重装载值给计数器递减
  37. IWDG_Enable(); // 开启独立看门狗
  38. while(1)
  39. {
  40. Key_Num(); // 按住按键不放,模拟程序卡死
  41. IWDG_ReloadCounter();
  42. // 直观显示一下喂狗
  43. OLED_ShowString(4,1,"FEED");
  44. Delay_ms(200);
  45. OLED_ShowString(4,1," ");
  46. Delay_ms(600);
  47. }
  48. }

2. WWDG 窗口看门狗

  1. // main.c
  2. #include "stm32f10x.h" // Device header
  3. #include "Delay.h"
  4. #include "OLED.h"
  5. #include "Key.h"
  6. int main(void)
  7. {
  8. OLED_Init();
  9. Key_Init();
  10. OLED_ShowString(1,1,"WWDG TEST ");
  11. if(RCC_GetFlagStatus(RCC_FLAG_WWDGRST)== SET ) // 判断是不是看门狗导致的复位
  12. {
  13. OLED_ShowString(2,1,"WWDGRST "); // 如果是的话,闪烁显示IWDGRST
  14. Delay_ms(500);
  15. OLED_ShowString(2,1," ");
  16. Delay_ms(10);
  17. RCC_ClearFlag(); // 标志位要手动清除,不然会一直进if
  18. }
  19. else // 如果是上电或者按键导致的复位
  20. {
  21. OLED_ShowString(4,1,"NormalRST "); // 如果是的话,闪烁显示NormalRST
  22. Delay_ms(500);
  23. OLED_ShowString(4,1," ");
  24. Delay_ms(10);
  25. }
  26. RCC_APB1PeriphClockCmd(RCC_APB1Periph_WWDG,ENABLE);
  27. /*
  28. 超时时间(下限):T(WWDG) = (1/36000)ms * 4096 * 分频系数 * (T[5:0]+1)
  29. 窗口时间(上限):T (WIN) = (1/36000)ms * 4096 * 分频系数 * (T[5:0] - W[5:0])
  30. ——————————————————————————————————————————————————————————————————————————————
  31. 所以 当T(WWDG)= 50ms T(WIN) =30ms 时
  32. 分频系数 = 8 ;
  33. T[5:0] = 54 ;
  34. W[5:0] = 21 ;
  35. */
  36. WWDG_SetPrescaler(WWDG_Prescaler_8); // 预分频器:设置预分频值
  37. WWDG_SetWindowValue(0x40|21); // 设置窗口值:CFR寄存器 设置窗口寄存器上限 0x40|是加上T6位
  38. WWDG_Enable(0x40|54); // 启动看门狗,这边一开始就得给计数器值,不然直接复位 0x40|是加上w6位
  39. while(1)
  40. {
  41. Key_Num();
  42. // 直观显示一下喂狗
  43. OLED_ShowString(4,1,"FEED");
  44. Delay_ms(20);
  45. OLED_ShowString(4,1," ");
  46. Delay_ms(20);
  47. WWDG_SetCounter(0x40|54); // 设置计数器值:看门狗控制寄存器C
  48. }
  49. }

4.3 现象分析

1. 独立看门狗 : 刚上电和按复位键的时候,OLED闪NormalRST,按住按键,因为按键函数里有死循环。会触发IWDG复位,OLED闪IWDGRST;

2.WWDG 窗口看门狗: 下限同上,WWDG_Enable(0x40|54); WWDG_SetCounter(0x40|54); 

这俩条放太近时,触发上限,触发IWDG复位,OLED闪WWDGRST;

五、FLASH

1. FLASH简介

主要学习STM32F1系列的FLASH的读写操作. FLASH属于ROM 掉电不丢失.(相当于手机的内存)  FLASH与传统的ROM有所不同,它具有可擦除和可编程的特性。说FLASH有多少KB,一般指的是程序存储器的大小.

  • 程序存储器: 程序存放的地方; 
  • 系统存储器: 存放原厂写入BOOTLOAD,用于串口下载;
  • 选项字节:独立于程序的配置(比如FLASH的写保护等);
  • ICP :通过串口+某种协议 = 更新程序 STLink下载程序就是这种方式 ;
  • IAP : 类似于OTA 在程序中编写程序, 完成程序更新;


                                      STM32F1系列中容量产品的FLASH分布情况

2. FLASH基本结构

橙色框的是FLASH   绿色的是FLASH的管理员  管理FLASH的擦除与编程

3. FLASH读写 (写保护)

  1. 读取操作:  直接用图中的格式去读;
  2. 擦除操作: 擦除分为页擦除 (擦除制定的某一页) 和全擦除 (所有页都擦除) .擦除之前得先解锁,擦完得上锁;
  3. 写入操作: 写入前要先进行擦除操作 写入只能最小以半字 (16位=2个字节) 为单位 . 想要单独一个字节想要先把一页读取到SRAM,在SRAM中进行读写,再擦除写入.
  4.  选项字节: 写保护; 

__IO 是宏定义  volatile 作用见:https://www.bilibili.com/video/BV1th411z7sn?p=48&vd_source=e0d1764e101d3ab24028ce747677a419icon-default.png?t=N7T8https://www.bilibili.com/video/BV1th411z7sn?p=48&vd_source=e0d1764e101d3ab24028ce747677a419这里的作用简单说就是, 让读写直接面对FLASH寄存器,不通过缓冲器什么的.

  • WRP0/1/2/3 : 四个字节Byte = 32 位 , 1位保护4页,  32 * 4 = 128页 正好对应中容量型的最大页数.

4. FLASH读写实例

4.1 程序代码

  1. MyFlash.c : 编写FLASH的基本操作函数  (读取 擦除 编程)
  2. Store.c :  编写应用层, 用SRAM数组管理FLASH任意一页,实现任意参数的读写与保存 (实现目标是: 往这个数组写入任意参数, 这些参数掉电不丢失)
  3. main.c : 测试应用.
  4. 读取设备FLASH内存大小和ID号

MyFlash.c

  1. #include "stm32f10x.h" // Device header
  2. /* 一个地址下有一个字节数据, 如果读半字,会读(指定地址和指定地址+1)下的数据*/
  3. // 全字读FLASH指定地址内容
  4. uint32_t MyFlash_ReadWord(uint32_t Address)
  5. {
  6. return *((__IO uint32_t *)(Address)); // 指针取地址
  7. }
  8. // 半字读FLASH指定地址内容
  9. uint16_t MyFlash_ReadHalfWord(uint32_t Address)
  10. {
  11. return *((__IO uint16_t *)(Address)); // 指针取地址
  12. }
  13. // 字节读FLASH指定地址内容
  14. uint8_t MyFlash_ReadByte(uint32_t Address)
  15. {
  16. return *((__IO uint8_t *)(Address)); // 指针取地址
  17. }
  18. // 页擦除
  19. void MyFlash_ErasePages(uint32_t Address)
  20. {
  21. FLASH_Unlock(); // FLASH 解锁
  22. FLASH_ErasePage(Address); // 页擦除
  23. FLASH_Lock(); // FLASH 上锁
  24. }
  25. // 全擦除
  26. void MyFlash_EraseAllPages(void)
  27. {
  28. FLASH_Unlock(); // FLASH 解锁
  29. FLASH_EraseAllPages(); // 全擦除
  30. FLASH_Lock(); // FLASH 上锁
  31. }
  32. // 以半字编程写入FLASH
  33. void MyFlash_WriteHalfWord(uint32_t Address, uint16_t Data)
  34. {
  35. FLASH_Unlock();
  36. FLASH_ProgramHalfWord(Address,Data);
  37. FLASH_Lock();
  38. }
  39. // 以全字编程写入FLASH
  40. void MyFlash_WriteWord(uint32_t Address, uint32_t Data)
  41. {
  42. FLASH_Unlock();
  43. FLASH_ProgramWord(Address,Data); // 内部已经有擦除
  44. FLASH_Lock();
  45. }

Store.c

  1. #include "stm32f10x.h" // Device header
  2. #include "MyFlash.h"
  3. #define STORE_START_ADDRESS 0x0800FC00
  4. #define STORE_COUNT 512
  5. /* 用SRAM数组管理FLASH任意一页,实现任意参数的读写与保存*/
  6. uint16_t Store_Data[STORE_COUNT]; // 1024个字节 对应最后一页
  7. // 第一次使用把FLASH最后一页参数置0,匹配SRAM数组初始的元素值都为0
  8. void Store_Init(void)
  9. {
  10. if(MyFlash_ReadHalfWord(STORE_START_ADDRESS) != 0xA5A5) // 第一次使用FLASH,不等于自定义标志位
  11. {
  12. MyFlash_ErasePages(STORE_START_ADDRESS); // 擦除最后一页
  13. MyFlash_WriteHalfWord(STORE_START_ADDRESS,0xA5A5); // 先写入自定义标志位(占用2个字节)
  14. for(uint16_t i=1 ; i<STORE_COUNT ; i++ ) // 从第三个字节开始
  15. {
  16. MyFlash_WriteHalfWord(STORE_START_ADDRESS + i * 2 ,0x0000); // 写入0
  17. }
  18. }
  19. // 上电时,把FLASH参数转移给SRAM数组(实现掉电不丢失)
  20. for(uint16_t i=0 ; i<STORE_COUNT ; i++ ) // 从第三个字节开始
  21. {
  22. Store_Data[i] = MyFlash_ReadHalfWord(STORE_START_ADDRESS + i * 2);
  23. }
  24. }
  25. // 把SRAM数组存入FLASH(实现掉电不丢失)
  26. void Store_Save(void)
  27. {
  28. MyFlash_ErasePages(STORE_START_ADDRESS);
  29. for(uint16_t i=0 ; i<STORE_COUNT ; i++ )
  30. {
  31. MyFlash_WriteHalfWord(STORE_START_ADDRESS + i * 2 ,Store_Data[i]); // 从第三个字节开始
  32. }
  33. }
  34. // 清0这个掉电不丢失的SRAM数组
  35. void Store_Clear(void)
  36. {
  37. for(uint16_t i=1 ; i<STORE_COUNT ; i++)
  38. {
  39. Store_Data[i]=0x0000;
  40. }
  41. Store_Save();
  42. }

main.c

  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. #include "Store.h"
  5. #include "Key.h"
  6. int main(void)
  7. {
  8. OLED_Init();
  9. Key_Init();
  10. Store_Init();
  11. OLED_ShowString(1,1,"Flag: ");
  12. OLED_ShowString(2,1,"Data: ");
  13. while(1)
  14. {
  15. uint8_t KeyNum = Key_Num();
  16. OLED_ShowNum(4,13,KeyNum,1);
  17. if(KeyNum == 1)
  18. {
  19. Store_Data[1]++;
  20. Store_Data[2]+=2;
  21. Store_Data[3]+=3;
  22. Store_Data[4]+=4;
  23. Store_Save();
  24. }
  25. if(KeyNum == 2)
  26. {
  27. Store_Clear();
  28. }
  29. OLED_ShowHexNum(1,6,Store_Data[0],4);
  30. OLED_ShowHexNum(3,1,Store_Data[1],4);
  31. OLED_ShowHexNum(3,6,Store_Data[2],4);
  32. OLED_ShowHexNum(4,1,Store_Data[3],4);
  33. OLED_ShowHexNum(4,6,Store_Data[4],4);
  34. }
  35. }
  1. #include "stm32f10x.h" // Device header
  2. #include "Delay.h"
  3. #include "OLED.h"
  4. int main(void)
  5. {
  6. OLED_Init();
  7. OLED_ShowString(1, 1, "F_SIZE:");
  8. OLED_ShowHexNum(1, 8, *((__IO uint16_t *)(0x1FFFF7E0)), 4);// FLASH容量大小
  9. OLED_ShowString(2, 1, "U_ID:");
  10. OLED_ShowHexNum(2, 6, *((__IO uint16_t *)(0x1FFFF7E8)), 4);
  11. OLED_ShowHexNum(2, 11, *((__IO uint16_t *)(0x1FFFF7E8 + 0x02)), 4);
  12. OLED_ShowHexNum(3, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x04)), 8);
  13. OLED_ShowHexNum(4, 1, *((__IO uint32_t *)(0x1FFFF7E8 + 0x08)), 8);
  14. while (1)
  15. {
  16. }
  17. }

4.2 注意事项

4.3 STM32 ST-LINK Utility

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/676752
推荐阅读
相关标签
  

闽ICP备14008679号