diff --git a/src/samd21/Kconfig b/src/samd21/Kconfig index 225eae17..18bd7d81 100644 --- a/src/samd21/Kconfig +++ b/src/samd21/Kconfig @@ -6,6 +6,7 @@ config SAMD_SELECT bool default y select HAVE_GPIO + select HAVE_GPIO_ADC select HAVE_GPIO_I2C select HAVE_GPIO_SPI select HAVE_GPIO_HARD_PWM diff --git a/src/samd21/Makefile b/src/samd21/Makefile index abe21013..2bcf9151 100644 --- a/src/samd21/Makefile +++ b/src/samd21/Makefile @@ -20,6 +20,7 @@ src-y += generic/armcm_irq.c generic/timer_irq.c src-y += ../lib/samd21/samd21a/gcc/gcc/startup_samd21.c src-$(CONFIG_USBSERIAL) += samd21/usbserial.c generic/usb_cdc.c src-$(CONFIG_SERIAL) += samd21/serial.c generic/serial_irq.c +src-$(CONFIG_HAVE_GPIO_ADC) += samd21/adc.c src-$(CONFIG_HAVE_GPIO_I2C) += samd21/i2c.c src-$(CONFIG_HAVE_GPIO_SPI) += samd21/spi.c src-$(CONFIG_HAVE_GPIO_HARD_PWM) += samd21/hard_pwm.c diff --git a/src/samd21/adc.c b/src/samd21/adc.c new file mode 100644 index 00000000..38836592 --- /dev/null +++ b/src/samd21/adc.c @@ -0,0 +1,121 @@ +// Analog to Digital Converter support +// +// Copyright (C) 2018 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "command.h" // shutdown +#include "gpio.h" // gpio_adc_read +#include "internal.h" // GPIO +#include "samd21.h" // ADC +#include "sched.h" // sched_shutdown + +static const uint8_t adc_pins[] = { + GPIO('A', 2), GPIO('A', 3), GPIO('B', 8), GPIO('B', 9), GPIO('A', 4), + GPIO('A', 5), GPIO('A', 6), GPIO('A', 7), GPIO('B', 0), GPIO('B', 1), + GPIO('B', 2), GPIO('B', 3), GPIO('B', 4), GPIO('B', 5), GPIO('B', 6), + GPIO('B', 7), GPIO('A', 8), GPIO('A', 9), GPIO('A', 10), GPIO('A', 11) +}; + +DECL_CONSTANT(ADC_MAX, 4095); + +static void +adc_init(void) +{ + static uint8_t have_run_init; + if (have_run_init) + return; + have_run_init = 1; + + // Enable adc clock + enable_pclock(ADC_GCLK_ID, PM_APBCMASK_ADC); + + // Load calibraiton info + uint32_t v = *((uint32_t*)ADC_FUSES_BIASCAL_ADDR); + uint32_t bias = (v & ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos; + v = *((uint32_t*)ADC_FUSES_LINEARITY_0_ADDR); + uint32_t li0 = (v & ADC_FUSES_LINEARITY_0_Msk) >> ADC_FUSES_LINEARITY_0_Pos; + v = *((uint32_t*)ADC_FUSES_LINEARITY_1_ADDR); + uint32_t li5 = (v & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos; + uint32_t lin = li0 | (li5 << 5); + ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(lin); + + // Setup and enable adc + ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; + ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV128; + ADC->SAMPCTRL.reg = 63; + ADC->CTRLA.reg = ADC_CTRLA_ENABLE; +} + +struct gpio_adc +gpio_adc_setup(uint8_t pin) +{ + // Find pin in adc_pins table + uint8_t chan; + for (chan=0; ; chan++) { + if (chan >= ARRAY_SIZE(adc_pins)) + shutdown("Not a valid ADC pin"); + if (adc_pins[chan] == pin) + break; + } + + // Enable ADC + adc_init(); + + // Set pin in ADC mode + gpio_peripheral(pin, 'B', 0); + + return (struct gpio_adc){ chan }; +} + +enum { ADC_DUMMY=0xff }; +static uint8_t last_analog_read = ADC_DUMMY; + +// Try to sample a value. Returns zero if sample ready, otherwise +// returns the number of clock ticks the caller should wait before +// retrying this function. +uint32_t +gpio_adc_sample(struct gpio_adc g) +{ + if (last_analog_read == g.chan) { + if (ADC->INTFLAG.reg & ADC_INTFLAG_RESRDY) + // Sample now ready + return 0; + // ADC is still busy + goto need_delay; + } + if (last_analog_read != ADC_DUMMY) + // Sample on another channel in progress + goto need_delay; + last_analog_read = g.chan; + + // Set the channel to sample + ADC->INPUTCTRL.reg = (ADC_INPUTCTRL_MUXPOS(g.chan) + | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_GAIN_DIV2); + + // Start the sample + ADC->SWTRIG.reg = ADC_SWTRIG_START; + + // Schedule next attempt after sample is likely to be complete +need_delay: + return 42 * 128 + 200; // 42 == 1 + (63+1)/2 + 1 + 12/2 + 1.5 +} + +// Read a value; use only after gpio_adc_sample() returns zero +uint16_t +gpio_adc_read(struct gpio_adc g) +{ + last_analog_read = ADC_DUMMY; + return ADC->RESULT.reg; +} + +// Cancel a sample that may have been started with gpio_adc_sample() +void +gpio_adc_cancel_sample(struct gpio_adc g) +{ + if (last_analog_read == g.chan) { + ADC->SWTRIG.reg = ADC_SWTRIG_FLUSH; + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + last_analog_read = ADC_DUMMY; + } +} diff --git a/src/samd21/gpio.h b/src/samd21/gpio.h index 6c910da8..5bf44454 100644 --- a/src/samd21/gpio.h +++ b/src/samd21/gpio.h @@ -27,6 +27,14 @@ struct gpio_pwm { struct gpio_pwm gpio_pwm_setup(uint8_t pin, uint32_t cycle_time, uint8_t val); void gpio_pwm_write(struct gpio_pwm g, uint8_t val); +struct gpio_adc { + uint32_t chan; +}; +struct gpio_adc gpio_adc_setup(uint8_t pin); +uint32_t gpio_adc_sample(struct gpio_adc g); +uint16_t gpio_adc_read(struct gpio_adc g); +void gpio_adc_cancel_sample(struct gpio_adc g); + struct spi_config { uint32_t ctrla, baud; };