--- tags: [source-summary] type: source source: "尚硅谷嵌入式技术之FreeRTOS实时操作系统 V1.0.3 — 第17~18章 + 配套代码19/20" author: "尚硅谷研究院" date: 2026-07-12 url: "" created: 2026-07-12 --- # 软件定时器与 Tickless 低功耗 --- ## 软件定时器 > **用生活理解**:软件定时器就像你手机上的"闹钟"—— > - **一次性定时器**:设一个闹钟,响了就没了(比如"30分钟后关火") > - **周期性定时器**:设一个重复闹钟,每天这个点都响(比如"每天早上7点起床") ### 硬件 vs 软件定时器 | 特性 | 硬件定时器 | 软件定时器 | |------|-----------|-----------| | 本质 | 芯片内部真实计数器 | 用RTOS tick模拟的"假定时器" | | 精度 | 微秒级 | 受任务调度影响(毫秒级) | | 数量 | 有限(STM32F1有几个TIM) | 无限(只要内存够) | | 回调 | 在中断中执行 | 在"定时器服务任务"中执行 | ### 示例:软件定时器(项目19) #### 情景 - 创建两个定时器:timer1(一次性,500ms)和 timer2(周期性,1000ms) - 按 KEY1 → 启动两个定时器 - 按 KEY2 → 停止两个定时器 #### 核心代码 ```c #include "timers.h" TimerHandle_t timer1_handle; // 一次性 TimerHandle_t timer2_handle; // 周期性 void freertos_start(void) { // 创建一次性定时器:500ms后执行一次 timer1_handle = xTimerCreate( "timer1", // 名字(调试用) 500, // 周期 = 500个tick pdFALSE, // pdFALSE = 一次性,pdTRUE = 周期性 (void *)1, // 定时器ID(可用于区分多个定时器) timer1_callback); // 超时回调函数 // 创建周期性定时器:每1000ms执行一次 timer2_handle = xTimerCreate( "timer2", 1000, pdTRUE, (void *)2, timer2_callback); xTaskCreate(/* task1 */); vTaskStartScheduler(); } /* ========== 控制任务 ========== */ void task1(void *pvParameters) { uint8_t key = 0; while (1) { key = Key_Detect(); if (key == KEY1_PRESS) { xTimerStart(timer1_handle, portMAX_DELAY); // 启动一次性 xTimerStart(timer2_handle, portMAX_DELAY); // 启动周期性 } else if (key == KEY2_PRESS) { xTimerStop(timer1_handle, portMAX_DELAY); // 停止 xTimerStop(timer2_handle, portMAX_DELAY); } vTaskDelay(500); } } /* ========== 回调函数 ========== */ void timer1_callback(TimerHandle_t xTimer) { static uint16_t count = 0; printf("一次性定时器触发了第%d次\r\n", ++count); // 一次性定时器:只会打印一次 } void timer2_callback(TimerHandle_t xTimer) { static uint16_t count = 0; printf("周期性定时器触发了第%d次\r\n", ++count); // 周期性定时器:一直打印下去 } ``` ### 定时器 API 速查 | 函数 | 作用 | 类比 | |------|------|------| | `xTimerCreate()` | 创建定时器 | 买一个闹钟 | | `xTimerStart()` | 启动 | 拧上发条开始计时 | | `xTimerStop()` | 停止 | 按掉闹钟 | | `xTimerReset()` | 复位(重新开始计时) | 把闹钟重设为当前时间+N | | `xTimerChangePeriod()` | 修改周期 | 把闹钟改成不同的时间响 | ### ⚠️ 定时器回调的约束 定时器回调函数在**定时器服务任务**中执行,**不能调用阻塞 API**: ```c // ❌ 错误:在回调中调用阻塞函数 void timer_callback(TimerHandle_t xTimer) { vTaskDelay(100); // 禁止!会阻塞整个定时器服务任务 xQueueReceive(queue, &val, portMAX_DELAY); // 禁止! xSemaphoreTake(sem, portMAX_DELAY); // 禁止! } // ✅ 正确做法:在回调里只做快速处理 void timer_callback(TimerHandle_t xTimer) { printf("超时了!\r\n"); // 可以:printf通常很快 LED_Toggle(LED1_Pin); // 可以:GPIO操作极快 xQueueSend(queue, &val, 0); // 可以:超时=0不阻塞 } ``` --- ## Tickless 低功耗模式 > **用生活理解**:普通模式下,系统像一个人"每秒醒来看一次有没有事做"。Tickless 模式下,没事做就直接睡大觉,直到有事了才被叫醒。省电! ### 工作原理 ``` 普通模式: 醒→看→没事→睡→醒→看→没事→睡→醒→看→有事→干→醒→看... ↑ ↑ ↑ 1秒醒一次 1秒醒一次 1秒醒一次(浪费电) Tickless: 醒→看→没事→睡大觉→有事→醒了→干活 ↑ ↑ 睡很久 有人叫醒(省电!) ``` ### 配置方法 ```c // FreeRTOSConfig.h #define configUSE_TICKLESS_IDLE 1 // 开启低功耗 // 定义"睡前处理"和"醒后处理" #define configPRE_SLEEP_PROCESSING(x) PRE_SLEEP_PROCESSING() #define configPOST_SLEEP_PROCESSING(x) POST_SLEEP_PROCESSING() // 睡前:关掉所有外设时钟以省电 void PRE_SLEEP_PROCESSING(void) { __HAL_RCC_GPIOA_CLK_DISABLE(); __HAL_RCC_GPIOB_CLK_DISABLE(); __HAL_RCC_GPIOC_CLK_DISABLE(); // ... 关掉当前不需要的外设 } // 醒后:重新打开外设时钟 void POST_SLEEP_PROCESSING(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); // ... 重新打开外设 } ``` ### STM32 低功耗模式对比 | 模式 | 功耗 | 唤醒方式 | 醒来速度 | Tickless用哪个? | |------|------|---------|---------|----------------| | 睡眠(Sleep) | 中 | 任何中断 | 最快 | ✅ 默认用这个 | | 停机(Stop) | 低 | EXTI/RTC/USB | 中等 | ⚠️ 需额外配置 | | 待机(Standby) | 极低 | 复位/WKUP/RTC | 最慢 | ❌ 用不了 | ### 配套代码 - `code/19_软件定时器/` - `code/20_低功耗模式/` > 后续章节:[[11-内存管理]]