From df6b40a87fe3ddcb37cde488ba1a2060625ab737 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 14:27:01 +1100 Subject: [PATCH 01/46] esp32: Workaround native code execution crash on ESP32-S2. Seemingly ESP-IDF incorrectly marks RTC FAST memory region as MALLOC_CAP_EXEC on ESP32-S2 when it isn't. This memory is the lowest priority, so it only is returned if D/IRAM is exhausted. Apply this workaround to treat the allocation as failed if it gives us non-executable RAM back, rather than crashing. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/main.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ports/esp32/main.c b/ports/esp32/main.c index 03dc0807a025..18ef9d735479 100644 --- a/ports/esp32/main.c +++ b/ports/esp32/main.c @@ -39,6 +39,7 @@ #include "esp_task.h" #include "esp_event.h" #include "esp_log.h" +#include "esp_memory_utils.h" #include "esp_psram.h" #include "py/cstack.h" @@ -237,6 +238,13 @@ void *esp_native_code_commit(void *buf, size_t len, void *reloc) { len = (len + 3) & ~3; size_t len_node = sizeof(native_code_node_t) + len; native_code_node_t *node = heap_caps_malloc(len_node, MALLOC_CAP_EXEC); + #if CONFIG_IDF_TARGET_ESP32S2 + // Workaround for ESP-IDF bug https://github.com/espressif/esp-idf/issues/14835 + if (node != NULL && !esp_ptr_executable(node)) { + free(node); + node = NULL; + } + #endif // CONFIG_IDF_TARGET_ESP32S2 if (node == NULL) { m_malloc_fail(len_node); } From 48f96e96605c020a646adfc2d06ee0db9b87fbe8 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:17:32 +1100 Subject: [PATCH 02/46] docs: Specify the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from the docs. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 11 ++++++----- docs/esp8266/quickref.rst | 8 ++++---- docs/esp8266/tutorial/network_basics.rst | 13 +++++++------ docs/library/espnow.rst | 24 ++++++++++++------------ docs/library/network.WLAN.rst | 8 ++++---- docs/reference/mpremote.rst | 2 +- 6 files changed, 34 insertions(+), 32 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index 3ab4e8f5ecd9..b9ca0f8225f8 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -79,11 +79,11 @@ Networking WLAN ^^^^ -The :mod:`network` module:: +The :class:`network.WLAN` class in the :mod:`network` module:: import network - wlan = network.WLAN(network.STA_IF) # create station interface + wlan = network.WLAN(network.WLAN.IF_STA) # create station interface wlan.active(True) # activate the interface wlan.scan() # scan for access points wlan.isconnected() # check if the station is connected to an AP @@ -91,7 +91,7 @@ The :mod:`network` module:: wlan.config('mac') # get the interface's MAC address wlan.ipconfig('addr4') # get the interface's IPv4 addresses - ap = network.WLAN(network.AP_IF) # create access-point interface + ap = network.WLAN(network.WLAN.IF_AP) # create access-point interface ap.config(ssid='ESP-AP') # set the SSID of the access point ap.config(max_clients=10) # set how many clients can connect to the network ap.active(True) # activate the interface @@ -100,7 +100,7 @@ A useful function for connecting to your local WiFi network is:: def do_connect(): import network - wlan = network.WLAN(network.STA_IF) + wlan = network.WLAN(network.WLAN.IF_STA) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') @@ -124,7 +124,8 @@ to reconnect forever). LAN ^^^ -To use the wired interfaces one has to specify the pins and mode :: +To use the wired interfaces via :class:`network.LAN` one has to specify the pins +and mode :: import network diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index b130ce65db2b..6f02da95d89d 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -49,11 +49,11 @@ The :mod:`esp` module:: Networking ---------- -The :mod:`network` module:: +The :class:`network.WLAN` class in the :mod:`network` module:: import network - wlan = network.WLAN(network.STA_IF) # create station interface + wlan = network.WLAN(network.WLAN.IF_STA) # create station interface wlan.active(True) # activate the interface wlan.scan() # scan for access points wlan.isconnected() # check if the station is connected to an AP @@ -61,7 +61,7 @@ The :mod:`network` module:: wlan.config('mac') # get the interface's MAC address wlan.ipconfig('addr4') # get the interface's IPv4 addresses - ap = network.WLAN(network.AP_IF) # create access-point interface + ap = network.WLAN(network.WLAN.IF_AP) # create access-point interface ap.active(True) # activate the interface ap.config(ssid='ESP-AP') # set the SSID of the access point @@ -69,7 +69,7 @@ A useful function for connecting to your local WiFi network is:: def do_connect(): import network - wlan = network.WLAN(network.STA_IF) + wlan = network.WLAN(network.WLAN.IF_STA) wlan.active(True) if not wlan.isconnected(): print('connecting to network...') diff --git a/docs/esp8266/tutorial/network_basics.rst b/docs/esp8266/tutorial/network_basics.rst index 9d74a6283ac4..e383c00c6cee 100644 --- a/docs/esp8266/tutorial/network_basics.rst +++ b/docs/esp8266/tutorial/network_basics.rst @@ -1,14 +1,15 @@ Network basics ============== -The network module is used to configure the WiFi connection. There are two WiFi -interfaces, one for the station (when the ESP8266 connects to a router) and one -for the access point (for other devices to connect to the ESP8266). Create +The :class:`network.WLAN` class in the :mod:`network` module is used to +configure the WiFi connection. There are two WiFi interfaces, one for +the station (when the ESP8266 connects to a router) and one for the +access point (for other devices to connect to the ESP8266). Create instances of these objects using:: >>> import network - >>> sta_if = network.WLAN(network.STA_IF) - >>> ap_if = network.WLAN(network.AP_IF) + >>> sta_if = network.WLAN(network.WLAN.IF_STA) + >>> ap_if = network.WLAN(network.WLAN.IF_AP) You can check if the interfaces are active by:: @@ -57,7 +58,7 @@ connect to your WiFi network:: def do_connect(): import network - sta_if = network.WLAN(network.STA_IF) + sta_if = network.WLAN(network.WLAN.IF_STA) if not sta_if.isconnected(): print('connecting to network...') sta_if.active(True) diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index f0b592dffc8a..1bcc9d7623d1 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -56,7 +56,7 @@ A simple example would be: import espnow # A WLAN interface must be active to send()/recv() - sta = network.WLAN(network.STA_IF) # Or network.AP_IF + sta = network.WLAN(network.WLAN.IF_STA) # Or network.WLAN.IF_AP sta.active(True) sta.disconnect() # For ESP8266 @@ -76,7 +76,7 @@ A simple example would be: import espnow # A WLAN interface must be active to send()/recv() - sta = network.WLAN(network.STA_IF) + sta = network.WLAN(network.WLAN.IF_STA) sta.active(True) sta.disconnect() # Because ESP8266 auto-connects to last Access Point @@ -182,14 +182,14 @@ Configuration Sending and Receiving Data -------------------------- -A wifi interface (``network.STA_IF`` or ``network.AP_IF``) must be +A wifi interface (``network.WLAN.IF_STA`` or ``network.WLAN.IF_AP``) must be `active()` before messages can be sent or received, but it is not necessary to connect or configure the WLAN interface. For example:: import network - sta = network.WLAN(network.STA_IF) + sta = network.WLAN(network.WLAN.IF_STA) sta.active(True) sta.disconnect() # For ESP8266 @@ -445,8 +445,8 @@ must first register the sender and use the same encryption keys as the sender - *ifidx*: (ESP32 only) Index of the wifi interface which will be used to send data to this peer. Must be an integer set to - ``network.STA_IF`` (=0) or ``network.AP_IF`` (=1). - (default=0/``network.STA_IF``). See `ESPNow and Wifi Operation`_ + ``network.WLAN.IF_STA`` (=0) or ``network.WLAN.IF_AP`` (=1). + (default=0/``network.WLAN.IF_STA``). See `ESPNow and Wifi Operation`_ below for more information. - *encrypt*: (ESP32 only) If set to ``True`` data exchanged with @@ -588,7 +588,7 @@ api-reference/network/esp_now.html#api-reference>`_. For example:: elif err.args[1] == 'ESP_ERR_ESPNOW_NOT_FOUND': e.add_peer(peer) elif err.args[1] == 'ESP_ERR_ESPNOW_IF': - network.WLAN(network.STA_IF).active(True) + network.WLAN(network.WLAN.IF_STA).active(True) else: raise err @@ -645,7 +645,7 @@ A small async server example:: import asyncio # A WLAN interface must be active to send()/recv() - network.WLAN(network.STA_IF).active(True) + network.WLAN(network.WLAN.IF_STA).active(True) e = aioespnow.AIOESPNow() # Returns AIOESPNow enhanced with async support e.active(True) @@ -747,8 +747,8 @@ ESPNow and Wifi Operation ------------------------- ESPNow messages may be sent and received on any `active()` -`WLAN` interface (``network.STA_IF`` or ``network.AP_IF``), even -if that interface is also connected to a wifi network or configured as an access +`WLAN` interface (``network.WLAN.IF_STA`` or ``network.WLAN.IF_AP``), +even if that interface is also connected to a wifi network or configured as an access point. When an ESP32 or ESP8266 device connects to a Wifi Access Point (see `ESP32 Quickref <../esp32/quickref.html#networking>`__) the following things happen which affect ESPNow communications: @@ -832,8 +832,8 @@ Other issues to take care with when using ESPNow with wifi are: import network, time def wifi_reset(): # Reset wifi to AP_IF off, STA_IF on and disconnected - sta = network.WLAN(network.STA_IF); sta.active(False) - ap = network.WLAN(network.AP_IF); ap.active(False) + sta = network.WLAN(network.WLAN.IF_STA); sta.active(False) + ap = network.WLAN(network.WLAN.IF_AP); ap.active(False) sta.active(True) while not sta.active(): time.sleep(0.1) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index c1eb520961fc..3c401acb420b 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -8,7 +8,7 @@ This class provides a driver for WiFi network processors. Example usage:: import network # enable station interface and connect to WiFi access point - nic = network.WLAN(network.STA_IF) + nic = network.WLAN(network.WLAN.IF_STA) nic.active(True) nic.connect('your-ssid', 'your-key') # now use sockets as usual @@ -18,8 +18,8 @@ Constructors .. class:: WLAN(interface_id) Create a WLAN network interface object. Supported interfaces are -``network.STA_IF`` (station aka client, connects to upstream WiFi access -points) and ``network.AP_IF`` (access point, allows other WiFi clients to +``network.WLAN.IF_STA`` (station aka client, connects to upstream WiFi access +points) and ``network.WLAN.IF_AP`` (access point, allows other WiFi clients to connect). Availability of the methods below depends on interface type. For example, only STA interface may `WLAN.connect()` to an access point. @@ -75,7 +75,7 @@ Methods Return the current status of the wireless connection. When called with no argument the return value describes the network link status. - The possible statuses are defined as constants: + The possible statuses are defined as constants in the :mod:`network` module: * ``STAT_IDLE`` -- no connection and no activity, * ``STAT_CONNECTING`` -- connecting in progress, diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index 4d86f0080b11..b47ec76c98ef 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -477,7 +477,7 @@ An example ``config.py`` might look like: """,], # Print out nearby WiFi networks. "wl_ipconfig": [ "exec", - "import network; sta_if = network.WLAN(network.STA_IF); print(sta_if.ipconfig('addr4'))", + "import network; sta_if = network.WLAN(network.WLAN.IF_STA); print(sta_if.ipconfig('addr4'))", """,], # Print ip address of station interface. "test": ["mount", ".", "exec", "import test"], # Mount current directory and run test.py. "demo": ["run", "path/to/demo.py"], # Execute demo.py on the device. From 285e1d0b80d821ba910249e8804697ec1739bac7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:18:37 +1100 Subject: [PATCH 03/46] esp32: Use the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from the esp32 port This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/README.md | 2 +- ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py | 2 +- ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py | 2 +- ports/esp32/help.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ports/esp32/README.md b/ports/esp32/README.md index a04ad0c6eea1..4eb791389380 100644 --- a/ports/esp32/README.md +++ b/ports/esp32/README.md @@ -195,7 +195,7 @@ quickly call `wlan_connect()` and it just works): ```python def wlan_connect(ssid='MYSSID', password='MYPASS'): import network - wlan = network.WLAN(network.STA_IF) + wlan = network.WLAN(network.WLAN.IF_STA) if not wlan.active() or not wlan.isconnected(): wlan.active(True) print('connecting to:', ssid) diff --git a/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py b/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py index bfe02c35765b..98ea0adcbf86 100644 --- a/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py +++ b/ports/esp32/boards/LILYGO_TTGO_LORA32/modules/lilygo_oled.py @@ -30,7 +30,7 @@ def display_wifi(self): self.text("Scan...", 0, 0, 1) self.show() - sta_if = network.WLAN(network.STA_IF) + sta_if = network.WLAN(network.WLAN.IF_STA) sta_if.active(True) _wifi = sta_if.scan() diff --git a/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py index 37dc5a340fe4..58120d44adf7 100644 --- a/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py +++ b/ports/esp32/boards/LOLIN_S2_PICO/modules/s2pico_oled.py @@ -37,7 +37,7 @@ def display_wifi(self): self.text("Scan...", 0, 0, 1) self.show() - sta_if = network.WLAN(network.STA_IF) + sta_if = network.WLAN(network.WLAN.IF_STA) sta_if.active(True) _wifi = sta_if.scan() diff --git a/ports/esp32/help.c b/ports/esp32/help.c index 2351d7dc7394..62639a0b408c 100644 --- a/ports/esp32/help.c +++ b/ports/esp32/help.c @@ -48,7 +48,7 @@ const char esp32_help_text[] = "Basic WiFi configuration:\n" "\n" "import network\n" - "sta_if = network.WLAN(network.STA_IF); sta_if.active(True)\n" + "sta_if = network.WLAN(network.WLAN.IF_STA); sta_if.active(True)\n" "sta_if.scan() # Scan for available access points\n" "sta_if.connect(\"\", \"\") # Connect to an AP\n" "sta_if.isconnected() # Check for successful connection\n" From f5b81bee6141f925ed32d2d7968b0c90d7b41b61 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:19:00 +1100 Subject: [PATCH 04/46] esp8266: Use the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from the esp8266 port This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp8266/help.c | 4 ++-- ports/esp8266/modules/inisetup.py | 2 +- ports/esp8266/modules/port_diag.py | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/ports/esp8266/help.c b/ports/esp8266/help.c index a9cb27bad51c..694cd90b951d 100644 --- a/ports/esp8266/help.c +++ b/ports/esp8266/help.c @@ -36,12 +36,12 @@ const char esp_help_text[] = "Basic WiFi configuration:\n" "\n" "import network\n" - "sta_if = network.WLAN(network.STA_IF); sta_if.active(True)\n" + "sta_if = network.WLAN(network.WLAN.IF_STA); sta_if.active(True)\n" "sta_if.scan() # Scan for available access points\n" "sta_if.connect(\"\", \"\") # Connect to an AP\n" "sta_if.isconnected() # Check for successful connection\n" "# Change name/password of ESP8266's AP:\n" - "ap_if = network.WLAN(network.AP_IF)\n" + "ap_if = network.WLAN(network.WLAN.IF_AP)\n" "ap_if.config(ssid=\"\", security=network.AUTH_WPA_WPA2_PSK, key=\"\")\n" "\n" "Control commands:\n" diff --git a/ports/esp8266/modules/inisetup.py b/ports/esp8266/modules/inisetup.py index 20bb28e80b06..f97c23852002 100644 --- a/ports/esp8266/modules/inisetup.py +++ b/ports/esp8266/modules/inisetup.py @@ -6,7 +6,7 @@ def wifi(): import binascii - ap_if = network.WLAN(network.AP_IF) + ap_if = network.WLAN(network.WLAN.IF_AP) ssid = b"MicroPython-%s" % binascii.hexlify(ap_if.config("mac")[-3:]) ap_if.config(ssid=ssid, security=network.AUTH_WPA_WPA2_PSK, key=b"micropythoN") diff --git a/ports/esp8266/modules/port_diag.py b/ports/esp8266/modules/port_diag.py index 4eea6a6d90d3..bbd6225a616f 100644 --- a/ports/esp8266/modules/port_diag.py +++ b/ports/esp8266/modules/port_diag.py @@ -23,8 +23,8 @@ def main(): print(esp.check_fw()) print("\nNetworking:") - print("STA ifconfig:", network.WLAN(network.STA_IF).ifconfig()) - print("AP ifconfig:", network.WLAN(network.AP_IF).ifconfig()) + print("STA ifconfig:", network.WLAN(network.WLAN.IF_STA).ifconfig()) + print("AP ifconfig:", network.WLAN(network.WLAN.IF_AP).ifconfig()) print("Free WiFi driver buffers of type:") for i, comm in enumerate( ("1,2 TX", "4 Mngmt TX(len: 0x41-0x100)", "5 Mngmt TX (len: 0-0x40)", "7", "8 RX") From 5dfbd4371480c175e6f54e57e4e5eb5d579cbd00 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 5 Nov 2024 11:19:19 +1100 Subject: [PATCH 05/46] tests: Use the recommended network.WLAN.IF_[AP|STA] constants. Removes the deprecated network.[AP|STA]_IF form from unit tests. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/multi_espnow/10_simple_data.py | 2 +- tests/multi_espnow/20_send_echo.py | 2 +- tests/multi_espnow/30_lmk_echo.py | 2 +- tests/multi_espnow/40_recv_test.py | 2 +- tests/multi_espnow/50_esp32_rssi_test.py | 2 +- tests/multi_espnow/60_irq_test.py | 2 +- tests/multi_espnow/80_asyncio_client.py | 2 +- tests/multi_espnow/81_asyncio_server.py | 2 +- tests/multi_espnow/90_memory_test.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/tests/multi_espnow/10_simple_data.py b/tests/multi_espnow/10_simple_data.py index 1d218fe98130..00d69f30d915 100644 --- a/tests/multi_espnow/10_simple_data.py +++ b/tests/multi_espnow/10_simple_data.py @@ -14,7 +14,7 @@ def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/20_send_echo.py b/tests/multi_espnow/20_send_echo.py index 4a1d1624d843..4c325bf68c51 100644 --- a/tests/multi_espnow/20_send_echo.py +++ b/tests/multi_espnow/20_send_echo.py @@ -61,7 +61,7 @@ def echo_client(e, peer, msglens): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/30_lmk_echo.py b/tests/multi_espnow/30_lmk_echo.py index ac8908049250..2a6c77c6331d 100644 --- a/tests/multi_espnow/30_lmk_echo.py +++ b/tests/multi_espnow/30_lmk_echo.py @@ -92,7 +92,7 @@ def echo_client(e, peer, msglens): # Initialise the wifi and espnow hardware and software def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/40_recv_test.py b/tests/multi_espnow/40_recv_test.py index 46f4f78df489..554db16ac171 100644 --- a/tests/multi_espnow/40_recv_test.py +++ b/tests/multi_espnow/40_recv_test.py @@ -48,7 +48,7 @@ def client_send(e, peer, msg, sync): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/50_esp32_rssi_test.py b/tests/multi_espnow/50_esp32_rssi_test.py index 6a47b540d35e..8aded1c09940 100644 --- a/tests/multi_espnow/50_esp32_rssi_test.py +++ b/tests/multi_espnow/50_esp32_rssi_test.py @@ -49,7 +49,7 @@ def client_send(e, peer, msg, sync): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/60_irq_test.py b/tests/multi_espnow/60_irq_test.py index 37fc57ce4ae7..db8b8168690e 100644 --- a/tests/multi_espnow/60_irq_test.py +++ b/tests/multi_espnow/60_irq_test.py @@ -49,7 +49,7 @@ def client_send(e, peer, msg, sync): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) diff --git a/tests/multi_espnow/80_asyncio_client.py b/tests/multi_espnow/80_asyncio_client.py index 4d58a451c1c0..8c34b98a3c4a 100644 --- a/tests/multi_espnow/80_asyncio_client.py +++ b/tests/multi_espnow/80_asyncio_client.py @@ -50,7 +50,7 @@ def client_send(e, peer, msg, sync): def init(e, sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e.active(True) e.set_pmk(default_pmk) wlans[0].active(sta_active) diff --git a/tests/multi_espnow/81_asyncio_server.py b/tests/multi_espnow/81_asyncio_server.py index df1dbb2ac8aa..e6002582c085 100644 --- a/tests/multi_espnow/81_asyncio_server.py +++ b/tests/multi_espnow/81_asyncio_server.py @@ -30,7 +30,7 @@ def client_send(e, peer, msg, sync): def init(e, sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e.active(True) e.set_pmk(default_pmk) wlans[0].active(sta_active) diff --git a/tests/multi_espnow/90_memory_test.py b/tests/multi_espnow/90_memory_test.py index 5e80eb0fdf66..b59ff61b594a 100644 --- a/tests/multi_espnow/90_memory_test.py +++ b/tests/multi_espnow/90_memory_test.py @@ -65,7 +65,7 @@ def echo_client(e, peer, msglens): def init(sta_active=True, ap_active=False): - wlans = [network.WLAN(i) for i in [network.STA_IF, network.AP_IF]] + wlans = [network.WLAN(i) for i in [network.WLAN.IF_STA, network.WLAN.IF_AP]] e = espnow.ESPNow() e.active(True) e.set_pmk(default_pmk) From 3844733d604d83fbfec5592cee20aab065ebff48 Mon Sep 17 00:00:00 2001 From: Jeff Epler Date: Sat, 2 Nov 2024 13:43:35 -0500 Subject: [PATCH 06/46] tests/cpydiff: Fix test case for modules_json_nonserializable. The test case was producing the following error: Traceback (most recent call last): File "", line 12, in UnicodeError: which did not demonstrate the intended difference (this particular non-json-serializable object DID throw an exception! just not TypeError). The updated test uses a byte string with all ASCII bytes inside, which better illustrates the diference. Signed-off-by: Jeff Epler --- tests/cpydiff/modules_json_nonserializable.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tests/cpydiff/modules_json_nonserializable.py b/tests/cpydiff/modules_json_nonserializable.py index ffe523786f50..d6a5660cadfd 100644 --- a/tests/cpydiff/modules_json_nonserializable.py +++ b/tests/cpydiff/modules_json_nonserializable.py @@ -6,10 +6,7 @@ """ import json -a = bytes(x for x in range(256)) try: - z = json.dumps(a) - x = json.loads(z) - print("Should not get here") + print(json.dumps(b"shouldn't be able to serialise bytes")) except TypeError: print("TypeError") From eab2869990b83dc6baa451f8c4983a312539a0be Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Nov 2024 11:18:42 +1100 Subject: [PATCH 07/46] extmod/modlwip: Don't allow writing to a TCP socket that is connecting. This follows the behaviour of unix MicroPython (POSIX sockets) and the esp32 port. Signed-off-by: Damien George --- extmod/modlwip.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/extmod/modlwip.c b/extmod/modlwip.c index 0d38bf41b205..ce70627c31c6 100644 --- a/extmod/modlwip.c +++ b/extmod/modlwip.c @@ -701,7 +701,12 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui MICROPY_PY_LWIP_ENTER - u16_t available = tcp_sndbuf(socket->pcb.tcp); + // If the socket is still connecting then don't let data be written to it. + // Otherwise, get the number of available bytes in the output buffer. + u16_t available = 0; + if (socket->state != STATE_CONNECTING) { + available = tcp_sndbuf(socket->pcb.tcp); + } if (available == 0) { // Non-blocking socket @@ -718,7 +723,8 @@ static mp_uint_t lwip_tcp_send(lwip_socket_obj_t *socket, const byte *buf, mp_ui // If peer fully closed socket, we would have socket->state set to ERR_RST (connection // reset) by error callback. // Avoid sending too small packets, so wait until at least 16 bytes available - while (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16) { + while (socket->state == STATE_CONNECTING + || (socket->state >= STATE_CONNECTED && (available = tcp_sndbuf(socket->pcb.tcp)) < 16)) { MICROPY_PY_LWIP_EXIT if (socket->timeout != -1 && mp_hal_ticks_ms() - start > socket->timeout) { *_errno = MP_ETIMEDOUT; @@ -1548,7 +1554,7 @@ static mp_uint_t lwip_socket_ioctl(mp_obj_t self_in, mp_uint_t request, uintptr_ // raw socket is writable ret |= MP_STREAM_POLL_WR; #endif - } else if (socket->pcb.tcp != NULL && tcp_sndbuf(socket->pcb.tcp) > 0) { + } else if (socket->state != STATE_CONNECTING && socket->pcb.tcp != NULL && tcp_sndbuf(socket->pcb.tcp) > 0) { // TCP socket is writable // Note: pcb.tcp==NULL if state<0, and in this case we can't call tcp_sndbuf ret |= MP_STREAM_POLL_WR; From 69023622ee837df051ef7e606c2f0511015d01f7 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Nov 2024 11:19:45 +1100 Subject: [PATCH 08/46] tests/net_hosted: Improve and simplify non-block-xfer test. CPython changed its non-blocking socket behaviour recently and this test would not run under CPython anymore. So the following steps were taken to get the test working again and then simplify it: - Run the test against CPython 3.10.10 and capture the output into the .exp file for the test. - Run this test on unix port of MicroPython and verify that the output matches the CPython 3.10.10 output in the new .exp file (it did). From now on take unix MicroPython as the source of truth for this test when modifying it. - Remove all code that was there for CPython compatibility. - Make it print out more useful information during the test run, including names of the OSError errno values. - Add polling of the socket before the send/write/recv/read to verify that the poll gives the correct result in non-blocking mode. Tested on unix MicroPython, ESP32_GENERIC, PYBD_SF2 and RPI_PICO_W boards. Signed-off-by: Damien George --- tests/net_hosted/connect_nonblock_xfer.py | 130 +++++++----------- tests/net_hosted/connect_nonblock_xfer.py.exp | 44 ++++++ 2 files changed, 94 insertions(+), 80 deletions(-) create mode 100644 tests/net_hosted/connect_nonblock_xfer.py.exp diff --git a/tests/net_hosted/connect_nonblock_xfer.py b/tests/net_hosted/connect_nonblock_xfer.py index dc4693cea6a8..23620908afbc 100644 --- a/tests/net_hosted/connect_nonblock_xfer.py +++ b/tests/net_hosted/connect_nonblock_xfer.py @@ -1,15 +1,24 @@ # test that socket.connect() on a non-blocking socket raises EINPROGRESS # and that an immediate write/send/read/recv does the right thing -import sys, time, socket, errno, ssl +import errno +import select +import socket +import ssl -isMP = sys.implementation.name == "micropython" +# only mbedTLS supports non-blocking mode +if not hasattr(ssl, "MBEDTLS_VERSION"): + print("SKIP") + raise SystemExit -def dp(e): - # uncomment next line for development and testing, to print the actual exceptions - # print(repr(e)) - pass +# get the name of an errno error code +def errno_name(er): + if er == errno.EAGAIN: + return "EAGAIN" + if er == errno.EINPROGRESS: + return "EINPROGRESS" + return er # do_connect establishes the socket and wraps it if tls is True. @@ -22,112 +31,75 @@ def do_connect(peer_addr, tls, handshake): # print("Connecting to", peer_addr) s.connect(peer_addr) except OSError as er: - print("connect:", er.errno == errno.EINPROGRESS) - if er.errno != errno.EINPROGRESS: - print(" got", er.errno) + print("connect:", errno_name(er.errno)) # wrap with ssl/tls if desired if tls: ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT) - if hasattr(ssl_context, "check_hostname"): - ssl_context.check_hostname = False - try: s = ssl_context.wrap_socket(s, do_handshake_on_connect=handshake) - print("wrap: True") + print("wrap ok: True") except Exception as e: - dp(e) - print("wrap:", e) - elif handshake: - # just sleep a little bit, this allows any connect() errors to happen - time.sleep(0.2) + print("wrap er:", e) return s +# poll a socket and print out the result +def poll(s): + poller = select.poll() + poller.register(s) + print("poll: ", poller.poll(0)) + + # test runs the test against a specific peer address. -def test(peer_addr, tls=False, handshake=False): - # MicroPython plain sockets have read/write, but CPython's don't - # MicroPython TLS sockets and CPython's have read/write - # hasRW captures this wonderful state of affairs - hasRW = isMP or tls +def test(peer_addr, tls, handshake): + # MicroPython plain and TLS sockets have read/write + hasRW = True - # MicroPython plain sockets and CPython's have send/recv - # MicroPython TLS sockets don't have send/recv, but CPython's do - # hasSR captures this wonderful state of affairs - hasSR = not (isMP and tls) + # MicroPython plain sockets have send/recv + # MicroPython TLS sockets don't have send/recv + hasSR = not tls # connect + send + # non-blocking send should raise EAGAIN if hasSR: s = do_connect(peer_addr, tls, handshake) - # send -> 4 or EAGAIN + poll(s) try: ret = s.send(b"1234") - print("send:", handshake and ret == 4) + print("send ok:", ret) # shouldn't get here except OSError as er: - # - dp(er) - print("send:", er.errno in (errno.EAGAIN, errno.EINPROGRESS)) + print("send er:", errno_name(er.errno)) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("send:", True) # connect + write + # non-blocking write should return None if hasRW: s = do_connect(peer_addr, tls, handshake) - # write -> None - try: - ret = s.write(b"1234") - print("write:", ret in (4, None)) # SSL may accept 4 into buffer - except OSError as er: - dp(er) - print("write:", False) # should not raise - except ValueError as er: # CPython - dp(er) - print("write:", er.args[0] == "Write on closed or unwrapped SSL socket.") + poll(s) + ret = s.write(b"1234") + print("write: ", ret) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("write:", True) + # connect + recv + # non-blocking recv should raise EAGAIN if hasSR: - # connect + recv s = do_connect(peer_addr, tls, handshake) - # recv -> EAGAIN + poll(s) try: - print("recv:", s.recv(10)) + ret = s.recv(10) + print("recv ok:", ret) # shouldn't get here except OSError as er: - dp(er) - print("recv:", er.errno == errno.EAGAIN) + print("recv er:", errno_name(er.errno)) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("recv:", True) # connect + read + # non-blocking read should return None if hasRW: s = do_connect(peer_addr, tls, handshake) - # read -> None - try: - ret = s.read(10) - print("read:", ret is None) - except OSError as er: - dp(er) - print("read:", False) # should not raise - except ValueError as er: # CPython - dp(er) - print("read:", er.args[0] == "Read on closed or unwrapped SSL socket.") + poll(s) + ret = s.read(10) + print("read: ", ret) s.close() - else: # fake it... - print("connect:", True) - if tls: - print("wrap:", True) - print("read:", True) if __name__ == "__main__": @@ -136,10 +108,8 @@ def test(peer_addr, tls=False, handshake=False): print("--- Plain sockets to nowhere ---") test(socket.getaddrinfo("192.0.2.1", 80)[0][-1], False, False) print("--- SSL sockets to nowhere ---") - # this test fails with AXTLS because do_handshake=False blocks on first read/write and - # there it times out until the connect is aborted test(socket.getaddrinfo("192.0.2.1", 443)[0][-1], True, False) print("--- Plain sockets ---") - test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, True) + test(socket.getaddrinfo("micropython.org", 80)[0][-1], False, False) print("--- SSL sockets ---") test(socket.getaddrinfo("micropython.org", 443)[0][-1], True, True) diff --git a/tests/net_hosted/connect_nonblock_xfer.py.exp b/tests/net_hosted/connect_nonblock_xfer.py.exp new file mode 100644 index 000000000000..c5498a03873a --- /dev/null +++ b/tests/net_hosted/connect_nonblock_xfer.py.exp @@ -0,0 +1,44 @@ +--- Plain sockets to nowhere --- +connect: EINPROGRESS +poll: [] +send er: EAGAIN +connect: EINPROGRESS +poll: [] +write: None +connect: EINPROGRESS +poll: [] +recv er: EAGAIN +connect: EINPROGRESS +poll: [] +read: None +--- SSL sockets to nowhere --- +connect: EINPROGRESS +wrap ok: True +poll: [] +write: None +connect: EINPROGRESS +wrap ok: True +poll: [] +read: None +--- Plain sockets --- +connect: EINPROGRESS +poll: [] +send er: EAGAIN +connect: EINPROGRESS +poll: [] +write: None +connect: EINPROGRESS +poll: [] +recv er: EAGAIN +connect: EINPROGRESS +poll: [] +read: None +--- SSL sockets --- +connect: EINPROGRESS +wrap ok: True +poll: [(, 4)] +write: 4 +connect: EINPROGRESS +wrap ok: True +poll: [(, 4)] +read: None From 76e6c6345cbaa694ac8f33cfc8b8daaa11fd58ed Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Nov 2024 12:43:04 +1100 Subject: [PATCH 09/46] tools/mpremote: Make sure stdout and stderr output appear in order. mpremote error messages now go to stderr, so make sure stdout is flushed before printing them. Also update the test runner to capture error messages. Signed-off-by: Damien George --- tools/mpremote/mpremote/main.py | 3 +++ tools/mpremote/tests/run-mpremote-tests.sh | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/tools/mpremote/mpremote/main.py b/tools/mpremote/mpremote/main.py index 8c0a6cd224c3..e6e397020fe8 100644 --- a/tools/mpremote/mpremote/main.py +++ b/tools/mpremote/mpremote/main.py @@ -547,7 +547,10 @@ def main(): return 0 except CommandError as e: + # Make sure existing stdout appears before the error message on stderr. + sys.stdout.flush() print(f"{_PROG}: {e}", file=sys.stderr) + sys.stderr.flush() return 1 finally: do_disconnect(state) diff --git a/tools/mpremote/tests/run-mpremote-tests.sh b/tools/mpremote/tests/run-mpremote-tests.sh index 11d82c9bb380..b25b5fd7d296 100755 --- a/tools/mpremote/tests/run-mpremote-tests.sh +++ b/tools/mpremote/tests/run-mpremote-tests.sh @@ -16,7 +16,7 @@ for t in $TESTS; do TMP=$(mktemp -d) echo -n "${t}: " # Strip CR and replace the random temp dir with a token. - if env MPREMOTE=${MPREMOTE} TMP="${TMP}" "${t}" | tr -d '\r' | sed "s,${TMP},"'${TMP},g' > "${t}.out"; then + if env MPREMOTE=${MPREMOTE} TMP="${TMP}" "${t}" 2>&1 | tr -d '\r' | sed "s,${TMP},"'${TMP},g' > "${t}.out"; then if diff "${t}.out" "${t}.exp" > /dev/null; then echo "OK" else From 3b6024a699491720ac0ba48b8534d6e540e828a9 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Nov 2024 12:44:50 +1100 Subject: [PATCH 10/46] tools/mpremote: Add test for forced copy. Signed-off-by: Damien George --- tools/mpremote/tests/test_filesystem.sh | 5 +++++ tools/mpremote/tests/test_filesystem.sh.exp | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index b6d5a7febcb8..727efea90eb6 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -66,6 +66,11 @@ $MPREMOTE resume cp "${TMP}/a.py" :aaa $MPREMOTE resume cp "${TMP}/a.py" :bbb/b.py $MPREMOTE resume cat :aaa/a.py bbb/b.py +# Test cp -f (force copy). +echo ----- +$MPREMOTE resume cp -f "${TMP}/a.py" :aaa +$MPREMOTE resume cat :aaa/a.py + echo ----- $MPREMOTE resume rm :b.py c.py $MPREMOTE resume ls diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index 7220dc41e472..b252bc7eb0d0 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -38,6 +38,10 @@ print("World") print("Hello") print("World") ----- +cp ${TMP}/a.py :aaa +print("Hello") +print("World") +----- rm :b.py rm :c.py ls : From 4fd5b72a8b71c6b3a074bcc3b9f6385b049c3dcb Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 6 Nov 2024 12:45:06 +1100 Subject: [PATCH 11/46] tools/mpremote: Support trailing slash on dest for non-recursive copy. This fixes a regression in db59e55fe7a0b67d3af868990468e7b8056afe42: prior to that commit `mpremote` supported trailing slashes on the destination of a normal (non-recursive) copy. Add back support for that, with the semantics that a trailing slash requires the destination to be an existing directory. Also add a test for this. Signed-off-by: Damien George --- tools/mpremote/mpremote/commands.py | 11 +++++++++-- tools/mpremote/tests/test_filesystem.sh | 5 +++++ tools/mpremote/tests/test_filesystem.sh.exp | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/tools/mpremote/mpremote/commands.py b/tools/mpremote/mpremote/commands.py index 2b1acea43808..f86befd08034 100644 --- a/tools/mpremote/mpremote/commands.py +++ b/tools/mpremote/mpremote/commands.py @@ -129,8 +129,15 @@ def _remote_path_basename(a): def do_filesystem_cp(state, src, dest, multiple, check_hash=False): if dest.startswith(":"): - dest_exists = state.transport.fs_exists(dest[1:]) - dest_isdir = dest_exists and state.transport.fs_isdir(dest[1:]) + dest_no_slash = dest.rstrip("/" + os.path.sep + (os.path.altsep or "")) + dest_exists = state.transport.fs_exists(dest_no_slash[1:]) + dest_isdir = dest_exists and state.transport.fs_isdir(dest_no_slash[1:]) + + # A trailing / on dest forces it to be a directory. + if dest != dest_no_slash: + if not dest_isdir: + raise CommandError("cp: destination is not a directory") + dest = dest_no_slash else: dest_exists = os.path.exists(dest) dest_isdir = dest_exists and os.path.isdir(dest) diff --git a/tools/mpremote/tests/test_filesystem.sh b/tools/mpremote/tests/test_filesystem.sh index 727efea90eb6..afeb7c91da8d 100755 --- a/tools/mpremote/tests/test_filesystem.sh +++ b/tools/mpremote/tests/test_filesystem.sh @@ -71,6 +71,11 @@ echo ----- $MPREMOTE resume cp -f "${TMP}/a.py" :aaa $MPREMOTE resume cat :aaa/a.py +# Test cp where the destination has a trailing /. +echo ----- +$MPREMOTE resume cp "${TMP}/a.py" :aaa/ +$MPREMOTE resume cp "${TMP}/a.py" :aaa/a.py/ || echo "expect error" + echo ----- $MPREMOTE resume rm :b.py c.py $MPREMOTE resume ls diff --git a/tools/mpremote/tests/test_filesystem.sh.exp b/tools/mpremote/tests/test_filesystem.sh.exp index b252bc7eb0d0..82fe7d6bf78c 100644 --- a/tools/mpremote/tests/test_filesystem.sh.exp +++ b/tools/mpremote/tests/test_filesystem.sh.exp @@ -42,6 +42,12 @@ cp ${TMP}/a.py :aaa print("Hello") print("World") ----- +cp ${TMP}/a.py :aaa/ +Up to date: aaa/a.py +cp ${TMP}/a.py :aaa/a.py/ +mpremote: cp: destination is not a directory +expect error +----- rm :b.py rm :c.py ls : From 161e2bd37df70556ef2a276c53af2c0cdc79af5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 9 Aug 2023 10:31:29 +0200 Subject: [PATCH 12/46] extmod/network_ppp: Add stream config parameter. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This makes the stream that the PPP object wraps, which is normally only set once via the constructor, accessible and configurable via the `ppp.config()` method. Signed-off-by: Daniël van de Giessen --- docs/library/network.PPP.rst | 7 +++++-- extmod/network_ppp_lwip.c | 11 ++++++++++- ports/esp32/network_ppp.c | 9 +++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/docs/library/network.PPP.rst b/docs/library/network.PPP.rst index 85f580ce540e..17a8d1c1cd89 100644 --- a/docs/library/network.PPP.rst +++ b/docs/library/network.PPP.rst @@ -70,8 +70,11 @@ Methods .. method:: PPP.config(config_parameters) - Sets or gets parameters of the PPP interface. There are currently no parameter that - can be set or retrieved. + Sets or gets parameters of the PPP interface. The only parameter that can be + retrieved and set is the underlying stream, using:: + + stream = PPP.config("stream") + PPP.config(stream=stream) .. method:: PPP.ipconfig('param') PPP.ipconfig(param=value, ...) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 2b77662a24f2..72d02602c0cc 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -153,12 +153,17 @@ static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t if (n_args != 1 && kwargs->used != 0) { mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); } - // network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); + network_ppp_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (kwargs->used != 0) { for (size_t i = 0; i < kwargs->alloc; i++) { if (mp_map_slot_is_filled(kwargs, i)) { switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_stream: { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + self->stream = kwargs->table[i].value; + break; + } default: break; } @@ -174,6 +179,10 @@ static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t mp_obj_t val = mp_const_none; switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_stream: { + val = self->stream; + break; + } default: mp_raise_ValueError(MP_ERROR_TEXT("unknown config param")); } diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index f2813f430d48..4f49efaf060a 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -323,6 +323,11 @@ static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs for (size_t i = 0; i < kwargs->alloc; i++) { if (mp_map_slot_is_filled(kwargs, i)) { switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { + case MP_QSTR_stream: { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + self->stream = kwargs->table[i].value; + break; + } default: break; } @@ -338,6 +343,10 @@ static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs mp_obj_t val = mp_const_none; switch (mp_obj_str_get_qstr(args[1])) { + case MP_QSTR_stream: { + val = self->stream; + break; + } case MP_QSTR_ifname: { if (self->pcb != NULL) { struct netif *pppif = ppp_netif(self->pcb); From 77406b4240b0489963782a7f18920ba3c5d11263 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20de=20Giessen?= Date: Wed, 28 Feb 2024 12:35:37 +0100 Subject: [PATCH 13/46] extmod/network_ppp: Allow stream=None to suspend PPP. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This allows the stream to be set to `None`, which essentially stops all PPP communication without disconnecting the session. This allows replacing the stream on-the-fly to suspend it, for example to send AT commands to a modem without completely disconnecting and re-establishing the PPP connection: uart = ppp.config('stream') ppp.config(stream=None) uart.write(b'+++') # do some AT commands uart.write(b'ATO\r\n') ppp.config(stream=uart) Any attempted communication by PPP while the stream is not connected will register as simple packet loss to the LwIP stack because we return 0 for any write calls, and protocols like TCP will then automatically handle retrying. Signed-off-by: Daniël van de Giessen --- extmod/network_ppp_lwip.c | 68 ++++++++++++++++++++++++++++----------- ports/esp32/network_ppp.c | 29 +++++++++++++---- 2 files changed, 72 insertions(+), 25 deletions(-) diff --git a/extmod/network_ppp_lwip.c b/extmod/network_ppp_lwip.c index 72d02602c0cc..8eb90ea4a652 100644 --- a/extmod/network_ppp_lwip.c +++ b/extmod/network_ppp_lwip.c @@ -60,6 +60,18 @@ const mp_obj_type_t mp_network_ppp_lwip_type; static mp_obj_t network_ppp___del__(mp_obj_t self_in); +static void network_ppp_stream_uart_irq_disable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } + + // Disable UART IRQ. + mp_obj_t dest[3]; + mp_load_method(self->stream, MP_QSTR_irq, dest); + dest[2] = mp_const_none; + mp_call_method_n_kw(1, 0, dest); +} + static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { network_ppp_obj_t *self = ctx; switch (err_code) { @@ -68,12 +80,9 @@ static void network_ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { break; case PPPERR_USER: if (self->state >= STATE_ERROR) { - // Disable UART IRQ. - mp_obj_t dest[3]; - mp_load_method(self->stream, MP_QSTR_irq, dest); - dest[2] = mp_const_none; - mp_call_method_n_kw(1, 0, dest); - // Indicate that the IRQ is disabled. + network_ppp_stream_uart_irq_disable(self); + // Indicate that we are no longer connected and thus + // only need to free the PPP PCB, not close it. self->state = STATE_ACTIVE; } // Clean up the PPP PCB. @@ -91,7 +100,9 @@ static mp_obj_t network_ppp_make_new(const mp_obj_type_t *type, size_t n_args, s mp_obj_t stream = all_args[0]; - mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (stream != mp_const_none) { + mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } network_ppp_obj_t *self = mp_obj_malloc_with_finaliser(network_ppp_obj_t, type); self->state = STATE_INACTIVE; @@ -105,7 +116,7 @@ static mp_obj_t network_ppp___del__(mp_obj_t self_in) { network_ppp_obj_t *self = MP_OBJ_TO_PTR(self_in); if (self->state >= STATE_ACTIVE) { if (self->state >= STATE_ERROR) { - // Still connected over the UART stream. + // Still connected over the stream. // Force the connection to close, with nocarrier=1. self->state = STATE_INACTIVE; ppp_close(self->pcb, 1); @@ -127,10 +138,11 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } mp_int_t total_len = 0; - for (;;) { + mp_obj_t stream = self->stream; + while (stream != mp_const_none) { uint8_t buf[256]; int err; - mp_uint_t len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); + mp_uint_t len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); if (len == 0) { break; } @@ -149,6 +161,19 @@ static mp_obj_t network_ppp_poll(size_t n_args, const mp_obj_t *args) { } static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(network_ppp_poll_obj, 1, 2, network_ppp_poll); +static void network_ppp_stream_uart_irq_enable(network_ppp_obj_t *self) { + if (self->stream == mp_const_none) { + return; + } + + // Enable UART IRQ to call PPP.poll() when incoming data is ready. + mp_obj_t dest[4]; + mp_load_method(self->stream, MP_QSTR_irq, dest); + dest[2] = mp_obj_new_bound_meth(MP_OBJ_FROM_PTR(&network_ppp_poll_obj), MP_OBJ_FROM_PTR(self)); + dest[3] = mp_load_attr(self->stream, MP_QSTR_IRQ_RXIDLE); + mp_call_method_n_kw(2, 0, dest); +} + static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs) { if (n_args != 1 && kwargs->used != 0) { mp_raise_TypeError(MP_ERROR_TEXT("either pos or kw args are allowed")); @@ -160,8 +185,16 @@ static mp_obj_t network_ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t if (mp_map_slot_is_filled(kwargs, i)) { switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { case MP_QSTR_stream: { - mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (kwargs->table[i].value != mp_const_none) { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_disable(self); + } self->stream = kwargs->table[i].value; + if (self->state >= STATE_ACTIVE) { + network_ppp_stream_uart_irq_enable(self); + } break; } default: @@ -210,10 +243,14 @@ static u32_t network_ppp_output_callback(ppp_pcb *pcb, const void *data, u32_t l } mp_printf(&mp_plat_print, ")\n"); #endif + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + return 0; + } int err; // The return value from this output callback is the number of bytes written out. // If it's less than the requested number of bytes then lwIP will propagate out an error. - return mp_stream_rw(self->stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); + return mp_stream_rw(stream, (void *)data, len, &err, MP_STREAM_RW_WRITE); } static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_t *kw_args) { @@ -236,12 +273,7 @@ static mp_obj_t network_ppp_connect(size_t n_args, const mp_obj_t *args, mp_map_ } self->state = STATE_ACTIVE; - // Enable UART IRQ to call PPP.poll() when incoming data is ready. - mp_obj_t dest[4]; - mp_load_method(self->stream, MP_QSTR_irq, dest); - dest[2] = mp_obj_new_bound_meth(MP_OBJ_FROM_PTR(&network_ppp_poll_obj), MP_OBJ_FROM_PTR(self)); - dest[3] = mp_load_attr(self->stream, MP_QSTR_IRQ_RXIDLE); - mp_call_method_n_kw(2, 0, dest); + network_ppp_stream_uart_irq_enable(self); } if (self->state == STATE_CONNECTING || self->state == STATE_CONNECTED) { diff --git a/ports/esp32/network_ppp.c b/ports/esp32/network_ppp.c index 4f49efaf060a..5c41cf948c1e 100644 --- a/ports/esp32/network_ppp.c +++ b/ports/esp32/network_ppp.c @@ -85,7 +85,9 @@ static void ppp_status_cb(ppp_pcb *pcb, int err_code, void *ctx) { } static mp_obj_t ppp_make_new(mp_obj_t stream) { - mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (stream != mp_const_none) { + mp_get_stream_raise(stream, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } ppp_if_obj_t *self = mp_obj_malloc_with_finaliser(ppp_if_obj_t, &ppp_if_type); self->stream = stream; @@ -100,8 +102,14 @@ MP_DEFINE_CONST_FUN_OBJ_1(esp_network_ppp_make_new_obj, ppp_make_new); static u32_t ppp_output_callback(ppp_pcb *pcb, u8_t *data, u32_t len, void *ctx) { ppp_if_obj_t *self = ctx; + + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + return 0; + } + int err; - return mp_stream_rw(self->stream, data, len, &err, MP_STREAM_RW_WRITE); + return mp_stream_rw(stream, data, len, &err, MP_STREAM_RW_WRITE); } static void pppos_client_task(void *self_in) { @@ -110,10 +118,15 @@ static void pppos_client_task(void *self_in) { int len = 0; while (ulTaskNotifyTake(pdTRUE, len <= 0) == 0) { - int err; - len = mp_stream_rw(self->stream, buf, sizeof(buf), &err, 0); - if (len > 0) { - pppos_input_tcpip(self->pcb, (u8_t *)buf, len); + mp_obj_t stream = self->stream; + if (stream == mp_const_none) { + len = 0; + } else { + int err; + len = mp_stream_rw(stream, buf, sizeof(buf), &err, 0); + if (len > 0) { + pppos_input_tcpip(self->pcb, (u8_t *)buf, len); + } } } @@ -324,7 +337,9 @@ static mp_obj_t ppp_config(size_t n_args, const mp_obj_t *args, mp_map_t *kwargs if (mp_map_slot_is_filled(kwargs, i)) { switch (mp_obj_str_get_qstr(kwargs->table[i].key)) { case MP_QSTR_stream: { - mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + if (kwargs->table[i].value != mp_const_none) { + mp_get_stream_raise(kwargs->table[i].value, MP_STREAM_OP_READ | MP_STREAM_OP_WRITE); + } self->stream = kwargs->table[i].value; break; } From 611d8f9ce82ab5e04fb86ab6cfc28d5ed98f33bf Mon Sep 17 00:00:00 2001 From: Damien George Date: Mon, 11 Nov 2024 12:04:57 +1100 Subject: [PATCH 14/46] esp32/modsocket: Fix getaddrinfo hints to set AI_CANONNAME. Because the `ai_canonname` field is subsequently used. ESP32_GENERIC_S3 (at least) crashes with IDF 5.2.3 without this set. Signed-off-by: Damien George --- ports/esp32/modsocket.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/ports/esp32/modsocket.c b/ports/esp32/modsocket.c index 916eb79bd926..7ea5e855d325 100644 --- a/ports/esp32/modsocket.c +++ b/ports/esp32/modsocket.c @@ -213,7 +213,7 @@ static int mdns_getaddrinfo(const char *host_str, const char *port_str, #endif // MICROPY_HW_ENABLE_MDNS_QUERIES static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, - const struct addrinfo *hints, struct addrinfo **res) { + struct addrinfo *hints, struct addrinfo **res) { int retval = 0; *res = NULL; @@ -235,6 +235,9 @@ static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, MP_THREAD_GIL_EXIT(); + // The ai_canonname field is used below, so set the hint. + hints->ai_flags |= AI_CANONNAME; + #if MICROPY_HW_ENABLE_MDNS_QUERIES retval = mdns_getaddrinfo(host_str, port_str, hints, res); #endif @@ -264,7 +267,8 @@ static void _getaddrinfo_inner(const mp_obj_t host, const mp_obj_t portx, static void _socket_getaddrinfo(const mp_obj_t addrtuple, struct addrinfo **resp) { mp_obj_t *elem; mp_obj_get_array_fixed_n(addrtuple, 2, &elem); - _getaddrinfo_inner(elem[0], elem[1], NULL, resp); + struct addrinfo hints = { 0 }; + _getaddrinfo_inner(elem[0], elem[1], &hints, resp); } static mp_obj_t socket_make_new(const mp_obj_type_t *type_in, size_t n_args, size_t n_kw, const mp_obj_t *args) { From 5dc9eda1953668eb6861be01ca85f147dcf8d406 Mon Sep 17 00:00:00 2001 From: Damien George Date: Wed, 13 Nov 2024 14:21:29 +1100 Subject: [PATCH 15/46] extmod/vfs_blockdev: Support bool return from Python read/write blocks. Commit f4ab9d924790581989f2398fe30bbac5d680577f inadvertently broke some Python block devices, for example esp32 and stm32 SDCard classes. Those classes return a bool from their `readblocks` and `writeblocks` methods instead of an integer errno code. With that change, both `False` and `True` return values are now be interpreted as non-zero and hence the block device call fails. The fix in this commit is to allow a bool and explicitly convert `True` to 0 and `False` to `-MP_EIO`. Signed-off-by: Damien George --- extmod/vfs_blockdev.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/extmod/vfs_blockdev.c b/extmod/vfs_blockdev.c index a7c14b76ee36..d43c96b08f32 100644 --- a/extmod/vfs_blockdev.c +++ b/extmod/vfs_blockdev.c @@ -58,6 +58,13 @@ static int mp_vfs_blockdev_call_rw(mp_obj_t *args, size_t block_num, size_t bloc if (ret == mp_const_none) { return 0; } else { + // Some block devices return a bool indicating success, so + // convert those to an errno integer code. + if (ret == mp_const_true) { + return 0; + } else if (ret == mp_const_false) { + return -MP_EIO; + } // Block device functions are expected to return 0 on success // and negative integer on errors. Check for positive integer // results as some callers (i.e. littlefs) will produce corrupt From 898407defbb6327fde379304846999033bf349c5 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 4 Nov 2024 16:04:56 +0100 Subject: [PATCH 16/46] ports: Make PWM duty_u16 have an upper value of 65535 across all ports. The following ports used 65536 as the upper value (100% duty cycle) and are changed in this commit to use 65535: esp8266, mimxrt, nrf, samd. Tested that output is high at `duty_u16(65535)` and low at `duty_u16(0)`. Also verified that at `duty_u16(32768)` the high and low pulse have the same length. Partially reverts #10850, commits 9c7ad68165bcd224c94ca6d8f172362cf8000d99 and 2ac643c15bec8c88ece0e944ce58f36d02dfd2dd. Signed-off-by: robert-hh --- docs/mimxrt/quickref.rst | 6 +++--- docs/samd/quickref.rst | 4 ++-- ports/esp8266/machine_pwm.c | 6 +++--- ports/mimxrt/hal/pwm_backport.c | 6 +++--- ports/mimxrt/hal/pwm_backport.h | 2 +- ports/nrf/modules/machine/pwm.c | 6 +++--- ports/samd/machine_pwm.c | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/mimxrt/quickref.rst b/docs/mimxrt/quickref.rst index cfd0605054a7..34e0aa79f139 100644 --- a/docs/mimxrt/quickref.rst +++ b/docs/mimxrt/quickref.rst @@ -193,7 +193,7 @@ PWM Constructor - *freq* should be an integer which sets the frequency in Hz for the PWM cycle. The valid frequency range is 15 Hz resp. 18Hz resp. 24Hz up to > 1 MHz. - - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65536``. + - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65535``. The duty cycle of a X channel can only be changed, if the A and B channel of the respective submodule is not used. Otherwise the duty_16 value of the X channel is 32768 (50%). @@ -231,7 +231,7 @@ is created by dividing the pwm_clk signal by an integral factor, according to th f = pwm_clk / (2**n * m) -with n being in the range of 0..7, and m in the range of 2..65536. pmw_clk is 125Mhz +with n being in the range of 0..7, and m in the range of 2..65535. pmw_clk is 125Mhz for MIMXRT1010/1015/1020, 150 MHz for MIMXRT1050/1060/1064 and 160MHz for MIMXRT1170. The lowest frequency is pwm_clk/2**23 (15, 18, 20Hz). The highest frequency with U16 resolution is pwm_clk/2**16 (1907, 2288, 2441 Hz), the highest frequency @@ -255,7 +255,7 @@ Use the :ref:`machine.ADC ` class:: from machine import ADC adc = ADC(Pin('A2')) # create ADC object on ADC pin - adc.read_u16() # read value, 0-65536 across voltage range 0.0v - 3.3v + adc.read_u16() # read value, 0-65535 across voltage range 0.0v - 3.3v The resolution of the ADC is 12 bit with 10 to 11 bit accuracy, irrespective of the value returned by read_u16(). If you need a higher resolution or better accuracy, use diff --git a/docs/samd/quickref.rst b/docs/samd/quickref.rst index 25b5a8fc8ab2..d57dc6790839 100644 --- a/docs/samd/quickref.rst +++ b/docs/samd/quickref.rst @@ -215,7 +215,7 @@ PWM Constructor - *freq* should be an integer which sets the frequency in Hz for the PWM cycle. The valid frequency range is 1 Hz to 24 MHz. - - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65536``. + - *duty_u16* sets the duty cycle as a ratio ``duty_u16 / 65535``. - *duty_ns* sets the pulse width in nanoseconds. The limitation for X channels apply as well. - *invert*\=True|False. Setting a bit inverts the respective output. @@ -246,7 +246,7 @@ Use the :ref:`machine.ADC ` class:: from machine import ADC adc0 = ADC(Pin('A0')) # create ADC object on ADC pin, average=16 - adc0.read_u16() # read value, 0-65536 across voltage range 0.0v - 3.3v + adc0.read_u16() # read value, 0-65535 across voltage range 0.0v - 3.3v adc1 = ADC(Pin('A1'), average=1) # create ADC object on ADC pin, average=1 The resolution of the ADC is 12 bit with 12 bit accuracy, irrespective of the diff --git a/ports/esp8266/machine_pwm.c b/ports/esp8266/machine_pwm.c index 25a2d6898f7c..0d5ed595428e 100644 --- a/ports/esp8266/machine_pwm.c +++ b/ports/esp8266/machine_pwm.c @@ -80,7 +80,7 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, size_t n_args, c pwm_set_duty(args[ARG_duty].u_int, self->channel); } if (args[ARG_duty_u16].u_int != -1) { - pwm_set_duty(args[ARG_duty_u16].u_int * 1000 / 65536, self->channel); + pwm_set_duty(args[ARG_duty_u16].u_int * 1000 / 65535, self->channel); } if (args[ARG_duty_ns].u_int != -1) { uint32_t freq = pwm_get_freq(0); @@ -164,13 +164,13 @@ static void mp_machine_pwm_duty_set(machine_pwm_obj_t *self, mp_int_t duty) { static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { set_active(self, true); - return MP_OBJ_NEW_SMALL_INT(pwm_get_duty(self->channel) * 65536 / 1024); + return MP_OBJ_NEW_SMALL_INT(pwm_get_duty(self->channel) * 65535 / 1024); } static void mp_machine_pwm_duty_set_u16(machine_pwm_obj_t *self, mp_int_t duty) { set_active(self, false); self->duty_ns = -1; - pwm_set_duty(duty * 1024 / 65536, self->channel); + pwm_set_duty(duty * 1024 / 65535, self->channel); pwm_start(); } diff --git a/ports/mimxrt/hal/pwm_backport.c b/ports/mimxrt/hal/pwm_backport.c index 7732e0e8167c..3df826496392 100644 --- a/ports/mimxrt/hal/pwm_backport.c +++ b/ports/mimxrt/hal/pwm_backport.c @@ -36,7 +36,7 @@ void PWM_UpdatePwmDutycycle_u16( // Setup the PWM dutycycle of channel A or B if (pwmSignal == kPWM_PwmA) { - if (dutyCycle >= 65536) { + if (dutyCycle >= PWM_FULL_SCALE) { base->SM[subModule].VAL2 = 0; base->SM[subModule].VAL3 = pulseCnt; } else { @@ -44,7 +44,7 @@ void PWM_UpdatePwmDutycycle_u16( base->SM[subModule].VAL3 = base->SM[subModule].VAL2 + pwmHighPulse; } } else { - if (dutyCycle >= 65536) { + if (dutyCycle >= PWM_FULL_SCALE) { base->SM[subModule].VAL4 = 0; base->SM[subModule].VAL5 = pulseCnt; } else { @@ -160,7 +160,7 @@ status_t QTMR_SetupPwm_u16(TMR_Type *base, qtmr_channel_selection_t channel, uin if (dutyCycleU16 == 0) { // Clear the output at the next compare reg |= (TMR_CTRL_LENGTH_MASK | TMR_CTRL_OUTMODE(kQTMR_ClearOnCompare)); - } else if (dutyCycleU16 >= 65536) { + } else if (dutyCycleU16 >= PWM_FULL_SCALE) { // Set the output at the next compare reg |= (TMR_CTRL_LENGTH_MASK | TMR_CTRL_OUTMODE(kQTMR_SetOnCompare)); } else { diff --git a/ports/mimxrt/hal/pwm_backport.h b/ports/mimxrt/hal/pwm_backport.h index 04899173e20b..9c9f6811add2 100644 --- a/ports/mimxrt/hal/pwm_backport.h +++ b/ports/mimxrt/hal/pwm_backport.h @@ -24,7 +24,7 @@ typedef struct _pwm_signal_param_u16 uint16_t deadtimeValue; // The deadtime value; only used if channel pair is operating in complementary mode } pwm_signal_param_u16_t; -#define PWM_FULL_SCALE (65536UL) +#define PWM_FULL_SCALE (65535UL) void PWM_UpdatePwmDutycycle_u16(PWM_Type *base, pwm_submodule_t subModule, pwm_channels_t pwmSignal, uint32_t dutyCycle, uint16_t center); diff --git a/ports/nrf/modules/machine/pwm.c b/ports/nrf/modules/machine/pwm.c index 8145509c7628..13d824e86674 100644 --- a/ports/nrf/modules/machine/pwm.c +++ b/ports/nrf/modules/machine/pwm.c @@ -285,7 +285,7 @@ static mp_obj_t mp_machine_pwm_duty_get(machine_pwm_obj_t *self) { if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) { return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); } else if (self->p_config->duty_mode[self->channel] == DUTY_U16) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 100 / 65536); + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 100 / 65535); } else { return MP_OBJ_NEW_SMALL_INT(-1); } @@ -301,7 +301,7 @@ static mp_obj_t mp_machine_pwm_duty_get_u16(machine_pwm_obj_t *self) { if (self->p_config->duty_mode[self->channel] == DUTY_U16) { return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel]); } else if (self->p_config->duty_mode[self->channel] == DUTY_PERCENT) { - return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 65536 / 100); + return MP_OBJ_NEW_SMALL_INT(self->p_config->duty[self->channel] * 65535 / 100); } else { return MP_OBJ_NEW_SMALL_INT(-1); } @@ -365,7 +365,7 @@ static void machine_hard_pwm_start(const machine_pwm_obj_t *self) { if (self->p_config->duty_mode[i] == DUTY_PERCENT) { pulse_width = ((period * self->p_config->duty[i]) / 100); } else if (self->p_config->duty_mode[i] == DUTY_U16) { - pulse_width = ((period * self->p_config->duty[i]) / 65536); + pulse_width = ((period * self->p_config->duty[i]) / 65535); } else if (self->p_config->duty_mode[i] == DUTY_NS) { pulse_width = (uint64_t)self->p_config->duty[i] * tick_freq / 1000000000ULL; } diff --git a/ports/samd/machine_pwm.c b/ports/samd/machine_pwm.c index b2a383c21cba..468e34a1653d 100644 --- a/ports/samd/machine_pwm.c +++ b/ports/samd/machine_pwm.c @@ -54,7 +54,7 @@ typedef struct _machine_pwm_obj_t { #define PWM_CLK_READY (1) #define PWM_TCC_ENABLED (2) #define PWM_MASTER_CLK (get_peripheral_freq()) -#define PWM_FULL_SCALE (65536) +#define PWM_FULL_SCALE (65535) #define PWM_UPDATE_TIMEOUT (2000) #define VALUE_NOT_SET (-1) From e2532e0f725107becea3cf7e6d14727ab3abec7c Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 6 Nov 2024 20:26:27 +0100 Subject: [PATCH 17/46] mimxrt/machine_pwm: Fix a few inconsistencies with PWM output. Changes in this commit: - When setting PWM parameters of a FLEXPWM AB channel twice within a PWM cycle, the second setting was ignored. Now the second setting persists. - With `duty_u16(0)` a FLEXPWM X channel was set to high impedance. Now it is set to low impedance with value 0 for `invert=False`, and 1 for `invert=True`. - The align parameter requires a duty rate and frequency to be set. Align will now be ignored if freq or duty are missing. Signed-off-by: robert-hh --- ports/mimxrt/hal/pwm_backport.c | 8 ++------ ports/mimxrt/machine_pwm.c | 29 ++++++++++++++++++++++++++--- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/ports/mimxrt/hal/pwm_backport.c b/ports/mimxrt/hal/pwm_backport.c index 3df826496392..7815fd1dff57 100644 --- a/ports/mimxrt/hal/pwm_backport.c +++ b/ports/mimxrt/hal/pwm_backport.c @@ -110,12 +110,8 @@ void PWM_SetupPwmx_u16(PWM_Type *base, pwm_submodule_t subModule, base->SM[subModule].OCTRL = (base->SM[subModule].OCTRL & ~PWM_OCTRL_POLX_MASK) | PWM_OCTRL_POLX(!invert); - // Switch the output on or off. - if (duty_cycle == 0) { - base->OUTEN &= ~(1U << subModule); - } else { - base->OUTEN |= (1U << subModule); - } + // Enable PWM output + base->OUTEN |= (1U << subModule); } #ifdef FSL_FEATURE_SOC_TMR_COUNT diff --git a/ports/mimxrt/machine_pwm.c b/ports/mimxrt/machine_pwm.c index f9ae5e22edcc..b68521281ae6 100644 --- a/ports/mimxrt/machine_pwm.c +++ b/ports/mimxrt/machine_pwm.c @@ -45,6 +45,8 @@ typedef struct _machine_pwm_obj_t { mp_obj_base_t base; PWM_Type *instance; + const machine_pin_obj_t *pwm_pin; + const machine_pin_af_obj_t *pwm_pin_af_obj; bool is_flexpwm; uint8_t complementary; uint8_t module; @@ -255,6 +257,8 @@ static void configure_flexpwm(machine_pwm_obj_t *self) { PWM_SetupFaultDisableMap(self->instance, self->submodule, self->channel2, kPWM_faultchannel_1, 0); } + // clear the load okay bit for the submodules in case there is a pending load + PWM_SetPwmLdok(self->instance, 1 << self->submodule, false); if (self->channel1 != kPWM_PwmX) { // Only for A/B channels // Initialize the channel parameters pwmSignal.pwmChannel = self->channel1; @@ -283,6 +287,17 @@ static void configure_flexpwm(machine_pwm_obj_t *self) { self->instance->SM[self->submodule].CTRL &= ~(PWM_CTRL_DBLEN_MASK | PWM_CTRL_SPLIT_MASK); } } else { + if (self->duty_u16 == 0) { + // For duty_u16 == 0 just set the output to GPIO mode + if (self->invert) { + mp_hal_pin_high(self->pwm_pin); + } else { + mp_hal_pin_low(self->pwm_pin); + } + IOMUXC_SetPinMux(self->pwm_pin->muxRegister, PIN_AF_MODE_ALT5, 0, 0, 0, 0U); + } else { + IOMUXC_SetPinMux(self->pwm_pin->muxRegister, self->pwm_pin_af_obj->af_mode, 0, 0, 0, 0U); + } PWM_SetupPwmx_u16(self->instance, self->submodule, self->freq, self->duty_u16, self->invert, pwmSourceClockInHz); if (self->xor) { @@ -408,12 +423,16 @@ static void mp_machine_pwm_init_helper(machine_pwm_obj_t *self, } self->center = center; } else { // Use alignment setting shortcut - if (args[ARG_align].u_int >= 0) { + uint32_t duty = self->duty_u16; + if (duty == VALUE_NOT_SET && self->duty_ns != VALUE_NOT_SET) { + duty = duty_ns_to_duty_u16(self->freq, self->duty_ns); + } + if (args[ARG_align].u_int >= 0 && duty != VALUE_NOT_SET && self->freq != VALUE_NOT_SET) { uint8_t align = args[ARG_align].u_int & 3; // limit to 0..3 if (align == PWM_BEGIN) { - self->center = self->duty_u16 / 2; + self->center = duty / 2; } else if (align == PWM_END) { - self->center = PWM_FULL_SCALE - self->duty_u16 / 2; + self->center = PWM_FULL_SCALE - duty / 2; } else { self->center = 32768; // Default value: mid. } @@ -515,6 +534,8 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args // Create and populate the PWM object. machine_pwm_obj_t *self = mp_obj_malloc(machine_pwm_obj_t, &machine_pwm_type); + self->pwm_pin = pin1; + self->pwm_pin_af_obj = af_obj1; self->is_flexpwm = is_flexpwm; self->instance = af_obj1->instance; self->module = module; @@ -534,6 +555,8 @@ static mp_obj_t mp_machine_pwm_make_new(const mp_obj_type_t *type, size_t n_args // Initialize the Pin(s). CLOCK_EnableClock(kCLOCK_Iomuxc); // just in case it was not set yet + // Configure PWMX channels to pin output mode to be prepared for duty_u16 == 0. + mp_hal_pin_output(pin1); IOMUXC_SetPinMux(pin1->muxRegister, af_obj1->af_mode, af_obj1->input_register, af_obj1->input_daisy, pin1->configRegister, 0U); IOMUXC_SetPinConfig(pin1->muxRegister, af_obj1->af_mode, af_obj1->input_register, af_obj1->input_daisy, From 5a70850b30e457ee9c6ab12fa735246bc3a1f8aa Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 2 Sep 2024 16:24:46 +0200 Subject: [PATCH 18/46] samd/machine_uart: Add full support for 9-bit data. Prior to this commit, 9-bit UART data could be specified in the constructor and was transmitted, but the 9th bit was set to 0 when sending, and ignored when receiving. This commit completes 9-bit support in that the 9th bit is taken from the data. 9-bit data has to be provided with `uart.write()` and and read with `uart.read()` as two bytes for each transmitted item, low order byte first. The data length supplied with `uart.write()` and requested by `uart.read()` has to be even, which is checked. The size of the UART buffers will be transparently doubled to cater for 9-bit data. Signed-off-by: robert-hh --- ports/samd/machine_uart.c | 84 ++++++++++++++++++++++++++++++++++----- 1 file changed, 73 insertions(+), 11 deletions(-) diff --git a/ports/samd/machine_uart.c b/ports/samd/machine_uart.c index b0dc4c5768d1..6e9a5538647b 100644 --- a/ports/samd/machine_uart.c +++ b/ports/samd/machine_uart.c @@ -107,10 +107,17 @@ static const char *_parity_name[] = {"None", "", "0", "1"}; // Is defined as 0, // take all bytes from the fifo and store them in the buffer static void uart_drain_rx_fifo(machine_uart_obj_t *self, Sercom *uart) { + uint8_t bits = self->bits; while (uart->USART.INTFLAG.bit.RXC != 0) { - if (ringbuf_free(&self->read_buffer) > 0) { - // get a byte from uart and put into the buffer - ringbuf_put(&(self->read_buffer), uart->USART.DATA.bit.DATA); + if (ringbuf_free(&self->read_buffer) >= (bits <= 8 ? 1 : 2)) { + // get a word from uart and put into the buffer + if (bits <= 8) { + ringbuf_put(&(self->read_buffer), uart->USART.DATA.bit.DATA); + } else { + uint16_t data = uart->USART.DATA.bit.DATA; + ringbuf_put(&(self->read_buffer), data); + ringbuf_put(&(self->read_buffer), data >> 8); + } } else { // if the buffer is full, disable the RX interrupt // allowing RTS to come up. It will be re-enabled by the next read @@ -150,7 +157,12 @@ void common_uart_irq_handler(int uart_id) { #if MICROPY_HW_UART_TXBUF // handle the outgoing data if (ringbuf_avail(&self->write_buffer) > 0) { - uart->USART.DATA.bit.DATA = ringbuf_get(&self->write_buffer); + if (self->bits <= 8) { + uart->USART.DATA.bit.DATA = ringbuf_get(&self->write_buffer); + } else { + uart->USART.DATA.bit.DATA = + ringbuf_get(&self->write_buffer) | (ringbuf_get(&self->write_buffer) << 8); + } } else { #if MICROPY_PY_MACHINE_UART_IRQ // Set the TXIDLE flag @@ -274,6 +286,17 @@ void machine_uart_set_baudrate(mp_obj_t self_in, uint32_t baudrate) { static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { machine_uart_obj_t *self = MP_OBJ_TO_PTR(self_in); + size_t rxbuf_len = self->read_buffer.size - 1; + #if MICROPY_HW_UART_TXBUF + size_t txbuf_len = self->write_buffer.size - 1; + #endif + if (self->bits > 8) { + rxbuf_len /= 2; + #if MICROPY_HW_UART_TXBUF + txbuf_len /= 2; + #endif + } + mp_printf(print, "UART(%u, baudrate=%u, bits=%u, parity=%s, stop=%u, " "timeout=%u, timeout_char=%u, rxbuf=%d" #if MICROPY_HW_UART_TXBUF @@ -287,9 +310,9 @@ static void mp_machine_uart_print(const mp_print_t *print, mp_obj_t self_in, mp_ #endif ")", self->id, self->baudrate, self->bits, _parity_name[self->parity], - self->stop + 1, self->timeout, self->timeout_char, self->read_buffer.size - 1 + self->stop + 1, self->timeout, self->timeout_char, rxbuf_len #if MICROPY_HW_UART_TXBUF - , self->write_buffer.size - 1 + , txbuf_len #endif #if MICROPY_HW_UART_RTSCTS , self->rts != 0xff ? pin_find_by_id(self->rts)->name : MP_QSTR_None @@ -411,6 +434,14 @@ static void mp_machine_uart_init_helper(machine_uart_obj_t *self, size_t n_args, } } #endif + + // Double the buffer lengths for 9 bit transfer + if (self->bits > 8) { + rxbuf_len *= 2; + #if MICROPY_HW_UART_TXBUF + txbuf_len *= 2; + #endif + } // Initialise the UART peripheral if any arguments given, or it was not initialised previously. if (n_args > 0 || kw_args->used > 0 || self->new) { self->new = false; @@ -619,7 +650,12 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t uint64_t timeout_char = self->timeout_char; uint8_t *dest = buf_in; - for (size_t i = 0; i < size; i++) { + // Check that size is even for 9 bit transfers. + if ((self->bits >= 9) && (size & 1)) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } + for (size_t i = 0; i < size;) { // Wait for the first/next character while (ringbuf_avail(&self->read_buffer) == 0) { if (mp_hal_ticks_ms_64() > t) { // timed out @@ -633,6 +669,11 @@ static mp_uint_t mp_machine_uart_read(mp_obj_t self_in, void *buf_in, mp_uint_t MICROPY_EVENT_POLL_HOOK } *dest++ = ringbuf_get(&(self->read_buffer)); + i++; + if (self->bits >= 9 && i < size) { + *dest++ = ringbuf_get(&(self->read_buffer)); + i++; + } t = mp_hal_ticks_ms_64() + timeout_char; // (Re-)Enable RXC interrupt if ((uart->USART.INTENSET.reg & SERCOM_USART_INTENSET_RXC) == 0) { @@ -647,12 +688,19 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ size_t i = 0; const uint8_t *src = buf_in; Sercom *uart = sercom_instance[self->id]; - uint64_t t = mp_hal_ticks_ms_64() + self->timeout; + uint8_t bits = self->bits; + // Check that size is even for 9 bit transfers. + if ((bits >= 9) && (size & 1)) { + *errcode = MP_EIO; + return MP_STREAM_ERROR; + } + #if MICROPY_HW_UART_TXBUF #if MICROPY_PY_MACHINE_UART_IRQ // Prefill the FIFO to get rid of the initial IRQ_TXIDLE event + // Do not care for 9 Bit transfer here since the UART is not yet started. while (i < size && ringbuf_free(&(self->write_buffer)) > 0) { ringbuf_put(&(self->write_buffer), *src++); i++; @@ -672,8 +720,16 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ } MICROPY_EVENT_POLL_HOOK } - ringbuf_put(&(self->write_buffer), *src++); - i++; + if (bits >= 9) { + mp_uint_t atomic_state = MICROPY_BEGIN_ATOMIC_SECTION(); + ringbuf_put(&(self->write_buffer), *src++); + ringbuf_put(&(self->write_buffer), *src++); + i += 2; + MICROPY_END_ATOMIC_SECTION(atomic_state); + } else { + ringbuf_put(&(self->write_buffer), *src++); + i += 1; + } uart->USART.INTENSET.reg = SERCOM_USART_INTENSET_DRE; // kick off the IRQ } @@ -691,7 +747,13 @@ static mp_uint_t mp_machine_uart_write(mp_obj_t self_in, const void *buf_in, mp_ } MICROPY_EVENT_POLL_HOOK } - uart->USART.DATA.bit.DATA = *src++; + if (self->bits > 8 && i < (size - 1)) { + uart->USART.DATA.bit.DATA = *(uint16_t *)src; + i++; + src += 2; + } else { + uart->USART.DATA.bit.DATA = *src++; + } i++; } #endif From 4d36ecf8a882e919b27c953c963e3577d9ae46aa Mon Sep 17 00:00:00 2001 From: robert-hh Date: Fri, 27 Sep 2024 09:03:53 +0200 Subject: [PATCH 19/46] samd/boards/SAMD21_XPLAINED_PRO: Add specific deploy instructions. Add instructions to install a bootloader to the board. The SAMD21 XPLAINED PRO board is shipped without a bootloader, which therefore has to be installed once before it can be used with MicroPython. Signed-off-by: robert-hh --- .../boards/SAMD21_XPLAINED_PRO/board.json | 2 +- .../deploy_xplained_pro.md | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json b/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json index c59ebc1b7b7b..899e56e905a6 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/board.json @@ -1,6 +1,6 @@ { "deploy": [ - "../deploy.md" + "deploy_xplained_pro.md" ], "docs": "", "features": [ diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md b/ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md new file mode 100644 index 000000000000..d95ab5061a30 --- /dev/null +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/deploy_xplained_pro.md @@ -0,0 +1,24 @@ +The SAMD21 Xplained Pro board is shipped without a bootloader. For use +with MicroPyhton a suitable bootloader has be be installed first. The +following procedure has been found to work and be simple: + +1. Get the bootloader from https://micropython.org/resources/firmware/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex. +2. Connect your board to the debug port. A drive with the name XPLAINED +shall appear. +3. Copy the Intel hex file of the bootloader to that drive. +4. Connect your board to the target port. A drive with the name SAMD21XPL should +appear. If not, push reset twice. Then it should appear. If that does not +happen, the bootloader was not properly installed or is not compatible. +5. Copy the MicroPython firmware, a .uf2 file, to the SAMD21 drive. When the SAMD21 +drive disappears, MicroPython is installed. + +From now on only steps 4 and 5 are needed to update MicroPython. You can use the +usual methods to invoke the bootloader, namely: + +- Push Reset twice. +- Call machine.bootloader(). +- Use the touch 1200 procedure by switching the USB baud rate to 1200 baud and back. + +At the above link above there are as well .uf2 versions of the bootloader +which one can install using steps 5. and 6. above once a .uf2 capable +bootloader is installed. From 85de67f55d2afb5c75388d639e51e3df7640b4a7 Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 30 Sep 2024 09:37:13 +0200 Subject: [PATCH 20/46] samd/mboot: Provide a UF2 bootloader for SAMD21 Xplained Pro. A bootloader labelled for the SAMD21 XPLAINED PRO board. The only difference to a generic bootloader are the names and ID of the USB port and the label of the drive that is opened. Signed-off-by: robert-hh --- ...oader-xplained-pro-v3.16.0-15-gaa52b22.hex | 513 ++++++++++++++++++ 1 file changed, 513 insertions(+) create mode 100644 ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex diff --git a/ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex b/ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex new file mode 100644 index 000000000000..df8d5afd19c2 --- /dev/null +++ b/ports/samd/mboot/bootloader-xplained-pro-v3.16.0-15-gaa52b22.hex @@ -0,0 +1,513 @@ +:10000000E02D002069020000650200006702000088 +:1000100000000000000000000000000000000000E0 +:100020000000000000000000000000006502000069 +:100030000000000000000000650200005D020000FA +:100040006502000065020000650200006502000014 +:100050006502000065020000650200006502000004 +:1000600065020000650200006502000065020000F4 +:1000700065020000650200006502000065020000E4 +:1000800065020000650200006502000065020000D4 +:1000900065020000650200006502000065020000C4 +:1000A00065020000650200006502000065020000B4 +:1000B00000000000074B1A7DD207FCD52022FF323A +:1000C0001A83054A4008D8611A801A7DD207FCD5E8 +:1000D0007047C0460040004102A5FFFF70B5802573 +:1000E0000400ED02AC4200D370BD20000134FFF7E4 +:1000F000E1FFFF34F6E7002330B59A4200D130BD6E +:100100009C000D5901330551F7E70000F0B580253B +:10011000104C114E6368114FAB436360002A00D14D +:10012000F0BD1300102A00D91023D21A2680257D95 +:10013000ED07FCD59B009C460023CD58C5500433E9 +:100140006345FAD1C018C9182780237DDB07FCD589 +:10015000E4E7C0460040004144A5FFFF04A5FFFFBF +:10016000802270B5002304000D005200E958E058C9 +:10017000814203D104339342F8D170BD2000FFF7D0 +:1001800099FF402229002000FFF7C0FFF5E700009B +:1001900010230249CA681A42FCD070470008004088 +:1001A0001E2270B52149224C4B6893431C3A1343DD +:1001B0004B60A284FFF7ECFF1E4B1B689B0E3F2B8E +:1001C00000D1203B00251C4A9B0213431B4AE262DC +:1001D000A362A584FFF7DCFF194BA384FFF7D8FFC8 +:1001E0000223A28C1343A384FFF7D2FF154B9D601B +:1001F0005A7852B2002AFBDB134A5A605A7852B23C +:10020000002AFBDBC021114A114B12485360036ADC +:1002100009061B021B0A0B4303620023936007338A +:10022000136030220C4B1A6070BDC0460040004184 +:100230000008004024608000FF01000080BB0A1C11 +:1002400024050000000C00400007030010E000E05F +:10025000E703000000ED00E00000002010B500F012 +:1002600007F910BDFEE7FEE72449254870B5814235 +:100270000AD0244BC41E0022A34203D303331A1A0C +:100280009208920001F058FC1F48204BC11E00222A +:10029000994203D803331A1A92089200002101F000 +:1002A00054FCFF221A4B032193431A4A0C259360F6 +:1002B000022208243026184B18485A62C3788B4310 +:1002C0001343C370C378AB432343C370144B987B71 +:1002D000B0430600202030439873987BA843044322 +:1002E0009C73987B884302439A7380230D4A51681C +:1002F0000B43536000F07AFEFEE7C0466C1D000021 +:1003000000000020D0010020D0010020DC0D0020E2 +:100310000000000000ED00E0FC70004100500041D2 +:100320000048004100400041F7B5134D01906B8833 +:10033000002B18D1082738001A0292B214B2A44632 +:1003400066465400A2B2002E02DA0C4A5440A2B211 +:10035000013880B20028F1D158002A52802201339E +:1003600052009342E7D1019A0B0A53405800285A91 +:100370000902484080B2FEBDD001002021100000DB +:10038000074B1B685843074B1A789523002A00D166 +:100390001233012243439A4200D370470132FAE7F5 +:1003A00000000020D003002070B505001C240020B0 +:1003B0000F260B00E3403340002A09D1002B07D160 +:1003C000002C0BD0002809D1043C231DF1D170BDB5 +:1003D000092B03DD37332B540130F5E73033FAE7CF +:1003E000054B064A1A60BFF34F8F054B054ADA608A +:1003F000BFF34F8FFEE7C046FC7F0020EF6926F079 +:1004000000ED00E00400FA0510B50B4A1368002B5C +:100410000ED10A4911600A490A6801320A60094985 +:100420000868002806D0824204D30B60FFF7D8FF8B +:10043000013B136010BDC046E4030020DC05000052 +:10044000E0030020D8030020054B064A1A60BFF3E2 +:100450004F8F054B054ADA60BFF34F8FFEE7C0466A +:10046000FC7F0020EF6916F000ED00E00400FA05C3 +:10047000012210B5194B1A491A701A4A1368013330 +:1004800013600A68002A0CD017481018834203D161 +:1004900080241648E4050460934201D100230B60D8 +:1004A00010BD1348DBB20278002B10D180210F4B16 +:1004B000C905196011000A39C9B20E4BF02902D9D9 +:1004C0001978494219701B78D2180270E8E79342F4 +:1004D000E6D18022084BD2051A60E1E7D003002064 +:1004E000DC030020D403002018FCFFFF98440041E7 +:1004F000050000200400002094440041074B084AF6 +:100500001B681168994207D2FA21C9005B18136071 +:100510008022044BD2051A607047C046D4030020E5 +:10052000DC03002094440041044B80221900D205D2 +:10053000883198330A601A607047C0460044004111 +:1005400070470000202370B5354A0F20D1690B4356 +:10055000D361012233490B7813430B70324B197866 +:100560008143197006211C78214319702F490C789A +:1005700022430A701A7802401A70602219780A43DE +:100580001A702B4A2B4B53805378DB09FCD1012284 +:10059000294B19780A431A709A78D207FCD41F2085 +:1005A00026490A68520B0240824200D105221C8D66 +:1005B000234D024092012C4022431A850A68920C76 +:1005C00002401F2A00D1023A1F24188D2240A04366 +:1005D000024307201A850A68D20D0240824200D1E8 +:1005E0000322198D02401748120301400A437F215C +:1005F0001A851A7814480A401A70042219780A4396 +:100600000C211A7058621A898A431A811A890B3987 +:100610008A431A8180220021520001F096FA70BDAF +:1006200000040040584400413C440041594400410A +:10063000000C004006400000005000412460800093 +:100640003FF8FFFFFF8FFFFF74060020F7B51C0087 +:10065000314B06005B6A0F0015000193002C05D199 +:10066000443454432D4BE418FFF7CEFE63782278D0 +:10067000934210D2D51ABD4200D93D00002E08D0B9 +:10068000211DC9182A00300001F056FA63785B1961 +:1006900063702800FEBD019BA1786A01D318002970 +:1006A00011D1211D196059681D48890B89035960B2 +:1006B0005968014059601B49551840212879014368 +:1006C00029710121A1701449002551180191164981 +:1006D0005218127AD207DCD55D68A278EDB2257087 +:1006E000022A04D12A001968201D01F025FABD4212 +:1006F00000D93D00002E0CD02A0030006570211D6D +:1007000001F01AFA0122019BFF331A720023A37031 +:10071000BFE76670F6E7C046005000413004002095 +:10072000FF3F00F000510041FF50004110B5002391 +:10073000FFF78CFF10BDF8B505000C0016001F0078 +:10074000002C00D1F8BD210028003B003200FFF74B +:100750007DFF241A2D18F3E7F8B50D001F49560147 +:100760004C6A3419002B1BD063695B005B08636122 +:1007700020616369AA049B0B920C9B031343636182 +:100780006369174A174813406361321802238021B6 +:10079000160013729171327A1A42FCD02800F8BD0B +:1007A000012163695B005B0F03339940A94209D8BB +:1007B000043A531E9A416369D1075B005A080A4301 +:1007C0006261D5E744275743074A0437BF18010041 +:1007D0002A00380001F0B0F93800C9E700500041A4 +:1007E000FF3F00F0FF5000413004002010B500230F +:1007F000FFF7B2FF10BD000010B5054A0B001188CD +:10080000994200D919000022FFF7F0FF10BDC04641 +:100810007407002007B500216B460A00D81D01703F +:10082000FFF7E4FF07BD0000F0B5B94C9BB0FFF740 +:10083000EBFDA38B08211A000A400392B54A0B4234 +:100840002ED080234020A183A372C024093151708F +:1008500090715371B04BB14D5968A40529402143A3 +:100860005960596929400C43AD495C6104311960F4 +:10087000AC49AD4C196159680C4080218902214373 +:1008800059605968890B8903596050710022A74B40 +:100890001A70A64B1878431E9841C0B21BB0F0BD29 +:1008A0001020137A0342F4D09D4D10726D899C483C +:1008B000AC46664680799E4D02909948984BC78811 +:1008C000C0792E80402501909B889548994E0089DB +:1008D0005571B34200D1B0E136DC81246400A342FB +:1008E00000D1BFE115DC822B00D190E106DC803B1A +:1008F000012B00D886E120239371CAE780214900AB +:100900008B42F8D00221FF318B42F4D1FFF782FFF6 +:10091000BFE7B02189008B4200D16DE10EDC0139C7 +:10092000FF398B42E7D0A23B8349FF3B0B42E2D128 +:10093000002308210893099308A8C4E0C021890076 +:100940008B42D8D07D49DFE77D488342DED000DD91 +:1009500096E07C498B4212DC01398B4200DBACE033 +:1009600079498B42E4D06031FF318B42C3D1FFF72C +:1009700051FF80235B423B43DBB2A37289E78821AE +:100980006A4809018B4200D195E080318B42B2D197 +:100990000770FFF73FFFA02303225B00E254C02251 +:1009A0005D4B5E49586C92050840104358644620E0 +:1009B000FF302554654830271864902040002754A4 +:1009C000586B8026084010435863922040002654FC +:1009D0005F481863B02040002554586F084058679E +:1009E000B220400026543C307D3E2654FC388446DC +:1009F0009C44604606680E4016430660A626FF36F5 +:100A0000A5551E005348A0363060C0267600A75575 +:100A10001F0094373E6880200E4016433E60C22679 +:100A20007600A0551E004C4F90363760E0264427D4 +:100A30007600A7550436A555A055C01984469C4498 +:100A4000604606680E4016430660D42084469C44E7 +:100A5000604606680E4016430660F0268020760049 +:100A6000A7550436A555A0551C00E4342068F4337E +:100A7000084010432060186801400A431A6008E7E4 +:100A80003648834200D122E10ADC35498B4200D14D +:100A90003CE7344934480B40834200D136E72AE72B +:100AA00032498B4200D131E731498B4200D022E7F5 +:100AB000002308A80380012105E080235B009F42FA +:100AC00004D112212B48FFF797FEE2E680239B001A +:100AD0009F4202D199212848F5E7019B032B78D149 +:100AE000029B032B00D906E708AC48220021200016 +:100AF00001F02BF8019B6370029B002B3ED1092271 +:100B000004332370A270E37020002178DBE7C04635 +:100B100000500041FF50004174060020FFFFFF8F8E +:100B2000E803002034040020FF3F00F02C040020E4 +:100B30007407002002030000FFFE00000103000014 +:100B40002109000081060000A1030000BC04002070 +:100B5000780400208805002044050020A121000021 +:100B600021200000FFFEFFFF210A000021220000DB +:100B7000A1FE0000981B00004400002004AE3200DB +:100B8000544B23CB23C200252F001B681360AB00FE +:100B9000514AF358D01919680122FFF705FC0135B5 +:100BA0003F18042DF3D100234B4AD3554B4B4C4AED +:100BB0009B799B009D58280000F0CFFF2300013057 +:100BC000400020702A7802330135002A9CD01A7028 +:100BD000F8E7019B0F2B02D13921424873E7019BB3 +:100BE000212B02D1092140486DE7019B222B00D027 +:100BF00081E621213D4866E7072800D07BE6AA214F +:100C00003B4860E7039B08A8022103805BE708429A +:100C100000D070E60F235022034008335B011042DE +:100C200006D0344A9B189B799B06DB0F08A8EBE79C +:100C3000304A9B189B79DB06F7E70F24030023401B +:100C4000204200D157E60140394300D053E6294AFB +:100C500008335B019B18020602D520225A7155E623 +:100C60001022FBE70F2403002340204200D142E67C +:100C70000140394300D03EE65B01020613D51E4910 +:100C80001C4A9A185B182021D879084200D13DE609 +:100C900059711B7A2B4200D138E613004022FF33F2 +:100CA0001A723E3ADAE710201349124A9A185B1872 +:100CB000D979014200D129E658711B7A01180B42FB +:100CC00000D123E61300FF3301221972C6E70B4857 +:100CD000F9E6C046881B000050060020E80300200B +:100CE000D01B000008000020E0000020AC1B00002A +:100CF000F400002000500041FF500041EC000020B3 +:100D000010B5FFF71FFC0022034B1A700223034AA1 +:100D100011780B43137010BD2C04002000500041CB +:100D200010B50C000122FFF761FD200010BD10B5C9 +:100D30000C000122FFF75AFD200010BD70B5040021 +:100D40000D00FFF771FD03000020834204D0022252 +:100D500029002000FFF7EAFC70BD70B505000C000B +:100D6000FFF762FD002807D000230222210028009F +:100D7000FFF7E1FC200070BD0400FBE730B5002365 +:100D80002025934200DB30BD0C78002C03D00131CC +:100D9000C4540133F5E72C00FAE70000F8B58022CF +:100DA0000C00050000212000920000F0CEFE002D76 +:100DB0000CD13E2220004A4900F0BEFEFF235522FE +:100DC0005B00E254474B9218E254F8BD7E2D23D8C5 +:100DD0006E1E3E2E01D9403D2E00002E12D08025E1 +:100DE0003302581C404EFF30ED001A1F591CAA4216 +:100DF00003D2B3420ED08BB223800B000234814267 +:100E0000F3D1E2E7F0230922FF212370601C00F0F8 +:100E10009CFEE4E7354BEFE7822D31D87F2DD4D10E +:100E20002F490B2220002B31FFF7A8FF8027282312 +:100E30002F4EE3727D3D3F03F0683B0020340028D5 +:100E400002D000F08AFE03001A0A237762771A0C98 +:100E50001B0EE3772B0A3100A277A5760B22E376EF +:100E60002000FFF78BFF67224D2301355242ADB2C0 +:100E700022746374227663761036052DDCD1A4E7E4 +:100E80002B00833B012B0CD8194A1B01D318DD68BA +:100E9000280000F062FE29000200200000F04CFE55 +:100EA00093E78023853D2902DB02994200D38CE73A +:100EB000104B114A2360114B20006360FE235B003E +:100EC000E2508023DB00A361802380229B01A3608A +:100ED0000B4B52006561E1602261E3612030DDE788 +:100EE000E01B0000FF01000003040000FFFF000002 +:100EF000881C00005546320A306FB10A57515D9E7A +:100F0000882BED6870B516001D000A682F4B0C0089 +:100F10009A4252D12E4B4A689A424ED1FE2252003A +:100F20002C4B8A589A4248D18B689A0403D52A4A96 +:100F3000C969914241D1DB0712D4802322695B0049 +:100F40009A420DD1E068C3B2002B09D1F822234B9D +:100F50009202C318934203D221002031FFF700F917 +:100F6000002D2BD0A369002B28D029681C4A8B4266 +:100F700006D0934201D8002901D001235B422B60A7 +:100F80006369934219D8072201211A409140DB0876 +:100F9000EB181A7AC8B2114204D1696802430131D0 +:100FA0001A7269606A682B689A4206D3002E04D1CF +:100FB0000C4B1B681E330C4A136070BD002EFCD115 +:100FC000084B1B682D33FF33F5E7C0465546320A00 +:100FD00057515D9E306FB10A882BED6800E0FFFF2E +:100FE00063040000E0030020D8030020F8B5624D40 +:100FF000AB68002B00D0FEE70221604B5A6B8A439E +:101000005A63DA681205FCD55D4A5A630222596BAD +:101010000A435A63DA689205FCD5DA685205FCD4B3 +:1010200002215A6B8A435A63DA681205FCD50822FA +:10103000596B0A435A630222596B0A4351495A6356 +:101040000B68013321D120224F4BFF32188B024312 +:101050001A8358684D4A02435A604D4ADA614D4A34 +:101060001A801A7DD207FCD54B4A1A801A7DD20706 +:10107000FCD54A4A0A604A4A4A4911604A4A1A80DB +:101080001A7DD207FCD5FFF7DFF90023474A13701A +:10109000D379DB09FCD1FFF747FA454B1E68454B76 +:1010A000F218F8239B029A4213D8434B434A19681B +:1010B000434C444B914232D11978434AC90702D478 +:1010C000216891422BD0414B22601B68404A323349 +:1010D0001360FFF765F8BFF35F8F62B6FFF710FE8E +:1010E0004020FFF72DFA80270A230126394DFF0102 +:1010F0002B70FFF799FB384C00280AD02378DAB21E +:10110000002B05D1324B38001A60FFF719FA2E7008 +:1011100026702378002B25D000F0D6FAFCE71B7848 +:10112000DB07DA0F002B0DD00023802223602B4B2E +:10113000D2051A6080239B011A6882F30888AB608D +:101140003047C6E72168264B994201D12260C0E7AB +:1011500021681D4A9142E7D0FA2023604000FFF742 +:101160000FF9E1E72378002BC3D1FF33C046013BE1 +:10117000002BFBD1BDE7C04600ED00E000080040B9 +:10118000040027000040800000400041800004006F +:101190000020400005A5FFFF44A5FFFFFAC7E0D8E7 +:1011A000044080005DFCFFFF06A5FFFF001000402B +:1011B0000420000000E0FFFFB42000007CB0EE87B8 +:1011C000FC7F002038040040EF6926F0E003002097 +:1011D000D80300200400002076070020944400413A +:1011E000EF6916F010B5054C12220021200000F026 +:1011F000ACFCF0232370E63BE37110BDB309002083 +:1012000010B5FFF7EFFF0022014B1A7310BDC04667 +:101210009E010020F7B580270400160000250191EB +:10122000BF00A368AB4200D8F7BD3900A278019B8C +:10123000E068FFF780FA80235B01E81801223300A1 +:10124000E168FFF75FFE0135EBE770B513000600BC +:101250000C00150000200A000121FFF7F7F9002318 +:10126000984206D02B0022001F213000FFF7EEF934 +:101270000123180070BD000010B504220D210248A2 +:10128000FFF7B4FA10BDC0469E01002010B50B4C0C +:101290000B4861792379A27909021943E379120491 +:1012A00011431B060B431A0A037142711A0C1B0EE1 +:1012B0008271C371FFF7E0FF10BDC04694090020A2 +:1012C0009E01002070B504220D00FFF78FFA154C27 +:1012D00085420FD0FFF786FF012304222373124BB0 +:1012E0009A700022DA701A715A719A711A735A73CD +:1012F000FFF7CCFFFFF784FF637A217A1B020B43D1 +:10130000A17AE27A0904120619431143491B0B0A18 +:10131000217263720B0C090EA372E172FFF7B6FF24 +:1013200070BDC0469E010020B309002070B5184C66 +:10133000142205000021200000F007FC154A002DB2 +:101340001FD021000823D07D08313F26527C324037 +:101350001C2A01D0B24205D10F4A0C330A80052263 +:10136000DBB2CA70191C834200D9011CC9B2002D1E +:101370000BD0023B9BB21B0223802000FFF7A2FF91 +:1013800070BD0423D07C211DDFE7013B2370F4E70F +:1013900078090020940900201C0A0000F0B5040020 +:1013A00000252A4B85B0597C03AAD170997C917095 +:1013B000D97C5170197D1170997D02AAD170DB7DA5 +:1013C000937053880193019BAB4205D8FFF718FF38 +:1013D000FFF75CFF05B0F0BDFFF726FA0028F9D053 +:1013E000039B1B4FEE18002C1FD039003000FFF775 +:1013F000D5FC8021042238008900FFF7F7F9154A4F +:101400000135507A137A917A00021843D37A09048D +:1014100008431B06104903435B18190A13725172E3 +:10142000190C1B0E9172D372CDE780212300380076 +:1014300005228900FFF77FF9220039003000074BB1 +:10144000FFF760FDFFF75AF8D9E7C046940900207E +:10145000780700209E01002000FEFFFFC809002041 +:101460007FB500F02FFB444E002205213000FFF72E +:10147000ECFE002849D0737A327AB57A1B021A43FF +:10148000F37A2D041B0615433C4C1D432B0A637253 +:101490002B0CA3722B0E2572E372F07B2F2827D81A +:1014A000192815D8032827D012282FD0002840D07B +:1014B000FFF798FE012305222373314B9A70002217 +:1014C000DA701A715A719A711A7320325A7332E0B3 +:1014D0001A381528ECD800F01BFB282CEBEB2CEB72 +:1014E000EBEBEB3FEB31EBEB39EB3DEBEBEBEB2CD1 +:1014F0005A28DDD1012019E0F37C191C122B00D9E8 +:1015000012211F48C9B2FFF7DDFE7FBD1D4C1E49E9 +:10151000200018220830FFF731FCF37C191C242B23 +:1015200000D924212000C9B2EDE70020FFF7FEFE1C +:10153000EBE7FFF765FEFFF7A9FEE6E78023134818 +:101540009B024360124B08210360DCE70120FFF798 +:1015500025FFDAE70020FAE70C260E49320001A841 +:1015600000F0EAFAB54204D9002326726372A3722E +:10157000E3720C2101A8C6E7940900209E01002017 +:10158000B3090020AC010020721B00008C09002070 +:1015900000003E7F231D000072B6BFF35F8F044B37 +:1015A000044A9A829A830022034B9A607047C0468D +:1015B00000500041FF03000000ED00E0F0B5C5B0B1 +:1015C0000DAF0400FFF7E8FF44220021380000F0CF +:1015D000BCFA982200211EA800F0B7FA02230F21BE +:1015E000BB70A37862780B40A370264B0A4005932A +:1015F000636805AD06930023E16807932248627093 +:101600002B7301221EABFFF77DFC390020001EAAC0 +:10161000FFF700FE280001230D216278FFF79CF8F8 +:10162000002505AE3A003000A178FFF70EFE002835 +:101630001ED0144B01AD0193069B6B60079BAB6002 +:1016400000232B73F37B002BE4D02A2B17D16B469E +:10165000B27D3900DA70F27D20009A705E881EAA91 +:10166000A660FFF7D7FDAB6876029E1BAE60D1E7A0 +:10167000064B9D4201D9FEF7B3FE0135D1E7FEF7D7 +:10168000E3FEC7E753425355FF0F0000F824010063 +:1016900030B5EFF30883054C2360036883F30888B3 +:1016A0004568A847236883F3088830BDC00A002036 +:1016B00007B5010001226846FEF776FE082168465C +:1016C000FFF72EFB07BD0000F8B500237A227F4C00 +:1016D00023607F4B1A70FFF7C3FE7E4D4021280028 +:1016E000FFF72CFB7C4B186000232B54984201D051 +:1016F000FEF704FF0022794B1D60794B1A60784891 +:10170000754B02681F68BA42E5D2744E31680B7897 +:10171000FF2B37D0734D232B00D0B4E06C4B1B78DC +:10172000532B34D12B680132013102603160BA1A77 +:101730009A4200D91A006C4D20682A6000F0FCF92A +:10174000674829680368654ACB18013B0360106845 +:101750004B1EC3181360FF23644D29701940614B61 +:101760001B688B4203D92068591AFFF7F6FAC04666 +:101770007A22574B1A7000225A4B1A60574A136844 +:1017800001331360564A136801331360B7E7522BD5 +:1017900004D129682068FFF7CAFAE9E74F2B03D183 +:1017A0002B6822681370E3E7482B03D12B6822686B +:1017B0001380DDE7572B0AD14D4B22689A4202D1A4 +:1017C0000020FEF7BDFE23682A681A60D0E76F2B61 +:1017D00004D101212068FFF7A3FAC9E7682B05D1DE +:1017E000022123681B882B602800F4E7772B04D1A3 +:1017F000236804211B682B60F6E7472B09D1286872 +:10180000FFF746FF3B4B1B78002BB1D001213A4834 +:10181000E1E7542BACD04E2BAAD0562B02D12A2173 +:101820003648D8E7582B05D12868FEF757FC032126 +:101830003348D0E7592B0DD12A682068314B002A54 +:1018400003D1186003213048C5E719689208FEF7F4 +:101850005DFCF7E75A2B8BD12F6800252668F71916 +:10186000B74209D101212948FFF75AFA2800FFF7AA +:101870001FFF03212648AEE729003078FEF754FD0C +:1018800001360500ECE71A00303AD1B2092904D834 +:101890002B681B0113432B6070E71A00413A052A9D +:1018A00003D82A68373B1201F4E71A00613A052A87 +:1018B00003D82A68573B1201ECE72C2B03D12B6885 +:1018C00023600023E7E7024A1370FAE7BC0A00200E +:1018D000610A0020680A0020B40A0020B80A00202B +:1018E000AC0A0020640A0020C80A0020B00A0020C8 +:1018F0000CED00E0600A00202F1D00003F1D0000DD +:10190000311D0000C40A0020351D0000391D0000F3 +:101910003B1D00000300F7B5473304001A7840214F +:1019200003000020FEF792FE002801D10020FEBD3A +:1019300000232370237901930423E356002BF5DB66 +:101940003F262000019FA51DEB8F37404830C0186F +:101950003A00611D00F0F0F8E88F019BC01980B2D9 +:10196000B34301D1E887E1E70023EB87DFE7F0B578 +:1019700006000C001F0093B001923F25AC4203DC2F +:101980002500002F00D140373B0002AA2B431370E3 +:1019900002AB31002A00581C00F0CEF86B46402103 +:1019A0001A7902A80123FEF7D7FE7619641BE4D149 +:1019B00013B0F0BD030010B547331A78043100238B +:1019C0004830FFF7D4FF10BDF7B50400FFF7A2FFC2 +:1019D000002842D0230048339A88A06C1A80002245 +:1019E00001385A80082865D800F09CF8150523278F +:1019F000252A483F3800314E300000F0AEF805008F +:101A0000200031002A004C3000F096F82900200018 +:101A1000FFF7D0FF21E00123E364FF332365802338 +:101A2000DB006365A0235B00A365254B1421E36500 +:101A3000EDE7FEF7D5FC0021E9E7FEF705FDFAE743 +:101A400020000021FFF7B6FF8023206D9B01984204 +:101A500003D321005431FEF783FBF7BD2100626DF3 +:101A6000206D5831FEF747FBE5E72000656D216DDD +:101A70002A004C30FEF73FFBA900C8E70026636D43 +:101A8000276D0193019BB34201DC5900BFE73D0084 +:101A900000212B001878FEF747FC7B1C0135FF3333 +:101AA00001009D42F5D1230072004C332F00985263 +:101AB0000136E7E701225A80BDE7C046B81C0000A6 +:101AC000882BED6807480622030010B547331A70CB +:101AD000FFF77AFF04480722030047331A70FFF725 +:101AE00073FF10BDCC0A0020540C002010B5E2B0EA +:101AF0000400FFF751FDC42200216846520000F0A7 +:101B000024F847236B441C706846FFF75DFFFBE732 +:101B100002B4714649084900095649008E4402BC86 +:101B20007047C04602B4714649084900095C490043 +:101B30008E4402BC7047C046002310B59A4200D1C3 +:101B400010BDCC5CC4540133F8E703008218934203 +:101B500000D1704719700133F9E70023C25C0133EB +:101B6000002AFBD1581E70474D6963726F63686924 +:101B7000700053414D4432312058706C61696E657C +:101B8000642050726F0000000CA0800040A0800014 +:101B900044A0800048A0800012011002EF02014022 +:101BA000D804022401420102030100000697FF0944 +:101BB00001A101150026FF00750895400901810269 +:101BC00095400901910295010901B102C000000090 +:101BD00000000000681B0000721B0000500600207F +:101BE000EB3C905546322055463220000201010060 +:101BF0000240007E3EF83F000100010000000000AE +:101C0000000000008000294200420053414D443250 +:101C10003158504C000046415431362020203C21A0 +:101C2000646F63747970652068746D6C3E0A3C68FB +:101C3000746D6C3E3C626F64793E3C736372697094 +:101C4000743E0A6C6F636174696F6E2E7265706C9E +:101C5000616365282268747470733A2F2F777777E1 +:101C60002E7078742E696F2F22293B0A3C2F7363E4 +:101C7000726970743E3C2F626F64793E3C2F6874C9 +:101C80006D6C3E0A00000000494E464F5F554632DB +:101C900054585400B81C0000494E44455820202098 +:101CA00048544D001E1C000043555252454E5420CE +:101CB000554632000000000055463220426F6F74D6 +:101CC0006C6F616465722076332E31362E302D3183 +:101CD000352D6761613532623232205346485752A2 +:101CE0004F0D0A4D6F64656C3A2053414D443231BB +:101CF0002058706C61696E65642050726F0D0A42E5 +:101D00006F6172642D49443A2053414D4432314A47 +:101D10003138412D58706C61696E65642D50726F59 +:101D20000D0A000000000800003E800200020006CC +:101D300000580A0D00590A0D005A00230A0D0076BA +:101D4000312E31205B41726475696E6F3A58595A71 +:101D50005D205365702032382032303234203135E6 +:101D60003A35353A34340A0D000000000100000015 +:101D700001C80000050F3900021810050038B60828 +:101D800034A909A0478BFDA0768815B6650001012E +:101D9000001C100500DF60DDD88945C74C9CD2656A +:101DA0009D9E648A9F00000306AA000200000000B6 +:101DB000090299000501008032080B0002020201AD +:101DC00000090400000102020100052400100104C2 +:101DD00024020605240600010524010301070583EA +:101DE000030800FF09040100020A00000007058142 +:101DF0000240000007050202400000090402000240 +:101E0000080650000705840240000007050502404F +:101E10000000090403000203000000092100010082 +:101E20000122210007058603400001070506034043 +:101E300000010904040002FF2A010007058703408E +:101E4000000107050703400001000000090403002A +:101E5000020300000300000000C2010000000800AF +:101E60000A00000000000306AA00080002000400A7 +:101E7000A0001400030057494E55534200000000D3 +:101E80000000000000008400040007002A00440055 +:101E90006500760069006300650049006E0074000B +:101EA0006500720066006100630065004700550030 +:101EB000490044007300000050007B0039003200EC +:101EC0004300450036003400360032002D00390052 +:101ED0004300370037002D0034003600460045002F +:101EE0002D0039003300330042002D003300310053 +:101EF00043004200390043003500420042003300F5 +:101F0000420039007D00000000005342535500009C +:101F1000000000000000000000800202200000001D +:101F200000000000000000000000000000000000B1 +:101F30000000000000000000312E303000000000E2 +:101F40000000000000000000000000000000000091 +:101F50000000000000000000000000000000000081 +:101F60000000000000000000000000000000000071 +:101F70000000000000000000000000000000000061 +:101F80000000000000000000000000000000000051 +:101F90000000000000000000000000000000000041 +:101FA0000000000000000000000000000000000031 +:101FB0000000000000000000000000000000000021 +:101FC0000000000000000000000000000000000011 +:101FD0000000000000000000000000000000000001 +:101FE00000000000000000000000000000000000F1 +:101FF00000000000ED1A0000BD150000B81C000034 +:00000001FF From 4a159d16febeba65b2de52cb05657b9515a9bcae Mon Sep 17 00:00:00 2001 From: robert-hh Date: Tue, 24 Sep 2024 17:09:06 +0200 Subject: [PATCH 21/46] samd/boards/SAMD21_XPLAINED_PRO: Use the SPI flash for the file system. The initial settings did not support it. The change required to add a dedicated handling of the Adesto 1MByte flash of the XPLAINED PRO board, which does not support the sfdp feature. Fixes the ID check of the Adesto/Renesas 1MByte flash. Signed-off-by: robert-hh --- .../SAMD21_XPLAINED_PRO/mpconfigboard.h | 4 ++ .../SAMD21_XPLAINED_PRO/mpconfigboard.mk | 2 + .../samd/boards/SAMD21_XPLAINED_PRO/pins.csv | 4 ++ ports/samd/samd_spiflash.c | 43 ++++++++++++++----- 4 files changed, 42 insertions(+), 11 deletions(-) diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h index 064d1ecc0d03..09bad81bb79a 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.h @@ -2,3 +2,7 @@ #define MICROPY_HW_MCU_NAME "SAMD21J18A" #define MICROPY_HW_XOSC32K (1) + +#define MICROPY_HW_SPIFLASH (1) +#define MICROPY_HW_SPIFLASH_ID (5) +#define MICROPY_HW_SPIFLASH_BAUDRATE (12000000) diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk index f95c6549381b..cc43c22cea58 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/mpconfigboard.mk @@ -2,3 +2,5 @@ MCU_SERIES = SAMD21 CMSIS_MCU = SAMD21J18A LD_FILES = boards/samd21x18a.ld sections.ld TEXT0 = 0x2000 + +MICROPY_HW_CODESIZE ?= 248K diff --git a/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv b/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv index 69d0c136d08e..e5327e7916bf 100644 --- a/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv +++ b/ports/samd/boards/SAMD21_XPLAINED_PRO/pins.csv @@ -53,3 +53,7 @@ USB_DP,PA25 SWCLK,PA30 SWDIO,PA31 +FLASH_MOSI,PB22 +FLASH_MISO,PB16 +FLASH_SCK,PB23 +FLASH_CS,PA13 diff --git a/ports/samd/samd_spiflash.c b/ports/samd/samd_spiflash.c index cd4f10640dbf..8ada7e96092c 100644 --- a/ports/samd/samd_spiflash.c +++ b/ports/samd/samd_spiflash.c @@ -45,6 +45,7 @@ const uint8_t _COMMANDS_32BIT[] = {0x13, 0x12, 0x21}; // READ, PROGRAM_PAGE, ER #define COMMAND_JEDEC_ID (0x9F) #define COMMAND_READ_STATUS (0x05) +#define COMMAND_WRITE_SR1 (0x01) #define COMMAND_WRITE_ENABLE (0x06) #define COMMAND_READ_SFDP (0x5A) #define PAGE_SIZE (256) @@ -88,7 +89,7 @@ static void wait(spiflash_obj_t *self) { mp_hal_pin_write(self->cs, 0); spi_transfer((mp_obj_base_t *)self->spi, 2, msg, msg); mp_hal_pin_write(self->cs, 1); - } while (msg[1] != 0 && timeout-- > 0); + } while ((msg[1] & 1) != 0 && timeout-- > 0); } static void get_id(spiflash_obj_t *self, uint8_t id[3]) { @@ -123,6 +124,17 @@ static void write_enable(spiflash_obj_t *self) { mp_hal_pin_write(self->cs, 1); } +// Write status register 1 +static void write_sr1(spiflash_obj_t *self, uint8_t value) { + uint8_t msg[2]; + msg[0] = COMMAND_WRITE_SR1; + msg[1] = value; + + mp_hal_pin_write(self->cs, 0); + spi_transfer(self->spi, 2, msg, NULL); + mp_hal_pin_write(self->cs, 1); +} + static void get_sfdp(spiflash_obj_t *self, uint32_t addr, uint8_t *buffer, int size) { uint8_t dummy[1]; write_addr(self, COMMAND_READ_SFDP, addr); @@ -155,27 +167,36 @@ static mp_obj_t spiflash_make_new(const mp_obj_type_t *type, size_t n_args, size mp_hal_pin_write(self->cs, 1); wait(self); - // Get the flash size from the device ID (default) uint8_t id[3]; get_id(self, id); + bool read_sfdp = true; + if (id[1] == 0x84 && id[2] == 1) { // Adesto self->size = 512 * 1024; - } else if (id[1] == 0x1f && id[2] == 1) { // Atmel / Renesas + } else if (id[0] == 0x1f && id[1] == 0x45 && id[2] == 1) { // Adesto/Renesas 8 MBit self->size = 1024 * 1024; + read_sfdp = false; + self->sectorsize = 4096; + self->addr_is_32bit = false; + // Globally unlock the sectors, which are locked after power on. + write_enable(self); + write_sr1(self, 0); } else { self->size = 1 << id[2]; } // Get the addr_is_32bit flag and the sector size - uint8_t buffer[128]; - get_sfdp(self, 0, buffer, 16); // get the header - int len = MIN(buffer[11] * 4, sizeof(buffer)); - if (len >= 29) { - int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); - get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table - self->sectorsize = 1 << buffer[28]; - self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + if (read_sfdp) { + uint8_t buffer[128]; + get_sfdp(self, 0, buffer, 16); // get the header + int len = MIN(buffer[11] * 4, sizeof(buffer)); + if (len >= 29) { + int addr = buffer[12] + (buffer[13] << 8) + (buffer[14] << 16); + get_sfdp(self, addr, buffer, len); // Get the JEDEC mandatory table + self->sectorsize = 1 << buffer[28]; + self->addr_is_32bit = ((buffer[2] >> 1) & 0x03) != 0; + } } self->commands = self->addr_is_32bit ? _COMMANDS_32BIT : _COMMANDS_24BIT; From ceae0e14946b48f0e4b01669f241e66467bedddc Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 18 Nov 2024 10:56:07 +0100 Subject: [PATCH 22/46] samd/samd_flash: Make flash read/write methods access self parameters. Use `self` (the first argument) instead of the global `samd_flash_obj` when accessing the `flash_base` parameter. This allows there to be multiple flash objects for various types of filesystem. Signed-off-by: robert-hh --- ports/samd/samd_flash.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/ports/samd/samd_flash.c b/ports/samd/samd_flash.c index ef0de7b6fb58..f68bdf140f49 100644 --- a/ports/samd/samd_flash.c +++ b/ports/samd/samd_flash.c @@ -103,7 +103,8 @@ static mp_obj_t samd_flash_version(void) { static MP_DEFINE_CONST_FUN_OBJ_0(samd_flash_version_obj, samd_flash_version); static mp_obj_t samd_flash_readblocks(size_t n_args, const mp_obj_t *args) { - uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + samd_flash_obj.flash_base; + samd_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + self->flash_base; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_WRITE); if (n_args == 4) { @@ -118,7 +119,8 @@ static mp_obj_t samd_flash_readblocks(size_t n_args, const mp_obj_t *args) { static MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(samd_flash_readblocks_obj, 3, 4, samd_flash_readblocks); static mp_obj_t samd_flash_writeblocks(size_t n_args, const mp_obj_t *args) { - uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + samd_flash_obj.flash_base; + samd_flash_obj_t *self = MP_OBJ_TO_PTR(args[0]); + uint32_t offset = (mp_obj_get_int(args[1]) * BLOCK_SIZE) + self->flash_base; mp_buffer_info_t bufinfo; mp_get_buffer_raise(args[2], &bufinfo, MP_BUFFER_READ); if (n_args == 3) { From cbffe61f96d060ed4af2bde7e3043a3664dc147a Mon Sep 17 00:00:00 2001 From: robert-hh Date: Mon, 18 Nov 2024 16:50:03 +0100 Subject: [PATCH 23/46] samd/mboot/README.md: Add information about the bootloader source. Includes the LICENSE file of the source and the specific board files for the Xplained Pro board. Signed-off-by: robert-hh --- ports/samd/mboot/LICENSE | 60 ++++++++++++++++++++++++++++++++++++++ ports/samd/mboot/README.md | 5 ++++ 2 files changed, 65 insertions(+) create mode 100644 ports/samd/mboot/LICENSE create mode 100644 ports/samd/mboot/README.md diff --git a/ports/samd/mboot/LICENSE b/ports/samd/mboot/LICENSE new file mode 100644 index 000000000000..7fffbab21e0e --- /dev/null +++ b/ports/samd/mboot/LICENSE @@ -0,0 +1,60 @@ +The MIT License (MIT) + +Copyright (c) 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. + +Third Party Programs: The software may include third party programs that +Microsoft, not the third party, licenses to you under this agreement. +Notices, if any, for the third party programs are included for your +information only. + + + +Otherwise, where noted: + +/* ---------------------------------------------------------------------------- + * SAM Software Package License + * ---------------------------------------------------------------------------- + * Copyright (c) 2011-2014, Atmel Corporation + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following condition is met: + * + * Redistributions of source code must retain the above copyright notice, + * this list of conditions and the disclaimer below. + * + * Atmel's name may not be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * DISCLAIMER: THIS SOFTWARE IS PROVIDED BY ATMEL "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE + * DISCLAIMED. IN NO EVENT SHALL ATMEL BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, + * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * ---------------------------------------------------------------------------- + */ + diff --git a/ports/samd/mboot/README.md b/ports/samd/mboot/README.md new file mode 100644 index 000000000000..1695f0a89112 --- /dev/null +++ b/ports/samd/mboot/README.md @@ -0,0 +1,5 @@ +The SAMD Xplained Pro bootloader is built using the files from the +repository https://github.com/adafruit/uf2-samdx1. The repository +includes the LICENSE file https://github.com/adafruit/uf2-samdx1/LICENSE +and a README https://github.com/adafruit/uf2-samdx1/README.md with +build instructions. From 0e7c3901b897a1edd53b56b6119f2d4119e88842 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 2 Oct 2024 19:16:39 +1000 Subject: [PATCH 24/46] docs: Add a "Reset and Boot Sequence" reference page. Previously individual ports documented these aspects to varying degrees, but most of the information is common to all ports. In particular, this adds a canonical explanation of `boot.py` and `main.py`. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp8266/general.rst | 35 +-- docs/esp8266/quickref.rst | 8 +- docs/library/builtins.rst | 4 + docs/library/machine.RTC.rst | 2 +- docs/library/machine.USBDevice.rst | 12 +- docs/library/machine.rst | 9 +- docs/library/pyb.rst | 13 +- docs/pyboard/tutorial/reset.rst | 2 + docs/reference/index.rst | 1 + docs/reference/mpremote.rst | 6 +- docs/reference/repl.rst | 9 +- docs/reference/reset_boot.rst | 260 ++++++++++++++++++ docs/renesas-ra/tutorial/program_in_flash.rst | 2 + docs/renesas-ra/tutorial/reset.rst | 20 +- docs/wipy/tutorial/reset.rst | 2 + 15 files changed, 311 insertions(+), 74 deletions(-) create mode 100644 docs/reference/reset_boot.rst diff --git a/docs/esp8266/general.rst b/docs/esp8266/general.rst index be0437e752ee..d7211aa5ab74 100644 --- a/docs/esp8266/general.rst +++ b/docs/esp8266/general.rst @@ -74,40 +74,7 @@ as possible after use. Boot process ------------ -On boot, MicroPython EPS8266 port executes ``_boot.py`` script from internal -frozen modules. It mounts filesystem in FlashROM, or if it's not available, -performs first-time setup of the module and creates the filesystem. This -part of the boot process is considered fixed, and not available for customization -for end users (even if you build from source, please refrain from changes to -it; customization of early boot process is available only to advanced users -and developers, who can diagnose themselves any issues arising from -modifying the standard process). - -Once the filesystem is mounted, ``boot.py`` is executed from it. The standard -version of this file is created during first-time module set up and has -commands to start a WebREPL daemon (disabled by default, configurable -with ``webrepl_setup`` module), etc. This -file is customizable by end users (for example, you may want to set some -parameters or add other services which should be run on -a module start-up). But keep in mind that incorrect modifications to boot.py -may still lead to boot loops or lock ups, requiring to reflash a module -from scratch. (In particular, it's recommended that you use either -``webrepl_setup`` module or manual editing to configure WebREPL, but not -both). - -As a final step of boot procedure, ``main.py`` is executed from filesystem, -if exists. This file is a hook to start up a user application each time -on boot (instead of going to REPL). For small test applications, you may -name them directly as ``main.py``, and upload to module, but instead it's -recommended to keep your application(s) in separate files, and have just -the following in ``main.py``:: - - import my_app - my_app.main() - -This will allow to keep the structure of your application clear, as well as -allow to install multiple applications on a board, and switch among them. - +See :doc:`/reference/reset_boot`. Known Issues ------------ diff --git a/docs/esp8266/quickref.rst b/docs/esp8266/quickref.rst index 6f02da95d89d..635f1f834bb6 100644 --- a/docs/esp8266/quickref.rst +++ b/docs/esp8266/quickref.rst @@ -163,10 +163,10 @@ sys.stdin.read() if it's needed to read characters from the UART(0) while it's also used for the REPL (or detach, read, then reattach). When detached the UART(0) can be used for other purposes. -If there are no objects in any of the dupterm slots when the REPL is -started (on hard or soft reset) then UART(0) is automatically attached. -Without this, the only way to recover a board without a REPL would be to -completely erase and reflash (which would install the default boot.py which +If there are no objects in any of the dupterm slots when the REPL is started (on +:doc:`hard or soft reset `) then UART(0) is automatically +attached. Without this, the only way to recover a board without a REPL would be +to completely erase and reflash (which would install the default boot.py which attaches the REPL). To detach the REPL from UART0, use:: diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index e489375b1f91..88b1fbcfe4a4 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -170,6 +170,10 @@ Exceptions .. exception:: KeyboardInterrupt + |see_cpython| `python:KeyboardInterrupt`. + + See also in the context of :ref:`soft_bricking`. + .. exception:: KeyError .. exception:: MemoryError diff --git a/docs/library/machine.RTC.rst b/docs/library/machine.RTC.rst index fcd78f1c3986..ad0b0759efe5 100644 --- a/docs/library/machine.RTC.rst +++ b/docs/library/machine.RTC.rst @@ -83,7 +83,7 @@ Methods a `bytes` object. Data written to RTC user memory is persistent across restarts, including - `machine.soft_reset()` and `machine.deepsleep()`. + :ref:`soft_reset` and `machine.deepsleep()`. The maximum length of RTC user memory is 2048 bytes by default on esp32, and 492 bytes on esp8266. diff --git a/docs/library/machine.USBDevice.rst b/docs/library/machine.USBDevice.rst index a47fda2a28d3..5c18c49a75bb 100644 --- a/docs/library/machine.USBDevice.rst +++ b/docs/library/machine.USBDevice.rst @@ -32,10 +32,10 @@ Managing a runtime USB interface can be tricky, especially if you are communicat with MicroPython over a built-in USB-CDC serial port that's part of the same USB device. -- A MicroPython soft reset will always clear all runtime USB interfaces, which - results in the entire USB device disconnecting from the host. If MicroPython - is also providing a built-in USB-CDC serial port then this will re-appear - after the soft reset. +- A MicroPython :ref:`soft reset ` will always clear all runtime USB + interfaces, which results in the entire USB device disconnecting from the + host. If MicroPython is also providing a built-in USB-CDC serial port then + this will re-appear after the soft reset. This means some functions (like ``mpremote run``) that target the USB-CDC serial port will immediately fail if a runtime USB interface is active, @@ -44,9 +44,9 @@ device. no more runtime USB interface. - To configure a runtime USB device on every boot, it's recommended to place the - configuration code in the ``boot.py`` file on the :ref:`device VFS + configuration code in the :ref:`boot.py` file on the :ref:`device VFS `. On each reset this file is executed before the USB subsystem is - initialised (and before ``main.py``), so it allows the board to come up with the runtime + initialised (and before :ref:`main.py`), so it allows the board to come up with the runtime USB device immediately. - For development or debugging, it may be convenient to connect a hardware diff --git a/docs/library/machine.rst b/docs/library/machine.rst index 7d2eb26a7ea3..76d111f11ef3 100644 --- a/docs/library/machine.rst +++ b/docs/library/machine.rst @@ -62,14 +62,13 @@ Reset related functions .. function:: reset() - Resets the device in a manner similar to pushing the external RESET - button. + :ref:`Hard resets ` the device in a manner similar to pushing the + external RESET button. .. function:: soft_reset() - Performs a soft reset of the interpreter, deleting all Python objects and - resetting the Python heap. It tries to retain the method by which the user - is connected to the MicroPython REPL (eg serial, USB, Wifi). + Performs a :ref:`soft reset ` of the interpreter, deleting all + Python objects and resetting the Python heap. .. function:: reset_cause() diff --git a/docs/library/pyb.rst b/docs/library/pyb.rst index f169a77f3a6c..5eb75c22b608 100644 --- a/docs/library/pyb.rst +++ b/docs/library/pyb.rst @@ -147,10 +147,10 @@ Power related functions (internal oscillator) directly. The higher frequencies use the HSE to drive the PLL (phase locked loop), and then use the output of the PLL. - Note that if you change the frequency while the USB is enabled then - the USB may become unreliable. It is best to change the frequency - in boot.py, before the USB peripheral is started. Also note that sysclk - frequencies below 36MHz do not allow the USB to function correctly. + Note that if you change the frequency while the USB is enabled then the USB + may become unreliable. It is best to change the frequency in :ref:`boot.py`, + before the USB peripheral is started. Also note that sysclk frequencies below + 36MHz do not allow the USB to function correctly. .. function:: wfi() @@ -205,8 +205,9 @@ Miscellaneous functions .. function:: main(filename) - Set the filename of the main script to run after boot.py is finished. If - this function is not called then the default file main.py will be executed. + Set the filename of the main script to run after :ref:`boot.py` is finished. + If this function is not called then the default file :ref:`main.py` will be + executed. It only makes sense to call this function from within boot.py. diff --git a/docs/pyboard/tutorial/reset.rst b/docs/pyboard/tutorial/reset.rst index 59a3cd82ae1a..ad56995c60dd 100644 --- a/docs/pyboard/tutorial/reset.rst +++ b/docs/pyboard/tutorial/reset.rst @@ -10,6 +10,8 @@ execution of ``boot.py`` and ``main.py`` and gives default USB settings. If you have problems with the filesystem you can do a factory reset, which restores the filesystem to its original state. +For more information, see :doc:`/reference/reset_boot`. + Safe mode --------- diff --git a/docs/reference/index.rst b/docs/reference/index.rst index 98f6b6573769..1558c0fdfa9b 100644 --- a/docs/reference/index.rst +++ b/docs/reference/index.rst @@ -21,6 +21,7 @@ implementation and the best practices to use them. glossary.rst repl.rst + reset_boot.rst mpremote.rst mpyfiles.rst isr_rules.rst diff --git a/docs/reference/mpremote.rst b/docs/reference/mpremote.rst index b47ec76c98ef..d4c6983595fe 100644 --- a/docs/reference/mpremote.rst +++ b/docs/reference/mpremote.rst @@ -547,9 +547,9 @@ device at ``/dev/ttyACM1``, printing each result. mpremote resume exec "print_state_info()" soft-reset -Connect to the device without triggering a soft reset and execute the -``print_state_info()`` function (e.g. to find out information about the current -program state), then trigger a soft reset. +Connect to the device without triggering a :ref:`soft reset ` and +execute the ``print_state_info()`` function (e.g. to find out information about +the current program state), then trigger a soft reset. .. code-block:: bash diff --git a/docs/reference/repl.rst b/docs/reference/repl.rst index 55f76ee1a03b..be02ddebde8e 100644 --- a/docs/reference/repl.rst +++ b/docs/reference/repl.rst @@ -143,10 +143,12 @@ the auto-indent feature, and changes the prompt from ``>>>`` to ``===``. For exa Paste Mode allows blank lines to be pasted. The pasted text is compiled as if it were a file. Pressing Ctrl-D exits paste mode and initiates the compilation. +.. _repl_soft_reset: + Soft reset ---------- -A soft reset will reset the python interpreter, but tries not to reset the +A :ref:`soft_reset` will reset the python interpreter, but tries not to reset the method by which you're connected to the MicroPython board (USB-serial, or Wifi). You can perform a soft reset from the REPL by pressing Ctrl-D, or from your python @@ -182,6 +184,9 @@ variables no longer exist: ['__name__', 'pyb'] >>> +For more information about reset types and the startup process, see +:doc:`/reference/reset_boot`. + The special variable _ (underscore) ----------------------------------- @@ -196,6 +201,8 @@ So you can use the underscore to save the result in a variable. For example: 15 >>> +.. _raw_repl: + Raw mode and raw-paste mode --------------------------- diff --git a/docs/reference/reset_boot.rst b/docs/reference/reset_boot.rst new file mode 100644 index 000000000000..7c0d5f334a6e --- /dev/null +++ b/docs/reference/reset_boot.rst @@ -0,0 +1,260 @@ +Reset and Boot Sequence +======================= + +A device running MicroPython follows a particular boot sequence to start up and +initialise itself after a reset. + +.. _hard_reset: + +Hard reset +---------- + +Booting from hard reset is what happens when a board is first powered up, a cold +boot. This is a complete reset of the MCU hardware. + +The MicroPython port code initialises all essential hardware (including embedded +clocks and power regulators, internal serial UART, etc), and then starts the +MicroPython environment. Existing :doc:`RTC ` +configuration may be retained after a hard reset, but all other hardware state +is cleared. + +The same hard reset boot sequence can be triggered by a number of events such as: + +- Python code executing :func:`machine.reset()`. +- User presses a physical Reset button on the board (where applicable). +- Waking from deep sleep (on most ports). +- MCU hardware watchdog reset. +- MCU hardware brown out detector. + +The details of hardware-specific reset triggers depend on the port and +associated hardware. The :func:`machine.reset_cause()` function can be used to +further determine the cause of a reset. + +.. _soft_reset: + +Soft Reset +---------- + +When MicroPython is already running, it's possible to trigger a soft reset by +:ref:`typing Ctrl-D in the REPL ` or executing +:func:`machine.soft_reset()`. + +A soft reset clears the Python interpreter, frees all Python memory, and starts +the MicroPython environment again. + +State which is cleared by a soft reset includes: + +- All Python variables, objects, imported modules, etc. +- Most peripherals configured using the :doc:`machine module + `. There are very limited exceptions, for example + :doc:`machine.Pin ` modes (i.e. if a pin is input or + output, high or low) are not reset on most ports. More advanced configuration + such as :func:`Pin.irq()` is always reset. +- Bluetooth. +- Network sockets. Open TCP sockets are closed cleanly with respect to the other party. +- Open files. The filesystem is left in a valid state. + +Some system state remains the same after a soft reset, including: + +- Any existing network connections (Ethernet, Wi-Fi, etc) remain active at the + IP Network layer. Querying the :doc:`network interface from code + ` may indicate the network interface is still active with a + configured IP address, etc. +- An active :doc:`REPL ` appears continuous before and after soft reset, + except in some unusual cases: + + * If the :ref:`machine.USBDevice ` class has been used to + create a custom USB interface then any built-in USB serial device will + appear to disconnect and reconnect as the custom USB interface must be + cleared during reset. + * A serial UART REPL will restore its default hardware configuration (baud + rate, etc). + +- CPU clock speed is usually not changed by a soft reset. +- :doc:`RTC ` configuration (i.e. setting of the current + time) is not changed by soft reset. + +.. _boot_sequence: + +Boot Sequence +------------- + +When MicroPython boots following either a hard or soft reset, it follows this +boot sequence in order: + +_boot.py +^^^^^^^^ + +This is an internal script :doc:`frozen into the MicroPython firmware +`. It is provided by MicroPython on many ports to do essential +initialisation. + +For example, on most ports ``_boot.py`` will detect the first boot of a new +device and format the :doc:`internal flash filesystem ` ready for +use. + +Unless you're creating a custom MicroPython build or adding a new port then you +probably don't need to worry about ``_boot.py``. It's best not to change the +contents unless you really know what you're doing. + +.. _boot.py: + +boot.py +^^^^^^^ + +A file named ``boot.py`` can be copied to the board's internal :ref:`filesystem +` using :doc:`mpremote `. + +If ``boot.py`` is found then it is executed. You can add code in ``boot.py`` to +perform custom one-off initialisation (for example, to configure the board's +hardware). + +A common practice is to configure a board's network connection in ``boot.py`` so +that it's always available after reset for use with the :doc:`REPL `, +:doc:`mpremote `, etc. + +.. warning:: boot.py should always exit and not run indefinitely. + + Depending on the port, some hardware initialisation is delayed until after + ``boot.py`` exits. This includes initialising USB on the stm32 port and all + ports which support :ref:`machine.USBDevice `. On these + ports, output printed from ``boot.py`` may not be visible on the built-in USB + serial port until after ``boot.py`` finishes running. + + The purpose of this late initialisation is so that it's possible to + pre-configure particular hardware in ``boot.py``, and then have it start with + the correct configuration. + +.. note:: It is sometimes simpler to not have a ``boot.py`` file and place any + initialisation code at the top of ``main.py`` instead. + +.. _main.py: + +main.py +^^^^^^^ + +Similar to ``boot.py``, a file named ``main.py`` can be copied to the board's +internal :ref:`filesystem `. If found then it is executed next in the +startup process. + +``main.py`` is for any Python code that you want to run each time your device +starts. + +Some tips for ``main.py`` usage: + +- ``main.py`` doesn't have to exit, feel free to put an infinite ``while + True`` loop in there. +- For complex Python applications then you don't need to put all your + code in ``main.py``. ``main.py`` can be a simple entry point that + imports your application and starts execution:: + + import my_app + my_app.main() + + This can help keep the structure of your application clear. It also makes + it easy to install multiple applications on a board and switch among them. +- It's good practice when writing robust apps to wrap code in ``main.py`` with an + exception handler to take appropriate action if the code crashes. For example:: + + import machine, sys + import my_app + try: + my_app.main() + except Exception as e: + print("Fatal error in main:") + sys.print_exception(e) + + # Following a normal Exception or main() exiting, reset the board. + # Following a non-Exception error such as KeyboardInterrupt (Ctrl-C), + # this code will drop to a REPL. Place machine.reset() in a finally + # block to always reset, instead. + machine.reset() + + Otherwise MicroPython will drop to the REPL following any crash or if main + exits (see below). + +- Any global variables that were set in ``boot.py`` will still be set in the + global context of ``main.py``. + +- To fully optimise flash usage and memory consumption, you can copy + :doc:`pre-compiled ` ``main.mpy`` and/or ``boot.mpy`` files to the + filesystem, or even :doc:`freeze ` them into the firmware build + instead. +- ``main.py`` execution is skipped when a soft reset is initiated from :ref:`raw + REPL mode ` (for example, when :doc:`mpremote ` or another + program is interacting directly with MicroPython). + +Interactive Interpreter (REPL) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If ``main.py`` is not found, or if ``main.py`` exits, then :doc:`repl` +will start immediately. + +.. note:: Even if ``main.py`` contains an infinite loop, typing Ctrl-C on the + REPL serial port will inject a `KeyboardInterrupt`. If no exception + handler catches it then ``main.py`` will exit and the REPL will start. + +Any global variables that were set in ``boot.py`` and ``main.py`` will still be +set in the global context of the REPL. + +The REPL continues executing until Python code triggers a hard or soft reset. + +.. _soft_bricking: + +Soft Bricking (failure to boot) +--------------------------------- + +It is rare but possible for MicroPython to become unresponsive during startup, a +state sometimes called "soft bricked". For example: + +- If ``boot.py`` execution gets stuck and the native USB serial port + never initialises. +- If Python code reconfigures the REPL interface, making it inaccessible. + +Rest assured, recovery is possible! + +KeyboardInterrupt +^^^^^^^^^^^^^^^^^ + +In many cases, opening the REPL serial port and typing ``Ctrl-C`` will inject +`KeyboardInterrupt` and may cause the running script to exit and a REPL to +start. From the REPL, you can use :func:`os.remove()` to remove the misbehaving +Python file:: + + import os + os.remove('main.py') + +To confirm which files are still present in the internal filesystem:: + + import os + os.listdir() + +Safe Mode and Factory Reset +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +If you're unable to easily access the REPL then you may need to perform one of +two processes: + +1. "Safe mode" boot, which skips ``boot.py`` and ``main.py`` and immediately + starts a REPL, allowing you to clean up. This is only supported on some ports. +2. Factory Reset to erase the entire contents of the flash filesystem. This may + also be necessary if the internal flash filesystem has become corrupted + somehow. + +The specific process(es) are different on each port: + +- :doc:`pyboard and stm32 port instructions ` +- :doc:`renesas-ra port instructions ` +- :doc:`wipy port instructions ` + +For ports without specific instructions linked above, the factory reset process +involves erasing the board's entire flash and then flashing MicroPython again +from scratch. Usually this will involve the same tool(s) that were originally +used to install MicroPython. Consult the installation docs for your board, or +ask on the `GitHub Discussions`_ if you're not sure. + +.. warning:: Re-flashing the MicroPython firmware without erasing the entire + flash first will usually not recover from soft bricking, as a + firmware update usually preserves the contents of the filesystem. + +.. _GitHub Discussions: https://github.com/orgs/micropython/discussions diff --git a/docs/renesas-ra/tutorial/program_in_flash.rst b/docs/renesas-ra/tutorial/program_in_flash.rst index 99a7a3839dd4..985ce6c7fe66 100644 --- a/docs/renesas-ra/tutorial/program_in_flash.rst +++ b/docs/renesas-ra/tutorial/program_in_flash.rst @@ -28,6 +28,8 @@ As the factory setting, following 2 files are created in the file system: * boot.py : executed first when the system starts * main.py : executed after boot.py completes +See :doc:`/reference/reset_boot` for more information. + Write a program in the internal file system ------------------------------------------- diff --git a/docs/renesas-ra/tutorial/reset.rst b/docs/renesas-ra/tutorial/reset.rst index de5f4db91b3e..879979647956 100644 --- a/docs/renesas-ra/tutorial/reset.rst +++ b/docs/renesas-ra/tutorial/reset.rst @@ -20,6 +20,8 @@ If that isn't working you can perform a hard reset (turn-it-off-and-on-again) by pressing the RESET button. This will end your session, disconnecting whatever program (PuTTY, screen, etc) that you used to connect to the board. +For more details, see :doc:`/reference/reset_boot`. + boot mode --------- @@ -29,7 +31,9 @@ There are 3 boot modes: * safe boot mode * factory filesystem boot mode -boot.py and main.py are executed on "normal boot mode". +boot.py and main.py are executed on "normal boot mode". See :ref:`boot_sequence`. + +The other modes can be used to recover from :ref:`soft_bricking`: boot.py and main.py are *NOT* executed on "safe boot mode". @@ -46,16 +50,4 @@ on the board: You have created the main.py which executes LED1 blinking in the previous part. If you change the boot mode to safe boot mode, the MicroPython starts without -the execution of main.py. Then you can remove the main.py by following -command or change the boot mode to factory file system boot mode.:: - - import os - os.remove('main.py') - -or change the boot mode to factory file system boot mode. - -You can confirm that the initialized file system that there are only boot.py and main.py files.:: - - import os - os.listdir() - +the execution of main.py. diff --git a/docs/wipy/tutorial/reset.rst b/docs/wipy/tutorial/reset.rst index 1715d3e29788..fc56429b81a3 100644 --- a/docs/wipy/tutorial/reset.rst +++ b/docs/wipy/tutorial/reset.rst @@ -16,6 +16,8 @@ There are soft resets and hard resets. import machine machine.reset() +For more information, see :doc:`/reference/reset_boot`. + Safe boot --------- From 9361a9f50af1e0b657eed270df6556ec253ddaeb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 3 Oct 2024 09:28:53 +1000 Subject: [PATCH 25/46] docs/rp2: Add a small factory reset page. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/reference/reset_boot.rst | 1 + docs/rp2/tutorial/intro.rst | 1 + docs/rp2/tutorial/reset.rst | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+) create mode 100644 docs/rp2/tutorial/reset.rst diff --git a/docs/reference/reset_boot.rst b/docs/reference/reset_boot.rst index 7c0d5f334a6e..f6307c08cdfb 100644 --- a/docs/reference/reset_boot.rst +++ b/docs/reference/reset_boot.rst @@ -245,6 +245,7 @@ The specific process(es) are different on each port: - :doc:`pyboard and stm32 port instructions ` - :doc:`renesas-ra port instructions ` +- :doc:`rp2 port instructions ` - :doc:`wipy port instructions ` For ports without specific instructions linked above, the factory reset process diff --git a/docs/rp2/tutorial/intro.rst b/docs/rp2/tutorial/intro.rst index 69c3e6b0a54a..2d2990105dba 100644 --- a/docs/rp2/tutorial/intro.rst +++ b/docs/rp2/tutorial/intro.rst @@ -8,4 +8,5 @@ Let's get started! .. toctree:: :maxdepth: 1 + reset.rst pio.rst diff --git a/docs/rp2/tutorial/reset.rst b/docs/rp2/tutorial/reset.rst new file mode 100644 index 000000000000..3c296ec46ee1 --- /dev/null +++ b/docs/rp2/tutorial/reset.rst @@ -0,0 +1,18 @@ +Factory reset +============= + +If something unexpected happens and your RP2xxx-based board no longer boots +MicroPython, then you may have to factory reset it. For more details, see +:ref:`soft_bricking`. + +Factory resetting the MicroPython rp2 port involves fully erasing the flash and +resetting the flash memory, so you will need to re-flash the MicroPython +firmware afterwards and copy any Python files to the filesystem again. + +1. Follow the instructions on the Raspberry Pi website for `resetting flash + memory`_. +2. Copy the MicroPython .uf2 firmware file to your board. If needed, this file + can be found on the `MicroPython downloads page`_. + +.. _resetting flash memory: https://www.raspberrypi.com/documentation/microcontrollers/pico-series.html#resetting-flash-memory +.. _MicroPython downloads page: https://micropython.org/download/?port=rp2 From a23277e3b0400b6b5ca6ce3d88e3a38859754a4e Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Thu, 3 Oct 2024 09:42:13 +1000 Subject: [PATCH 26/46] docs/esp32: Add a factory reset page. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/esp32/tutorial/index.rst | 1 + docs/esp32/tutorial/intro.rst | 2 ++ docs/esp32/tutorial/reset.rst | 25 +++++++++++++++++++++++++ docs/reference/reset_boot.rst | 1 + 4 files changed, 29 insertions(+) create mode 100644 docs/esp32/tutorial/reset.rst diff --git a/docs/esp32/tutorial/index.rst b/docs/esp32/tutorial/index.rst index c6242d731f18..2435f2ecd2f5 100644 --- a/docs/esp32/tutorial/index.rst +++ b/docs/esp32/tutorial/index.rst @@ -21,3 +21,4 @@ to ``__. intro.rst pwm.rst peripheral_access.rst + reset.rst diff --git a/docs/esp32/tutorial/intro.rst b/docs/esp32/tutorial/intro.rst index be09599871ce..cf4d0bcbd2f5 100644 --- a/docs/esp32/tutorial/intro.rst +++ b/docs/esp32/tutorial/intro.rst @@ -50,6 +50,8 @@ features, there are daily builds. If your board has SPIRAM support you can use either the standard firmware or the firmware with SPIRAM support, and in the latter case you will have access to more RAM for Python objects. +.. _esp32_flashing: + Deploying the firmware ---------------------- diff --git a/docs/esp32/tutorial/reset.rst b/docs/esp32/tutorial/reset.rst new file mode 100644 index 000000000000..b3fc6a85bd43 --- /dev/null +++ b/docs/esp32/tutorial/reset.rst @@ -0,0 +1,25 @@ +Factory reset +============= + +If something unexpected happens and your ESP32-based board no longer boots +MicroPython, then you may have to factory reset it. For more details, see +:ref:`soft_bricking`. + +Factory resetting the MicroPython esp32 port involves fully erasing the flash +and resetting the flash memory, so you will need to re-flash the MicroPython +firmware afterwards and copy any Python files to the filesystem again. + +1. You will need the Espressif `esptool`_ installed on your system. This is the + same tool that you may have used to initially install MicroPython on your + board (see :ref:`installation instructions `). +2. Find the serial port name of your board, and then use esptool to erase the + entire flash contents:: + + esptool.py -p PORTNAME erase_flash + +3. Use esptool to flash the MicroPython file to your board again. If needed, + this file and flashing instructions can be found on the `MicroPython + downloads page`_. + +.. _esptool: https://github.com/espressif/esptool +.. _MicroPython downloads page: https://micropython.org/download/?port=esp32 diff --git a/docs/reference/reset_boot.rst b/docs/reference/reset_boot.rst index f6307c08cdfb..4772d02dd1e3 100644 --- a/docs/reference/reset_boot.rst +++ b/docs/reference/reset_boot.rst @@ -244,6 +244,7 @@ two processes: The specific process(es) are different on each port: - :doc:`pyboard and stm32 port instructions ` +- :doc:`esp32 port instructions ` - :doc:`renesas-ra port instructions ` - :doc:`rp2 port instructions ` - :doc:`wipy port instructions ` From c5d74fe46876767cc43d7d23d949d0f7c3dcd6bd Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Tue, 8 Oct 2024 14:41:44 +1100 Subject: [PATCH 27/46] docs/library: Note link between machine.soft_reset() and sys.exit(). This is currently an implementation detail of MicroPython rather than by design. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/builtins.rst | 6 ++++++ docs/library/sys.rst | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/docs/library/builtins.rst b/docs/library/builtins.rst index 88b1fbcfe4a4..5956aea7ab20 100644 --- a/docs/library/builtins.rst +++ b/docs/library/builtins.rst @@ -194,6 +194,12 @@ Exceptions |see_cpython| `python:SystemExit`. + On non-embedded ports (i.e. Windows and Unix), an unhandled ``SystemExit`` + exits the MicroPython process in a similar way to CPython. + + On embedded ports, an unhandled ``SystemExit`` currently causes a + :ref:`soft_reset` of MicroPython. + .. exception:: TypeError |see_cpython| `python:TypeError`. diff --git a/docs/library/sys.rst b/docs/library/sys.rst index 7b34a0e31c6b..c72214c13108 100644 --- a/docs/library/sys.rst +++ b/docs/library/sys.rst @@ -12,9 +12,12 @@ Functions .. function:: exit(retval=0, /) Terminate current program with a given exit code. Underlyingly, this - function raise as `SystemExit` exception. If an argument is given, its + function raises a `SystemExit` exception. If an argument is given, its value given as an argument to `SystemExit`. + On embedded ports (i.e. all ports but Windows and Unix), an unhandled + `SystemExit` currently causes a :ref:`soft_reset` of MicroPython. + .. function:: atexit(func) Register *func* to be called upon termination. *func* must be a callable From c1b8e65c8e216ce2e90933aef6772de46ad54407 Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 5 Nov 2024 12:24:21 +1100 Subject: [PATCH 28/46] docs: Change copyright line to mention "authors and contributors". The docs have been authored by many people now. Instead of singling out individuals in the copyright line, prefer to mention all "MicroPython authors and contributors". Individual contributions can still be discovered via the git history. Signed-off-by: Damien George --- docs/conf.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index 32cc2be10e34..e5c6ba98090a 100755 --- a/docs/conf.py +++ b/docs/conf.py @@ -36,6 +36,9 @@ "is_release": micropy_version != "latest", } +# Authors used in various parts of the documentation. +micropy_authors = "MicroPython authors and contributors" + # -- General configuration ------------------------------------------------ @@ -68,7 +71,7 @@ # General information about the project. project = "MicroPython" -copyright = "- The MicroPython Documentation is Copyright © 2014-2024, Damien P. George, Paul Sokolovsky, and contributors" +copyright = "- The MicroPython Documentation is Copyright © 2014-2024, " + micropy_authors # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -244,7 +247,7 @@ master_doc, "MicroPython.tex", "MicroPython Documentation", - "Damien P. George, Paul Sokolovsky, and contributors", + micropy_authors, "manual", ), ] @@ -281,7 +284,7 @@ "index", "micropython", "MicroPython Documentation", - ["Damien P. George, Paul Sokolovsky, and contributors"], + [micropy_authors], 1, ), ] @@ -300,7 +303,7 @@ master_doc, "MicroPython", "MicroPython Documentation", - "Damien P. George, Paul Sokolovsky, and contributors", + micropy_authors, "MicroPython", "One line description of project.", "Miscellaneous", From f562aa1291ad44605f6b5be75b325a40a208ec41 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 18:14:47 +1100 Subject: [PATCH 29/46] extmod/network_cyw43: Fix isconnected() result on AP interface. This function is documented to return True if any stations are connected to the AP. Without this fix it returns True whenever the driver has brought the AP interface up. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/network_cyw43.c | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 3066cac75d3b..891e945de511 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -311,7 +311,17 @@ static MP_DEFINE_CONST_FUN_OBJ_1(network_cyw43_disconnect_obj, network_cyw43_dis static mp_obj_t network_cyw43_isconnected(mp_obj_t self_in) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(self_in); - return mp_obj_new_bool(cyw43_tcpip_link_status(self->cyw, self->itf) == 3); + bool result = (cyw43_tcpip_link_status(self->cyw, self->itf) == CYW43_LINK_UP); + + if (result && self->itf == CYW43_ITF_AP) { + // For AP we need to not only know if the link is up, but also if any stations + // have associated. + uint8_t mac_buf[6]; + int num_stas = 1; + cyw43_wifi_ap_get_stas(self->cyw, &num_stas, mac_buf); + result = num_stas > 0; + } + return mp_obj_new_bool(result); } static MP_DEFINE_CONST_FUN_OBJ_1(network_cyw43_isconnected_obj, network_cyw43_isconnected); From af743eaf59196e9133bd3c6de53cae1bb6c9a59a Mon Sep 17 00:00:00 2001 From: Damien George Date: Tue, 19 Nov 2024 11:26:34 +1100 Subject: [PATCH 30/46] extmod/network_cyw43: Fix uninitialised variable in status('stations'). The `num_stas` was uninitialised and if it happened to take the value 0 then no results were returned. It now has the correct maximum value. Signed-off-by: Damien George --- extmod/network_cyw43.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index 891e945de511..cebc8deb0da9 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -361,13 +361,15 @@ static mp_obj_t network_cyw43_status(size_t n_args, const mp_obj_t *args) { if (self->itf != CYW43_ITF_AP) { mp_raise_ValueError(MP_ERROR_TEXT("AP required")); } - int num_stas; - uint8_t macs[32 * 6]; + static const unsigned mac_len = 6; + static const unsigned max_stas = 32; + int num_stas = max_stas; + uint8_t macs[max_stas * mac_len]; cyw43_wifi_ap_get_stas(self->cyw, &num_stas, macs); mp_obj_t list = mp_obj_new_list(num_stas, NULL); for (int i = 0; i < num_stas; ++i) { mp_obj_t tuple[1] = { - mp_obj_new_bytes(&macs[i * 6], 6), + mp_obj_new_bytes(&macs[i * mac_len], mac_len), }; ((mp_obj_list_t *)MP_OBJ_TO_PTR(list))->items[i] = mp_obj_new_tuple(1, tuple); } From 181800eebde2f3dbda2862c175150d884eef728d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 18:04:59 +1100 Subject: [PATCH 31/46] extmod/network_cyw43: Allow configuring active AP interface. Configuring the AP for cyw43 writes to some buffers that are only sent to the modem when the interface is brought up. This means you can't configure the AP after calling active(True), the new settings seem to be accepted but the radio doesn't change. This is different to the WLAN behaviour on other ports. The esp8266 port requires calling active(True) on the AP before configuring, even. Fix this by bouncing the AP interface after a config change, if it's active. Configuring with active(False) still works the same as before. Adds a static variable to track interface active state, rather than relying on the LWIP interface state. This is because the interface state is updated by a driver callback and there's a race: if code calls active(True) and then config(a=b) then the driver doesn't know it's active yet and the changes aren't correctly applied. It is possible this pattern will cause the AP to come up briefly with the default "PICOabcd" SSID before being reconfigured, however (due to the aforementioned race condition) it seems like this may not happen at all before the new config is applied. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- extmod/network_cyw43.c | 30 +++++++++++++++++++++++++++--- 1 file changed, 27 insertions(+), 3 deletions(-) diff --git a/extmod/network_cyw43.c b/extmod/network_cyw43.c index cebc8deb0da9..02b022cb8132 100644 --- a/extmod/network_cyw43.c +++ b/extmod/network_cyw43.c @@ -60,6 +60,10 @@ typedef struct _network_cyw43_obj_t { static const network_cyw43_obj_t network_cyw43_wl_sta = { { &mp_network_cyw43_type }, &cyw43_state, CYW43_ITF_STA }; static const network_cyw43_obj_t network_cyw43_wl_ap = { { &mp_network_cyw43_type }, &cyw43_state, CYW43_ITF_AP }; +// Avoid race conditions with callbacks by tracking the last up or down request +// we have made for each interface. +static bool if_active[2]; + static void network_cyw43_print(const mp_print_t *print, mp_obj_t self_in, mp_print_kind_t kind) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(self_in); struct netif *netif = &self->cyw->netif[self->itf]; @@ -122,6 +126,10 @@ static MP_DEFINE_CONST_FUN_OBJ_3(network_cyw43_ioctl_obj, network_cyw43_ioctl); /*******************************************************************************/ // network API +static uint32_t get_country_code(void) { + return CYW43_COUNTRY(mod_network_country_code[0], mod_network_country_code[1], 0); +} + static mp_obj_t network_cyw43_deinit(mp_obj_t self_in) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(self_in); cyw43_deinit(self->cyw); @@ -132,10 +140,11 @@ static MP_DEFINE_CONST_FUN_OBJ_1(network_cyw43_deinit_obj, network_cyw43_deinit) static mp_obj_t network_cyw43_active(size_t n_args, const mp_obj_t *args) { network_cyw43_obj_t *self = MP_OBJ_TO_PTR(args[0]); if (n_args == 1) { - return mp_obj_new_bool(cyw43_tcpip_link_status(self->cyw, self->itf)); + return mp_obj_new_bool(if_active[self->itf]); } else { - uint32_t country = CYW43_COUNTRY(mod_network_country_code[0], mod_network_country_code[1], 0); - cyw43_wifi_set_up(self->cyw, self->itf, mp_obj_is_true(args[1]), country); + bool value = mp_obj_is_true(args[1]); + cyw43_wifi_set_up(self->cyw, self->itf, value, get_country_code()); + if_active[self->itf] = value; return mp_const_none; } } @@ -457,6 +466,10 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map mp_raise_TypeError(MP_ERROR_TEXT("can't specify pos and kw args")); } + // A number of these options only update buffers in memory, and + // won't do anything until the interface is cycled down and back up + bool cycle_active = false; + for (size_t i = 0; i < kwargs->alloc; ++i) { if (MP_MAP_SLOT_IS_FILLED(kwargs, i)) { mp_map_elem_t *e = &kwargs->table[i]; @@ -469,6 +482,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map } case MP_QSTR_channel: { cyw43_wifi_ap_set_channel(self->cyw, mp_obj_get_int(e->value)); + cycle_active = true; break; } case MP_QSTR_ssid: @@ -476,6 +490,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map size_t len; const char *str = mp_obj_str_get_data(e->value, &len); cyw43_wifi_ap_set_ssid(self->cyw, len, (const uint8_t *)str); + cycle_active = true; break; } case MP_QSTR_monitor: { @@ -495,6 +510,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map } case MP_QSTR_security: { cyw43_wifi_ap_set_auth(self->cyw, mp_obj_get_int(e->value)); + cycle_active = true; break; } case MP_QSTR_key: @@ -502,6 +518,7 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map size_t len; const char *str = mp_obj_str_get_data(e->value, &len); cyw43_wifi_ap_set_password(self->cyw, len, (const uint8_t *)str); + cycle_active = true; break; } case MP_QSTR_pm: { @@ -531,6 +548,13 @@ static mp_obj_t network_cyw43_config(size_t n_args, const mp_obj_t *args, mp_map } } + // If the interface is already active, cycle it down and up + if (cycle_active && if_active[self->itf]) { + uint32_t country = get_country_code(); + cyw43_wifi_set_up(self->cyw, self->itf, false, country); + cyw43_wifi_set_up(self->cyw, self->itf, true, country); + } + return mp_const_none; } } From b65e89107c0194bfdaf92720786341a2047f36b6 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 16 Oct 2024 12:03:39 +1100 Subject: [PATCH 32/46] py/py.mk: Add check that any specified USER_C_MODULES folder exists. Signed-off-by: Andrew Leech --- py/py.mk | 3 +++ 1 file changed, 3 insertions(+) diff --git a/py/py.mk b/py/py.mk index 2e7ffe9905e0..9592fbb9170e 100644 --- a/py/py.mk +++ b/py/py.mk @@ -33,6 +33,9 @@ ifneq ($(USER_C_MODULES),) # pre-define USERMOD variables as expanded so that variables are immediate # expanded as they're added to them +# Confirm the provided path exists, show abspath if not to make it clearer to fix. +$(if $(wildcard $(USER_C_MODULES)/.),,$(error USER_C_MODULES doesn't exist: $(abspath $(USER_C_MODULES)))) + # C/C++ files that are included in the QSTR/module build SRC_USERMOD_C := SRC_USERMOD_CXX := From dccd206f4cbbf7c15e1a5721ec5ab830d92ffa8e Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 16 Oct 2024 13:12:19 +1100 Subject: [PATCH 33/46] py/usermod.cmake: Add check that any specified USER_C_MODULES exists. Signed-off-by: Andrew Leech --- py/usermod.cmake | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/py/usermod.cmake b/py/usermod.cmake index c814369a4865..93fce13b3007 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -42,6 +42,12 @@ endfunction() # Include CMake files for user modules. if (USER_C_MODULES) foreach(USER_C_MODULE_PATH ${USER_C_MODULES}) + # Confirm the provided path exists, show abspath if not to make it clearer to fix. + if (NOT EXISTS ${USER_C_MODULE_PATH}) + get_filename_component(USER_C_MODULES_ABS "${USER_C_MODULE_PATH}" ABSOLUTE) + message(FATAL_ERROR "USER_C_MODULES doesn't exist: ${USER_C_MODULES_ABS}") + endif() + message("Including User C Module(s) from ${USER_C_MODULE_PATH}") include(${USER_C_MODULE_PATH}) endforeach() From 78d017fc4e70bf21b7c53f193550d220f8959e38 Mon Sep 17 00:00:00 2001 From: Andrew Leech Date: Wed, 16 Oct 2024 13:12:56 +1100 Subject: [PATCH 34/46] py/usermod.cmake: If USER_C_MODULES is a folder add micropython.cmake. This mirrors how it works when using a Makefile. Signed-off-by: Andrew Leech --- py/usermod.cmake | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/py/usermod.cmake b/py/usermod.cmake index 93fce13b3007..4a8b99ff31b4 100644 --- a/py/usermod.cmake +++ b/py/usermod.cmake @@ -42,6 +42,10 @@ endfunction() # Include CMake files for user modules. if (USER_C_MODULES) foreach(USER_C_MODULE_PATH ${USER_C_MODULES}) + # If a directory is given, append the micropython.cmake to it. + if (IS_DIRECTORY ${USER_C_MODULE_PATH}) + set(USER_C_MODULE_PATH "${USER_C_MODULE_PATH}/micropython.cmake") + endif() # Confirm the provided path exists, show abspath if not to make it clearer to fix. if (NOT EXISTS ${USER_C_MODULE_PATH}) get_filename_component(USER_C_MODULES_ABS "${USER_C_MODULE_PATH}" ABSOLUTE) From 7647c828de4ffb9877163909017bbe909e79aacb Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 11:20:03 +1100 Subject: [PATCH 35/46] tests/multi_espnow: Add channel setting test, add some docs. Test currently passes. It was added so it can be used to check for regressions when fixing channel selection for AP mode in a follow-up commit. Also add some docs about how channel setting is observed to work for ESP-NOW. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- docs/library/espnow.rst | 7 ++- docs/library/network.WLAN.rst | 2 +- tests/multi_espnow/70_channel.py | 89 ++++++++++++++++++++++++++++ tests/multi_espnow/70_channel.py.exp | 13 ++++ 4 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 tests/multi_espnow/70_channel.py create mode 100644 tests/multi_espnow/70_channel.py.exp diff --git a/docs/library/espnow.rst b/docs/library/espnow.rst index 1bcc9d7623d1..379e60486f22 100644 --- a/docs/library/espnow.rst +++ b/docs/library/espnow.rst @@ -441,7 +441,9 @@ must first register the sender and use the same encryption keys as the sender - *channel*: The wifi channel (2.4GHz) to communicate with this peer. Must be an integer from 0 to 14. If channel is set to 0 the current - channel of the wifi device will be used. (default=0) + channel of the wifi device will be used, if channel is set to another + value then this must match the channel currently configured on the + interface (see :func:`WLAN.config`). (default=0) - *ifidx*: (ESP32 only) Index of the wifi interface which will be used to send data to this peer. Must be an integer set to @@ -470,6 +472,9 @@ must first register the sender and use the same encryption keys as the sender registered. - ``OSError(num, "ESP_ERR_ESPNOW_FULL")`` if too many peers are already registered. + - ``OSError(num, "ESP_ERR_ESPNOW_CHAN")`` if a channel value was + set that doesn't match the channel currently configured for this + interface. - ``ValueError()`` on invalid keyword args or values. .. method:: ESPNow.del_peer(mac) diff --git a/docs/library/network.WLAN.rst b/docs/library/network.WLAN.rst index 3c401acb420b..09fefc5929f0 100644 --- a/docs/library/network.WLAN.rst +++ b/docs/library/network.WLAN.rst @@ -126,7 +126,7 @@ Methods ============= =========== mac MAC address (bytes) ssid WiFi access point name (string) - channel WiFi channel (integer) + channel WiFi channel (integer). Depending on the port this may only be supported on the AP interface. hidden Whether SSID is hidden (boolean) security Security protocol supported (enumeration, see module constants) key Access key (string) diff --git a/tests/multi_espnow/70_channel.py b/tests/multi_espnow/70_channel.py new file mode 100644 index 000000000000..f3e8b947b123 --- /dev/null +++ b/tests/multi_espnow/70_channel.py @@ -0,0 +1,89 @@ +# Test that ESP-NOW picks up the channel configuration for STA +# mode on ESP32. +# +# Note that setting the channel on a peer in ESP-NOW on modern ESP-IDF only +# checks it against the configured channel, it doesn't ever change the radio +# channel +import sys +import time + +try: + import network + import espnow +except ImportError: + print("SKIP") + raise SystemExit + +# ESP8266 doesn't support config('channel') on the STA interface, +# and the channel parameter to add_peer doesn't appear to set the +# channel either. +if sys.platform == "esp8266": + print("SKIP") + raise SystemExit + + +timeout_ms = 1000 +default_pmk = b"MicroPyth0nRules" + +CHANNEL = 3 +WRONG_CHANNEL = 8 + + +def init_sta(): + sta = network.WLAN(network.WLAN.IF_STA) + e = espnow.ESPNow() + e.active(True) + sta.active(True) + sta.disconnect() # Force AP disconnect for any saved config, important so the channel doesn't change + sta.config(channel=CHANNEL) + e.set_pmk(default_pmk) + return sta, e + + +# Receiver +def instance0(): + sta, e = init_sta() + multitest.globals(PEER=sta.config("mac")) + multitest.next() + print(sta.config("channel")) + while True: + peer, msg = e.recv(timeout_ms) + if peer is None: + print("Timeout") + break + print(msg) + e.active(False) + + +# Sender +def instance1(): + sta, e = init_sta() + multitest.next() + peer = PEER + + # both instances set channel via sta.config(), above + msg = b"sent to right channel 1" + e.add_peer(peer, channel=CHANNEL) + for _ in range(3): + e.send(peer, msg) + e.del_peer(peer) + print(sta.config("channel")) + + sta.config(channel=WRONG_CHANNEL) + msg = b"sent to wrong channel" + e.add_peer(peer, channel=WRONG_CHANNEL) + for _ in range(3): + e.send(peer, msg) + e.del_peer(peer) + print(sta.config("channel")) + + # switching back to the correct channel should also work + sta.config(channel=CHANNEL) + msg = b"sent to right channel 2" + e.add_peer(peer, channel=CHANNEL) + for _ in range(3): + e.send(peer, msg) + e.del_peer(peer) + print(sta.config("channel")) + + e.active(False) diff --git a/tests/multi_espnow/70_channel.py.exp b/tests/multi_espnow/70_channel.py.exp new file mode 100644 index 000000000000..d8553966032c --- /dev/null +++ b/tests/multi_espnow/70_channel.py.exp @@ -0,0 +1,13 @@ +--- instance0 --- +3 +b'sent to right channel 1' +b'sent to right channel 1' +b'sent to right channel 1' +b'sent to right channel 2' +b'sent to right channel 2' +b'sent to right channel 2' +Timeout +--- instance1 --- +3 +8 +3 From 951a10e7078413d2dee2758fc2b7cb292e2b50b7 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 10:16:25 +1100 Subject: [PATCH 36/46] esp32: Fix setting WLAN channel in AP mode. - Previously the call to esp_wifi_set_channel() would be immediately overridden by calling esp_wifi_config(...) with the previous channel set. - AP interface doesn't seem to need more than esp_wifi_config(...) to work. It will automatically configure 40MHz bandwidth and place the secondary channel using similar logic to what was being explicitly calculated here. - However, calling esp_wifi_set_channel() on the STA interface is necessary if using this interface with ESP-NOW (without connecting to an AP). So the esp_wifi_set_channel() call is kept in for this purpose. Without this, tests/multi_espnow/70_channel.py fails. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/network_wlan.c | 43 ++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/ports/esp32/network_wlan.c b/ports/esp32/network_wlan.c index d415c56d3217..6509b4fc6aae 100644 --- a/ports/esp32/network_wlan.c +++ b/ports/esp32/network_wlan.c @@ -549,21 +549,38 @@ static mp_obj_t network_wlan_config(size_t n_args, const mp_obj_t *args, mp_map_ break; } case MP_QSTR_channel: { - uint8_t primary; - wifi_second_chan_t secondary; - // Get the current value of secondary - esp_exceptions(esp_wifi_get_channel(&primary, &secondary)); - primary = mp_obj_get_int(kwargs->table[i].value); - esp_err_t err = esp_wifi_set_channel(primary, secondary); - if (err == ESP_ERR_INVALID_ARG) { - // May need to swap secondary channel above to below or below to above - secondary = ( - (secondary == WIFI_SECOND_CHAN_ABOVE) - ? WIFI_SECOND_CHAN_BELOW - : (secondary == WIFI_SECOND_CHAN_BELOW) + uint8_t channel = mp_obj_get_int(kwargs->table[i].value); + if (self->if_id == ESP_IF_WIFI_AP) { + cfg.ap.channel = channel; + } else { + // This setting is only used to determine the + // starting channel for a scan, so it can result in + // slightly faster connection times. + cfg.sta.channel = channel; + + // This additional code to directly set the channel + // on the STA interface is only relevant for ESP-NOW + // (when there is no STA connection attempt.) + uint8_t old_primary; + wifi_second_chan_t secondary; + // Get the current value of secondary + esp_exceptions(esp_wifi_get_channel(&old_primary, &secondary)); + esp_err_t err = esp_wifi_set_channel(channel, secondary); + if (err == ESP_ERR_INVALID_ARG) { + // May need to swap secondary channel above to below or below to above + secondary = ( + (secondary == WIFI_SECOND_CHAN_ABOVE) + ? WIFI_SECOND_CHAN_BELOW + : (secondary == WIFI_SECOND_CHAN_BELOW) ? WIFI_SECOND_CHAN_ABOVE : WIFI_SECOND_CHAN_NONE); - esp_exceptions(esp_wifi_set_channel(primary, secondary)); + err = esp_wifi_set_channel(channel, secondary); + } + esp_exceptions(err); + if (channel != old_primary) { + // Workaround the ESP-IDF Wi-Fi stack sometimes taking a moment to change channels + mp_hal_delay_ms(1); + } } break; } From 0e383a31b9560334b0c8d26b378deccc1c6af961 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 13 Nov 2024 17:40:02 +1100 Subject: [PATCH 37/46] tests: Add basic wlan test. Includes adding some ESP8266 port output to the ignored output list for the multitest runner. This test passes on ESP8266 and various ESP32s (including talking to each other). Without the fix in the parent commit, ESP32 AP will fail if the station can report its channel (i.e. channel is wrong). Testing with a CYW43 (RPI_PICO_W) currently fails but I have some fixes to submit so it can pass as well. Signed-off-by: Angus Gratton --- tests/multi_wlan/01_ap_sta.py | 117 ++++++++++++++++++++++++++++++ tests/multi_wlan/01_ap_sta.py.exp | 13 ++++ tests/run-multitests.py | 11 ++- 3 files changed, 135 insertions(+), 6 deletions(-) create mode 100644 tests/multi_wlan/01_ap_sta.py create mode 100644 tests/multi_wlan/01_ap_sta.py.exp diff --git a/tests/multi_wlan/01_ap_sta.py b/tests/multi_wlan/01_ap_sta.py new file mode 100644 index 000000000000..1a8e80cd33aa --- /dev/null +++ b/tests/multi_wlan/01_ap_sta.py @@ -0,0 +1,117 @@ +# Basic Wi-Fi MAC layer test where one device creates an Access Point and the +# other device connects to it as a Station. Also tests channel assignment (where +# possible) and disconnection. + +try: + import network + + network.WLAN +except (ImportError, NameError): + print("SKIP") + raise SystemExit + +import os +import sys +import time + +CHANNEL = 8 + +# Note that on slower Wi-Fi stacks this bumps up against the run-multitests.py +# timeout which expects <10s between lines of output. We work around this by +# logging something half way through the wait_for loop... +CONNECT_TIMEOUT = 15000 + + +def wait_for(test_func): + has_printed = False + start = time.ticks_ms() + while not test_func(): + time.sleep(0.1) + delta = time.ticks_diff(time.ticks_ms(), start) + if not has_printed and delta > CONNECT_TIMEOUT / 2: + print("...") + has_printed = True + elif delta > CONNECT_TIMEOUT: + break + + if not has_printed: + print("...") # keep the output consistent + + return test_func() + + +# AP +def instance0(): + ap = network.WLAN(network.WLAN.IF_AP) + ssid = "MP-test-" + os.urandom(6).hex() + psk = "Secret-" + os.urandom(6).hex() + + # stop any previous activity + network.WLAN(network.WLAN.IF_STA).active(False) + ap.active(False) + + ap.active(True) + ap.config(ssid=ssid, key=psk, channel=CHANNEL, security=network.WLAN.SEC_WPA_WPA2) + + # print("AP setup", ssid, psk) + print("AP started") + + multitest.globals(SSID=ssid, PSK=psk) + multitest.next() + + # Wait for station + if not wait_for(ap.isconnected): + raise RuntimeError("Timed out waiting for station, status ", ap.status()) + + print("AP got station") + time.sleep( + 3 + ) # depending on port, may still need to negotiate DHCP lease for STA to see connection + + print("AP disabling...") + ap.active(False) + + +# STA +def instance1(): + sta = network.WLAN(network.WLAN.IF_STA) + + # stop any previous activity + network.WLAN(network.WLAN.IF_AP).active(False) + sta.active(False) + + multitest.next() + ssid = SSID + psk = PSK + + # print("STA setup", ssid, psk) + + sta.active(True) + sta.connect(ssid, psk) + + print("STA connecting...") + + if not wait_for(sta.isconnected): + raise RuntimeError("Timed out waiting to connect, status ", sta.status()) + + print("STA connected") + + # Print the current channel, if the port support this + try: + print("channel", sta.config("channel")) + except OSError as e: + if "AP" in str(e): + # ESP8266 only supports reading channel on the AP interface, so fake this result + print("channel", CHANNEL) + else: + raise + + print("STA waiting for disconnect...") + + # Expect the AP to disconnect us immediately + if not wait_for(lambda: not sta.isconnected()): + raise RuntimeError("Timed out waiting for AP to disconnect us, status ", sta.status()) + + print("STA disconnected") + + sta.active(False) diff --git a/tests/multi_wlan/01_ap_sta.py.exp b/tests/multi_wlan/01_ap_sta.py.exp new file mode 100644 index 000000000000..8fc023a39805 --- /dev/null +++ b/tests/multi_wlan/01_ap_sta.py.exp @@ -0,0 +1,13 @@ +--- instance0 --- +AP started +... +AP got station +AP disabling... +--- instance1 --- +STA connecting... +... +STA connected +channel 8 +STA waiting for disconnect... +... +STA disconnected diff --git a/tests/run-multitests.py b/tests/run-multitests.py index 93a6d3844d23..387eec7018bb 100755 --- a/tests/run-multitests.py +++ b/tests/run-multitests.py @@ -105,15 +105,14 @@ def output_metric(data): multitest.flush() """ -# The btstack implementation on Unix generates some spurious output that we -# can't control. Also other platforms may output certain warnings/errors that -# can be safely ignored. +# Some ports generate output we can't control, and that can be safely ignored. IGNORE_OUTPUT_MATCHES = ( - "libusb: error ", # It tries to open devices that it doesn't have access to (libusb prints unconditionally). + "libusb: error ", # unix btstack tries to open devices that it doesn't have access to (libusb prints unconditionally). "hci_transport_h2_libusb.c", # Same issue. We enable LOG_ERROR in btstack. - "USB Path: ", # Hardcoded in btstack's libusb transport. - "hci_number_completed_packet", # Warning from btstack. + "USB Path: ", # Hardcoded in unix btstack's libusb transport. + "hci_number_completed_packet", # Warning from unix btstack. "lld_pdu_get_tx_flush_nb HCI packet count mismatch (", # From ESP-IDF, see https://github.com/espressif/esp-idf/issues/5105 + " ets_task(", # ESP8266 port debug output ) From ed3c75a3af16405508f7bfa66257abd7b53b4a9d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 16:48:56 +1100 Subject: [PATCH 38/46] esp32: Use hardware version for touchpad macro defines. ESP32 has hardware V1 and S2/S3 has V2, and future chips may have different versions. This should still compile to the same binary before and after. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_touchpad.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index 7612063e51d4..a35e7fccda21 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -29,12 +29,14 @@ #include "modmachine.h" #include "driver/gpio.h" -#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#if SOC_TOUCH_SENSOR_SUPPORTED -#if CONFIG_IDF_TARGET_ESP32 +#if SOC_TOUCH_VERSION_1 // ESP32 only #include "driver/touch_pad.h" -#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 +#elif SOC_TOUCH_VERSION_2 // All other SoCs with touch, to date #include "driver/touch_sensor.h" +#else +#error "Unknown touch hardware version" #endif typedef struct _mtp_obj_t { @@ -70,6 +72,8 @@ static const mtp_obj_t touchpad_obj[] = { {{&machine_touchpad_type}, GPIO_NUM_12, TOUCH_PAD_NUM12}, {{&machine_touchpad_type}, GPIO_NUM_13, TOUCH_PAD_NUM13}, {{&machine_touchpad_type}, GPIO_NUM_14, TOUCH_PAD_NUM14}, + #else + #error "Please add GPIO mapping for this SoC" #endif }; @@ -92,14 +96,14 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ if (!initialized) { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - #if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #if TOUCH_HW_VER == 2 touch_pad_fsm_start(); #endif initialized = 1; } - #if CONFIG_IDF_TARGET_ESP32 + #if SOC_TOUCH_VERSION_1 esp_err_t err = touch_pad_config(self->touchpad_id, 0); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #elif SOC_TOUCH_VERSION_2 esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { @@ -110,10 +114,10 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ static mp_obj_t mtp_config(mp_obj_t self_in, mp_obj_t value_in) { mtp_obj_t *self = self_in; - #if CONFIG_IDF_TARGET_ESP32 + #if SOC_TOUCH_VERSION_1 uint16_t value = mp_obj_get_int(value_in); esp_err_t err = touch_pad_config(self->touchpad_id, value); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #elif SOC_TOUCH_VERSION_2 esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { @@ -125,10 +129,10 @@ MP_DEFINE_CONST_FUN_OBJ_2(mtp_config_obj, mtp_config); static mp_obj_t mtp_read(mp_obj_t self_in) { mtp_obj_t *self = self_in; - #if CONFIG_IDF_TARGET_ESP32 + #if SOC_TOUCH_VERSION_1 uint16_t value; esp_err_t err = touch_pad_read(self->touchpad_id, &value); - #elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3 + #elif SOC_TOUCH_VERSION_2 uint32_t value; esp_err_t err = touch_pad_read_raw_data(self->touchpad_id, &value); #endif @@ -155,4 +159,4 @@ MP_DEFINE_CONST_OBJ_TYPE( locals_dict, &mtp_locals_dict ); -#endif +#endif // SOC_TOUCH_SENSOR_SUPPORTED From 66e699e8a538ed7c1dc2d65035e5d93643e56667 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 17:14:09 +1100 Subject: [PATCH 39/46] esp32: Fix machine.TouchPad startup on ESP32-S2 and S3. Closes #13178. TouchPad confirmed working on both chips, and fixes the the ESP32-S3 reading constant max value. Was unable to reproduce the bug on ESP32-S2 but this may be due to my test setup, and it still works with the fix. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- ports/esp32/machine_touchpad.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/ports/esp32/machine_touchpad.c b/ports/esp32/machine_touchpad.c index a35e7fccda21..48250280bada 100644 --- a/ports/esp32/machine_touchpad.c +++ b/ports/esp32/machine_touchpad.c @@ -96,9 +96,6 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ if (!initialized) { touch_pad_init(); touch_pad_set_fsm_mode(TOUCH_FSM_MODE_TIMER); - #if TOUCH_HW_VER == 2 - touch_pad_fsm_start(); - #endif initialized = 1; } #if SOC_TOUCH_VERSION_1 @@ -107,6 +104,10 @@ static mp_obj_t mtp_make_new(const mp_obj_type_t *type, size_t n_args, size_t n_ esp_err_t err = touch_pad_config(self->touchpad_id); #endif if (err == ESP_OK) { + #if SOC_TOUCH_VERSION_2 + touch_pad_fsm_start(); + #endif + return MP_OBJ_FROM_PTR(self); } mp_raise_ValueError(MP_ERROR_TEXT("Touch pad error")); From 154d1419659795aa6785778e4ddd6f23f09f2861 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 6 Nov 2024 18:06:48 +1100 Subject: [PATCH 40/46] docs,esp32: Update machine.TouchPad docs for ESP32-S2 and ESP32-S3. Signed-off-by: Angus Gratton --- docs/esp32/quickref.rst | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/docs/esp32/quickref.rst b/docs/esp32/quickref.rst index b9ca0f8225f8..5cce96d68787 100644 --- a/docs/esp32/quickref.rst +++ b/docs/esp32/quickref.rst @@ -751,20 +751,33 @@ APA102 (DotStar) uses a different driver as it has an additional clock pin. Capacitive touch ---------------- -Use the ``TouchPad`` class in the ``machine`` module:: +ESP32, ESP32-S2 and ESP32-S3 support capacitive touch via the ``TouchPad`` class +in the ``machine`` module:: from machine import TouchPad, Pin t = TouchPad(Pin(14)) t.read() # Returns a smaller number when touched -``TouchPad.read`` returns a value relative to the capacitive variation. Small numbers (typically in -the *tens*) are common when a pin is touched, larger numbers (above *one thousand*) when -no touch is present. However the values are *relative* and can vary depending on the board -and surrounding composition so some calibration may be required. - -There are ten capacitive touch-enabled pins that can be used on the ESP32: 0, 2, 4, 12, 13 -14, 15, 27, 32, 33. Trying to assign to any other pins will result in a ``ValueError``. +``TouchPad.read`` returns a value proportional to the capacitance between the +pin and the board's Ground connection. On ESP32 the number becomes smaller when +the pin (or connected touch pad) is touched, on ESP32-S2 and ESP32-S3 the number +becomes larger when the pin is touched. + +In all cases, a touch causes a significant change in the return value. Note the +returned values are *relative* and can vary depending on the board and +surrounding environment so some calibration (i.e. comparison to a baseline or +rolling average) may be required. + +========= ============================================== +Chip Touch-enabled pins +--------- ---------------------------------------------- +ESP32 0, 2, 4, 12, 13, 14, 15, 27, 32, 33 +ESP32-S2 1 to 14 inclusive +ESP32-S3 1 to 14 inclusive +========= ============================================== + +Trying to assign to any other pins will result in a ``ValueError``. Note that TouchPads can be used to wake an ESP32 from sleep:: From e70048cf5934bf3db65ec7a1b6490635716282f5 Mon Sep 17 00:00:00 2001 From: Corran Webster Date: Thu, 14 Nov 2024 10:15:10 +0000 Subject: [PATCH 41/46] extmod/modframebuf: Fix 0 radius bug in FrameBuffer.ellipse. This fixes a bug in FrameBuffer.ellipse where it goes into an infinite loop if both radii are 0. This fixes the bug with a simple pre-check to see if both radii are 0, and in that case sets a single pixel at the center. This is consistent with the behaviour of the method when called with just one of the radii set to 0, where it will draw a horizontal or vertical line of 1 pixel width. The pixel is set with setpixel_checked so it should handle out-of-bounds drawing correctly. This fix also includes three new tests: one for the default behaviour, one for drawing out-of-bounds, and one for when the sector mask is 0. Fixes issue #16053. Signed-off-by: Corran Webster --- extmod/modframebuf.c | 4 ++ tests/extmod/framebuf_ellipse.py | 15 +++++ tests/extmod/framebuf_ellipse.py.exp | 96 ++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) diff --git a/extmod/modframebuf.c b/extmod/modframebuf.c index cd0f50d10442..b718a66cc62f 100644 --- a/extmod/modframebuf.c +++ b/extmod/modframebuf.c @@ -536,6 +536,10 @@ static mp_obj_t framebuf_ellipse(size_t n_args, const mp_obj_t *args_in) { } else { mask |= ELLIPSE_MASK_ALL; } + if (args[2] == 0 && args[3] == 0) { + setpixel_checked(self, args[0], args[1], args[4], mask & ELLIPSE_MASK_ALL); + return mp_const_none; + } mp_int_t two_asquare = 2 * args[2] * args[2]; mp_int_t two_bsquare = 2 * args[3] * args[3]; mp_int_t x = args[2]; diff --git a/tests/extmod/framebuf_ellipse.py b/tests/extmod/framebuf_ellipse.py index a4c784aff876..ec0461e66ca0 100644 --- a/tests/extmod/framebuf_ellipse.py +++ b/tests/extmod/framebuf_ellipse.py @@ -63,3 +63,18 @@ def printbuf(): fbuf.fill(0) fbuf.ellipse(x, y, 6, 12, 0xAA, True) printbuf() + +# Draw an ellipse with both radius 0 +fbuf.fill(0) +fbuf.ellipse(15, 15, 0, 0, 0xFF, True) +printbuf() + +# Draw an ellipse with both radius 0 out of bounds +fbuf.fill(0) +fbuf.ellipse(45, 45, 0, 0, 0xFF, True) +printbuf() + +# Draw an ellipse with radius 0 and all sectors masked out +fbuf.fill(0) +fbuf.ellipse(15, 15, 0, 0, 0xFF, True, 0) +printbuf() diff --git a/tests/extmod/framebuf_ellipse.py.exp b/tests/extmod/framebuf_ellipse.py.exp index ae6ad1ee7e48..96a60c24da1f 100644 --- a/tests/extmod/framebuf_ellipse.py.exp +++ b/tests/extmod/framebuf_ellipse.py.exp @@ -702,3 +702,99 @@ aaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000 aaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000 aaaaaaaaaaaaaaaaaaaaaa00000000000000000000000000000000000000 -->8-- +--8<-- +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000ff0000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +-->8-- +--8<-- +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +-->8-- +--8<-- +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000 +-->8-- From da692d01ac6c09286ee682c0d4a3780777c67a8f Mon Sep 17 00:00:00 2001 From: robert-hh Date: Wed, 27 Nov 2024 16:57:02 +0100 Subject: [PATCH 42/46] nrf/drivers/ticker: Reset slow ticker callback count on soft reboot. The micro:bit board (and probably other boards using the music or display module) locked up on soft reboot. Reason was a buffer overflow caused by an index counter, which was not reset on soft_reboot. That's fixed in this commit. Tested with a micro:bit board, performing a series of soft reboots. Signed-off-by: robert-hh --- ports/nrf/drivers/ticker.c | 1 + 1 file changed, 1 insertion(+) diff --git a/ports/nrf/drivers/ticker.c b/ports/nrf/drivers/ticker.c index c2fa31e7c207..e9c07c842ecd 100644 --- a/ports/nrf/drivers/ticker.c +++ b/ports/nrf/drivers/ticker.c @@ -62,6 +62,7 @@ void ticker_init0(void) { #else NRFX_IRQ_PRIORITY_SET(FastTicker_IRQn, 2); #endif + m_num_of_slow_tickers = 0; NRFX_IRQ_PRIORITY_SET(SlowTicker_IRQn, 3); From 3b3b48892f96e2c81e7887f996fd7ff457acbb5d Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:15:20 +1100 Subject: [PATCH 43/46] py/objfloat: Workaround non-constant NAN definition on Windows MSVC. Recent MSVC versions have changed the definition of NAN to a non-constant expression! This is a bug, C standard says it should be a constant. Good explanation and workaround at: https://stackoverflow.com/a/79199887 This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- py/objfloat.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/py/objfloat.c b/py/objfloat.c index 5c90b1491ceb..0728fce3151f 100644 --- a/py/objfloat.c +++ b/py/objfloat.c @@ -47,6 +47,13 @@ #define M_PI (3.14159265358979323846) #endif +// Workaround a bug in recent MSVC where NAN is no longer constant. +// (By redefining back to the previous MSVC definition of NAN) +#if defined(_MSC_VER) && _MSC_VER >= 1942 +#undef NAN +#define NAN (-(float)(((float)(1e+300 * 1e+300)) * 0.0F)) +#endif + typedef struct _mp_obj_float_t { mp_obj_base_t base; mp_float_t value; From 8e11e5f1a14ca3395afaab2e42edebe41221eba1 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:46:21 +1100 Subject: [PATCH 44/46] tests/misc/sys_settrace_features.py: Add note about CPython 3.12 issue. CPython 3.12 has a documented issue with settrace for opcodes, apparently due to PEP 669. "This behavior will be changed back in 3.13 to be consistent with previous versions." No easy way to make the test pass on CPython 3.12, but at least this helps signal what the problem is to anyone who runs into a failure. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/misc/sys_settrace_features.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/misc/sys_settrace_features.py b/tests/misc/sys_settrace_features.py index 8ca6b382e3c6..6eeb2b900f18 100644 --- a/tests/misc/sys_settrace_features.py +++ b/tests/misc/sys_settrace_features.py @@ -6,6 +6,10 @@ print("SKIP") raise SystemExit +if sys.version.startswith("3.12"): + # There is a CPython change in settrace that is reverted in 3.13! + print("WARNING: this test will fail when compared to CPython 3.12.x behaviour") + def print_stacktrace(frame, level=0): # Ignore CPython specific helpers. From 8ec067272a2b03c182f4723026e1d94dd6a44835 Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:28:33 +1100 Subject: [PATCH 45/46] github/workflows: Workaround using CPython 3.12 in MSYS2 builds. Once MSYS2 repository updates past Python 3.12, this commit can be reverted. Explanation: CPython 3.12 can't pass sys_settrace_features test (see parent commit for explanation). MSYS2 mingw-w64-ARCH-python package is currently 3.12.7. MSYS2 doesn't recommend installing old packages from their archive (due to library dependencies), so switch to the GitHub CI setup-python action for now. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- .github/workflows/ports_windows.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ports_windows.yml b/.github/workflows/ports_windows.yml index 91a3192a10a4..84e018ba15d1 100644 --- a/.github/workflows/ports_windows.yml +++ b/.github/workflows/ports_windows.yml @@ -110,6 +110,11 @@ jobs: run: shell: msys2 {0} steps: + - uses: actions/setup-python@v5 + # note: can go back to installing mingw-w64-${{ matrix.env }}-python after + # MSYS2 updates to Python >3.12 (due to settrace compatibility issue) + with: + python-version: '3.11' - uses: msys2/setup-msys2@v2 with: msystem: ${{ matrix.sys }} @@ -118,9 +123,9 @@ jobs: make mingw-w64-${{ matrix.env }}-gcc pkg-config - mingw-w64-${{ matrix.env }}-python3 git diffutils + path-type: inherit # Remove when setup-python is removed - uses: actions/checkout@v4 - name: Build mpy-cross.exe run: make -C mpy-cross -j2 From eb80b04944e01d437efc2fa29189ad1e2bdd997f Mon Sep 17 00:00:00 2001 From: Angus Gratton Date: Wed, 20 Nov 2024 16:22:56 +1100 Subject: [PATCH 46/46] tests/extmod: Workaround CPython warning in asyncio_new_event_loop test. This started failing in CI on the mingw build, after CPython updated to 3.12.7. The test prints two warnings during interpreter shutdown of "Task was destroyed but it is pending!". This didn't happen on other CPython builds, and I think that's because of finalizer order in CPython interpreter shutdown but not certain (the loop finalizer calls loop.close() if not already closed). Adding explicit calls to loop.close() causes the warning to be printed on every run with CPython 3.12.7 on Linux. Next, added the workaround exception handler to swallow this exception as MicroPython doesn't produce an equivalent. This work was funded through GitHub Sponsors. Signed-off-by: Angus Gratton --- tests/extmod/asyncio_new_event_loop.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/tests/extmod/asyncio_new_event_loop.py b/tests/extmod/asyncio_new_event_loop.py index bebc3bf70cc5..3f05ffdd551d 100644 --- a/tests/extmod/asyncio_new_event_loop.py +++ b/tests/extmod/asyncio_new_event_loop.py @@ -12,6 +12,16 @@ asyncio.set_event_loop(asyncio.new_event_loop()) +def exception_handler(loop, context): + # This is a workaround for a difference between CPython and MicroPython: if + # a CPython event loop is closed while there are tasks pending (i.e. not finished) + # on it, then the task will log an error. MicroPython does not log this error. + if context.get("message", "") == "Task was destroyed but it is pending!": + pass + else: + loop.default_exception_handler(context) + + async def task(): for i in range(4): print("task", i) @@ -22,17 +32,21 @@ async def task(): async def main(): print("start") loop.create_task(task()) - await asyncio.sleep(0) + await asyncio.sleep(0) # yields, meaning new task will run once print("stop") loop.stop() # Use default event loop to run some tasks loop = asyncio.get_event_loop() +loop.set_exception_handler(exception_handler) loop.create_task(main()) loop.run_forever() +loop.close() # Create new event loop, old one should not keep running loop = asyncio.new_event_loop() +loop.set_exception_handler(exception_handler) loop.create_task(main()) loop.run_forever() +loop.close()