From 728f234d635427af157c650324914618853f84b4 Mon Sep 17 00:00:00 2001 From: "Spence Konde (aka Dr. Azzy)" Date: Wed, 8 Jul 2020 03:17:47 -0400 Subject: [PATCH] Add document describing ADC free running mode Also add init_ADC1() to wiring.c for parts that have that. --- ChangeLog.md | 1 + README.md | 5 +- megaavr/cores/arduino/api/Common.h | 4 + megaavr/cores/arduino/wiring.c | 64 +++++++++++++--- megaavr/extras/ADCFreerunAndMore.md | 114 +++++++++++++++++++++++++++- 5 files changed, 172 insertions(+), 16 deletions(-) diff --git a/ChangeLog.md b/ChangeLog.md index 0a5ce844..f70bd46a 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -1,4 +1,5 @@ 2.0.5 +* Internal change to ADC initialization (saves a bit of flash) and init_ADC1() function for parts that have ADC1. * Quick fix to naming of .hex and .lst files 2.0.4 * Switch to new and improved compiler toolchain - now get informative errors if sketch is too big on parts that use RJMP, bugfix for eeprom.h (not to be confused with EEPROM.h), various header improvements (board manager install only). diff --git a/README.md b/README.md index 4660524e..1d02d96e 100644 --- a/README.md +++ b/README.md @@ -99,8 +99,9 @@ This core uses a simple scheme for assigning the Arduino pin numbers: Pins are n #### Arduino Pin Numbers When a single number is used to refer to a pin - in the documentation, or in your code - it is always the "Arduino pin number". These are the pin numbers shown in orange (for pins capable of analogRead()) and blue (for pins that are not) on the pinout charts. All of the other ways of referring to pins are #defined to the corresponding Arduino pin number. + #### An and PIN_An constants -The core also provides An and PIN_An constants (where n is a number from 0 to 11). These refer to the ADC0 *channel* numbers. This naming system is similar to what was used on many classic AVR cores - on some of those, it is used to simplify the code behind analogRead() - but here, they are just #defined as the corresponding Arduino pin number. These are not shown on the pinout charts, as this is a deprecated way of referring to pins. The mapping of analog channels to pins is shown in the the datasheet under the I/O Multiplexing Considerations chapter. There are additionally PIN_An defines for compatibility with the official cores - these likewise point to the digital pin number associated with the analog channel. Note that channel A0 is on the UPDI/Reset pin - however, even when configured as UPDI, it can be used as an input as long as the signals it can be exposed to do not look like the UPDI enable sequence. +The core also provides An and PIN_An constants (where n is a number from 0 to 11). These refer to the ADC0 *channel* numbers. This naming system is similar to what was used on many classic AVR cores **but here, they are just #defined as the corresponding Arduino pin number**. If you need to get the analog channel number on a digital pin, use the `digitalPinToAnalogInput(pin)` macro. The An numbers are not shown on the pinout charts - just use the digital pin numbers. The mapping of analog channels to pins is shown in the the datasheet under the I/O Multiplexing Considerations chapter, and reproduced in the [advanced ADC documentation page](megaavr/extras/ADCFreerunAndMore.md). Note that channel A0 is on the UPDI/Reset pin - however, even when configured as UPDI, it can be used as an input as long as the signals it can be exposed to do not look like the UPDI enable sequence. ### Serial (UART) Support All of these parts have a single hardware serial port (UART). It works exactly like the one on official Arduino boards (except that there is no auto-reset, unless you are using Optiboot and have configured that pin to act as reset, or have wired up an "ersatz reset pin" as described above). See the pinout charts for the location of the serial pins. @@ -222,7 +223,7 @@ ADC0.SAMPCTRL=0; //minimum sampling length = 0+2 = 2 ADC clock cycles With the minimum sampling length, analogRead() speed would be approximately doubled from it's already-faster value. -**Note:** The 3217,1617,3216,1616, and 1614 have a second ADC, ADC1. On the 20 and 24-pin parts, these could be used to provide analogRead() on additional pins (it can also read from DAC2). Currently, there are no plans to implement this in the core due to the large number of currently available pins. Instead, it is recommended that users who wish to "take over" and ADC to use it's more advanced functionality choose ADC1 for this purpose. In the future, examples showing use of ADC1 in this way may be published. +**Note:** The 3217,1617,3216,1616, and 1614 have a second ADC, ADC1. On the 20 and 24-pin parts, these could be used to provide analogRead() on additional pins (it can also read from DAC2). Currently, there are no plans to implement this in the core due to the large number of currently available pins. Instead, it is recommended that users who wish to "take over" an ADC to use it's more advanced functionality choose ADC1 for this purpose. In the future, examples showing use of ADC1 in this way may be published. As of 2.0.5, megaTinyCore provides a function `init_ADC1()` which initializes ADC1 in the same way that ADC0 is (with correct prescaler for clock speed and VDD reference). ### DAC Support The 1-series parts have an 8-bit DAC which can generate a real analog voltage (note that this provides very low current and can only be used as a voltage reference, it cannot be used to power other devices). This generates voltages between 0 and the selected VREF (which cannot be VCC, unfortunately). In 2.0.0 and later, set the DAC reference voltage via the DACReference() function - pass it one of the INTERNAL reference options listed under the ADC section above. In versions prior to 2.0.0, select the DAC VREF voltage from the Tools -> DAC Voltage Reference submenu. This voltage must be lower than Vcc to get the correct voltages. Call analogWrite() on the DAC pin to set the voltage to be output by the DAC. To turn off the DAC output, call digitalWrite() on that pin. diff --git a/megaavr/cores/arduino/api/Common.h b/megaavr/cores/arduino/api/Common.h index afab024b..3f7313fb 100644 --- a/megaavr/cores/arduino/api/Common.h +++ b/megaavr/cores/arduino/api/Common.h @@ -103,6 +103,10 @@ int8_t digitalRead(pin_size_t pinNumber); void DACReference(uint8_t mode); #endif +#ifdef ADC0 +void init_ADC1(void); +#endif + #ifndef DISABLEMILLIS unsigned long millis(void); void init_millis(); diff --git a/megaavr/cores/arduino/wiring.c b/megaavr/cores/arduino/wiring.c index 68c9ffa7..7317f118 100644 --- a/megaavr/cores/arduino/wiring.c +++ b/megaavr/cores/arduino/wiring.c @@ -595,35 +595,34 @@ void init() compensate for this! */ #if F_CPU >= 12000000 // 16 MHz / 16 = 1 MHz, 20 MHz / 16 = 1.25 MHz - ADC0.CTRLC |= ADC_PRESC_DIV16_gc; + ADC0.CTRLC = ADC_PRESC_DIV16_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #elif F_CPU >= 6000000 // 8 MHz / 8 = 1 MHz, 10 MHz / 64 = 1.25 MHz - ADC0.CTRLC |= ADC_PRESC_DIV8_gc; + ADC0.CTRLC = ADC_PRESC_DIV8_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #elif F_CPU >= 3000000 // 4 MHz / 32 = 1 MHz, 5 MHz / 32 = 1.25 MHz - ADC0.CTRLC |= ADC_PRESC_DIV4_gc; + ADC0.CTRLC = ADC_PRESC_DIV4_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #else // 1 MHz / 2 = 500 kHz - the lowest setting - ADC0.CTRLC |= ADC_PRESC_DIV2_gc; + ADC0.CTRLC = ADC_PRESC_DIV2_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #endif ADC0.SAMPCTRL=14; //16 ADC clock sampling time - should be about the same amount of *time* as originally? #else //if SLOWADC is defined - as of 2.0.0 this option isn't exposed. /* ADC clock around 125 kHz - datasheet spec's 50 kHz to 1.5 MHz */ #if F_CPU >= 16000000 // 16 MHz / 128 = 125 kHz, 20 MHz / 128 = 156.250 kHz - ADC0.CTRLC |= ADC_PRESC_DIV128_gc; + ADC0.CTRLC = ADC_PRESC_DIV128_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #elif F_CPU >= 8000000 // 8 MHz / 64 = 125 kHz, 10 MHz / 64 = 156.25 KHz - ADC0.CTRLC |= ADC_PRESC_DIV64_gc; + ADC0.CTRLC = ADC_PRESC_DIV64_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #elif F_CPU >= 4000000 // 4 MHz / 32 = 125 kHz, 5 MHz / 32 = 156.25 KHz - ADC0.CTRLC |= ADC_PRESC_DIV32_gc; + ADC0.CTRLC = ADC_PRESC_DIV32_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #elif F_CPU >= 2000000 // 2 MHz / 16 = 125 kHz - note that megaTinyCore does not provide support for 2 MHz - ADC0.CTRLC |= ADC_PRESC_DIV16_gc; + ADC0.CTRLC = ADC_PRESC_DIV16_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #elif F_CPU >= 1000000 // 1 MHz / 8 = 125 kHz - ADC0.CTRLC |= ADC_PRESC_DIV8_gc; + ADC0.CTRLC = ADC_PRESC_DIV8_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #else // 128 kHz / 2 = 64 kHz -> This is the closest you can get, the prescaler is 2 - ADC0.CTRLC |= ADC_PRESC_DIV2_gc; + ADC0.CTRLC = ADC_PRESC_DIV2_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; #endif #endif /* Enable ADC */ ADC0.CTRLA |= ADC_ENABLE_bm; - analogReference(VDD); #endif #ifdef __AVR_ATtinyxy2__ @@ -640,6 +639,49 @@ void init() sei(); } +#ifdef ADC1 + void init_ADC1(){ + #ifndef SLOWADC + /* ADC clock 1 MHz to 1.25 MHz at frequencies supported by megaTinyCore + Unlike the classic AVRs, which demand 50~200 kHz, for these, the datasheet + spec's 50 kHz to 1.5 MHz. We hypothesize that lower clocks provide better + response to high impedance signals, since the sample and hold circuit will + be connected to the pin for longer, though the datasheet does not explicitly + state that this is the case. However, we can use the SAMPLEN register to + compensate for this! */ + + #if F_CPU >= 12000000 // 16 MHz / 16 = 1 MHz, 20 MHz / 16 = 1.25 MHz + ADC1.CTRLC = ADC_PRESC_DIV16_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #elif F_CPU >= 6000000 // 8 MHz / 8 = 1 MHz, 10 MHz / 64 = 1.25 MHz + ADC1.CTRLC = ADC_PRESC_DIV8_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #elif F_CPU >= 3000000 // 4 MHz / 32 = 1 MHz, 5 MHz / 32 = 1.25 MHz + ADC1.CTRLC = ADC_PRESC_DIV4_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #else // 1 MHz / 2 = 500 kHz - the lowest setting + ADC1.CTRLC = ADC_PRESC_DIV2_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #endif + ADC1.SAMPCTRL=14; //16 ADC clock sampling time - should be about the same amount of *time* as originally? + #else //if SLOWADC is defined - as of 2.0.0 this option isn't exposed. + /* ADC clock around 125 kHz - datasheet spec's 50 kHz to 1.5 MHz */ + #if F_CPU >= 16000000 // 16 MHz / 128 = 125 kHz, 20 MHz / 128 = 156.250 kHz + ADC1.CTRLC = ADC_PRESC_DIV128_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #elif F_CPU >= 8000000 // 8 MHz / 64 = 125 kHz, 10 MHz / 64 = 156.25 KHz + ADC1.CTRLC = ADC_PRESC_DIV64_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #elif F_CPU >= 4000000 // 4 MHz / 32 = 125 kHz, 5 MHz / 32 = 156.25 KHz + ADC1.CTRLC = ADC_PRESC_DIV32_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #elif F_CPU >= 2000000 // 2 MHz / 16 = 125 kHz - note that megaTinyCore does not provide support for 2 MHz + ADC1.CTRLC = ADC_PRESC_DIV16_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #elif F_CPU >= 1000000 // 1 MHz / 8 = 125 kHz + ADC1.CTRLC = ADC_PRESC_DIV8_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #else // 128 kHz / 2 = 64 kHz -> This is the closest you can get, the prescaler is 2 + ADC1.CTRLC = ADC_PRESC_DIV2_gc|ADC_REFSEL_VDDREF_gc|ADC_SAMPCAP_bm;; + #endif + #endif + + /* Enable ADC */ + ADC1.CTRLA |= ADC_ENABLE_bm; + } +#endif + void setup_timers() { /* TYPE A TIMER */ diff --git a/megaavr/extras/ADCFreerunAndMore.md b/megaavr/extras/ADCFreerunAndMore.md index e84810b5..8dbad4d0 100644 --- a/megaavr/extras/ADCFreerunAndMore.md +++ b/megaavr/extras/ADCFreerunAndMore.md @@ -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 +} +``` +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. \ No newline at end of file