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:
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:
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:
Open sample project file.
To build the target, follow the steps listed on the Generating App Image in Quick Start.
After a successful compilation, the app bin
app_MP_xxx.bin
will be generated in the directorymdk\bin
orgcc\bin
.To download app bin into EVB board, follow the steps listed on the MP Tool Download in Quick Start.
Press reset button on EVB board and it will start running.
Experimental Verification
ADC Configurations:
If the ADC is configured as
ADC_DIVIDE_MODE
, print the following log.[ADC]ADC sample mode is divide mode !
If the ADC is configured as
ADC_BYPASS_MODE
, print the following log.[ADC]ADC sample mode is bypass mode !
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:
Peripheral initialization will be introduced in chapter Initialization.
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:
In
global_data_init
, executeglobal_data_adc_init
. This function is for global initialization and includes the following process: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.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)); }
In
board_init
, executeboard_adc_init
, which is responsible for PAD/PINMUX settings and includes the following process:Set pin as SW mode, PowerOn, internal Pull-None, disable output.
After executing
os_sched_start()
to start task scheduling, in theapp_main_task
main task, executedriver_init
to initialize and configure the peripheral drivers.In
driver_init
, executedriver_adc_init
, which is the initialization function for the ADC peripheral, including the following process:Enable RCC clock.
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.
Set ADC sampling time and WaterLevel.
If configured as bypass mode, execute
ADC_BypassCmd()
to enable the high resistance mode of the corresponding channel.Configure ADC interrupt
ADC_INT_FIFO_RD_ERR
andADC_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.
In
driver_init
executedriver_gdma_adc_init
, which is the initialization of the DMA peripheral and includes the following steps:Initialize the ADC data receiving array.
Set the DMA transfer direction to peripheral to memory.
Set the source address to be fixed and the destination address to increment.
Set the source address to ADC FIFO and the destination address to
ADC_Recv_Buffer
.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
Execute
os_sched_start()
to start task scheduling. When the stack is ready, executeapp_handle_dev_state_evt
and executeio_adc_sample_start
to start ADC sampling.Execute
GDMA_Cmd()
to enable DMA transfer.Execute
ADC_ClearFIFO()
to clear the ADC FIFO.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); }
When there is an ADC FIFO data read error, it triggers the
ADC_INT_FIFO_RD_ERR
interrupt, and enters the interrupt handling functionADC_Handler
.Define the message type
IO_MSG_TYPE_ADC
, with the message subtypeIO_MSG_ADC_FIFO_READ_ERR
, and send the msg to the task.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); } ... }
When the ADC FIFO data overflows, it triggers the
ADC_INT_FIFO_OVERFLOW
interrupt and enters the interrupt handler functionADC_Handler
.Define the message type
IO_MSG_TYPE_ADC
, with the message subtype asIO_MSG_ADC_FIFO_OVERFLOW
, and send the message to the task.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); } }
When the DMA completes data transfer, it triggers the
GDMA_INT_Transfer
interrupt and enters the DMA interrupt handler functionADC_GDMA_Channel_Handler
.Disable the ADC continuous sampling mode; clear the
GDMA_INT_Transfer
interrupt pending bit.Define the message type
IO_MSG_TYPE_GDMA
, save theADC_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); }
In
app_main_task
, loop to check the message queue. When a message (msg) is detected, execute theapp_handle_io_msg
function to process the msg.In the
app_handle_io_msg
function, if the message type is determined to beIO_MSG_TYPE_ADC
, execute theio_handle_adc_msg
function, and executeio_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; } }
In the
app_handle_io_msg
function, if the message type is determined to beIO_MSG_TYPE_GDMA
, execute theio_handle_gdma_msg
function and then executeio_adc_voltage_calculate
.Extract the sampling data from the msg.
Execute
ADC_GetVoltage()
to calculate the sampling voltage value based on the sampling mode.Print the sampling data and sampling voltage.
Reset the source address, destination address, total transfer data amount of the DMA, clear the DMA interrupt flag, and re-enable the DMA.
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
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!
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