Phase Detection - DLPS

该示例通过使用 QDEC 进行 DLPS 唤醒。

当系统处于 IDLE 状态时,会自动进入 DLPS 状态。

当 QDEC 检测到新数据时会将系统从 DLPS 唤醒,同时触发 QDEC 中断,在中断函数内读取传感器装置运动信息。

QDEC 与检测传感器模块的相关介绍请参考 Phase Detection

环境需求

该示例支持以下开发套件:

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

更多信息请参考 快速入门

硬件连线

EVB 外接转动传感器模块,连接 P2_3 和 PhaseA,P2_4 和 PhaseB。

编译和下载

该示例的工程路径如下:

Project file: board\evb\io_sample\QDEC\QDEC_DLPS\mdk

Project file: board\evb\io_sample\QDEC\QDEC_DLPS\gcc

请按照以下步骤操作构建并运行该示例:

  1. 打开工程文件。

  2. 按照 快速入门编译 APP Image 给出的步骤构建目标文件。

  3. 编译成功后,在路径 mdk\bingcc\bin 下会生成 app bin app_MP_xxx.bin 文件。

  4. 按照 快速入门MP Tool 给出的步骤将 app bin 烧录至 EVB 内。

  5. 按下 reset 按键,开始运行。

测试验证

当 QDEC Y 轴检测到新数据时,唤醒系统,并触发中断,在中断函数内打印滚轮滚动方向和次数。

Qdecode_Handler: Y_Axis_Direction = xx, Y_Axis_Count = xx

代码介绍

该章节分为以下几个部分:

  1. 源码路径

  2. 初始化函数将在 初始化 章节介绍。

  3. 初始化后的功能实现将在 功能实现 章节介绍。

源码路径

  • 工程路径: sdk\board\evb\io_sample\QDEC\QDEC_DLPS

  • 源码路径: sdk\src\sample\io_sample\QDEC\QDEC_DLPS

该工程的工程文件代码结构如下:

└── Project: qdec_dlps
    └── secure_only_app
        └── include
            ├── app_define.h
            └── rom_uuid.h
        ├── cmsis                    includes CMSIS header files and startup files
            ├── overlay_mgr.c
            ├── system_rtl876x.c
            └── startup_rtl876x.s
        ├── lib                      includes all binary symbol files that user application is built on
            ├── rtl8752h_sdk.lib
            ├── gap_utils.lib
            └── ROM.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            ├── rtl876x_io_dlps.c
            └── rtl876x_qdec.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            ├── main.c
            ├── ancs.c
            ├── app.c
            ├── app_task.c
            └── io_qdec.c

初始化

当 EVB 复位启动时,调用 main() 函数,将执行以下流程:

int main(void)
{
    extern uint32_t random_seed_value;
    srand(random_seed_value);
    global_data_init();
    board_init();
    le_gap_init(APP_MAX_LINKS);
    gap_lib_init();
    app_le_gap_init();
    app_le_profile_init();
    pwr_mgr_init();
    task_init();
    os_sched_start();

    return 0;
}

备注

le_gap_init()gap_lib_init()app_le_gap_initapp_le_profile_init 等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。

与外设相关的初始化流程具体如下:

  1. board_init 中,执行 board_qdec_init ,该函数为 QDEC 相关引脚的 PAD/PINMUX 设置,包含如下流程:

    1. 配置 PAD:设置引脚、PINMUX 模式、PowerOn、内部上拉、输出失能。

    2. 配置 PINMUX:分配引脚分别为 qdec_phase_a_y 和 qdec_phase_b_y 功能。

    void board_qdec_init(void)
    {
        Pad_Config(QDEC_Y_PHA_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE,
               PAD_OUT_LOW);
        Pad_Config(QDEC_Y_PHB_PIN, PAD_PINMUX_MODE, PAD_IS_PWRON, PAD_PULL_UP, PAD_OUT_DISABLE,
               PAD_OUT_LOW);
        Pinmux_Config(QDEC_Y_PHA_PIN, qdec_phase_a_y);
        Pinmux_Config(QDEC_Y_PHB_PIN, qdec_phase_b_y);
    }
    
  2. 在执行 os_sched_start() 开启任务调度后,在 app_main_task 主任务内,执行 driver_init 对外设驱动进行初始化配置。

  3. driver_init 中执行 driver_qdec_init ,该函数为 QDEC 外设的初始化,包含如下流程:

    1. 使能 RCC 时钟。

    2. 使能 Y 轴功能。

    3. 使能去抖动功能。

    4. 配置 QDEC Y 轴新数据产生中断 QDEC_Y_INT_NEW_DATA

    5. 配置并使能 QDEC 的 IRQ 通道。

    void driver_qdec_init(void)
    {
        QDEC_DeInit(QDEC);
        RCC_PeriphClockCmd(APBPeriph_QDEC, APBPeriph_QDEC_CLOCK, ENABLE);
    
        QDEC_InitTypeDef QDEC_InitStruct;
        QDEC_StructInit(&QDEC_InitStruct);
        QDEC_InitStruct.axisConfigY       = ENABLE;
        QDEC_InitStruct.debounceEnableY   = Debounce_Enable;
        QDEC_Init(QDEC, &QDEC_InitStruct);
        QDEC_INTConfig(QDEC, QDEC_Y_INT_NEW_DATA, ENABLE);
    
        /** To debug, enable QDEC when the GAP stack is ready.
        * In app.c->app_handle_dev_state_evt()
        */
    //    QDEC_Cmd(QDEC, QDEC_AXIS_Y, ENABLE);
    
        NVIC_InitTypeDef nvic_init_struct;
        nvic_init_struct.NVIC_IRQChannel         = Qdecode_IRQn;
        nvic_init_struct.NVIC_IRQChannelCmd      = (FunctionalState)ENABLE;
        nvic_init_struct.NVIC_IRQChannelPriority = 3;
        NVIC_Init(&nvic_init_struct);
    
    }
    
  1. 执行 pwr_mgr_init ,该函数为 DLPS 的电压模式设置,包含如下流程:

    1. 注册用户进入 DLPS 回调函数 app_enter_dlps_config ,注册用户退出 DLPS 回调函数 app_exit_dlps_config

      1. app_enter_dlps_config 内执行 io_qdec_dlps_enter ,设置引脚为 SW 模式,设置 DLPS 唤醒方式。

        void io_qdec_dlps_enter(void)
        {
            /* Switch pad to Software mode */
            Pad_ControlSelectValue(QDEC_Y_PHA_PIN, PAD_SW_MODE);
            Pad_ControlSelectValue(QDEC_Y_PHB_PIN, PAD_SW_MODE);
        
            System_WakeUpPinEnable(QDEC_Y_PHA_PIN, PAD_WAKEUP_POL_LOW, 0, 0);
        }
        
      2. app_exit_dlps_config 内执行 io_qdec_dlps_exit ,设置引脚为 PINMUX 模式。

        void io_qdec_dlps_exit(void)
        {
            /* Switch pad to Pinmux mode */
            Pad_ControlSelectValue(QDEC_Y_PHA_PIN, PAD_PINMUX_MODE);
            Pad_ControlSelectValue(QDEC_Y_PHB_PIN, PAD_PINMUX_MODE);
        }
        
    2. 注册硬件控制回调函数 DLPS_IO_EnterDlpsCbDLPS_IO_ExitDlpsCb ,进入 DLPS 会保存 CPU、PINMUX、Peripheral 等,退出 DLPS 会恢复 CPU、PINMUX、Peripheral 等。

    3. 设置电源模式为 DLPS 模式。

    void pwr_mgr_init(void)
    {
        if (false == dlps_check_cb_reg(app_dlps_check_cb))
        {
            APP_PRINT_ERROR0("Error: dlps_check_cb_reg(app_dlps_check_cb) failed!");
        }
        DLPS_IORegUserDlpsEnterCb(app_enter_dlps_config);
        DLPS_IORegUserDlpsExitCb(app_exit_dlps_config);
        DLPS_IORegister();
        lps_mode_set(PLATFORM_DLPS_PFM);
    
        /* Config WakeUp pin */
        System_WakeUpPinEnable(QDEC_Y_PHA_PIN, PAD_WAKEUP_POL_LOW, 0, 0);
    }
    

功能实现

  1. 在主函数中执行 os_sched_start() ,开启任务调度。当 stack 准备好时,执行 app_handle_dev_state_evt ,执行 QDEC_Cmd() 使能 QDEC Y 轴。

    void app_handle_dev_state_evt(T_GAP_DEV_STATE new_state, uint16_t cause)
    {
        ...
        if (gap_dev_state.gap_init_state != new_state.gap_init_state)
        {
            if (new_state.gap_init_state == GAP_INIT_STATE_STACK_READY)
            {
                APP_PRINT_INFO0("GAP stack ready");
                /*stack ready*/
                QDEC_Cmd(QDEC, QDEC_AXIS_Y, ENABLE);
            }
        }
        ...
    }
    
  2. 当 QDEC Y 轴检测到新数据时,触发 DLPS 唤醒,同时触发 QDEC_Y_INT_NEW_DATA 中断,进入中断处理函数 Qdecode_Handler

    1. 屏蔽 QDEC Y 轴新数据产生中断。

    2. 读 Y 轴运动方向、计数值。

    3. 发送消息给 app task。主 task 检测到消息后,执行 io_handle_qdec_msg,打印滚轮滚动方向和次数,设置全局变量 IO_QDEC_DLPS_Enter_AllowedPM_CHECK_PASS,代表可进入 DLPS 状态。

    4. 清除中断标志位,取消屏蔽中断。

    void Qdecode_Handler(void)
    {
        if (QDEC_GetFlagState(QDEC, QDEC_FLAG_NEW_CT_STATUS_Y) == SET)
        {
            /* Mask qdec interrupt */
            QDEC_INTMask(QDEC, QDEC_Y_CT_INT_MASK, ENABLE);
    
            /* Read direction & count */
            Y_Axis_Data.AxisDirection = QDEC_GetAxisDirection(QDEC, QDEC_AXIS_Y);
            Y_Axis_Data.AxisCount = QDEC_GetAxisCount(QDEC, QDEC_AXIS_Y);
    
            T_IO_MSG int_qdec_msg;
    
            int_qdec_msg.type = IO_MSG_TYPE_QDECODE;
            int_qdec_msg.u.buf = (void *)&Y_Axis_Data;
            if (false == app_send_msg_to_apptask(&int_qdec_msg))
            {
                APP_PRINT_ERROR0("[io_qdec]Qdecode_Handler: Send int_qdec_msg failed!");
                //Add user code here!
                /* Clear qdec interrupt flags */
                QDEC_ClearINTPendingBit(QDEC, QDEC_CLR_NEW_CT_Y);
                return;
            }
            /* Clear qdec interrupt flags */
            QDEC_ClearINTPendingBit(QDEC, QDEC_CLR_NEW_CT_Y);
            /* Unmask qdec interrupt */
            QDEC_INTMask(QDEC, QDEC_Y_CT_INT_MASK, DISABLE);
        }
    }
    
    void io_handle_qdec_msg(T_IO_MSG *io_qdec_msg)
    {
        QDEC_Data_TypeDef *p_buf = io_qdec_msg->u.buf;
        APP_PRINT_INFO2("[io_qdec]io_handle_qdec_msg: Y_Axis_Direction = %d,Y_Axis_Count = %d,",
                        p_buf->AxisDirection, p_buf->AxisCount);
        global_data_qdec_init();
    
        IO_QDEC_DLPS_Enter_Allowed = PM_CHECK_PASS;
    }