嵌入式C代码规范要点及基于AutoSar架构BUG分享(持续更新..) BMS1–BMS基础(硬件电路软件配置 原理 项目实例)BMS2–BMS底软HVIL (硬件电路软件配置 原理 项目实例)[万字图解 含ADC PWM Port配置及原理详解]BMS3–BMS充电管理(国标原理项目)AutoSar1–先导篇 AutoSar 模块及分层开发活动详解AutoSar2–BMS编译调试工具介绍AutoSar3–Polyspace 代码常犯错误汇总AutoSar4–AUTOSAR平台OS模块配置AutoSar5–CAN通信栈(Can通讯原理配置实战项目需求)动力总成1 OBC DCDC(万字原理BMS VCU交互国标详解)C代码规范及AutoSar架构BUG分享一、C代码规范1 宏定义1.1 #define X (0u)/(0x00u)2 函数定义2.1定义函数的AUTOSAR宏FUNC(void, X_CODE)X()二、BUG分享1. NM休眠唤醒插枪防呆策略2. 定时器外部清零规范及定时器历史累加问题嵌入式软件定时器的底层原理定时器耦合的三大设计流派符合 AUTOSAR 与主机厂规范的定时器军规一、C代码规范1 宏定义1.1 #define X (0u)/(0x00u)举例Infineon AURIX2G 平台 Std_Type.h/******************************************************************************* ** ** ** Copyright (C) Infineon Technologies (2018) ** ** ** ** All rights reserved. ** ** ** ** This document contains proprietary information belonging to Infineon ** ** Technologies. Passing on and copying of this document, and communication ** ** of its contents is not permitted without prior written authorization. ** ** ** ******************************************************************************** ** ** ** FILENAME : Std_Types.h ** ** ** ** VERSION : 2.0.0 ** ** ** ** DATE : 2018-03-26 ** ** ** ** VARIANT : NA ** ** ** ** PLATFORM : Infineon AURIX2G ** ** ** ** AUTHOR : DL-AUTOSAR-Engineering ** ** ** ** VENDOR : Infineon Technologies ** ** ** ** DESCRIPTION : AUTOSAR Standard Tyes ** ** ** ** SPECIFICATION(S) : AUTOSAR_SWS_StandardTypes.pdf, AUTOSAR Release 4.2.2 ** ** ** ** MAY BE CHANGED BY USER : no ** ** ** *******************************************************************************/#ifndefSTD_TYPES_H#defineSTD_TYPES_H/* Traceability: [cover parentID{9B86C499-F591-4d3c-8A6C-5ED8FCAD72A1}] Include protection [/cover]*//******************************************************************************* ** Includes ** *******************************************************************************//* Traceability: [cover parentID{B6DD2FBA-E4D7-4e6f-9065-124B8D8B5F50}] Files inclusion [/cover]*/#includeCompiler.h#includePlatform_Types.h/******************************************************************************* ** Global Macro Definitions ** *******************************************************************************//* Published information */#defineSTD_TYPES_VENDOR_ID(17u)#defineSTD_TYPES_AR_RELEASE_MAJOR_VERSION(4u)#defineSTD_TYPES_AR_RELEASE_MINOR_VERSION(2u)#defineSTD_TYPES_AR_RELEASE_REVISION_VERSION(2u)#defineSTD_TYPES_SW_MAJOR_VERSION(1u)#defineSTD_TYPES_SW_MINOR_VERSION(0u)#defineSTD_TYPES_SW_PATCH_VERSION(0u)/* [cover parentID{D3B7A33C-16F3-4536-948B-9A6B08314102}]Std_StatusType */#ifndefSTATUSTYPEDEFINED#defineSTATUSTYPEDEFINED#defineE_OK0x00utypedefunsignedcharStatusType;/* OSEK compliance */#endif/* [/cover] */#defineE_NOT_OK0x01u/* [cover parentID{48655FD7-0240-489e-9A11-02B5238A1A86}] STD_HIGH, STD_LOW*/#defineSTD_HIGH0x01u/* Standard HIGH */#defineSTD_LOW0x00u/* Standard LOW *//* [/cover] *//* [cover parentID{80CAA429-4E81-4c18-9C31-F38F97D4B9A2}]STD_ACTIVE, STD_IDLE*/#defineSTD_ACTIVE0x01u/* Logical state active */#defineSTD_IDLE0x00u/* Logical state idle *//* [/cover] *//* [cover parentID{4861BF77-68F1-4433-BF83-505E4DB52C88}]STD_ON, STD_OFF */#defineSTD_ON0x01u/* Standard ON */#defineSTD_OFF0x00u/* Standard OFF *//* [/cover] *//******************************************************************************* ** Global Type Definitions ** *******************************************************************************//* Describes the standard Type Definitions used in the project *//* Standard return type *//* [cover parentID{B22BF253-F56E-4f6e-9EF6-E6F924E968C9}]Std_ReturnType */typedefuint8 Std_ReturnType;/*[/cover]*//* Structure for the Version of the module.This is requested by calling Module name_GetVersionInfo() *//* [cover parentID{267E034F-2D9A-4ef5-9453-2D429CAEE5BC}]Std_VersionInfoType*/typedefstruct{uint16 vendorID;uint16 moduleID;uint8 sw_major_version;uint8 sw_minor_version;uint8 sw_patch_version;}Std_VersionInfoType;/*[/cover]*//******************************************************************************* ** Global Constant Declarations ** *******************************************************************************//******************************************************************************* ** Global Variable Declarations ** *******************************************************************************//******************************************************************************* ** Global Function Declarations ** *******************************************************************************/#endif/* STD_TYPES_H*/0u通常用于值在一般整数上下文中概念上仅为“零”的情况。例如STD_TYPES_SW_MINOR_VERSION和STD_TYPES_SW_PATCH_VERSION是版本号此时简单使用0即可。当值表示字节或位掩码时即使该字节值为零也更推荐使用0x00u。使用十六进制0x…能更明确地表明该值需按字节或位级别解释或表明其属于一组相关常量如 0x01u、0x02u、0x04u 等这些常量均采用十六进制定义。在给定代码片段中E_OK、STD_LOW和STD_IDLE作为状态指示符使用0x00u可与其他相关状态码如E_NOT_OK 0x01u或STD_HIGH 0x01u保持十六进制表示的一致性。编码风格与一致性在AUTOSAR基础软件BSW模块等大型项目中通常存在严格的编码规范如MISRA C和既定惯例。综上所述尽管0u与0x00u在数值和类型上等价但使用0x00u通常意味着十六进制表示能提升可读性——尤其在处理字节级值、状态码或位掩码时——并有助于在模块内保持一致的编码风格。2 函数定义2.1定义函数的AUTOSAR宏FUNC(void, X_CODE)X()举例FUNC(void,X_CODE)X_vidSchedulerInit(uint8 u8X){#if(ASW_DEBUGSTD_ON)X_u8Xu8X;#else(void)u8X;#endifreturn;}FUNC(void, X_CODE)此为定义函数的AUTOSAR宏。区别于一般FUNC(void)函数X_CODE指定函数可执行代码的存储区域。确保X组件的所有代码集中存储于微控制器内存的特定区域。X_vidSchedulerInit(uint8 u8X)函数签名。二、BUG分享1. NM休眠唤醒插枪防呆策略整车厂OEM关于“300秒”的真实策略是什么在新能源汽车国标GB/T和各大主机厂如奇瑞、吉利、长安等的电源管理策略中这个300秒5分钟被称为“插枪防呆超时CC Wakeup Timeout”。它的设计初衷是这样的场景 A防呆 车主把充电枪插上去了触发 CC 唤醒 ECU但车主去跟人聊天了迟迟不刷卡/扫码启动充电。如果不休眠OBC和VCU一直傻醒着几个小时就把小电瓶12V耗亏电了。所以策略规定光插枪不干活只给你5分钟300s的等待时间超时立刻休眠。场景 B正常充电结束 车主插枪10秒后刷卡开始充电。充了2个小时早就远超300s了。现在充满电了VCU 撤销了充电请求Req - No Req。此时系统应该立刻休眠还是再等300s答案是应该立刻休眠严格说是走完 NM 标准退网时间后休眠。为什么 因为充电这个“活儿”已经干完了高压已经断开了这时候每多醒一秒钟都是在浪费 12V 蓄电池的电量没有任何意义再等 5 分钟。问题代码/* 原有错误代码逻辑简述 */if(FALSELoc_bCC_Connected){// 没插枪允许休眠清零300s计时器TIMER_vidClearTimer(CombSatMach_pstrAutoTimer[TMR_NMREQ_2_2_5MIN]);}elseif((ComUsr_u8VcuBMSAcChrgPerm!TRUE)...){// 插了枪且没有充电加热请求检查计时器有没有到300sif(TRUETIMER_bIsAutoTimeout(CombSatMach_pstrAutoTimer[TMR_NMREQ_2_2_5MIN])){Loc_bRes2TRUE;}}else{/* Bug 就在这里只要有充电/加热请求就会疯狂清零300s计时器 */TIMER_vidClearTimer(CombSatMach_pstrAutoTimer[TMR_NMREQ_2_2_5MIN]);}充电/加热请求取消后DUT多等了300s才休眠【现象本质】 需求原意是“300秒”衡量的是插枪后的总闲置时间。只要插上枪超过了300秒且当前没有充/放电/加热请求就应该立刻释放网络休眠。 但实测发现当取消请求Req - No Req时DUT 并没有立刻休眠而是重新从 0 开始等了 300 秒修复后代码if(FALSELoc_bCC_Connected){Loc_bRes2TRUE;TIMER_vidClearTimer(CombSatMach_pstrAutoTimer[TMR_NMREQ_2_2_5MIN]);}else{/* Requirement 2:if CC connectTimer count;Until have no charge/discharge Reqcheak count *//* charge finish-VCU(Req-No Req)-Contactor BK */if((ComUsr_u8VcuBMSAcChrgPerm!TRUE)(ComUsr_u8VcuBMSPreheatKeepWarmAllow!CSM_u8ALLOWACPREHEAT)(ComUsr_u8VcuBMSPreheatKeepWarmAllow!CSM_u8ALLOWKEEPWARM)){if(TRUETIMER_bIsAutoTimeout(CombSatMach_pstrAutoTimer[TMR_NMREQ_2_2_5MIN])){Loc_bRes2TRUE;}}}【场景推演正常充电中】枪插着CC Connected。VCU发来了充电请求ComUsr_u8VcuBMSAcChrgPerm TRUE。此时代码执行到 if(ComUsr_u8VcuBMSAcChrgPerm ! TRUE) 时发现条件为假False既然外层 if 为假里面判断300s的逻辑根本就不会执行。因此Loc_bRes2 永远是 FALSE。只要它为 FALSECSM_bIsNMRelease() 就会返回 FALSE调用 ComM_RequestComMode(…, COMM_FULL_COMMUNICATION) 持续请求网络绝对不会休眠。2. 定时器外部清零规范及定时器历史累加问题比如需求是充放电中在某一状态维持xx秒比如充完电后维持在POWERDOWN或者AFTERRUN模式等待电压泄放60s举例STANDBY - POWERDOWN 的 60s 定时器危险场景当处于充电CHARGERING模式时单片机跑了几个小时。因为没在 STANDBY 状态代码从未清零定时器 TMR_STANDBY_TO_POWERDOWN_LAST_60s。等充电结束一进入 STANDBY一查定时器发现已经几小时60s了状态机会瞬间下电闪穿完全失去 60s 的泄放防抖缓冲作用修复方法只要不在 STANDBY 模式必须在外部将其清零。/* External Reset Timer: if not in STANDBY mode,clear 60s Timer */if(CSM_u8WorkMode!CSM_u8_WORKING_STANDBY){TIMER_vidClearTimer(CombSatMach_pstrAutoTimer[TMR_STANDBY_TO_POWERDOWN_LAST_60s]);}嵌入式软件定时器的底层原理在 AUTOSAR 应用层中我们通常不会使用硬件定时器中断Hardware Timer ISR而是使用基于 OS Task 调度周期的软件定时器Software Timer。1. 软件定时器的基本构成typedefstruct{uint8 u8Index;boolean bIsTimeout;boolean bTiming;uint32 u32Timer;uint32 u32Counter;}TIMER_tstrAutoTimer;typedefstruct{uint8 u8Index;boolean bIsTimeout;uint32 u32Timer;uint32 u32Counter;}TIMER_tstrTimer;TIMER_tstrTimer CombSatMach_pstrTimer[TMR2_MAX]{{TMR2_SYS_REBOOT_MORETHAN_1S_FIT,FALSE,0u,CNT_1S_IN_TASK_10MS}};TIMER_tstrAutoTimer CombSatMach_pstrAutoTimer[TMR_MAX]{{TMR_OBC_FAULT_TO_POWERDOWN_LAST_5s,FALSE,FALSE,0u,CNT_5S_IN_TASK_50MS},{TMR_OBC_STANDBY_TO_POWERDOWN_LAST_60s,FALSE,FALSE,0u,CNT_60S_IN_TASK_50MS},{TMR_NMREQ_2_2_5MIN,FALSE,FALSE,0u,CNT_300S_IN_TASK_50MS},{TMR_NMREQ_2_3_60s,FALSE,FALSE,0u,CNT_60S_IN_TASK_50MS},{TMR_DIAG_PROG_WAIT_100ms,FALSE,FALSE,0u,CNT_100MS_IN_TASK_50MS},{TMR_OBC_PWRDN_TO_STBY_500ms,FALSE,FALSE,0u,CNT_500MS_IN_TASK_50MS},{TMR_OBC_PWRDN_TO_AFTERRUN_120s,FALSE,FALSE,0u,CNT_60S_IN_TASK_50MS*2}/* 120s */};FUNC(boolean,TIMER_CODE)TIMER_bIsTimeout(TIMER_tstrTimer*pstrTimer){if(pstrTimer!NULL_PTR){if(pstrTimer-bIsTimeout!TRUE){if(pstrTimer-u32TimerpstrTimer-u32Counter){pstrTimer-bIsTimeoutTRUE;}else{pstrTimer-u32TimerpstrTimer-u32Timer1u;}}}returnpstrTimer-bIsTimeout;}FUNC(void,TIMER_CODE)TIMER_vidClearTimer(TIMER_tstrTimer*pstrTimer){if(pstrTimer!NULL_PTR){pstrTimer-bIsTimeoutFALSE;pstrTimer-u32Timer0u;}}FUNC(boolean,TIMER_CODE)TIMER_bIsAutoTimeout(TIMER_tstrAutoTimer*pstrTimer){boolean Loc_bRetFALSE;if(pstrTimer!NULL_PTR){Loc_bRetpstrTimer-bIsTimeout;pstrTimer-bTimingTRUE;}returnLoc_bRet;}FUNC(void,TIMER_CODE)TIMER_vidAutoTimerRuning(TIMER_tstrAutoTimer*pstrTimer){if(pstrTimer!NULL_PTR){if(pstrTimer-bTimingTRUE){if(pstrTimer-u32TimerpstrTimer-u32Counter){pstrTimer-bIsTimeoutTRUE;}else{pstrTimer-u32TimerpstrTimer-u32Timer1u;}}else{pstrTimer-u32Timer0u;pstrTimer-bIsTimeoutFALSE;}pstrTimer-bTimingFALSE;}return;}}比如上述示例中 TIMER_tstrAutoTimer 结构体就是一个非常标准的软件定时器封装它通常包含三个核心元素当前计数值Counter记录已经过去了多少个周期。目标阈值Threshold / Timeout需要等待的总周期数。比如在 50ms 的 Task 中等待 5s 的阈值就是 5000 / 50 100。使能/状态标志Enable/Status Flag标记这个定时器是在运行中还是处于空闲/超时状态。2. “滴答Tick”驱动机制AUTOSAR OS 会按固定的周期如 10ms, 50ms调用Runnable。 在这个函数内部调用 TIMER_vidAutoTimerRuning() 时它只做极其简单的加法if(Timer-EnableTRUE){Timer-Counter;if(Timer-CounterTimer-Threshold){Timer-IsTimeoutTRUE;}}定时器耦合的三大设计流派为什么代码之前会出 Bug本质上是因为 C 语言没有原生的面向对象OOP特性无法自动管理状态的“进入Entry”和“退出Exit”动作。管理软件定时器通常有以下三种流派流派 1局部自理派初学者常用不推荐在每个状态内部自己维护静态变量。caseSTATE_POWERDOWN:staticuint16 s_u16Timer0;s_u16Timer;if(s_u16Timer120){s_u16Timer0;// 必须手动清零StateSTATE_AFTERRUN;}break;致命缺点如果状态机因为外部中断被强行切走到其他状态比如发生 FAULTs_u16Timer 就会保留上次计到一半的值。下次再进入 POWERDOWN 时就会发生“历史累加闪穿”。流派 2事件驱动派严谨AUTOSAR 推荐严格区分状态的 ENTRY进入、DURING运行中和 EXIT退出动作。// 当检测到需要切换状态时if(Condition_Powerdown_Met){TIMER_vidClearTimer(Timer_120s);// ENTRY动作进入新状态前把定时器清零StateSTATE_POWERDOWN;}// 在处理该状态时caseSTATE_POWERDOWN:TIMER_vidAutoTimerRuning(Timer_120s);// DURING动作只在当前状态累加if(TIMER_bIsAutoTimeout(Timer_120s)){StateSTATE_AFTERRUN;}break;优点逻辑极其清晰定时器绝对不会在后台乱跑。流派 3全局扫盲派目前工程采用的架构把所有的定时器累加动作放到函数的最后统一执行TIMER_vidAutoTimerRuning而在特定状态内部只做超时判断。必须配套的防护法则如果不在此刻必置为零这正是我们之前修复 Bug 时补上的逻辑。// 全局防护墙外部清零if(State!STATE_POWERDOWN){TIMER_vidClearTimer(Timer_120s);// 只要我不在这个状态你就必须归零}符合 AUTOSAR 与主机厂规范的定时器军规主机厂如奇瑞、吉利、长城等在做代码审查Code Review和静态分析QAC/Polyspace时对定时器有极严的规范要求规范 1绝对禁止阻塞式延时No Blocking Delay在 AUTOSAR OS 环境下绝对禁止使用 while(time 1000) 这种死循环延时。原因OS 任务是协同调度或抢占式的一个 Task 死等会导致同优先级或低优先级任务全部饿死直接触发 Watchdog看门狗复位。规范 2魔术数字的消除No Magic Numbers不能在代码里直接写 if(Counter 100)。原因如果将来 OS Task 的调度周期从 50ms 改成了 10ms原本的 5s 延时就会变成 1s引发严重的整车事故。比如使用宏定义 {TMR_OBC_PWRDN_TO_AFTERRUN_120s, FALSE, FALSE, 0u, CNT_60S_IN_TASK_50MS * 2}。 这使得无论 Task 周期怎么变只需修改底层的基准宏即可业务层无需修改。规范 3防范整型溢出Integer Overflow Protection如果是使用绝对时间戳计算延时比如 if(GetTick() TargetTick)很容易在 49 天后发生 32位无符号整数溢出导致严重 Bug。主机厂强制要求如果必须用系统 Tick必须使用减法进行无符号回绕计算 if((uint32)(CurrentTick - StartTick) TimeoutTick)。可以通过自定义结构体当 IsTimeout TRUE 时停止累加天然避开了溢出问题。规范 4功能安全ISO 26262的容错机制定时器不能成为单点故障源。Jitter时间抖动50ms 的 Task 在重负载下可能会有 ±5ms 的抖动。如果对时间精度要求极高如逆变器 PWM 控制不能依赖应用层的 Task 定时器必须交到底层 MCALGPT/GTM 模块用硬件定时器处理。失效安全Fail-Safe像 POWERDOWN高压下电这种涉及人身安全的状态。120s 超时是作为“兜底Timeout Backup”存在的。正常的逻辑是高压降到安全阈值以下立即下电只有当主动泄放回路损坏时才硬等 120s 然后强行下电。这种**“条件达成优先超时兜底在后”的双重判定是典型的高等级功能安全设计。四、 总结培养“上帝视角”在阅读和编写包含定时器的状态机代码时一定要培养出“时间轴上的上帝视角”**诞生它在何时、什么条件下开始计时是否被正确复位为0存活它在什么环境下累加有没有在不需要它的时候偷偷累加死亡/触发它超时后触发什么动作触发后是否不再影响后续逻辑