From 7ba89f5330fb2a7d48efb56f1b5920e8dfffedef Mon Sep 17 00:00:00 2001 From: Thea Flowers Date: Sat, 18 Jul 2020 22:33:06 -0700 Subject: [PATCH] Add _bhb user module for Big Honking Button BHB needs better accuracy from the ADC readings. To avoid changing the ADC configuration for all boards or adding complexity to AnalogIn, I implemented a custom user module to allow the BHB to talk to the ADC in the way that it needs to. I'm open to other approaches here, but this seemed like the least invasive and complex option. --- .../mpconfigboard.mk | 4 + .../usermods/_bhb/bhb.c | 120 ++++++++++++++++++ .../usermods/_bhb/micropython.mk | 6 + 3 files changed, 130 insertions(+) create mode 100644 ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/bhb.c create mode 100644 ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/micropython.mk diff --git a/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk b/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk index 466e345911..35a72819b8 100644 --- a/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk +++ b/ports/atmel-samd/boards/winterbloom_big_honking_button/mpconfigboard.mk @@ -32,3 +32,7 @@ CIRCUITPY_RGBMATRIX = 0 CIRCUITPY_PS2IO = 0 CIRCUITPY_USB_HID = 0 CIRCUITPY_RTC = 0 + +# Enable board-specific modules +USER_C_MODULES = boards/winterbloom_big_honking_button/usermods +CFLAGS += -DMODULE_BHB_ENABLED=1 diff --git a/ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/bhb.c b/ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/bhb.c new file mode 100644 index 0000000000..54ce173d81 --- /dev/null +++ b/ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/bhb.c @@ -0,0 +1,120 @@ +#include "py/obj.h" +#include "py/runtime.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "samd/pins.h" +#include "sam.h" + +STATIC mp_obj_t _bhb_read_adc(void); + +STATIC mp_obj_t _bhb_init_adc(void) { + claim_pin(&pin_PB08); + common_hal_never_reset_pin(&pin_PB08); + + /* Enable the APB clock for the ADC. */ + PM->APBCMASK.reg |= PM_APBCMASK_ADC; + + /* Enable GCLK0 for the ADC */ + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | + GCLK_CLKCTRL_GEN_GCLK0 | + GCLK_CLKCTRL_ID_ADC; + + /* Wait for bus synchronization. */ + while (GCLK->STATUS.bit.SYNCBUSY) {}; + + uint32_t bias = (*((uint32_t *) ADC_FUSES_BIASCAL_ADDR) & ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos; + uint32_t linearity = (*((uint32_t *) ADC_FUSES_LINEARITY_0_ADDR) & ADC_FUSES_LINEARITY_0_Msk) >> ADC_FUSES_LINEARITY_0_Pos; + linearity |= ((*((uint32_t *) ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5; + + /* Wait for bus synchronization. */ + while (ADC->STATUS.bit.SYNCBUSY) {}; + + /* Write the calibration data. */ + ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); + + /* Use the internal VCC reference. This is 1/2 of what's on VCCA. + since VCCA is 3.3v, this is 1.65v. + */ + ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; + + /* Capture 64 samples. */ + ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_64 | ADC_AVGCTRL_ADJRES(4); + + /* Set the clock prescaler to 32, which is the same as the CircuitPython default. + Set the resolution to 16 for averaging + */ + ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV32 | + ADC_CTRLB_RESSEL_16BIT; + + /* Configure the input parameters. + + - GAIN_DIV2 means that the input voltage is halved. This is important + because the voltage reference is 1/2 of VCCA. So if you want to + measure 0-3.3v, you need to halve the input as well. + + - MUXNEG_GND means that the ADC should compare the input value to GND. + + - MUXPOS_PIN3 means that the ADC should read from AIN2, or PB08. + */ + ADC->INPUTCTRL.reg = ADC_INPUTCTRL_GAIN_DIV2 | + ADC_INPUTCTRL_MUXNEG_GND | + ADC_INPUTCTRL_MUXPOS_PIN2; + + + /* Set PB08 as an input pin. */ + PORT->Group[1].DIRCLR.reg = PORT_PB08; + + /* Enable the peripheral multiplexer for PB08. */ + PORT->Group[1].PINCFG[8].reg |= PORT_PINCFG_PMUXEN; + + /* Set PB08 to function B which is analog input. */ + PORT->Group[1].PMUX[4].reg |= PORT_PMUX_PMUXE_B; + + /* Wait for bus synchronization. */ + while (ADC->STATUS.bit.SYNCBUSY) {}; + + /* Enable the ADC. */ + ADC->CTRLA.bit.ENABLE = true; + + /* Make one read and throw it away, as per the datasheet. */ + _bhb_read_adc(); + + return mp_const_none; +} + +STATIC mp_obj_t _bhb_read_adc(void) { + /* Wait for bus synchronization. */ + while (ADC->STATUS.bit.SYNCBUSY) {}; + + /* Start the ADC using a software trigger. */ + ADC->SWTRIG.bit.START = true; + + /* Wait for the result ready flag to be set. */ + while (ADC->INTFLAG.bit.RESRDY == 0); + + /* Clear the flag. */ + ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; + + /* Read the value. */ + uint32_t result = ADC->RESULT.reg; + + return MP_OBJ_NEW_SMALL_INT(result); +} + + +STATIC MP_DEFINE_CONST_FUN_OBJ_0(_bhb_init_adc_obj, _bhb_init_adc); +STATIC MP_DEFINE_CONST_FUN_OBJ_0(_bhb_read_adc_obj, _bhb_read_adc); + +STATIC const mp_rom_map_elem_t _bhb_module_globals_table[] = { + { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR__bhb) }, + { MP_ROM_QSTR(MP_QSTR_init_adc), MP_ROM_PTR(&_bhb_init_adc_obj) }, + { MP_ROM_QSTR(MP_QSTR_read_adc), MP_ROM_PTR(&_bhb_read_adc_obj) }, +}; + +STATIC MP_DEFINE_CONST_DICT(_bhb_module_globals, _bhb_module_globals_table); + +const mp_obj_module_t _bhb_user_cmodule = { + .base = { &mp_type_module }, + .globals = (mp_obj_dict_t*)&_bhb_module_globals, +}; + +MP_REGISTER_MODULE(MP_QSTR__bhb, _bhb_user_cmodule, MODULE_BHB_ENABLED); diff --git a/ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/micropython.mk b/ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/micropython.mk new file mode 100644 index 0000000000..9374ec34ea --- /dev/null +++ b/ports/atmel-samd/boards/winterbloom_big_honking_button/usermods/_bhb/micropython.mk @@ -0,0 +1,6 @@ +USERMODULES_DIR := $(USERMOD_DIR) + +# Add all C files to SRC_USERMOD. +SRC_USERMOD += $(USERMODULES_DIR)/bhb.c + +CFLAGS_USERMOD += -I$(USERMODULES_DIR)