看门狗外设的原理与应用
概述
随着单片机在工业控制、智能设备等领域广泛应用,系统稳定性成为关键。电磁干扰、电压波动等外部因素可能导致程序“跑飞”,即程序执行失控,表现为数据丢失、寄存器值异常、程序指针指向非法地址等。
看门狗(WatchDog Timer,简称WDT)是一种硬件定时器,用于监控程序运行状态。若程序在规定时间内未“喂狗”,看门狗将触发系统复位,使程序重新运行,从而提高系统可靠性。
看门狗
STM32F4系列内置两种看门狗:
-
独立看门狗(IWDG):基于独立时钟(LSI),可在递减计数器到达0x000之前的任意时间喂狗。

框图

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

框图

喂狗策略与注意事项
喂狗策略:
- 在主循环、定时中断、关键任务结束处分别喂狗。
- 对耗时任务(如Flash操作、复杂计算)应在子步骤中插入喂狗。
- 可采用分层喂狗:如在主循环或高优先级任务中执行,用于监控整体流程。在独立中断(如独立定时器中断)中执行,监控主程序是否被阻塞。
注意事项:
- 避免喂狗过于集中:仅在主循环中喂狗可能导致中断或后台任务超时,引发误复位。
- 避免喂狗频率过高:高频喂狗可能掩盖主程序死锁等问题。
- 记录复位原因:应在非易失存储器中记录看门狗复位次数与原因,便于故障分析。
判断复位来源
通过检查RCC_FLAG_IWDGRST标志位,可区分是看门狗复位还是手动复位。

独立看门狗示例代码(含注释)
#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");}
}

低功耗模式
低功耗模式概述
| 模式 | 功耗 | 唤醒源 | 恢复后状态 |
|---|---|---|---|
| 睡眠模式 | 较低 | 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上升沿检测(待机模式专属唤醒源)。
- 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 日历模块完全独立,不参与日期和时间的计数与更新。
注意:通过寄存器开发是,配置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+空闲中断接收不定长数据,确保数据完整性。






#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, ¤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未复位,无需重新初始化时间。
RTC_WriteBackupRegister(RTC_BKP_DR0, 0x32F2); //事先写入

RTC闹钟设置

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