I wanted to use the ArduinoFFT library for a sound reactive project but the Nano Every which I'm using has the atmega4809 instead of 328 so I need to initialize the ADC differently.
Just trying to get it working for now, using a potentiometer as input and sending the value to serial monitor. The problem is, the values I'm reading in are not correct. They're sort of all over the place, as in wildly bouncing between ~100-1023. There's no problem with the wiring because it works as intended when I use analogRead() instead of initializing the ADC through its registers.
Here's what I got. Any ideas? Thanks in advance!
#include <avr/io.h>
#include <stdbool.h>
uint16_t adcVal;
void ADC0_init(void);
uint16_t ADC0_read(void);
void ADC0_start(void);
bool ADC0_conversionDone(void);
void ADC0_init(void){
//disable digital input buffer
PORTD.PIN4CTRL &= ~PORT_ISC_gm;
PORTD.PIN4CTRL |= PORT_ISC_INPUT_DISABLE_gc;
//disable pull-up resistor
PORTD.PIN4CTRL &= ~PORT_PULLUPEN_bm;
// divide clock by 4 | set reference voltage to VDD
ADC0.CTRLC = ADC_PRESC_DIV4_gc | ADC_REFSEL_VDDREF_gc;
// Enable ADC | 10 bit mode
ADC0.CTRLA = ADC_ENABLE_bm | ADC_RESSEL_10BIT_gc;
// Select ADC channel - using Arduino Nano Every pin A6 which is PD4 which is channel AIN4
ADC0.MUXPOS = ADC_MUXPOS_AIN4_gc;
// Enable freerun mode
ADC0.CTRLA |= ADC_FREERUN_bm;
}
uint16_t ADC0_read(void){
// clear interrupt flag by writing 1
ADC0.INTFLAGS = ADC_RESRDY_bm;
// return value stored in result register
return ADC0.RES;
}
void ADC0_start(void){
// start conversion
ADC0.COMMAND = ADC_STCONV_bm;
}
bool ADC0_conversionDone(void){
// returns 1 if result ready interrupt flag is set
return (ADC0.INTFLAGS & ADC_RESRDY_bm);
}
void setup() {
Serial.begin(9600);
ADC0_init();
ADC0_start();
}
void loop() {
if(ADC0_conversionDone()){
adcVal = ADC0_read();
}
Serial.println(adcVal);
}
Isn't clock/4 (4MHz) too fast? Should be less than 1.5MHz.
This isn't the order of initialization recommended in the datasheet. In particular, it suggests that ENABLE happen last. Since the ADC will presumably already have been initialized by the Arduino code, you may have to clear the ENABLE bit before changing the config. (I didn't actually see anything that said that the config can only be changed while the ADC is disabled, but that sort of thing is pretty common.)
Hey, so this totally works and im ecstatic. I only had to change ADC0_RESL to ADC0_RES since RESL only accesses the lower 8 bits of resolution. Or at least that's what it seems like from looking at the datasheet and the fact that adcVal overflows back to 0 once it goes past 255. But its working with the full 10 bit resolution now. Now I gotta see if I can implement the free running mode on top of this.
In general, your init doesn't seem that different from mine. So I'm not really sure what was going wrong with mine.
I have some general coding questions if you don't mind:
Why do you use ADC0_COMMAND instead of ADC0.COMMAND or ADC0_CTRLA instead of ADC0.CTRLA etc? Like what is the difference between using a period or underscore with struct variables?
in this while loop, what is the purpose of bitwise AND'ing ADC0_COMMAND with ADC_STCONV_bm? According to the datasheet, ADC0_COMMAND is 1 while a conversion is in progress and automatically clears to 0 when the conversion is done, so shouldn't that in and of itself satisfy the logic for the while loop?
Is this a requirement somewhere I didn't see? I used 4 as the prescaler because thats what was used in the example code in the app note.
hmm, I noticed this too and actually agree with you. But again I just followed the order that was in the app note example code.
So based on your comments I changed the prescaler to 32 and moved the enable to the end of the initialization. And I'm actually reading the right values! However, it only reads it correctly on startup, it never updates adcVal with new values. So I'm not sure what the problem is there. Either way, progress is being made!
uint16_t ADC0_read(void){
resultCopy = ADC0.RES;
// clear interrupt flag by writing 1
ADC0.INTFLAGS = ADC_RESRDY_bm;
// return value stored in result register
return resultCopy;
}
Oh, I was just using (playing with) the low level register defines. The resource (header file) for that is iom4809.h. This file is difficult to track down, it might be available on github somewhere, but it also might be on your PC after installing Atmel Studio or MPLAB or maybe Visual Studio. On my PC, the folder is C:\Program Files (x86)\Atmel\Studio\7.0\packs\atmel\ATmega_DFP\1.6.364\xc8\avr\include\avr iom4809.h (218.2 KB)
The underscore versions are not "structure variables." The low-level defines create bother the structure based definitions (with the "."), and old-style single-register defintions (the ones with underscores.) It's nice that the naming is pretty consistent!
(I believe that the structure-based accesses should be slightly more efficient than individual accesses on most modern CPU/Compiler combimations. The compiler will generally put the base address of the peripheral in an index register, and thereafter reference the sub-parts as index+offset load/stores, which are smaller than a load/store to an arbitrary address. Especially if you access more than one of the sub-parts "nearby.")
iom4809.h. This file is difficult to track down
On a typical windows Arduino install, it should be in: ...\AppData\Local\Arduino15\packages\DxCore\tools\avr-gcc\7.3.0-atmel3.6.1-azduino4b\avr\include\avr\iom4809.h
I'm going to be honest, this answer doesn't make a lot of sense to me. Unfortunately I lack the knowledge of C/C++ to really understand so a lot of the terms you are using fly over my head. Maybe I could pick your brain a little more?
I have some working code here, got the ADC working properly in free running mode. So for all intents and purposes, I've achieved what I came here to do. You can see that I'm using the underscores. It also functions identically if I change the underscores to periods (e.g., if I change ADC0_COMMAND to ADC.COMMAND). So what exactly are they doing differently from each other behind the scenes?
Perusing through the source files in platformIO, it shows that, for example, ADC0_COMMAND is in a #define for _SFR_MEM8(0x0608). And then ADC0.COMMAND is the variable COMMAND within the ADC_t struct (could be wrong, i really don't fully understand structs yet). So how are they accomplishing the same thing?
Huh. It turns out that in this case they produce exactly the same code. That wasn't what I expected.
ADC0_COMMAND is in a #define for _SFR_MEM8(0x0608). And then ADC0.COMMAND is the variable COMMAND within the ADC_t struct
So how are they accomplishing the same thing?
ADC0_COMMAND is defined as a a register at address 0x608. Simple.
The ADC_t struct defines that COMMMAND is the 8th byte of the ADC_t struct, and elsewhere ADC0 will be defined as an ADC_t structure that starts at 0x600. So they both end up referencing the register at 0x608.
The advantages of the struct method is that you could have a second ADC, ADC1, and you'd only have to define ADC1 as being a struct that starts at 0x700, instead of having to defined ALL of the ADC1_xxx addresses. And sometimes the compiler will load "the address of ADC0" into an "index register", and then notice that all the pieces are a "short distance away", and use a smaller (or fewer) instructions to access "8 past the start of the struct" than it would use to access multiple individual addresses.
On an AVR, this is the difference between LDS Rn, 0x608 ; (a 32bit instruction)
and LDD Rn, Z+8 ; (a 16bit instruction)
Which isn't much at all, especially since it would also have to set up Z.
Call it a bit more than half the size. And the same speed.
On an ARM, you ALWAYS have to set up the equivalent of Z, which is usually a 16bit instruction plus a 32bit constant (or two 16bit instructions), so it gets closer to 1/3 the size (and 3x faster.)
Really great comment, thank you, this helps a lot. So if I'm understanding right, if I do:
ADC0_COMMAND |= ADC_STCONV_bm;
I am writing a 1 directly to the first bit of the memory address 0x608. Nothing more than that. It is completely independent from the ADC0 struct.
If I do:
ADC0.COMMAND |= ADC_STCONV_bm;
I am writing a 1 to the first bit of the 8bit variable COMMAND. COMMAND is the 8th byte within the ADC0 struct, and the ADC0 struct starts at 0x600 so this 1 gets written to the first bit of 0x608. Thus it produces the same result.
So I had another unrelated question; In the example code from microchips app note, they use a simple function which returns 1 when the ADC0 conversion is done and a result is ready to be read, like this:
What is the reason they do a bitwise AND with ADC_RESRDY_bm? ADC_RESRDY_bm is equal to 0x01. Is this used to make sure that if any other bit in the INTFLAGS register is set, it doesn't evaluate the logic expression as true?
Yes; exactly.
The value & BITMASK construct ("Pattern"?) is extremely common for checking whether any bits from the mask are set. The single-bit mask checks a single bit.