编程规范
参考链接
C语言 | 嵌入式C语言编程规范_函数名开头加uc-CSDN博客
[!tip]
以下文件排版将会按照自顶向下的方式进行编排,==文件夹 > 文件名 > 排版 > 函数 > 变量==
文件架构
缩写
该项目文件树参考AUTOSAR结构,为了便于理解,此处展示部分缩写的全称:
| 缩写 | 全称 | 描述 |
|---|---|---|
| AUTOSAR | AUTomotive Open System ARchitecture | 汽车开放系统架构,是汽车软件开发的标准框架。 |
| ASW | Application Software | 应用软件,主要负责实现特定功能的业务逻辑。 |
| BSW | Basic Software | 基础软件,负责底层硬件控制和通用服务的实现。 |
| BSP | Board Support Package | 板级支持包,包含硬件初始化、驱动和相关工具。 |
| HAL | Hardware Abstraction Layer | 硬件抽象层,提供对硬件的抽象访问接口。 |
| MCAL | Microcontroller Abstraction Layer | 微控制器抽象层,提供对芯片外设的直接访问接口。 |
| RTE | Run-Time Environment | 运行时环境,负责 ASW 与 BSW 的通信管理。 |
| UDS | Unified Diagnostic Services | 统一诊断服务协议(ISO 14229),用于 ECU 的诊断通信。 |
| Dcm | Diagnostic Communication Manager | 诊断通信管理模块,实现 UDS 服务的解析和处理。 |
| Dem | Diagnostic Event Manager | 诊断事件管理模块,负责故障码存储和管理。 |
| PduR | Protocol Data Unit Router | 协议数据单元路由模块,负责数据路由。 |
| CanTp | CAN Transport Protocol | CAN 传输协议模块,用于实现 UDS 消息的传输。 |
| NvM | Non-Volatile Memory Manager | 非易失性内存管理模块,负责数据的持久化存储。 |
| SoAd | Socket Adapter | 套接字适配器,用于实现基于 TCP/IP 的通信协议(如 DoIP)。 |
| ECU | Electronic Control Unit | 电子控制单元,是汽车的嵌入式控制器。 |
| GPIO | General Purpose Input/Output | 通用输入输出,硬件外设的一种。 |
| UART | Universal Asynchronous Receiver-Transmitter | 通用异步收发器,用于串行通信。 |
| SPI | Serial Peripheral Interface | 串行外设接口,用于高性能串行通信。 |
| I2C | Inter-Integrated Circuit | 一种常用的两线串行通信协议。 |
| CAN | Controller Area Network | 控制器局域网,是汽车通信总线的一种。 |
| FlexRay | Flexible Ray | 一种高速汽车通信协议,用于安全关键应用。 |
| LIN | Local Interconnect Network | 局域互连网络,低成本汽车通信协议。 |
| DoIP | Diagnostics over Internet Protocol | 基于 IP 协议的诊断通信。 |
| OS | Operating System | 操作系统,提供任务调度和资源管理功能。 |
| SWC | Software Component | 软件组件,用于实现应用逻辑,是 ASW 层的核心构成模块。 |
概览
1 | ├─.vscode |
[!important]
不知道要将代码放在哪个文件夹中需要参考以下准则:
- 与硬件无关的请存放于ASW,也就是逻辑相关
- 与硬件有关的请存放于BSW,也就是对接硬件平台
- OS中存放系统相关,如文件系统、操作系统、调度系统
- BOOT中存放和BOOT相关的文件,采用的协议文件,如UDS、OSI
[!note]
为了保持风格的一致性,高两级的文件夹请使用开头大写(ASW、OS、User、Components等),再低级的文件夹就使用小写(inc、src、cfg等)
ASW
1 | ├─.vscode |
[!note]
注意,ASW中存放和硬件无关的代码,所以在写其中的代码,我们应该要尽量减少和硬件有关的代码,使用抽象代码,如使用定死的函数名访问,我们并不需要知道它下面是怎么实现的,我们只需要调用。(使用定死的函数名方便进行抽象)
[!important]
‼‼‼在ASW中写代码,一定要有抽象的概念‼‼‼
例如SWC是组件的意思,也就意味着,当你写好一个组件,那么我下次使用别的平台的时候可以直接调用你的组件,而不需要再修改很多东西,如定义一个宏开关来控制。
对于一个组件来说,我只需要知道你的组件的输入和输出即可,所以其中的代码尽量写的抽象,减少重复造轮子。
BSW
1 | ├─BSW 存放与硬件相关代码,所以需要区分不同芯片型号 |
[!tip]
需要考虑的是,是否要增加芯片厂商的文件夹,如ZX/FR3032D,如果要制定芯片厂商,请和plm等保持一致的缩写,如ZX代表智芯
BOOT
1 | ├─BOOT |
Documents
1 | ├─Documents 存放和项目相关的手册、点检表、变更履历表,如果要存放过多的文件,请使用文件夹分类,便于查阅 |
OS
1 | ├─OS |
Output
1 | ├─Output |
[!note]
请务必在工程中设置output和listing以保证项目树的整洁性
Project
1 | └─Project |
[!important]
和项目相关的工程请存放至此,我们常用的工程有:
- Keil
- IAR
- SourceInsight
- Vscode
- TSMaster
- Canoe
- ZLG
- JFLash
- QAC
- Tessy
[!note]
QAC和Tessy的测试输出报告请不要放置Document中,需要存放在Project下的相关文件夹中
ReadMe
ReadMe文件中用来存放和项目相关教程,如Flash、Ram分布,如何使用函数,当使用的时候需要修改哪些等等。
[!note]
最好是看完ReadMe就能知道项目怎么配置‼‼‼
文件
文件的命名要准确清晰地表达其内容,同时文件名应该精练,防止文件名过长而造成使用不便。在文件名中可以适当地使用缩写。 以下提供两种命名方式以供参考:
-
各程序模块的文件命名开头 2 个消协字母代表本模块的功能:如:主控程序为 mpMain.c,mpDisp.c 等。
-
不写模块功能标识:如:主控程序为 Main.c,Disp.c 等。
文件名命名规则
- 模块前缀:
- 文件名通常以模块或组件的缩写作为前缀,例如:
Com_:通信栈模块。Dcm_:诊断通信管理模块。EcuM_:ECU状态管理模块。Os_:操作系统模块。BswM_:基础软件管理模块。
- 文件名通常以模块或组件的缩写作为前缀,例如:
- 文件类型:
- 文件名中通常包含文件类型的标识,例如:
_Cfg:配置文件。_Types:类型定义文件。_Api:API接口文件。_Impl:实现文件。_PbCfg:Post-build 配置文件。
- 文件名中通常包含文件类型的标识,例如:
- 文件内容描述:
- 文件名中应包含文件内容的简要描述,例如:
ComStack_Types.h:通信栈的类型定义文件。Dcm_Cfg.c:诊断通信管理的配置文件。
- 文件名中应包含文件内容的简要描述,例如:
- 文件扩展名:
- 头文件:
.h - 源文件:
.c - 配置文件:
.c或.h - 描述文件:
.arxml(AUTOSAR XML 文件)
- 头文件:
- 大小写:
- 文件名通常采用驼峰命名法(CamelCase),首字母大写。
命名标准
[!important]
请严格遵守以下命名准则书写文件名‼‼‼‼‼
通信栈模块(Com)
-
类型定义文件:
1
ComStack_Types.h
-
配置文件:
1
Com_Cfg.c
-
API接口文件:
1
Com_Api.h
诊断通信管理模块(Dcm)
-
类型定义文件:
1
Dcm_Types.h
-
配置文件:
1
Dcm_Cfg.c
-
实现文件:
1
Dcm_Impl.c
基础板级支持包(BSP)
命名规则为:==Bsp_芯片型号 _模块名称==
1 | Bsp_ZX116_ADC.h |
ECU状态管理模块(EcuM)
-
类型定义文件:
1
EcuM_Types.h
-
配置文件:
1
EcuM_Cfg.c
-
API接口文件:
1
EcuM_Api.h
操作系统模块(Os)
-
类型定义文件:
1
Os_Types.h
-
配置文件:
1
Os_Cfg.c
-
API接口文件:
1
Os_Api.h
基础软件管理模块(BswM)
-
类型定义文件:
1
BswM_Types.h
-
配置文件:
1
BswM_Cfg.c
-
API接口文件:
1
BswM_Api.h
非易失性存储管理模块(Nvm)
-
类型定义文件:
1
Nvm_Types.h
-
配置文件:
1
Nvm_Cfg.c
-
API接口文件:
1
Nvm_Api.h
AUTOSAR XML 文件
-
系统描述文件:
1
SystemDescription.arxml
-
ECU配置描述文件:
1
EcuConfiguration.arxml
AUTOSAR文件名命名总结
-
模块前缀:文件名以模块或组件的缩写作为前缀。
-
文件类型:文件名中包含文件类型的标识(如
_Cfg、_Types)。 -
文件内容描述:文件名应简要描述文件内容。
-
文件扩展名:根据文件类型使用合适的扩展名(如
.h、.c、.arxml)。 -
大小写:采用驼峰命名法,首字母大写。
-
文件命名规范
AUTOSAR对文件的命名有严格的规范,以确保文件的可读性和一致性。常见的命名规则包括:
模块名前缀:文件名通常以模块名作为前缀。例如:(如:Dio_Cfg.h:DIO模块的配置文件、Can_PBcfg.c:CAN模块的Post-Build配置文件)文件类型后缀:文件名中包含文件类型的标识。例如:(
_Cfg:表示配置文件、_PBcfg:表示Post-Build配置文、_LCfg:表示Link-Time配置文件。)
版本信息:文件名中可以包含版本号或日期信息,以便于版本管理。
头文件
在写头文件时,可以按照以下的段落排版顺序写:
1 | // 1、文件头注释 |
如果使用绝对路径,当需要移动目录时,必须修改所有相关代码,繁琐且不安全;使用相对路径,当需要移动目录时,只需修改编译器的某个选项即可。例如:
1 |
在引用头文件时,注意<> “”的区别:
1 |
在写头文件时要加上条件编译指令以防止头文件被重复引用:
1 |
在头文件中只存放申明而不存放定义
1 | /*模块1头文件:nodule1.h*/ |
并且文件的长度没有非常严格的要求,但应避免文件过长,一般来说,文件长度应尽量保持在1000行之内。
[!important]
尽量使用模板自动配置,具体配置见
4.5,使用软件自动配置管理不仅可以节约时间,也可保持一致性。
排版
缩进与对齐
缩进
- 使用 4 个空格 进行缩进,禁止使用 Tab(如果要使用Tab键,请确保一个Tab等于4个空格)。
- 缩进应清晰反映代码的层次结构。
示例:
1 | void Function(void) |
对齐
- 括号
{}应独占一行,并与上一行的代码对齐。 - 代码块内的语句应对齐。
示例:
1 | if (condition) |
行长度与换行
行长度
- 每行代码不应超过 120 个字符。
- 如果一行代码过长,应将其拆分为多行。
示例:
1 | uint8 result = FunctionWithManyParameters(parameter1, parameter2, |
换行规则
- 在运算符后换行,保持运算符在行尾。
- 换行后应对齐到上一行的起始位置或适当缩进。
示例:
1 | uint8 result = value1 + value2 + value3 + |
空格与空行
空格
- 在运算符两侧添加空格。
- 在逗号后添加空格。
- 在关键字后添加空格。
示例:
1 | uint8 result = (value1 + value2) * value3; |
空行
- 在函数之间添加空行,以分隔不同的代码块。
- 在逻辑相关的代码块之间添加空行,以提高可读性。
示例:
1 | void Function1(void) |
括号与分号
括号
- 括号
{}应独占一行,并与上一行的代码对齐。 - 即使代码块只有一行,也应使用括号。
示例:
1 | if (condition) |
分号
- 每个语句应以分号
;结尾。 - 分号前不应有空格。
示例:
1 | uint8 value = 10; |
注释格式
单行注释
- 使用
//进行单行注释。 - 注释应与代码对齐。
示例:
1 | uint8 value = 10; // Initialize value |
多行注释
- 使用
/* ... */进行多行注释。 - 注释内容应清晰、简洁。
示例:
1 | /* |
函数排版
函数声明
- 函数声明应包含返回值类型、函数名和参数列表。
- 参数列表应换行对齐。
示例:
1 | Std_ReturnType FunctionName(uint8 parameter1, |
函数定义
- 函数定义应包含详细的注释,描述其功能、参数和返回值。
- 函数体应缩进 4 个空格。
示例:
1 | /****************************************************************************** |
结构体与枚举排版
结构体
- 结构体定义应包含详细的注释,说明其用途和成员的含义。
- 结构体成员应对齐。
示例:
1 | /****************************************************************************** |
枚举
- 枚举定义应包含详细的注释,说明其用途和枚举值的含义。
- 枚举值应对齐。
示例:
1 | /****************************************************************************** |
注释
文件注释规范
文件头注释
每个文件(包括 .c 和 .h 文件)应包含文件头注释,通常包括以下内容:
- 文件名:文件的名称。
- 描述:文件的简要功能描述。
- 作者:文件的作者或开发者。
- 版本:文件的版本号。
- 日期:文件的创建或修改日期。
- 版权声明:文件的版权信息。
注释格式
- 使用
/* ... */进行多行注释。 - 注释内容应清晰、简洁,避免冗余信息。
文件头注释示例
.c 文件注释
1 | /****************************************************************************** |
.h 文件注释
1 | /****************************************************************************** |
文件内容注释
模块注释
在文件的开头,添加模块的详细描述,说明模块的功能、依赖关系和设计思路。
示例:
1 | /****************************************************************************** |
函数注释
每个函数应包含详细的注释,描述其功能、参数和返回值。
示例:
1 | /****************************************************************************** |
结构体和枚举注释
为结构体和枚举类型添加注释,说明其用途和成员的含义。
示例:
1 | /****************************************************************************** |
其他注释
代码块注释
在复杂的代码块前添加注释,解释其逻辑和实现思路。
示例:
1 | // Check if the CAN message is valid |
修改记录
在文件的末尾或注释中添加修改记录,记录每次修改的内容、作者和日期。
示例:
1 | /****************************************************************************** |
软件配置
Vscode
下载C/C++ Snippets插件,上网查询相关配置,该插件用于对代码进行位置分布做限定,参考配置如下:
1 | { |
下载KoroFileHeader插件,该配置用于生成头部申明和函数注释参考配置如下:
1 | "fileheader.customMade": { |
最终效果为:
1 | /* |
变量
变量类型的定义遵循严格的规范,以确保代码的可移植性、可读性和一致性。本规范定义了一套标准的数据类型,这些数据类型通常通过标准化类型定义文件(如 Std_Types.h)来实现。变量类型的前缀缩写用于标识变量的用途、模块或功能域。这些前缀缩写有助于提高代码的可读性和可维护性,同时确保变量命名的一致性和规范性。
常见变量类型前缀缩写
| 前缀缩写 | 含义 | 示例 |
|---|---|---|
| u | 无符号(unsigned) | uint8:无符号8位整数;uint16:无符号16位整数。 |
| s | 有符号(signed) | sint8:有符号8位整数;sint16:有符号16位整数。 |
| f | 浮点数(float) | float32:32位浮点数;float64:64位浮点数。 |
| p | 指针(pointer) | pBuf:指向缓冲区的指针;pData:指向数据的指针。 |
| a | 数组(array) | aSignal:信号数组;aData:数据数组。 |
| b | 布尔(boolean) | bFlag:布尔标志;bStatus:布尔状态。 |
| e | 枚举(enumeration) | eState:状态枚举;eMode:模式枚举。 |
| t | 结构体(struct)或类型(type) | tConfig:配置结构体;tData:数据结构体。 |
| n | 数量(number)或计数器(counter) | nCount:计数器;nSize:大小。 |
| c | 常量(constant) | cMaxValue:最大值常量;cMinValue:最小值常量。 |
| k | 配置常量(configuration constant) | kConfigParam:配置参数常量。 |
| g | 全局变量(global) | g_State:全局状态;g_Data:全局数据。 |
| m | 模块内部变量(module internal) | m_InternalData:模块内部数据;m_State:模块内部状态。 |
| r | 引用(reference) | rData:数据引用;rConfig:配置引用。 |
| h | 句柄(handle) | hModule:模块句柄;hResource:资源句柄。 |
| x | 扩展类型(extended type)或复杂类型 | xComplexData:复杂数据类型;xExtendedConfig:扩展配置类型。 |
[!important]
组合的顺序为:
1 [作用域前缀][数据类型前缀]_[变量含义]作用域前缀可选:
前缀 含义 示例 g 全局变量(extern修饰或在c文件定义的全局变量且没有static修饰) gu8Counter m 模块级静态变量(文件中使用static定义的变量) mu8Counter p 指针类型 pu8Counter 无 局部变量(函数中的变量、形参。结构体中的变量) u8Counter ==注意p可以和m或g组合,组合顺序为先g或m,之后再加p。g和m互斥,也就是说前缀可以是mp、gp,但不可以g和m同时出现,如gmp。==
例子:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 typedef struct _test
>{
uint8 u8Counter,
uint8* pu8Counter
}Test_t;
static uint16 mu16Counter;
uint16* gpu16Counter;
Test_t gtCounter;
void Func(uint8 u8Counter,uint8* pu8Counter)
{
sint8 s8Test = 0;
gtCounter.u8Counter = u8Counter;
gtCounter.pu8Counter = pu8Counter;
}
模块相关变量前缀缩写
除了通用的变量前缀缩写,还有一些与模块相关的变量前缀缩写:
| 前缀缩写 | 模块/功能域 | 示例 |
|---|---|---|
| Com_ | 通信栈(Communication Stack) | Com_Signal:通信信号;Com_Pdu:通信PDU。 |
| Pdu_ | 协议数据单元(Protocol Data Unit) | Pdu_Info:PDU信息;Pdu_Length:PDU长度。 |
| Can_ | CAN通信模块(CAN Driver) | Can_Pdu:CAN PDU;Can_State:CAN状态。 |
| Lin_ | LIN通信模块(LIN Driver) | Lin_Pdu:LIN PDU;Lin_Config:LIN配置。 |
| Eth_ | 以太网通信模块(Ethernet Driver) | Eth_Frame:以太网帧;Eth_Config:以太网配置。 |
| Dcm_ | 诊断通信管理(Diagnostic Communication Manager) | Dcm_Request:诊断请求;Dcm_Response:诊断响应。 |
| Dem_ | 诊断事件管理(Diagnostic Event Manager) | Dem_Event:诊断事件;Dem_Status:诊断状态。 |
| Nvm_ | 非易失性存储管理(NVRAM Manager) | Nvm_Block:NVM块;Nvm_Data:NVM数据。 |
| EcuM_ | ECU状态管理(ECU Manager) | EcuM_State:ECU状态;EcuM_Mode:ECU模式。 |
| BswM_ | 基础软件管理(Basic Software Manager) | BswM_Mode:BSW模式;BswM_State:BSW状态。 |
| Os_ | 操作系统(Operating System) | Os_Task:OS任务;Os_Alarm:OS报警。 |
| SchM_ | 调度管理(Scheduler Manager) | SchM_Task:调度任务;SchM_Config:调度配置。 |
| Wdg_ | 看门狗管理(Watchdog Manager) | Wdg_Mode:看门狗模式;Wdg_Config:看门狗配置。 |
| Adc_ | 模拟数字转换(ADC Driver) | Adc_Channel:ADC通道;Adc_Value:ADC值。 |
| Gpt_ | 通用定时器(General Purpose Timer) | Gpt_Channel:GPT通道;Gpt_Config:GPT配置。 |
| Mcu_ | 微控制器驱动(Microcontroller Driver) | Mcu_Mode:MCU模式;Mcu_Config:MCU配置。 |
| Port_ | 端口驱动(Port Driver) | Port_Pin:端口引脚;Port_Config:端口配置。 |
| Dio_ | 数字输入输出(Digital I/O Driver) | Dio_Channel:DIO通道;Dio_Config:DIO配置。 |
| Pwm_ | 脉宽调制(PWM Driver) | Pwm_Channel:PWM通道;Pwm_Config:PWM配置。 |
| Icu_ | 输入捕获单元(Input Capture Unit) | Icu_Channel:ICU通道;Icu_Config:ICU配置。 |
| Rte_ | 运行时环境(Runtime Environment) | Rte_Instance:RTE实例;Rte_Config:RTE配置。 |
基本数据类型
在 Std_Types.h 中定义的标准基本数据类型:
1 | typedef unsigned char uint8; // 无符号8位整数 |
布尔类型
AUTOSAR定义了标准的布尔类型:
1 | typedef unsigned char boolean; // 布尔类型 |
状态类型
AUTOSAR定义了标准的状态类型:
1 | typedef uint8 Std_ReturnType; // 标准返回类型 |
复合数据类型
复合数据类型(如结构体、枚举)通常用于表示复杂的数据结构。
-
结构体示例:
1
2
3
4typedef struct {
uint8 channelState; // 通道状态
uint8 channelId; // 通道ID
} ComM_Channel_t; // 通信管理模块的通道类型 -
枚举示例:
1
2
3
4typedef enum {
COM_M_CHANNEL_INACTIVE, // 通道未激活
COM_M_CHANNEL_ACTIVE // 通道激活
} ComM_ChannelState_e; // 通信管理模块的通道状态类型
指针类型
AUTOSAR中通常使用标准化的指针类型:
1 | typedef uint8* Ptr_u8; // 指向uint8的指针 |
数组类型
数组类型通常用于表示一组相同类型的数据:
1 | typedef uint8 Com_SignalArrayType[10]; // 通信信号数组类型 |
常量、宏、模版的名字应该全部大写。如果这些名字由多个单词组成,则单词之间用下划线分隔。
1 |
配置头文件
[!important]
请务必将此文件加入到BSW/Include或ASP/Include文件夹中,该文件为基本类型定义。
1 |
|
函数
命名规则
-
函数的命名规则:
每一个函数名前缀需包含模块名,模块名为小写,与函数名区别开。
1
uartReceive(串口接收)
对于非常简单的程序,可以不加模块名。
-
函数形参
函数的的形参都以下划线_开头,已示与普通变量进行区分,对于没有形参为空的函数(void)括号紧跟函数后面。
1
2
3
4uint32_t uartConvUartBaud(uint32_t _ulBaud)
{
} -
一个函数仅完成一件功能。
-
函数名应准确描述函数的功能,使用动宾词组为执行某操作的函数命名。
避免用含义不清的动词如
process、handle等为函数命名,因为这些动词并没有说明要具体做什么。参照如下方式命名函数。1
2
3void PrintRecord(uint32_t _RecInd);
int32 InputRecord (void);
uint8 t GetCurrentColor(void); -
避免设计五个以上参数函数,不使用的参数从接口中去掉。目的减少函数间接口的复杂度,复杂的参数可以使用结构传递。
-
在调用函数填写参数时,应尽量减少没有必要的默认数据类型转换或强制数据类型转换。因为数据类型转换或多或少存在危险。
-
防止把没有关联的语句放到一个函数中。如下函数就是一种随机内聚。
1
2
3
4
5
6
7void InitVar(void)
{
Rect.length = 0;
Rect.width = 0;/*初始化矩形的长与宽*/
Point.x = 10;
Point.y = 10;/*初始化“点”的坐标*/
}矩形的长、宽与点的坐标基本没有任何关系,故以上函数是随机内聚。应如下分为两个函数:
1
2
3
4
5
6
7
8
9
10void InitRect(void)
{
Rect.length = 0;
Rect.width = 0;/*初始化矩形的长与宽*/
}
void InitPoint(void)
{
Point.x = 10;
Point.y = 10;/*初始化“点”的坐标*/
}
API接口函数的命名遵循严格的规范,以确保代码的可读性、一致性和可维护性。 API接口函数通常用于模块之间的交互,例如操作系统(OS)、通信栈(COM)、诊断栈(DCM)等。以下是 API接口函数命名的详细规则和示例。
-
前缀:模块或服务的缩写(如
Os_、Com_、Dcm_、Bsp或Bsp<模块名>)。 -
动作描述:描述函数的主要操作(如
Init、Start、Get、Set)。 -
对象描述:描述操作的对象(如
State、Value、Mode)。 -
大小写:采用驼峰命名法,首字母大写。
例如:
1 | void BspWdg_Init(void); |
-
前缀:
- 使用模块或服务的缩写作为前缀,例如:
Os_:操作系统(OS)模块。Com_:通信栈(COM)模块。Dcm_:诊断通信管理(DCM)模块。EcuM_:ECU状态管理(ECU Manager)模块。SchM_:调度管理(Scheduler Manager)模块。BspGpio、BspAdc。
- 使用模块或服务的缩写作为前缀,例如:
-
动作描述:
- 描述函数的主要操作,例如:
Init:初始化。Start:启动。Stop:停止。Get:获取数据。Set:设置数据。MainFunction:主函数(周期性调用)。
- 描述函数的主要操作,例如:
-
对象描述:
- 描述函数操作的对象或目标,例如:
State:状态。Value:值。Mode:模式。Status:状态。
- 描述函数操作的对象或目标,例如:
-
大小写:
- 采用驼峰命名法(CamelCase),首字母大写。
BSP层函数名详细设计
-
前缀+模块名称:
-
使用
Bsp_作为前缀,表示属于BSP层的函数。 -
如果BSP模块进一步细分(如GPIO、ADC等),可能会使用更具体的前缀,例如
BspGpio_或BspAdc_。 -
模块名称:指明具体的硬件模块或功能,例如
Gpio、Adc、Pwm等。
-
-
动作描述:
- 描述函数的具体操作,例如
Read、Write、Init、Set等。
- 描述函数的具体操作,例如
-
对象描述:
- 描述函数操作的对象或目标,例如
Pin、Channel、Port等。
- 描述函数操作的对象或目标,例如
-
大小写:
- 采用驼峰命名法(CamelCase),首字母大写。
[!important]
请严格参照以下写法书写BSP函数接口以方便系统移植‼‼‼‼
GPIO模块
-
初始化GPIO:
1
void BspGpio_Init(void);
-
设置GPIO引脚状态:
1
void BspGpio_SetPin(uint32_t Port,uint32_t Pin,uint32_t State);
-
读取GPIO引脚状态:
1
uint8_t* BspGpio_ReadPin(uint32_t Port,uint32_t Pin);
ADC模块
-
初始化ADC:
1
void BspAdc_Init(void);
-
读取ADC通道的值:
1
uint16_t BspAdc_ReadChannel(uint8 Channel);
PWM模块
-
初始化PWM:
1
void BspPwm_Init(void);
-
设置PWM占空比:
1
void BspPwm_SetDutyCycle(uint8 Channel, uint8 DutyCycle);
CAN模块
-
初始化CAN控制器:
1
void BspCan_Init(void);
-
发送CAN消息:
1
void BspCan_WriteMessage(uint8 MessageId, uint8* Data, uint8 Length);
Timer模块
-
初始化定时器:
1
void BspTimer_Init(void);
-
启动定时器:
1
void BspTimer_Start(uint8 TimerId);
Watchdog模块
-
初始化看门狗:
1
void BspWdg_Init(void);
-
刷新看门狗:
1
void BspWdg_Refresh(void);
操作系统(OS)模块
-
启动操作系统:
1
void Os_Start(void);
-
获取任务状态:
1
StatusType Os_GetTaskState(TaskType TaskId, TaskStateRefType State);
通信栈(COM)模块
-
初始化通信栈:
1
void Com_Init(void);
-
发送信号:
1
Std_ReturnType Com_SendSignal(Com_SignalIdType SignalId, const void* SignalData);
-
通信栈主函数:
1
void Com_MainFunction(void);
诊断通信管理(DCM)模块
-
初始化诊断模块:
1
void Dcm_Init(void);
-
处理诊断请求:
1
Std_ReturnType Dcm_ProcessRequest(Dcm_MessageType RequestMessage);
ECU状态管理(EcuM)模块
-
初始化ECU状态管理:
1
void EcuM_Init(void);
-
启动ECU:
1
void EcuM_Start(void);
调度管理(SchM)模块
-
初始化调度管理:
1
void SchM_Init(void);
-
主调度函数:
1
void SchM_MainFunction(void);
存储栈(NVM)模块
-
初始化NVM模块:
1
void Nvm_Init(void);
-
读取数据:
1
Std_ReturnType Nvm_ReadBlock(Nvm_BlockIdType BlockId, void* Data);
看门狗管理(Wdg)模块
-
初始化看门狗:
1
void Wdg_Init(void);
-
刷新看门狗:
1
void Wdg_Refresh(void);
注意事项
- 一致性:确保所有API接口函数的命名风格一致。
- 可读性:函数名应清晰表达其功能,便于开发人员理解。
- 模块化:根据模块的功能划分API接口,避免功能混杂。
- 标准化:遵循AUTOSAR官方文档中的命名规范,确保与AUTOSAR标准兼容。
6.3 函数设计
在 AUTOSAR 中,函数设计规范是确保代码可读性、可维护性和一致性的重要部分。AUTOSAR 的函数设计规范通常基于 MISRA C 标准,并结合汽车电子系统的特殊需求。以下是 AUTOSAR 中函数设计规范的主要内容:
6.3.1. 函数命名规范
命名规则
- 模块前缀:函数名应以模块名作为前缀(如
Can_表示 CAN 模块,Dcm_表示诊断通信管理模块)。 - 动作描述:函数名应清晰描述其功能,使用动词开头。
- 命名风格:使用驼峰命名法(CamelCase)。
示例:
1 | void Can_Init(void); // CAN 模块初始化 |
6.3.2. 函数长度
函数长度限制
- 每个函数的代码行数不应超过 50 行。
- 如果函数逻辑复杂,应将其拆分为多个子函数。
6.3.3. 函数参数
参数数量
- 函数的参数数量不应超过 5 个。
- 如果参数过多,应考虑使用结构体封装。
示例:
1 | typedef struct |
数类型
- 使用 AUTOSAR 定义的标准数据类型(如
uint8、sint16、float32等)。 - 使用
const修饰只读参数。
示例:
1 | void Can_ProcessMessage(const uint8* data, uint8 length); |
参数顺序
- 输入参数在前,输出参数在后。
- 如果函数有返回值,输出参数应放在最后。
示例:
1 | Std_ReturnType Can_ReadMessage(uint8 messageId, uint8* data, uint8* length); |
6.3.4. 返回值
返回值类型
-
使用
Std_ReturnType作为函数返回值类型,表示函数执行状态。1
2
3typedef uint8 Std_ReturnType;
示例:
1 | Std_ReturnType Can_Init(void); |
返回值检查
- 调用函数时,应检查其返回值,确保错误被正确处理。
示例:
1 | Std_ReturnType ret = Can_Init(); |
6.4 函数内部设计
局部变量
- 局部变量应在函数开头声明,并初始化。
- 变量名应清晰表达其用途。
示例:
1 | void Can_ProcessMessage(const uint8* data, uint8 length) |
控制结构
- 使用括号明确条件表达式的优先级。
- 避免使用复杂的嵌套条件语句。
示例:
1 | if ((u8Value > 0) && (u8Value < 100)) |
错误处理
- 在函数内部检查输入参数的有效性,并在发现错误时立即返回。
- 使用
assert检查程序中的假设条件。
示例:
1 | Std_ReturnType Can_SendMessage(uint8 messageId, const uint8* data, uint8 length) |
6.5 函数复用与模块化
复用性
- 将通用功能封装为独立的函数,以便复用。
- 避免在多个函数中重复相同的代码。
示例:
1 | uint8 CalculateChecksum(const uint8* data, uint8 length) |
模块化
- 将功能相关的函数组织在同一个模块中。
- 使用头文件声明模块的接口函数。
示例:
1 | // can_driver.h |