diff --git a/.github/workflows/micropython.yml b/.github/workflows/micropython.yml index 42386ddd..f9b44b06 100644 --- a/.github/workflows/micropython.yml +++ b/.github/workflows/micropython.yml @@ -22,6 +22,8 @@ jobs: board: RPI_PICO - name: pico_usb board: RPI_PICO_USB + - name: pico_ppp + board: RPI_PICO_PPP - name: picow board: RPI_PICO_W - name: tiny2040_8mb diff --git a/micropython/board/RPI_PICO_PPP/board.json b/micropython/board/RPI_PICO_PPP/board.json new file mode 100644 index 00000000..5a67a25e --- /dev/null +++ b/micropython/board/RPI_PICO_PPP/board.json @@ -0,0 +1,20 @@ +{ + "deploy": [ + "../deploy.md" + ], + "docs": "", + "features": [ + "Breadboard friendly", + "Castellated Pads", + "Micro USB" + ], + "id": "rp2-pico", + "images": [ + "rp2-pico.jpg" + ], + "mcu": "rp2040", + "product": "Pico", + "thumbnail": "", + "url": "https://www.raspberrypi.com/products/raspberry-pi-pico/", + "vendor": "Raspberry Pi" +} \ No newline at end of file diff --git a/micropython/board/RPI_PICO_PPP/manifest.py b/micropython/board/RPI_PICO_PPP/manifest.py new file mode 100644 index 00000000..6eae8974 --- /dev/null +++ b/micropython/board/RPI_PICO_PPP/manifest.py @@ -0,0 +1,7 @@ +include("$(PORT_DIR)/boards/manifest.py") + +require("bundle-networking") + +include("../manifest_pico.py") + +freeze("../../modules_py", "lte.py") \ No newline at end of file diff --git a/micropython/board/RPI_PICO_PPP/mpconfigboard.cmake b/micropython/board/RPI_PICO_PPP/mpconfigboard.cmake new file mode 100644 index 00000000..1aeeff20 --- /dev/null +++ b/micropython/board/RPI_PICO_PPP/mpconfigboard.cmake @@ -0,0 +1,9 @@ +# cmake file for Raspberry Pi Pico +set(PICO_BOARD "pico") + +set(MICROPY_PY_LWIP ON) + +# Board specific version of the frozen manifest +set(MICROPY_FROZEN_MANIFEST ${MICROPY_BOARD_DIR}/manifest.py) + +set(MICROPY_C_HEAP_SIZE 4096) \ No newline at end of file diff --git a/micropython/board/RPI_PICO_PPP/mpconfigboard.h b/micropython/board/RPI_PICO_PPP/mpconfigboard.h new file mode 100644 index 00000000..27e43a5a --- /dev/null +++ b/micropython/board/RPI_PICO_PPP/mpconfigboard.h @@ -0,0 +1,16 @@ +// Board and hardware specific configuration +#define MICROPY_HW_BOARD_NAME "Raspberry Pi Pico" +#define MICROPY_HW_FLASH_STORAGE_BYTES (1024 * 1024) + +// Enable networking. +#define MICROPY_PY_NETWORK 1 +#define MICROPY_PY_NETWORK_HOSTNAME_DEFAULT "Pico" + +#define MICROPY_PY_NETWORK_PPP_LWIP 1 + +#define MICROPY_HW_NIC_PPP { MP_ROM_QSTR(MP_QSTR_PINT), MP_ROM_PTR(&mp_network_ppp_lwip_type) }, + +#define MICROPY_BOARD_NETWORK_INTERFACES \ + MICROPY_HW_NIC_PPP + +#define MICROPY_PY_SOCKET_EXTENDED_STATE 1 \ No newline at end of file diff --git a/micropython/board/RPI_PICO_PPP/pins.csv b/micropython/board/RPI_PICO_PPP/pins.csv new file mode 100644 index 00000000..9c40b41c --- /dev/null +++ b/micropython/board/RPI_PICO_PPP/pins.csv @@ -0,0 +1,28 @@ +GP0,GPIO0 +GP1,GPIO1 +GP2,GPIO2 +GP3,GPIO3 +GP4,GPIO4 +GP5,GPIO5 +GP6,GPIO6 +GP7,GPIO7 +GP8,GPIO8 +GP9,GPIO9 +GP10,GPIO10 +GP11,GPIO11 +GP12,GPIO12 +GP13,GPIO13 +GP14,GPIO14 +GP15,GPIO15 +GP16,GPIO16 +GP17,GPIO17 +GP18,GPIO18 +GP19,GPIO19 +GP20,GPIO20 +GP21,GPIO21 +GP22,GPIO22 +GP25,GPIO25 +GP26,GPIO26 +GP27,GPIO27 +GP28,GPIO28 +LED,GPIO25 \ No newline at end of file diff --git a/micropython/modules/micropython-enviro.cmake b/micropython/modules/micropython-enviro.cmake index d305caf7..331e7a6b 100644 --- a/micropython/modules/micropython-enviro.cmake +++ b/micropython/modules/micropython-enviro.cmake @@ -31,6 +31,9 @@ include(wakeup/micropython) # Configure wakeup for Enviro target_compile_definitions(usermod_wakeup INTERFACE -DWAKEUP_HAS_RTC=1 + -DWAKEUP_PIN_MASK=0b01000100 + -DWAKEUP_PIN_DIR=0b01000100 + -DWAKEUP_PIN_VALUE=0b01000100 ) # LEDs & Matrices diff --git a/micropython/modules/micropython-inky_frame.cmake b/micropython/modules/micropython-inky_frame.cmake index 6cd21038..3c568afa 100644 --- a/micropython/modules/micropython-inky_frame.cmake +++ b/micropython/modules/micropython-inky_frame.cmake @@ -33,6 +33,9 @@ include(wakeup/micropython) target_compile_definitions(usermod_wakeup INTERFACE -DWAKEUP_HAS_RTC=1 -DWAKEUP_HAS_SHIFT_REGISTER=1 + -DWAKEUP_PIN_MASK=0b01000100 + -DWAKEUP_PIN_DIR=0b01000100 + -DWAKEUP_PIN_VALUE=0b01000100 ) # LEDs & Matrices diff --git a/micropython/modules/micropython-pico_ppp.cmake b/micropython/modules/micropython-pico_ppp.cmake new file mode 100644 index 00000000..d4cb4fb9 --- /dev/null +++ b/micropython/modules/micropython-pico_ppp.cmake @@ -0,0 +1,16 @@ +include_directories(${CMAKE_CURRENT_LIST_DIR}/../../) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../") +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/../../") + +set(CMAKE_C_STANDARD 11) +set(CMAKE_CXX_STANDARD 17) + +include(micropython-common) + +# C++ Magic Memory +include(cppmem/micropython) + +# Disable build-busting C++ exceptions +include(micropython-disable-exceptions) diff --git a/micropython/modules/wakeup/micropython.cmake b/micropython/modules/wakeup/micropython.cmake index d5e781ea..4be8830f 100644 --- a/micropython/modules/wakeup/micropython.cmake +++ b/micropython/modules/wakeup/micropython.cmake @@ -5,7 +5,6 @@ add_library(usermod_${MOD_NAME} INTERFACE) target_sources(usermod_${MOD_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c ${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp - #${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.S ) target_include_directories(usermod_${MOD_NAME} INTERFACE diff --git a/micropython/modules/wakeup/wakeup.S b/micropython/modules/wakeup/wakeup.S deleted file mode 100644 index 4e762049..00000000 --- a/micropython/modules/wakeup/wakeup.S +++ /dev/null @@ -1,36 +0,0 @@ - -.syntax unified -.cpu cortex-m0plus -.thumb - -#include "pico/asm_helper.S" - -// This macro tells the pico runtime to call __wakeup_gpio_latch very early in boot -__pre_init __wakeup_gpio_latch, 00000 - -.section .data.wakeup_gpio_latch -.global wakeup_gpio_state -.align 4 -wakeup_gpio_state: -.word 0x00000000 - -.section .text -.thumb_func -__wakeup_gpio_latch: - // Read GPIO state for front buttons and store - movs r3, 0xd0 // Load 0xd0 into r3 - lsls r3, r3, 24 // Shift left 24 to get 0xd0000000 - ldr r1, [r3, 4] // Load GPIO state (0xd0000004) into r1 - ldr r2, =wakeup_gpio_state // Load output var addr into r2 - str r1, [r2] // Store r1 to r2 - - // Enable 3v3 pin on the badger - ldr r1, =0x40014054 // GPIO control register 10 - movs r2, 5 // SIO function - str r2, [r1] // Set Enable 3v3 to SIO // https://github.com/raspberrypi/pico-sdk/blob/2e6142b15b8a75c1227dd3edbe839193b2bf9041/src/rp2_common/hardware_gpio/include/hardware/gpio.h#L96 - str r2, [r1, 120] // Also set LED (25) to SIO - ldr r2, =0x02000400 // Pins 25 and 10 - str r2, [r3, 36] // Enable pins out - str r2, [r3, 20] // Set pins high - - bx lr // Return diff --git a/micropython/modules/wakeup/wakeup.cpp b/micropython/modules/wakeup/wakeup.cpp index e5127526..703e6367 100644 --- a/micropython/modules/wakeup/wakeup.cpp +++ b/micropython/modules/wakeup/wakeup.cpp @@ -1,18 +1,30 @@ #include "hardware/gpio.h" #include "wakeup.config.hpp" -extern uint32_t runtime_wakeup_gpio_state; namespace { struct Wakeup { public: uint8_t shift_register_state = 0b0; + uint32_t gpio_state = 0; Wakeup() { // Assert wakeup pins (indicator LEDs, VSYS hold etc) - //gpio_init_mask(WAKEUP_PIN_MASK); - //gpio_set_dir_masked(WAKEUP_PIN_MASK, WAKEUP_PIN_DIR); - //gpio_put_masked(WAKEUP_PIN_MASK, WAKEUP_PIN_VALUE); + gpio_init_mask(WAKEUP_PIN_MASK); + gpio_set_dir_masked(WAKEUP_PIN_MASK, WAKEUP_PIN_DIR); + gpio_put_masked(WAKEUP_PIN_MASK, WAKEUP_PIN_VALUE); + + // Init all GPIOS not specified in the wakeup mask +#if PICO_RP2350 + gpio_init_mask(~WAKEUP_PIN_MASK); + gpio_set_dir_in_masked(~WAKEUP_PIN_MASK); +#endif + gpio_state = gpio_get_all(); + sleep_ms(5); + gpio_state |= gpio_get_all(); +#if PICO_RP2350 + gpio_init_mask(~WAKEUP_PIN_MASK); +#endif #if WAKEUP_HAS_RTC==1 // Set up RTC I2C pins and send reset command @@ -59,11 +71,11 @@ extern "C" { #include "wakeup.h" mp_obj_t Wakeup_get_gpio_state() { - return mp_obj_new_int(runtime_wakeup_gpio_state); + return mp_obj_new_int(wakeup.gpio_state); } mp_obj_t Wakeup_reset_gpio_state() { - runtime_wakeup_gpio_state = 0; + wakeup.gpio_state = 0; return mp_const_none; } diff --git a/micropython/modules_py/lte.py b/micropython/modules_py/lte.py new file mode 100644 index 00000000..c64abb7c --- /dev/null +++ b/micropython/modules_py/lte.py @@ -0,0 +1,205 @@ +import time + +from machine import UART, Pin +from network import PPP + +from micropython import const + +DEFAULT_PIN_RST = 35 +DEFAULT_PIN_NETLIGHT = 34 +DEFAULT_PIN_RX = 33 +DEFAULT_PIN_TX = 32 +DEFAULT_UART_ID = 0 + +DEFAULT_UART_TIMEOUT = const(1) +DEFAULT_UART_TIMEOUT_CHAR = const(1) +DEFAULT_UART_RXBUF = const(1024) +DEFAULT_UART_STARTUP_BAUD = const(115200) +DEFAULT_UART_BAUD = const(460800) + + +class CellularError(Exception): + def __init__(self, message=None): + self.message = "CellularError: " + message + + +class LTE(): + def __init__(self, apn, uart=None, reset_pin=None, netlight_pin=None, netlight_led=None, skip_reset=False): + self._apn = apn + self._reset = reset_pin or Pin(DEFAULT_PIN_RST, Pin.OUT) + self._uart = uart or UART( + DEFAULT_UART_ID, + tx=Pin(DEFAULT_PIN_TX, Pin.OUT), + rx=Pin(DEFAULT_PIN_RX, Pin.OUT)) + + # Set PPP timeouts and rxbuf + self._uart.init( + timeout=DEFAULT_UART_TIMEOUT, + timeout_char=DEFAULT_UART_TIMEOUT_CHAR, + rxbuf=DEFAULT_UART_RXBUF) + + if not skip_reset: + self._reset.value(0) + time.sleep(1.0) + self._reset.value(1) + + if netlight_led: + self._led = netlight_led + self._netlight = netlight_pin or Pin(DEFAULT_PIN_NETLIGHT, Pin.IN) + self._netlight.irq(self._netlight_irq) + + def _netlight_irq(self, pin): + self._led.value(pin.value()) + + def ipconfig(self, *args, **kwargs): + if len(args): + return self._ppp.ipconfig(*args) + else: + return self._ppp.ipconfig(**kwargs) + + def iccid(self): + try: + return self._send_at_command("AT+CICCID", 1) + except CellularError: + return None + + def status(self): + lte_status = self._send_at_command("AT+CEREG?", 1) + gsm_status = self._send_at_command("AT+CGREG?", 1) + return lte_status, gsm_status + + def signal_quality(self): + try: + response = self._send_at_command("AT+CSQ", 1) + quality = int(response.split(":")[1].split(",")[0]) + # Conversion as per AT command set datasheet + db = -113 + (2 * quality) + return db + except CellularError: + pass + return None + + def stop_ppp(self): + self._ppp.disconnect() + self._send_at_command(f"AT+IPR={DEFAULT_UART_STARTUP_BAUD}") + self._flush_uart() + + def start_ppp(self, baudrate=DEFAULT_UART_BAUD, connect=True): + self._wait_ready(poll_time=1.0, timeout=30) + + # Switch to a faster baudrate + self._send_at_command(f"AT+IPR={baudrate}") + self._flush_uart() + self._uart.init( + baudrate=baudrate, + timeout=DEFAULT_UART_TIMEOUT, + timeout_char=DEFAULT_UART_TIMEOUT_CHAR, + rxbuf=DEFAULT_UART_RXBUF) + self._wait_ready(poll_time=1.0) + + # Connect! + if connect: + self.connect() + + # Force PPP to use modem's default settings... + self._flush_uart() + self._uart.write("ATD*99#\r") + self._uart.flush() + + self._ppp = PPP(self._uart) + self._ppp.connect() + while self._ppp.status() != 4: + time.sleep(1.0) + + return self._ppp.ifconfig() + + def connect(self, timeout=60): + print(" - setting up cellular uart") + # Connect to and flush the uart + # Discard any unsolicited messages first, we don't need those + self._flush_uart() + + print(" - waiting for cellular module to be ready") + + # Wait for the cellular module to respond to AT commands + self._wait_ready() + + self._send_at_command("ATE0") # Disable local echo + self._send_at_command(f"AT+CGDCONT=1,\"IP\",\"{self._apn}\"") # Set apn and activate pdp context + + # Wait for roaming lte connection to be established + giveup = time.time() + timeout + status = None + while status != "+CEREG: 0,5" and status != "+CEREG: 0,1": + status = self._send_at_command("AT+CEREG?", 1) + time.sleep(0.25) + if time.time() > giveup: + raise CellularError("timed out getting network registration") + + # Disable server and client certification validation + self._send_at_command("AT+CSSLCFG=\"authmode\",0,0") + self._send_at_command("AT+CSSLCFG=\"enableSNI\",0,1") + + print(f" - SIM ICCID is {self.iccid()}") + + def _wait_ready(self, poll_time=0.25, timeout=10): + giveup = time.time() + timeout + while time.time() <= giveup: + try: + self._send_at_command("AT") + return # If __send_at_command doesn't throw an exception then we're good! + except CellularError as e: + print(e) + time.sleep(poll_time) + + raise CellularError("timed out waiting for AT response") + + def _flush_uart(self): + self._uart.flush() + time.sleep(0.25) + while self._uart.any(): + self._uart.read(self._uart.any()) + time.sleep(0.25) + + def _send_at_command(self, command, result_lines=0, timeout=5.0): + # Discard any unsolicited messages first, we don't need those + self._flush_uart() + + self._uart.write(command + "\r") + self._uart.flush() + status, data = self._read_result(result_lines, timeout=timeout) + + print(" -", command, status, data) + + if status == "TIMEOUT": + raise CellularError(f"cellular module timed out for command {command}") + + if status not in ["OK", "DOWNLOAD"]: + raise CellularError(f"non 'OK' or 'DOWNLOAD' result for command {command}") + + if result_lines == 1: + return data[0] + if result_lines > 1: + return data + return None + + def _read_result(self, result_lines, timeout=1.0): + status = None + result = [] + start = time.ticks_ms() + timeout *= 1000 + while len(result) < result_lines or status is None: + if (time.ticks_ms() - start) > timeout: + return "TIMEOUT", [] + + line = self._uart.readline() + + if line: + line = line.strip() + if line in [b"OK", b"ERROR", b"DOWNLOAD"]: + status = line.decode("ascii") + elif line != b"": + result.append(str(line, "ascii")) + start = time.ticks_ms() + + return status, result