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:
parent
f275ffbdfc
commit
a8320f20f7
4 changed files with 159 additions and 39 deletions
|
@ -101,6 +101,7 @@
|
||||||
* [Hand Wiring Guide](hand_wire.md)
|
* [Hand Wiring Guide](hand_wire.md)
|
||||||
* [ISP Flashing Guide](isp_flashing_guide.md)
|
* [ISP Flashing Guide](isp_flashing_guide.md)
|
||||||
* [ARM Debugging Guide](arm_debugging.md)
|
* [ARM Debugging Guide](arm_debugging.md)
|
||||||
|
* [ADC Driver](adc_driver.md)
|
||||||
* [I2C Driver](i2c_driver.md)
|
* [I2C Driver](i2c_driver.md)
|
||||||
* [WS2812 Driver](ws2812_driver.md)
|
* [WS2812 Driver](ws2812_driver.md)
|
||||||
* [GPIO Controls](internals_gpio_control.md)
|
* [GPIO Controls](internals_gpio_control.md)
|
||||||
|
|
50
docs/adc_driver.md
Normal file
50
docs/adc_driver.md
Normal 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. |
|
|
@ -14,24 +14,31 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Simple analog to digitial conversion
|
|
||||||
|
|
||||||
#include <avr/io.h>
|
#include <avr/io.h>
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include "analog.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
|
// Arduino compatible pin input
|
||||||
int16_t analogRead(uint8_t pin) {
|
int16_t analogRead(uint8_t pin) {
|
||||||
#if defined(__AVR_ATmega32U4__)
|
#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;
|
if (pin >= 12) return 0;
|
||||||
return adc_read(pgm_read_byte(pin_to_mux + pin));
|
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;
|
if (pin >= 8) return 0;
|
||||||
return adc_read(pin);
|
return adc_read(pin);
|
||||||
#else
|
#else
|
||||||
|
@ -39,20 +46,87 @@ int16_t analogRead(uint8_t pin) {
|
||||||
#endif
|
#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) {
|
int16_t adc_read(uint8_t mux) {
|
||||||
#if defined(__AVR_AT90USB162__)
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
uint8_t low;
|
uint8_t low;
|
||||||
|
|
||||||
ADCSRA = (1 << ADEN) | ADC_PRESCALER; // enable ADC
|
// Enable ADC and configure prescaler
|
||||||
ADCSRB = (1 << ADHSM) | (mux & 0x20); // high speed mode
|
ADCSRA = _BV(ADEN) | ADC_PRESCALER;
|
||||||
ADMUX = aref | (mux & 0x1F); // configure mux input
|
|
||||||
ADCSRA = (1 << ADEN) | ADC_PRESCALER | (1 << ADSC); // start the conversion
|
#if defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)
|
||||||
while (ADCSRA & (1 << ADSC))
|
// High speed mode and ADC8-13
|
||||||
; // wait for result
|
ADCSRB = _BV(ADHSM) | (mux & _BV(MUX5));
|
||||||
low = ADCL; // must read LSB first
|
#elif defined(__AVR_AT90USB646__) || defined(__AVR_AT90USB647__) || defined(__AVR_AT90USB1286__) || defined(__AVR_AT90USB1287__)
|
||||||
return (ADCH << 8) | low; // must read MSB only once!
|
// High speed mode only
|
||||||
|
ADCSRB = _BV(ADHSM);
|
||||||
#endif
|
#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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,45 +14,40 @@
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef _analog_h_included__
|
#pragma once
|
||||||
#define _analog_h_included__
|
|
||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include "quantum.h"
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
void analogReference(uint8_t mode);
|
void analogReference(uint8_t mode);
|
||||||
int16_t analogRead(uint8_t pin);
|
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);
|
int16_t adc_read(uint8_t mux);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define ADC_REF_POWER (1 << REFS0)
|
#define ADC_REF_EXTERNAL 0 // AREF, Internal Vref turned off
|
||||||
#define ADC_REF_INTERNAL ((1 << REFS1) | (1 << REFS0))
|
#define ADC_REF_POWER _BV(REFS0) // AVCC with external capacitor on AREF pin
|
||||||
#define ADC_REF_EXTERNAL (0)
|
#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
|
// These prescaler values are for high speed mode, ADHSM = 1
|
||||||
#if F_CPU == 16000000L
|
#if F_CPU == 16000000L || F_CPU == 12000000L
|
||||||
# define ADC_PRESCALER ((1 << ADPS2) | (1 << ADPS1))
|
# define ADC_PRESCALER (_BV(ADPS2) | _BV(ADPS1)) // /64
|
||||||
#elif F_CPU == 8000000L
|
#elif F_CPU == 8000000L
|
||||||
# define ADC_PRESCALER ((1 << ADPS2) | (1 << ADPS0))
|
# define ADC_PRESCALER (_BV(ADPS2) | _BV(ADPS0)) // /32
|
||||||
#elif F_CPU == 4000000L
|
#elif F_CPU == 4000000L
|
||||||
# define ADC_PRESCALER ((1 << ADPS2))
|
# define ADC_PRESCALER (_BV(ADPS2)) // /16
|
||||||
#elif F_CPU == 2000000L
|
#elif F_CPU == 2000000L
|
||||||
# define ADC_PRESCALER ((1 << ADPS1) | (1 << ADPS0))
|
# define ADC_PRESCALER (_BV(ADPS1) | _BV(ADPS0)) // /8
|
||||||
#elif F_CPU == 1000000L
|
#elif F_CPU == 1000000L
|
||||||
# define ADC_PRESCALER ((1 << ADPS1))
|
# define ADC_PRESCALER _BV(ADPS1) // /4
|
||||||
#else
|
#else
|
||||||
# define ADC_PRESCALER ((1 << ADPS0))
|
# define ADC_PRESCALER _BV(ADPS0) // /2
|
||||||
#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
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue