ADC - DLPS

This example demonstrates using the ADC single sample mode to detect internal VBAT voltage when the system supports DLPS.

When the system is in the IDLE state, it will automatically enter the DLPS state. The system will be awakened from DLPS when the input level of pin P4_0 is low.

After the system wakes up, the internal voltage VBAT is detected via interrupt. Once the ADC sampling is completed, it triggers the ADC_INT_ONE_SHOT_DONE interrupt, in which the raw data is read and voltage conversion calculations are performed.

Requirements

The sample supports the following development kits:

Development Kits

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

For more requirements, please refer to Quick Start.

Wiring

Connect P4_0 to the external input signal.

Building and Downloading

This sample can be found in the SDK folder:

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

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

Please follow these steps to build and run the example:

  1. Open sample project file.

  2. To build the target, follow the steps listed on the Generating App Image in Quick Start.

  3. After a successful compilation, the app bin app_MP_xxx.bin will be generated in the directory mdk\bin or gcc\bin.

  4. To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.

  5. Press reset button on EVB board and it will start running.

Experimental Verification

  1. After the EVB is reset, an ADC sampling will be performed first. Once the ADC sampling is completed, the amount of collected data and the raw data values will be printed in the Debug Analyzer.

    io_adc_handle_msg: len = xxx
    adc raw data = xxx
    ...
    
  2. After the first sampling is completed, the system enters the DLPS state. Print the corresponding information in the Debug Analyzer.

    io_adc_dlps_enter
    
  3. When P4_0 detects a low-level input, the system is awakened and exits the DLPS state. Print the corresponding information in the Debug Analyzer.

    io_adc_dlps_exit
    
  4. When P4_0 detects a low-level input, the system is awakened and simultaneously triggers a GPIO interrupt, printing the corresponding information in the Debug Analyzer.

    GPIO_Input_Handler
    
  5. GPIO interrupt will trigger ADC sampling. After the ADC sampling is complete, the amount of data collected and the raw data values will be printed in the Debug Analyzer.

Code Overview

This chapter will be introduced according to the following several parts:

  1. Source Code Directory.

  2. Peripheral initialization will be introduced in chapter Initialization.

  3. Functional implementation after initialization will be introduced in chapter Function Implementation.

Source Code Directory

  • Project directory: sdk\board\evb\io_sample\ADC\DLPS

  • Source code directory: sdk\src\sample\io_sample\ADC\DLPS

Source files are currently categorized into several groups as below.

└── Project: adc_continuous
    └── 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

Initialization

When the EVB reset is initiated, the main() function is called, and the following process will be executed:

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;
}

Note

le_gap_init(), gap_lib_init(), app_le_gap_init, and app_le_profile_init are related to the initialization of the privacy management module. Refer to the initialization process description in LE Peripheral Privacy.

The specific initialization process related to peripherals is as follows:

  1. In global_data_init, execute global_data_adc_init. This function is for global initialization and includes the following process:

    1. Set the global variable IO_ADC_DLPS_Enter_Allowed to PM_CHECK_PASS, indicating that entering DLPS state is allowed.

    2. Initialize the global variable 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. In board_init, execute board_gpio_init, which is a function for PAD/PINMUX settings and includes the following processes:

    1. Configure PAD: Set the pin, PINMUX mode, PowerOn, internal pull-up, and disable output.

    2. Configure PINMUX: Assign the pin for GPIO function.

  3. After executing os_sched_start() to start task scheduling, in the app_main_task main task, execute driver_init to initialize the peripheral drivers.

  4. In driver_init, execute driver_adc_init, which is the initialization function for the ADC peripheral, and includes the following steps:

    1. Enable the RCC clock.

    2. Configure the ADC sampling channel, set channel 0 to VBAT voltage mode, and set the Bitmap to 0x01.

    3. Configure the ADC ADC_INT_ONE_SHOT_DONE interrupt.

    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. In driver_init, execute driver_gpio_init, which is the initialization function for the GPIO peripheral and includes the following processes:

    1. Enable the RCC clock.

    2. Configure the GPIO pin to input mode.

    3. Configure the GPIO interrupt enable, with trigger mode and polarity set to falling edge trigger.

    4. Enable the GPIO debounce function and set the debounce time.

    5. Configure the GPIO interrupt.

    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. Execute pwr_mgr_init, this function sets the voltage mode for DLPS, and includes the following process:

    1. Register the user-entering DLPS callback function app_enter_dlps_config, register the user-exiting DLPS callback function app_exit_dlps_config.

      1. Execute the io_adc_dlps_enter function within app_enter_dlps_config, set the pin to SW mode, and set the DLPS wake-up method.

        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. Execute the io_adc_dlps_exit function within app_exit_dlps_config to set the pin to PINMUX mode.

        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. Register hardware control callback functions DLPS_IO_EnterDlpsCb and DLPS_IO_ExitDlpsCb. Entering DLPS will save CPU, PINMUX, Peripheral, etc., and exiting DLPS will restore CPU, PINMUX, Peripheral, etc.

    3. Set the power mode to DLPS mode.

    4. Set the wake-up method from DLPS to wake up by ADC_DLPS_WAKEUP_PIN low level.

    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
    }
    

Functional Implementation

  1. Execute os_sched_start() to start task scheduling. When the stack is ready, execute app_handle_dev_state_evt, execute adc_sample_start, and execute ADC_Cmd() to start ADC sampling.

    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. When the ADC completes a single sample, it triggers the ADC_INT_ONE_SHOT_DONE interrupt and enters the interrupt handler function ADC_Handler.

    1. Read the ADC sample value.

    2. Define the message type IO_MSG_TYPE_ADC, save the collected data to a global variable, and execute app_send_msg_to_apptask to send a message to the 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. In app_main_task, loop to check the message queue. When a msg is detected, execute the app_handle_io_msg function to process the msg.

  4. In the app_handle_io_msg function, if the message type is determined to be IO_MSG_TYPE_ADC, execute the io_handle_adc_msg function, followed by the io_adc_handle_msg function.

    1. Extract the sampled data from msg.

    2. Print the ADC data length and data values.

    3. Set the global variable IO_ADC_DLPS_Enter_Allowed to PM_CHECK_PASS, indicating that DLPS mode can be entered.

    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. When pin P4_0 inputs a low level, the system exits DLPS state. When the system wakes up, it enters System_Handler.

    1. Clear the wake-up interrupt pending bit of P4_0.

    2. Disable the wake-up function of P4_0.

    3. Set the global variable IO_ADC_DLPS_Enter_Allowed to PM_CHECK_FAIL, indicating that entering the DLPS state is not allowed.

    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. When pin P4_0 inputs a low level, it triggers the GPIO_PIN_INPUT interrupt and enters the interrupt handler function GPIO_Input_Handler.

    1. Disable the GPIO interrupt and mask the GPIO interrupt.

    2. Define the message type IO_MSG_TYPE_GPIO and execute app_send_msg_to_apptask to send a message to the task.

    3. Clear the GPIO interrupt pending bit, unmask the GPIO interrupt, and enable the GPIO interrupt.

    4. In the app_handle_io_msg function, if the message type is determined to be IO_MSG_TYPE_GPIO, execute io_adc_sample_start to start ADC sampling.

    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. After the ADC sampling is completed, the system will enter the DLPS state again, and the above process will be repeated cyclically.