FreeRTOS 学习笔记(七):任务通知
FreeRTOS 里让两个任务通信,大家第一反应是队列或信号量——需要 xQueueCreate/xSemaphoreCreateBinary 新建内核对象,分配 TCB 和缓冲区。而任务通知是内置在每个任务的 TCB 里的,不需要额外创建任何东西。 这带来两个好处:快和省内存。在 STM32F4 上的实测数据: 12二值信号量 Give → Take:~140 个 CPU 周期任务通知 Notify → Take: ~45 个 CPU 周期(快了 3 倍) 内存方面——每省一个信号量,省约 80 字节 RAM。10 个信号量替代成 10 个任务通知,省 800 字节。 任务通知的机制。 每个任务的 TCB 里有两个字段:一个是 ulNotifiedValue(32 位通知值),一个是状态标志(pending 状态)。你不需要初始化任何东西——创建任务时就带着。 最基本的用法:二进制通知,替代二值信号量。 123456// 发送方(可以是 ISR)xTaskNotifyGive(TaskHandle_t xTaskToNotify);vTaskNotifyGiveF...
FreeRTOS 学习笔记(六):内存管理
FreeRTOS 不替你管理堆——它提供了五种 heap_x.c 实现,你选一种编译进项目。选错了,轻则 RAM 碎片导致 pvPortMalloc 返回 NULL,重则任务创建失败系统起不来。 核心问题不是”哪种最好”,而是”你的项目能接受什么代价”。 先从 FreeRTOS 什么时候需要内存开始。 每次调 xTaskCreate、xQueueCreate、xSemaphoreCreateBinary、xTimerCreate——这些创建 API 内部都调 pvPortMalloc,从你提供的堆里分一块。 一个典型项目的内存去向: 1234567891011┌──────────────────────────────────────┐│ FreeRTOS 堆(ucHeap[N] 或链接脚本段) ││ ││ Task Stack (LED, 128W) ││ Task Stack (Logger, 256W) ││ Task Stack (Button,...
FreeRTOS 学习笔记(五):中断管理
ISR 和任务的边界是 FreeRTOS 里最容易踩坑的地方。调错 API——xQueueSend 代替 xQueueSendFromISR——编译器不报错,运行时随机崩。临界区太长——任务响应延迟暴涨。中断优先级设错——高优先级 ISR 被 FreeRTOS 意外关掉。 这笔记记了我在 STM32F4 上做中断管理时踩过的坑和排查方法。 Cortex-M 的中断优先级和 FreeRTOS 的关系。 Cortex-M 用 NVIC,优先级数字越小越高,0 是最高。但 STM32 的 HAL 库(CMSIS)用 PreemptPriority + SubPriority,NVIC_PriorityGroupConfig() 决定高 4 位里几位是抢占、几位是子优先级。 FreeRTOS 只关心:哪些 ISR 能调 FromISR API,哪些不能。 12345// FreeRTOSConfig.h#define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 0xF // 最低优先级(Cortex-M 级别)#define configLI...
FreeRTOS 学习笔记(四):软件定时器
硬件定时器有限——STM32F4 总共 14 个,去掉基本定时器和被 PWM/编码器占用的,空闲的不多。当你需要七八个周期性动作(100ms 读传感器、500ms 喂狗、2s 上报状态、10ms 按键扫描),又不想每个都起一个任务,软件定时器是最好的选择。 FreeRTOS 的软件定时器靠”定时器服务任务”在后台跑,底层依赖 vTaskIncrementTick() 驱动。精度取决于 tick 频率——configTICK_RATE_HZ=1000 时最小分辨率 1ms,100 时只有 10ms。 创建定时器: 1234567TimerHandle_t xTimerCreate( const char * pcTimerName, // 调试用 TickType_t xTimerPeriod, // 周期,单位 tick 不是 ms UBaseType_t uxAutoReload, // pdTRUE=周期,pdFALSE=单次 void * pvTimerID, ...
FreeRTOS 学习笔记(三):信号量与互斥锁
信号量和队列是亲戚——信号量本质上就是个不许传数据的队列。它不关心消息内容,只关心”有没有”。 二值信号量(Binary Semaphore) 就像一个只能放一个令牌的盒子。任务调用 xSemaphoreTake() 拿走令牌,盒子空了;另一个任务(或 ISR)调用 xSemaphoreGive() 放回令牌,任务被唤醒。 12345678910SemaphoreHandle_t xSemaphoreCreateBinary(void);// 消费者if (xSemaphoreTake(sem, portMAX_DELAY) == pdTRUE) { // 拿到令牌,干活}// 生产者(任务或 ISR)xSemaphoreGive(sem); // 任务里用xSemaphoreGiveFromISR(sem, &woken); // ISR 里用 最常见的场景:ISR 通知任务”数据准备好了”。 12345678910111213141516SemaphoreHandle_t g_data_ready;// UART...
FreeRTOS 学习笔记(二):队列
任务之间怎么传数据?最简单的办法是全局变量。但全局变量没有”阻塞等待”能力——消费者不知道数据什么时候准备好,只能轮询。 队列解决了这个问题:生产者往里面放,消费者从里面取。如果队列空了,消费者可以选择阻塞等待。 它本质上是个先进先出的缓冲区,但多了任务间同步的能力。 创建队列: 1234QueueHandle_t xQueueCreate( UBaseType_t uxQueueLength, // 最多存几条消息 UBaseType_t uxItemSize // 每条消息多大); 注意:uxItemSize 是每条消息的大小,不是总大小。队列的实际内存 = uxQueueLength * uxItemSize,这块内存由 FreeRTOS 从堆上分配。 如果不想用堆,可以用 xQueueCreateStatic(),自己提供 uint8_t 缓冲区。 发送: 12345BaseType_t xQueueSend( QueueHandle_t xQueue, const void * pvItemToQueue, ...
FreeRTOS 学习笔记(一):任务管理
一个 FreeRTOS 任务就是一个永不返回的 C 函数: 123456void MyTask(void *pvParameters) { while (1) { // 干点什么 vTaskDelay(pdMS_TO_TICKS(100)); }} 任务在五种状态之间切换: 123456789101112131415161718192021 xTaskCreate() │ ▼┌──────────── 就绪态 ◄────────────────┐│ │ ││ 调度器选中 时间片到/被抢占│ │ ││ ▼ ││ 运行态 ───────────────...
C++ 学习笔记(十一):C++ 在嵌入式里的取舍
C++ 的很多特性在桌面和服务器上开销微不足道,但嵌入式的约束不一样——几十 KB 的 Flash、几 KB 的 RAM、没有 MMU、实时性要求。不是所有 C++ 特性都适合拿进来用,也不是所有特性都该排斥。 一、零开销抽象C++ 的设计原则之一是”零开销抽象(zero-overhead abstraction)”:你不使用的特性,不产生任何开销;你使用的特性,手写也不会更快。 这个原则在嵌入式里基本成立,但有例外。先看哪些是真的零开销: 内联函数 123inline int clamp(int v, int lo, int hi) { return v < lo ? lo : (v > hi ? hi : v);} 编译器展开后和宏一样,没有函数调用开销,同时有类型检查。 模板 12template<typename T>T max(T a, T b) { return a > b ? a : b; } 每个实例化是独立的函数,编译器可以充分优化,运行时没有类型分发开销。 constexpr 1...
C++ 学习笔记(十):Lambda 与 std::function
C 里把函数当参数传递要用函数指针,写起来繁琐,而且无法捕获上下文。Lambda 是 C++11 引入的匿名函数,可以在需要的地方直接定义,还能捕获周围的变量。 一、Lambda 基本语法1[捕获列表](参数列表) -> 返回类型 { 函数体 } 返回类型可以省略,编译器自动推导: 12auto add = [](int a, int b) { return a + b; };int result = add(3, 4); // 7 没有参数时括号可以省略: 12auto greet = [] { printf("hello\n"); };greet(); 二、捕获列表Lambda 可以捕获外部作用域的变量,这是函数指针做不到的: 1234567int threshold = 30;auto is_hot = [threshold](float temp) { return temp > threshold; // 使用了外部变量 threshold...
C++ 学习笔记(九):STL 容器实战——vector、map、string
STL 容器是 C++ 标准库里最常用的部分。不同容器背后是不同的数据结构,选错了轻则性能差,重则在嵌入式上把内存吃光。这篇重点讲三个最常用的:vector、map、string,以及在嵌入式里该怎么用。 一、vector:动态数组vector 是最常用的容器,本质是一块连续内存的动态数组,支持随机访问,尾部插入摊销 O(1)。 12345678910111213141516171819202122#include <vector>std::vector<int> v;v.push_back(1);v.push_back(2);v.push_back(3);// 访问元素v[0]; // 不检查越界v.at(0); // 检查越界,越界抛 std::out_of_rangev.front(); // 第一个元素v.back(); // 最后一个元素// 大小v.size(); // 元素个数v.capacity(); // 当前分配的容量v.empty(); // 是否为空// 删除v....





