看门狗外设的原理与应用
概述
随着单片机在工业控制、智能设备等领域广泛应用,系统稳定性成为关键。电磁干扰、电压波动等外部因素可能导致程序"跑飞",即程序执行失控,表现为数据丢失、寄存器值异常、程序指针指向非法地址等。
看门狗(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上升沿检测(待机模式专属唤醒源)。
- RTC复用事件 :通过
- 注意事项 :
- 睡眠模式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, ¤t_time);
RTC_GetDate(RTC_Format_BIN, ¤t_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型号,具体寄存器操作请参考对应型号的参考手册。