Burst mode problems
This forum thread has thrown up an odd problem with reading the CHN(26::24) channel number from the ADGDR register. After some investigation (and oddly the solution came to me while driving my son to a birthday party rather than banging the keyboard) I realised what the root cause is. This issue is only an issue if you are using back-to-back burst mode.
I'm not going to describe the problem, there are enough posts in this thread that describe it. But the solution is hidden in the results:-
Progam output
Array index 00 : ADC input channel 1 = 0x000 0.000 volts OV=0
Array index 01 : ADC input channel 2 = 0xfe9 3.281 volts OV=0
Array index 02 : ADC input channel 0 = 0x7f5 1.641 volts OV=0
What I expected here was channel 0, 1, 2 but we got 1, 2, 0. What we are actually seeing here is the results of ADDR0, ADDR1 and ADDR2 however, the channel number isn't latched into ADGDR because the ADGDR is not a register, it's an amalgmation of various pieces of information. So what we actually are seeing is a channel result but the channel number is the currently converting channel, not the channel that made the conversion.
The proof is simple. Lowering the ADC clock rate down a a much lower level (thus spreading out the conversion times) suddenly makes it work as expected. After much trial and error I found that at around 8Mhz sampling rate all works fine. But as we tip over the 8Mhz limit suddenly the channel number begins to slip because a new conversion starts before ADGDR is read.
At first glance one might expect the overrun bit to be set. However, the next conversion isn't on the channel we are about to read, it's moved on. The ADGDR overrun bit is a copy of "the last channel converted" and this information appears to be conserved (lateched). It's the CHN(26::24) that doesn't get preserved (latched).
Oddly enough, after experimenting I found that the ADC conversion clock frequency differs with regards to the method of reading the ADGDR. I tried three basic methods.
- Reading the ADGDR within main's while(1) loop (polling)
- Reading the ADGDR by setting up interrupts
- Reading the ADGDR using the GPDMA system.
In all the following examples we sample AD0.0, AD0.1 and AD0.2 with known voltages applied to each (Mbed p15, p16 and p17) in burst mode. Setup is ommitted for clarity in code snippets.
Polling
OK, lets look at a simple real time polling example (the one I used):-
int limit = 0;
while (1) {
uint32_t adval = LPC_ADC->ADGDR;
if(adval & (1UL << 31) && limit < 9 ) {
// DONE flag is set, so determine channel from CHN bits
channel = (adval >> 24) & 7;
overrun = (adval & (1UL << 30)) ? 1 : 0;
int value = (adval >> 4) & 0xFFF;
double fVal = 3.3 * (double)((double)value) / ((double)0x1000); // scale to 0v to 3.3v
pc.printf("ADC input (channel=%d) = 0x%03x %01.3f volts OV=%d\n", channel, value, fVal, overrun);
limit++; // Just used to prevent printf()ing forever, just display 9 conversions.
}
}
Now, it's worth noting that I used my MODSERIAL library for the serial port with a large TX buffer so that printf() didn't skew things too much. The "real time" processing portion of this is minimal. So, playing with ADCR's CLKDIV(15:8) what did I find? Well, I started loose channel information at around 184.6kHz (yes, you read that right, kilohertz, no where near the 12MHz one would normally run the ADC conversion clock). Given that simple piece of information I am not even going to bother continue with the polling scenorio. Suffice to say, polling with multiple channels in burst mode is simple not an option. So, lets move onto the more obvious solution, interrupts.
Interrupt driven system
Once a conversion is complete it is possible for the ADC_Handler interrupt service routine (ISR) to be invoked. Once inside the ISR we have two methods of getting the data we need.
Method 1
uint32_t stat = LPC_ADC->ADSTAT;
if (stat & 0x01) { values[limit] = LPC_ADC->ADDR0; ch[limit] = 0; }
else if(stat & 0x02) { values[limit] = LPC_ADC->ADDR1; ch[limit] = 1; }
else if(stat & 0x04) { values[limit] = LPC_ADC->ADDR2; ch[limit] = 2; }
else if(stat & 0x08) { values[limit] = LPC_ADC->ADDR3; ch[limit] = 3; }
else if(stat & 0x10) { values[limit] = LPC_ADC->ADDR4; ch[limit] = 4; }
else if(stat & 0x20) { values[limit] = LPC_ADC->ADDR5; ch[limit] = 5; }
else if(stat & 0x40) { values[limit] = LPC_ADC->ADDR6; ch[limit] = 6; }
else if(stat & 0x80) { values[limit] = LPC_ADC->ADDR7; ch[limit] = 7; }
limit++;
if (limit == 9) {
NVIC_DisableIRQ(ADC_IRQn);
}
Method 2
if (limit < 9) {
values[limit] = LPC_ADC->ADGDR;
ch[limit] = (values[limit] >> 24) & 0x7;
// Reading ADGDR does not reset the DONE bit and so alone
// will not clear the interrupt. The only way to clear it
// is to either read ADGDR and write ADCR or by reading
// the ADDRx register directly. Since we are in burst mode
// then we shouldn't be writing the ADCR so we are left with
// a manual read of the actual ADDRx.
uint32_t trash __attribute__((unused));
switch (ch[limit]) { // We might just aswell use stat like above.
case 0: trash = LPC_ADC->ADDR0; break;
case 1: trash = LPC_ADC->ADDR1; break;
case 2: trash = LPC_ADC->ADDR2; break;
case 3: trash = LPC_ADC->ADDR3; break;
case 4: trash = LPC_ADC->ADDR4; break;
case 5: trash = LPC_ADC->ADDR5; break;
case 6: trash = LPC_ADC->ADDR6; break;
case 7: trash = LPC_ADC->ADDR7; break;
}
limit++;
}
if (limit == 9) {
NVIC_DisableIRQ(ADC_IRQn);
}
Method 1
As expected this method produced a flawless set of results right up to 12MHz. However, it's worth noting here that in the ISR we are not doing any real time processing. We are just storing the results into a buffer. More about this in the concluesion at the end of this article.
Method 2
If you have looked at the code for Method 1 it should be obvious, why would you do this? Well, here's why. Even without doing any real time processing but only storing to a buffer the channel information within ADGDR began to slip at 3MHz. Below that ADC clock conversion frequency we kept value channel information. At or above that it became meaningless. And given that to clear the interrupt condition we still needed to read the ADDRx register this method is pointless. I did it for the sake of completeness of this investigation.
Using the GPDMA
Now, it's onto using the GPDMA to pipe the ADC results to a memory buffer, the method that cause this entire problem to be highlighted. By now you should have an understanding of the issues. It's all down to the ADC conversion frequency and just when ADGDR is read. And what it boils down to is even using the GPDMA and having the INT REQ line from the ADC feeding the GPDMA transfer logic, the CHN(26:24) began to slip at 8MHz. Given that the GPDMA mode relies totally on the existance of ADGDR (it cannot read an individual channel) then it would appear there's a flaw in the system somewhere. The idea of using the GPDMA to aquire a series of samples into a memory buffer seems like a good one. However, it seems it itself simply cannot keep up with back-to-back bursts when the ADC conversion frequency exceeds 8MHz.
It would appear on the whole to me that ADGDR only real purpose is to feed the GPDMA (there's no reason if using software not to read the actual real data within the ISR) it appears the ADGDR/GPDMA is flawed. It simply doesn't work upto the supposed "13MHz" the manual states.
Conclusions
Having got this far it should be obvious that burst mode isn't usable at all for "real time" processing. You need to buffer a series of samples and then post process these after capture (just like a digital storeage scope would, capture and then display).
If you want to use burst mode to gather a series of samples then there's really only two choices:-
- Use interrupts. This is really the only method that will get you up to 12MHz. However, if the CPU usage is too high and you need to "background it" then there's GPDMA mode.
- However, if you are going to use GPDMA you will need to use a workaround. If you want to go to 12MHz then you'll need to take into account the channel number slippage buried in your data buffer. Experimentation will be needed to ensure you understand how the slippage occurs depending on how many channels you sample and how long your data series is.
Hi,
Does anyone have sample code of how to use MODDMA to perform a number of two channel ADC conversion?
The ADC conversions need to be timer driven (so their sample frequency can be programmed and is xtal stable).
I read the manual and see that the ADC needs some setup that is not include in MODDMA (probably not possible there).