FreeRTOS系列第2篇——了解一些规范
本文详细介绍了嵌入式开发中的代码规范,主要包括:1.变量命名采用匈牙利命名法,通过前缀标识类型;2.函数命名遵循"前缀+模块名+动作"结构;3.宏定义采用"小写模块名_大写描述"格式;4.规范FreeRTOS数据类型(TickType_t/BaseType_t等)的使用;5.代码格式要求使用Tab缩进(4字符宽度)、80字符行宽限制、K&R风格大括号
目录
一、变量命名规则
1.核心思想:匈牙利命名法,通过变量名就能直观地看出其类型,大大提高代码可读性和可维护性。
2.核心概念:前缀是类型标记,不是变量名的全部。变量名通常为:前缀 + 描述性单词。描述性单词通常使用“驼峰命名法”。
3.具体变量命名
——uint32_t
规则:前缀 ul(unsigned long)
//例子:
uint32_t ulSystemTick = 0;// 系统滴答计数器
uint32_t ulErrorCode;// 错误码
void Delay(uint32_t ulMilliseconds);// 延时函数参数
——uint16_t
规则:前缀 us(unsigned short)
//例子:
uint16_t usAdcValue;// ADC采样值
uint16_t usYear = 2023;// 年份
——uint8_t
规则:前缀 uc(unsigned char)
//例子:
uint8_t ucSwitchStatus;// 开关状态 (0/1)
uint8_t ucBuffer[128];// 数据缓冲区
——int32_t,int16_t,int8_t
规则:对应的有符号类型,前缀去掉 u,即 l, s, c。但注意,这里的 c代表 char,不一定是“字符”,可能是8位有符号整数。
//例子:
int32_t lTemperature;// 温度值,可能为负
int16_t sAcceleration;// 加速度值
int8_t cDelta;// 变化量,范围-128~127
——size_t(通常为 unsigned int)
规则:前缀 ux(与 UBaseType_t 规则一致)
//例子:
size_t uxBufferLength;// 缓冲区长度
size_t uxBytesReceived;// 接收到的字节数
——枚举类型
规则:前缀 e
//例子
// 定义一个状态枚举
typedef enum {
eIDLE = 0,
eRUNNING,
eERROR
} SystemState_t;
// 声明一个该枚举类型的变量
SystemState_t eCurrentState = eIDLE;
——指针类型
规则:在基本类型前缀前再加一个 p。
//例子:
uint32_t *pulTickCount;// 指向滴答计数的指针
uint16_t *pusSensorData;// 指向传感器数据数组的指针
BaseType_t *pxStatus;// 指向状态变量的指针
SystemState_t *peStateMachine;// 指向状态机(枚举)的指针
——字符与字符串
为了避免将 char误用作整数。
char(仅用于存储单个 ASCII 字符)
规则:char定义的变量只能用于ASCII字符。前缀 c。
例子:char cKeyPressed = 'A';// 按下的键
//例子:
char cKeyPressed = 'A';// 按下的键
char*(仅用于指向 ASCII 字符串)
规则:char*定义的指针变量只能用于ASCII字符。前缀 pc。
//例子:
char *pcDeviceName = “STM32F407”;// 设备名称字符串
const char *pcLogMessage;// 日志消息字符串
——TickType_t(通常为 uint32_t)
规则:前缀 x(因为不在 stdint.h 中定义)
//例子:
TickType_t xLastWakeTime;// FreeRTOS 中记录任务上次唤醒的时间
——UBaseType_t(通常为 unsigned int)
规则:前缀 ux(无符号 + 其他类型)
//例子:
UBaseType_t uxPriority;// 任务优先级
| 变量类型 |
前缀 |
例子(声明) | 例子(合理用途) |
| uint32_t | ul | uint32_t ulCounter; | 计时器、大计数器、内存地址 |
| uint16_t | us | uint16_t usVoltage; | 传感器数据、PID参数、CRC校验值 |
| uint8_t | uc | uint8_t ucFlag; | 状态标志位、数组索引、原始数据字节 |
| int32_t | l | int32_t lPosition; | 有符号位置、差值 |
| BaseType_t | x | BaseType_t xResult; | FreeRTOS API返回值、布尔状态 |
| TickType_t | x | TickType_t xDelayTime; | FreeRTOS 延时时间 |
| UBaseType_t | ux | UBaseType_t uxHighWaterMark; | 栈空间高水位线、计数 |
| size_t | ux | size_t uxSize; | 大小、长度、数量 |
| enum | e | TaskState_t eTaskStatus; | 状态机、模式选择、错误等级 |
| 指针 | p+前缀 | uint32_t *pulData; | 传递数组、动态内存、外设寄存器 |
| char(ASCII) | c | char cCommand; | 从终端读取的单个字符 |
| char*(ASCII) | pc | const char *pcName; | 调试信息、配置字符串 |
二、函数命名规则
1.核心思想:函数名结构通常为:前缀 + 文件名标识 + 描述性动作。
这能让你一眼看出:1) 函数在哪用(作用域) 2) 返回什么 3) 属于哪个模块 4) 做什么。
2.静态函数举例
——静态函数(static声明)
规则:前缀 prv(private 的缩写)。static函数只在定义它的文件中可见,prv前缀强调了它的私有性。
// 在文件 "temperature.c" 中
// 这是一个私有函数,只在 temperature.c 内使用
static int32_t prvTemperatureReadSensor(void) {
// 读取温度传感器的原始值
return lRawData;
}
static void prvTemperatureCalibrate(int32_t lRawValue) {
// 内部校准函数,外部不需要知道
}
3.函数返回值类型前缀
函数的“类型”由其返回值决定,命名时要加上对应变量类型的前缀。
——void函数(无返回值)
规则:前缀 v
//void函数
void vLedInit(void); // 初始化LED
void vDelayMs(uint32_t ulMs); // 毫秒延时
——返回 uint32_t的函数
规则:前缀 ul
//例子:
uint32_t ulGetSystemTick(void); // 获取系统时钟
——返回 uint8_t的函数
规则:前缀 uc
//例子:
uint8_t ucIsButtonPressed(void); // 检查按键状态,返回 0/1
——返回指针的函数
规则:前缀 p+ 指针指向类型的前缀
//例子:
char* pcGetDeviceName(void); // 返回指向字符串的指针
uint16_t* pusGetDataBuffer(void); // 返回指向数据缓冲区的指针
——返回枚举的函数
规则:前缀 e
//例子:
ErrorStatus_t eCheckSystemStatus(void); // 返回系统状态枚举
4.文件标识(模块化组织)
这是将函数与其所属的源文件(模块)关联起来,防止不同模块间的函数名冲突,并增强可读性。
规则:函数名中应包含其定义所在的源文件(模块)名。格式通常是:前缀+ 模块名+ 动作描述。
//例子1:Task模块。在task.c文件中
// tasks.c
// 公共函数
void vTaskCreate(...) { // 创建任务
// ... 内部可能会调用私有函数 ...
prvTaskInitialiseControlBlock(...);
}
uint32_t ulTaskGetTickCount(...) { // 获取任务滴答计数
// ...
}
// 静态(私有)函数
static void prvTaskInitialiseControlBlock(...) { // 初始化任务控制块
// ...
}
//例子2:Queue 模块。在 queue.c文件中。
// queue.c
// 公共函数
void vQueueCreate(...) { // 创建队列
// ...
}
BaseType_t xQueueSend(...) { // 发送数据到队列
// ...
}
// 静态函数
static void prvQueueLock(...) { // 内部上锁函数
// ...
}
| 规则 | 前缀 | 例子 | 说明 |
| 静态函数 | prv | prvInternalProcess() | 只在定义它的文件中可见 |
| void函数 | v | vModuleInit() | 无返回值 |
返回 uint32_t |
ul | ulModuleGetCount() | 返回无符号长整型 |
返回 uint16_t |
us | usModuleReadData() | 返回无符号短整型 |
返回 BaseType_t |
x | xModuleSend() | 返回FreeRTOS常用类型 |
| 返回指针 | p+类型 | pcModuleGetName() | 返回字符指针 |
| 文件名标识 | (无固定) | vTaskCreate | 函数名包含模块名"Task"(来自tasks.c) |
三、宏定义命名规范
1.核心思想:宏命名结构为:文件名标识(小写)+ 描述性部分(大写),用下划线连接。
这能让你一眼看出宏定义属于哪个模块/文件,便于管理和查找。
2.举例
——前缀小写(文件名标识)
前缀通常是源文件名(不包含扩展名)的缩写
必须全部小写
如文件 config.h→ 前缀 config
——主体部分大写
前缀之后的部分全部大写
单词之间用下划线 _分隔
例子1:
// 文件: FreeRTOSConfig.h
// 前缀: config
#define configUSE_PREEMPTION 1 // 启用抢占式调度
#define configTICK_RATE_HZ 1000 // 系统时钟频率
#define configMAX_PRIORITIES 32 // 最大优先级数
#define configMINIMAL_STACK_SIZE 128 // 最小任务栈大小
#define configTOTAL_HEAP_SIZE (10 * 1024) // 总堆大小
例子2:
// 文件: hal_gpio.h
// 前缀: hal
#define hal_GPIO_MODE_INPUT 0x00
#define hal_GPIO_MODE_OUTPUT_PP 0x01
#define hal_GPIO_MODE_OUTPUT_OD 0x02
#define hal_GPIO_PIN_0 (1 << 0)
#define hal_GPIO_PIN_1 (1 << 1)
#define hal_GPIO_PIN_13 (1 << 13)
特殊情况例子3:
// 文件: stm32f4xx.h
// 前缀: 可以使用设备名缩写
#define stm32_RCC_BASE (0x40023800UL)
#define stm32_GPIOA_BASE (0x40020000UL)
#define stm32_GPIO_PIN_0 (1 << 0)
3.char型变量的符号设置
开发中一个重要的编译器设置,它决定了char类型的默认符号属性。
——什么是char的符号问题?
在C语言标准中,char类型可以是有符号(signed) 或无符号(unsigned) 的,这由编译器实现决定。这会导致代码在不同的编译器或设置下行为不一致。
——MDK(Keil)中的设置
在MDK(Keil uVision)中:
-
默认不勾选:
char是无符号的(0-255) -
勾选"Plain Char is Signed":
char是有符号的(-128~127)
设置路径:Options → C/C++ → Code Generation → "Plain Char is Signed"

四、FreeRTOS 中数据类型
1.TickType_t - 系统节拍计数器
作用:用于FreeRTOS的时间相关操作,如延时、超时检测等。
定义规则:
——16位系统(已很少用):#define configUSE_16_BIT_TICKS 1
TickType_t是 uint16_t
范围:0~65535
最大可计时间:65535个节拍
——32位系统(推荐):#define configUSE_16_BIT_TICKS 0
TickType_t是 uint32_t
范围:0~4,294,967,295
几乎可以无限计数
——为什么32位系统要禁用16位节拍?
假设系统时钟节拍是1ms:
16位:最多65.535秒就会溢出
32位:大约49.7天才会溢出,足够大部分应用
// FreeRTOSConfig.h
#define configUSE_16_BIT_TICKS 0 // 32位系统设为0
#define configTICK_RATE_HZ 1000 // 1ms一个节拍
// 代码中使用
TickType_t xLastWakeTime;
TickType_t xDelayTime = pdMS_TO_TICKS(1000); // 1000ms -> 1000个节拍
// 获取当前节拍计数
TickType_t xCurrentTicks = xTaskGetTickCount();
// 延时500ms
vTaskDelay(pdMS_TO_TICKS(500));
// 等待队列,最多等1秒
BaseType_t xResult = xQueueReceive(xQueue, &data, pdMS_TO_TICKS(1000));
2.BaseType_t - 基本返回类型
作用:FreeRTOS中最常用的返回值类型,用于表示成功/失败、状态等。
定义规则:
16位系统:通常是 int16_t或 short
32位系统:通常是 int32_t或 int
必须是有符号数,因为要用负数表示错误
常用返回值:
pdPASS=1(成功)
pdPASS=0(成功)
其他负值表示特定错误
// 创建任务
BaseType_t xTaskCreateResult = xTaskCreate(vTaskFunction, "Task1", configMINIMAL_STACK_SIZE, NULL, 1, &xTaskHandle);
if (xTaskCreateResult != pdPASS) {
// 任务创建失败
vPrintString("Error: Task creation failed!\n");
}
// 创建队列
QueueHandle_t xQueue = xQueueCreate(10, sizeof(uint32_t));
if (xQueue == NULL) {
// 队列创建失败
vPrintString("Error: Queue creation failed!\n");
}
// 发送数据到队列
uint32_t ulData = 100;
BaseType_t xSendResult = xQueueSend(xQueue, &ulData, 0);
if (xSendResult == pdFAIL) {
vPrintString("Queue is full!\n");
}
// 信号量操作
BaseType_t xSemaphoreTakeResult = xSemaphoreTake(xSemaphore, portMAX_DELAY);
if (xSemaphoreTakeResult == pdPASS) {
// 成功获取信号量
// 执行临界区操作
xSemaphoreGive(xSemaphore);
}
3.UBaseType_t - 无符号基本类型
作用:用于不需要负数的情况,如优先级、计数等。
定义规则:
是 BaseType_t的无符号版本
16位系统:通常是 uint16_t
32位系统:通常是 uint32_t
// 获取当前任务优先级
UBaseType_t uxPriority = uxTaskPriorityGet(NULL);
// 设置任务优先级
UBaseType_t uxNewPriority = 3;
vTaskPrioritySet(xTaskHandle, uxNewPriority);
// 获取队列中可用的消息数量
UBaseType_t uxMessagesWaiting = uxQueueMessagesWaiting(xQueue);
// 获取队列剩余空间
UBaseType_t uxSpacesAvailable = uxQueueSpacesAvailable(xQueue);
// 优先级比较
UBaseType_t uxPriority1 = 2;
UBaseType_t uxPriority2 = 5;
if (uxPriority1 > uxPriority2) {
// 优先级1比优先级2高
}
// 任务栈使用情况
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
vPrintString("Stack high water mark: ");
vPrintNumber(uxHighWaterMark);
4.StackType_t - 栈数据类型
作用:定义任务栈中每个元素的数据类型,决定栈的内存对齐和大小。
定义规则:
16位系统:通常是 uint16_t
32位系统:通常是 uint32_t
自动匹配系统架构
// 静态分配任务栈
#define TASK_STACK_SIZE 128
static StackType_t xTaskStack[TASK_STACK_SIZE];
// 创建任务时使用静态栈
xTaskCreateStatic(vTaskFunction, "Task1", TASK_STACK_SIZE, NULL, 1, xTaskStack, &xTaskBuffer);
// 获取栈使用情况
UBaseType_t uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);
vPrintString("Remaining stack: ");
vPrintNumber(uxHighWaterMark * sizeof(StackType_t)); // 字节数
vPrintString(" bytes\n");
// 栈溢出检测
configCHECK_FOR_STACK_OVERFLOW // 在FreeRTOSConfig.h中启用
| 数据类型 | 32位系统定义 | 16位系统定义 | 主要用途 | 示例用途 |
| TickType_t | uint32_t | uint16_t | 时间、延时 | 任务延时、超时设置、节拍计数 |
| BaseType_t | int32_t | int16_t | 函数返回值 | 成功/失败、错误码、布尔值 |
| UBaseType_t | uint32_t | uint16_t | 无符号计数 | 优先级、消息计数、栈大小 |
| StackType_t | uint32_t | uint16_t | 任务栈 | 静态栈数组、栈大小计算 |
五、缩进规范(Tab键,4字符宽度)
1.FreeRTOS使用Tab键而不是空格进行缩进,每个Tab等于4个字符宽度。
注意:在代码编辑器设置中,要确保Tab显示为4个字符宽度。不同的IDE设置:
Keil: Editor → 勾选"Insert spaces for tabs"(但FreeRTOS实际用Tab)

2.注释规范(不超过80字符)
规则:
只用 /* */,不用 //
不超过80个字符宽度(便于在任何编辑器/终端中查看)
注释在代码上方,不在一行代码的右侧
//正确示范
/*
* 任务函数:处理主要业务逻辑
* 参数 pvParameters: 任务创建时传入的参数
*/
void vTaskFunction(void *pvParameters)
{
/* 等待信号量,最大等待时间设置为portMAX_DELAY表示无限等待。 */
if (xSemaphoreTake(xSemaphore, portMAX_DELAY) == pdPASS) {
/* 成功获取信号量,执行临界区操作。 */
vProcessData();
/* 释放信号量。 */
xSemaphoreGive(xSemaphore);
}
}
3.代码布局与可读性
——大括号位置
/* 正确的 —— 函数定义 */
void vTaskFunction(void *pvParameters)
{
/* 函数体 */
}
/* 不要使用下列结构------*/
void vTaskFunction(void *pvParameters) {
/* 不用 */
}
void vTaskFunction(void *pvParameters)
{
/* 不用 */
}
/*-控制结构--正确范例---------------------------------------*/
if (xCondition == pdTRUE) {
/* 条件为真时执行 */
} else {
/* 条件为假时执行 */
}
/* 不要使用下列结构------*/
void vTaskFunction(void *pvParameters) {
/* Allman风格,不用 */
}
void vTaskFunction(void *pvParameters)
{
/* GNU风格,不用 */
}
4.空格与换行
/* 运算符周围有空格 */
uint32_t ulResult = ulValue1 + ulValue2;
if (ulResult > MAX_VALUE) {
vHandleError();
}
/* 函数名和括号之间无空格 */
xStatus = xQueueSend(xQueue, &xData, 0);
/* 参数逗号后有空格 */
vPrintString("Count: ", ulCount, pdTRUE);
/* 类型转换无空格 */
xData = (uint32_t *)pvBuffer;
5.长行分割
当一行超过80个字符时,要适当换行;复杂表达式换行;
BaseType_t xQueueSendToFront(QueueHandle_t xQueue,
const void *pvItemToQueue,
TickType_t xTicksToWait)
{
BaseType_t xReturn = pdFAIL;
/* 在发送到队列前,检查队列句柄是否有效。 */
if (xQueue != NULL) {
/* ... 实现代码 ... */
}
return xReturn;
}
if ((xQueue != NULL) && (pvItemToQueue != NULL) &&
(xTicksToWait <= portMAX_DELAY)) {
/* 所有条件都满足 */
vProcessQueue();
}
6.代码可读性
垂直对齐,分组逻辑
/*垂直对称-----------------------------------------------------*/
/* 对齐结构体成员 */
typedef struct {
uint32_t ulVersion; /* 版本号 */
uint8_t ucDeviceId; /* 设备ID */
uint16_t usChecksum; /* 校验和 */
uint8_t ucStatus; /* 状态标志 */
uint32_t ulTimestamp; /* 时间戳 */
} Packet_t;
/* 对齐宏定义值 */
#define configUSE_PREEMPTION 1
#define configUSE_IDLE_HOOK 0
#define configUSE_TICK_HOOK 0
#define configTICK_RATE_HZ 1000
#define configMAX_PRIORITIES 5
#define configMINIMAL_STACK_SIZE 128
#define configTOTAL_HEAP_SIZE (1024 * 10)
/*分组逻辑--------------------------------------------------*/
void vProcessData(uint8_t *pucData, uint32_t ulLength)
{
/* ---------- 第一部分:参数验证 ---------- */
if (pucData == NULL) {
return;
}
if (ulLength == 0) {
return;
}
/* ---------- 第二部分:数据预处理 ---------- */
/* 计算校验和 */
uint16_t usCalculatedChecksum = usCalculateChecksum(pucData, ulLength);
/* 验证数据长度 */
if (ulLength < MIN_DATA_LENGTH) {
vHandleShortData();
return;
}
/* ---------- 第三部分:主处理逻辑 ---------- */
/* 解密数据 */
vDecryptData(pucData, ulLength);
/* 解析协议 */
xParseProtocol(pucData, ulLength);
/* ---------- 第四部分:清理 ---------- */
vCleanupResources();
}
总结要点:
- 缩进:用Tab,不要用空格
- 大括号:K&R风格,左大括号不单独一行
- 注释:只用
/* */,不超过80字符 - 对齐:相似的元素要对齐
- 换行:超过80字符要换行,参数对齐
- 注释位置:在代码上方,不在右侧
- 可读性:分组逻辑,空行分隔
---------------------------------------------------------------------------------------------------------------------------------
愿学者在学习的路上不迷路~
以上仅仅属于本人学习心得,可供学习参考,禁止商用~
更多推荐



所有评论(0)