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.AsynchPredivhrtc.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_TimeTypeDefRTC_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);
}
  1. HSE(外部高速时钟)

    • HSE通常是一个外部晶振(如8MHz),提供更精确和稳定的时钟源。

    • RCC_OscInitStruct.HSEState = RCC_HSE_ON:启用HSE。

  2. 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等外设的时钟。

  3. 时钟树配置

    • 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)。

  4. 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
}

配置原因:

  1. 挂起SysTick

    • HAL_SuspendTick():在进入睡眠模式前,挂起SysTick定时器,避免其产生中断唤醒CPU。
  2. 进入睡眠模式

    • HAL_PWR_EnterSLEEPMode(PWR_MAINREGULATOR_ON, PWR_SLEEPENTRY_WFI)

      • PWR_MAINREGULATOR_ON:保持主稳压器开启,以快速恢复运行。

      • PWR_SLEEPENTRY_WFI:使用WFI指令进入睡眠模式。

  3. 恢复SysTick

    • HAL_ResumeTick():退出睡眠模式后,恢复SysTick定时器。

停止模式的配置

停止模式下,所有时钟都停止,但SRAM和寄存器内容保持不变。可以通过WFIWFE指令进入停止模式。

代码解析:

复制代码
void Enter_Stop_Mode(void) {
    HAL_SuspendTick();  // 挂起SysTick
    HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
    HAL_ResumeTick();   // 恢复SysTick
    SystemClock_Config();  // 重新配置时钟
}

配置原因:

  1. 挂起SysTick

    • HAL_SuspendTick():挂起SysTick定时器。
  2. 进入停止模式

    • HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI)

      • PWR_LOWPOWERREGULATOR_ON:启用低功耗稳压器,进一步降低功耗。

      • PWR_STOPENTRY_WFI:使用WFI指令进入停止模式。

  3. 恢复时钟

    • 退出停止模式后,时钟会被关闭,因此需要重新配置时钟树(SystemClock_Config())。

待机模式的配置

待机模式下,除了备份域和待机电路,所有时钟和电源都被关闭。可以通过WFIWFE指令进入待机模式。

代码解析:

复制代码
void Enter_Standby_Mode(void) {
    HAL_PWR_EnterSTANDBYMode();
}

配置原因:

  1. 进入待机模式

    • HAL_PWR_EnterSTANDBYMode():关闭所有时钟和电源,仅保留备份域和待机电路。

    • 唤醒后,系统会从头开始执行(类似于复位)。

总结

  • 修改主频:通过配置时钟树,选择合适的时钟源和PLL参数,达到所需的主频。

  • 睡眠模式 :挂起SysTick,使用WFI进入睡眠模式,适用于需要快速唤醒的场景。

  • 停止模式 :挂起SysTick,使用WFI进入停止模式,适用于需要更低功耗的场景。

  • 待机模式:直接进入待机模式,适用于需要最低功耗的场景。

WDG看门狗

看门狗(Watchdog,简称WDG)是嵌入式系统中用于检测和恢复系统故障的重要机制。它的核心原理是通过定时器监控系统的运行状态,如果系统在一定时间内未能正常"喂狗"(即重置看门狗定时器),看门狗会强制复位系统,从而防止系统因软件错误或硬件故障而进入死循环或卡死状态。

看门狗的基本原理

看门狗的核心是一个递减计数器(或递增计数器),当计数器达到特定值时,看门狗会触发系统复位。为了防止复位,系统需要定期"喂狗",即在计数器达到特定值之前重置计数器。

工作流程:

  1. 初始化看门狗

    • 配置看门狗的计数器初始值和超时时间。
  2. 启动看门狗

    • 启动看门狗定时器,计数器开始递减(或递增)。
  3. 喂狗

    • 在程序正常运行期间,定期重置看门狗计数器。
  4. 超时复位

    • 如果程序未能及时喂狗,计数器达到超时值,看门狗触发系统复位。
  1. 独立看门狗(IWDG,Independent Watchdog)

    • 由独立的硬件模块实现,通常使用内部低速时钟(LSI)作为时钟源。

    • 独立于主系统时钟,即使主系统时钟失效,独立看门狗仍能正常工作。

    • 适用于检测系统死机或严重故障。

  2. 窗口看门狗(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 内存的特点

  1. 非易失性

    • 数据在断电后不会丢失。
  2. 按扇区管理

    • Flash 内存通常分为多个扇区,每个扇区可以独立擦除和编程。
  3. 有限的擦写次数

    • Flash 内存的擦写次数有限(通常为 10,000 到 100,000 次),因此需要谨慎使用。
  4. 读写速度较慢

    • 相比 RAM,Flash 的读写速度较慢,尤其是写操作需要较长时间。
  5. 写前需擦除

    • Flash 内存的写操作需要先擦除,擦除的最小单位通常是一个扇区。

STM32 的 Flash 内存结构

以 STM32F4 系列为例,Flash 内存的结构如下:

  • 主存储区:用于存储程序代码和数据。

  • 系统存储区:存储 Bootloader 代码(由 ST 提供,用于串口或 USB 下载程序)。

  • 选项字节:用于配置读写保护、看门狗、复位模式等。

相关推荐
我爱挣钱我也要早睡!2 小时前
Java 复习笔记
java·开发语言·笔记
汇能感知7 小时前
摄像头模块在运动相机中的特殊应用
经验分享·笔记·科技
阿巴Jun7 小时前
【数学】线性代数知识点总结
笔记·线性代数·矩阵
茯苓gao8 小时前
STM32G4 速度环开环,电流环闭环 IF模式建模
笔记·stm32·单片机·嵌入式硬件·学习
是誰萆微了承諾8 小时前
【golang学习笔记 gin 】1.2 redis 的使用
笔记·学习·golang
DKPT8 小时前
Java内存区域与内存溢出
java·开发语言·jvm·笔记·学习
ST.J9 小时前
前端笔记2025
前端·javascript·css·vue.js·笔记
Suckerbin9 小时前
LAMPSecurity: CTF5靶场渗透
笔记·安全·web安全·网络安全
小憩-10 小时前
【机器学习】吴恩达机器学习笔记
人工智能·笔记·机器学习
UQI-LIUWJ10 小时前
unsloth笔记:运行&微调 gemma
人工智能·笔记·深度学习