One Shot Mode - Interrupt Mode

This example uses the ADC one shot mode for voltage detection.

This example detects the input voltage of P2_7 through an interrupt. When the ADC sampling is completed, an interrupt is triggered, and the ADC sample data raw data is read in the interrupt function and voltage conversion calculations are performed.

In this example, the ADC voltage sampling range can be selected by configuring the macro ADC_MODE_DIVIDE_OR_BYPASS.

In this example, the Hardware Average function can be enabled by configuring the macro ADC_DATA_HW_AVERAGE.

In this example, whether the ADC sample data is stored in the FIFO can be selected by configuring the macro ADC_DATA_OUTPUT_TO_FIFO.

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 P2_7 to the external voltage input.

Configurations

The macros that can be configured in this example are as follows:

  1. ADC_MODE_DIVIDE_OR_BYPASS : Configures the ADC voltage sampling range, with the following selectable values.

    • ADC_DIVIDE_MODE : In Divide Mode, the ADC samples voltage values ranging from 0 to 3.3V.

    • ADC_BYPASS_MODE : In Bypass Mode, the ADC samples voltage values ranging from 0 to 0.9V.

  2. ADC_DATA_HW_AVERAGE :Configures the ADC Hardware Average function.

  3. ADC_DATA_OUTPUT_TO_FIFO :Configures the sampled data of the ADC to be stored in the ADC FIFO.

Note

The Hardware Average function is only applicable to channel 0 of the ADC. The Hardware Average function and the Data Output to FIFO function cannot be enabled simultaneously.

Building and Downloading

This sample can be found in the SDK folder:

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

Project file: board\evb\io_sample\ADC\OneShotMode\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. ADC Configurations:

    1. If the ADC is configured as ADC_DIVIDE_MODE, print the following log.

      [ADC]ADC sample mode is divide mode !
      
    2. If the ADC is configured as ADC_BYPASS_MODE, print the following log.

      [ADC]ADC sample mode is bypass mode !
      
  2. After the ADC sampling is finished, the raw data acquired and the converted voltage values are printed within the Debug Analyzer.

    [io_adc] io_adc_voltage_calculate: ADC rawdata = xxx, voltage = xxxmV
    ...
    

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\OneShotMode

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

Source files are currently categorized into several groups as below.

└── Project: adc_oneshot
    └── 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. Execute the ADC_CalibrationInit() function for ADC calibration. If the return value is false, ADC calibration failed, possibly because the IC has not undergone FT, thus unable to accurately obtain voltage values.

    2. Initialize the global variable ADC_Global_Data.

    void global_data_adc_init(void)
    {
        /* Initialize adc k value! */
        APP_PRINT_INFO0("[io_adc] global_data_adc_init");
        bool adc_k_status = false;
        adc_k_status = ADC_CalibrationInit();
        if (false == adc_k_status)
        {
            APP_PRINT_ERROR0("[io_adc] global_data_adc_init: ADC_CalibrationInit fail!");
        }
        memset(&ADC_Global_Data, 0, sizeof(ADC_Global_Data));
    }
    
  2. In board_init, execute board_adc_init, which is responsible for PAD/PINMUX settings and includes the following process:

    1. Config PAD: Set pin as SW mode, PowerOn, internal Pull-None, disable output.

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

  4. In driver_init, execute driver_adc_init, which is the initialization function for the ADC peripheral, including the following process:

    1. Enable RCC clock.

    2. Configure ADC sampling channels, configure channel 0 to P2_7 single-ended mode, set Bitmap to 0x01.

    3. If configured as bypass mode, execute ADC_BypassCmd() to enable the high resistance mode of the corresponding channel.

    4. If the macro ADC_DATA_HW_AVERAGE is enabled, enable the ADC Hardware Average mode and configure the number of averages.

    5. If the macro ADC_DATA_OUTPUT_TO_FIFO is enabled, enable the ADC to store sampled values into FIFO and configure the FIFO threshold.

    6. If the macro ADC_DATA_OUTPUT_TO_FIFO is enabled, configure the ADC_INT_FIFO_THD interrupt; otherwise, configure the 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);
    
        /* Configure the ADC sampling schedule0 */
        ADC_InitStruct.ADC_SchIndex[0]      = EXT_SINGLE_ENDED(ADC_SAMPLE_CHANNEL_7);
        /* Set the bitmap corresponding to schedule0*/
        ADC_InitStruct.ADC_Bitmap           = 0x01;
    
    #if (ADC_DATA_HW_AVERAGE && ADC_DATA_OUTPUT_TO_FIFO)
        APP_PRINT_ERROR0("[io_adc] driver_adc_init: ADC config error !");
    #elif (ADC_DATA_HW_AVERAGE )
        ADC_InitStruct.ADC_DataAvgEn        = ADC_DATA_AVERAGE_ENABLE;
        ADC_InitStruct.ADC_DataAvgSel       = ADC_DATA_AVERAGE_OF_4;
    #elif (ADC_DATA_OUTPUT_TO_FIFO)
        ADC_InitStruct.ADC_DataWriteToFifo  = ADC_DATA_WRITE_TO_FIFO_ENABLE;
        ADC_InitStruct.ADC_FifoThdLevel     = 0x0A;
    #endif
    
        ADC_InitStruct.ADC_PowerAlwaysOnEn  = ADC_POWER_ALWAYS_ON_ENABLE;
        /* Fixed 255 in OneShot mode. */
        ADC_InitStruct.ADC_SampleTime       = 255;
    
        ADC_Init(ADC, &ADC_InitStruct);
    
    #if (ADC_MODE_DIVIDE_OR_BYPASS == ADC_BYPASS_MODE)
        /* High bypass resistance mode config, please notice that the input voltage of
          adc channel using high bypass mode should not be over 0.9V */
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_0, ENABLE);
        APP_PRINT_INFO0("[io_adc] driver_adc_init: ADC sample mode is bypass mode !");
    #else
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_0, DISABLE);
        APP_PRINT_INFO0("[io_adc] driver_adc_init: ADC sample mode is divide mode !");
    #endif
    
    #if (!ADC_DATA_OUTPUT_TO_FIFO)
        ADC_INTConfig(ADC, ADC_INT_ONE_SHOT_DONE, ENABLE);
    #else
        ADC_INTConfig(ADC, ADC_INT_FIFO_THD, ENABLE);
    #endif
    
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = ADC_IRQn;
        NVIC_InitStruct.NVIC_IRQChannelPriority = 3;
        NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStruct);
    }
    

Functional Implementation

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

    1. If the macro ADC_DATA_OUTPUT_TO_FIFO is enabled, clear the ADC FIFO and then perform 10 consecutive ADC samples.

    2. If the macro ADC_DATA_OUTPUT_TO_FIFO is not enabled, directly 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*/
                io_adc_sample_start();
            }
        }
        ...
    }
    
    void adc_sample_start(void)
    {
    #if (ADC_DATA_OUTPUT_TO_FIFO)
        ADC_ClearFifo(ADC);
        for (uint16_t i = 0; i < 10; i++)
        {
            ADC_Cmd(ADC, ADC_ONE_SHOT_MODE, ENABLE);
            platform_delay_ms(10);
        }
    #else
        /* Enable adc sample */
        ADC_Cmd(ADC, ADC_ONE_SHOT_MODE, ENABLE);
    #endif
    }
    
  2. If the macro ADC_DATA_OUTPUT_TO_FIFO is enabled, when the ADC FIFO number exceeds the set threshold, it triggers the ADC_INT_FIFO_THD interrupt and enters the interrupt handler function ADC_Handler.

    1. Determine whether the interrupt status of the ADC FIFO number exceeding the given value is SET.

    2. Execute ADC_GetFIFODataLen() to get the amount of data in the ADC FIFO, and execute ADC_ReadFIFOData() to get the data in the ADC FIFO.

    3. 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.

    4. Execute ADC_ClearFIFO() to clear the data in the ADC FIFO and clear the interrupt flag.

    void ADC_Handler(void)
    {
        ...
        if (ADC_GetIntFlagStatus(ADC, ADC_INT_FIFO_THD) == SET)
        {
            ADC_Global_Data.RawDataLen = ADC_GetFifoLen(ADC);
            ADC_GetFifoData(ADC, ADC_Global_Data.RawData, ADC_Global_Data.RawDataLen);
    
            /* Send msg to app task */
            T_IO_MSG int_adc_msg;
            int_adc_msg.type = IO_MSG_TYPE_ADC;
            int_adc_msg.u.buf = (void *)(&ADC_Global_Data);
            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!
                ADC_ClearFifo(ADC);
                ADC_ClearINTPendingBit(ADC, ADC_INT_FIFO_THD);
                return;
            }
            ADC_ClearINTPendingBit(ADC, ADC_INT_FIFO_THD);
            ADC_ClearFifo(ADC);
        }
    }
    
  3. If the macro ADC_DATA_OUTPUT_TO_FIFO is not enabled, when the ADC completes a single sampling, it triggers the ADC_INT_ONE_SHOT_DONE interrupt and enters the interrupt handler function ADC_Handler.

    1. Check if the ADC single sampling interrupt status is SET and clear the interrupt flag.

    2. Execute ADC_ReadRawData() to read the ADC sampling value.

    3. 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 the message to the task.

    void ADC_Handler(void)
    {
        ...
        if (ADC_GetINTStatus(ADC, ADC_INT_ONE_SHOT_DONE) == SET)
        {
            ADC_ClearINTPendingBit(ADC, ADC_INT_ONE_SHOT_DONE);
    
            uint16_t sample_data = 0;
            sample_data = ADC_ReadRawData(ADC, ADC_Schedule_Index_0);
    
            T_IO_MSG int_adc_msg;
            int_adc_msg.type = IO_MSG_TYPE_ADC;
            int_adc_msg.subtype = 0;
            ADC_Global_Data.RawDataLen = 1;
            ADC_Global_Data.RawData[0] = sample_data;
            int_adc_msg.u.buf = (void *)(&ADC_Global_Data);
            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!
                ADC_ClearINTPendingBit(ADC, ADC_INT_ONE_SHOT_DONE);
                return;
            }
        }
        ...
    }
    
  4. In app_main_task, loop to check the message queue. When a message (msg) is detected, execute the app_handle_io_msg function to process the msg.

  5. 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, and execute io_adc_voltage_calculate.

    1. Extract the sampling data from the msg.

    2. If the macro ADC_DATA_HW_AVERAGE is enabled, calculate the integer and fractional parts of the sampled values separately.

    3. Execute ADC_GetVoltage() to calculate the sampling voltage value based on the sampling mode.

    Note

    The raw data obtained in ADC Hardware Average mode has the lower 2 bits as the decimal part and the upper 12 bits as the integer part.

    static void io_adc_voltage_calculate(T_IO_MSG *io_adc_msg)
    {
        ADC_Data_TypeDef *p_buf = io_adc_msg->u.buf;
        uint8_t sample_data_len = 0;
        uint16_t sample_data = 0;
    
        sample_data_len = p_buf->RawDataLen;
        for (uint8_t i = 0; i < sample_data_len; i++)
        {
            sample_data = p_buf->RawData[i];
            DBG_DIRECT("io_adc_voltage_calculate: raw_data = 0x%X", sample_data);
    #if (ADC_DATA_HW_AVERAGE )
            sample_data = (p_buf->RawData[i] & 0x3FFC) >> 2;
            uint16_t sample_data_decimal = (p_buf->RawData[i] & 0x3);
    
            float cacl_result = sample_data;
            float cacl_result_dec = 0;
            cacl_result_dec = (float)(sample_data_decimal & 0x1) / 2 + (float)((sample_data_decimal >> 1) & 0x1)
                            / 4;
            cacl_result += cacl_result_dec;
            DBG_DIRECT("io_adc_voltage_calculate: sample_data = %d, cacl_result = %d\r\n",
                    sample_data, (uint32_t)cacl_result);
    
    #endif
            float sample_voltage = 0;
            ADC_ErrorStatus error_status = NO_ERROR;
    #if (ADC_MODE_DIVIDE_OR_BYPASS == ADC_BYPASS_MODE)
            sample_voltage = ADC_GetVoltage(BYPASS_SINGLE_MODE, (int32_t)sample_data, &error_status);
    #else
            sample_voltage = ADC_GetVoltage(DIVIDE_SINGLE_MODE, (int32_t)sample_data, &error_status);
    #endif
            if (error_status < 0)
            {
                APP_PRINT_INFO1("[io_adc] io_adc_voltage_calculate: ADC parameter or efuse data error! error_status = %d",
                                error_status);
            }
            else
            {
                APP_PRINT_INFO2("[io_adc] io_adc_voltage_calculate: ADC rawdata = %d, voltage = %dmV ",
                                sample_data, (uint32_t)sample_voltage);
            }
        }
        memset(&ADC_Global_Data, 0, sizeof(ADC_Global_Data));
    }
    

Troubleshooting

  1. If the IC obtained has not been verified by FT, the ADC will not be able to convert to the correct voltage value. The following message will be printed within the log tool.

    [ADC]ADC_CalibrationInit fail!
    
  2. If the ADC sample value is incorrect, print the error status.

    [ADC]adc_sample_demo: ADC parameter or efuse data error! i = xxx, error_status = xxx