diff --git a/locale/circuitpython.pot b/locale/circuitpython.pot index f261eb33d388d..054ab501d8e37 100644 --- a/locale/circuitpython.pot +++ b/locale/circuitpython.pot @@ -344,8 +344,8 @@ msgstr "" msgid "All SPI peripherals are in use" msgstr "" -#: ports/esp32s2/common-hal/busio/UART.c ports/nrf/common-hal/busio/UART.c -#: ports/raspberrypi/common-hal/busio/UART.c +#: ports/esp32s2/common-hal/busio/UART.c ports/nrf/common-hal/busio/JACDAC.c +#: ports/nrf/common-hal/busio/UART.c ports/raspberrypi/common-hal/busio/UART.c msgid "All UART peripherals are in use" msgstr "" @@ -353,6 +353,10 @@ msgstr "" msgid "All channels in use" msgstr "" +#: ports/esp32s2/common-hal/busio/JACDAC.c +msgid "All context->uart_hw peripherals are in use" +msgstr "" + #: ports/atmel-samd/common-hal/audioio/AudioOut.c msgid "All event channels in use" msgstr "" @@ -380,7 +384,8 @@ msgstr "" #: ports/esp32s2/common-hal/pulseio/PulseIn.c #: ports/esp32s2/common-hal/pulseio/PulseOut.c #: ports/nrf/common-hal/audiopwmio/PWMAudioOut.c -#: ports/nrf/common-hal/pulseio/PulseIn.c ports/nrf/peripherals/nrf/timers.c +#: ports/nrf/common-hal/busio/JACDAC.c ports/nrf/common-hal/pulseio/PulseIn.c +#: ports/nrf/peripherals/nrf/timers.c #: ports/raspberrypi/common-hal/audiopwmio/PWMAudioOut.c #: ports/stm/peripherals/timers.c shared-bindings/pwmio/PWMOut.c msgid "All timers in use" @@ -1248,8 +1253,10 @@ msgstr "" msgid "Invalid Pin" msgstr "" -#: ports/esp32s2/bindings/espidf/__init__.c ports/esp32s2/esp_error.c -#: py/moduerrno.c shared-module/rgbmatrix/RGBMatrix.c +#: ports/esp32s2/bindings/espidf/__init__.c +#: ports/esp32s2/common-hal/busio/JACDAC.c ports/esp32s2/esp_error.c +#: py/moduerrno.c shared-bindings/busio/JACDAC.c +#: shared-module/rgbmatrix/RGBMatrix.c msgid "Invalid argument" msgstr "" @@ -1388,6 +1395,10 @@ msgstr "" msgid "Invalid word/bit length" msgstr "" +#: shared-bindings/busio/JACDAC.c +msgid "Jacdac TX queue full" +msgstr "" + #: shared-bindings/aesio/aes.c msgid "Key must be 16, 24, or 32 bytes long" msgstr "" diff --git a/ports/esp32s2/background.c b/ports/esp32s2/background.c index 80b60bebe3505..16f8fecd2f8c7 100644 --- a/ports/esp32s2/background.c +++ b/ports/esp32s2/background.c @@ -39,6 +39,8 @@ #include "common-hal/pulseio/PulseIn.h" #endif +#include "shared-bindings/microcontroller/__init__.h" + void port_background_task(void) { // Zero delay in case FreeRTOS wants to switch to something else. @@ -46,6 +48,32 @@ void port_background_task(void) { #if CIRCUITPY_PULSEIO pulsein_background(); #endif + + if (codalLogStore.ptr) { + char *dmesgCopy = malloc(sizeof(codalLogStore)); + + uint32_t len; + + common_hal_mcu_disable_interrupts(); + len = codalLogStore.ptr; + memcpy(dmesgCopy, codalLogStore.buffer, len); + codalLogStore.ptr = 0; + codalLogStore.buffer[0] = 0; + common_hal_mcu_enable_interrupts(); + + if (len) { + if (dmesgCopy[len - 1] == '\n') { + len--; + } + dmesgCopy[len] = 0; + if (strchr(dmesgCopy, '\n')) { + ESP_LOGW("JD", "DMESG:\n%s", dmesgCopy); + } else { + ESP_LOGW("JD", "DMESG: %s", dmesgCopy); + } + } + free(dmesgCopy); + } } void port_start_background_task(void) { diff --git a/ports/esp32s2/boards/msr_jacdac_iot/board.c b/ports/esp32s2/boards/msr_jacdac_iot/board.c new file mode 100644 index 0000000000000..e40b6335bcb96 --- /dev/null +++ b/ports/esp32s2/boards/msr_jacdac_iot/board.c @@ -0,0 +1,52 @@ +/* + * 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 "supervisor/board.h" +#include "mpconfigboard.h" +#include "shared-bindings/microcontroller/Pin.h" + +void board_init(void) { + // USB + common_hal_never_reset_pin(&pin_GPIO19); + common_hal_never_reset_pin(&pin_GPIO20); + + // Debug UART + #ifdef DEBUG + common_hal_never_reset_pin(&pin_GPIO43); + common_hal_never_reset_pin(&pin_GPIO44); + #endif /* DEBUG */ +} + +bool board_requests_safe_mode(void) { + return false; +} + +void reset_board(void) { + +} + +void board_deinit(void) { +} diff --git a/ports/esp32s2/boards/msr_jacdac_iot/mpconfigboard.h b/ports/esp32s2/boards/msr_jacdac_iot/mpconfigboard.h new file mode 100644 index 0000000000000..6553df9854fce --- /dev/null +++ b/ports/esp32s2/boards/msr_jacdac_iot/mpconfigboard.h @@ -0,0 +1,41 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2019 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. + */ + +// Micropython setup + +#define MICROPY_HW_BOARD_NAME "Jacdac IoT 48" +#define MICROPY_HW_MCU_NAME "ESP32S2" + +#define CIRCUITPY_RGB_STATUS_R (&pin_GPIO8) +#define CIRCUITPY_RGB_STATUS_G (&pin_GPIO7) +#define CIRCUITPY_RGB_STATUS_B (&pin_GPIO6) +#define CIRCUITPY_RGB_STATUS_INVERTED_PWM 1 + +#define CIRCUITPY_BOOT_BUTTON (&pin_GPIO0) + +#define BOARD_USER_SAFE_MODE_ACTION translate("pressing boot button at start up.\n") + +#define AUTORESET_DELAY_MS 500 diff --git a/ports/esp32s2/boards/msr_jacdac_iot/mpconfigboard.mk b/ports/esp32s2/boards/msr_jacdac_iot/mpconfigboard.mk new file mode 100644 index 0000000000000..419ef983700b1 --- /dev/null +++ b/ports/esp32s2/boards/msr_jacdac_iot/mpconfigboard.mk @@ -0,0 +1,17 @@ +USB_VID = 0x239A +USB_PID = 0x80A8 # TODO +USB_PRODUCT = "Jacdac IoT 48" +USB_MANUFACTURER = "MSR" + +INTERNAL_FLASH_FILESYSTEM = 1 +LONGINT_IMPL = MPZ + +# The default queue depth of 16 overflows on release builds, +# so increase it to 32. +CFLAGS += -DCFG_TUD_TASK_QUEUE_SZ=32 + +CIRCUITPY_ESP_FLASH_MODE=dio +CIRCUITPY_ESP_FLASH_FREQ=40m +CIRCUITPY_ESP_FLASH_SIZE=4MB + +CIRCUITPY_MODULE=wroom diff --git a/ports/esp32s2/boards/msr_jacdac_iot/pins.c b/ports/esp32s2/boards/msr_jacdac_iot/pins.c new file mode 100644 index 0000000000000..e4ce71bda61ce --- /dev/null +++ b/ports/esp32s2/boards/msr_jacdac_iot/pins.c @@ -0,0 +1,39 @@ +#include "shared-bindings/board/__init__.h" + +STATIC const mp_rom_map_elem_t board_global_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_IO0), MP_ROM_PTR(&pin_GPIO0) }, + { MP_ROM_QSTR(MP_QSTR_ILIM_STATUS), MP_ROM_PTR(&pin_GPIO1) }, + { MP_ROM_QSTR(MP_QSTR_ILIM_EN), MP_ROM_PTR(&pin_GPIO2) }, + { MP_ROM_QSTR(MP_QSTR_ILIM_CTRL), MP_ROM_PTR(&pin_GPIO3) }, + { MP_ROM_QSTR(MP_QSTR_IO4), MP_ROM_PTR(&pin_GPIO4) }, + { MP_ROM_QSTR(MP_QSTR_IO5), MP_ROM_PTR(&pin_GPIO5) }, + /* used as status LED in CP - can't be defined here + { MP_ROM_QSTR(MP_QSTR_BLUE_LED), MP_ROM_PTR(&pin_GPIO6) }, + { MP_ROM_QSTR(MP_QSTR_GREEN_LED), MP_ROM_PTR(&pin_GPIO7) }, + { MP_ROM_QSTR(MP_QSTR_RED_LED), MP_ROM_PTR(&pin_GPIO8) }, + */ + { MP_ROM_QSTR(MP_QSTR_IO9), MP_ROM_PTR(&pin_GPIO9) }, + { MP_ROM_QSTR(MP_QSTR_IO10), MP_ROM_PTR(&pin_GPIO10) }, + { MP_ROM_QSTR(MP_QSTR_CC1), MP_ROM_PTR(&pin_GPIO11) }, + { MP_ROM_QSTR(MP_QSTR_CC2), MP_ROM_PTR(&pin_GPIO12) }, + { MP_ROM_QSTR(MP_QSTR_ILIM_FAULT), MP_ROM_PTR(&pin_GPIO13) }, + { MP_ROM_QSTR(MP_QSTR_IO14), MP_ROM_PTR(&pin_GPIO14) }, + + { MP_ROM_QSTR(MP_QSTR_IO26), MP_ROM_PTR(&pin_GPIO26) }, + { MP_ROM_QSTR(MP_QSTR_IO33), MP_ROM_PTR(&pin_GPIO33) }, + { MP_ROM_QSTR(MP_QSTR_IO34), MP_ROM_PTR(&pin_GPIO34) }, + { MP_ROM_QSTR(MP_QSTR_IO35), MP_ROM_PTR(&pin_GPIO35) }, + { MP_ROM_QSTR(MP_QSTR_IO36), MP_ROM_PTR(&pin_GPIO36) }, + { MP_ROM_QSTR(MP_QSTR_IO37), MP_ROM_PTR(&pin_GPIO37) }, + { MP_ROM_QSTR(MP_QSTR_IO38), MP_ROM_PTR(&pin_GPIO38) }, + + { MP_ROM_QSTR(MP_QSTR_IO45), MP_ROM_PTR(&pin_GPIO45) }, + { MP_ROM_QSTR(MP_QSTR_IO46), MP_ROM_PTR(&pin_GPIO46) }, + + { MP_ROM_QSTR(MP_QSTR_JACDAC), MP_ROM_PTR(&pin_GPIO17) }, + + { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_GPIO43) }, + { MP_ROM_QSTR(MP_QSTR_RX), MP_ROM_PTR(&pin_GPIO44) }, + +}; +MP_DEFINE_CONST_DICT(board_module_globals, board_global_dict_table); diff --git a/ports/esp32s2/boards/msr_jacdac_iot/sdkconfig b/ports/esp32s2/boards/msr_jacdac_iot/sdkconfig new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/ports/esp32s2/common-hal/busio/JACDAC.c b/ports/esp32s2/common-hal/busio/JACDAC.c new file mode 100644 index 0000000000000..287b26d62e3c9 --- /dev/null +++ b/ports/esp32s2/common-hal/busio/JACDAC.c @@ -0,0 +1,487 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 Microsoft Corp. + * + * 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/microcontroller/__init__.h" +#include "shared-bindings/busio/JACDAC.h" +#include "shared-bindings/microcontroller/Pin.h" + +#include "lib/utils/interrupt_char.h" +#include "py/mpconfig.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "supervisor/shared/translate.h" + +#include "common-hal/busio/JACDAC.h" +#include "common-hal/busio/UART.h" + +#include "driver/uart.h" +#include "driver/gpio.h" +#include "components/driver/include/driver/periph_ctrl.h" + +// #define PIN_LOG_0 GPIO_NUM_3 +// #define PIN_LOG_1 GPIO_NUM_4 + +#define LOG NOLOG +// #define LOG DMESG + +#define UART_EMPTY_THRESH_DEFAULT (10) +#define UART_FULL_THRESH_DEFAULT (120) +#define UART_TOUT_THRESH_DEFAULT (10) + +#define NUM_HW_ALLOC 2 + +static busio_jacdac_hw_alloc_t jd_hw_alloc[NUM_HW_ALLOC]; + +static IRAM_ATTR void uart_isr(busio_jacdac_obj_t *context); + +static void log_pin_pulse(int pinid, int numpulses) { + #ifdef PIN_LOG_0 + uint32_t mask = pinid == 0 ? 1 << PIN_LOG_0 : 1 << PIN_LOG_1; + while (numpulses--) { + GPIO.out_w1ts = mask; + GPIO.out_w1tc = mask; + } + #endif +} + +static void jd_timer(busio_jacdac_obj_t *context) { + busio_jacdac_base_callback_t f = context->timer_cb; + if (f) { + context->timer_cb = NULL; + f(context); + } +} + +static void jd_panic(void) { + abort(); +} + +#define CHK(e) \ + if ((e) != ESP_OK) \ + jd_panic() + + +void common_hal_busio_jacdac_construct(busio_jacdac_obj_t *context, const mcu_pin_obj_t *pin) { + if (!(1 <= pin->number && pin->number < 32)) { + // we do not support pins from out1_*, and GPIO0 doesn't sound like a good idea either + mp_raise_ValueError(translate("Invalid argument")); + } + + context->uart_num = UART_NUM_MAX; + for (uart_port_t num = 0; num < UART_NUM_MAX; num++) { + if (!uart_is_used(num)) { + context->uart_num = num; + } + } + if (context->uart_num == UART_NUM_MAX) { + mp_raise_ValueError(translate("All UART peripherals are in use")); + } + + DMESG("Jacdac on UART%d IO%d", context->uart_num, pin->number); + + context->uart_hw = UART_LL_GET_HW(context->uart_num); + + context->hw_alloc = NULL; + + for (int i = 0; i < NUM_HW_ALLOC; ++i) { + if (!jd_hw_alloc[i].timer) { + context->hw_alloc = &jd_hw_alloc[i]; + break; + } + } + + if (!context->hw_alloc) { + // this is very unlikely - we would first run out of UARTs + mp_raise_ValueError(translate("Invalid argument")); + } + + busio_jacdac_init(&context->base); + + esp_timer_create_args_t args; + args.callback = (esp_timer_cb_t)jd_timer; + args.arg = context; + args.dispatch_method = ESP_TIMER_TASK; + args.name = "JD timeout"; + esp_timer_create(&args, &context->hw_alloc->timer); + + claim_pin(pin); + context->pinobj = pin; + context->pin_num = pin->number; + + context->hw_alloc->pin_num = pin->number; + context->hw_alloc->uart_num = context->uart_num; + + uart_mark_used(context->uart_num, true); + + periph_module_enable(uart_periph_signal[context->uart_num].module); + + const uart_config_t uart_config = + {.baud_rate = 1000000, + .data_bits = UART_DATA_8_BITS, + .parity = UART_PARITY_DISABLE, + .stop_bits = UART_STOP_BITS_1, + .flow_ctrl = UART_HW_FLOWCTRL_DISABLE}; + CHK(uart_param_config(context->uart_num, &uart_config)); + CHK(uart_isr_register(context->uart_num, (void (*)(void *))uart_isr, context, 0, &context->hw_alloc->intr_handle)); + + uart_intr_config_t uart_intr = + {.intr_enable_mask = 0, + .rxfifo_full_thresh = UART_FULL_THRESH_DEFAULT, + .rx_timeout_thresh = 30, // 30us + .txfifo_empty_intr_thresh = UART_EMPTY_THRESH_DEFAULT}; + CHK(uart_intr_config(context->uart_num, &uart_intr)); + + gpio_config_t cfg = { + .pin_bit_mask = BIT64(context->pin_num), + .mode = GPIO_MODE_INPUT, + .pull_up_en = true, + .pull_down_en = false, + .intr_type = GPIO_INTR_DISABLE, + }; + gpio_config(&cfg); + + // gpio_set_pull_mode(context->pin_num, GPIO_PULLUP_ONLY); + // gpio_set_direction(context->pin_num, GPIO_MODE_INPUT); + + #ifdef PIN_LOG_0 + gpio_set_direction(PIN_LOG_0, GPIO_MODE_OUTPUT); + gpio_set_direction(PIN_LOG_1, GPIO_MODE_OUTPUT); + #endif + + common_hal_busio_jacdac_cancel(context); +} + +static void jd_deinit_hw_alloc(busio_jacdac_hw_alloc_t *hw_alloc) { + if (!hw_alloc->timer) { + return; + } + + uart_dev_t *uart_hw = UART_LL_GET_HW(hw_alloc->uart_num); + + esp_timer_stop(hw_alloc->timer); + esp_timer_delete(hw_alloc->timer); + hw_alloc->timer = NULL; + uart_mark_used(hw_alloc->uart_num, false); + + uart_hw->int_clr.val = 0xffffffff; + uart_hw->int_ena.val = 0; + + esp_intr_free(hw_alloc->intr_handle); + hw_alloc->intr_handle = NULL; + + periph_module_disable(uart_periph_signal[hw_alloc->uart_num].module); + common_hal_mcu_pin_reset_number(hw_alloc->pin_num); +} + +void jacdac_reset() { + DMESG("jacdac_reset"); + for (int i = 0; i < NUM_HW_ALLOC; ++i) { + jd_deinit_hw_alloc(&jd_hw_alloc[i]); + } +} + +void common_hal_busio_jacdac_deinit(busio_jacdac_obj_t *context) { + if (common_hal_busio_jacdac_deinited(context)) { + return; + } + + jd_deinit_hw_alloc(context->hw_alloc); + context->hw_alloc = NULL; + context->pinobj = NULL; + + busio_jacdac_deinit(&context->base); +} + +bool common_hal_busio_jacdac_deinited(busio_jacdac_obj_t *context) { + return context->hw_alloc == NULL || context->hw_alloc->timer == NULL; +} + +void common_hal_busio_jacdac_set_timer(busio_jacdac_obj_t *context, uint32_t us, busio_jacdac_base_callback_t callback) { + common_hal_mcu_disable_interrupts(); + if (context->hw_alloc) { + context->timer_cb = callback; + esp_timer_stop(context->hw_alloc->timer); + if (callback) { + esp_timer_start_once(context->hw_alloc->timer, us); + } + } + common_hal_mcu_enable_interrupts(); +} + +static IRAM_ATTR esp_err_t xgpio_set_level(gpio_num_t gpio_num, uint32_t level) { + if (level) { + GPIO.out_w1ts = (1 << gpio_num); + } else { + GPIO.out_w1tc = (1 << gpio_num); + } + return ESP_OK; +} + +static IRAM_ATTR void pin_rx(busio_jacdac_obj_t *context) { + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[context->pin_num], PIN_FUNC_GPIO); + REG_SET_BIT(GPIO_PIN_MUX_REG[context->pin_num], FUN_PU); + PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[context->pin_num]); + GPIO.enable_w1tc = (0x1 << context->pin_num); + REG_WRITE(GPIO_FUNC0_OUT_SEL_CFG_REG + (context->pin_num * 4), SIG_GPIO_OUT_IDX); + gpio_matrix_in(context->pin_num, uart_periph_signal[context->uart_num].rx_sig, 0); +} + +static IRAM_ATTR void pin_tx(busio_jacdac_obj_t *context) { + gpio_matrix_in(GPIO_FUNC_IN_HIGH, uart_periph_signal[context->uart_num].rx_sig, 0); // context->uart_hw + GPIO.pin[context->pin_num].int_type = GPIO_PIN_INTR_DISABLE; + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[context->pin_num], PIN_FUNC_GPIO); + gpio_set_level(context->pin_num, 1); + gpio_matrix_out(context->pin_num, uart_periph_signal[context->uart_num].tx_sig, 0, 0); +} + +int common_hal_busio_jacdac_wait_high(busio_jacdac_obj_t *context) { + // we already started RX at this point + return 0; +} + +static IRAM_ATTR void fill_fifo(busio_jacdac_obj_t *context) { + if (!context->tx_len) { + return; + } + + int space = UART_FIFO_LEN - context->uart_hw->status.txfifo_cnt; + if (context->tx_len < space) { + space = context->tx_len; + } + + uart_ll_write_txfifo(context->uart_hw, context->fifo_buf, space); + + context->fifo_buf += space; + context->tx_len -= space; + + if (context->tx_len == 0) { + LOG("txbrk"); + context->uart_hw->idle_conf.tx_brk_num = 14; // 14us + context->uart_hw->conf0.txd_brk = 1; + context->uart_hw->int_clr.tx_brk_done = 1; + context->uart_hw->int_ena.tx_brk_done = 1; + } + + context->uart_hw->int_clr.txfifo_empty = 1; + context->uart_hw->conf1.txfifo_empty_thrhd = UART_EMPTY_THRESH_DEFAULT; + context->uart_hw->int_ena.txfifo_empty = 1; +} + +static IRAM_ATTR void read_fifo(busio_jacdac_obj_t *context, int force) { + uart_dev_t *uart_reg = context->uart_hw; + int rx_fifo_len = uart_reg->status.rxfifo_cnt; + + if (!force && context->fifo_buf == NULL && rx_fifo_len < UART_FULL_THRESH_DEFAULT - 1) { + return; // read not started yet and we're not overflowing + + } + if (rx_fifo_len) { + LOG("rxfifo %d", rx_fifo_len); + int n = rx_fifo_len; + int needs_rst = 0; + if (n > context->rx_len) { + n = context->rx_len; + needs_rst = 1; + } + + if (n) { + context->rx_len -= n; + rx_fifo_len -= n; + uart_ll_read_rxfifo(uart_reg, context->fifo_buf, n); + context->fifo_buf += n; + } + + // and drop the rest of data + if (needs_rst) { + uart_ll_rxfifo_rst(uart_reg); + } + } +} + +void common_hal_busio_jacdac_cancel(busio_jacdac_obj_t *context) { + context->uart_hw->int_clr.val = 0xffffffff; + context->uart_hw->int_ena.val = UART_BRK_DET_INT_ENA; + context->seen_low = 0; + context->rx_len = context->tx_len = 0; + context->fifo_buf = NULL; + context->rx_ended = 0; + read_fifo(context, 1); + pin_rx(context); + log_pin_pulse(1, 1); +} + +void common_hal_busio_jacdac_force_read(busio_jacdac_obj_t *context) { + read_fifo(context, 1); +} + +#define END_RX_FLAGS (UART_RXFIFO_TOUT_INT_ST | UART_BRK_DET_INT_ST | UART_FRM_ERR_INT_ST) + +static IRAM_ATTR void start_bg_rx(busio_jacdac_obj_t *context) { + read_fifo(context, 1); // flush any data + context->seen_low = 1; + context->uart_hw->int_ena.val |= END_RX_FLAGS | UART_RXFIFO_FULL_INT_ENA; + if (!context->fifo_buf) { + busio_jacdac_line_falling(context); + } +} + +static IRAM_ATTR void uart_isr(busio_jacdac_obj_t *context) { + log_pin_pulse(0, 1); + + if (!context->hw_alloc) { + return; + } + + uart_dev_t *uart_reg = context->uart_hw; + + uint32_t uart_intr_status = uart_reg->int_st.val; + uart_reg->int_clr.val = uart_intr_status; // clear all + + LOG("ISR %x", uart_intr_status); + + read_fifo(context, 0); + + if (!context->seen_low && (uart_intr_status & UART_BRK_DET_INT_ST)) { + log_pin_pulse(0, 2); + start_bg_rx(context); + } else if (uart_intr_status & UART_TX_BRK_DONE_INT_ST) { + uart_reg->conf0.txd_brk = 0; + common_hal_busio_jacdac_cancel(context); + busio_jacdac_tx_completed(context); + } else if (uart_intr_status & UART_TXFIFO_EMPTY_INT_ST) { + uart_reg->int_ena.txfifo_empty = 0; + fill_fifo(context); + } else if (uart_intr_status & END_RX_FLAGS) { + log_pin_pulse(0, 4); + context->data_left = context->rx_len; + int had_buf = context->fifo_buf != NULL; + LOG("%d end, rx=%d %d", (int)esp_timer_get_time(), context->rx_len, had_buf); + common_hal_busio_jacdac_cancel(context); + if (had_buf) { + log_pin_pulse(0, 5); + busio_jacdac_rx_completed(context); + } else { + context->rx_ended = 1; + } + } +} + +static IRAM_ATTR NOINLINE_ATTR void probe_and_set(volatile uint32_t *oe, volatile uint32_t *inp, + uint32_t mask) { + *oe = *inp & mask; +} + +static void tx_race(busio_jacdac_obj_t *context) { + // don't reconnect the pin in the middle of the low-pulse + int timeout = 50000; + while (timeout-- > 0 && gpio_get_level(context->pin_num) == 0) { + ; + } + pin_rx(context); + start_bg_rx(context); +} + +IRAM_ATTR static void target_wait_us(uint32_t us) { + int64_t later = esp_timer_get_time() + us; + while (esp_timer_get_time() < later) { + ; + } +} + +IRAM_ATTR int common_hal_busio_jacdac_start_tx(busio_jacdac_obj_t *context, const void *data, uint32_t numbytes) { + if (context->tx_len) { + jd_panic(); + } + + if (!context->hw_alloc || context->seen_low || context->uart_hw->int_raw.brk_det) { + LOG("seen low %p %d %d", context->hw_alloc, context->seen_low, context->uart_hw->int_raw.brk_det); + return -1; + } + + common_hal_mcu_disable_interrupts(); + + gpio_matrix_in(GPIO_FUNC_IN_HIGH, uart_periph_signal[context->uart_num].rx_sig, 0); // context->uart_hw + PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[context->pin_num], PIN_FUNC_GPIO); + GPIO.out_w1tc = (1 << context->pin_num); + + probe_and_set(&GPIO.enable_w1ts, &GPIO.in, 1 << context->pin_num); + + if (!(GPIO.enable & (1 << context->pin_num))) { + // the line went down in the meantime + tx_race(context); + common_hal_mcu_enable_interrupts(); + return -1; + } + + target_wait_us(12); // low pulse is 14us with wait of 12 here + xgpio_set_level(context->pin_num, 1); + + target_wait_us(40); // ~55us from end of low pulse to start bit + + pin_tx(context); + + context->fifo_buf = (uint8_t *)data; + context->tx_len = numbytes; + + context->uart_hw->int_clr.val = 0xffffffff; + + fill_fifo(context); + + common_hal_mcu_enable_interrupts(); + + return 0; +} + +void uart_flush_rx(busio_jacdac_obj_t *context) { + common_hal_mcu_disable_interrupts(); + read_fifo(context, 1); + common_hal_mcu_enable_interrupts(); +} + +void common_hal_busio_jacdac_start_rx(busio_jacdac_obj_t *context, void *data, uint32_t maxbytes) { + if (context->rx_len || context->tx_len) { + jd_panic(); + } + + log_pin_pulse(0, 3); + + context->fifo_buf = data; + context->rx_len = maxbytes; + LOG("ini rx=%d", maxbytes); + + uart_flush_rx(context); + + // log_pin_pulse(0, 2); + + if (context->rx_ended) { + context->rx_ended = 0; + context->rx_len = 0; + context->fifo_buf = NULL; + // log_pin_pulse(0, 2); + busio_jacdac_rx_completed(context); + } +} diff --git a/ports/esp32s2/common-hal/busio/JACDAC.h b/ports/esp32s2/common-hal/busio/JACDAC.h new file mode 100644 index 0000000000000..4f9ad28b89eb3 --- /dev/null +++ b/ports/esp32s2/common-hal/busio/JACDAC.h @@ -0,0 +1,67 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2021 Microsoft + * + * 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_ESP32S2_COMMON_HAL_BUSIO_JACDAC_H +#define MICROPY_INCLUDED_ESP32S2_COMMON_HAL_BUSIO_JACDAC_H + +#include "common-hal/microcontroller/Pin.h" +#include "py/obj.h" +#include "shared-bindings/busio/JACDAC.h" + +#include "components/esp_timer/include/esp_timer.h" +#include "hal/uart_ll.h" +#include "driver/uart.h" + +typedef struct busio_jacdac_hw_alloc { + esp_timer_handle_t timer; + intr_handle_t intr_handle; + uint8_t uart_num; + uint8_t pin_num; +} busio_jacdac_hw_alloc_t; + +struct busio_jacdac_obj { + busio_jacdac_base_obj_t base; + const mcu_pin_obj_t *pinobj; + uint8_t pin_num; + uint8_t uart_num; + bool seen_low; + bool rx_ended; + uint16_t tx_len; + uint16_t rx_len; + uint16_t data_left; + uint8_t *fifo_buf; + busio_jacdac_base_callback_t timer_cb; + uart_dev_t *uart_hw; + busio_jacdac_hw_alloc_t *hw_alloc; +}; + +extern void jacdac_reset(void); +extern bool common_hal_busio_jacdac_is_free(uart_port_t port); + + +#define JD_WR_OVERHEAD 20 + +#endif // MICROPY_INCLUDED_ESP32S2_COMMON_HAL_BUSIO_JACDAC_H diff --git a/ports/esp32s2/common-hal/busio/UART.c b/ports/esp32s2/common-hal/busio/UART.c index 7af43a476de20..4669c0bbbd6fd 100644 --- a/ports/esp32s2/common-hal/busio/UART.c +++ b/ports/esp32s2/common-hal/busio/UART.c @@ -52,6 +52,19 @@ void uart_reset(void) { } } +static uint8_t used_uarts; +bool uart_is_used(uart_port_t num) { + return uart_is_driver_installed(num) || (used_uarts & (1 << (int)num)) != 0; +} + +void uart_mark_used(uart_port_t num, bool used) { + if (used) { + used_uarts |= (1 << (int)num); + } else { + used_uarts &= ~(1 << (int)num); + } +} + 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, @@ -86,7 +99,7 @@ void common_hal_busio_uart_construct(busio_uart_obj_t *self, self->uart_num = UART_NUM_MAX; for (uart_port_t num = 0; num < UART_NUM_MAX; num++) { - if (!uart_is_driver_installed(num)) { + if (!uart_is_used(num)) { self->uart_num = num; } } @@ -94,6 +107,8 @@ void common_hal_busio_uart_construct(busio_uart_obj_t *self, mp_raise_ValueError(translate("All UART peripherals are in use")); } + // no need to call uart_mark_used() as we're installing driver + uart_mode_t mode = UART_MODE_UART; uart_hw_flowcontrol_t flow_control = UART_HW_FLOWCTRL_DISABLE; if (have_rs485_dir) { diff --git a/ports/esp32s2/common-hal/busio/UART.h b/ports/esp32s2/common-hal/busio/UART.h index 0d19221c8f28f..f73cc0651de42 100644 --- a/ports/esp32s2/common-hal/busio/UART.h +++ b/ports/esp32s2/common-hal/busio/UART.h @@ -45,5 +45,8 @@ typedef struct { } busio_uart_obj_t; void uart_reset(void); +// Checks if UART is marked as used or has driver installed +bool uart_is_used(uart_port_t num); +void uart_mark_used(uart_port_t num, bool used); #endif // MICROPY_INCLUDED_ESP32S2_COMMON_HAL_BUSIO_UART_H diff --git a/ports/esp32s2/supervisor/port.c b/ports/esp32s2/supervisor/port.c index d48b7ef8927b8..2bf61548ed0fa 100644 --- a/ports/esp32s2/supervisor/port.c +++ b/ports/esp32s2/supervisor/port.c @@ -41,6 +41,7 @@ #include "common-hal/busio/I2C.h" #include "common-hal/busio/SPI.h" #include "common-hal/busio/UART.h" +#include "common-hal/busio/JACDAC.h" #include "common-hal/dualbank/__init__.h" #include "common-hal/ps2io/Ps2.h" #include "common-hal/pulseio/PulseIn.h" @@ -189,6 +190,7 @@ void reset_port(void) { i2c_reset(); spi_reset(); uart_reset(); + jacdac_reset(); #endif #if defined(CIRCUITPY_COUNTIO) || defined(CIRCUITPY_ROTARYIO) diff --git a/ports/nrf/boards/pca10056/mpconfigboard.h b/ports/nrf/boards/pca10056/mpconfigboard.h index 4856a957001e4..71c7cc7e4c071 100644 --- a/ports/nrf/boards/pca10056/mpconfigboard.h +++ b/ports/nrf/boards/pca10056/mpconfigboard.h @@ -42,6 +42,8 @@ #define DEFAULT_UART_BUS_RX (&pin_P1_01) #define DEFAULT_UART_BUS_TX (&pin_P1_02) +#define DEFAULT_JACDAC_BUS (&pin_P1_02) + // Flash operation mode is determined by MICROPY_QSPI_DATAn pin configuration. // A pin config is valid if it is defined and its value is not 0xFF. // Quad mode: If all DATA0 --> DATA3 are valid diff --git a/ports/nrf/boards/pca10056/pins.c b/ports/nrf/boards/pca10056/pins.c index e00bc8a11e62d..9f05116d21deb 100644 --- a/ports/nrf/boards/pca10056/pins.c +++ b/ports/nrf/boards/pca10056/pins.c @@ -80,6 +80,7 @@ STATIC const mp_rom_map_elem_t board_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_P1_02), MP_ROM_PTR(&pin_P1_02) }, { MP_ROM_QSTR(MP_QSTR_D1), MP_ROM_PTR(&pin_P1_02) }, { MP_ROM_QSTR(MP_QSTR_TX), MP_ROM_PTR(&pin_P1_02) }, + { MP_ROM_QSTR(MP_QSTR_JACDAC), MP_ROM_PTR(&pin_P1_02) }, { MP_ROM_QSTR(MP_QSTR_P1_03), MP_ROM_PTR(&pin_P1_03) }, { MP_ROM_QSTR(MP_QSTR_D2), MP_ROM_PTR(&pin_P1_03) }, diff --git a/ports/nrf/common-hal/busio/JACDAC.c b/ports/nrf/common-hal/busio/JACDAC.c new file mode 100644 index 0000000000000..b4c0ce7c47c40 --- /dev/null +++ b/ports/nrf/common-hal/busio/JACDAC.c @@ -0,0 +1,960 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 J Devine, M Lambrichts + * + * 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/microcontroller/__init__.h" +#include "shared-bindings/busio/JACDAC.h" + +#include "lib/utils/interrupt_char.h" +#include "py/mpconfig.h" +#include "py/gc.h" +#include "py/mperrno.h" +#include "py/runtime.h" +#include "supervisor/shared/translate.h" + +#include "nrfx_config.h" +#include "nrfx_gpiote.h" +#include "nrf_gpio.h" + + +#include +#include +#include + +#define JD_LOG_SIZE 512 +volatile uint32_t jd_log[JD_LOG_SIZE] = {0}; // TODO uint32_t ?! +uint32_t logidx = 0; + +static inline void log_char(char c) { + jd_log[logidx] = c; + logidx = (logidx + 1) % JD_LOG_SIZE; +} + + +#define JD_FRAME_SIZE(pkt) ((pkt)->size + 12) +#define JD_PERIPHERALS 2 + + +// states +#define ACTIVE 0x03 +#define RX_ACTIVE 0x01 +#define TX_ACTIVE 0x02 + +#define TX_CONFIGURED 0x04 +#define RX_CONFIGURED 0x08 + +#define TX_PENDING 0x10 + +#define JD_INST_ARRAY_SIZE 4 // TODO 2? +busio_jacdac_obj_t *jd_instances[JD_INST_ARRAY_SIZE] = { NULL }; + +static void tim_set_timer(busio_jacdac_obj_t *self, int delta, cb_t cb); +static void initial_rx_timeout(busio_jacdac_obj_t *self); +void tx_start(busio_jacdac_obj_t *self); + +static inline void cfg_dbg_pins(void) { + nrf_gpio_cfg_output(4); // P0 + nrf_gpio_cfg_output(5); // P1 + nrf_gpio_cfg_output(3); // P2 +} + +static inline void set_P0(int val) { + nrf_gpio_pin_write(4, val); +} + +static inline void set_P1(int val) { + nrf_gpio_pin_write(5, val); +} + +static inline void set_P2(int val) { + nrf_gpio_pin_write(3, val); +} + +static uint32_t seed; + +static void seed_random(uint32_t s) { + seed = (seed * 0x1000193) ^ s; +} + +static uint32_t get_random(void) { + if (seed == 0) { + seed_random(13 + *((uint32_t *)0x20032e20)); + } + + // xorshift algorithm + uint32_t x = seed; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + seed = x; + return x; +} + +// return v +/- 25% or so +static uint32_t random_around(uint32_t v) { // TODO move to common + uint32_t mask = 0xfffffff; + while (mask > v) { + mask >>= 1; + } + return (v - (mask >> 1)) + (get_random() & mask); +} + +/************************************************************************************* +* Configuration +*/ + + +// static busio_jacdac_obj_t* peripherals[JD_PERIPHERALS]; +// static void allocate_peripheral(busio_jacdac_obj_t* peripheral) { +// for (uint8_t i = 0; i < JD_PERIPHERALS; i++) { +// if (peripherals[i] == NULL) { +// peripherals[i] = peripheral; +// return; +// } +// } + +// mp_raise_RuntimeError(translate("All JACDAC peripherals in use")); +// } + +// static void free_peripheral(busio_jacdac_obj_t* peripheral) { +// for (uint8_t i = 0; i < JD_PERIPHERALS; i++) { +// if (peripherals[i] != NULL && peripherals[i]->pin == peripheral->pin) { +// peripherals[i] = NULL; +// return; +// } +// } +// } + +static nrfx_uarte_t nrfx_uartes[] = { + #if NRFX_CHECK(NRFX_UARTE0_ENABLED) + NRFX_UARTE_INSTANCE(0), + #endif + #if NRFX_CHECK(NRFX_UARTE1_ENABLED) + NRFX_UARTE_INSTANCE(1), + #endif +}; + + +/************************************************************************************* +* Helper methods +*/ + + +// static int8_t irq_disabled; + +// static void target_enable_irq(void) { +// irq_disabled--; +// if (irq_disabled <= 0) { +// irq_disabled = 0; +// __enable_irq(); +// } +// // } + +// static void target_disable_irq(void) { +// __disable_irq(); +// irq_disabled++; +// } + +static void target_panic(void) { + log_char('P'); + while (1) { + } +} + +static uint16_t jd_crc16(const void *data, uint32_t size) { + const uint8_t *ptr = (const uint8_t *)data; + uint16_t crc = 0xffff; + while (size--) { + uint8_t d = *ptr++; + uint8_t x = (crc >> 8) ^ d; + x ^= x >> 4; + crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x; + } + return crc; +} + +// static jd_diagnostics_t jd_diagnostics; +// jd_diagnostics_t *jd_get_diagnostics(void) { +// jd_diagnostics.bus_state = 0; +// return &jd_diagnostics; +// } + + +/************************************************************************************* +* GPIO helper +*/ + + +static void gpio_set(busio_jacdac_obj_t *self, uint32_t value) { + nrf_gpio_cfg_output(self->pin); + nrf_gpio_pin_write(self->pin, value); +} + +static uint32_t gpio_get(busio_jacdac_obj_t *self) { + nrf_gpio_cfg_input(self->pin, NRF_GPIO_PIN_PULLUP); + return nrf_gpio_pin_read(self->pin); +} + +static inline bool gpio_is_output(busio_jacdac_obj_t *self) { + return nrf_gpio_pin_dir_get(self->pin) == NRF_GPIO_PIN_DIR_OUTPUT; +} + +static inline void enable_gpio_interrupts(busio_jacdac_obj_t *self) { + nrf_gpio_cfg_input(self->pin, NRF_GPIO_PIN_PULLUP); + nrfx_gpiote_in_event_enable(self->pin, true); +} + +static inline void disable_gpio_interrupts(busio_jacdac_obj_t *self) { + nrfx_gpiote_in_event_disable(self->pin); +} + + +jd_frame_t *buffer_from_pool(busio_jacdac_obj_t *self) { + jd_frame_t *ret = NULL; + + __disable_irq(); + for (int i = 0; i < JD_POOL_SIZE; i++) { + if (self->buffer_pool[i]) { + ret = self->buffer_pool[i]; + self->buffer_pool[i] = NULL; + break; + } + } + __enable_irq(); + + return ret; +} + +int move_to_rx_queue(busio_jacdac_obj_t *self, jd_frame_t *f) { + int i; + + for (i = 0; i < JD_RX_SIZE; i++) { + if (self->rx_queue[i] == NULL) { + self->rx_queue[i] = f; + break; + } + } + + if (i == JD_RX_SIZE) { + return -1; + } + + return 0; +} + +int move_to_tx_queue(busio_jacdac_obj_t *self, jd_frame_t *f) { + int i; + + for (i = 0; i < JD_TX_SIZE; i++) { + if (self->tx_queue[i] == NULL) { + self->tx_queue[i] = f; + break; + } + } + + if (i == JD_TX_SIZE) { + return -1; + } + + return 1; +} + +void return_buffer_to_pool(busio_jacdac_obj_t *self, jd_frame_t *buf) { + __disable_irq(); + for (int i = 0; i < JD_POOL_SIZE; i++) { + if (self->buffer_pool[i] == NULL) { + self->buffer_pool[i] = buf; + break; + } + } + __enable_irq(); +} + +/************************************************************************************* +* Status helper +*/ + + +// set the given status +static inline void set_status(busio_jacdac_obj_t *self, uint16_t status) { + self->status |= status; +} + +// clear the given status +static inline void clr_status(busio_jacdac_obj_t *self, uint16_t status) { + self->status &= ~status; +} + +// check if the given status is set or not +static inline bool is_status(busio_jacdac_obj_t *self, uint16_t status) { + return self->status & status; +} + + +/************************************************************************************* +* UART configuration +*/ + + +static void uart_configure_tx(busio_jacdac_obj_t *self, int enable) { + if (enable && !is_status(self, TX_CONFIGURED)) { + NRF_P0->DIR |= (1 << self->pin); + NRF_P0->PIN_CNF[self->pin] = 3 << 2; // this overrides DIR setting above + self->uarte->p_reg->PSEL.TXD = self->pin; + self->uarte->p_reg->EVENTS_ENDTX = 0; + self->uarte->p_reg->INTENSET = (UARTE_INTENSET_ENDTX_Msk); + self->uarte->p_reg->ENABLE = 8; + while (!(self->uarte->p_reg->ENABLE)) { + ; + } + set_status(self, TX_CONFIGURED); + } else if (!enable && is_status(self, TX_CONFIGURED)) { + self->uarte->p_reg->TASKS_STOPTX = 1; + while (self->uarte->p_reg->TASKS_STOPTX) { + ; + } + self->uarte->p_reg->ENABLE = 0; + while ((self->uarte->p_reg->ENABLE)) { + ; + } + self->uarte->p_reg->INTENCLR = (UARTE_INTENCLR_ENDTX_Msk); + self->uarte->p_reg->PSEL.TXD = 0xFFFFFFFF; + clr_status(self, TX_CONFIGURED); + } +} + +static void uart_configure_rx(busio_jacdac_obj_t *self, int enable) { + if (enable && !is_status(self, RX_CONFIGURED)) { + NRF_P0->DIR &= ~(1 << self->pin); + NRF_P0->PIN_CNF[self->pin] = 3 << 2; // this overrides DIR setting above + self->uarte->p_reg->PSEL.RXD = self->pin; + self->uarte->p_reg->EVENTS_ENDRX = 0; + self->uarte->p_reg->EVENTS_ERROR = 0; + self->uarte->p_reg->ERRORSRC = self->uarte->p_reg->ERRORSRC; + self->uarte->p_reg->INTENSET = (UARTE_INTENSET_ENDRX_Msk | UARTE_INTENSET_ERROR_Msk); + self->uarte->p_reg->ENABLE = 8; + while (!(self->uarte->p_reg->ENABLE)) { + ; + } + set_status(self, RX_CONFIGURED); + } else if (!enable && is_status(self, RX_CONFIGURED)) { + self->uarte->p_reg->TASKS_STOPRX = 1; + while (self->uarte->p_reg->TASKS_STOPRX) { + ; + } + self->uarte->p_reg->ENABLE = 0; + while ((self->uarte->p_reg->ENABLE)) { + ; + } + self->uarte->p_reg->INTENCLR = (UARTE_INTENCLR_ENDRX_Msk | UARTE_INTENCLR_ERROR_Msk); + self->uarte->p_reg->PSEL.RXD = 0xFFFFFFFF; + clr_status(self, RX_CONFIGURED); + } +} + + +/************************************************************************************* +* Pin configuration +*/ + + +// set the JACDAC pin to act as the UART tx pin +static inline void set_pin_tx(busio_jacdac_obj_t *self) { + if (is_status(self, TX_CONFIGURED)) { + return; + } + + uart_configure_rx(self, 0); + uart_configure_tx(self, 1); +} + +// set the JACDAC pin to act as the UART rx pin +static inline void set_pin_rx(busio_jacdac_obj_t *self) { + if (is_status(self, RX_CONFIGURED)) { + return; + } + + uart_configure_tx(self, 0); + uart_configure_rx(self, 1); +} + +// set the JACDAC pin to act as a gpio pin +static inline void set_pin_gpio(busio_jacdac_obj_t *self) { + uart_configure_tx(self, 0); + uart_configure_rx(self, 0); +} + + +/************************************************************************************* +* Receiving +*/ +static void stop_uart_dma(busio_jacdac_obj_t *self) { + nrfx_uarte_tx_abort(self->uarte); + nrfx_uarte_rx_abort(self->uarte); +} + +static void rx_timeout(busio_jacdac_obj_t *self) { + log_char('?'); + set_P1(0); + + // if (!is_status(self, RX_ACTIVE)) + // target_panic(); + + // disable uart + stop_uart_dma(self); + + set_pin_gpio(self); + clr_status(self, RX_ACTIVE); + // restart normal operation + enable_gpio_interrupts(self); +} + +static void rx_start(busio_jacdac_obj_t *self) { + log_char('r'); + if (is_status(self, RX_ACTIVE)) { + target_panic(); + } + + set_P1(1); + + disable_gpio_interrupts(self); + set_status(self, RX_ACTIVE); + + // TODO set timer for ticks. + gpio_get(self); + tim_set_timer(self, 1000, rx_timeout); // TODO this won't work + while (nrf_gpio_pin_read(self->pin) == 0) { + ; + } + + set_pin_rx(self); + + // if we don't receive these bytes (CRC) after 200 us assume an error + // TODO this was supposed to erase first 4 words not bytes + uint8_t *b = (uint8_t *)self->rx_buffer; + b[0] = 0; + b[1] = 0; + b[2] = 0; + b[3] = 0; + + nrfx_uarte_rx(self->uarte, (uint8_t *)self->rx_buffer, sizeof(jd_frame_t)); + tim_set_timer(self, 200, initial_rx_timeout); +} + +static void rx_done(busio_jacdac_obj_t *self) { + set_P1(0); + + log_char('R'); + // clear any upcoming timer interrupts + nrfx_timer_capture(self->timer, NRF_TIMER_CC_CHANNEL0); + + if (!is_status(self, RX_ACTIVE)) { + target_panic(); + } + + set_pin_gpio(self); + clr_status(self, RX_ACTIVE); + + gpio_get(self); + while (nrf_gpio_pin_read(self->pin) == 0) { + ; + } + + + // check size + uint32_t txSize = sizeof(*self->rx_buffer); + uint32_t declaredSize = JD_FRAME_SIZE(self->rx_buffer); + if (txSize < declaredSize || declaredSize == 0) { + // jd_diagnostics.bus_uart_error++; + log_char('V'); + enable_gpio_interrupts(self); + return; + } + + // check crc + uint16_t crc = jd_crc16((uint8_t *)self->rx_buffer + 2, declaredSize - 2); + if (crc != self->rx_buffer->crc) { + // .bus_uart_error++; + log_char('M'); + // target_panic(); + enable_gpio_interrupts(self); + return; + } + + jd_frame_t *rx = self->rx_buffer; + self->rx_buffer = buffer_from_pool(self); + + int ret = move_to_rx_queue(self, rx); + + // drop but ensure memory is not left floating around... + if (ret == -1) { + return_buffer_to_pool(self, rx); + } + + stop_uart_dma(self); + // restart normal operation + enable_gpio_interrupts(self); + + if (is_status(self, TX_PENDING)) { + tim_set_timer(self, random_around(150), tx_start); + } +} + + +/************************************************************************************* +* JACDAC - transmitting +*/ +void tx_start(busio_jacdac_obj_t *self) { + if ((self->status & TX_ACTIVE) || (self->status & RX_ACTIVE)) { + target_panic(); + } + + log_char('T'); + + if (self->tx_buffer == NULL) { + __disable_irq(); + for (int i = 0; i < JD_RX_SIZE; i++) { + if (self->tx_queue[i]) { + self->tx_buffer = self->tx_queue[i]; + self->tx_queue[i] = NULL; + break; + } + } + __enable_irq(); + + if (self->tx_buffer == NULL) { + clr_status(self, TX_PENDING); + return; + } + } + + // try to pull the line low, provided it currently reads as high + if (gpio_get(self) == 0) { + set_P0(0); + set_status(self, TX_PENDING); + return; + } + + disable_gpio_interrupts(self); + + set_P0(1); + log_char(';'); + gpio_set(self, 0); + // start pulse (11-15µs) + common_hal_mcu_delay_us(10); + clr_status(self, TX_PENDING); + set_status(self, TX_ACTIVE); + + // start-data gap (40-89µs) + gpio_set(self, 1); + uint16_t *data = (uint16_t *)self->tx_buffer; + *data = jd_crc16((uint8_t *)self->tx_buffer + 2, JD_FRAME_SIZE(self->tx_buffer) - 2); + common_hal_mcu_delay_us(19); + + // setup UART tx + set_pin_tx(self); + nrfx_uarte_tx(self->uarte, (uint8_t *)self->tx_buffer, JD_FRAME_SIZE(self->tx_buffer)); +} + +static void tx_done(busio_jacdac_obj_t *self) { + set_P0(0); + log_char('t'); + + if (!is_status(self, TX_ACTIVE)) { + target_panic(); + } + + set_pin_gpio(self); + + // end pulse (11-15µs) + gpio_set(self, 0); + common_hal_mcu_delay_us(10); + return_buffer_to_pool(self, self->tx_buffer); + self->tx_buffer = NULL; + gpio_set(self, 1); + + uart_configure_tx(self, 0); + + // restart idle operation + clr_status(self, TX_ACTIVE); + + __disable_irq(); + bool more = false; + for (int i = 0; i < JD_RX_SIZE; i++) { + if (self->tx_queue[i]) { + more = true; + break; + } + } + __enable_irq(); + + if (more) { + set_status(self, TX_PENDING); + tim_set_timer(self, random_around(150), tx_start); + } + + enable_gpio_interrupts(self); +} + +static void tim_set_timer(busio_jacdac_obj_t *self, int delta, cb_t cb) { + self->tim_cb = cb; + uint32_t now = nrfx_timer_capture(self->timer, NRF_TIMER_CC_CHANNEL1); + nrfx_timer_compare(self->timer, NRF_TIMER_CC_CHANNEL0, now + delta, true); +} + +/************************************************************************************* +* Interrupt handlers +*/ + + +// interrupt handler for UART +static void uart_irq(const nrfx_uarte_event_t *event, void *context) { + busio_jacdac_obj_t *self = (busio_jacdac_obj_t *)context; + + switch (event->type) { + case NRFX_UARTE_EVT_RX_DONE: + rx_done(self); + break; + + case NRFX_UARTE_EVT_TX_DONE: + tx_done(self); + break; + + case NRFX_UARTE_EVT_ERROR: + log_char('E'); + log_char((char)event->data.error.error_mask); + // Possible Error source is Overrun, Parity, Framing, Break + if (is_status(self, RX_ACTIVE)) { + log_char('&'); + if ((event->data.error.error_mask & NRF_UARTE_ERROR_BREAK_MASK)) { + rx_done(self); + } + } else if (is_status(self, TX_ACTIVE)) { + log_char('*'); + } + + break; + } +} + +static void initial_rx_timeout(busio_jacdac_obj_t *self) { + log_char('N'); + if (is_status(self, RX_ACTIVE)) { + uint8_t *b = (uint8_t *)self->rx_buffer; + + if (b[0] == 0 && b[1] == 0) { + log_char('Y'); + rx_timeout(self); + return; + } + + log_char('I'); + tim_set_timer(self, JD_FRAME_SIZE(self->rx_buffer) * 12 + 60, rx_timeout); + } +} + +// interrupt handler for timers +static void timer_irq(nrf_timer_event_t event_type, void *context) { + log_char('i'); + set_P2(1); + busio_jacdac_obj_t *self = (busio_jacdac_obj_t *)context; + + if (event_type == NRF_TIMER_EVENT_COMPARE0) { + if (self->tim_cb) { + self->tim_cb(self); + } + } + set_P2(0); +} + +// interrupt handler for GPIO +static void gpiote_callback(nrfx_gpiote_pin_t pin, nrf_gpiote_polarity_t action) { + + busio_jacdac_obj_t *self = NULL; + + for (int i = 0; i < JD_INST_ARRAY_SIZE; i++) { + if (jd_instances[i]->pin == pin) { + self = jd_instances[i]; + break; + } + } + + if (self && !is_status(self, TX_ACTIVE)) { + rx_start(self); + } +} + + +/************************************************************************************* +* Initialization +*/ + +static void initialize_gpio(busio_jacdac_obj_t *self) { + + nrfx_gpiote_in_config_t cfg = { + .sense = NRF_GPIOTE_POLARITY_HITOLO, + .pull = NRF_GPIO_PIN_PULLUP, // idle_state ? NRF_GPIO_PIN_PULLDOWN : NRF_GPIO_PIN_PULLUP, + .is_watcher = false, // nrf_gpio_cfg_watcher vs nrf_gpio_cfg_input + .hi_accuracy = true, + .skip_gpio_setup = false + }; + + nrfx_gpiote_init(0); + nrfx_gpiote_in_init(self->pin, &cfg, gpiote_callback); + nrfx_gpiote_in_event_enable(self->pin, true); + + enable_gpio_interrupts(self); +} + +static void initialize_timer(busio_jacdac_obj_t *self) { + self->timer = nrf_peripherals_allocate_timer(); + + if (self->timer == NULL) { + target_panic(); + mp_raise_RuntimeError(translate("All timers in use")); + } + + nrfx_timer_config_t timer_config = { + .frequency = NRF_TIMER_FREQ_1MHz, + .mode = NRF_TIMER_MODE_TIMER, + .bit_width = NRF_TIMER_BIT_WIDTH_32, + .interrupt_priority = 2, + .p_context = self + }; + + nrfx_timer_init(self->timer, &timer_config, &timer_irq); + nrfx_timer_enable(self->timer); +} + +static void initialize_uart(busio_jacdac_obj_t *self) { + self->uarte = NULL; + + for (size_t i = 0; i < MP_ARRAY_SIZE(nrfx_uartes); i++) { + if ((nrfx_uartes[i].p_reg->ENABLE & UARTE_ENABLE_ENABLE_Msk) == 0) { + self->uarte = &nrfx_uartes[i]; + break; + } + } + + if (self->uarte == NULL) { + mp_raise_ValueError(translate("All UART peripherals are in use")); + } + + nrfx_uarte_config_t uart_config = { + .pseltxd = NRF_UARTE_PSEL_DISCONNECTED, + .pselrxd = NRF_UARTE_PSEL_DISCONNECTED, + .pselcts = NRF_UARTE_PSEL_DISCONNECTED, + .pselrts = NRF_UARTE_PSEL_DISCONNECTED, + .p_context = self, + .baudrate = NRF_UARTE_BAUDRATE_1000000, + .interrupt_priority = 1, + .hal_cfg = { + .hwfc = NRF_UARTE_HWFC_DISABLED, + .parity = NRF_UARTE_PARITY_EXCLUDED, + } + }; + + nrfx_uarte_init(self->uarte, &uart_config, uart_irq); +} + + +/************************************************************************************* +* Public JACDAC methods +*/ + +void common_hal_busio_jacdac_construct(busio_jacdac_obj_t *self, const mcu_pin_obj_t *pin) { + log_char('A'); + + bool found = false; + for (int i = 0; i < JD_INST_ARRAY_SIZE; i++) { + if (jd_instances[i] == self) { + found = true; + break; + } + } + + if (!found) { + int i; + for (i = 0; i < JD_INST_ARRAY_SIZE; i++) { + if (jd_instances[i] == NULL) { + jd_instances[i] = self; + break; + } + } + + if (i == JD_INST_ARRAY_SIZE) { + target_panic(); + } + } + + self->pin = pin->number; + self->status = 0; + self->tim_cb = NULL; + + for (int i = 0; i < JD_POOL_SIZE; i++) { + self->buffer_pool[i] = m_malloc(sizeof(jd_frame_t), true); + } + + for (int i = 0; i < JD_RX_SIZE; i++) { + self->rx_queue[i] = NULL; + } + + for (int i = 0; i < JD_TX_SIZE; i++) { + self->tx_queue[i] = NULL; + } + + self->rx_buffer = buffer_from_pool(self); + self->tx_buffer = NULL; + + cfg_dbg_pins(); + + claim_pin(pin); + initialize_timer(self); + initialize_gpio(self); + initialize_uart(self); + + log_char('B'); +} + + +void common_hal_busio_jacdac_deinit(busio_jacdac_obj_t *self) { + if (common_hal_busio_jacdac_deinited(self)) { + return; + } + + nrfx_gpiote_in_event_disable(self->pin); + nrfx_gpiote_in_uninit(self->pin); + + nrf_peripherals_free_timer(self->timer); + + // uart + if (self->uarte) { + nrfx_uarte_tx_abort(self->uarte); + nrfx_uarte_rx_abort(self->uarte); + nrfx_uarte_uninit(self->uarte); + } + + // pin + reset_pin_number(self->pin); + + for (int i = 0; i < JD_POOL_SIZE; i++) + { + if (self->buffer_pool[i]) { + m_free(self->buffer_pool[i]); + self->buffer_pool[i] = NULL; + } + } + + for (int i = 0; i < JD_RX_SIZE; i++) + { + if (self->rx_queue[i]) { + m_free(self->rx_queue[i]); + self->rx_queue[i] = NULL; + } + } + + for (int i = 0; i < JD_TX_SIZE; i++) + { + if (self->tx_queue[i]) { + m_free(self->tx_queue[i]); + self->tx_queue[i] = NULL; + } + } + + if (self->rx_buffer) { + m_free(self->rx_buffer); + self->rx_buffer = NULL; + } + + if (self->tx_buffer) { + m_free(self->tx_buffer); + self->tx_buffer = NULL; + } + + self->pin = NO_PIN; +} + +bool common_hal_busio_jacdac_deinited(busio_jacdac_obj_t *self) { + return self->pin == NO_PIN; +} + +int common_hal_busio_jacdac_send(busio_jacdac_obj_t *self, const uint8_t *data, size_t len) { + jd_frame_t *f = buffer_from_pool(self); + + if (f == NULL) { + return -1; + } + + memcpy(f, data, MIN(len, JD_MAX_FRAME_SIZE)); + + int ret = move_to_tx_queue(self, f); + + if (ret == -1) { + return_buffer_to_pool(self, f); + return -1; + } + + if (!is_status(self, TX_PENDING | TX_ACTIVE | RX_ACTIVE)) { + set_status(self, TX_PENDING); + tim_set_timer(self, 100, tx_start); + } + + return 1; +} + +int common_hal_busio_jacdac_receive(busio_jacdac_obj_t *self, uint8_t *data, size_t len) { + + jd_frame_t *f = NULL; + + __disable_irq(); + for (int i = 0; i < JD_RX_SIZE; i++) { + if (self->rx_queue[i]) { + f = self->rx_queue[i]; + self->rx_queue[i] = NULL; + break; + } + } + __enable_irq(); + + if (f) { + memcpy(data, f, sizeof(jd_frame_t)); + return_buffer_to_pool(self, f); + return 1; + } + + return 0; +} + +void jacdac_reset(void) { + log_char('}'); + + for (int i = 0; i < JD_INST_ARRAY_SIZE; i++) + { + if (jd_instances[i]) { + common_hal_busio_jacdac_deinit(jd_instances[i]); + jd_instances[i] = NULL; + } + } +} diff --git a/ports/nrf/common-hal/busio/JACDAC.h b/ports/nrf/common-hal/busio/JACDAC.h new file mode 100644 index 0000000000000..76825c162b5e5 --- /dev/null +++ b/ports/nrf/common-hal/busio/JACDAC.h @@ -0,0 +1,79 @@ +/* + * This file is part of the MicroPython project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 J Devine, M Lambrichts + * + * 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_NRF_COMMON_HAL_BUSIO_JACDAC_H +#define MICROPY_INCLUDED_NRF_COMMON_HAL_BUSIO_JACDAC_H + +#include "common-hal/microcontroller/Pin.h" +#include "py/obj.h" + +#include "nrfx_uarte.h" +#include "nrf/timers.h" + +#define JD_POOL_SIZE 20 +#define JD_RX_SIZE 10 +#define JD_TX_SIZE 10 + + + + +typedef struct busio_jacdac_obj { + mp_obj_base_t base; + uint8_t pin; + uint16_t status; + + nrfx_uarte_t *uarte; + nrfx_timer_t *timer; + + void (*tim_cb)(struct busio_jacdac_obj *); + + jd_frame_t *buffer_pool[JD_POOL_SIZE]; + jd_frame_t *rx_queue[JD_RX_SIZE]; + jd_frame_t *tx_queue[JD_TX_SIZE]; + + jd_frame_t *rx_buffer; + jd_frame_t *tx_buffer; +} busio_jacdac_obj_t; + +typedef void (*cb_t)(busio_jacdac_obj_t *); + +/* +typedef struct { + uint32_t bus_state; + uint32_t bus_lo_error; + uint32_t bus_uart_error; + uint32_t bus_timeout_error; + uint32_t packets_sent; + uint32_t packets_received; + uint32_t packets_dropped; +} jd_diagnostics_t; +jd_diagnostics_t *jd_get_diagnostics(void); +*/ + +void jacdac_reset(void); + + +#endif // MICROPY_INCLUDED_NRF_COMMON_HAL_BUSIO_JACDAC_H diff --git a/ports/nrf/common-hal/busio/__init__.c b/ports/nrf/common-hal/busio/__init__.c index 41761b6743aea..e9f75c8ec230f 100644 --- a/ports/nrf/common-hal/busio/__init__.c +++ b/ports/nrf/common-hal/busio/__init__.c @@ -1 +1 @@ -// No busio module functions. +// No busio module functions. \ No newline at end of file diff --git a/ports/nrf/supervisor/port.c b/ports/nrf/supervisor/port.c index edf17bacf13bb..636c6f3f86e2d 100644 --- a/ports/nrf/supervisor/port.c +++ b/ports/nrf/supervisor/port.c @@ -46,6 +46,7 @@ #include "common-hal/busio/I2C.h" #include "common-hal/busio/SPI.h" #include "common-hal/busio/UART.h" +#include "common-hal/busio/JACDAC.h" #include "common-hal/pulseio/PulseOut.h" #include "common-hal/pulseio/PulseIn.h" #include "common-hal/pwmio/PWMOut.h" @@ -218,6 +219,7 @@ void reset_port(void) { #if CIRCUITPY_BUSIO i2c_reset(); spi_reset(); + jacdac_reset(); uart_reset(); #endif diff --git a/py/circuitpy_defns.mk b/py/circuitpy_defns.mk index 41f8d71abdc3f..8bd034580d2fb 100644 --- a/py/circuitpy_defns.mk +++ b/py/circuitpy_defns.mk @@ -359,6 +359,7 @@ SRC_COMMON_HAL_ALL = \ busio/I2C.c \ busio/SPI.c \ busio/UART.c \ + busio/JACDAC.c \ busio/__init__.c \ camera/__init__.c \ camera/Camera.c \ @@ -455,7 +456,7 @@ $(filter $(SRC_PATTERNS), \ ) SRC_BINDINGS_ENUMS += \ - util.c + util.c \ SRC_SHARED_MODULE_ALL = \ _bleio/Address.c \ diff --git a/py/circuitpy_mpconfig.h b/py/circuitpy_mpconfig.h index 009b428645f3a..de62d5295224a 100644 --- a/py/circuitpy_mpconfig.h +++ b/py/circuitpy_mpconfig.h @@ -339,6 +339,7 @@ extern const struct _mp_obj_module_t board_module; #define BOARD_I2C (defined(DEFAULT_I2C_BUS_SDA) && defined(DEFAULT_I2C_BUS_SCL)) #define BOARD_SPI (defined(DEFAULT_SPI_BUS_SCK) && defined(DEFAULT_SPI_BUS_MISO) && defined(DEFAULT_SPI_BUS_MOSI)) #define BOARD_UART (defined(DEFAULT_UART_BUS_RX) && defined(DEFAULT_UART_BUS_TX)) +#define BOARD_JACDAC (defined(DEFAULT_JACDAC_BUS)) // I2C and SPI are always allocated off the heap. @@ -348,6 +349,12 @@ extern const struct _mp_obj_module_t board_module; #define BOARD_UART_ROOT_POINTER #endif +#if BOARD_JACDAC +#define BOARD_JACDAC_ROOT_POINTER mp_obj_t shared_jacdac_bus; +#else +#define BOARD_JACDAC_ROOT_POINTER +#endif + #else #define BOARD_MODULE #define BOARD_UART_ROOT_POINTER @@ -956,6 +963,7 @@ struct _supervisor_allocation_node; GAMEPAD_ROOT_POINTERS \ mp_obj_t pew_singleton; \ BOARD_UART_ROOT_POINTER \ + BOARD_JACDAC_ROOT_POINTER \ FLASH_ROOT_POINTERS \ MEMORYMONITOR_ROOT_POINTERS \ NETWORK_ROOT_POINTERS \ diff --git a/py/dmesg.c b/py/dmesg.c new file mode 100644 index 0000000000000..515ae8b072fba --- /dev/null +++ b/py/dmesg.c @@ -0,0 +1,199 @@ +#include "dmesg.h" +#include +#include + +#if DEVICE_DMESG_BUFFER_SIZE > 0 + +#include "shared-bindings/microcontroller/__init__.h" +#define target_disable_irq common_hal_mcu_disable_interrupts +#define target_enable_irq common_hal_mcu_enable_interrupts + +struct CodalLogStore codalLogStore; + +/** + * Performs an in buffer reverse of a given char array. + * + * @param s the string to reverse. + * + * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. + */ +int string_reverse(char *s) { + // sanity check... + if (s == NULL) { + return -1; + } + + char *j; + int c; + + j = s + strlen(s) - 1; + + while (s < j) { + c = *s; + *s++ = *j; + *j-- = c; + } + + return 0; +} + +/** + * Converts a given integer into a string representation. + * + * @param n The number to convert. + * + * @param s A pointer to the buffer where the resulting string will be stored. + * + * @return DEVICE_OK, or DEVICE_INVALID_PARAMETER. + */ +int myitoa(int n, char *s) { + int i = 0; + int positive = (n >= 0); + + if (s == NULL) { + return -1; + } + + // Record the sign of the number, + // Ensure our working value is positive. + unsigned k = positive ? n : -n; + + // Calculate each character, starting with the LSB. + do { + s[i++] = (k % 10) + '0'; + } while ((k /= 10) > 0); + + // Add a negative sign as needed + if (!positive) { + s[i++] = '-'; + } + + // Terminate the string. + s[i] = '\0'; + + // Flip the order. + string_reverse(s); + + return 0; +} + + + +static void logwriten(const char *msg, int l) { + target_disable_irq(); + if (codalLogStore.ptr + l >= sizeof(codalLogStore.buffer)) { + #if 1 + codalLogStore.buffer[0] = '.'; + codalLogStore.buffer[1] = '.'; + codalLogStore.buffer[2] = '.'; + codalLogStore.ptr = 3; + #else + // this messes with timings too much + const int jump = sizeof(codalLogStore.buffer) / 4; + codalLogStore.ptr -= jump; + memmove(codalLogStore.buffer, codalLogStore.buffer + jump, codalLogStore.ptr); + // zero-out the rest so it looks OK in the debugger + memset(codalLogStore.buffer + codalLogStore.ptr, 0, + sizeof(codalLogStore.buffer) - codalLogStore.ptr); + #endif + } + if (l + codalLogStore.ptr >= sizeof(codalLogStore.buffer)) { + return; // shouldn't happen + } + memcpy(codalLogStore.buffer + codalLogStore.ptr, msg, l); + codalLogStore.ptr += l; + codalLogStore.buffer[codalLogStore.ptr] = 0; + target_enable_irq(); +} + +static void writeNum(char *buf, uint32_t n, int full) { + int i = 0; + int sh = 28; + while (sh >= 0) { + int d = (n >> sh) & 0xf; + if (full || d || sh == 0 || i) { + buf[i++] = d > 9 ? 'A' + d - 10 : '0' + d; + } + sh -= 4; + } + buf[i] = 0; +} + +void codal_dmesg(const char *format, ...) { + va_list arg; + va_start(arg, format); + codal_vdmesg(format, arg); + va_end(arg); +} + +void codal_vdmesg(const char *format, va_list ap) { + char tmp[80]; + codal_vsprintf(tmp, sizeof(tmp) - 1, format, ap); + int len = strlen(tmp); + tmp[len] = '\n'; + tmp[len + 1] = 0; + logwriten(tmp, len + 1); +} + +#define WRITEN(p, sz_) \ + do { \ + sz = sz_; \ + ptr += sz; \ + if (ptr < dstsize) { \ + memcpy(dst + ptr - sz, p, sz); \ + dst[ptr] = 0; \ + } \ + } while (0) + +int codal_vsprintf(char *dst, unsigned dstsize, const char *format, va_list ap) { + const char *end = format; + unsigned ptr = 0, sz; + char buf[16]; + + for (;;) { + char c = *end++; + if (c == 0 || c == '%') { + if (format != end) { + WRITEN(format, end - format - 1); + } + if (c == 0) { + break; + } + + uint32_t val = va_arg(ap, uint32_t); + c = *end++; + buf[1] = 0; + switch (c) { + case 'c': + buf[0] = val; + break; + case 'd': + myitoa(val, buf); + break; + case 'x': + case 'p': + case 'X': + buf[0] = '0'; + buf[1] = 'x'; + writeNum(buf + 2, val, c != 'x'); + break; + case 's': + WRITEN((char *)(void *)val, strlen((char *)(void *)val)); + buf[0] = 0; + break; + case '%': + buf[0] = c; + break; + default: + buf[0] = '?'; + break; + } + format = end; + WRITEN(buf, strlen(buf)); + } + } + + return ptr; +} + +#endif diff --git a/py/dmesg.h b/py/dmesg.h new file mode 100644 index 0000000000000..54db58f019ed9 --- /dev/null +++ b/py/dmesg.h @@ -0,0 +1,75 @@ +#ifndef CODAL_DMESG_H +#define CODAL_DMESG_H + +#include +#include + +#define NOLOG(...) ((void)0) + +#ifndef __APPLE__ +#define DEVICE_DMESG_BUFFER_SIZE 1024 +#endif + +#if DEVICE_DMESG_BUFFER_SIZE > 0 + +#if DEVICE_DMESG_BUFFER_SIZE < 256 +#error "Too small DMESG buffer" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct CodalLogStore +{ + uint32_t ptr; + char buffer[DEVICE_DMESG_BUFFER_SIZE]; +}; +extern struct CodalLogStore codalLogStore; + +/** + * Log formatted message to an internal buffer. + * + * Supported format strings: + * %c - single character + * %d - decimal number + * %x - hexadecimal number (with 0x) + * %p - hexadecimal number padded with zeros (and with 0x) + * %X - hexadecimal number padded with zeros (and with 0x) + * %s - '\0'-terminated string + * %% - literal % + * Typically used via the DMESG() macro. + * + * @param format Format string + * + * @code + * uint32_t k; + * void *ptr; + * ... + * DMESG("USB: Error #%d at %X", k, ptr); + * @endcode + */ +void codal_dmesg(const char *format, ...); +void codal_dmesgf(const char *format, ...); + +void codal_dmesg_set_flush_fn(void (*fn)(void)); +void codal_dmesg_flush(void); + +void codal_vdmesg(const char *format, va_list ap); + +int codal_vsprintf(char *dst, unsigned dstsize, const char *format, va_list ap); + +#define DMESG codal_dmesg +#define DMESGF codal_dmesgf + +#ifdef __cplusplus +} +#endif + +#else + +#define DMESG(...) ((void)0) + +#endif + +#endif diff --git a/py/py.cmake b/py/py.cmake index 2b5d437b5762b..299539af7b06a 100644 --- a/py/py.cmake +++ b/py/py.cmake @@ -19,6 +19,7 @@ set(MICROPY_SOURCE_PY ${MICROPY_PY_DIR}/builtinhelp.c ${MICROPY_PY_DIR}/builtinimport.c ${MICROPY_PY_DIR}/compile.c + ${MICROPY_PY_DIR}/dmesg.c ${MICROPY_PY_DIR}/emitbc.c ${MICROPY_PY_DIR}/emitcommon.c ${MICROPY_PY_DIR}/emitglue.c diff --git a/py/py.mk b/py/py.mk index 94ac82f6f027a..cc6a46eaa93b5 100644 --- a/py/py.mk +++ b/py/py.mk @@ -183,6 +183,7 @@ PY_CORE_O_BASENAME = $(addprefix py/,\ repl.o \ smallint.o \ frozenmod.o \ + dmesg.o \ ) PY_EXTMOD_O_BASENAME = \ diff --git a/py/runtime.h b/py/runtime.h index a8a3120eef87e..cd19a8dc47a4d 100644 --- a/py/runtime.h +++ b/py/runtime.h @@ -30,6 +30,7 @@ #include "py/mpstate.h" #include "py/pystack.h" +#include "py/dmesg.h" #include "supervisor/linker.h" diff --git a/shared-bindings/board/__init__.c b/shared-bindings/board/__init__.c index 06b3a899d8089..413fc520c17ee 100644 --- a/shared-bindings/board/__init__.c +++ b/shared-bindings/board/__init__.c @@ -120,6 +120,31 @@ mp_obj_t board_uart(void) { #endif MP_DEFINE_CONST_FUN_OBJ_0(board_uart_obj, board_uart); + +//| def JACDAC() -> busio.JACDAC: +#if BOARD_JACDAC +mp_obj_t board_jacdac(void) { + mp_obj_t singleton = common_hal_board_get_jacdac(); + if (singleton != NULL) { + return singleton; + } + + assert_pin_free(DEFAULT_JACDAC_BUS); + + return common_hal_board_create_jacdac(); +} +#else +mp_obj_t board_jacdac(void) { + mp_raise_NotImplementedError_varg(translate("No default %q bus"), MP_QSTR_JACDAC); + return NULL; +} + +#endif +MP_DEFINE_CONST_FUN_OBJ_0(board_jacdac_obj, board_jacdac); + + + + const mp_obj_module_t board_module = { .base = { &mp_type_module }, .globals = (mp_obj_dict_t *)&board_module_globals, diff --git a/shared-bindings/board/__init__.h b/shared-bindings/board/__init__.h index a9b652ba8dfaf..9704e2d94bb11 100644 --- a/shared-bindings/board/__init__.h +++ b/shared-bindings/board/__init__.h @@ -45,4 +45,8 @@ mp_obj_t common_hal_board_get_uart(void); mp_obj_t common_hal_board_create_uart(void); MP_DECLARE_CONST_FUN_OBJ_0(board_uart_obj); +mp_obj_t common_hal_board_get_jacdac(void); +mp_obj_t common_hal_board_create_jacdac(void); +MP_DECLARE_CONST_FUN_OBJ_0(board_jacdac_obj); + #endif // MICROPY_INCLUDED_SHARED_BINDINGS_BOARD___INIT___H diff --git a/shared-bindings/busio/JACDAC.c b/shared-bindings/busio/JACDAC.c new file mode 100644 index 0000000000000..ae2938a0f7e1a --- /dev/null +++ b/shared-bindings/busio/JACDAC.c @@ -0,0 +1,676 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 J Devine, M Lambrichts + * + * 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 + +#include "shared-bindings/busio/JACDAC.h" +#include "shared-bindings/microcontroller/Pin.h" +#include "shared-bindings/util.h" +#include "shared-bindings/time/__init__.h" +#include "shared-bindings/microcontroller/__init__.h" +#include "shared-bindings/microcontroller/Processor.h" +#include "shared-bindings/random/__init__.h" + +#include "lib/utils/buffer_helper.h" +#include "lib/utils/context_manager_helpers.h" +#include "lib/utils/interrupt_char.h" + +#include "py/ioctl.h" +#include "py/objproperty.h" +#include "py/runtime.h" +#include "supervisor/shared/translate.h" +#include "supervisor/shared/tick.h" + +#include "common-hal/busio/JACDAC.h" + +#define JD_TX_QUEUE_MAX_SIZE 1024 +#define JD_RX_QUEUE_MAX_SIZE 1024 +#define JD_RX_TMP_QUEUE_SIZE 511 + +static void set_tick_timer(busio_jacdac_obj_t *ctx, uint8_t statusClear); + +//| class JACDAC: +//| """A bidirectional single wire serial protocol""" +//| def __init__(self, pin: microcontroller.Pin) -> None: +//| """A 1mbaud packet transfer protocol. +//| +//| :param ~microcontroller.Pin pin: the pin to transmit with.""" +//| ... +//| +STATIC mp_obj_t busio_jacdac_make_new(const mp_obj_type_t *type, size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args) { + // There might be non-movable objects referencing hardware in busio_jacdac_obj_t. + // Also, this will likely live forever. + busio_jacdac_obj_t *self = m_new0_ll(busio_jacdac_obj_t, 1); + self->base.base.type = &busio_jacdac_type; + + enum { ARG_pin }; + static const mp_arg_t allowed_args[] = { + { MP_QSTR_pin, MP_ARG_OBJ, {.u_obj = mp_const_none} }, + }; + + mp_arg_val_t args[MP_ARRAY_SIZE(allowed_args)]; + mp_arg_parse_all(n_args, pos_args, kw_args, MP_ARRAY_SIZE(allowed_args), allowed_args, args); + + const mcu_pin_obj_t *pin = validate_obj_is_free_pin(args[ARG_pin].u_obj); + + common_hal_busio_jacdac_construct(self, pin); + + supervisor_enable_tick(); + + return (mp_obj_t)self; +} + +void busio_jacdac_init(busio_jacdac_base_obj_t *self) { + // Use long-lived allocations - will likely live forever, and rxBuffer at the very least cannot be moved due to DMA + ringbuf_alloc(&self->rxTmpQueue, JD_RX_TMP_QUEUE_SIZE, true); + self->rxFrame = m_new_ll_obj(jd_frame_t); +} + +static void free_list(jd_linked_frame_t *l) { + while (l) { + jd_linked_frame_t *tmp = l; + l = l->next; + m_free(tmp); + } +} + +void busio_jacdac_deinit(busio_jacdac_base_obj_t *self) { + jd_linked_frame_t *rx, *tx; + common_hal_mcu_disable_interrupts(); + rx = self->rxQueue; + self->rxQueue = NULL; + tx = self->txQueue; + self->txQueue = NULL; + common_hal_mcu_enable_interrupts(); + free_list(rx); + free_list(tx); + self->frameToSplit = NULL; + ringbuf_free(&self->rxTmpQueue); + m_free(self->rxFrame); + self->rxFrame = NULL; +} + +//| def deinit(self) -> None: +//| """Deinitializes JACDAC and releases any hardware resources for reuse.""" +//| ... +//| +STATIC mp_obj_t busio_jacdac_obj_deinit(mp_obj_t self_in) { + busio_jacdac_obj_t *self = MP_OBJ_TO_PTR(self_in); + common_hal_busio_jacdac_deinit(self); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_1(busio_jacdac_deinit_obj, busio_jacdac_obj_deinit); + +STATIC void check_for_deinit(busio_jacdac_base_obj_t *self) { + if (common_hal_busio_jacdac_deinited((busio_jacdac_obj_t *)self)) { + raise_deinited_error(); + } +} + +//| def __enter__(self) -> JACDAC: +//| """No-op used by Context Managers.""" +//| ... +//| +// Provided by context manager helper. + +//| def __exit__(self) -> None: +//| """Automatically deinitializes the hardware when exiting a context. See +//| :ref:`lifetime-and-contextmanagers` for more info.""" +//| ... +//| +STATIC mp_obj_t busio_jacdac_obj___exit__(size_t n_args, const mp_obj_t *args) { + (void)n_args; + common_hal_busio_jacdac_deinit(args[0]); + return mp_const_none; +} +STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(busio_jacdac___exit___obj, 4, 4, busio_jacdac_obj___exit__); + + +// https://wiki.nicksoft.info/mcu:pic16:crc-16:home +static uint16_t jd_crc16(const void *data, uint32_t size) { + const uint8_t *ptr = (const uint8_t *)data; + uint16_t crc = 0xffff; + while (size--) { + uint8_t b = *ptr++; + uint8_t x = (crc >> 8) ^ b; + x ^= x >> 4; + crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x; + } + return crc; +} + +// used to estimate memory used by the queue +#define PACKET_OVERHEAD (JD_LINKED_FRAME_HEADER_SIZE + 12 + 8) + +static int copy_and_append(jd_linked_frame_t *volatile *q, const uint8_t *data, int max_bytes) { + size_t frm_sz = data[2] + 12; + // use short-lived allocation - given this is a native buffer, hopefully it's impossible + // for anyone to make it long-lived later + jd_linked_frame_t *buf = m_malloc(frm_sz + JD_LINKED_FRAME_HEADER_SIZE, false); + + buf->timestamp_ms = common_hal_time_monotonic_ms(); + + memcpy(&buf->frame, data, frm_sz); + + common_hal_mcu_disable_interrupts(); + jd_linked_frame_t *last = *q; + int num = 0; + buf->next = NULL; + while (last && last->next) { + num += PACKET_OVERHEAD + last->frame.size; + last = last->next; + } + if (last) { + num += PACKET_OVERHEAD + last->frame.size; + } + if (num < max_bytes) { + if (last) { + last->next = buf; + } else { + *q = buf; + } + buf = NULL; + } + common_hal_mcu_enable_interrupts(); + + if (buf == NULL) { + return 0; + } else { + m_free(buf); + return -1; + } +} + +//| def send(self, buf: _typing.WriteableBuffer) -> None: +//| """Queue a packet to be sent. Rises an exception if the transmit queue is full.""" +//| ... +//| +STATIC mp_obj_t busio_jacdac_send(mp_obj_t self_, mp_obj_t buffer) { + busio_jacdac_base_obj_t *self = MP_OBJ_TO_PTR(self_); + check_for_deinit(self); + + // setup buffer + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(buffer, &bufinfo, MP_BUFFER_RW); + + if (bufinfo.len < 16 || bufinfo.len > 252) { + mp_raise_ValueError(translate("Invalid argument")); + } + + uint8_t *p = bufinfo.buf; + p[2] = bufinfo.len - 12; + uint16_t crc = jd_crc16(p + 2, bufinfo.len - 2); + p[0] = crc & 0xff; + p[1] = crc >> 8; + + if (copy_and_append(&self->txQueue, bufinfo.buf, JD_TX_QUEUE_MAX_SIZE) != 0) { + mp_raise_ValueError(translate("Jacdac TX queue full")); + } + + common_hal_mcu_disable_interrupts(); + if (self->status == 0) { + set_tick_timer((busio_jacdac_obj_t *)self, 0); + } + common_hal_mcu_enable_interrupts(); + + return mp_const_none; +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_jacdac_send_obj, busio_jacdac_send); + +#define JD_FRAME_FLAG_ACK_REQUESTED 0x02 +#define ALIGN(x) (((x) + 3) & ~3) +static int jd_shift_frame(jd_frame_t *frame) { + int psize = frame->size; + int oldsz = frame->service_size + 4; + if (ALIGN(oldsz) >= psize) { + return 0; // nothing to shift + } + + uint8_t *data = &frame->service_size; + + int ptr; + if (data[oldsz] == 0xff) { + ptr = data[oldsz + 1]; + if (ptr >= psize) { + return 0; // End-of-frame + } + if (ptr <= oldsz) { + return 0; // don't let it go back, must be some corruption + } + } else { + ptr = ALIGN(oldsz); + } + + // assume the first one got the ACK sorted + frame->flags &= ~JD_FRAME_FLAG_ACK_REQUESTED; + + uint8_t *src = data + ptr; + int newsz = *src + 4; + if (ptr + newsz > psize) { + return 0; + } + uint32_t *dst = (uint32_t *)(void *)data; + uint32_t *srcw = (uint32_t *)(void *)src; + // don't trust memmove() + for (int i = 0; i < newsz; i += 4) { + *dst++ = *srcw++; + } + // store ptr + ptr += ALIGN(newsz); + data[newsz] = 0xff; + data[newsz + 1] = ptr; + + return 1; +} + +//| def receive(self) -> Optional[bytes]: +//| """Fetches a Jacdac packet from reception queue. Returns None if queue is empty.""" +//| ... +//| +STATIC mp_obj_t busio_jacdac_receive(mp_obj_t self_) { + busio_jacdac_base_obj_t *self = MP_OBJ_TO_PTR(self_); + check_for_deinit(self); + mp_obj_t res = mp_const_none; + jd_linked_frame_t *lnk = self->frameToSplit; + + if (lnk == NULL) { + common_hal_mcu_disable_interrupts(); + lnk = self->rxQueue; + if (lnk) { + self->frameToSplit = lnk; + self->rxQueue = lnk->next; + } + common_hal_mcu_enable_interrupts(); + } + + if (lnk) { + res = mp_obj_new_bytearray(lnk->frame.service_size + 16, (void *)&lnk->frame); + if (jd_shift_frame(&lnk->frame) == 0) { + m_free(lnk); + self->frameToSplit = NULL; + } + } + + return res; +} +MP_DEFINE_CONST_FUN_OBJ_1(busio_jacdac_receive_obj, busio_jacdac_receive); + +//| def id(self) -> bytearray: +//| """Get 64 bit device identifier.""" +//| ... +//| +STATIC mp_obj_t busio_jacdac_uid(mp_obj_t self_) { + busio_jacdac_base_obj_t *self = MP_OBJ_TO_PTR(self_); + check_for_deinit(self); + uint64_t id = busio_jacdac_device_id(); + return mp_obj_new_bytearray(sizeof(id), &id); +} +MP_DEFINE_CONST_FUN_OBJ_1(busio_jacdac_uid_obj, busio_jacdac_uid); + +// https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function +static uint32_t hash_fnv1(const void *data, unsigned len) { + const uint8_t *d = (const uint8_t *)data; + uint32_t h = 0x811c9dc5; + while (len--) { + h = (h * 0x1000193) ^ *d++; + } + return h; +} + +uint64_t busio_jacdac_device_id(void) { + uint8_t raw_id[COMMON_HAL_MCU_PROCESSOR_UID_LENGTH]; + int len = COMMON_HAL_MCU_PROCESSOR_UID_LENGTH; + common_hal_mcu_processor_get_uid(raw_id); + #if COMMON_HAL_MCU_PROCESSOR_UID_LENGTH <= 8 + uint8_t dev_id[8] = {0}; + dev_id[0] = 0xfe; + dev_id[7] = 0xff; + memcpy(dev_id + (8 - len) / 2, raw_id, len); + uint64_t res; + memcpy(&res, dev_id, sizeof(res)); + return res; + #else + return ((uint64_t)hash_fnv1(raw_id, len) << 32) + | ((uint64_t)hash_fnv1(raw_id + 1, len - 1)); + #endif +} + +static uint32_t jd_hash(uint8_t *buf, size_t length, int bits) { + if (bits < 1) { + return 0; + } + + uint32_t h = hash_fnv1(buf, length); + + if (bits >= 32) { + return h; + } else { + return (h ^ (h >> bits)) & ((1 << bits) - 1); + } +} + + +STATIC mp_obj_t busio_jacdac_hash(mp_obj_t bufobj, mp_obj_t numbits) { + // setup buffer + mp_buffer_info_t bufinfo; + mp_get_buffer_raise(bufobj, &bufinfo, MP_BUFFER_READ); + + uint32_t h = jd_hash(((uint8_t *)bufinfo.buf), bufinfo.len, mp_obj_get_int(numbits)); + + return mp_obj_new_int_from_uint(h); + + /* + vstr.buf[0] = 0x41 + h % 26; + vstr.buf[1] = 0x41 + (h / 26) % 26; + vstr.buf[2] = 0x30 + (h / (26 * 26)) % 10; + vstr.buf[3] = 0x30 + (h / (26 * 26 * 10)) % 10; + */ +} +MP_DEFINE_CONST_FUN_OBJ_2(busio_jacdac_hash_obj, busio_jacdac_hash); + + +STATIC const mp_rom_map_elem_t busio_jacdac_locals_dict_table[] = { + { MP_ROM_QSTR(MP_QSTR_deinit), MP_ROM_PTR(&busio_jacdac_deinit_obj) }, + { MP_ROM_QSTR(MP_QSTR___enter__), MP_ROM_PTR(&default___enter___obj) }, + { MP_ROM_QSTR(MP_QSTR___exit__), MP_ROM_PTR(&busio_jacdac___exit___obj) }, + + { MP_ROM_QSTR(MP_QSTR_send), MP_ROM_PTR(&busio_jacdac_send_obj) }, + { MP_ROM_QSTR(MP_QSTR_receive), MP_ROM_PTR(&busio_jacdac_receive_obj) }, + { MP_ROM_QSTR(MP_QSTR_hash), MP_ROM_PTR(&busio_jacdac_hash_obj) }, + { MP_ROM_QSTR(MP_QSTR_uid), MP_ROM_PTR(&busio_jacdac_uid_obj) }, +}; +STATIC MP_DEFINE_CONST_DICT(busio_jacdac_locals_dict, busio_jacdac_locals_dict_table); + +const mp_obj_type_t busio_jacdac_type = { + { &mp_type_type }, + .name = MP_QSTR_JACDAC, + .make_new = busio_jacdac_make_new, + .locals_dict = (mp_obj_dict_t *)&busio_jacdac_locals_dict, +}; + + +// +// +// + +// #define JD_LOG(...) do {} while (0) +#define JD_LOG DMESG +#define ERROR(msg, ...) \ + do { \ + JD_LOG("JD-ERROR: " msg,##__VA_ARGS__); \ + } while (0) +#define LOG NOLOG + +#define JD_STATUS_RX_ACTIVE 0x01 +#define JD_STATUS_TX_ACTIVE 0x02 +#define JD_STATUS_TX_QUEUED 0x04 + +// implement own rng - only uses MP's one to init so as not to run rng at "random" points the program +// (eg when Jacdac packets arrive) and mess with user's expectation of random seed +static uint32_t jd_random(void) { + static uint32_t seed; + if (!seed) { + seed = shared_modules_random_getrandbits(32); + } + // xorshift algorithm + uint32_t x = seed; + x ^= x << 13; + x ^= x >> 17; + x ^= x << 5; + seed = x; + return x; +} + +// return v +/- 25% or so +static uint32_t jd_random_around(uint32_t v) { + uint32_t mask = 0xfffffff; + while (mask > v) { + mask >>= 1; + } + return (v - (mask >> 1)) + (jd_random() & mask); +} + +static void jd_panic(void) { + while (1) { + ; + } +} + +static void pulse1(void) { +} + +static void signal_error(void) { +} + +static void signal_write(int v) { +} + +static void signal_read(int v) { +} + +static void pulse_log_pin(void) { +} + +static void tx_done(busio_jacdac_obj_t *ctx) { + signal_write(0); + set_tick_timer(ctx, JD_STATUS_TX_ACTIVE); +} + +void busio_jacdac_tx_completed(busio_jacdac_obj_t *ctx) { + jd_linked_frame_t *f; + common_hal_mcu_disable_interrupts(); + f = ctx->base.txQueue; + if (f) { + ctx->base.txQueue = f->next; + } + common_hal_mcu_enable_interrupts(); + if (f) { + m_free(f); + } + tx_done(ctx); +} + +static void flush_tx_queue(busio_jacdac_obj_t *ctx) { + LOG("flush %d", ctx->base.status); + + common_hal_mcu_disable_interrupts(); + if (ctx->base.status & (JD_STATUS_RX_ACTIVE | JD_STATUS_TX_ACTIVE)) { + common_hal_mcu_enable_interrupts(); + return; + } + ctx->base.status |= JD_STATUS_TX_ACTIVE; + common_hal_mcu_enable_interrupts(); + + jd_linked_frame_t *f = ctx->base.txQueue; + if (!f) { + tx_done(ctx); + JD_LOG("nothing to flush"); + return; + } + + signal_write(1); + if (common_hal_busio_jacdac_start_tx(ctx, &f->frame, JD_FRAME_SIZE(&f->frame)) < 0) { + ERROR("race on TX"); + // jd_diagnostics.bus_lo_error++; + tx_done(ctx); + return; + } + + set_tick_timer(ctx, 0); +} + +static void set_tick_timer(busio_jacdac_obj_t *ctx, uint8_t statusClear) { + common_hal_mcu_disable_interrupts(); + if (statusClear) { + // LOG("st %d @%d", statusClear, ctx->base.status); + ctx->base.status &= ~statusClear; + } + if ((ctx->base.status & JD_STATUS_RX_ACTIVE) == 0) { + if (ctx->base.txQueue && !(ctx->base.status & JD_STATUS_TX_ACTIVE)) { + pulse1(); + // the JD_WR_OVERHEAD value should be such, that the time from pulse1() above + // to beginning of low-pulse generated by the current device is exactly 150us + // (when the line below is uncommented) + // common_hal_busio_jacdac_set_timer(150 - JD_WR_OVERHEAD, flush_tx_queue); + ctx->base.status |= JD_STATUS_TX_QUEUED; + common_hal_busio_jacdac_set_timer(ctx, jd_random_around(150) - JD_WR_OVERHEAD, flush_tx_queue); + } else { + ctx->base.status &= ~JD_STATUS_TX_QUEUED; + common_hal_busio_jacdac_set_timer(ctx, 0, NULL); + } + } + common_hal_mcu_enable_interrupts(); +} + +static void rx_timeout(busio_jacdac_obj_t *ctx) { + common_hal_mcu_disable_interrupts(); + // jd_diagnostics.bus_timeout_error++; + ERROR("RX timeout"); + common_hal_busio_jacdac_cancel(ctx); + signal_read(0); + set_tick_timer(ctx, JD_STATUS_RX_ACTIVE); + common_hal_mcu_enable_interrupts(); + signal_error(); +} + +static void setup_rx_timeout(busio_jacdac_obj_t *ctx) { + common_hal_mcu_disable_interrupts(); + if (ctx->base.status & JD_STATUS_RX_ACTIVE) { + common_hal_busio_jacdac_force_read(ctx); + uint32_t *p = (uint32_t *)ctx->base.rxFrame; + if (p[0] == 0 && p[1] == 0) { + rx_timeout(ctx); // didn't get any data after lo-pulse + } else { + // got the size - set timeout for whole packet + common_hal_busio_jacdac_set_timer(ctx, JD_FRAME_SIZE(ctx->base.rxFrame) * 12 + 60, rx_timeout); + } + } + common_hal_mcu_enable_interrupts(); +} + +void busio_jacdac_line_falling(busio_jacdac_obj_t *ctx) { + // JD_LOG("line fall"); + // log_pin_set(1, 1); + pulse_log_pin(); + signal_read(1); + + // common_hal_mcu_disable_interrupts(); + // no need to disable IRQ - we're at the highest IRQ level + if (ctx->base.status & JD_STATUS_RX_ACTIVE) { + jd_panic(); + } + ctx->base.status |= JD_STATUS_RX_ACTIVE; + + // 1us faster than memset() on SAMD21 + uint32_t *p = (uint32_t *)ctx->base.rxFrame; + p[0] = 0; + p[1] = 0; + p[2] = 0; + p[3] = 0; + + // otherwise we can enable RX in the middle of LO pulse + if (common_hal_busio_jacdac_wait_high(ctx) < 0) { + // line didn't get high in 1ms or so - bail out + rx_timeout(ctx); + return; + } + // pulse1(); + // target_wait_us(2); + + common_hal_busio_jacdac_start_rx(ctx, ctx->base.rxFrame, sizeof(*ctx->base.rxFrame)); + // log_pin_set(1, 0); + + // if we're not active after start_rx, it means the rx already finished + if (!(ctx->base.status & JD_STATUS_RX_ACTIVE)) { + return; + } + + // 200us max delay according to spec, +50us to get the first 4 bytes of data + common_hal_busio_jacdac_set_timer(ctx, 250, setup_rx_timeout); + // JD_LOG("%d af rx t/o", (int)esp_timer_get_time()); + + // common_hal_mcu_enable_interrupts(); +} + +static void flush_rx(busio_jacdac_obj_t *ctx) { + if (common_hal_busio_jacdac_deinited(ctx)) { + return; + } + for (;;) { + jd_frame_t frame; + uint8_t *data = (uint8_t *)&frame; + + common_hal_mcu_disable_interrupts(); + int v = ringbuf_get(&ctx->base.rxTmpQueue); + if (v >= 0) { + data[0] = v; + for (int i = 1; i < JD_FRAME_SIZE(&frame); ++i) { + data[i] = ringbuf_get(&ctx->base.rxTmpQueue); + } + } + common_hal_mcu_enable_interrupts(); + + if (v >= 0) { + copy_and_append(&ctx->base.rxQueue, (void *)&frame, JD_FRAME_SIZE(&frame)); + } else { + break; + } + } +} + +void busio_jacdac_rx_completed(busio_jacdac_obj_t *ctx) { + LOG("rx cmpl"); + jd_frame_t *frame = ctx->base.rxFrame; + + signal_read(0); + set_tick_timer(ctx, JD_STATUS_RX_ACTIVE); + + if (frame->flags & 0x80) { + // reserved for JACDAC vNext + ERROR("vNext frame"); + return; + } + + size_t declaredSize = JD_FRAME_SIZE(frame); + uint16_t crc = jd_crc16((uint8_t *)frame + 2, declaredSize - 2); + if (crc != frame->crc) { + ERROR("crc mismatch"); + // jd_diagnostics.bus_uart_error++; + return; + } + + // jd_diagnostics.packets_received++; + + // pulse1(); + common_hal_mcu_disable_interrupts(); + if (ringbuf_num_empty(&ctx->base.rxTmpQueue) < declaredSize) { + // jd_diagnostics.packets_dropped++; + } else { + ringbuf_put_n(&ctx->base.rxTmpQueue, (void *)frame, declaredSize); + background_callback_add(&ctx->base.callback, (background_callback_fun)flush_rx, ctx); + } + common_hal_mcu_enable_interrupts(); +} diff --git a/shared-bindings/busio/JACDAC.h b/shared-bindings/busio/JACDAC.h new file mode 100644 index 0000000000000..6390b81767106 --- /dev/null +++ b/shared-bindings/busio/JACDAC.h @@ -0,0 +1,103 @@ +/* + * This file is part of the Micro Python project, http://micropython.org/ + * + * The MIT License (MIT) + * + * Copyright (c) 2020 J Devine, M Lambrichts + * + * 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_SHARED_BINDINGS_BUSIO_JACDAC_H +#define MICROPY_INCLUDED_SHARED_BINDINGS_BUSIO_JACDAC_H + +#include "common-hal/microcontroller/Pin.h" +#include "py/ringbuf.h" +#include "supervisor/background_callback.h" + +// 255 minus size of the serial header, rounded down to 4 +#define JD_SERIAL_PAYLOAD_SIZE 236 +#define JD_MAX_FRAME_SIZE 252 + +// structure for a jacdac frame +struct _jd_frame_t { + uint16_t crc; + uint8_t size; + uint8_t flags; + + uint64_t device_identifier; + + uint8_t service_size; + uint8_t service_number; + uint16_t service_command; + + uint8_t _data[JD_SERIAL_PAYLOAD_SIZE]; +} __attribute__((__packed__, aligned(4))); +typedef struct _jd_frame_t jd_frame_t; +#define JD_FRAME_SIZE(pkt) ((pkt)->size + 12) + +typedef struct jd_linked_frame { + struct jd_linked_frame *next; + uint32_t timestamp_ms; + jd_frame_t frame; +} jd_linked_frame_t; +#define JD_LINKED_FRAME_HEADER_SIZE (sizeof(uint32_t) + sizeof(void *)) + +typedef struct busio_jacdac_obj busio_jacdac_obj_t; // defined in common-hal + +typedef struct { + mp_obj_base_t base; + background_callback_t callback; + jd_frame_t *rxFrame; + ringbuf_t rxTmpQueue; + jd_linked_frame_t *txQueue; + jd_linked_frame_t *rxQueue; + jd_linked_frame_t *frameToSplit; + + volatile uint8_t status; +} busio_jacdac_base_obj_t; + +extern const mp_obj_type_t busio_jacdac_type; + +typedef void (*busio_jacdac_base_callback_t)(busio_jacdac_obj_t *); + +// to be called from common-hal only: +extern void busio_jacdac_init(busio_jacdac_base_obj_t *self); +extern void busio_jacdac_deinit(busio_jacdac_base_obj_t *self); +extern void busio_jacdac_rx_completed(busio_jacdac_obj_t *ctx); +extern void busio_jacdac_line_falling(busio_jacdac_obj_t *ctx); +extern void busio_jacdac_tx_completed(busio_jacdac_obj_t *ctx); + +extern uint64_t busio_jacdac_device_id(void); + + +// standard MP stuff +extern void common_hal_busio_jacdac_construct(busio_jacdac_obj_t *context, const mcu_pin_obj_t *pin); +extern void common_hal_busio_jacdac_deinit(busio_jacdac_obj_t *context); +extern bool common_hal_busio_jacdac_deinited(busio_jacdac_obj_t *context); + +// these follow jacdac-c +extern void common_hal_busio_jacdac_set_timer(busio_jacdac_obj_t *context, uint32_t us, busio_jacdac_base_callback_t callback); +extern int common_hal_busio_jacdac_start_tx(busio_jacdac_obj_t *context, const void *data, uint32_t numbytes); +extern void common_hal_busio_jacdac_start_rx(busio_jacdac_obj_t *context, void *data, uint32_t maxbytes); +extern void common_hal_busio_jacdac_cancel(busio_jacdac_obj_t *context); +extern int common_hal_busio_jacdac_wait_high(busio_jacdac_obj_t *context); +extern void common_hal_busio_jacdac_force_read(busio_jacdac_obj_t *context); + +#endif // MICROPY_INCLUDED_SHARED_BINDINGS_BUSIO_JACDAC_H diff --git a/shared-bindings/busio/__init__.c b/shared-bindings/busio/__init__.c index e7df331c12bcc..3225c3ccde5f0 100644 --- a/shared-bindings/busio/__init__.c +++ b/shared-bindings/busio/__init__.c @@ -35,6 +35,7 @@ #include "shared-bindings/busio/OneWire.h" #include "shared-bindings/busio/SPI.h" #include "shared-bindings/busio/UART.h" +#include "shared-bindings/busio/JACDAC.h" #include "py/runtime.h" @@ -75,6 +76,7 @@ STATIC const mp_rom_map_elem_t busio_module_globals_table[] = { { MP_ROM_QSTR(MP_QSTR_SPI), MP_ROM_PTR(&busio_spi_type) }, { MP_ROM_QSTR(MP_QSTR_OneWire), MP_ROM_PTR(&busio_onewire_type) }, { MP_ROM_QSTR(MP_QSTR_UART), MP_ROM_PTR(&busio_uart_type) }, + { MP_ROM_QSTR(MP_QSTR_JACDAC), MP_ROM_PTR(&busio_jacdac_type) }, }; STATIC MP_DEFINE_CONST_DICT(busio_module_globals, busio_module_globals_table); diff --git a/shared-module/board/__init__.c b/shared-module/board/__init__.c index 05900040d8032..d17a821c99b6f 100644 --- a/shared-module/board/__init__.c +++ b/shared-module/board/__init__.c @@ -33,6 +33,7 @@ #include "shared-bindings/busio/I2C.h" #include "shared-bindings/busio/SPI.h" #include "shared-bindings/busio/UART.h" +#include "shared-bindings/busio/JACDAC.h" #endif #if CIRCUITPY_DISPLAYIO @@ -132,6 +133,24 @@ mp_obj_t common_hal_board_create_uart(void) { } #endif +/* +#if BOARD_JACDAC +mp_obj_t common_hal_board_get_jacdac(void) { + return MP_STATE_VM(shared_jacdac_bus); +} + +mp_obj_t common_hal_board_create_jacdac(void) { + busio_jacdac_obj_t *self = m_new_ll_obj(busio_jacdac_obj_t); + self->base.type = &busio_jacdac_type; + + const mcu_pin_obj_t* bus = MP_OBJ_TO_PTR(DEFAULT_JACDAC_BUS); + + common_hal_busio_jacdac_construct(self, bus); + MP_STATE_VM(shared_jacdac_bus) = MP_OBJ_FROM_PTR(self); + return MP_STATE_VM(shared_jacdac_bus); +} +#endif +*/ void reset_board_busses(void) { #if BOARD_I2C bool display_using_i2c = false; @@ -181,4 +200,9 @@ void reset_board_busses(void) { #if BOARD_UART MP_STATE_VM(shared_uart_bus) = NULL; #endif +/* +#if BOARD_JACDAC + MP_STATE_VM(shared_jacdac_bus) = NULL; +#endif +*/ }