电子产业一站式赋能平台

PCB联盟网

搜索
查看: 66|回复: 0
收起左侧

嵌入式代码常见的容错设计

[复制链接]

499

主题

499

帖子

3322

积分

四级会员

Rank: 4

积分
3322
发表于 2024-9-13 11:38:00 | 显示全部楼层 |阅读模式
关注+星标公众,不错过精彩内容

mffwx2jnc2464014812800.gif

mffwx2jnc2464014812800.gif

作者 | strongerHuang
微信公众号 | strongerHuang
如果一个大型嵌入式项目,代码没有做容错设计,你能想象后果是什么吗?
有经验的朋友肯定能想到,这样的项目会有无数bug,而且有些bug很难查找。
今天就来聊聊嵌入式代码常见的一些容错设计方法。
使用断言(Assert)
什么是Assert断言?这里举一个栗子来说明吧。

有这么一个数组和函数:
  • int Array[5] = {0xA1, 0xB2, 0xC3, 0xD4, 0xE5};
    int Fun(char i){    return Array;}
    假如按下下面方式调用Fun函数,你觉得会出错吗?
  • int a;
    a = Fun(8);
    有经验的朋友肯定都猜到了,在Fun函数中增加断言(Assert)机制,就可以避免出错。

    断言(Assert)是代码中最常见的一种容错设计,很多源码库都能看到断言的身影,比如STM32外设库:
  • void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct){  /* Check the parameters */  assert_param(IS_GPIO_ALL_PERIPH(GPIOx));  assert_param(IS_GPIO_MODE(GPIO_InitStruct->GPIO_Mode));  assert_param(IS_GPIO_PIN(GPIO_InitStruct->GPIO_Pin));  /* ... */}
    明确返回值和错误码
    大家常用的协议栈、外设库、操作系统等,它们的API大多设计的很完美,为函数设计合理的返回值,用于反馈操作的成功或失败。例如,使用0表示成功,非0值表示特定的错误代码。

    比如RTOS创建任务函数:
  • INT8U  OSTaskCreate (void   (*task)(void *p_arg),                     void    *p_arg,                     OS_STK  *ptos,                     INT8U    prio){    OS_STK     *psp;    INT8U       err;#if OS_CRITICAL_METHOD == 3u                 /* Allocate storage for CPU status register               */    OS_CPU_SR   cpu_sr = 0u;#endif
    #ifdef OS_SAFETY_CRITICAL_IEC61508    if (OSSafetyCriticalStartFlag == OS_TRUE) {        OS_SAFETY_CRITICAL_EXCEPTION();        return (OS_ERR_ILLEGAL_CREATE_RUN_TIME);    }#endif
    #if OS_ARG_CHK_EN > 0u    if (prio > OS_LOWEST_PRIO) {             /* Make sure priority is within allowable range           */        return (OS_ERR_PRIO_INVALID);    }#endif    OS_ENTER_CRITICAL();    if (OSIntNesting > 0u) {                 /* Make sure we don't create the task from within an ISR  */        OS_EXIT_CRITICAL();        return (OS_ERR_TASK_CREATE_ISR);    }    /* ... */}为函数设计合理的返回值和错误码,也会让你的代码更健壮,特别是找bug时更容易。
    日志记录
    我们为什么要记录日志?记录详细的日志信息,包括错误发生的时间、位置、原因等,以便在有bug出现时进行追踪和分析。
    我们学嵌入式之初,基本都会学习 printf 这种打印输出的功能,这种打印对应的另一种功能就是日志记录。

    除了存储在本地的日志之外,也可以使用 printf 打印输出至另外终端(比如上位机)进行存储日志。

    致命Bug重启策略
    我们软件遇到一些致命的bug时,比如硬件故障(HardFault)、内存溢出(MemManage)等,这个时候可以选择重启策略。

    当然,重启也要根据项目实际情况,选择什么方式重启,比如:内核复位、系统复位。

    1. 内核复位
    只复位Cortex-M内核,不会复位UART这些片内外设。

    在Cortex-M内核文档中大概有这样的描述:通过设置 NVIC 中应用程序中断与复位控制寄存器(AIRCR)的VECTRESET 位,可只复位处理器内核而不复位其它片上设施。
    内核复位函数(参考内核代码修改而来):
  • void NVIC_CoreReset(void){  __DSB();  SCB->AIRCR  = ((0x5FA                  (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |                 SCB_AIRCR_VECTRESET_Msk);       //置位 VECTRESET  __DSB();  while(1) { __NOP(); }}
    2. 系统复位
    软件复位中的系统复位操作的寄存器位(SYSRESETREQ)不同,复位的对象为整个芯片(除后备区域)。

    系统复位函数:
  • void NVIC_SysReset(void){  __DSB();  SCB->AIRCR  = ((0x5FA                  (SCB->AIRCR & SCB_AIRCR_PRIGROUP_Msk) |                  SCB_AIRCR_SYSRESETREQ_Msk);     //置位 SYSRESETREQ  __DSB();  while(1) { __NOP(); }}
    静态分析工具
    使用静态分析工具检查代码中的潜在问题,如未初始化的变量、内存泄漏、缓冲区溢出等。这些工具可以在编译前发现许多问题,从而提高代码质量。

    虽然这算不上容错设计,但这也是开发过程中重要的一个环节,其作用在一定程度上超过常规的容错设计。

    这里推荐阅读:嵌入式开发常用的代码静态分析工具
    最后,代码bug千千万,除了常规的容错设计,代码规范其实也很重要。
    最最后,你们平时写代码,有考虑哪些容错设计,欢迎留言评论。
    猜你喜欢:
    WiFi6+蓝牙+星闪,三合一开发板,真香!
    Github上热门 C 语言项目汇总!
    嵌入式,可测试性软件设计!
    一些低功耗软件设计的要点!
    嵌入式 C 保护结构体的方式
    实用 | 10分钟教你通过网页点灯
    谈谈嵌入式软件的兼容性!
  • 回复

    使用道具 举报

    发表回复

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则


    联系客服 关注微信 下载APP 返回顶部 返回列表