ADC - DLPS

该示例演示系统支持 DLPS 时,使用 ADC 单次采样模式进行内部 VBAT 电压检测。

当系统处于 IDLE 状态时,会自动进入 DLPS 状态。当引脚 P4_0 输入电平为低电平时,会将系统从 DLPS 唤醒。

系统唤醒后以中断的方式检测内部电压 VBAT。当 ADC 采样完成之后触发 ADC_INT_ONE_SHOT_DONE 中断,在中断函数内读取 raw data 并进行电压转换计算。

环境需求

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

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

更多信息请参考 快速入门

硬件连线

连接 P4_0 至外部信号输入。

编译和下载

该示例的工程路径如下:

Project file: board\evb\io_sample\ADC\DLPS\mdk

Project file: board\evb\io_sample\ADC\DLPS\gcc

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

  1. 打开工程文件。

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

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

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

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

测试验证

  1. 当 EVB 复位后,先执行一次 ADC 采样。ADC 采样结束后,会在 Debug Analyzer 工具内打印采集得到的数据量和 raw data 值。

    io_adc_handle_msg: len = xxx
    adc raw data = xxx
    ...
    
  2. 第一次采样结束后,系统进入 DLPS 状态。在 Debug Analyzer 工具内打印相应信息。

    io_adc_dlps_enter
    
  3. 当 P4_0 检测到低电平输入时,系统被唤醒,退出 DLPS 状态。在 Debug Analyzer 工具内打印相应信息。

    io_adc_dlps_exit
    
  4. 当 P4_0 检测到低电平输入时,系统被唤醒的同时会触发 GPIO 中断,在 Debug Analyzer 工具内打印相应信息。

    GPIO_Input_Handler
    
  5. GPIO 中断会触发 ADC 采样。ADC 采样结束后,会在 Debug Analyzer 工具内打印采集得到的数据量和 raw data 值。

代码介绍

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

  1. 源码路径

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

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

源码路径

  • 工程路径: sdk\board\evb\io_sample\ADC\DLPS

  • 源码路径: sdk\src\sample\io_sample\ADC\DLPS

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

└── Project: adc_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
            └── adc.lib
        ├── peripheral               includes all peripheral drivers and module code used by the application
            ├── rtl876x_rcc.c
            ├── rtl876x_pinmux.c
            ├── rtl876x_nvic.c
            └── rtl876x_adc.c
        ├── profile
        └── app                      includes the ble_peripheral user application implementation
            ├── main.c
            ├── ancs.c
            ├── app.c
            ├── app_task.c
            └── io_adc.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. global_data_init 中,执行 global_data_adc_init ,该函数为全局初始化,包含如下流程:

    1. 设置全局变量 IO_ADC_DLPS_Enter_AllowedPM_CHECK_PASS ,代表可以进入 DLPS 状态。

    2. 初始化全局变量 ADC_Recv_Buffer

    void global_data_adc_init(void)
    {
        IO_ADC_DLPS_Enter_Allowed = PM_CHECK_PASS;
        ADC_DATA_Length = 0;
        memset(ADC_DATA_Buffer, 0, sizeof(ADC_DATA_Buffer));
    }
    
  2. board_init 中,执行 board_gpio_init ,该函数为 PAD/PINMUX 设置,包含如下流程:

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

    2. 配置 PINMUX:分配引脚为 GPIO 功能。

  3. 在执行 os_sched_start() 开启任务调度后,在 app_main_task 主任务内,执行 driver_init 对外设驱动进行初始化配置。

  4. driver_init 中执行 driver_adc_init ,该函数为 ADC 外设的初始化,包含如下流程:

    1. 使能 RCC 时钟。

    2. 配置 ADC 的采样通道,配置通道 0 为 VBAT 电压模式,设置 Bitmap 为 0x01。

    3. 配置 ADC ADC_INT_ONE_SHOT_DONE 中断。

    void driver_adc_init(void)
    {
        RCC_PeriphClockCmd(APBPeriph_ADC, APBPeriph_ADC_CLOCK, ENABLE);
    
        ADC_InitTypeDef ADC_InitStruct;
        ADC_StructInit(&ADC_InitStruct);
    
        ADC_InitStruct.ADC_SampleTime       = 255;  /* (n + 1) cycle of 10MHz,n = 0~255 or n = 2048~14591 */
    
        ADC_InitStruct.ADC_SchIndex[0]      = INTERNAL_VBAT_MODE;
        ADC_InitStruct.ADC_Bitmap           = 0x01;
        ADC_InitStruct.ADC_PowerAlwaysOnEn  = ADC_POWER_ALWAYS_ON_ENABLE;
        ADC_Init(ADC, &ADC_InitStruct);
    
        ADC_INTConfig(ADC, ADC_INT_ONE_SHOT_DONE, ENABLE);
    
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = ADC_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    }
    
  5. driver_init 中执行 driver_gpio_init ,该函数为 GPIO 外设的初始化,包含如下流程:

    1. 使能 RCC 时钟。

    2. 配置 GPIO 引脚,模式为输入模式。

    3. 配置 GPIO 中断使能,触发模式与极性为下降沿触发。

    4. 使能 GPIO 去抖功能,设置去抖时间。

    5. 配置 GPIO 中断。

    void driver_gpio_init(void)
    {
        /* Initialize GPIO peripheral */
        RCC_PeriphClockCmd(APBPeriph_GPIO, APBPeriph_GPIO_CLOCK, ENABLE);
    
        GPIO_InitTypeDef GPIO_InitStruct;
        GPIO_StructInit(&GPIO_InitStruct);
        GPIO_InitStruct.GPIO_Pin        = GPIO_PIN_INPUT;
        GPIO_InitStruct.GPIO_Mode       = GPIO_Mode_IN;
        GPIO_InitStruct.GPIO_ITCmd      = ENABLE;
        GPIO_InitStruct.GPIO_ITTrigger  = GPIO_INT_Trigger_EDGE;
        GPIO_InitStruct.GPIO_ITPolarity = GPIO_INT_POLARITY_ACTIVE_LOW;
        GPIO_InitStruct.GPIO_ITDebounce = GPIO_INT_DEBOUNCE_ENABLE;
        GPIO_InitStruct.GPIO_DebounceTime = 64;/* unit:ms , can be 1~64 ms */
        GPIO_Init(&GPIO_InitStruct);
    
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = GPIO_PIN_INPUT_IRQN;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    
        GPIO_MaskINTConfig(GPIO_PIN_INPUT, DISABLE);
        GPIO_INTConfig(GPIO_PIN_INPUT, ENABLE);
    }
    
  6. 执行 pwr_mgr_init ,该函数为 DLPS 的电压模式设置,包含如下流程:

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

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

        void io_adc_dlps_enter(void)
        {
            /* Switch pad to Software mode */
            Pad_ControlSelectValue(ADC_DLPS_WAKEUP_PIN, PAD_SW_MODE);
            System_WakeUpPinEnable(ADC_DLPS_WAKEUP_PIN, PAD_WAKEUP_POL_LOW, 0, 0);
            DBG_DIRECT("io_adc_dlps_enter");
        }
        
      2. app_exit_dlps_config 内执行 io_adc_dlps_exit 函数,设置引脚为 PINMUX 模式。

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

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

    4. 设置唤醒 DLPS 方式为 ADC_DLPS_WAKEUP_PIN 低电平唤醒。

    void pwr_mgr_init(void)
    {
    #if DLPS_EN
        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(ADC_DLPS_WAKEUP_PIN, PAD_WAKEUP_POL_LOW, 0, 0);
    #else
        lps_mode_set(LPM_ACTIVE_MODE);
    #endif
    }
    

功能实现

  1. 执行 os_sched_start() ,开启任务调度。当 stack 准备好时,执行 app_handle_dev_state_evt ,执行 adc_sample_start ,执行 ADC_Cmd() ,开始 ADC 采样。

    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*/
                adc_sample_start();
            }
        }
        ...
    }
    
    void adc_sample_start(void)
    {
        /* Enable adc sample */
        ADC_Cmd(ADC, ADC_ONE_SHOT_MODE, ENABLE);
    }
    
  2. 当 ADC 完成单次采样时,触发 ADC_INT_ONE_SHOT_DONE 中断,进入中断处理函数 ADC_Handler

    1. 读取 ADC 采样值。

    2. 定义消息类型 IO_MSG_TYPE_ADC ,保存采集数据至全局变量,执行 app_send_msg_to_apptask 发送 msg 给 task。

    void ADC_Handler(void)
    {
        uint16_t sample_data[32];
    
        if (ADC_GetINTStatus(ADC, ADC_INT_ONE_SHOT_DONE) == SET)
        {
            ADC_ClearINTPendingBit(ADC, ADC_INT_ONE_SHOT_DONE);
    
            /* Send msg to app task */
            ADC_DATA_Length = 1;
            sample_data[0] = ADC_ReadRawData(ADC, ADC_Schedule_Index_0);
    
            ADC_DATA_Buffer[0] = sample_data[0];
            T_IO_MSG int_adc_msg;
    
            int_adc_msg.type = IO_MSG_TYPE_ADC;
            int_adc_msg.u.buf = (void *)(&ADC_DATA_Buffer);
            if (false == app_send_msg_to_apptask(&int_adc_msg))
            {
                APP_PRINT_ERROR0("[io_adc] ADC_Handler: Send int_adc_msg failed!");
                //Add user code here!
                return;
            }
        }
    }
    
  3. app_main_task 中循环检测消息队列。当检测到 msg 时,执行 app_handle_io_msg 函数对 msg 进行处理。

  4. app_handle_io_msg 函数中,若判断消息类型为 IO_MSG_TYPE_ADC ,执行 io_handle_adc_msg 函数,执行 io_adc_handle_msg

    1. 从 msg 中取出采样数据。

    2. 打印 ADC 数据长度和数据值。

    3. 设置全局变量 IO_ADC_DLPS_Enter_AllowedPM_CHECK_PASS ,代表可以进入 DLPS 状态。

    void io_adc_handle_msg(T_IO_MSG *io_adc_msg)
    {
        uint16_t *p_buf = io_adc_msg->u.buf;
        uint16_t type = io_adc_msg->type;
    
        if (IO_MSG_TYPE_ADC == type)
        {
            DBG_DIRECT("io_adc_handle_msg: len = %d", ADC_DATA_Length);
            for (uint8_t i = 0; i < ADC_DATA_Length; i++)
            {
                DBG_DIRECT("adc raw data = %d", p_buf[i]);
            }
            platform_delay_ms(1000);
            IO_ADC_DLPS_Enter_Allowed = PM_CHECK_PASS;
        }
    }
    
  5. 当引脚 P4_0 输入低电平时,系统退出 DLPS 状态,当系统被唤醒时,进入 System_Handler

    1. 清除 P4_0 的唤醒中断挂起位。

    2. 失能 P4_0 的唤醒功能。

    3. 设置全局变量 IO_ADC_DLPS_Enter_AllowedPM_CHECK_FAIL ,代表不可进入 DLPS 状态。

    void System_Handler(void)
    {
        if (System_WakeUpInterruptValue(ADC_DLPS_WAKEUP_PIN) == SET)
        {
            APP_PRINT_INFO0("System_Handler");
            Pad_ClearWakeupINTPendingBit(ADC_DLPS_WAKEUP_PIN);
            System_WakeUpPinDisable(ADC_DLPS_WAKEUP_PIN);
            IO_ADC_DLPS_Enter_Allowed = PM_CHECK_FAIL;
        }
    }
    
  6. 当引脚 P4_0 输入低电平时,触发 GPIO_PIN_INPUT 中断,进入中断处理函数 GPIO_Input_Handler

    1. 关 GPIO 中断,屏蔽 GPIO 中断。

    2. 定义消息类型 IO_MSG_TYPE_GPIO ,执行 app_send_msg_to_apptask 发送 msg 给 task。

    3. 清除 GPIO 中断挂起位,取消屏蔽 GPIO 中断,使能 GPIO 中断。

    4. app_handle_io_msg 函数中,若判断消息类型为 IO_MSG_TYPE_GPIO ,执行 io_adc_sample_start ,开始 ADC 采样。

    void GPIO_Input_Handler(void)
    {
        DBG_DIRECT("GPIO_Input_Handler");
        GPIO_INTConfig(GPIO_PIN_INPUT, DISABLE);
        GPIO_MaskINTConfig(GPIO_PIN_INPUT, ENABLE);
    
        T_IO_MSG int_gpio_msg;
    
        int_gpio_msg.type = IO_MSG_TYPE_GPIO;
        int_gpio_msg.subtype = 0;
        if (false == app_send_msg_to_apptask(&int_gpio_msg))
        {
            APP_PRINT_ERROR0("[io_gpio] GPIO_Input_Handler: Send int_gpio_msg failed!");
            //Add user code here!
            GPIO_ClearINTPendingBit(GPIO_PIN_INPUT);
            return;
        }
    
        GPIO_ClearINTPendingBit(GPIO_PIN_INPUT);
        GPIO_MaskINTConfig(GPIO_PIN_INPUT, DISABLE);
        GPIO_INTConfig(GPIO_PIN_INPUT, ENABLE);
    }
    
  7. ADC 采样完成后,系统会再次进入 DLPS 状态,循环重复上述过程。