adc7scan1_dma_conv_order.c - Samples 7 channels sequentially with automatic channel scanning in ordered mode¶
Performs a basic config of the ADC and samples seven channels sequentially with automatic channel scanning. ADC values are 10-bit results. Samples are obtained continuously, and DMA is used to write a memory buffer. The DMA is done using Ordered mode, with a block size determined by CONVERSIONS_PER_INPUT. For a block size = 1 and ordered mode, this means that results are written to the DMA buffer in the order in which the conversion is done, in this case:
Uses DMA completion interrupts to get values from the DMA memory buffer. The DMA mode for Ordered mode should be DMA_AMODE_REGISTER_POSTINC (register mode, post increment) Main routine fetches the “latest” values from memory. This code assumes a maximum of 16 ANx inputs. For block size > 1, the main code averages the values for a given channel. Compare the main averaging code with ‘adc7scan1_dma1.c’ to see the difference in how results are stored in the buffer for ‘ordered’ mode versus ‘scatter/gather’.
Conversion results are printed to screen to match adc2pots1.c project (HEX values and voltages are printed.) This is only for PIC24 CPUs with DMA; see the Appendix H online supplement.
#include "pic24_all.h"
#include "stdio.h"
#if !defined(_DMA0IF) || defined(__PIC24E__) || defined(__dsPIC33E__)
# warning "This processor selection does not have a DMA module; this code example is incompatible with a PIC24 CPU that does not have DMA."
# warning "OR this is a E family processor, which this example does not support."
int main(void) {
return 0;
}
#else
#define CONFIG_LED2() CONFIG_RB5_AS_DIG_OUTPUT()
#define LED2 _LATB5
uncomment the next line to setup this project for a 12-bit ADC
//#define USE_12BIT_ADC
#ifdef USE_12BIT_ADC
#define ADC_LEN 12
#define ADC_NSTEPS 4096
#define ADC_12BIT_FLAG 1
#else
#define ADC_LEN 10
#define ADC_NSTEPS 1024
#define ADC_12BIT_FLAG 0
#endif
/***
*** HERE IS THE CODE!
***
***/
set this to one of the values of 1, 2, 4, 8, 16, 32, 64, 128
#define CONVERSIONS_PER_INPUT 1
#define MAX_CHANNELS 16
//DMA transfer size is in words.
#define MAX_TRANSFER (CONVERSIONS_PER_INPUT*MAX_CHANNELS) //make power of two for alignment to work
//DMA buffers, alignment is based on number of bytes
uint16_t au16_bufferA[MAX_TRANSFER] __attribute__((space(dma),aligned(MAX_TRANSFER*2)));
uint8_t u8_NumChannelsScanned; //need this global for the main averaging code
//generic DMA/ADC configuration function, enables scanning, uses DMA channel 0
//returns the number of channels that are scanned as specified by the mask.
uint8_t configDMA_ADC(uint16_t u16_ch0ScanMask, \
uint8_t u8_autoSampleTime, \
uint8_t u8_use12bit,
uint8_t u8_useScatterGather,
uint8_t u8_dmaLocsPerInput) {
uint8_t u8_i, u8_nChannels=0;
uint16_t u16_mask = 0x0001;
uint16_t u16_dmaMode;
compute the number of Channels the user wants to scan over
for (u8_i=0; u8_i<16; u8_i++) {
if (u16_ch0ScanMask & u16_mask)
u8_nChannels++;
u16_mask<<=1;
} //end for
if (u8_autoSampleTime > 31) u8_autoSampleTime=31;
AD1CON1bits.ADON = 0; // turn off ADC (changing setting while ADON is not allowed)
/** Configure the internal ADC **/
AD1CON1 = ADC_CLK_AUTO | ADC_AUTO_SAMPLING_ON;
#ifdef _AD12B
if (u8_use12bit)
AD1CON1bits.AD12B = 1;
else
AD1CON1bits.AD12B = 0;
#endif
if (u8_useScatterGather) {
AD1CON1bits.ADDMABM = 0;
u16_dmaMode = DMA_AMODE_PERIPHERAL_INDIRECT;
} else {
//order mode
AD1CON1bits.ADDMABM = 1;
u16_dmaMode = DMA_AMODE_REGISTER_POSTINC;
}
//AD1CON3 = ADC_CONV_CLK_INTERNAL_RC | (u8_autoSampleTime<<8);
//at FCY = 40 MHz, Tcy = 25 ns, and use ADC clock = 10* Tcy = 10 * 25 ns = 250 ns
//use clock based on Tcy so that we can accurately measure ADC clock period
AD1CON3 = ADC_CONV_CLK_SYSTEM | (u8_autoSampleTime<<8) |ADC_CONV_CLK_10Tcy;
//Note: PIC24H family reference manual (16.13.2) says that for 'ordered' mode, the
//SMPI bits should be cleared. However, when scanning, this seems to be incorrect as the
//settings that work are the same ones used for 'scatter/gather' mode.
AD1CON2 = ADC_VREF_AVDD_AVSS | ADC_CONVERT_CH0 | ADC_SCAN_ON | ((u8_nChannels-1)<<2);
#if (defined(__PIC24H__)|| defined(__dsPIC33F__))
AD1CHS0 = ADC_CH0_NEG_SAMPLEA_VREFN;
#else
AD1CHS = ADC_CH0_NEG_SAMPLEA_VREFN;
#endif
AD1CSSL = u16_ch0ScanMask;
switch (u8_dmaLocsPerInput) {
case 1 :
AD1CON4 = ADC_1_WORD_PER_INPUT;
break;
case 2 :
AD1CON4 = ADC_2_WORD_PER_INPUT;
break;
case 4 :
AD1CON4 = ADC_4_WORD_PER_INPUT;
break;
case 8 :
AD1CON4 = ADC_8_WORD_PER_INPUT;
break;
case 16 :
AD1CON4 = ADC_16_WORD_PER_INPUT;
break;
case 32 :
AD1CON4 = ADC_32_WORD_PER_INPUT;
break;
case 64 :
AD1CON4 = ADC_64_WORD_PER_INPUT;
break;
case 128 :
AD1CON4 = ADC_128_WORD_PER_INPUT;
break;
default:
AD1CON4 = ADC_1_WORD_PER_INPUT;
break;
}
//configure the DMA channel 0 interrupt
DMA0PAD = (unsigned int) &ADC1BUF0;
DMA0REQ = DMA_IRQ_ADC1;
DMA0STA = __builtin_dmaoffset(au16_bufferA);
DMA0CNT = (u8_nChannels * u8_dmaLocsPerInput)-1;
DMA0CON = //configure and enable the module Module
(DMA_MODULE_ON |
DMA_SIZE_WORD |
DMA_DIR_READ_PERIPHERAL |
DMA_INTERRUPT_FULL |
DMA_NULLW_OFF |
u16_dmaMode |
DMA_MODE_CONTINUOUS);
_DMA0IF = 0;
_DMA0IP = 6;
_DMA0IE = 1;
AD1CON1bits.ADON = 1; // turn on the ADC
return(u8_nChannels);
}
volatile uint16_t au16_buffer[MAX_TRANSFER];
volatile uint16_t au16_bufferSum[MAX_CHANNELS];
volatile uint8_t u8_waiting;
void _ISRFAST _DMA0Interrupt(void) {
uint8_t u8_i;
uint16_t* au16_adcHWBuff = (uint16_t*) &au16_bufferA;
_DMA0IF = 0;
if (u8_waiting ) {
for ( u8_i=0; u8_i<MAX_TRANSFER; u8_i++) {
au16_buffer[u8_i] = au16_adcHWBuff[u8_i];
} //end for()
u8_waiting = 0; // signal main() that data is ready
}
toggle a port pin so that we can measure how often DMA IRQs are coming in
LED2 = !LED2;
}
int main (void) {
uint8_t u8_i, u8_j, u8_k;
uint16_t u16_pot;
float f_pot;
configBasic(HELLO_MSG);
make RA0/AN0/VREF+ a digital input to kill the pullup and set the TRISA bit, then make it ANALOG so the ADC will work
CONFIG_RA0_AS_ANALOG();
CONFIG_RA1_AS_ANALOG();
CONFIG_RB2_AS_ANALOG();
CONFIG_RB3_AS_ANALOG();
CONFIG_RB14_AS_ANALOG();
CONFIG_RB13_AS_ANALOG();
CONFIG_RB12_AS_ANALOG();
CONFIG_LED2();
u8_NumChannelsScanned = configDMA_ADC( ADC_SCAN_AN0 | ADC_SCAN_AN1 | ADC_SCAN_AN4 | \
ADC_SCAN_AN5 | ADC_SCAN_AN10 | ADC_SCAN_AN11 | ADC_SCAN_AN12,
31, ADC_12BIT_FLAG, 0, CONVERSIONS_PER_INPUT);
u8_waiting = 1;
while (1) {
while (u8_waiting) {}; // wait for valid data in ISR
//data is updated in array by DMA ISR when u8_waiting flag is cleared
//iterate over channels, and average results for each channel
//data in array will not be updated again by DMA ISR until u8_waiting flag is set.
u8_k = 0; //buffer index
for (u8_j=0; u8_j<CONVERSIONS_PER_INPUT; u8_j++) {
for ( u8_i=0; u8_i<u8_NumChannelsScanned; u8_i++) { //each channel
//each result per channel
if (u8_j == 0) au16_bufferSum[u8_i] = au16_buffer[u8_k];
else au16_bufferSum[u8_i] += au16_buffer[u8_k];
u8_k++;
}
}
//zero out unused channels
for (u8_i=u8_NumChannelsScanned; u8_i<MAX_CHANNELS ; u8_i++) {
au16_bufferSum[u8_i] = 0;
}
//now average and print
for ( u8_i=0; u8_i<MAX_CHANNELS ; u8_i++) {
u16_pot = au16_bufferSum[u8_i]/CONVERSIONS_PER_INPUT; //take the average
f_pot = 3.3 / ADC_NSTEPS * u16_pot;
printf("r");
if (u8_i < 10) outChar( '0'+u8_i );
else outChar( 'A'-10+u8_i );
printf(":0x%04X=%1.3fV ", u16_pot, (double) f_pot );
if ((u8_i % 4) == 3) printf("\n");
} //end for()
printf("\n");
u8_waiting = 1;
doHeartbeat();
DELAY_MS(1500);
} //endof while()
} // endof main()
#endif