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
This commit is contained in:
fauxpark 2019-12-08 12:11:29 +11:00 committed by GitHub
parent f275ffbdfc
commit a8320f20f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 159 additions and 39 deletions

View File

@ -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)

50
docs/adc_driver.md Normal file
View File

@ -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` | | |
<sup>\* 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.</sup>
## 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. |

View File

@ -14,24 +14,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Simple analog to digitial conversion
#include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdint.h>
#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;
}

View File

@ -14,45 +14,40 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _analog_h_included__
#define _analog_h_included__
#pragma once
#include <stdint.h>
#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