解码STM32 看门狗、低功耗与RTC外设

news/2026/1/25 22:08:02/文章来源:https://www.cnblogs.com/YouEmbedded/p/19530591

看门狗外设的原理与应用

概述

随着单片机在工业控制、智能设备等领域广泛应用,系统稳定性成为关键。电磁干扰、电压波动等外部因素可能导致程序“跑飞”,即程序执行失控,表现为数据丢失、寄存器值异常、程序指针指向非法地址等。

看门狗(WatchDog Timer,简称WDT)是一种硬件定时器,用于监控程序运行状态。若程序在规定时间内未“喂狗”,看门狗将触发系统复位,使程序重新运行,从而提高系统可靠性。

看门狗

STM32F4系列内置两种看门狗:

  • 独立看门狗(IWDG):基于独立时钟(LSI),可在递减计数器到达0x000之前的任意时间喂狗。

    图片1

    框图

    图片3

    使用流程

  • 窗口看门狗(WWDG):必须在预设的时间窗口内喂狗,过早或过晚均会触发复位。

    图片2

    框图

    image

喂狗策略与注意事项

喂狗策略:

  • 在主循环、定时中断、关键任务结束处分别喂狗。
  • 对耗时任务(如Flash操作、复杂计算)应在子步骤中插入喂狗。
  • 可采用分层喂狗:如在主循环或高优先级任务中执行,用于监控整体流程。在独立中断(如独立定时器中断)中执行,监控主程序是否被阻塞。

注意事项:

  • 避免喂狗过于集中:仅在主循环中喂狗可能导致中断或后台任务超时,引发误复位。
  • 避免喂狗频率过高:高频喂狗可能掩盖主程序死锁等问题。
  • 记录复位原因:应在非易失存储器中记录看门狗复位次数与原因,便于故障分析。

判断复位来源

通过检查RCC_FLAG_IWDGRST标志位,可区分是看门狗复位还是手动复位。

图片4

独立看门狗示例代码(含注释)

#include "stm32f4xx.h"
#include <string.h>
#include <stdio.h>
#include <stdbool.h>/* 全局变量(用于LSI频率捕获)--------------------------------------------------*/
__IO uint32_t uwCaptureNumber = 0;  // 捕获次数计数
__IO uint32_t uwPeriodValue = 0;    // LSI周期值
__IO uint32_t uwLSIFrequency = 0;   // 实际LSI频率/* 宏定义(看门狗配置)--------------------------------------------------------*/
#define IWDG_TARGET_TIMEOUT_MS 250  // 目标看门狗超时时间(毫秒),可自定义//fputc函数重定向,支持printf输出到USART1
int fputc(int ch, FILE *f) 
{while( USART_GetFlagStatus(USART1,USART_FLAG_TXE) == RESET );USART_SendData(USART1, (uint8_t)ch);  return ch;
}/* 延时函数 -------------------------------------------------------------------*/
/*** @brief  延时微秒* @param  nus: 待延时时间(us)* @retval None* @note   Systick时钟源为21MHz*/
void delay_us(uint32_t nus)
{SysTick->CTRL = 0;              // 关闭定时器SysTick->LOAD = nus * 21 - 1;   // 设置重载值SysTick->VAL  = 0;              // 清除当前值SysTick->CTRL = 1;              // 启动定时器while ((SysTick->CTRL & 0x00010000) == 0); // 等待计时完成SysTick->CTRL = 0;              // 关闭定时器
}/*** @brief  延时毫秒* @param  nms: 待延时时间(ms)* @retval None* @note   Systick时钟源为21MHz*/
void delay_ms(uint32_t nms)
{while(nms--) {SysTick->CTRL = 0;              // 关闭定时器SysTick->LOAD = 21 * 1000 - 1;  // 设置重载值SysTick->VAL  = 0;              // 清除当前值SysTick->CTRL = 1;              // 启动定时器while ((SysTick->CTRL & 0x00010000) == 0); // 等待计时完成SysTick->CTRL = 0;              // 关闭定时器}   
}/* USART1初始化 ----------------------------------------------------------------*/
static void PC_Config(uint32_t baud)
{USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;// 1. 使能GPIOA时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);// 2. 使能USART1时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);// 3. 配置引脚复用功能GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);  // PA9 -> USART1_TXGPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); // PA10 -> USART1_RX// 4. 配置GPIO参数GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;         // 复用模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10;GPIO_Init(GPIOA, &GPIO_InitStructure);// 5. 配置USART参数USART_InitStructure.USART_BaudRate    = baud;                     // 波特率USART_InitStructure.USART_WordLength  = USART_WordLength_8b;      // 8位数据位USART_InitStructure.USART_StopBits    = USART_StopBits_1;         // 1位停止位USART_InitStructure.USART_Parity      = USART_Parity_No;          // 无校验USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   // 收发模式USART_Init(USART1, &USART_InitStructure);// 6. 配置USART1中断(仅接收中断)NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;  // 优先级低于TIM5中断,避免影响LSI捕获NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 7. 使能接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 8. 使能USART1USART_Cmd(USART1, ENABLE);
}/* LSI频率获取函数 -------------------------------------------------------------*/
static uint32_t GetLSIFrequency(void)
{NVIC_InitTypeDef   NVIC_InitStructure;TIM_ICInitTypeDef  TIM_ICInitStructure;RCC_ClocksTypeDef  RCC_ClockFreq;// 1. 使能LSI振荡器RCC_LSICmd(ENABLE);// 等待LSI稳定while (RCC_GetFlagStatus(RCC_FLAG_LSIRDY) == RESET);// 2. 配置TIM5用于LSI频率捕获RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);  // 使能TIM5时钟TIM_RemapConfig(TIM5, TIM5_LSI);                      // TIM5_CH4映射到LSITIM_PrescalerConfig(TIM5, 0, TIM_PSCReloadMode_Immediate); // 预分频=0,立即生效// 3. 配置输入捕获模式TIM_ICInitStructure.TIM_Channel = TIM_Channel_4;TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;  // 上升沿捕获TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // DirectTI是直接映射 IC4——TI4 让第 x 个输入捕获通道(ICx)直接监听第 x 个定时器输入引脚(TIx)的信号TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV8;        // 输入分频8TIM_ICInitStructure.TIM_ICFilter = 0;TIM_ICInit(TIM5, &TIM_ICInitStructure);// 4. 配置TIM5中断NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;  // 最高抢占优先级NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 5. 启动TIM5并使能捕获中断TIM_Cmd(TIM5, ENABLE);TIM5->SR = 0;  // 清除标志位TIM_ITConfig(TIM5, TIM_IT_CC4, ENABLE);// 6. 等待捕获2个LSI上升沿(计算周期)while(uwCaptureNumber != 2);// 7. 停止TIM5并释放资源TIM_Cmd(TIM5, DISABLE);TIM_ITConfig(TIM5, TIM_IT_CC4, DISABLE);TIM_DeInit(TIM5);// 8. 计算LSI实际频率RCC_GetClocksFreq(&RCC_ClockFreq);if ((RCC->CFGR & RCC_CFGR_PPRE1) == 0){ // PCLK1预分频=1 → TIM5时钟=PCLK1return ((RCC_ClockFreq.PCLK1_Frequency / uwPeriodValue) * 8); // 每数一个数t =  1/RCC_ClockFreq.PCLK1_Frequency, T = uwPeriodValue*t,频率=1/T;}else{ // PCLK1预分频≠1 → TIM5时钟=2*PCLK1return (((2 * RCC_ClockFreq.PCLK1_Frequency) / uwPeriodValue) * 8);}
}/* TIM5中断服务函数(LSI捕获专用)----------------------------------------------*/
void TIM5_IRQHandler(void)
{static uint32_t uwCaptureValue1 = 0;static uint32_t uwCaptureValue2 = 0;if (TIM_GetITStatus(TIM5, TIM_IT_CC4) != RESET){TIM_ClearITPendingBit(TIM5, TIM_IT_CC4); // 清除中断标志if(uwCaptureNumber == 0){uwCaptureValue1 = TIM_GetCapture4(TIM5); // 捕获第一个上升沿uwCaptureNumber = 1;}else if(uwCaptureNumber == 1){uwCaptureValue2 = TIM_GetCapture4(TIM5); // 捕获第二个上升沿if (uwCaptureValue2 > uwCaptureValue1){uwPeriodValue = uwCaptureValue2 - uwCaptureValue1;}else{uwPeriodValue = (0xFFFF - uwCaptureValue1) + uwCaptureValue2;}uwCaptureNumber = 2; // 捕获完成}}
}/* USART1中断服务函数 ----------------------------------------------------------*/
void USART1_IRQHandler(void)
{uint8_t data = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET){data = USART_ReceiveData(USART1);       // 读取接收数据USART_SendData(USART1, data);           // 回显数据USART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志}
}/* IWDG配置函数(基于真实LSI频率)----------------------------------------------*/
static void IWDG_Config(uint32_t targetTimeoutMs)
{uint32_t prescalerDiv = 0;    // 预分频系数(实际除数)uint16_t reloadValue = 0;     // 重载值uint8_t iwdgPrescaler = 0;    // IWDG预分频寄存器值// 1. 枚举所有可能的预分频系数,找到最优配置const uint32_t prescalerTable[] = {4, 8, 16, 32, 64, 128, 256}; // IWDG预分频对应除数const uint8_t prescalerRegTable[] = {IWDG_Prescaler_4, IWDG_Prescaler_8, IWDG_Prescaler_16,IWDG_Prescaler_32, IWDG_Prescaler_64, IWDG_Prescaler_128,IWDG_Prescaler_256};// 遍历预分频表,计算满足超时时间的最小重载值(≤0xFFF)for (uint8_t i = 0; i < 7; i++){prescalerDiv = prescalerTable[i];// 计算公式:重载值 = (LSI频率 / 预分频除数) * 超时时间(秒)reloadValue = (uwLSIFrequency / prescalerDiv) * (targetTimeoutMs / 1000.0f);if (reloadValue <= 0xFFF) // IWDG重载值最大为0xFFF(4095){iwdgPrescaler = prescalerRegTable[i];break;}}// 2. 配置IWDGIWDG_WriteAccessCmd(IWDG_WriteAccess_Enable);   // 解除写保护IWDG_SetPrescaler(iwdgPrescaler);               // 设置预分频IWDG_SetReload(reloadValue);                    // 设置重载值IWDG_ReloadCounter();                           // 喂狗(刷新计数器)IWDG_Enable();                                  // 启动看门狗// 打印配置信息(调试用)printf("LSI真实频率: %d Hz\r\n", uwLSIFrequency);printf("IWDG预分频除数: %d\r\n", prescalerDiv);printf("IWDG重载值: %d\r\n", reloadValue);printf("IWDG实际超时时间: %.1f ms\r\n", (float)reloadValue * prescalerDiv * 1000 / uwLSIFrequency);
}/* 主函数 ---------------------------------------------------------------------*/
int main(void)
{// 1. NVIC优先级分组(必须最先配置)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); // 2. 初始化USART1(用于打印调试信息)PC_Config(115200);delay_ms(100); // 等待串口稳定// 3. 分析复位原因if (RCC_GetFlagStatus(RCC_FLAG_IWDGRST) != RESET){printf("=== MCU由IWDG看门狗复位 ===\r\n");RCC_ClearFlag(); // 清除复位标志}else{printf("=== MCU由用户复位 ===\r\n");}// 4. 获取LSI真实频率uwLSIFrequency = GetLSIFrequency();printf("=== LSI频率检测完成 ===\r\n");// 5. 基于真实LSI频率配置IWDG(目标超时250ms)IWDG_Config(IWDG_TARGET_TIMEOUT_MS);// 6. 主循环(喂狗测试)printf("=== IWDG配置完成,开始喂狗 ===\r\n");while (1){delay_ms(IWDG_TARGET_TIMEOUT_MS - 20); // 喂狗时间略小于超时时间(留20ms余量)IWDG_ReloadCounter();                  // 喂狗printf("喂狗成功\r\n");}
}

image

低功耗模式

低功耗模式概述

模式 功耗 唤醒源 恢复后状态
睡眠模式 较低 WFI:NVIC确认的任意外设中断;WFE:任意唤醒事件 保留所有状态,从暂停处继续运行
停止模式 更低 WFI:EXTI中断;WFE:EXTI事件;RTC复用事件(闹钟/唤醒/入侵/时间戳) 保留RAM/寄存器,系统时钟自动切HSI,需重新配置时钟树
待机模式 最低 WKUP引脚上升沿、RTC复用事件、NRST复位、IWDG复位 仅备份域(RTC/备份SRAM)保留,系统复位,程序从头执行

模式详解

睡眠模式

  • 特点:仅CPU内核时钟停止,外设/时钟源正常运行,唤醒无延迟。

  • 进入方式

    // 配置为立即休眠(SLEEPONEXIT=0),非深度睡眠(SLEEPDEEP=0)
    SCB->SCR &= ~(SCB_SCR_SLEEPDEEP_Msk | SCB_SCR_SLEEPONEXIT_Msk);
    __WFI();  // 等待中断唤醒 或 __WFE(); // 等待事件唤醒
    

停止模式

  • 特点:CPU/1.2V域所有时钟停止,HSI/HSE关闭,RAM/寄存器全保留,调压器可选低功耗模式。

  • 进入步骤

    // 1. 使能PWR时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    // 2. 配置深度睡眠,选择停止模式,调压器低功耗(可选)
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;    // SLEEPDEEP=1
    PWR->CR &= ~PWR_CR_PDDS_Msk;          // PDDS=0(停止模式)
    PWR->CR |= PWR_CR_LPDS_Msk;           // LPDS=1(低功耗调压器)
    // 3. 清零EXTI挂起位、RTC标志(必要前置操作)
    EXTI->PR = 0xFFFFFFFF;                // 清零所有EXTI挂起位
    // 4. 进入停止模式
    __WFI();  // 或 __WFE();
    

待机模式

  • 特点:1.2V域断电,调压器关闭,仅备份域保留数据,功耗达到最低。

  • 进入步骤

    // 1. 使能PWR时钟
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    // 2. 配置深度睡眠,选择待机模式
    SCB->SCR |= SCB_SCR_SLEEPDEEP_Msk;    // SLEEPDEEP=1
    PWR->CR |= PWR_CR_PDDS_Msk;           // PDDS=1(待机模式)
    // 3. 清零唤醒标志(必要前置操作)
    PWR->CR |= PWR_CR_CWUF_Msk;           // 清零WUF位
    // 4. 进入待机模式
    __WFI();  // 或 __WFE();
    

关键配置

  • 唤醒源配置
    • RTC复用事件:通过RCC_BDCR->RTCSEL选择LSE/LSI为RTC时钟源;停止模式需配置对应EXTI线(闹钟=17、唤醒=22、入侵/时间戳=21)检测上升沿,待机模式无需EXTI配置。
    • WKUP引脚:使能PA0上升沿检测(待机模式专属唤醒源)。
  • 注意事项
    • 睡眠模式WFE唤醒后,若为“中断未使能NVIC”方式,需手动清除外设/NVIC中断挂起位。
    • 停止模式进入前需关闭ADC/DAC,唤醒后系统时钟自动切换为HSI。
    • 待机模式进入前需按顺序清零RTC标志→PWR_WUF位,否则无法正常进入。

总结对比

  • 睡眠模式侧重“快速唤醒”,仅停CPU,外设全运行,适合短时间等待;
  • 停止模式侧重“低功耗+保留状态”,停所有时钟,需重新配置时钟,适合中等时长闲置;
  • 待机模式侧重“极致功耗”,仅备份域保留,程序复位执行,适合长时间闲置。

RTC外设的原理与应用

基本概念

RTC(Real Time Clock)是用于提供精确时间信息的硬件模块,可记录年、月、日、时、分、秒,支持低功耗运行(依靠备份电池供电)。广泛应用于智能手表、家电、医疗设备等。

主要特点

图片5

框图

图片6

RTC初始化流程

RTC 唤醒定时器是专为低功耗场景设计的定时唤醒模块,核心作用是在 MCU 进入 Stop/Standby 等低功耗模式时,按预设时长触发唤醒信号,让 MCU 退出低功耗并恢复运行。

  • 它依托 RTC 的独立时钟域(LSE/LSI 驱动)工作,低功耗模式下无需主时钟,功耗极低。
  • 可配置固定周期的唤醒中断,用于执行周期性低功耗任务,比如定时采集传感器数据、上报设备状态。
  • 功能与 RTC 日历模块完全独立,不参与日期和时间的计数与更新。

sequenceDiagramparticipant MCUparticipant PWRparticipant RCCparticipant RTCparticipant NVICparticipant EXTI%% 1. 使能PWR外设时钟MCU->>RCC: 使能PWR外设时钟(RCC_APB1Periph_PWR)%% 2. 置位DBP位,允许访问备份域MCU->>PWR: 设置DBP位,使能备份域访问%% 3. 启动LSE低速外部时钟MCU->>RCC: 启动LSE时钟(RCC_LSE_ON)%% 4. 等待LSE时钟就绪MCU->>RCC: 等待LSE就绪标志(RCC_FLAG_LSERDY)%% 5. 选择LSE作为RTC时钟源MCU->>RCC: 选择LSE作为RTC时钟源%% 6. 使能RTC时钟MCU->>RCC: 使能RTC时钟(RCC_RTCCLKCmd)%% 7. 等待RTC寄存器同步MCU->>RTC: 等待RTC寄存器同步(RTC_WaitForSynchro)%% 8. 配置RTC预分频器和小时格式MCU->>RTC: 配置异步预分频(127)、同步预分频(255)、24小时制%% 9. 设置RTC初始日期MCU->>RTC: 设置日期(2026年1月19日 周一,BCD格式)%% 10. 设置RTC初始时间MCU->>RTC: 设置时间(15:05:50 AM,BCD格式)%% 11. 配置RTC唤醒中断NVIC优先级(下述中断配置可选,非必选根据实际情况配置)MCU->>NVIC: 配置RTC_WKUP_IRQn抢占优先级1、子优先级0(下述中断配置可选,非必选根据实际情况配置)%% 12. 配置EXTI_Line22中断模式MCU->>EXTI: 配置EXTI_Line22为上升沿触发中断%% 13. 清除EXTI_Line22中断挂起位MCU->>EXTI: 清除EXTI_Line22中断标志%% 14. 先禁用唤醒定时器(配置前准备)MCU->>RTC: 禁用RTC唤醒定时器%% 15. 配置唤醒定时器时钟源MCU->>RTC: 选择唤醒时钟源为CK_SPRE(1Hz)%% 16. 设置唤醒定时器重载值MCU->>RTC: 设置唤醒重载值为0(1Hz唤醒频率)%% 17. 使能RTC唤醒中断MCU->>RTC: 使能RTC唤醒中断(RTC_IT_WUT)%% 18. 清除RTC唤醒中断标志MCU->>RTC: 清除RTC唤醒中断挂起位%% 19. 启用RTC唤醒定时器MCU->>RTC: 启用RTC唤醒定时器

注意:通过寄存器开发是,配置RTC日期时间前要解除写保护(RTC->WPR = 0xCA;RTC->WPR = 0x53;)、配置完RTC日期时间后要重新激活写保护(RTC->WPR = 0xFF;)——库函数封装在函数内部
BCD与十进制转换

// 十进制转BCD
uint8_t dec_to_bcd(uint8_t dec) {return ((dec / 10) << 4) | (dec % 10);
}// BCD转十进制
uint8_t bcd_to_dec(uint8_t bcd) {return (bcd >> 4) * 10 + (bcd & 0x0F);
}

时间设置函数

可通过Wi-Fi模块(如ESP8266)连接云服务(如巴法云)获取北京时间,并写入RTC寄存器。建议使用DMA+空闲中断接收不定长数据,确保数据完整性。

D1

D2

D3

D4

D5

image

#include "stm32f4xx.h"
#include <stdio.h>
#include <string.h>
#include <stdbool.h>// ========================== 宏定义 ==========================
/** 串口接收缓冲区大小,适配Wi-Fi透传数据长度 */
#define BUFFERSIZE 1024/** Wi-Fi配置参数 - 需根据实际环境修改 */
#define WIFI_SSID      "wby"                  // 路由器SSID
#define WIFI_PASSWORD  "12345678"             // 路由器密码
#define BEMFA_UID      "xxxxxxxxxxxxxxxxxxxxxxx"  // 巴法云平台UID// ========================== 全局变量 ==========================
uint8_t wifi_recvbuf[BUFFERSIZE];            // Wi-Fi接收缓冲区
uint32_t wifi_counter = 0;                   // 接收数据长度计数
volatile uint8_t wifi_data_ready = 0;        // 数据接收完成标志(volatile防止编译器优化)
volatile uint8_t time_received = 0;          // 时间数据解析完成标志
char current_time[20] = {0};                 // 存储解析后的时间字符串
uint8_t wifi_init_flag = 0;                  // Wi-Fi初始化完成标志// ========================== 基础工具函数 ==========================
/*** @brief  微秒级精准延时函数* @param  nus: 延时微秒数(最大值:79891us,因SysTick最大重载值0xFFFFFF/21≈79891)* @retval None* @note   基于SysTick定时器实现,时钟源为外部21MHz(HCLK/8)*/
void delay_us(uint32_t nus)
{uint32_t reload = 0;SysTick->CTRL = 0;                       // 关闭SysTick定时器reload = nus * 21;                       // 计算重载值:1μs对应21个时钟周期reload = reload > 0xFFFFFF ? 0xFFFFFF : reload; // 限制最大值SysTick->LOAD = reload;                  // 设置重载寄存器SysTick->VAL = 0;                        // 清空当前计数值SysTick->CTRL = 1;                       // 使能SysTick,外部时钟源,无中断while(!(SysTick->CTRL & (1 << 16)));     // 等待计数完成(COUNTFLAG置位)SysTick->CTRL = 0;                       // 关闭SysTick
}/*** @brief  毫秒级延时函数* @param  nms: 延时毫秒数(无上限,通过循环调用us级延时实现),有误差* @retval None*/
void delay_ms(uint32_t nms)
{while(nms--){delay_us(1000); // 1ms = 1000μs}
}/*** @brief  蔡勒公式(Zeller's Congruence)计算指定日期的星期* @param  year: 年份(如2026)* @param  month: 月份(1-12)* @param  day: 日期(1-31)* @retval 星期值(1=周一, 2=周二, ..., 7=周日),适配RTC的WeekDay定义* @note   适配格里高利历(公历),支持1582年10月15日之后的日期计算*/
uint8_t CalculateWeekDay(int year, int month, int day)
{int h, q, m, K, J;// 蔡勒公式特殊处理:1月/2月视为上一年的13月/14月if (month < 3){month += 12;year--;}q = day;                                 // 日期m = month;                               // 月份(3-14)K = year % 100;                          // 年份后两位J = year / 100;                          // 年份前两位// 蔡勒核心公式:h = (q + [(13(m+1))/5] + K + [K/4] + [J/4] + 5J) mod 7h = (q + (13 * (m + 1)) / 5 + K + K / 4 + J / 4 + 5 * J) % 7;// 转换为RTC标准格式:h=0(周六)→6, h=1(周日)→7, h=2(周一)→1...h=6(周五)→5switch(h){case 0: return 6;  // 周六case 1: return 7;  // 周日case 2: return 1;  // 周一case 3: return 2;  // 周二case 4: return 3;  // 周三case 5: return 4;  // 周四case 6: return 5;  // 周五default: return 1; // 异常默认周一}
}// ========================== 串口相关函数(USART1/USART3) ==========================
/*** @brief  USART1初始化配置(用于调试输出到PC)* @param  baud: 波特率(常用:9600/115200)* @retval None* @note   引脚:PA9(TX)、PA10(RX),开启接收中断*/
void PC_Config(uint32_t baud)
{USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;// 1. 使能外设时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);  // GPIOA时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); // USART1时钟(APB2总线)// 2. 配置GPIO复用功能GPIO_PinAFConfig(GPIOA, GPIO_PinSource9, GPIO_AF_USART1);  // PA9复用为USART1_TXGPIO_PinAFConfig(GPIOA, GPIO_PinSource10, GPIO_AF_USART1); // PA10复用为USART1_RX// 3. 配置GPIO参数GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;         // 复用模式GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;    // 高速模式GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;        // 推挽输出GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;         // 上拉电阻GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9 | GPIO_Pin_10;GPIO_Init(GPIOA, &GPIO_InitStructure);// 4. 配置USART参数USART_InitStructure.USART_BaudRate    = baud;                     // 波特率USART_InitStructure.USART_WordLength  = USART_WordLength_8b;      // 8位数据位USART_InitStructure.USART_StopBits    = USART_StopBits_1;         // 1位停止位USART_InitStructure.USART_Parity      = USART_Parity_No;          // 无校验USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;   // 收发模式USART_Init(USART1, &USART_InitStructure);// 5. 配置中断优先级NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 6. 使能接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 7. 清空发送寄存器并使能USARTwhile( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );USART_ClearITPendingBit(USART1, USART_IT_RXNE);USART_Cmd(USART1, ENABLE);
}/*** @brief  USART1中断服务函数(仅用于调试数据回显)* @param  None* @retval None*/
void USART1_IRQHandler(void)
{uint8_t data = 0;if (USART_GetITStatus(USART1, USART_IT_RXNE) == SET) // 接收数据中断{data = USART_ReceiveData(USART1);               // 读取接收数据while( USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET );USART_SendData(USART1, data);                   // 回显到PCUSART_ClearITPendingBit(USART1, USART_IT_RXNE); // 清除中断标志}
}/*** @brief  重启DMA接收(用于USART3的Wi-Fi数据接收)* @param  None* @retval None* @note   清空标志位并重新配置传输长度,保证连续接收*/
void Restart_DMA_Receive(void)
{DMA_Cmd(DMA1_Stream1, DISABLE); // 禁用DMA// 清除所有DMA中断标志DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TCIF1 | DMA_IT_HTIF1 | DMA_IT_TEIF1 | DMA_IT_DMEIF1 | DMA_IT_FEIF1);DMA_SetCurrDataCounter(DMA1_Stream1, BUFFERSIZE); // 重置传输长度DMA_Cmd(DMA1_Stream1, ENABLE);                    // 重新使能DMA
}/*** @brief  USART3的DMA接收初始化(优化版,降低CPU占用)* @param  None* @retval None* @note   通道配置:DMA1 Stream1 Channel4,外设→内存,字节传输*/
void UART3_DMA_Config(void)
{DMA_InitTypeDef DMA_InitStructure;RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_DMA1, ENABLE); // 使能DMA1时钟DMA_DeInit(DMA1_Stream1);                            // 复位DMA配置// 配置DMA核心参数DMA_InitStructure.DMA_Channel = DMA_Channel_4;                            // USART3_RX对应通道4DMA_InitStructure.DMA_PeripheralBaseAddr = (uint32_t)(&(USART3->DR));     // 外设地址(USART3数据寄存器)DMA_InitStructure.DMA_Memory0BaseAddr = (uint32_t)wifi_recvbuf;           // 内存缓冲区地址DMA_InitStructure.DMA_DIR = DMA_DIR_PeripheralToMemory;                   // 外设→内存DMA_InitStructure.DMA_BufferSize = BUFFERSIZE;                            // 缓冲区大小DMA_InitStructure.DMA_PeripheralInc = DMA_PeripheralInc_Disable;          // 外设地址不递增DMA_InitStructure.DMA_MemoryInc = DMA_MemoryInc_Enable;                   // 内存地址递增DMA_InitStructure.DMA_PeripheralDataSize = DMA_PeripheralDataSize_Byte;   // 字节传输DMA_InitStructure.DMA_MemoryDataSize = DMA_MemoryDataSize_Byte;DMA_InitStructure.DMA_Mode = DMA_Mode_Normal;                             // 普通模式(非循环)DMA_InitStructure.DMA_Priority = DMA_Priority_High;                       // 高优先级DMA_InitStructure.DMA_FIFOMode = DMA_FIFOMode_Disable;                    // 禁用FIFODMA_InitStructure.DMA_FIFOThreshold = DMA_FIFOThreshold_1QuarterFull;DMA_InitStructure.DMA_MemoryBurst = DMA_MemoryBurst_Single;DMA_InitStructure.DMA_PeripheralBurst = DMA_PeripheralBurst_Single;DMA_Init(DMA1_Stream1, &DMA_InitStructure);DMA_ClearITPendingBit(DMA1_Stream1, DMA_IT_TCIF1 | DMA_IT_HTIF1 | DMA_IT_TEIF1 | DMA_IT_DMEIF1 | DMA_IT_FEIF1);DMA_Cmd(DMA1_Stream1, ENABLE);USART_DMACmd(USART3, USART_DMAReq_Rx, ENABLE); // 关联USART3和DMA接收
}/*** @brief  USART3初始化配置(用于Wi-Fi模块通信)* @param  baud: 波特率(Wi-Fi模块常用115200)* @retval None* @note   引脚:PB10(TX)、PB11(RX),开启空闲中断+DMA接收*/
void UART3_Config(uint32_t baud)
{USART_InitTypeDef USART_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;GPIO_InitTypeDef GPIO_InitStructure;// 1. 使能外设时钟RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);  // GPIOB时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); // USART3时钟(APB1总线)// 2. 配置GPIO复用功能GPIO_PinAFConfig(GPIOB, GPIO_PinSource10, GPIO_AF_USART3); // PB10→USART3_TXGPIO_PinAFConfig(GPIOB, GPIO_PinSource11, GPIO_AF_USART3); // PB11→USART3_RX// 3. 配置GPIO参数GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;GPIO_InitStructure.GPIO_PuPd  = GPIO_PuPd_UP;GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_10 | GPIO_Pin_11;GPIO_Init(GPIOB, &GPIO_InitStructure);// 4. 配置USART参数USART_InitStructure.USART_BaudRate    = baud;USART_InitStructure.USART_WordLength  = USART_WordLength_8b;USART_InitStructure.USART_StopBits    = USART_StopBits_1;USART_InitStructure.USART_Parity      = USART_Parity_No;USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;USART_Init(USART3, &USART_InitStructure);// 5. 配置中断优先级NVIC_InitStructure.NVIC_IRQChannel = USART3_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 子优先级低于USART1NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 6. 使能空闲中断(用于检测数据帧结束)USART_ITConfig(USART3, USART_IT_IDLE, ENABLE);USART_ClearITPendingBit(USART3, USART_IT_IDLE);// 7. 配置DMA接收UART3_DMA_Config();// 8. 使能USART3USART_Cmd(USART3, ENABLE);while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); // 等待发送完成
}/*** @brief  USART3发送字符串(带发送完成等待)* @param  str: 待发送的字符串(以'\0'结尾)* @retval None*/
void UART3_SendStr(char * str)
{while(*str != '\0'){while( USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET ); // 等待发送寄存器空USART_SendData(USART3, *str++);}while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET); // 等待帧发送完成
}/*** @brief  USART3发送指定长度的数据(适用于无结束符的二进制/指令数据)* @param  str: 数据缓冲区* @param  len: 发送长度* @retval None*/
void UART3_SendData(char * str, int len)
{while(len--){while( USART_GetFlagStatus(USART3, USART_FLAG_TXE) == RESET );USART_SendData(USART3, *str++);}while (USART_GetFlagStatus(USART3, USART_FLAG_TC) == RESET);
}/*** @brief  USART3中断服务函数(空闲中断处理DMA接收完成)* @param  None* @retval None* @note   空闲中断触发表示一帧数据接收完成,计算长度并标记就绪*/
void USART3_IRQHandler(void)
{uint32_t len = 0;if (USART_GetITStatus(USART3, USART_IT_IDLE) != RESET) // 检测空闲中断{USART_ReceiveData(USART3); // 读取DR寄存器清除空闲中断标志USART_ClearITPendingBit(USART3, USART_IT_IDLE);DMA_Cmd(DMA1_Stream1, DISABLE); // 暂停DMA接收len = BUFFERSIZE - DMA_GetCurrDataCounter(DMA1_Stream1); // 计算接收长度if (len > 0 && len < BUFFERSIZE) // 有效数据判断{wifi_recvbuf[len] = '\0';    // 添加字符串结束符wifi_counter = len;wifi_data_ready = 1;         // 标记数据就绪}Restart_DMA_Receive(); // 重启DMA准备下一次接收}
}// ========================== 定时器相关函数(TIM6) ==========================
/*** @brief  TIM6定时器初始化(用于30秒心跳包发送)* @param  None* @retval None* @note   时基配置:84MHz→42000分频→2000Hz→60000计数→30秒中断一次*/
void TIM6_Config(void)
{NVIC_InitTypeDef NVIC_InitStructure;TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE); // 使能TIM6时钟// 配置中断优先级NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQn;NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; // 抢占优先级1NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);// 配置定时器时基TIM_TimeBaseStructure.TIM_Prescaler = 42000-1;        // 预分频值TIM_TimeBaseStructure.TIM_Period    = 60000-1;        // 自动重载值TIM_TimeBaseStructure.TIM_ClockDivision = 0;          // 不分频TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE); // 使能更新中断TIM_Cmd(TIM6, ENABLE);                     // 启动定时器
}/*** @brief  TIM6中断服务函数(发送Wi-Fi心跳包)* @param  None* @retval None*/
void TIM6_DAC_IRQHandler(void)
{if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET){if(wifi_init_flag) // Wi-Fi初始化完成后发送心跳包{UART3_SendStr("ping\r\n");}TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 清除中断标志}
}// ========================== WIFI相关函数 ==========================
/*** @brief  Wi-Fi模块发送AT指令并等待响应* @param  str: AT指令字符串(需包含\r\n结束符)* @param  time: 超时时间(ms)* @retval true: 指令执行成功(收到OK/>),false: 超时/失败* @note   自动清空接收缓冲区,非阻塞式等待响应*/
bool WIFI_SendAT(char *str, uint16_t time)
{// 初始化接收状态memset((char *)wifi_recvbuf, 0, BUFFERSIZE);wifi_counter = 0;wifi_data_ready = 0;UART3_SendStr(str); // 发送AT指令// 超时等待响应while(time > 0){delay_ms(1);time--;if(wifi_data_ready) // 收到响应数据{wifi_data_ready = 0;// 检查响应是否包含OK或>(透传模式提示符)if( strstr((char *)wifi_recvbuf, "OK") || strstr((char *)wifi_recvbuf, ">") ){return true;}}}return false; // 超时失败
}/*** @brief  Wi-Fi模块初始化(连接路由器+巴法云服务器)* @param  None* @retval None* @note   执行流程:硬件初始化→AT测试→设置STA模式→连接路由→透传配置→连接服务器*/
void WIFI_Config(void)
{char connect_cmd[100] = {0};// 1. 初始化USART3(Wi-Fi通信口)UART3_Config(115200);delay_ms(1000); // 等待模块上电稳定UART3_SendStr("+++"); //退出透传delay_ms(1000);// 2. 测试模块在线状态if ( WIFI_SendAT("AT\r\n", 5000) ){printf("Wi-Fi模块在线\r\n");}else{printf("Wi-Fi模块离线,初始化失败\r\n");return;}// 3. 设置Wi-Fi为STA模式if ( WIFI_SendAT("AT+CWMODE_DEF=1\r\n", 5000) ){printf("Wi-Fi STA模式设置成功\r\n");}else{printf("Wi-Fi模式设置失败\r\n");return;}// 4. 连接路由器sprintf(connect_cmd, "AT+CWJAP_DEF=\"%s\",\"%s\"\r\n", WIFI_SSID, WIFI_PASSWORD);if ( WIFI_SendAT(connect_cmd, 10000) ) // 连接超时10秒{printf("Wi-Fi连接路由器成功\r\n");}else{printf("Wi-Fi连接路由器失败\r\n");return;}// 5. 启用透传模式if ( WIFI_SendAT("AT+CIPMODE=1\r\n", 10000) ){printf("Wi-Fi透传模式设置成功\r\n");}else{printf("Wi-Fi透传模式设置失败\r\n");return;}// 6. 连接巴法云服务器(TCP)if ( WIFI_SendAT("AT+CIPSTART=\"TCP\",\"bemfa.com\",8344\r\n", 10000) ){printf("连接巴法云服务器成功\r\n");}else{printf("连接巴法云服务器失败\r\n");return;}// 7. 进入透传发送模式if ( WIFI_SendAT("AT+CIPSEND\r\n", 10000) ){printf("进入Wi-Fi透传模式成功\r\n");wifi_init_flag = 1; // 标记Wi-Fi初始化完成}else{printf("进入透传模式失败\r\n");}
}// ========================== RTC相关函数 ==========================
/*** @brief  RTC实时时钟初始化配置* @param  None* @retval None* @note   时钟源:LSE(32.768kHz外部晶振),24小时制,预分频32768→1Hz*/
void RTC_Config(void)
{RTC_DateTypeDef  RTC_DateStructure;RTC_TimeTypeDef  RTC_TimeStructure;RTC_InitTypeDef  RTC_InitStructure;// 1. 使能PWR时钟并解锁备份域RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR, ENABLE);PWR_BackupAccessCmd(ENABLE); // 允许访问RTC备份寄存器// 2. 配置LSE时钟作为RTC源RCC_LSEConfig(RCC_LSE_ON);                // 启用外部低速晶振while(RCC_GetFlagStatus(RCC_FLAG_LSERDY) == RESET); // 等待LSE稳定RCC_RTCCLKConfig(RCC_RTCCLKSource_LSE);   // 选择LSE为RTC时钟源RCC_RTCCLKCmd(ENABLE);                    // 使能RTC时钟// 3. 等待RTC寄存器同步RTC_WaitForSynchro();// 4. 配置RTC预分频和格式RTC_WriteProtectionCmd(DISABLE); // 关闭RTC写保护RTC_InitStructure.RTC_AsynchPrediv    = 128-1; // 异步预分频器RTC_InitStructure.RTC_SynchPrediv     = 256-1; // 同步预分频器RTC_InitStructure.RTC_HourFormat      = RTC_HourFormat_24; // 24小时制RTC_Init(&RTC_InitStructure);// 5. 设置初始日期和时间(后续会被网络时间覆盖)RTC_DateStructure.RTC_Year    = 0x26;   // 2026年RTC_DateStructure.RTC_Month   = 0x01;   // 1月RTC_DateStructure.RTC_Date    = 0x01;   // 1日RTC_DateStructure.RTC_WeekDay = 0x01;   // 周一RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure);RTC_TimeStructure.RTC_H12     = RTC_H12_AM;RTC_TimeStructure.RTC_Hours   = 0x12;   // 12时RTC_TimeStructure.RTC_Minutes = 0x00;   // 0分RTC_TimeStructure.RTC_Seconds = 0x00;   // 0秒RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);RTC_WriteProtectionCmd(ENABLE); // 开启RTC写保护printf("RTC时钟初始化完成\r\n");
}/*** @brief  解析时间字符串并写入RTC(含星期计算)* @param  time_str: 时间字符串(格式:"2026-01-01 12:00:00")* @retval None* @note   调用蔡勒公式计算星期,自动转换为RTC的BIN格式*/
void ParseAndSetRTC(char *time_str)
{int year, month, day, hour, minute, second;uint8_t week_day = 1;// 解析时间字符串(sscanf返回匹配的字段数)if (sscanf(time_str, "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second) == 6){RTC_DateTypeDef RTC_DateStructure;RTC_TimeTypeDef RTC_TimeStructure;// 1. 计算星期几(调用蔡勒公式)week_day = CalculateWeekDay(year, month, day);printf("计算星期:%d年%d月%d日 → 星期%d\r\n", year, month, day, week_day);// 2. 写入RTC日期(关闭写保护)RTC_WriteProtectionCmd(DISABLE);RTC_DateStructure.RTC_Year = year - 2000;  // RTC年份存储2000年后的偏移值RTC_DateStructure.RTC_Month = month;RTC_DateStructure.RTC_Date = day;RTC_DateStructure.RTC_WeekDay = week_day;  // 设置计算得到的星期RTC_SetDate(RTC_Format_BIN, &RTC_DateStructure);// 3. 写入RTC时间RTC_TimeStructure.RTC_H12 = RTC_H12_AM;    // 24小时制无需区分AM/PMRTC_TimeStructure.RTC_Hours = hour;RTC_TimeStructure.RTC_Minutes = minute;RTC_TimeStructure.RTC_Seconds = second;RTC_SetTime(RTC_Format_BIN, &RTC_TimeStructure);RTC_WriteProtectionCmd(ENABLE); // 开启写保护// 打印更新信息printf("RTC时间更新成功:%d-%02d-%02d 星期%d %02d:%02d:%02d\r\n", year, month, day, week_day, hour, minute, second);}else{printf("时间字符串解析失败:%s\r\n", time_str);}
}// ========================== 巴法云时间交互函数 ==========================
/*** @brief  从Wi-Fi接收缓冲区提取时间数据* @param  None* @retval None* @note   快速定位以"20"开头的时间字符串,校验格式后解析*/
void ExtractTimeFromBuffer(void)
{char *time_start = NULL;char time_str[20] = {0};time_start = strstr((char *)wifi_recvbuf, "20"); // 定位时间字符串起始位置if (time_start != NULL && strlen(time_start) >= 19){// 校验时间格式(xxxx-xx-xx xx:xx:xx)if (time_start[4] == '-' && time_start[7] == '-' && time_start[10] == ' ' && time_start[13] == ':' && time_start[16] == ':'){strncpy(time_str, time_start, 19); // 提取19位时间字符串time_str[19] = '\0';strcpy(current_time, time_str);time_received = 1;ParseAndSetRTC(time_str); // 解析并写入RTC}}
}/*** @brief  发送巴法云时间获取指令* @param  None* @retval None* @note   指令格式:cmd=7&uid=xxx&type=1\r\n(type=1返回完整时间)*/
void SendTimeRequest(void)
{char time_cmd[100] = {0};sprintf(time_cmd, "cmd=7&uid=%s&type=1\r\n", BEMFA_UID);UART3_SendData(time_cmd, strlen(time_cmd)); // 发送指定长度(避免\0传输)
}/*** @brief  从巴法云获取网络时间* @param  None* @retval true: 获取成功,false: 超时失败* @note   超时时间3秒,期间循环检测数据就绪标志*/
bool Bemfa_Gettime(void)
{uint32_t timeout = 3000; // 3秒超时// 初始化接收状态wifi_data_ready = 0;time_received = 0;memset(wifi_recvbuf, 0, BUFFERSIZE);SendTimeRequest(); // 发送时间获取指令// 等待响应while(timeout--){if(wifi_data_ready){ExtractTimeFromBuffer(); // 提取并解析时间if(time_received){printf("网络时间获取成功:%s\r\n", current_time);return true;}wifi_data_ready = 0;}delay_ms(1);}printf("网络时间获取超时\r\n");return false;
}// ========================== 主函数 ==========================
/*** @brief  程序入口函数* @param  None* @retval None* @note   执行流程:串口→RTC→Wi-Fi→定时器→主循环(时间同步+打印)*/
int main(void)
{//进行NVIC优先级分组 分组4: 抢占优先级4bit(0~15)NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);// 初始化调试串口PC_Config(115200);printf("=============== 系统启动 ===============\r\n");// 初始化RTC时钟RTC_Config();// 初始化Wi-Fi模块printf("=============== 初始化Wi-Fi ===============\r\n");WIFI_Config();// 初始化心跳包定时器TIM6_Config();printf("=============== 系统初始化完成 ===============\r\n");// 首次获取网络时间printf("=============== 获取网络时间 ===============\r\n");if(Bemfa_Gettime()){printf("时间同步成功\r\n");}else{printf("时间同步失败,使用RTC初始时间\r\n");}// 主循环while(1){RTC_TimeTypeDef current_time;RTC_DateTypeDef current_date;char *week_str[] = {"", "一", "二", "三", "四", "五", "六", "日"}; // 星期字符串映射// 读取并打印当前RTC时间(含星期)RTC_GetTime(RTC_Format_BIN, &current_time);RTC_GetDate(RTC_Format_BIN, &current_date);printf("当前时间:20%02d-%02d-%02d 星期%s %02d:%02d:%02d\r\n",current_date.RTC_Year,current_date.RTC_Month,current_date.RTC_Date,week_str[current_date.RTC_WeekDay],current_time.RTC_Hours,current_time.RTC_Minutes,current_time.RTC_Seconds);delay_ms(1000); // 1秒打印一次}
}/*** @brief  重定向printf到USART1* @param  ch: 输出字符* @param  f: 文件指针(未使用)* @retval 输出的字符*/
int fputc(int ch, FILE *f)
{while(USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);USART_SendData(USART1, (uint8_t)ch);return ch;
}

image

image

RTC备份寄存器在VBAT供电下保持内容,可用于标记系统是否完全断电。若标记未丢失,说明RTC未复位,无需重新初始化时间。

RTC_WriteBackupRegister(RTC_BKP_DR0, 0x32F2);   //事先写入

image

RTC闹钟设置

image

sequenceDiagramparticipant MCUparticipant PWRparticipant RCCparticipant RTCparticipant NVICparticipant EXTI%% 1. 使能PWR外设时钟MCU->>RCC: 使能PWR外设时钟(RCC_APB1Periph_PWR)%% 2. 置位DBP位,允许访问备份域MCU->>PWR: 设置DBP位,使能备份域访问(PWR_BackupAccessCmd)%% 3. 启动LSE低速外部时钟MCU->>RCC: 启动LSE时钟(RCC_LSE_ON)%% 4. 等待LSE时钟就绪MCU->>RCC: 等待LSE就绪标志(RCC_FLAG_LSERDY)%% 5. 选择LSE作为RTC时钟源MCU->>RCC: 选择LSE作为RTC时钟源(RCC_RTCCLKSource_LSE)%% 6. 使能RTC时钟MCU->>RCC: 使能RTC时钟(RCC_RTCCLKCmd)%% 7. 等待RTC寄存器同步MCU->>RTC: 等待RTC寄存器同步(RTC_WaitForSynchro)%% 8. 配置RTC预分频器和小时格式MCU->>RTC: 配置异步预分频(127)、同步预分频(255)、24小时制%% 9. 设置RTC初始日期MCU->>RTC: 设置日期%% 10. 设置RTC初始时间MCU->>RTC: 设置时间%% 11. 配置闹钟A的时间参数MCU->>RTC: 配置闹钟A时间%% 12. 配置闹钟A的日期/星期参数MCU->>RTC: 配置闹钟A日期为31、选择日期匹配、屏蔽日期/星期掩码%% 13. 设置并生效闹钟A配置MCU->>RTC: 设置闹钟A(RTC_Alarm_A)参数%% 14. 使能RTC闹钟A中断MCU->>RTC: 使能RTC闹钟A中断(RTC_IT_ALRA)%% 15. 启用RTC闹钟A功能MCU->>RTC: 启用RTC闹钟A(RTC_AlarmCmd)%% 16. 清除RTC闹钟A标志位MCU->>RTC: 清除RTC闹钟A标志(RTC_FLAG_ALRAF)%% 17. 配置RTC闹钟中断NVIC优先级MCU->>NVIC: 配置RTC_Alarm_IRQn抢占优先级0、子优先级0并使能%% 18. 清除EXTI_Line17中断挂起位(前置)MCU->>EXTI: 清除EXTI_Line17中断挂起位%% 19. 配置EXTI_Line17中断模式MCU->>EXTI: 配置EXTI_Line17为上升沿触发、中断模式并使能%% 20. 再次清除EXTI_Line17中断挂起位MCU->>EXTI: 清除EXTI_Line17中断挂起位
RTC_AlarmTypeDef RTC_AlarmStructure;// 设置闹钟时间
RTC_AlarmStructure.RTC_AlarmTime.RTC_Hours   = 0x08;    // 08时
RTC_AlarmStructure.RTC_AlarmTime.RTC_Minutes = 0x30;    // 30分
RTC_AlarmStructure.RTC_AlarmTime.RTC_Seconds = 0x00;
**RTC_AlarmStructure.RTC_AlarmDateWeekDay = 0x01; // 没影响因为每天触发**
RTC_AlarmStructure.RTC_AlarmDateWeekDaySel = RTC_AlarmDateWeekDaySel_Date;
RTC_AlarmStructure.RTC_AlarmMask = RTC_AlarmMask_DateWeekDay;  // 忽略日期,每日触发RTC_SetAlarm(RTC_Format_BCD, RTC_Alarm_A, &RTC_AlarmStructure);
RTC_ITConfig(RTC_IT_ALRA, ENABLE);   // 使能闹钟中断
RTC_AlarmCmd(RTC_Alarm_A, ENABLE);
RTC_ClearFlag(RTC_FLAG_ALRAF);NVIC_InitStructure.NVIC_IRQChannel = RTC_Alarm_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);EXTI_ClearITPendingBit(EXTI_Line17);
EXTI_InitStructure.EXTI_Line = EXTI_Line17;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);/*** @brief  闹钟中断服务函数*/
void RTC_Alarm_IRQHandler(void)
{if (RTC_GetITStatus(RTC_IT_ALRA) != RESET){// 闹钟触发后操作(可自行补充)RTC_ClearITPendingBit(RTC_IT_ALRA);EXTI_ClearITPendingBit(EXTI_Line17);}
}

总结

  • 看门狗是系统“守护者”,用于复位异常程序,分为独立看门狗和窗口看门狗,喂狗策略需合理设计。
  • RTC是“实时时钟”,提供精确时间与日历功能,支持闹钟、唤醒、备份寄存器等,常用于定时任务与时间记录。
  • 结合DMA与空闲中断可实现高效、可靠的数据接收,适用于网络对时等场景。

此文章基于STM32F4系列,代码与原理适用于大多数STM32型号,具体寄存器操作请参考对应型号的参考手册。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/1215931.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

M3U8链接健康检查:结构解析+分片验证+监控告警配置

全面解析 M3U8 链接失效的检测与调试方法,涵盖 HTTP 状态码验证、文件结构校验、播放测试、加密流解密及 TS 分片验证。结合 FFmpeg、curl、streamlink 等命令行工具与 Python 自动化脚本,实现批量检测与分钟级监控。…

Struts2_S2-048漏洞复现:原理详解+环境搭建+渗透实践(CVE-2017-9791) - 指南

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

AI时代本质的思考

目标&#xff08;Goal&#xff09;&#xff1a;我到底想解决什么&#xff1f; 约束&#xff08;Constraint&#xff09;&#xff1a;不能碰什么&#xff1f; 成功标准&#xff08;Success&#xff09;&#xff1a;做到什么算赢 这个问题非常前沿&#xff0c;而且你这个“人路由…

2025年YOLO算法案例应用领域应用趋势

2025年&#xff0c;基于YOLO系列算法&#xff08;涵盖YOLOv5至YOLO26&#xff09;的案例呈现多领域渗透、技术迭代与场景定制化的特点&#xff0c;覆盖智能交通、工业质检、农业、公共安全、军事、海洋监测、智能家居等多个垂直领域。以下按月份时间线梳理典型案例&#xff0c;…

【SEO黑科技】关键词都埋好了,流量还是差?揭秘图片里的“隐形权重”,用 AI 翻译多拿 30% 搜索流量!

Python 亚马逊SEO 图片索引 A9算法 跨境电商流量 GoogleShopping 图片翻译摘要在跨境电商的精细化运营中&#xff0c;大家都在卷 Listing 的标题和各种埋词。但你可能忽略了一个巨大的流量入口&#xff1a;图片内部的文字。随着亚马逊 A9 算法和 Google Lens 的进化&#xff0c…

论文笔记(一百零六)RynnVLA-002: A Unified Vision-Language-Action and World Model - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

【服装卖家专享】尺码表全是中文怎么破?揭秘 AI 如何智能重构“表格图片”,降低 50% 的尺码退货率!

Python 服装电商 尺码表翻译 表格识别 降低退货 自动化工具 Shein模式摘要在跨境电商的服装、鞋帽类目中&#xff0c;“因尺码不符退货” 造成的损失往往占到总利润的 20% 以上。很多时候&#xff0c;并非产品尺码不准&#xff0c;而是卖家直接使用了厂家提供的 中文尺码表图片…

得物月付额度可以回收提现使用吗

得物月付额度仅限在得物APP内消费,不支持提现、转账或线下使用,且仅对符合条件的用户开放,系统会根据综合评估给出专属额度。以下是具体使用范围与限制: 核心使用范围全品类商品覆盖:可用于购买得物平台自营及部分…

【读书笔记】《跑外卖》

《跑外卖&#xff1a;一个女骑手的世界》读书笔记 一、作者背景与写作缘起 1.1 作者简介 姓名&#xff1a;王婉&#xff08;婉婉&#xff09;出生地&#xff1a;山东某县城童年记忆&#xff1a;北京庙的传说——据说站在庙上能望见北京城&#xff0c;但她多次尝试从未看到过…

冥想第一千七百七十四天(1774)

1.今天周日&#xff0c;然后今天早上先去跑了8公里今天明显感觉到自己的跑步水平退步得很很大自己的最大然后降低到了56&#xff0c;觉着还是有点累的&#xff0c;然后今天没有做太多事情&#xff0c;今天就和朋友一块儿玩了&#xff0c;玩游戏只周末玩&#xff0c;晚上又带着溪…

冥想第一千七百七十二天(1772)

1.今天周五了&#xff0c;项目上也非常忙&#xff0c;然后下了班本来是想着昨天跑步了&#xff0c;然后但是今天昨天没有时间&#xff0c;然后今天就跑了&#xff0c;感觉最近退步了退步的还是很多的不过这也感觉很正常&#xff0c;人总会有高潮和低谷。 2.感谢感谢父母&#x…

冥想第一千七百七十三天(1773)

今天是周六&#xff0c;然后今天是休息日和朋友约着&#xff0c;然后下午一块儿去了&#xff0c;那个参加了跑团的年会&#xff0c;今年的点比较背一个奖品也没抽中&#xff0c;不过孩子玩的倒是挺开心的&#xff0c;晚上到家都快10:00了吧 2.感谢父母&#xff0c;感谢朋友&…

大数据领域:数据清洗推动企业数字化转型

大数据领域&#xff1a;数据清洗推动企业数字化转型关键词&#xff1a;数据清洗、数据质量、企业数字化转型、大数据处理、数据治理、数据价值、数据生命周期摘要&#xff1a;在企业数字化转型的浪潮中&#xff0c;“数据"被称为新时代的"石油”。但未经处理的原始数…

费雪的管理层访谈技巧:洞察公司文化

费雪的管理层访谈技巧&#xff1a;洞察公司文化关键词&#xff1a;费雪、管理层访谈技巧、洞察、公司文化、投资分析摘要&#xff1a;本文聚焦于费雪所提出的管理层访谈技巧&#xff0c;并深入探讨如何通过这些技巧洞察公司文化。公司文化对企业的长期发展和业绩表现有着至关重…

Agent设计哲学:简洁、可靠、可控——构建可信赖智能系统的三大支柱与落地框架

Agent设计哲学:简洁、可靠、可控 副标题:构建可信赖智能系统的三大支柱与落地框架 作者:光子AI 出版社:AI智能体时代虚拟出版社 创作时间:2026-01-17 面对智能Agent时应有的审慎与敬畏——追逐智能能力的同时,不忘回到问题本质,用简洁对抗冗余,用可靠筑牢底线,用可控…

Agentic AI:从技术架构到商业落地:构建自主、协作、可信的下一代智能系统

Agentic AI:从技术架构到商业落地:构建自主、协作、可信的下一代智能系统 作者:光子AI 出版社:AI智能体时代虚拟出版社 创作时间:2026-01-18 前言 当ChatGPT以惊人的自然语言理解能力掀起生成式AI风暴时,整个行业都在欢呼一个新时代的到来。然而,作为这场变革的深度参与…

UF_Modl.h

UF_MODL.h 和 UF_MODL_legacy.h 函数分类介绍 一、uf_modl.h 函数分类 1. 特征管理UF_MODL_ask_feature_sign:获取特征符号(布尔操作类型) UF_MODL_ask_immediate_children:获取建模即时子项偏好设置 UF_MODL_set_…

Hadoop 助力大数据领域的精准营销

Hadoop 助力大数据领域的精准营销 关键词&#xff1a;Hadoop、大数据、精准营销、分布式计算、用户画像、数据挖掘、商业智能 摘要&#xff1a;在“酒香也怕巷子深”的数字时代&#xff0c;企业如何从海量数据中精准找到目标用户&#xff1f;Hadoop作为大数据领域的“基建狂魔”…

大数据领域分布式存储的多租户支持方案

大数据领域分布式存储的多租户支持方案 关键词:分布式存储、多租户架构、资源隔离、性能优化、成本管理、QoS保障、容器化技术 摘要:本文深入探讨大数据领域分布式存储系统中多租户支持的核心技术与实现方案。通过分析多租户架构的技术挑战,提出包含资源隔离、性能保障、安全…

巴菲特的财务报表分析:解读平台经济的新指标

巴菲特的财务报表分析&#xff1a;解读平台经济的新指标 关键词&#xff1a;巴菲特、财务报表分析、平台经济、新指标、价值评估 摘要&#xff1a;本文深入探讨了巴菲特的财务报表分析方法在平台经济领域的应用&#xff0c;旨在寻找解读平台经济的新指标。通过介绍背景知识&…