diff options
author | Damien George <damien.p.george@gmail.com> | 2019-07-20 12:37:58 +1000 |
---|---|---|
committer | Damien George <damien.p.george@gmail.com> | 2019-09-04 15:40:24 +1000 |
commit | ebacdfabb6e2ac695d6660f2a6420ab84db76d39 (patch) | |
tree | bd219ff069f6c80685bd0a40a8b16782860d1154 | |
parent | e509da22df8cd337c1cc8dd531c6150e6fdb4ee0 (diff) |
stm32/machine_adc: Add machine.ADC class.
-rw-r--r-- | ports/stm32/Makefile | 1 | ||||
-rw-r--r-- | ports/stm32/machine_adc.c | 409 | ||||
-rw-r--r-- | ports/stm32/modmachine.c | 4 | ||||
-rw-r--r-- | ports/stm32/modmachine.h | 2 |
4 files changed, 413 insertions, 3 deletions
diff --git a/ports/stm32/Makefile b/ports/stm32/Makefile index e868d6de1..6c0c29572 100644 --- a/ports/stm32/Makefile +++ b/ports/stm32/Makefile @@ -261,6 +261,7 @@ SRC_C = \ eth.c \ gccollect.c \ help.c \ + machine_adc.c \ machine_i2c.c \ machine_spi.c \ machine_uart.c \ diff --git a/ports/stm32/machine_adc.c b/ports/stm32/machine_adc.c new file mode 100644 index 000000000..58c013359 --- /dev/null +++ b/ports/stm32/machine_adc.c @@ -0,0 +1,409 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Damien P. George + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include "py/runtime.h" +#include "py/mphal.h" + +#if defined(STM32F0) || defined(STM32H7) || defined(STM32L0) || defined(STM32L4) || defined(STM32WB) +#define ADC_V2 (1) +#else +#define ADC_V2 (0) +#endif + +#if defined(STM32F0) || defined(STM32L0) +#define ADC_STAB_DELAY_US (1) +#define ADC_TEMPSENSOR_DELAY_US (10) +#elif defined(STM32L4) +#define ADC_STAB_DELAY_US (10) +#elif defined(STM32WB) +#define ADC_STAB_DELAY_US (1) +#endif + +#if defined(STM32F0) +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_71CYCLES_5 +#define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_239CYCLES_5 +#elif defined(STM32F4) || defined(STM32F7) +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_15CYCLES +#define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_480CYCLES +#elif defined(STM32H7) +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_8CYCLES_5 +#define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_387CYCLES_5 +#elif defined(STM32L0) +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_12CYCLES_5 +#define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_160CYCLES_5 +#elif defined(STM32L4) || defined(STM32WB) +#define ADC_SAMPLETIME_DEFAULT ADC_SAMPLETIME_12CYCLES_5 +#define ADC_SAMPLETIME_DEFAULT_INT ADC_SAMPLETIME_247CYCLES_5 +#endif + +// Timeout for waiting for end-of-conversion +#define ADC_EOC_TIMEOUT_MS (10) + +// This is a synthesised channel representing the maximum ADC reading (useful to scale other channels) +#define ADC_CHANNEL_VREF (0xffff) + +static inline void adc_stabilisation_delay_us(uint32_t us) { + mp_hal_delay_us(us + 1); +} + +STATIC void adc_wait_eoc(ADC_TypeDef *adc, int32_t timeout_ms) { + uint32_t t0 = mp_hal_ticks_ms(); + #if ADC_V2 + while (!(adc->ISR & ADC_FLAG_EOC)) + #else + while (!(adc->SR & ADC_FLAG_EOC)) + #endif + { + if (mp_hal_ticks_ms() - t0 > timeout_ms) { + break; // timeout + } + } +} + +#if defined(STM32H7) +STATIC const uint8_t adc_cr_to_bits_table[] = {16, 14, 12, 10, 8, 8, 8, 8}; +#else +STATIC const uint8_t adc_cr_to_bits_table[] = {12, 10, 8, 6}; +#endif + +STATIC void adc_config(ADC_TypeDef *adc, uint32_t bits) { + #if defined(STM32L4) || defined(STM32WB) + __HAL_RCC_ADC_CLK_ENABLE(); + #else + if (adc == ADC1) { + #if defined(STM32H7) + __HAL_RCC_ADC12_CLK_ENABLE(); + #else + __HAL_RCC_ADC1_CLK_ENABLE(); + #endif + } + #if defined(ADC2) + if (adc == ADC2) { + #if defined(STM32H7) + __HAL_RCC_ADC12_CLK_ENABLE(); + #else + __HAL_RCC_ADC2_CLK_ENABLE(); + #endif + } + #endif + #if defined(ADC3) + if (adc == ADC3) { + __HAL_RCC_ADC3_CLK_ENABLE(); + } + #endif + #endif + + #if ADC_V2 + if (adc->CR & ADC_CR_ADEN) { + // ADC enabled, need to disable it to change configuration + if (adc->CR & ADC_CR_ADSTART) { + adc->CR |= ADC_CR_ADSTP; + while (adc->CR & ADC_CR_ADSTP) { + } + } + adc->CR |= ADC_CR_ADDIS; + while (adc->CR & ADC_CR_ADDIS) { + } + } + #endif + + // TODO check all these + #if defined(STM32F0) + adc->CFGR2 = 1 << ADC_CFGR2_CKMODE_Pos; // PCLK/2 (synchronous clock mode) + #elif defined(STM32F4) || defined(STM32F7) || defined(STM32L4) + ADC123_COMMON->CCR = 0; // ADCPR=PCLK/2 + #elif defined(STM32H7) + __HAL_RCC_ADC_CONFIG(RCC_ADCCLKSOURCE_CLKP); + #elif defined(STM32L0) || defined(STM32WB) + ADC1_COMMON->CCR = 0; // ADCPR=PCLK/2 + #endif + + // Find resolution, defaulting to last element in table + uint32_t res; + for (res = 0; res <= MP_ARRAY_SIZE(adc_cr_to_bits_table); ++res) { + if (adc_cr_to_bits_table[res] == bits) { + break; + } + } + + #if defined(STM32F0) || defined(STM32L0) + + uint32_t cfgr1_clr = ADC_CFGR1_CONT | ADC_CFGR1_EXTEN | ADC_CFGR1_ALIGN | ADC_CFGR1_RES | ADC_CFGR1_DMAEN; + uint32_t cfgr1 = res << ADC_CFGR1_RES_Pos; + adc->CFGR1 = (adc->CFGR1 & ~cfgr1_clr) | cfgr1; + + #elif defined(STM32F4) || defined(STM32F7) + + uint32_t cr1_clr = ADC_CR1_RES; + uint32_t cr1 = res << ADC_CR1_RES_Pos; + adc->CR1 = (adc->CR1 & ~cr1_clr) | cr1; + uint32_t cr2_clr = ADC_CR2_EXTEN | ADC_CR2_ALIGN | ADC_CR2_DMA | ADC_CR2_CONT; + uint32_t cr2 = 0; + adc->CR2 = (adc->CR2 & ~cr2_clr) | cr2; + adc->SQR1 = 1 << ADC_SQR1_L_Pos; // 1 conversion in regular sequence + + #elif defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + + uint32_t cfgr_clr = ADC_CFGR_CONT | ADC_CFGR_EXTEN | ADC_CFGR_RES; + #if defined(STM32H7) + cfgr_clr |= ADC_CFGR_DMNGT; + #else + cfgr_clr |= ADC_CFGR_ALIGN | ADC_CFGR_DMAEN; + #endif + uint32_t cfgr = res << ADC_CFGR_RES_Pos; + adc->CFGR = (adc->CFGR & ~cfgr_clr) | cfgr; + + #endif +} + +STATIC int adc_get_bits(ADC_TypeDef *adc) { + #if defined(STM32F0) || defined(STM32L0) + uint32_t res = (adc->CFGR1 & ADC_CFGR1_RES) >> ADC_CFGR1_RES_Pos; + #elif defined(STM32F4) || defined(STM32F7) + uint32_t res = (adc->CR1 & ADC_CR1_RES) >> ADC_CR1_RES_Pos; + #elif defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + uint32_t res = (adc->CFGR & ADC_CFGR_RES) >> ADC_CFGR_RES_Pos; + #endif + return adc_cr_to_bits_table[res]; +} + +STATIC void adc_config_channel(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) { + #if ADC_V2 + if (!(adc->CR & ADC_CR_ADEN)) { + if (adc->CR) { + // Cannot enable ADC with CR!=0 + return; + } + adc->CR |= ADC_CR_ADEN; + adc_stabilisation_delay_us(ADC_STAB_DELAY_US); + while (!(adc->ISR & ADC_ISR_ADRDY)) { + } + } + #else + if (!(adc->CR2 & ADC_CR2_ADON)) { + adc->CR2 |= ADC_CR2_ADON; + adc_stabilisation_delay_us(ADC_STAB_DELAY_US); + } + #endif + + #if defined(STM32F0) || defined(STM32L0) + + if (channel == ADC_CHANNEL_VREFINT) { + ADC1_COMMON->CCR |= ADC_CCR_VREFEN; + } else if (channel == ADC_CHANNEL_TEMPSENSOR) { + ADC1_COMMON->CCR |= ADC_CCR_TSEN; + adc_stabilisation_delay_us(ADC_TEMPSENSOR_DELAY_US); + #if defined(ADC_CHANNEL_VBAT) + } else if (channel == ADC_CHANNEL_VBAT) { + ADC1_COMMON->CCR |= ADC_CCR_VBATEN; + #endif + } + adc->SMPR = sample_time << ADC_SMPR_SMP_Pos; // select sample time + adc->CHSELR = 1 << channel; // select channel for conversion + + #elif defined(STM32F4) || defined(STM32F7) + + if (channel == ADC_CHANNEL_VREFINT || channel == ADC_CHANNEL_TEMPSENSOR) { + ADC123_COMMON->CCR = (ADC123_COMMON->CCR & ~ADC_CCR_VBATE) | ADC_CCR_TSVREFE; + if (channel == ADC_CHANNEL_TEMPSENSOR) { + adc_stabilisation_delay_us(ADC_TEMPSENSOR_DELAY_US); + } + } else if (channel == ADC_CHANNEL_VBAT) { + ADC123_COMMON->CCR |= ADC_CCR_VBATE; + } + + adc->SQR3 = (channel & 0x1f) << ADC_SQR3_SQ1_Pos; // select channel for first conversion + + __IO uint32_t *smpr; + if (channel <= 9) { + smpr = &adc->SMPR2; + } else { + smpr = &adc->SMPR1; + channel -= 10; + } + *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time + + #elif defined(STM32H7) || defined(STM32L4) || defined(STM32WB) + + #if defined(STM32H7) + ADC_Common_TypeDef *adc_common = adc == ADC3 ? ADC3_COMMON : ADC12_COMMON; + #elif defined(STM32L4) + ADC_Common_TypeDef *adc_common = ADC123_COMMON; + #elif defined(STM32WB) + ADC_Common_TypeDef *adc_common = ADC1_COMMON; + #endif + if (channel == ADC_CHANNEL_VREFINT) { + adc_common->CCR |= ADC_CCR_VREFEN; + } else if (channel == ADC_CHANNEL_TEMPSENSOR) { + adc_common->CCR |= ADC_CCR_TSEN; + adc_stabilisation_delay_us(ADC_TEMPSENSOR_DELAY_US); + } else if (channel == ADC_CHANNEL_VBAT) { + adc_common->CCR |= ADC_CCR_VBATEN; + } + adc->SQR1 = (channel & 0x1f) << ADC_SQR1_SQ1_Pos | 1 << ADC_SQR1_L_Pos; + __IO uint32_t *smpr; + if (channel <= 9) { + smpr = &adc->SMPR1; + } else { + smpr = &adc->SMPR2; + channel -= 10; + } + *smpr = (*smpr & ~(7 << (channel * 3))) | sample_time << (channel * 3); // select sample time + + #endif +} + +STATIC uint32_t adc_read_channel(ADC_TypeDef *adc) { + #if ADC_V2 + adc->CR |= ADC_CR_ADSTART; + #else + adc->CR2 |= ADC_CR2_SWSTART; + #endif + adc_wait_eoc(adc, ADC_EOC_TIMEOUT_MS); + uint32_t value = adc->DR; + return value; +} + +STATIC uint32_t adc_config_and_read_u16(ADC_TypeDef *adc, uint32_t channel, uint32_t sample_time) { + if (channel == ADC_CHANNEL_VREF) { + return 0xffff; + } + + adc_config_channel(adc, channel, sample_time); + uint32_t raw = adc_read_channel(adc); + uint32_t bits = adc_get_bits(adc); + // Scale raw reading to 16 bit value using a Taylor expansion (for 8 <= bits <= 16) + #if defined(STM32H7) + if (bits < 8) { + // For 6 and 7 bits + return raw << (16 - bits) | raw << (16 - 2 * bits) | raw >> (3 * bits - 16); + } + #endif + return raw << (16 - bits) | raw >> (2 * bits - 16); +} + +/******************************************************************************/ +// MicroPython bindings for machine.ADC + +const mp_obj_type_t machine_adc_type; + +typedef struct _machine_adc_obj_t { + mp_obj_base_t base; + ADC_TypeDef *adc; + uint32_t channel; + uint32_t sample_time; +} machine_adc_obj_t; + +STATIC void machine_adc_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + #if defined(STM32F0) || defined(STM32L0) || defined(STM32WB) + unsigned adc_id = 1; + #else + unsigned adc_id = (self->adc - ADC1) / (ADC2 - ADC1) + 1; + #endif + mp_printf(print, "<ADC%u channel=%u>", adc_id, self->channel); +} + +// ADC(id) +STATIC mp_obj_t machine_adc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *all_args) { + // Check number of arguments + mp_arg_check_num(n_args, n_kw, 1, 1, false); + + mp_obj_t source = all_args[0]; + + uint32_t channel; + uint32_t sample_time = ADC_SAMPLETIME_DEFAULT; + ADC_TypeDef *adc; + if (mp_obj_is_int(source)) { + adc = ADC1; + channel = mp_obj_get_int(source); + if (channel == ADC_CHANNEL_VREFINT + || channel == ADC_CHANNEL_TEMPSENSOR + #if defined(ADC_CHANNEL_VBAT) + || channel == ADC_CHANNEL_VBAT + #endif + ) { + sample_time = ADC_SAMPLETIME_DEFAULT_INT; + } + } else { + const pin_obj_t *pin = pin_find(source); + if (pin->adc_num & PIN_ADC1) { + adc = ADC1; + #if defined(ADC2) + } else if (pin->adc_num & PIN_ADC2) { + adc = ADC2; + #endif + #if defined(ADC2) + } else if (pin->adc_num & PIN_ADC3) { + adc = ADC3; + #endif + } else { + // No ADC function on given pin + nlr_raise(mp_obj_new_exception_msg_varg(&mp_type_ValueError, "Pin(%q) does not have ADC capabilities", pin->name)); + } + channel = pin->adc_channel; + + // Configure the GPIO pin in ADC mode + mp_hal_pin_config(pin, MP_HAL_PIN_MODE_ADC, MP_HAL_PIN_PULL_NONE, 0); + } + + adc_config(adc, 12); + + machine_adc_obj_t *o = m_new_obj(machine_adc_obj_t); + o->base.type = &machine_adc_type; + o->adc = adc; + o->channel = channel; + o->sample_time = sample_time; + + return MP_OBJ_FROM_PTR(o); +} + +// read_u16() +STATIC mp_obj_t machine_adc_read_u16(mp_obj_t self_in) { + machine_adc_obj_t *self = MP_OBJ_TO_PTR(self_in); + return MP_OBJ_NEW_SMALL_INT(adc_config_and_read_u16(self->adc, self->channel, self->sample_time)); +} +MP_DEFINE_CONST_FUN_OBJ_1(machine_adc_read_u16_obj, machine_adc_read_u16); + +STATIC const mp_rom_map_elem_t machine_adc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_read_u16), MP_ROM_PTR(&machine_adc_read_u16_obj) }, + + { MP_ROM_QSTR(MP_QSTR_VREF), MP_ROM_INT(ADC_CHANNEL_VREF) }, + { MP_ROM_QSTR(MP_QSTR_CORE_VREF), MP_ROM_INT(ADC_CHANNEL_VREFINT) }, + { MP_ROM_QSTR(MP_QSTR_CORE_TEMP), MP_ROM_INT(ADC_CHANNEL_TEMPSENSOR) }, + #if defined(ADC_CHANNEL_VBAT) + { MP_ROM_QSTR(MP_QSTR_CORE_VBAT), MP_ROM_INT(ADC_CHANNEL_VBAT) }, + #endif +}; +STATIC MP_DEFINE_CONST_DICT(machine_adc_locals_dict, machine_adc_locals_dict_table); + +const mp_obj_type_t machine_adc_type = { + { &mp_type_type }, + .name = MP_QSTR_ADC, + .print = machine_adc_print, + .make_new = machine_adc_make_new, + .locals_dict = (mp_obj_dict_t*)&machine_adc_locals_dict, +}; diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index e45f81479..eaa536a1d 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -392,9 +392,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&pyb_rtc_type) }, -#if 0 - { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&pyb_adc_type) }, -#endif + { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, #if MICROPY_PY_MACHINE_I2C { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, #endif diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h index 414e5b37c..4b727d3cb 100644 --- a/ports/stm32/modmachine.h +++ b/ports/stm32/modmachine.h @@ -28,6 +28,8 @@ #include "py/obj.h" +extern const mp_obj_type_t machine_adc_type; + void machine_init(void); void machine_deinit(void); |