diff --git a/src/stm32/Kconfig b/src/stm32/Kconfig index 378fb00b..af186416 100644 --- a/src/stm32/Kconfig +++ b/src/stm32/Kconfig @@ -6,7 +6,7 @@ config STM32_SELECT bool default y select HAVE_GPIO - select HAVE_GPIO_ADC if !MACH_STM32F0 + select HAVE_GPIO_ADC select HAVE_GPIO_I2C if !MACH_STM32F0 select HAVE_GPIO_SPI select HAVE_GPIO_BITBANGING diff --git a/src/stm32/Makefile b/src/stm32/Makefile index d05f6b24..79abc881 100644 --- a/src/stm32/Makefile +++ b/src/stm32/Makefile @@ -26,12 +26,13 @@ src-y += stm32/watchdog.c stm32/gpio.c generic/crc16_ccitt.c src-y += generic/armcm_boot.c generic/armcm_irq.c generic/armcm_reset.c src-$(CONFIG_MACH_STM32F0) += ../lib/stm32f0/system_stm32f0xx.c src-$(CONFIG_MACH_STM32F0) += generic/timer_irq.c stm32/stm32f0_timer.c -src-$(CONFIG_MACH_STM32F0) += stm32/stm32f0.c +src-$(CONFIG_MACH_STM32F0) += stm32/stm32f0.c stm32/stm32f0_adc.c src-$(CONFIG_MACH_STM32F1) += ../lib/stm32f1/system_stm32f1xx.c src-$(CONFIG_MACH_STM32F1) += stm32/stm32f1.c generic/armcm_timer.c +src-$(CONFIG_MACH_STM32F1) += stm32/adc.c src-$(CONFIG_MACH_STM32F4) += ../lib/stm32f4/system_stm32f4xx.c src-$(CONFIG_MACH_STM32F4) += stm32/stm32f4.c generic/armcm_timer.c -src-$(CONFIG_HAVE_GPIO_ADC) += stm32/adc.c +src-$(CONFIG_MACH_STM32F4) += stm32/adc.c src-$(CONFIG_HAVE_GPIO_I2C) += stm32/i2c.c src-$(CONFIG_HAVE_GPIO_SPI) += stm32/spi.c usb-src-$(CONFIG_HAVE_STM32_USBFS) := stm32/usbfs.c diff --git a/src/stm32/stm32f0.c b/src/stm32/stm32f0.c index 26a3512c..f9eabeab 100644 --- a/src/stm32/stm32f0.c +++ b/src/stm32/stm32f0.c @@ -156,6 +156,16 @@ hsi48_setup(void) #endif } +// Enable high speed internal 14Mhz clock for ADC +static void +hsi14_setup(void) +{ + // Enable HSI14 for ADC + RCC->CR2 = RCC_CR2_HSI14ON; + while (!(RCC->CR2 & RCC_CR2_HSI14RDY)) + ; +} + // Main entry point - called from armcm_boot.c:ResetHandler() void armcm_main(void) @@ -179,6 +189,9 @@ armcm_main(void) else pll_setup(); + // Turn on hsi14 oscillator for ADC + hsi14_setup(); + // Support alternate USB pins on stm32f042 #ifdef SYSCFG_CFGR1_PA11_PA12_RMP if (CONFIG_STM32F042_USB_PIN_SWAP) { diff --git a/src/stm32/stm32f0_adc.c b/src/stm32/stm32f0_adc.c new file mode 100644 index 00000000..21ef6a6a --- /dev/null +++ b/src/stm32/stm32f0_adc.c @@ -0,0 +1,127 @@ +// ADC functions on STM32 +// +// Copyright (C) 2019 Kevin O'Connor +// +// This file may be distributed under the terms of the GNU GPLv3 license. + +#include "board/irq.h" // irq_save +#include "board/misc.h" // timer_from_us +#include "command.h" // shutdown +#include "compiler.h" // ARRAY_SIZE +#include "generic/armcm_timer.h" // udelay +#include "gpio.h" // gpio_adc_setup +#include "internal.h" // GPIO +#include "sched.h" // sched_shutdown + +DECL_CONSTANT("ADC_MAX", 4095); + +static const uint32_t adc_pins[][2] = { + {GPIO('A', 0), ADC_CHSELR_CHSEL0}, + {GPIO('A', 1), ADC_CHSELR_CHSEL1}, + {GPIO('A', 2), ADC_CHSELR_CHSEL2}, + {GPIO('A', 3), ADC_CHSELR_CHSEL3}, + {GPIO('A', 4), ADC_CHSELR_CHSEL4}, + {GPIO('A', 5), ADC_CHSELR_CHSEL5}, + {GPIO('A', 6), ADC_CHSELR_CHSEL6}, + {GPIO('A', 7), ADC_CHSELR_CHSEL7}, + {GPIO('B', 0), ADC_CHSELR_CHSEL8}, + {GPIO('B', 1), ADC_CHSELR_CHSEL9}, + {GPIO('C', 0), ADC_CHSELR_CHSEL10}, + {GPIO('C', 1), ADC_CHSELR_CHSEL11}, + {GPIO('C', 2), ADC_CHSELR_CHSEL12}, + {GPIO('C', 3), ADC_CHSELR_CHSEL13}, + {GPIO('C', 4), ADC_CHSELR_CHSEL14}, + {GPIO('C', 5), ADC_CHSELR_CHSEL15} +}; + +struct gpio_adc +gpio_adc_setup(uint32_t pin) +{ + // Find pin in adc_pins table + int chan; + for (chan=0; ; chan++) { + if (chan >= ARRAY_SIZE(adc_pins)) + shutdown("Not a valid ADC pin"); + if (adc_pins[chan][0] == pin) + break; + } + + // Determine which ADC block to use + ADC_TypeDef *adc = ADC1; + uint32_t adc_base = ADC1_BASE; + + // Enable the ADC + if (!is_enabled_pclock(adc_base)) { + enable_pclock(adc_base); + + // 100: 41.5 ADC clock cycles + adc->SMPR |= (~ADC_SMPR_SMP_Msk | ADC_SMPR_SMP_2 ); + adc->CFGR2 |= ADC_CFGR2_CKMODE; + adc->CFGR1 &= ~ADC_CFGR1_AUTOFF; + adc->CFGR1 |= ADC_CFGR1_EXTSEL; + + // do not enable ADC before calibration + adc->CR &= ~ADC_CR_ADEN; + while (adc->CR & ADC_CR_ADEN) + ; + while (adc->CFGR1 & ADC_CFGR1_DMAEN) + ; + // start calibration and wait for completion + adc->CR |= ADC_CR_ADCAL; + while (adc->CR & ADC_CR_ADCAL) + ; + + // if not enabled + if (!(adc->CR & ADC_CR_ADEN)){ + adc->ISR |= ADC_ISR_ADRDY; + adc->CR |= ADC_CR_ADEN; + while (!(ADC1->ISR & ADC_ISR_ADRDY)) + ; + } + } + + gpio_peripheral(pin, GPIO_ANALOG, 0); + + return (struct gpio_adc){ .adc = adc, .chan = adc_pins[chan][1] }; +} + +// 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) +{ + ADC_TypeDef *adc = g.adc; + if ((adc->ISR & ADC_ISR_EOC) && (adc->CHSELR == g.chan)){ + return 0; + } + if (adc->CR & ADC_CR_ADSTART){ + goto need_delay; + } + adc->CHSELR = g.chan; + adc->CR |= ADC_CR_ADSTART; + +need_delay: + return timer_from_us(10); +} + +// Read a value; use only after gpio_adc_sample() returns zero +uint16_t +gpio_adc_read(struct gpio_adc g) +{ + ADC_TypeDef *adc = g.adc; + adc->ISR &= ~ADC_ISR_EOSEQ; + return adc->DR; +} + +// Cancel a sample that may have been started with gpio_adc_sample() +void +gpio_adc_cancel_sample(struct gpio_adc g) +{ + ADC_TypeDef *adc = g.adc; + irqstatus_t flag = irq_save(); + if (!(adc->ISR & ADC_ISR_EOC) && (adc->CHSELR == g.chan)){ + adc->CR |= ADC_CR_ADSTP; + } + irq_restore(flag); +}