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 至外部电压输入。
配置选项
该示例可配置的宏如下:
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
请按照以下步骤操作构建并运行该示例:
打开工程文件。
按照 快速入门 中 编译 APP Image 给出的步骤构建目标文件。
编译成功后,在路径
mdk\bin
或gcc\bin
下会生成 app binapp_MP_xxx.bin
文件。按下 reset 按键,开始运行。
测试验证
ADC 初始化配置:
若配置为
ADC_DIVIDE_MODE
,则会打印如下 log。[ADC] ADC sample mode is divide mode !
若配置为
ADC_BYPASS_MODE
,则会打印如下 log。[ADC] ADC sample mode is bypass mode !
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 ...
代码介绍
该章节分为以下几个部分:
源码路径
工程路径:
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_init
,app_le_profile_init
等为 privacy 管理模块相关的初始化,参考 LE Peripheral Privacy 中的初始化流程介绍。
与外设相关的初始化流程具体如下:
在
global_data_init
中,执行global_data_adc_init
,该函数为全局初始化,包含如下流程:执行
ADC_CalibrationInit()
函数进行 ADC 校准。若返回值为 false,则 ADC 校准失败,可能是因为该 IC 未经 FT 导致,因此无法准确得到电压值。初始化全局变量
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)); }
在
board_init
中,执行board_adc_init
,该函数为 PAD/PINMUX 设置,包含如下流程:配置 PAD:设置引脚、SW 模式、PowerOn、无内部上拉、输出失能。
在执行
os_sched_start()
开启任务调度后,在app_main_task
主任务内,执行driver_init
对外设驱动进行初始化配置。在
driver_init
中执行driver_adc_init
,该函数为 ADC 外设的初始化,包含如下流程:使能 RCC 时钟。
配置 ADC 的采样通道,配置通道 0 为 P2_4 单端模式,配置通道 1 为 P2_5 单端模式,配置通道 2 为 P2_7 单端模式,设置 Bitmap 为 0x07。
设置 ADC 采样时间与 Water Level 值。
若开启 Bypass Mode,执行函数
ADC_BypassCmd()
开启对应引脚的 Bypass 模式。配置 ADC
ADC_INT_FIFO_RD_ERR
和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); }
备注
ADC 的 Continuous 模式下,数据默认存放至 ADC FIFO 中。
在
driver_init
中执行driver_gdma_adc_init
,该函数为 DMA 外设的初始化,包含如下流程:初始化 ADC 数据接收数组。
设置 DMA 的传输方向为外设到内存传输。
设置源端地址固定,目的端地址自增。
设置源地址为 ADC FIFO,目的地址为
ADC_Recv_Buffer
。配置 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); }
功能实现
在主函数中执行
os_sched_start()
,开启任务调度。当 stack 准备好时,执行app_handle_dev_state_evt
,执行io_adc_sample_start
开启 ADC 采样。执行
GDMA_Cmd()
,使能 DMA 传输。执行
ADC_ClearFIFO()
,清空 ADC FIFO。执行
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); }
当 ADC FIFO 数据读取错误时,触发
ADC_INT_FIFO_RD_ERR
中断,进入中断处理函数ADC_Handler
。定义消息类型
IO_MSG_TYPE_ADC
,消息子类型为IO_MSG_ADC_FIFO_READ_ERR
,发送 msg 给 task。清除中断标志位。
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); } ... }
当 ADC FIFO 数据溢出时,触发
ADC_INT_FIFO_OVERFLOW
中断,进入中断处理函数ADC_Handler
。定义消息类型
IO_MSG_TYPE_ADC
,消息子类型为IO_MSG_ADC_FIFO_OVERFLOW
,发送 msg 给 task。清空 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); } }
当 DMA 搬运数据完成时,触发
GDMA_INT_Transfer
中断,进入 DMA 中断处理函数ADC_GDMA_Channel_Handler
。失能 ADC 连续采样模式;清除
GDMA_INT_Transfer
中断挂起位。定义消息类型
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); }
在
app_main_task
中循环检测消息队列。当检测到 msg 时,执行app_handle_io_msg
函数对 msg 进行处理。在
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; } }
在
app_handle_io_msg
函数中,若判断消息类型为IO_MSG_TYPE_GDMA
,执行io_handle_gdma_msg
函数,执行io_adc_voltage_calculate
。从 msg 中取出采样数据。
执行
ADC_GetVoltage()
,根据采样模式计算采样电压值。打印采样数据和采样电压。
重新设置 DMA 的源地址,目的地址,总传输数据量等,清除 DMA 中断标志位,重新使能 DMA。
清空 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); }
常见问题
若拿到的 IC 未经过 FT 校验,ADC 则无法转换为正确的电压值。在 log 工具内会打印如下信息。
[ADC]ADC_CalibrationInit fail!
若 ADC 采样值不正确,则会打印错误状态信息。
ADC parameter or efuse data error! i = xxx, error_status = xxx