summaryrefslogtreecommitdiff
path: root/ports
diff options
context:
space:
mode:
authorJeff Epler <jepler@gmail.com>2020-10-21 10:38:01 -0500
committerJeff Epler <jepler@gmail.com>2020-10-26 09:54:58 -0500
commit396d92f867c66673c8dffc77f7c0cbcaa83a157f (patch)
tree98500b5dca6ac07e33e7615fd902a33cca718e49 /ports
parentfa4c4c2c10758a1af3d17a3853610ac9bbcc2a44 (diff)
esp32s2: Add canio
This works in loopback mode, though the hardware filtering only permits a single address or mask filter.
Diffstat (limited to 'ports')
-rw-r--r--ports/esp32s2/Makefile4
-rw-r--r--ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk2
-rw-r--r--ports/esp32s2/common-hal/canio/CAN.c297
-rw-r--r--ports/esp32s2/common-hal/canio/CAN.h52
-rw-r--r--ports/esp32s2/common-hal/canio/Listener.c182
-rw-r--r--ports/esp32s2/common-hal/canio/Listener.h40
-rw-r--r--ports/esp32s2/common-hal/canio/__init__.c25
-rw-r--r--ports/esp32s2/common-hal/canio/__init__.h27
-rw-r--r--ports/esp32s2/mpconfigport.mk1
9 files changed, 627 insertions, 3 deletions
diff --git a/ports/esp32s2/Makefile b/ports/esp32s2/Makefile
index 8d891edd0..5d8ccad50 100644
--- a/ports/esp32s2/Makefile
+++ b/ports/esp32s2/Makefile
@@ -331,10 +331,10 @@ $(BUILD)/firmware.uf2: $(BUILD)/circuitpython-firmware.bin
$(Q)$(PYTHON3) $(TOP)/tools/uf2/utils/uf2conv.py -f 0xbfdd4eee -b 0x0000 -c -o $@ $^
flash: $(BUILD)/firmware.bin
- esptool.py --chip esp32s2 -p $(PORT) --no-stub -b 460800 --before=default_reset --after=hard_reset write_flash $(FLASH_FLAGS) 0x0000 $^
+ esptool.py --chip esp32s2 -p $(PORT) -b 460800 --before=default_reset --after=hard_reset write_flash $(FLASH_FLAGS) 0x0000 $^
flash-circuitpython-only: $(BUILD)/circuitpython-firmware.bin
- esptool.py --chip esp32s2 -p $(PORT) --no-stub -b 460800 --before=default_reset --after=hard_reset write_flash $(FLASH_FLAGS) 0x10000 $^
+ esptool.py --chip esp32s2 -p $(PORT) -b 460800 --before=default_reset --after=hard_reset write_flash $(FLASH_FLAGS) 0x10000 $^
include $(TOP)/py/mkrules.mk
diff --git a/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk b/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk
index ba85e46ef..2dce03881 100644
--- a/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk
+++ b/ports/esp32s2/boards/espressif_kaluga_1/mpconfigboard.mk
@@ -11,7 +11,7 @@ LONGINT_IMPL = MPZ
CFLAGS += -DCFG_TUD_TASK_QUEUE_SZ=32
CIRCUITPY_ESP_FLASH_MODE=dio
-CIRCUITPY_ESP_FLASH_FREQ=40m
+CIRCUITPY_ESP_FLASH_FREQ=80m
CIRCUITPY_ESP_FLASH_SIZE=4MB
CIRCUITPY_MODULE=wrover
diff --git a/ports/esp32s2/common-hal/canio/CAN.c b/ports/esp32s2/common-hal/canio/CAN.c
new file mode 100644
index 000000000..f1741969d
--- /dev/null
+++ b/ports/esp32s2/common-hal/canio/CAN.c
@@ -0,0 +1,297 @@
+/*
+ * This file is part of the MicroPython 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 <string.h>
+
+#include "py/runtime.h"
+#include "py/mperrno.h"
+
+#include "common-hal/canio/CAN.h"
+#include "shared-bindings/microcontroller/Pin.h"
+#include "shared-bindings/util.h"
+#include "supervisor/port.h"
+#include "hal/twai_ll.h"
+
+#include "hal/twai_types.h"
+
+STATIC bool reserved_can;
+
+twai_timing_config_t get_t_config(int baudrate) {
+ switch(baudrate) {
+ case 1000000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1MBITS();
+ return t_config;
+ }
+ case 800000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_800KBITS();
+ return t_config;
+ }
+ case 500000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
+ return t_config;
+ }
+ case 250000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_250KBITS();
+ return t_config;
+ }
+ case 125000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_125KBITS();
+ return t_config;
+ }
+ case 100000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_100KBITS();
+ return t_config;
+ }
+ case 50000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_50KBITS();
+ return t_config;
+ }
+ case 25000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_25KBITS();
+ return t_config;
+ }
+ case 20000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_20KBITS();
+ return t_config;
+ }
+ case 16000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_16KBITS();
+ return t_config;
+ }
+ case 12500:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_12_5KBITS();
+ return t_config;
+ }
+ case 10000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_10KBITS();
+ return t_config;
+ }
+ case 5000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_5KBITS();
+ return t_config;
+ }
+ case 1000:
+ {
+ twai_timing_config_t t_config = TWAI_TIMING_CONFIG_1KBITS();
+ return t_config;
+ }
+ default:
+ mp_raise_ValueError(translate("Baudrate not supported by peripheral"));
+ }
+}
+
+__attribute__((optimize("O0")))
+void common_hal_canio_can_construct(canio_can_obj_t *self, mcu_pin_obj_t *tx, mcu_pin_obj_t *rx, int baudrate, bool loopback, bool silent)
+{
+#define DIV_ROUND(a, b) (((a) + (b)/2) / (b))
+#define DIV_ROUND_UP(a, b) (((a) + (b) - 1) / (b))
+ if (reserved_can) {
+ mp_raise_ValueError(translate("All CAN peripherals are in use"));
+ }
+
+ if (loopback && silent) {
+ mp_raise_ValueError(translate("loopback + silent mode not supported by peripheral"));
+ }
+
+ self->t_config = get_t_config(baudrate);;
+ twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(-1, -1, TWAI_MODE_NORMAL);
+ g_config.tx_io = tx->number;
+ g_config.rx_io = rx->number;
+ if (loopback) {
+ g_config.mode = TWAI_MODE_NO_ACK;
+ }
+ if (silent) {
+ g_config.mode = TWAI_MODE_LISTEN_ONLY;
+ }
+ self->g_config = g_config;
+
+ {
+ twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
+ self->f_config = f_config;
+ }
+
+ esp_err_t result = twai_driver_install(&self->g_config, &self->t_config, &self->f_config);
+ if (result == ESP_ERR_NO_MEM) {
+ mp_raise_msg(&mp_type_MemoryError, translate("ESP-IDF memory allocation failed"));
+ } else if (result == ESP_ERR_INVALID_ARG) {
+ mp_raise_ValueError(translate("Invalid pins"));
+ } else if (result != ESP_OK) {
+ mp_raise_OSError_msg_varg(translate("twai_driver_install returned esp-idf error #%d"), (int)result);
+ }
+
+ result = twai_start();
+ if (result != ESP_OK) {
+ mp_raise_OSError_msg_varg(translate("twai_start returned esp-idf error #%d"), (int)result);
+ }
+
+ self->silent = silent;
+ self->loopback = loopback;
+ self->baudrate = baudrate;
+ self->tx_pin = tx;
+ self->rx_pin = rx;
+
+ claim_pin(tx);
+ claim_pin(rx);
+
+ reserved_can = true;
+}
+
+bool common_hal_canio_can_loopback_get(canio_can_obj_t *self)
+{
+ return self->loopback;
+}
+
+int common_hal_canio_can_baudrate_get(canio_can_obj_t *self)
+{
+ return self->baudrate;
+}
+
+int common_hal_canio_can_transmit_error_count_get(canio_can_obj_t *self)
+{
+ twai_status_info_t info;
+ twai_get_status_info(&info);
+ return info.tx_error_counter;
+}
+
+int common_hal_canio_can_receive_error_count_get(canio_can_obj_t *self)
+{
+ twai_status_info_t info;
+ twai_get_status_info(&info);
+ return info.rx_error_counter;
+}
+
+canio_bus_state_t common_hal_canio_can_state_get(canio_can_obj_t *self) {
+ twai_status_info_t info;
+ twai_get_status_info(&info);
+ if (info.state == TWAI_STATE_BUS_OFF || info.state == TWAI_STATE_RECOVERING) {
+ return BUS_STATE_OFF;
+ }
+ if (info.tx_error_counter > 127 || info.rx_error_counter > 127) {
+ return BUS_STATE_ERROR_PASSIVE;
+ }
+ if (info.tx_error_counter > 96 || info.rx_error_counter > 96) {
+ return BUS_STATE_ERROR_WARNING;
+ }
+ return BUS_STATE_ERROR_ACTIVE;
+}
+
+static void can_restart(void) {
+ twai_status_info_t info;
+ twai_get_status_info(&info);
+ if (info.state != TWAI_STATE_BUS_OFF) {
+ return;
+ }
+ twai_initiate_recovery();
+ // wait 100ms (hard coded for now) for bus to recover
+ uint64_t deadline = port_get_raw_ticks(NULL) + 100;
+ do {
+ twai_get_status_info(&info);
+ } while (port_get_raw_ticks(NULL) < deadline && (info.state == TWAI_STATE_BUS_OFF || info.state == TWAI_STATE_RECOVERING));
+}
+
+void canio_maybe_auto_restart(canio_can_obj_t *self) {
+ if (self->auto_restart) can_restart();
+}
+
+void common_hal_canio_can_restart(canio_can_obj_t *self) {
+ if (!common_hal_canio_can_auto_restart_get(self)) {
+ can_restart();
+ }
+}
+
+bool common_hal_canio_can_auto_restart_get(canio_can_obj_t *self) {
+ return self->auto_restart;
+}
+
+void common_hal_canio_can_auto_restart_set(canio_can_obj_t *self, bool value) {
+ self->auto_restart = value;
+ canio_maybe_auto_restart(self);
+}
+
+void common_hal_canio_can_send(canio_can_obj_t *self, mp_obj_t message_in)
+{
+ canio_maybe_auto_restart(self);
+ canio_message_obj_t *message = message_in;
+ bool rtr = message->base.type == &canio_remote_transmission_request_type;
+ twai_message_t message_out = {
+ .extd = message->extended,
+ .rtr = rtr,
+ .self = self->loopback,
+ .identifier = message->id,
+ .data_length_code = message->size,
+ };
+ if (!rtr) {
+ memcpy(message_out.data, message->data, message->size);
+ }
+ // Allow transmission to occur in background
+ twai_transmit(&message_out, 0);
+}
+
+bool common_hal_canio_can_silent_get(canio_can_obj_t *self) {
+ return self->silent;
+}
+
+bool common_hal_canio_can_deinited(canio_can_obj_t *self) {
+ return !self->tx_pin;
+}
+
+void common_hal_canio_can_check_for_deinit(canio_can_obj_t *self) {
+ if (common_hal_canio_can_deinited(self)) {
+ raise_deinited_error();
+ }
+}
+
+void common_hal_canio_can_deinit(canio_can_obj_t *self)
+{
+ if (self->tx_pin) {
+ (void)twai_stop();
+ (void)twai_driver_uninstall();
+ reset_pin_number(self->tx_pin->number);
+ reset_pin_number(self->rx_pin->number);
+ reserved_can = false;
+ }
+ self->tx_pin = NULL;
+ self->rx_pin = NULL;
+}
+
+void common_hal_canio_reset(void) {
+ (void)twai_stop();
+ (void)twai_driver_uninstall();
+ reserved_can = false;
+}
diff --git a/ports/esp32s2/common-hal/canio/CAN.h b/ports/esp32s2/common-hal/canio/CAN.h
new file mode 100644
index 000000000..e15d51590
--- /dev/null
+++ b/ports/esp32s2/common-hal/canio/CAN.h
@@ -0,0 +1,52 @@
+/*
+ * This file is part of the MicroPython 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.
+ */
+
+#pragma once
+
+#include "py/obj.h"
+#include "shared-bindings/canio/__init__.h"
+#include "shared-bindings/canio/CAN.h"
+#include "common-hal/microcontroller/Pin.h"
+#include "common-hal/canio/__init__.h"
+#include "shared-module/canio/Message.h"
+
+#include "driver/twai.h"
+
+#define FILTER_BANK_COUNT (28)
+
+typedef struct canio_can_obj {
+ mp_obj_base_t base;
+ int baudrate;
+ const mcu_pin_obj_t *rx_pin;
+ const mcu_pin_obj_t *tx_pin;
+ bool loopback:1;
+ bool silent:1;
+ bool auto_restart:1;
+ bool fifo_in_use:1;
+ twai_filter_config_t f_config;
+ twai_general_config_t g_config;
+ twai_timing_config_t t_config;
+} canio_can_obj_t;
diff --git a/ports/esp32s2/common-hal/canio/Listener.c b/ports/esp32s2/common-hal/canio/Listener.c
new file mode 100644
index 000000000..fddbfeb58
--- /dev/null
+++ b/ports/esp32s2/common-hal/canio/Listener.c
@@ -0,0 +1,182 @@
+/*
+ * This file is part of the MicroPython 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 <math.h>
+#include <string.h>
+
+#include "py/obj.h"
+#include "py/runtime.h"
+
+#include "common-hal/canio/__init__.h"
+#include "common-hal/canio/Listener.h"
+#include "shared-bindings/canio/Listener.h"
+#include "shared-bindings/util.h"
+#include "supervisor/shared/tick.h"
+#include "supervisor/shared/safe_mode.h"
+
+#include "hal/twai_ll.h"
+
+// IDE = "extended ID" flag of packet header. We always add this bit to the
+// mask because a match is always for just one kind of address length
+#define FILTER16_IDE (1<<3)
+#define FILTER32_IDE (1<<2)
+
+// Work around a problem reported at
+// https://github.com/espressif/esp-idf/issues/6020 where
+// twai_ll_set_acc_filter does not work under -Os optimization
+__attribute__((optimize("O0")))
+__attribute__((noinline))
+static void canio_set_acc_filter(twai_dev_t* hw, uint32_t code, uint32_t mask, bool single_filter)
+{
+ uint32_t code_swapped = __builtin_bswap32(code);
+ uint32_t mask_swapped = __builtin_bswap32(mask);
+ for (int i = 0; i < 4; i++) {
+ hw->acceptance_filter.acr[i].val = ((code_swapped >> (i * 8)) & 0xFF);
+ hw->acceptance_filter.amr[i].val = ((mask_swapped >> (i * 8)) & 0xFF);
+ }
+ hw->mode_reg.afm = single_filter;
+}
+
+STATIC void install_standard_filter(canio_listener_obj_t *self, canio_match_obj_t *match) {
+ canio_set_acc_filter(&TWAI, match->id << 21, ~(match->mask << 21), true);
+ self->extended = false;
+ self->standard = true;
+}
+
+STATIC void install_extended_filter(canio_listener_obj_t *self, canio_match_obj_t *match) {
+ canio_set_acc_filter(&TWAI, match->id << 3, ~(match->mask << 3), true);
+ self->extended = true;
+ self->standard = false;
+}
+
+STATIC void install_all_match_filter(canio_listener_obj_t *self) {
+ canio_set_acc_filter(&TWAI, 0u, ~0u, true);
+ self->extended = true;
+ self->standard = true;
+}
+
+__attribute__((noinline,optimize("O0")))
+void set_filters(canio_listener_obj_t *self, size_t nmatch, canio_match_obj_t **matches) {
+ twai_ll_enter_reset_mode(&TWAI);
+
+ if (!nmatch) {
+ install_all_match_filter(self);
+ } else {
+ canio_match_obj_t *match = matches[0];
+ if (match->extended) {
+ install_extended_filter(self, match);
+ } else {
+ install_standard_filter(self, match);
+ }
+ }
+
+ twai_ll_exit_reset_mode(&TWAI);
+}
+
+
+void common_hal_canio_listener_construct(canio_listener_obj_t *self, canio_can_obj_t *can, size_t nmatch, canio_match_obj_t **matches, float timeout) {
+ if (can->fifo_in_use) {
+ mp_raise_ValueError(translate("All RX FIFOs in use"));
+ }
+ if (nmatch > 1) {
+ mp_raise_ValueError(translate("Filters too complex"));
+ }
+
+ // Nothing can fail now so it's safe to assign self->can
+ can->fifo_in_use = 1;
+ self->can = can;
+ self->pending = false;
+
+ set_filters(self, nmatch, matches);
+ self->extended = self->standard = true;
+
+ common_hal_canio_listener_set_timeout(self, timeout);
+}
+
+void common_hal_canio_listener_set_timeout(canio_listener_obj_t *self, float timeout) {
+ self->timeout_ms = (int)MICROPY_FLOAT_C_FUN(ceil)(timeout * 1000);
+}
+
+float common_hal_canio_listener_get_timeout(canio_listener_obj_t *self) {
+ return self->timeout_ms / 1000.0f;
+}
+
+void common_hal_canio_listener_check_for_deinit(canio_listener_obj_t *self) {
+ if (!self->can) {
+ raise_deinited_error();
+ }
+ common_hal_canio_can_check_for_deinit(self->can);
+}
+
+// The API has no peek method so we must receive a packet into a holding area
+// and then we can say that we have 1 message pending
+int common_hal_canio_listener_in_waiting(canio_listener_obj_t *self) {
+ while (!self->pending && twai_receive(&self->message_in, 0) == ESP_OK) {
+ if (self->message_in.extd && self->extended) {
+ self->pending = true;
+ }
+ if (!self->message_in.extd && self->standard) {
+ self->pending = true;
+ }
+ }
+ return self->pending;
+}
+
+mp_obj_t common_hal_canio_listener_receive(canio_listener_obj_t *self) {
+ if (!common_hal_canio_listener_in_waiting(self)) {
+ uint64_t deadline = supervisor_ticks_ms64() + self->timeout_ms;
+ do {
+ if (supervisor_ticks_ms64() > deadline) {
+ return NULL;
+ }
+ } while (!common_hal_canio_listener_in_waiting(self));
+ }
+
+ bool rtr = self->message_in.rtr;
+
+ int dlc = self->message_in.data_length_code;
+ canio_message_obj_t *message = m_new_obj(canio_message_obj_t);
+ message->base.type = rtr ? &canio_remote_transmission_request_type : &canio_message_type;
+ message->extended = self->message_in.extd;
+ message->id = self->message_in.identifier;
+ message->size = dlc;
+
+ if (!rtr) {
+ MP_STATIC_ASSERT(sizeof(self->message_in.data) == sizeof(message->data));
+ memcpy(message->data, self->message_in.data, sizeof(message->data));
+ }
+
+ self->pending = false;
+
+ return message;
+}
+
+void common_hal_canio_listener_deinit(canio_listener_obj_t *self) {
+ if (self->can) {
+ self->can->fifo_in_use = false;
+ }
+ self->can = NULL;
+}
diff --git a/ports/esp32s2/common-hal/canio/Listener.h b/ports/esp32s2/common-hal/canio/Listener.h
new file mode 100644
index 000000000..d24900e43
--- /dev/null
+++ b/ports/esp32s2/common-hal/canio/Listener.h
@@ -0,0 +1,40 @@
+/*
+ * This file is part of the MicroPython 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.
+ */
+
+#pragma once
+
+#include "common-hal/canio/CAN.h"
+#include "shared-module/canio/Match.h"
+
+typedef struct canio_listener_obj {
+ mp_obj_base_t base;
+ canio_can_obj_t *can;
+ bool extended:1;
+ bool standard:1;
+ bool pending:1;
+ twai_message_t message_in;
+ uint32_t timeout_ms;
+} canio_listener_obj_t;
diff --git a/ports/esp32s2/common-hal/canio/__init__.c b/ports/esp32s2/common-hal/canio/__init__.c
new file mode 100644
index 000000000..7932bfc2d
--- /dev/null
+++ b/ports/esp32s2/common-hal/canio/__init__.c
@@ -0,0 +1,25 @@
+/*
+ * This file is part of the MicroPython 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.
+ */
diff --git a/ports/esp32s2/common-hal/canio/__init__.h b/ports/esp32s2/common-hal/canio/__init__.h
new file mode 100644
index 000000000..20b6638cd
--- /dev/null
+++ b/ports/esp32s2/common-hal/canio/__init__.h
@@ -0,0 +1,27 @@
+/*
+ * This file is part of the MicroPython 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.
+ */
+
+#pragma once
diff --git a/ports/esp32s2/mpconfigport.mk b/ports/esp32s2/mpconfigport.mk
index 4e8a7bdef..c9863572a 100644
--- a/ports/esp32s2/mpconfigport.mk
+++ b/ports/esp32s2/mpconfigport.mk
@@ -16,6 +16,7 @@ LONGINT_IMPL = MPZ
CIRCUITPY_FULL_BUILD = 1
CIRCUITPY_AUDIOBUSIO = 0
CIRCUITPY_AUDIOIO = 0
+CIRCUITPY_CANIO = 1
CIRCUITPY_COUNTIO = 0
CIRCUITPY_FREQUENCYIO = 0
CIRCUITPY_I2CPERIPHERAL = 0