25/1/18 嵌入式笔记 STM32F103
Unix时间戳
BKP和RTC
RTC基本结构
是一种用于跟踪时间的硬件模块,通常集成在微控制器或独立的芯片中。RTC 可以提供年、月、日、时、分、秒等时间信息,并且在系统断电时通过备用电池保持运行。
RTC 的基本功能
-
时间跟踪:提供年、月、日、时、分、秒等信息。
-
日期计算:自动处理闰年、月份天数等。
-
闹钟功能:在特定时间触发中断。
-
低功耗:在系统断电时通过备用电池供电。
RTC 的硬件连接
RTC 通常需要以下硬件支持:
-
主电源:为 RTC 提供运行电源。
-
备用电池:在系统断电时为 RTC 供电(如纽扣电池)。
-
晶振:提供精确的时钟信号(通常为 32.768 kHz)。
RTC 的寄存器
RTC 通常通过一组寄存器来配置和读取时间信息。常见的寄存器包括:
-
时间寄存器:存储时、分、秒。
-
日期寄存器:存储年、月、日。
-
控制寄存器:配置 RTC 的工作模式。
-
闹钟寄存器:设置闹钟时间。
RTC 的实现步骤
初始化 RTC
RTC_HandleTypeDef hrtc;
void RTC_Init(void) {
hrtc.Instance = RTC;
hrtc.Init.HourFormat = RTC_HOURFORMAT_24; // 24 小时制
hrtc.Init.AsynchPrediv = 127; // 异步预分频
hrtc.Init.SynchPrediv = 255; // 同步预分频
hrtc.Init.OutPut = RTC_OUTPUT_DISABLE; // 禁用输出
hrtc.Init.OutPutPolarity = RTC_OUTPUT_POLARITY_HIGH;
hrtc.Init.OutPutType = RTC_OUTPUT_TYPE_OPENDRAIN;
HAL_RTC_Init(&hrtc);
}
解析:
hrtc.Instance
:选择 RTC 外设实例。
hrtc.Init.HourFormat
:设置时间格式(12 小时制或 24 小时制)。
hrtc.Init.AsynchPrediv
和hrtc.Init.SynchPrediv
:设置预分频值,用于生成 1 Hz 的时钟信号。
HAL_RTC_Init(&hrtc)
:初始化 RTC 外设。
设置时间和日期
void RTC_SetTime(uint8_t hour, uint8_t minute, uint8_t second) {
RTC_TimeTypeDef sTime = {0};
sTime.Hours = hour;
sTime.Minutes = minute;
sTime.Seconds = second;
HAL_RTC_SetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
}
void RTC_SetDate(uint8_t year, uint8_t month, uint8_t day) {
RTC_DateTypeDef sDate = {0};
sDate.Year = year;
sDate.Month = month;
sDate.Date = day;
HAL_RTC_SetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
}
RTC 需要初始时间和日期才能开始计时。
通过设置函数初始化 RTC 的时间和日期。
解析:
RTC_TimeTypeDef
和RTC_DateTypeDef
:时间和日期的结构体。
HAL_RTC_SetTime()
和HAL_RTC_SetDate()
:HAL 库函数,用于设置时间和日期。
读取时间和日期
void RTC_GetTime(uint8_t *hour, uint8_t *minute, uint8_t *second) {
RTC_TimeTypeDef sTime = {0};
HAL_RTC_GetTime(&hrtc, &sTime, RTC_FORMAT_BIN);
*hour = sTime.Hours;
*minute = sTime.Minutes;
*second = sTime.Seconds;
}
void RTC_GetDate(uint8_t *year, uint8_t *month, uint8_t *day) {
RTC_DateTypeDef sDate = {0};
HAL_RTC_GetDate(&hrtc, &sDate, RTC_FORMAT_BIN);
*year = sDate.Year;
*month = sDate.Month;
*day = sDate.Date;
}
作用:
读取 RTC 的当前时间和日期。
为什么需要:
通过读取函数获取 RTC 的当前时间和日期。
解析:
HAL_RTC_GetTime()
和HAL_RTC_GetDate()
:HAL 库函数,用于读取时间和日期。
主函数中使用
int main(void) {
HAL_Init();
SystemClock_Config();
RTC_Init(); // 初始化 RTC
// 设置初始时间和日期
RTC_SetTime(12, 0, 0); // 12:00:00
RTC_SetDate(23, 10, 15); // 2023 年 10 月 15 日
uint8_t hour, minute, second, year, month, day;
while (1) {
// 读取当前时间和日期
RTC_GetTime(&hour, &minute, &second);
RTC_GetDate(&year, &month, &day);
// 打印时间和日期(通过串口或调试工具)
printf("Time: %02d:%02d:%02d\n", hour, minute, second);
printf("Date: 20%02d-%02d-%02d\n", year, month, day);
HAL_Delay(1000); // 延时 1 秒
}
}
解析:
HAL_Init()
:初始化 HAL 库。
SystemClock_Config()
:配置系统时钟。
RTC_Init()
:初始化 RTC 外设。
RTC_SetTime()
和RTC_SetDate()
:设置初始时间和日期。
RTC_GetTime()
和RTC_GetDate()
:读取当前时间和日期。
printf()
:打印时间和日期(需要重定向串口)。
HAL_Delay(1000)
:延时 1 秒。
PWR电源控制
修改主频的配置
修改主频是通过配置系统时钟源和分频器来实现的。STM32 的时钟源包括:
-
HSI:内部高速时钟(通常为 8 MHz)。
-
HSE:外部高速时钟(通常为 8-25 MHz)。
-
PLL:锁相环,用于倍频时钟。
代码实现
void SystemClock_Config(void) {
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 配置HSE和PLL
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置时钟树
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK
| RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
}
HSE(外部高速时钟):
HSE通常是一个外部晶振(如8MHz),提供更精确和稳定的时钟源。
RCC_OscInitStruct.HSEState = RCC_HSE_ON
:启用HSE。PLL(锁相环):
PLL用于将输入时钟倍频到更高的频率。
PLLM = 8
:将HSE时钟分频为1MHz(8MHz / 8 = 1MHz)。
PLLN = 336
:将1MHz倍频到336MHz。
PLLP = RCC_PLLP_DIV2
:将PLL输出分频为168MHz(336MHz / 2 = 168MHz),作为系统时钟(SYSCLK)。
PLLQ = 7
:用于生成USB、SDIO等外设的时钟。时钟树配置:
SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK
:选择PLL输出作为系统时钟。
AHBCLKDivider = RCC_SYSCLK_DIV1
:AHB总线时钟与系统时钟相同(168MHz)。
APB1CLKDivider = RCC_HCLK_DIV4
:APB1总线时钟为42MHz(168MHz / 4)。
APB2CLKDivider = RCC_HCLK_DIV2
:APB2总线时钟为84MHz(168MHz / 2)。FLASH_LATENCY:
根据主频设置FLASH延迟,确保CPU能够正确读取FLASH中的数据。168MHz需要
FLASH_LATENCY_5
。
睡眠模式的配置
睡眠模式下,CPU停止运行,但外设和时钟仍然运行。可以通过WFI
(等待中断)或WFE
(等待事件)指令进入睡眠模式。
代码解析:
void Enter_Sleep_Mode(void) {
HAL_SuspendTick(); // 挂起SysTick
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI);
HAL_ResumeTick(); // 恢复SysTick
}
配置原因:
挂起SysTick:
HAL_SuspendTick()
:在进入睡眠模式前,挂起SysTick定时器,避免其产生中断唤醒CPU。进入睡眠模式:
HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI)
:
PWR_MAINREGULATOR_ON
:保持主稳压器开启,以快速恢复运行。
PWR_SLEEPENTRY_WFI
:使用WFI
指令进入睡眠模式。恢复SysTick:
HAL_ResumeTick()
:退出睡眠模式后,恢复SysTick定时器。
停止模式的配置
停止模式下,所有时钟都停止,但SRAM和寄存器内容保持不变。可以通过WFI
或WFE
指令进入停止模式。
代码解析:
void Enter_Stop_Mode(void) {
HAL_SuspendTick(); // 挂起SysTick
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
HAL_ResumeTick(); // 恢复SysTick
SystemClock_Config(); // 重新配置时钟
}
配置原因:
挂起SysTick:
HAL_SuspendTick()
:挂起SysTick定时器。进入停止模式:
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI)
:
PWR_LOWPOWERREGULATOR_ON
:启用低功耗稳压器,进一步降低功耗。
PWR_STOPENTRY_WFI
:使用WFI
指令进入停止模式。恢复时钟:
退出停止模式后,时钟会被关闭,因此需要重新配置时钟树(
SystemClock_Config()
)。
待机模式的配置
待机模式下,除了备份域和待机电路,所有时钟和电源都被关闭。可以通过WFI
或WFE
指令进入待机模式。
代码解析:
void Enter_Standby_Mode(void) {
HAL_PWR_EnterSTANDBYMode();
}
配置原因:
进入待机模式:
HAL_PWR_EnterSTANDBYMode()
:关闭所有时钟和电源,仅保留备份域和待机电路。唤醒后,系统会从头开始执行(类似于复位)。
总结
-
修改主频:通过配置时钟树,选择合适的时钟源和PLL参数,达到所需的主频。
-
睡眠模式:挂起SysTick,使用
WFI
进入睡眠模式,适用于需要快速唤醒的场景。 -
停止模式:挂起SysTick,使用
WFI
进入停止模式,适用于需要更低功耗的场景。 -
待机模式:直接进入待机模式,适用于需要最低功耗的场景。
WDG看门狗
看门狗(Watchdog,简称WDG)是嵌入式系统中用于检测和恢复系统故障的重要机制。它的核心原理是通过定时器监控系统的运行状态,如果系统在一定时间内未能正常“喂狗”(即重置看门狗定时器),看门狗会强制复位系统,从而防止系统因软件错误或硬件故障而进入死循环或卡死状态。
看门狗的基本原理
看门狗的核心是一个递减计数器(或递增计数器),当计数器达到特定值时,看门狗会触发系统复位。为了防止复位,系统需要定期“喂狗”,即在计数器达到特定值之前重置计数器。
工作流程:
初始化看门狗:
配置看门狗的计数器初始值和超时时间。
启动看门狗:
启动看门狗定时器,计数器开始递减(或递增)。
喂狗:
在程序正常运行期间,定期重置看门狗计数器。
超时复位:
如果程序未能及时喂狗,计数器达到超时值,看门狗触发系统复位。
独立看门狗(IWDG,Independent Watchdog):
由独立的硬件模块实现,通常使用内部低速时钟(LSI)作为时钟源。
独立于主系统时钟,即使主系统时钟失效,独立看门狗仍能正常工作。
适用于检测系统死机或严重故障。
窗口看门狗(WWDG,Window Watchdog):
由主系统时钟驱动,通常用于检测软件逻辑错误。
喂狗时间必须在规定的“窗口”内,过早或过晚喂狗都会触发复位。
适用于检测程序跑飞或逻辑错误。
独立看门狗(IWDG)配置示例(STM32 HAL库)
#include "stm32f4xx_hal.h"
void IWDG_Init(void) {
// 初始化独立看门狗
hiwdg.Instance = IWDG;
hiwdg.Init.Prescaler = IWDG_PRESCALER_32; // 预分频系数
hiwdg.Init.Reload = 4095; // 计数器初值
HAL_IWDG_Init(&hiwdg);
}
void IWDG_Feed(void) {
// 喂狗
HAL_IWDG_Refresh(&hiwdg);
}
窗口看门狗(WWDG)配置示例(STM32 HAL库):
#include "stm32f4xx_hal.h"
void WWDG_Init(void) {
// 初始化窗口看门狗
hwwdg.Instance = WWDG;
hwwdg.Init.Prescaler = WWDG_PRESCALER_8; // 预分频系数
hwwdg.Init.Window = 0x5F; // 上窗口值
hwwdg.Init.Counter = 0x7F; // 计数器初值
hwwdg.Init.EWIMode = WWDG_EWI_DISABLE; // 禁用早期唤醒中断
HAL_WWDG_Init(&hwwdg);
}
void WWDG_Feed(void) {
// 喂狗
HAL_WWDG_Refresh(&hwwdg);
}
Flash 内存的特点
-
非易失性:
-
数据在断电后不会丢失。
-
-
按扇区管理:
-
Flash 内存通常分为多个扇区,每个扇区可以独立擦除和编程。
-
-
有限的擦写次数:
-
Flash 内存的擦写次数有限(通常为 10,000 到 100,000 次),因此需要谨慎使用。
-
-
读写速度较慢:
-
相比 RAM,Flash 的读写速度较慢,尤其是写操作需要较长时间。
-
-
写前需擦除:
-
Flash 内存的写操作需要先擦除,擦除的最小单位通常是一个扇区。
-
STM32 的 Flash 内存结构
以 STM32F4 系列为例,Flash 内存的结构如下:
-
主存储区:用于存储程序代码和数据。
-
系统存储区:存储 Bootloader 代码(由 ST 提供,用于串口或 USB 下载程序)。
-
选项字节:用于配置读写保护、看门狗、复位模式等。