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

看门狗外设的原理与应用

概述

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

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

看门狗

STM32F4系列内置两种看门狗:

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

    框图

    使用流程

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

    框图

喂狗策略与注意事项

喂狗策略:

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

注意事项:

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

判断复位来源

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

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

c 复制代码
#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_TX
    GPIO_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. 使能USART1
    USART_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映射到LSI
    TIM_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;        // 输入分频8
    TIM_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时钟=PCLK1
        return ((RCC_ClockFreq.PCLK1_Frequency / uwPeriodValue) * 8); // 每数一个数t =  1/RCC_ClockFreq.PCLK1_Frequency, T = uwPeriodValue*t,频率=1/T;
    }
    else
    { 
        // PCLK1预分频≠1 → TIM5时钟=2*PCLK1
        return (((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. 配置IWDG
    IWDG_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");
    }
}

低功耗模式

低功耗模式概述

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

模式详解

睡眠模式

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

  • 进入方式

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

停止模式

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

  • 进入步骤

    c 复制代码
    // 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域断电,调压器关闭,仅备份域保留数据,功耗达到最低。

  • 进入步骤

    c 复制代码
    // 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)是用于提供精确时间信息的硬件模块,可记录年、月、日、时、分、秒,支持低功耗运行(依靠备份电池供电)。广泛应用于智能手表、家电、医疗设备等。

主要特点

框图

RTC初始化流程

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

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

<>
EXTI NVIC RTC RCC PWR MCU EXTI NVIC RTC RCC PWR MCU 使能PWR外设时钟(RCC_APB1Periph_PWR) 设置DBP位,使能备份域访问 启动LSE时钟(RCC_LSE_ON) 等待LSE就绪标志(RCC_FLAG_LSERDY) 选择LSE作为RTC时钟源 使能RTC时钟(RCC_RTCCLKCmd) 等待RTC寄存器同步(RTC_WaitForSynchro) 配置异步预分频(127)、同步预分频(255)、24小时制 设置日期(2026年1月19日 周一,BCD格式) 设置时间(15:05:50 AM,BCD格式) 配置RTC_WKUP_IRQn抢占优先级1、子优先级0(下述中断配置可选,非必选根据实际情况配置) 配置EXTI_Line22为上升沿触发中断 清除EXTI_Line22中断标志 禁用RTC唤醒定时器 选择唤醒时钟源为CK_SPRE(1Hz) 设置唤醒重载值为0(1Hz唤醒频率) 使能RTC唤醒中断(RTC_IT_WUT) 清除RTC唤醒中断挂起位 启用RTC唤醒定时器

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

c 复制代码
// 十进制转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+空闲中断接收不定长数据,确保数据完整性。

c 复制代码
#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 7
    h = (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(周五)→5
    switch(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_TX
    GPIO_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. 清空发送寄存器并使能USART
    while( 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);                   // 回显到PC
        USART_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对应通道4
    DMA_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;                    // 禁用FIFO
    DMA_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_TX
    GPIO_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; // 子优先级低于USART1
    NVIC_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. 使能USART3
    USART_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; // 抢占优先级1
    NVIC_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/PM
        RTC_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;
}

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

c 复制代码
RTC_WriteBackupRegister(RTC_BKP_DR0, 0x32F2);   //事先写入

RTC闹钟设置


EXTI NVIC RTC RCC PWR MCU EXTI NVIC RTC RCC PWR MCU 使能PWR外设时钟(RCC_APB1Periph_PWR) 设置DBP位,使能备份域访问(PWR_BackupAccessCmd) 启动LSE时钟(RCC_LSE_ON) 等待LSE就绪标志(RCC_FLAG_LSERDY) 选择LSE作为RTC时钟源(RCC_RTCCLKSource_LSE) 使能RTC时钟(RCC_RTCCLKCmd) 等待RTC寄存器同步(RTC_WaitForSynchro) 配置异步预分频(127)、同步预分频(255)、24小时制 设置日期 设置时间 配置闹钟A时间 配置闹钟A日期为31、选择日期匹配、屏蔽日期/星期掩码 设置闹钟A(RTC_Alarm_A)参数 使能RTC闹钟A中断(RTC_IT_ALRA) 启用RTC闹钟A(RTC_AlarmCmd) 清除RTC闹钟A标志(RTC_FLAG_ALRAF) 配置RTC_Alarm_IRQn抢占优先级0、子优先级0并使能 清除EXTI_Line17中断挂起位 配置EXTI_Line17为上升沿触发、中断模式并使能 清除EXTI_Line17中断挂起位

c 复制代码
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型号,具体寄存器操作请参考对应型号的参考手册。

相关推荐
YouEmbedded2 小时前
解码模数转换器(ADC)
stm32·adc·电位器adc·ps2摇杆模块adc·adc数据滤波算法·光敏电阻adc
Zeku3 小时前
Linux驱动学习笔记:SPI子系统中的内核线程初始化
stm32·freertos·linux驱动开发·linux应用开发
1379号监听员_3 小时前
stm32平衡车
stm32·单片机·嵌入式硬件
兆龙电子单片机设计3 小时前
【STM32项目开源】STM32单片机智能台灯控制系统-机智云
stm32·单片机·嵌入式硬件·物联网·开源·毕业设计
云山工作室3 小时前
基于STM32单片机的智能鱼缸(论文+源码)
stm32·单片机·嵌入式硬件
雾削木5 小时前
STM32 HAL库 BMP280气压计读取
linux·stm32·单片机·嵌入式硬件
Y1rong5 小时前
STM32之ADC
stm32·单片机·嵌入式硬件
蓬荜生灰5 小时前
STM32(8)-- 自己创建库函数
stm32·单片机·嵌入式硬件
yuan199976 小时前
STM32F103CBT6驱动AW9523B实现呼吸灯实例
stm32·单片机·嵌入式硬件