Introduction
Problem Statement: K32L2B ADCs don't have the capability to scan through all the connected channels in one conversion. The input channels are muxed into the ADC, So you need to set the ADCx_Rn register to the channel number you want to convert, Run the conversion, read the results and then Set the other ADCx_Rn in that ADC to the next channel to be converted. The next conversion can then be triggered. You have to tell each conversion the input channel it is to convert. Everytime setting a new channel and reading previous channel data can consume much CPU core bandwidth.
The goal of this example is to read all ADC inputs of Kinetis K32L2B in a row without the need of using CPU core for switching channels and reading individual result values.
Project Description
The DMA in this example is used for controlling all the channel switching, reading the results for series of measurement into a memory buffer.
The Direct Memory Access (DMA) channels are configured for writing and reading ADC registers the following way:
DMA channel 0 reads converted results (ADC0_RA register) and stores them in g_adc16SampleDataArray[ ].
DMA channel 1 selects the ADC channel and starts conversion (ADC0_SC1A register) using values from memory g_adc16ChannelDataArray[ ]
The data for the DMA channel 1 is prepared in the g_adc16ChannelDataArray prepared in memory and the DMA operates in the following cycle:
Step 1 In the beginning, the DMA channel 1 transfer is started using software trigger. This configures the channel and starts the conversion.
Step 2 After the conversion is complete, the result is read by DMA channel 0 and stored to results array. Channel linking executes the Channel 1 transfer and the cycle continues.
After all needed channels are measured (DMA byte counter reaches 0), the DMA Interrupt is invoked so the user code is notified.
See the following figure describing the process:
Driver Configuration
1. ADC Init (ADC16_configuration)
Resolution is 16bit
Single step conversion
Software triggered
Enable DMA on COCO=1(conversion complete)
Code:
static void ADC16_Configuration(void) { // adc16_config_t adcUserConfig;
ADC16_GetDefaultConfig(&adcUserConfig); adcUserConfig.resolution = kADC16_Resolution16Bit; adcUserConfig.enableContinuousConversion = false; adcUserConfig.clockSource = kADC16_ClockSourceAlt0;
adcUserConfig.enableAsynchronousClock = false; adcUserConfig.clockDivider = kADC16_ClockDivider2;
adcUserConfig.longSampleMode = kADC16_LongSampleCycle24; adcUserConfig.enableLowPower = false; #if ((defined BOARD_ADC_USE_ALT_VREF) && BOARD_ADC_USE_ALT_VREF) adcUserConfig.referenceVoltageSource = kADC16_ReferenceVoltageSourceValt; #endif ADC16_Init(DEMO_ADC16_BASEADDR, &adcUserConfig); ADC16_SetHardwareAverage(DEMO_ADC16_BASEADDR,kADC16_HardwareAverageCount4);
/* Configure SIM for ADC hw trigger source selection */ // SIM->SOPT7 |= 0x8EU;
#if defined(FSL_FEATURE_ADC16_HAS_CALIBRATION) && FSL_FEATURE_ADC16_HAS_CALIBRATION /* Auto calibration */ if (kStatus_Success == ADC16_DoAutoCalibration(DEMO_ADC16_BASEADDR)) { PRINTF("ADC16_DoAutoCalibration() Done.\r\n"); } else { PRINTF("ADC16_DoAutoCalibration() Failed.\r\n"); } #endif
/* Enable software trigger. */ ADC16_EnableHardwareTrigger(DEMO_ADC16_BASEADDR, false); /* Enable DMA. */ ADC16_EnableDMA(DEMO_ADC16_BASEADDR, true);
}
2. DMA Channel0(DMA_config_ch_0())
32-bit results are transferred from ADC0_RA register (see proprerty Data source / Address).
Transfer mode is Cycle-steal, which means that only one transaction is done per each external request.
Channel linking is set to trigger channel 1 after each transfer
DMA mux settings for the Channel 0 are enabled and ADC0_DMA_Request is selected, which is the signal from ADC when the conversion ends.
External request (request from ADC) is Enabled to start the transfer. Byte count will also be changed before every sequence
Auto Increment of destination address after each transfer to point to next index in adc result array (PeripheraltoMemory)
3. DMA Channel1(DMA_config_ch_1())
DMA channel 1 selects the ADC channel and starts conversion (ADC0_SC1A register) using values from memory array g_adc16ChannelDataArray[n].
8 bits of data is transferred at once.
Auto Increment of source address takes place to point towards next index in array.
No channel is linked after the transfer ends.
No external channel request is selected, this channel transfer is triggered by linking from CH0.
Code:
void get_DMA_Channel_link(dma_channel_link_config_t *config) { config->linkType = 2; config->channel1 = 1; }
static void DMA_config_ch_0() { DMA_CreateHandle(&g_DMA_Handle, DEMO_DMA_BASEADDR, DEMO_DMA_CHANNEL); DMA_SetCallback(&g_DMA_Handle, DMA_Callback, NULL); DMA_PrepareTransfer(&g_transferConfig, (void *)ADC16_RESULT_REG_ADDR, sizeof(uint32_t), &g_adc16SampleDataArray[0], sizeof(uint32_t), 20, kDMA_PeripheralToMemory); DMA_SubmitTransfer(&g_DMA_Handle, &g_transferConfig, kDMA_EnableInterrupt); get_DMA_Channel_link(&channel_link); DMA_SetChannelLinkConfig(g_DMA_Handle.base,0,&channel_link); }
static void DMA_config_ch_1() { DMA_CreateHandle(&g_DMA_Handle_channel_1, DEMO_DMA_BASEADDR, 1); // DMA_SetCallback(&g_DMA_Handle_channel_1, DMA_Callback_channel_1, NULL); DMA_PrepareTransfer(&g_transferConfig_channel_1,&g_adc16ChannelDataArray[0], sizeof(uint8_t), (void *)CHANNEL_ADDRESS, sizeof(uint8_t), 5, kDMA_MemoryToPeripheral); DMA_SubmitTransfer(&g_DMA_Handle_channel_1, &g_transferConfig_channel_1, kDMA_NoOptions); DMA_TriggerChannelStart(g_DMA_Handle_channel_1.base,1); }
4. Running the Demo
Import the attached project into MCUXpresso v11.2, SDK v2.8.0 K32L2B
Build the project
Connect FRDM K32L2B board with PC
Debug and Run the application
Watch ADC result in g_adc16SampleDataArray[n] in Global variables window for the channels SC1A data given in g_adc16ChannelDataArray[n] respectively
Attached is the Project for reference.
Do let me know if any other information required.
Thanks!!
View full article