From a8320f20f76782789b274f7a8c3e3ad4278a075c Mon Sep 17 00:00:00 2001 From: fauxpark Date: Sun, 8 Dec 2019 12:11:29 +1100 Subject: [PATCH] Improve support and docs for ADC driver (#7191) * Improve support and docs for ADC driver * Comment ADC channels * Move to Makers and Modders section, and fix usage instructions * Flesh out intro * Superscript 328P note * Fix pin_to_mux LUT * Support USB64/1287 as well * analogReadPin() defaults to 0V mux on invalid pin * Update pinToMux() function documentation * Dot * Accept (some of) the `qmk cformat` changes * Do clang-format properly * More wording tweaks * Link to encoder docs --- docs/_summary.md | 1 + docs/adc_driver.md | 50 ++++++++++++++++++++ drivers/avr/analog.c | 110 ++++++++++++++++++++++++++++++++++++------- drivers/avr/analog.h | 37 +++++++-------- 4 files changed, 159 insertions(+), 39 deletions(-) create mode 100644 docs/adc_driver.md diff --git a/docs/_summary.md b/docs/_summary.md index b6ee4a923..f6b03867f 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -101,6 +101,7 @@ * [Hand Wiring Guide](hand_wire.md) * [ISP Flashing Guide](isp_flashing_guide.md) * [ARM Debugging Guide](arm_debugging.md) + * [ADC Driver](adc_driver.md) * [I2C Driver](i2c_driver.md) * [WS2812 Driver](ws2812_driver.md) * [GPIO Controls](internals_gpio_control.md) diff --git a/docs/adc_driver.md b/docs/adc_driver.md new file mode 100644 index 000000000..26e148add --- /dev/null +++ b/docs/adc_driver.md @@ -0,0 +1,50 @@ +# ADC Driver + +QMK can leverage the Analog-to-Digital Converter (ADC) on supported MCUs to measure voltages on certain pins. This can be useful for implementing things such as battery level indicators for Bluetooth keyboards, or volume controls using a potentiometer, as opposed to a [rotary encoder](feature_encoders.md). + +This driver is currently AVR-only. The values returned are 10-bit integers (0-1023) mapped between 0V and VCC (usually 5V or 3.3V). + +## Usage + +To use this driver, add the following to your `rules.mk`: + +```make +SRC += analog.c +``` + +Then place this include at the top of your code: + +```c +#include "analog.h" +``` + +## Channels + +|Channel|AT90USB64/128|ATmega16/32U4|ATmega32A|ATmega328P| +|-------|-------------|-------------|---------|----------| +|0 |`F0` |`F0` |`A0` |`C0` | +|1 |`F1` |`F1` |`A1` |`C1` | +|2 |`F2` | |`A2` |`C2` | +|3 |`F3` | |`A3` |`C3` | +|4 |`F4` |`F4` |`A4` |`C4` | +|5 |`F5` |`F5` |`A5` |`C5` | +|6 |`F6` |`F6` |`A6` |* | +|7 |`F7` |`F7` |`A7` |* | +|8 | |`D4` | | | +|9 | |`D6` | | | +|10 | |`D7` | | | +|11 | |`B4` | | | +|12 | |`B5` | | | +|13 | |`B6` | | | + +\* The ATmega328P possesses two extra ADC channels; however, they are not present on the DIP pinout, and are not shared with GPIO pins. You can use `adc_read()` directly to gain access to these. + +## Functions + +|Function |Description | +|----------------------------|-------------------------------------------------------------------------------------------------------------------| +|`analogReference(mode)` |Sets the analog voltage reference source. Must be one of `ADC_REF_EXTERNAL`, `ADC_REF_POWER` or `ADC_REF_INTERNAL`.| +|`analogRead(pin)` |Reads the value from the specified Arduino pin, eg. `4` for ADC6 on the ATmega32U4. | +|`analogReadPin(pin)` |Reads the value from the specified QMK pin, eg. `F6` for ADC6 on the ATmega32U4. | +|`pinToMux(pin)` |Translates a given QMK pin to a mux value. If an unsupported pin is given, returns the mux value for "0V (GND)". | +|`adc_read(mux)` |Reads the value from the ADC according to the specified mux. See your MCU's datasheet for more information. | diff --git a/drivers/avr/analog.c b/drivers/avr/analog.c index 1a8da4261..abe478b71 100644 --- a/drivers/avr/analog.c +++ b/drivers/avr/analog.c @@ -14,24 +14,31 @@ * along with this program. If not, see . */ -// Simple analog to digitial conversion - #include #include #include #include "analog.h" -static uint8_t aref = (1 << REFS0); // default to AREF = Vcc +static uint8_t aref = ADC_REF_POWER; -void analogReference(uint8_t mode) { aref = mode & 0xC0; } +void analogReference(uint8_t mode) { aref = mode & (_BV(REFS1) | _BV(REFS0)); } // Arduino compatible pin input int16_t analogRead(uint8_t pin) { #if defined(__AVR_ATmega32U4__) - static const uint8_t PROGMEM pin_to_mux[] = {0x00, 0x01, 0x04, 0x05, 0x06, 0x07, 0x25, 0x24, 0x23, 0x22, 0x21, 0x20}; + // clang-format off + static const uint8_t PROGMEM pin_to_mux[] = { + //A0 A1 A2 A3 A4 A5 + //F7 F6 F5 F4 F1 F0 + 0x07, 0x06, 0x05, 0x04, 0x01, 0x00, + //A6 A7 A8 A9 A10 A11 + //D4 D7 B4 B5 B6 D6 + 0x20, 0x22, 0x23, 0x24, 0x25, 0x21 + }; + // clang-format on if (pin >= 12) return 0; return adc_read(pgm_read_byte(pin_to_mux + pin)); -#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) +#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) || defined(__AVR_ATmega328P__) if (pin >= 8) return 0; return adc_read(pin); #else @@ -39,20 +46,87 @@ int16_t analogRead(uint8_t pin) { #endif } -// Mux input +int16_t analogReadPin(pin_t pin) { return adc_read(pinToMux(pin)); } + +uint8_t pinToMux(pin_t pin) { + switch (pin) { + // clang-format off +#if defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) + case F0: return 0; // ADC0 + case F1: return _BV(MUX0); // ADC1 + case F2: return _BV(MUX1); // ADC2 + case F3: return _BV(MUX1) | _BV(MUX0); // ADC3 + case F4: return _BV(MUX2); // ADC4 + case F5: return _BV(MUX2) | _BV(MUX0); // ADC5 + case F6: return _BV(MUX2) | _BV(MUX1); // ADC6 + case F7: return _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // ADC7 + default: return _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // 0V +#elif defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__) + case F0: return 0; // ADC0 + case F1: return _BV(MUX0); // ADC1 + case F4: return _BV(MUX2); // ADC4 + case F5: return _BV(MUX2) | _BV(MUX0); // ADC5 + case F6: return _BV(MUX2) | _BV(MUX1); // ADC6 + case F7: return _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // ADC7 + case D4: return _BV(MUX5); // ADC8 + case D6: return _BV(MUX5) | _BV(MUX0); // ADC9 + case D7: return _BV(MUX5) | _BV(MUX1); // ADC10 + case B4: return _BV(MUX5) | _BV(MUX1) | _BV(MUX0); // ADC11 + case B5: return _BV(MUX5) | _BV(MUX2); // ADC12 + case B6: return _BV(MUX5) | _BV(MUX2) | _BV(MUX0); // ADC13 + default: return _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // 0V +#elif defined(__AVR_ATmega32A__) + case A0: return 0; // ADC0 + case A1: return _BV(MUX0); // ADC1 + case A2: return _BV(MUX1); // ADC2 + case A3: return _BV(MUX1) | _BV(MUX0); // ADC3 + case A4: return _BV(MUX2); // ADC4 + case A5: return _BV(MUX2) | _BV(MUX0); // ADC5 + case A6: return _BV(MUX2) | _BV(MUX1); // ADC6 + case A7: return _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // ADC7 + default: return _BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // 0V +#elif defined(__AVR_ATmega328P__) + case C0: return 0; // ADC0 + case C1: return _BV(MUX0); // ADC1 + case C2: return _BV(MUX1); // ADC2 + case C3: return _BV(MUX1) | _BV(MUX0); // ADC3 + case C4: return _BV(MUX2); // ADC4 + case C5: return _BV(MUX2) | _BV(MUX0); // ADC5 + // ADC7:6 not present in DIP package and not shared by GPIO pins + default: return _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0); // 0V +#endif + // clang-format on + } +} + int16_t adc_read(uint8_t mux) { -#if defined(__AVR_AT90USB162__) - return 0; -#else uint8_t low; - ADCSRA = (1 << ADEN) | ADC_PRESCALER; // enable ADC - ADCSRB = (1 << ADHSM) | (mux & 0x20); // high speed mode - ADMUX = aref | (mux & 0x1F); // configure mux input - ADCSRA = (1 << ADEN) | ADC_PRESCALER | (1 << ADSC); // start the conversion - while (ADCSRA & (1 << ADSC)) - ; // wait for result - low = ADCL; // must read LSB first - return (ADCH << 8) | low; // must read MSB only once! + // Enable ADC and configure prescaler + ADCSRA = _BV(ADEN) | ADC_PRESCALER; + +#if defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__) + // High speed mode and ADC8-13 + ADCSRB = _BV(ADHSM) | (mux & _BV(MUX5)); +#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__) + // High speed mode only + ADCSRB = _BV(ADHSM); #endif + + // Configure mux input +#if defined(MUX4) + ADMUX = aref | (mux & (_BV(MUX4) | _BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0))); +#else + ADMUX = aref | (mux & (_BV(MUX3) | _BV(MUX2) | _BV(MUX1) | _BV(MUX0))); +#endif + + // Start the conversion + ADCSRA |= _BV(ADSC); + // Wait for result + while (ADCSRA & _BV(ADSC)) + ; + // Must read LSB first + low = ADCL; + // Must read MSB only once! + return (ADCH << 8) | low; } diff --git a/drivers/avr/analog.h b/drivers/avr/analog.h index 1b773d82c..058882450 100644 --- a/drivers/avr/analog.h +++ b/drivers/avr/analog.h @@ -14,45 +14,40 @@ * along with this program. If not, see . */ -#ifndef _analog_h_included__ -#define _analog_h_included__ +#pragma once #include +#include "quantum.h" #ifdef __cplusplus extern "C" { #endif void analogReference(uint8_t mode); int16_t analogRead(uint8_t pin); + +int16_t analogReadPin(pin_t pin); +uint8_t pinToMux(pin_t pin); + int16_t adc_read(uint8_t mux); #ifdef __cplusplus } #endif -#define ADC_REF_POWER (1 << REFS0) -#define ADC_REF_INTERNAL ((1 << REFS1) | (1 << REFS0)) -#define ADC_REF_EXTERNAL (0) +#define ADC_REF_EXTERNAL 0 // AREF, Internal Vref turned off +#define ADC_REF_POWER _BV(REFS0) // AVCC with external capacitor on AREF pin +#define ADC_REF_INTERNAL (_BV(REFS1) | _BV(REFS0)) // Internal 2.56V Voltage Reference with external capacitor on AREF pin (1.1V for 328P) // These prescaler values are for high speed mode, ADHSM = 1 -#if F_CPU == 16000000L -# define ADC_PRESCALER ((1 << ADPS2) | (1 << ADPS1)) +#if F_CPU == 16000000L || F_CPU == 12000000L +# define ADC_PRESCALER (_BV(ADPS2) | _BV(ADPS1)) // /64 #elif F_CPU == 8000000L -# define ADC_PRESCALER ((1 << ADPS2) | (1 << ADPS0)) +# define ADC_PRESCALER (_BV(ADPS2) | _BV(ADPS0)) // /32 #elif F_CPU == 4000000L -# define ADC_PRESCALER ((1 << ADPS2)) +# define ADC_PRESCALER (_BV(ADPS2)) // /16 #elif F_CPU == 2000000L -# define ADC_PRESCALER ((1 << ADPS1) | (1 << ADPS0)) +# define ADC_PRESCALER (_BV(ADPS1) | _BV(ADPS0)) // /8 #elif F_CPU == 1000000L -# define ADC_PRESCALER ((1 << ADPS1)) +# define ADC_PRESCALER _BV(ADPS1) // /4 #else -# define ADC_PRESCALER ((1 << ADPS0)) -#endif - -// some avr-libc versions do not properly define ADHSM -#if defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB1286__) -# if !defined(ADHSM) -# define ADHSM (7) -# endif -#endif - +# define ADC_PRESCALER _BV(ADPS0) // /2 #endif