Continuous Mode - DMA Transfer

该示例通过使用 ADC 连续采样模式进行电压检测,采用 DMA 进行数据搬运。

该示例检测 P2_4,P2_5 和 P2_7 的输入电压。当 ADC FIFO 内的值达到设定的 waterlevel 值时,触发 DMA 搬运,读取 ADC FIFO 内的采样数据 raw data 并进行电压转换计算。

该示例中可通过配置宏 ADC_MODE_DIVIDE_OR_BYPASS 选择 ADC 的电压采样范围。

环境需求

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

开发套件

Hardware Platforms

Board Name

RTL8752H HDK

RTL8752H EVB

更多信息请参考 快速入门

硬件连线

连接 P2_4,P2_5 和 P2_7 至外部电压输入。

配置选项

该示例可配置的宏如下:

  1. ADC_MODE_DIVIDE_OR_BYPASS :配置该宏可选择 ADC 的电压采样范围,可选择的值如下。

    • ADC_DIVIDE_MODE :在 Divide Mode 下,ADC 的采样电压值范围为 0~3.3V。

    • ADC_BYPASS_MODE :在 Bypass Mode 下,ADC 的采样电压值范围为 0~0.9V。

编译和下载

该示例的工程路径如下:

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

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

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

  1. 打开工程文件。

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

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

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

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

测试验证

  1. ADC 初始化配置:

    1. 若配置为 ADC_DIVIDE_MODE ,则会打印如下 log。

      [ADC] ADC sample mode is divide mode !
      
    2. 若配置为 ADC_BYPASS_MODE ,则会打印如下 log。

      [ADC] ADC sample mode is bypass mode !
      
  2. ADC 采样结束后,会在 Debug Analyzer 工具内打印采集得到的 raw data 和转换后的电压值。

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

代码介绍

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

  1. 源码路径

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

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

源码路径

  • 工程路径: sdk\board\evb\io_sample\ADC\ContinuousMode+GDMA

  • 源码路径: sdk\src\sample\io_sample\ADC\ContinuousMode+GDMA

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

└── 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

初始化

当 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. 执行 ADC_CalibrationInit() 函数进行 ADC 校准。若返回值为 false,则 ADC 校准失败,可能是因为该 IC 未经 FT 导致,因此无法准确得到电压值。

    2. 初始化全局变量 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. board_init 中,执行 board_adc_init ,该函数为 PAD/PINMUX 设置,包含如下流程:

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

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

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

    1. 使能 RCC 时钟。

    2. 配置 ADC 的采样通道,配置通道 0 为 P2_4 单端模式,配置通道 1 为 P2_5 单端模式,配置通道 2 为 P2_7 单端模式,设置 Bitmap 为 0x07。

    3. 设置 ADC 采样时间与 Water Level 值。

    4. 若开启 Bypass Mode,执行函数 ADC_BypassCmd() 开启对应引脚的 Bypass 模式。

    5. 配置 ADC ADC_INT_FIFO_RD_ERRADC_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);
    }
    

    备注

    ADC 的 Continuous 模式下,数据默认存放至 ADC FIFO 中。

  5. driver_init 中执行 driver_gdma_adc_init ,该函数为 DMA 外设的初始化,包含如下流程:

    1. 初始化 ADC 数据接收数组。

    2. 设置 DMA 的传输方向为外设到内存传输。

    3. 设置源端地址固定,目的端地址自增。

    4. 设置源地址为 ADC FIFO,目的地址为 ADC_Recv_Buffer

    5. 配置 DMA 总传输完成中断 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);
    
    }
    

功能实现

  1. 在主函数中执行 os_sched_start() ,开启任务调度。当 stack 准备好时,执行 app_handle_dev_state_evt ,执行 io_adc_sample_start 开启 ADC 采样。

    1. 执行 GDMA_Cmd() ,使能 DMA 传输。

    2. 执行 ADC_ClearFIFO() ,清空 ADC FIFO。

    3. 执行 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*/
                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. 当 ADC FIFO 数据读取错误时,触发 ADC_INT_FIFO_RD_ERR 中断,进入中断处理函数 ADC_Handler

    1. 定义消息类型 IO_MSG_TYPE_ADC ,消息子类型为 IO_MSG_ADC_FIFO_READ_ERR ,发送 msg 给 task。

    2. 清除中断标志位。

    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. 当 ADC FIFO 数据溢出时,触发 ADC_INT_FIFO_OVERFLOW 中断,进入中断处理函数 ADC_Handler

    1. 定义消息类型 IO_MSG_TYPE_ADC,消息子类型为 IO_MSG_ADC_FIFO_OVERFLOW,发送 msg 给 task。

    2. 清空 ADC FIFO,使能 ADC 向 FIFO 中写入数据,清除中断标志位。

    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. 当 DMA 搬运数据完成时,触发 GDMA_INT_Transfer 中断,进入 DMA 中断处理函数 ADC_GDMA_Channel_Handler

    1. 失能 ADC 连续采样模式;清除 GDMA_INT_Transfer 中断挂起位。

    2. 定义消息类型 IO_MSG_TYPE_GDMA,保存 ADC_Recv_Buffer 数据,发送 msg 给 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. app_main_task 中循环检测消息队列。当检测到 msg 时,执行 app_handle_io_msg 函数对 msg 进行处理。

  6. app_handle_io_msg 函数中,若判断消息类型为 IO_MSG_TYPE_ADC ,执行 io_handle_adc_msg 函数,执行 io_handle_adc_error 。检测子消息类型,分别打印对应的错误信息。

    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. app_handle_io_msg 函数中,若判断消息类型为 IO_MSG_TYPE_GDMA ,执行 io_handle_gdma_msg 函数,执行 io_adc_voltage_calculate

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

    2. 执行 ADC_GetVoltage() ,根据采样模式计算采样电压值。

    3. 打印采样数据和采样电压。

    4. 重新设置 DMA 的源地址,目的地址,总传输数据量等,清除 DMA 中断标志位,重新使能 DMA。

    5. 清空 ADC FIFO,重新使能 ADC 连续转换模式。

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

常见问题

  1. 若拿到的 IC 未经过 FT 校验,ADC 则无法转换为正确的电压值。在 log 工具内会打印如下信息。

    [ADC]ADC_CalibrationInit fail!
    
  2. 若 ADC 采样值不正确,则会打印错误状态信息。

    ADC parameter or efuse data error! i = xxx, error_status = xxx