summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDamien George <damien.p.george@gmail.com>2019-07-20 12:37:58 +1000
committerDamien George <damien.p.george@gmail.com>2019-09-04 15:40:24 +1000
commitebacdfabb6e2ac695d6660f2a6420ab84db76d39 (patch)
treebd219ff069f6c80685bd0a40a8b16782860d1154
parente509da22df8cd337c1cc8dd531c6150e6fdb4ee0 (diff)
stm32/machine_adc: Add machine.ADC class.
-rw-r--r--ports/stm32/Makefile1
-rw-r--r--ports/stm32/machine_adc.c409
-rw-r--r--ports/stm32/modmachine.c4
-rw-r--r--ports/stm32/modmachine.h2
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);