From 815920c87f9bda6b3fb7ec24686154210c9e8774 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Oct 2022 15:49:52 +1100 Subject: [PATCH 001/823] extmod/utime_mphal: Make ticks_add check for overflow of delta. Work done in collaboration with @jimmo. Signed-off-by: Damien George --- extmod/utime_mphal.c | 13 +++++++++++ tests/extmod/ticks_add.py | 42 +++++++++++++++++++++++++++++++++++ tests/extmod/ticks_add.py.exp | 36 ++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 tests/extmod/ticks_add.py create mode 100644 tests/extmod/ticks_add.py.exp diff --git a/extmod/utime_mphal.c b/extmod/utime_mphal.c index 3d1cdfd82062..cd91c95530cb 100644 --- a/extmod/utime_mphal.c +++ b/extmod/utime_mphal.c @@ -95,6 +95,19 @@ STATIC mp_obj_t time_ticks_add(mp_obj_t ticks_in, mp_obj_t delta_in) { // we assume that first argument come from ticks_xx so is small int mp_uint_t ticks = MP_OBJ_SMALL_INT_VALUE(ticks_in); mp_uint_t delta = mp_obj_get_int(delta_in); + + // Check that delta does not overflow the range that ticks_diff can handle. + // This ensures the following: + // - ticks_diff(ticks_add(T, delta), T) == delta + // - ticks_diff(T, ticks_add(T, delta)) == -delta + // The latter requires excluding delta=-TICKS_PERIOD/2. + // + // This unsigned comparison is equivalent to a signed comparison of: + // delta <= TICKS_PERIOD/2 || delta >= TICKS_PERIOD/2 + if (delta + MICROPY_PY_UTIME_TICKS_PERIOD / 2 - 1 >= MICROPY_PY_UTIME_TICKS_PERIOD - 1) { + mp_raise_msg(&mp_type_OverflowError, MP_ERROR_TEXT("ticks interval overflow")); + } + return MP_OBJ_NEW_SMALL_INT((ticks + delta) & (MICROPY_PY_UTIME_TICKS_PERIOD - 1)); } MP_DEFINE_CONST_FUN_OBJ_2(mp_utime_ticks_add_obj, time_ticks_add); diff --git a/tests/extmod/ticks_add.py b/tests/extmod/ticks_add.py new file mode 100644 index 000000000000..2f1ba6c8109d --- /dev/null +++ b/tests/extmod/ticks_add.py @@ -0,0 +1,42 @@ +try: + from utime import ticks_diff, ticks_add +except ImportError: + print("SKIP") + raise SystemExit + +# Maximum value returned from ticks_add, ticks_ms, etc. +TICKS_MAX = ticks_add(0, -1) +# Maximum value returned from ticks_diff. +TICKS_INTERVAL_MAX = TICKS_MAX // 2 + +# Invariants: +# - ticks_diff(ticks_add(T, delta), T) == delta +# - ticks_diff(T, ticks_add(T, delta)) == -delta + +# Check actual values of ticks_add. +print(ticks_add(20, 12)) +print(ticks_add(20, -12)) + +# Check invariant. +print(ticks_diff(ticks_add(100, 123), 100)) +print(ticks_diff(ticks_add(100, -123), 100)) +print(ticks_diff(100, ticks_add(100, 123))) +print(ticks_diff(100, ticks_add(100, -123))) + +# Check limits. +for T in (0, 10, TICKS_MAX): + for delta in ( + -TICKS_INTERVAL_MAX - 1, + -TICKS_INTERVAL_MAX, + 0, + TICKS_INTERVAL_MAX, + TICKS_INTERVAL_MAX + 1, + ): + try: + print(ticks_diff(ticks_add(T, delta), T) == delta) + except OverflowError: + print("OverflowError") + try: + print(ticks_diff(T, ticks_add(T, delta)) == -delta) + except OverflowError: + print("OverflowError") diff --git a/tests/extmod/ticks_add.py.exp b/tests/extmod/ticks_add.py.exp new file mode 100644 index 000000000000..60dc6f5afda6 --- /dev/null +++ b/tests/extmod/ticks_add.py.exp @@ -0,0 +1,36 @@ +32 +8 +123 +-123 +-123 +123 +OverflowError +OverflowError +True +True +True +True +True +True +OverflowError +OverflowError +OverflowError +OverflowError +True +True +True +True +True +True +OverflowError +OverflowError +OverflowError +OverflowError +True +True +True +True +True +True +OverflowError +OverflowError From 965a87b53cd9da32a546b03cdd01042058080498 Mon Sep 17 00:00:00 2001 From: Damien George Date: Fri, 14 Oct 2022 16:10:38 +1100 Subject: [PATCH 002/823] tests/extmod: Add test for sleep_ms value that overflows ticks. Addresses #9516. Signed-off-by: Damien George --- tests/extmod/uasyncio_micropython.py | 6 ++++++ tests/extmod/uasyncio_micropython.py.exp | 1 + 2 files changed, 7 insertions(+) diff --git a/tests/extmod/uasyncio_micropython.py b/tests/extmod/uasyncio_micropython.py index 69e5fa3224df..a6b65bb2a84c 100644 --- a/tests/extmod/uasyncio_micropython.py +++ b/tests/extmod/uasyncio_micropython.py @@ -22,6 +22,12 @@ async def main(): await uasyncio.sleep_ms(1) print(utime.ticks_diff(utime.ticks_ms(), t0) < 100) + try: + # Sleep 1ms beyond maximum allowed sleep value + await uasyncio.sleep_ms(utime.ticks_add(0, -1) // 2 + 1) + except OverflowError: + print("OverflowError") + # When task finished before the timeout print(await uasyncio.wait_for_ms(task(1, 5), 50)) diff --git a/tests/extmod/uasyncio_micropython.py.exp b/tests/extmod/uasyncio_micropython.py.exp index f5be1dc75a25..4d1c6d681f2f 100644 --- a/tests/extmod/uasyncio_micropython.py.exp +++ b/tests/extmod/uasyncio_micropython.py.exp @@ -1,4 +1,5 @@ True +OverflowError task start 1 task end 1 2 From af4ba6d1b4558e0ce1c81b8acd975f0bcbe83072 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 19:48:07 +0200 Subject: [PATCH 003/823] stm32: Rename machine I2C and SPI types consistently across ports. This renames: - machine_hard_i2c_type -> machine_i2c_type - machine_hard_spi_type -> machine_spi_type --- ports/stm32/machine_i2c.c | 18 +++++++++--------- ports/stm32/machine_spi.c | 14 +++++++------- ports/stm32/modmachine.c | 4 ++-- ports/stm32/modmachine.h | 4 +++- ports/stm32/spi.c | 4 ++-- ports/stm32/spi.h | 2 +- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/ports/stm32/machine_i2c.c b/ports/stm32/machine_i2c.c index 9a2d338f5498..7718d1bfe038 100644 --- a/ports/stm32/machine_i2c.c +++ b/ports/stm32/machine_i2c.c @@ -49,16 +49,16 @@ typedef struct _machine_hard_i2c_obj_t { STATIC const machine_hard_i2c_obj_t machine_hard_i2c_obj[MICROPY_HW_MAX_I2C] = { #if defined(MICROPY_HW_I2C1_SCL) - [0] = {{&machine_hard_i2c_type}, I2C1, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, + [0] = {{&machine_i2c_type}, I2C1, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, #endif #if defined(MICROPY_HW_I2C2_SCL) - [1] = {{&machine_hard_i2c_type}, I2C2, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, + [1] = {{&machine_i2c_type}, I2C2, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, #endif #if defined(MICROPY_HW_I2C3_SCL) - [2] = {{&machine_hard_i2c_type}, I2C3, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, + [2] = {{&machine_i2c_type}, I2C3, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, #endif #if defined(MICROPY_HW_I2C4_SCL) - [3] = {{&machine_hard_i2c_type}, I2C4, MICROPY_HW_I2C4_SCL, MICROPY_HW_I2C4_SDA}, + [3] = {{&machine_i2c_type}, I2C4, MICROPY_HW_I2C4_SCL, MICROPY_HW_I2C4_SDA}, #endif }; @@ -140,16 +140,16 @@ typedef mp_machine_soft_i2c_obj_t machine_hard_i2c_obj_t; STATIC machine_hard_i2c_obj_t machine_hard_i2c_obj[MICROPY_HW_MAX_I2C] = { #if defined(MICROPY_HW_I2C1_SCL) - [0] = {{&machine_hard_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, + [0] = {{&machine_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA}, #endif #if defined(MICROPY_HW_I2C2_SCL) - [1] = {{&machine_hard_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, + [1] = {{&machine_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C2_SCL, MICROPY_HW_I2C2_SDA}, #endif #if defined(MICROPY_HW_I2C3_SCL) - [2] = {{&machine_hard_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, + [2] = {{&machine_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C3_SCL, MICROPY_HW_I2C3_SDA}, #endif #if defined(MICROPY_HW_I2C4_SCL) - [3] = {{&machine_hard_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C4_SCL, MICROPY_HW_I2C4_SDA}, + [3] = {{&machine_i2c_type}, 1, I2C_POLL_DEFAULT_TIMEOUT_US, MICROPY_HW_I2C4_SCL, MICROPY_HW_I2C4_SDA}, #endif }; @@ -237,7 +237,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_i2c_type, + machine_i2c_type, MP_QSTR_I2C, MP_TYPE_FLAG_NONE, make_new, machine_hard_i2c_make_new, diff --git a/ports/stm32/machine_spi.c b/ports/stm32/machine_spi.c index 718ae1af5e5a..7ccff75f1ccf 100644 --- a/ports/stm32/machine_spi.c +++ b/ports/stm32/machine_spi.c @@ -32,12 +32,12 @@ // Implementation of hard SPI for machine module STATIC const machine_hard_spi_obj_t machine_hard_spi_obj[] = { - {{&machine_hard_spi_type}, &spi_obj[0]}, - {{&machine_hard_spi_type}, &spi_obj[1]}, - {{&machine_hard_spi_type}, &spi_obj[2]}, - {{&machine_hard_spi_type}, &spi_obj[3]}, - {{&machine_hard_spi_type}, &spi_obj[4]}, - {{&machine_hard_spi_type}, &spi_obj[5]}, + {{&machine_spi_type}, &spi_obj[0]}, + {{&machine_spi_type}, &spi_obj[1]}, + {{&machine_spi_type}, &spi_obj[2]}, + {{&machine_spi_type}, &spi_obj[3]}, + {{&machine_spi_type}, &spi_obj[4]}, + {{&machine_spi_type}, &spi_obj[5]}, }; STATIC void machine_hard_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -136,7 +136,7 @@ STATIC const mp_machine_spi_p_t machine_hard_spi_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_spi_type, + machine_spi_type, MP_QSTR_SPI, MP_TYPE_FLAG_NONE, make_new, machine_hard_spi_make_new, diff --git a/ports/stm32/modmachine.c b/ports/stm32/modmachine.c index e1796d1cf025..06fdd0e2c1bd 100644 --- a/ports/stm32/modmachine.c +++ b/ports/stm32/modmachine.c @@ -424,14 +424,14 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, #if MICROPY_PY_MACHINE_I2C #if MICROPY_HW_ENABLE_HW_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, #else { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_SPI - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, #endif #if MICROPY_HW_ENABLE_I2S diff --git a/ports/stm32/modmachine.h b/ports/stm32/modmachine.h index 9fa7851582e1..0c776280b416 100644 --- a/ports/stm32/modmachine.h +++ b/ports/stm32/modmachine.h @@ -30,8 +30,10 @@ extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_timer_type; -extern const mp_obj_type_t machine_hard_i2c_type; +extern const mp_obj_type_t machine_i2c_type; extern const mp_obj_type_t machine_i2s_type; +extern const mp_obj_type_t machine_spi_type; +extern const mp_obj_type_t machine_timer_type; void machine_init(void); void machine_deinit(void); diff --git a/ports/stm32/spi.c b/ports/stm32/spi.c index 9c031cad2e28..e21ebcd02b9d 100644 --- a/ports/stm32/spi.c +++ b/ports/stm32/spi.c @@ -678,7 +678,7 @@ const spi_t *spi_from_mp_obj(mp_obj_t o) { if (mp_obj_is_type(o, &pyb_spi_type)) { pyb_spi_obj_t *self = MP_OBJ_TO_PTR(o); return self->spi; - } else if (mp_obj_is_type(o, &machine_hard_spi_type)) { + } else if (mp_obj_is_type(o, &machine_spi_type)) { machine_hard_spi_obj_t *self = MP_OBJ_TO_PTR(o); return self->spi; } else { @@ -687,7 +687,7 @@ const spi_t *spi_from_mp_obj(mp_obj_t o) { } mp_obj_base_t *mp_hal_get_spi_obj(mp_obj_t o) { - if (mp_obj_is_type(o, &machine_hard_spi_type)) { + if (mp_obj_is_type(o, &machine_spi_type)) { return MP_OBJ_TO_PTR(o); } #if MICROPY_PY_MACHINE_SOFTSPI diff --git a/ports/stm32/spi.h b/ports/stm32/spi.h index 17f1bf6c4ab9..ef3d718cddfd 100644 --- a/ports/stm32/spi.h +++ b/ports/stm32/spi.h @@ -65,7 +65,7 @@ extern const spi_t spi_obj[6]; extern const mp_spi_proto_t spi_proto; extern const mp_obj_type_t pyb_spi_type; -extern const mp_obj_type_t machine_hard_spi_type; +extern const mp_obj_type_t machine_spi_type; // A transfer of "len" bytes should take len*8*1000/baudrate milliseconds. // To simplify the calculation we assume the baudrate is never less than 8kHz From 427670c21047585fb9295ddf35cdbdaa8265fbe1 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 20:20:03 +0200 Subject: [PATCH 004/823] esp32: Rename machine I2C and SPI types consistently across ports. This renames: - machine_hw_i2c_type -> machine_i2c_type - machine_hw_spi_type -> machine_spi_type --- ports/esp32/machine_hw_spi.c | 4 ++-- ports/esp32/machine_i2c.c | 4 ++-- ports/esp32/modmachine.c | 4 ++-- ports/esp32/modmachine.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ports/esp32/machine_hw_spi.c b/ports/esp32/machine_hw_spi.c index 647874e17f64..35ecc397b339 100644 --- a/ports/esp32/machine_hw_spi.c +++ b/ports/esp32/machine_hw_spi.c @@ -490,7 +490,7 @@ mp_obj_t machine_hw_spi_make_new(const mp_obj_type_t *type, size_t n_args, size_ self = &machine_hw_spi_obj[1]; default_pins = &machine_hw_spi_default_pins[1]; } - self->base.type = &machine_hw_spi_type; + self->base.type = &machine_spi_type; int8_t sck, mosi, miso; @@ -540,7 +540,7 @@ STATIC const mp_machine_spi_p_t machine_hw_spi_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hw_spi_type, + machine_spi_type, MP_QSTR_SPI, MP_TYPE_FLAG_NONE, make_new, machine_hw_spi_make_new, diff --git a/ports/esp32/machine_i2c.c b/ports/esp32/machine_i2c.c index 9244343dcf61..17e98ffc5bb4 100644 --- a/ports/esp32/machine_i2c.c +++ b/ports/esp32/machine_i2c.c @@ -161,7 +161,7 @@ mp_obj_t machine_hw_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_ bool first_init = false; if (self->base.type == NULL) { // Created for the first time, set default pins - self->base.type = &machine_hw_i2c_type; + self->base.type = &machine_i2c_type; self->port = i2c_id; if (self->port == I2C_NUM_0) { self->scl = MICROPY_HW_I2C0_SCL; @@ -193,7 +193,7 @@ STATIC const mp_machine_i2c_p_t machine_hw_i2c_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hw_i2c_type, + machine_i2c_type, MP_QSTR_I2C, MP_TYPE_FLAG_NONE, make_new, machine_hw_i2c_make_new, diff --git a/ports/esp32/modmachine.c b/ports/esp32/modmachine.c index a70f2fbedb59..e24afd040350 100644 --- a/ports/esp32/modmachine.c +++ b/ports/esp32/modmachine.c @@ -315,14 +315,14 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { #if MICROPY_PY_MACHINE_DAC { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, #endif - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #if MICROPY_PY_MACHINE_I2S { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) }, #endif { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hw_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, diff --git a/ports/esp32/modmachine.h b/ports/esp32/modmachine.h index 4d2ab9020d16..138a89e9c33d 100644 --- a/ports/esp32/modmachine.h +++ b/ports/esp32/modmachine.h @@ -16,8 +16,8 @@ extern const mp_obj_type_t machine_touchpad_type; extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_adcblock_type; extern const mp_obj_type_t machine_dac_type; -extern const mp_obj_type_t machine_hw_i2c_type; -extern const mp_obj_type_t machine_hw_spi_type; +extern const mp_obj_type_t machine_i2c_type; +extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_i2s_type; extern const mp_obj_type_t machine_uart_type; extern const mp_obj_type_t machine_rtc_type; From 22ad45fda649b754916883f246139e35ff20e126 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 20:21:51 +0200 Subject: [PATCH 005/823] rp2: Rename machine I2C type consistently across ports. This renames: - machine_hw_i2c_type -> machine_i2c_type --- ports/rp2/machine_i2c.c | 6 +++--- ports/rp2/modmachine.c | 2 +- ports/rp2/modmachine.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/rp2/machine_i2c.c b/ports/rp2/machine_i2c.c index 00dc36a4e0cb..85d12c771368 100644 --- a/ports/rp2/machine_i2c.c +++ b/ports/rp2/machine_i2c.c @@ -68,8 +68,8 @@ typedef struct _machine_i2c_obj_t { } machine_i2c_obj_t; STATIC machine_i2c_obj_t machine_i2c_obj[] = { - {{&machine_hw_i2c_type}, i2c0, 0, MICROPY_HW_I2C0_SCL, MICROPY_HW_I2C0_SDA, 0}, - {{&machine_hw_i2c_type}, i2c1, 1, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA, 0}, + {{&machine_i2c_type}, i2c0, 0, MICROPY_HW_I2C0_SCL, MICROPY_HW_I2C0_SDA, 0}, + {{&machine_i2c_type}, i2c1, 1, MICROPY_HW_I2C1_SCL, MICROPY_HW_I2C1_SDA, 0}, }; STATIC void machine_i2c_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { @@ -177,7 +177,7 @@ STATIC const mp_machine_i2c_p_t machine_i2c_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hw_i2c_type, + machine_i2c_type, MP_QSTR_I2C, MP_TYPE_FLAG_NONE, make_new, machine_i2c_make_new, diff --git a/ports/rp2/modmachine.c b/ports/rp2/modmachine.c index 3c8922c41721..8058189a92b6 100644 --- a/ports/rp2/modmachine.c +++ b/ports/rp2/modmachine.c @@ -239,7 +239,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_I2S), MP_ROM_PTR(&machine_i2s_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, diff --git a/ports/rp2/modmachine.h b/ports/rp2/modmachine.h index 503c1ca86c8a..6ea4def91d97 100644 --- a/ports/rp2/modmachine.h +++ b/ports/rp2/modmachine.h @@ -4,7 +4,7 @@ #include "py/obj.h" extern const mp_obj_type_t machine_adc_type; -extern const mp_obj_type_t machine_hw_i2c_type; +extern const mp_obj_type_t machine_i2c_type; extern const mp_obj_type_t machine_i2s_type; extern const mp_obj_type_t machine_pin_type; extern const mp_obj_type_t machine_rtc_type; From e275a58ac12c72d8d8e99f8c558f6e4a1f6637c1 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 20:22:57 +0200 Subject: [PATCH 006/823] samd: Rename machine I2C type consistently across ports. This renames: - machine_hw_i2c_type -> machine_i2c_type --- ports/samd/machine_i2c.c | 4 ++-- ports/samd/modmachine.c | 2 +- ports/samd/modmachine.h | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index 25dfa99340ff..0a8f5b94db08 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -144,7 +144,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n } // Get the peripheral object. - machine_i2c_obj_t *self = mp_obj_malloc(machine_i2c_obj_t, &machine_hw_i2c_type); + machine_i2c_obj_t *self = mp_obj_malloc(machine_i2c_obj_t, &machine_i2c_type); self->id = id; self->instance = sercom_instance[self->id]; @@ -260,7 +260,7 @@ STATIC const mp_machine_i2c_p_t machine_i2c_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hw_i2c_type, + machine_i2c_type, MP_QSTR_I2C, MP_TYPE_FLAG_NONE, make_new, machine_i2c_make_new, diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index f6cf7f8155c9..b9de026ed425 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -157,7 +157,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hw_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, diff --git a/ports/samd/modmachine.h b/ports/samd/modmachine.h index 6a745da0675a..e99ca990fbba 100644 --- a/ports/samd/modmachine.h +++ b/ports/samd/modmachine.h @@ -30,7 +30,7 @@ extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_dac_type; -extern const mp_obj_type_t machine_hw_i2c_type; +extern const mp_obj_type_t machine_i2c_type; extern const mp_obj_type_t machine_led_type; extern const mp_obj_type_t machine_pin_type; extern const mp_obj_type_t machine_pwm_type; From bfa6f34404374bb7c7d892ca6cb32175e8dd19f6 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 20:25:34 +0200 Subject: [PATCH 007/823] renesas-ra: Rename machine SPI type consistently across ports. This renames: - machine_hard_spi_type -> machine_spi_type --- ports/renesas-ra/machine_spi.c | 6 +++--- ports/renesas-ra/modmachine.c | 2 +- ports/renesas-ra/modmachine.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/renesas-ra/machine_spi.c b/ports/renesas-ra/machine_spi.c index b9f5b1ad1b4e..2c71c83b666c 100644 --- a/ports/renesas-ra/machine_spi.c +++ b/ports/renesas-ra/machine_spi.c @@ -70,7 +70,7 @@ typedef struct _machine_hard_spi_obj_t { STATIC machine_hard_spi_obj_t machine_hard_spi_obj[] = { #if defined(MICROPY_HW_SPI0_RSPCK) { - {&machine_hard_spi_type}, 0, + {&machine_spi_type}, 0, DEFAULT_SPI_POLARITY, DEFAULT_SPI_PHASE, DEFAULT_SPI_BITS, DEFAULT_SPI_FIRSTBIT, DEFAULT_SPI_BAUDRATE, MICROPY_HW_SPI0_RSPCK, MICROPY_HW_SPI0_MOSI, MICROPY_HW_SPI0_MISO, @@ -78,7 +78,7 @@ STATIC machine_hard_spi_obj_t machine_hard_spi_obj[] = { #endif #if defined(MICROPY_HW_SPI1_RSPCK) { - {&machine_hard_spi_type}, 1, + {&machine_spi_type}, 1, DEFAULT_SPI_POLARITY, DEFAULT_SPI_PHASE, DEFAULT_SPI_BITS, DEFAULT_SPI_FIRSTBIT, DEFAULT_SPI_BAUDRATE, MICROPY_HW_SPI1_RSPCK, MICROPY_HW_SPI1_MOSI, MICROPY_HW_SPI1_MISO, @@ -298,7 +298,7 @@ STATIC const mp_machine_spi_p_t machine_hard_spi_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_spi_type, + machine_spi_type, MP_QSTR_SPI, MP_TYPE_FLAG_NONE, make_new, machine_hard_spi_make_new, diff --git a/ports/renesas-ra/modmachine.c b/ports/renesas-ra/modmachine.c index e6f78747fdba..7db36298db4a 100644 --- a/ports/renesas-ra/modmachine.c +++ b/ports/renesas-ra/modmachine.c @@ -286,7 +286,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_SPI - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, { MP_ROM_QSTR(MP_QSTR_SoftSPI), MP_ROM_PTR(&mp_machine_soft_spi_type) }, #endif { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, diff --git a/ports/renesas-ra/modmachine.h b/ports/renesas-ra/modmachine.h index de421c5db81f..5dbda6bda8ae 100644 --- a/ports/renesas-ra/modmachine.h +++ b/ports/renesas-ra/modmachine.h @@ -37,7 +37,7 @@ extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_adcblock_type; extern const mp_obj_type_t machine_dac_type; extern const mp_obj_type_t machine_i2c_type; -extern const mp_obj_type_t machine_hard_spi_type; +extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_i2s_type; extern const mp_obj_type_t machine_uart_type; extern const mp_obj_type_t machine_rtc_type; From 5e90ec233139e90b5dc022932b15b3bc9fd7865c Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 20:28:31 +0200 Subject: [PATCH 008/823] zephyr: Rename machine I2C and SPI types consistently across ports. This renames: - machine_hard_i2c_type -> machine_i2c_type - machine_hard_spi_type -> machine_spi_type --- ports/zephyr/machine_i2c.c | 4 ++-- ports/zephyr/machine_spi.c | 4 ++-- ports/zephyr/modmachine.c | 4 ++-- ports/zephyr/modmachine.h | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) diff --git a/ports/zephyr/machine_i2c.c b/ports/zephyr/machine_i2c.c index 8f0f25257ba5..c261ffad0d25 100644 --- a/ports/zephyr/machine_i2c.c +++ b/ports/zephyr/machine_i2c.c @@ -84,7 +84,7 @@ mp_obj_t machine_hard_i2c_make_new(const mp_obj_type_t *type, size_t n_args, siz mp_raise_NotImplementedError(MP_ERROR_TEXT("explicit choice of timeout is not implemented")); } - machine_hard_i2c_obj_t *self = mp_obj_malloc(machine_hard_i2c_obj_t, &machine_hard_i2c_type); + machine_hard_i2c_obj_t *self = mp_obj_malloc(machine_hard_i2c_obj_t, &machine_i2c_type); self->dev = dev; self->restart = false; @@ -127,7 +127,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_i2c_type, + machine_i2c_type, MP_QSTR_I2C, MP_TYPE_FLAG_NONE, make_new, machine_hard_i2c_make_new, diff --git a/ports/zephyr/machine_spi.c b/ports/zephyr/machine_spi.c index 19d4ae6dee68..507d839c6644 100644 --- a/ports/zephyr/machine_spi.c +++ b/ports/zephyr/machine_spi.c @@ -106,7 +106,7 @@ mp_obj_t machine_hard_spi_make_new(const mp_obj_type_t *type, size_t n_args, siz .cs = NULL }; - machine_hard_spi_obj_t *self = mp_obj_malloc(machine_hard_spi_obj_t, &machine_hard_spi_type); + machine_hard_spi_obj_t *self = mp_obj_malloc(machine_hard_spi_obj_t, &machine_spi_type); self->dev = dev; self->config = cfg; @@ -198,7 +198,7 @@ STATIC const mp_machine_spi_p_t machine_hard_spi_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_spi_type, + machine_spi_type, MP_QSTR_SPI, MP_TYPE_FLAG_NONE, make_new, machine_hard_spi_make_new, diff --git a/ports/zephyr/modmachine.c b/ports/zephyr/modmachine.c index 63bd3369897d..95a66c51199b 100644 --- a/ports/zephyr/modmachine.c +++ b/ports/zephyr/modmachine.c @@ -69,10 +69,10 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, #endif #if MICROPY_PY_MACHINE_SPI - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, #endif { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, diff --git a/ports/zephyr/modmachine.h b/ports/zephyr/modmachine.h index a3cdd2b30fe6..a605ada0de76 100644 --- a/ports/zephyr/modmachine.h +++ b/ports/zephyr/modmachine.h @@ -4,8 +4,8 @@ #include "py/obj.h" extern const mp_obj_type_t machine_pin_type; -extern const mp_obj_type_t machine_hard_i2c_type; -extern const mp_obj_type_t machine_hard_spi_type; +extern const mp_obj_type_t machine_i2c_type; +extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_uart_type; MP_DECLARE_CONST_FUN_OBJ_0(machine_info_obj); From 93f3910fe7e43de5661ff0d6f961c6eccb8537e5 Mon Sep 17 00:00:00 2001 From: iabdalkader Date: Thu, 6 Oct 2022 20:27:09 +0200 Subject: [PATCH 009/823] nrf: Rename machine I2C, PWM, SPI, UART types consistently across ports. This renames: - machine_hard_i2c_type -> machine_i2c_type - machine_hard_pwm_type -> machine_pwm_type - machine_hard_spi_type -> machine_spi_type - machine_hard_uart_type -> machine_uart_type --- ports/nrf/main.c | 2 +- ports/nrf/modules/machine/i2c.c | 6 +++--- ports/nrf/modules/machine/i2c.h | 2 +- ports/nrf/modules/machine/modmachine.c | 8 ++++---- ports/nrf/modules/machine/pwm.c | 16 ++++++++-------- ports/nrf/modules/machine/pwm.h | 2 +- ports/nrf/modules/machine/spi.c | 14 +++++++------- ports/nrf/modules/machine/spi.h | 2 +- ports/nrf/modules/machine/uart.c | 4 ++-- ports/nrf/modules/machine/uart.h | 2 +- ports/nrf/modules/uos/moduos.c | 2 +- 11 files changed, 30 insertions(+), 30 deletions(-) diff --git a/ports/nrf/main.c b/ports/nrf/main.c index bcfaafd39c7e..989db58b3fa8 100644 --- a/ports/nrf/main.c +++ b/ports/nrf/main.c @@ -170,7 +170,7 @@ int main(int argc, char **argv) { MP_OBJ_NEW_SMALL_INT(0), MP_OBJ_NEW_SMALL_INT(115200), }; - MP_STATE_PORT(board_stdio_uart) = MP_OBJ_TYPE_GET_SLOT(&machine_hard_uart_type, make_new)((mp_obj_t)&machine_hard_uart_type, MP_ARRAY_SIZE(args), 0, args); + MP_STATE_PORT(board_stdio_uart) = MP_OBJ_TYPE_GET_SLOT(&machine_uart_type, make_new)((mp_obj_t)&machine_uart_type, MP_ARRAY_SIZE(args), 0, args); } #endif diff --git a/ports/nrf/modules/machine/i2c.c b/ports/nrf/modules/machine/i2c.c index c16c3669ec57..ac331b8d7af6 100644 --- a/ports/nrf/modules/machine/i2c.c +++ b/ports/nrf/modules/machine/i2c.c @@ -69,8 +69,8 @@ typedef struct _machine_hard_i2c_obj_t { } machine_hard_i2c_obj_t; STATIC const machine_hard_i2c_obj_t machine_hard_i2c_obj[] = { - {{&machine_hard_i2c_type}, .p_twi = NRFX_TWI_INSTANCE(0)}, - {{&machine_hard_i2c_type}, .p_twi = NRFX_TWI_INSTANCE(1)}, + {{&machine_i2c_type}, .p_twi = NRFX_TWI_INSTANCE(0)}, + {{&machine_i2c_type}, .p_twi = NRFX_TWI_INSTANCE(1)}, }; void i2c_init0(void) { @@ -162,7 +162,7 @@ STATIC const mp_machine_i2c_p_t machine_hard_i2c_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_i2c_type, + machine_i2c_type, MP_QSTR_I2C, MP_TYPE_FLAG_NONE, make_new, machine_hard_i2c_make_new, diff --git a/ports/nrf/modules/machine/i2c.h b/ports/nrf/modules/machine/i2c.h index 1dfb1f077a44..5c5befc28535 100644 --- a/ports/nrf/modules/machine/i2c.h +++ b/ports/nrf/modules/machine/i2c.h @@ -29,7 +29,7 @@ #include "extmod/machine_i2c.h" -extern const mp_obj_type_t machine_hard_i2c_type; +extern const mp_obj_type_t machine_i2c_type; void i2c_init0(void); diff --git a/ports/nrf/modules/machine/modmachine.c b/ports/nrf/modules/machine/modmachine.c index d322e450671f..d315582ae6fd 100644 --- a/ports/nrf/modules/machine/modmachine.c +++ b/ports/nrf/modules/machine/modmachine.c @@ -217,13 +217,13 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_mem32), MP_ROM_PTR(&machine_mem32_obj) }, #if MICROPY_PY_MACHINE_UART - { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_hard_uart_type) }, + { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, #endif #if MICROPY_PY_MACHINE_HW_SPI - { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_hard_spi_type) }, + { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&machine_spi_type) }, #endif #if MICROPY_PY_MACHINE_I2C - { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_hard_i2c_type) }, + { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, #endif #if MICROPY_PY_MACHINE_ADC @@ -236,7 +236,7 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, #endif #if MICROPY_PY_MACHINE_HW_PWM - { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_hard_pwm_type) }, + { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, #endif #if MICROPY_PY_MACHINE_TEMP { MP_ROM_QSTR(MP_QSTR_Temp), MP_ROM_PTR(&machine_temp_type) }, diff --git a/ports/nrf/modules/machine/pwm.c b/ports/nrf/modules/machine/pwm.c index 862e1907cbf3..7c84a8c885f9 100644 --- a/ports/nrf/modules/machine/pwm.c +++ b/ports/nrf/modules/machine/pwm.c @@ -77,11 +77,11 @@ STATIC machine_pwm_config_t hard_configs[MP_ARRAY_SIZE(machine_hard_pwm_instance STATIC const machine_hard_pwm_obj_t machine_hard_pwm_obj[] = { #if defined(NRF52_SERIES) - {{&machine_hard_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0]}, - {{&machine_hard_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1]}, - {{&machine_hard_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2]}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[0], .p_config = &hard_configs[0]}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[1], .p_config = &hard_configs[1]}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[2], .p_config = &hard_configs[2]}, #if NRF52840 - {{&machine_hard_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3]}, + {{&machine_pwm_type}, .p_pwm = &machine_hard_pwm_instances[3], .p_config = &hard_configs[3]}, #endif #endif }; @@ -155,7 +155,7 @@ STATIC mp_obj_t machine_pwm_init(size_t n_args, const mp_obj_t *pos_args, mp_map mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // dispatch to specific implementation - if (mp_obj_get_type(self) == &machine_hard_pwm_type) { + if (mp_obj_get_type(self) == &machine_pwm_type) { machine_hard_pwm_init(self, args); } @@ -165,7 +165,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_pwm_init_obj, 1, machine_pwm_init); STATIC mp_obj_t machine_pwm_deinit(mp_obj_t self) { // dispatch to specific implementation - if (mp_obj_get_type(self) == &machine_hard_pwm_type) { + if (mp_obj_get_type(self) == &machine_pwm_type) { machine_hard_pwm_deinit(self); } return mp_const_none; @@ -182,7 +182,7 @@ STATIC mp_obj_t machine_pwm_freq(size_t n_args, const mp_obj_t *pos_args, mp_map mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); - if (mp_obj_get_type(self) == &machine_hard_pwm_type) { + if (mp_obj_get_type(self) == &machine_pwm_type) { machine_hard_pwm_freq(self, args); } else { // soft pwm @@ -340,7 +340,7 @@ STATIC mp_obj_t machine_hard_pwm_freq(mp_obj_t self_in, mp_arg_val_t *args) { } MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_pwm_type, + machine_pwm_type, MP_QSTR_PWM, MP_TYPE_FLAG_NONE, make_new, machine_pwm_make_new, diff --git a/ports/nrf/modules/machine/pwm.h b/ports/nrf/modules/machine/pwm.h index 7a5b72e0e84f..ab2d927fa4e5 100644 --- a/ports/nrf/modules/machine/pwm.h +++ b/ports/nrf/modules/machine/pwm.h @@ -26,4 +26,4 @@ void pwm_init0(void); -extern const mp_obj_type_t machine_hard_pwm_type; +extern const mp_obj_type_t machine_pwm_type; diff --git a/ports/nrf/modules/machine/spi.c b/ports/nrf/modules/machine/spi.c index fd7f7bb5bcce..0dee20027b2d 100644 --- a/ports/nrf/modules/machine/spi.c +++ b/ports/nrf/modules/machine/spi.c @@ -117,12 +117,12 @@ STATIC const nrfx_spi_t machine_spi_instances[] = { STATIC nrfx_spi_config_t configs[MP_ARRAY_SIZE(machine_spi_instances)]; STATIC const machine_hard_spi_obj_t machine_hard_spi_obj[] = { - {{&machine_hard_spi_type}, .p_spi = &machine_spi_instances[0], .p_config = &configs[0]}, - {{&machine_hard_spi_type}, .p_spi = &machine_spi_instances[1], .p_config = &configs[1]}, + {{&machine_spi_type}, .p_spi = &machine_spi_instances[0], .p_config = &configs[0]}, + {{&machine_spi_type}, .p_spi = &machine_spi_instances[1], .p_config = &configs[1]}, #if defined(NRF52_SERIES) - {{&machine_hard_spi_type}, .p_spi = &machine_spi_instances[2], .p_config = &configs[2]}, + {{&machine_spi_type}, .p_spi = &machine_spi_instances[2], .p_config = &configs[2]}, #if defined(NRF52840_XXAA) && NRFX_SPIM_ENABLED - {{&machine_hard_spi_type}, .p_spi = &machine_spi_instances[3], .p_config = &configs[3]}, + {{&machine_spi_type}, .p_spi = &machine_spi_instances[3], .p_config = &configs[3]}, #endif // NRF52840_XXAA && NRFX_SPIM_ENABLED #endif // NRF52_SERIES }; @@ -235,7 +235,7 @@ STATIC mp_obj_t machine_spi_init(size_t n_args, const mp_obj_t *pos_args, mp_map mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // dispatch to specific implementation - if (mp_obj_get_type(self) == &machine_hard_spi_type) { + if (mp_obj_get_type(self) == &machine_spi_type) { machine_hard_spi_init(self, args); } @@ -245,7 +245,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_KW(machine_spi_init_obj, 1, machine_spi_init); STATIC mp_obj_t machine_spi_deinit(mp_obj_t self) { // dispatch to specific implementation - if (mp_obj_get_type(self) == &machine_hard_spi_type) { + if (mp_obj_get_type(self) == &machine_spi_type) { machine_hard_spi_deinit(self); } return mp_const_none; @@ -428,7 +428,7 @@ STATIC const mp_machine_spi_p_t machine_hard_spi_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_spi_type, + machine_spi_type, MP_QSTR_SPI, MP_TYPE_FLAG_NONE, make_new, machine_spi_make_new, diff --git a/ports/nrf/modules/machine/spi.h b/ports/nrf/modules/machine/spi.h index c6f64a19da2a..e1505781ac38 100644 --- a/ports/nrf/modules/machine/spi.h +++ b/ports/nrf/modules/machine/spi.h @@ -27,7 +27,7 @@ #include "py/obj.h" typedef struct _machine_hard_spi_obj_t machine_hard_spi_obj_t; -extern const mp_obj_type_t machine_hard_spi_type; +extern const mp_obj_type_t machine_spi_type; void spi_init0(void); void spi_transfer(const machine_hard_spi_obj_t * self, diff --git a/ports/nrf/modules/machine/uart.c b/ports/nrf/modules/machine/uart.c index 035a30df9c01..eee5b8b79eac 100644 --- a/ports/nrf/modules/machine/uart.c +++ b/ports/nrf/modules/machine/uart.c @@ -106,7 +106,7 @@ static const nrfx_uart_t instance0 = NRFX_UART_INSTANCE(0); STATIC machine_hard_uart_buf_t machine_hard_uart_buf[1]; STATIC const machine_hard_uart_obj_t machine_hard_uart_obj[] = { - {{&machine_hard_uart_type}, .p_uart = &instance0, .buf = &machine_hard_uart_buf[0]}, + {{&machine_uart_type}, .p_uart = &instance0, .buf = &machine_hard_uart_buf[0]}, }; void uart_init0(void) { @@ -371,7 +371,7 @@ STATIC const mp_stream_p_t uart_stream_p = { }; MP_DEFINE_CONST_OBJ_TYPE( - machine_hard_uart_type, + machine_uart_type, MP_QSTR_UART, MP_TYPE_FLAG_ITER_IS_STREAM, make_new, machine_hard_uart_make_new, diff --git a/ports/nrf/modules/machine/uart.h b/ports/nrf/modules/machine/uart.h index 121f83cd39b3..d3e23645b902 100644 --- a/ports/nrf/modules/machine/uart.h +++ b/ports/nrf/modules/machine/uart.h @@ -32,7 +32,7 @@ #include "genhdr/pins.h" typedef struct _machine_hard_uart_obj_t machine_hard_uart_obj_t; -extern const mp_obj_type_t machine_hard_uart_type; +extern const mp_obj_type_t machine_uart_type; void uart_init0(void); void uart_deinit(void); diff --git a/ports/nrf/modules/uos/moduos.c b/ports/nrf/modules/uos/moduos.c index 3fdd8eb19ec1..402096aacd78 100644 --- a/ports/nrf/modules/uos/moduos.c +++ b/ports/nrf/modules/uos/moduos.c @@ -126,7 +126,7 @@ STATIC mp_obj_t os_dupterm(mp_uint_t n_args, const mp_obj_t *args) { } else { if (args[0] == mp_const_none) { MP_STATE_PORT(board_stdio_uart) = NULL; - } else if (mp_obj_get_type(args[0]) == &machine_hard_uart_type) { + } else if (mp_obj_get_type(args[0]) == &machine_uart_type) { MP_STATE_PORT(board_stdio_uart) = args[0]; } else { mp_raise_ValueError(MP_ERROR_TEXT("need a UART object")); From 4f946ba963b452cb79524225e0a88134921f2ebb Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Sep 2022 12:51:10 +1000 Subject: [PATCH 010/823] lib/btstack: Update to v1.5.3. Signed-off-by: Damien George --- lib/btstack | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/btstack b/lib/btstack index c8b9823f68c6..87087689bb0c 160000 --- a/lib/btstack +++ b/lib/btstack @@ -1 +1 @@ -Subproject commit c8b9823f68c6af0fa52e2c4e009aba4dbf257232 +Subproject commit 87087689bb0c37ff4b9b3e3ba670b7019c306ebd From 67f98ba10c3d894e737f275f0a508b7ccf4f1807 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 8 Sep 2022 12:51:19 +1000 Subject: [PATCH 011/823] extmod/btstack: Update BTstack bindings to work with latest BTstack. The following multi-tests pass (eg with PYBD_SF6+LEGO_HUB_NO6): ble_gap_advertise.py ble_gap_connect.py ble_gap_device_name.py ble_gattc_discover_services.py ble_gatt_data_transfer.py perf_gatt_char_write.py perf_gatt_notify.py stress_log_filesystem.py These are the same tests that passed prior to this BTstack update. Also tested on the unix port using H4 transport. Signed-off-by: Damien George --- extmod/btstack/btstack_config.h | 2 +- extmod/btstack/btstack_hci_uart.c | 6 ++++++ extmod/btstack/btstack_hci_uart.h | 2 +- extmod/btstack/modbluetooth_btstack.c | 28 ++++++++++++++++++++++----- ports/stm32/mpbtstackport.c | 11 +++-------- ports/unix/mpbtstackport_common.c | 8 +------- ports/unix/mpbtstackport_h4.c | 14 ++++++++------ ports/unix/mpbtstackport_usb.c | 1 + 8 files changed, 44 insertions(+), 28 deletions(-) diff --git a/extmod/btstack/btstack_config.h b/extmod/btstack/btstack_config.h index e56a84f94a16..7de938cb6f7e 100644 --- a/extmod/btstack/btstack_config.h +++ b/extmod/btstack/btstack_config.h @@ -6,7 +6,7 @@ #define ENABLE_LE_PERIPHERAL #define ENABLE_LE_CENTRAL // #define ENABLE_CLASSIC -#define ENABLE_LE_DATA_CHANNELS +#define ENABLE_L2CAP_LE_CREDIT_BASED_FLOW_CONTROL_MODE // #define ENABLE_LOG_INFO // #define ENABLE_LOG_DEBUG #define ENABLE_LOG_ERROR diff --git a/extmod/btstack/btstack_hci_uart.c b/extmod/btstack/btstack_hci_uart.c index 83e865b71d2d..f945efc762cb 100644 --- a/extmod/btstack/btstack_hci_uart.c +++ b/extmod/btstack/btstack_hci_uart.c @@ -159,6 +159,12 @@ const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block = { &btstack_uart_get_supported_sleep_modes, &btstack_uart_set_sleep, &btstack_uart_set_wakeup_handler, + + // The following are needed for H5 mode only. + NULL, // set_frame_received + NULL, // set_frame_sent, + NULL, // receive_frame, + NULL, // send_frame, }; void mp_bluetooth_btstack_hci_uart_process(void) { diff --git a/extmod/btstack/btstack_hci_uart.h b/extmod/btstack/btstack_hci_uart.h index 8011e587dee3..74983808ec28 100644 --- a/extmod/btstack/btstack_hci_uart.h +++ b/extmod/btstack/btstack_hci_uart.h @@ -28,7 +28,7 @@ #ifndef MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H #define MICROPY_INCLUDED_EXTMOD_BTSTACK_HCI_UART_H -#include "lib/btstack/src/btstack.h" +#include "lib/btstack/src/btstack_uart_block.h" // --- Used by the port to create the HCI transport --------------------------- extern const btstack_uart_block_t mp_bluetooth_btstack_hci_uart_block; diff --git a/extmod/btstack/modbluetooth_btstack.c b/extmod/btstack/modbluetooth_btstack.c index b58be78a9942..e9c0037394b2 100644 --- a/extmod/btstack/modbluetooth_btstack.c +++ b/extmod/btstack/modbluetooth_btstack.c @@ -368,7 +368,7 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t event_type == SM_EVENT_PAIRING_COMPLETE || // event_type == GAP_EVENT_DEDICATED_BONDING_COMPLETED || // No conn_handle event_type == HCI_EVENT_ENCRYPTION_CHANGE) { - DEBUG_printf(" --> enc/auth/pair/bond change\n", ); + DEBUG_printf(" --> enc/auth/pair/bond change\n"); #if MICROPY_PY_BLUETOOTH_ENABLE_PAIRING_BONDING uint16_t conn_handle; switch (event_type) { @@ -420,6 +420,11 @@ STATIC void btstack_packet_handler(uint8_t packet_type, uint8_t *packet, uint8_t mp_bluetooth_gap_on_scan_result(address_type, address, adv_event_type, rssi, data, length); #endif // MICROPY_PY_BLUETOOTH_ENABLE_CENTRAL_MODE #if MICROPY_PY_BLUETOOTH_ENABLE_GATT_CLIENT + } else if (event_type == GATT_EVENT_MTU) { + // This is triggered in client mode. + uint16_t conn_handle = gatt_event_mtu_get_handle(packet); + uint16_t mtu = gatt_event_mtu_get_MTU(packet); + mp_bluetooth_gatts_on_mtu_exchanged(conn_handle, mtu); } else if (event_type == GATT_EVENT_QUERY_COMPLETE) { uint16_t conn_handle = gatt_event_query_complete_get_handle(packet); uint16_t status = gatt_event_query_complete_get_att_status(packet); @@ -625,6 +630,19 @@ STATIC void set_random_address(void) { DEBUG_printf("set_random_address: Address loaded by controller\n"); } +STATIC void deinit_stack(void) { + mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; + + // Deinitialise BTstack components. + sm_deinit(); + l2cap_deinit(); + hci_deinit(); + btstack_memory_deinit(); + btstack_run_loop_deinit(); + + MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; +} + int mp_bluetooth_init(void) { DEBUG_printf("mp_bluetooth_init\n"); @@ -702,8 +720,8 @@ int mp_bluetooth_init(void) { // Attempt a shutdown (may not do anything). mp_bluetooth_btstack_port_deinit(); - // Clean up. - MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; + // Clean up BTstack. + deinit_stack(); return timeout ? MP_ETIMEDOUT : MP_EINVAL; } @@ -757,8 +775,8 @@ void mp_bluetooth_deinit(void) { } btstack_run_loop_remove_timer(&btstack_init_deinit_timeout); - mp_bluetooth_btstack_state = MP_BLUETOOTH_BTSTACK_STATE_OFF; - MP_STATE_PORT(bluetooth_btstack_root_pointers) = NULL; + // Clean up BTstack. + deinit_stack(); DEBUG_printf("mp_bluetooth_deinit: complete\n"); } diff --git a/ports/stm32/mpbtstackport.c b/ports/stm32/mpbtstackport.c index 795534042eae..301ac30e2060 100644 --- a/ports/stm32/mpbtstackport.c +++ b/ports/stm32/mpbtstackport.c @@ -31,6 +31,7 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK #include "lib/btstack/src/btstack.h" +#include "lib/btstack/src/hci_transport_h4.h" #include "extmod/mpbthci.h" #include "extmod/btstack/btstack_hci_uart.h" #include "extmod/btstack/modbluetooth_btstack.h" @@ -137,16 +138,10 @@ void mp_bluetooth_hci_poll(void) { } void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(&mp_btstack_runloop_stm32); - } else { - mp_btstack_runloop_stm32.init(); - } + btstack_run_loop_init(&mp_btstack_runloop_stm32); // hci_dump_open(NULL, HCI_DUMP_STDOUT); - const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + const hci_transport_t *transport = hci_transport_h4_instance_for_uart(&mp_bluetooth_btstack_hci_uart_block); hci_init(transport, &hci_transport_config_uart); #ifdef MICROPY_HW_BLE_BTSTACK_CHIPSET_INSTANCE diff --git a/ports/unix/mpbtstackport_common.c b/ports/unix/mpbtstackport_common.c index ec40db65bc3a..66a3a0536aed 100644 --- a/ports/unix/mpbtstackport_common.c +++ b/ports/unix/mpbtstackport_common.c @@ -79,13 +79,7 @@ uint32_t hal_time_ms(void) { } void mp_bluetooth_btstack_port_init(void) { - static bool run_loop_init = false; - if (!run_loop_init) { - run_loop_init = true; - btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); - } else { - btstack_run_loop_embedded_get_instance()->init(); - } + btstack_run_loop_init(btstack_run_loop_embedded_get_instance()); // hci_dump_open(NULL, HCI_DUMP_STDOUT); diff --git a/ports/unix/mpbtstackport_h4.c b/ports/unix/mpbtstackport_h4.c index 4fdc20c22b69..dacfff9a498a 100644 --- a/ports/unix/mpbtstackport_h4.c +++ b/ports/unix/mpbtstackport_h4.c @@ -32,6 +32,7 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_H4 +#include "lib/btstack/src/hci_transport_h4.h" #include "lib/btstack/chipset/zephyr/btstack_chipset_zephyr.h" #include "extmod/btstack/btstack_hci_uart.h" @@ -42,11 +43,12 @@ #define DEBUG_printf(...) // printf(__VA_ARGS__) STATIC hci_transport_config_uart_t hci_transport_config_uart = { - HCI_TRANSPORT_CONFIG_UART, - 1000000, // initial baudrate - 0, // main baudrate - 1, // flow control - NULL, // device name + .type = HCI_TRANSPORT_CONFIG_UART, + .baudrate_init = 1000000, + .baudrate_main = 0, + .flowcontrol = 1, + .device_name = NULL, + .parity = BTSTACK_UART_PARITY_OFF, }; void mp_bluetooth_hci_poll_h4(void) { @@ -58,7 +60,7 @@ void mp_bluetooth_hci_poll_h4(void) { void mp_bluetooth_btstack_port_init_h4(void) { DEBUG_printf("mp_bluetooth_btstack_port_init_h4\n"); - const hci_transport_t *transport = hci_transport_h4_instance(&mp_bluetooth_btstack_hci_uart_block); + const hci_transport_t *transport = hci_transport_h4_instance_for_uart(&mp_bluetooth_btstack_hci_uart_block); hci_init(transport, &hci_transport_config_uart); hci_set_chipset(btstack_chipset_zephyr_instance()); diff --git a/ports/unix/mpbtstackport_usb.c b/ports/unix/mpbtstackport_usb.c index 28d2c8c543f7..b8c7b758d93e 100644 --- a/ports/unix/mpbtstackport_usb.c +++ b/ports/unix/mpbtstackport_usb.c @@ -34,6 +34,7 @@ #if MICROPY_PY_BLUETOOTH && MICROPY_BLUETOOTH_BTSTACK && MICROPY_BLUETOOTH_BTSTACK_USB #include "lib/btstack/src/btstack.h" +#include "lib/btstack/src/hci_transport_usb.h" #include "lib/btstack/platform/embedded/btstack_run_loop_embedded.h" #include "lib/btstack/platform/embedded/hal_cpu.h" #include "lib/btstack/platform/embedded/hal_time_ms.h" From b33767896430a36f163c0f9bb7020e688ccb9c5b Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 5 Oct 2022 10:46:11 +1100 Subject: [PATCH 012/823] extmod/mbedtls: Add common configuration file, and use it in all ports. This is a no-op change. Signed-off-by: Damien George --- extmod/mbedtls/mbedtls_config_common.h | 110 +++++++++++++++++++++++++ ports/mimxrt/mbedtls/mbedtls_config.h | 69 +--------------- ports/rp2/mbedtls/mbedtls_config.h | 73 ++-------------- ports/stm32/mbedtls/mbedtls_config.h | 69 +--------------- ports/unix/mbedtls/mbedtls_config.h | 58 +------------ 5 files changed, 125 insertions(+), 254 deletions(-) create mode 100644 extmod/mbedtls/mbedtls_config_common.h diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h new file mode 100644 index 000000000000..6c9385dea5f7 --- /dev/null +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -0,0 +1,110 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018-2022 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. + */ +#ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_COMMON_H +#define MICROPY_INCLUDED_MBEDTLS_CONFIG_COMMON_H + +// If you want to debug MBEDTLS uncomment the following and +// pass "3" to mbedtls_debug_set_threshold in socket_new. +// #define MBEDTLS_DEBUG_C + +// Set mbedtls configuration. +#define MBEDTLS_DEPRECATED_REMOVED +#define MBEDTLS_AES_ROM_TABLES +#define MBEDTLS_CIPHER_MODE_CBC +#define MBEDTLS_ECP_DP_SECP192R1_ENABLED +#define MBEDTLS_ECP_DP_SECP224R1_ENABLED +#define MBEDTLS_ECP_DP_SECP256R1_ENABLED +#define MBEDTLS_ECP_DP_SECP384R1_ENABLED +#define MBEDTLS_ECP_DP_SECP521R1_ENABLED +#define MBEDTLS_ECP_DP_SECP192K1_ENABLED +#define MBEDTLS_ECP_DP_SECP224K1_ENABLED +#define MBEDTLS_ECP_DP_SECP256K1_ENABLED +#define MBEDTLS_ECP_DP_BP256R1_ENABLED +#define MBEDTLS_ECP_DP_BP384R1_ENABLED +#define MBEDTLS_ECP_DP_BP512R1_ENABLED +#define MBEDTLS_ECP_DP_CURVE25519_ENABLED +#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +#define MBEDTLS_NO_PLATFORM_ENTROPY +#define MBEDTLS_PKCS1_V15 +#define MBEDTLS_SHA256_SMALLER +#define MBEDTLS_SSL_PROTO_TLS1 +#define MBEDTLS_SSL_PROTO_TLS1_1 +#define MBEDTLS_SSL_PROTO_TLS1_2 +#define MBEDTLS_SSL_SERVER_NAME_INDICATION + +// Use a smaller output buffer to reduce size of SSL context. +#define MBEDTLS_SSL_MAX_CONTENT_LEN (16384) +#define MBEDTLS_SSL_IN_CONTENT_LEN (MBEDTLS_SSL_MAX_CONTENT_LEN) +#define MBEDTLS_SSL_OUT_CONTENT_LEN (4096) + +// Enable mbedtls modules. +#define MBEDTLS_AES_C +#define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_BIGNUM_C +#define MBEDTLS_CIPHER_C +#define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ENTROPY_C +#define MBEDTLS_ERROR_C +#define MBEDTLS_MD_C +#define MBEDTLS_MD5_C +#define MBEDTLS_OID_C +#define MBEDTLS_PKCS5_C +#define MBEDTLS_PK_C +#define MBEDTLS_PK_PARSE_C +#define MBEDTLS_PLATFORM_C +#define MBEDTLS_RSA_C +#define MBEDTLS_SHA1_C +#define MBEDTLS_SHA256_C +#define MBEDTLS_SHA512_C +#define MBEDTLS_SSL_CLI_C +#define MBEDTLS_SSL_SRV_C +#define MBEDTLS_SSL_TLS_C +#define MBEDTLS_X509_CRT_PARSE_C +#define MBEDTLS_X509_USE_C + +// A port may enable this option to select additional bare-metal configuration. +#if MICROPY_MBEDTLS_CONFIG_BARE_METAL + +// Bare-metal mbedtls configuration. +#define MBEDTLS_PLATFORM_MEMORY +#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS +#define MBEDTLS_ENTROPY_HARDWARE_ALT + +// Bare-metal memory allocation hooks. +#include +#include +void *m_tracked_calloc(size_t nmemb, size_t size); +void m_tracked_free(void *ptr); +#define MBEDTLS_PLATFORM_STD_CALLOC m_tracked_calloc +#define MBEDTLS_PLATFORM_STD_FREE m_tracked_free +#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf + +#endif + +// Include mbedtls configuration checker. +#include "mbedtls/check_config.h" + +#endif // MICROPY_INCLUDED_MBEDTLS_CONFIG_COMMON_H diff --git a/ports/mimxrt/mbedtls/mbedtls_config.h b/ports/mimxrt/mbedtls/mbedtls_config.h index 8e054ed51713..cc71aa7ec113 100644 --- a/ports/mimxrt/mbedtls/mbedtls_config.h +++ b/ports/mimxrt/mbedtls/mbedtls_config.h @@ -26,74 +26,13 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_H -// Set mbedtls configuration -#define MBEDTLS_PLATFORM_MEMORY -#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS -#define MBEDTLS_DEPRECATED_REMOVED -#define MBEDTLS_ENTROPY_HARDWARE_ALT -#define MBEDTLS_AES_ROM_TABLES -#define MBEDTLS_CIPHER_MODE_CBC -#define MBEDTLS_ECP_DP_SECP192R1_ENABLED -#define MBEDTLS_ECP_DP_SECP224R1_ENABLED -#define MBEDTLS_ECP_DP_SECP256R1_ENABLED -#define MBEDTLS_ECP_DP_SECP384R1_ENABLED -#define MBEDTLS_ECP_DP_SECP521R1_ENABLED -#define MBEDTLS_ECP_DP_SECP192K1_ENABLED -#define MBEDTLS_ECP_DP_SECP224K1_ENABLED -#define MBEDTLS_ECP_DP_SECP256K1_ENABLED -#define MBEDTLS_ECP_DP_BP256R1_ENABLED -#define MBEDTLS_ECP_DP_BP384R1_ENABLED -#define MBEDTLS_ECP_DP_BP512R1_ENABLED -#define MBEDTLS_ECP_DP_CURVE25519_ENABLED -#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED -#define MBEDTLS_NO_PLATFORM_ENTROPY -#define MBEDTLS_PKCS1_V15 -#define MBEDTLS_SHA256_SMALLER -#define MBEDTLS_SSL_PROTO_TLS1 -#define MBEDTLS_SSL_PROTO_TLS1_1 -#define MBEDTLS_SSL_PROTO_TLS1_2 -#define MBEDTLS_SSL_SERVER_NAME_INDICATION - -// Use a smaller output buffer to reduce size of SSL context -#define MBEDTLS_SSL_MAX_CONTENT_LEN (16384) -#define MBEDTLS_SSL_IN_CONTENT_LEN (MBEDTLS_SSL_MAX_CONTENT_LEN) -#define MBEDTLS_SSL_OUT_CONTENT_LEN (4096) - // Enable mbedtls modules -#define MBEDTLS_AES_C -#define MBEDTLS_ASN1_PARSE_C -#define MBEDTLS_BIGNUM_C -#define MBEDTLS_CIPHER_C -#define MBEDTLS_CTR_DRBG_C // #define MBEDTLS_ECP_C -#define MBEDTLS_ENTROPY_C -#define MBEDTLS_ERROR_C -#define MBEDTLS_MD_C -#define MBEDTLS_MD5_C -#define MBEDTLS_OID_C -#define MBEDTLS_PKCS5_C -#define MBEDTLS_PK_C -#define MBEDTLS_PK_PARSE_C -#define MBEDTLS_PLATFORM_C -#define MBEDTLS_RSA_C -#define MBEDTLS_SHA1_C -#define MBEDTLS_SHA256_C -#define MBEDTLS_SHA512_C -#define MBEDTLS_SSL_CLI_C -#define MBEDTLS_SSL_SRV_C -#define MBEDTLS_SSL_TLS_C -#define MBEDTLS_X509_CRT_PARSE_C -#define MBEDTLS_X509_USE_C -// Memory allocation hooks -#include -#include -void *m_tracked_calloc(size_t nmemb, size_t size); -void m_tracked_free(void *ptr); -#define MBEDTLS_PLATFORM_STD_CALLOC m_tracked_calloc -#define MBEDTLS_PLATFORM_STD_FREE m_tracked_free -#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf +// Set MicroPython-specific options. +#define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) -#include "mbedtls/check_config.h" +// Include common mbedtls configuration. +#include "extmod/mbedtls/mbedtls_config_common.h" #endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ diff --git a/ports/rp2/mbedtls/mbedtls_config.h b/ports/rp2/mbedtls/mbedtls_config.h index c80aa5bc18c2..61f622295ffa 100644 --- a/ports/rp2/mbedtls/mbedtls_config.h +++ b/ports/rp2/mbedtls/mbedtls_config.h @@ -26,90 +26,27 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_H -// If you want to debug MBEDTLS uncomment the following and -// Pass 3 to mbedtls_debug_set_threshold in socket_new -// #define MBEDTLS_DEBUG_C - // Set mbedtls configuration -#define MBEDTLS_PLATFORM_MEMORY -#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS -#define MBEDTLS_DEPRECATED_REMOVED -#define MBEDTLS_ENTROPY_HARDWARE_ALT -#define MBEDTLS_AES_ROM_TABLES -#define MBEDTLS_CIPHER_MODE_CBC -#define MBEDTLS_ECP_DP_SECP192R1_ENABLED -#define MBEDTLS_ECP_DP_SECP224R1_ENABLED -#define MBEDTLS_ECP_DP_SECP256R1_ENABLED -#define MBEDTLS_ECP_DP_SECP384R1_ENABLED -#define MBEDTLS_ECP_DP_SECP521R1_ENABLED -#define MBEDTLS_ECP_DP_SECP192K1_ENABLED -#define MBEDTLS_ECP_DP_SECP224K1_ENABLED -#define MBEDTLS_ECP_DP_SECP256K1_ENABLED -#define MBEDTLS_ECP_DP_BP256R1_ENABLED -#define MBEDTLS_ECP_DP_BP384R1_ENABLED -#define MBEDTLS_ECP_DP_BP512R1_ENABLED -#define MBEDTLS_ECP_DP_CURVE25519_ENABLED #define MBEDTLS_ECP_NIST_OPTIM #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED -#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED -#define MBEDTLS_NO_PLATFORM_ENTROPY -#define MBEDTLS_PKCS1_V15 -#define MBEDTLS_SHA256_SMALLER -#define MBEDTLS_SSL_PROTO_TLS1 -#define MBEDTLS_SSL_PROTO_TLS1_1 -#define MBEDTLS_SSL_PROTO_TLS1_2 -#define MBEDTLS_SSL_SERVER_NAME_INDICATION - -// Use a smaller output buffer to reduce size of SSL context -#define MBEDTLS_SSL_MAX_CONTENT_LEN (16384) -#define MBEDTLS_SSL_IN_CONTENT_LEN (MBEDTLS_SSL_MAX_CONTENT_LEN) -#define MBEDTLS_SSL_OUT_CONTENT_LEN (4096) // Enable mbedtls modules -#define MBEDTLS_AES_C -#define MBEDTLS_ASN1_PARSE_C -#define MBEDTLS_BIGNUM_C -#define MBEDTLS_CIPHER_C -#define MBEDTLS_CTR_DRBG_C #define MBEDTLS_ECDH_C #define MBEDTLS_ECP_C -#define MBEDTLS_ENTROPY_C -#define MBEDTLS_ERROR_C #define MBEDTLS_GCM_C -#define MBEDTLS_MD_C -#define MBEDTLS_MD5_C -#define MBEDTLS_OID_C -#define MBEDTLS_PKCS5_C -#define MBEDTLS_PK_C -#define MBEDTLS_PK_PARSE_C -#define MBEDTLS_PLATFORM_C -#define MBEDTLS_RSA_C -#define MBEDTLS_SHA1_C -#define MBEDTLS_SHA256_C -#define MBEDTLS_SHA512_C -#define MBEDTLS_SSL_CLI_C -#define MBEDTLS_SSL_SRV_C -#define MBEDTLS_SSL_TLS_C #define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE -#define MBEDTLS_X509_CRT_PARSE_C -#define MBEDTLS_X509_USE_C #define MBEDTLS_HAVE_TIME #define MBEDTLS_HAVE_TIME_DATE -// Memory allocation hooks -#include -#include -void *m_tracked_calloc(size_t nmemb, size_t size); -void m_tracked_free(void *ptr); -#define MBEDTLS_PLATFORM_STD_CALLOC m_tracked_calloc -#define MBEDTLS_PLATFORM_STD_FREE m_tracked_free -#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf - // Time hook #include time_t rp2_rtctime_seconds(time_t *timer); #define MBEDTLS_PLATFORM_TIME_MACRO rp2_rtctime_seconds -#include "mbedtls/check_config.h" +// Set MicroPython-specific options. +#define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) + +// Include common mbedtls configuration. +#include "extmod/mbedtls/mbedtls_config_common.h" #endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ diff --git a/ports/stm32/mbedtls/mbedtls_config.h b/ports/stm32/mbedtls/mbedtls_config.h index 2e0cb7651a48..b8548866f168 100644 --- a/ports/stm32/mbedtls/mbedtls_config.h +++ b/ports/stm32/mbedtls/mbedtls_config.h @@ -26,75 +26,14 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_H -// Set mbedtls configuration -#define MBEDTLS_PLATFORM_MEMORY -#define MBEDTLS_PLATFORM_NO_STD_FUNCTIONS -#define MBEDTLS_DEPRECATED_REMOVED -#define MBEDTLS_ENTROPY_HARDWARE_ALT -#define MBEDTLS_AES_ROM_TABLES -#define MBEDTLS_CIPHER_MODE_CBC -#define MBEDTLS_ECP_DP_SECP192R1_ENABLED -#define MBEDTLS_ECP_DP_SECP224R1_ENABLED -#define MBEDTLS_ECP_DP_SECP256R1_ENABLED -#define MBEDTLS_ECP_DP_SECP384R1_ENABLED -#define MBEDTLS_ECP_DP_SECP521R1_ENABLED -#define MBEDTLS_ECP_DP_SECP192K1_ENABLED -#define MBEDTLS_ECP_DP_SECP224K1_ENABLED -#define MBEDTLS_ECP_DP_SECP256K1_ENABLED -#define MBEDTLS_ECP_DP_BP256R1_ENABLED -#define MBEDTLS_ECP_DP_BP384R1_ENABLED -#define MBEDTLS_ECP_DP_BP512R1_ENABLED -#define MBEDTLS_ECP_DP_CURVE25519_ENABLED -#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED -#define MBEDTLS_NO_PLATFORM_ENTROPY -#define MBEDTLS_PKCS1_V15 -#define MBEDTLS_SHA256_SMALLER -#define MBEDTLS_SSL_PROTO_TLS1 -#define MBEDTLS_SSL_PROTO_TLS1_1 -#define MBEDTLS_SSL_PROTO_TLS1_2 -#define MBEDTLS_SSL_SERVER_NAME_INDICATION - -// Use a smaller output buffer to reduce size of SSL context -#define MBEDTLS_SSL_MAX_CONTENT_LEN (16384) -#define MBEDTLS_SSL_IN_CONTENT_LEN (MBEDTLS_SSL_MAX_CONTENT_LEN) -#define MBEDTLS_SSL_OUT_CONTENT_LEN (4096) - // Enable mbedtls modules -#define MBEDTLS_AES_C -#define MBEDTLS_ASN1_PARSE_C -#define MBEDTLS_BIGNUM_C -#define MBEDTLS_CIPHER_C -#define MBEDTLS_CTR_DRBG_C // #define MBEDTLS_ECP_C -#define MBEDTLS_ENTROPY_C -#define MBEDTLS_ERROR_C -#define MBEDTLS_MD_C -#define MBEDTLS_MD5_C -#define MBEDTLS_OID_C -#define MBEDTLS_PKCS5_C -#define MBEDTLS_PK_C -#define MBEDTLS_PK_PARSE_C -#define MBEDTLS_PLATFORM_C -#define MBEDTLS_RSA_C -#define MBEDTLS_SHA1_C -#define MBEDTLS_SHA256_C -#define MBEDTLS_SHA512_C -#define MBEDTLS_SSL_CLI_C -#define MBEDTLS_SSL_SRV_C -#define MBEDTLS_SSL_TLS_C #define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE -#define MBEDTLS_X509_CRT_PARSE_C -#define MBEDTLS_X509_USE_C -// Memory allocation hooks -#include -#include -void *m_tracked_calloc(size_t nmemb, size_t size); -void m_tracked_free(void *ptr); -#define MBEDTLS_PLATFORM_STD_CALLOC m_tracked_calloc -#define MBEDTLS_PLATFORM_STD_FREE m_tracked_free -#define MBEDTLS_PLATFORM_SNPRINTF_MACRO snprintf +// Set MicroPython-specific options. +#define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) -#include "mbedtls/check_config.h" +// Include common mbedtls configuration. +#include "extmod/mbedtls/mbedtls_config_common.h" #endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ diff --git a/ports/unix/mbedtls/mbedtls_config.h b/ports/unix/mbedtls/mbedtls_config.h index b119d09092f0..c83f1c86f3dd 100644 --- a/ports/unix/mbedtls/mbedtls_config.h +++ b/ports/unix/mbedtls/mbedtls_config.h @@ -26,69 +26,15 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_H -// If you want to debug MBEDTLS uncomment the following and -// Pass 3 to mbedtls_debug_set_threshold in socket_new -// #define MBEDTLS_DEBUG_C - // Set mbedtls configuration -#define MBEDTLS_DEPRECATED_REMOVED -#define MBEDTLS_AES_ROM_TABLES -#define MBEDTLS_CIPHER_MODE_CBC #define MBEDTLS_CIPHER_MODE_CTR // needed for MICROPY_PY_UCRYPTOLIB_CTR -#define MBEDTLS_ECP_DP_SECP192R1_ENABLED -#define MBEDTLS_ECP_DP_SECP224R1_ENABLED -#define MBEDTLS_ECP_DP_SECP256R1_ENABLED -#define MBEDTLS_ECP_DP_SECP384R1_ENABLED -#define MBEDTLS_ECP_DP_SECP521R1_ENABLED -#define MBEDTLS_ECP_DP_SECP192K1_ENABLED -#define MBEDTLS_ECP_DP_SECP224K1_ENABLED -#define MBEDTLS_ECP_DP_SECP256K1_ENABLED -#define MBEDTLS_ECP_DP_BP256R1_ENABLED -#define MBEDTLS_ECP_DP_BP384R1_ENABLED -#define MBEDTLS_ECP_DP_BP512R1_ENABLED -#define MBEDTLS_ECP_DP_CURVE25519_ENABLED -#define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED -#define MBEDTLS_NO_PLATFORM_ENTROPY -#define MBEDTLS_PKCS1_V15 -#define MBEDTLS_SHA256_SMALLER -#define MBEDTLS_SSL_PROTO_TLS1 -#define MBEDTLS_SSL_PROTO_TLS1_1 -#define MBEDTLS_SSL_PROTO_TLS1_2 -#define MBEDTLS_SSL_SERVER_NAME_INDICATION - -// Use a smaller output buffer to reduce size of SSL context -#define MBEDTLS_SSL_MAX_CONTENT_LEN (16384) -#define MBEDTLS_SSL_IN_CONTENT_LEN (MBEDTLS_SSL_MAX_CONTENT_LEN) -#define MBEDTLS_SSL_OUT_CONTENT_LEN (4096) // Enable mbedtls modules -#define MBEDTLS_AES_C -#define MBEDTLS_ASN1_PARSE_C -#define MBEDTLS_BIGNUM_C -#define MBEDTLS_CIPHER_C -#define MBEDTLS_CTR_DRBG_C -#define MBEDTLS_ENTROPY_C -#define MBEDTLS_ERROR_C #define MBEDTLS_HAVEGE_C -#define MBEDTLS_MD_C -#define MBEDTLS_MD5_C -#define MBEDTLS_OID_C -#define MBEDTLS_PKCS5_C -#define MBEDTLS_PK_C -#define MBEDTLS_PK_PARSE_C -#define MBEDTLS_PLATFORM_C -#define MBEDTLS_RSA_C -#define MBEDTLS_SHA1_C -#define MBEDTLS_SHA256_C -#define MBEDTLS_SHA512_C -#define MBEDTLS_SSL_CLI_C -#define MBEDTLS_SSL_SRV_C -#define MBEDTLS_SSL_TLS_C #define MBEDTLS_TIMING_C #define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE -#define MBEDTLS_X509_CRT_PARSE_C -#define MBEDTLS_X509_USE_C -#include "mbedtls/check_config.h" +// Include common mbedtls configuration. +#include "extmod/mbedtls/mbedtls_config_common.h" #endif /* MICROPY_INCLUDED_MBEDTLS_CONFIG_H */ From 9347545f9ee66210a835cb2f1e9860949feb139f Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 5 Oct 2022 10:48:31 +1100 Subject: [PATCH 013/823] extmod/mbedtls: Enable MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE. This was already enabled on all ports except mimxrt. Now it's enabled on all of them. Signed-off-by: Damien George --- extmod/mbedtls/mbedtls_config_common.h | 1 + ports/rp2/mbedtls/mbedtls_config.h | 1 - ports/stm32/mbedtls/mbedtls_config.h | 1 - ports/unix/mbedtls/mbedtls_config.h | 1 - 4 files changed, 1 insertion(+), 3 deletions(-) diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 6c9385dea5f7..4c2be6228af0 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -82,6 +82,7 @@ #define MBEDTLS_SSL_CLI_C #define MBEDTLS_SSL_SRV_C #define MBEDTLS_SSL_TLS_C +#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE #define MBEDTLS_X509_CRT_PARSE_C #define MBEDTLS_X509_USE_C diff --git a/ports/rp2/mbedtls/mbedtls_config.h b/ports/rp2/mbedtls/mbedtls_config.h index 61f622295ffa..9c930b7d9c49 100644 --- a/ports/rp2/mbedtls/mbedtls_config.h +++ b/ports/rp2/mbedtls/mbedtls_config.h @@ -34,7 +34,6 @@ #define MBEDTLS_ECDH_C #define MBEDTLS_ECP_C #define MBEDTLS_GCM_C -#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE #define MBEDTLS_HAVE_TIME #define MBEDTLS_HAVE_TIME_DATE diff --git a/ports/stm32/mbedtls/mbedtls_config.h b/ports/stm32/mbedtls/mbedtls_config.h index b8548866f168..cc71aa7ec113 100644 --- a/ports/stm32/mbedtls/mbedtls_config.h +++ b/ports/stm32/mbedtls/mbedtls_config.h @@ -28,7 +28,6 @@ // Enable mbedtls modules // #define MBEDTLS_ECP_C -#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE // Set MicroPython-specific options. #define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) diff --git a/ports/unix/mbedtls/mbedtls_config.h b/ports/unix/mbedtls/mbedtls_config.h index c83f1c86f3dd..c8ffab0832a1 100644 --- a/ports/unix/mbedtls/mbedtls_config.h +++ b/ports/unix/mbedtls/mbedtls_config.h @@ -32,7 +32,6 @@ // Enable mbedtls modules #define MBEDTLS_HAVEGE_C #define MBEDTLS_TIMING_C -#define MBEDTLS_TLS_DEFAULT_ALLOW_SHA1_IN_KEY_EXCHANGE // Include common mbedtls configuration. #include "extmod/mbedtls/mbedtls_config_common.h" From 8874a09119e74bb0edf73a63495b559d983767b7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 5 Oct 2022 11:02:47 +1100 Subject: [PATCH 014/823] extmod/mbedtls: Enable elliptic curve DH and DSA cryptography. This is necessary to access sites that only support these protocols. The rp2 port already has ECDH enabled, so this just adds ECDSA there. The other ports now gain both ECDH and ECDSA. The code size increase is: - rp2 (PICO_W): +2916 bytes flash, +24 bytes BSS - stm32 (PYBD_SF6): +20480 bytes flash, +32 bytes data, +48 bytes BSS - mimxrt (TEENSY41): +20708 bytes flash, +32 bytes data, +48 bytes BSS - unix (standard x86-64): +39344 executable, +1744 bytes data, +96 BSS This is obviously a large increase in code size. But there doesn't seem to be any other option because without elliptic curve cryptography devices are partially cut off from the internet. For use cases that require small firmware size, they'll need to build custom firmware with a custom mbedtls config. Signed-off-by: Damien George --- extmod/mbedtls/mbedtls_config_common.h | 6 ++++++ ports/mimxrt/mbedtls/mbedtls_config.h | 3 --- ports/rp2/mbedtls/mbedtls_config.h | 2 -- ports/stm32/mbedtls/mbedtls_config.h | 3 --- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 4c2be6228af0..252f293e0337 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -47,6 +47,8 @@ #define MBEDTLS_ECP_DP_BP512R1_ENABLED #define MBEDTLS_ECP_DP_CURVE25519_ENABLED #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED +// #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED // enabling this currently breaks ssl_data.py test +#define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED #define MBEDTLS_NO_PLATFORM_ENTROPY #define MBEDTLS_PKCS1_V15 #define MBEDTLS_SHA256_SMALLER @@ -63,9 +65,13 @@ // Enable mbedtls modules. #define MBEDTLS_AES_C #define MBEDTLS_ASN1_PARSE_C +#define MBEDTLS_ASN1_WRITE_C #define MBEDTLS_BIGNUM_C #define MBEDTLS_CIPHER_C #define MBEDTLS_CTR_DRBG_C +#define MBEDTLS_ECDH_C +#define MBEDTLS_ECDSA_C +#define MBEDTLS_ECP_C #define MBEDTLS_ENTROPY_C #define MBEDTLS_ERROR_C #define MBEDTLS_MD_C diff --git a/ports/mimxrt/mbedtls/mbedtls_config.h b/ports/mimxrt/mbedtls/mbedtls_config.h index cc71aa7ec113..4140bb514555 100644 --- a/ports/mimxrt/mbedtls/mbedtls_config.h +++ b/ports/mimxrt/mbedtls/mbedtls_config.h @@ -26,9 +26,6 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_H -// Enable mbedtls modules -// #define MBEDTLS_ECP_C - // Set MicroPython-specific options. #define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) diff --git a/ports/rp2/mbedtls/mbedtls_config.h b/ports/rp2/mbedtls/mbedtls_config.h index 9c930b7d9c49..1b6d3dd43a65 100644 --- a/ports/rp2/mbedtls/mbedtls_config.h +++ b/ports/rp2/mbedtls/mbedtls_config.h @@ -31,8 +31,6 @@ #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED // Enable mbedtls modules -#define MBEDTLS_ECDH_C -#define MBEDTLS_ECP_C #define MBEDTLS_GCM_C #define MBEDTLS_HAVE_TIME #define MBEDTLS_HAVE_TIME_DATE diff --git a/ports/stm32/mbedtls/mbedtls_config.h b/ports/stm32/mbedtls/mbedtls_config.h index cc71aa7ec113..4140bb514555 100644 --- a/ports/stm32/mbedtls/mbedtls_config.h +++ b/ports/stm32/mbedtls/mbedtls_config.h @@ -26,9 +26,6 @@ #ifndef MICROPY_INCLUDED_MBEDTLS_CONFIG_H #define MICROPY_INCLUDED_MBEDTLS_CONFIG_H -// Enable mbedtls modules -// #define MBEDTLS_ECP_C - // Set MicroPython-specific options. #define MICROPY_MBEDTLS_CONFIG_BARE_METAL (1) From e24159dec9588d83fc532aa6fcc2f418aae9df85 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Oct 2022 19:22:48 +1100 Subject: [PATCH 015/823] extmod/mbedtls: Remove MBEDTLS_ECP_DP_CURVE25519_ENABLED config. Curve25519 arithmetic is supported in mbedtls, but it's not used for TLS. So there's no need to have this option enabled. Reduces rp2 PICO_W firmware by 2440 bytes. Thanks to @Carglglz for the information. Signed-off-by: Damien George --- extmod/mbedtls/mbedtls_config_common.h | 1 - 1 file changed, 1 deletion(-) diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 252f293e0337..9ec2af85537f 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -45,7 +45,6 @@ #define MBEDTLS_ECP_DP_BP256R1_ENABLED #define MBEDTLS_ECP_DP_BP384R1_ENABLED #define MBEDTLS_ECP_DP_BP512R1_ENABLED -#define MBEDTLS_ECP_DP_CURVE25519_ENABLED #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED // #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED // enabling this currently breaks ssl_data.py test #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED From 68f166dae9ad6dfd94038d5f4394defbb44238af Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 11 Oct 2022 19:25:05 +1100 Subject: [PATCH 016/823] extmod/mbedtls: Remove brainpool curves from config. They are much slower than NIST (SECP) curves and shouldn't be needed. Reduces rp2 PICO_W firmware by 1328 bytes. Thanks to @Carglglz for the information. Signed-off-by: Damien George --- extmod/mbedtls/mbedtls_config_common.h | 3 --- 1 file changed, 3 deletions(-) diff --git a/extmod/mbedtls/mbedtls_config_common.h b/extmod/mbedtls/mbedtls_config_common.h index 9ec2af85537f..bfbc6f7ba298 100644 --- a/extmod/mbedtls/mbedtls_config_common.h +++ b/extmod/mbedtls/mbedtls_config_common.h @@ -42,9 +42,6 @@ #define MBEDTLS_ECP_DP_SECP192K1_ENABLED #define MBEDTLS_ECP_DP_SECP224K1_ENABLED #define MBEDTLS_ECP_DP_SECP256K1_ENABLED -#define MBEDTLS_ECP_DP_BP256R1_ENABLED -#define MBEDTLS_ECP_DP_BP384R1_ENABLED -#define MBEDTLS_ECP_DP_BP512R1_ENABLED #define MBEDTLS_KEY_EXCHANGE_RSA_ENABLED // #define MBEDTLS_KEY_EXCHANGE_ECDHE_RSA_ENABLED // enabling this currently breaks ssl_data.py test #define MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED From b161abc574b8e4fb2b00ef76be2b9c8967e18584 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 18 Oct 2022 00:18:54 +1100 Subject: [PATCH 017/823] py/obj: Verify floating point type is correct for repr C. Prevents double-precision floats being enabled on 32-bit architectures where they will not fit into the mp_obj_t encoding. Signed-off-by: Jim Mussared --- py/obj.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py/obj.h b/py/obj.h index 8aa5b0a8e667..8d62dd4f3cf0 100644 --- a/py/obj.h +++ b/py/obj.h @@ -173,6 +173,10 @@ static inline bool mp_obj_is_obj(mp_const_obj_t o) { #elif MICROPY_OBJ_REPR == MICROPY_OBJ_REPR_C +#if MICROPY_FLOAT_IMPL == MICROPY_FLOAT_IMPL_NONE +#error "MICROPY_OBJ_REPR_C requires float to be enabled." +#endif + static inline bool mp_obj_is_small_int(mp_const_obj_t o) { return (((mp_int_t)(o)) & 1) != 0; } @@ -189,6 +193,9 @@ static inline bool mp_obj_is_small_int(mp_const_obj_t o) { #endif static inline bool mp_obj_is_float(mp_const_obj_t o) { + // Ensure that 32-bit arch can only use single precision. + MP_STATIC_ASSERT(sizeof(mp_float_t) <= sizeof(mp_obj_t)); + return (((mp_uint_t)(o)) & 3) == 2 && (((mp_uint_t)(o)) & 0xff800007) != 0x00000006; } static inline mp_float_t mp_obj_float_get(mp_const_obj_t o) { From 5ee1cb27711d3747747db3ecd7b761c2a064addf Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 18 Oct 2022 00:22:53 +1100 Subject: [PATCH 018/823] stm32/boards/ARDUINO_PORTENTA_H7: Revert to single-precision float. Using repr C is incompatible with double-precision floats on 32-bit arch. Signed-off-by: Jim Mussared --- ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk index e5b48d0644f0..7151a5abc5d7 100644 --- a/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk +++ b/ports/stm32/boards/ARDUINO_PORTENTA_H7/mpconfigboard.mk @@ -10,7 +10,7 @@ BOOTLOADER_DFU_USB_PID = 0x035b # MCU settings MCU_SERIES = h7 CMSIS_MCU = STM32H747xx -MICROPY_FLOAT_IMPL = double +MICROPY_FLOAT_IMPL = single AF_FILE = boards/stm32h743_af.csv LD_FILES = boards/ARDUINO_PORTENTA_H7/stm32h747.ld TEXT0_ADDR = 0x08040000 From 1ba0e8ff96334af986d2b7d90f6d86af27595d28 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Thu, 20 Oct 2022 13:14:25 +1100 Subject: [PATCH 019/823] py/persistentcode: Only emit sub-version if generated code has native. In order for v1.19.1 to load a .mpy, the formerly-feature-flags which are now used for the sub-version must be zero. The sub-version is only used to indicate a native version change, so it should be zero when emitting bytecode-only .mpy files. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- py/persistentcode.c | 7 ++----- tools/mpy-tool.py | 2 +- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/py/persistentcode.c b/py/persistentcode.c index 67c8f327f002..5408f756cf8f 100644 --- a/py/persistentcode.c +++ b/py/persistentcode.c @@ -591,21 +591,18 @@ void mp_raw_code_save(mp_compiled_module_t *cm, mp_print_t *print) { // header contains: // byte 'M' // byte version - // byte feature flags + // byte native arch (and sub-version if native) // byte number of bits in a small int byte header[4] = { 'M', MPY_VERSION, - MPY_FEATURE_ENCODE_SUB_VERSION(MPY_SUB_VERSION), + cm->has_native ? MPY_FEATURE_ENCODE_SUB_VERSION(MPY_SUB_VERSION) | MPY_FEATURE_ENCODE_ARCH(MPY_FEATURE_ARCH_DYNAMIC) : 0, #if MICROPY_DYNAMIC_COMPILER mp_dynamic_compiler.small_int_bits, #else MP_SMALL_INT_BITS, #endif }; - if (cm->has_native) { - header[2] |= MPY_FEATURE_ENCODE_ARCH(MPY_FEATURE_ARCH_DYNAMIC); - } mp_print_bytes(print, header, sizeof(header)); // Number of entries in constant table. diff --git a/tools/mpy-tool.py b/tools/mpy-tool.py index 8b644c137f4c..0b8a0403ca28 100755 --- a/tools/mpy-tool.py +++ b/tools/mpy-tool.py @@ -1676,7 +1676,7 @@ def merge_mpy(compiled_modules, output_file): header = bytearray(4) header[0] = ord("M") header[1] = config.MPY_VERSION - header[2] = config.native_arch << 2 | config.MPY_SUB_VERSION + header[2] = config.native_arch << 2 | config.MPY_SUB_VERSION if config.native_arch else 0 header[3] = config.mp_small_int_bits merged_mpy.extend(header) From edc3f3d0d3c4099dc6bce5371df0e1c3c5a4aecb Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 29 Jun 2022 15:31:21 +0200 Subject: [PATCH 020/823] samd/clock_config: Extend the range of machine.freq(). The value given for machine.freq(f) is extend to the range of 1_000_000 to 200_000_000. Frequencies below 48 MHz will be forced to an integer fraction of 48 MHz. At frequencies below 8 MHz USB is switched off. The power consumption e.g. of ADAFRUIT_ITSYBITSY_M4_EXPRESS drops to about 1.5 mA at 1 MHz. Since the peripheral frequency is dropped as well, timing e.g. of PWM, UART, I2C and SPI is affected and frequency/baud rate has to set again after a frequency change below 48 MHz. --- ports/samd/mcu/samd51/clock_config.c | 72 ++++++++++++++++++++-------- ports/samd/modmachine.c | 4 +- ports/samd/samd_soc.c | 2 +- 3 files changed, 54 insertions(+), 24 deletions(-) diff --git a/ports/samd/mcu/samd51/clock_config.c b/ports/samd/mcu/samd51/clock_config.c index 31c8f5a8654c..9fb48705adcb 100644 --- a/ports/samd/mcu/samd51/clock_config.c +++ b/ports/samd/mcu/samd51/clock_config.c @@ -54,35 +54,63 @@ uint32_t get_peripheral_freq(void) { } void set_cpu_freq(uint32_t cpu_freq_arg) { - cpu_freq = cpu_freq_arg; // Setup GCLK0 for 48MHz as default state to keep the MCU running during config change. GCLK->GENCTRL[0].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; while (GCLK->SYNCBUSY.bit.GENCTRL0) { } - // Setup DPLL0 for 120 MHz // first: disable DPLL0 in case it is running OSCCTRL->Dpll[0].DPLLCTRLA.bit.ENABLE = 0; while (OSCCTRL->Dpll[0].DPLLSYNCBUSY.bit.ENABLE == 1) { } - // Now configure the registers - OSCCTRL->Dpll[0].DPLLCTRLB.reg = OSCCTRL_DPLLCTRLB_DIV(1) | OSCCTRL_DPLLCTRLB_LBYPASS | - OSCCTRL_DPLLCTRLB_REFCLK(0) | OSCCTRL_DPLLCTRLB_WUF | OSCCTRL_DPLLCTRLB_FILTER(0x01); - - uint32_t div = cpu_freq / DPLLx_REF_FREQ; - uint32_t frac = (cpu_freq - div * DPLLx_REF_FREQ) / (DPLLx_REF_FREQ / 32); - OSCCTRL->Dpll[0].DPLLRATIO.reg = (frac << 16) + div - 1; - // enable it again - OSCCTRL->Dpll[0].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE | OSCCTRL_DPLLCTRLA_RUNSTDBY; - - // Per errata 2.13.1 - while (!(OSCCTRL->Dpll[0].DPLLSTATUS.bit.CLKRDY == 1)) { + if (cpu_freq_arg > DFLL48M_FREQ) { + + cpu_freq = cpu_freq_arg; + peripheral_freq = DFLL48M_FREQ; + // Now configure the registers + OSCCTRL->Dpll[0].DPLLCTRLB.reg = OSCCTRL_DPLLCTRLB_DIV(1) | OSCCTRL_DPLLCTRLB_LBYPASS | + OSCCTRL_DPLLCTRLB_REFCLK(0) | OSCCTRL_DPLLCTRLB_WUF | OSCCTRL_DPLLCTRLB_FILTER(0x01); + + uint32_t div = cpu_freq / DPLLx_REF_FREQ; + uint32_t frac = (cpu_freq - div * DPLLx_REF_FREQ) / (DPLLx_REF_FREQ / 32); + OSCCTRL->Dpll[0].DPLLRATIO.reg = (frac << 16) + div - 1; + // enable it again + OSCCTRL->Dpll[0].DPLLCTRLA.reg = OSCCTRL_DPLLCTRLA_ENABLE | OSCCTRL_DPLLCTRLA_RUNSTDBY; + + // Per errata 2.13.1 + while (!(OSCCTRL->Dpll[0].DPLLSTATUS.bit.CLKRDY == 1)) { + } + // Setup GCLK0 for DPLL0 output (48 or 48-200MHz) + GCLK->GENCTRL[0].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DPLL0; + while (GCLK->SYNCBUSY.bit.GENCTRL0) { + } + // Set GCLK 2 back to 48 MHz + GCLK->GENCTRL[2].reg = GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; + while (GCLK->SYNCBUSY.bit.GENCTRL2) { + } + } else { + int div = DFLL48M_FREQ / cpu_freq_arg; + // Setup GCLK1 for the low freq + GCLK->GENCTRL[2].reg = GCLK_GENCTRL_DIV(div) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; + while (GCLK->SYNCBUSY.bit.GENCTRL2) { + } + GCLK->GENCTRL[0].reg = GCLK_GENCTRL_DIV(div) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; + while (GCLK->SYNCBUSY.bit.GENCTRL0) { + } + peripheral_freq = DFLL48M_FREQ / div; + cpu_freq = DFLL48M_FREQ / div; } - - // Setup GCLK0 for DPLL0 output (48 or 48-200MHz) - GCLK->GENCTRL[0].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DPLL0; - while (GCLK->SYNCBUSY.bit.GENCTRL0) { + if (cpu_freq >= 8000000) { + // Setup GCLK5 for DFLL48M output (48 MHz) + GCLK->GENCTRL[5].reg = GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; + while (GCLK->SYNCBUSY.bit.GENCTRL5) { + } + } else { + // Setup GCLK5 off if CPU Clk < 8 MHz + GCLK->GENCTRL[5].reg = 0; + while (GCLK->SYNCBUSY.bit.GENCTRL5) { + } } } @@ -120,9 +148,10 @@ void init_clocks(uint32_t cpu_freq) { // SAMD51 clock settings // GCLK0: 48MHz from DFLL48M or 48 - 200 MHz from DPLL0 (SAMD51) // GCLK1: 32768 Hz from 32KULP or DFLL48M - // GCLK2: 48MHz from DFLL48M for Peripheral devices - // GCLK3: 16Mhz for the us-counter (TC0/TC1) + // GCLK2: 8-48MHz from DFLL48M for Peripheral devices + // GCLK3: 8Mhz for the us-counter (TC0/TC1) // GCLK4: 32kHz from crystal, if present + // GCLK5: 48MHz from DFLL48M for USB // DPLL0: 48 - 200 MHz // Steps to set up clocks: @@ -136,6 +165,7 @@ void init_clocks(uint32_t cpu_freq) { // Setup GCLK2 to 48MHz for Peripherals // Setup GCLK3 to 8MHz for TC0/TC1 // Setup GCLK4 to 32kHz crystal, if present + // Setup GCLK5 to 48 MHz // Setup GCLK0 for 48MHz as default state to keep the MCU running during config change. GCLK->GENCTRL[0].reg = GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; @@ -238,7 +268,7 @@ void init_clocks(uint32_t cpu_freq) { peripheral_freq = DFLL48M_FREQ; // To be changed if CPU_FREQ < 48M - // Setup GCLK2 for DPLL1 output (48 MHz) + // Setup GCLK2 for DFLL48M output (48 MHz), may be scaled down later by calls to set_cpu_freq GCLK->GENCTRL[2].reg = GCLK_GENCTRL_DIV(1) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; while (GCLK->SYNCBUSY.bit.GENCTRL2) { } diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index b9de026ed425..d16b660645f3 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -69,9 +69,9 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { } else { #if defined(MCU_SAMD51) uint32_t freq = mp_obj_get_int(args[0]); - if (freq >= 48000000 && freq <= 200000000) { + if (freq >= 1000000 && freq <= 200000000) { set_cpu_freq(freq); - SysTick_Config(freq / 1000); + SysTick_Config(get_cpu_freq() / 1000); } #endif return mp_const_none; diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index f4a27f3df740..113529aeea36 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -46,7 +46,7 @@ static void usb_init(void) { PM->APBBMASK.bit.USB_ = 1; uint8_t alt = 6; // alt G, USB #elif defined(MCU_SAMD51) - GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK2; + GCLK->PCHCTRL[USB_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK5; while (GCLK->PCHCTRL[USB_GCLK_ID].bit.CHEN == 0) { } MCLK->AHBMASK.bit.USB_ = 1; From 1c32cec7f11864a9bc67f988e39e7dfc7c33188e Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 29 Jun 2022 17:22:20 +0200 Subject: [PATCH 021/823] samd/clock_config: Support changing machine.freq() for SAMD21. The range is 1MHz - 48 MHz. Note that below 8 MHz there is no USB support. The frequency will be set to an integer fraction of 48 MHz. And after changing the frequency, the peripherals like PWM, UART, I2C, SPI have to be reconfigured. Current consumption e.g. of the Seeed Xiao board at 1 MHz is about 1.5 mA, mostly caused by the on-board LED (green LED with 1k resistor at 3.3V). --- ports/samd/mcu/samd21/clock_config.c | 43 +++++++++++++++++++++------- ports/samd/mcu/samd21/mpconfigmcu.h | 1 + ports/samd/mcu/samd51/mpconfigmcu.h | 1 + ports/samd/modmachine.c | 4 +-- ports/samd/samd_soc.c | 2 +- 5 files changed, 36 insertions(+), 15 deletions(-) diff --git a/ports/samd/mcu/samd21/clock_config.c b/ports/samd/mcu/samd21/clock_config.c index 2402ed2e3107..204a5294f257 100644 --- a/ports/samd/mcu/samd21/clock_config.c +++ b/ports/samd/mcu/samd21/clock_config.c @@ -51,7 +51,36 @@ uint32_t get_peripheral_freq(void) { } void set_cpu_freq(uint32_t cpu_freq_arg) { - cpu_freq = cpu_freq_arg; + + // Set 1 waitstate to be safe + NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_MANW | NVMCTRL_CTRLB_RWS(1); + + int div = DFLL48M_FREQ / cpu_freq_arg; + peripheral_freq = cpu_freq = DFLL48M_FREQ / div; + + // Enable GCLK output: 48M on both CCLK0 and GCLK2 + GCLK->GENDIV.reg = GCLK_GENDIV_ID(0) | GCLK_GENDIV_DIV(div); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(0); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(div); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(2); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + if (cpu_freq >= 8000000) { + // Enable GCLK output: 48MHz on GCLK5 for USB + GCLK->GENDIV.reg = GCLK_GENDIV_ID(5) | GCLK_GENDIV_DIV(1); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(5); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + } else { + // Disable GCLK output on GCLK5 for USB, since USB is not reliable below 8 Mhz. + GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(5); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + } + // Set 0 waitstates for slower CPU clock + NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_MANW | NVMCTRL_CTRLB_RWS(cpu_freq > 24000000 ? 1 : 0); } void check_usb_recovery_mode(void) { @@ -76,6 +105,7 @@ void init_clocks(uint32_t cpu_freq) { // GCLK2: 48MHz from DFLL for Peripherals // GCLK3: 1Mhz for the us-counter (TC4/TC5) // GCLK4: 32kHz from crystal, if present + // GCLK5: 48MHz from DFLL for USB // GCLK8: 1kHz clock for WDT NVMCTRL->CTRLB.bit.MANW = 1; // errata "Spurious Writes" @@ -180,15 +210,7 @@ void init_clocks(uint32_t cpu_freq) { #endif // MICROPY_HW_XOSC32K - // Enable GCLK output: 48M on both CCLK0 and GCLK2 - GCLK->GENDIV.reg = GCLK_GENDIV_ID(0) | GCLK_GENDIV_DIV(1); - GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(0); - while (GCLK->STATUS.bit.SYNCBUSY) { - } - GCLK->GENDIV.reg = GCLK_GENDIV_ID(2) | GCLK_GENDIV_DIV(1); - GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(2); - while (GCLK->STATUS.bit.SYNCBUSY) { - } + set_cpu_freq(cpu_freq); // Enable GCLK output: 1MHz on GCLK3 for TC4 GCLK->GENDIV.reg = GCLK_GENDIV_ID(3) | GCLK_GENDIV_DIV(48); @@ -200,7 +222,6 @@ void init_clocks(uint32_t cpu_freq) { GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(8); while (GCLK->STATUS.bit.SYNCBUSY) { } - } void enable_sercom_clock(int id) { diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index a84b31276b42..e0af60552b02 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -19,6 +19,7 @@ #define CPU_FREQ (48000000) #define DFLL48M_FREQ (48000000) +#define MAX_CPU_FREQ (48000000) #define IRQ_PRI_PENDSV ((1 << __NVIC_PRIO_BITS) - 1) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 19193992f058..819bc1bb11b7 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -26,6 +26,7 @@ unsigned long trng_random_u32(void); #define CPU_FREQ (120000000) #define DFLL48M_FREQ (48000000) +#define MAX_CPU_FREQ (200000000) #define DPLLx_REF_FREQ (32768) #define NVIC_PRIORITYGROUP_4 ((uint32_t)0x00000003) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index d16b660645f3..21d700ac31ca 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -67,13 +67,11 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { if (n_args == 0) { return MP_OBJ_NEW_SMALL_INT(get_cpu_freq()); } else { - #if defined(MCU_SAMD51) uint32_t freq = mp_obj_get_int(args[0]); - if (freq >= 1000000 && freq <= 200000000) { + if (freq >= 1000000 && freq <= MAX_CPU_FREQ) { set_cpu_freq(freq); SysTick_Config(get_cpu_freq() / 1000); } - #endif return mp_const_none; } } diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index 113529aeea36..6d8348ebcd83 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -41,7 +41,7 @@ static void usb_init(void) { // Init USB clock #if defined(MCU_SAMD21) - GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID_USB; + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK5 | GCLK_CLKCTRL_ID_USB; PM->AHBMASK.bit.USB_ = 1; PM->APBBMASK.bit.USB_ = 1; uint8_t alt = 6; // alt G, USB From 64e3c351de0959660d1a6b691ee7dbf2de565fbe Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 29 Jun 2022 14:09:57 +0200 Subject: [PATCH 022/823] samd/modmachine: Add machine.reset_cause(). --- ports/samd/modmachine.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 21d700ac31ca..db3f159cc2c3 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -139,6 +139,17 @@ STATIC mp_obj_t machine_enable_irq(mp_obj_t state_in) { } MP_DEFINE_CONST_FUN_OBJ_1(machine_enable_irq_obj, machine_enable_irq); +STATIC mp_obj_t machine_reset_cause(void) { + #if defined(MCU_SAMD21) + return MP_OBJ_NEW_SMALL_INT(PM->RCAUSE.reg); + #elif defined(MCU_SAMD51) + return MP_OBJ_NEW_SMALL_INT(RSTC->RCAUSE.reg); + #else + return MP_OBJ_NEW_SMALL_INT(0); + #endif +} +MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); + STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, @@ -165,8 +176,18 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, + { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, { MP_ROM_QSTR(MP_QSTR_bitstream), MP_ROM_PTR(&machine_bitstream_obj) }, + + // Class constants. + // Use numerical constants instead of the symbolic names, + // since the names differ between SAMD21 and SAMD51. + { MP_ROM_QSTR(MP_QSTR_PWRON_RESET), MP_ROM_INT(0x01) }, + { MP_ROM_QSTR(MP_QSTR_HARD_RESET), MP_ROM_INT(0x10) }, + { MP_ROM_QSTR(MP_QSTR_WDT_RESET), MP_ROM_INT(0x20) }, + { MP_ROM_QSTR(MP_QSTR_SOFT_RESET), MP_ROM_INT(0x40) }, + { MP_ROM_QSTR(MP_QSTR_DEEPSLEEP_RESET), MP_ROM_INT(0x80) }, }; STATIC MP_DEFINE_CONST_DICT(machine_module_globals, machine_module_globals_table); From 0d3f0d7470586f9c328a047ac3043420df16d4ab Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 2 Jul 2022 18:12:55 +0200 Subject: [PATCH 023/823] samd/boards/SPARKFUN_SAMD51_THING_PLUS: Add board files for Thing Plus. That device uses an SAMD51J20 MCU with 256k RAM and 1024k flash. --- .../SPARKFUN_SAMD51_THING_PLUS/board.json | 21 ++++++++++ .../mpconfigboard.h | 4 ++ .../mpconfigboard.mk | 8 ++++ .../SPARKFUN_SAMD51_THING_PLUS/pins.csv | 42 +++++++++++++++++++ ports/samd/boards/samd51x20a.ld | 20 +++++++++ 5 files changed, 95 insertions(+) create mode 100644 ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json create mode 100644 ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h create mode 100644 ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk create mode 100644 ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv create mode 100644 ports/samd/boards/samd51x20a.ld diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json new file mode 100644 index 000000000000..af643f5c5f3c --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/board.json @@ -0,0 +1,21 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Battery Charging", + "Breadboard Friendly", + "Micro USB", + "QWIIC", + "SPI Flash" + ], + "images": [ + "sparkfun_samd51_thing_plus.jpg" + ], + "mcu": "samd51", + "product": "Sparkfun SAMD51 Thing Plus", + "thumbnail": "", + "url": "https://www.sparkfun.com/products/14713", + "vendor": "Sparkfun" +} diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h new file mode 100644 index 000000000000..e797ccca9da1 --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h @@ -0,0 +1,4 @@ +#define MICROPY_HW_BOARD_NAME "Sparkfun SAMD51 Thing Plus" +#define MICROPY_HW_MCU_NAME "SAMD51J20A" + +#define MICROPY_HW_XOSC32K (1) diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk new file mode 100644 index 000000000000..9e5cf887dfce --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.mk @@ -0,0 +1,8 @@ +MCU_SERIES = SAMD51 +CMSIS_MCU = SAMD51J20A +LD_FILES = boards/samd51x20a.ld sections.ld +TEXT0 = 0x4000 + +# The ?='s allow overriding in mpconfigboard.mk. +# MicroPython settings +MICROPY_VFS_LFS1 ?= 1 diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv new file mode 100644 index 000000000000..b60fb909714e --- /dev/null +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv @@ -0,0 +1,42 @@ +# Pin rows contain Pin number and pin name. +# Pin rows start with PIN_ +# LED rows start with LED_ +# If the pin name is omitted, the pin number is added as name. +# Rows for empty entries have to start with '-' +# Empty lines and lines not starting with PIN_ or LED_ are ignored + +PIN_PA13,D0 +PIN_PA12,D1 +PIN_PB23,RXD +PIN_PB22,TXD +PIN_PA06,D4 +PIN_PA15,D5 +PIN_PA20,D6 +PIN_PA21,D7 +- +PIN_PA07,D9 +PIN_PA18,D10 +PIN_PA16,D11 +PIN_PA19,D12 +PIN_PA17,D13 +PIN_PA02,A0 +PIN_PB08,A1 +PIN_PB09,A2 +PIN_PA04,A3 +PIN_PA05,A4 +PIN_PB02,A5 +PIN_PA22,SDA +PIN_PA23,SCL +PIN_PB12,MOSI +PIN_PB11,MISO +PIN_PB13,SCK +PIN_PA08,FLASH_MOSI +PIN_PA09,FLASH_SCK +PIN_PA10,FLASH_CS +PIN_PA11,FLASH_MISO +PIN_PA30,SWDCLK +PIN_PA31,SWDIO + +LED_PA17,LED +LED_PB03,RXLED +LED_PA27,TXLED diff --git a/ports/samd/boards/samd51x20a.ld b/ports/samd/boards/samd51x20a.ld new file mode 100644 index 000000000000..f0d5e5c6aeb6 --- /dev/null +++ b/ports/samd/boards/samd51x20a.ld @@ -0,0 +1,20 @@ +/* + GNU linker script for SAMD51x20 +*/ + +/* Specify the memory areas */ +MEMORY +{ + FLASH (rx) : ORIGIN = 0x00004000, LENGTH = 1024K - 16K + RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 256K +} + +/* Top end of the stack, with room for double-tap variable */ +_estack = ORIGIN(RAM) + LENGTH(RAM) - 8; +_sstack = _estack - 16K; + +_oflash_fs = ORIGIN(FLASH) + 384K - 16K; +_sflash_fs = LENGTH(FLASH) - 384K + 16K - 1; + +_sheap = _ebss; +_eheap = _sstack; From a7113e95d78583a47f8074965e17d1e0499e0494 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 7 Jul 2022 09:49:51 +0200 Subject: [PATCH 024/823] samd/modmachine: Add machine.dht_readinto and enable on SAMD51. --- ports/samd/mcu/samd51/mpconfigmcu.h | 1 + ports/samd/mcu/samd51/mpconfigmcu.mk | 2 ++ ports/samd/modmachine.c | 4 ++++ ports/samd/mphalport.h | 3 +++ 4 files changed, 10 insertions(+) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 819bc1bb11b7..a266adf93cad 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -13,6 +13,7 @@ #define MP_NEED_LOG2 (1) #define MICROPY_PY_CMATH (0) +#define MICROPY_PY_MACHINE_DHT_READINTO (1) #define MICROPY_PY_UOS_URANDOM (1) #define MICROPY_PY_URANDOM_SEED_INIT_FUNC (trng_random_u32()) unsigned long trng_random_u32(void); diff --git a/ports/samd/mcu/samd51/mpconfigmcu.mk b/ports/samd/mcu/samd51/mpconfigmcu.mk index f8ed02f841d9..ed4df82c6912 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.mk +++ b/ports/samd/mcu/samd51/mpconfigmcu.mk @@ -2,6 +2,8 @@ MICROPY_VFS_LFS2 ?= 1 SRC_S += shared/runtime/gchelper_m3.s +SRC_C += drivers/dht/dht.c \ + LIBM_SRC_C += $(addprefix lib/libm/,\ acoshf.c \ asinfacosf.c \ diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index db3f159cc2c3..14baf279d549 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -30,6 +30,7 @@ #include "extmod/machine_pulse.h" #include "extmod/machine_i2c.h" #include "extmod/machine_spi.h" +#include "drivers/dht/dht.h" #include "modmachine.h" #include "samd_soc.h" @@ -179,6 +180,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, { MP_ROM_QSTR(MP_QSTR_bitstream), MP_ROM_PTR(&machine_bitstream_obj) }, + #if MICROPY_PY_MACHINE_DHT_READINTO + { MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) }, + #endif // Class constants. // Use numerical constants instead of the symbolic names, diff --git a/ports/samd/mphalport.h b/ports/samd/mphalport.h index 6f4f838cfba2..3879240f9a6a 100644 --- a/ports/samd/mphalport.h +++ b/ports/samd/mphalport.h @@ -99,6 +99,9 @@ static inline uint64_t mp_hal_time_ns(void) { #define MP_HAL_PIN_FMT "%u" #define mp_hal_pin_obj_t uint +#define mp_hal_quiet_timing_enter() MICROPY_BEGIN_ATOMIC_SECTION() +#define mp_hal_quiet_timing_exit(irq_state) MICROPY_END_ATOMIC_SECTION(irq_state) + extern uint32_t machine_pin_open_drain_mask[]; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t pin_in); From fe31fca462011785cf78b4730bf34bb552e9a707 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 21 Jun 2022 14:27:20 +0200 Subject: [PATCH 025/823] samd/mcu/samd51: Enable onewire support for SAMD51. --- ports/samd/mcu/samd51/mpconfigmcu.h | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index a266adf93cad..5735f512b5a7 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -14,6 +14,7 @@ #define MICROPY_PY_CMATH (0) #define MICROPY_PY_MACHINE_DHT_READINTO (1) +#define MICROPY_PY_ONEWIRE (1) #define MICROPY_PY_UOS_URANDOM (1) #define MICROPY_PY_URANDOM_SEED_INIT_FUNC (trng_random_u32()) unsigned long trng_random_u32(void); From 4c9e4c3310faff5ec05b183454df88d0f33f9e78 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 10 Jul 2022 12:04:14 +0200 Subject: [PATCH 026/823] samd/mcu/samd51: Enable FAT support for SAMD51. Tested with a SD card connected to a SAMD51 board. The SEEED WIO terminal has a SD-Card reader built-in. Also a side change to remove a few obsolete lines from Makefile. --- ports/samd/Makefile | 4 --- ports/samd/fatfs_port.c | 41 ++++++++++++++++++++++++++++ ports/samd/mcu/samd51/mpconfigmcu.h | 6 ++++ ports/samd/mcu/samd51/mpconfigmcu.mk | 5 +++- ports/samd/modutime.c | 2 +- 5 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 ports/samd/fatfs_port.c diff --git a/ports/samd/Makefile b/ports/samd/Makefile index 2f97b1993e99..abec4e83a553 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -61,7 +61,6 @@ CFLAGS_MCU_SAMD51 = -mtune=cortex-m4 -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-a CFLAGS += $(INC) -Wall -Werror -std=c99 -nostdlib -mthumb $(CFLAGS_MCU_$(MCU_SERIES)) -fsingle-precision-constant -Wdouble-promotion CFLAGS += -DMCU_$(MCU_SERIES) -D__$(CMSIS_MCU)__ CFLAGS += $(CFLAGS_EXTRA) -CFLAGS += -DMPCONFIG_MCU_H='' LDFLAGS += -nostdlib $(addprefix -T,$(LD_FILES)) -Map=$@.map --cref @@ -169,9 +168,6 @@ CFLAGS += -DMICROPY_MODULE_FROZEN_STR CFLAGS += -DMICROPY_QSTR_EXTRA_POOL=mp_qstr_frozen_const_pool endif -# Workaround for bug in older gcc, warning on "static usbd_device_t _usbd_dev = { 0 };" -$(BUILD)/lib/tinyusb/src/device/usbd.o: CFLAGS += -Wno-missing-braces - all: $(BUILD)/firmware.uf2 $(BUILD)/firmware.elf: $(OBJ) diff --git a/ports/samd/fatfs_port.c b/ports/samd/fatfs_port.c new file mode 100644 index 000000000000..9ee1764ebc0a --- /dev/null +++ b/ports/samd/fatfs_port.c @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2013, 2014 Damien P. George + * Copyright (c) 2021 Robert Hammelrath + * + * 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 "mphalport.h" +#include "py/runtime.h" +#include "shared/timeutils/timeutils.h" +#include "lib/oofatfs/ff.h" + +extern uint32_t time_offset; + +MP_WEAK DWORD get_fattime(void) { + timeutils_struct_time_t tm; + + timeutils_seconds_since_epoch_to_struct_time(mp_hal_ticks_ms_64() / 1000 + time_offset, &tm); + return ((tm.tm_year - 1980) << 25) | ((tm.tm_mon) << 21) | ((tm.tm_mday) << 16) | + ((tm.tm_hour) << 11) | ((tm.tm_min) << 5) | (tm.tm_sec / 2); +} diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 5735f512b5a7..686cfd6104df 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -22,6 +22,12 @@ unsigned long trng_random_u32(void); // Due to a limitation in the TC counter for us, the ticks period is 2**29 #define MICROPY_PY_UTIME_TICKS_PERIOD (0x20000000) +// fatfs configuration used in ffconf.h +#define MICROPY_FATFS_ENABLE_LFN (1) +#define MICROPY_FATFS_RPATH (2) +#define MICROPY_FATFS_MAX_SS (4096) +#define MICROPY_FATFS_LFN_CODE_PAGE 437 /* 1=SFN/ANSI 437=LFN/U.S.(OEM) */ + #define VFS_BLOCK_SIZE_BYTES (1536) // #define MICROPY_HW_UART_TXBUF (1) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.mk b/ports/samd/mcu/samd51/mpconfigmcu.mk index ed4df82c6912..305e780b8787 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.mk +++ b/ports/samd/mcu/samd51/mpconfigmcu.mk @@ -1,8 +1,11 @@ MICROPY_VFS_LFS2 ?= 1 +MICROPY_VFS_FAT ?= 1 SRC_S += shared/runtime/gchelper_m3.s -SRC_C += drivers/dht/dht.c \ +SRC_C += \ + fatfs_port.c \ + drivers/dht/dht.c \ LIBM_SRC_C += $(addprefix lib/libm/,\ acoshf.c \ diff --git a/ports/samd/modutime.c b/ports/samd/modutime.c index a54544e62c6c..4169c15d9117 100644 --- a/ports/samd/modutime.c +++ b/ports/samd/modutime.c @@ -29,7 +29,7 @@ #include "shared/timeutils/timeutils.h" #include "mphalport.h" -static uint32_t time_offset = 0; +uint32_t time_offset = 0; // localtime([secs]) STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { From 03075a68399a6450fd0e238dd75314805f8365b7 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 1 Aug 2022 17:23:11 +0200 Subject: [PATCH 027/823] samd/modmachine: Implement machine.lightsleep(). Which just sets the CPU clock to 200kHz and switches the peripheral clock off. There are two modes: machine.lightsleep(duration_ms) and machine.lightsleep() In any mode any configured pin.irq() event will terminate the sleep. Current consumption in lightsleep for some boards: - 1.5 - 2.5 mA when supplied trough an active USB (Seeed XIAO w/o power LED, Adafruit ItsyBitsy) - 0.8 - 2 mA when supplied through Gnd/+5V (Vusb) (Seeed XIAO w/o power LED, Adafruit ItsyBitsy) - < 1 mA for SAMD51 when supplied trough a battery connector (Sparkfun Thing SAMD51 plus) Related change: move the calls to SysTick_Config() into set_cpu_freq(). It is required after each CPU freq change to have ticks_ms run at the proper rate. --- ports/samd/machine_pin.c | 3 ++ ports/samd/mcu/samd21/clock_config.c | 1 + ports/samd/mcu/samd51/clock_config.c | 1 + ports/samd/modmachine.c | 61 +++++++++++++++++++++++++++- ports/samd/samd_soc.c | 1 - 5 files changed, 65 insertions(+), 2 deletions(-) diff --git a/ports/samd/machine_pin.c b/ports/samd/machine_pin.c index fc72c8f7569f..2dc10f9b4634 100644 --- a/ports/samd/machine_pin.c +++ b/ports/samd/machine_pin.c @@ -57,6 +57,8 @@ typedef struct _machine_pin_irq_obj_t { STATIC const mp_irq_methods_t machine_pin_irq_methods; +bool EIC_occured; + uint32_t machine_pin_open_drain_mask[4]; // Open drain behaviour is simulated. @@ -410,6 +412,7 @@ void EIC_Handler() { for (int eic_id = 0; eic_id < 16; eic_id++, mask <<= 1) { // Did the ISR fire? if (isr & mask) { + EIC_occured = true; EIC->INTFLAG.reg |= mask; // clear the ISR flag machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_objects[eic_id]); if (irq != NULL) { diff --git a/ports/samd/mcu/samd21/clock_config.c b/ports/samd/mcu/samd21/clock_config.c index 204a5294f257..a4011c05cbbc 100644 --- a/ports/samd/mcu/samd21/clock_config.c +++ b/ports/samd/mcu/samd21/clock_config.c @@ -81,6 +81,7 @@ void set_cpu_freq(uint32_t cpu_freq_arg) { } // Set 0 waitstates for slower CPU clock NVMCTRL->CTRLB.reg = NVMCTRL_CTRLB_MANW | NVMCTRL_CTRLB_RWS(cpu_freq > 24000000 ? 1 : 0); + SysTick_Config(cpu_freq / 1000); } void check_usb_recovery_mode(void) { diff --git a/ports/samd/mcu/samd51/clock_config.c b/ports/samd/mcu/samd51/clock_config.c index 9fb48705adcb..f67d297e8eb1 100644 --- a/ports/samd/mcu/samd51/clock_config.c +++ b/ports/samd/mcu/samd51/clock_config.c @@ -112,6 +112,7 @@ void set_cpu_freq(uint32_t cpu_freq_arg) { while (GCLK->SYNCBUSY.bit.GENCTRL5) { } } + SysTick_Config(cpu_freq / 1000); } void check_usb_recovery_mode(void) { diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 14baf279d549..2a8e34a966a8 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -50,6 +50,10 @@ #define DBL_TAP_MAGIC_LOADER 0xf01669ef #define DBL_TAP_MAGIC_RESET 0xf02669ef +#define LIGHTSLEEP_CPU_FREQ 200000 + +extern bool EIC_occured; + STATIC mp_obj_t machine_reset(void) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_RESET; NVIC_SystemReset(); @@ -71,7 +75,6 @@ STATIC mp_obj_t machine_freq(size_t n_args, const mp_obj_t *args) { uint32_t freq = mp_obj_get_int(args[0]); if (freq >= 1000000 && freq <= MAX_CPU_FREQ) { set_cpu_freq(freq); - SysTick_Config(get_cpu_freq() / 1000); } return mp_const_none; } @@ -151,6 +154,60 @@ STATIC mp_obj_t machine_reset_cause(void) { } MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_cause_obj, machine_reset_cause); +STATIC mp_obj_t machine_lightsleep(size_t n_args, const mp_obj_t *args) { + int32_t duration = -1; + uint32_t freq = get_cpu_freq(); + if (n_args > 0) { + duration = mp_obj_get_int(args[0]); + } + EIC_occured = false; + // Slow down + set_cpu_freq(LIGHTSLEEP_CPU_FREQ); + #if defined(MCU_SAMD21) + // Switch the peripheral clock off + GCLK->GENCTRL.reg = GCLK_GENCTRL_ID(2); + while (GCLK->STATUS.bit.SYNCBUSY) { + } + // Switch the EIC temporarily to GCLK3, since GCLK2 is off + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK3 | EIC_GCLK_ID; + if (duration > 0) { + uint32_t t0 = systick_ms; + while ((systick_ms - t0 < duration) && (EIC_occured == false)) { + __WFI(); + } + } else { + while (EIC_occured == false) { + __WFI(); + } + } + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK2 | EIC_GCLK_ID; + + #elif defined(MCU_SAMD51) + // Switch the peripheral clock off + GCLK->GENCTRL[2].reg = 0; + while (GCLK->SYNCBUSY.bit.GENCTRL2) { + } + // Switch the EIC temporarily to GCLK3, since GCLK2 is off + GCLK->PCHCTRL[EIC_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK3; + if (duration > 0) { + uint32_t t0 = systick_ms; + while ((systick_ms - t0 < duration) && (EIC_occured == false)) { + __WFI(); + } + } else { + while (EIC_occured == false) { + __WFI(); + } + } + GCLK->PCHCTRL[EIC_GCLK_ID].reg = GCLK_PCHCTRL_CHEN | GCLK_PCHCTRL_GEN_GCLK2; + + #endif + // Speed up again + set_cpu_freq(freq); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine_lightsleep); + STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, @@ -179,6 +236,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_enable_irq), MP_ROM_PTR(&machine_enable_irq_obj) }, { MP_ROM_QSTR(MP_QSTR_reset_cause), MP_ROM_PTR(&machine_reset_cause_obj) }, { MP_ROM_QSTR(MP_QSTR_time_pulse_us), MP_ROM_PTR(&machine_time_pulse_us_obj) }, + { MP_ROM_QSTR(MP_QSTR_lightsleep), MP_ROM_PTR(&machine_lightsleep_obj) }, + { MP_ROM_QSTR(MP_QSTR_bitstream), MP_ROM_PTR(&machine_bitstream_obj) }, #if MICROPY_PY_MACHINE_DHT_READINTO { MP_ROM_QSTR(MP_QSTR_dht_readinto), MP_ROM_PTR(&dht_readinto_obj) }, diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index 6d8348ebcd83..3608306e8719 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -108,7 +108,6 @@ void init_us_counter(void) { void samd_init(void) { init_clocks(get_cpu_freq()); - SysTick_Config(get_cpu_freq() / 1000); init_us_counter(); usb_init(); check_usb_recovery_mode(); From f0399d35e4f7ad4e48b22f02b80a7ee506c9ec64 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 11 Aug 2022 11:22:58 +0200 Subject: [PATCH 028/823] samd/modmachine: Get the bootloader magic address from the lib. Instead of being hard-coded, and then it works for all MCUs. That fits except for a Sparkfun SAMD51 Thing Plus (known) bug, which uses 192k - 4 as magic address. Therefore, that address is set as well to avoid a problem when this bug is fixed by Sparkfun. --- .../SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h | 6 ++++++ ports/samd/modmachine.c | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h index e797ccca9da1..a51b71c363e1 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/mpconfigboard.h @@ -2,3 +2,9 @@ #define MICROPY_HW_MCU_NAME "SAMD51J20A" #define MICROPY_HW_XOSC32K (1) + +// There seems to be an inconsistency in the SAMD51 Thing bootloader in that +// the bootloader magic address is at the end of a 192k RAM area, instead of +// 256k. Since the SAMD51x20A has 256k RAM, the loader symbol is at that address +// and so there is a fix here using the previous definition. +#define DBL_TAP_ADDR_ALT ((volatile uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 0x10000 - 4)) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 2a8e34a966a8..ce5fef76f753 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -41,21 +41,26 @@ #include "hpl_pm_base.h" #if MICROPY_PY_MACHINE - #if defined(MCU_SAMD21) -#define DBL_TAP_ADDR ((volatile uint32_t *)(0x20000000 + 32 * 1024 - 4)) +#define DBL_TAP_ADDR ((volatile uint32_t *)(HMCRAMC0_ADDR + HMCRAMC0_SIZE - 4)) #elif defined(MCU_SAMD51) -#define DBL_TAP_ADDR ((volatile uint32_t *)(0x20000000 + 192 * 1024 - 4)) +#define DBL_TAP_ADDR ((volatile uint32_t *)(HSRAM_ADDR + HSRAM_SIZE - 4)) #endif +// A board may define a DPL_TAP_ADDR_ALT, which will be set as well +// Needed at the moment for Sparkfun SAMD51 Thing Plus #define DBL_TAP_MAGIC_LOADER 0xf01669ef #define DBL_TAP_MAGIC_RESET 0xf02669ef #define LIGHTSLEEP_CPU_FREQ 200000 extern bool EIC_occured; +extern uint32_t _dbl_tap_addr; STATIC mp_obj_t machine_reset(void) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_RESET; + #ifdef DBL_TAP_ADDR_ALT + *DBL_TAP_ADDR_ALT = DBL_TAP_MAGIC_RESET; + #endif NVIC_SystemReset(); return mp_const_none; } @@ -63,6 +68,9 @@ MP_DEFINE_CONST_FUN_OBJ_0(machine_reset_obj, machine_reset); STATIC mp_obj_t machine_bootloader(void) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_LOADER; + #ifdef DBL_TAP_ADDR_ALT + *DBL_TAP_ADDR_ALT = DBL_TAP_MAGIC_LOADER; + #endif NVIC_SystemReset(); return mp_const_none; } From ddd41b8bbf6823ea730e781d508babbecdd41304 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 13 Aug 2022 17:33:04 +0200 Subject: [PATCH 029/823] samd/clock_config: Document the #defines use in init_clocks(). Which may be set in the respective mpconfigboard.h files. --- ports/samd/mcu/samd21/clock_config.c | 34 +++++++++++++++++++++++ ports/samd/mcu/samd51/clock_config.c | 40 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+) diff --git a/ports/samd/mcu/samd21/clock_config.c b/ports/samd/mcu/samd21/clock_config.c index a4011c05cbbc..a195cb692053 100644 --- a/ports/samd/mcu/samd21/clock_config.c +++ b/ports/samd/mcu/samd21/clock_config.c @@ -96,6 +96,40 @@ void check_usb_recovery_mode(void) { #endif // MICROPY_HW_XOSC32K } +// Purpose of the #defines for the clock configuration. +// +// Both CPU and periperal devices are clocked by the DFLL48M clock. +// DFLL48M is either free running, or controlled by the 32kHz crystal, or +// Synchronized with the USB clock. +// +// #define MICROPY_HW_XOSC32K (0 | 1) +// +// If MICROPY_HW_XOSC32K = 1, the 32kHz crystal is used as input for GCLK 1, which +// serves as refernce clock source for the DFLL48M oscillator, +// The crystal is used, unless MICROPY_HW_MCU_OSC32KULP is set. +// In that case GCLK1 (and the CPU clock) is driven by the 32K Low power oscillator. +// The reason for offering this option is a design flaw of the Adafruit +// Feather boards, where the RGB Led and Debug signals interfere with the +// crystal, causing the CPU to fail if it is driven by the crystal. +// +// If MICROPY_HW_XOSC32K = 0, the 32kHz signal for GCLK1 (and the CPU) is +// created by dividing the 48MHz clock of DFLL48M, but not used otherwise. +// +// If MICROPY_HW_DFLL_USB_SYNC = 0, the DFLL48M oscillator is free running using +// the pre-configured trim values. In that mode, the peripheral clock is +// not exactly 48Mhz and has a substantional temperature drift. +// +// If MICROPY_HW_DFLL_USB_SYNC = 1, the DFLL48 is synchronized with the 1 kHz USB sync +// signal. If after boot there is no USB sync withing 500ms, the configuratuion falls +// back to a free running 48Mhz oscillator. +// +// In all modes, the 48MHz signal has a substantial jitter, largest when +// MICROPY_HW_DFLL_USB_SYNC is active. That is caused by the repective +// reference frequencies of 32kHz or 1 kHz being low. That affects most +// PWM. Std Dev at 1kHz 0.156Hz (w. Crystal) up to 0.4 Hz (with USB sync). +// +// If none of the mentioned defines is set, the device uses the internal oscillators. + void init_clocks(uint32_t cpu_freq) { dfll48m_calibration = 0; // please the compiler diff --git a/ports/samd/mcu/samd51/clock_config.c b/ports/samd/mcu/samd51/clock_config.c index f67d297e8eb1..3bc4616de9e9 100644 --- a/ports/samd/mcu/samd51/clock_config.c +++ b/ports/samd/mcu/samd51/clock_config.c @@ -142,6 +142,46 @@ void check_usb_recovery_mode(void) { #endif // MICROPY_HW_XOSC32K } +// Purpose of the #defines for the clock configuration. +// +// The CPU clock is generated by DPLL0, which takes 32768 Hz as reference frequency, +// supplied through GCLK1. +// +// DFLL48M is used for the peripheral clock, e.g. for PWM, UART, SPI, I2C. +// DFLL48M is either free running, or controlled by the 32kHz crystal, or +// Synchronized with the USB clock. +// +// GCLK1 takes it's input either from the 32kHz crystal, the internal low power +// RC oscillator or from DFLL48M. +// +// #define MICROPY_HW_XOSC32K (0 | 1) +// +// If MICROPY_HW_XOSC32K = 1, the 32kHz crystal is used for the DFLL48M oscillator +// and for GCLK1, feeding the CPU, unless MICROPY_HW_MCU_OSC32KULP is set. +// In that case GCLK1 (and the CPU clock) is driven by the 32K Low power oscillator. +// The reason for offering this option is a design flaw of the Adafruit +// Feather boards, where the RGB Led and Debug signals interfere with the +// crystal, causing the CPU to fail if it is driven by the crystal. The +// peripheral devices are affected as well, but continue it's operation. +// +// If MICROPY_HW_XOSC32K = 0, the 32kHz signal for GCLK1 (and the CPU) is +// created by dividing the 48MHz clock of DFLL48M. +// +// If MICROPY_HW_DFLL_USB_SYNC = 0, the DFLL48M oscillator is free running using +// the pre-configured trim values. In that mode, the peripheral clock is +// not exactly 48Mhz and has a substantional temperature drift. +// +// If MICROPY_HW_DFLL_USB_SYNC = 1, the DFLL48 is synchronized with the 1 kHz USB sync +// signal. If after boot there is no USB sync withing 500ms, the configuratuion falls +// back to a free running 48Mhz oscillator. +// +// In all modes, the 48MHz signal has a substantial jitter, largest when +// MICROPY_HW_DFLL_USB_SYNC is active. That is caused by the repective +// reference frequencies of 32kHz or 1 kHz being low. That affects most +// PWM. Std Dev at 1kHz 0.156Hz (w. Crystal) up to 0.4 Hz (with USB sync). +// +// If none of the mentioned defines is set, the device uses the internal oscillators. + void init_clocks(uint32_t cpu_freq) { dfll48m_calibration = 0; // please the compiler From 2251cb774b7b7db12323a32b339903fa27ff7b08 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 18 Aug 2022 11:58:09 +0200 Subject: [PATCH 030/823] samd/machine_uart: Implement uart.txdone() and uart.flush(). Using the stream method for uart.flush(). uart.txdone() returns True, if the uart not busy, False otherwise. uart.flush() waits until all bytes have been transmitted or a timeout triggers. The timeout is determined by the buffer size and the baud rate. Also fix two inconsistencies when not using txbuf: - Report in ioctl as being writeable if there is room in the tx buffer, only if it is configured. - Print the txbuf size if configured. --- ports/samd/machine_uart.c | 53 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 49 insertions(+), 4 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 1031f26c2648..76b564e874a6 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -116,9 +116,17 @@ void sercom_enable(Sercom *uart, int state) { STATIC void machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, " - "timeout=%u, timeout_char=%u, rxbuf=%d)", + "timeout=%u, timeout_char=%u, rxbuf=%d" + #if MICROPY_HW_UART_TXBUF + ", txbuf=%d" + #endif + ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], - self->stop + 1, self->timeout, self->timeout_char, self->read_buffer.size - 1); + self->stop + 1, self->timeout, self->timeout_char, self->read_buffer.size - 1 + #if MICROPY_HW_UART_TXBUF + , self->write_buffer.size - 1 + #endif + ); } STATIC mp_obj_t machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { @@ -385,6 +393,22 @@ STATIC mp_obj_t machine_uart_sendbreak(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_sendbreak_obj, machine_uart_sendbreak); +STATIC mp_obj_t machine_uart_txdone(mp_obj_t self_in) { + machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + Sercom *uart = sercom_instance[self->id]; + + if (uart->USART.INTFLAG.bit.DRE + #if MICROPY_HW_UART_TXBUF + && ringbuf_avail(&self->write_buffer) == 0 + #endif + && uart->USART.INTFLAG.bit.TXC) { + return mp_const_true; + } else { + return mp_const_false; + } +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_txdone_obj, machine_uart_txdone); + void uart_deinit_all(void) { for (int i = 0; i < SERCOM_INST_NUM; i++) { if (uart_table[i] != NULL) { @@ -399,7 +423,9 @@ STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_any), MP_ROM_PTR(&machine_uart_any_obj) }, { MP_ROM_QSTR(MP_QSTR_sendbreak), MP_ROM_PTR(&machine_uart_sendbreak_obj) }, + { MP_ROM_QSTR(MP_QSTR_txdone), MP_ROM_PTR(&machine_uart_txdone_obj) }, + { MP_ROM_QSTR(MP_QSTR_flush), MP_ROM_PTR(&mp_stream_flush_obj) }, { MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&mp_stream_read_obj) }, { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, @@ -414,7 +440,6 @@ STATIC mp_uint_t machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t siz uint8_t *dest = buf_in; Sercom *uart = sercom_instance[self->id]; - // t.b.d. Cater timeout for timer wrap after 50 days. for (size_t i = 0; i < size; i++) { // Wait for the first/next character while (ringbuf_avail(&self->read_buffer) == 0) { @@ -488,9 +513,29 @@ STATIC mp_uint_t machine_uart_ioctl(mp_obj_t self_in, mp_uint_t request, mp_uint if ((flags & MP_STREAM_POLL_RD) && (uart->USART.INTFLAG.bit.RXC != 0 || ringbuf_avail(&self->read_buffer) > 0)) { ret |= MP_STREAM_POLL_RD; } - if ((flags & MP_STREAM_POLL_WR) && (uart->USART.INTFLAG.bit.DRE != 0)) { + if ((flags & MP_STREAM_POLL_WR) && (uart->USART.INTFLAG.bit.DRE != 0 + #if MICROPY_HW_UART_TXBUF + || ringbuf_avail(&self->write_buffer) > 0 + #endif + )) { ret |= MP_STREAM_POLL_WR; } + } else if (request == MP_STREAM_FLUSH) { + // The timeout is defined by the buffer size and the baudrate. + // Take the worst case assumtions at 13 bit symbol size times 2. + uint64_t timeout = mp_hal_ticks_ms_64() + (3 + #if MICROPY_HW_UART_TXBUF + + self->write_buffer.size + #endif + ) * 13000 * 2 / self->baudrate; + do { + if (machine_uart_txdone((mp_obj_t)self) == mp_const_true) { + return 0; + } + MICROPY_EVENT_POLL_HOOK + } while (mp_hal_ticks_ms_64() < timeout); + *errcode = MP_ETIMEDOUT; + ret = MP_STREAM_ERROR; } else { *errcode = MP_EINVAL; ret = MP_STREAM_ERROR; From be31fde012e0cf522ec9d70f50923a0a899f438c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 8 Sep 2022 17:55:08 +0200 Subject: [PATCH 031/823] samd/mcu: Make some settings in mpconfigmcu.h conditional. And set the default for MICROPY_PY_MATH as 1 for both MCU types. --- ports/samd/mcu/samd21/mpconfigmcu.h | 13 ++++++++++++- ports/samd/mcu/samd21/mpconfigmcu.mk | 29 ++++++++++++++++++++++++++++ ports/samd/mcu/samd51/mpconfigmcu.h | 11 +++++++++++ 3 files changed, 52 insertions(+), 1 deletion(-) diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index e0af60552b02..8d155f93ab03 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -9,13 +9,24 @@ #define MICROPY_MODULE_BUILTIN_INIT (1) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) + +#ifndef MICROPY_PY_BUILTINS_COMPLEX #define MICROPY_PY_BUILTINS_COMPLEX (0) -#define MICROPY_PY_MATH (0) +#endif + +#ifndef MICROPY_PY_MATH +#define MICROPY_PY_MATH (1) +#endif + +#ifndef MICROPY_PY_CMATH #define MICROPY_PY_CMATH (0) +#endif #define VFS_BLOCK_SIZE_BYTES (1536) // 24x 64B flash pages; +#ifndef MICROPY_HW_UART_TXBUF #define MICROPY_HW_UART_TXBUF (1) +#endif #define CPU_FREQ (48000000) #define DFLL48M_FREQ (48000000) diff --git a/ports/samd/mcu/samd21/mpconfigmcu.mk b/ports/samd/mcu/samd21/mpconfigmcu.mk index cc435da8cc91..287d6d18be7b 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.mk +++ b/ports/samd/mcu/samd21/mpconfigmcu.mk @@ -1 +1,30 @@ SRC_S += shared/runtime/gchelper_m0.s + +LIBM_SRC_C += $(addprefix lib/libm/,\ + acoshf.c \ + asinfacosf.c \ + asinhf.c \ + atan2f.c \ + atanf.c \ + atanhf.c \ + ef_rem_pio2.c \ + erf_lgamma.c \ + fmodf.c \ + kf_cos.c \ + kf_rem_pio2.c \ + kf_sin.c \ + kf_tan.c \ + log1pf.c \ + math.c \ + nearbyintf.c \ + roundf.c \ + sf_cos.c \ + sf_erf.c \ + sf_frexp.c \ + sf_ldexp.c \ + sf_modf.c \ + sf_sin.c \ + sf_tan.c \ + wf_lgamma.c \ + wf_tgamma.c \ + ) diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 686cfd6104df..666370c983b7 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -8,10 +8,19 @@ #define MICROPY_EMIT_INLINE_THUMB (1) #define MICROPY_FLOAT_IMPL (MICROPY_FLOAT_IMPL_FLOAT) + +#ifndef MICROPY_PY_BUILTINS_COMPLEX #define MICROPY_PY_BUILTINS_COMPLEX (0) +#endif + +#ifndef MICROPY_PY_MATH #define MICROPY_PY_MATH (1) #define MP_NEED_LOG2 (1) +#endif + +#ifndef MICROPY_PY_CMATH #define MICROPY_PY_CMATH (0) +#endif #define MICROPY_PY_MACHINE_DHT_READINTO (1) #define MICROPY_PY_ONEWIRE (1) @@ -30,7 +39,9 @@ unsigned long trng_random_u32(void); #define VFS_BLOCK_SIZE_BYTES (1536) // +#ifndef MICROPY_HW_UART_TXBUF #define MICROPY_HW_UART_TXBUF (1) +#endif #define CPU_FREQ (120000000) #define DFLL48M_FREQ (48000000) From fc9d66fac64422a6a6ea4a79bfd8b2460cc8e9e8 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 15 Sep 2022 15:58:54 +0200 Subject: [PATCH 032/823] samd/machine_rtc: Add the machine.RTC class. Methods implemented are: - rtc.init(date) - rtc.datetime([new_date]) - rtc.calibration(value) The presence of this class can be controlled by MICROPY_PY_MACHINE_RTC. If the RTC module is used, the time module uses the RTC as well. For boards without a 32kHz crystal, using RTC makes no sense, since it will then use the ULP32K oscillator, which is not precise at all. Therefore, it will by default only be enabled for boards using a crystal, but can be enabled in the respective mpconfigboard.h. --- ports/samd/Makefile | 1 + ports/samd/fatfs_port.c | 6 +- ports/samd/machine_rtc.c | 181 +++++++++++++++++++++++++++ ports/samd/mcu/samd21/clock_config.c | 17 ++- ports/samd/mcu/samd21/mpconfigmcu.h | 6 + ports/samd/mcu/samd51/clock_config.c | 20 +-- ports/samd/mcu/samd51/mpconfigmcu.h | 6 + ports/samd/modmachine.c | 3 + ports/samd/modmachine.h | 3 + ports/samd/modutime.c | 30 ++++- ports/samd/samd_soc.c | 7 ++ 11 files changed, 263 insertions(+), 17 deletions(-) create mode 100644 ports/samd/machine_rtc.c diff --git a/ports/samd/Makefile b/ports/samd/Makefile index abec4e83a553..e6e592a0352b 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -94,6 +94,7 @@ SRC_C += \ machine_i2c.c \ machine_led.c \ machine_pin.c \ + machine_rtc.c \ machine_spi.c \ machine_timer.c \ machine_uart.c \ diff --git a/ports/samd/fatfs_port.c b/ports/samd/fatfs_port.c index 9ee1764ebc0a..a3e3f1b67b3a 100644 --- a/ports/samd/fatfs_port.c +++ b/ports/samd/fatfs_port.c @@ -33,9 +33,13 @@ extern uint32_t time_offset; MP_WEAK DWORD get_fattime(void) { + #if MICROPY_PY_MACHINE_RTC + return (RTC->MODE2.CLOCK.reg >> 1) + (20 << 25); + #else + extern void rtc_gettime(timeutils_struct_time_t *tm); timeutils_struct_time_t tm; - timeutils_seconds_since_epoch_to_struct_time(mp_hal_ticks_ms_64() / 1000 + time_offset, &tm); return ((tm.tm_year - 1980) << 25) | ((tm.tm_mon) << 21) | ((tm.tm_mday) << 16) | ((tm.tm_hour) << 11) | ((tm.tm_min) << 5) | (tm.tm_sec / 2); + #endif } diff --git a/ports/samd/machine_rtc.c b/ports/samd/machine_rtc.c new file mode 100644 index 000000000000..57bfa998e583 --- /dev/null +++ b/ports/samd/machine_rtc.c @@ -0,0 +1,181 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Damien P. George + * Copyright (c) 2022 "Robert Hammelrath" + * + * 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 "shared/timeutils/timeutils.h" +#include "modmachine.h" +#include "py/mphal.h" +#include "sam.h" + +#if MICROPY_PY_MACHINE_RTC + +typedef struct _machine_rtc_obj_t { + mp_obj_base_t base; + mp_obj_t callback; +} machine_rtc_obj_t; + +// Singleton RTC object. +STATIC const machine_rtc_obj_t machine_rtc_obj = {{&machine_rtc_type}}; + +// Start the RTC Timer. +void machine_rtc_start(bool force) { + #if defined(MCU_SAMD21) + + if (RTC->MODE2.CTRL.bit.ENABLE == 0 || force) { + // Enable the 1k Clock + GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK8 | GCLK_CLKCTRL_ID_RTC; + + RTC->MODE2.CTRL.reg = RTC_MODE2_CTRL_SWRST; + while (RTC->MODE2.STATUS.bit.SYNCBUSY) { + } + RTC->MODE2.CTRL.reg = + RTC_MODE2_CTRL_MODE_CLOCK | + RTC_MODE2_CTRL_PRESCALER_DIV1024 | + RTC_MODE2_CTRL_ENABLE; + while (RTC->MODE2.STATUS.bit.SYNCBUSY) { + } + } + + #elif defined(MCU_SAMD51) + + if (RTC->MODE2.CTRLA.bit.ENABLE == 0 || force) { + RTC->MODE2.CTRLA.reg = RTC_MODE2_CTRLA_SWRST; + while (RTC->MODE2.SYNCBUSY.bit.SWRST) { + } + RTC->MODE2.CTRLA.reg = + RTC_MODE2_CTRLA_MODE_CLOCK | + RTC_MODE2_CTRLA_CLOCKSYNC | + RTC_MODE2_CTRLA_PRESCALER_DIV1024 | + RTC_MODE2_CTRLA_ENABLE; + while (RTC->MODE2.SYNCBUSY.bit.ENABLE) { + } + } + #endif +} + +// Get the time from the RTC and put it into a tm struct. +void rtc_gettime(timeutils_struct_time_t *tm) { + tm->tm_year = RTC->MODE2.CLOCK.bit.YEAR + 2000; + tm->tm_mon = RTC->MODE2.CLOCK.bit.MONTH; + tm->tm_mday = RTC->MODE2.CLOCK.bit.DAY; + tm->tm_hour = RTC->MODE2.CLOCK.bit.HOUR; + tm->tm_min = RTC->MODE2.CLOCK.bit.MINUTE; + tm->tm_sec = RTC->MODE2.CLOCK.bit.SECOND; +} + +STATIC mp_obj_t machine_rtc_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { + // Check arguments. + mp_arg_check_num(n_args, n_kw, 0, 0, false); + // RTC was already started at boot time. So nothing to do here. + // Return constant object. + return (mp_obj_t)&machine_rtc_obj; +} + +STATIC mp_obj_t machine_rtc_datetime_helper(size_t n_args, const mp_obj_t *args) { + // Rtc *rtc = RTC; + if (n_args == 1) { + // Get date and time. + timeutils_struct_time_t tm; + rtc_gettime(&tm); + + mp_obj_t tuple[8] = { + mp_obj_new_int(tm.tm_year), + mp_obj_new_int(tm.tm_mon), + mp_obj_new_int(tm.tm_mday), + mp_obj_new_int(timeutils_calc_weekday(tm.tm_year, tm.tm_mon, tm.tm_mday)), + mp_obj_new_int(tm.tm_hour), + mp_obj_new_int(tm.tm_min), + mp_obj_new_int(tm.tm_sec), + mp_obj_new_int(0), + }; + return mp_obj_new_tuple(8, tuple); + } else { + // Set date and time. + mp_obj_t *items; + mp_obj_get_array_fixed_n(args[1], 8, &items); + + uint32_t date = + RTC_MODE2_CLOCK_YEAR(mp_obj_get_int(items[0]) % 100) | + RTC_MODE2_CLOCK_MONTH(mp_obj_get_int(items[1])) | + RTC_MODE2_CLOCK_DAY(mp_obj_get_int(items[2])) | + RTC_MODE2_CLOCK_HOUR(mp_obj_get_int(items[4])) | + RTC_MODE2_CLOCK_MINUTE(mp_obj_get_int(items[5])) | + RTC_MODE2_CLOCK_SECOND(mp_obj_get_int(items[6])); + + RTC->MODE2.CLOCK.reg = date; + #if defined(MCU_SAMD21) + while (RTC->MODE2.STATUS.bit.SYNCBUSY) { + } + #elif defined(MCU_SAMD51) + while (RTC->MODE2.SYNCBUSY.bit.CLOCKSYNC) { + } + #endif + + return mp_const_none; + } +} + +STATIC mp_obj_t machine_rtc_datetime(mp_uint_t n_args, const mp_obj_t *args) { + return machine_rtc_datetime_helper(n_args, args); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_rtc_datetime_obj, 1, 2, machine_rtc_datetime); + +STATIC mp_obj_t machine_rtc_init(mp_obj_t self_in, mp_obj_t date) { + mp_obj_t args[2] = {self_in, date}; + machine_rtc_datetime_helper(2, args); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_rtc_init_obj, machine_rtc_init); + +// calibration(cal) +// When the argument is a number in the range [-16 to 15], set the calibration value. +STATIC mp_obj_t machine_rtc_calibration(mp_obj_t self_in, mp_obj_t cal_in) { + int8_t cal = 0; + // Make it negative for a "natural" behavior: + // value > 0: faster, value < 0: slower + cal = -mp_obj_get_int(cal_in); + RTC->MODE2.FREQCORR.reg = (uint8_t)cal; + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(machine_rtc_calibration_obj, machine_rtc_calibration); + +STATIC const mp_rom_map_elem_t machine_rtc_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_rtc_init_obj) }, + { MP_ROM_QSTR(MP_QSTR_datetime), MP_ROM_PTR(&machine_rtc_datetime_obj) }, + { MP_ROM_QSTR(MP_QSTR_calibration), MP_ROM_PTR(&machine_rtc_calibration_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(machine_rtc_locals_dict, machine_rtc_locals_dict_table); + +MP_DEFINE_CONST_OBJ_TYPE( + machine_rtc_type, + MP_QSTR_RTC, + MP_TYPE_FLAG_NONE, + make_new, machine_rtc_make_new, + locals_dict, &machine_rtc_locals_dict + ); + +#endif // MICROPY_PY_MACHINE_RTC diff --git a/ports/samd/mcu/samd21/clock_config.c b/ports/samd/mcu/samd21/clock_config.c index a195cb692053..00f743cc49d1 100644 --- a/ports/samd/mcu/samd21/clock_config.c +++ b/ports/samd/mcu/samd21/clock_config.c @@ -141,7 +141,7 @@ void init_clocks(uint32_t cpu_freq) { // GCLK3: 1Mhz for the us-counter (TC4/TC5) // GCLK4: 32kHz from crystal, if present // GCLK5: 48MHz from DFLL for USB - // GCLK8: 1kHz clock for WDT + // GCLK8: 1kHz clock for WDT and RTC NVMCTRL->CTRLB.bit.MANW = 1; // errata "Spurious Writes" NVMCTRL->CTRLB.bit.RWS = 1; // 1 read wait state for 48MHz @@ -203,6 +203,11 @@ void init_clocks(uint32_t cpu_freq) { SYSCTRL_DFLLCTRL_BPLCKC | SYSCTRL_DFLLCTRL_ENABLE; while (SYSCTRL->PCLKSR.bit.DFLLLCKF == 0) { } + // Set GCLK8 to 1 kHz. + GCLK->GENDIV.reg = GCLK_GENDIV_ID(8) | GCLK_GENDIV_DIV(32); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_XOSC32K | GCLK_GENCTRL_ID(8); + while (GCLK->STATUS.bit.SYNCBUSY) { + } #else // MICROPY_HW_XOSC32K @@ -242,6 +247,11 @@ void init_clocks(uint32_t cpu_freq) { GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(1); while (GCLK->STATUS.bit.SYNCBUSY) { } + // Set GCLK8 to 1 kHz. + GCLK->GENDIV.reg = GCLK_GENDIV_ID(8) | GCLK_GENDIV_DIV(32); + GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(8); + while (GCLK->STATUS.bit.SYNCBUSY) { + } #endif // MICROPY_HW_XOSC32K @@ -252,11 +262,6 @@ void init_clocks(uint32_t cpu_freq) { GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL48M | GCLK_GENCTRL_ID(3); while (GCLK->STATUS.bit.SYNCBUSY) { } - // Set GCLK8 to 1 kHz. - GCLK->GENDIV.reg = GCLK_GENDIV_ID(8) | GCLK_GENDIV_DIV(32); - GCLK->GENCTRL.reg = GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_OSCULP32K | GCLK_GENCTRL_ID(8); - while (GCLK->STATUS.bit.SYNCBUSY) { - } } void enable_sercom_clock(int id) { diff --git a/ports/samd/mcu/samd21/mpconfigmcu.h b/ports/samd/mcu/samd21/mpconfigmcu.h index 8d155f93ab03..331df8e2130a 100644 --- a/ports/samd/mcu/samd21/mpconfigmcu.h +++ b/ports/samd/mcu/samd21/mpconfigmcu.h @@ -28,6 +28,12 @@ #define MICROPY_HW_UART_TXBUF (1) #endif +#ifndef MICROPY_PY_MACHINE_RTC +#if MICROPY_HW_XOSC32K +#define MICROPY_PY_MACHINE_RTC (1) +#endif +#endif + #define CPU_FREQ (48000000) #define DFLL48M_FREQ (48000000) #define MAX_CPU_FREQ (48000000) diff --git a/ports/samd/mcu/samd51/clock_config.c b/ports/samd/mcu/samd51/clock_config.c index 3bc4616de9e9..c5f508cae8b6 100644 --- a/ports/samd/mcu/samd51/clock_config.c +++ b/ports/samd/mcu/samd51/clock_config.c @@ -215,16 +215,19 @@ void init_clocks(uint32_t cpu_freq) { #if MICROPY_HW_XOSC32K // OSCILLATOR CONTROL + // Enable the clock for RTC + OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL_XOSC1K; // Setup XOSC32K OSC32KCTRL->INTFLAG.reg = OSC32KCTRL_INTFLAG_XOSC32KRDY | OSC32KCTRL_INTFLAG_XOSC32KFAIL; - OSC32KCTRL->XOSC32K.bit.CGM = OSC32KCTRL_XOSC32K_CGM_HS_Val; - OSC32KCTRL->XOSC32K.bit.XTALEN = 1; // 0: Generator 1: Crystal - OSC32KCTRL->XOSC32K.bit.EN32K = 1; - OSC32KCTRL->XOSC32K.bit.ONDEMAND = 0; - OSC32KCTRL->XOSC32K.bit.RUNSTDBY = 1; - OSC32KCTRL->XOSC32K.bit.STARTUP = 4; OSC32KCTRL->CFDCTRL.bit.CFDEN = 1; // Fall back to internal Osc on crystal fail - OSC32KCTRL->XOSC32K.bit.ENABLE = 1; + OSC32KCTRL->XOSC32K.reg = + OSC32KCTRL_XOSC32K_CGM_HS | + OSC32KCTRL_XOSC32K_XTALEN | + OSC32KCTRL_XOSC32K_EN32K | + OSC32KCTRL_XOSC32K_EN1K | + OSC32KCTRL_XOSC32K_RUNSTDBY | + OSC32KCTRL_XOSC32K_STARTUP(4) | + OSC32KCTRL_XOSC32K_ENABLE; // make sure osc32kcrtl is ready while (OSC32KCTRL->STATUS.bit.XOSC32KRDY == 0) { } @@ -270,6 +273,9 @@ void init_clocks(uint32_t cpu_freq) { #else // MICROPY_HW_XOSC32K + // Enable the clock for RTC + OSC32KCTRL->RTCCTRL.reg = OSC32KCTRL_RTCCTRL_RTCSEL_ULP1K; + // Derive GCLK1 from DFLL48M at DPLL0_REF_FREQ as defined in mpconfigboard.h (e.g. 32768 Hz) GCLK->GENCTRL[1].reg = ((DFLL48M_FREQ + DPLLx_REF_FREQ / 2) / DPLLx_REF_FREQ) << GCLK_GENCTRL_DIV_Pos | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 666370c983b7..541fba9009f3 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -28,6 +28,12 @@ #define MICROPY_PY_URANDOM_SEED_INIT_FUNC (trng_random_u32()) unsigned long trng_random_u32(void); +#ifndef MICROPY_PY_MACHINE_RTC +#if MICROPY_HW_XOSC32K +#define MICROPY_PY_MACHINE_RTC (1) +#endif +#endif + // Due to a limitation in the TC counter for us, the ticks period is 2**29 #define MICROPY_PY_UTIME_TICKS_PERIOD (0x20000000) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index ce5fef76f753..12e9f7c34134 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -238,6 +238,9 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_Timer), MP_ROM_PTR(&machine_timer_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&machine_uart_type) }, { MP_ROM_QSTR(MP_QSTR_WDT), MP_ROM_PTR(&machine_wdt_type) }, + #if MICROPY_PY_MACHINE_RTC + { MP_ROM_QSTR(MP_QSTR_RTC), MP_ROM_PTR(&machine_rtc_type) }, + #endif { MP_ROM_QSTR(MP_QSTR_idle), MP_ROM_PTR(&machine_idle_obj) }, { MP_ROM_QSTR(MP_QSTR_disable_irq), MP_ROM_PTR(&machine_disable_irq_obj) }, diff --git a/ports/samd/modmachine.h b/ports/samd/modmachine.h index e99ca990fbba..8f85e149896f 100644 --- a/ports/samd/modmachine.h +++ b/ports/samd/modmachine.h @@ -38,5 +38,8 @@ extern const mp_obj_type_t machine_spi_type; extern const mp_obj_type_t machine_timer_type; extern const mp_obj_type_t machine_uart_type; extern const mp_obj_type_t machine_wdt_type; +#if MICROPY_PY_MACHINE_RTC +extern const mp_obj_type_t machine_rtc_type; +#endif #endif // MICROPY_INCLUDED_SAMD_MODMACHINE_H diff --git a/ports/samd/modutime.c b/ports/samd/modutime.c index 4169c15d9117..6b04134497fb 100644 --- a/ports/samd/modutime.c +++ b/ports/samd/modutime.c @@ -29,20 +29,34 @@ #include "shared/timeutils/timeutils.h" #include "mphalport.h" +#if !MICROPY_PY_MACHINE_RTC uint32_t time_offset = 0; +#endif // !MICROPY_PY_MACHINE_RTC // localtime([secs]) STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { timeutils_struct_time_t tm; mp_int_t seconds; + + #if MICROPY_PY_MACHINE_RTC + extern void rtc_gettime(timeutils_struct_time_t *tm); + if (n_args == 0 || args[0] == mp_const_none) { + rtc_gettime(&tm); + } else { + seconds = mp_obj_get_int(args[0]); + timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + } + + #else if (n_args == 0 || args[0] == mp_const_none) { - // seconds = pyb_rtc_get_us_since_epoch() / 1000 / 1000; seconds = mp_hal_ticks_ms_64() / 1000 + time_offset; } else { seconds = mp_obj_get_int(args[0]); time_offset = seconds - mp_hal_ticks_ms_64() / 1000; } timeutils_seconds_since_epoch_to_struct_time(seconds, &tm); + + #endif // MICROPY_PY_MACHINE_RTC mp_obj_t tuple[8] = { tuple[0] = mp_obj_new_int(tm.tm_year), tuple[1] = mp_obj_new_int(tm.tm_mon), @@ -50,8 +64,8 @@ STATIC mp_obj_t time_localtime(size_t n_args, const mp_obj_t *args) { tuple[3] = mp_obj_new_int(tm.tm_hour), tuple[4] = mp_obj_new_int(tm.tm_min), tuple[5] = mp_obj_new_int(tm.tm_sec), - tuple[6] = mp_obj_new_int(tm.tm_wday), - tuple[7] = mp_obj_new_int(tm.tm_yday), + tuple[6] = mp_obj_new_int(timeutils_calc_weekday(tm.tm_year, tm.tm_mon, tm.tm_mday)), + tuple[7] = mp_obj_new_int(timeutils_year_day(tm.tm_year, tm.tm_mon, tm.tm_mday)), }; return mp_obj_new_tuple(8, tuple); } @@ -76,7 +90,17 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(time_mktime_obj, time_mktime); // time() STATIC mp_obj_t time_time(void) { + #if MICROPY_PY_MACHINE_RTC + extern void rtc_gettime(timeutils_struct_time_t *tm); + timeutils_struct_time_t tm; + rtc_gettime(&tm); + return mp_obj_new_int_from_uint(timeutils_mktime( + tm.tm_year, tm.tm_mon, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec)); + + #else return mp_obj_new_int_from_uint(mp_hal_ticks_ms_64() / 1000 + time_offset); + + #endif // MICROPY_PY_MACHINE_RTC } STATIC MP_DEFINE_CONST_FUN_OBJ_0(time_time_obj, time_time); diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index 3608306e8719..bd3eea536fdb 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -38,6 +38,10 @@ #include "tusb.h" #include "mphalport.h" +#if MICROPY_PY_MACHINE_RTC +extern void machine_rtc_start(bool force); +#endif + static void usb_init(void) { // Init USB clock #if defined(MCU_SAMD21) @@ -114,4 +118,7 @@ void samd_init(void) { #if defined(MCU_SAMD51) mp_hal_ticks_cpu_enable(); #endif + #if MICROPY_PY_MACHINE_RTC + machine_rtc_start(false); + #endif } From e33db80a5958772095a16b0b3753ed3aa72d07b2 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 29 Sep 2022 16:13:23 +0200 Subject: [PATCH 033/823] samd/clock_config: Extend the SAMD51 us-counter to 60 bit. This removes the difference in the time.ticks_us() range between SAMD21 and SAMD51. The function mp_hal_ticks_us_64() is added and used for: - SAMD51's mp_hal_ticks_us and mp_hal_delay_us(). For SAMD21, keep the previous methods, which are faster. - mp_hal_ticks_ms() and mp_hal_tick_ms_64(), which saves some bytes and removes a potential race condition every 50 days. Also set the us-counter for SAMD51 to 16 MHz for a faster reading of the microsecond value. Note: With SAMD51, mp_hal_ticks_us_64() has a 60 bit range only, which is still a long time (~36000 years). --- ports/samd/mcu/samd51/clock_config.c | 8 +++--- ports/samd/mcu/samd51/mpconfigmcu.h | 3 --- ports/samd/mphalport.c | 40 ++++++++++++++++++++++++---- ports/samd/mphalport.h | 23 +++++----------- ports/samd/samd_isr.c | 24 ++++++++++++----- ports/samd/samd_soc.c | 8 +++++- 6 files changed, 71 insertions(+), 35 deletions(-) diff --git a/ports/samd/mcu/samd51/clock_config.c b/ports/samd/mcu/samd51/clock_config.c index c5f508cae8b6..b55419d24b67 100644 --- a/ports/samd/mcu/samd51/clock_config.c +++ b/ports/samd/mcu/samd51/clock_config.c @@ -190,7 +190,7 @@ void init_clocks(uint32_t cpu_freq) { // GCLK0: 48MHz from DFLL48M or 48 - 200 MHz from DPLL0 (SAMD51) // GCLK1: 32768 Hz from 32KULP or DFLL48M // GCLK2: 8-48MHz from DFLL48M for Peripheral devices - // GCLK3: 8Mhz for the us-counter (TC0/TC1) + // GCLK3: 16Mhz for the us-counter (TC0/TC1) // GCLK4: 32kHz from crystal, if present // GCLK5: 48MHz from DFLL48M for USB // DPLL0: 48 - 200 MHz @@ -204,7 +204,7 @@ void init_clocks(uint32_t cpu_freq) { // Setup DPLL0 to 120MHz // Setup GCLK0 to 120MHz // Setup GCLK2 to 48MHz for Peripherals - // Setup GCLK3 to 8MHz for TC0/TC1 + // Setup GCLK3 to 16MHz for TC0/TC1 // Setup GCLK4 to 32kHz crystal, if present // Setup GCLK5 to 48 MHz @@ -320,8 +320,8 @@ void init_clocks(uint32_t cpu_freq) { while (GCLK->SYNCBUSY.bit.GENCTRL2) { } - // Setup GCLK3 for 8MHz, Used for TC0/1 counter - GCLK->GENCTRL[3].reg = GCLK_GENCTRL_DIV(6) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; + // Setup GCLK3 for 16MHz, Used for TC0/1 counter + GCLK->GENCTRL[3].reg = GCLK_GENCTRL_DIV(3) | GCLK_GENCTRL_RUNSTDBY | GCLK_GENCTRL_GENEN | GCLK_GENCTRL_SRC_DFLL; while (GCLK->SYNCBUSY.bit.GENCTRL3) { } } diff --git a/ports/samd/mcu/samd51/mpconfigmcu.h b/ports/samd/mcu/samd51/mpconfigmcu.h index 541fba9009f3..16b2c101f02b 100644 --- a/ports/samd/mcu/samd51/mpconfigmcu.h +++ b/ports/samd/mcu/samd51/mpconfigmcu.h @@ -34,9 +34,6 @@ unsigned long trng_random_u32(void); #endif #endif -// Due to a limitation in the TC counter for us, the ticks period is 2**29 -#define MICROPY_PY_UTIME_TICKS_PERIOD (0x20000000) - // fatfs configuration used in ffconf.h #define MICROPY_FATFS_ENABLE_LFN (1) #define MICROPY_FATFS_RPATH (2) diff --git a/ports/samd/mphalport.c b/ports/samd/mphalport.c index ad3817768f77..b60bada480b4 100644 --- a/ports/samd/mphalport.c +++ b/ports/samd/mphalport.c @@ -37,6 +37,8 @@ #define MICROPY_HW_STDIN_BUFFER_LEN 128 #endif +extern volatile uint32_t ticks_us64_upper; + STATIC uint8_t stdin_ringbuf_array[MICROPY_HW_STDIN_BUFFER_LEN]; ringbuf_t stdin_ringbuf = { stdin_ringbuf_array, sizeof(stdin_ringbuf_array), 0, 0 }; @@ -111,19 +113,47 @@ void mp_hal_delay_ms(mp_uint_t ms) { void mp_hal_delay_us(mp_uint_t us) { if (us > 0) { - uint32_t start = mp_hal_ticks_us(); #if defined(MCU_SAMD21) - // SAMD21 counter has effective 32 bit width + uint32_t start = mp_hal_ticks_us(); while ((mp_hal_ticks_us() - start) < us) { } - #elif defined(MCU_SAMD51) - // SAMD51 counter has effective 29 bit width - while (((mp_hal_ticks_us() - start) & (MICROPY_PY_UTIME_TICKS_PERIOD - 1)) < us) { + #else + uint64_t stop = mp_hal_ticks_us_64() + us; + while (mp_hal_ticks_us_64() < stop) { } #endif } } +uint64_t mp_hal_ticks_us_64(void) { + uint32_t us64_upper = ticks_us64_upper; + uint32_t us64_lower; + uint8_t intflag; + __disable_irq(); + #if defined(MCU_SAMD21) + us64_lower = REG_TC4_COUNT32_COUNT; + intflag = TC4->COUNT32.INTFLAG.reg; + #elif defined(MCU_SAMD51) + TC0->COUNT32.CTRLBSET.reg = TC_CTRLBSET_CMD_READSYNC; + while (TC0->COUNT32.CTRLBSET.reg != 0) { + } + us64_lower = REG_TC0_COUNT32_COUNT; + intflag = TC0->COUNT32.INTFLAG.reg; + #endif + __enable_irq(); + if ((intflag & TC_INTFLAG_OVF) && us64_lower < 0x10000000) { + // The timer counter overflowed before reading it but the IRQ handler + // has not yet been called, so perform the IRQ arithmetic now. + us64_upper++; + } + #if defined(MCU_SAMD21) + return ((uint64_t)us64_upper << 32) | us64_lower; + #elif defined(MCU_SAMD51) + return ((uint64_t)us64_upper << 28) | (us64_lower >> 4); + #endif + +} + uintptr_t mp_hal_stdio_poll(uintptr_t poll_flags) { uintptr_t ret = 0; diff --git a/ports/samd/mphalport.h b/ports/samd/mphalport.h index 3879240f9a6a..2cbb3333f60f 100644 --- a/ports/samd/mphalport.h +++ b/ports/samd/mphalport.h @@ -37,7 +37,7 @@ extern int mp_interrupt_char; extern volatile uint32_t systick_ms; -extern volatile uint32_t systick_ms_upper; +uint64_t mp_hal_ticks_us_64(void); void mp_hal_set_interrupt_char(int c); @@ -47,28 +47,19 @@ void mp_hal_set_interrupt_char(int c); #define mp_hal_delay_us_fast mp_hal_delay_us -static inline mp_uint_t mp_hal_ticks_ms(void) { - return systick_ms; +static inline uint64_t mp_hal_ticks_ms_64(void) { + return mp_hal_ticks_us_64() / 1000; } -static inline uint64_t mp_hal_ticks_ms_64(void) { - return ((uint64_t)systick_ms_upper << 32) + systick_ms; +static inline mp_uint_t mp_hal_ticks_ms(void) { + return (mp_uint_t)mp_hal_ticks_ms_64(); } static inline mp_uint_t mp_hal_ticks_us(void) { #if defined(MCU_SAMD21) - return REG_TC4_COUNT32_COUNT; - - #elif defined(MCU_SAMD51) - - TC0->COUNT32.CTRLBSET.reg = TC_CTRLBSET_CMD_READSYNC; - while (TC0->COUNT32.CTRLBSET.reg != 0) { - } - return REG_TC0_COUNT32_COUNT >> 3; - #else - return systick_ms * 1000; + return (mp_uint_t)mp_hal_ticks_us_64(); #endif } @@ -89,7 +80,7 @@ static inline mp_uint_t mp_hal_ticks_cpu(void) { #endif static inline uint64_t mp_hal_time_ns(void) { - return mp_hal_ticks_ms_64() * 1000000; + return mp_hal_ticks_us_64() * 1000; } // C-level pin HAL diff --git a/ports/samd/samd_isr.c b/ports/samd/samd_isr.c index b507d5d1aa73..1d6febaaa226 100644 --- a/ports/samd/samd_isr.c +++ b/ports/samd/samd_isr.c @@ -41,7 +41,7 @@ extern void EIC_Handler(void); const ISR isr_vector[]; volatile uint32_t systick_ms; -volatile uint32_t systick_ms_upper; +volatile uint32_t ticks_us64_upper; void Reset_Handler(void) __attribute__((naked)); void Reset_Handler(void) { @@ -93,15 +93,27 @@ void Default_Handler(void) { void SysTick_Handler(void) { uint32_t next_tick = systick_ms + 1; systick_ms = next_tick; - if (systick_ms == 0) { - systick_ms_upper += 1; - } if (soft_timer_next == next_tick) { pendsv_schedule_dispatch(PENDSV_DISPATCH_SOFT_TIMER, soft_timer_handler); } } +void us_timer_IRQ(void) { + #if defined(MCU_SAMD21) + if (TC4->COUNT32.INTFLAG.reg & TC_INTFLAG_OVF) { + ticks_us64_upper++; + } + TC4->COUNT32.INTFLAG.reg = TC_INTFLAG_OVF; + #elif defined(MCU_SAMD51) + if (TC0->COUNT32.INTFLAG.reg & TC_INTFLAG_OVF) { + ticks_us64_upper++; + } + TC0->COUNT32.INTFLAG.reg = TC_INTFLAG_OVF; + #endif +} + +// Sercom IRQ handler support void (*sercom_irq_handler_table[SERCOM_INST_NUM])(int num) = {}; void sercom_register_irq(int sercom_id, void (*sercom_irq_handler)) { @@ -180,7 +192,7 @@ const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { 0, // 16 Timer Counter Control 1 (TCC1) 0, // 17 Timer Counter Control 2 (TCC2) 0, // 18 Basic Timer Counter 3 (TC3) - 0, // 19 Basic Timer Counter 4 (TC4) + &us_timer_IRQ, // 19 Basic Timer Counter 4 (TC4) 0, // 20 Basic Timer Counter 5 (TC5) 0, // 21 Basic Timer Counter 6 (TC6) 0, // 22 Basic Timer Counter 7 (TC7) @@ -316,7 +328,7 @@ const ISR isr_vector[] __attribute__((section(".isr_vector"))) = { 0, // 104 Timer Counter Control 4 (TCC4): TCC4_CNT_A ... 0, // 105 Timer Counter Control 4 (TCC4): TCC4_MC_0 0, // 106 Timer Counter Control 4 (TCC4): TCC4_MC_1 - 0, // 107 Basic Timer Counter 0 (TC0) + &us_timer_IRQ, // 107 Basic Timer Counter 0 (TC0) 0, // 108 Basic Timer Counter 1 (TC1) 0, // 109 Basic Timer Counter 2 (TC2) 0, // 110 Basic Timer Counter 3 (TC3) diff --git a/ports/samd/samd_soc.c b/ports/samd/samd_soc.c index bd3eea536fdb..a81e7c68810d 100644 --- a/ports/samd/samd_soc.c +++ b/ports/samd/samd_soc.c @@ -68,7 +68,7 @@ static void usb_init(void) { tusb_init(); } -// Initialize the microsecond counter on TC 0/1 +// Initialize the µs counter on TC 0/1 or TC4/5 void init_us_counter(void) { #if defined(MCU_SAMD21) @@ -89,6 +89,9 @@ void init_us_counter(void) { TC4->COUNT32.READREQ.reg = TC_READREQ_RREQ | TC_READREQ_RCONT | 0x10; while (TC4->COUNT32.STATUS.bit.SYNCBUSY) { } + // Enable the IRQ + TC4->COUNT32.INTENSET.reg = TC_INTENSET_OVF; + NVIC_EnableIRQ(TC4_IRQn); #elif defined(MCU_SAMD51) @@ -107,6 +110,9 @@ void init_us_counter(void) { while (TC0->COUNT32.SYNCBUSY.bit.ENABLE) { } + // Enable the IRQ + TC0->COUNT32.INTENSET.reg = TC_INTENSET_OVF; + NVIC_EnableIRQ(TC0_IRQn); #endif } From e7aa9700cad4ace55fb9100c98b2bbfa811870d2 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 4 Oct 2022 13:39:43 +0200 Subject: [PATCH 034/823] samd/boards/SEEED_WIO_TERMINAL: Declare more pins for SEEED WIO board. Defining all pins from the external 40 Pin connector, and some internal pins like the one for SD and LCD. --- ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv index 72da71224a4d..9bc57070d198 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv @@ -22,9 +22,43 @@ PIN_PD20,SWITCH_U PIN_PC26,BUTTON_1 PIN_PC27,BUTTON_2 PIN_PC28,BUTTON_3 -PIN_PD11,BUZZER_CTR PIN_PC14,5V_ENABLE PIN_PC15,3V3_ENABLE +PIN_PB26,TX +PIN_PB27,RX +PIN_PA13,SDA0 +PIN_PA12,SCL0 +PIN_PA17,SDA1 +PIN_PA16,SCL1 +PIN_PB02,MOSI +PIN_PB00,MISO +PIN_PB03,SCK +PIN_PB01,CS +PIN_PB15,GPCLK0 +PIN_PB12,GPCLK1 +PIN_PB13,GPCLK2 +PIN_PC16,SD_MOSI +PIN_PC18,SD_MISO +PIN_PC17,SD_SCK +PIN_PC19,SD_CS +PIN_PD21,SD_DET +PIN_PA20,I2S_LRCLK +PIN_PA21,I2S_SDIN +PIN_PA22,I2S_SDOUT +PIN_PB16,I2C_BCLK +PIN_PB18,LCD_MISO +PIN_PB19,LCD_MOSI +PIN_PB20,LCD_SCK +PIN_PB21,LCD_CS +PIN_PC05,LCD_BACKLIGHT +PIN_PC06,LCD_D/C +PIN_PC07,LCD_RESET +PIN_PC10,LCD_XL +PIN_PC11,LCD_YU +PIN_PC12,LCD_XR +PIN_PC13,LCD_YD +PIN_PC30,MIC +PIN_PD11,BUZZER LED_PA15,LED_BLUE LED_PC05,LED_LCD From e5cf3fab951adf39e7d39c2a8600b61f84e517ce Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 17 Sep 2022 17:27:27 +0200 Subject: [PATCH 035/823] samd/machine_pin: Change the pin handling and naming/numbering. Pin numbers are now the MCU port numbers in the range: PA0..PA31: 0..31 PB0..PB31: 32..63 PC0..PC31: 64..95 PD0..PD31: 96..127 Pins can be denoted by the GPIO port number, the name as defined in pins.csv or a string in the form Pxnn, like "PA16" or "PD03". The pins.c and pins.h files are now obsolete. The pin objects are part of the AF table. As result of a simplification, the code now supports using pin names or numbers instead of pin objects for modules like UART, SPI, PWM, I2C, ADC, pininfo. --- ports/samd/Makefile | 26 +--- .../{make-pin-af.py => make-pin-table.py} | 54 +++++++- ports/samd/boards/make-pins.py | 128 ------------------ ports/samd/machine_adc.c | 4 +- ports/samd/machine_led.c | 27 ++-- ports/samd/machine_pin.c | 119 ++++++---------- ports/samd/machine_pwm.c | 4 +- ports/samd/modsamd.c | 60 ++++---- ports/samd/pin_af.c | 69 +++++++++- ports/samd/pin_af.h | 18 ++- 10 files changed, 221 insertions(+), 288 deletions(-) rename ports/samd/boards/{make-pin-af.py => make-pin-table.py} (59%) delete mode 100644 ports/samd/boards/make-pins.py diff --git a/ports/samd/Makefile b/ports/samd/Makefile index e6e592a0352b..f512fb0b2e33 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -47,14 +47,10 @@ INC += -I$(TOP)/lib/asf4/$(MCU_SERIES_LOWER)/include INC += -I$(TOP)/lib/asf4/$(MCU_SERIES_LOWER)/include/pio INC += -I$(TOP)/lib/tinyusb/src -MAKE_PIN_AF = boards/make-pin-af.py +MAKE_PIN_AF = boards/make-pin-table.py PIN_AF_TABLE_CSV = mcu/$(MCU_SERIES_LOWER)/pin-af-table.csv -GEN_PIN_AF = pin_af_table.c - -MAKE_PINS = boards/make-pins.py BOARD_PINS = $(BOARD_DIR)/pins.csv -GEN_PINS_SRC = $(BUILD)/pins.c -GEN_PINS_HDR = $(BUILD)/pins.h +GEN_PIN_AF = pin_af_table.c CFLAGS_MCU_SAMD21 = -mtune=cortex-m0plus -mcpu=cortex-m0plus -msoft-float CFLAGS_MCU_SAMD51 = -mtune=cortex-m4 -mcpu=cortex-m4 -mfpu=fpv4-sp-d16 -mfloat-abi=hard @@ -150,7 +146,7 @@ DRIVERS_SRC_C += \ drivers/bus/softspi.c \ # List of sources for qstr extraction -SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(SRC_CXX) $(GEN_PINS_SRC) +SRC_QSTR += $(SRC_C) $(SHARED_SRC_C) $(SRC_CXX) OBJ += $(PY_O) OBJ += $(addprefix $(BUILD)/, $(SRC_C:.c=.o)) @@ -161,7 +157,6 @@ OBJ += $(addprefix $(BUILD)/, $(ASF4_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(LIBM_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(DRIVERS_SRC_C:.c=.o)) OBJ += $(addprefix $(BUILD)/, $(TINYUSB_SRC_C:.c=.o)) -OBJ += $(GEN_PINS_SRC:.c=.o) ifneq ($(FROZEN_MANIFEST),) CFLAGS += -DMICROPY_MODULE_FROZEN_MPY @@ -182,19 +177,10 @@ $(BUILD)/firmware.bin: $(BUILD)/firmware.elf $(BUILD)/firmware.uf2: $(BUILD)/firmware.bin $(Q)$(PYTHON) $(UF2CONV) -b $(TEXT0) -c -o $@ $< -pin_af.c: $(BUILD)/$(GEN_PIN_AF) +pin_af.c: $(BUILD)/$(GEN_PIN_AF) | $(HEADER_BUILD) -$(BUILD)/$(GEN_PIN_AF): $(PIN_AF_TABLE_CSV) | $(HEADER_BUILD) +$(BUILD)/$(GEN_PIN_AF): $(PIN_AF_TABLE_CSV) $(BOARD_PINS) | $(HEADER_BUILD) $(ECHO) "Create $@" - $(Q)$(PYTHON) $(MAKE_PIN_AF) --csv $(PIN_AF_TABLE_CSV) --table $(BUILD)/$(GEN_PIN_AF) --mcu $(MCU_SERIES) - -machine_led.c machine_pin.c modsamd.c: $(GEN_PINS_HDR) - -$(GEN_PINS_SRC) $(GEN_PINS_HDR): $(BOARD_PINS) | $(HEADER_BUILD) - $(ECHO) "Create $@" - $(Q)$(PYTHON) $(MAKE_PINS) --board $(BOARD_PINS) --pins $(GEN_PINS_SRC) --inc $(GEN_PINS_HDR) - -$(GEN_PINS_SRC:.c=.o): $(GEN_PINS_SRC) - $(call compile_c) + $(Q)$(PYTHON) $(MAKE_PIN_AF) --csv $(PIN_AF_TABLE_CSV) --board $(BOARD_PINS) --table $(BUILD)/$(GEN_PIN_AF) --mcu $(MCU_SERIES) include $(TOP)/py/mkrules.mk diff --git a/ports/samd/boards/make-pin-af.py b/ports/samd/boards/make-pin-table.py similarity index 59% rename from ports/samd/boards/make-pin-af.py rename to ports/samd/boards/make-pin-table.py index d895ef9dd99a..327478568afa 100644 --- a/ports/samd/boards/make-pin-af.py +++ b/ports/samd/boards/make-pin-table.py @@ -16,6 +16,7 @@ class Pins: def __init__(self): self.board_pins = [] # list of pin objects + self.pin_names = {} def parse_csv_file(self, filename): with open(filename, "r") as csvfile: @@ -25,17 +26,43 @@ def parse_csv_file(self, filename): if len(row) > 0 and row[0].strip().upper()[:2] in ("PA", "PB", "PC", "PD"): self.board_pins.append(row) + def parse_pin_file(self, filename): + with open(filename, "r") as csvfile: + rows = csv.reader(csvfile, skipinitialspace=True) + for row in rows: + # Pin numbers must start with "PIN_" + # LED numbers must start with "LED_" + if len(row) > 0: + # for compatibility, map LED_ to PIN_ + if row[0].startswith("LED_"): + row[0] = "PIN_" + row[0][4:] + if len(row) == 1: + self.pin_names[row[0]] = (row[0][4:], "{&machine_led_type}") + else: + self.pin_names[row[0]] = (row[1], "{&machine_led_type}") + elif row[0].startswith("PIN_"): + if len(row) == 1: + self.pin_names[row[0]] = (row[0][4:], "{&machine_pin_type}") + else: + self.pin_names[row[0]] = (row[1], "{&machine_pin_type}") + def print_table(self, table_filename, mcu_name): with open(table_filename, "wt") as table_file: table_file.write(table_header) - table_file.write("const pin_af_t pin_af_table[] = {\n") + table_file.write("const machine_pin_obj_t pin_af_table[] = {\n") if mcu_name == "SAMD21": for row in self.board_pins: pin = "PIN_" + row[0].upper() table_file.write(" #ifdef " + pin + "\n") eic = row[1] if row[1] else "0xff" adc = row[2] if row[2] else "0xff" - table_file.write(" {%s, %s, %s" % (pin, eic, adc)) + if pin in self.pin_names: + name = '"%s"' % self.pin_names[pin][0] + type = self.pin_names[pin][1] + else: + name = '"-"' + type = "{&machine_pin_type}" + table_file.write(" {%s, %s, %s, %s, %s" % (type, pin, name, eic, adc)) for cell in row[3:]: if cell: table_file.write( @@ -52,7 +79,15 @@ def print_table(self, table_filename, mcu_name): eic = row[1] if row[1] else "0xff" adc0 = row[2] if row[2] else "0xff" adc1 = row[3] if row[3] else "0xff" - table_file.write(" {%s, %s, %s, %s" % (pin, eic, adc0, adc1)) + if pin in self.pin_names: + name = '"%s"' % self.pin_names[pin][0] + type = self.pin_names[pin][1] + else: + name = '"-"' + type = "{&machine_pin_type}" + table_file.write( + " {%s, %s, %s, %s, %s, %s" % (type, pin, name, eic, adc0, adc1) + ) for cell in row[4:]: if cell: table_file.write( @@ -67,7 +102,7 @@ def print_table(self, table_filename, mcu_name): def main(): parser = argparse.ArgumentParser( - prog="make-pin-cap.py", + prog="make-pin-af.py", usage="%(prog)s [options] [command]", description="Generate MCU-specific pin cap table file", ) @@ -75,7 +110,13 @@ def main(): "-c", "--csv", dest="csv_filename", - help="Specifies the pin-mux-xxxx.csv filename", + help="Specifies the pin-af-table.csv filename", + ) + parser.add_argument( + "-b", + "--board", + dest="pin_filename", + help="Specifies the pins.csv filename", ) parser.add_argument( "-t", @@ -96,6 +137,9 @@ def main(): if args.csv_filename: pins.parse_csv_file(args.csv_filename) + if args.pin_filename: + pins.parse_pin_file(args.pin_filename) + if args.table_filename: pins.print_table(args.table_filename, args.mcu_name) diff --git a/ports/samd/boards/make-pins.py b/ports/samd/boards/make-pins.py deleted file mode 100644 index 679e2c9d192c..000000000000 --- a/ports/samd/boards/make-pins.py +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env python -"""Generates the pins file for the SAMD port.""" - -from __future__ import print_function - -import argparse -import sys -import csv - -pins_header_prefix = """// This file was automatically generated by make-pins.py -// -typedef struct _machine_pin_obj_t { - mp_obj_base_t base; - uint32_t id; - char *name; -} machine_pin_obj_t; - -int pin_find(mp_obj_t pin, const machine_pin_obj_t machine_pin_obj[], int table_size); - -""" - -led_header_prefix = """typedef struct _machine_led_obj_t { - mp_obj_base_t base; - uint32_t id; - char *name; -} machine_led_obj_t; - -""" - - -class Pins: - def __init__(self): - self.board_pins = [] # list of pin objects - self.board_leds = [] # list of led objects - - def parse_csv_file(self, filename): - with open(filename, "r") as csvfile: - rows = csv.reader(csvfile, skipinitialspace=True) - for row in rows: - # Pin numbers must start with "PIN_" - # LED numbers must start with "LED_" - if len(row) > 0: - if row[0].startswith("PIN_"): - if len(row) == 1: - self.board_pins.append([row[0], row[0][4:]]) - else: - self.board_pins.append([row[0], row[1]]) - elif row[0].startswith("LED_"): - self.board_leds.append(["PIN_" + row[0][4:], row[1]]) - elif row[0].startswith("-"): - self.board_pins.append(["-1", ""]) - - def print_pins(self, pins_filename): - with open(pins_filename, "wt") as pins_file: - pins_file.write("// This file was automatically generated by make-pins.py\n") - pins_file.write("//\n") - pins_file.write('#include "modmachine.h"\n') - pins_file.write('#include "sam.h"\n') - pins_file.write('#include "pins.h"\n\n') - - pins_file.write("const machine_pin_obj_t machine_pin_obj[] = {\n") - for pin in self.board_pins: - pins_file.write(" {{&machine_pin_type}, ") - pins_file.write(pin[0] + ', "' + pin[1]) - pins_file.write('"},\n') - pins_file.write("};\n") - - if self.board_leds: - pins_file.write("\nconst machine_led_obj_t machine_led_obj[] = {\n") - for pin in self.board_leds: - pins_file.write(" {{&machine_led_type}, ") - pins_file.write(pin[0] + ', "' + pin[1]) - pins_file.write('"},\n') - pins_file.write("};\n") - - def print_header(self, hdr_filename): - with open(hdr_filename, "wt") as hdr_file: - hdr_file.write(pins_header_prefix) - if self.board_leds: - hdr_file.write(led_header_prefix) - hdr_file.write( - "extern const machine_pin_obj_t machine_pin_obj[%d];\n" % len(self.board_pins) - ) - if self.board_leds: - hdr_file.write( - "extern const machine_led_obj_t machine_led_obj[%d];\n" % len(self.board_leds) - ) - - -def main(): - parser = argparse.ArgumentParser( - prog="make-pins.py", - usage="%(prog)s [options] [command]", - description="Generate board specific pin file", - ) - parser.add_argument( - "-b", - "--board", - dest="csv_filename", - help="Specifies the pins.csv filename", - ) - parser.add_argument( - "-p", - "--pins", - dest="pins_filename", - help="Specifies the name of the generated pins.c file", - ) - parser.add_argument( - "-i", - "--inc", - dest="hdr_filename", - help="Specifies name of generated pin header file", - ) - args = parser.parse_args(sys.argv[1:]) - - pins = Pins() - - if args.csv_filename: - pins.parse_csv_file(args.csv_filename) - - if args.pins_filename: - pins.print_pins(args.pins_filename) - - pins.print_header(args.hdr_filename) - - -if __name__ == "__main__": - main() diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index 97b6a14f17a4..efe0f041cdfa 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -66,8 +66,8 @@ STATIC void adc_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t k (void)kind; machine_adc_obj_t *self = MP_OBJ_TO_PTR(o); - mp_printf(print, "ADC(P%c%02u, ADC%u, channel=%u, bits=%u, average=%u)", - "ABCD"[self->id / 32], self->id % 32, self->adc_config.device, + mp_printf(print, "ADC(%s, ADC%u, channel=%u, bits=%u, average=%u)", + pin_name(self->id), self->adc_config.device, self->adc_config.channel, self->bits, 1 << self->avg); } diff --git a/ports/samd/machine_led.c b/ports/samd/machine_led.c index 54c2cb18bc86..c18bc052bd2f 100644 --- a/ports/samd/machine_led.c +++ b/ports/samd/machine_led.c @@ -30,7 +30,7 @@ #include "py/mphal.h" #include "extmod/virtpin.h" #include "modmachine.h" -#include "pins.h" +#include "pin_af.h" extern mp_obj_t machine_pin_low_obj; extern mp_obj_t machine_pin_high_obj; @@ -38,8 +38,10 @@ extern mp_obj_t machine_pin_toggle_obj; extern mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); STATIC void machine_led_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_led_obj_t *self = self_in; - mp_printf(print, "LED(\"%s\")", self->name); + machine_pin_obj_t *self = self_in; + mp_printf(print, "LED(\"%s\", GPIO=P%c%02u)", + pin_name(self->pin_id), + "ABCD"[self->pin_id / 32], self->pin_id % 32); } // constructor(id, ...) @@ -47,23 +49,16 @@ mp_obj_t mp_led_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); // get the wanted LED object - int wanted_led = pin_find(args[0], (const machine_pin_obj_t *)machine_led_obj, MP_ARRAY_SIZE(machine_led_obj)); - const machine_led_obj_t *self = NULL; - if (0 <= wanted_led && wanted_led < MP_ARRAY_SIZE(machine_led_obj)) { - self = (machine_led_obj_t *)&machine_led_obj[wanted_led]; - } - // the array could be padded with 'nulls' (see other Ports). - // Will also error if the asked for LED (index) is greater than the array row size. - if (self == NULL || self->base.type == NULL) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid LED")); - } - mp_hal_pin_output(self->id); - mp_hal_pin_low(self->id); + const machine_pin_obj_t *self; + + self = pin_find(args[0], &machine_led_type); + + mp_hal_pin_output(self->pin_id); + mp_hal_pin_low(self->pin_id); return MP_OBJ_FROM_PTR(self); } - STATIC const mp_rom_map_elem_t machine_led_locals_dict_table[] = { // instance methods { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_low_obj) }, diff --git a/ports/samd/machine_pin.c b/ports/samd/machine_pin.c index 2dc10f9b4634..a7cd86d386ed 100644 --- a/ports/samd/machine_pin.c +++ b/ports/samd/machine_pin.c @@ -34,7 +34,6 @@ #include "extmod/virtpin.h" #include "modmachine.h" #include "samd_soc.h" -#include "pins.h" #include "pin_af.h" #include "hal_gpio.h" @@ -68,17 +67,17 @@ STATIC void machine_pin_print(const mp_print_t *print, mp_obj_t self_in, mp_prin machine_pin_obj_t *self = self_in; char *mode_str; char *pull_str[] = {"PULL_OFF", "PULL_UP", "PULL_DOWN"}; - if (GPIO_IS_OPEN_DRAIN(self->id)) { + if (GPIO_IS_OPEN_DRAIN(self->pin_id)) { mode_str = "OPEN_DRAIN"; } else { - mode_str = (mp_hal_get_pin_direction(self->id) == GPIO_DIRECTION_OUT) ? "OUT" : "IN"; + mode_str = (mp_hal_get_pin_direction(self->pin_id) == GPIO_DIRECTION_OUT) ? "OUT" : "IN"; } mp_printf(print, "Pin(\"%s\", mode=%s, pull=%s, GPIO=P%c%02u)", - self->name, + pin_name(self->pin_id), mode_str, - pull_str[mp_hal_get_pull_mode(self->id)], - "ABCD"[self->id / 32], self->id % 32); + pull_str[mp_hal_get_pull_mode(self->pin_id)], + "ABCD"[self->pin_id / 32], self->pin_id % 32); } STATIC void pin_validate_drive(bool strength) { @@ -87,25 +86,6 @@ STATIC void pin_validate_drive(bool strength) { } } -int pin_find(mp_obj_t pin, const machine_pin_obj_t machine_pin_obj[], int table_size) { - int wanted_pin = -1; - if (mp_obj_is_small_int(pin)) { - // Pin defined by the index of pin table - wanted_pin = mp_obj_get_int(pin); - } else if (mp_obj_is_str(pin)) { - // Search by name - size_t slen; - const char *s = mp_obj_str_get_data(pin, &slen); - for (wanted_pin = 0; wanted_pin < table_size; wanted_pin++) { - if (slen == strlen(machine_pin_obj[wanted_pin].name) && - strncmp(s, machine_pin_obj[wanted_pin].name, slen) == 0) { - break; - } - } - } - return wanted_pin; -} - // Pin.init(mode, pull=None, *, value=None, drive=0). No 'alt' yet. STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { enum { ARG_mode, ARG_pull, ARG_value, ARG_drive, ARG_alt }; @@ -120,32 +100,34 @@ STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + // clear any existing mux setting + mp_hal_clr_pin_mux(self->pin_id); // set initial value (do this before configuring mode/pull) if (args[ARG_value].u_obj != mp_const_none) { - mp_hal_pin_write(self->id, mp_obj_is_true(args[ARG_value].u_obj)); + mp_hal_pin_write(self->pin_id, mp_obj_is_true(args[ARG_value].u_obj)); } // configure mode if (args[ARG_mode].u_obj != mp_const_none) { mp_int_t mode = mp_obj_get_int(args[ARG_mode].u_obj); if (mode == GPIO_MODE_IN) { - mp_hal_pin_input(self->id); + mp_hal_pin_input(self->pin_id); } else if (mode == GPIO_MODE_OUT) { - mp_hal_pin_output(self->id); + mp_hal_pin_output(self->pin_id); } else if (mode == GPIO_MODE_OPEN_DRAIN) { - mp_hal_pin_open_drain(self->id); + mp_hal_pin_open_drain(self->pin_id); } else { - mp_hal_pin_input(self->id); // If no args are given, the Pin is 'input'. + mp_hal_pin_input(self->pin_id); // If no args are given, the Pin is 'input'. } } // configure pull. Only to be used with IN mode. The function sets the pin to INPUT. uint32_t pull = 0; - mp_int_t dir = mp_hal_get_pin_direction(self->id); + mp_int_t dir = mp_hal_get_pin_direction(self->pin_id); if (dir == GPIO_DIRECTION_OUT && args[ARG_pull].u_obj != mp_const_none) { mp_raise_ValueError(MP_ERROR_TEXT("OUT incompatible with pull")); } else if (args[ARG_pull].u_obj != mp_const_none) { pull = mp_obj_get_int(args[ARG_pull].u_obj); - gpio_set_pin_pull_mode(self->id, pull); // hal_gpio.h + gpio_set_pin_pull_mode(self->pin_id, pull); // hal_gpio.h } // get the strength @@ -158,18 +140,10 @@ STATIC mp_obj_t machine_pin_obj_init_helper(const machine_pin_obj_t *self, size_ // constructor(id, ...) mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); + const machine_pin_obj_t *self; // get the wanted pin object - int wanted_pin = pin_find(args[0], machine_pin_obj, MP_ARRAY_SIZE(machine_pin_obj)); - - const machine_pin_obj_t *self = NULL; - if (0 <= wanted_pin && wanted_pin < MP_ARRAY_SIZE(machine_pin_obj)) { - self = (machine_pin_obj_t *)&machine_pin_obj[wanted_pin]; - } - - if (self == NULL || self->base.type == NULL) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid pin")); - } + self = pin_find(args[0], &machine_pin_type); if (n_args > 1 || n_kw > 0) { // pin mode given, so configure this GPIO @@ -187,18 +161,18 @@ mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp machine_pin_obj_t *self = self_in; if (n_args == 0) { // get pin - return MP_OBJ_NEW_SMALL_INT(mp_hal_pin_read(self->id)); + return MP_OBJ_NEW_SMALL_INT(mp_hal_pin_read(self->pin_id)); } else { // set pin bool value = mp_obj_is_true(args[0]); - if (GPIO_IS_OPEN_DRAIN(self->id)) { + if (GPIO_IS_OPEN_DRAIN(self->pin_id)) { if (value == 0) { - mp_hal_pin_od_low(self->id); + mp_hal_pin_od_low(self->pin_id); } else { - mp_hal_pin_od_high(self->id); + mp_hal_pin_od_high(self->pin_id); } } else { - mp_hal_pin_write(self->id, value); + mp_hal_pin_write(self->pin_id, value); } return mp_const_none; } @@ -219,7 +193,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_pin_value_obj, 1, 2, machine_ // Pin.disable(pin) STATIC mp_obj_t machine_pin_disable(mp_obj_t self_in) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - gpio_set_pin_direction(self->id, GPIO_DIRECTION_OFF); // Disables the pin (low power state) + gpio_set_pin_direction(self->pin_id, GPIO_DIRECTION_OFF); // Disables the pin (low power state) return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_disable_obj, machine_pin_disable); @@ -227,10 +201,10 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_disable_obj, machine_pin_disable); // Pin.low() Totem-pole (push-pull) STATIC mp_obj_t machine_pin_low(mp_obj_t self_in) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (GPIO_IS_OPEN_DRAIN(self->id)) { - mp_hal_pin_od_low(self->id); + if (GPIO_IS_OPEN_DRAIN(self->pin_id)) { + mp_hal_pin_od_low(self->pin_id); } else { - mp_hal_pin_low(self->id); + mp_hal_pin_low(self->pin_id); } return mp_const_none; } @@ -239,10 +213,10 @@ MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_low_obj, machine_pin_low); // Pin.high() Totem-pole (push-pull) STATIC mp_obj_t machine_pin_high(mp_obj_t self_in) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - if (GPIO_IS_OPEN_DRAIN(self->id)) { - mp_hal_pin_od_high(self->id); + if (GPIO_IS_OPEN_DRAIN(self->pin_id)) { + mp_hal_pin_od_high(self->pin_id); } else { - mp_hal_pin_high(self->id); + mp_hal_pin_high(self->pin_id); } return mp_const_none; } @@ -255,16 +229,16 @@ STATIC mp_obj_t machine_pin_toggle(mp_obj_t self_in) { // Determine DIRECTION of PIN. bool pin_dir; - if (GPIO_IS_OPEN_DRAIN(self->id)) { - pin_dir = mp_hal_get_pin_direction(self->id); + if (GPIO_IS_OPEN_DRAIN(self->pin_id)) { + pin_dir = mp_hal_get_pin_direction(self->pin_id); if (pin_dir) { // Pin is output, thus low, switch to high - mp_hal_pin_od_high(self->id); + mp_hal_pin_od_high(self->pin_id); } else { - mp_hal_pin_od_low(self->id); + mp_hal_pin_od_low(self->pin_id); } } else { - gpio_toggle_pin_level(self->id); + gpio_toggle_pin_level(self->pin_id); } return mp_const_none; } @@ -280,8 +254,8 @@ STATIC mp_obj_t machine_pin_drive(size_t n_args, const mp_obj_t *args) { pin_validate_drive(strength); // Set the DRVSTR bit (ASF hri/hri_port_dxx.h hri_port_write_PINCFG_DRVSTR_bit(PORT, - (enum gpio_port)GPIO_PORT(self->id), - GPIO_PIN(self->id), + (enum gpio_port)GPIO_PORT(self->pin_id), + GPIO_PIN(self->pin_id), strength); return mp_const_none; } @@ -301,9 +275,9 @@ STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ mp_arg_parse_all(n_args - 1, pos_args + 1, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); // Get the IRQ object. - uint8_t eic_id = get_pin_af_info(self->id)->eic; + uint8_t eic_id = get_pin_obj_ptr(self->pin_id)->eic; machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_objects[eic_id]); - if (irq != NULL && irq->pin_id != self->id) { + if (irq != NULL && irq->pin_id != self->pin_id) { mp_raise_ValueError(MP_ERROR_TEXT("IRQ already used")); } @@ -322,7 +296,7 @@ STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ if (n_args > 1 || kw_args->used != 0) { // set the mux config of the pin. - mp_hal_set_pin_mux(self->id, ALT_FCT_EIC); + mp_hal_set_pin_mux(self->pin_id, ALT_FCT_EIC); // Configure IRQ. #if defined(MCU_SAMD21) @@ -362,7 +336,7 @@ STATIC mp_obj_t machine_pin_irq(size_t n_args, const mp_obj_t *pos_args, mp_map_ irq->base.ishard = args[ARG_hard].u_bool; irq->flags = 0; irq->trigger = args[ARG_trigger].u_int; - irq->pin_id = self->id; + irq->pin_id = self->pin_id; // Enable IRQ if a handler is given. if (args[ARG_handler].u_obj != mp_const_none) { @@ -457,10 +431,10 @@ STATIC mp_uint_t pin_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_t arg, i switch (request) { case MP_PIN_READ: { - return mp_hal_pin_read(self->id); + return mp_hal_pin_read(self->pin_id); } case MP_PIN_WRITE: { - mp_hal_pin_write(self->id, arg); + mp_hal_pin_write(self->pin_id, arg); return 0; } } @@ -494,7 +468,7 @@ static uint8_t find_eic_id(int pin) { STATIC mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - uint8_t eic_id = find_eic_id(self->id); + uint8_t eic_id = find_eic_id(self->pin_id); if (eic_id != 0xff) { machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_objects[eic_id]); EIC->INTENCLR.reg |= (1 << eic_id); @@ -507,7 +481,7 @@ STATIC mp_uint_t machine_pin_irq_trigger(mp_obj_t self_in, mp_uint_t new_trigger STATIC mp_uint_t machine_pin_irq_info(mp_obj_t self_in, mp_uint_t info_type) { machine_pin_obj_t *self = MP_OBJ_TO_PTR(self_in); - uint8_t eic_id = find_eic_id(self->id); + uint8_t eic_id = find_eic_id(self->pin_id); if (eic_id != 0xff) { machine_pin_irq_obj_t *irq = MP_STATE_PORT(machine_pin_irq_objects[eic_id]); if (info_type == MP_IRQ_INFO_FLAGS) { @@ -525,11 +499,8 @@ STATIC const mp_irq_methods_t machine_pin_irq_methods = { }; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t obj) { - if (!mp_obj_is_type(obj, &machine_pin_type)) { - mp_raise_ValueError(MP_ERROR_TEXT("expecting a Pin")); - } - machine_pin_obj_t *pin = MP_OBJ_TO_PTR(obj); - return pin->id; + const machine_pin_obj_t *pin = pin_find(obj, &machine_pin_type); + return pin->pin_id; } MP_REGISTER_ROOT_POINTER(void *machine_pin_irq_objects[16]); diff --git a/ports/samd/machine_pwm.c b/ports/samd/machine_pwm.c index d987927d310c..91982a6f74a8 100644 --- a/ports/samd/machine_pwm.c +++ b/ports/samd/machine_pwm.c @@ -107,8 +107,8 @@ STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "PWM P%c%02u device=%u channel=%u output=%u", - "ABCD"[self->pin_id / 32], self->pin_id % 32, self->device, self->channel, self->output); + mp_printf(print, "PWM %s device=%u channel=%u output=%u", + pin_name(self->pin_id), self->device, self->channel, self->output); } // PWM(pin) diff --git a/ports/samd/modsamd.c b/ports/samd/modsamd.c index 05ed69bad014..79e7b4cc35f1 100644 --- a/ports/samd/modsamd.c +++ b/ports/samd/modsamd.c @@ -30,42 +30,40 @@ #include "sam.h" #include "pin_af.h" -#include "pins.h" #include "samd_soc.h" extern const mp_obj_type_t samd_flash_type; STATIC mp_obj_t samd_pininfo(mp_obj_t pin_obj) { - mp_hal_pin_obj_t pin = mp_hal_get_pin_obj(pin_obj); - const pin_af_t *pin_af = get_pin_af_info(pin); - const char *name = ((machine_pin_obj_t *)MP_OBJ_TO_PTR(pin_obj))->name; - if (pin_af) { - #if defined(MCU_SAMD21) - mp_obj_t tuple[7] = { - tuple[0] = mp_obj_new_str(name, strlen(name)), - tuple[1] = mp_obj_new_int(pin_af->eic), - tuple[2] = mp_obj_new_int(pin_af->adc0), - tuple[3] = mp_obj_new_int(pin_af->sercom1), - tuple[4] = mp_obj_new_int(pin_af->sercom2), - tuple[5] = mp_obj_new_int(pin_af->tcc1), - tuple[6] = mp_obj_new_int(pin_af->tcc2), - }; - return mp_obj_new_tuple(7, tuple); - #elif defined(MCU_SAMD51) - mp_obj_t tuple[9] = { - tuple[0] = mp_obj_new_str(name, strlen(name)), - tuple[1] = mp_obj_new_int(pin_af->eic), - tuple[2] = mp_obj_new_int(pin_af->adc0), - tuple[3] = mp_obj_new_int(pin_af->adc1), - tuple[4] = mp_obj_new_int(pin_af->sercom1), - tuple[5] = mp_obj_new_int(pin_af->sercom2), - tuple[6] = mp_obj_new_int(pin_af->tc), - tuple[7] = mp_obj_new_int(pin_af->tcc1), - tuple[8] = mp_obj_new_int(pin_af->tcc2), - }; - return mp_obj_new_tuple(9, tuple); - #endif - } + const machine_pin_obj_t *pin_af = pin_find(pin_obj, NULL); + // Get the name, now that it is not in the pin object + const char *name = pin_af->name; + + #if defined(MCU_SAMD21) + mp_obj_t tuple[7] = { + tuple[0] = mp_obj_new_str(name, strlen(name)), + tuple[1] = mp_obj_new_int(pin_af->eic), + tuple[2] = mp_obj_new_int(pin_af->adc0), + tuple[3] = mp_obj_new_int(pin_af->sercom1), + tuple[4] = mp_obj_new_int(pin_af->sercom2), + tuple[5] = mp_obj_new_int(pin_af->tcc1), + tuple[6] = mp_obj_new_int(pin_af->tcc2), + }; + return mp_obj_new_tuple(7, tuple); + #elif defined(MCU_SAMD51) + mp_obj_t tuple[9] = { + tuple[0] = mp_obj_new_str(name, strlen(name)), + tuple[1] = mp_obj_new_int(pin_af->eic), + tuple[2] = mp_obj_new_int(pin_af->adc0), + tuple[3] = mp_obj_new_int(pin_af->adc1), + tuple[4] = mp_obj_new_int(pin_af->sercom1), + tuple[5] = mp_obj_new_int(pin_af->sercom2), + tuple[6] = mp_obj_new_int(pin_af->tc), + tuple[7] = mp_obj_new_int(pin_af->tcc1), + tuple[8] = mp_obj_new_int(pin_af->tcc2), + }; + return mp_obj_new_tuple(9, tuple); + #endif return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_1(samd_pininfo_obj, samd_pininfo); diff --git a/ports/samd/pin_af.c b/ports/samd/pin_af.c index 926c4ae0c23a..fc2a0f3d54d9 100644 --- a/ports/samd/pin_af.c +++ b/ports/samd/pin_af.c @@ -29,7 +29,9 @@ */ #include +#include "string.h" +#include "modmachine.h" #include "py/runtime.h" #include "py/misc.h" #include "pin_af.h" @@ -43,13 +45,65 @@ extern const uint8_t tcc_channel_count[]; // Just look for an table entry for a given pin and raise an error // in case of no match (which should not happen). -const pin_af_t *get_pin_af_info(int pin_id) { +const machine_pin_obj_t *get_pin_obj_ptr(int pin_id) { for (int i = 0; i < MP_ARRAY_SIZE(pin_af_table); i++) { if (pin_af_table[i].pin_id == pin_id) { // Pin match return &pin_af_table[i]; } } - mp_raise_ValueError(MP_ERROR_TEXT("wrong pin")); + mp_raise_ValueError(MP_ERROR_TEXT("not a Pin")); +} + +const machine_pin_obj_t *pin_find(mp_obj_t pin, const mp_obj_type_t *type) { + const machine_pin_obj_t *self = NULL; + // Is already a object of the proper type + if (mp_obj_is_type(pin, type)) { + return pin; + } + if (mp_obj_is_small_int(pin)) { + // Pin defined by pin number for PAnn, PBnn, etc. + self = get_pin_obj_ptr(mp_obj_get_int(pin)); + } else if (mp_obj_is_str(pin)) { + // Search by name + size_t slen; + const char *s = mp_obj_str_get_data(pin, &slen); + // Check for a string like PA02 or PD12 + if (slen == 4 && s[0] == 'P' && strchr("ABCD", s[1]) != NULL && + strchr("0123456789", s[2]) != NULL && strchr("0123456789", s[2]) != NULL) { + int num = (s[1] - 'A') * 32 + (s[2] - '0') * 10 + (s[3] - '0'); + self = get_pin_obj_ptr(num); + } else { + for (int i = 0; i < MP_ARRAY_SIZE(pin_af_table); i++) { + if (slen == strlen(pin_af_table[i].name) && + strncmp(s, pin_af_table[i].name, slen) == 0) { + self = &pin_af_table[i]; + } + } + } + } + if (self != NULL && (type == NULL || mp_obj_is_type(self, type))) { + return self; + } else { + mp_raise_ValueError(MP_ERROR_TEXT("not a Pin")); + } +} + +const char *pin_name(int id) { + static char board_name[5] = "Pxnn"; + for (int i = 0; i < sizeof(pin_af_table); i++) { + if (pin_af_table[i].pin_id == id) { + if (pin_af_table[i].name[0] != '-') { + return pin_af_table[i].name; + } else { + board_name[1] = "ABCD"[id / 32]; + id %= 32; + board_name[2] = '0' + id / 10; + board_name[3] = '0' + id % 10; + return board_name; + } + } + } + return "-"; } // Test, wether the given pin is defined and has signals for sercom. @@ -57,7 +111,7 @@ const pin_af_t *get_pin_af_info(int pin_id) { // If not, an error will be raised. sercom_pad_config_t get_sercom_config(int pin_id, uint8_t sercom_nr) { - const pin_af_t *pct_ptr = get_pin_af_info(pin_id); + const machine_pin_obj_t *pct_ptr = get_pin_obj_ptr(pin_id); if ((pct_ptr->sercom1 >> 4) == sercom_nr) { return (sercom_pad_config_t) {ALT_FCT_SERCOM1, pct_ptr->sercom1 & 0x0f}; } else if ((pct_ptr->sercom2 >> 4) == sercom_nr) { @@ -72,7 +126,12 @@ sercom_pad_config_t get_sercom_config(int pin_id, uint8_t sercom_nr) { // If not, an error will be raised. adc_config_t get_adc_config(int pin_id, int32_t flag) { - const pin_af_t *pct_ptr = get_pin_af_info(pin_id); + const machine_pin_obj_t *pct_ptr = get_pin_obj_ptr(pin_id); + #if defined(MCU_SAMD51) + if (pct_ptr->adc1 != 0xff && (flag & (1 << (pct_ptr->adc1 + 16))) == 0) { + return (adc_config_t) {1, pct_ptr->adc1}; + } else + #endif if (pct_ptr->adc0 != 0xff && (flag & (1 << pct_ptr->adc0)) == 0) { return (adc_config_t) {0, pct_ptr->adc0}; #if defined(MUC_SAMD51) @@ -91,7 +150,7 @@ adc_config_t get_adc_config(int pin_id, int32_t flag) { // tries to provide an unused device, if available. pwm_config_t get_pwm_config(int pin_id, int wanted_dev, uint8_t device_status[]) { - const pin_af_t *pct_ptr = get_pin_af_info(pin_id); + const machine_pin_obj_t *pct_ptr = get_pin_obj_ptr(pin_id); uint8_t tcc1 = pct_ptr->tcc1; uint8_t tcc2 = pct_ptr->tcc2; diff --git a/ports/samd/pin_af.h b/ports/samd/pin_af.h index b75f4ddd0256..5bb65a098a65 100644 --- a/ports/samd/pin_af.h +++ b/ports/samd/pin_af.h @@ -30,15 +30,17 @@ #if defined(MCU_SAMD21) -typedef struct { +typedef struct _machine_pin_obj_t { + mp_obj_base_t base; uint8_t pin_id; + char *name; uint8_t eic; uint8_t adc0; uint8_t sercom1; uint8_t sercom2; uint8_t tcc1; uint8_t tcc2; -} pin_af_t; +} machine_pin_obj_t; #define ALT_FCT_TC 4 #define ALT_FCT_TCC1 4 @@ -46,8 +48,10 @@ typedef struct { #elif defined(MCU_SAMD51) -typedef struct { +typedef struct _machine_pin_obj_t { + mp_obj_base_t base; uint8_t pin_id; + char *name; uint8_t eic; uint8_t adc0; uint8_t adc1; @@ -56,7 +60,7 @@ typedef struct { uint8_t tc; uint8_t tcc1; uint8_t tcc2; -} pin_af_t; +} machine_pin_obj_t; #define ALT_FCT_TC 4 #define ALT_FCT_TCC1 5 @@ -85,7 +89,11 @@ typedef struct _pwm_config_t { #define ALT_FCT_SERCOM1 2 #define ALT_FCT_SERCOM2 3 +extern const machine_pin_obj_t pin_af_table[]; + sercom_pad_config_t get_sercom_config(int pin_id, uint8_t sercom); adc_config_t get_adc_config(int pin_id, int32_t flag); pwm_config_t get_pwm_config(int pin_id, int wanted_dev, uint8_t used_dev[]); -const pin_af_t *get_pin_af_info(int pin_id); +const machine_pin_obj_t *get_pin_obj_ptr(int pin_id); +const char *pin_name(int id); +const machine_pin_obj_t *pin_find(mp_obj_t pin, const mp_obj_type_t *type); From 4d38ab652ea57a60c6f2ff085547f72012ac7df6 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 7 Oct 2022 09:40:45 +0200 Subject: [PATCH 036/823] samd: Make ADC, DAC, PWM, SPI objects consistent in how they print out. All of ADC, DAC, Pin, PWM and SPI looked different before this change. --- ports/samd/machine_adc.c | 2 +- ports/samd/machine_dac.c | 2 +- ports/samd/machine_pwm.c | 2 +- ports/samd/machine_spi.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/samd/machine_adc.c b/ports/samd/machine_adc.c index efe0f041cdfa..8fd3e3bf4e7f 100644 --- a/ports/samd/machine_adc.c +++ b/ports/samd/machine_adc.c @@ -66,7 +66,7 @@ STATIC void adc_obj_print(const mp_print_t *print, mp_obj_t o, mp_print_kind_t k (void)kind; machine_adc_obj_t *self = MP_OBJ_TO_PTR(o); - mp_printf(print, "ADC(%s, ADC%u, channel=%u, bits=%u, average=%u)", + mp_printf(print, "ADC(%s, device=%u, channel=%u, bits=%u, average=%u)", pin_name(self->id), self->adc_config.device, self->adc_config.channel, self->bits, 1 << self->avg); } diff --git a/ports/samd/machine_dac.c b/ports/samd/machine_dac.c index 41c6784cceb8..53407c0d375a 100644 --- a/ports/samd/machine_dac.c +++ b/ports/samd/machine_dac.c @@ -126,7 +126,7 @@ STATIC mp_obj_t dac_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ STATIC void dac_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { dac_obj_t *self = self_in; - mp_printf(print, "DAC(%u) PIN_PA%02u", self->id, self->gpio_id); + mp_printf(print, "DAC(%u, Pin=%s)", self->id, pin_name(self->gpio_id)); } STATIC mp_obj_t dac_write(mp_obj_t self_in, mp_obj_t value_in) { diff --git a/ports/samd/machine_pwm.c b/ports/samd/machine_pwm.c index 91982a6f74a8..2ae841a9743b 100644 --- a/ports/samd/machine_pwm.c +++ b/ports/samd/machine_pwm.c @@ -107,7 +107,7 @@ STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns STATIC void mp_machine_pwm_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_pwm_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "PWM %s device=%u channel=%u output=%u", + mp_printf(print, "PWM(%s, device=%u, channel=%u, output=%u)", pin_name(self->pin_id), self->device, self->channel, self->output); } diff --git a/ports/samd/machine_spi.c b/ports/samd/machine_spi.c index 8321f19cf0dd..43150f67f15a 100644 --- a/ports/samd/machine_spi.c +++ b/ports/samd/machine_spi.c @@ -82,7 +82,7 @@ void common_spi_irq_handler(int spi_id) { STATIC void machine_spi_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); - mp_printf(print, "SPI(%u), baudrate=%u, firstbit=%u, polarity=%u, phase=%u, bits=8", + mp_printf(print, "SPI(%u, baudrate=%u, firstbit=%u, polarity=%u, phase=%u, bits=8)", self->id, self->baudrate, self->firstbit, self->polarity, self->phase); } From a6760bd4efa883ef463795036ff74d678ff65c7b Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sun, 9 Oct 2022 10:56:29 +0200 Subject: [PATCH 037/823] samd/modmachine: Replace the LED class by the Signal class. It simplifies and improves the code. The LED_Pxxx lines of the board.csv lines can still be used, but will be taken as Pin definitions. --- ports/samd/Makefile | 1 - ports/samd/boards/make-pin-table.py | 6 +-- ports/samd/machine_led.c | 78 ----------------------------- ports/samd/machine_pin.c | 4 +- ports/samd/modmachine.c | 3 +- ports/samd/modmachine.h | 1 - ports/samd/modsamd.c | 2 +- ports/samd/mpconfigport.h | 1 + ports/samd/pin_af.c | 6 +-- ports/samd/pin_af.h | 2 +- 10 files changed, 11 insertions(+), 93 deletions(-) delete mode 100644 ports/samd/machine_led.c diff --git a/ports/samd/Makefile b/ports/samd/Makefile index f512fb0b2e33..b2e84460ea26 100644 --- a/ports/samd/Makefile +++ b/ports/samd/Makefile @@ -88,7 +88,6 @@ SRC_C += \ machine_bitstream.c \ machine_dac.c \ machine_i2c.c \ - machine_led.c \ machine_pin.c \ machine_rtc.c \ machine_spi.c \ diff --git a/ports/samd/boards/make-pin-table.py b/ports/samd/boards/make-pin-table.py index 327478568afa..40bcd166dd08 100644 --- a/ports/samd/boards/make-pin-table.py +++ b/ports/samd/boards/make-pin-table.py @@ -36,11 +36,7 @@ def parse_pin_file(self, filename): # for compatibility, map LED_ to PIN_ if row[0].startswith("LED_"): row[0] = "PIN_" + row[0][4:] - if len(row) == 1: - self.pin_names[row[0]] = (row[0][4:], "{&machine_led_type}") - else: - self.pin_names[row[0]] = (row[1], "{&machine_led_type}") - elif row[0].startswith("PIN_"): + if row[0].startswith("PIN_"): if len(row) == 1: self.pin_names[row[0]] = (row[0][4:], "{&machine_pin_type}") else: diff --git a/ports/samd/machine_led.c b/ports/samd/machine_led.c deleted file mode 100644 index c18bc052bd2f..000000000000 --- a/ports/samd/machine_led.c +++ /dev/null @@ -1,78 +0,0 @@ -/* - * This is part of the MicroPython project, http://micropython.org/ - * - * The MIT License (MIT) - * - * Copyright (c) 2016-2021 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. - * - * Uses pins.h & pins.c to create board (MCU package) specific 'machine_led_obj' array. - */ - -#include "py/runtime.h" -#include "py/mphal.h" -#include "extmod/virtpin.h" -#include "modmachine.h" -#include "pin_af.h" - -extern mp_obj_t machine_pin_low_obj; -extern mp_obj_t machine_pin_high_obj; -extern mp_obj_t machine_pin_toggle_obj; -extern mp_obj_t machine_pin_call(mp_obj_t self_in, size_t n_args, size_t n_kw, const mp_obj_t *args); - -STATIC void machine_led_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { - machine_pin_obj_t *self = self_in; - mp_printf(print, "LED(\"%s\", GPIO=P%c%02u)", - pin_name(self->pin_id), - "ABCD"[self->pin_id / 32], self->pin_id % 32); -} - -// constructor(id, ...) -mp_obj_t mp_led_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const mp_obj_t *args) { - mp_arg_check_num(n_args, n_kw, 1, MP_OBJ_FUN_ARGS_MAX, true); - - // get the wanted LED object - const machine_pin_obj_t *self; - - self = pin_find(args[0], &machine_led_type); - - mp_hal_pin_output(self->pin_id); - mp_hal_pin_low(self->pin_id); - - return MP_OBJ_FROM_PTR(self); -} - -STATIC const mp_rom_map_elem_t machine_led_locals_dict_table[] = { - // instance methods - { MP_ROM_QSTR(MP_QSTR_off), MP_ROM_PTR(&machine_pin_low_obj) }, - { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_high_obj) }, - { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, -}; -STATIC MP_DEFINE_CONST_DICT(machine_led_locals_dict, machine_led_locals_dict_table); - -MP_DEFINE_CONST_OBJ_TYPE( - machine_led_type, - MP_QSTR_LED, - MP_TYPE_FLAG_NONE, - make_new, mp_led_make_new, - print, machine_led_print, - call, machine_pin_call, - locals_dict, &machine_led_locals_dict - ); diff --git a/ports/samd/machine_pin.c b/ports/samd/machine_pin.c index a7cd86d386ed..87684858325f 100644 --- a/ports/samd/machine_pin.c +++ b/ports/samd/machine_pin.c @@ -143,7 +143,7 @@ mp_obj_t mp_pin_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_kw, const machine_pin_obj_t *self; // get the wanted pin object - self = pin_find(args[0], &machine_pin_type); + self = pin_find(args[0]); if (n_args > 1 || n_kw > 0) { // pin mode given, so configure this GPIO @@ -499,7 +499,7 @@ STATIC const mp_irq_methods_t machine_pin_irq_methods = { }; mp_hal_pin_obj_t mp_hal_get_pin_obj(mp_obj_t obj) { - const machine_pin_obj_t *pin = pin_find(obj, &machine_pin_type); + const machine_pin_obj_t *pin = pin_find(obj); return pin->pin_id; } diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 12e9f7c34134..47fc03d82410 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -29,6 +29,7 @@ #include "extmod/machine_mem.h" #include "extmod/machine_pulse.h" #include "extmod/machine_i2c.h" +#include "extmod/machine_signal.h" #include "extmod/machine_spi.h" #include "drivers/dht/dht.h" #include "modmachine.h" @@ -228,8 +229,8 @@ STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_ADC), MP_ROM_PTR(&machine_adc_type) }, { MP_ROM_QSTR(MP_QSTR_DAC), MP_ROM_PTR(&machine_dac_type) }, - { MP_ROM_QSTR(MP_QSTR_LED), MP_ROM_PTR(&machine_led_type) }, { MP_ROM_QSTR(MP_QSTR_Pin), MP_ROM_PTR(&machine_pin_type) }, + { MP_ROM_QSTR(MP_QSTR_Signal), MP_ROM_PTR(&machine_signal_type) }, { MP_ROM_QSTR(MP_QSTR_PWM), MP_ROM_PTR(&machine_pwm_type) }, { MP_ROM_QSTR(MP_QSTR_SoftI2C), MP_ROM_PTR(&mp_machine_soft_i2c_type) }, { MP_ROM_QSTR(MP_QSTR_I2C), MP_ROM_PTR(&machine_i2c_type) }, diff --git a/ports/samd/modmachine.h b/ports/samd/modmachine.h index 8f85e149896f..792fca1b9292 100644 --- a/ports/samd/modmachine.h +++ b/ports/samd/modmachine.h @@ -31,7 +31,6 @@ extern const mp_obj_type_t machine_adc_type; extern const mp_obj_type_t machine_dac_type; extern const mp_obj_type_t machine_i2c_type; -extern const mp_obj_type_t machine_led_type; extern const mp_obj_type_t machine_pin_type; extern const mp_obj_type_t machine_pwm_type; extern const mp_obj_type_t machine_spi_type; diff --git a/ports/samd/modsamd.c b/ports/samd/modsamd.c index 79e7b4cc35f1..5a2cf4616d1d 100644 --- a/ports/samd/modsamd.c +++ b/ports/samd/modsamd.c @@ -35,7 +35,7 @@ extern const mp_obj_type_t samd_flash_type; STATIC mp_obj_t samd_pininfo(mp_obj_t pin_obj) { - const machine_pin_obj_t *pin_af = pin_find(pin_obj, NULL); + const machine_pin_obj_t *pin_af = pin_find(pin_obj); // Get the name, now that it is not in the pin object const char *name = pin_af->name; diff --git a/ports/samd/mpconfigport.h b/ports/samd/mpconfigport.h index 8b8305f0b47e..f03f8a11d764 100644 --- a/ports/samd/mpconfigport.h +++ b/ports/samd/mpconfigport.h @@ -100,6 +100,7 @@ #define MICROPY_PY_MACHINE_PWM_INIT (0) #define MICROPY_PY_MACHINE_PWM_DUTY_U16_NS (1) #define MICROPY_PY_MACHINE_PWM_INCLUDEFILE "ports/samd/machine_pwm.c" +#define MICROPY_PY_MACHINE_PIN_MAKE_NEW mp_pin_make_new #define MP_STATE_PORT MP_STATE_VM diff --git a/ports/samd/pin_af.c b/ports/samd/pin_af.c index fc2a0f3d54d9..a7a72ac96abe 100644 --- a/ports/samd/pin_af.c +++ b/ports/samd/pin_af.c @@ -54,10 +54,10 @@ const machine_pin_obj_t *get_pin_obj_ptr(int pin_id) { mp_raise_ValueError(MP_ERROR_TEXT("not a Pin")); } -const machine_pin_obj_t *pin_find(mp_obj_t pin, const mp_obj_type_t *type) { +const machine_pin_obj_t *pin_find(mp_obj_t pin) { const machine_pin_obj_t *self = NULL; // Is already a object of the proper type - if (mp_obj_is_type(pin, type)) { + if (mp_obj_is_type(pin, &machine_pin_type)) { return pin; } if (mp_obj_is_small_int(pin)) { @@ -81,7 +81,7 @@ const machine_pin_obj_t *pin_find(mp_obj_t pin, const mp_obj_type_t *type) { } } } - if (self != NULL && (type == NULL || mp_obj_is_type(self, type))) { + if (self != NULL) { return self; } else { mp_raise_ValueError(MP_ERROR_TEXT("not a Pin")); diff --git a/ports/samd/pin_af.h b/ports/samd/pin_af.h index 5bb65a098a65..4b0c80250cd5 100644 --- a/ports/samd/pin_af.h +++ b/ports/samd/pin_af.h @@ -96,4 +96,4 @@ adc_config_t get_adc_config(int pin_id, int32_t flag); pwm_config_t get_pwm_config(int pin_id, int wanted_dev, uint8_t used_dev[]); const machine_pin_obj_t *get_pin_obj_ptr(int pin_id); const char *pin_name(int id); -const machine_pin_obj_t *pin_find(mp_obj_t pin, const mp_obj_type_t *type); +const machine_pin_obj_t *pin_find(mp_obj_t pin); From ac1e31267b0bf21caa31b8136208c116b1612789 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 8 Oct 2022 21:12:50 +0200 Subject: [PATCH 038/823] samd/boards: Rework the pins.csv files. Changes are: - Remove the LED_Pxxx definitions from pins.csv, now that the LED class is gone. - Remove the '-' lines. - Add default lines for USB and SWCLK, SWDIO. --- .../boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv | 11 ++++++++--- .../boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv | 10 ++++++---- .../boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv | 8 +++++--- .../boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv | 9 ++++++--- ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv | 8 +++++++- ports/samd/boards/MINISAM_M4/pins.csv | 9 ++++++++- ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv | 15 +++++++-------- ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv | 11 +++++++++-- ports/samd/boards/SEEED_XIAO/pins.csv | 12 +++++++++--- .../boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv | 15 +++++++++------ 10 files changed, 74 insertions(+), 34 deletions(-) diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv index 0985ee1646d1..45b05c2694f5 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M0_EXPRESS/pins.csv @@ -5,6 +5,9 @@ # Rows for empty entries have to start with '-' # Empty lines and lines starting with # are ignored +PIN_PA27,LED_TX +PIN_PB03,LED_RX + PIN_PA11,D0 PIN_PA10,D1 PIN_PA14,D2 @@ -35,6 +38,8 @@ PIN_PB11,SCK PIN_PA06,NEOPIXEL PIN_PA13,FLASH_CS -LED_PA17,LED -LED_PA27,LED_TX -LED_PB03,LED_RX +PIN_PA24,USB_DM +PIN_PA25,USB_DP + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv index 32daac3d0f4c..ca58a4be79f6 100644 --- a/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_FEATHER_M4_EXPRESS/pins.csv @@ -7,12 +7,9 @@ PIN_PB17,D0 PIN_PB16,D1 -- -- PIN_PA14,D4 PIN_PA16,D5 PIN_PA18,D6 -- PIN_PB03,D8 PIN_PA19,D9 PIN_PA20,D10 @@ -40,4 +37,9 @@ PIN_PA09,FLASH_MISO PIN_PA10,FLASH_WP PIN_PA11,FLASH_HOLD -LED_PA17,LED +PIN_PA24,USB_DM +PIN_PA25,USB_DP +PIN_PA26,USB_SOF + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv index d7d59c23575d..e93e00ab9c94 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M0_EXPRESS/pins.csv @@ -11,9 +11,7 @@ PIN_PA14,D2 PIN_PA09,D3 PIN_PA08,D4 PIN_PA15,D5 -- PIN_PA21,D7 -- PIN_PA07,D9 PIN_PA18,D10 PIN_PA16,D11 @@ -37,4 +35,8 @@ PIN_PB03,FLASH_MISO PIN_PB23,FLASH_SCK PIN_PA27,FLASH_CS -LED_PA17,LED +PIN_PA24,USB_DM +PIN_PA25,USB_DP + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv index ce760a269c24..3dee9bf4c35d 100644 --- a/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv +++ b/ports/samd/boards/ADAFRUIT_ITSYBITSY_M4_EXPRESS/pins.csv @@ -11,9 +11,7 @@ PIN_PA07,D2 PIN_PB22,D3 PIN_PA14,D4 PIN_PA15,D5 -- PIN_PA18,D7 -- PIN_PA19,D9 PIN_PA20,D10 PIN_PA21,D11 @@ -39,4 +37,9 @@ PIN_PA09,FLASH_MISO PIN_PA10,FLASH_WP PIN_PA11,FLASH_HOLD -LED_PA22,LED +PIN_PA24,USB_DM +PIN_PA25,USB_DP +PIN_PA26,USB_SOF + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv b/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv index 797b3f70bb92..95f76815f980 100644 --- a/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv +++ b/ports/samd/boards/ADAFRUIT_TRINKET_M0/pins.csv @@ -13,4 +13,10 @@ PIN_PA06,D4 PIN_PA00,DOTSTAR_DATA PIN_PA01,DOTSTAR_CLK -LED_PA10,LED +PIN_PA10,LED + +PIN_PA24,USB_DM +PIN_PA25,USB_DP + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/MINISAM_M4/pins.csv b/ports/samd/boards/MINISAM_M4/pins.csv index cf9a3bd19b21..f6d0ab657fef 100644 --- a/ports/samd/boards/MINISAM_M4/pins.csv +++ b/ports/samd/boards/MINISAM_M4/pins.csv @@ -27,4 +27,11 @@ PIN_PA01,SCK PIN_PB03,DOTSTAR_DATA PIN_PB02,DOTSTAR_CLK -LED_PA15,LED +PIN_PA15,LED + +PIN_PA24,USB_DM +PIN_PA25,USB_DP +PIN_PA26,USB_SOF + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv b/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv index 438119d90c1a..bd0757d5647c 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv @@ -32,8 +32,6 @@ PIN_PB12,EXT2_PIN7 PIN_PB13,EXT2_PIN8 PIN_PB14,EXT2_PIN9 PIN_PB15,EXT2_PIN10 -- -- PIN_PB11,EXT2_PIN13 PIN_PB10,EXT2_PIN14 PIN_PA17,EXT2_PIN15 @@ -44,20 +42,21 @@ PIN_PA19,EXT2_PIN18 # EXT3 PIN_PA02,EXT3_PIN3 PIN_PA03,EXT3_PIN4 -- PIN_PA15,EXT3_PIN6 PIN_PA12,EXT3_PIN7 PIN_PA13,EXT3_PIN8 PIN_PA28,EXT3_PIN9 PIN_PA27,EXT3_PIN10 -- -- -- -- PIN_PB17,EXT3_PIN15 PIN_PB22,EXT3_PIN16 PIN_PB16,EXT3_PIN17 PIN_PB23,EXT3_PIN18 -LED_PB30,LED +PIN_PB30,LED + +PIN_PA24,USB_DM +PIN_PA25,USB_DP + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv index 9bc57070d198..4a3d9e21aee0 100644 --- a/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv +++ b/ports/samd/boards/SEEED_WIO_TERMINAL/pins.csv @@ -60,5 +60,12 @@ PIN_PC13,LCD_YD PIN_PC30,MIC PIN_PD11,BUZZER -LED_PA15,LED_BLUE -LED_PC05,LED_LCD +PIN_PA15,LED_BLUE +PIN_PC05,LED_LCD + +PIN_PA24,USB_DM +PIN_PA25,USB_DP +PIN_PA26,USB_SOF + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/SEEED_XIAO/pins.csv b/ports/samd/boards/SEEED_XIAO/pins.csv index 8a70a7714563..a06d3ac6ac60 100644 --- a/ports/samd/boards/SEEED_XIAO/pins.csv +++ b/ports/samd/boards/SEEED_XIAO/pins.csv @@ -17,6 +17,12 @@ PIN_PA07,A8_D8 PIN_PA05,A9_D9 PIN_PA06,A10_D10 -LED_PA17,USER_LED -LED_PA18,RX_LED -LED_PA19,TX_LED +PIN_PA17,USER_LED +PIN_PA18,RX_LED +PIN_PA19,TX_LED + +PIN_PA24,USB_DM +PIN_PA25,USB_DP + +PIN_PA30,SWCLK +PIN_PA31,SWDIO diff --git a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv index b60fb909714e..dfd6641333fa 100644 --- a/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv +++ b/ports/samd/boards/SPARKFUN_SAMD51_THING_PLUS/pins.csv @@ -13,7 +13,6 @@ PIN_PA06,D4 PIN_PA15,D5 PIN_PA20,D6 PIN_PA21,D7 -- PIN_PA07,D9 PIN_PA18,D10 PIN_PA16,D11 @@ -34,9 +33,13 @@ PIN_PA08,FLASH_MOSI PIN_PA09,FLASH_SCK PIN_PA10,FLASH_CS PIN_PA11,FLASH_MISO -PIN_PA30,SWDCLK -PIN_PA31,SWDIO -LED_PA17,LED -LED_PB03,RXLED -LED_PA27,TXLED +PIN_PB03,RXLED +PIN_PA27,TXLED + +PIN_PA24,USB_DM +PIN_PA25,USB_DP +PIN_PA26,USB_SOF + +PIN_PA30,SWCLK +PIN_PA31,SWDIO From 474233c250536612a5245231aff1ef3ad74ccf20 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 11 Oct 2022 10:05:01 +0200 Subject: [PATCH 039/823] samd/machine_pwm: Serialize fast update of PWM settings. Any update of freq or duty_cycle requires the previous PWM cycle to be finished. Otherwise the new settings are not accepted. Other changes in this commit: - Report the set duty cycles even when the PWM is not yet started. - pwm.freq(0) stops the pwm device, instead of raising an expception. - Clear the duty cycle value cache on soft reset. --- ports/samd/machine_pwm.c | 51 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 5 deletions(-) diff --git a/ports/samd/machine_pwm.c b/ports/samd/machine_pwm.c index 2ae841a9743b..f6b417631e1d 100644 --- a/ports/samd/machine_pwm.c +++ b/ports/samd/machine_pwm.c @@ -25,6 +25,7 @@ * THE SOFTWARE. */ +#include #include "py/runtime.h" #include "py/mphal.h" #include "modmachine.h" @@ -46,6 +47,8 @@ typedef struct _machine_pwm_obj_t { uint8_t output; uint16_t prescaler; uint32_t period; // full period count ticks + uint32_t duty_ns; // just for reporting + uint16_t duty_u16; // just for reporting } machine_pwm_obj_t; #define PWM_NOT_INIT (0) @@ -53,6 +56,7 @@ typedef struct _machine_pwm_obj_t { #define PWM_TCC_ENABLED (2) #define PWM_MASTER_CLK (get_peripheral_freq()) #define PWM_FULL_SCALE (65536) +#define PWM_UPDATE_TIMEOUT (2000) static Tcc *tcc_instance[] = TCC_INSTS; @@ -148,6 +152,7 @@ STATIC mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args self->output = config.device_channel & 0x0f; self->prescaler = 1; self->period = 1; // Use an invalid but safe value + self->duty_u16 = self->duty_ns = 0; put_duty_value(self->device, self->channel, 0); Tcc *tcc = self->instance; @@ -240,6 +245,7 @@ void pwm_deinit_all(void) { device_status[i] = PWM_NOT_INIT; duty_type_flags[i] = 0; output_active[i] = 0; + memset(pwm_duty_values, 0, sizeof(pwm_duty_values)); } } @@ -256,8 +262,25 @@ STATIC void mp_machine_pwm_deinit(machine_pwm_obj_t *self) { } } +STATIC void wait_for_register_update(Tcc *tcc) { + // Wait for a period's end (may be long) to have the change settled + // Each loop cycle takes at least 1 ms, giving an implicit timeout. + for (int i = 0; i < PWM_UPDATE_TIMEOUT; i++) { + if (tcc->INTFLAG.reg & TCC_INTFLAG_OVF) { + break; + } + MICROPY_EVENT_POLL_HOOK + } + // Clear the flag, telling that a cycle has been handled. + tcc->INTFLAG.reg = TCC_INTFLAG_OVF; +} + STATIC mp_obj_t mp_machine_pwm_freq_get(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(PWM_MASTER_CLK / self->prescaler / self->period); + if (self->instance->CTRLA.reg & TCC_CTRLA_ENABLE) { + return MP_OBJ_NEW_SMALL_INT(PWM_MASTER_CLK / self->prescaler / self->period); + } else { + return MP_OBJ_NEW_SMALL_INT(0); + } } STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { @@ -267,7 +290,8 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { Tcc *tcc = self->instance; if (freq < 1) { - mp_raise_ValueError(MP_ERROR_TEXT("invalid freq")); + pwm_stop_device(self->device); + return; } // Get the actual settings of prescaler & period from the unit @@ -288,6 +312,11 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { if (period < 2) { mp_raise_ValueError(MP_ERROR_TEXT("freq too large")); } + // If the PWM is running, ensure that a cycle has passed since the + // previous setting before setting a new frequency/duty value + if (tcc->CTRLA.reg & TCC_CTRLA_ENABLE) { + wait_for_register_update(tcc); + } // Check, if the prescaler has to be changed and stop the device if so. if (index != tcc->CTRLA.bit.PRESCALER) { // stop the device @@ -339,25 +368,37 @@ STATIC void mp_machine_pwm_freq_set(machine_pwm_obj_t *self, mp_int_t freq) { } STATIC mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(self->instance->CC[self->channel].reg * PWM_FULL_SCALE / (self->instance->PER.reg + 1)); + return MP_OBJ_NEW_SMALL_INT(self->duty_u16); } STATIC void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty_u16) { + // Remember the values for update & reporting put_duty_value(self->device, self->channel, duty_u16); + self->duty_u16 = duty_u16; + self->duty_ns = 0; // If the device is enabled, than the period is set and we get a reasonable value for // the duty cycle, set to the CCBUF register. Otherwise, PWM does not start. if (self->instance->CTRLA.reg & TCC_CTRLA_ENABLE) { - self->instance->CCBUF[self->channel].reg = (uint64_t)duty_u16 * (self->instance->PER.reg + 1) / PWM_FULL_SCALE; + // Ensure that a cycle has passed updating the registers + // since the previous setting before setting a new duty value + wait_for_register_update(self->instance); + self->instance->CCBUF[self->channel].reg = (uint64_t)duty_u16 * (self->period) / PWM_FULL_SCALE; } duty_type_flags[self->device] |= 1 << self->channel; } STATIC mp_obj_t mp_machine_pwm_duty_get_ns(machine_pwm_obj_t *self) { - return MP_OBJ_NEW_SMALL_INT(1000000000ULL * self->instance->CC[self->channel].reg * self->prescaler / PWM_MASTER_CLK); + return MP_OBJ_NEW_SMALL_INT(self->duty_ns); } STATIC void mp_machine_pwm_duty_set_ns(machine_pwm_obj_t *self, mp_int_t duty_ns) { + // Remember the values for update & reporting put_duty_value(self->device, self->channel, duty_ns); + self->duty_ns = duty_ns; + self->duty_u16 = 0; + // Ensure that a cycle has passed updating the registers + // since the previous setting before setting a new duty value + wait_for_register_update(self->instance); self->instance->CCBUF[self->channel].reg = (uint64_t)duty_ns * PWM_MASTER_CLK / self->prescaler / 1000000000ULL; duty_type_flags[self->device] &= ~(1 << self->channel); } From 9c2bc379f183ff9f3280842eb236e8f4b8635835 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 8 Oct 2022 12:32:55 +0200 Subject: [PATCH 040/823] samd/machine_uart: Use a finaliser to tidy up UART on soft reset. And use the common sercom_table, saving a few bytes of RAM. --- ports/samd/machine_uart.c | 22 ++++++++-------------- ports/samd/main.c | 2 -- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 76b564e874a6..2526b450931b 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -59,7 +59,7 @@ typedef struct _machine_uart_obj_t { } machine_uart_obj_t; Sercom *sercom_instance[] = SERCOM_INSTS; -machine_uart_obj_t *uart_table[SERCOM_INST_NUM] = {}; +extern void *sercom_table[SERCOM_INST_NUM]; STATIC const char *_parity_name[] = {"None", "", "0", "1"}; // Is defined as 0, 2, 3 @@ -82,7 +82,7 @@ STATIC void uart_drain_rx_fifo(machine_uart_obj_t *self, Sercom *uart) { } void common_uart_irq_handler(int uart_id) { - machine_uart_obj_t *self = uart_table[uart_id]; + machine_uart_obj_t *self = sercom_table[uart_id]; // Handle IRQ if (self != NULL) { Sercom *uart = sercom_instance[self->id]; @@ -322,7 +322,8 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, } // Create the UART object and fill it with defaults. - machine_uart_obj_t *self = mp_obj_malloc(machine_uart_obj_t, &machine_uart_type); + machine_uart_obj_t *self = m_new_obj_with_finaliser(machine_uart_obj_t); + self->base.type = &machine_uart_type; self->id = uart_id; self->baudrate = DEFAULT_UART_BAUDRATE; self->bits = 8; @@ -332,7 +333,7 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, self->tx = 0xff; self->rx = 0xff; self->new = true; - uart_table[uart_id] = self; + sercom_table[uart_id] = self; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); @@ -348,10 +349,10 @@ MP_DEFINE_CONST_FUN_OBJ_KW(machine_uart_init_obj, 1, machine_uart_init); STATIC mp_obj_t machine_uart_deinit(mp_obj_t self_in) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); Sercom *uart = sercom_instance[self->id]; - // clear table entry of uart - uart_table[self->id] = NULL; // Disable interrupts uart->USART.INTENCLR.reg = 0xff; + // clear table entry of uart + sercom_table[self->id] = NULL; MP_STATE_PORT(samd_uart_rx_buffer[self->id]) = NULL; #if MICROPY_HW_UART_TXBUF MP_STATE_PORT(samd_uart_tx_buffer[self->id]) = NULL; @@ -409,14 +410,6 @@ STATIC mp_obj_t machine_uart_txdone(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_uart_txdone_obj, machine_uart_txdone); -void uart_deinit_all(void) { - for (int i = 0; i < SERCOM_INST_NUM; i++) { - if (uart_table[i] != NULL) { - machine_uart_deinit((mp_obj_t)uart_table[i]); - } - } -} - STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_uart_init_obj) }, { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&machine_uart_deinit_obj) }, @@ -430,6 +423,7 @@ STATIC const mp_rom_map_elem_t machine_uart_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_readline), MP_ROM_PTR(&mp_stream_unbuffered_readline_obj) }, { MP_ROM_QSTR(MP_QSTR_readinto), MP_ROM_PTR(&mp_stream_readinto_obj) }, { MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&mp_stream_write_obj) }, + { MP_ROM_QSTR(MP_QSTR___del__), MP_ROM_PTR(&machine_uart_deinit_obj) }, }; STATIC MP_DEFINE_CONST_DICT(machine_uart_locals_dict, machine_uart_locals_dict_table); diff --git a/ports/samd/main.c b/ports/samd/main.c index 0f24e2382ee8..725fd651d02c 100644 --- a/ports/samd/main.c +++ b/ports/samd/main.c @@ -39,7 +39,6 @@ extern void adc_deinit_all(void); extern void pin_irq_deinit_all(void); extern void pwm_deinit_all(void); extern void sercom_deinit_all(void); -extern void uart_deinit_all(void); void samd_main(void) { mp_stack_set_top(&_estack); @@ -86,7 +85,6 @@ void samd_main(void) { pin_irq_deinit_all(); pwm_deinit_all(); sercom_deinit_all(); - uart_deinit_all(); soft_timer_deinit(); gc_sweep_all(); mp_deinit(); From d74215a3137da5e9001aed92c8c1b06d828ff9d2 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 8 Oct 2022 12:34:45 +0200 Subject: [PATCH 041/823] samd/machine_spi: Implement spi.deinit() and simplify sercom_deinit_all. The sercom_deinit_all() function does not need the object pointers. --- ports/samd/machine_spi.c | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/ports/samd/machine_spi.c b/ports/samd/machine_spi.c index 43150f67f15a..d4c7a05bb60f 100644 --- a/ports/samd/machine_spi.c +++ b/ports/samd/machine_spi.c @@ -254,17 +254,23 @@ STATIC mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, s return self; } +STATIC void machine_sercom_deinit(mp_obj_base_t *self_in) { + machine_spi_obj_t *self = MP_OBJ_TO_PTR(self_in); + Sercom *spi = sercom_instance[self->id]; + // Disable interrupts (if any) + spi->SPI.INTENCLR.reg = 0xff; + sercom_enable(spi, 0); + // clear table entry of spi + sercom_table[self->id] = NULL; +} + void sercom_deinit_all(void) { for (int i = 0; i < SERCOM_INST_NUM; i++) { - if (sercom_table[i] != NULL) { - machine_spi_obj_t *self = sercom_table[i]; - Sercom *spi = sercom_instance[self->id]; - // Disable interrupts (if any) - spi->SPI.INTENCLR.reg = 0xff; - // clear table entry of spi - sercom_table[i] = NULL; - sercom_enable(spi, 0); - } + Sercom *spi = sercom_instance[i]; + spi->SPI.INTENCLR.reg = 0xff; + sercom_register_irq(i, NULL); + sercom_enable(spi, 0); + sercom_table[i] = NULL; } } @@ -316,6 +322,7 @@ STATIC void machine_spi_transfer(mp_obj_base_t *self_in, size_t len, const uint8 STATIC const mp_machine_spi_p_t machine_spi_p = { .init = machine_spi_init, + .deinit = machine_sercom_deinit, .transfer = machine_spi_transfer, }; From a1eebc507eda8f5ec052d0ef0450c52f2320e106 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Thu, 13 Oct 2022 19:04:37 +0200 Subject: [PATCH 042/823] samd/machine_spi: Register SerCom objects as root pointers. Protect SerCom (UART, SPI, I2C) objects from getting freed by the GC when they go out of scope without being deinitialized. Otherwise the ISR of a Sercom may access an invalid data structure. --- ports/samd/machine_i2c.c | 5 ++--- ports/samd/machine_spi.c | 10 +++++----- ports/samd/machine_uart.c | 7 +++---- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/ports/samd/machine_i2c.c b/ports/samd/machine_i2c.c index 0a8f5b94db08..d943b6b2f422 100644 --- a/ports/samd/machine_i2c.c +++ b/ports/samd/machine_i2c.c @@ -69,7 +69,6 @@ typedef struct _machine_i2c_obj_t { } machine_i2c_obj_t; extern Sercom *sercom_instance[]; -extern void *sercom_table[SERCOM_INST_NUM]; STATIC void i2c_send_command(Sercom *i2c, uint8_t command) { i2c->I2CM.CTRLB.bit.CMD = command; @@ -79,7 +78,7 @@ STATIC void i2c_send_command(Sercom *i2c, uint8_t command) { void common_i2c_irq_handler(int i2c_id) { // handle Sercom I2C IRQ - machine_i2c_obj_t *self = sercom_table[i2c_id]; + machine_i2c_obj_t *self = MP_STATE_PORT(sercom_table[i2c_id]); // Handle IRQ if (self != NULL) { Sercom *i2c = self->instance; @@ -159,7 +158,7 @@ mp_obj_t machine_i2c_make_new(const mp_obj_type_t *type, size_t n_args, size_t n if (sda_pad_config.pad_nr != 0 || scl_pad_config.pad_nr != 1) { mp_raise_ValueError(MP_ERROR_TEXT("invalid pin for sda or scl")); } - sercom_table[self->id] = self; + MP_STATE_PORT(sercom_table[self->id]) = self; self->freq = args[ARG_freq].u_int; // Configure the Pin mux. diff --git a/ports/samd/machine_spi.c b/ports/samd/machine_spi.c index d4c7a05bb60f..4ffc7095cc2e 100644 --- a/ports/samd/machine_spi.c +++ b/ports/samd/machine_spi.c @@ -57,11 +57,11 @@ typedef struct _machine_spi_obj_t { } machine_spi_obj_t; extern Sercom *sercom_instance[]; -void *sercom_table[SERCOM_INST_NUM] = {}; +MP_REGISTER_ROOT_POINTER(void *sercom_table[SERCOM_INST_NUM]); void common_spi_irq_handler(int spi_id) { // handle Sercom IRQ RXC - machine_spi_obj_t *self = sercom_table[spi_id]; + machine_spi_obj_t *self = MP_STATE_PORT(sercom_table[spi_id]); // Handle IRQ if (self != NULL) { Sercom *spi = sercom_instance[self->id]; @@ -246,7 +246,7 @@ STATIC mp_obj_t machine_spi_make_new(const mp_obj_type_t *type, size_t n_args, s self->sck = 0xff; self->new = true; - sercom_table[spi_id] = self; + MP_STATE_PORT(sercom_table[spi_id]) = self; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); @@ -261,7 +261,7 @@ STATIC void machine_sercom_deinit(mp_obj_base_t *self_in) { spi->SPI.INTENCLR.reg = 0xff; sercom_enable(spi, 0); // clear table entry of spi - sercom_table[self->id] = NULL; + MP_STATE_PORT(sercom_table[self->id]) = NULL; } void sercom_deinit_all(void) { @@ -270,7 +270,7 @@ void sercom_deinit_all(void) { spi->SPI.INTENCLR.reg = 0xff; sercom_register_irq(i, NULL); sercom_enable(spi, 0); - sercom_table[i] = NULL; + MP_STATE_PORT(sercom_table[i]) = NULL; } } diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index 2526b450931b..4b8fa3b60d8f 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -59,7 +59,6 @@ typedef struct _machine_uart_obj_t { } machine_uart_obj_t; Sercom *sercom_instance[] = SERCOM_INSTS; -extern void *sercom_table[SERCOM_INST_NUM]; STATIC const char *_parity_name[] = {"None", "", "0", "1"}; // Is defined as 0, 2, 3 @@ -82,7 +81,7 @@ STATIC void uart_drain_rx_fifo(machine_uart_obj_t *self, Sercom *uart) { } void common_uart_irq_handler(int uart_id) { - machine_uart_obj_t *self = sercom_table[uart_id]; + machine_uart_obj_t *self = MP_STATE_PORT(sercom_table[uart_id]); // Handle IRQ if (self != NULL) { Sercom *uart = sercom_instance[self->id]; @@ -333,7 +332,7 @@ STATIC mp_obj_t machine_uart_make_new(const mp_obj_type_t *type, size_t n_args, self->tx = 0xff; self->rx = 0xff; self->new = true; - sercom_table[uart_id] = self; + MP_STATE_PORT(sercom_table[uart_id]) = self; mp_map_t kw_args; mp_map_init_fixed_table(&kw_args, n_kw, args + n_args); @@ -352,7 +351,7 @@ STATIC mp_obj_t machine_uart_deinit(mp_obj_t self_in) { // Disable interrupts uart->USART.INTENCLR.reg = 0xff; // clear table entry of uart - sercom_table[self->id] = NULL; + MP_STATE_PORT(sercom_table[self->id]) = NULL; MP_STATE_PORT(samd_uart_rx_buffer[self->id]) = NULL; #if MICROPY_HW_UART_TXBUF MP_STATE_PORT(samd_uart_tx_buffer[self->id]) = NULL; From fcccfc176b225001b402ce2ced062344990789e0 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 17 Oct 2022 18:05:28 +0200 Subject: [PATCH 043/823] samd/modmachine: Add machine.softreset(). For consistency with other ports. --- ports/samd/modmachine.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/samd/modmachine.c b/ports/samd/modmachine.c index 47fc03d82410..8adc06f52654 100644 --- a/ports/samd/modmachine.c +++ b/ports/samd/modmachine.c @@ -32,6 +32,7 @@ #include "extmod/machine_signal.h" #include "extmod/machine_spi.h" #include "drivers/dht/dht.h" +#include "shared/runtime/pyexec.h" #include "modmachine.h" #include "samd_soc.h" @@ -57,6 +58,12 @@ extern bool EIC_occured; extern uint32_t _dbl_tap_addr; +STATIC mp_obj_t machine_soft_reset(void) { + pyexec_system_exit = PYEXEC_FORCED_EXIT; + mp_raise_type(&mp_type_SystemExit); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_0(machine_soft_reset_obj, machine_soft_reset); + STATIC mp_obj_t machine_reset(void) { *DBL_TAP_ADDR = DBL_TAP_MAGIC_RESET; #ifdef DBL_TAP_ADDR_ALT @@ -219,6 +226,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(machine_lightsleep_obj, 0, 1, machine STATIC const mp_rom_map_elem_t machine_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR___name__), MP_ROM_QSTR(MP_QSTR_umachine) }, + { MP_ROM_QSTR(MP_QSTR_soft_reset), MP_ROM_PTR(&machine_soft_reset_obj) }, { MP_ROM_QSTR(MP_QSTR_reset), MP_ROM_PTR(&machine_reset_obj) }, { MP_ROM_QSTR(MP_QSTR_bootloader), MP_ROM_PTR(&machine_bootloader_obj) }, { MP_ROM_QSTR(MP_QSTR_freq), MP_ROM_PTR(&machine_freq_obj) }, From d75c7e822cbfe7014fede1420f996a0af04bb13c Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Wed, 21 Sep 2022 09:57:10 +1000 Subject: [PATCH 044/823] py/obj: Add comments explaining the slot index scheme. Signed-off-by: Jim Mussared --- py/obj.h | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/py/obj.h b/py/obj.h index 8d62dd4f3cf0..b5b855bad395 100644 --- a/py/obj.h +++ b/py/obj.h @@ -753,10 +753,16 @@ typedef struct _mp_obj_full_type_t { #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_11(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, } } #define MP_DEFINE_CONST_OBJ_TYPE_NARGS_12(_struct_type, _typename, _name, _flags, f1, v1, f2, v2, f3, v3, f4, v4, f5, v5, f6, v6, f7, v7, f8, v8, f9, v9, f10, v10, f11, v11, f12, v12) const _struct_type _typename = { .base = { &mp_type_type }, .name = _name, .flags = _flags, .slot_index_##f1 = 1, .slot_index_##f2 = 2, .slot_index_##f3 = 3, .slot_index_##f4 = 4, .slot_index_##f5 = 5, .slot_index_##f6 = 6, .slot_index_##f7 = 7, .slot_index_##f8 = 8, .slot_index_##f9 = 9, .slot_index_##f10 = 10, .slot_index_##f11 = 11, .slot_index_##f12 = 12, .slots = { v1, v2, v3, v4, v5, v6, v7, v8, v9, v10, v11, v12, } } +// Because the mp_obj_type_t instances are in (zero-initialised) ROM, we take +// slot_index_foo=0 to mean that the slot is unset. This also simplifies checking +// if the slot is set. That means that we need to store index+1 in slot_index_foo +// though and then access it as slots[slot_index_foo - 1]. This is an implementation +// detail, the user of these macros doesn't need to be aware of it, and when using +// MP_OBJ_TYPE_OFFSETOF_SLOT you should use zero-based indexing. #define MP_OBJ_TYPE_HAS_SLOT(t, f) ((t)->slot_index_##f) #define MP_OBJ_TYPE_GET_SLOT(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(t)->slots[(t)->slot_index_##f - 1]) #define MP_OBJ_TYPE_GET_SLOT_OR_NULL(t, f) (_MP_OBJ_TYPE_SLOT_TYPE_##f(MP_OBJ_TYPE_HAS_SLOT(t, f) ? MP_OBJ_TYPE_GET_SLOT(t, f) : NULL)) -#define MP_OBJ_TYPE_SET_SLOT(t, f, v, n) ((t)->slot_index_##f = (n) + 1, (t)->slots[(t)->slot_index_##f - 1] = (void *)v) +#define MP_OBJ_TYPE_SET_SLOT(t, f, v, n) ((t)->slot_index_##f = (n) + 1, (t)->slots[(n)] = (void *)v) #define MP_OBJ_TYPE_OFFSETOF_SLOT(f) (offsetof(mp_obj_type_t, slot_index_##f)) #define MP_OBJ_TYPE_HAS_SLOT_BY_OFFSET(t, offset) (*(uint8_t *)((char *)(t) + (offset)) != 0) From 64af916c111b61bce82c00f356a6b1cb81946d87 Mon Sep 17 00:00:00 2001 From: Jim Mussared Date: Tue, 18 Oct 2022 16:24:33 +1100 Subject: [PATCH 045/823] docs/templates/layout.html: Indicate latest vs release docs. When looking at latest (the default for docs.micropython.org), make it clear that this isn't the release version. - Changes the version in the top-left to "latest". - Adds a message to the top of each page to explain. For future release versions, add a short message to link to the latest version. This work was funded through GitHub Sponsors. Signed-off-by: Jim Mussared --- docs/conf.py | 3 ++- docs/templates/layout.html | 25 +++++++++++++++++++++++++ docs/templates/topindex.html | 3 +-- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 5533bf01918a..ce5c037568ad 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -33,6 +33,7 @@ 'downloads':[ ('PDF', url_pattern % micropy_version + '/micropython-docs.pdf'), ], + 'is_release': micropy_version != 'latest', } @@ -74,7 +75,7 @@ # # We don't follow "The short X.Y version" vs "The full version, including alpha/beta/rc tags" # breakdown, so use the same version identifier for both to avoid confusion. -version = release = '1.19.1' +version = release = micropy_version # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/docs/templates/layout.html b/docs/templates/layout.html index a6caa0bc5a11..8563f02af00a 100644 --- a/docs/templates/layout.html +++ b/docs/templates/layout.html @@ -4,3 +4,28 @@ {# we change the master_doc variable so that links to the index page are to index.html instead of _index.html #} {% set master_doc = "index" %} + +{% block document %} + {% if is_release %} +
+

+ This is the v{{ release }} version of the MicroPython + documentation. The latest + development version of this page may be more current. +

+
+ {% else %} +
+

+ This is the documentation for the latest development branch of + MicroPython and may refer to features that are not available in released + versions. +

+

+ If you are looking for the documentation for a specific release, use + the drop-down menu on the left and select the desired version. +

+
+ {% endif %} + {{ super() }} +{% endblock %} diff --git a/docs/templates/topindex.html b/docs/templates/topindex.html index cfe3ad1516ac..4e163b6efe90 100644 --- a/docs/templates/topindex.html +++ b/docs/templates/topindex.html @@ -5,8 +5,7 @@

MicroPython documentation

- {{ _('Welcome! This is the documentation for MicroPython') }} - v{{ release|e }}{% if last_updated %}, {{ _('last updated') }} {{ last_updated|e }}{% endif %}. + {{ _('Welcome! This is the documentation for MicroPython') }}{% if last_updated %}, {{ _('last updated') }} {{ last_updated|e }}{% endif %}.

From 11910e2fa1c8f5dc76cbc90598ff81526d1f6312 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 20 Jun 2022 14:02:30 +0200 Subject: [PATCH 046/823] docs/samd: Add documentation for the samd port. Includes a general overview, a quickref, pinout tables, and the beginnings of a tutorial. --- docs/index.rst | 1 + docs/samd/general.rst | 88 +++ docs/samd/img/itsybitsy_m4_express.jpg | Bin 0 -> 151296 bytes docs/samd/pinout.rst | 850 +++++++++++++++++++++++++ docs/samd/quickref.rst | 469 ++++++++++++++ docs/samd/tutorial/intro.rst | 84 +++ docs/templates/topindex.html | 4 + 7 files changed, 1496 insertions(+) create mode 100644 docs/samd/general.rst create mode 100644 docs/samd/img/itsybitsy_m4_express.jpg create mode 100644 docs/samd/pinout.rst create mode 100644 docs/samd/quickref.rst create mode 100644 docs/samd/tutorial/intro.rst diff --git a/docs/index.rst b/docs/index.rst index 9a021b3906d4..64b83618da14 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -17,3 +17,4 @@ MicroPython documentation and references unix/quickref.rst zephyr/quickref.rst renesas-ra/quickref.rst + samd/quickref.rst diff --git a/docs/samd/general.rst b/docs/samd/general.rst new file mode 100644 index 000000000000..66e6d2a31f69 --- /dev/null +++ b/docs/samd/general.rst @@ -0,0 +1,88 @@ +.. _samd_general: + +General information about the SAMD port +======================================= + +The SAMD21/SAMD51 MCU family is a high performance family of devices made by MicroChip. +The SAMD21 devices are based on an ARM M0+ core, the SAMD51 device on ARM Cortex M4 core. +They provide many on-chip I/O units for building small to medium sized devices. + +Multitude of boards +------------------- + +There is a multitude of modules and boards from different sources which carry +an SAMD21/SAMD51 chip. MicroPython aims to provide a generic port which runs on +as many boards/modules as possible, but there may be limitations. The +Adafruit ItsyBitsy M0 Express, Adafruit Feather M4 Express and the Adafruit ItsyBitsy M4 Express +development boards are taken as reference for the port (for example, testing is performed on them). +For any board you are using please make sure you have a data sheet, schematics +and other reference materials so you can look up any board-specific functions. + +The following boards are at the moment supported by the port: + +- ADAFRUIT FEATHER M0 EXPRESS +- ADAFRUIT FEATHER M4 EXPRESS +- ADAFRUIT ITSYBITSY M0 EXPRESS +- ADAFRUIT ITSYBITSY M4 EXPRESS +- ADAFRUIT TRINKET M0 +- MINISAM M4 +- SAMD21 XPLAINED PRO +- SEEED WIO TERMINAL +- SEEED XIAO + +To make a generic SAMD port and support as many boards as possible the +following design and implementation decision were made: + +* GPIO pin numbering is based on the board numbering. + Please have the manual/pin diagram of your board at hand + to find correspondence between your board pins and actual SAMD21/SAMD51 pins. + For the boards listed above, the relation between the board pin number and + the GPIO number can be found at :ref:`samd_pinout`. +* The pins that can be used by MicroPython are limited to those listed + in the board definition files. + +Technical specifications and SoC data sheets +-------------------------------------------- + +The data sheets and other reference material for SAMD21/SAMD51 chip are available +from the vendor site: https://www.microchip.com/en-us/products/microcontrollers-and-microprocessors/32-bit-mcus/sam-32-bit-mcus +They are the primary reference for the chip technical specifications, capabilities, +operating modes, internal functioning, etc. + +For your convenience, a few technical specifications are provided below: + +SAMD21: + +* Architecture: ARM Cortex M0+ +* CPU frequency: up to 48MHz +* Total RAM available: up to 32 kB (see table) +* Internal FlashROM: up to 256 kB + Some boards provide additional external SPI flash. +* GPIO: up to 52 (GPIOs are multiplexed with other functions, including + external FlashROM, UART, etc.) +* UART: up to 6 serial devices, which can used for UART, SPI or I2C. +* I2S: 1 I2S interfaces +* ADC: One 12-bit SAR ADC converter with 16 channels. +* Programming: using BootROM bootloader from USB. + +SAMD51: + +* Architecture: ARM Cortex M4 +* CPU frequency: up to 120MHz +* Total RAM available: up to 256 kB +* Internal FlashROM: up to 1 MB + Some boards provide additional external SPI flash. +* GPIO: up to 99 (GPIOs are multiplexed with other functions, including + external FlashROM, UART, etc.) +* UART: up to 8 serial devices, which can used for UART, SPI or I2C. +* I2S: 1 I2S interfaces +* ADC: Dual 12-bit SAR ADC converter with 16 channels. +* Programming: using BootROM bootloader from USB. + + +For more information see the excellent SAMD21/SAMD51 data sheets or reference manuals. + +At the moment, the SAM21 port of MicroPython requires 256kB flash, of which 64kB is used +for a small file system. The SAM51 port requires 512 kB of flash, of which all flash beyond +384kB is used for a file system. Some boards have additional flash memory, which can be used +as additional file space. diff --git a/docs/samd/img/itsybitsy_m4_express.jpg b/docs/samd/img/itsybitsy_m4_express.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ea18b5c3899dee9a6668451c794d3d50ad0fa390 GIT binary patch literal 151296 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⋘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�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 Xh{Ssd+t0FRX#At&<8SrXIs^aN+iI5( literal 0 HcmV?d00001 diff --git a/docs/samd/pinout.rst b/docs/samd/pinout.rst new file mode 100644 index 000000000000..7e8d7ad9014e --- /dev/null +++ b/docs/samd/pinout.rst @@ -0,0 +1,850 @@ +.. _samd_pinout: + +Pinout for the SAMD machine modules +=================================== + +The assignment of device functions to pins is very flexible. The same function may be used +at different pins. The representation of the assignment choices are given by a table, +which is a subset of the MCU's Pin MUX table and is specific to each board, as the +available pin set varies. The structure of the table is the same for each board, but +the set of rows is different. + +.. _samd21_pinout_table: + +Adafruit ItsyBitsy M0 Express pin assignment table +-------------------------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 0 PA11 D0 11 19 0/3 2/3 1/1 0/3 + 1 PA10 D1 10 18 0/2 2/2 1/0 0/2 + 2 PA14 D2 14 - 2/2 4/2 3/0 0/4 + 3 PA09 D3 9 17 0/1 2/1 0/1 1/3 + 4 PA08 D4 - 16 0/0 2/0 0/0 1/2 + 5 PA15 D5 15 - 2/3 4/3 3/1 0/5 + 7 PA21 D7 5 - 5/3 3/3 7/1 0/7 + 9 PA07 D9 7 7 - 0/3 1/1 - + 10 PA18 D10 2 - 1/2 3/2 3/0 0/2 + 11 PA16 D11 0 - 1/0 3/0 2/0 0/6 + 12 PA19 D12 3 - 1/3 3/3 3/1 0/3 + 13 PA17 D13 1 - 1/1 3/1 2/1 0/7 + 14 PA02 A0 2 0 - - - - + 15 PB08 A1 8 2 - 4/0 4/0 - + 16 PB09 A2 9 3 - 4/1 4/1 - + 17 PA04 A3 4 4 - 0/0 0/0 - + 18 PA05 A4 5 5 - 0/1 0/1 - + 19 PB02 A5 2 - - 5/0 6/0 - + 20 PA22 SDA 6 - 3/0 5/0 4/0 0/4 + 21 PA23 SCL 7 - 3/1 5/1 4/1 0/5 + 22 PB10 MOSI 10 - - 4/2 5/0 0/4 + 23 PA12 MISO 12 - 2/0 4/0 2/0 0/6 + 24 PB11 SCK 11 - - 4/3 5/1 0/5 + 25 PA00 DOTSTAR_CLK 0 - - 1/0 2/0 - + 26 PA01 DOTSTAR_DATA 1 - - 1/1 2/1 - + 27 PB22 FLASH_MOSI 6 - - 5/2 7/0 - + 28 PB03 FLASH_MISO 3 - - 5/1 6/1 - + 29 PB23 FLASH_SCK 7 - - 5/3 7/1 - +=== ==== ============ ==== ==== ====== ====== ====== ====== + + +Description of the columns: + +- *Pin* - The number that is expected at ``machine.Pin(n)``, if the pin is given + as a number. This is NOT the GPIO number, but the board pin number, as + given in the board specific definition file. +- *GPIO* - The GPIO number. +- *Pin Name* - The name of a Pin which is expected argument to ``machine.Pin("name")``. +- *IRQ* - The IRQ number assigned to that GPIO, used internally by ``Pin.irq()``. When + using ``Pin.irq()``, different pins must use different IRQs +- *ADC* - The ADC channel assigned to the pin. When using ADC, different pins must + not use the same ADC channel. +- *Serial* - Two columns with optional Serial signal assignments. Both may be used. + The cell content is device #/pad #. The pad # is the respective internal + signal of that serial device. Details below. +- *TCC/TC* - Two columns with assignments of the TCC modules for PWM. + The cell content is device #/output #. For PWM, devices 0, 1, and 2 + are used. The TC device pair 3/4 is used for ``ticks_us()``. + +SAMD21 UART assignments +``````````````````````` +The UART devices and signals must be chosen according to the following rules: + +- The TX signal must be at a Pin with pad numbers 2 or 0, like Pin 1 with serial + device 0 or 2. +- The RX pin may be assigned to one of the other pads. + +Examples for Adafruit ItsyBitsy M0 Express: + +- uart 0 at pins 0/1 This is the default UART at the RX/TX labelled pins +- uart 1 at pins 12/10 +- uart 2 at pins 0/1 +- uart 3 at pins 11/13 +- uart 4 at pins 2/5 +- uart 5 at pins SCL/SDA + +or other combinations. + +SAMD21 I2C assignments +`````````````````````` +The I2C devices and signals must be chosen according to the following rules: + +- The SDA signal must be at a Pin with pad numbers 0. +- The SCL signal must be at a Pin with pad numbers 1. + +Examples for Adafruit ItsyBitsy M0 Express: + +- I2C 0 at Pin A3/A4 +- I2C 1 at pins 11/13 +- I2C 2 at the pins 4/3 +- I2C 3 at the pins SDA/SCL This is the default I2C device at the SDA/SCl labelled pin +- I2C 4 at the pins A1/A2 +- I2C 5 at the pins SDA/SCL, + +or other combinations. + +SAMD21 SPI assignments +`````````````````````` +The I2C devices and signals must be chosen according to the following rules: + +- The following pad number pairs are suitable for MOSI/SCK: 0/1, 2/3, 3/1, and 0/3. +- The MISO signal must be at a Pin with a different pad number than MOSI or SCK. + +Examples for Adafruit ItsyBitsy M0 Express: + +- SPI 0 at pins 0/4/1 +- SPI 1 at pins 11/12/13 +- SPI 2 at pins 0/4/1 +- SPI 3 at pins 11/12/13 +- SPI 4 at Pin MOSI/MISO/SCK This is the default SPI device at the MOSI/MISO/SCK labelled pins. + +or other combinations. + + +SAMD21 PWM assignments +`````````````````````` + +The TCC/TC device numbers 0, 1 and 2 can be used for PWM. Device 0 has four +channels, device 1 and 2 have two channels. So in total 3 different PWM +frequencies can be used, and 8 different duty cycle values. + +The DAC output for the Adafruit ItsyBitsy M0 Express board is available at the pin A0. + +.. _samd51_pinout_table: + +Adafruit ItsyBitsy M4 Express pin assignment table +-------------------------------------------------- + +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== +Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + 2 PA02 A0 2 0 - - - - - - + 5 PA05 A1 5 5 - - 0/1 0/1 - - + 40 PB08 A2 8 2 0 - 4/0 4/0 - - + 41 PB09 A3 9 3 1 - 4/1 4/1 - - + 4 PA04 A4 4 4 - - 0/0 0/0 - - + 6 PA06 A5 6 6 - - 0/2 1/0 - - + 16 PA16 D0 0 - - 1/0 3/1 2/0 1/0 0/4 + 17 PA17 D1 1 - - 1/1 3/0 2/1 1/1 0/5 + 7 PA07 D2 7 7 - - 0/3 1/1 - - + 54 PB22 D3 22 - - 1/2 5/2 7/0 - - + 14 PA14 D4 14 - - 2/2 4/2 3/0 2/0 1/2 + 15 PA15 D5 15 - - 2/3 4/3 3/1 2/1 1/3 + 18 PA18 D7 2 - - 1/2 3/2 3/0 1/2 0/6 + 19 PA19 D9 3 - - 1/3 3/3 3/1 1/3 0/7 + 20 PA20 D10 4 - - 5/2 3/2 7/0 1/4 0/0 + 21 PA21 D11 5 - - 5/3 3/3 7/1 1/5 0/1 + 23 PA23 D12 7 - - 3/1 5/0 4/1 1/7 0/3 + 22 PA22 D13 6 - - 3/0 5/1 4/0 1/6 0/2 + 34 PB02 DOTSTAR_CLK 2 14 - - 5/0 6/0 2/2 - + 35 PB03 DOTSTAR_DATA 9 15 - - 5/1 6/1 - - + 43 PB11 FLASH_CS 12 - - - 4/3 5/1 0/5 1/1 + 11 PA11 FLASH_HOLD 11 11 - 0/3 2/3 1/1 0/3 1/7 + 9 PA09 FLASH_MISO 9 9 3 0/1 2/0 0/1 0/1 1/5 + 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 + 42 PB10 FLASH_SCK 10 - - - 4/2 5/0 0/4 1/0 + 10 PA10 FLASH_WP 10 10 - 0/2 2/2 1/0 0/2 1/6 + 55 PB23 MISO 7 - - 1/3 5/3 7/1 - - + 0 PA00 MOSI 0 - - - 1/0 2/0 - - + 1 PA01 SCK 1 - - - 1/1 2/1 - - + 13 PA13 SCL 13 - - 2/1 4/0 2/1 0/7 1/3 + 12 PA12 SDA 12 - - 2/0 4/1 2/0 0/6 1/2 + 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - + 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - + 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - + 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - + 3 PA03 - 3 10 - - - - - - + 27 PA27 - 11 - - - - - - - +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + + +Description of the columns: + +- *Pin* - The number that is expected at ``machine.Pin(n)``, if the pin is given + as a number. This is NOT the GPIO number, but the board pin number, as + given in the board specific definition file. +- *GPIO* - The GPIO number. +- *Pin Name* The name of a Pin which is expected argument to ``machine.Pin("name")``. +- *IRQ* - The IRQ number assigned to that GPIO, used internally by ``Pin.irq()``. When + using ``Pin.irq()``, different pins must use different IRQs +- *ADC* - The ADC0/1 channel assigned to the pin. When using ADC, different pins must + not use the same ADC device and channel. +- *Serial* - Two columns with optional Serial signal assignments. Both may be used. + The cell content is device #/pad #. The pad # is the respective internal + signal of that serial device. Details below. +- *TC* - These device are currently not assigned to Pin. the TC device pair 0/1 + is used for ``ticks_us()``. +- *PWM* - Two columns with assignments of the TCC modules for PWM + The cell content is device #/output #. Details below. + +SAMD51 UART assignments +``````````````````````` +The UART devices and signals must be chosen according to the following rules: + +- The TX signal must be at a Pin with pad numbers 0, like Pin 1 with serial + device 3. +- The RX pin may be assigned to one of the other pads. + +Examples for Adafruit ItsyBitsy 4 Express: + +- uart 0 at pins A4/A1 +- uart 1 at pins 1/0 This is the default UART at the RX/TX labelled pins +- uart 2 at pins SCL/SDA This is the default I2C device at the SDA/SCl labelled pin +- uart 3 at pins 0/1 +- uart 4 at pins SDA/SCL +- uart 5 at pins D12/D13 + +or other combinations. + +SAMD51 I2C assignments +`````````````````````` +The I2C devices and signals must be chosen according to the following rules: + +- The SDA signal must be at a Pin with pad numbers 0. +- The SCL signal must be at a Pin with pad numbers 1. + +Examples for Adafruit ItsyBitsy M0 Express: + +- I2C 0 at pins A3/A4 +- I2C 1 at pins 0/1 +- I2C 2 at the pins SDA/SCL +- I2C 3 at the pins 1/0 +- I2C 4 at the pins A2/A3 +- I2C 5 at the pins 12/13 + +or other combinations. + +SAMD51 SPI assignments +`````````````````````` +The SPI devices and signals must be chosen according to the following rules: + +- The following pad number pairs are suitable for MOSI/SCK: 0/1 and 3/1. +- The MISO signal must be at a Pin with a different pad number than MOSI or SCK. + +Examples for Adafruit ItsyBitsy M0 Express: + +- SPI 1 at Pin MOSI/MISO/SCK This is the default SPI device at the MOSI/MISO/SCK labelled pins. +- SPI 3 at pins 13/11/12 +- SPI 5 at pins 12/3/13 + +or other combinations. + + +SAMD51 PWM assignments +`````````````````````` + +The TCC/PWM device numbers 0 through 4 can be used for PWM. Device 0 has six +channels, device 1 has four channels, device 2 has three channels and devices +3 and 4 have two channels. So in total up to 5 different PWM frequencies +can be used, and up to 17 different duty cycle values. Note that these numbers +do not apply to every board. + +The DAC outputs for the Adafruit ItsyBitsy M4 Express board are available at the pins A0 and A1. + +Adafruit Feather M4 Express pin assignment table +------------------------------------------------ + +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== +Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + 2 PA02 A0 2 0 - - - - - - + 5 PA05 A1 5 5 - - 0/1 0/1 - - + 40 PB08 A2 8 2 0 - 4/0 4/0 - - + 41 PB09 A3 9 3 1 - 4/1 4/1 - - + 4 PA04 A4 4 4 - - 0/0 0/0 - - + 38 PB06 A5 6 - 8 - - - - - + 49 PB17 D0 1 - - 5/1 - 6/1 3/1 0/5 + 48 PB16 D1 0 - - 5/0 - 6/0 3/0 0/4 + 14 PA14 D4 14 - - 2/2 4/2 3/0 2/0 1/2 + 16 PA16 D5 0 - - 1/0 3/1 2/0 1/0 0/4 + 18 PA18 D6 2 - - 1/2 3/2 3/0 1/2 0/6 + 19 PA19 D9 3 - - 1/3 3/3 3/1 1/3 0/7 + 3 PA03 AREF 3 10 - - - - - - + 20 PA20 D10 4 - - 5/2 3/2 7/0 1/4 0/0 + 21 PA21 D11 5 - - 5/3 3/3 7/1 1/5 0/1 + 22 PA22 D12 6 - - 3/0 5/1 4/0 1/6 0/2 + 23 PA23 D13 7 - - 3/1 5/0 4/1 1/7 0/3 + 43 PB11 FLASH_CS 12 - - - 4/3 5/1 0/5 1/1 + 11 PA11 FLASH_HOLD 11 11 - 0/3 2/3 1/1 0/3 1/7 + 9 PA09 FLASH_MISO 9 9 3 0/1 2/0 0/1 0/1 1/5 + 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 + 42 PB10 FLASH_SCK 10 - - - 4/2 5/0 0/4 1/0 + 10 PA10 FLASH_WP 10 10 - 0/2 2/2 1/0 0/2 1/6 + 54 PB22 MISO 22 - - 1/2 5/2 7/0 - - + 55 PB23 MOSI 7 - - 1/3 5/3 7/1 - - + 35 PB03 NEOPIXEL 9 15 - - 5/1 6/1 - - + 17 PA17 SCK 1 - - 1/1 3/0 2/1 1/1 0/5 + 13 PA13 SCL 13 - - 2/1 4/0 2/1 0/7 1/3 + 12 PA12 SDA 12 - - 2/0 4/1 2/0 0/6 1/2 + 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - + 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - + 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - + 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - + 33 PB01 VDIV 1 13 - - 5/3 7/1 - - + 0 PA00 - 0 - - - 1/0 2/0 - - + 1 PA01 - 1 - - - 1/1 2/1 - - + 6 PA06 - 6 6 - - 0/2 1/0 - - + 7 PA07 - 7 7 - - 0/3 1/1 - - + 15 PA15 - 15 - - 2/3 4/3 3/1 2/1 1/3 + 27 PA27 - 11 - - - - - - - + 32 PB00 - 9 12 - - 5/2 7/0 - - + 34 PB02 - 2 14 - - 5/0 6/0 2/2 - + 36 PB04 - 4 - 6 - - - - - + 37 PB05 - 5 - 7 - - - - - + 39 PB07 - 7 - 9 - - - - - + 44 PB12 - 12 - - 4/0 - 4/0 3/0 0/0 + 45 PB13 - 13 - - 4/1 - 4/1 3/1 0/1 + 46 PB14 - 14 - - 4/2 - 5/0 4/0 0/2 + 47 PB15 - 15 - - 4/3 - 5/1 4/1 0/3 + 62 PB30 - 14 - - 7/0 5/1 0/0 4/0 0/6 + 63 PB31 - 15 - - 7/1 5/0 0/1 4/1 0/7 +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M4 Express :ref:`samd51_pinout_table`. + +The default devices at the board are: + +- UART 5 at pins 0/1, labelled RX/TX +- I2C 2 at pins 21/20, labelled SDA/SCL +- SPI 1 at pins 22/23/24, labelled MOSI, MISO and SCK +- DAC output on pins 14 and 15, labelled A0 and A1 + +SEEED XIAO pin assignment table +------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 2 PA02 A0_D0 2 0 - - - - + 4 PA04 A1_D1 4 4 - 0/0 0/0 - + 10 PA10 A2_D2 10 18 0/2 2/2 1/0 0/2 + 11 PA11 A3_D3 11 19 0/3 2/3 1/1 0/3 + 8 PA08 A4_D4 - 16 0/0 2/0 0/0 1/2 + 9 PA09 A5_D5 9 17 0/1 2/1 0/1 1/3 + 40 PB08 A6_D6 8 2 - 4/0 4/0 - + 41 PB09 A7_D7 9 3 - 4/1 4/1 - + 7 PA07 A8_D8 7 7 - 0/3 1/1 - + 5 PA05 A9_D9 5 5 - 0/1 0/1 - + 6 PA06 A10_D10 6 6 - 0/2 1/0 - + 18 PA18 RX_LED 2 - 1/2 3/2 3/0 0/2 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 19 PA19 TX_LED 3 - 1/3 3/3 3/1 0/3 + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 17 PA17 USER_LED 1 - 1/1 3/1 2/1 0/7 + 0 PA00 - 0 - - 1/0 2/0 - + 1 PA01 - 1 - - 1/1 2/1 - + 3 PA03 - 3 1 - - - - + 12 PA12 - 12 - 2/0 4/0 2/0 0/6 + 13 PA13 - 13 - 2/1 4/1 2/0 0/7 + 14 PA14 - 14 - 2/2 4/2 3/0 0/4 + 15 PA15 - 15 - 2/3 4/3 3/1 0/5 + 16 PA16 - 0 - 1/0 3/0 2/0 0/6 + 20 PA20 - 4 - 5/2 3/2 7/0 0/4 + 21 PA21 - 5 - 5/3 3/3 7/1 0/7 + 22 PA22 - 6 - 3/0 5/0 4/0 0/4 + 23 PA23 - 7 - 3/1 5/1 4/1 0/5 + 27 PA27 - 15 - - - - - + 28 PA28 - 8 - - - - - + 34 PB02 - 2 10 - 5/0 6/0 - + 35 PB03 - 3 11 - 5/1 6/1 - + 42 PB10 - 10 - - 4/2 5/0 0/4 + 43 PB11 - 11 - - 4/3 5/1 0/5 + 54 PB22 - 6 - - 5/2 7/0 - + 55 PB23 - 7 - - 5/3 7/1 - +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The default devices at the board are: + +- UART 4 at pins 7/6, labelled A6_D6/A7_D7 +- I2C 2 at pins 4/5, labelled A4_D4/A5_D5 +- SPI 0 at pins 10/9/8, labelled A10_D10, A9_D9 and A8_D8 +- DAC output on pin 0, labelled A0_D0 + +Adafruit Feather M0 Express pin assignment table +------------------------------------------------ + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 2 PA02 A0 2 0 - - - - + 40 PB08 A1 8 2 - 4/0 4/0 - + 41 PB09 A2 9 3 - 4/1 4/1 - + 4 PA04 A3 4 4 - 0/0 0/0 - + 5 PA05 A4 5 5 - 0/1 0/1 - + 34 PB02 A5 2 10 - 5/0 6/0 - + 11 PA11 D0 11 19 0/3 2/3 1/1 0/3 + 10 PA10 D1 10 18 0/2 2/2 1/0 0/2 + 14 PA14 D2 14 - 2/2 4/2 3/0 0/4 + 9 PA09 D3 9 17 0/1 2/1 0/1 1/3 + 8 PA08 D4 - 16 0/0 2/0 0/0 1/2 + 15 PA15 D5 15 - 2/3 4/3 3/1 0/5 + 20 PA20 D6 4 - 5/2 3/2 7/0 0/4 + 21 PA21 D7 5 - 5/3 3/3 7/1 0/7 + 7 PA07 D9 7 7 - 0/3 1/1 - + 55 PB23 RX 7 - - 5/3 7/1 - + 54 PB22 TX 6 - - 5/2 7/0 - + 18 PA18 D10 2 - 1/2 3/2 3/0 0/2 + 16 PA16 D11 0 - 1/0 3/0 2/0 0/6 + 19 PA19 D12 3 - 1/3 3/3 3/1 0/3 + 17 PA17 D13 1 - 1/1 3/1 2/1 0/7 + 13 PA13 FLASH_CS 13 - 2/1 4/1 2/0 0/7 + 35 PB03 LED_RX 3 11 - 5/1 6/1 - + 27 PA27 LED_TX 15 - - - - - + 12 PA12 MISO 12 - 2/0 4/0 2/0 0/6 + 42 PB10 MOSI 10 - - 4/2 5/0 0/4 + 6 PA06 NEOPIXEL 6 6 - 0/2 1/0 - + 43 PB11 SCK 11 - - 4/3 5/1 0/5 + 23 PA23 SCL 7 - 3/1 5/1 4/1 0/5 + 22 PA22 SDA 6 - 3/0 5/0 4/0 0/4 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 0 PA00 - 0 - - 1/0 2/0 - + 1 PA01 - 1 - - 1/1 2/1 - + 3 PA03 - 3 1 - - - - + 28 PA28 - 8 - - - - - +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The default devices at the board are: + +- UART 5 at pins 21/20, labelled RX/TX +- I2C 3 at pins 22/23, labelled SDA/SCL +- SPI 4 at pins 24/25/26, labelled MOSI, MISO and SCK +- DAC output on pin 14, labelled A0 + +Adafruit Trinket M0 pin assignment table +------------------------------------------------ + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 8 PA08 D0 - 16 0/0 2/0 0/0 1/2 + 2 PA02 D1 2 0 - - - - + 9 PA09 D2 9 17 0/1 2/1 0/1 1/3 + 7 PA07 D3 7 7 - 0/3 1/1 - + 6 PA06 D4 6 6 - 0/2 1/0 - + 1 PA01 DOTSTAR_CLK 1 - - 1/1 2/1 - + 0 PA00 DOTSTAR_DATA 0 - - 1/0 2/0 - + 10 PA10 LED 10 18 0/2 2/2 1/0 0/2 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 3 PA03 - 3 1 - - - - + 4 PA04 - 4 4 - 0/0 0/0 - + 5 PA05 - 5 5 - 0/1 0/1 - + 11 PA11 - 11 19 0/3 2/3 1/1 0/3 + 14 PA14 - 14 - 2/2 4/2 3/0 0/4 + 15 PA15 - 15 - 2/3 4/3 3/1 0/5 + 16 PA16 - 0 - 1/0 3/0 2/0 0/6 + 17 PA17 - 1 - 1/1 3/1 2/1 0/7 + 18 PA18 - 2 - 1/2 3/2 3/0 0/2 + 19 PA19 - 3 - 1/3 3/3 3/1 0/3 + 22 PA22 - 6 - 3/0 5/0 4/0 0/4 + 23 PA23 - 7 - 3/1 5/1 4/1 0/5 + 27 PA27 - 15 - - - - - + 28 PA28 - 8 - - - - - +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +The default devices at the board are: + +- UART 0 at pins 3/4, labelled D3/D4 +- I2C 2 at pins 0/2, labelled D0/D2 +- SPI 0 at pins 4/2/3, labelled D4, D2 and D0 +- DAC output on pin 1, labelled D1 + +SAMD21 Xplained PRO pin assignment table +---------------------------------------- + +=== ==== ============ ==== ==== ====== ====== ====== ====== +Pin GPIO Pin name IRQ ADC Serial Serial TCC/TC TCC/TC +=== ==== ============ ==== ==== ====== ====== ====== ====== + 32 PB00 EXT1_PIN3 0 8 - 5/2 7/0 - + 33 PB01 EXT1_PIN4 1 9 - 5/3 7/1 - + 38 PB06 EXT1_PIN5 6 14 - - - - + 39 PB07 EXT1_PIN6 7 15 - - - - + 34 PB02 EXT1_PIN7 2 10 - 5/0 6/0 - + 35 PB03 EXT1_PIN8 3 11 - 5/1 6/1 - + 36 PB04 EXT1_PIN9 4 12 - - - - + 37 PB05 EXT1_PIN10 5 13 - - - - + 8 PA08 EXT1_PIN11 - 16 0/0 2/0 0/0 1/2 + 9 PA09 EXT1_PIN12 9 17 0/1 2/1 0/1 1/3 + 41 PB09 EXT1_PIN13 9 3 - 4/1 4/1 - + 40 PB08 EXT1_PIN14 8 2 - 4/0 4/0 - + 5 PA05 EXT1_PIN15 5 5 - 0/1 0/1 - + 6 PA06 EXT1_PIN16 6 6 - 0/2 1/0 - + 4 PA04 EXT1_PIN17 4 4 - 0/0 0/0 - + 7 PA07 EXT1_PIN18 7 7 - 0/3 1/1 - + 10 PA10 EXT2_PIN3 10 18 0/2 2/2 1/0 0/2 + 11 PA11 EXT2_PIN4 11 19 0/3 2/3 1/1 0/3 + 20 PA20 EXT2_PIN5 4 - 5/2 3/2 7/0 0/4 + 21 PA21 EXT2_PIN6 5 - 5/3 3/3 7/1 0/7 + 44 PB12 EXT2_PIN7 12 - 4/0 - 4/0 0/6 + 45 PB13 EXT2_PIN8 13 - 4/1 - 4/1 0/7 + 46 PB14 EXT2_PIN9 14 - 4/2 - 5/0 - + 47 PB15 EXT2_PIN10 15 - 4/3 - 5/1 - + 43 PB11 EXT2_PIN13 11 - - 4/3 5/1 0/5 + 42 PB10 EXT2_PIN14 10 - - 4/2 5/0 0/4 + 17 PA17 EXT2_PIN15 1 - 1/1 3/1 2/1 0/7 + 18 PA18 EXT2_PIN16 2 - 1/2 3/2 3/0 0/2 + 16 PA16 EXT2_PIN17 0 - 1/0 3/0 2/0 0/6 + 19 PA19 EXT2_PIN18 3 - 1/3 3/3 3/1 0/3 + 2 PA02 EXT3_PIN3 2 0 - - - - + 3 PA03 EXT3_PIN4 3 1 - - - - + 15 PA15 EXT3_PIN6 15 - 2/3 4/3 3/1 0/5 + 12 PA12 EXT3_PIN7 12 - 2/0 4/0 2/0 0/6 + 13 PA13 EXT3_PIN8 13 - 2/1 4/1 2/0 0/7 + 28 PA28 EXT3_PIN9 8 - - - - - + 27 PA27 EXT3_PIN10 15 - - - - - + 49 PB17 EXT3_PIN15 1 - 5/1 - 6/1 0/5 + 54 PB22 EXT3_PIN16 6 - - 5/2 7/0 - + 48 PB16 EXT3_PIN17 9 - 5/0 - 6/0 0/4 + 55 PB23 EXT3_PIN18 7 - - 5/3 7/1 - + 62 PB30 LED 14 - - 5/0 0/0 1/2 + 30 PA30 SWCLK 10 - - 1/2 1/0 - + 31 PA31 SWDIO 11 - - 1/3 1/1 - + 24 PA24 USB_DM 12 - 3/2 5/2 5/0 1/2 + 25 PA25 USB_DP 13 - 3/3 5/3 5/1 1/3 + 0 PA00 - 0 - - 1/0 2/0 - + 1 PA01 - 1 - - 1/1 2/1 - + 14 PA14 - 14 - 2/2 4/2 3/0 0/4 + 22 PA22 - 6 - 3/0 5/0 4/0 0/4 + 23 PA23 - 7 - 3/1 5/1 4/1 0/5 + 63 PB31 - 15 - - 5/1 0/1 1/3 +=== ==== ============ ==== ==== ====== ====== ====== ====== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M0 Express :ref:`samd21_pinout_table`. + +There are no pins labelled for default devices on this board. DAC output +is on pin 32, labelled EXT3_PIN3 + +Minisam M4 pin assignment table +------------------------------- + +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== +Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + 16 PA16 D0 0 - - 1/0 3/1 2/0 1/0 0/4 + 17 PA17 D1 1 - - 1/1 3/0 2/1 1/1 0/5 + 19 PA19 D3 3 - - 1/3 3/3 3/1 1/3 0/7 + 20 PA20 D4 4 - - 5/2 3/2 7/0 1/4 0/0 + 21 PA21 D5 5 - - 5/3 3/3 7/1 1/5 0/1 + 2 PA02 A0_D9 2 0 - - - - - - + 40 PB08 A1_D10 8 2 0 - 4/0 4/0 - - + 41 PB09 A2_D11 9 3 1 - 4/1 4/1 - - + 4 PA04 A3_D12 4 4 - - 0/0 0/0 - - + 5 PA05 A4_D13 5 5 - - 0/1 0/1 - - + 6 PA06 A5 6 6 - - 0/2 1/0 - - + 7 PA07 A6_D2 7 7 - - 0/3 1/1 - - + 3 PA03 AREF 3 10 - - - - - - + 0 PA00 BUTTON 0 - - - 1/0 2/0 - - + 34 PB02 DOTSTAR_CLK 2 14 - - 5/0 6/0 2/2 - + 35 PB03 DOTSTAR_DATA 9 15 - - 5/1 6/1 - - + 15 PA15 LED 15 - - 2/3 4/3 3/1 2/1 1/3 + 55 PB23 MISO 7 - - 1/3 5/3 7/1 - - + 54 PB22 MOSI 22 - - 1/2 5/2 7/0 - - + 1 PA01 SCK 1 - - - 1/1 2/1 - - + 13 PA13 SCL 13 - - 2/1 4/0 2/1 0/7 1/3 + 12 PA12 SDA 12 - - 2/0 4/1 2/0 0/6 1/2 + 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - + 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - + 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - + 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - + 8 PA08 - - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 - 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 - 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 - 11 11 - 0/3 2/3 1/1 0/3 1/7 + 14 PA14 - 14 - - 2/2 4/2 3/0 2/0 1/2 + 18 PA18 - 2 - - 1/2 3/2 3/0 1/2 0/6 + 22 PA22 - 6 - - 3/0 5/1 4/0 1/6 0/2 + 23 PA23 - 7 - - 3/1 5/0 4/1 1/7 0/3 + 27 PA27 - 11 - - - - - - - + 42 PB10 - 10 - - - 4/2 5/0 0/4 1/0 + 43 PB11 - 12 - - - 4/3 5/1 0/5 1/1 +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M4 Express :ref:`samd51_pinout_table`. + +The default devices at the board are: + +- UART 1 at pins 6/7, labelled D0/D1 +- I2C 2 at pins 14/15, labelled SDA/SCL +- SPI 1 at pins 16/17/18, labelled MOSI, MISO and SCK +- DAC output on pins 0 and 4, labelled A0_D9 and A4_D13 + +Seeed WIO Terminal pin assignment table +--------------------------------------- + +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== +Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + 33 PB01 CS 1 13 - - 5/3 7/1 - - + 59 PB27 RX 13 - - 2/1 4/0 - 1/3 - + 58 PB26 TX 12 - - 2/0 4/1 - 1/2 - + 79 PC15 3V3_ENABLE 15 - - 7/3 6/3 - 0/5 1/1 + 78 PC14 5V_ENABLE 14 - - 7/2 6/2 - 0/4 1/0 + 40 PB08 A0_D0 8 2 0 - 4/0 4/0 - - + 41 PB09 A1_D1 9 3 1 - 4/1 4/1 - - + 7 PA07 A2_D2 7 7 - - 0/3 1/1 - - + 36 PB04 A3_D3 4 - 6 - - - - - + 37 PB05 A4_D4 5 - 7 - - - - - + 38 PB06 A5_D5 6 - 8 - - - - - + 4 PA04 A6_D6 4 4 - - 0/0 0/0 - - + 39 PB07 A7_D7 7 - 9 - - - - - + 6 PA06 A8_D8 6 6 - - 0/2 1/0 - - + 90 PC26 BUTTON_1 10 - - - - - - - + 91 PC27 BUTTON_2 11 - - 1/0 - - - - + 92 PC28 BUTTON_3 12 - - 1/1 - - - - +107 PD11 BUZZER 6 - - 7/3 6/3 - 0/4 - + 47 PB15 GPCLK0 15 - - 4/3 - 5/1 4/1 0/3 + 44 PB12 GPCLK1 12 - - 4/0 - 4/0 3/0 0/0 + 45 PB13 GPCLK2 13 - - 4/1 - 4/1 3/1 0/1 + 48 PB16 I2C_BCLK 0 - - 5/0 - 6/0 3/0 0/4 + 20 PA20 I2S_LRCLK 4 - - 5/2 3/2 7/0 1/4 0/0 + 21 PA21 I2S_SDIN 5 - - 5/3 3/3 7/1 1/5 0/1 + 22 PA22 I2S_SDOUT 6 - - 3/0 5/1 4/0 1/6 0/2 + 50 PB18 LCD_MISO 2 - - 5/2 7/2 - 1/0 - + 51 PB19 LCD_MOSI 3 - - 5/3 7/3 - 1/1 - + 52 PB20 LCD_SCK 4 - - 3/0 7/1 - 1/2 - + 53 PB21 LCD_CS 5 - - 3/1 7/0 - 1/3 - + 70 PC06 LCD_D/C 6 - - 6/2 - - - - + 71 PC07 LCD_RESET 9 - - 6/3 - - - - + 74 PC10 LCD_XL 10 - - 6/2 7/2 - 0/0 1/4 + 76 PC12 LCD_XR 12 - - 7/0 6/1 - 0/2 1/6 + 77 PC13 LCD_YD 13 - - 7/1 6/0 - 0/3 1/7 + 75 PC11 LCD_YU 11 - - 6/3 7/3 - 0/1 1/5 + 15 PA15 LED_BLUE 15 - - 2/3 4/3 3/1 2/1 1/3 + 69 PC05 LED_LCD 5 - - 6/1 - - - - + 94 PC30 MIC 14 - 12 - - - - - + 32 PB00 MISO 9 12 - - 5/2 7/0 - - + 34 PB02 MOSI 2 14 - - 5/0 6/0 2/2 - + 35 PB03 SCK 9 15 - - 5/1 6/1 - - + 12 PA12 SCL0 12 - - 2/0 4/1 2/0 0/6 1/2 + 13 PA13 SDA0 13 - - 2/1 4/0 2/1 0/7 1/3 + 16 PA16 SCL1 0 - - 1/0 3/1 2/0 1/0 0/4 + 17 PA17 SDA1 1 - - 1/1 3/0 2/1 1/1 0/5 +117 PD21 SD_DET 11 - - 1/3 3/3 - 1/1 - + 83 PC19 SD_CS 3 - - 6/3 0/3 - 0/3 - + 82 PC18 SD_MISO 2 - - 6/2 0/2 - 0/2 - + 80 PC16 SD_MOSI 0 - - 6/0 0/1 - 0/0 - + 81 PC17 SD_SCK 1 - - 6/1 0/0 - 0/1 - + 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - + 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - +108 PD12 SWITCH_B 7 - - - - - 0/5 - +116 PD20 SWITCH_U 10 - - 1/2 3/2 - 1/0 - +104 PD08 SWITCH_X 3 - - 7/0 6/1 - 0/1 - +105 PD09 SWITCH_Y 4 - - 7/1 6/0 - 0/2 - +106 PD10 SWITCH_Z 5 - - 7/2 6/2 - 0/3 - + 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - + 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - + 0 PA00 - 0 - - - 1/0 2/0 - - + 1 PA01 - 1 - - - 1/1 2/1 - - + 2 PA02 - 2 0 - - - - - - + 3 PA03 - 3 10 - - - - - - + 5 PA05 - 5 5 - - 0/1 0/1 - - + 8 PA08 - - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 - 9 9 3 0/1 2/0 0/1 0/1 1/5 + 10 PA10 - 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 - 11 11 - 0/3 2/3 1/1 0/3 1/7 + 14 PA14 - 14 - - 2/2 4/2 3/0 2/0 1/2 + 18 PA18 - 2 - - 1/2 3/2 3/0 1/2 0/6 + 19 PA19 - 3 - - 1/3 3/3 3/1 1/3 0/7 + 23 PA23 - 7 - - 3/1 5/0 4/1 1/7 0/3 + 27 PA27 - 11 - - - - - - - + 42 PB10 - 10 - - - 4/2 5/0 0/4 1/0 + 43 PB11 - 12 - - - 4/3 5/1 0/5 1/1 + 46 PB14 - 14 - - 4/2 - 5/0 4/0 0/2 + 49 PB17 - 1 - - 5/1 - 6/1 3/1 0/5 + 54 PB22 - 22 - - 1/2 5/2 7/0 - - + 55 PB23 - 7 - - 1/3 5/3 7/1 - - + 56 PB24 - 8 - - 0/0 2/1 - - - + 57 PB25 - 9 - - 0/1 2/0 - - - + 60 PB28 - 14 - - 2/2 4/2 - 1/4 - + 61 PB29 - 15 - - 2/3 4/3 - 1/5 - + 62 PB30 - 14 - - 7/0 5/1 0/0 4/0 0/6 + 63 PB31 - 15 - - 7/1 5/0 0/1 4/1 0/7 + 64 PC00 - 0 - 10 - - - - - + 65 PC01 - 1 - 11 - - - - - + 66 PC02 - 2 - 4 - - - - - + 67 PC03 - 3 - 5 - - - - - + 68 PC04 - 4 - - 6/0 - - 0/0 - + 84 PC20 - 4 - - - - - 0/4 - + 85 PC21 - 5 - - - - - 0/5 - + 86 PC22 - 6 - - 1/0 3/1 - 0/5 - + 87 PC23 - 7 - - 1/1 3/0 - 0/7 - + 88 PC24 - 8 - - 0/2 2/2 - - - + 89 PC25 - 9 - - 0/3 2/3 - - - + 95 PC31 - 15 - 13 - - - - - + 96 PD00 - 0 - 14 - - - - - + 97 PD01 - 1 - 15 - - - - - +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M4 Express :ref:`samd51_pinout_table`. + +There seems to be no default pin assignment for this board. + +Sparkfun SAMD51 Thing Plus pin assignment table +------------------------------------------------ + +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== +Pin GPIO Pin name IRQ ADC ADC Serial Serial TC PWM PWM +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + 2 PA02 A0 2 0 - - - - - - + 40 PB08 A1 8 2 0 - 4/0 4/0 - - + 41 PB09 A2 9 3 1 - 4/1 4/1 - - + 4 PA04 A3 4 4 - - 0/0 0/0 - - + 5 PA05 A4 5 5 - - 0/1 0/1 - - + 34 PB02 A5 2 14 - - 5/0 6/0 2/2 - + 13 PA13 D0 13 - - 2/1 4/0 2/1 0/7 1/3 + 12 PA12 D1 12 - - 2/0 4/1 2/0 0/6 1/2 + 6 PA06 D4 6 6 - - 0/2 1/0 - - + 15 PA15 D5 15 - - 2/3 4/3 3/1 2/1 1/3 + 20 PA20 D6 4 - - 5/2 3/2 7/0 1/4 0/0 + 21 PA21 D7 5 - - 5/3 3/3 7/1 1/5 0/1 + 7 PA07 D9 7 7 - - 0/3 1/1 - - + 18 PA18 D10 2 - - 1/2 3/2 3/0 1/2 0/6 + 16 PA16 D11 0 - - 1/0 3/1 2/0 1/0 0/4 + 19 PA19 D12 3 - - 1/3 3/3 3/1 1/3 0/7 + 17 PA17 D13 1 - - 1/1 3/0 2/1 1/1 0/5 + 10 PA10 FLASH_CS 10 10 - 0/2 2/2 1/0 0/2 1/6 + 11 PA11 FLASH_MISO 11 11 - 0/3 2/3 1/1 0/3 1/7 + 8 PA08 FLASH_MOSI - 8 2 0/0 2/1 0/0 0/0 1/4 + 9 PA09 FLASH_SCK 9 9 3 0/1 2/0 0/1 0/1 1/5 + 43 PB11 MISO 12 - - - 4/3 5/1 0/5 1/1 + 44 PB12 MOSI 12 - - 4/0 - 4/0 3/0 0/0 + 55 PB23 RXD 7 - - 1/3 5/3 7/1 - - + 35 PB03 RXLED 9 15 - - 5/1 6/1 - - + 45 PB13 SCK 13 - - 4/1 - 4/1 3/1 0/1 + 23 PA23 SCL 7 - - 3/1 5/0 4/1 1/7 0/3 + 22 PA22 SDA 6 - - 3/0 5/1 4/0 1/6 0/2 + 30 PA30 SWCLK 14 - - 7/2 1/2 6/0 2/0 - + 31 PA31 SWDIO 15 - - 7/3 1/3 6/1 2/1 - + 54 PB22 TXD 22 - - 1/2 5/2 7/0 - - + 27 PA27 TXLED 11 - - - - - - - + 24 PA24 USB_DM 8 - - 3/2 5/2 5/0 2/2 - + 25 PA25 USB_DP 9 - - 3/3 5/3 5/1 - - + 0 PA00 - 0 - - - 1/0 2/0 - - + 1 PA01 - 1 - - - 1/1 2/1 - - + 3 PA03 - 3 10 - - - - - - + 14 PA14 - 14 - - 2/2 4/2 3/0 2/0 1/2 + 32 PB00 - 9 12 - - 5/2 7/0 - - + 33 PB01 - 1 13 - - 5/3 7/1 - - + 36 PB04 - 4 - 6 - - - - - + 37 PB05 - 5 - 7 - - - - - + 38 PB06 - 6 - 8 - - - - - + 39 PB07 - 7 - 9 - - - - - + 42 PB10 - 10 - - - 4/2 5/0 0/4 1/0 + 46 PB14 - 14 - - 4/2 - 5/0 4/0 0/2 + 47 PB15 - 15 - - 4/3 - 5/1 4/1 0/3 + 48 PB16 - 0 - - 5/0 - 6/0 3/0 0/4 + 49 PB17 - 1 - - 5/1 - 6/1 3/1 0/5 + 62 PB30 - 14 - - 7/0 5/1 0/0 4/0 0/6 + 63 PB31 - 15 - - 7/1 5/0 0/1 4/1 0/7 +=== ==== ============ ==== ==== ==== ====== ====== ===== ===== ===== + +For the definition of the table columns see the explanation at the table for +Adafruit ItsyBitsy M4 Express :ref:`samd51_pinout_table`. + +The default devices at the board are: + +- UART 1 at pins 2/3, labelled RXD/TXD +- I2C 5 at pins 20/21, labelled SDA/SCL +- SPI 4 at pins 22/23/24, labelled MOSI, MISO and SCK +- DAC output on pins 14 and 18, labelled A0 and A4 + +Scripts for creating the pin assignment tables +---------------------------------------------- + +The tables shown above were created with small a Python script running on the target board:: + + from samd import pininfo + from machine import Pin + import os + + def print_entry(e, txt): + print(txt, end=": ") + if e == 255: + print(" - ", end="") + else: + print("%d/%d" % (e >> 4, e & 0x0f), end="") + + def print_pininfo(pin, info): + print("%3d" % pin, end=" ") + print("P%c%02d" % ("ABCD"[pin // 32], pin % 32), end="") + print(" %12s" % info[0], end="") + print(" IRQ:%2s" % (info[1] if info[1] != 255 else "-"), end="") + print(" ADC0:%2s" % (info[2] if info[2] != 255 else "-"), end="") + if len(info) == 7: + print_entry(info[3], " Serial1") + print_entry(info[4], " Serial2") + print_entry(info[5], " PWM1" if (info[5] >> 4) < 3 else " TC") + print_entry(info[6], " PWM2") + else: + print(" ADC1:%2s" % (info[3] if info[3] != 255 else "-"), end="") + print_entry(info[4], " Serial1") + print_entry(info[5], " Serial2") + print_entry(info[6], " TC") + print_entry(info[7], " PWM1") + print_entry(info[8], " PWM2") + print() + + def tblkey(i): + name = i[1][0] + if name != "-": + if len(name) < 3: + return " " + name + else: + return name + else: + return "zzzzzzz%03d" % i[0] + + def table(num = 127): + pintbl = [] + for i in range(num): + try: + pintbl.append((i, pininfo(i))) + except: + pass + # print("not defined") + + pintbl.sort(key=tblkey) + for item in pintbl: + print_pininfo(item[0], item[1]) + + table() diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst new file mode 100644 index 000000000000..5e8298d1b366 --- /dev/null +++ b/docs/samd/quickref.rst @@ -0,0 +1,469 @@ +.. _samd_quickref: + +Quick reference for the SAMD21/SAMD51 family +============================================ + +.. image:: img/itsybitsy_m4_express.jpg + :alt: Adafruit ItsyBitsy M4 Express board + :width: 640px + +The Adafruit ItsyBitsy M4 Express board. + +Below is a quick reference for SAMD21/SAMD51-based boards. If it is your first time +working with this board it may be useful to get an overview of the microcontroller: + +.. toctree:: + :maxdepth: 1 + + general.rst + tutorial/intro.rst + pinout.rst + + +Installing MicroPython +---------------------- + +See the corresponding section of tutorial: :ref:`samd_intro`. It also includes +a troubleshooting subsection. + +General board control +--------------------- + +The MicroPython REPL is on the USB port, configured in VCP mode. +Tab-completion is useful to find out what methods an object has. +Paste mode (Ctrl-E) is useful to paste a large slab of Python code into +the REPL. + +The :mod:`machine` module:: + + import machine + + machine.freq() # get the current frequency of the CPU + machine.freq(96_000_000) # set the CPU frequency to 96 MHz + +The range accepted by the function call is 1_000_000 to 200_000_000 (1 MHz to 200 MHz) +for SAMD51 and 1_000_000 to 48_000_000 (1 MHz to 48 MHz) for SAMD21. The safe +range for SAMD51 according to the data sheet is 96 MHz to 120 MHz. +At frequencies below 8 MHz USB will be disabled. Changing the frequency below 48 MHz +impacts the baud rates of UART, I2C and SPI. These have to be set again after +changing the CPU frequency. The ms and µs timers are not affected by the frequency +change. + + +Delay and timing +---------------- + +Use the :mod:`time

MicroPython documentation

Quick reference for the Renesas RA
general information for Renesas RA based boards, snippets of useful code, and a tutorial

+ From c138e10fbb60981ca9e9b7459e7b9ae0c2528c94 Mon Sep 17 00:00:00 2001 From: Damien George Date: Thu, 27 Oct 2022 12:57:10 +1100 Subject: [PATCH 047/823] py/makeversionhdr: Fall back to py/mpconfig.h instead of docs/conf.py. Commit 64af916c111b61bce82c00f356a6b1cb81946d87 removed the version string from docs/conf.py. py/mpconfig.h is a better place to get the version from, so use that (when there is no git repository). Signed-off-by: Damien George --- py/makeversionhdr.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/py/makeversionhdr.py b/py/makeversionhdr.py index 54b7fa9ab7d2..d1b41e63b084 100644 --- a/py/makeversionhdr.py +++ b/py/makeversionhdr.py @@ -62,21 +62,27 @@ def get_version_info_from_git(): return git_tag, git_hash -def get_version_info_from_docs_conf(): - with open(os.path.join(os.path.dirname(sys.argv[0]), "..", "docs", "conf.py")) as f: +def get_version_info_from_mpconfig(): + with open(os.path.join(os.path.dirname(sys.argv[0]), "..", "py", "mpconfig.h")) as f: for line in f: - if line.startswith("version = release = '"): - ver = line.strip().split(" = ")[2].strip("'") - git_tag = "v" + ver + if line.startswith("#define MICROPY_VERSION_MAJOR "): + ver_major = int(line.strip().split()[2]) + elif line.startswith("#define MICROPY_VERSION_MINOR "): + ver_minor = int(line.strip().split()[2]) + elif line.startswith("#define MICROPY_VERSION_MICRO "): + ver_micro = int(line.strip().split()[2]) + git_tag = "v%d.%d" % (ver_major, ver_minor) + if ver_micro != 0: + git_tag += ".%d" % (ver_micro,) return git_tag, "" return None def make_version_header(filename): - # Get version info using git, with fallback to docs/conf.py + # Get version info using git, with fallback to py/mpconfig.h info = get_version_info_from_git() if info is None: - info = get_version_info_from_docs_conf() + info = get_version_info_from_mpconfig() git_tag, git_hash = info From e20bb98392c988a410ba04d01d3f573e6c15803d Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 15 Oct 2022 21:37:51 +0200 Subject: [PATCH 048/823] mimxrt/machine_pin: Add the Pin.toggle() method. For consistency with other ports, even if this method is undocumented at the moment. --- ports/mimxrt/machine_pin.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/ports/mimxrt/machine_pin.c b/ports/mimxrt/machine_pin.c index 836bd8524b72..bb2c820100be 100644 --- a/ports/mimxrt/machine_pin.c +++ b/ports/mimxrt/machine_pin.c @@ -285,6 +285,14 @@ STATIC mp_obj_t machine_pin_on(mp_obj_t self_in) { } STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_on_obj, machine_pin_on); +// pin.toggle() +STATIC mp_obj_t machine_pin_toggle(mp_obj_t self_in) { + machine_pin_obj_t *self = self_in; + mp_hal_pin_toggle(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(machine_pin_toggle_obj, machine_pin_toggle); + // pin.value([value]) STATIC mp_obj_t machine_pin_value(size_t n_args, const mp_obj_t *args) { return machine_pin_obj_call(args[0], (n_args - 1), 0, args + 1); @@ -366,6 +374,7 @@ STATIC const mp_rom_map_elem_t machine_pin_locals_dict_table[] = { { MP_ROM_QSTR(MP_QSTR_on), MP_ROM_PTR(&machine_pin_on_obj) }, { MP_ROM_QSTR(MP_QSTR_low), MP_ROM_PTR(&machine_pin_off_obj) }, { MP_ROM_QSTR(MP_QSTR_high), MP_ROM_PTR(&machine_pin_on_obj) }, + { MP_ROM_QSTR(MP_QSTR_toggle), MP_ROM_PTR(&machine_pin_toggle_obj) }, { MP_ROM_QSTR(MP_QSTR_value), MP_ROM_PTR(&machine_pin_value_obj) }, { MP_ROM_QSTR(MP_QSTR_init), MP_ROM_PTR(&machine_pin_init_obj) }, { MP_ROM_QSTR(MP_QSTR_irq), MP_ROM_PTR(&machine_pin_irq_obj) }, From 9d2e179fa565d8af96fb6ce3643e00f999c9210a Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 22 Oct 2022 21:28:42 +0200 Subject: [PATCH 049/823] mimxrt: Fix CPU freeze when calling __WFE() in MICROPY_EVENT_POLL_HOOK. This issue affected i.MX RT 1052, 1062 and 1064. It seems to be addressed by Errata ERR006223, which also mentions i.MX RT101x and 102x, but these devices worked well even without the change. As a side effect, the current consumption at an idle REPL drops significantly with this fix. Fixes issue #7235. --- ports/mimxrt/boards/MIMXRT1052_clock_config.c | 1 + ports/mimxrt/boards/MIMXRT1062_clock_config.c | 1 + ports/mimxrt/boards/MIMXRT1064_clock_config.c | 1 + ports/mimxrt/mpconfigport.h | 1 + 4 files changed, 4 insertions(+) diff --git a/ports/mimxrt/boards/MIMXRT1052_clock_config.c b/ports/mimxrt/boards/MIMXRT1052_clock_config.c index 93492812977a..fa7450d487a6 100644 --- a/ports/mimxrt/boards/MIMXRT1052_clock_config.c +++ b/ports/mimxrt/boards/MIMXRT1052_clock_config.c @@ -465,4 +465,5 @@ void BOARD_BootClockRUN(void) { IOMUXC_GPR->GPR5 &= ~IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT2_MASK; /* Set SystemCoreClock variable. */ SystemCoreClock = BOARD_BOOTCLOCKRUN_CORE_CLOCK; + CLOCK_SetMode(kCLOCK_ModeRun); } diff --git a/ports/mimxrt/boards/MIMXRT1062_clock_config.c b/ports/mimxrt/boards/MIMXRT1062_clock_config.c index 05474167bd85..589ffb0b5831 100644 --- a/ports/mimxrt/boards/MIMXRT1062_clock_config.c +++ b/ports/mimxrt/boards/MIMXRT1062_clock_config.c @@ -487,4 +487,5 @@ void BOARD_BootClockRUN(void) { IOMUXC_GPR->GPR5 &= ~IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT2_MASK; /* Set SystemCoreClock variable. */ SystemCoreClock = BOARD_BOOTCLOCKRUN_CORE_CLOCK; + CLOCK_SetMode(kCLOCK_ModeRun); } diff --git a/ports/mimxrt/boards/MIMXRT1064_clock_config.c b/ports/mimxrt/boards/MIMXRT1064_clock_config.c index 5e49a2fff80e..56dd75d7fbf6 100644 --- a/ports/mimxrt/boards/MIMXRT1064_clock_config.c +++ b/ports/mimxrt/boards/MIMXRT1064_clock_config.c @@ -487,4 +487,5 @@ void BOARD_BootClockRUN(void) { IOMUXC_GPR->GPR5 &= ~IOMUXC_GPR_GPR5_VREF_1M_CLK_GPT2_MASK; /* Set SystemCoreClock variable. */ SystemCoreClock = BOARD_BOOTCLOCKRUN_CORE_CLOCK; + CLOCK_SetMode(kCLOCK_ModeRun); } diff --git a/ports/mimxrt/mpconfigport.h b/ports/mimxrt/mpconfigport.h index 8642d53ecc45..3f87800e3056 100644 --- a/ports/mimxrt/mpconfigport.h +++ b/ports/mimxrt/mpconfigport.h @@ -241,6 +241,7 @@ extern const struct _mp_obj_type_t network_lan_type; do { \ extern void mp_handle_pending(bool); \ mp_handle_pending(true); \ + __WFE(); \ } while (0); #define MICROPY_MAKE_POINTER_CALLABLE(p) ((void *)((mp_uint_t)(p) | 1)) From 65fa7fd8bbaf570be3f4654bcc597a46c70e4b75 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Sat, 15 Oct 2022 11:40:22 +0200 Subject: [PATCH 050/823] mimxrt/machine_timer: Use soft-timer implementation for machine.Timer. This releases the hardware timers for other tasks, which need a higher resolution and faster response. And it is less port-specific code. --- docs/mimxrt/quickref.rst | 9 +- ports/mimxrt/Makefile | 1 + ports/mimxrt/board_init.c | 3 - ports/mimxrt/machine_timer.c | 158 +++++++++-------------------------- ports/mimxrt/main.c | 2 + ports/mimxrt/mphalport.h | 4 + ports/mimxrt/systick.c | 7 ++ 7 files changed, 58 insertions(+), 126 deletions(-) diff --git a/docs/mimxrt/quickref.rst b/docs/mimxrt/quickref.rst index c75fe60c8d66..06f91f7f5031 100644 --- a/docs/mimxrt/quickref.rst +++ b/docs/mimxrt/quickref.rst @@ -56,21 +56,18 @@ Use the :mod:`time