123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- /*
- * Simple PWM controller
- * ADC measurement
- *
- * Copyright (c) 2018-2020 Michael Buesch <m@bues.ch>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License along
- * with this program; if not, write to the Free Software Foundation, Inc.,
- * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- */
- #include "compat.h"
- #include "debug.h"
- #include "adc.h"
- #include "util.h"
- #include "main.h"
- #include "pwm.h"
- #include "battery.h"
- #include "arithmetic.h"
- #include "outputsp.h"
- #include "standby.h"
- #include "remote.h"
- /* ADC configuration. */
- #define ADC_HYST 1u /* ADC hysteresis */
- #define ADC_MIN 0u /* Physical ADC minimum */
- #define ADC_MAX 0x3FFu /* Physical ADC maximum */
- #define ADC_VBG_MV 1100u /* Vbg in millivolts. */
- #define NR_ADC NR_PWM
- #if IS_ATMEGAx8
- # define ADC0_MUX ((0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (0 << MUX0))
- # define ADC1_MUX ((0 << MUX3) | (0 << MUX2) | (0 << MUX1) | (1 << MUX0))
- # define ADC2_MUX ((0 << MUX3) | (0 << MUX2) | (1 << MUX1) | (0 << MUX0))
- #else
- # define ADC0_MUX ((0 << MUX3) | (0 << MUX2) | (1 << MUX1) | (0 << MUX0))
- # define ADC1_MUX 0
- # define ADC2_MUX 0
- #endif
- #if NR_ADC == 3
- # define ADC_DIDR_MASK ((1 << ADC2D) | (1 << ADC1D) | (1 << ADC0D))
- #elif NR_ADC == 1
- # define ADC_DIDR_MASK (1 << ADC2D)
- #else
- # error
- #endif
- /* Sample discard support. */
- #define USE_ADC_DISCARD (USE_BAT_MONITOR || (NR_ADC > 1))
- /* Analog pin switching support. */
- #define USE_APIN_SWITCH USE_REMOTE
- static struct {
- /* Currently active ADC MUX */
- uint8_t index;
- /* Battery measurement requested. */
- bool battery_meas_requested;
- /* Battery measurement running. */
- bool battery_meas_running;
- /* Is the input idle? */
- bool standby_ready[NR_ADC];
- /* Number of ADC inputs sucessfully measured. */
- uint8_t conv_count;
- /* Number of samples to discard. */
- uint8_t discard;
- /* Previous PWM interrupt count state. */
- uint8_t prev_pwm_count;
- /* Analog input pin measurement enabled? */
- bool analogpins_enabled;
- } adc;
- static bool adc_hw_enabled(void)
- {
- return !!(ADCSRA & (1 << ADEN));
- }
- #define ADC_MUXMODE_NORM 0u
- #define ADC_MUXMODE_BAT 1u
- /* Configure the ADC multiplexer.
- * Interrupts must be disabled before calling this function. */
- static inline void adc_configure_mux(uint8_t mux_mode, uint8_t index)
- {
- uint8_t mux_bits;
- if (mux_mode == ADC_MUXMODE_NORM) {
- /* Normal input signal measurement mode. */
- if (NR_ADC <= 1u || index == 0u)
- mux_bits = ADC0_MUX;
- else if (index == 1u)
- mux_bits = ADC1_MUX;
- else
- mux_bits = ADC2_MUX;
- if (IS_ATMEGAx8) {
- /* Ref = Vcc; in = ADC0/1/2; Right adjust */
- ADMUX = (0 << REFS1) | (1 << REFS0) |
- (0 << ADLAR) | mux_bits;
- } else {
- /* Ref = Vcc; in = ADC2; Right adjust */
- ADMUX = (0 << REFS2) | (0 << REFS1) | (0 << REFS0) |
- (0 << ADLAR) | mux_bits;
- }
- } else { /* mux_mode == ADC_MUXMODE_BAT */
- /* Battery voltage measurement mode. */
- if (IS_ATMEGAx8) {
- /* Ref = Vcc; in = Vbg (1.1V); Right adjust */
- ADMUX = (0 << REFS1) | (1 << REFS0) |
- (0 << ADLAR) |
- (1 << MUX3) | (1 << MUX2) | (1 << MUX1) | (0 << MUX0);
- } else {
- /* Ref = Vcc; in = Vbg (1.1V); Right adjust */
- ADMUX = (0 << REFS2) | (0 << REFS1) | (0 << REFS0) |
- (0 << ADLAR) |
- (1 << MUX3) | (1 << MUX2) | (0 << MUX1) | (0 << MUX0);
- }
- }
- }
- /* Configure the ADC hardware.
- * Interrupts must be disabled before calling this function. */
- static void adc_configure(bool enable)
- {
- /* Disable ADC unit. */
- ADCSRA = 0;
- /* Disable ADC digital input */
- DIDR0 = ADC_DIDR_MASK;
- /* Trigger source = free running */
- ADCSRB = (0 << ADTS2) | (0 << ADTS1) | (0 << ADTS0);
- if (enable) {
- if (adc_battery_measurement_active()) {
- /* Configure the MUX to battery measurement. */
- adc.battery_meas_requested = false;
- adc.battery_meas_running = true;
- adc_configure_mux(ADC_MUXMODE_BAT, 0u);
- /* Enable and start ADC; free running; PS = 64; IRQ enabled */
- ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) |
- (1 << ADIF) | (1 << ADIE) |
- (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
- /* Discard the first few results to compensate
- * for Vbg settling time (1 ms). */
- adc.discard = 10;
- } else if (battery_voltage_is_critical()) {
- /* Battery voltage is critical and no battery measurement
- * has been requested.
- * Keep the ADC shut down. */
- } else if (!adc_analogpins_enabled()) {
- /* Analog input pins are disabled.
- * Keep the ADC shut down. */
- } else {
- /* Measure input pins. */
- /* Configure the MUX to the active input ADC. */
- adc_configure_mux(ADC_MUXMODE_NORM, adc.index);
- /* Enable and start ADC; free running; PS = 64; IRQ enabled */
- ADCSRA = (1 << ADEN) | (1 << ADSC) | (1 << ADATE) |
- (1 << ADIF) | (1 << ADIE) |
- (1 << ADPS2) | (1 << ADPS1) | (0 << ADPS0);
- if (NR_ADC > 1)
- adc.discard = 1;
- }
- }
- }
- /* Returns true, if a battery measurement conversion is currently active. */
- bool adc_battery_measurement_active(void)
- {
- if (USE_BAT_MONITOR)
- return adc.battery_meas_requested || adc.battery_meas_running;
- return false;
- }
- /* Request a measurement of the battery voltage.
- * Interrupts must be disabled before calling this function. */
- void adc_request_battery_measurement(void)
- {
- if (USE_BAT_MONITOR) {
- adc.battery_meas_requested = true;
- if (adc_hw_enabled()) {
- /* The ADC will be reconfigured by the completion
- * interrupt of the currently running conversion. */
- } else {
- /* No conversion currently running. Reconfigure ADC now. */
- adc_configure(true);
- }
- }
- }
- /* Get the analogpin measurement state. */
- bool adc_analogpins_enabled(void)
- {
- if (USE_APIN_SWITCH)
- return adc.analogpins_enabled;
- return true;
- }
- /* Enable/disable the analog input pin measurement. */
- void adc_analogpins_enable(bool enable)
- {
- uint8_t irq_state;
- if (USE_APIN_SWITCH) {
- irq_state = irq_disable_save();
- if (adc.analogpins_enabled != enable) {
- adc.analogpins_enabled = enable;
- if (enable && !adc_hw_enabled())
- adc_configure(true);
- set_standby_suppress(STANDBY_SRC_ADC, enable);
- }
- irq_restore(irq_state);
- }
- }
- /* Handle wake up from deep sleep.
- * Called with interrupts disabled. */
- void adc_handle_deep_sleep_wakeup(void)
- {
- if (!adc_analogpins_enabled())
- set_standby_suppress(STANDBY_SRC_ADC, false);
- }
- /* ADC conversion complete interrupt service routine */
- ISR(ADC_vect)
- {
- uint16_t raw_adc;
- uint16_t raw_setpoint;
- uint16_t filt_setpoint;
- uint16_t vcc_mv;
- uint8_t pwm_count;
- uint8_t index;
- uint8_t i;
- bool pwm_collision;
- bool allow_standby;
- bool go_standby;
- memory_barrier();
- /* Check if we had a PWM interrupt during our ADC measurement.
- * This only checks for collisions with the low frequency IRQ mode PWM.
- * Do this check with interrupts still disabled. */
- pwm_count = pwm_get_irq_count();
- pwm_collision = (pwm_count != adc.prev_pwm_count);
- adc.prev_pwm_count = pwm_count;
- /* Disable the ADC interrupt and
- * globally enable interrupts.
- * This allows TIM0_OVF_vect to interrupt us. */
- ADCSRA &= (uint8_t)~(1u << ADIE);
- irq_enable();
- /* Read the analog input */
- raw_adc = ADC;
- if (!USE_ADC_DISCARD || adc.discard == 0) {
- if (USE_BAT_MONITOR && adc.battery_meas_running) {
- /* Battery voltage measurement mode. */
- /* Convert the raw ADC value to millivolts. */
- if (raw_adc > 0u) {
- vcc_mv = lim_u16((((uint32_t)ADC_MAX + 1u) * (uint32_t)ADC_VBG_MV) /
- (uint32_t)raw_adc);
- } else
- vcc_mv = UINT16_MAX;
- /* Report the measured battery voltage to the
- * battery voltage logic. */
- evaluate_battery_voltage(vcc_mv);
- /* Disable interrupts for
- * - PWM shut down
- * - ADC re-init */
- irq_disable();
- if (battery_voltage_is_critical()) {
- /* Turn the output off immediately.
- * This also reconfigures the
- * battery measurement interval. */
- for (i = 0u; i < NR_PWM; i++)
- output_setpoint_set(IF_MULTIPWM(i,) false, 0u);
- }
- /* We're done.
- * Turn off battery measurement mode and
- * return to normal ADC operation mode
- * (if battery voltage is not critical). */
- adc.battery_meas_running = false;
- adc_configure(true);
- } else {
- if (pwm_collision) {
- /* An IRQ controlled PWM output actuation happened
- * during the measurement.
- * Discard this measurement. */
- irq_disable();
- allow_standby = false;
- } else {
- /* Normal operation mode. */
- if (ADC_INVERT)
- raw_adc = ADC_MAX - raw_adc;
- index = (NR_ADC > 1u) ? adc.index : 0u;
- output_setpoint_transform(IF_MULTIPWM(index,)
- ADC_HSL,
- raw_adc,
- &raw_setpoint,
- &filt_setpoint);
- /* This channel is ready for standby, if idle. */
- if (USE_DEEP_SLEEP) {
- adc.standby_ready[index] = (raw_setpoint == 0u);
- adc.conv_count = add_sat_u8(adc.conv_count, 1u);
- }
- /* Globally disable interrupts.
- * TIM0_OVF_vect must not interrupt re-programming of the PWM below. */
- irq_disable();
- /* Change the output signal (PWM). */
- if (adc_analogpins_enabled()) {
- output_setpoint_set(IF_MULTIPWM(index,)
- ADC_HSL,
- filt_setpoint);
- }
- /* Increment index to the next ADC. */
- if (NR_ADC > 1u) {
- if (++adc.index >= NR_ADC)
- adc.index = 0u;
- /* Switch MUX to the next ADC. */
- adc_configure_mux(ADC_MUXMODE_NORM, adc.index);
- adc.discard = 1;
- }
- allow_standby = true;
- }
- if (USE_BAT_MONITOR && adc.battery_meas_requested) {
- /* Battery measurement requested.
- * Reconfigure the ADC for battery measurement. */
- adc_configure(true);
- } else if (!adc_analogpins_enabled()) {
- adc_configure(false);
- set_standby_suppress(STANDBY_SRC_ADC, false);
- } else {
- /* If the PWM is disabled, request deep sleep to save power. */
- if (USE_DEEP_SLEEP && adc.conv_count >= NR_ADC) {
- adc.conv_count = 0u;
- go_standby = allow_standby;
- for (i = 0u; i < NR_ADC; i++)
- go_standby &= adc.standby_ready[i];
- set_standby_suppress(STANDBY_SRC_ADC, !go_standby);
- }
- }
- }
- } else {
- /* Discard this sample. */
- adc.discard--;
- irq_disable();
- }
- /* If deep sleep support is disabled, then the watchdog IRQ is also disabled.
- * Poke the watchdog here. */
- if (!USE_DEEP_SLEEP)
- wdt_reset();
- /* Re-enable the ADC interrupt. */
- ADCSRA |= (1u << ADIE);
- ADCSRA |= (1u << ADIF);
- memory_barrier();
- }
- /* Initialize the input ADC measurement.
- * Interrupts must be disabled before calling this function. */
- void adc_init(bool enable)
- {
- adc.prev_pwm_count = pwm_get_irq_count();
- memory_barrier();
- adc_configure(enable);
- }
|