外挂MCU以及SOC 上下电方案


REVISION HISTORY

Revision No.
Description
Date
1.0
  • Initial release
  • 06/07/2024

    1 概述

    本文档用于说明在SOC未下电完成而上电事件触发导致的系统异常问题以及解决方案。主要介绍 SOC上下电流程、问题产生原因以及解决方案。

    2 SOC 上下电流程


    图1 SOC 上下电流程

    1. SOC suspend 状态:此时 SOC 处于下电状态,RTC 事件可正常将SOC 唤醒
    2. SOC 上电流程:该阶段为 SOC 硬件上电流程
    3. SOC resume流程:该阶段为 SOC 软件 resume 流程
    4. SOC alive 状态:SOC 正常工作阶段
    5. SOC suspend 流程:该阶段为软件 suspend 流程
    6. SOC 下电流程:该阶段为 SOC 硬件下电流程,该阶段结束RTC 事件可正常将SOC 唤醒

    3 存在问题

    1. SOC 对于电源上电有严格的时序要求,当 RTCIO0/1 事件在SOC 处于硬件下电流程时(图1阶段6)触发,会导致SOC 并未完全下电而又要走上电流程,最终导致上电时序异常。
    2. 由于RTC wakeup 节点只会在 RTCIO4 被拉高之后才会被更新,因此在 RTCIO4 为 HIGH 的阶段(阶段⅔/5)触发的 RTCIO0/1 事件就无法被AOV demo 获取到,导致 RTCIO0/1(pir/wifi) 事件会丢失。
    3. RTC 每天 8万余次,是否可靠,如何防呆问题

    4 解决方案

    4.1 方案一(推荐方案)


    图2 方案1事件流程图


    图3 SOC 硬件修改方案
    需要按照图3 进行硬件修改,硬件修改方案如下:

    1. 3.3V_GPIO:从SSC30XXE 芯片端任意选择一个3.3V且默认内部PU的GPIO
    2. PQ1: 增强型NMOS,建议VthGS越小越好
    3. MCU_INT: MCU外部事件中断输出脚
    4. RTC_IO1:自带按键防抖功能,有8ms delay才会拉高RTC_IO4
    5. R3: 该电阻值根据MCU IO 输出特性自行调整阻值

    基本原理:Vg大于VthGS 则NMOS 保持导通,RTC_IO1保持低电平相当于会屏蔽掉MCU_INT 的信号,Vg 小于VthGS则NMOS保持关断,RTC_IO1相当于直连MCU_INT,可以正常接收MCU中断,无论何时MCU_INT 拉高都只能在此阶段被 RTC_IO1 响应,响应8ms之后,内部POC模块才会拉高RTC_IO4,硬件上有8ms给系统更多掉电时间,同时也能保证二次上电时序正常。

    将 RTC IO0 的输入源由 RTC ALARM 修改为 MCU timer ,将RTC_IO1 的输入源按如下修改


    图4 方案1硬件连接示意图

    原理:在gpio2 电平为 HIGH 时,根据 CMOS 原理,即使此时 MCU 拉高RTC_IO1,此时 SOC 也无法响应RTC_IO1上升沿中断,根据CMOS 管特性(导通电压是0.7v),图中的gpio2 下降沿时的电压为 0.7v,此外由于 RTCIO1 的防抖措施(拉高RTCIO1 8ms 之后才会拉高RTCIO4 )的存在,在 gpio 2下降沿后拉高 RTCIO1 一定是安全的。gpio2 上升沿事件是 SOC 软件 resume 时将其拉高,gpio2 下降沿事件是 SOC 硬件下电流程中 GPIO 电源域的电压拉低触发。

    具体实现 flow

    1. MCU 通过timer 唤醒 SOC,MCU alive,在此后任意阶段,若发生 Pir & WiFi 事件,MCU 将 gpio1 举高,然后直接拉高 RTCIO1 即可

    2. SOC 上电启动到阶段 2 到 gpio2 上升沿时,MCU 拉高 RTCIO1,此时由于gpio2 为 LOW ,SOC 虽然会响应,但是由于RTCIO4已经为高,SOC 无事发生

    3. SOC 启动处于 gpio2 上升沿 至 gpio2 下降沿阶段内时,gpio2 为 HIGH,由于 CMOS 的原因,即使 MCU 拉高 RTCIO1,SOC 看到的 RTCIO1依旧为低

      SOC 启动到阶段 4,通过查询 gpio1 电平状态确认是否有 Pir & WiFi 事件发生,若有则切换高帧模式,然后修改 gpio3 状态(比如连续多个上升沿)告知 MCU 已经查询过事件,MCU 获取gpio3状态后,将gpio 1 拉低

      此后 SOC 在阶段4会持续 polling MCU gpio1 电平状态以及时响应阶段 4 发生的 Pir & WiFi 事件

    4. SOC 启动到 gpio2 下降沿到 阶段1,此时gpio2 为 LOW ,MCU 拉高 RTCIO1 后 RTCIO1会在 8ms 之后才会真正将RTCIO4拉高(8ms 足够让gpio2 由0.7v 掉电到零),这时 SOC 会被直接唤醒。

    注意

    1、若有多个外部唤醒事件的话,则 gpio1 的功能需要多根 gpio 实现

    2、使用 gpio3 告知 MCU 状态已经被获取的方式,也可以由业务逻辑实现,此时只是用 gpio 举例而已

    4.2 方案二


    图5 方案2事件流程图

    修改 RTCPOC 模块的输入信号源为下图所示


    图6 方案2硬件连接示意图

    将 RTC IO0 的输入源由 RTC ALARM 修改为 MCU timer ,将RTC_IO1 的输入源 修改为 Pir & WiFi。

    原理:MCU 通过 timer 唤醒 SOC 时,在 SOC 处于 2、3、4、5、6 阶段时,MCU 同样处于 ALIVE 状态,此时加上 1fps 的控制源修改,MCU 可以清楚地知道 SOC 的上下电时间点,就可以将 图4 的阶段 2、3、5、6 发生的 Pir & WiFi事件保存在 MCU 内部,在阶段 4 通过串口将事件状态发送给 SOC 。

    具体实现 flow

    1. MCU 通过timer 唤醒 SOC,然后等待SOC通过串口发送的 suspend 信号或者是 询问信号,

      suspend 信号:表示 SOC 开始下电

      询问信号:询问 MCU 在 2、3、5、6 阶段是否发生 Pir & WiFi 事件,事件发生意味着 SOC 进入阶段 4

    2. SOC 上电启动到阶段 2、3,此时若发生 Pir & WiFi 事件,MCU 保留事件发生状态,不去触发 RTCIO0/1事件

    3. SOC 启动到阶段 4,通过串口发送问询信号给 MCU,MCU 通过串口回复有或者没有,若此时接收到了 MCU 有 Pir & WiFi 事件发生,那切换高帧模式

      此后 SOC 在阶段4会持续等待 MCU 串口的发送信息,等待接收 MCU 串口的发送信息是为了响应阶段 4 发生的 Pir & WiFi 事件,当阶段 4 发生 Pir & WiFi 事件,直接通过串口告诉 SOC 即可。

      SOC 阶段4进入 STR 之前,通过串口告诉 MCU 准备下电

    4. SOC 启动到阶段 5、6,此时若发生 Pir & WiFi 事件,MCU 保留事件发生状态,不去触发 RTCIO0/1事件,阶段 6 SOC 硬件下电流程中 GPIO 电源域的电压拉低之后,GPIO 会被拉低,此时 MCU 会收到一个下降沿中断

    5. MCU 接收到 下降沿中断后,延时一个 t 的时间等到 SOC 完全下电进入阶段 1

      MCU 下电完成后,若在 SOC 阶段 5、6 有 Pir & WiFi 事件触发就立刻唤醒 SOC。回到步骤2

    让 MCU 能够知道当前 SOC 下电完成时间节点,在下电之后再去响应 pir/wifi 事件唤醒SOC 即可。

    t =  𝒉𝒘_𝒑𝒐𝒘𝒆𝒓𝒅𝒐𝒘𝒏
    𝒉𝒘_𝒑𝒐𝒘𝒆𝒓𝒅𝒐𝒘𝒏:硬件开始掉电(RTC_IO4 为0)直到到板子完全掉电时间
    

    4.3 RTC 如何防呆问题

    方案1:基于上面的功能实现,可直接将问询信号或者 GPIO 下降沿中断作为 SOC 心跳包使用,在规定时间内没有接到 SOC 的心跳包,则认为 SOC 出现异常,

    同理将 MCU 对问询信号的回复信息作为 MCU 的心跳包,在规定时间内没有接到 MCU 的心跳包,则认为 MCU 出现异常。

    5 伪代码

    5.1 SOC AOV demo 部分

    init 阶段:pull up 指定的GPIO 引脚
        // 创建新线程等待接收 MCU 串口信息
        creat_new_thred( func)
        // 创建新线程检测心跳包
        creat_new_thred( heart_beat)
        uart_recive_count=0
    
    处理阶段:
        uart_trans( Querying )
        uart_recive(event_flag)
        uart_recive_count++;
        if(event_flag==TRUE)
        {
            //切换高帧
            .
            .
            .
        }
        else
        {
            //维持现状
            .
            .
            .
        }
    
        // SOC ALIVE 处理完成
        uart_trans( suspend )
        //进入 suspend
    
    
    heart_beat()
    {
       temp_count = uart_recive_count
       sleep( 500ms )
       if(uart_recive_count<=temp_count)
       {
           // MCU 异常
       }
    }
    
    //线程处理函数 func
     func()
     {
         uart_recive()
         if(MCU 有事件触发)
         {
             //检查当前模式
             if(mode=低帧)
             {
                 //切换到高帧
             }else{
                 sleep()
             }
    
         }else{
             sleep()
         }
     }
    

    5.2 MCU demo 部分

    init 阶段:
        // 创建新线程等待接收 SOC 串口信息
        creat_new_thred( func)
        // 创建新线程检测心跳包
        creat_new_thred( heart_beat)
        Querying=false
        suspend=false
        uart_recive_count=0
    
    处理阶段:
    send_pir_event_flag=true
    
        if(PIR || WIFI)
        {
            if(Querying=false )
            {
                // 保存事件到变量
                save_event()
    
            }
            else if(Querying=true&& suspend=false)
            {
               uart_trans(true)
            }
            else if(Querying=true&& suspend=true)
            {
               // 保存事件到变量
                save_event()
                sleep( t )
                pull_up(RTC_IO1)
            }
        }
    
    
    heart_beat()
    {
       temp_count = uart_recive_count
       sleep( 500ms )
       if(uart_recive_count<=temp_count)
       {
           // MCU 异常
       }
    }
    
    //线程处理函数 func
     func()
     {
         while(uart_recive())
         {
              if(Querying)
             {
                 uart_recive_count++
                 if(PIR || WIFI)
                 {
                   uart_trans(true)
                 }else
                 {
                     uart_trans(false)
                 }
                 Querying=true
             }else if(suspend)
             {
                 // 此时开始不要响应 IO0/1 事件
                 suspend=true
                 break;
             }
         }
     }