Continuous Mode - DMA Transfer

This example demonstrates voltage detection using the ADC continuous sampling mode and employs DMA for data transfer.

The example detects the input voltages of P2_4, P2_5, and P2_7. When the value in the ADC FIFO reaches the set waterlevel, it triggers the DMA transfer, reads the sampling data raw data in the ADC FIFO, and performs voltage conversion calculations.

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

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_4, P2_5 and 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.

Building and Downloading

This sample can be found in the SDK folder:

Project file: board\evb\io_sample\ADC\ContinuousMode+GDMA\mdk

Project file: board\evb\io_sample\ADC\ContinuousMode+GDMA\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_0 = xxx, voltage_0 = xxxmV
    [io_adc] io_adc_voltage_calculate: ADC rawdata_1 = xxx, voltage_1 = 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\ContinuousMode+GDMA

  • Source code directory: sdk\src\sample\io_sample\ADC\ContinuousMode+GDMA

Source files are currently categorized into several groups as below.

└── Project: adc_continuous_gdma
    └── 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_gdma.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_Recv_Buffer.

    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_Recv_Buffer, 0, sizeof(ADC_Recv_Buffer));
    }
    
  2. In board_init, execute board_adc_init, which is responsible for PAD/PINMUX settings and includes the following process:

    1. 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_4 single-ended mode, configure channel 1 to P2_5 single-ended mode, configure channel 2 to P2_7 single-ended mode, set Bitmap to 0x07.

    3. Set ADC sampling time and WaterLevel.

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

    5. Configure ADC interrupt ADC_INT_FIFO_RD_ERR and ADC_INT_FIFO_OVERFLOW.

    void driver_adc_init(void)
    {
        RCC_PeriphClockCmd(APBPeriph_ADC, APBPeriph_ADC_CLOCK, ENABLE);
    
        ADC_InitTypeDef ADC_InitStruct;
        ADC_StructInit(&ADC_InitStruct);
    
        ADC_InitStruct.ADC_WaterLevel   = 4;
        /* Configure the ADC sampling schedule, a schedule represents an ADC channel data,
        up to 16, i.e. schIndex[0] ~ schIndex[15] */
        ADC_InitStruct.ADC_SchIndex[0]  = EXT_SINGLE_ENDED(ADC_SAMPLE_CHANNEL_4);
        ADC_InitStruct.ADC_SchIndex[1]  = EXT_SINGLE_ENDED(ADC_SAMPLE_CHANNEL_5);
        ADC_InitStruct.ADC_SchIndex[2]  = EXT_SINGLE_ENDED(ADC_SAMPLE_CHANNEL_7);
        ADC_InitStruct.ADC_Bitmap       = 0x07;
        /* Configuration of ADC continuous sampling cycle. */
        ADC_InitStruct.ADC_SampleTime   = ADC_CONTINUOUS_SAMPLE_PERIOD;
    
        ADC_InitStruct.ADC_PowerAlwaysOnEn = ADC_POWER_ALWAYS_ON_ENABLE;
    
        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_4, ENABLE);
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_5, ENABLE);
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_7, ENABLE);
        APP_PRINT_INFO0("[io_adc]driver_adc_init: ADC sample mode is bypass mode !");
    #else
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_4, DISABLE);
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_5, DISABLE);
        ADC_BypassCmd(ADC_SAMPLE_CHANNEL_7, DISABLE);
    #endif
    
        ADC_INTConfig(ADC, ADC_INT_FIFO_RD_ERR, ENABLE);
        ADC_INTConfig(ADC, ADC_INT_FIFO_OVERFLOW, 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);
    }
    

    Note

    In Continuous mode of ADC, data is by default stored in the ADC FIFO.

  5. In driver_init execute driver_gdma_adc_init, which is the initialization of the DMA peripheral and includes the following steps:

    1. Initialize the ADC data receiving array.

    2. Set the DMA transfer direction to peripheral to memory.

    3. Set the source address to be fixed and the destination address to increment.

    4. Set the source address to ADC FIFO and the destination address to ADC_Recv_Buffer.

    5. Configure DMA total transfer complete interrupt GDMA_INT_Transfer.

    void driver_gdma_adc_init(void)
    {
        uint32_t i = 0;
    
        /* Initialize data buffer which for storing data from adc */
        for (i = 0; i < GDMA_TRANSFER_SIZE; i++)
        {
            ADC_Recv_Buffer[i] = 0;
        }
    
        /* GDMA init */
        GDMA_InitTypeDef GDMA_InitStruct;
        GDMA_StructInit(&GDMA_InitStruct);
    
        GDMA_InitStruct.GDMA_ChannelNum         = ADC_GDMA_CHANNEL_NUM;
        GDMA_InitStruct.GDMA_DIR                = GDMA_DIR_PeripheralToMemory;
        GDMA_InitStruct.GDMA_BufferSize         = GDMA_TRANSFER_SIZE;//Determine total transfer size
        GDMA_InitStruct.GDMA_SourceInc          = DMA_SourceInc_Fix;
        GDMA_InitStruct.GDMA_DestinationInc     = DMA_DestinationInc_Inc;
        GDMA_InitStruct.GDMA_SourceDataSize     = GDMA_DataSize_HalfWord;
        GDMA_InitStruct.GDMA_DestinationDataSize = GDMA_DataSize_HalfWord;
        GDMA_InitStruct.GDMA_SourceMsize        = GDMA_Msize_4;
        GDMA_InitStruct.GDMA_DestinationMsize   = GDMA_Msize_4;
        GDMA_InitStruct.GDMA_SourceAddr         = (uint32_t)(&(ADC->FIFO));
        GDMA_InitStruct.GDMA_DestinationAddr    = (uint32_t)ADC_Recv_Buffer;
        GDMA_InitStruct.GDMA_SourceHandshake    = GDMA_Handshake_ADC;
    
        GDMA_Init(ADC_GDMA_Channel, &GDMA_InitStruct);
    
        GDMA_INTConfig(ADC_GDMA_CHANNEL_NUM, GDMA_INT_Transfer, ENABLE);
    
        /* GDMA irq init */
        NVIC_InitTypeDef NVIC_InitStruct;
        NVIC_InitStruct.NVIC_IRQChannel = ADC_GDMA_Channel_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 io_adc_sample_start to start ADC sampling.

    1. Execute GDMA_Cmd() to enable DMA transfer.

    2. Execute ADC_ClearFIFO() to clear the ADC FIFO.

    3. 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 io_adc_sample_start(void)
    {
        /* Start to receive data */
        GDMA_Cmd(ADC_GDMA_CHANNEL_NUM, ENABLE);
    
        /* Clear adc fifo */
        ADC_ClearFIFO(ADC);
        /* Enable adc */
        ADC_Cmd(ADC, ADC_CONTINUOUS_MODE, ENABLE);
    }
    
  2. When there is an ADC FIFO data read error, it triggers the ADC_INT_FIFO_RD_ERR interrupt, and enters the interrupt handling function ADC_Handler.

    1. Define the message type IO_MSG_TYPE_ADC, with the message subtype IO_MSG_ADC_FIFO_READ_ERR, and send the msg to the task.

    2. Clear the interrupt flag.

    void ADC_Handler(void)
    {
        if (ADC_GetINTStatus(ADC, ADC_INT_FIFO_RD_ERR) == SET)
        {
            T_IO_MSG int_adc_msg;
    
            int_adc_msg.type = IO_MSG_TYPE_ADC;
            int_adc_msg.subtype = IO_MSG_ADC_FIFO_READ_ERR;
            if (false == app_send_msg_to_apptask(&int_adc_msg))
            {
                APP_PRINT_ERROR0("[io_adc]ADC_Handler: 1111Send int_adc_msg failed!");
                //Add user code here!
                io_adc_sample_stop();
                ADC_ClearINTPendingBit(ADC, ADC_INT_FIFO_RD_ERR);
                return;
            }
            ADC_ClearINTPendingBit(ADC, ADC_INT_FIFO_RD_ERR);
        }
        ...
    }
    
  3. When the ADC FIFO data overflows, it triggers the ADC_INT_FIFO_OVERFLOW interrupt and enters the interrupt handler function ADC_Handler.

    1. Define the message type IO_MSG_TYPE_ADC, with the message subtype as IO_MSG_ADC_FIFO_OVERFLOW, and send the message to the task.

    2. Clear the ADC FIFO, enable ADC to write data into the FIFO, and clear the interrupt flag.

    void ADC_Handler(void)
    {
        ...
        if (ADC_GetINTStatus(ADC, ADC_INT_FIFO_OVERFLOW) == SET)
        {
            ADC_WriteFIFOCmd(ADC, DISABLE);
            DBG_DIRECT("ADC_INT_FIFO_OVERFLOW");
    
            T_IO_MSG int_adc_msg;
    
            int_adc_msg.type = IO_MSG_TYPE_ADC;
            int_adc_msg.subtype = IO_MSG_ADC_FIFO_OVERFLOW;
            if (false == app_send_msg_to_apptask(&int_adc_msg))
            {
                APP_PRINT_ERROR0("[io_adc]ADC_Handler: Send int_adc_msg failed!");
                io_adc_sample_stop();
                ADC_ClearINTPendingBit(ADC, ADC_INT_FIFO_OVERFLOW);
                return;
            }
            ADC_ClearFIFO(ADC);
            ADC_WriteFIFOCmd(ADC, ENABLE);
            ADC_ClearINTPendingBit(ADC, ADC_INT_FIFO_OVERFLOW);
        }
    }
    
  4. When the DMA completes data transfer, it triggers the GDMA_INT_Transfer interrupt and enters the DMA interrupt handler function ADC_GDMA_Channel_Handler.

    1. Disable the ADC continuous sampling mode; clear the GDMA_INT_Transfer interrupt pending bit.

    2. Define the message type IO_MSG_TYPE_GDMA, save the ADC_Recv_Buffer data, and send the message to the task.

    void ADC_GDMA_Channel_Handler(void)
    {
        ADC_Cmd(ADC, ADC_CONTINUOUS_MODE, DISABLE);
    
        T_IO_MSG int_gdma_msg;
    
        int_gdma_msg.type = IO_MSG_TYPE_GDMA;
        int_gdma_msg.subtype = 0;
        int_gdma_msg.u.buf = (void *)(ADC_Recv_Buffer);
        if (false == app_send_msg_to_apptask(&int_gdma_msg))
        {
            APP_PRINT_ERROR0("[io_adc]ADC_GDMA_Channel_Handler: Send int_gdma_msg failed!");
            io_adc_sample_stop();
            GDMA_ClearINTPendingBit(ADC_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
            return;
        }
        GDMA_ClearINTPendingBit(ADC_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
    }
    
  5. 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.

  6. 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_handle_adc_error. Detect the sub-message type and print the corresponding error message accordingly.

    static void io_handle_adc_error(T_IO_MSG *io_adc_msg)
    {
        uint16_t adc_msg_subtype = io_adc_msg->subtype;
    
        switch (adc_msg_subtype)
        {
        case IO_MSG_ADC_FIFO_READ_ERR:
            {
                APP_PRINT_ERROR0("[io_adc]io_handle_adc_error: IO_MSG_ADC_FIFO_READ_ERR!");
            }
            break;
        case IO_MSG_ADC_FIFO_OVERFLOW:
            {
                APP_PRINT_ERROR0("[io_adc]io_handle_adc_error: IO_MSG_ADC_FIFO_OVERFLOW!");
            }
            break;
        default:
            break;
        }
    }
    
  7. In the app_handle_io_msg function, if the message type is determined to be IO_MSG_TYPE_GDMA, execute the io_handle_gdma_msg function and then execute io_adc_voltage_calculate.

    1. Extract the sampling data from the msg.

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

    3. Print the sampling data and sampling voltage.

    4. Reset the source address, destination address, total transfer data amount of the DMA, clear the DMA interrupt flag, and re-enable the DMA.

    5. Clear the ADC FIFO and re-enable the ADC continuous mode.

    static void io_adc_voltage_calculate(T_IO_MSG *io_adc_msg)
    {
        uint16_t *p_buf = io_adc_msg->u.buf;
    
        for (uint32_t i = 0; i < GDMA_TRANSFER_SIZE; i++)
        {
            uint16_t sample_data = p_buf[i];
            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_INFO2("[io_adc]io_adc_voltage_calculate: ADC parameter or efuse data error! i = %d, error_status = %d",
                                i, error_status);
            }
            else
            {
                APP_PRINT_INFO4("[io_adc]io_adc_voltage_calculate: ADC rawdata_%-4d = %d, voltage_%-4d = %dmV ", i,
                                sample_data, i, (uint32_t)sample_voltage);
            }
        }
    
        platform_delay_ms(1000);
        /* Restart dma and adc */
        GDMA_SetSourceAddress(ADC_GDMA_Channel, (uint32_t)(&(ADC->FIFO)));
        GDMA_SetDestinationAddress(ADC_GDMA_Channel, (uint32_t)(ADC_Recv_Buffer));
        GDMA_SetBufferSize(ADC_GDMA_Channel, GDMA_TRANSFER_SIZE);
        GDMA_ClearINTPendingBit(ADC_GDMA_CHANNEL_NUM, GDMA_INT_Transfer);
        GDMA_Cmd(ADC_GDMA_CHANNEL_NUM, ENABLE);
        ADC_ClearFIFO(ADC);
        ADC_Cmd(ADC, ADC_CONTINUOUS_MODE, ENABLE);
    }
    

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