目录

一、变量命名规则

二、函数命名规则

三、宏定义命名规范

四、FreeRTOS 中数据类型

五、缩进规范(Tab键,4字符宽度)


一、变量命名规则

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)中:

  1. 默认不勾选char无符号的(0-255)

  2. 勾选"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_tshort

32位系统:通常是 int32_tint

必须是有符号数,因为要用负数表示错误

常用返回值:

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字符要换行,参数对齐
  • 注释位置:在代码上方,不在右侧
  • 可读性:分组逻辑,空行分隔

---------------------------------------------------------------------------------------------------------------------------------

愿学者在学习的路上不迷路~

以上仅仅属于本人学习心得,可供学习参考,禁止商用~

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐