-
Notifications
You must be signed in to change notification settings - Fork 148
Commit
Also add init_ADC1() to wiring.c for parts that have that.
- Loading branch information
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -103,6 +103,10 @@ int8_t digitalRead(pin_size_t pinNumber); | |
void DACReference(uint8_t mode); | ||
#endif | ||
|
||
#ifdef ADC0 | ||
This comment has been minimized.
Sorry, something went wrong.
This comment has been minimized.
Sorry, something went wrong.
SpenceKonde
via email
Author
Owner
|
||
void init_ADC1(void); | ||
#endif | ||
|
||
#ifndef DISABLEMILLIS | ||
unsigned long millis(void); | ||
void init_millis(); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,9 +6,117 @@ Free-running mode is also particularly useful if you need to do something if the | |
|
||
## Enabling free-running mode | ||
|
||
``` | ||
ADC0.MUXPOS=0x06; //reads from PA6/arduino pin 2, ADC0 channel 6 | ||
ADC0.CTRLA=ADC_ENABLE_bm|ADC_FREERUN_bm; //start in freerun | ||
ADC0.COMMAND=ADC_STCONV_bm; //start first conversion! | ||
``` | ||
|
||
The MUXPOS is the number of the analog channel that you want to read in free running mode. See the table below - you can also use digitalPinToAnalogInput(p) - obviously this only works for ADC0. | ||
|
||
Pin | 8-pin | 14-pin | 20-pin | 24-pin | ADC0 | ADC1 | ||
--------|-------|--------|--------|--------|-------|---- | ||
PIN_PA0 | 5 | 11 | 17 | 21 | AIN0 | - | ||
PIN_PA1 | 2 | 8 | 14 | 18 | AIN1 | - | ||
PIN_PA2 | 3 | 9 | 15 | 19 | AIN2 | - | ||
PIN_PA3 | 4 | 10 | 16 | 20 | AIN3 | - | ||
PIN_PA4 | - | 0 | 0 | 0 | AIN4 | AIN0 | ||
PIN_PA5 | - | 1 | 1 | 1 | AIN5 | AIN1 | ||
PIN_PA6 | 0 | 2 | 2 | 2 | AIN6 | AIN2 | ||
PIN_PA7 | 1 | 3 | 3 | 3 | AIN7 | AIN3 | ||
PIN_PB7 | - | - | - | 4 | - | AIN4 | ||
PIN_PB6 | - | - | - | 5 | - | AIN5 | ||
PIN_PB5 | - | - | 4 | 6 | AIN8 | - | ||
PIN_PB4 | - | - | 5 | 7 | AIN9 | - | ||
PIN_PB5 | - | 4 | 6 | 8 | - | - | ||
PIN_PB4 | - | 5 | 7 | 9 | - | - | ||
PIN_PB1 | - | 6 | 8 | 10 | AIN10 | - | ||
PIN_PB0 | - | 7 | 9 | 11 | AIN11 | - | ||
PIN_PC0 | - | - | 10 | 12 | - | AIN6 | ||
PIN_PC1 | - | - | 11 | 13 | - | AIN7 | ||
PIN_PC2 | - | - | 12 | 14 | - | AIN8 | ||
PIN_PC3 | - | - | 13 | 15 | - | AIN9 | ||
PIN_PC4 | - | - | - | 16 | - | AIN10 | ||
PIN_PC5 | - | - | - | 17 | - | AIN11 | ||
|
||
## Reading Results | ||
When using the ADC in free running mode, you must write your own code to get the value. Typically, you want to grab the most recent value; one decision to make is whether you want to wait for a reading if you have already read the value since the last reading was taken, or if that doesn't matter. | ||
|
||
Reading the value when you want to make sure you have a fresh value is straightforward: | ||
``` | ||
uint16_t getAnalogValue() { | ||
while (!(ADC0.INTFLAGS & ADC_RESRDY_bm)) { // Check for the RESRDY flag (Result Ready) | ||
;//spin if there's no new result | ||
} | ||
return ADC0.RES; // return the current result on channel A - this clears the RESRDY flag | ||
} | ||
``` | ||
This comment has been minimized.
Sorry, something went wrong.
MCUdude
Contributor
|
||
If you don't, it's even simpler: | ||
``` | ||
uint16_t getAnalogValue() { | ||
return ADC0.RES; // return the most recent result on channel A | ||
} | ||
``` | ||
|
||
Alternately, you may wish to use an ISR when the ADC conversion is complete - though be aware that with the ADC so much faster on these parts, you may find yourself dealing with more interrupts than you were expecting (unless you are using the accumulation funciton, see below). This makes sense when there's a clear thing that you want to do with each result - just be sure that whatever it is, it's fast (for example, if you're controlling PWM duty cycle, write the register directly, don't use analogWrite()). | ||
|
||
``` | ||
// during setup proceedures, before starting freerunning mode conversions. | ||
ADC0.INTCTRL=ADC_RESRDY_bm; //enable the interrupt | ||
// The ISR | ||
ISR(ADC0_RESRDY_vect) { | ||
useADCValue(ADC0.RES); //reading ADC0.RES clears the RESRDY flag. | ||
// If for some reason you're not reading the value, though, you need to manually clear the flag by writing a 1 to it. | ||
} | ||
``` | ||
|
||
## Turning off free-running mode | ||
To stop taking samples in free running mode, simply clear the `ADC_FREERUN_bm` bit in `ADCn.CTRLA`. Be aware that an "extra" reading is always taken when disabling free running mode. See [silicon errata](Errata.md) for more details. | ||
|
||
## Changing channel, other settings in free running mode | ||
It is recommended to stop free running mode prior to changing the channel. On some tinyAVR parts, most ADC settings cannot be changed without first turning off free running mode. See [the table of silicon errata](Errata.md) | ||
|
||
# Automatic Accumulation | ||
One of the most common applications of multiple consecutive ADC reads is to sum multiple readings to take an average, or for the purpose of inccreasing accuracy through the technique of oversampling and decimation. The modern AVR devices have an automatic accumulator feature to do this for you to make this process easier; it is controlled by `ADCn.CTRLB`. This is the only thing controlled by this register, so you can set it directly. Valid options are: | ||
* `ADC_SAMPNUM_ACC1_gc` | ||
* `ADC_SAMPNUM_ACC2_gc` | ||
* `ADC_SAMPNUM_ACC4_gc` | ||
* `ADC_SAMPNUM_ACC8_gc` | ||
* `ADC_SAMPNUM_ACC16_gc` | ||
* `ADC_SAMPNUM_ACC32_gc` | ||
* `ADC_SAMPNUM_ACC64_gc` | ||
|
||
When the conversion is complete, you can read it from the `ADCn.RES` register. This can be used either with the builtin analogRead() functions (set this register before using analogRead()), in free running mode, or manually. Because taking large numbers of samples in this way can take a while, manually starting a conversion after setting an interrupt on RESRDY may be of particular use here. | ||
|
||
# Oversampling and decimation | ||
If you need more accuracy - assuming the signal is changing (it won't work if you get the same result every time - you'll just get that value out) - you can use the technique of "oversampling and decimation" - for each extra bit of accuracy you want, take 4 times as many samples, and then right-shift the result one bit. For example, if we want 1e3 bits of accuracy from a 10-bit ADC: | ||
|
||
``` | ||
// global variables: | ||
volatile uint16_t latest_reading=0; // remember to disabe interrupts while reading this so it doesn't get changed by the ISR while you're halfway through reading it. | ||
// Setup: | ||
ADC0.CTRLB=ADC_SAMPNUM_ACC64_gc; //take 64 samples for 3 extra bits | ||
ADC0.MUXPOS=0x06; //reads from PA6/arduino pin 2, ADC0 channel 6 | ||
ADC0.CTRLA=ADC_ENABLE_bm|ADC_FREERUN_bm; //start in freerun | ||
ADC0.COMMAND=ADC_STCONV_bm; //start first conversion! | ||
// These readings will take forever (1.5-1.8ms), so we will keep that global updated via an ISR: | ||
ISR(ADC_RESRDY_vect) { | ||
uint16_t raw_reading=ADC0.RES; | ||
latest_reading=raw_reading>>3; // 0~8191 | ||
} | ||
``` | ||
|
||
Note that future updates of megaTinyCore may provide an automated way of configuring analogRead() to do this via an extension to analogReadResolution() - though the need for non-blocking code may render this inappropriate. | ||
|
||
# Faster ADC conversions? Extra-high-impedance sources? | ||
On classic AVRs, the ADC wanted to be clocked at 100-200kHz, and sampled for 1.5 ADC clocks; hence, the sampling time was 15-30 uS. On the tinyAVR 0/1-series, the ADC clock can be up to 1.5 MHz. megaTinyCore configures it for 1 MHz on 16 MHz derived clocks, and 1.25 MHz on 20 MHz derived clocks. These parts also provide the ADCn.SAMPCTRL register to allow a longer sampling time - the sampling time is 2+ADCn.SAMPCTRL. By default, megaTinyCore configures this for 14. 14+2 = 16; this is a conservative attempt to ensure that the accuracy on modern AVR parts does not suffer with higher-impedance sources, particularly ones that worked with classic AVRs. Since the faster clock permits much faster conversions anyway, this was considered an acceptable limitation for the defaut. Because the sampling cap is smaller on these parts, though, this is probably significantly higher than it needs to be. For low impedance sources, this can be cranked all the way back to 0 if conversion speed is of particular importance - this will reduce the conversion time from 29 ADC clocks (29us or 23us for 16 and 20 MHz derived clocks, respectively) to 13 (13us or 10.5us). By comparison, on classic AVRs, the ADC conversion time was between 65u and 130us (104us on most common clock speeds). | ||
|
||
## | ||
On the other hand, if you are reading a particularly high impedance source, you may wish to *increase* the sampling time instead. SAMPCTRL can be set as high as 31, which would give a sampling time of 33 ADC clocks (conversion time 46 ADC clocks - 46 or 37 us). | ||
|
||
# Faster ADC conversions? | ||
If the signal you are measuring is low impedance, you | ||
# ADC1 on ATtiny1614, 1616, 1617, 3216, 3217 | ||
These parts have a second ADC, just like the first - everything about it is identical except the pin assignments. As of 2.0.5, megaTinyCore provides a function `init_ADC1()` that can be called to initialize ADC1 the same way as ADC0 is initialized. This is particularly convenient if writing library code which may run on multiple clock speeds, as you dont have to duplicate the calculation of an appropriate prescaler based on F_CPU. Because it is fully independent, it is well suited to use in the background, and/or for long-running accumulated readings. |
This must be a typo...