diff options
author | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 |
---|---|---|
committer | Raghuram Subramani <raghus2247@gmail.com> | 2022-06-19 19:47:51 +0530 |
commit | 4fd287655a72b9aea14cdac715ad5b90ed082ed2 (patch) | |
tree | 65d393bc0e699dd12d05b29ba568e04cea666207 /circuitpython/ports/raspberrypi/common-hal | |
parent | 0150f70ce9c39e9e6dd878766c0620c85e47bed0 (diff) |
add circuitpython code
Diffstat (limited to 'circuitpython/ports/raspberrypi/common-hal')
83 files changed, 7352 insertions, 0 deletions
diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/SleepMemory.c b/circuitpython/ports/raspberrypi/common-hal/alarm/SleepMemory.c new file mode 100644 index 0000000..c176529 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/SleepMemory.c @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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 <string.h> + +#include "py/runtime.h" +#include "common-hal/alarm/SleepMemory.h" +#include "shared-bindings/alarm/SleepMemory.h" + +void alarm_sleep_memory_reset(void) { +} + +uint32_t common_hal_alarm_sleep_memory_get_length(alarm_sleep_memory_obj_t *self) { + mp_raise_NotImplementedError(translate("Sleep Memory not available")); + return 0; +} + +bool common_hal_alarm_sleep_memory_set_bytes(alarm_sleep_memory_obj_t *self, uint32_t start_index, const uint8_t *values, uint32_t len) { + mp_raise_NotImplementedError(translate("Sleep Memory not available")); + return false; +} + +void common_hal_alarm_sleep_memory_get_bytes(alarm_sleep_memory_obj_t *self, uint32_t start_index, uint8_t *values, uint32_t len) { + mp_raise_NotImplementedError(translate("Sleep Memory not available")); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/SleepMemory.h b/circuitpython/ports/raspberrypi/common-hal/alarm/SleepMemory.h new file mode 100644 index 0000000..59d0429 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/SleepMemory.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_ALARM_SLEEPMEMORY_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ALARM_SLEEPMEMORY_H + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; +} alarm_sleep_memory_obj_t; + +extern void alarm_sleep_memory_reset(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ALARM_SLEEPMEMORY_H diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/__init__.c b/circuitpython/ports/raspberrypi/common-hal/alarm/__init__.c new file mode 100644 index 0000000..0d67345 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/__init__.c @@ -0,0 +1,258 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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/gc.h" +#include "py/obj.h" +#include "py/objtuple.h" +#include "py/runtime.h" +#include "shared/runtime/interrupt_char.h" + +#include "shared-bindings/alarm/__init__.h" +#include "shared-bindings/alarm/SleepMemory.h" +#include "shared-bindings/alarm/pin/PinAlarm.h" +#include "shared-bindings/alarm/time/TimeAlarm.h" +#include "shared-bindings/alarm/touch/TouchAlarm.h" + +#include "shared-bindings/microcontroller/__init__.h" + +#include "supervisor/port.h" +#include "supervisor/shared/workflow.h" + +#include "pico/stdlib.h" +#include "hardware/sync.h" +#include "hardware/clocks.h" +#include "hardware/xosc.h" +#include "hardware/structs/scb.h" +#include "hardware/watchdog.h" +#include "hardware/structs/watchdog.h" + +// XOSC shutdown +#include "hardware/rtc.h" +#include "hardware/pll.h" +#include "hardware/regs/io_bank0.h" + +// Watchdog scratch register +// Not used elsewhere in the SDK for now, keep an eye on it +#define RP_WKUP_SCRATCH_REG 0 + +// Light sleep turns off nonvolatile Busio and other wake-only peripherals +// TODO: this only saves about 2mA right now, expand with other non-essentials +const uint32_t RP_LIGHTSLEEP_EN0_MASK = ~( + CLOCKS_SLEEP_EN0_CLK_SYS_SPI1_BITS | + CLOCKS_SLEEP_EN0_CLK_PERI_SPI1_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_SPI0_BITS | + CLOCKS_SLEEP_EN0_CLK_PERI_SPI0_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PWM_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PIO1_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PIO0_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_I2C1_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_I2C0_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_ADC_BITS | + CLOCKS_SLEEP_EN0_CLK_ADC_ADC_BITS + ); +// This bank has the USB clocks in it, leave it for now +const uint32_t RP_LIGHTSLEEP_EN1_MASK = CLOCKS_SLEEP_EN1_RESET; + +// Light sleeps used for TimeAlarm deep sleep turn off almost everything +const uint32_t RP_LIGHTSLEEP_EN0_MASK_HARSH = ( + CLOCKS_SLEEP_EN0_CLK_RTC_RTC_BITS | + CLOCKS_SLEEP_EN0_CLK_SYS_PADS_BITS + ); +const uint32_t RP_LIGHTSLEEP_EN1_MASK_HARSH = 0x0; + +STATIC void prepare_for_dormant_xosc(void); + +// Singleton instance of SleepMemory. +const alarm_sleep_memory_obj_t alarm_sleep_memory_obj = { + .base = { + .type = &alarm_sleep_memory_type, + }, +}; + +void alarm_reset(void) { + alarm_sleep_memory_reset(); + alarm_pin_pinalarm_reset(); + alarm_time_timealarm_reset(); + + // Reset the scratch source + watchdog_hw->scratch[RP_WKUP_SCRATCH_REG] = RP_SLEEP_WAKEUP_UNDEF; +} + +STATIC uint8_t _get_wakeup_cause(void) { + // First check if the modules remember what last woke up + if (alarm_pin_pinalarm_woke_this_cycle()) { + return RP_SLEEP_WAKEUP_GPIO; + } + if (alarm_time_timealarm_woke_this_cycle()) { + return RP_SLEEP_WAKEUP_RTC; + } + // If waking from true deep sleep, modules will have lost their state, + // so check the deep wakeup cause manually + if (watchdog_hw->scratch[RP_WKUP_SCRATCH_REG] != RP_SLEEP_WAKEUP_UNDEF) { + return watchdog_hw->scratch[RP_WKUP_SCRATCH_REG]; + } + return RP_SLEEP_WAKEUP_UNDEF; +} + +// Set up light sleep or deep sleep alarms. +STATIC void _setup_sleep_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + alarm_pin_pinalarm_set_alarms(deep_sleep, n_alarms, alarms); + alarm_time_timealarm_set_alarms(deep_sleep, n_alarms, alarms); +} + +bool common_hal_alarm_woken_from_sleep(void) { + return _get_wakeup_cause() != RP_SLEEP_WAKEUP_UNDEF; +} + +mp_obj_t common_hal_alarm_create_wake_alarm(void) { + // If woken from deep sleep, create a copy alarm similar to what would have + // been passed in originally. Otherwise, just return none + uint8_t cause = _get_wakeup_cause(); + switch (cause) { + case RP_SLEEP_WAKEUP_RTC: { + return alarm_time_timealarm_create_wakeup_alarm(); + } + + case RP_SLEEP_WAKEUP_GPIO: { + return alarm_pin_pinalarm_create_wakeup_alarm(); + } + + case RP_SLEEP_WAKEUP_UNDEF: + default: + // Not a deep sleep reset. + break; + } + return mp_const_none; +} + +mp_obj_t common_hal_alarm_light_sleep_until_alarms(size_t n_alarms, const mp_obj_t *alarms) { + _setup_sleep_alarms(false, n_alarms, alarms); + + mp_obj_t wake_alarm = mp_const_none; + + while (!mp_hal_is_interrupted()) { + RUN_BACKGROUND_TASKS; + // Detect if interrupt was alarm or ctrl-C interrupt. + if (common_hal_alarm_woken_from_sleep()) { + uint8_t cause = _get_wakeup_cause(); + switch (cause) { + case RP_SLEEP_WAKEUP_RTC: { + wake_alarm = alarm_time_timealarm_find_triggered_alarm(n_alarms,alarms); + break; + } + case RP_SLEEP_WAKEUP_GPIO: { + wake_alarm = alarm_pin_pinalarm_find_triggered_alarm(n_alarms,alarms); + break; + } + default: + // Should not reach this, if all light sleep types are covered correctly + break; + } + shared_alarm_save_wake_alarm(wake_alarm); + break; + } + + // Prune the clock for sleep + clocks_hw->sleep_en0 &= RP_LIGHTSLEEP_EN0_MASK; + clocks_hw->sleep_en1 = RP_LIGHTSLEEP_EN1_MASK; + + // Enable System Control Block (SCB) deep sleep + uint save = scb_hw->scr; + scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; + + __wfi(); + } + + if (mp_hal_is_interrupted()) { + return mp_const_none; // Shouldn't be given to python code because exception handling should kick in. + } + + alarm_reset(); + return wake_alarm; +} + +void common_hal_alarm_set_deep_sleep_alarms(size_t n_alarms, const mp_obj_t *alarms) { + _setup_sleep_alarms(true, n_alarms, alarms); +} + +void NORETURN common_hal_alarm_enter_deep_sleep(void) { + bool timealarm_set = alarm_time_timealarm_is_set(); + + // If there's a timealarm, just enter a very deep light sleep + if (timealarm_set) { + // Prune the clock for sleep + clocks_hw->sleep_en0 &= RP_LIGHTSLEEP_EN0_MASK_HARSH; + clocks_hw->sleep_en1 = RP_LIGHTSLEEP_EN1_MASK_HARSH; + // Enable System Control Block (SCB) deep sleep + uint save = scb_hw->scr; + scb_hw->scr = save | M0PLUS_SCR_SLEEPDEEP_BITS; + __wfi(); + } else { + prepare_for_dormant_xosc(); + xosc_dormant(); + } + // // TODO: support ROSC when available in SDK + // rosc_set_dormant(); + + // Reset uses the watchdog. Use scratch registers to store wake reason + watchdog_hw->scratch[RP_WKUP_SCRATCH_REG] = _get_wakeup_cause(); + reset_cpu(); +} + +void common_hal_alarm_gc_collect(void) { + gc_collect_ptr(shared_alarm_get_wake_alarm()); +} + +STATIC void prepare_for_dormant_xosc(void) { + // TODO: add ROSC support with sleep_run_from_dormant_source when it's added to SDK + uint src_hz = XOSC_MHZ * MHZ; + uint clk_ref_src = CLOCKS_CLK_REF_CTRL_SRC_VALUE_XOSC_CLKSRC; + clock_configure(clk_ref, + clk_ref_src, + 0, // No aux mux + src_hz, + src_hz); + clock_configure(clk_sys, + CLOCKS_CLK_SYS_CTRL_SRC_VALUE_CLK_REF, + 0, // Using glitchless mux + src_hz, + src_hz); + clock_stop(clk_usb); + clock_stop(clk_adc); + uint clk_rtc_src = CLOCKS_CLK_RTC_CTRL_AUXSRC_VALUE_XOSC_CLKSRC; + clock_configure(clk_rtc, + 0, // No GLMUX + clk_rtc_src, + src_hz, + 46875); + clock_configure(clk_peri, + 0, + CLOCKS_CLK_PERI_CTRL_AUXSRC_VALUE_CLK_SYS, + src_hz, + src_hz); + pll_deinit(pll_sys); + pll_deinit(pll_usb); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/__init__.h b/circuitpython/ports/raspberrypi/common-hal/alarm/__init__.h new file mode 100644 index 0000000..3d5d86f --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/__init__.h @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_ALARM__INIT__H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ALARM__INIT__H + +#include "common-hal/alarm/SleepMemory.h" + +#include "hardware/regs/clocks.h" + +#define RP_SLEEP_WAKEUP_UNDEF 0 +#define RP_SLEEP_WAKEUP_GPIO 1 +#define RP_SLEEP_WAKEUP_RTC 2 + +extern const alarm_sleep_memory_obj_t alarm_sleep_memory_obj; + +extern void alarm_reset(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ALARM__INIT__H diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c b/circuitpython/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c new file mode 100644 index 0000000..53cbfca --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.c @@ -0,0 +1,164 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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-bindings/alarm/pin/PinAlarm.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "common-hal/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "pico/stdlib.h" +#include "hardware/gpio.h" +#include "hardware/structs/iobank0.h" + +STATIC bool woke_up; +STATIC uint64_t alarm_triggered_pins; // 36 actual pins +STATIC uint64_t alarm_reserved_pins; // 36 actual pins +STATIC bool _pinalarm_set = false; + +#define GPIO_IRQ_ALL_EVENTS 0x15u + +STATIC void gpio_callback(uint gpio, uint32_t events) { + alarm_triggered_pins |= (1 << gpio); + woke_up = true; + + // does this need to be called, to prevent IRQ from constantly going off? + gpio_acknowledge_irq(gpio, events); + + // Disable IRQ automatically + gpio_set_irq_enabled(gpio, events, false); + gpio_set_dormant_irq_enabled(gpio, events, false); +} + +void common_hal_alarm_pin_pinalarm_construct(alarm_pin_pinalarm_obj_t *self, const mcu_pin_obj_t *pin, bool value, bool edge, bool pull) { + self->pin = pin; + self->value = value; + self->edge = edge; + self->pull = pull; +} + +const mcu_pin_obj_t *common_hal_alarm_pin_pinalarm_get_pin(alarm_pin_pinalarm_obj_t *self) { + return self->pin; +} + +bool common_hal_alarm_pin_pinalarm_get_value(alarm_pin_pinalarm_obj_t *self) { + return self->value; +} + +bool common_hal_alarm_pin_pinalarm_get_edge(alarm_pin_pinalarm_obj_t *self) { + return self->edge; +} + +bool common_hal_alarm_pin_pinalarm_get_pull(alarm_pin_pinalarm_obj_t *self) { + return self->pull; +} + +bool alarm_pin_pinalarm_woke_this_cycle(void) { + return woke_up; +} + +mp_obj_t alarm_pin_pinalarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms) { + for (size_t i = 0; i < n_alarms; i++) { + if (!mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) { + continue; + } + alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]); + if (alarm_triggered_pins & (1 << alarm->pin->number)) { + return alarms[i]; + } + } + return mp_const_none; +} + +mp_obj_t alarm_pin_pinalarm_create_wakeup_alarm(void) { + alarm_pin_pinalarm_obj_t *alarm = m_new_obj(alarm_pin_pinalarm_obj_t); + alarm->base.type = &alarm_pin_pinalarm_type; + // TODO: how to obtain the correct pin from memory? + return alarm; +} + +void alarm_pin_pinalarm_reset(void) { + alarm_triggered_pins = 0; + woke_up = false; + + // Clear all GPIO interrupts + for (uint8_t i = 0; i < 4; i++) { + iobank0_hw->intr[i] = 0; + } + + // Reset pins and pin IRQs + for (size_t i = 0; i < TOTAL_GPIO_COUNT; i++) { + if (alarm_reserved_pins & (1 << i)) { + gpio_set_irq_enabled(i, GPIO_IRQ_ALL_EVENTS, false); + reset_pin_number(i); + } + } + alarm_reserved_pins = 0; +} + +void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + for (size_t i = 0; i < n_alarms; i++) { + if (mp_obj_is_type(alarms[i], &alarm_pin_pinalarm_type)) { + alarm_pin_pinalarm_obj_t *alarm = MP_OBJ_TO_PTR(alarms[i]); + + gpio_init(alarm->pin->number); + if (alarm->pull) { + // If value is high, the pullup should be off, and vice versa + gpio_set_pulls(alarm->pin->number, !alarm->value, alarm->value); + } else { + // Clear in case the pulls are already on + gpio_set_pulls(alarm->pin->number, false, false); + } + gpio_set_dir(alarm->pin->number, GPIO_IN); + // Don't reset at end of VM (instead, pinalarm_reset will reset before next VM) + common_hal_never_reset_pin(alarm->pin); + alarm_reserved_pins |= (1 << alarm->pin->number); + + uint32_t event; + if (alarm->value == true && alarm->edge == true) { + event = GPIO_IRQ_EDGE_RISE; + } else if (alarm->value == false && alarm->edge == true) { + event = GPIO_IRQ_EDGE_FALL; + } else if (alarm->value == true && alarm->edge == false) { + event = GPIO_IRQ_LEVEL_HIGH; + } else { // both false + event = GPIO_IRQ_LEVEL_LOW; + } + + gpio_set_irq_enabled_with_callback((uint)alarm->pin->number, event, true, &gpio_callback); + if (deep_sleep) { + gpio_set_dormant_irq_enabled((uint)alarm->pin->number, event, true); + } + + _pinalarm_set = true; + } + } +} + +bool alarm_pin_pinalarm_is_set(void) { + return _pinalarm_set; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h b/circuitpython/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h new file mode 100644 index 0000000..d31ac6c --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/pin/PinAlarm.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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/obj.h" +#include "py/objtuple.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; + bool value; + bool pull; + bool edge; +} alarm_pin_pinalarm_obj_t; + +mp_obj_t alarm_pin_pinalarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms); +mp_obj_t alarm_pin_pinalarm_create_wakeup_alarm(void); + +void alarm_pin_pinalarm_reset(void); +void alarm_pin_pinalarm_light_reset(void); +void alarm_pin_pinalarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms); +bool alarm_pin_pinalarm_woke_this_cycle(void); +bool alarm_pin_pinalarm_is_set(void); diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c b/circuitpython/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c new file mode 100644 index 0000000..16ac8fc --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.c @@ -0,0 +1,130 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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-bindings/alarm/time/TimeAlarm.h" +#include "shared-bindings/time/__init__.h" + +#include "shared/timeutils/timeutils.h" + +#include "hardware/gpio.h" +#include "hardware/rtc.h" + +STATIC bool woke_up = false; +STATIC bool _timealarm_set = false; + +STATIC void timer_callback(void) { + woke_up = true; +} + +void common_hal_alarm_time_timealarm_construct(alarm_time_timealarm_obj_t *self, mp_float_t monotonic_time) { + self->monotonic_time = monotonic_time; +} + +mp_float_t common_hal_alarm_time_timealarm_get_monotonic_time(alarm_time_timealarm_obj_t *self) { + return self->monotonic_time; +} + +mp_obj_t alarm_time_timealarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms) { + for (size_t i = 0; i < n_alarms; i++) { + if (mp_obj_is_type(alarms[i], &alarm_time_timealarm_type)) { + return alarms[i]; + } + } + return mp_const_none; +} + +mp_obj_t alarm_time_timealarm_create_wakeup_alarm(void) { + alarm_time_timealarm_obj_t *timer = m_new_obj(alarm_time_timealarm_obj_t); + timer->base.type = &alarm_time_timealarm_type; + // TODO: Set monotonic_time based on the RTC state. + timer->monotonic_time = 0.0f; + return timer; +} + +bool alarm_time_timealarm_woke_this_cycle(void) { + return woke_up; +} + +void alarm_time_timealarm_reset(void) { + rtc_disable_alarm(); + woke_up = false; +} + +void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms) { + bool timealarm_set = false; + alarm_time_timealarm_obj_t *timealarm = MP_OBJ_NULL; + + for (size_t i = 0; i < n_alarms; i++) { + if (!mp_obj_is_type(alarms[i], &alarm_time_timealarm_type)) { + continue; + } + if (timealarm_set) { + mp_raise_ValueError(translate("Only one alarm.time alarm can be set.")); + } + timealarm = MP_OBJ_TO_PTR(alarms[i]); + timealarm_set = true; + } + if (!timealarm_set) { + return; + } + if (deep_sleep) { + _timealarm_set = true; + } + + // Compute how long to actually sleep, considering the time now. + mp_float_t mono_seconds_to_date = uint64_to_float(common_hal_time_monotonic_ms()) / 1000.0f; + mp_float_t wakeup_in_secs = MAX(0.0f, timealarm->monotonic_time - mono_seconds_to_date); + datetime_t t; + + rtc_get_datetime(&t); + + uint32_t rtc_seconds_to_date = timeutils_seconds_since_2000(t.year, t.month, + t.day, t.hour, t.min, t.sec); + + // The float value is always slightly under, so add 1 to compensate + uint32_t alarm_seconds = rtc_seconds_to_date + (uint32_t)wakeup_in_secs + 1; + timeutils_struct_time_t tm; + timeutils_seconds_since_2000_to_struct_time(alarm_seconds, &tm); + + // reuse t + t.hour = tm.tm_hour; + t.min = tm.tm_min; + t.sec = tm.tm_sec; + t.day = tm.tm_mday; + t.month = tm.tm_mon; + t.year = tm.tm_year; + t.dotw = (tm.tm_wday + 1) % 7; + + rtc_set_alarm(&t, &timer_callback); + + woke_up = false; +} + +bool alarm_time_timealarm_is_set(void) { + return _timealarm_set; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h b/circuitpython/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h new file mode 100644 index 0000000..d524855 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/time/TimeAlarm.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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/obj.h" + +typedef struct { + mp_obj_base_t base; + mp_float_t monotonic_time; +} alarm_time_timealarm_obj_t; + +mp_obj_t alarm_time_timealarm_find_triggered_alarm(size_t n_alarms, const mp_obj_t *alarms); +mp_obj_t alarm_time_timealarm_create_wakeup_alarm(void); + +void alarm_time_timealarm_reset(void); +void alarm_time_timealarm_set_alarms(bool deep_sleep, size_t n_alarms, const mp_obj_t *alarms); +bool alarm_time_timealarm_woke_this_cycle(void); +bool alarm_time_timealarm_is_set(void); diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.c b/circuitpython/ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.c new file mode 100644 index 0000000..88c7372 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.c @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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 "shared-bindings/alarm/touch/TouchAlarm.h" +#include "shared-bindings/microcontroller/__init__.h" + +void common_hal_alarm_touch_touchalarm_construct(alarm_touch_touchalarm_obj_t *self, const mcu_pin_obj_t *pin) { + mp_raise_NotImplementedError(translate("Touch alarms not available")); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.h b/circuitpython/ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.h new file mode 100644 index 0000000..f631293 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/alarm/touch/TouchAlarm.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Lucian Copeland for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_ALARM_TOUCH_TOUCHALARM_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ALARM_TOUCH_TOUCHALARM_H + +#include "py/obj.h" +#include "common-hal/microcontroller/Pin.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; +} alarm_touch_touchalarm_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ALARM_TOUCH_TOUCHALARM_H diff --git a/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogIn.c b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogIn.c new file mode 100644 index 0000000..1d77630 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogIn.c @@ -0,0 +1,74 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 "common-hal/analogio/AnalogIn.h" +#include "shared-bindings/analogio/AnalogIn.h" +#include "py/runtime.h" +#include "supervisor/shared/translate.h" + +#include "src/rp2_common/hardware_adc/include/hardware/adc.h" + +#define ADC_FIRST_PIN_NUMBER 26 +#define ADC_PIN_COUNT 4 + +void common_hal_analogio_analogin_construct(analogio_analogin_obj_t *self, const mcu_pin_obj_t *pin) { + if (pin->number < ADC_FIRST_PIN_NUMBER || pin->number > ADC_FIRST_PIN_NUMBER + ADC_PIN_COUNT) { + mp_raise_ValueError(translate("Pin does not have ADC capabilities")); + } + + adc_init(); + + adc_gpio_init(pin->number); + + claim_pin(pin); + self->pin = pin; +} + +bool common_hal_analogio_analogin_deinited(analogio_analogin_obj_t *self) { + return self->pin == NULL; +} + +void common_hal_analogio_analogin_deinit(analogio_analogin_obj_t *self) { + if (common_hal_analogio_analogin_deinited(self)) { + return; + } + + reset_pin_number(self->pin->number); + self->pin = NULL; +} + +uint16_t common_hal_analogio_analogin_get_value(analogio_analogin_obj_t *self) { + adc_select_input(self->pin->number - ADC_FIRST_PIN_NUMBER); + uint16_t value = adc_read(); + + // Map value to from 12 to 16 bits + return value << 4; +} + +float common_hal_analogio_analogin_get_reference_voltage(analogio_analogin_obj_t *self) { + // The nominal VCC voltage + return 3.3f; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogIn.h b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogIn.h new file mode 100644 index 0000000..c322a67 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogIn.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_ANALOGIO_ANALOGIN_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ANALOGIO_ANALOGIN_H + +#include "common-hal/microcontroller/Pin.h" + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; +} analogio_analogin_obj_t; + +void analogin_init(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ANALOGIO_ANALOGIN_H diff --git a/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogOut.c b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogOut.c new file mode 100644 index 0000000..7afa773 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogOut.c @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Dan Halbert for Adafruit Industries + * + * 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 "shared-bindings/analogio/AnalogOut.h" + +#include <stdint.h> +#include <string.h> + +#include "py/mperrno.h" +#include "py/runtime.h" +#include "supervisor/shared/translate.h" + +void common_hal_analogio_analogout_construct(analogio_analogout_obj_t *self, const mcu_pin_obj_t *pin) { + mp_raise_RuntimeError(translate("AnalogOut functionality not supported")); +} + +bool common_hal_analogio_analogout_deinited(analogio_analogout_obj_t *self) { + return true; +} + +void common_hal_analogio_analogout_deinit(analogio_analogout_obj_t *self) { +} + +void common_hal_analogio_analogout_set_value(analogio_analogout_obj_t *self, uint16_t value) { +} diff --git a/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogOut.h b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogOut.h new file mode 100644 index 0000000..7c7a61a --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/analogio/AnalogOut.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2016 Scott Shawcroft + * + * 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_RASPBERRYPI_COMMON_HAL_ANALOGIO_ANALOGOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ANALOGIO_ANALOGOUT_H + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; +} analogio_analogout_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_ANALOGIO_ANALOGOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/analogio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/analogio/__init__.c new file mode 100644 index 0000000..eea58c7 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/analogio/__init__.c @@ -0,0 +1 @@ +// No analogio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/audiobusio/I2SOut.c b/circuitpython/ports/raspberrypi/common-hal/audiobusio/I2SOut.c new file mode 100644 index 0000000..0371634 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiobusio/I2SOut.c @@ -0,0 +1,242 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 <stdint.h> +#include <string.h> + +#include "mpconfigport.h" + +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "common-hal/audiobusio/I2SOut.h" +#include "shared-bindings/audiobusio/I2SOut.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-module/audiocore/__init__.h" +#include "bindings/rp2pio/StateMachine.h" +#include "supervisor/shared/translate.h" + +const uint16_t i2s_program[] = { +// ; Load the next set of samples +// ; /--- LRCLK +// ; |/-- BCLK +// ; || +// pull noblock side 0b01 ; Loads OSR with the next FIFO value or X + 0x8880, +// mov x osr side 0b01 ; Save the new value in case we need it again + 0xa827, +// set y 14 side 0b01 + 0xe84e, +// bitloop1: +// out pins 1 side 0b00 [2] + 0x6201, +// jmp y-- bitloop1 side 0b01 [2] + 0x0a83, +// out pins 1 side 0b10 [2] + 0x7201, +// set y 14 side 0b11 [2] + 0xfa4e, +// bitloop0: +// out pins 1 side 0b10 [2] + 0x7201, +// jmp y-- bitloop0 side 0b11 [2] + 0x1a87, +// out pins 1 side 0b00 [2] + 0x6201 +}; + +const uint16_t i2s_program_left_justified[] = { +// ; Load the next set of samples +// ; /--- LRCLK +// ; |/-- BCLK +// ; || +// pull noblock side 0b11 ; Loads OSR with the next FIFO value or X + 0x9880, +// mov x osr side 0b11 ; Save the new value in case we need it again + 0xb827, +// set y 14 side 0b11 + 0xf84e, +// bitloop1: +// out pins 1 side 0b00 [2] + 0x6201, +// jmp y-- bitloop1 side 0b01 [2] + 0x0a83, +// out pins 1 side 0b10 [2] + 0x6201, +// set y 14 side 0b01 [2] + 0xea4e, +// bitloop0: +// out pins 1 side 0b10 [2] + 0x7201, +// jmp y-- bitloop0 side 0b11 [2] + 0x1a87, +// out pins 1 side 0b10 [2] + 0x7201 +}; + +void i2sout_reset(void) { +} + +// Caller validates that pins are free. +void common_hal_audiobusio_i2sout_construct(audiobusio_i2sout_obj_t *self, + const mcu_pin_obj_t *bit_clock, const mcu_pin_obj_t *word_select, + const mcu_pin_obj_t *data, bool left_justified) { + if (bit_clock->number != word_select->number - 1) { + mp_raise_ValueError(translate("Bit clock and word select must be sequential pins")); + } + + const uint16_t *program = i2s_program; + size_t program_len = sizeof(i2s_program) / sizeof(i2s_program[0]); + if (left_justified) { + program = i2s_program_left_justified; + program_len = sizeof(i2s_program_left_justified) / sizeof(i2s_program_left_justified[0]); + ; + } + + // Use the state machine to manage pins. + common_hal_rp2pio_statemachine_construct( + &self->state_machine, + program, program_len, + 44100 * 32 * 6, // Clock at 44.1 khz to warm the DAC up. + NULL, 0, + data, 1, 0, 0xffffffff, // out pin + NULL, 0, // in pins + 0, 0, // in pulls + NULL, 0, 0, 0x1f, // set pins + bit_clock, 2, 0, 0x1f, // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + 0, // wait gpio pins + true, // exclusive pin use + false, 32, false, // shift out left to start with MSB + false, // Wait for txstall + false, 32, false, // in settings + false, // Not user-interruptible. + 0, -1); // wrap settings + + self->playing = false; + audio_dma_init(&self->dma); +} + +bool common_hal_audiobusio_i2sout_deinited(audiobusio_i2sout_obj_t *self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_audiobusio_i2sout_deinit(audiobusio_i2sout_obj_t *self) { + if (common_hal_audiobusio_i2sout_deinited(self)) { + return; + } + + if (common_hal_audiobusio_i2sout_get_playing(self)) { + common_hal_audiobusio_i2sout_stop(self); + } + + common_hal_rp2pio_statemachine_deinit(&self->state_machine); + + audio_dma_deinit(&self->dma); +} + +void common_hal_audiobusio_i2sout_play(audiobusio_i2sout_obj_t *self, + mp_obj_t sample, bool loop) { + if (common_hal_audiobusio_i2sout_get_playing(self)) { + common_hal_audiobusio_i2sout_stop(self); + } + + uint8_t bits_per_sample = audiosample_bits_per_sample(sample); + // Make sure we transmit a minimum of 16 bits. + // TODO: Maybe we need an intermediate object to upsample instead. This is + // only needed for some I2S devices that expect at least 8. + if (bits_per_sample < 16) { + bits_per_sample = 16; + } + // We always output stereo so output twice as many bits. + uint16_t bits_per_sample_output = bits_per_sample * 2; + size_t clocks_per_bit = 6; + uint32_t frequency = bits_per_sample_output * audiosample_sample_rate(sample); + uint8_t channel_count = audiosample_channel_count(sample); + if (channel_count > 2) { + mp_raise_ValueError(translate("Too many channels in sample.")); + } + + common_hal_rp2pio_statemachine_set_frequency(&self->state_machine, clocks_per_bit * frequency); + common_hal_rp2pio_statemachine_restart(&self->state_machine); + + // On the RP2040, output registers are always written with a 32-bit write. + // If the write is 8 or 16 bits wide, the data will be replicated in upper bytes. + // See section 2.1.4 Narrow IO Register Writes in the RP2040 datasheet. + // This means that identical 16-bit audio data will be written in both halves of the incoming PIO + // FIFO register. Thus we get mono-to-stereo conversion for the I2S output for free. + audio_dma_result result = audio_dma_setup_playback( + &self->dma, + sample, + loop, + false, // single channel + 0, // audio channel + true, // output signed + bits_per_sample, + (uint32_t)&self->state_machine.pio->txf[self->state_machine.state_machine], // output register + self->state_machine.tx_dreq); // data request line + + if (result == AUDIO_DMA_DMA_BUSY) { + common_hal_audiobusio_i2sout_stop(self); + mp_raise_RuntimeError(translate("No DMA channel found")); + } else if (result == AUDIO_DMA_MEMORY_ERROR) { + common_hal_audiobusio_i2sout_stop(self); + mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion")); + } + + self->playing = true; +} + +void common_hal_audiobusio_i2sout_pause(audiobusio_i2sout_obj_t *self) { + audio_dma_pause(&self->dma); +} + +void common_hal_audiobusio_i2sout_resume(audiobusio_i2sout_obj_t *self) { + // Maybe: Clear any overrun/underrun errors + + audio_dma_resume(&self->dma); +} + +bool common_hal_audiobusio_i2sout_get_paused(audiobusio_i2sout_obj_t *self) { + return audio_dma_get_paused(&self->dma); +} + +void common_hal_audiobusio_i2sout_stop(audiobusio_i2sout_obj_t *self) { + audio_dma_stop(&self->dma); + + common_hal_rp2pio_statemachine_stop(&self->state_machine); + + self->playing = false; +} + +bool common_hal_audiobusio_i2sout_get_playing(audiobusio_i2sout_obj_t *self) { + bool playing = audio_dma_get_playing(&self->dma); + if (!playing && self->playing) { + common_hal_audiobusio_i2sout_stop(self); + } + return playing; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/audiobusio/I2SOut.h b/circuitpython/ports/raspberrypi/common-hal/audiobusio/I2SOut.h new file mode 100644 index 0000000..52226ae --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiobusio/I2SOut.h @@ -0,0 +1,48 @@ + +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H + +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/rp2pio/StateMachine.h" + +#include "audio_dma.h" +#include "py/obj.h" + +// We don't bit pack because we'll only have two at most. Its better to save code size instead. +typedef struct { + mp_obj_base_t base; + rp2pio_statemachine_obj_t state_machine; + audio_dma_t dma; + bool left_justified; + bool playing; +} audiobusio_i2sout_obj_t; + +void i2sout_reset(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_I2SOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/audiobusio/PDMIn.c b/circuitpython/ports/raspberrypi/common-hal/audiobusio/PDMIn.c new file mode 100644 index 0000000..8301f9f --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiobusio/PDMIn.c @@ -0,0 +1,175 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 <stdint.h> +#include <string.h> +#include <math.h> + +#include "py/mperrno.h" +#include "py/runtime.h" +#include "shared-bindings/audiobusio/PDMIn.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "supervisor/shared/translate.h" + +#include "audio_dma.h" + +#define OVERSAMPLING 64 +#define SAMPLES_PER_BUFFER 32 + +// MEMS microphones must be clocked at at least 1MHz. +#define MIN_MIC_CLOCK 1000000 + +const uint16_t pdmin[] = { + // in pins 1 side 0b1 + 0x5001, + // push iffull side 0b0 + 0x8040 +}; + +// Caller validates that pins are free. +void common_hal_audiobusio_pdmin_construct(audiobusio_pdmin_obj_t *self, + const mcu_pin_obj_t *clock_pin, + const mcu_pin_obj_t *data_pin, + uint32_t sample_rate, + uint8_t bit_depth, + bool mono, + uint8_t oversample) { + if (!(bit_depth == 16 || bit_depth == 8) || !mono || oversample != OVERSAMPLING) { + mp_raise_NotImplementedError(translate("Only 8 or 16 bit mono with " MP_STRINGIFY(OVERSAMPLING) "x oversampling is supported.")); + } + + // Use the state machine to manage pins. + common_hal_rp2pio_statemachine_construct(&self->state_machine, + pdmin, MP_ARRAY_SIZE(pdmin), + sample_rate * 32 * 2, // Frequency based on sample rate + NULL, 0, + NULL, 1, 0, 0xffffffff, // out pin + data_pin, 1, // in pins + 0, 0, // in pulls + NULL, 0, 0, 0x1f, // set pins + clock_pin, 1, 0, 0x1f, // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + 0, // wait gpio pins + true, // exclusive pin use + false, 32, false, // out settings + false, // Wait for txstall + false, 32, true, // in settings + false, // Not user-interruptible. + 0, -1); // wrap settings + + uint32_t actual_frequency = common_hal_rp2pio_statemachine_get_frequency(&self->state_machine); + if (actual_frequency < MIN_MIC_CLOCK) { + mp_raise_ValueError(translate("sampling rate out of range")); + } + + self->sample_rate = actual_frequency / oversample; + self->bit_depth = bit_depth; +} + +bool common_hal_audiobusio_pdmin_deinited(audiobusio_pdmin_obj_t *self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_audiobusio_pdmin_deinit(audiobusio_pdmin_obj_t *self) { + if (common_hal_audiobusio_pdmin_deinited(self)) { + return; + } + return common_hal_rp2pio_statemachine_deinit(&self->state_machine); +} + +uint8_t common_hal_audiobusio_pdmin_get_bit_depth(audiobusio_pdmin_obj_t *self) { + return self->bit_depth; +} + +uint32_t common_hal_audiobusio_pdmin_get_sample_rate(audiobusio_pdmin_obj_t *self) { + return self->sample_rate; +} + +// a windowed sinc filter for 44 khz, 64 samples +// +// This filter is good enough to use for lower sample rates as +// well. It does not increase the noise enough to be a problem. +// +// In the long run we could use a fast filter like this to do the +// decimation and initial filtering in real time, filtering to a +// higher sample rate than specified. Then after the audio is +// recorded, a more expensive filter non-real-time filter could be +// used to down-sample and low-pass. +const uint16_t sinc_filter[OVERSAMPLING] = { + 0, 2, 9, 21, 39, 63, 94, 132, + 179, 236, 302, 379, 467, 565, 674, 792, + 920, 1055, 1196, 1341, 1487, 1633, 1776, 1913, + 2042, 2159, 2263, 2352, 2422, 2474, 2506, 2516, + 2506, 2474, 2422, 2352, 2263, 2159, 2042, 1913, + 1776, 1633, 1487, 1341, 1196, 1055, 920, 792, + 674, 565, 467, 379, 302, 236, 179, 132, + 94, 63, 39, 21, 9, 2, 0, 0 +}; + +#define REPEAT_32_TIMES(X) do { X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X X } while (0) + +static uint16_t filter_sample(uint32_t pdm_samples[2]) { + uint16_t running_sum = 0; + const uint16_t *filter_ptr = sinc_filter; + for (uint8_t i = 0; i < 2; i++) { + uint32_t pdm_sample = pdm_samples[i]; + REPEAT_32_TIMES({ + if (pdm_sample & 0x1) { + running_sum += *filter_ptr; + } + filter_ptr++; + pdm_sample >>= 1; + } + ); + } + return running_sum; +} + +// output_buffer may be a byte buffer or a halfword buffer. +// output_buffer_length is the number of slots, not the number of bytes. +uint32_t common_hal_audiobusio_pdmin_record_to_buffer(audiobusio_pdmin_obj_t *self, + uint16_t *output_buffer, uint32_t output_buffer_length) { + uint32_t samples[2]; + size_t output_count = 0; + common_hal_rp2pio_statemachine_clear_rxfifo(&self->state_machine); + // Do one read to get the mic going and throw it away. + common_hal_rp2pio_statemachine_readinto(&self->state_machine, (uint8_t *)samples, 2 * sizeof(uint32_t), sizeof(uint32_t), false); + while (output_count < output_buffer_length && !common_hal_rp2pio_statemachine_get_rxstall(&self->state_machine)) { + common_hal_rp2pio_statemachine_readinto(&self->state_machine, (uint8_t *)samples, 2 * sizeof(uint32_t), sizeof(uint32_t), false); + // Call filter_sample just one place so it can be inlined. + uint16_t value = filter_sample(samples); + if (self->bit_depth == 8) { + // Truncate to 8 bits. + ((uint8_t *)output_buffer)[output_count] = value >> 8; + } else { + output_buffer[output_count] = value; + } + output_count++; + } + + return output_count; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/audiobusio/PDMIn.h b/circuitpython/ports/raspberrypi/common-hal/audiobusio/PDMIn.h new file mode 100644 index 0000000..995ffb5 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiobusio/PDMIn.h @@ -0,0 +1,50 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H + +#include "common-hal/microcontroller/Pin.h" +#include "bindings/rp2pio/StateMachine.h" + +#include "extmod/vfs_fat.h" +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint32_t sample_rate; + uint8_t serializer; + uint8_t clock_unit; + uint8_t bytes_per_sample; + uint8_t bit_depth; + rp2pio_statemachine_obj_t state_machine; +} audiobusio_pdmin_obj_t; + +void pdmin_reset(void); + +void pdmin_background(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOBUSIO_AUDIOOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/audiobusio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/audiobusio/__init__.c new file mode 100644 index 0000000..87db404 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiobusio/__init__.c @@ -0,0 +1 @@ +// No audiobusio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c b/circuitpython/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c new file mode 100644 index 0000000..2f09124 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c @@ -0,0 +1,287 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * 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 "common-hal/audiopwmio/PWMAudioOut.h" + +#include <math.h> +#include <stdint.h> +#include <string.h> + +#include "extmod/vfs_fat.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "shared-bindings/pwmio/PWMOut.h" +#include "shared-bindings/audiopwmio/PWMAudioOut.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "supervisor/shared/translate.h" + +#include "src/rp2040/hardware_structs/include/hardware/structs/dma.h" +#include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" + +// The PWM clock frequency is base_clock_rate / PWM_TOP, typically 125_000_000 / PWM_TOP. +// We pick BITS_PER_SAMPLE so we get a clock frequency that is above what would cause aliasing. +#define BITS_PER_SAMPLE 10 +#define SAMPLE_BITS_TO_DISCARD (16 - BITS_PER_SAMPLE) +#define PWM_TOP ((1 << BITS_PER_SAMPLE) - 1) + + +static uint32_t gcd(uint32_t a, uint32_t b) { + while (b) { + uint32_t tmp = a % b; + a = b; + b = tmp; + } + return a; +} + +static uint32_t limit_denominator(uint32_t max_denominator, uint32_t num_in, uint32_t den_in, uint32_t *den_out) { +// Algorithm based on Python's limit_denominator + uint32_t p0 = 0, q0 = 1, p1 = 1, q1 = 0; + uint32_t d = den_in, n = num_in; + uint32_t g = gcd(n, d); + d /= g; + n /= g; + if (d < max_denominator) { + *den_out = d; + return n; + } + while (1) { + uint32_t a = n / d; + uint32_t q2 = q0 + a * q1; + if (q2 > max_denominator) { + break; + } + + uint32_t p_tmp = p0 + a * p1; + p0 = p1; + q0 = q1; + p1 = p_tmp; + q1 = q2; + + uint32_t d_tmp = n - a * d; + n = d; + d = d_tmp; + } + uint32_t k = (max_denominator - q0) / q1; + uint32_t bound1_num = p0 + k * p1, bound1_den = q0 + k * q1; + uint32_t bound2_num = p1, bound2_den = q1; + + if (fabsf((float)bound1_num / bound1_den - (float)num_in / den_in) <= + fabsf((float)bound2_num / bound2_den - (float)num_in / den_in)) { + *den_out = bound2_den; + return bound2_num; + } + + *den_out = bound1_den; + return bound1_num; +} + +void audiopwmout_reset() { + for (size_t i = 0; i < NUM_DMA_TIMERS; i++) { + dma_hw->timer[i] = 0; + } +} + +// Caller validates that pins are free. +void common_hal_audiopwmio_pwmaudioout_construct(audiopwmio_pwmaudioout_obj_t *self, + const mcu_pin_obj_t *left_channel, const mcu_pin_obj_t *right_channel, uint16_t quiescent_value) { + + self->stereo = right_channel != NULL; + + if (self->stereo) { + if (pwm_gpio_to_slice_num(left_channel->number) != pwm_gpio_to_slice_num(right_channel->number)) { + mp_raise_ValueError(translate("Pins must share PWM slice")); + } + if (pwm_gpio_to_channel(left_channel->number) != 0) { + mp_raise_ValueError(translate("Stereo left must be on PWM channel A")); + } + if (pwm_gpio_to_channel(right_channel->number) != 1) { + mp_raise_ValueError(translate("Stereo right must be on PWM channel B")); + } + } + + // Typically pwmout doesn't let us change frequency with two objects on the + // same PWM slice. However, we have private access to it so we can do what + // we want. ;-) We mark ourselves variable only if we're a mono output to + // prevent other PWM use on the other channel. If stereo, we say fixed + // frequency so we can allocate with ourselves. + + // We don't actually know our frequency yet. It is set when + // pwmio_pwmout_set_top() is called. This value is unimportant; it just needs to be valid. + const uint32_t frequency = 12000000; + + // Make sure the PWMOut's are "deinited" by default. + self->left_pwm.pin = NULL; + self->right_pwm.pin = NULL; + + pwmout_result_t result = + common_hal_pwmio_pwmout_construct(&self->left_pwm, left_channel, 0, frequency, !self->stereo); + if (result == PWMOUT_OK && right_channel != NULL) { + result = + common_hal_pwmio_pwmout_construct(&self->right_pwm, right_channel, 0, frequency, false); + if (result != PWMOUT_OK) { + common_hal_pwmio_pwmout_deinit(&self->left_pwm); + } + } + if (result != PWMOUT_OK) { + mp_raise_RuntimeError(translate("All timers in use")); + } + + self->quiescent_value = quiescent_value >> SAMPLE_BITS_TO_DISCARD; + common_hal_pwmio_pwmout_set_duty_cycle(&self->left_pwm, self->quiescent_value); + pwmio_pwmout_set_top(&self->left_pwm, PWM_TOP); + if (self->stereo) { + common_hal_pwmio_pwmout_set_duty_cycle(&self->right_pwm, self->quiescent_value); + pwmio_pwmout_set_top(&self->right_pwm, PWM_TOP); + } + + audio_dma_init(&self->dma); + self->pacing_timer = NUM_DMA_TIMERS; +} + +bool common_hal_audiopwmio_pwmaudioout_deinited(audiopwmio_pwmaudioout_obj_t *self) { + return common_hal_pwmio_pwmout_deinited(&self->left_pwm); +} + +void common_hal_audiopwmio_pwmaudioout_deinit(audiopwmio_pwmaudioout_obj_t *self) { + if (common_hal_audiopwmio_pwmaudioout_deinited(self)) { + return; + } + + if (common_hal_audiopwmio_pwmaudioout_get_playing(self)) { + common_hal_audiopwmio_pwmaudioout_stop(self); + } + + // TODO: ramp the pwm down from quiescent value to 0 + common_hal_pwmio_pwmout_deinit(&self->left_pwm); + common_hal_pwmio_pwmout_deinit(&self->right_pwm); + + audio_dma_deinit(&self->dma); +} + +void common_hal_audiopwmio_pwmaudioout_play(audiopwmio_pwmaudioout_obj_t *self, mp_obj_t sample, bool loop) { + + if (common_hal_audiopwmio_pwmaudioout_get_playing(self)) { + common_hal_audiopwmio_pwmaudioout_stop(self); + } + + // TODO: Share pacing timers based on frequency. + size_t pacing_timer = NUM_DMA_TIMERS; + for (size_t i = 0; i < NUM_DMA_TIMERS; i++) { + if (dma_hw->timer[i] == 0) { + pacing_timer = i; + break; + } + } + if (pacing_timer == NUM_DMA_TIMERS) { + mp_raise_RuntimeError(translate("No DMA pacing timer found")); + } + uint32_t tx_register = (uint32_t)&pwm_hw->slice[self->left_pwm.slice].cc; + if (self->stereo) { + // Shift the destination if we are outputting to both PWM channels. + tx_register += self->left_pwm.ab_channel * sizeof(uint16_t); + } + + self->pacing_timer = pacing_timer; + + // Playback with two independent clocks. One is the sample rate which + // determines when we push a new sample to the PWM slice. The second is the + // PWM frequency itself. + + // Determine the DMA divisor. The RP2040 has four pacing timers we can use + // to trigger the DMA. Each has a 16 bit fractional divisor system clock * X / Y where X and Y + // are 16-bit. + + uint32_t sample_rate = audiosample_sample_rate(sample); + + uint32_t system_clock = common_hal_mcu_processor_get_frequency(); + uint32_t best_denominator; + uint32_t best_numerator = limit_denominator(0xffff, sample_rate, system_clock, &best_denominator); + + dma_hw->timer[pacing_timer] = best_numerator << 16 | best_denominator; + audio_dma_result result = audio_dma_setup_playback( + &self->dma, + sample, + loop, + false, // single channel + 0, // audio channel + false, // output signed + BITS_PER_SAMPLE, + (uint32_t)tx_register, // output register: PWM cc register + 0x3b + pacing_timer); // data request line + + if (result == AUDIO_DMA_DMA_BUSY) { + common_hal_audiopwmio_pwmaudioout_stop(self); + mp_raise_RuntimeError(translate("No DMA channel found")); + } + if (result == AUDIO_DMA_MEMORY_ERROR) { + common_hal_audiopwmio_pwmaudioout_stop(self); + mp_raise_RuntimeError(translate("Unable to allocate buffers for signed conversion")); + } + // OK! We got all of the resources we need and dma is ready. +} + +void common_hal_audiopwmio_pwmaudioout_stop(audiopwmio_pwmaudioout_obj_t *self) { + if (self->pacing_timer < NUM_DMA_TIMERS) { + dma_hw->timer[self->pacing_timer] = 0; + self->pacing_timer = NUM_DMA_TIMERS; + } + + audio_dma_stop(&self->dma); + + // Set to quiescent level. + common_hal_pwmio_pwmout_set_duty_cycle(&self->left_pwm, self->quiescent_value); + pwmio_pwmout_set_top(&self->left_pwm, PWM_TOP); + if (self->stereo) { + common_hal_pwmio_pwmout_set_duty_cycle(&self->right_pwm, self->quiescent_value); + pwmio_pwmout_set_top(&self->right_pwm, PWM_TOP); + } +} + +bool common_hal_audiopwmio_pwmaudioout_get_playing(audiopwmio_pwmaudioout_obj_t *self) { + bool playing = audio_dma_get_playing(&self->dma); + + if (!playing && self->pacing_timer < NUM_DMA_TIMERS) { + common_hal_audiopwmio_pwmaudioout_stop(self); + } + + return playing; +} + +void common_hal_audiopwmio_pwmaudioout_pause(audiopwmio_pwmaudioout_obj_t *self) { + audio_dma_pause(&self->dma); +} + +void common_hal_audiopwmio_pwmaudioout_resume(audiopwmio_pwmaudioout_obj_t *self) { + audio_dma_resume(&self->dma); +} + +bool common_hal_audiopwmio_pwmaudioout_get_paused(audiopwmio_pwmaudioout_obj_t *self) { + return audio_dma_get_paused(&self->dma); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h b/circuitpython/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h new file mode 100644 index 0000000..e9f0f68 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.h @@ -0,0 +1,48 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 Jeff Epler for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_AUDIOPWMIO_PWMAUDIOOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOPWMIO_PWMAUDIOOUT_H + +#include "common-hal/pwmio/PWMOut.h" + +#include "audio_dma.h" + +typedef struct { + mp_obj_base_t base; + pwmio_pwmout_obj_t left_pwm; + pwmio_pwmout_obj_t right_pwm; + audio_dma_t dma; + uint16_t quiescent_value; + uint8_t pacing_timer; + bool stereo; // if false, only using left_pwm. +} audiopwmio_pwmaudioout_obj_t; + +void audiopwmout_reset(void); + +void audiopwmout_background(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_AUDIOPWMIO_PWMAUDIOOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/audiopwmio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/audiopwmio/__init__.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/audiopwmio/__init__.c diff --git a/circuitpython/ports/raspberrypi/common-hal/board/__init__.c b/circuitpython/ports/raspberrypi/common-hal/board/__init__.c new file mode 100644 index 0000000..3c7f30d --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/board/__init__.c @@ -0,0 +1,34 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 <string.h> + +#include "py/runtime.h" +#include "py/mphal.h" +#include "common-hal/microcontroller/Pin.h" + +// Pins aren't actually defined here. They are in the board specific directory +// such as boards/arduino_zero/pins.c. diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/I2C.c b/circuitpython/ports/raspberrypi/common-hal/busio/I2C.c new file mode 100644 index 0000000..516cee2 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/I2C.c @@ -0,0 +1,242 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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/mperrno.h" +#include "py/mphal.h" +#include "shared-bindings/busio/I2C.h" +#include "py/runtime.h" + +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/bitbangio/I2C.h" + +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +// Synopsys DW_apb_i2c (v2.01) IP + +#define NO_PIN 0xff + +// One second +#define BUS_TIMEOUT_US 1000000 + +STATIC bool never_reset_i2c[2]; +STATIC i2c_inst_t *i2c[2] = {i2c0, i2c1}; + +void reset_i2c(void) { + for (size_t i = 0; i < 2; i++) { + if (never_reset_i2c[i]) { + continue; + } + + i2c_deinit(i2c[i]); + } +} + +void common_hal_busio_i2c_construct(busio_i2c_obj_t *self, + const mcu_pin_obj_t *scl, const mcu_pin_obj_t *sda, uint32_t frequency, uint32_t timeout) { + self->peripheral = NULL; + // I2C pins have a regular pattern. SCL is always odd and SDA is even. They match up in pairs + // so we can divide by two to get the instance. This pattern repeats. + size_t scl_instance = (scl->number / 2) % 2; + size_t sda_instance = (sda->number / 2) % 2; + if (scl->number % 2 == 1 && sda->number % 2 == 0 && scl_instance == sda_instance) { + self->peripheral = i2c[sda_instance]; + } + if (self->peripheral == NULL) { + mp_raise_ValueError(translate("Invalid pins")); + } + if ((i2c_get_hw(self->peripheral)->enable & I2C_IC_ENABLE_ENABLE_BITS) != 0) { + mp_raise_ValueError(translate("I2C peripheral in use")); + } + if (frequency > 1000000) { + mp_raise_ValueError(translate("Unsupported baudrate")); + } + + #if CIRCUITPY_REQUIRE_I2C_PULLUPS + // Test that the pins are in a high state. (Hopefully indicating they are pulled up.) + gpio_set_function(sda->number, GPIO_FUNC_SIO); + gpio_set_function(scl->number, GPIO_FUNC_SIO); + gpio_set_dir(sda->number, GPIO_IN); + gpio_set_dir(scl->number, GPIO_IN); + + gpio_set_pulls(sda->number, false, true); + gpio_set_pulls(scl->number, false, true); + + common_hal_mcu_delay_us(10); + + gpio_set_pulls(sda->number, false, false); + gpio_set_pulls(scl->number, false, false); + + // We must pull up within 3us to achieve 400khz. + common_hal_mcu_delay_us(3); + + if (!gpio_get(sda->number) || !gpio_get(scl->number)) { + reset_pin_number(sda->number); + reset_pin_number(scl->number); + mp_raise_RuntimeError(translate("No pull up found on SDA or SCL; check your wiring")); + } + #endif + + // Create a bitbangio.I2C object to do 0 byte writes. + // + // These are used to non-invasively detect I2C devices by sending + // the address and confirming an ACK. + // They are not supported by the RP2040 hardware. + // + // Must be done before setting up the I2C pins, since they will be + // set up as GPIO by the bitbangio.I2C object. + // + // Sets pins to open drain, high, and input. + // + // Do not use the default supplied clock stretching timeout here. + // It is too short for some devices. Use the busio timeout instead. + shared_module_bitbangio_i2c_construct(&self->bitbangio_i2c, scl, sda, + frequency, BUS_TIMEOUT_US); + + self->baudrate = i2c_init(self->peripheral, frequency); + + self->scl_pin = scl->number; + self->sda_pin = sda->number; + claim_pin(scl); + claim_pin(sda); + + gpio_set_function(self->scl_pin, GPIO_FUNC_I2C); + gpio_set_function(self->sda_pin, GPIO_FUNC_I2C); +} + +bool common_hal_busio_i2c_deinited(busio_i2c_obj_t *self) { + return self->sda_pin == NO_PIN; +} + +void common_hal_busio_i2c_deinit(busio_i2c_obj_t *self) { + if (common_hal_busio_i2c_deinited(self)) { + return; + } + never_reset_i2c[i2c_hw_index(self->peripheral)] = false; + + i2c_deinit(self->peripheral); + + reset_pin_number(self->sda_pin); + reset_pin_number(self->scl_pin); + self->sda_pin = NO_PIN; + self->scl_pin = NO_PIN; +} + +bool common_hal_busio_i2c_probe(busio_i2c_obj_t *self, uint8_t addr) { + return common_hal_busio_i2c_write(self, addr, NULL, 0) == 0; +} + +bool common_hal_busio_i2c_try_lock(busio_i2c_obj_t *self) { + bool grabbed_lock = false; + if (!self->has_lock) { + grabbed_lock = true; + self->has_lock = true; + } + return grabbed_lock; +} + +bool common_hal_busio_i2c_has_lock(busio_i2c_obj_t *self) { + return self->has_lock; +} + +void common_hal_busio_i2c_unlock(busio_i2c_obj_t *self) { + self->has_lock = false; +} + +STATIC uint8_t _common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr, + const uint8_t *data, size_t len, bool transmit_stop_bit) { + if (len == 0) { + // The RP2040 I2C peripheral will not perform 0 byte writes. + // So use bitbangio.I2C to do the write. + + gpio_set_function(self->scl_pin, GPIO_FUNC_SIO); + gpio_set_function(self->sda_pin, GPIO_FUNC_SIO); + gpio_set_dir(self->scl_pin, GPIO_IN); + gpio_set_dir(self->sda_pin, GPIO_IN); + gpio_put(self->scl_pin, false); + gpio_put(self->sda_pin, false); + + uint8_t status = shared_module_bitbangio_i2c_write(&self->bitbangio_i2c, + addr, data, len, transmit_stop_bit); + + // The pins must be set back to GPIO_FUNC_I2C in the order given here, + // SCL first, otherwise reads will hang. + gpio_set_function(self->scl_pin, GPIO_FUNC_I2C); + gpio_set_function(self->sda_pin, GPIO_FUNC_I2C); + + return status; + } + + int result = i2c_write_timeout_us(self->peripheral, addr, data, len, !transmit_stop_bit, BUS_TIMEOUT_US); + if (result == len) { + return 0; + } + switch (result) { + case PICO_ERROR_GENERIC: + return MP_ENODEV; + case PICO_ERROR_TIMEOUT: + return MP_ETIMEDOUT; + default: + return MP_EIO; + } +} + +uint8_t common_hal_busio_i2c_write(busio_i2c_obj_t *self, uint16_t addr, + const uint8_t *data, size_t len) { + return _common_hal_busio_i2c_write(self, addr, data, len, true); +} + +uint8_t common_hal_busio_i2c_read(busio_i2c_obj_t *self, uint16_t addr, + uint8_t *data, size_t len) { + int result = i2c_read_timeout_us(self->peripheral, addr, data, len, false, BUS_TIMEOUT_US); + if (result == len) { + return 0; + } + switch (result) { + case PICO_ERROR_GENERIC: + return MP_ENODEV; + case PICO_ERROR_TIMEOUT: + return MP_ETIMEDOUT; + default: + return MP_EIO; + } +} + +uint8_t common_hal_busio_i2c_write_read(busio_i2c_obj_t *self, uint16_t addr, + uint8_t *out_data, size_t out_len, uint8_t *in_data, size_t in_len) { + uint8_t result = _common_hal_busio_i2c_write(self, addr, out_data, out_len, false); + if (result != 0) { + return result; + } + + return common_hal_busio_i2c_read(self, addr, in_data, in_len); +} + +void common_hal_busio_i2c_never_reset(busio_i2c_obj_t *self) { + never_reset_i2c[i2c_hw_index(self->peripheral)] = true; + + never_reset_pin_number(self->scl_pin); + never_reset_pin_number(self->sda_pin); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/I2C.h b/circuitpython/ports/raspberrypi/common-hal/busio/I2C.h new file mode 100644 index 0000000..dbaede1 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/I2C.h @@ -0,0 +1,49 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_BUSIO_I2C_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_BUSIO_I2C_H + +#include "common-hal/microcontroller/Pin.h" +#include "shared-module/bitbangio/I2C.h" + +#include "py/obj.h" + +#include "src/rp2_common/hardware_i2c/include/hardware/i2c.h" + +typedef struct { + mp_obj_base_t base; + i2c_inst_t *peripheral; + bitbangio_i2c_obj_t bitbangio_i2c; + bool has_lock; + uint baudrate; + uint8_t scl_pin; + uint8_t sda_pin; +} busio_i2c_obj_t; + +void reset_i2c(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_BUSIO_I2C_H diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/SPI.c b/circuitpython/ports/raspberrypi/common-hal/busio/SPI.c new file mode 100644 index 0000000..4a18d62 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/SPI.c @@ -0,0 +1,290 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 "shared-bindings/busio/SPI.h" + +#include "shared/runtime/interrupt_char.h" +#include "py/mperrno.h" +#include "py/runtime.h" + +#include "supervisor/board.h" +#include "common-hal/microcontroller/Pin.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "src/rp2_common/hardware_dma/include/hardware/dma.h" +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +#define NO_INSTANCE 0xff + +STATIC bool never_reset_spi[2]; +STATIC spi_inst_t *spi[2] = {spi0, spi1}; + +void reset_spi(void) { + for (size_t i = 0; i < 2; i++) { + if (never_reset_spi[i]) { + continue; + } + + spi_deinit(spi[i]); + } +} + +void common_hal_busio_spi_construct(busio_spi_obj_t *self, + const mcu_pin_obj_t *clock, const mcu_pin_obj_t *mosi, + const mcu_pin_obj_t *miso, bool half_duplex) { + size_t instance_index = NO_INSTANCE; + + if (half_duplex) { + mp_raise_NotImplementedError(translate("Half duplex SPI is not implemented")); + } + + if (clock->number % 4 == 2) { + instance_index = (clock->number / 8) % 2; + } + if (mosi != NULL) { + // Make sure the set MOSI matches the clock settings. + if (mosi->number % 4 != 3 || + (mosi->number / 8) % 2 != instance_index) { + instance_index = NO_INSTANCE; + } + } + if (miso != NULL) { + // Make sure the set MOSI matches the clock settings. + if (miso->number % 4 != 0 || + (miso->number / 8) % 2 != instance_index) { + instance_index = NO_INSTANCE; + } + } + + // TODO: Check to see if we're sharing the SPI with a native APA102. + + if (instance_index > 1) { + mp_raise_ValueError(translate("Invalid pins")); + } + + if (instance_index == 0) { + self->peripheral = spi0; + } else if (instance_index == 1) { + self->peripheral = spi1; + } + + if ((spi_get_hw(self->peripheral)->cr1 & SPI_SSPCR1_SSE_BITS) != 0) { + mp_raise_ValueError(translate("SPI peripheral in use")); + } + + self->target_frequency = 250000; + self->real_frequency = spi_init(self->peripheral, self->target_frequency); + + gpio_set_function(clock->number, GPIO_FUNC_SPI); + claim_pin(clock); + self->clock = clock; + + self->MOSI = mosi; + if (mosi != NULL) { + gpio_set_function(mosi->number, GPIO_FUNC_SPI); + claim_pin(mosi); + } + + self->MISO = miso; + if (miso != NULL) { + gpio_set_function(miso->number, GPIO_FUNC_SPI); + claim_pin(miso); + } +} + +void common_hal_busio_spi_never_reset(busio_spi_obj_t *self) { + never_reset_spi[spi_get_index(self->peripheral)] = true; + + common_hal_never_reset_pin(self->clock); + common_hal_never_reset_pin(self->MOSI); + common_hal_never_reset_pin(self->MISO); +} + +bool common_hal_busio_spi_deinited(busio_spi_obj_t *self) { + return self->clock == NULL; +} + +void common_hal_busio_spi_deinit(busio_spi_obj_t *self) { + if (common_hal_busio_spi_deinited(self)) { + return; + } + never_reset_spi[spi_get_index(self->peripheral)] = false; + spi_deinit(self->peripheral); + + common_hal_reset_pin(self->clock); + common_hal_reset_pin(self->MOSI); + common_hal_reset_pin(self->MISO); + self->clock = NULL; +} + +bool common_hal_busio_spi_configure(busio_spi_obj_t *self, + uint32_t baudrate, uint8_t polarity, uint8_t phase, uint8_t bits) { + if (baudrate == self->target_frequency && + polarity == self->polarity && + phase == self->phase && + bits == self->bits) { + return true; + } + + spi_set_format(self->peripheral, bits, polarity, phase, SPI_MSB_FIRST); + + self->polarity = polarity; + self->phase = phase; + self->bits = bits; + self->target_frequency = baudrate; + self->real_frequency = spi_set_baudrate(self->peripheral, baudrate); + + return true; +} + +bool common_hal_busio_spi_try_lock(busio_spi_obj_t *self) { + bool grabbed_lock = false; + if (!self->has_lock) { + grabbed_lock = true; + self->has_lock = true; + } + return grabbed_lock; +} + +bool common_hal_busio_spi_has_lock(busio_spi_obj_t *self) { + return self->has_lock; +} + +void common_hal_busio_spi_unlock(busio_spi_obj_t *self) { + self->has_lock = false; +} + +static bool _transfer(busio_spi_obj_t *self, + const uint8_t *data_out, size_t out_len, + uint8_t *data_in, size_t in_len) { + // Use DMA for large transfers if channels are available + const size_t dma_min_size_threshold = 32; + int chan_tx = -1; + int chan_rx = -1; + size_t len = MAX(out_len, in_len); + if (len >= dma_min_size_threshold) { + // Use two DMA channels to service the two FIFOs + chan_tx = dma_claim_unused_channel(false); + chan_rx = dma_claim_unused_channel(false); + } + bool use_dma = chan_rx >= 0 && chan_tx >= 0; + if (use_dma) { + dma_channel_config c = dma_channel_get_default_config(chan_tx); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, spi_get_index(self->peripheral) ? DREQ_SPI1_TX : DREQ_SPI0_TX); + channel_config_set_read_increment(&c, out_len == len); + channel_config_set_write_increment(&c, false); + dma_channel_configure(chan_tx, &c, + &spi_get_hw(self->peripheral)->dr, + data_out, + len, + false); + + c = dma_channel_get_default_config(chan_rx); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, spi_get_index(self->peripheral) ? DREQ_SPI1_RX : DREQ_SPI0_RX); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, in_len == len); + dma_channel_configure(chan_rx, &c, + data_in, + &spi_get_hw(self->peripheral)->dr, + len, + false); + + dma_start_channel_mask((1u << chan_rx) | (1u << chan_tx)); + while (dma_channel_is_busy(chan_rx) || dma_channel_is_busy(chan_tx)) { + // TODO: We should idle here until we get a DMA interrupt or something else. + RUN_BACKGROUND_TASKS; + } + } + + // If we have claimed only one channel successfully, we should release immediately. This also + // releases the DMA after use_dma has been done. + if (chan_rx >= 0) { + dma_channel_unclaim(chan_rx); + } + if (chan_tx >= 0) { + dma_channel_unclaim(chan_tx); + } + + if (!use_dma) { + // Use software for small transfers, or if couldn't claim two DMA channels + // Never have more transfers in flight than will fit into the RX FIFO, + // else FIFO will overflow if this code is heavily interrupted. + const size_t fifo_depth = 8; + size_t rx_remaining = len; + size_t tx_remaining = len; + + while (rx_remaining || tx_remaining) { + if (tx_remaining && spi_is_writable(self->peripheral) && rx_remaining - tx_remaining < fifo_depth) { + spi_get_hw(self->peripheral)->dr = (uint32_t)*data_out; + // Increment only if the buffer is the transfer length. It's 1 otherwise. + if (out_len == len) { + data_out++; + } + --tx_remaining; + } + if (rx_remaining && spi_is_readable(self->peripheral)) { + *data_in = (uint8_t)spi_get_hw(self->peripheral)->dr; + // Increment only if the buffer is the transfer length. It's 1 otherwise. + if (in_len == len) { + data_in++; + } + --rx_remaining; + } + RUN_BACKGROUND_TASKS; + } + } + return true; +} + +bool common_hal_busio_spi_write(busio_spi_obj_t *self, + const uint8_t *data, size_t len) { + uint32_t data_in; + return _transfer(self, data, len, (uint8_t *)&data_in, MIN(len, 4)); +} + +bool common_hal_busio_spi_read(busio_spi_obj_t *self, + uint8_t *data, size_t len, uint8_t write_value) { + uint32_t data_out = write_value << 24 | write_value << 16 | write_value << 8 | write_value; + return _transfer(self, (const uint8_t *)&data_out, MIN(4, len), data, len); +} + +bool common_hal_busio_spi_transfer(busio_spi_obj_t *self, const uint8_t *data_out, uint8_t *data_in, size_t len) { + return _transfer(self, data_out, len, data_in, len); +} + +uint32_t common_hal_busio_spi_get_frequency(busio_spi_obj_t *self) { + return self->real_frequency; +} + +uint8_t common_hal_busio_spi_get_phase(busio_spi_obj_t *self) { + return self->phase; +} + +uint8_t common_hal_busio_spi_get_polarity(busio_spi_obj_t *self) { + return self->polarity; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/SPI.h b/circuitpython/ports/raspberrypi/common-hal/busio/SPI.h new file mode 100644 index 0000000..b76dbbf --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/SPI.h @@ -0,0 +1,52 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_BUSIO_SPI_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_BUSIO_SPI_H + +#include "common-hal/microcontroller/Pin.h" + +#include "py/obj.h" + +#include "src/rp2_common/hardware_spi/include/hardware/spi.h" + +typedef struct { + mp_obj_base_t base; + spi_inst_t *peripheral; + bool has_lock; + const mcu_pin_obj_t *clock; + const mcu_pin_obj_t *MOSI; + const mcu_pin_obj_t *MISO; + uint32_t target_frequency; + int32_t real_frequency; + uint8_t polarity; + uint8_t phase; + uint8_t bits; +} busio_spi_obj_t; + +void reset_spi(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_BUSIO_SPI_H diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/UART.c b/circuitpython/ports/raspberrypi/common-hal/busio/UART.c new file mode 100644 index 0000000..97af41d --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/UART.c @@ -0,0 +1,339 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 microDev + * + * 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 "shared-bindings/busio/UART.h" + +#include "py/stream.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "supervisor/shared/tick.h" +#include "shared/runtime/interrupt_char.h" +#include "common-hal/microcontroller/Pin.h" + +#include "src/rp2_common/hardware_irq/include/hardware/irq.h" +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +#define NO_PIN 0xff + +#define UART_INST(uart) (((uart) ? uart1 : uart0)) + +typedef enum { + STATUS_FREE = 0, + STATUS_BUSY, + STATUS_NEVER_RESET +} uart_status_t; + +static uart_status_t uart_status[NUM_UARTS]; + +void reset_uart(void) { + for (uint8_t num = 0; num < NUM_UARTS; num++) { + if (uart_status[num] == STATUS_BUSY) { + uart_status[num] = STATUS_FREE; + uart_deinit(UART_INST(num)); + } + } +} + +void never_reset_uart(uint8_t num) { + uart_status[num] = STATUS_NEVER_RESET; +} + +static uint8_t pin_init(const uint8_t uart, const mcu_pin_obj_t *pin, const uint8_t pin_type) { + if (pin == NULL) { + return NO_PIN; + } + if (!(((pin->number % 4) == pin_type) && ((((pin->number + 4) / 8) % NUM_UARTS) == uart))) { + mp_raise_ValueError(translate("Invalid pins")); + } + claim_pin(pin); + gpio_set_function(pin->number, GPIO_FUNC_UART); + return pin->number; +} + +static busio_uart_obj_t *active_uarts[NUM_UARTS]; + +static void _copy_into_ringbuf(ringbuf_t *r, uart_inst_t *uart) { + while (uart_is_readable(uart) && ringbuf_num_empty(r) > 0) { + ringbuf_put(r, (uint8_t)uart_get_hw(uart)->dr); + } +} + +static void shared_callback(busio_uart_obj_t *self) { + _copy_into_ringbuf(&self->ringbuf, self->uart); + // We always clear the interrupt so it doesn't continue to fire because we + // may not have read everything available. + uart_get_hw(self->uart)->icr = UART_UARTICR_RXIC_BITS | UART_UARTICR_RTIC_BITS; +} + +static void uart0_callback(void) { + shared_callback(active_uarts[0]); +} + +static void uart1_callback(void) { + shared_callback(active_uarts[1]); +} + +void common_hal_busio_uart_construct(busio_uart_obj_t *self, + const mcu_pin_obj_t *tx, const mcu_pin_obj_t *rx, + const mcu_pin_obj_t *rts, const mcu_pin_obj_t *cts, + const mcu_pin_obj_t *rs485_dir, bool rs485_invert, + uint32_t baudrate, uint8_t bits, busio_uart_parity_t parity, uint8_t stop, + mp_float_t timeout, uint16_t receiver_buffer_size, byte *receiver_buffer, + bool sigint_enabled) { + + if (bits > 8) { + mp_raise_ValueError(translate("Invalid word/bit length")); + } + + if (receiver_buffer_size == 0) { + mp_raise_ValueError(translate("Invalid buffer size")); + } + + uint8_t uart_id = ((((tx != NULL) ? tx->number : rx->number) + 4) / 8) % NUM_UARTS; + + if (uart_status[uart_id] != STATUS_FREE) { + mp_raise_RuntimeError(translate("All UART peripherals are in use")); + } + // These may raise exceptions if pins are already in use. + self->tx_pin = pin_init(uart_id, tx, 0); + self->rx_pin = pin_init(uart_id, rx, 1); + self->cts_pin = pin_init(uart_id, cts, 2); + self->rts_pin = pin_init(uart_id, rts, 3); + + if (rs485_dir != NULL) { + uint8_t pin = rs485_dir->number; + self->rs485_dir_pin = pin; + self->rs485_invert = rs485_invert; + + gpio_init(pin); + + claim_pin(rs485_dir); + + gpio_disable_pulls(pin); + + // Turn on "strong" pin driving (more current available). + hw_write_masked(&padsbank0_hw->io[pin], + PADS_BANK0_GPIO0_DRIVE_VALUE_12MA << PADS_BANK0_GPIO0_DRIVE_LSB, + PADS_BANK0_GPIO0_DRIVE_BITS); + + gpio_put(self->rs485_dir_pin, rs485_invert); + gpio_set_dir(self->rs485_dir_pin, GPIO_OUT); + } else { + self->rs485_dir_pin = NO_PIN; + } + + uart_status[uart_id] = STATUS_BUSY; + + + self->uart = UART_INST(uart_id); + self->uart_id = uart_id; + self->baudrate = baudrate; + self->timeout_ms = timeout * 1000; + + uart_init(self->uart, self->baudrate); + uart_set_fifo_enabled(self->uart, true); + uart_set_format(self->uart, bits, stop, parity); + uart_set_hw_flow(self->uart, (cts != NULL), (rts != NULL)); + + if (rx != NULL) { + // Initially allocate the UART's buffer in the long-lived part of the + // heap. UARTs are generally long-lived objects, but the "make long- + // lived" machinery is incapable of moving internal pointers like + // self->buffer, so do it manually. (However, as long as internal + // pointers like this are NOT moved, allocating the buffer + // in the long-lived pool is not strictly necessary) + // (This is a macro.) + if (!ringbuf_alloc(&self->ringbuf, receiver_buffer_size, true)) { + mp_raise_msg(&mp_type_MemoryError, translate("Failed to allocate RX buffer")); + } + active_uarts[uart_id] = self; + if (uart_id == 1) { + self->uart_irq_id = UART1_IRQ; + irq_set_exclusive_handler(self->uart_irq_id, uart1_callback); + } else { + self->uart_irq_id = UART0_IRQ; + irq_set_exclusive_handler(self->uart_irq_id, uart0_callback); + } + irq_set_enabled(self->uart_irq_id, true); + uart_set_irq_enables(self->uart, true /* rx has data */, false /* tx needs data */); + } +} + +bool common_hal_busio_uart_deinited(busio_uart_obj_t *self) { + return self->tx_pin == NO_PIN && self->rx_pin == NO_PIN; +} + +void common_hal_busio_uart_deinit(busio_uart_obj_t *self) { + if (common_hal_busio_uart_deinited(self)) { + return; + } + uart_deinit(self->uart); + ringbuf_free(&self->ringbuf); + active_uarts[self->uart_id] = NULL; + uart_status[self->uart_id] = STATUS_FREE; + reset_pin_number(self->tx_pin); + reset_pin_number(self->rx_pin); + reset_pin_number(self->cts_pin); + reset_pin_number(self->rts_pin); + reset_pin_number(self->rs485_dir_pin); + self->tx_pin = NO_PIN; + self->rx_pin = NO_PIN; + self->cts_pin = NO_PIN; + self->rts_pin = NO_PIN; + self->rs485_dir_pin = NO_PIN; +} + +// Write characters. +size_t common_hal_busio_uart_write(busio_uart_obj_t *self, const uint8_t *data, size_t len, int *errcode) { + if (self->tx_pin == NO_PIN) { + mp_raise_ValueError(translate("No TX pin")); + } + + if (self->rs485_dir_pin != NO_PIN) { + uart_tx_wait_blocking(self->uart); + gpio_put(self->rs485_dir_pin, !self->rs485_invert); + } + + size_t left_to_write = len; + while (left_to_write > 0) { + while (uart_is_writable(self->uart) && left_to_write > 0) { + // Write and advance. + uart_get_hw(self->uart)->dr = *data++; + // Decrease how many chars left to write. + left_to_write--; + } + RUN_BACKGROUND_TASKS; + } + if (self->rs485_dir_pin != NO_PIN) { + uart_tx_wait_blocking(self->uart); + gpio_put(self->rs485_dir_pin, self->rs485_invert); + } + return len; +} + +// Read characters. +size_t common_hal_busio_uart_read(busio_uart_obj_t *self, uint8_t *data, size_t len, int *errcode) { + if (self->rx_pin == NO_PIN) { + mp_raise_ValueError(translate("No RX pin")); + } + + if (len == 0) { + // Nothing to read. + return 0; + } + + // Prevent conflict with uart irq. + irq_set_enabled(self->uart_irq_id, false); + + // Copy as much received data as available, up to len bytes. + size_t total_read = ringbuf_get_n(&self->ringbuf, data, len); + + // Check if we still need to read more data. + if (len > total_read) { + len -= total_read; + uint64_t start_ticks = supervisor_ticks_ms64(); + // Busy-wait until timeout or until we've read enough chars. + while (len > 0 && (supervisor_ticks_ms64() - start_ticks < self->timeout_ms)) { + if (uart_is_readable(self->uart)) { + // Read and advance. + data[total_read] = uart_get_hw(self->uart)->dr; + + // Adjust the counters. + len--; + total_read++; + + // Reset the timeout on every character read. + start_ticks = supervisor_ticks_ms64(); + } + RUN_BACKGROUND_TASKS; + // Allow user to break out of a timeout with a KeyboardInterrupt. + if (mp_hal_is_interrupted()) { + break; + } + } + } + + // Now that we've emptied the ringbuf some, fill it up with anything in the + // FIFO. This ensures that we'll empty the FIFO as much as possible and + // reset the interrupt when we catch up. + _copy_into_ringbuf(&self->ringbuf, self->uart); + + // Re-enable irq. + irq_set_enabled(self->uart_irq_id, true); + + if (total_read == 0) { + *errcode = EAGAIN; + return MP_STREAM_ERROR; + } + + return total_read; +} + +uint32_t common_hal_busio_uart_get_baudrate(busio_uart_obj_t *self) { + return self->baudrate; +} + +void common_hal_busio_uart_set_baudrate(busio_uart_obj_t *self, uint32_t baudrate) { + self->baudrate = baudrate; + uart_set_baudrate(self->uart, baudrate); +} + +mp_float_t common_hal_busio_uart_get_timeout(busio_uart_obj_t *self) { + return (mp_float_t)(self->timeout_ms / 1000.0f); +} + +void common_hal_busio_uart_set_timeout(busio_uart_obj_t *self, mp_float_t timeout) { + self->timeout_ms = timeout * 1000; +} + +uint32_t common_hal_busio_uart_rx_characters_available(busio_uart_obj_t *self) { + // Prevent conflict with uart irq. + irq_set_enabled(self->uart_irq_id, false); + // The UART only interrupts after a threshold so make sure to copy anything + // out of its FIFO before measuring how many bytes we've received. + _copy_into_ringbuf(&self->ringbuf, self->uart); + irq_set_enabled(self->uart_irq_id, false); + return ringbuf_num_filled(&self->ringbuf); +} + +void common_hal_busio_uart_clear_rx_buffer(busio_uart_obj_t *self) { + // Prevent conflict with uart irq. + irq_set_enabled(self->uart_irq_id, false); + ringbuf_clear(&self->ringbuf); + + // Throw away the FIFO contents too. + while (uart_is_readable(self->uart)) { + (void)uart_get_hw(self->uart)->dr; + } + irq_set_enabled(self->uart_irq_id, true); +} + +bool common_hal_busio_uart_ready_to_tx(busio_uart_obj_t *self) { + if (self->tx_pin == NO_PIN) { + return false; + } + return uart_is_writable(self->uart); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/UART.h b/circuitpython/ports/raspberrypi/common-hal/busio/UART.h new file mode 100644 index 0000000..db416b7 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/UART.h @@ -0,0 +1,54 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 microDev + * + * 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_RASPBERRYPI_COMMON_HAL_BUSIO_UART_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_BUSIO_UART_H + +#include "py/obj.h" +#include "py/ringbuf.h" + +#include "src/rp2_common/hardware_uart/include/hardware/uart.h" + +typedef struct { + mp_obj_base_t base; + uint8_t tx_pin; + uint8_t rx_pin; + uint8_t cts_pin; + uint8_t rs485_dir_pin; + bool rs485_invert; + uint8_t rts_pin; + uint8_t uart_id; + uint8_t uart_irq_id; + uint32_t baudrate; + uint32_t timeout_ms; + uart_inst_t *uart; + ringbuf_t ringbuf; +} busio_uart_obj_t; + +extern void reset_uart(void); +extern void never_reset_uart(uint8_t num); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_BUSIO_UART_H diff --git a/circuitpython/ports/raspberrypi/common-hal/busio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/busio/__init__.c new file mode 100644 index 0000000..41761b6 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/busio/__init__.c @@ -0,0 +1 @@ +// No busio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/countio/Counter.c b/circuitpython/ports/raspberrypi/common-hal/countio/Counter.c new file mode 100644 index 0000000..0ae773b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/countio/Counter.c @@ -0,0 +1,115 @@ +#include "shared-bindings/countio/Counter.h" + +#include "py/runtime.h" +#include "py/mpstate.h" +#include "supervisor/shared/translate.h" + +#include "shared-bindings/countio/Edge.h" +#include "shared-bindings/digitalio/Pull.h" +#include "common-hal/pwmio/PWMOut.h" + +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" +#include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" +#include "src/rp2_common/hardware_irq/include/hardware/irq.h" + + +void common_hal_countio_counter_construct(countio_counter_obj_t *self, + const mcu_pin_obj_t *pin, countio_edge_t edge, digitalio_pull_t pull) { + + if (pwm_gpio_to_channel(pin->number) != PWM_CHAN_B) { + mp_raise_RuntimeError(translate("Pin must be on PWM Channel B")); + } + + if (edge == EDGE_RISE_AND_FALL) { + mp_raise_NotImplementedError(translate("RISE_AND_FALL not available on this chip")); + } + + self->pin = pin->number; + self->slice_num = pwm_gpio_to_slice_num(self->pin); + + if (MP_STATE_PORT(counting)[self->slice_num] != NULL) { + mp_raise_RuntimeError(translate("PWM slice already in use")); + } + + uint8_t ab_channel = pwm_gpio_to_channel(self->pin); + if (!pwmio_claim_slice_ab_channels(self->slice_num)) { + mp_raise_RuntimeError(translate("PWM slice channel A already in use")); + } + + pwm_clear_irq(self->slice_num); + pwm_set_irq_enabled(self->slice_num, true); + irq_set_exclusive_handler(PWM_IRQ_WRAP, counter_interrupt_handler); + irq_set_enabled(PWM_IRQ_WRAP, true); + + pwm_config cfg = pwm_get_default_config(); + pwm_config_set_clkdiv_mode(&cfg, edge == EDGE_RISE ? PWM_DIV_B_RISING : PWM_DIV_B_FALLING); + pwm_init(self->slice_num, &cfg, false); + gpio_set_function(self->pin, GPIO_FUNC_PWM); + gpio_set_pulls(self->pin, pull == PULL_UP, pull == PULL_DOWN); + + claim_pin(pin); + + MP_STATE_PORT(counting)[self->slice_num] = self; + + self->count = 0; + pwm_set_enabled(self->slice_num, true); +} + + +void reset_countio(void) { + for (size_t i = 0; i < NUM_PWM_SLICES; i++) { + if (MP_STATE_PORT(counting)[i] != NULL) { + common_hal_countio_counter_deinit(MP_STATE_PORT(counting)[i]); + } + } +} + +bool common_hal_countio_counter_deinited(countio_counter_obj_t *self) { + return self->pin == 0; +} + +void common_hal_countio_counter_deinit(countio_counter_obj_t *self) { + if (common_hal_countio_counter_deinited(self)) { + return; + } + + pwm_set_enabled(self->slice_num, false); + pwm_set_irq_enabled(self->slice_num, false); + + pwmio_release_slice_ab_channels(self->slice_num); + + reset_pin_number(self->pin); + + MP_STATE_PORT(counting)[self->slice_num] = NULL; + self->pin = 0; + self->slice_num = 0; +} + +mp_int_t common_hal_countio_counter_get_count(countio_counter_obj_t *self) { + self->count += pwm_get_counter(self->slice_num); + pwm_set_counter(self->slice_num, 0); + return self->count; +} + +void common_hal_countio_counter_set_count(countio_counter_obj_t *self, + mp_int_t new_count) { + pwm_set_counter(self->slice_num, 0); + self->count = new_count; +} + +void counter_interrupt_handler(void) { + uint32_t mask = pwm_get_irq_status_mask(); + + uint8_t i = 1; + uint8_t pos = 0; + while (!(i & mask)) { + i = i << 1; + ++pos; + } + + countio_counter_obj_t *self = MP_STATE_PORT(counting)[pos]; + if (self != NULL) { + pwm_clear_irq(self->slice_num); + self->count += 65536; + } +} diff --git a/circuitpython/ports/raspberrypi/common-hal/countio/Counter.h b/circuitpython/ports/raspberrypi/common-hal/countio/Counter.h new file mode 100644 index 0000000..3355036 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/countio/Counter.h @@ -0,0 +1,21 @@ + +#ifndef MICROPY_INCLUDED_RASPBERRRYPI_COMMON_HAL_COUNTIO_COUNTER_H +#define MICROPY_INCLUDED_RASPBERRRYPI_COMMON_HAL_COUNTIO_COUNTER_H + +#include "common-hal/microcontroller/Pin.h" + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint8_t pin; + uint8_t slice_num; + mp_int_t count; +} countio_counter_obj_t; + + +void counter_interrupt_handler(void); + +void reset_countio(void); + +#endif // MICROPY_INCLUDED_RASPBERRRYPI_COMMON_HAL_COUNTIO_COUNTER_H diff --git a/circuitpython/ports/raspberrypi/common-hal/countio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/countio/__init__.c new file mode 100644 index 0000000..d47de33 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/countio/__init__.c @@ -0,0 +1 @@ +// No countio module functions diff --git a/circuitpython/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c b/circuitpython/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c new file mode 100644 index 0000000..a8a5a6b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/digitalio/DigitalInOut.c @@ -0,0 +1,191 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Scott Shawcroft for Adafruit Industries + * + * 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 <stdint.h> +#include <string.h> + +#include "py/runtime.h" +#include "py/mphal.h" + +#include "common-hal/microcontroller/Pin.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "supervisor/shared/translate.h" + +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +digitalinout_result_t common_hal_digitalio_digitalinout_construct( + digitalio_digitalinout_obj_t *self, const mcu_pin_obj_t *pin) { + claim_pin(pin); + self->pin = pin; + self->output = false; + self->open_drain = false; + + // Set to input. No output value. + gpio_init(pin->number); + return DIGITALINOUT_OK; +} + +void common_hal_digitalio_digitalinout_never_reset( + digitalio_digitalinout_obj_t *self) { + never_reset_pin_number(self->pin->number); +} + +bool common_hal_digitalio_digitalinout_deinited(digitalio_digitalinout_obj_t *self) { + return self->pin == NULL; +} + +void common_hal_digitalio_digitalinout_deinit(digitalio_digitalinout_obj_t *self) { + if (common_hal_digitalio_digitalinout_deinited(self)) { + return; + } + reset_pin_number(self->pin->number); + self->pin = NULL; +} + +void common_hal_digitalio_digitalinout_switch_to_input( + digitalio_digitalinout_obj_t *self, digitalio_pull_t pull) { + self->output = false; + // This also sets direction to input. + common_hal_digitalio_digitalinout_set_pull(self, pull); +} + +digitalinout_result_t common_hal_digitalio_digitalinout_switch_to_output( + digitalio_digitalinout_obj_t *self, bool value, + digitalio_drive_mode_t drive_mode) { + const uint8_t pin = self->pin->number; + gpio_disable_pulls(pin); + + // Turn on "strong" pin driving (more current available). + hw_write_masked(&padsbank0_hw->io[pin], + PADS_BANK0_GPIO0_DRIVE_VALUE_12MA << PADS_BANK0_GPIO0_DRIVE_LSB, + PADS_BANK0_GPIO0_DRIVE_BITS); + + self->output = true; + self->open_drain = drive_mode == DRIVE_MODE_OPEN_DRAIN; + + // Pin direction is ultimately set in set_value. We don't need to do it here. + common_hal_digitalio_digitalinout_set_value(self, value); + return DIGITALINOUT_OK; +} + +digitalio_direction_t common_hal_digitalio_digitalinout_get_direction( + digitalio_digitalinout_obj_t *self) { + return self->output ? DIRECTION_OUTPUT : DIRECTION_INPUT; +} + +void common_hal_digitalio_digitalinout_set_value( + digitalio_digitalinout_obj_t *self, bool value) { + const uint8_t pin = self->pin->number; + if (self->open_drain && value) { + // If true and open-drain, set the direction -before- setting + // the pin value, to to avoid a high glitch on the pin before + // switching from output to input for open-drain. + gpio_set_dir(pin, GPIO_IN); + gpio_put(pin, value); + } else { + // Otherwise set the direction -after- setting the pin value, + // to avoid a glitch which might occur if the old value was + // different and the pin was previously set to input. + gpio_put(pin, value); + gpio_set_dir(pin, GPIO_OUT); + } +} + +bool common_hal_digitalio_digitalinout_get_value( + digitalio_digitalinout_obj_t *self) { + return gpio_get(self->pin->number); +} + +digitalinout_result_t common_hal_digitalio_digitalinout_set_drive_mode( + digitalio_digitalinout_obj_t *self, + digitalio_drive_mode_t drive_mode) { + const uint8_t pin = self->pin->number; + bool value = common_hal_digitalio_digitalinout_get_value(self); + self->open_drain = drive_mode == DRIVE_MODE_OPEN_DRAIN; + // True is implemented differently between modes so reset the value to make + // sure it's correct for the new mode. + if (value) { + common_hal_digitalio_digitalinout_set_value(self, value); + } + return DIGITALINOUT_OK; +} + +digitalio_drive_mode_t common_hal_digitalio_digitalinout_get_drive_mode( + digitalio_digitalinout_obj_t *self) { + if (self->open_drain) { + return DRIVE_MODE_OPEN_DRAIN; + } else { + return DRIVE_MODE_PUSH_PULL; + } +} + +void common_hal_digitalio_digitalinout_set_pull( + digitalio_digitalinout_obj_t *self, digitalio_pull_t pull) { + const uint8_t pin = self->pin->number; + gpio_set_pulls(pin, pull == PULL_UP, pull == PULL_DOWN); + gpio_set_dir(pin, GPIO_IN); +} + +digitalio_pull_t common_hal_digitalio_digitalinout_get_pull( + digitalio_digitalinout_obj_t *self) { + uint32_t pin = self->pin->number; + if (self->output) { + mp_raise_AttributeError(translate("Cannot get pull while in output mode")); + return PULL_NONE; + } else { + if (gpio_is_pulled_up(pin)) { + return PULL_UP; + } else if (gpio_is_pulled_down(pin)) { + return PULL_DOWN; + } + } + return PULL_NONE; +} + +bool common_hal_digitalio_has_reg_op(digitalinout_reg_op_t op) { + return true; +} + +volatile uint32_t *common_hal_digitalio_digitalinout_get_reg(digitalio_digitalinout_obj_t *self, digitalinout_reg_op_t op, uint32_t *mask) { + const uint8_t pin = self->pin->number; + + *mask = 1u << pin; + + switch (op) { + case DIGITALINOUT_REG_READ: + return (volatile uint32_t *)&sio_hw->gpio_in; + case DIGITALINOUT_REG_WRITE: + return &sio_hw->gpio_out; + case DIGITALINOUT_REG_SET: + return &sio_hw->gpio_set; + case DIGITALINOUT_REG_RESET: + return &sio_hw->gpio_clr; + case DIGITALINOUT_REG_TOGGLE: + return &sio_hw->gpio_togl; + default: + return NULL; + } +} diff --git a/circuitpython/ports/raspberrypi/common-hal/digitalio/DigitalInOut.h b/circuitpython/ports/raspberrypi/common-hal/digitalio/DigitalInOut.h new file mode 100644 index 0000000..d656f60 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/digitalio/DigitalInOut.h @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_DIGITALIO_DIGITALINOUT_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_DIGITALIO_DIGITALINOUT_H + +#include "common-hal/microcontroller/Pin.h" +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; + bool output; + bool open_drain; +} digitalio_digitalinout_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_DIGITALIO_DIGITALINOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/digitalio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/digitalio/__init__.c new file mode 100644 index 0000000..20fad45 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/digitalio/__init__.c @@ -0,0 +1 @@ +// No digitalio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/floppyio/__init__.h b/circuitpython/ports/raspberrypi/common-hal/floppyio/__init__.h new file mode 100644 index 0000000..7dbf8a7 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/floppyio/__init__.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2022 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#pragma once + +// empirical-ish from RP2040 @ 125MHz for floppy_flux_readinto +#define FLOPPYIO_SAMPLERATE (24000000) +// empirical-ish from RP2040 @ 125MHz for floppy_mfm_readinto +// my guess is these are slower because the more complex routine falls out of cache, but it's just +// speculation because the loops are very similar. When looking at raw bins with a modified +// version of adafruit_floppy, it can be seen that there are _two_ peaks for T2 and T3, rather +// than a single one, around 36 (mostly) and 43 (rarer), compared to a single peak around 48. +#define T2_5 (54) +#define T3_5 (75) diff --git a/circuitpython/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c b/circuitpython/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c new file mode 100644 index 0000000..3c5c57e --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.c @@ -0,0 +1,159 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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/obj.h" +#include "py/runtime.h" + +#include "shared/runtime/context_manager_helpers.h" +#include "shared/runtime/interrupt_char.h" + +#include "bindings/rp2pio/StateMachine.h" +#include "bindings/rp2pio/__init__.h" +#include "common-hal/imagecapture/ParallelImageCapture.h" +#include "shared-bindings/imagecapture/ParallelImageCapture.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "shared-bindings/microcontroller/__init__.h" + +#include "src/rp2_common/hardware_pio/include/hardware/pio.h" +#include "src/rp2_common/hardware_pio/include/hardware/pio_instructions.h" + +// Define this to (1), and you can scope the instruction-pointer of the state machine on D26..28 (note the weird encoding though!) +#define DEBUG_STATE_MACHINE (0) +#if DEBUG_STATE_MACHINE +#define SIDE(x) ((x) << 8) +#else +#define SIDE(x) (0) +#endif + +#define _0 SIDE(0b11100) +#define _1 SIDE(0b00000) +#define _2 SIDE(0b10000) +#define _3 SIDE(0b10100) +#define _4 SIDE(0b11000) +#define _5 SIDE(0b10100) + +#define IMAGECAPTURE_CODE(width, pclk, vsync, href) \ + { \ +/* 0 */ pio_encode_wait_gpio(0, vsync) | _0, \ +/* 1 */ pio_encode_wait_gpio(1, vsync) | _1, \ + /* .wrap_target */ \ +/* 2 */ pio_encode_wait_gpio(1, href) | _2, \ +/* 3 */ pio_encode_wait_gpio(1, pclk) | _3, \ +/* 4 */ pio_encode_in(pio_pins, width) | _4, \ +/* 5 */ pio_encode_wait_gpio(0, pclk) | _5, \ + /* .wrap */ \ + } + +STATIC mcu_pin_obj_t *pin_from_number(uint8_t number) { + const mp_map_t *mcu_map = &mcu_pin_globals.map; + for (uint8_t i = 0; i < mcu_map->alloc; i++) { + mp_obj_t val = mcu_map->table[i].value; + if (!mp_obj_is_type(val, &mcu_pin_type)) { + continue; + } + mcu_pin_obj_t *pin = MP_OBJ_TO_PTR(val); + if (pin->number == number) { + return pin; + } + } + return NULL; +} + +void common_hal_imagecapture_parallelimagecapture_construct(imagecapture_parallelimagecapture_obj_t *self, + const uint8_t data_pins[], + uint8_t data_count, + const mcu_pin_obj_t *data_clock, + const mcu_pin_obj_t *vertical_sync, + const mcu_pin_obj_t *horizontal_reference) { + + for (int i = 1; i < data_count; i++) { + if (data_pins[i] - data_pins[0] != i) { + mp_raise_RuntimeError(translate("Pins must be sequential")); + } + } + + uint16_t imagecapture_code[] = IMAGECAPTURE_CODE(data_count, data_clock->number, vertical_sync->number, horizontal_reference->number); + + common_hal_rp2pio_statemachine_construct(&self->state_machine, + imagecapture_code, MP_ARRAY_SIZE(imagecapture_code), + common_hal_mcu_processor_get_frequency(), // full speed (4 instructions per loop -> max pclk 30MHz @ 120MHz) + 0, 0, // init + NULL, 0, 0, 0, // out pins + pin_from_number(data_pins[0]), data_count, // in pins + 0, 0, // in pulls + NULL, 0, 0, 0, // set pins + #if DEBUG_STATE_MACHINE + &pin_GPIO26, 3, 7, 7, // sideset pins + #else + NULL, 0, 0, 0, // sideset pins + #endif + false, // No sideset enable + NULL, PULL_NONE, // jump pin + (1 << vertical_sync->number) | (1 << horizontal_reference->number) | (1 << data_clock->number), // wait gpio pins + true, // exclusive pin use + false, 32, false, // out settings + false, // wait for txstall + true, 32, true, // in settings + false, // Not user-interruptible. + 2, 5); // wrap settings + + + PIO pio = self->state_machine.pio; + uint8_t pio_index = pio_get_index(pio); + uint sm = self->state_machine.state_machine; +} + +void common_hal_imagecapture_parallelimagecapture_deinit(imagecapture_parallelimagecapture_obj_t *self) { + if (common_hal_imagecapture_parallelimagecapture_deinited(self)) { + return; + } + return common_hal_rp2pio_statemachine_deinit(&self->state_machine); +} + +bool common_hal_imagecapture_parallelimagecapture_deinited(imagecapture_parallelimagecapture_obj_t *self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_imagecapture_parallelimagecapture_singleshot_capture(imagecapture_parallelimagecapture_obj_t *self, mp_obj_t buffer) { + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer, &bufinfo, MP_BUFFER_RW); + + PIO pio = self->state_machine.pio; + uint sm = self->state_machine.state_machine; + uint8_t offset = rp2pio_statemachine_program_offset(&self->state_machine); + + pio_sm_set_enabled(pio, sm, false); + pio_sm_clear_fifos(pio, sm); + + pio_sm_restart(pio, sm); + pio_sm_exec(pio, sm, pio_encode_jmp(offset)); + pio_sm_set_enabled(pio, sm, true); + + common_hal_rp2pio_statemachine_readinto(&self->state_machine, bufinfo.buf, bufinfo.len, 4, false); + + pio_sm_set_enabled(pio, sm, false); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.h b/circuitpython/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.h new file mode 100644 index 0000000..9f5d1ab --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/imagecapture/ParallelImageCapture.h @@ -0,0 +1,35 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include "common-hal/rp2pio/StateMachine.h" +#include "shared-bindings/imagecapture/ParallelImageCapture.h" + +struct imagecapture_parallelimagecapture_obj { + mp_obj_base_t base; + rp2pio_statemachine_obj_t state_machine; +}; diff --git a/circuitpython/ports/raspberrypi/common-hal/imagecapture/__init__.c b/circuitpython/ports/raspberrypi/common-hal/imagecapture/__init__.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/imagecapture/__init__.c diff --git a/circuitpython/ports/raspberrypi/common-hal/imagecapture/__init__.h b/circuitpython/ports/raspberrypi/common-hal/imagecapture/__init__.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/imagecapture/__init__.h diff --git a/circuitpython/ports/raspberrypi/common-hal/microcontroller/Pin.c b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Pin.c new file mode 100644 index 0000000..d40c1f4 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Pin.c @@ -0,0 +1,105 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 "common-hal/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +STATIC uint32_t never_reset_pins; + +void reset_all_pins(void) { + for (size_t i = 0; i < TOTAL_GPIO_COUNT; i++) { + if ((never_reset_pins & (1 << i)) != 0) { + continue; + } + reset_pin_number(i); + } +} + +void never_reset_pin_number(uint8_t pin_number) { + if (pin_number >= TOTAL_GPIO_COUNT) { + return; + } + + never_reset_pins |= 1 << pin_number; +} + +void reset_pin_number(uint8_t pin_number) { + if (pin_number >= TOTAL_GPIO_COUNT) { + return; + } + + never_reset_pins &= ~(1 << pin_number); + + // We are very aggressive in shutting down the pad fully. Both pulls are + // disabled and both buffers are as well. + gpio_init(pin_number); + hw_clear_bits(&padsbank0_hw->io[pin_number], PADS_BANK0_GPIO0_IE_BITS | + PADS_BANK0_GPIO0_PUE_BITS | + PADS_BANK0_GPIO0_PDE_BITS); + hw_set_bits(&padsbank0_hw->io[pin_number], PADS_BANK0_GPIO0_OD_BITS); +} + +void common_hal_never_reset_pin(const mcu_pin_obj_t *pin) { + never_reset_pin_number(pin->number); +} + +void common_hal_reset_pin(const mcu_pin_obj_t *pin) { + reset_pin_number(pin->number); +} + +void claim_pin(const mcu_pin_obj_t *pin) { + // Nothing to do because all changes will set the GPIO settings. +} + +bool pin_number_is_free(uint8_t pin_number) { + if (pin_number >= TOTAL_GPIO_COUNT) { + return false; + } + + uint32_t pad_state = padsbank0_hw->io[pin_number]; + return (pad_state & PADS_BANK0_GPIO0_IE_BITS) == 0 && + (pad_state & PADS_BANK0_GPIO0_OD_BITS) != 0; +} + +bool common_hal_mcu_pin_is_free(const mcu_pin_obj_t *pin) { + return pin_number_is_free(pin->number); +} + +uint8_t common_hal_mcu_pin_number(const mcu_pin_obj_t *pin) { + return pin->number; +} + +void common_hal_mcu_pin_claim(const mcu_pin_obj_t *pin) { + return claim_pin(pin); +} + +void common_hal_mcu_pin_reset_number(uint8_t pin_no) { + reset_pin_number(pin_no); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/microcontroller/Pin.h b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Pin.h new file mode 100644 index 0000000..3e2287d --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Pin.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER_PIN_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER_PIN_H + +#include <assert.h> +#include <stdint.h> + +#include <py/obj.h> + +#include "peripherals/pins.h" + +void reset_all_pins(void); +// reset_pin_number takes the pin number instead of the pointer so that objects don't +// need to store a full pointer. +void reset_pin_number(uint8_t pin_number); +void never_reset_pin_number(uint8_t pin_number); +void claim_pin(const mcu_pin_obj_t *pin); +bool pin_number_is_free(uint8_t pin_number); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER_PIN_H diff --git a/circuitpython/ports/raspberrypi/common-hal/microcontroller/Processor.c b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Processor.c new file mode 100644 index 0000000..8c5bc7b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Processor.c @@ -0,0 +1,99 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 <math.h> +#include <string.h> + +#include "py/mphal.h" +#include "common-hal/microcontroller/Processor.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "shared-bindings/microcontroller/ResetReason.h" + +#include "src/rp2_common/hardware_adc/include/hardware/adc.h" +#include "src/rp2_common/hardware_clocks/include/hardware/clocks.h" + +#include "src/rp2040/hardware_regs/include/hardware/regs/vreg_and_chip_reset.h" +#include "src/rp2040/hardware_regs/include/hardware/regs/watchdog.h" +#include "src/rp2040/hardware_structs/include/hardware/structs/vreg_and_chip_reset.h" +#include "src/rp2040/hardware_structs/include/hardware/structs/watchdog.h" + +float common_hal_mcu_processor_get_temperature(void) { + adc_init(); + adc_set_temp_sensor_enabled(true); + adc_select_input(4); + uint16_t value = adc_read(); + adc_set_temp_sensor_enabled(false); + float voltage = value * 3.3 / (1 << 12); + // TODO: turn the ADC back off + return 27 - (voltage - 0.706) / 0.001721; +} + +float common_hal_mcu_processor_get_voltage(void) { + return 3.3f; +} + +uint32_t common_hal_mcu_processor_get_frequency(void) { + return clock_get_hz(clk_sys); +} + +void common_hal_mcu_processor_get_uid(uint8_t raw_id[]) { + pico_unique_board_id_t retrieved_id; + pico_get_unique_board_id(&retrieved_id); + memcpy(raw_id, retrieved_id.id, COMMON_HAL_MCU_PROCESSOR_UID_LENGTH); +} + +mcu_reset_reason_t common_hal_mcu_processor_get_reset_reason(void) { + mcu_reset_reason_t reason = RESET_REASON_UNKNOWN; + + uint32_t watchdog_reset_reg = watchdog_hw->reason; + uint32_t chip_reset_reg = vreg_and_chip_reset_hw->chip_reset; + + if (chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_PSM_RESTART_BITS) { + reason = RESET_REASON_RESCUE_DEBUG; + } + + if (chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_RUN_BITS) { + reason = RESET_REASON_RESET_PIN; + } + + if (chip_reset_reg & VREG_AND_CHIP_RESET_CHIP_RESET_HAD_POR_BITS) { + // NOTE: This register is also used for brownout, but there is no way to differentiate between power on and brown out + reason = RESET_REASON_POWER_ON; + } + + // Check watchdog after chip reset since watchdog doesn't clear chip_reset, while chip_reset clears the watchdog + + if (watchdog_reset_reg & WATCHDOG_REASON_TIMER_BITS) { + // This bit can also be set during a software reset because the pico-sdk performs a software reset by setting an extremely low timeout on the watchdog, rather than triggering a watchdog reset manually + reason = RESET_REASON_WATCHDOG; + } + + if (watchdog_reset_reg & WATCHDOG_REASON_FORCE_BITS) { + reason = RESET_REASON_SOFTWARE; + } + + return reason; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/microcontroller/Processor.h b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Processor.h new file mode 100644 index 0000000..afb43f9 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/microcontroller/Processor.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER_PROCESSOR_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER_PROCESSOR_H + +#include "src/rp2_common/pico_unique_id/include/pico/unique_id.h" + +#define COMMON_HAL_MCU_PROCESSOR_UID_LENGTH PICO_UNIQUE_BOARD_ID_SIZE_BYTES + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + // Stores no state currently. +} mcu_processor_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER_PROCESSOR_H diff --git a/circuitpython/ports/raspberrypi/common-hal/microcontroller/__init__.c b/circuitpython/ports/raspberrypi/common-hal/microcontroller/__init__.c new file mode 100644 index 0000000..ed68835 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/microcontroller/__init__.c @@ -0,0 +1,184 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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/mphal.h" +#include "py/obj.h" +#include "py/runtime.h" + +#include "common-hal/microcontroller/__init__.h" +#if CIRCUITPY_NVM +#include "shared-bindings/nvm/ByteArray.h" +#endif +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "supervisor/filesystem.h" +#include "supervisor/port.h" +#include "supervisor/shared/safe_mode.h" +#include "supervisor/shared/translate.h" + +#include "src/rp2040/hardware_structs/include/hardware/structs/sio.h" +#include "src/rp2_common/hardware_sync/include/hardware/sync.h" + +#include "hardware/watchdog.h" + +void common_hal_mcu_delay_us(uint32_t delay) { + mp_hal_delay_us(delay); +} + +volatile uint32_t nesting_count = 0; +void common_hal_mcu_disable_interrupts(void) { + // We don't use save_and_disable_interrupts() from the sdk because we don't want to worry about PRIMASK. + // This is what we do on the SAMD21 via CMSIS. + asm volatile ("cpsid i" : : : "memory"); + __dmb(); + nesting_count++; +} + +void common_hal_mcu_enable_interrupts(void) { + if (nesting_count == 0) { + // reset_into_safe_mode(LOCKING_ERROR); + } + nesting_count--; + if (nesting_count > 0) { + return; + } + __dmb(); + asm volatile ("cpsie i" : : : "memory"); +} + +static bool next_reset_to_bootloader = false; + +void common_hal_mcu_on_next_reset(mcu_runmode_t runmode) { + switch (runmode) { + case RUNMODE_UF2: + case RUNMODE_BOOTLOADER: + next_reset_to_bootloader = true; + break; + case RUNMODE_SAFE_MODE: + safe_mode_on_next_reset(PROGRAMMATIC_SAFE_MODE); + break; + default: + break; + } +} + +void common_hal_mcu_reset(void) { + filesystem_flush(); + if (next_reset_to_bootloader) { + reset_to_bootloader(); + } else { + reset_cpu(); + } +} + +// The singleton microcontroller.Processor object, bound to microcontroller.cpu +// It currently only has properties, and no state. +#if CIRCUITPY_PROCESSOR_COUNT > 1 +static const mcu_processor_obj_t processor0 = { + .base = { + .type = &mcu_processor_type, + }, +}; + +static const mcu_processor_obj_t processor1 = { + .base = { + .type = &mcu_processor_type, + }, +}; + +const mp_rom_obj_tuple_t common_hal_multi_processor_obj = { + {&mp_type_tuple}, + CIRCUITPY_PROCESSOR_COUNT, + { + MP_ROM_PTR(&processor0), + MP_ROM_PTR(&processor1) + } +}; +#endif + +const mcu_processor_obj_t common_hal_mcu_processor_obj = { + .base = { + .type = &mcu_processor_type, + }, +}; + +#if CIRCUITPY_NVM && CIRCUITPY_INTERNAL_NVM_SIZE > 0 +// The singleton nvm.ByteArray object. +const nvm_bytearray_obj_t common_hal_mcu_nvm_obj = { + .base = { + .type = &nvm_bytearray_type, + }, + .len = CIRCUITPY_INTERNAL_NVM_SIZE, + .start_address = (uint8_t *)(CIRCUITPY_INTERNAL_NVM_START_ADDR) +}; +#endif + +#if CIRCUITPY_WATCHDOG +// The singleton watchdog.WatchDogTimer object. +watchdog_watchdogtimer_obj_t common_hal_mcu_watchdogtimer_obj = { + .base = { + .type = &watchdog_watchdogtimer_type, + }, + .timeout = 0.0f, + .mode = WATCHDOGMODE_NONE, +}; +#endif + +// This maps MCU pin names to pin objects. +const mp_rom_map_elem_t mcu_pin_global_dict_table[TOTAL_GPIO_COUNT] = { + { MP_ROM_QSTR(MP_QSTR_GPIO0), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_GPIO1), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_GPIO2), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_GPIO3), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_GPIO4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_GPIO5), MP_ROM_PTR(&pin_GPIO5) }, + { MP_ROM_QSTR(MP_QSTR_GPIO6), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_GPIO7), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_GPIO8), MP_ROM_PTR(&pin_GPIO8) }, + { MP_ROM_QSTR(MP_QSTR_GPIO9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_GPIO10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_GPIO11), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_GPIO12), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_GPIO13), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_GPIO14), MP_ROM_PTR(&pin_GPIO14) }, + { MP_ROM_QSTR(MP_QSTR_GPIO15), MP_ROM_PTR(&pin_GPIO15) }, + { MP_ROM_QSTR(MP_QSTR_GPIO16), MP_ROM_PTR(&pin_GPIO16) }, + { MP_ROM_QSTR(MP_QSTR_GPIO17), MP_ROM_PTR(&pin_GPIO17) }, + { MP_ROM_QSTR(MP_QSTR_GPIO18), MP_ROM_PTR(&pin_GPIO18) }, + { MP_ROM_QSTR(MP_QSTR_GPIO19), MP_ROM_PTR(&pin_GPIO19) }, + { MP_ROM_QSTR(MP_QSTR_GPIO20), MP_ROM_PTR(&pin_GPIO20) }, + { MP_ROM_QSTR(MP_QSTR_GPIO21), MP_ROM_PTR(&pin_GPIO21) }, + { MP_ROM_QSTR(MP_QSTR_GPIO22), MP_ROM_PTR(&pin_GPIO22) }, + { MP_ROM_QSTR(MP_QSTR_GPIO23), MP_ROM_PTR(&pin_GPIO23) }, + { MP_ROM_QSTR(MP_QSTR_GPIO24), MP_ROM_PTR(&pin_GPIO24) }, + { MP_ROM_QSTR(MP_QSTR_GPIO25), MP_ROM_PTR(&pin_GPIO25) }, + { MP_ROM_QSTR(MP_QSTR_GPIO26), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_GPIO27), MP_ROM_PTR(&pin_GPIO27) }, + { MP_ROM_QSTR(MP_QSTR_GPIO28), MP_ROM_PTR(&pin_GPIO28) }, + { MP_ROM_QSTR(MP_QSTR_GPIO29), MP_ROM_PTR(&pin_GPIO29) }, +}; +MP_DEFINE_CONST_DICT(mcu_pin_globals, mcu_pin_global_dict_table); diff --git a/circuitpython/ports/raspberrypi/common-hal/microcontroller/__init__.h b/circuitpython/ports/raspberrypi/common-hal/microcontroller/__init__.h new file mode 100644 index 0000000..cc509b6 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/microcontroller/__init__.h @@ -0,0 +1,36 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER___INIT___H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER___INIT___H + +#include "src/rp2040/hardware_regs/include/hardware/platform_defs.h" + +#define TOTAL_GPIO_COUNT NUM_BANK0_GPIOS + +extern const mp_rom_map_elem_t mcu_pin_global_dict_table[TOTAL_GPIO_COUNT]; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_MICROCONTROLLER___INIT___H diff --git a/circuitpython/ports/raspberrypi/common-hal/neopixel_write/__init__.c b/circuitpython/ports/raspberrypi/common-hal/neopixel_write/__init__.c new file mode 100644 index 0000000..d473285 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/neopixel_write/__init__.c @@ -0,0 +1,104 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 "shared-bindings/neopixel_write/__init__.h" + +#include "bindings/rp2pio/StateMachine.h" +#include "common-hal/rp2pio/StateMachine.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/digitalio/DigitalInOut.h" + +#include "supervisor/port.h" + +uint64_t next_start_raw_ticks = 0; + +// NeoPixels are 800khz bit streams. We are choosing zeros as <312ns hi, 936 lo> and ones +// and ones as <700 ns hi, 556 ns lo>. +// cycle. The first two instructions always run while only one of the two final +// instructions run per bit. We start with the low period because it can be +// longer while waiting for more data. +const uint16_t neopixel_program[] = { +// bitloop: +// out x 1 side 0 [6]; Drive low. Side-set still takes place before instruction stalls. + 0x6621, +// jmp !x do_zero side 1 [3]; Branch on the bit we shifted out previous delay. Drive high. + 0x1323, +// do_one: +// jmp bitloop side 1 [4]; Continue driving high, for a one (long pulse) + 0x1400, +// do_zero: +// nop side 0 [4]; Or drive low, for a zero (short pulse) + 0xa442 +}; + +void common_hal_neopixel_write(const digitalio_digitalinout_obj_t *digitalinout, uint8_t *pixels, uint32_t num_bytes) { + // Set everything up. + rp2pio_statemachine_obj_t state_machine; + + // TODO: Cache the state machine after we create it once. We'll need a way to + // change the pins then though. + uint32_t pins_we_use = 1 << digitalinout->pin->number; + bool ok = rp2pio_statemachine_construct(&state_machine, + neopixel_program, sizeof(neopixel_program) / sizeof(neopixel_program[0]), + 12800000, // 12.8MHz, to get appropriate sub-bit times in PIO program. + NULL, 0, // init program + NULL, 1, // out + NULL, 1, // in + 0, 0, // in pulls + NULL, 1, // set + digitalinout->pin, 1, // sideset + 0, pins_we_use, // initial pin state + NULL, // jump pin + pins_we_use, true, false, + true, 8, false, // TX, auto pull every 8 bits. shift left to output msb first + true, // Wait for txstall. If we don't, then we'll deinit too quickly. + false, 32, true, // RX setting we don't use + false, // claim pins + false, // Not user-interruptible. + false, // No sideset enable + 0, -1); // wrap + if (!ok) { + // Do nothing. Maybe bitbang? + return; + } + + // Wait to make sure we don't append onto the last transmission. This should only be a tick or + // two. + while (port_get_raw_ticks(NULL) < next_start_raw_ticks) { + } + + common_hal_rp2pio_statemachine_write(&state_machine, pixels, num_bytes, 1 /* stride in bytes */, false); + + // Use a private deinit of the state machine that doesn't reset the pin. + rp2pio_statemachine_deinit(&state_machine, true); + + // Reset the pin and release it from the PIO + gpio_init(digitalinout->pin->number); + common_hal_digitalio_digitalinout_switch_to_output((digitalio_digitalinout_obj_t *)digitalinout, false, DRIVE_MODE_PUSH_PULL); + + // Update the next start to +2 ticks. This ensures we give it at least 300us. + next_start_raw_ticks = port_get_raw_ticks(NULL) + 2; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/nvm/ByteArray.c b/circuitpython/ports/raspberrypi/common-hal/nvm/ByteArray.c new file mode 100644 index 0000000..cc52c88 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/nvm/ByteArray.c @@ -0,0 +1,121 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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 "common-hal/nvm/ByteArray.h" +#include "shared-bindings/nvm/ByteArray.h" + +#include <string.h> + +#include "py/runtime.h" +#include "src/rp2_common/hardware_flash/include/hardware/flash.h" +#include "shared-bindings/microcontroller/__init__.h" + +extern uint32_t __flash_binary_start; +static const uint32_t flash_binary_start = (uint32_t)&__flash_binary_start; + +#define RMV_OFFSET(addr) addr - flash_binary_start + +uint32_t common_hal_nvm_bytearray_get_length(const nvm_bytearray_obj_t *self) { + return self->len; +} + +static void write_page(uint32_t page_addr, uint32_t offset, uint32_t len, uint8_t *bytes) { + // Write a whole page to flash, buffering it first and then erasing and rewriting it + // since we can only write a whole page at a time. + if (offset == 0 && len == FLASH_PAGE_SIZE) { + // disable interrupts to prevent core hang on rp2040 + common_hal_mcu_disable_interrupts(); + flash_range_program(RMV_OFFSET(page_addr), bytes, FLASH_PAGE_SIZE); + common_hal_mcu_enable_interrupts(); + } else { + uint8_t buffer[FLASH_PAGE_SIZE]; + memcpy(buffer, (uint8_t *)page_addr, FLASH_PAGE_SIZE); + memcpy(buffer + offset, bytes, len); + common_hal_mcu_disable_interrupts(); + flash_range_program(RMV_OFFSET(page_addr), buffer, FLASH_PAGE_SIZE); + common_hal_mcu_enable_interrupts(); + } + +} + +static void erase_and_write_sector(uint32_t address, uint32_t len, uint8_t *bytes) { + // Write a whole sector to flash, buffering it first and then erasing and rewriting it + // since we can only erase a whole sector at a time. + uint8_t buffer[FLASH_SECTOR_SIZE]; + #pragma GCC diagnostic push + #if __GNUC__ >= 11 + // TODO: Update this to a better workaround for GCC 11 when one is provided. + // See: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=99578#c20 + #pragma GCC diagnostic ignored "-Warray-bounds" + #pragma GCC diagnostic ignored "-Wstringop-overread" + #endif + memcpy(buffer, (uint8_t *)CIRCUITPY_INTERNAL_NVM_START_ADDR, FLASH_SECTOR_SIZE); + #pragma GCC diagnostic pop + memcpy(buffer + address, bytes, len); + // disable interrupts to prevent core hang on rp2040 + common_hal_mcu_disable_interrupts(); + flash_range_erase(RMV_OFFSET(CIRCUITPY_INTERNAL_NVM_START_ADDR), FLASH_SECTOR_SIZE); + flash_range_program(RMV_OFFSET(CIRCUITPY_INTERNAL_NVM_START_ADDR), buffer, FLASH_SECTOR_SIZE); + common_hal_mcu_enable_interrupts(); +} + +void common_hal_nvm_bytearray_get_bytes(const nvm_bytearray_obj_t *self, + uint32_t start_index, uint32_t len, uint8_t *values) { + memcpy(values, self->start_address + start_index, len); +} + +bool common_hal_nvm_bytearray_set_bytes(const nvm_bytearray_obj_t *self, + uint32_t start_index, uint8_t *values, uint32_t len) { + uint8_t values_in[len]; + common_hal_nvm_bytearray_get_bytes(self, start_index, len, values_in); + + bool all_ones = true; + for (uint32_t i = 0; i < len; i++) { + if (values_in[i] != UINT8_MAX) { + all_ones = false; + break; + } + } + + if (all_ones) { + uint32_t address = (uint32_t)self->start_address + start_index; + uint32_t offset = address % FLASH_PAGE_SIZE; + uint32_t page_addr = address - offset; + + while (len) { + uint32_t write_len = MIN(len, FLASH_PAGE_SIZE - offset); + write_page(page_addr, offset, write_len, values); + len -= write_len; + values += write_len; + page_addr += FLASH_PAGE_SIZE; + offset = 0; + } + } else { + erase_and_write_sector(start_index, len, values); + } + + return true; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/nvm/ByteArray.h b/circuitpython/ports/raspberrypi/common-hal/nvm/ByteArray.h new file mode 100644 index 0000000..4667e6b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/nvm/ByteArray.h @@ -0,0 +1,38 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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_RASPBERRYPI_COMMON_HAL_NVM_BYTEARRAY_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_NVM_BYTEARRAY_H + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint8_t *start_address; + uint32_t len; +} nvm_bytearray_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_NVM_BYTEARRAY_H diff --git a/circuitpython/ports/raspberrypi/common-hal/nvm/__init__.c b/circuitpython/ports/raspberrypi/common-hal/nvm/__init__.c new file mode 100644 index 0000000..1b702a1 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/nvm/__init__.c @@ -0,0 +1 @@ +// No nvm module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/os/__init__.c b/circuitpython/ports/raspberrypi/common-hal/os/__init__.c new file mode 100644 index 0000000..8054182 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/os/__init__.c @@ -0,0 +1,127 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 "genhdr/mpversion.h" +#include "py/mpconfig.h" +#include "py/objstr.h" +#include "py/objtuple.h" +#include "py/qstr.h" + +#include "shared-bindings/os/__init__.h" + +#include "lib/crypto-algorithms/sha256.h" + +#include "hardware/structs/rosc.h" + +#include <string.h> + +STATIC const qstr os_uname_info_fields[] = { + MP_QSTR_sysname, MP_QSTR_nodename, + MP_QSTR_release, MP_QSTR_version, MP_QSTR_machine +}; +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_sysname_obj, "rp2040"); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_nodename_obj, "rp2040"); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_release_obj, MICROPY_VERSION_STRING); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_version_obj, MICROPY_GIT_TAG " on " MICROPY_BUILD_DATE); +STATIC const MP_DEFINE_STR_OBJ(os_uname_info_machine_obj, MICROPY_HW_BOARD_NAME " with " MICROPY_HW_MCU_NAME); + + +STATIC MP_DEFINE_ATTRTUPLE( + os_uname_info_obj, + os_uname_info_fields, + 5, + (mp_obj_t)&os_uname_info_sysname_obj, + (mp_obj_t)&os_uname_info_nodename_obj, + (mp_obj_t)&os_uname_info_release_obj, + (mp_obj_t)&os_uname_info_version_obj, + (mp_obj_t)&os_uname_info_machine_obj + ); + +mp_obj_t common_hal_os_uname(void) { + return (mp_obj_t)&os_uname_info_obj; +} + +// NIST Special Publication 800-90B (draft) recommends several extractors, +// including the SHA hash family and states that if the amount of entropy input +// is twice the number of bits output from them, that output can be considered +// essentially fully random. If every RANDOM_SAFETY_MARGIN bits from +// `rosc_hw->randombit` have at least 1 bit of entropy, then this criterion is met. +// +// This works by seeding the `random_state` with plenty of random bits (SHA256 +// as entropy harvesting function), then using that state it as a counter input +// (SHA256 as a CSPRNG), re-seeding at least every 256 blocks (8kB). +// +// In practice, `PractRand` doesn't detect any gross problems with the output +// random numbers on samples of 1 to 8 megabytes, no matter the setting of +// RANDOM_SAFETY_MARGIN. (it does detect "unusual" results from time to time, +// as it will with any RNG) +#define RANDOM_SAFETY_MARGIN (4) + +static BYTE random_state[SHA256_BLOCK_SIZE]; +static void seed_random_bits(BYTE out[SHA256_BLOCK_SIZE]) { + CRYAL_SHA256_CTX context; + sha256_init(&context); + for (int i = 0; i < 2 * RANDOM_SAFETY_MARGIN; i++) { + for (int j = 0; j < SHA256_BLOCK_SIZE; j++) { + out[j] = rosc_hw->randombit & 1; + for (int k = 0; k < 8; k++) { + out[j] = (out[j] << 1) ^ (rosc_hw->randombit & 1); + } + } + sha256_update(&context, out, SHA256_BLOCK_SIZE); + } + sha256_final(&context, out); +} + +static void get_random_bits(BYTE out[SHA256_BLOCK_SIZE]) { + if (!random_state[0]++) { + seed_random_bits(random_state); + } + CRYAL_SHA256_CTX context; + sha256_init(&context); + sha256_update(&context, random_state, SHA256_BLOCK_SIZE); + sha256_final(&context, out); +} + +bool common_hal_os_urandom(uint8_t *buffer, mp_uint_t length) { +#define ROSC_POWER_SAVE (1) // assume ROSC is not necessarily active all the time + #if ROSC_POWER_SAVE + uint32_t old_rosc_ctrl = rosc_hw->ctrl; + rosc_hw->ctrl = (old_rosc_ctrl & ~ROSC_CTRL_ENABLE_BITS) | (ROSC_CTRL_ENABLE_VALUE_ENABLE << 12); + #endif + while (length) { + size_t n = MIN(length, SHA256_BLOCK_SIZE); + BYTE sha_buf[SHA256_BLOCK_SIZE]; + get_random_bits(sha_buf); + memcpy(buffer, sha_buf, n); + buffer += n; + length -= n; + } + #if ROSC_POWER_SAVE + rosc_hw->ctrl = old_rosc_ctrl; + #endif + return true; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/paralleldisplay/ParallelBus.c b/circuitpython/ports/raspberrypi/common-hal/paralleldisplay/ParallelBus.c new file mode 100644 index 0000000..96c89ad --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/paralleldisplay/ParallelBus.c @@ -0,0 +1,170 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 "shared-bindings/paralleldisplay/ParallelBus.h" + +#include <stdint.h> + +#include "common-hal/microcontroller/Pin.h" +#include "py/runtime.h" +#include "shared-bindings/digitalio/DigitalInOut.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "bindings/rp2pio/StateMachine.h" +#include "common-hal/rp2pio/StateMachine.h" + +static const uint16_t parallel_program[] = { +// .side_set 1 +// .wrap_target + 0x6008, // out pins, 8 side 0 + 0xB042 // nop side 1 +// .wrap +}; + +void common_hal_paralleldisplay_parallelbus_construct(paralleldisplay_parallelbus_obj_t *self, + const mcu_pin_obj_t *data0, const mcu_pin_obj_t *command, const mcu_pin_obj_t *chip_select, + const mcu_pin_obj_t *write, const mcu_pin_obj_t *read, const mcu_pin_obj_t *reset, uint32_t frequency) { + + uint8_t data_pin = data0->number; + for (uint8_t i = 0; i < 8; i++) { + if (!pin_number_is_free(data_pin + i)) { + mp_raise_ValueError_varg(translate("Bus pin %d is already in use"), i); + } + } + + uint8_t write_pin = write->number; + if (!pin_number_is_free(write_pin)) { + mp_raise_ValueError_varg(translate("Bus pin %d is already in use"), write_pin); + } + + self->command.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&self->command, command); + common_hal_digitalio_digitalinout_switch_to_output(&self->command, true, DRIVE_MODE_PUSH_PULL); + + self->chip_select.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&self->chip_select, chip_select); + common_hal_digitalio_digitalinout_switch_to_output(&self->chip_select, true, DRIVE_MODE_PUSH_PULL); + + self->read.base.type = &mp_type_NoneType; + if (read != NULL) { + self->read.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&self->read, read); + common_hal_digitalio_digitalinout_switch_to_output(&self->read, true, DRIVE_MODE_PUSH_PULL); + never_reset_pin_number(read->number); + } + + self->data0_pin = data_pin; + self->write = write_pin; + + self->reset.base.type = &mp_type_NoneType; + if (reset != NULL) { + self->reset.base.type = &digitalio_digitalinout_type; + common_hal_digitalio_digitalinout_construct(&self->reset, reset); + common_hal_digitalio_digitalinout_switch_to_output(&self->reset, true, DRIVE_MODE_PUSH_PULL); + never_reset_pin_number(reset->number); + common_hal_paralleldisplay_parallelbus_reset(self); + } + + never_reset_pin_number(command->number); + never_reset_pin_number(chip_select->number); + never_reset_pin_number(write_pin); + for (uint8_t i = 0; i < 8; i++) { + never_reset_pin_number(data_pin + i); + } + + common_hal_rp2pio_statemachine_construct(&self->state_machine, + parallel_program, MP_ARRAY_SIZE(parallel_program), + frequency * 2, // frequency multiplied by 2 as 2 PIO instructions + NULL, 0, // init + data0, 8, 0, 255, // first out pin, # out pins + NULL, 0, 0, 0, // first in pin, # in pins + NULL, 0, 0, 0, // first set pin + write, 1, 0, 1, // first sideset pin + false, // No sideset enable + NULL, PULL_NONE, // jump pin + 0, // wait gpio pins + true, // exclusive pin usage + true, 8, true, // TX, auto pull every 8 bits. shift left to output msb first + false, // wait for TX stall + false, 32, true, // RX setting we don't use + false, // Not user-interruptible. + 0, -1); // wrap settings + + common_hal_rp2pio_statemachine_never_reset(&self->state_machine); +} + +void common_hal_paralleldisplay_parallelbus_deinit(paralleldisplay_parallelbus_obj_t *self) { + common_hal_rp2pio_statemachine_deinit(&self->state_machine); + + for (uint8_t i = 0; i < 8; i++) { + reset_pin_number(self->data0_pin + i); + } + + reset_pin_number(self->command.pin->number); + reset_pin_number(self->chip_select.pin->number); + reset_pin_number(self->write); + if (self->read.base.type != &mp_type_NoneType) { + reset_pin_number(self->read.pin->number); + } + if (self->reset.base.type != &mp_type_NoneType) { + reset_pin_number(self->reset.pin->number); + } +} + +bool common_hal_paralleldisplay_parallelbus_reset(mp_obj_t obj) { + paralleldisplay_parallelbus_obj_t *self = MP_OBJ_TO_PTR(obj); + if (self->reset.base.type == &mp_type_NoneType) { + return false; + } + + common_hal_digitalio_digitalinout_set_value(&self->reset, false); + common_hal_mcu_delay_us(4); + common_hal_digitalio_digitalinout_set_value(&self->reset, true); + return true; +} + +bool common_hal_paralleldisplay_parallelbus_bus_free(mp_obj_t obj) { + return true; +} + +bool common_hal_paralleldisplay_parallelbus_begin_transaction(mp_obj_t obj) { + paralleldisplay_parallelbus_obj_t *self = MP_OBJ_TO_PTR(obj); + common_hal_digitalio_digitalinout_set_value(&self->chip_select, false); + return true; +} + +void common_hal_paralleldisplay_parallelbus_send(mp_obj_t obj, display_byte_type_t byte_type, + display_chip_select_behavior_t chip_select, const uint8_t *data, uint32_t data_length) { + + paralleldisplay_parallelbus_obj_t *self = MP_OBJ_TO_PTR(obj); + + common_hal_digitalio_digitalinout_set_value(&self->command, byte_type == DISPLAY_DATA); + common_hal_rp2pio_statemachine_write(&self->state_machine, data, data_length, 1, false); +} + +void common_hal_paralleldisplay_parallelbus_end_transaction(mp_obj_t obj) { + paralleldisplay_parallelbus_obj_t *self = MP_OBJ_TO_PTR(obj); + common_hal_digitalio_digitalinout_set_value(&self->chip_select, true); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/paralleldisplay/ParallelBus.h b/circuitpython/ports/raspberrypi/common-hal/paralleldisplay/ParallelBus.h new file mode 100644 index 0000000..87f149b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/paralleldisplay/ParallelBus.h @@ -0,0 +1,45 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_PARALLELDISPLAY_PARALLELBUS_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_PARALLELDISPLAY_PARALLELBUS_H + +#include "common-hal/digitalio/DigitalInOut.h" +#include "bindings/rp2pio/StateMachine.h" +#include "common-hal/rp2pio/StateMachine.h" + +typedef struct { + mp_obj_base_t base; + digitalio_digitalinout_obj_t command; + digitalio_digitalinout_obj_t chip_select; + digitalio_digitalinout_obj_t reset; + digitalio_digitalinout_obj_t read; + uint8_t write; + uint8_t data0_pin; + rp2pio_statemachine_obj_t state_machine; +} paralleldisplay_parallelbus_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_PARALLELDISPLAY_PARALLELBUS_H diff --git a/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseIn.c b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseIn.c new file mode 100644 index 0000000..45d3162 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseIn.c @@ -0,0 +1,244 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Dave Putz for Adafruit Industries + * + * 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 "src/rp2_common/hardware_gpio/include/hardware/gpio.h" + +#include <stdint.h> + +#include "py/runtime.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/pulseio/PulseIn.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "supervisor/shared/translate.h" +#include "bindings/rp2pio/StateMachine.h" +#include "common-hal/pulseio/PulseIn.h" + +#define NO_PIN 0xff +#define MAX_PULSE 65535 +#define MIN_PULSE 10 + +uint16_t pulsein_program[] = { + 0x4001, // 1: in pins, 1 +}; + +void common_hal_pulseio_pulsein_construct(pulseio_pulsein_obj_t *self, + const mcu_pin_obj_t *pin, uint16_t maxlen, bool idle_state) { + + self->buffer = (uint16_t *)m_malloc(maxlen * sizeof(uint16_t), false); + if (self->buffer == NULL) { + mp_raise_msg_varg(&mp_type_MemoryError, translate("Failed to allocate RX buffer of %d bytes"), maxlen * sizeof(uint16_t)); + } + self->pin = pin->number; + self->maxlen = maxlen; + self->idle_state = idle_state; + self->start = 0; + self->len = 0; + + bool ok = rp2pio_statemachine_construct(&self->state_machine, + pulsein_program, sizeof(pulsein_program) / sizeof(pulsein_program[0]), + 1000000, + NULL, 0, + NULL, 0, + pin, 1, + 0,0, + NULL, 0, + NULL, 0, + 1, 0, + NULL, // jump pin + 1 << self->pin, false, true, + false, 8, false, // TX, unused + false, + true, 32, true, // RX auto-push every 32 bits + false, // claim pins + false, // Not user-interruptible. + false, // No sideset enable + 0, -1); // wrap settings + + if (!ok) { + mp_raise_RuntimeError(translate("All state machines in use")); + } + + pio_sm_set_enabled(self->state_machine.pio,self->state_machine.state_machine, false); + pio_sm_clear_fifos(self->state_machine.pio,self->state_machine.state_machine); + self->last_level = self->idle_state; + self->level_count = 0; + self->buf_index = 0; + + pio_sm_set_in_pins(self->state_machine.pio,self->state_machine.state_machine,pin->number); + common_hal_rp2pio_statemachine_set_interrupt_handler(&(self->state_machine),&common_hal_pulseio_pulsein_interrupt,self,PIO_IRQ0_INTE_SM0_RXNEMPTY_BITS); + + // exec a set pindirs to 0 for input + pio_sm_exec(self->state_machine.pio,self->state_machine.state_machine,0xe080); + // exec the appropriate wait for pin + if (self->idle_state == true) { + pio_sm_exec(self->state_machine.pio,self->state_machine.state_machine,0x2020); + } else { + pio_sm_exec(self->state_machine.pio,self->state_machine.state_machine,0x20a0); + } + pio_sm_set_enabled(self->state_machine.pio, self->state_machine.state_machine, true); +} + +bool common_hal_pulseio_pulsein_deinited(pulseio_pulsein_obj_t *self) { + return self->pin == NO_PIN; +} + +void common_hal_pulseio_pulsein_deinit(pulseio_pulsein_obj_t *self) { + if (common_hal_pulseio_pulsein_deinited(self)) { + return; + } + pio_sm_set_enabled(self->state_machine.pio, self->state_machine.state_machine, false); + common_hal_rp2pio_statemachine_deinit(&self->state_machine); + m_free(self->buffer); + reset_pin_number(self->pin); + self->pin = NO_PIN; +} + +void common_hal_pulseio_pulsein_pause(pulseio_pulsein_obj_t *self) { + pio_sm_restart(self->state_machine.pio, self->state_machine.state_machine); + pio_sm_set_enabled(self->state_machine.pio, self->state_machine.state_machine, false); + self->last_level = self->idle_state; + self->level_count = 0; + self->buf_index = 0; +} +void common_hal_pulseio_pulsein_interrupt(void *self_in) { + pulseio_pulsein_obj_t *self = self_in; + + uint32_t rxfifo = 0; + + rxfifo = pio_sm_get_blocking(self->state_machine.pio, self->state_machine.state_machine); + // translate from fifo to buffer + if ((rxfifo == 0 && self->last_level == false) || (rxfifo == 0xffffffff && self->last_level == true)) { + self->level_count = self->level_count + 32; + } else { + for (uint i = 0; i < 32; i++) { + bool level = (rxfifo & (1 << i)) >> i; + if (level == self->last_level) { + self->level_count++; + } else { + uint32_t result = self->level_count; + self->last_level = level; + self->level_count = 0; + // Pulses that are longer than MAX_PULSE will return MAX_PULSE + if (result > MAX_PULSE) { + result = MAX_PULSE; + } + // return pulses that are not too short + if (result > MIN_PULSE) { + size_t buf_index = (self->start + self->len) % self->maxlen; + self->buffer[buf_index] = (uint16_t)result; + if (self->len < self->maxlen) { + self->len++; + } else { + self->start = (self->start + 1) % self->maxlen; + } + if (self->buf_index < self->maxlen) { + self->buf_index++; + } else { + self->start = 0; + self->buf_index = 0; + } + } + } + } + } + +// check for a pulse thats too long (MAX_PULSE us) or maxlen reached, and reset + if ((self->level_count > MAX_PULSE) || (self->buf_index >= self->maxlen)) { + pio_sm_set_enabled(self->state_machine.pio, self->state_machine.state_machine, false); + pio_sm_init(self->state_machine.pio, self->state_machine.state_machine, self->state_machine.offset, &self->state_machine.sm_config); + pio_sm_restart(self->state_machine.pio,self->state_machine.state_machine); + pio_sm_set_enabled(self->state_machine.pio, self->state_machine.state_machine, true); + self->buf_index = 0; + } +} +void common_hal_pulseio_pulsein_resume(pulseio_pulsein_obj_t *self, + uint16_t trigger_duration) { + // Send the trigger pulse. + if (trigger_duration > 0) { + gpio_set_function(self->pin,GPIO_FUNC_SIO); + gpio_set_dir(self->pin,true); + gpio_put(self->pin, !self->idle_state); + common_hal_mcu_delay_us((uint32_t)trigger_duration); + gpio_set_function(self->pin,GPIO_FUNC_PIO0); + common_hal_mcu_delay_us(125); + } + + // Reconfigure the pin for PIO + gpio_set_function(self->pin, GPIO_FUNC_PIO0); + // exec a wait for the selected pin to change state + if (self->idle_state == true) { + pio_sm_exec(self->state_machine.pio,self->state_machine.state_machine,0x2020); + } else { + pio_sm_exec(self->state_machine.pio,self->state_machine.state_machine,0x20a0); + } + pio_sm_set_enabled(self->state_machine.pio, self->state_machine.state_machine, true); +} + +void common_hal_pulseio_pulsein_clear(pulseio_pulsein_obj_t *self) { + self->start = 0; + self->len = 0; + self->buf_index = 0; +} + +uint16_t common_hal_pulseio_pulsein_popleft(pulseio_pulsein_obj_t *self) { + if (self->len == 0) { + mp_raise_IndexError_varg(translate("pop from empty %q"), MP_QSTR_PulseIn); + } + uint16_t value = self->buffer[self->start]; + self->start = (self->start + 1) % self->maxlen; + self->len--; + // if we are empty reset buffer pointer and counters + if (self->len == 0) { + self->start = 0; + self->buf_index = 0; + self->level_count = 0; + } + return value; +} + +uint16_t common_hal_pulseio_pulsein_get_maxlen(pulseio_pulsein_obj_t *self) { + return self->maxlen; +} + +uint16_t common_hal_pulseio_pulsein_get_len(pulseio_pulsein_obj_t *self) { + return self->len; +} + +bool common_hal_pulseio_pulsein_get_paused(pulseio_pulsein_obj_t *self) { + return true; +} + +uint16_t common_hal_pulseio_pulsein_get_item(pulseio_pulsein_obj_t *self, + int16_t index) { + if (index < 0) { + index += self->len; + } + if (index < 0 || index >= self->len) { + mp_raise_IndexError_varg(translate("%q index out of range"), MP_QSTR_PulseIn); + } + uint16_t value = self->buffer[(self->start + index) % self->maxlen]; + return value; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseIn.h b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseIn.h new file mode 100644 index 0000000..f2c4fc0 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseIn.h @@ -0,0 +1,53 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Dave Putz for Adafruit Industries + * + * 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_ATMEL_SAMD_COMMON_HAL_PULSEIO_PULSEIN_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_PULSEIO_PULSEIN_H + +#include "common-hal/microcontroller/Pin.h" +#include "src/rp2_common/hardware_pio/include/hardware/pio.h" +#include "common-hal/rp2pio/StateMachine.h" + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + uint8_t pin; + bool idle_state; + uint16_t maxlen; + uint16_t *buffer; + volatile bool last_level; + volatile uint32_t level_count; + volatile uint16_t len; + volatile uint16_t start; + volatile uint16_t buf_index; + rp2pio_statemachine_obj_t state_machine; +} pulseio_pulsein_obj_t; + +void pulsein_reset(void); +void common_hal_pulseio_pulsein_interrupt(void *); + +#endif // MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_PULSEIO_PULSEIN_H diff --git a/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseOut.c b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseOut.c new file mode 100644 index 0000000..7704fdf --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseOut.c @@ -0,0 +1,134 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Dave Putz for Adafruit Industries + * + * 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 "common-hal/pulseio/PulseOut.h" + +#include <stdint.h> +#include "mpconfigport.h" +#include "py/runtime.h" +#include "shared-bindings/pulseio/PulseOut.h" +#include "shared-bindings/pwmio/PWMOut.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "common-hal/pwmio/PWMOut.h" +#include "supervisor/shared/translate.h" +#include "src/rp2040/hardware_structs/include/hardware/structs/pwm.h" +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" +#include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" +#include "src/common/pico_time/include/pico/time.h" + +volatile alarm_id_t cur_alarm = 0; + +static void pulse_finish(pulseio_pulseout_obj_t *self) { + self->pulse_index++; + // Turn pwm pin off by switching the GPIO mux to SIO (the cpu manual + // control). + if (self->pulse_index >= self->pulse_length) { + gpio_set_function(self->pin, GPIO_FUNC_SIO); + return; + } + if (self->pulse_index % 2 == 0) { + gpio_set_function(self->pin, GPIO_FUNC_PWM); + } else { + gpio_set_function(self->pin, GPIO_FUNC_SIO); + } + uint64_t delay = self->pulse_buffer[self->pulse_index]; + if (delay < self->min_pulse) { + delay = self->min_pulse; + } + cur_alarm = 0; + // if the alarm cannot be set, try again with a longer delay + while (cur_alarm == 0) { + cur_alarm = add_alarm_in_us(delay, pulseout_interrupt_handler, self, false); + delay = delay + 1; + } +} + +int64_t pulseout_interrupt_handler(alarm_id_t id, void *user_data) { + pulse_finish(user_data); + return 0; +} + +void pulseout_reset() { +} + +void common_hal_pulseio_pulseout_construct(pulseio_pulseout_obj_t *self, + const mcu_pin_obj_t *pin, + uint32_t frequency, + uint16_t duty_cycle) { + + pwmout_result_t result = common_hal_pwmio_pwmout_construct( + &self->carrier, pin, 0, frequency, false); + // This will raise an exception and not return if needed. + common_hal_pwmio_pwmout_raise_error(result); + + // Disable gpio output before we set the duty cycle. + gpio_put(pin->number, false); + gpio_set_dir(pin->number, GPIO_OUT); + gpio_set_function(pin->number, GPIO_FUNC_SIO); + common_hal_pwmio_pwmout_set_duty_cycle(&self->carrier, duty_cycle); + + self->pin = pin->number; + self->slice = self->carrier.slice; + self->min_pulse = (1000000 / self->carrier.actual_frequency); +} + +bool common_hal_pulseio_pulseout_deinited(pulseio_pulseout_obj_t *self) { + return self->pin == NO_PIN; +} + +void common_hal_pulseio_pulseout_deinit(pulseio_pulseout_obj_t *self) { + if (common_hal_pulseio_pulseout_deinited(self)) { + return; + } + gpio_set_dir(self->pin, GPIO_IN); + common_hal_pwmio_pwmout_deinit(&self->carrier); + self->pin = NO_PIN; +} + +void common_hal_pulseio_pulseout_send(pulseio_pulseout_obj_t *self, uint16_t *pulses, uint16_t length) { + self->pulse_buffer = pulses; + self->pulse_index = 0; + self->pulse_length = length; + + // Turn on the signal by connecting the PWM to the outside pin. + gpio_set_function(self->pin, GPIO_FUNC_PWM); + uint64_t delay = self->pulse_buffer[0]; + if (delay < self->min_pulse) { + delay = self->min_pulse; + } + cur_alarm = 0; + // if the alarm cannot be set, try again with a longer delay + while (cur_alarm == 0) { + cur_alarm = add_alarm_in_us(delay, pulseout_interrupt_handler, self, false); + delay = delay + 1; + } + + while (self->pulse_index < length) { + // Do other things while we wait. The interrupts will handle sending the + // signal. + RUN_BACKGROUND_TASKS; + } +} diff --git a/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseOut.h b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseOut.h new file mode 100644 index 0000000..3366946 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pulseio/PulseOut.h @@ -0,0 +1,52 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Dave Putz for Adafruit Industries + * + * 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_ATMEL_SAMD_COMMON_HAL_PULSEIO_PULSEOUT_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_PULSEIO_PULSEOUT_H + +#include "common-hal/microcontroller/Pin.h" +#include "common-hal/pwmio/PWMOut.h" +#include "src/common/pico_time/include/pico/time.h" + +#include "py/obj.h" + +#define NO_PIN 0xff + +typedef struct { + mp_obj_base_t base; + uint8_t pin; + uint8_t slice; + pwmio_pwmout_obj_t carrier; + uint16_t *pulse_buffer; + uint16_t pulse_length; + uint32_t min_pulse; + volatile uint16_t pulse_index; +} pulseio_pulseout_obj_t; + +void pulseout_reset(void); +int64_t pulseout_interrupt_handler(alarm_id_t id, void *user_data); + +#endif // MICROPY_INCLUDED_ATMEL SAMD_COMMON_HAL_PULSEIO_PULSEOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/pulseio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/pulseio/__init__.c new file mode 100644 index 0000000..2bee925 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pulseio/__init__.c @@ -0,0 +1 @@ +// No pulseio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/pwmio/PWMOut.c b/circuitpython/ports/raspberrypi/common-hal/pwmio/PWMOut.c new file mode 100644 index 0000000..3ef4fb5 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pwmio/PWMOut.c @@ -0,0 +1,307 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 <stdint.h> + +#include "shared/runtime/interrupt_char.h" +#include "py/runtime.h" +#include "common-hal/pwmio/PWMOut.h" +#include "shared-bindings/pwmio/PWMOut.h" +#include "shared-bindings/microcontroller/Processor.h" + +#include "supervisor/shared/translate.h" + +#include "src/rp2040/hardware_regs/include/hardware/platform_defs.h" +#include "src/rp2_common/hardware_clocks/include/hardware/clocks.h" +#include "src/rp2_common/hardware_gpio/include/hardware/gpio.h" +#include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" + +uint32_t target_slice_frequencies[NUM_PWM_SLICES]; +uint32_t slice_variable_frequency; + +#define AB_CHANNELS_PER_SLICE 2 +static uint32_t channel_use; +static uint32_t never_reset_channel; + +// Per the RP2040 datasheet: +// +// "A CC value of 0 will produce a 0% output, i.e. the output signal +// is always low. A CC value of TOP + 1 (i.e. equal to the period, in +// non-phase-correct mode) will produce a 100% output. For example, if +// TOP is programmed to 254, the counter will have a period of 255 +// cycles, and CC values in the range of 0 to 255 inclusive will +// produce duty cycles in the range 0% to 100% inclusive." +// +// So 65534 should be the maximum top value, and we'll set CC to be TOP+1 as appropriate. +#define MAX_TOP 65534 + +static uint32_t _mask(uint8_t slice, uint8_t ab_channel) { + return 1 << (slice * AB_CHANNELS_PER_SLICE + ab_channel); +} + +bool pwmio_claim_slice_ab_channels(uint8_t slice) { + uint32_t channel_use_mask_a = _mask(slice, 0); + uint32_t channel_use_mask_b = _mask(slice, 1); + + if ((channel_use & channel_use_mask_a) != 0) { + return false; + } + if ((channel_use & channel_use_mask_b) != 0) { + return false; + } + + channel_use |= channel_use_mask_a; + channel_use |= channel_use_mask_b; + return true; +} + +void pwmio_release_slice_ab_channels(uint8_t slice) { + uint32_t channel_mask = _mask(slice, 0); + channel_use &= ~channel_mask; + channel_mask = _mask(slice, 1); + channel_use &= ~channel_mask; +} + +void pwmout_never_reset(uint8_t slice, uint8_t ab_channel) { + never_reset_channel |= _mask(slice, ab_channel); +} + +void pwmout_reset_ok(uint8_t slice, uint8_t ab_channel) { + never_reset_channel &= ~_mask(slice, ab_channel); +} + +void common_hal_pwmio_pwmout_never_reset(pwmio_pwmout_obj_t *self) { + pwmout_never_reset(self->slice, self->ab_channel); + + never_reset_pin_number(self->pin->number); +} + +void common_hal_pwmio_pwmout_reset_ok(pwmio_pwmout_obj_t *self) { + pwmout_reset_ok(self->slice, self->ab_channel); +} + +void pwmout_reset(void) { + // Reset all slices + for (size_t slice = 0; slice < NUM_PWM_SLICES; slice++) { + bool reset = true; + for (size_t ab_channel = 0; ab_channel < AB_CHANNELS_PER_SLICE; ab_channel++) { + uint32_t channel_use_mask = _mask(slice, ab_channel); + if ((never_reset_channel & channel_use_mask) != 0) { + reset = false; + continue; + } + channel_use &= ~channel_use_mask; + } + if (!reset) { + continue; + } + pwm_set_enabled(slice, false); + target_slice_frequencies[slice] = 0; + slice_variable_frequency &= ~(1 << slice); + } +} + +pwmout_result_t pwmout_allocate(uint8_t slice, uint8_t ab_channel, bool variable_frequency, uint32_t frequency) { + uint32_t channel_use_mask = _mask(slice, ab_channel); + + // Check the channel first. + if ((channel_use & channel_use_mask) != 0) { + return PWMOUT_ALL_TIMERS_ON_PIN_IN_USE; + } + // Now check if the slice is in use and if we can share with it. + if (target_slice_frequencies[slice] > 0) { + // If we want to change frequency then we can't share. + if (variable_frequency) { + return PWMOUT_ALL_TIMERS_ON_PIN_IN_USE; + } + // If the other user wants a variable frequency then we can't share either. + if ((slice_variable_frequency & (1 << slice)) != 0) { + return PWMOUT_ALL_TIMERS_ON_PIN_IN_USE; + } + // If we're both fixed frequency but we don't match target frequencies then we can't share. + if (target_slice_frequencies[slice] != frequency) { + return PWMOUT_ALL_TIMERS_ON_PIN_IN_USE; + } + } + + channel_use |= channel_use_mask; + if (variable_frequency) { + slice_variable_frequency |= 1 << slice; + } + + return PWMOUT_OK; +} + +pwmout_result_t common_hal_pwmio_pwmout_construct(pwmio_pwmout_obj_t *self, + const mcu_pin_obj_t *pin, + uint16_t duty, + uint32_t frequency, + bool variable_frequency) { + self->pin = pin; + self->variable_frequency = variable_frequency; + self->duty_cycle = duty; + + claim_pin(pin); + + if (frequency == 0 || frequency > (common_hal_mcu_processor_get_frequency() / 2)) { + return PWMOUT_INVALID_FREQUENCY; + } + + uint8_t slice = pwm_gpio_to_slice_num(pin->number); + uint8_t ab_channel = pwm_gpio_to_channel(pin->number); + + int r = pwmout_allocate(slice, ab_channel, variable_frequency, frequency); + if (r != PWMOUT_OK) { + return r; + } + + self->slice = slice; + self->ab_channel = ab_channel; + + if (target_slice_frequencies[slice] != frequency) { + // Reset the counter and compare values. + pwm_hw->slice[slice].ctr = PWM_CH0_CTR_RESET; + common_hal_pwmio_pwmout_set_duty_cycle(self, duty); + common_hal_pwmio_pwmout_set_frequency(self, frequency); + pwm_set_enabled(slice, true); + } else { + common_hal_pwmio_pwmout_set_frequency(self, frequency); + common_hal_pwmio_pwmout_set_duty_cycle(self, duty); + } + + // Connect to the pad last to avoid any glitches from changing settings. + gpio_set_function(pin->number, GPIO_FUNC_PWM); + + return PWMOUT_OK; +} + +bool common_hal_pwmio_pwmout_deinited(pwmio_pwmout_obj_t *self) { + return self->pin == NULL; +} + +void pwmout_free(uint8_t slice, uint8_t ab_channel) { + uint32_t channel_mask = _mask(slice, ab_channel); + channel_use &= ~channel_mask; + never_reset_channel &= ~channel_mask; + uint32_t slice_mask = ((1 << AB_CHANNELS_PER_SLICE) - 1) << (slice * AB_CHANNELS_PER_SLICE); + if ((channel_use & slice_mask) == 0) { + target_slice_frequencies[slice] = 0; + slice_variable_frequency &= ~(1 << slice); + pwm_set_enabled(slice, false); + } +} + +void common_hal_pwmio_pwmout_deinit(pwmio_pwmout_obj_t *self) { + if (common_hal_pwmio_pwmout_deinited(self)) { + return; + } + pwmout_free(self->slice, self->ab_channel); + reset_pin_number(self->pin->number); + self->pin = NULL; +} + +extern void common_hal_pwmio_pwmout_set_duty_cycle(pwmio_pwmout_obj_t *self, uint16_t duty) { + self->duty_cycle = duty; + // Do arithmetic in 32 bits to prevent overflow. + uint16_t compare_count; + if (duty == 65535) { + // Ensure that 100% duty cycle is 100% full on and not rounded down, + // but do MIN() to keep value in range, just in case. + compare_count = MIN(UINT16_MAX, (uint32_t)self->top + 1); + } else { + compare_count = ((uint32_t)duty * self->top + MAX_TOP / 2) / MAX_TOP; + } + // compare_count is the CC register value, which should be TOP+1 for 100% duty cycle. + pwm_set_chan_level(self->slice, self->ab_channel, compare_count); + // Wait for wrap so that we know our new cc value has been applied. Clear + // the internal interrupt and then wait for it to be set. Worst case, we + // wait a full cycle. + pwm_hw->intr = 1 << self->slice; + while ((pwm_hw->en & (1 << self->slice)) != 0 && + (pwm_hw->intr & (1 << self->slice)) == 0 && + !mp_hal_is_interrupted()) { + } +} + +uint16_t common_hal_pwmio_pwmout_get_duty_cycle(pwmio_pwmout_obj_t *self) { + return self->duty_cycle; +} + +void pwmio_pwmout_set_top(pwmio_pwmout_obj_t *self, uint16_t top) { + self->actual_frequency = common_hal_mcu_processor_get_frequency() / top; + self->top = top; + pwm_set_clkdiv_int_frac(self->slice, 1, 0); + pwm_set_wrap(self->slice, self->top); +} + +void common_hal_pwmio_pwmout_set_frequency(pwmio_pwmout_obj_t *self, uint32_t frequency) { + if (frequency == 0 || frequency > (common_hal_mcu_processor_get_frequency() / 2)) { + mp_raise_ValueError(translate("Invalid PWM frequency")); + } + + target_slice_frequencies[self->slice] = frequency; + + // For low frequencies use the divider to give us full resolution duty_cycle. + if (frequency <= (common_hal_mcu_processor_get_frequency() / (1 << 16))) { + // Compute the divisor. It's an 8 bit integer and 4 bit fraction. Therefore, + // we compute everything * 16 for the fractional part. + // This is 1 << 12 because 4 bits are the * 16. + uint64_t frequency16 = ((uint64_t)clock_get_hz(clk_sys)) / (1 << 12); + uint64_t div16 = frequency16 / frequency; + // Round the divisor to try and get closest to the target frequency. We could + // also always round up and use TOP to get us closer. We may not need that though. + if (frequency16 % frequency >= frequency / 2) { + div16 += 1; + } + if (div16 >= (1 << 12)) { + div16 = (1 << 12) - 1; + } + self->actual_frequency = (frequency16 + (div16 / 2)) / div16; + self->top = MAX_TOP; + pwm_set_clkdiv_int_frac(self->slice, div16 / 16, div16 % 16); + pwm_set_wrap(self->slice, self->top); + } else { + uint32_t top = common_hal_mcu_processor_get_frequency() / frequency; + self->actual_frequency = common_hal_mcu_processor_get_frequency() / top; + self->top = MIN(MAX_TOP, top); + pwm_set_clkdiv_int_frac(self->slice, 1, 0); + // Set TOP register. For 100% duty cycle, CC must be set to TOP+1. + pwm_set_wrap(self->slice, self->top); + } + common_hal_pwmio_pwmout_set_duty_cycle(self, self->duty_cycle); +} + +uint32_t common_hal_pwmio_pwmout_get_frequency(pwmio_pwmout_obj_t *self) { + return self->actual_frequency; +} + +bool common_hal_pwmio_pwmout_get_variable_frequency(pwmio_pwmout_obj_t *self) { + return self->variable_frequency; +} + +const mcu_pin_obj_t *common_hal_pwmio_pwmout_get_pin(pwmio_pwmout_obj_t *self) { + return self->pin; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/pwmio/PWMOut.h b/circuitpython/ports/raspberrypi/common-hal/pwmio/PWMOut.h new file mode 100644 index 0000000..0d17993 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pwmio/PWMOut.h @@ -0,0 +1,58 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRY_PI_COMMON_HAL_PWMIO_PWMOUT_H +#define MICROPY_INCLUDED_RASPBERRY_PI_COMMON_HAL_PWMIO_PWMOUT_H + +#include "common-hal/microcontroller/Pin.h" + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + const mcu_pin_obj_t *pin; + uint8_t slice; // 0-7 + uint8_t ab_channel; // 0-1: A or B slice channel + bool variable_frequency; + uint16_t duty_cycle; + uint32_t actual_frequency; + uint16_t top; +} pwmio_pwmout_obj_t; + +void pwmout_reset(void); +// Private API for AudioPWMOut. +void pwmio_pwmout_set_top(pwmio_pwmout_obj_t *self, uint16_t top); +// Private APIs for RGBMatrix +enum pwmout_result_t pwmout_allocate(uint8_t slice, uint8_t ab_channel, bool variable_frequency, uint32_t frequency); +void pwmout_free(uint8_t slice, uint8_t ab_channel); +void pwmout_never_reset(uint8_t slice, uint8_t ab_channel); +void pwmout_reset_ok(uint8_t slice, uint8_t ab_channel); + +// Private API for countio to claim both ab_channels on a slice +bool pwmio_claim_slice_ab_channels(uint8_t slice); +void pwmio_release_slice_ab_channels(uint8_t slice); + +#endif // MICROPY_INCLUDED_RASPBERRY_PI_COMMON_HAL_PWMIO_PWMOUT_H diff --git a/circuitpython/ports/raspberrypi/common-hal/pwmio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/pwmio/__init__.c new file mode 100644 index 0000000..9e551a1 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/pwmio/__init__.c @@ -0,0 +1 @@ +// No pwmio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/RGBMatrix.c b/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/RGBMatrix.c new file mode 100644 index 0000000..d580b4c --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/RGBMatrix.c @@ -0,0 +1,75 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * 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 <stddef.h> + +#include "py/mphal.h" + +#include "common-hal/rgbmatrix/RGBMatrix.h" +#include "shared-bindings/pwmio/PWMOut.h" +#include "shared-module/rgbmatrix/RGBMatrix.h" + +#include "src/rp2_common/hardware_pwm/include/hardware/pwm.h" +#include "src/rp2_common/hardware_irq/include/hardware/irq.h" + +void *common_hal_rgbmatrix_timer_allocate(rgbmatrix_rgbmatrix_obj_t *self) { + // Choose a PWM channel based on the first RGB pin + uint8_t slice = pwm_gpio_to_slice_num(self->rgb_pins[0]); + uint8_t channel = pwm_gpio_to_channel(self->rgb_pins[0]); + int result = pwmout_allocate(slice, channel, true, 125000000 / 3); + if (result == PWMOUT_OK) { + // return value must be nonzero (but slice and channel can both be + // zero), so set bit 16... + pwmout_never_reset(slice, channel); + return (void *)(intptr_t)(slice | (channel << 8) | 0x10000); + } + return NULL; +} + +void common_hal_rgbmatrix_timer_enable(void *ptr) { + int8_t slice = ((intptr_t)ptr) & 0xff; + pwm_set_enabled(slice, false); + pwm_clear_irq(slice); + pwm_set_enabled(slice, true); +} + +void common_hal_rgbmatrix_timer_disable(void *ptr) { + int8_t slice = ((intptr_t)ptr) & 0xff; + pwm_set_enabled(slice, false); + irq_set_enabled(PWM_IRQ_WRAP, false); + pwm_clear_irq(slice); +} + +void common_hal_rgbmatrix_timer_free(void *ptr) { + intptr_t value = (intptr_t)ptr; + uint8_t slice = value & 0xff; + uint8_t channel = value >> 8; + pwm_set_enabled(slice, false); + irq_set_enabled(PWM_IRQ_WRAP, false); + pwm_clear_irq(slice); + pwmout_free(slice, channel); + return; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/RGBMatrix.h b/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/RGBMatrix.h new file mode 100644 index 0000000..d14cd9b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/RGBMatrix.h @@ -0,0 +1,37 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Jeff Epler for Adafruit Industries + * + * 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_ATMEL_SAMD_COMMON_HAL_RGBMATRIX_RGBMATRIX_H +#define MICROPY_INCLUDED_ATMEL_SAMD_COMMON_HAL_RGBMATRIX_RGBMATRIX_H + +#include "shared-module/rgbmatrix/RGBMatrix.h" + +void *common_hal_rgbmatrix_timer_allocate(rgbmatrix_rgbmatrix_obj_t *self); +void common_hal_rgbmatrix_timer_enable(void *); +void common_hal_rgbmatrix_timer_disable(void *); +void common_hal_rgbmatrix_timer_free(void *); + +#endif diff --git a/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/__init__.c b/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/__init__.c new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rgbmatrix/__init__.c diff --git a/circuitpython/ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c b/circuitpython/ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c new file mode 100644 index 0000000..0da12e5 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.c @@ -0,0 +1,135 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * 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 <hardware/regs/pio.h> +#include "common-hal/rotaryio/IncrementalEncoder.h" +#include "shared-bindings/rotaryio/IncrementalEncoder.h" +#include "shared-module/rotaryio/IncrementalEncoder.h" +#include "bindings/rp2pio/__init__.h" +#include "bindings/rp2pio/StateMachine.h" + +STATIC const uint16_t encoder[] = { + // again: + // in pins, 2 + 0x4002, + // mov x, isr + 0xa026, + // jmp x!=y, push_data + 0x00a5, + // mov isr, null + 0xa0c3, + // jmp again + 0x0000, + // push_data: + // push + 0x8020, + // mov y, x + 0xa041, +}; + +STATIC const uint16_t encoder_init[] = { + // set y, 31 + 0xe05f, +}; + +STATIC void incrementalencoder_interrupt_handler(void *self_in); + +void common_hal_rotaryio_incrementalencoder_construct(rotaryio_incrementalencoder_obj_t *self, + const mcu_pin_obj_t *pin_a, const mcu_pin_obj_t *pin_b) { + mp_obj_t pins[] = {MP_OBJ_FROM_PTR(pin_a), MP_OBJ_FROM_PTR(pin_b)}; + // Start out with swapped to match behavior with other ports. + self->swapped = true; + if (!common_hal_rp2pio_pins_are_sequential(2, pins)) { + pins[0] = MP_OBJ_FROM_PTR(pin_b); + pins[1] = MP_OBJ_FROM_PTR(pin_a); + self->swapped = false; + if (!common_hal_rp2pio_pins_are_sequential(2, pins)) { + mp_raise_RuntimeError(translate("Pins must be sequential GPIO pins")); + } + } + + self->position = 0; + self->sub_count = 0; + + common_hal_rp2pio_statemachine_construct(&self->state_machine, + encoder, MP_ARRAY_SIZE(encoder), + 1000000, + encoder_init, MP_ARRAY_SIZE(encoder_init), // init + NULL, 0, 0, 0, // out pin + pins[0], 2, // in pins + 3, 0, // in pulls + NULL, 0, 0, 0x1f, // set pins + NULL, 0, 0, 0x1f, // sideset pins + false, // No sideset enable + NULL, PULL_NONE, // jump pin + 0, // wait gpio pins + true, // exclusive pin use + false, 32, false, // out settings + false, // Wait for txstall + false, 32, false, // in settings + false, // Not user-interruptible. + 0, MP_ARRAY_SIZE(encoder) - 1 // wrap settings + ); + + // We're guaranteed by the init code that some output will be available promptly + uint8_t quiescent_state; + common_hal_rp2pio_statemachine_readinto(&self->state_machine, &quiescent_state, 1, 1, false); + + shared_module_softencoder_state_init(self, quiescent_state & 3); + common_hal_rp2pio_statemachine_set_interrupt_handler(&self->state_machine, incrementalencoder_interrupt_handler, self, PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS); +} + +bool common_hal_rotaryio_incrementalencoder_deinited(rotaryio_incrementalencoder_obj_t *self) { + return common_hal_rp2pio_statemachine_deinited(&self->state_machine); +} + +void common_hal_rotaryio_incrementalencoder_deinit(rotaryio_incrementalencoder_obj_t *self) { + if (common_hal_rotaryio_incrementalencoder_deinited(self)) { + return; + } + common_hal_rp2pio_statemachine_set_interrupt_handler(&self->state_machine, NULL, NULL, 0); + common_hal_rp2pio_statemachine_deinit(&self->state_machine); +} + +STATIC void incrementalencoder_interrupt_handler(void *self_in) { + rotaryio_incrementalencoder_obj_t *self = self_in; + + while (common_hal_rp2pio_statemachine_get_in_waiting(&self->state_machine)) { + // Bypass all the logic of StateMachine.c:_transfer, we need something + // very simple and fast for an interrupt! + uint8_t new_state = self->state_machine.pio->rxf[self->state_machine.state_machine]; + if (self->swapped) { + if (new_state == 0x1) { + new_state = 0x2; + } else if (new_state == 0x2) { + new_state = 0x1; + } + } + shared_module_softencoder_state_update(self, new_state); + } +} diff --git a/circuitpython/ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.h b/circuitpython/ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.h new file mode 100644 index 0000000..812ba09 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rotaryio/IncrementalEncoder.h @@ -0,0 +1,42 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Scott Shawcroft for Adafruit Industries + * + * 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. + */ + +#pragma once + +#include "common-hal/rp2pio/StateMachine.h" +#include "common-hal/microcontroller/Pin.h" + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + rp2pio_statemachine_obj_t state_machine; + uint8_t state; // <old A><old B> + int8_t sub_count; // count intermediate transitions between detents + int8_t divisor; // Number of quadrature edges required per count + bool swapped; // Did the pins need to be swapped to be sequential? + mp_int_t position; +} rotaryio_incrementalencoder_obj_t; diff --git a/circuitpython/ports/raspberrypi/common-hal/rotaryio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/rotaryio/__init__.c new file mode 100644 index 0000000..0aae79c --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rotaryio/__init__.c @@ -0,0 +1 @@ +// No rotaryio module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/rotaryio/__init__.h b/circuitpython/ports/raspberrypi/common-hal/rotaryio/__init__.h new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rotaryio/__init__.h diff --git a/circuitpython/ports/raspberrypi/common-hal/rp2pio/StateMachine.c b/circuitpython/ports/raspberrypi/common-hal/rp2pio/StateMachine.c new file mode 100644 index 0000000..bee7723 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rp2pio/StateMachine.c @@ -0,0 +1,1034 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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 <string.h> + +#include "bindings/rp2pio/StateMachine.h" + +#include "common-hal/microcontroller/__init__.h" +#include "shared-bindings/digitalio/Pull.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "src/rp2040/hardware_regs/include/hardware/platform_defs.h" +#include "src/rp2_common/hardware_clocks/include/hardware/clocks.h" +#include "src/rp2_common/hardware_dma/include/hardware/dma.h" +#include "src/rp2_common/hardware_pio/include/hardware/pio_instructions.h" +#include "src/rp2040/hardware_structs/include/hardware/structs/iobank0.h" +#include "src/rp2_common/hardware_irq/include/hardware/irq.h" + +#include "shared/runtime/interrupt_char.h" +#include "py/obj.h" +#include "py/objproperty.h" +#include "py/runtime.h" + +#define NO_DMA_CHANNEL (-1) + +// Count how many state machines are using each pin. +STATIC uint8_t _pin_reference_count[TOTAL_GPIO_COUNT]; +STATIC uint32_t _current_program_id[NUM_PIOS][NUM_PIO_STATE_MACHINES]; +STATIC uint8_t _current_program_offset[NUM_PIOS][NUM_PIO_STATE_MACHINES]; +STATIC uint8_t _current_program_len[NUM_PIOS][NUM_PIO_STATE_MACHINES]; +STATIC bool _never_reset[NUM_PIOS][NUM_PIO_STATE_MACHINES]; + +STATIC uint32_t _current_pins[NUM_PIOS]; +STATIC uint32_t _current_sm_pins[NUM_PIOS][NUM_PIO_STATE_MACHINES]; +STATIC int8_t _sm_dma_plus_one[NUM_PIOS][NUM_PIO_STATE_MACHINES]; + +#define SM_DMA_ALLOCATED(pio_index, sm) (_sm_dma_plus_one[(pio_index)][(sm)] != 0) +#define SM_DMA_GET_CHANNEL(pio_index, sm) (_sm_dma_plus_one[(pio_index)][(sm)] - 1) +#define SM_DMA_CLEAR_CHANNEL(pio_index, sm) (_sm_dma_plus_one[(pio_index)][(sm)] = 0) +#define SM_DMA_SET_CHANNEL(pio_isntance, sm, channel) (_sm_dma_plus_one[(pio_index)][(sm)] = (channel) + 1) + +STATIC PIO pio_instances[2] = {pio0, pio1}; +typedef void (*interrupt_handler_type)(void *); +STATIC interrupt_handler_type _interrupt_handler[NUM_PIOS][NUM_PIO_STATE_MACHINES]; +STATIC void *_interrupt_arg[NUM_PIOS][NUM_PIO_STATE_MACHINES]; + +STATIC void rp2pio_statemachine_interrupt_handler(void); + +static void rp2pio_statemachine_set_pull(uint32_t pull_pin_up, uint32_t pull_pin_down, uint32_t pins_we_use) { + for (int i = 0; i < TOTAL_GPIO_COUNT; i++) { + bool used = pins_we_use & (1 << i); + if (used) { + bool pull_up = pull_pin_up & (1 << i); + bool pull_down = pull_pin_down & (1 << i); + gpio_set_pulls(i, pull_up, pull_down); + } + } +} + +STATIC void rp2pio_statemachine_clear_dma(int pio_index, int sm) { + if (SM_DMA_ALLOCATED(pio_index, sm)) { + int channel = SM_DMA_GET_CHANNEL(pio_index, sm); + uint32_t channel_mask = 1u << channel; + dma_hw->inte0 &= ~channel_mask; + if (!dma_hw->inte0) { + irq_set_mask_enabled(1 << DMA_IRQ_0, false); + } + MP_STATE_PORT(background_pio)[channel] = NULL; + dma_channel_abort(channel); + dma_channel_unclaim(channel); + } + SM_DMA_CLEAR_CHANNEL(pio_index, sm); +} + +STATIC void _reset_statemachine(PIO pio, uint8_t sm, bool leave_pins) { + uint8_t pio_index = pio_get_index(pio); + rp2pio_statemachine_clear_dma(pio_index, sm); + uint32_t program_id = _current_program_id[pio_index][sm]; + if (program_id == 0) { + return; + } + _current_program_id[pio_index][sm] = 0; + bool program_in_use = false; + for (size_t i = 0; i < NUM_PIO_STATE_MACHINES; i++) { + if (_current_program_id[pio_index][i] == program_id) { + program_in_use = true; + break; + } + } + if (!program_in_use) { + uint8_t offset = _current_program_offset[pio_index][sm]; + pio_program_t program_struct = { + .length = _current_program_len[pio_index][sm] + }; + pio_remove_program(pio, &program_struct, offset); + } + + uint32_t pins = _current_sm_pins[pio_index][sm]; + for (size_t pin_number = 0; pin_number < TOTAL_GPIO_COUNT; pin_number++) { + if ((pins & (1 << pin_number)) == 0) { + continue; + } + _pin_reference_count[pin_number]--; + if (_pin_reference_count[pin_number] == 0) { + if (!leave_pins) { + reset_pin_number(pin_number); + } + _current_pins[pio_index] &= ~(1 << pin_number); + } + } + _current_sm_pins[pio_index][sm] = 0; + pio->inte0 &= ~((PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS | PIO_IRQ0_INTF_SM0_TXNFULL_BITS | PIO_IRQ0_INTF_SM0_BITS) << sm); + pio_sm_unclaim(pio, sm); +} + +void reset_rp2pio_statemachine(void) { + for (size_t i = 0; i < NUM_PIOS; i++) { + PIO pio = pio_instances[i]; + for (size_t j = 0; j < NUM_PIO_STATE_MACHINES; j++) { + if (_never_reset[i][j]) { + continue; + } + _reset_statemachine(pio, j, false); + } + } + for (uint8_t irq = PIO0_IRQ_0; irq <= PIO1_IRQ_1; irq++) { + irq_handler_t int_handler = irq_get_exclusive_handler(irq); + if (int_handler > 0) { + irq_set_enabled(irq, false); + irq_remove_handler(irq,int_handler); + } + } +} + +STATIC uint32_t _check_pins_free(const mcu_pin_obj_t *first_pin, uint8_t pin_count, bool exclusive_pin_use) { + uint32_t pins_we_use = 0; + if (first_pin != NULL) { + for (size_t i = 0; i < pin_count; i++) { + uint8_t pin_number = first_pin->number + i; + if (pin_number >= TOTAL_GPIO_COUNT) { + mp_raise_ValueError(translate("Pin count too large")); + } + const mcu_pin_obj_t *pin = mcu_pin_global_dict_table[pin_number].value; + if (exclusive_pin_use || _pin_reference_count[pin_number] == 0) { + assert_pin_free(pin); + } + pins_we_use |= 1 << pin_number; + } + } + return pins_we_use; +} + + +bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, + const uint16_t *program, size_t program_len, + size_t frequency, + const uint16_t *init, size_t init_len, + const mcu_pin_obj_t *first_out_pin, uint8_t out_pin_count, + const mcu_pin_obj_t *first_in_pin, uint8_t in_pin_count, + uint32_t pull_pin_up, uint32_t pull_pin_down, + const mcu_pin_obj_t *first_set_pin, uint8_t set_pin_count, + const mcu_pin_obj_t *first_sideset_pin, uint8_t sideset_pin_count, + uint32_t initial_pin_state, uint32_t initial_pin_direction, + const mcu_pin_obj_t *jmp_pin, + uint32_t pins_we_use, bool tx_fifo, bool rx_fifo, + bool auto_pull, uint8_t pull_threshold, bool out_shift_right, + bool wait_for_txstall, + bool auto_push, uint8_t push_threshold, bool in_shift_right, + bool claim_pins, + bool user_interruptible, + bool sideset_enable, + int wrap_target, int wrap + ) { + // Create a program id that isn't the pointer so we can store it without storing the original object. + uint32_t program_id = ~((uint32_t)program); + + // Next, find a PIO and state machine to use. + size_t pio_index = NUM_PIOS; + uint8_t program_offset = 32; + pio_program_t program_struct = { + .instructions = (uint16_t *)program, + .length = program_len, + .origin = -1 + }; + for (size_t i = 0; i < NUM_PIOS; i++) { + PIO pio = pio_instances[i]; + uint8_t free_count = 0; + for (size_t j = 0; j < NUM_PIO_STATE_MACHINES; j++) { + if (_current_program_id[i][j] == program_id && + _current_program_len[i][j] == program_len) { + program_offset = _current_program_offset[i][j]; + } + int temp_claim = pio_claim_unused_sm(pio, false); + if (temp_claim >= 0) { + pio_sm_unclaim(pio, temp_claim); + free_count++; + } + } + if (free_count > 0 && (program_offset < 32 || pio_can_add_program(pio, &program_struct))) { + pio_index = i; + if (program_offset < 32) { + break; + } + } + // Reset program offset if we weren't able to find a free state machine + // on that PIO. (We would have broken the loop otherwise.) + program_offset = 32; + } + + int state_machine = -1; + if (pio_index < NUM_PIOS) { + PIO pio = pio_instances[pio_index]; + for (size_t i = 0; i < NUM_PIOS; i++) { + if (i == pio_index) { + continue; + } + if ((_current_pins[i] & pins_we_use) != 0) { + // Pin in use by another PIO already. + return false; + } + } + state_machine = pio_claim_unused_sm(pio, false); + } + if (pio_index == NUM_PIOS || state_machine < 0 || state_machine >= NUM_PIO_STATE_MACHINES) { + return false; + } + + self->pio = pio_instances[pio_index]; + self->state_machine = state_machine; + if (program_offset == 32) { + program_offset = pio_add_program(self->pio, &program_struct); + } + self->offset = program_offset; + _current_program_id[pio_index][state_machine] = program_id; + _current_program_len[pio_index][state_machine] = program_len; + _current_program_offset[pio_index][state_machine] = program_offset; + _current_sm_pins[pio_index][state_machine] = pins_we_use; + _current_pins[pio_index] |= pins_we_use; + + pio_sm_set_pins_with_mask(self->pio, state_machine, initial_pin_state, pins_we_use); + pio_sm_set_pindirs_with_mask(self->pio, state_machine, initial_pin_direction, pins_we_use); + rp2pio_statemachine_set_pull(pull_pin_up, pull_pin_down, pins_we_use); + self->initial_pin_state = initial_pin_state; + self->initial_pin_direction = initial_pin_direction; + self->pull_pin_up = pull_pin_up; + self->pull_pin_down = pull_pin_down; + + for (size_t pin_number = 0; pin_number < TOTAL_GPIO_COUNT; pin_number++) { + if ((pins_we_use & (1 << pin_number)) == 0) { + continue; + } + _pin_reference_count[pin_number]++; + const mcu_pin_obj_t *pin = mcu_pin_global_dict_table[pin_number].value; + // Also claim the pin at the top level when we're the first to grab it. + if (_pin_reference_count[pin_number] == 1) { + if (claim_pins) { + claim_pin(pin); + } + pio_gpio_init(self->pio, pin_number); + } + } + + pio_sm_config c = {0, 0, 0}; + + if (frequency == 0) { + frequency = clock_get_hz(clk_sys); + } + uint64_t frequency256 = ((uint64_t)clock_get_hz(clk_sys)) * 256; + uint64_t div256 = frequency256 / frequency; + if (frequency256 % div256 > 0) { + div256 += 1; + } + self->actual_frequency = frequency256 / div256; + sm_config_set_clkdiv_int_frac(&c, div256 / 256, div256 % 256); + + if (first_out_pin != NULL) { + sm_config_set_out_pins(&c, first_out_pin->number, out_pin_count); + } + if (first_in_pin != NULL) { + sm_config_set_in_pins(&c, first_in_pin->number); + } + if (first_set_pin != NULL) { + sm_config_set_set_pins(&c, first_set_pin->number, set_pin_count); + } + if (first_sideset_pin != NULL) { + size_t total_sideset_bits = sideset_pin_count; + if (sideset_enable) { + total_sideset_bits += 1; + } + sm_config_set_sideset(&c, total_sideset_bits, sideset_enable, false /* pin direction */); + sm_config_set_sideset_pins(&c, first_sideset_pin->number); + } + if (jmp_pin != NULL) { + sm_config_set_jmp_pin(&c, jmp_pin->number); + } + + mp_arg_validate_int_range(wrap, -1, program_len - 1, MP_QSTR_wrap); + if (wrap == -1) { + wrap = program_len - 1; + } + + mp_arg_validate_int_range(wrap_target, 0, program_len - 1, MP_QSTR_wrap_target); + + wrap += program_offset; + wrap_target += program_offset; + + sm_config_set_wrap(&c, wrap_target, wrap); + sm_config_set_in_shift(&c, in_shift_right, auto_push, push_threshold); + sm_config_set_out_shift(&c, out_shift_right, auto_pull, pull_threshold); + + enum pio_fifo_join join = PIO_FIFO_JOIN_NONE; + if (!rx_fifo) { + join = PIO_FIFO_JOIN_TX; + } else if (!tx_fifo) { + join = PIO_FIFO_JOIN_RX; + } + if (rx_fifo) { + self->rx_dreq = pio_get_dreq(self->pio, self->state_machine, false); + } + if (tx_fifo) { + self->tx_dreq = pio_get_dreq(self->pio, self->state_machine, true); + } + self->in = rx_fifo; + self->out = tx_fifo; + self->out_shift_right = out_shift_right; + self->in_shift_right = in_shift_right; + self->wait_for_txstall = wait_for_txstall; + self->user_interruptible = user_interruptible; + + self->init = init; + self->init_len = init_len; + + sm_config_set_fifo_join(&c, join); + self->sm_config = c; + + // no DMA allocated + SM_DMA_CLEAR_CHANNEL(pio_index, state_machine); + + pio_sm_init(self->pio, self->state_machine, program_offset, &c); + common_hal_rp2pio_statemachine_run(self, init, init_len); + + common_hal_rp2pio_statemachine_set_frequency(self, frequency); + pio_sm_set_enabled(self->pio, self->state_machine, true); + return true; +} + +static uint32_t mask_and_rotate(const mcu_pin_obj_t *first_pin, uint32_t bit_count, uint32_t value) { + value = value & ((1 << bit_count) - 1); + uint32_t shift = first_pin->number; + return value << shift | value >> (32 - shift); +} + +void common_hal_rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, + const uint16_t *program, size_t program_len, + size_t frequency, + const uint16_t *init, size_t init_len, + const mcu_pin_obj_t *first_out_pin, uint8_t out_pin_count, uint32_t initial_out_pin_state, uint32_t initial_out_pin_direction, + const mcu_pin_obj_t *first_in_pin, uint8_t in_pin_count, + uint32_t pull_pin_up, uint32_t pull_pin_down, + const mcu_pin_obj_t *first_set_pin, uint8_t set_pin_count, uint32_t initial_set_pin_state, uint32_t initial_set_pin_direction, + const mcu_pin_obj_t *first_sideset_pin, uint8_t sideset_pin_count, uint32_t initial_sideset_pin_state, uint32_t initial_sideset_pin_direction, + bool sideset_enable, + const mcu_pin_obj_t *jmp_pin, digitalio_pull_t jmp_pull, + uint32_t wait_gpio_mask, + bool exclusive_pin_use, + bool auto_pull, uint8_t pull_threshold, bool out_shift_right, + bool wait_for_txstall, + bool auto_push, uint8_t push_threshold, bool in_shift_right, + bool user_interruptible, + int wrap_target, int wrap) { + + // First, check that all pins are free OR already in use by any PIO if exclusive_pin_use is false. + uint32_t pins_we_use = wait_gpio_mask; + pins_we_use |= _check_pins_free(first_out_pin, out_pin_count, exclusive_pin_use); + pins_we_use |= _check_pins_free(first_in_pin, in_pin_count, exclusive_pin_use); + pins_we_use |= _check_pins_free(first_set_pin, set_pin_count, exclusive_pin_use); + pins_we_use |= _check_pins_free(first_sideset_pin, sideset_pin_count, exclusive_pin_use); + pins_we_use |= _check_pins_free(jmp_pin, 1, exclusive_pin_use); + + // Look through the program to see what we reference and make sure it was provided. + bool tx_fifo = false; + bool rx_fifo = false; + bool in_loaded = false; // can be loaded in other ways besides the fifo + bool out_loaded = false; + bool in_used = false; + bool out_used = false; + for (size_t i = 0; i < program_len; i++) { + uint16_t full_instruction = program[i]; + uint16_t instruction = full_instruction & 0xe000; + if (instruction == 0x8000) { + if ((full_instruction & 0xe080) == pio_instr_bits_push) { + rx_fifo = true; + in_loaded = true; + } else { // pull otherwise. + tx_fifo = true; + out_loaded = true; + } + } + if (instruction == pio_instr_bits_jmp) { + uint16_t condition = (full_instruction & 0x00e0) >> 5; + if ((condition == 0x6) && (jmp_pin == NULL)) { + mp_raise_ValueError_varg(translate("Missing jmp_pin. Instruction %d jumps on pin"), i); + } + } + if (instruction == pio_instr_bits_wait) { + uint16_t wait_source = (full_instruction & 0x0060) >> 5; + uint16_t wait_index = full_instruction & 0x001f; + if (wait_source == 0 && (pins_we_use & (1 << wait_index)) == 0) { // GPIO + mp_raise_ValueError_varg(translate("Instruction %d uses extra pin"), i); + } + if (wait_source == 1) { // Input pin + if (first_in_pin == NULL) { + mp_raise_ValueError_varg(translate("Missing first_in_pin. Instruction %d waits based on pin"), i); + } + if (wait_index > in_pin_count) { + mp_raise_ValueError_varg(translate("Instruction %d waits on input outside of count"), i); + } + } + } + if (instruction == pio_instr_bits_in) { + uint16_t source = (full_instruction & 0x00e0) >> 5; + uint16_t bit_count = full_instruction & 0x001f; + if (source == 0) { + if (first_in_pin == NULL) { + mp_raise_ValueError_varg(translate("Missing first_in_pin. Instruction %d shifts in from pin(s)"), i); + } + if (bit_count > in_pin_count) { + mp_raise_ValueError_varg(translate("Instruction %d shifts in more bits than pin count"), i); + } + } + if (auto_push) { + in_loaded = true; + rx_fifo = true; + } + in_used = true; + } + if (instruction == pio_instr_bits_out) { + uint16_t bit_count = full_instruction & 0x001f; + uint16_t destination = (full_instruction & 0x00e0) >> 5; + // Check for pins or pindirs destination. + if (destination == 0x0 || destination == 0x4) { + if (first_out_pin == NULL) { + mp_raise_ValueError_varg(translate("Missing first_out_pin. Instruction %d shifts out to pin(s)"), i); + } + if (bit_count > out_pin_count) { + mp_raise_ValueError_varg(translate("Instruction %d shifts out more bits than pin count"), i); + } + } + if (auto_pull) { + out_loaded = true; + tx_fifo = true; + } + out_used = true; + } + if (instruction == pio_instr_bits_set) { + uint16_t destination = (full_instruction & 0x00e0) >> 5; + // Check for pins or pindirs destination. + if ((destination == 0x00 || destination == 0x4) && first_set_pin == NULL) { + mp_raise_ValueError_varg(translate("Missing first_set_pin. Instruction %d sets pin(s)"), i); + } + } + if (instruction == pio_instr_bits_mov) { + uint16_t source = full_instruction & 0x0007; + uint16_t destination = (full_instruction & 0x00e0) >> 5; + // Check for pins or pindirs destination. + if (destination == 0x0 && first_out_pin == NULL) { + mp_raise_ValueError_varg(translate("Missing first_out_pin. Instruction %d writes pin(s)"), i); + } + if (source == 0x0 && first_in_pin == NULL) { + mp_raise_ValueError_varg(translate("Missing first_in_pin. Instruction %d reads pin(s)"), i); + } + if (destination == 0x6) { + in_loaded = true; + } else if (destination == 0x7) { + out_loaded = true; + } + } + } + + if (!in_loaded && in_used) { + mp_raise_ValueError_varg(translate("Program does IN without loading ISR")); + } + if (!out_loaded && out_used) { + mp_raise_ValueError_varg(translate("Program does OUT without loading OSR")); + } + + uint32_t initial_pin_state = mask_and_rotate(first_out_pin, out_pin_count, initial_out_pin_state); + uint32_t initial_pin_direction = mask_and_rotate(first_out_pin, out_pin_count, initial_out_pin_direction); + initial_set_pin_state = mask_and_rotate(first_set_pin, set_pin_count, initial_set_pin_state); + initial_set_pin_direction = mask_and_rotate(first_set_pin, set_pin_count, initial_set_pin_direction); + uint32_t set_out_overlap = mask_and_rotate(first_out_pin, out_pin_count, 0xffffffff) & + mask_and_rotate(first_set_pin, set_pin_count, 0xffffffff); + // Check that OUT and SET settings agree because we don't have a way of picking one over the other. + if ((initial_pin_state & set_out_overlap) != (initial_set_pin_state & set_out_overlap)) { + mp_raise_ValueError(translate("Initial set pin state conflicts with initial out pin state")); + } + if ((initial_pin_direction & set_out_overlap) != (initial_set_pin_direction & set_out_overlap)) { + mp_raise_ValueError(translate("Initial set pin direction conflicts with initial out pin direction")); + } + initial_pin_state |= initial_set_pin_state; + initial_pin_direction |= initial_set_pin_direction; + + // Sideset overrides OUT or SET so we always use its values. + uint32_t sideset_mask = mask_and_rotate(first_sideset_pin, sideset_pin_count, 0x1f); + initial_pin_state = (initial_pin_state & ~sideset_mask) | mask_and_rotate(first_sideset_pin, sideset_pin_count, initial_sideset_pin_state); + initial_pin_direction = (initial_pin_direction & ~sideset_mask) | mask_and_rotate(first_sideset_pin, sideset_pin_count, initial_sideset_pin_direction); + + // Deal with pull up/downs + uint32_t pull_up = mask_and_rotate(first_in_pin, in_pin_count, pull_pin_up); + uint32_t pull_down = mask_and_rotate(first_in_pin, in_pin_count, pull_pin_down); + + if (jmp_pin) { + uint32_t jmp_mask = mask_and_rotate(jmp_pin, 1, 0x1f); + if (jmp_pull == PULL_UP) { + pull_up |= jmp_mask; + } + if (jmp_pull == PULL_DOWN) { + pull_up |= jmp_mask; + } + } + if (initial_pin_direction & (pull_up | pull_down)) { + mp_raise_ValueError(translate("pull masks conflict with direction masks")); + } + bool ok = rp2pio_statemachine_construct( + self, + program, program_len, + frequency, + init, init_len, + first_out_pin, out_pin_count, + first_in_pin, in_pin_count, + pull_up, pull_down, + first_set_pin, set_pin_count, + first_sideset_pin, sideset_pin_count, + initial_pin_state, initial_pin_direction, + jmp_pin, + pins_we_use, tx_fifo, rx_fifo, + auto_pull, pull_threshold, out_shift_right, + wait_for_txstall, + auto_push, push_threshold, in_shift_right, + true /* claim pins */, + user_interruptible, + sideset_enable, + wrap_target, wrap); + if (!ok) { + mp_raise_RuntimeError(translate("All state machines in use")); + } +} + +void common_hal_rp2pio_statemachine_restart(rp2pio_statemachine_obj_t *self) { + common_hal_rp2pio_statemachine_stop(self); + // Reset program counter to the original offset. A JMP is 0x0000 plus + // the desired offset, so we can just use self->offset. + pio_sm_exec(self->pio, self->state_machine, self->offset); + pio_sm_restart(self->pio, self->state_machine); + uint8_t pio_index = pio_get_index(self->pio); + uint32_t pins_we_use = _current_sm_pins[pio_index][self->state_machine]; + pio_sm_set_pins_with_mask(self->pio, self->state_machine, self->initial_pin_state, pins_we_use); + pio_sm_set_pindirs_with_mask(self->pio, self->state_machine, self->initial_pin_direction, pins_we_use); + rp2pio_statemachine_set_pull(self->pull_pin_up, self->pull_pin_down, pins_we_use); + common_hal_rp2pio_statemachine_run(self, self->init, self->init_len); + pio_sm_set_enabled(self->pio, self->state_machine, true); +} + +void common_hal_rp2pio_statemachine_stop(rp2pio_statemachine_obj_t *self) { + pio_sm_set_enabled(self->pio, self->state_machine, false); +} + +void common_hal_rp2pio_statemachine_run(rp2pio_statemachine_obj_t *self, const uint16_t *instructions, size_t len) { + for (size_t i = 0; i < len; i++) { + pio_sm_exec(self->pio, self->state_machine, instructions[i]); + } +} + +uint32_t common_hal_rp2pio_statemachine_get_frequency(rp2pio_statemachine_obj_t *self) { + return self->actual_frequency; +} + +void common_hal_rp2pio_statemachine_set_frequency(rp2pio_statemachine_obj_t *self, uint32_t frequency) { + if (frequency == 0) { + frequency = clock_get_hz(clk_sys); + } + uint64_t frequency256 = ((uint64_t)clock_get_hz(clk_sys)) * 256; + uint64_t div256 = frequency256 / frequency; + if (frequency256 % div256 > 0) { + div256 += 1; + } + // 0 is interpreted as 0x10000 so it's valid. + if (div256 / 256 > 0x10000 || frequency > clock_get_hz(clk_sys)) { + mp_raise_ValueError_varg(translate("%q out of range"), MP_QSTR_frequency); + } + self->actual_frequency = frequency256 / div256; + + pio_sm_set_clkdiv_int_frac(self->pio, self->state_machine, div256 / 256, div256 % 256); + // Reset the clkdiv counter in case our new TOP is lower. + pio_sm_clkdiv_restart(self->pio, self->state_machine); +} + +void rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self, bool leave_pins) { + common_hal_rp2pio_statemachine_stop(self); + (void)common_hal_rp2pio_statemachine_stop_background_write(self); + + uint8_t sm = self->state_machine; + uint8_t pio_index = pio_get_index(self->pio); + common_hal_mcu_disable_interrupts(); + _interrupt_arg[pio_index][sm] = NULL; + _interrupt_handler[pio_index][sm] = NULL; + common_hal_mcu_enable_interrupts(); + _never_reset[pio_index][sm] = false; + _reset_statemachine(self->pio, sm, leave_pins); + self->state_machine = NUM_PIO_STATE_MACHINES; +} + +void common_hal_rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self) { + rp2pio_statemachine_deinit(self, false); +} + +void common_hal_rp2pio_statemachine_never_reset(rp2pio_statemachine_obj_t *self) { + uint8_t sm = self->state_machine; + uint8_t pio_index = pio_get_index(self->pio); + _never_reset[pio_index][sm] = true; + // TODO: never reset all the pins +} + +bool common_hal_rp2pio_statemachine_deinited(rp2pio_statemachine_obj_t *self) { + return self->state_machine == NUM_PIO_STATE_MACHINES; +} + +STATIC enum dma_channel_transfer_size _stride_to_dma_size(uint8_t stride) { + switch (stride) { + case 4: + return DMA_SIZE_32; + case 2: + return DMA_SIZE_16; + case 1: + default: + return DMA_SIZE_8; + } +} + +static bool _transfer(rp2pio_statemachine_obj_t *self, + const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes, + uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes, bool swap_out, bool swap_in) { + // This implementation is based on SPI but varies because the tx and rx buffers + // may be different lengths and occur at different times or speeds. + + // Use DMA for large transfers if channels are available + const size_t dma_min_size_threshold = 32; + int chan_tx = -1; + int chan_rx = -1; + size_t len = MAX(out_len, in_len); + bool tx = data_out != NULL; + bool rx = data_in != NULL; + bool use_dma = len >= dma_min_size_threshold || swap_out || swap_in; + if (use_dma) { + // Use DMA channels to service the two FIFOs + if (tx) { + chan_tx = dma_claim_unused_channel(false); + // DMA allocation failed... + if (chan_tx < 0) { + return false; + } + } + if (rx) { + chan_rx = dma_claim_unused_channel(false); + // DMA allocation failed... + if (chan_rx < 0) { + // may need to free tx channel + if (chan_tx >= 0) { + dma_channel_unclaim(chan_tx); + } + return false; + } + } + } + volatile uint8_t *tx_destination = NULL; + const volatile uint8_t *rx_source = NULL; + if (tx) { + tx_destination = (volatile uint8_t *)&self->pio->txf[self->state_machine]; + if (!self->out_shift_right) { + tx_destination += 4 - out_stride_in_bytes; + } + } + if (rx) { + rx_source = (const volatile uint8_t *)&self->pio->rxf[self->state_machine]; + if (self->in_shift_right) { + rx_source += 4 - in_stride_in_bytes; + } + } + uint32_t stall_mask = 1 << (PIO_FDEBUG_TXSTALL_LSB + self->state_machine); + if (use_dma) { + dma_channel_config c; + uint32_t channel_mask = 0; + if (tx) { + c = dma_channel_get_default_config(chan_tx); + channel_config_set_transfer_data_size(&c, _stride_to_dma_size(out_stride_in_bytes)); + channel_config_set_dreq(&c, self->tx_dreq); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_bswap(&c, swap_out); + dma_channel_configure(chan_tx, &c, + tx_destination, + data_out, + out_len / out_stride_in_bytes, + false); + channel_mask |= 1u << chan_tx; + } + if (rx) { + c = dma_channel_get_default_config(chan_rx); + channel_config_set_transfer_data_size(&c, _stride_to_dma_size(in_stride_in_bytes)); + channel_config_set_dreq(&c, self->rx_dreq); + channel_config_set_read_increment(&c, false); + channel_config_set_write_increment(&c, true); + channel_config_set_bswap(&c, swap_in); + dma_channel_configure(chan_rx, &c, + data_in, + rx_source, + in_len / in_stride_in_bytes, + false); + channel_mask |= 1u << chan_rx; + } + + dma_start_channel_mask(channel_mask); + while ((rx && dma_channel_is_busy(chan_rx)) || + (tx && dma_channel_is_busy(chan_tx))) { + // TODO: We should idle here until we get a DMA interrupt or something else. + RUN_BACKGROUND_TASKS; + if (self->user_interruptible && mp_hal_is_interrupted()) { + if (rx && dma_channel_is_busy(chan_rx)) { + dma_channel_abort(chan_rx); + } + if (tx && dma_channel_is_busy(chan_tx)) { + dma_channel_abort(chan_tx); + } + break; + } + + } + // Clear the stall bit so we can detect when the state machine is done transmitting. + self->pio->fdebug = stall_mask; + } + + // If we have claimed only one channel successfully, we should release immediately. This also + // releases the DMA after use_dma has been done. + if (chan_rx >= 0) { + dma_channel_unclaim(chan_rx); + } + if (chan_tx >= 0) { + dma_channel_unclaim(chan_tx); + } + + if (!use_dma && !(self->user_interruptible && mp_hal_is_interrupted())) { + // Use software for small transfers, or if couldn't claim two DMA channels + size_t rx_remaining = in_len / in_stride_in_bytes; + size_t tx_remaining = out_len / out_stride_in_bytes; + + while (rx_remaining || tx_remaining) { + while (tx_remaining && !pio_sm_is_tx_fifo_full(self->pio, self->state_machine)) { + if (out_stride_in_bytes == 1) { + *tx_destination = *data_out; + } else if (out_stride_in_bytes == 2) { + *((uint16_t *)tx_destination) = *((uint16_t *)data_out); + } else if (out_stride_in_bytes == 4) { + *((uint32_t *)tx_destination) = *((uint32_t *)data_out); + } + data_out += out_stride_in_bytes; + --tx_remaining; + } + while (rx_remaining && !pio_sm_is_rx_fifo_empty(self->pio, self->state_machine)) { + if (in_stride_in_bytes == 1) { + *data_in = (uint8_t)*rx_source; + } else if (in_stride_in_bytes == 2) { + *((uint16_t *)data_in) = *((uint16_t *)rx_source); + } else if (in_stride_in_bytes == 4) { + *((uint32_t *)data_in) = *((uint32_t *)rx_source); + } + data_in += in_stride_in_bytes; + --rx_remaining; + } + RUN_BACKGROUND_TASKS; + if (self->user_interruptible && mp_hal_is_interrupted()) { + break; + } + } + // Clear the stall bit so we can detect when the state machine is done transmitting. + self->pio->fdebug = stall_mask; + } + // Wait for the state machine to finish transmitting the data we've queued + // up. + if (tx) { + while (!pio_sm_is_tx_fifo_empty(self->pio, self->state_machine) || + (self->wait_for_txstall && (self->pio->fdebug & stall_mask) == 0)) { + RUN_BACKGROUND_TASKS; + if (self->user_interruptible && mp_hal_is_interrupted()) { + break; + } + } + } + return true; +} + +// TODO: Provide a way around these checks in case someone wants to use the FIFO +// with manually run code. + +bool common_hal_rp2pio_statemachine_write(rp2pio_statemachine_obj_t *self, const uint8_t *data, size_t len, uint8_t stride_in_bytes, bool swap) { + if (!self->out) { + mp_raise_RuntimeError(translate("No out in program")); + } + return _transfer(self, data, len, stride_in_bytes, NULL, 0, 0, swap, false); +} + +bool common_hal_rp2pio_statemachine_readinto(rp2pio_statemachine_obj_t *self, uint8_t *data, size_t len, uint8_t stride_in_bytes, bool swap) { + if (!self->in) { + mp_raise_RuntimeError(translate("No in in program")); + } + return _transfer(self, NULL, 0, 0, data, len, stride_in_bytes, false, swap); +} + +bool common_hal_rp2pio_statemachine_write_readinto(rp2pio_statemachine_obj_t *self, + const uint8_t *data_out, size_t out_len, uint8_t out_stride_in_bytes, + uint8_t *data_in, size_t in_len, uint8_t in_stride_in_bytes, bool swap_out, bool swap_in) { + if (!self->in || !self->out) { + mp_raise_RuntimeError(translate("No in or out in program")); + } + return _transfer(self, data_out, out_len, out_stride_in_bytes, data_in, in_len, in_stride_in_bytes, swap_out, swap_in); +} + +bool common_hal_rp2pio_statemachine_get_rxstall(rp2pio_statemachine_obj_t *self) { + uint32_t stall_mask = 1 << (PIO_FDEBUG_RXSTALL_LSB + self->state_machine); + return (self->pio->fdebug & stall_mask) != 0; +} + +void common_hal_rp2pio_statemachine_clear_rxfifo(rp2pio_statemachine_obj_t *self) { + uint8_t level = pio_sm_get_rx_fifo_level(self->pio, self->state_machine); + uint32_t stall_mask = 1 << (PIO_FDEBUG_RXSTALL_LSB + self->state_machine); + for (size_t i = 0; i < level; i++) { + (void)self->pio->rxf[self->state_machine]; + } + self->pio->fdebug = stall_mask; +} + +bool common_hal_rp2pio_statemachine_get_txstall(rp2pio_statemachine_obj_t *self) { + uint32_t stall_mask = 1 << (PIO_FDEBUG_TXSTALL_LSB + self->state_machine); + return (self->pio->fdebug & stall_mask) != 0; +} + +void common_hal_rp2pio_statemachine_clear_txstall(rp2pio_statemachine_obj_t *self) { + uint8_t level = pio_sm_get_rx_fifo_level(self->pio, self->state_machine); + uint32_t stall_mask = 1 << (PIO_FDEBUG_TXSTALL_LSB + self->state_machine); + self->pio->fdebug = stall_mask; +} + + +size_t common_hal_rp2pio_statemachine_get_in_waiting(rp2pio_statemachine_obj_t *self) { + uint8_t level = pio_sm_get_rx_fifo_level(self->pio, self->state_machine); + return level; +} + +void common_hal_rp2pio_statemachine_set_interrupt_handler(rp2pio_statemachine_obj_t *self, void (*handler)(void *), void *arg, int mask) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + + common_hal_mcu_disable_interrupts(); + uint32_t inte = self->pio->inte0; + inte &= ~((PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS | PIO_IRQ0_INTF_SM0_TXNFULL_BITS | PIO_IRQ0_INTF_SM0_BITS) << sm); + inte |= (mask << sm); + self->pio->inte0 = inte; + _interrupt_arg[pio_index][sm] = arg; + _interrupt_handler[pio_index][sm] = handler; + irq_set_exclusive_handler(PIO0_IRQ_0 + 2 * pio_index, rp2pio_statemachine_interrupt_handler); + irq_set_enabled(PIO0_IRQ_0 + 2 * pio_index, true); + common_hal_mcu_enable_interrupts(); +} + +STATIC void rp2pio_statemachine_interrupt_handler(void) { + for (size_t pio_index = 0; pio_index < NUM_PIOS; pio_index++) { + PIO pio = pio_instances[pio_index]; + for (size_t sm = 0; sm < NUM_PIO_STATE_MACHINES; sm++) { + if (!_interrupt_handler[pio_index][sm]) { + continue; + } + uint32_t intf = (PIO_IRQ0_INTF_SM0_RXNEMPTY_BITS | PIO_IRQ0_INTF_SM0_TXNFULL_BITS | PIO_IRQ0_INTF_SM0_BITS) << sm; + if (pio->ints0 & intf) { + _interrupt_handler[pio_index][sm](_interrupt_arg[pio_index][sm]); + } + } + } +} + +uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + return _current_program_offset[pio_index][sm]; +} + +bool common_hal_rp2pio_statemachine_background_write(rp2pio_statemachine_obj_t *self, const sm_buf_info *once, const sm_buf_info *loop, uint8_t stride_in_bytes, bool swap) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + + int pending_buffers = (once->info.len != 0) + (loop->info.len != 0); + if (!once->info.len) { + once = loop; + } + + if (SM_DMA_ALLOCATED(pio_index, sm)) { + if (stride_in_bytes != self->background_stride_in_bytes) { + mp_raise_ValueError(translate("Mismatched data size")); + } + if (swap != self->byteswap) { + mp_raise_ValueError(translate("Mismatched swap flag")); + } + + while (self->pending_buffers) { + RUN_BACKGROUND_TASKS; + if (self->user_interruptible && mp_hal_is_interrupted()) { + return false; + } + } + + common_hal_mcu_disable_interrupts(); + self->once = *once; + self->loop = *loop; + self->pending_buffers = pending_buffers; + + if (self->dma_completed && self->once.info.len) { + rp2pio_statemachine_dma_complete(self, SM_DMA_GET_CHANNEL(pio_index, sm)); + self->dma_completed = false; + } + + common_hal_mcu_enable_interrupts(); + + return true; + } + + int channel = dma_claim_unused_channel(false); + if (channel == -1) { + return false; + } + + SM_DMA_SET_CHANNEL(pio_index, sm, channel); + + volatile uint8_t *tx_destination = (volatile uint8_t *)&self->pio->txf[self->state_machine]; + + self->tx_dreq = pio_get_dreq(self->pio, self->state_machine, true); + + dma_channel_config c; + + self->current = *once; + self->once = *loop; + self->loop = *loop; + self->pending_buffers = pending_buffers; + self->dma_completed = false; + self->background_stride_in_bytes = stride_in_bytes; + self->byteswap = swap; + + c = dma_channel_get_default_config(channel); + channel_config_set_transfer_data_size(&c, _stride_to_dma_size(stride_in_bytes)); + channel_config_set_dreq(&c, self->tx_dreq); + channel_config_set_read_increment(&c, true); + channel_config_set_write_increment(&c, false); + channel_config_set_bswap(&c, swap); + dma_channel_configure(channel, &c, + tx_destination, + once->info.buf, + once->info.len / stride_in_bytes, + false); + + common_hal_mcu_disable_interrupts(); + MP_STATE_PORT(background_pio)[channel] = self; + dma_hw->inte0 |= 1u << channel; + irq_set_mask_enabled(1 << DMA_IRQ_0, true); + dma_start_channel_mask(1u << channel); + common_hal_mcu_enable_interrupts(); + + return true; +} + +void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel) { + self->current = self->once; + self->once = self->loop; + + if (self->current.info.buf) { + if (self->pending_buffers > 0) { + self->pending_buffers--; + } + dma_channel_set_read_addr(channel, self->current.info.buf, false); + dma_channel_set_trans_count(channel, self->current.info.len / self->background_stride_in_bytes, true); + } else { + self->dma_completed = true; + self->pending_buffers = 0; // should be a no-op + } +} + +bool common_hal_rp2pio_statemachine_stop_background_write(rp2pio_statemachine_obj_t *self) { + uint8_t pio_index = pio_get_index(self->pio); + uint8_t sm = self->state_machine; + rp2pio_statemachine_clear_dma(pio_index, sm); + memset(&self->current, 0, sizeof(self->current)); + memset(&self->once, 0, sizeof(self->once)); + memset(&self->loop, 0, sizeof(self->loop)); + self->pending_buffers = 0; + return true; +} + +bool common_hal_rp2pio_statemachine_get_writing(rp2pio_statemachine_obj_t *self) { + return !self->dma_completed; +} + +int common_hal_rp2pio_statemachine_get_pending(rp2pio_statemachine_obj_t *self) { + return self->pending_buffers; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/rp2pio/StateMachine.h b/circuitpython/ports/raspberrypi/common-hal/rp2pio/StateMachine.h new file mode 100644 index 0000000..03dadc5 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rp2pio/StateMachine.h @@ -0,0 +1,100 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Scott Shawcroft for Adafruit Industries + * + * 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_RASPBERRYPI_COMMON_HAL_RP2PIO_STATEMACHINE_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_RP2PIO_STATEMACHINE_H + +#include "py/obj.h" + +#include "common-hal/microcontroller/Pin.h" +#include "src/rp2_common/hardware_pio/include/hardware/pio.h" + +typedef struct sm_buf_info { + mp_obj_t obj; + mp_buffer_info_t info; +} sm_buf_info; + +typedef struct { + mp_obj_base_t base; + uint32_t pins; // Bitmask of what pins this state machine uses. + int state_machine; + PIO pio; + const uint16_t *init; + size_t init_len; + uint32_t initial_pin_state; + uint32_t initial_pin_direction; + uint32_t pull_pin_up; + uint32_t pull_pin_down; + uint tx_dreq; + uint rx_dreq; + uint32_t actual_frequency; + pio_sm_config sm_config; + bool in; + bool out; + bool wait_for_txstall; + bool out_shift_right; + bool in_shift_right; + bool user_interruptible; + uint8_t offset; + + // dma-related items + volatile int pending_buffers; + sm_buf_info current, once, loop; + int background_stride_in_bytes; + bool dma_completed, byteswap; +} rp2pio_statemachine_obj_t; + +void reset_rp2pio_statemachine(void); + +// Minimal internal version that only fails on pin error (not in use) or full PIO. +bool rp2pio_statemachine_construct(rp2pio_statemachine_obj_t *self, + const uint16_t *program, size_t program_len, + size_t frequency, + const uint16_t *init, size_t init_len, + const mcu_pin_obj_t *first_out_pin, uint8_t out_pin_count, + const mcu_pin_obj_t *first_in_pin, uint8_t in_pin_count, + uint32_t pull_pin_up, uint32_t pull_pin_down, + const mcu_pin_obj_t *first_set_pin, uint8_t set_pin_count, + const mcu_pin_obj_t *first_sideset_pin, uint8_t sideset_pin_count, + uint32_t initial_pin_state, uint32_t initial_pin_direction, + const mcu_pin_obj_t *jmp_pin, + uint32_t pins_we_use, bool tx_fifo, bool rx_fifo, + bool auto_pull, uint8_t pull_threshold, bool out_shift_right, + bool wait_for_txstall, + bool auto_push, uint8_t push_threshold, bool in_shift_right, + bool claim_pins, + bool interruptible, + bool sideset_enable, + int wrap_target, int wrap); + +uint8_t rp2pio_statemachine_program_offset(rp2pio_statemachine_obj_t *self); + +void rp2pio_statemachine_deinit(rp2pio_statemachine_obj_t *self, bool leave_pins); +void rp2pio_statemachine_dma_complete(rp2pio_statemachine_obj_t *self, int channel); + +extern const mp_obj_type_t rp2pio_statemachine_type; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_RP2PIO_STATEMACHINE_H diff --git a/circuitpython/ports/raspberrypi/common-hal/rp2pio/__init__.c b/circuitpython/ports/raspberrypi/common-hal/rp2pio/__init__.c new file mode 100644 index 0000000..8b8101f --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rp2pio/__init__.c @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Jeff Epler for Adafruit Industries + * + * 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/obj.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "bindings/rp2pio/__init__.h" + +bool common_hal_rp2pio_pins_are_sequential(size_t len, mp_obj_t *items) { + if (len == 0) { + return true; + } + const mcu_pin_obj_t *last_pin = validate_obj_is_pin(items[0]); + for (int i = 1; i < len; i++) { + const mcu_pin_obj_t *pin = validate_obj_is_pin(items[i]); + if (pin->number != last_pin->number + 1) { + return false; + } + } + return true; +} diff --git a/circuitpython/ports/raspberrypi/common-hal/rtc/RTC.c b/circuitpython/ports/raspberrypi/common-hal/rtc/RTC.c new file mode 100644 index 0000000..9bd10ab --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rtc/RTC.c @@ -0,0 +1,96 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright 2020 microDev + * + * 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 "shared-bindings/rtc/RTC.h" +#include "common-hal/rtc/RTC.h" + +#include <sys/time.h> + +#include "py/runtime.h" +#include "src/rp2_common/hardware_rtc/include/hardware/rtc.h" +#include "src/rp2_common/hardware_clocks/include/hardware/clocks.h" + +void common_hal_rtc_init(void) { + datetime_t t = { + .year = 2020, + .month = 1, + .day = 1, + .dotw = 3, // 0 is Sunday, so 3 is Wednesday + .hour = 0, + .min = 0, + .sec = 0 + }; + + // Start the RTC + rtc_init(); + rtc_set_datetime(&t); + +} + +void common_hal_rtc_get_time(timeutils_struct_time_t *tm) { + datetime_t t; + rtc_get_datetime(&t); + + tm->tm_year = t.year; + tm->tm_mon = t.month; + tm->tm_mday = t.day; + tm->tm_wday = t.dotw; + tm->tm_hour = t.hour; + tm->tm_min = t.min; + tm->tm_sec = t.sec; + + if (tm->tm_wday == 0) { + tm->tm_wday = 6; + } else { + tm->tm_wday -= 1; + } +} + +void common_hal_rtc_set_time(timeutils_struct_time_t *tm) { + if (tm->tm_wday == 6) { + tm->tm_wday = 0; + } else { + tm->tm_wday += 1; + } + + datetime_t t = { + .year = tm->tm_year, + .month = tm->tm_mon, + .day = tm->tm_mday, + .dotw = tm->tm_wday, + .hour = tm->tm_hour, + .min = tm->tm_min, + .sec = tm->tm_sec + }; + rtc_set_datetime(&t); +} + +int common_hal_rtc_get_calibration(void) { + return 0; +} + +void common_hal_rtc_set_calibration(int calibration) { + mp_raise_NotImplementedError(translate("RTC calibration is not supported on this board")); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/rtc/RTC.h b/circuitpython/ports/raspberrypi/common-hal/rtc/RTC.h new file mode 100644 index 0000000..426a4ec --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rtc/RTC.h @@ -0,0 +1,32 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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_RASPBERRYPI_COMMON_HAL_RTC_RTC_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_RTC_RTC_H + +extern void common_hal_rtc_init(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_RTC_RTC_H diff --git a/circuitpython/ports/raspberrypi/common-hal/rtc/__init__.c b/circuitpython/ports/raspberrypi/common-hal/rtc/__init__.c new file mode 100644 index 0000000..f5e6b6b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/rtc/__init__.c @@ -0,0 +1 @@ +// No RTC module functions diff --git a/circuitpython/ports/raspberrypi/common-hal/supervisor/Runtime.c b/circuitpython/ports/raspberrypi/common-hal/supervisor/Runtime.c new file mode 100644 index 0000000..f827651 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/supervisor/Runtime.c @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Michael Schroeder + * + * 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 <stdbool.h> +#include "shared-bindings/supervisor/Runtime.h" +#include "supervisor/serial.h" + +bool common_hal_supervisor_runtime_get_serial_connected(void) { + return (bool)serial_connected(); +} + +bool common_hal_supervisor_runtime_get_serial_bytes_available(void) { + return (bool)serial_bytes_available(); +} diff --git a/circuitpython/ports/raspberrypi/common-hal/supervisor/Runtime.h b/circuitpython/ports/raspberrypi/common-hal/supervisor/Runtime.h new file mode 100755 index 0000000..45db489 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/supervisor/Runtime.h @@ -0,0 +1,37 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Michael Schroeder + * + * 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_RASPBERRYPI_COMMON_HAL_SUPERVISOR_RUNTIME_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_SUPERVISOR_RUNTIME_H + +#include "py/obj.h" + +typedef struct { + mp_obj_base_t base; + // Stores no state currently. +} super_runtime_obj_t; + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_SUPERVISOR_RUNTIME_H diff --git a/circuitpython/ports/raspberrypi/common-hal/supervisor/__init__.c b/circuitpython/ports/raspberrypi/common-hal/supervisor/__init__.c new file mode 100755 index 0000000..6dca35f --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/supervisor/__init__.c @@ -0,0 +1,40 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2018 Michael Schroeder + * + * 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/obj.h" + +#include "shared-bindings/supervisor/__init__.h" +#include "shared-bindings/supervisor/Runtime.h" + + +// The singleton supervisor.Runtime object, bound to supervisor.runtime +// It currently only has properties, and no state. +const super_runtime_obj_t common_hal_supervisor_runtime_obj = { + .base = { + .type = &supervisor_runtime_type, + }, +}; diff --git a/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogMode.c b/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogMode.c new file mode 100644 index 0000000..fc4e0e0 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogMode.c @@ -0,0 +1 @@ +// No watchdog module functions. diff --git a/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogTimer.c b/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogTimer.c new file mode 100644 index 0000000..f23ccda --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogTimer.c @@ -0,0 +1,83 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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 "common-hal/watchdog/WatchDogTimer.h" + +#include "shared-bindings/watchdog/__init__.h" +#include "shared-bindings/microcontroller/__init__.h" + +#include "src/rp2_common/hardware_watchdog/include/hardware/watchdog.h" + +void common_hal_watchdog_feed(watchdog_watchdogtimer_obj_t *self) { + watchdog_update(); +} + +void common_hal_watchdog_deinit(watchdog_watchdogtimer_obj_t *self) { + if (self->mode == WATCHDOGMODE_RESET) { + mp_raise_RuntimeError(translate("WatchDogTimer cannot be deinitialized once mode is set to RESET")); + } else { + self->mode = WATCHDOGMODE_NONE; + } +} + +/* +void watchdog_reset(void) { + common_hal_watchdog_deinit(&common_hal_mcu_watchdogtimer_obj); +} +*/ + +mp_float_t common_hal_watchdog_get_timeout(watchdog_watchdogtimer_obj_t *self) { + return self->timeout; +} + +void common_hal_watchdog_set_timeout(watchdog_watchdogtimer_obj_t *self, mp_float_t new_timeout) { + // max timeout is 8.388607 sec + // this is rounded down to 8.388 sec + uint64_t timeout = new_timeout * 1000; + if (timeout > 8388) { + mp_raise_ValueError(translate("timeout duration exceeded the maximum supported value")); + } + if ((uint16_t)self->timeout != timeout) { + watchdog_enable(timeout, false); + self->timeout = new_timeout; + } +} + +watchdog_watchdogmode_t common_hal_watchdog_get_mode(watchdog_watchdogtimer_obj_t *self) { + return self->mode; +} + +void common_hal_watchdog_set_mode(watchdog_watchdogtimer_obj_t *self, watchdog_watchdogmode_t new_mode) { + if (self->mode != new_mode) { + if (new_mode == WATCHDOGMODE_RAISE) { + mp_raise_NotImplementedError(translate("RAISE mode is not implemented")); + } else if (new_mode == WATCHDOGMODE_NONE) { + common_hal_watchdog_deinit(self); + } + self->mode = new_mode; + } +} diff --git a/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogTimer.h b/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogTimer.h new file mode 100644 index 0000000..ce34f0b --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/watchdog/WatchDogTimer.h @@ -0,0 +1,43 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 microDev + * + * 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_RASPBERRYPI_COMMON_HAL_WATCHDOG_WATCHDOGTIMER_H +#define MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_WATCHDOG_WATCHDOGTIMER_H + +#include "py/obj.h" +#include "shared-bindings/watchdog/WatchDogMode.h" +#include "shared-bindings/watchdog/WatchDogTimer.h" + +struct _watchdog_watchdogtimer_obj_t { + mp_obj_base_t base; + mp_float_t timeout; + watchdog_watchdogmode_t mode; +}; + +// This needs to be called in order to disable the watchdog +// void watchdog_reset(void); + +#endif // MICROPY_INCLUDED_RASPBERRYPI_COMMON_HAL_WATCHDOG_WATCHDOGTIMER_H diff --git a/circuitpython/ports/raspberrypi/common-hal/watchdog/__init__.c b/circuitpython/ports/raspberrypi/common-hal/watchdog/__init__.c new file mode 100644 index 0000000..fc4e0e0 --- /dev/null +++ b/circuitpython/ports/raspberrypi/common-hal/watchdog/__init__.c @@ -0,0 +1 @@ +// No watchdog module functions. |