You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
203 lines
5.4 KiB
203 lines
5.4 KiB
/// -*- tab-width: 4; Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil -*- |
|
|
|
#include <FastSerial.h> |
|
#include "AP_AnalogSource_Arduino.h" |
|
|
|
// increase this if we need more than 5 analog sources |
|
#define MAX_PIN_SOURCES 5 |
|
|
|
// number of times to read a pin |
|
// before considering the value valid. |
|
// This ensures the value has settled on |
|
// the new source |
|
#define PIN_READ_REPEAT 2 |
|
|
|
static uint8_t num_pins_watched; |
|
static uint8_t next_pin_index; |
|
static uint8_t next_pin_count; |
|
static volatile struct { |
|
uint8_t pin; |
|
uint8_t sum_count; |
|
uint16_t output; |
|
uint16_t sum; |
|
} pins[MAX_PIN_SOURCES]; |
|
|
|
// ADC conversion timer. This is called at 1kHz by the timer |
|
// interrupt |
|
// each conversion takes about 125 microseconds |
|
static void adc_timer(uint32_t t) |
|
{ |
|
if (pins[next_pin_index].pin == ANALOG_PIN_NONE) { |
|
goto next_pin; |
|
} |
|
|
|
if (bit_is_set(ADCSRA, ADSC) || num_pins_watched == 0) { |
|
// conversion is still running. This should be |
|
// very rare, as we are called at 1kHz |
|
return; |
|
} |
|
|
|
next_pin_count++; |
|
if (next_pin_count != PIN_READ_REPEAT) { |
|
// we don't want this value, so start the next conversion |
|
// immediately, discarding this value |
|
ADCSRA |= _BV(ADSC); |
|
return; |
|
} |
|
|
|
// remember the value we got |
|
{ |
|
uint8_t low = ADCL; |
|
uint8_t high = ADCH; |
|
pins[next_pin_index].output = low | (high<<8); |
|
} |
|
pins[next_pin_index].sum += pins[next_pin_index].output; |
|
if (pins[next_pin_index].sum_count >= 63) { |
|
// we risk overflowing the 16 bit sum |
|
pins[next_pin_index].sum >>= 1; |
|
pins[next_pin_index].sum_count = 32; |
|
} else { |
|
pins[next_pin_index].sum_count++; |
|
} |
|
|
|
next_pin: |
|
next_pin_count = 0; |
|
if (num_pins_watched != 0) { |
|
next_pin_index = (next_pin_index+1) % num_pins_watched; |
|
} |
|
uint8_t pin = pins[next_pin_index].pin; |
|
|
|
if (pin == ANALOG_PIN_NONE) { |
|
// setup for returning 0 |
|
pins[next_pin_index].output = 0; |
|
pins[next_pin_index].sum = 0; |
|
pins[next_pin_index].sum_count = 1; |
|
} else if (pin == ANALOG_PIN_VCC) { |
|
// we're reading the board voltage |
|
ADMUX = _BV(REFS0)|_BV(MUX4)|_BV(MUX3)|_BV(MUX2)|_BV(MUX1); |
|
|
|
// start the next conversion |
|
ADCSRA |= _BV(ADSC); |
|
} else { |
|
// we're reading an external pin |
|
ADCSRB = (ADCSRB & ~(1 << MUX5)) | (((pin >> 3) & 0x01) << MUX5); |
|
ADMUX = _BV(REFS0) | (pin & 0x07); |
|
|
|
// start the next conversion |
|
ADCSRA |= _BV(ADSC); |
|
} |
|
} |
|
|
|
// setup the timer process. This must be called before any analog |
|
// values are available |
|
void AP_AnalogSource_Arduino::init_timer(AP_PeriodicProcess * scheduler) |
|
{ |
|
scheduler->register_process(adc_timer); |
|
} |
|
|
|
// read raw 16 bit value |
|
uint16_t AP_AnalogSource_Arduino::read_raw(void) |
|
{ |
|
uint16_t ret; |
|
uint8_t oldSREG = SREG; |
|
cli(); |
|
ret = pins[_pin_index].output; |
|
SREG = oldSREG; |
|
return ret; |
|
} |
|
|
|
// scaled read for board Vcc |
|
uint16_t AP_AnalogSource_Arduino::read_vcc(void) |
|
{ |
|
uint16_t v = read_raw(); |
|
if (v == 0) { |
|
return 0; |
|
} |
|
return 1126400UL / v; |
|
} |
|
|
|
// read the average 16 bit value since the last |
|
// time read_average() was called. This gives a very cheap |
|
// filtered value, as new values are produced at 500/N Hz |
|
// where N is the total number of analog sources |
|
float AP_AnalogSource_Arduino::read_average(void) |
|
{ |
|
uint16_t sum; |
|
uint8_t sum_count; |
|
|
|
// we don't expect this loop to trigger, unless |
|
// you call read_average() very frequently |
|
while (pins[_pin_index].sum_count == 0) ; |
|
|
|
uint8_t oldSREG = SREG; |
|
cli(); |
|
sum = pins[_pin_index].sum; |
|
sum_count = pins[_pin_index].sum_count; |
|
pins[_pin_index].sum = 0; |
|
pins[_pin_index].sum_count = 0; |
|
SREG = oldSREG; |
|
return sum / (float)sum_count; |
|
} |
|
|
|
// read with the prescaler. This uses the averaged value since |
|
// the last read, which matches that the AP_ADC APM1 library does |
|
// for ADC sources |
|
float AP_AnalogSource_Arduino::read(void) |
|
{ |
|
return read_average() * _prescale; |
|
} |
|
|
|
|
|
// remap pin numbers to physical pin |
|
uint8_t AP_AnalogSource_Arduino::_remap_pin(uint8_t pin) |
|
{ |
|
if (pin != ANALOG_PIN_VCC && pin != ANALOG_PIN_NONE) { |
|
// allow pin to be a channel (i.e. "A0") or an actual pin |
|
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) |
|
if (pin >= 54) pin -= 54; |
|
#elif defined(__AVR_ATmega32U4__) |
|
if (pin >= 18) pin -= 18; |
|
#elif defined(__AVR_ATmega1284__) |
|
if (pin >= 24) pin -= 24; |
|
#else |
|
if (pin >= 14) pin -= 14; |
|
#endif |
|
} |
|
return pin; |
|
} |
|
|
|
// assign a slot in the pins_watched |
|
void AP_AnalogSource_Arduino::_assign_pin_index(uint8_t pin) |
|
{ |
|
// ensure we don't try to read from too many analog pins |
|
if (num_pins_watched == MAX_PIN_SOURCES) { |
|
while (true) { |
|
Serial.printf_P(PSTR("MAX_PIN_SOURCES REACHED\n")); |
|
delay(1000); |
|
} |
|
} |
|
|
|
pin = _remap_pin(pin); |
|
_pin_index = num_pins_watched; |
|
pins[_pin_index].pin = pin; |
|
num_pins_watched++; |
|
if (num_pins_watched == 1) { |
|
// enable the ADC |
|
PRR0 &= ~_BV(PRADC); |
|
ADCSRA |= _BV(ADEN); |
|
} |
|
} |
|
|
|
// change which pin to read |
|
void AP_AnalogSource_Arduino::set_pin(uint8_t pin) |
|
{ |
|
pin = _remap_pin(pin); |
|
if (pins[_pin_index].pin != pin) { |
|
uint8_t oldSREG = SREG; |
|
cli(); |
|
pins[_pin_index].pin = pin; |
|
pins[_pin_index].sum = 0; |
|
pins[_pin_index].sum_count = 0; |
|
SREG = oldSREG; |
|
} |
|
}
|
|
|