嵌入式 MCU 的 Class B 安全功能实现

前言

在家用电器及类似电气控制设备的设计和制造中,安全性始终是至关重要的。全球标准化机构,如国际电工委员会(IEC)、美国保险商实验室(UL)和加拿大标准协会(CSA),制定了相关安全标准,以确保电器的可靠性和安全性。其中,IEC60730 标准为电器设备的自动控制系统提供了一套全面的规范,旨在确保设备在各种使用场景中的安全性、可靠性和性能。尽管该标准最初主要针对家用电器,如洗衣机、冰箱和烤箱,但其应用范围已经扩展到商业和某些工业设备。如今,TUV、VDE(主要在欧洲)、UL及CSA(主要在美国和加拿大)等机构均认可并要求在认证过程中应用这一标准。

IEC60730 标准的 A、B、C三类功能

IEC60730 标准定义了A、B、C三类功能,分别关注于设备的使用便利性、安全性和特殊危险预防。这些分类帮助设计人员确保设备在各种操作条件下能够安全可靠地运行,为用户提供了更高的操作安全和舒适体验。

  • A类功能是指那些用于一般自动控制目的的设备,这些设备的主要目标是提高使用便利性和效率,而不直接涉及到设备的基本安全保障。A类功能的设计通常关注设备的操作舒适性和用户友好性。如智能家居系统中的智能灯光控制,可以通过定时器或传感器自动调节室内灯光。家用洗衣机的洗涤程序控制提供选择不同的洗涤模式,如快速洗、节能洗等。
  • B类功能是指那些直接涉及到设备安全的自动控制功能。这些功能的主要目标是防止设备在使用过程中出现可能导致用户受伤或设备损坏的情况。比如烤箱或电热水壶的热切断装置,防止烤箱或电热水壶等设备过热,当温度超过设定值时自动切断电源,以避免火灾或设备损坏。
  • C类功能是指那些用于防止特殊危险、或更高风险的自动控制功能,特别是在可能出现爆炸、火灾等严重事故的环境中。如热水器的压力保护装置,实时监控热水器内部的压力,防止因压力过高而导致的爆炸或水箱损坏。电动窗帘的安全装置,在窗帘升降过程中,如果感应到障碍物,自动停止或反向操作以防止夹伤等等。

IEC60730 标准认证对软硬件的要求

Class A、B 和 C 等级反映了对设备安全性、可靠性和错误处理能力的不同要求。Class A 认证适用于那些对安全性和可靠性要求较低的设备,Class B 认证针对的是那些需要较高安全性但不如 Class C 严格的自动电气控制装置,Class C 的要求比 Class B 更高,涵盖了更广泛的安全性和可靠性方面,确保设备在更苛刻的环境下也能稳定、安全地运行。

Class A:

  • 硬件要求:基础的电气安全和机械强度,主要确保设备在正常条件下的稳定运行。
  • 软件要求:实现基本功能并提供用户友好的操作界面。错误检测和恢复能力要求较低,关注点在于操作的简便性和基本功能的实现。
  • 安全上的要求主要看硬件设计,软件正常业务通常足以满足要求。

Class B:

  • 硬件要求:提升了电气安全性,要求更高的耐用性和抗干扰能力。
    • 绝缘和保护:必须确保设备的绝缘设计满足安全标准,以防电气冲击和短路。
    • 机械强度:硬件应具备足够的机械强度,能抵御正常使用中的物理压力或碰撞。
    • 组件需经得起长期使用中的磨损和老化,确保长期的安全运行。
    • 必须符合电气安全标准,如电气绝缘、电气间隙等要求,避免出现过热或短路问题。
    • 硬件设计需考虑电磁兼容性(EMC),避免或减轻电磁干扰(EMI)对设备功能的影响。
  • 软件要求:要求较高的错误检测和恢复能力,致力于实现高水平的功能安全和长期可靠性。
    • 软件设计需要满足功能安全要求,包括对故障条件的处理能力和错误检测机制。
    • 应用程序应能有效处理意外的操作错误,避免因软件故障导致的安全问题。
  • 安全上的要求除了看硬件设计,还要考虑硬件失效后软件的保护,以及软件的周期性自检以保证软件保护功能能正常起作用。

Class C:

  • 硬件要求:重点在于特殊危险预防,要求额外的防护措施,以应对潜在的特殊风险,如高温、高湿等。
  • 软件要求:针对特殊危险情境的软件处理,如应对极端条件下的功能失效。实现额外的错误处理和预防机制,确保在特殊情况下仍能保持功能安全。
  • 无论硬件设计还是软件设计,都要提供更多重的保护机制,比如软硬件的一重保护失效后,有第二重保护防范。

Class B 安全功能设计

在嵌入式设备的设计中,安全功能的定义往往是基于特定业务场景而拆分出来的,这种方法通常满足了初步的安全需求,例如移动机器人,机器人在自动导航时遇到障碍物(如人)会立即停止,这一功能是符合基本的安全要求的。然而,这种基于业务场景的安全定义通常只是一个起点,它并未考虑到更深层次的问题和潜在的安全隐患。例如,如果机器人的雷达传感器发生故障,导致其无法准确感应到障碍物,或者控制机器移动的轮毂失控,那么原本设计良好的安全功能就会失效。这种问题的存在说明,安全功能的设计不仅需要关注单一场景的正确实现,还必须在系统层面上考虑到各种可能的故障模式和失效情况。

为应对这些复杂的安全挑战,需要从更高级别的认证标准(如Class B)出发,细化安全功能的分类,以全面覆盖潜在的故障模式和风险。
例如,MCU(微控制单元)自检功能可以确保处理器在正常工作状态下进行自我监测,从而保证系统的稳定性。同时,通信检测功能能够确保当雷达检测到障碍物时,相关的控制指令(如轮毂停止)能够及时且准确地传递。轮毂的编码器检测,能够确保轮毂真实地移动或停止等等,这些措施帮助确保在复杂或异常情况下,系统的安全功能仍能有效地保护人身安全。

具体来说,我们可以从以下方面实现安全功能:

安全功能说明
CPU 寄存器自检包括 R13(栈指针)、R14(链接寄存器)和 PSP(进程栈指针)
PC程序计数器防止程序计数器丢失或终止
看门狗自检避免出现复位时间太快、太慢或卡滞不工作的情况
非易失性存储器完整性检查主要是 FLASH,避免目标 FLASH 区域被修改或损坏
易失性存储器检查主要是 RAM,检查 RAM 是否可正常读写
系统时钟频率检查检查外部输入时钟源是否准确
IO 外设检查涉及安全功能的输入输出信号,如限位传感器信号、PWM 脉冲信号
ADC 检查涉及安全功能的模拟信号量,如温度、电压、电流
中断检查避免中断频率过高或过低或无中断响应
通信检查涉及安全功能的通信,如基于 SPI、UART、CAN 通信的轮毂电机控制

安全功能软件库

在实际开发中,部分 MCU 厂商会提供符合认证要求的安全功能包,以便用户快速实现 MCU 功能自检。例如,STM32 的 STL 软件包。
在这里插入图片描述
经过认证的 STM32 STL 固件包由下列软件模块组成:
• CPU寄存器测试
• 系统时钟监控
• RAM功能检查
• Flash CRC完整性检查
• 看门狗自检
• 栈上溢监控

借助厂商提供的安全功能固件包,我们可以针对性地开发自检程序,从而加速认证过程。

启动自检与运行自检

在复位微控制器后,首次检查应包括在初始化阶段运行启动自检。这一过程在应用业务尚未启动之前,对 MCU 相关组件(CPU寄存器、看门狗、Flash完整性、RAM功能、系统时钟)进行全面的检测,确保系统的基础功能都处于最佳状态。

启动自检结构:

在这里插入图片描述

运行时测试是在主循环中定期执行的测试块。除了包含 MCU 启动自检相关的测试(其中部分启动自检功能可能在运行时检测时会简化),还应包括应用业务中的安全功能相关测试。这些测试应涵盖与安全功能相关的 IO、ADC、中断、通信等模块,以确保这些功能在运行期间的正确性和可靠性。

运行时自检结构:
在这里插入图片描述

CPU 寄存器自检

CPU启动自检检查内核标记、寄存器和栈指针的正确功能。如果发现任何错误,应进行故障
处理并上报异常。

具体实现:从 ACC 寄存器开始,用 0x55、0xAA分别填充全部 CPU 寄存器(可能引起 CPU 工作异常的特殊寄存器除外),再读出对比,测试读写是否正常。
在这里插入图片描述
以下是ST提供的部分CPU自检代码:

  /********************/
  /* CPU Test modules */
  /********************/

#ifdef ARTI_FAILING_CPU_TM
  /* Artificial failing feature -
     when activated, it forces the STL outputs to predefined values */
  ArtifFailing.aCpuTmStatus[0] = STL_PASSED;
  ArtifFailing.aCpuTmStatus[1] = STL_PASSED;
  ArtifFailing.aCpuTmStatus[2] = STL_FAILED;
  STL_SCH_StartArtifFailing(&ArtifFailing);
#endif /* ARTI_FAILING_CPU_TM */

  /* CPU TM1L */
  if (STL_SCH_RunCpuTM1L(&StlCpuTm1LStatus) != STL_OK)
  {
    FailSafe_Handler(TM1L_ERR_CODE + DEF_PROG_OFFSET);
  }
  if (StlCpuTm1LStatus != STL_PASSED)
  {
    FailSafe_Handler(TM1L_ERR_CODE);
  }
  /* CPU TM7 */
  if (STL_SCH_RunCpuTM7(&StlCpuTm7Status) != STL_OK)
  {
    FailSafe_Handler(TM7_ERR_CODE + DEF_PROG_OFFSET);
  }
  if (StlCpuTm7Status != STL_PASSED)
  {
    FailSafe_Handler(TM7_ERR_CODE);
  }
  /* CPU TMCB */
  if (STL_SCH_RunCpuTMCB(&StlCpuTmCBStatus) != STL_OK)
  {
    FailSafe_Handler(TMCB_ERR_CODE + DEF_PROG_OFFSET);
  }
  if (StlCpuTmCBStatus != STL_PASSED)
  {
    FailSafe_Handler(TMCB_ERR_CODE);
  }

#ifdef ARTI_FAILING_CPU_TM
  STL_SCH_StopArtifFailing();
#endif /* ARTI_FAILING_CPU_TM */

看门狗启动自检

看门狗自检基于复位状态寄存器内容,不同的复位原因会有相应的寄存器标志。在系统正常上电时,IWDG 和 WWDG 都应处于未触发状态。首先进行 IWDG 的复位测试,将 IWDG 设置为最短复位周期,清除所有复位标志后,进入等待状态。经过设定时间后,IWDG 应触发系统复位。复位重启后,IWDG 标志应被设置,并且应该是唯一的复位原因,否则需清除所有复位标志并重新开始检测。随后进行 WWDG 测试,将 WWDG 设置为最短复位周期,等待系统再次触发复位,复位重启后,IWDG 和 WWDG 寄存器标志均被设置时,测试才算通过,否则需清除所有复位标志并重新开始检测。测试通过后记得清除所有复位标志。
在这里插入图片描述
以下是ST提供的看门狗自检代码:

/******************************************************************************/
/**
  * @brief  Verifies the watchdog by forcing watchdog resets
  * @param  : None
  * @retval : None
  */
void STL_WDGSelfTest(void)
{
  /* ==============================================================================*/
  /* MISRA violation of rule 12.4 - side effect of && and || operators ignored */
  #ifdef __IAR_SYSTEMS_ICC__  /* IAR Compiler */
    #pragma diag_suppress=Pm026
  #endif /* __IAR_SYSTEMS_ICC__ */

  #ifdef STL_VERBOSE_POR  
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)  != RESET) printf("Pin reset \r\n");
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST)  != RESET) printf("POR reset \r\n");
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST)  != RESET) printf("SW reset \r\n");
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) printf("IWDG reset \r\n");
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET) printf("WWDG reset \r\n");
    if (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST) != RESET) printf("LP reset \r\n");
  #endif /* STL_VERBOSE_POR */

  /* start watchdogs test if one of the 4 conditions below is valid */
  if ( (__HAL_RCC_GET_FLAG(RCC_FLAG_PORRST) != RESET)\
   ||  (__HAL_RCC_GET_FLAG(RCC_FLAG_SFTRST) != RESET)\
   ||  (__HAL_RCC_GET_FLAG(RCC_FLAG_LPWRRST) != RESET)\
   || ((__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST) != RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) == RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) == RESET)))
  {
    #ifdef STL_VERBOSE_POR
      printf("... Power-on or software reset, testing IWDG ... \r\n");
    #endif  /* STL_VERBOSE_POR */

    #if defined(STL_EVAL_MODE)
      /* IWDG at debug mode */
      __DBGMCU_CLK_ENABLE();
      __DBGMCU_FREEZE_IWDG();
    #endif  /* STL_EVAL_MODE */

    /* Clear all flags before resuming test */
    __HAL_RCC_CLEAR_FLAG();

    /* Setup IWDG to minimum period */
    IwdgHandle.Instance = IWDG;
    IwdgHandle.Init.Prescaler = IWDG_PRESCALER_4;
    IwdgHandle.Init.Reload = 1U;
    #ifdef IWDG_FEATURES_BY_WINDOW_OPTION
      IwdgHandle.Init.Window = IWDG_WINDOW_DISABLE;
    #endif /* IWDG_FEATURES_BY_WINDOW_OPTION */
    /* Initialization */
    HAL_IWDG_Init(&IwdgHandle);

    /* Wait for an independent watchdog reset */
    while(1)
    { }
  }
  else  /* Watchdog test or software reset triggered by application failure */
  {
    /* If WWDG only was set, re-start the complete test (indicates a reset triggered by safety routines */
    if ((__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)  != RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) == RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET))
    {
      __HAL_RCC_CLEAR_FLAG();
      #ifdef STL_VERBOSE_POR
        printf("... WWDG reset, re-start WDG test ... \r\n");
      #endif  /* STL_VERBOSE_POR */
      NVIC_SystemReset();
    }
    else  /* If IWDG only was set, continue the test with WWDG test*/
    {
      if ((__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)  != RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) == RESET))
      { /* If IWDG only was set, test WWDG*/
        #ifdef STL_VERBOSE_POR
          printf("... IWDG reset from test or application, testing WWDG\r\n");
        #endif  /* STL_VERBOSE_POR */
          
        #if defined(STL_EVAL_MODE)
          /* WWDG at debug mode */
          __DBGMCU_CLK_ENABLE();
          __DBGMCU_FREEZE_WWDG();
        #endif  /* STL_EVAL_MODE */
          
         /* Setup WWDG to minimum period */
        __WWDG_CLK_ENABLE();
        WwdgHandle.Instance = WWDG;
        WwdgHandle.Init.Prescaler = WWDG_PRESCALER_1;
        WwdgHandle.Init.Counter = 64U;
        WwdgHandle.Init.Window = 63U;
        WwdgHandle.Init.EWIMode = WWDG_EWI_DISABLE;
        HAL_WWDG_Init(&WwdgHandle);

        while(1)
        { }
      }
      else  /* If both flags IWDG & WWDG flags are set, means that watchdog test is completed */
      {
        if ((__HAL_RCC_GET_FLAG(RCC_FLAG_PINRST)  != RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_IWDGRST) != RESET) && (__HAL_RCC_GET_FLAG(RCC_FLAG_WWDGRST) != RESET))
        {
          __HAL_RCC_CLEAR_FLAG();
          #ifdef STL_VERBOSE_POR
            printf("... WWDG reset, WDG test completed ... \r\n");
          #endif  /* STL_VERBOSE_POR */
        }
        else  /* Unexpected Flag configuration, re-start WDG test */
        {
          __HAL_RCC_CLEAR_FLAG();
          #ifdef STL_VERBOSE_POR
            printf("...Unexpected Flag configuration, re-start WDG test... \r\n");
          #endif  /* STL_VERBOSE_POR */
        NVIC_SystemReset();
        } /* End of Unexpected Flag configuration */
      } /* End of normal test sequence */
    } /* End of partial WDG test (IWDG test done) */
  } /* End of part where 1 or 2 Watchdog flags are set */

  #ifdef __IAR_SYSTEMS_ICC__  /* IAR Compiler */
    #pragma diag_default=Pm026
  #endif /* __IAR_SYSTEMS_ICC__ */
  /* ==============================================================================*/
}

Flash存储器完整校验和自检

ST 的 Flash 自检主要是通过在编译时计算 bin 文件整个用户区 FLASH 的 CRC 校验值,并通过 ST 提供的 CRC 预计算工具 STM32CubeProgrammer 存储在 bin 文件用户区 Flash 末尾地址位置,然后烧录进芯片 Flash。

  • 程序刚启动时,用同样的算法重新计算整个 FLASH 的 CRC 校验值(不包括前面存储在 FLASH 末尾位置的 CRC 校验值),并与存储在 FLASH 中的 CRC 校验值做比较。
  • 在运行过程周期检测时,则是对 FLASH 分块逐次计算出最终的 CRC 校验值;将最终结果与正确的CRC 校验值做比较。
    在这里插入图片描述

以下是ST提供的部分Flash启动自检代码:

/*********************/
  /* FLASH Test Module */
  /*********************/

#ifdef ARTI_FAILING_FLASH_TM
    /* forced STL_FAILED status simulates the TM configuration error and
       STL_NOT_TESTED status simulates TM run error */
    ArtifFailing.FlashTmStatus = STL_FAILED;
  STL_SCH_StartArtifFailing(&ArtifFailing);
#endif /* ARTI_FAILING_FLASH_TM */

  /* The following configuration tests the entire program in the flash, in one shot */
  STL_MemSubset_t FlashSubsetFullTest = {
      .StartAddr = TEST_ROM_START_ADDR,
      .EndAddr   = TEST_ROM_END_ADDR,
      .pNext     = NULL
  };
  STL_MemConfig_t FlashConfigFullTest = {
      .NumSectionsAtomic = UINT32_MAX,
      .pSubset           = &FlashSubsetFullTest
  };
  if (STL_SCH_InitFlash(&StlFlashStatus) != STL_OK)
  {
    FailSafe_Handler(TMF_ERR_CODE + DEF_PROG_OFFSET);
  }
  if (STL_SCH_ConfigureFlash(&StlFlashStatus, &FlashConfigFullTest) != STL_OK)
  {
    FailSafe_Handler(TMF_ERR_CODE + DEF_PROG_OFFSET);
  }
  else if (StlFlashStatus != STL_NOT_TESTED)
  {
    FailSafe_Handler(TMF_ERR_CODE);
  }
  if (STL_SCH_RunFlashTM(&StlFlashStatus) != STL_OK)
  {
    FailSafe_Handler(TMF_ERR_CODE + DEF_PROG_OFFSET);
  }
  else if (StlFlashStatus != STL_PASSED){
    FailSafe_Handler(TMF_ERR_CODE);
  }
  StlFlashStatus = STL_NOT_TESTED;

#ifdef ARTI_FAILING_FLASH_TM
  STL_SCH_StopArtifFailing();
#endif /* ARTI_FAILING_FLASH_TM */

运行时 Flash 分块逐步计算获取最终 CRC,与启动时计算整个 Flash CRC 的区别在于 计算区域NumSectionsAtomic 设定为每次 1KB 而不是最大值 UINT32_MAX。

  /* FLASH test Config
     *****************
     In this example, single continuous area (subset) is tested under unique configuration setting.
     User can apply wider set of subsets tested sequentially to cover separated memory areas 
     as well as different configurations to be applied for selected subset(s) - see UM */ 
  FlashSubsetRunTime.StartAddr = TEST_ROM_START_ADDR;
  FlashSubsetRunTime.EndAddr = TEST_ROM_END_ADDR;
  FlashSubsetRunTime.pNext = NULL;
  FlashConfigRunTime.pSubset = &FlashSubsetRunTime;
  FlashConfigRunTime.NumSectionsAtomic = 1; /* split test into sections of 1kB */

完整 RAM March-C自检

RAM自检采用 March-C 算法,为了不影响 MCU 的运行,将 RAM 分成很多小块,每次测试其中一块, 先将要测试的块清零,然后按位逐位置1,每置一位,测试该位是不是1,是就继续,不是就报错;全部置完后,再逐位清0,每清一个位,测试该位清0是不是0,如果是就正确,不是就报错。如果是对工作区的 RAM,数据需要保留,需要在 RAM 中开一个安全保留区(比如一块4字节大小的安全区域),先对安全保留区 March C,然后把要测试的区的数据复制进安全区,再对要测试的工作区进行 March-C,测试-- 复制进安全区-- 测试–复制进安全区… 完成整个空间的测试。

RAM自检也分为启动自检和运行自检两部分:

  • RAM 在启动自检的过程中会检测全部的 RAM。在下图所示的 6个循环中,March-C 算法会交替检查整个RAM空间,并用背景模式(值0x00000000)和反向背景模式(值0xFFFFFFFF)逐字填充。前三个循环按地址的递增顺序执行,后三个循环按相反的顺序执行。填充后读取出来比较看值是否相等。
  • RAM 在运行自检的过程中只检测部分 RAM,也就是局部分块分多步检测,每个测试的内存块都始终与测试上一步和下一步的两个附加的相邻字重叠。目前主要检测实际使用区域范围内的 RAM。
    在这里插入图片描述

以下是ST提供的部分RAM启动自检代码:

 /********************/
  /* RAM Test module  */
  /********************/

#ifdef ARTI_FAILING_RAM_TM
    /* forced STL_FAILED status simulates the TM configuration error and
       STL_NOT_TESTED status simulates TM run error */
    ArtifFailing.RamTmStatus = STL_FAILED;
  STL_SCH_StartArtifFailing(&ArtifFailing);
#endif /* ARTI_FAILING_RAM_TM */

  /* The following configuration tests the entire user-defined range of RAM at once */
  STL_MemSubset_t RamSubsetFullTest = {
      .StartAddr = TEST_RAM_START_ADDR,
      .EndAddr   = TEST_RAM_END_ADDR_FULL,
      .pNext     = NULL
  };
  STL_MemConfig_t RamConfigFullTest = {
      .NumSectionsAtomic = UINT32_MAX,
      .pSubset = &RamSubsetFullTest
  };

  if (STL_SCH_InitRam(&StlRamStatus) != STL_OK)
  {
    FailSafe_Handler(TMR_ERR_CODE + DEF_PROG_OFFSET);
  }
  if (STL_SCH_ConfigureRam(&StlRamStatus, &RamConfigFullTest) != STL_OK)
  {
    FailSafe_Handler(TMR_ERR_CODE + DEF_PROG_OFFSET);
  }
  else if (StlRamStatus != STL_NOT_TESTED)
  {
    FailSafe_Handler(TMR_ERR_CODE);
  }
  if (STL_SCH_RunRamTM(&StlRamStatus) != STL_OK)
  {
    FailSafe_Handler(TMR_ERR_CODE + DEF_PROG_OFFSET);
  }
  else if (StlRamStatus != STL_PASSED)
  {
    FailSafe_Handler(TMR_ERR_CODE);
  }
  StlRamStatus = STL_NOT_TESTED;

#ifdef ARTI_FAILING_RAM_TM
  STL_SCH_StopArtifFailing();
#endif /* ARTI_FAILING_RAM_TM */

运行时 RAM 分块测试 RAM 区域,与启动时检测整个 RAM 的区别在于检测区域 NumSectionsAtomic 设定为每次128字节而不是最大值 UINT32_MAX。

   /* RAM test Config
     ***************
     In this example, single continuous area (subset) is tested under unique configuration setting.
     User can apply wider set of subsets tested sequentially to cover separated memory areas 
     as well as different configurations to be applied for selected subset(s) - see UM */ 
  RamSubsetRunTime.StartAddr = TEST_RAM_START_ADDR;
  RamSubsetRunTime.EndAddr = TEST_RAM_START_ADDR + TEST_RAM_SECTION_NB_RUN * RAM_SECTION_SIZE - 1;
  RamSubsetRunTime.pNext = NULL;
  RamConfigRunTime.pSubset = &RamSubsetRunTime;
  RamConfigRunTime.NumSectionsAtomic = 1; /* split test into sections of 128 bytes */
  /* RamConfig.NumSectionsAtomic = (2 * TEST_RAM_SECTION_NB);  */  /* - use this setting for one shot */

时钟自检

时钟自检的检测方法是采用两个独立时钟源交叉检查来进行测量,一个已知的时钟源做为定时器时钟输入,另一个被测试的时钟源做为定时器的外部捕获通道输入源。

例如,使用内部低速RC振荡器时基LSI (32.768KHz)可检测外部晶振(HSE)的频率是否正确。根据定时器时钟频率 LSI 和定时器脉冲捕获计数,计算出捕获通道输入信号 HSE 的实际频率。接着测试检查 HSE 率是否在预期范围内(标称值的±25%),如果发现差别较大或 HSE 信号丢失,或测量中断消失,则 CPU 时钟源会立即切换回 HIS 且 HSE 返回故障状态,否则,测试返回至正常状态。

在这里插入图片描述
以下是ST提供的 Clock 自检代码:

/* Value of the system clock frequency at run time in Hz */
#define SYSTCLK_AT_RUN (uint32_t)(64000000uL)
/* Value of the Internal LSI oscillator in Hz */
#define LSI_Freq    ((uint32_t)32000uL)
/* CLK frequency above this limit considered as harmonics at case of HSE */
#define CLK_LimitHigh(fcy) ((uint32_t)(((fcy)/LSI_Freq)*8u*5u)/4u) /* (Value + 25%) */
/* CLK frequency below this limit considered as sub-harmonics at case of HSE */
#define CLK_LimitLow(fcy) ((uint32_t)(((fcy)/LSI_Freq)*8u*3u)/4u)  /* (Value - 25%) */
/* if SYSCK is derived from HSI, the upper fixed clock limits can be set more severe 
   e.g., ...*8u*6u)/5 resp ...*8u*4u)/5 (~ value +/- 20%) but such a severe restriction
   can require including some adaptive flow to compensate possible temperature drift
   of HSI by making acceptable window defined by these limits variable in time */ 

/**
  * @brief  Verification result of the cross check frequencies process
  * @param  : None
  * @retval : None
  */
STL_Status_t STL_RunClockTest(STL_TmStatus_t *clk_sts)
{
  STL_Status_t tst_res;
  
  *clk_sts = STL_PARTIAL_PASSED;
  /* next line can produce compilation warning due to accessing of two volatile
     variables making single result value integrity pair. The pair is changed
     simultaneously exclusively at TIM16 IRQ and its integrity is verified here
     before the clock measurement is compared if it fits within expected range */
  if (((PeriodValue ^ PeriodValueInv) == 0xFFFFFFFFuL)\
  &&   (LSIPeriodFlag == 0xAAAA5555u) )
  {
  #ifndef ARTI_FAILING_CLK_TM
    *clk_sts = STL_PASSED;
    if (PeriodValue < CLK_LimitLow(SYSTCLK_AT_RUN))
    {
      *clk_sts = STL_FAILED;	/* Sub-harmonics: HSE -25% below expected */
    }
    if (PeriodValue > CLK_LimitHigh(SYSTCLK_AT_RUN))
    {
      *clk_sts = STL_FAILED;	/* Harmonics: HSE +25% above expected */
    }
  #else
    *clk_sts = STL_FAILED;
  #endif /* ARTI_FAILING_CLK_TM */
    
    /* clear flag here to ensure refreshed LSI measurement result will be taken at next check */
    LSIPeriodFlag = 0u;
    tst_res = STL_OK;
  }
  else
  {
    tst_res = STL_KO; /* Clock measurement flow error */
  }
  return(tst_res);
}

中断检测

中断检测的主要目标是监测中断的频率是否正常。由于中断的发生具有随机性,监测可能会比较复杂,因此需要结合实际业务场景进行分析。以下是一些参考思路:

  • 对于定时中断和 MCU 主动触发的中断,如定时器中断、ADC 转换完成中断、串口/CAN 发送中断等。可以为每种中断定义一个独立的计数器,用于记录每种中断的发生次数。然后,在一个独立的时钟周期性中断中,检查这些中断的计数值,以判断是否存在频繁中断或无中断的异常情况。
  • 对于外部信号触发的中断,如PWM 捕获中断、GPIO 捕获中断、串口/CAN 接收中断等。可以通过检查实际业务功能的正常性来间接检测中断。例如:对于用于检测电机转速的 PWM 捕获中断,可以设定电机目标转速,并与实际捕获的转速进行比较。如果实际转速与目标转速不符,可能表明从输入信号到中断处理的整个环节存在问题。对于传感器的串口接收中断,可以根据传感器数据的接收帧率来判断。如果帧率不符合预期,可能表明串口接收中断处理存在问题。

这些方法可以帮助我们更有效地监测中断的正常性,并及时发现潜在的异常情况。

IO 检测

IO 检测主要目的是监测 IO 引脚是否可以拉高拉低,如果是模拟 IO 的输入,可以和 ADC 配合检测。

  • 对于数字 I/O,选用恰当的 I/O,输出 0/1,再检查 I/O 状态是否正常,以及 I/O 与电源之间是否有短路或开路。对可能会导致危险的关键信号引脚,可以用冗余输入引脚检查信号状态是否正常。
  • 对于模拟 I/O,可以输入一个恒定电压,用 AD 转换,检查转换值是否在可接受的较小范围内。

当然,在实际应用中,这种测试可能会影响业务的正常运转。因此,我们也可以通过检测实际业务功能的正常性来间接检测 IO 状态。例如:

  • 当加热(或者制冷)控制引脚打开(或者关闭)时,应该检查温度传感器的模拟信号变化是否正常。
  • 对于电机的方向控制引脚,如果电机配备了相应的到位传感器或编码器组件,我们可以利用这些传感器和编码器的反馈来判断方向引脚的设置是否正确。需要注意的是,这些传感器和编码器本身也需要进行自检,以确保它们的功能正常。因此,涉及安全功能的业务通常由多个组件协同工作,这种方法不仅能检测单个组件的状态,还能反映各组件之间的协作是否顺畅。

ADC检测

对于ADC的检测,为了验证 ADC 的正确性,可以使用多路 ADC 监控同一输入信号源。

  • 比如通过在硬件上复用信号源,使用多个ADC通道,或在同一引脚上分别使用多个 ADC(例如 ADC1 和 ADC2)检测相互验证。
  • 当然,也可以检测实际业务功能的正常性来验证ADC,如模拟信号采样转换过来的电压、电流、温度是否处于在正常范围内,并且变化规律符合预期。

通信检测

为确保通信数据交换的正确性,可以通过加入冗余进行检查。

  • 例如,使用奇偶校验、同步信号、CRC 校验和、块重复或协议编号等方法。
  • 也可以使用稳定的应用软件协议栈(如 UAVCAN、Modbus、TCP/IP)等,确保通信正常和可靠。
  • 此外,还必须对通信事件的周期性、发生率以及协议错误信号进行持续监控。

认证的一些建议

  • 安全功能实现原理:深入理解和验证每个安全功能的实现原理,确保其在实际应用中的有效性。
  • 灵活处理:根据实际业务需求灵活调整测试策略,特别是针对中断、IO、ADC 和通信模块的运行时测试。
  • 测试记录:记录所有测试步骤、输入数据、环境配置、预期结果、实际结果和问题描述。为后续的审查提供充足的依据。
  • 软件模块设计书:确保设计文档包括系统架构图、模块设计、功能实现原理、软件仿真和测试记录、修改管理等,做到内容全面、准确,方便后续审查和认证。
  • 认证机构沟通:与认证机构保持定期沟通,了解他们的要求和期望,确保测试和文档符合认证标准。
  • 持续更新:关注标准更新和认证要求的变化,及时调整测试和文档以符合最新要求。

最后,祝大家的产品都能顺利通过认证。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值