├── boards ├── esp32 │ ├── sdkconfig.esp32 │ ├── arch.json │ ├── esp32_bare.board.json │ └── esp32_devkit_c.board.json ├── idf_component.yml ├── .prettierrc ├── esp32s2 │ ├── idf_component.yml │ ├── sdkconfig.esp32s2 │ ├── msr207_v43.board.json │ ├── arch.json │ ├── msr207_v42.board.json │ ├── adafruit_feather_esp32_s2.board.json │ ├── esp32s2_bare.board.json │ ├── msr48.board.json │ └── feather_s2.board.json ├── esp32s3 │ ├── idf_component.yml │ ├── sdkconfig.esp32s3 │ ├── arch.json │ ├── esp32s3_bare.board.json │ └── esp32s3_devkit_m.board.json ├── esp32c3 │ ├── sdkconfig.esp32c3 │ ├── arch.json │ ├── seeed_xiao_esp32c3.board.json │ ├── esp32c3_bare.board.json │ ├── esp32_c3fh4_rgb.board.json │ ├── esp32c3_supermini.board.json │ ├── adafruit_qt_py_c3.board.json │ ├── esp32c3_rust_devkit.board.json │ ├── seeed_xiao_esp32c3_msr218.board.json │ └── kittenbot_grapebit_esp32c3.board.json ├── partitions.csv ├── regen.sh ├── esp32.d.ts ├── sdkconfig.common └── esp32archconfig.schema.json ├── .gitmodules ├── main ├── jdlow.h ├── jdtcp.h ├── CMakeLists.txt ├── hw.h ├── led_strip_encoder.h ├── flash.c ├── jdesp.h ├── jd_user_config.h ├── worker.c ├── utils.c ├── i2c.c ├── adc.c ├── platform.c ├── ledstrip.c ├── spi.c ├── dmesg.c ├── led_strip.h ├── sdcard.c ├── jdprotocol.h ├── led_strip_encoder.c ├── main.c ├── wifi_impl.c ├── led.c ├── usb.c ├── sock.c └── hw-esp32.c ├── .clang-format ├── .gitignore ├── scripts ├── decode-stack.sh ├── certs.md ├── bump.sh ├── parsestack.js ├── filter-bundle.js ├── uf2families.json └── uf2conv.py ├── sample-Makefile.user ├── CODE_OF_CONDUCT.md ├── .vscode ├── tasks.json └── c_cpp_properties.json ├── CMakeLists.txt ├── LICENSE ├── .github └── workflows │ ├── build.yml │ └── codeql-analysis.yml ├── SECURITY.md ├── README.md └── Makefile /boards/esp32/sdkconfig.esp32: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32" 2 | -------------------------------------------------------------------------------- /boards/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | idf: 3 | version: ">=5.0.2" 4 | -------------------------------------------------------------------------------- /boards/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "avoid", 3 | "semi": false, 4 | "tabWidth": 4 5 | } 6 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "devicescript"] 2 | path = devicescript 3 | url = https://github.com/microsoft/devicescript 4 | -------------------------------------------------------------------------------- /boards/esp32s2/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | espressif/esp_tinyusb: "~1.0.0" 3 | idf: 4 | version: ">=5.0.2" 5 | -------------------------------------------------------------------------------- /boards/esp32s3/idf_component.yml: -------------------------------------------------------------------------------- 1 | dependencies: 2 | espressif/esp_tinyusb: "~1.0.0" 3 | idf: 4 | version: ">=5.0.2" 5 | -------------------------------------------------------------------------------- /main/jdlow.h: -------------------------------------------------------------------------------- 1 | #ifndef __JDLOW_H 2 | #define __JDLOW_H 3 | 4 | #include "jd_protocol.h" 5 | #include "hw.h" 6 | 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /main/jdtcp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "jd_protocol.h" 4 | 5 | #include "jacdac/dist/c/wifi.h" 6 | #include "jacdac/dist/c/tcp.h" 7 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | UseTab: Never 4 | ColumnLimit: 100 5 | AllowShortFunctionsOnASingleLine: Inline 6 | SortIncludes: false 7 | -------------------------------------------------------------------------------- /boards/esp32s2/sdkconfig.esp32s2: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32s2" 2 | 3 | CONFIG_TINYUSB=y 4 | CONFIG_TINYUSB_CDC_ENABLED=y 5 | CONFIG_TINYUSB_CDC_TX_BUFSIZE=128 6 | -------------------------------------------------------------------------------- /boards/esp32s3/sdkconfig.esp32s3: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32s3" 2 | 3 | CONFIG_TINYUSB=y 4 | CONFIG_TINYUSB_CDC_ENABLED=y 5 | CONFIG_TINYUSB_CDC_TX_BUFSIZE=128 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | tmp 3 | .vscode/settings.json 4 | sdkconfig 5 | sdkconfig.defaults 6 | Makefile.user 7 | dist/ 8 | dependencies.lock 9 | managed_components/ 10 | main/idf_component.yml 11 | -------------------------------------------------------------------------------- /scripts/decode-stack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | while read LINE ; do 4 | for w in $LINE ; do 5 | echo $w 6 | done 7 | done | riscv32-esp-elf-addr2line -pf -e build/espjd.elf | grep -v '?? ??:0' 8 | -------------------------------------------------------------------------------- /boards/esp32c3/sdkconfig.esp32c3: -------------------------------------------------------------------------------- 1 | CONFIG_IDF_TARGET="esp32c3" 2 | CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=n 3 | CONFIG_ESP_CONSOLE_SECONDARY_NONE=y 4 | # This is not doing anything: 5 | # CONFIG_RTC_CLOCK_BBPLL_POWER_ON_WITH_USB=y 6 | 7 | -------------------------------------------------------------------------------- /sample-Makefile.user: -------------------------------------------------------------------------------- 1 | # Choose your ESP target SOC 2 | TARGET = esp32s2 3 | #TARGET = esp32c3 4 | 5 | # Windows: 6 | #SERIAL_PORT = COM12 7 | # macOS - FTDI device 8 | #SERIAL_PORT = $(wildcard /dev/cu.usbserial-14*1) 9 | # macOS - builtin USB serial on C3 10 | #SERIAL_PORT = $(wildcard /dev/cu.usbmodem14*1) 11 | -------------------------------------------------------------------------------- /boards/esp32s2/msr207_v43.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "MSR JM Brain S2-mini 207 v4.3", 4 | "$include": "msr207_v42.board.json", 5 | "services": [ 6 | { 7 | "service": "power", 8 | "mode": 1 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /boards/partitions.csv: -------------------------------------------------------------------------------- 1 | # Name, Type, SubType, Offset, Size, Flags 2 | dcfg, 0x8A, 0x00, 0x9000, 0x2000, 3 | nvs, data, nvs, 0xb000, 0x4000, 4 | phy_init, data, phy, 0xf000, 0x1000, 5 | factory, app, factory, 0x10000, 1728K, 6 | fstor, 0x8A, 0x01, 0x1c0000, 256K, 7 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB MAIN_FILES 2 | *.c 3 | ) 4 | 5 | idf_component_register( 6 | SRCS 7 | "../build/version.c" 8 | ${MAIN_FILES} 9 | 10 | INCLUDE_DIRS 11 | "." 12 | ) 13 | 14 | target_link_libraries(${COMPONENT_LIB} PUBLIC jacdac) 15 | target_link_libraries(${COMPONENT_LIB} PUBLIC devicescript) 16 | -------------------------------------------------------------------------------- /boards/regen.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | set -x 5 | npx typescript-json-schema --noExtraProps --required --defaultNumberType integer \ 6 | esp32.d.ts ESP32DeviceConfig --out esp32deviceconfig.schema.json 7 | npx typescript-json-schema --noExtraProps --required --defaultNumberType integer \ 8 | esp32.d.ts ESP32ArchConfig --out esp32archconfig.schema.json 9 | -------------------------------------------------------------------------------- /boards/esp32.d.ts: -------------------------------------------------------------------------------- 1 | import { JsonComment, Pin } from "@devicescript/srvcfg" 2 | import { 3 | DeviceConfig, 4 | ArchConfig, 5 | } from "../devicescript/interop/src/archconfig" 6 | 7 | interface ESP32DeviceConfig extends DeviceConfig { 8 | sd?: SdCardConfig 9 | } 10 | 11 | interface ESP32ArchConfig extends ArchConfig {} 12 | 13 | interface SdCardConfig extends JsonComment { 14 | pinMISO: Pin 15 | pinMOSI: Pin 16 | pinSCK: Pin 17 | pinCS: Pin 18 | } 19 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cppbuild", 6 | "label": "C/C++: make", 7 | "command": "make", 8 | "args": [ 9 | "vscode" 10 | ], 11 | "options": { 12 | "cwd": "${workspaceFolder}" 13 | }, 14 | "problemMatcher": { 15 | "base": "$gcc", 16 | "fileLocation": [ 17 | "relative", 18 | "${workspaceFolder}/build" 19 | ] 20 | }, 21 | "group": "build", 22 | "detail": "compiler: make" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /main/hw.h: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "sdkconfig.h" 6 | 7 | #include "freertos/FreeRTOS.h" 8 | #include "freertos/task.h" 9 | #include "freertos/queue.h" 10 | #include "freertos/semphr.h" 11 | 12 | #include "esp_system.h" 13 | #include "esp_log.h" 14 | 15 | #include "driver/gpio.h" 16 | 17 | #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32C3) 18 | #define WORKER_CPU PRO_CPU_NUM 19 | #else 20 | #define WORKER_CPU APP_CPU_NUM 21 | #endif 22 | -------------------------------------------------------------------------------- /boards/esp32c3/arch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32archconfig.schema.json", 3 | "name": "ESP32-C3", 4 | "id": "esp32c3", 5 | "dcfgOffset": "0x9000", 6 | "binFlashOffset": 0, 7 | "binGenericFlashOffset": "0x10000", 8 | "flashPageSize": 4096, 9 | "fstorPages": 64, 10 | "fstorOffset": "0x1c0000", 11 | "pins": { 12 | "analogIn": "0-4", 13 | "bootUart": "20,21", 14 | "io": "0-10,bootUart", 15 | "boot": "2,8,9", 16 | "flash": "11-17", 17 | "usb": "18,19", 18 | "debug": "4-7" 19 | } 20 | } -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Mac", 5 | "includePath": [ 6 | "${workspaceFolder}/**" 7 | ], 8 | "defines": [], 9 | "macFrameworkPath": [], 10 | "compilerPath": "/usr/local/bin/arm-none-eabi-gcc", 11 | "cStandard": "c11", 12 | "cppStandard": "gnu++14", 13 | "intelliSenseMode": "clang-x64", 14 | "compileCommands": "${workspaceFolder}/build/compile_commands.json" 15 | } 16 | ], 17 | "version": 4 18 | } -------------------------------------------------------------------------------- /boards/esp32s2/arch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32archconfig.schema.json", 3 | "name": "ESP32-S2", 4 | "id": "esp32s2", 5 | "dcfgOffset": "0x9000", 6 | "binFlashOffset": "0x1000", 7 | "binGenericFlashOffset": "0x10000", 8 | "flashPageSize": 4096, 9 | "fstorPages": 64, 10 | "fstorOffset": "0x1c0000", 11 | "pins": { 12 | "boot": "0,45,46", 13 | "analogIn": "1-10", 14 | "io": "0-18,21,33-45", 15 | "input": "io,46", 16 | "flash": "26-32", 17 | "debug": "39-42", 18 | "analogOut": "17,18", 19 | "usb": "19,20" 20 | } 21 | } -------------------------------------------------------------------------------- /boards/esp32s3/arch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32archconfig.schema.json", 3 | "name": "ESP32-S3", 4 | "id": "esp32s3", 5 | "dcfgOffset": "0x9000", 6 | "binFlashOffset": "0x0", 7 | "binGenericFlashOffset": "0x10000", 8 | "flashPageSize": 4096, 9 | "fstorPages": 64, 10 | "fstorOffset": "0x1c0000", 11 | "pins": { 12 | "boot": "0,45,46", 13 | "analogIn": "1-10", 14 | "io": "0-18,21,33-48", 15 | "input": "io", 16 | "flash": "26-32", 17 | "octal-flash": "33-37", 18 | "debug": "39-42", 19 | "analogOut": "17,18", 20 | "usb": "19,20" 21 | } 22 | } -------------------------------------------------------------------------------- /scripts/certs.md: -------------------------------------------------------------------------------- 1 | # Certificates 2 | 3 | The built-in certificate list is intersection of the 4 | [list used by Mozilla](https://hg.mozilla.org/releases/mozilla-release/raw-file/default/security/nss/lib/ckfw/builtins/certdata.txt) 5 | with the [Microsoft Trusted Root Program](https://docs.microsoft.com/en-us/security/trusted-root/participants-list) 6 | 7 | To re-generate the list do the following: 8 | 9 | ```bash 10 | curl https://raw.githubusercontent.com/curl/curl/master/scripts/mk-ca-bundle.pl > mk-ca-bundle.pl 11 | perl mk-ca-bundle.pl -f 12 | node filter-bundle.js ca-bundle.crt ca-bundle.pem 13 | ``` 14 | 15 | The `ca-bundle.pem` file should be checked in. 16 | 17 | -------------------------------------------------------------------------------- /boards/esp32/arch.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32archconfig.schema.json", 3 | "name": "ESP32", 4 | "id": "esp32", 5 | "dcfgOffset": "0x9000", 6 | "binFlashOffset": "0x1000", 7 | "binGenericFlashOffset": "0x10000", 8 | "flashPageSize": 4096, 9 | "fstorPages": 64, 10 | "fstorOffset": "0x1c0000", 11 | "pins": { 12 | "boot": "0,2,5,12,15", 13 | "bootUart": "1,3", 14 | "io": "boot,4,13,14,18,19,21,22,23,25,26,27,32,33", 15 | "input": "io,34,35,36,37,38,39", 16 | "analogIn": "32-39", 17 | "analogOut": "25,26", 18 | "psram": "16,17", 19 | "flash": "6-11", 20 | "touch": "0,2,4,12-15,27,32,33", 21 | "debug": "12-15" 22 | } 23 | } -------------------------------------------------------------------------------- /boards/esp32/esp32_bare.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32 (bare)", 4 | "productId": "0x3ff6ffeb", 5 | "$description": "Bare ESP32 without any default functions for pins.", 6 | "url": "https://www.espressif.com/en/products/socs/esp32", 7 | "pins": { 8 | "P4": 4, 9 | "P13": 13, 10 | "P14": 14, 11 | "P18": 18, 12 | "P19": 19, 13 | "P21": 21, 14 | "P22": 22, 15 | "P23": 23, 16 | "P25": 25, 17 | "P26": 26, 18 | "P27": 27, 19 | "P32": 32, 20 | "P33": 33, 21 | "P34": 34, 22 | "P35": 35, 23 | "P36": 36, 24 | "P39": 39 25 | } 26 | } -------------------------------------------------------------------------------- /boards/sdkconfig.common: -------------------------------------------------------------------------------- 1 | 2 | # leave empty line above 3 | 4 | CONFIG_NEWLIB_ENABLE=y 5 | CONFIG_NEWLIB_LIBRARY_LEVEL_NORMAL=y 6 | CONFIG_NEWLIB_NANO_FORMAT= 7 | 8 | CONFIG_SSL_USING_MBEDTLS=y 9 | CONFIG_LWIP_IPV6=y 10 | CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE=y 11 | CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE=y 12 | CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE_PATH="scripts" 13 | 14 | CONFIG_ESP_TASK_WDT_TIMEOUT_S=5 15 | CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=n 16 | CONFIG_ESP_TASK_WDT=n 17 | 18 | CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=5120 19 | 20 | # CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE=y 21 | 22 | CONFIG_PARTITION_TABLE_CUSTOM=y 23 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="boards/partitions.csv" 24 | CONFIG_PARTITION_TABLE_FILENAME="boards/partitions.csv" 25 | -------------------------------------------------------------------------------- /boards/esp32c3/seeed_xiao_esp32c3.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Seeed Studio XIAO ESP32C3", 4 | "productId": "0x3eff6b51", 5 | "$description": "A tiny ESP32-C3 board.", 6 | "url": "https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html", 7 | "log": { 8 | "pinTX": "TX_D6" 9 | }, 10 | "pins": { 11 | "A0_D0": 2, 12 | "A1_D1": 3, 13 | "A2_D2": 4, 14 | "A3_D3": 5, 15 | "SDA_D4": 6, 16 | "SCL_D5": 7, 17 | "TX_D6": 21, 18 | "RX_D7": 20, 19 | "SCK_D8": 8, 20 | "MISO_D9": 9, 21 | "MOSI_D10": 10 22 | }, 23 | "$services": [ 24 | { 25 | "service": "button", 26 | "name": "buttonBOOT", 27 | "pin": "MISO_D9" 28 | } 29 | ] 30 | } -------------------------------------------------------------------------------- /scripts/bump.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ "X`git status --porcelain --untracked-files=no`" != X ] ; then 4 | git status 5 | echo 6 | echo "*** You have local changes; cannot bump." 7 | exit 1 8 | fi 9 | 10 | git pull || exit 1 11 | 12 | eval `git describe --dirty --tags --match 'v[0-9]*' --always | sed -e 's/-.*//; s/v/v0=/; s/\./ v1=/; s/\./ v2=/'` 13 | defl=0.0.0 14 | if [ "X$v0" != X ] ; then 15 | defl=$v0.$v1.$(($v2 + 1)) 16 | fi 17 | set -e 18 | echo "Enter version [Enter = $defl; Ctrl-C to cancel]:" 19 | read ver 20 | if [ "X$ver" = "X" ] ; then 21 | ver="$defl" 22 | else 23 | ver=$(echo "$ver" | sed -e 's/v//i') 24 | fi 25 | if echo "$ver" | grep -qE '^[0-9]+\.[0-9]+\.[0-9]+$' ; then 26 | : 27 | else 28 | echo "Invalid version: $ver" 29 | exit 1 30 | fi 31 | 32 | set -x 33 | git tag "v$ver" 34 | git push --tags 35 | git push 36 | -------------------------------------------------------------------------------- /boards/esp32c3/esp32c3_bare.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32-C3 (bare)", 4 | "productId": "0x3a1d89be", 5 | "$description": "A bare ESP32-C3 board without any pin functions.", 6 | "url": "https://www.espressif.com/en/products/socs/esp32-c3", 7 | "log": { 8 | "pinTX": "P21" 9 | }, 10 | "pins": { 11 | "P0": 0, 12 | "P1": 1, 13 | "P2": 2, 14 | "P3": 3, 15 | "P4": 4, 16 | "P5": 5, 17 | "P6": 6, 18 | "P7": 7, 19 | "P8": 8, 20 | "P9": 9, 21 | "P10": 10, 22 | "P20": 20, 23 | "P21": 21 24 | }, 25 | "$services": [ 26 | { 27 | "service": "button", 28 | "name": "buttonBOOT", 29 | "pin": "P9" 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /boards/esp32c3/esp32_c3fh4_rgb.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "ESP32-C3FH4-RGB", 4 | "productId": "0x3a90885c", 5 | "$description": "A tiny ESP32-C3 board with 5x5 LED array.", 6 | "url": "https://github.com/01Space/ESP32-C3FH4-RGB", 7 | "$services": [ 8 | { 9 | "name": "buttonBOOT", 10 | "pin": 9, 11 | "service": "button" 12 | } 13 | ], 14 | "i2c": { 15 | "$connector": "Qwiic", 16 | "pinSCL": 1, 17 | "pinSDA": 0 18 | }, 19 | "led": { 20 | "pin": 10, 21 | "isMono": true 22 | }, 23 | "log": { 24 | "pinTX": 21 25 | }, 26 | "pins": { 27 | "P2": 2, 28 | "P3": 3, 29 | "P4": 4, 30 | "P5": 5, 31 | "P6": 6, 32 | "P7": 7, 33 | "LEDS": 8, 34 | "P20": 20 35 | } 36 | } -------------------------------------------------------------------------------- /boards/esp32c3/esp32c3_supermini.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "ESP32-C3 SuperMini", 4 | "productId": "0x31606c1c", 5 | "$description": "A super tiny ESP32-C3 board.", 6 | "url": "https://banggood.com/ESP32-C3-Development-Board-ESP32-SuperMini-WiFi-Bluetooth-Mini-Module-p-1997449.html", 7 | "$services": [ 8 | { 9 | "name": "buttonBOOT", 10 | "pin": 9, 11 | "service": "button" 12 | } 13 | ], 14 | "i2c": { 15 | "pinSCL": 7, 16 | "pinSDA": 6 17 | }, 18 | "led": { 19 | "pin": 8, 20 | "isMono": true 21 | }, 22 | "log": { 23 | "pinTX": 21 24 | }, 25 | "pins": { 26 | "P0": 0, 27 | "P1": 1, 28 | "P2": 2, 29 | "P3": 3, 30 | "P4": 4, 31 | "P5": 5, 32 | "P10": 10, 33 | "P20": 20 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /boards/esp32c3/adafruit_qt_py_c3.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Adafruit QT Py ESP32-C3 WiFi", 4 | "productId": "0x3693d40b", 5 | "$description": "A tiny ESP32-C3 board.", 6 | "url": "https://www.adafruit.com/product/5405", 7 | "log": { 8 | "pinTX": "TX_D6" 9 | }, 10 | "led": { 11 | "type": 1, 12 | "pin": 2 13 | }, 14 | "i2c": { 15 | "$connector": "Qwiic", 16 | "pinSCL": "SCL_D5", 17 | "pinSDA": "SDA_D4" 18 | }, 19 | "pins": { 20 | "A0_D0": 4, 21 | "A1_D1": 3, 22 | "A2_D2": 1, 23 | "A3_D3": 0, 24 | "SDA_D4": 5, 25 | "SCL_D5": 6, 26 | "TX_D6": 21, 27 | "RX_D7": 20, 28 | "SCK_D8": 10, 29 | "MISO_D9": 8, 30 | "MOSI_D10": 7 31 | }, 32 | "$services": [ 33 | { 34 | "service": "button", 35 | "name": "buttonBOOT", 36 | "pin": 9 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.5) 2 | 3 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 4 | include(build/options.cmake) 5 | 6 | idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=esp_panic_handler" APPEND) 7 | idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=uart_hal_write_txfifo" APPEND) 8 | idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=panic_restart" APPEND) 9 | idf_build_set_property(LINK_OPTIONS "-Wl,--wrap=_esp_error_check_failed" APPEND) 10 | 11 | project(espjd) 12 | 13 | # this passes the esp-idf compile options down to jacdac-c/devicescript build 14 | idf_build_get_property(compile_options COMPILE_OPTIONS GENERATOR_EXPRESSION) 15 | idf_build_get_property(c_compile_options C_COMPILE_OPTIONS GENERATOR_EXPRESSION) 16 | add_compile_options("${compile_options}") 17 | add_c_compile_options("${c_compile_options}") 18 | 19 | # location of jd_user_config.h 20 | set(JACDAC_USER_CONFIG_DIR "../../../main") 21 | add_subdirectory(devicescript/runtime/jacdac-c) 22 | add_subdirectory(devicescript/runtime/devicescript) 23 | -------------------------------------------------------------------------------- /boards/esp32c3/esp32c3_rust_devkit.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32-C3-RUST-DevKit", 4 | "productId": "0x33f29c59", 5 | "$description": "A ESP32-C3 dev-board from Espressif with IMU and Temp/Humidity sensor, originally for ESP32 Rust port.", 6 | "url": "https://github.com/esp-rs/esp-rust-board", 7 | "log": { 8 | "pinTX": "P21" 9 | }, 10 | "led": { 11 | "type": 1, 12 | "pin": 2 13 | }, 14 | "i2c": { 15 | "$connector": "Header", 16 | "pinSCL": 8, 17 | "pinSDA": 10 18 | }, 19 | "pins": { 20 | "P0": 0, 21 | "P1": 1, 22 | "P3": 3, 23 | "P4": 4, 24 | "P5": 5, 25 | "P6": 6, 26 | "P9": 9, 27 | "LED": 7, 28 | "P21": 21, 29 | "P20": 20 30 | }, 31 | "$services": [ 32 | { 33 | "service": "button", 34 | "name": "buttonBOOT", 35 | "pin": "P9" 36 | } 37 | ] 38 | } -------------------------------------------------------------------------------- /main/led_strip_encoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | #pragma once 7 | 8 | #include 9 | #include "driver/rmt_encoder.h" 10 | 11 | #ifdef __cplusplus 12 | extern "C" { 13 | #endif 14 | 15 | /** 16 | * @brief Type of led strip encoder configuration 17 | */ 18 | typedef struct { 19 | uint32_t resolution; /*!< Encoder resolution, in Hz */ 20 | } led_strip_encoder_config_t; 21 | 22 | /** 23 | * @brief Create RMT encoder for encoding LED strip pixels into RMT symbols 24 | * 25 | * @param[in] config Encoder configuration 26 | * @param[out] ret_encoder Returned encoder handle 27 | * @return 28 | * - ESP_ERR_INVALID_ARG for any invalid arguments 29 | * - ESP_ERR_NO_MEM out of memory when creating led strip encoder 30 | * - ESP_OK if creating encoder successfully 31 | */ 32 | esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder); 33 | 34 | #ifdef __cplusplus 35 | } 36 | #endif 37 | -------------------------------------------------------------------------------- /boards/esp32c3/seeed_xiao_esp32c3_msr218.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Seeed Studio XIAO ESP32C3 with MSR218 base", 4 | "productId": "0x36b64827", 5 | "$description": "A tiny ESP32-C3 board mounted on base with Jacdac, Qwiic and Grove connectors.", 6 | "url": "https://www.seeedstudio.com/Seeed-XIAO-ESP32C3-p-5431.html", 7 | "log": { 8 | "pinTX": "TX" 9 | }, 10 | "pins": { 11 | "A0": 2, 12 | "A1": 3, 13 | "A2": 4, 14 | "JD": 5, 15 | "SDA": 6, 16 | "SCL": 7, 17 | "TX": 21, 18 | "RX": 20, 19 | "LED_PWR": 8, 20 | "D9": 9, 21 | "LED": 10 22 | }, 23 | "jacdac": { 24 | "$connector": "Jacdac", 25 | "pin": "JD" 26 | }, 27 | "led": { 28 | "type": 1, 29 | "pin": "LED" 30 | }, 31 | "sPin": { 32 | "LED_PWR": 1 33 | }, 34 | "i2c": { 35 | "$connector": "Qwiic", 36 | "pinSCL": "SCL", 37 | "pinSDA": "SDA" 38 | }, 39 | "$services": [] 40 | } -------------------------------------------------------------------------------- /boards/esp32s2/msr207_v42.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "MSR JM Brain S2-mini 207 v4.2", 4 | "productId": "0x322e0e64", 5 | "log": { 6 | "pinTX": 43 7 | }, 8 | "jacdac": { 9 | "$connector": "Jacdac", 10 | "pin": 17 11 | }, 12 | "led": { 13 | "rgb": [ 14 | { 15 | "pin": 8, 16 | "mult": 250 17 | }, 18 | { 19 | "pin": 7, 20 | "mult": 60 21 | }, 22 | { 23 | "pin": 6, 24 | "mult": 150 25 | } 26 | ] 27 | }, 28 | "pins": { 29 | "P33": 33, 30 | "P34": 34 31 | }, 32 | "sd": { 33 | "pinCS": 38, 34 | "pinMISO": 37, 35 | "pinMOSI": 35, 36 | "pinSCK": 36 37 | }, 38 | "services": [ 39 | { 40 | "service": "power", 41 | "name": "power", 42 | "mode": 0, 43 | "pinEn": 2, 44 | "pinFault": 13, 45 | "faultIgnoreMs": 100 46 | } 47 | ] 48 | } -------------------------------------------------------------------------------- /boards/esp32/esp32_devkit_c.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32-DevKitC", 4 | "productId": "0x3c507a05", 5 | "$description": "There are currently issues with serial chip on these, best avoid. ESP32-DevKitC development board. This will also work with DOIT DevkitV1, NodeMCU ESP32, ... (search for 'esp32 devkit'). Some of these boards do not have the LED.", 6 | "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32/hw-reference/esp32/get-started-devkitc.html", 7 | "led": { 8 | "pin": 2 9 | }, 10 | "pins": { 11 | "VP": 36, 12 | "VN": 39, 13 | "P34": 34, 14 | "P35": 35, 15 | "P32": 32, 16 | "P33": 33, 17 | "P25": 25, 18 | "P26": 26, 19 | "P27": 27, 20 | "P14": 14, 21 | "P13": 13, 22 | "P23": 23, 23 | "P22": 22, 24 | "P21": 21, 25 | "P19": 19, 26 | "P18": 18, 27 | "P4": 4 28 | }, 29 | "$services": [ 30 | { 31 | "service": "button", 32 | "name": "buttonIO0", 33 | "pin": 0 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /boards/esp32s2/adafruit_feather_esp32_s2.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Adafruit Feather ESP32-S2", 4 | "productId": "0x3c2ed99e", 5 | "$description": "A S2 Feather from Adafruit. (untested)", 6 | "url": "https://www.adafruit.com/product/5000", 7 | "log": { 8 | "pinTX": 43 9 | }, 10 | "i2c": { 11 | "pinSCL": "SCL", 12 | "pinSDA": "SDA", 13 | "$connector": "Qwiic" 14 | }, 15 | "led": { 16 | "pin": 33, 17 | "type": 1 18 | }, 19 | "sPin": { 20 | "LED_PWR": 1, 21 | "PWR": 1 22 | }, 23 | "pins": { 24 | "A0": 18, 25 | "A1": 17, 26 | "A2": 16, 27 | "A3": 15, 28 | "A4_D24": 14, 29 | "A5_D25": 8, 30 | "SCK": 36, 31 | "MOSI": 35, 32 | "MISO": 37, 33 | "RX_D0": 38, 34 | "TX_D1": 39, 35 | "SDA": 3, 36 | "SCL": 4, 37 | "D5": 5, 38 | "D6": 6, 39 | "D9": 9, 40 | "D10": 10, 41 | "D11": 11, 42 | "D12": 12, 43 | "D13": 13, 44 | "LED_PWR": 21, 45 | "PWR": 7 46 | } 47 | } -------------------------------------------------------------------------------- /boards/esp32s2/esp32s2_bare.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32-S2 (bare)", 4 | "productId": "0x3f140dcc", 5 | "$description": "A bare ESP32-S2 board without any pin functions.", 6 | "url": "https://www.espressif.com/en/products/socs/esp32-s2", 7 | "log": { 8 | "pinTX": "P43" 9 | }, 10 | "pins": { 11 | "P0": 0, 12 | "P1": 1, 13 | "P2": 2, 14 | "P3": 3, 15 | "P4": 4, 16 | "P5": 5, 17 | "P6": 6, 18 | "P7": 7, 19 | "P8": 8, 20 | "P9": 9, 21 | "P10": 10, 22 | "P11": 11, 23 | "P12": 12, 24 | "P13": 13, 25 | "P14": 14, 26 | "P15": 15, 27 | "P16": 16, 28 | "P17": 17, 29 | "P18": 18, 30 | "P21": 21, 31 | "P33": 33, 32 | "P34": 34, 33 | "P35": 35, 34 | "P36": 36, 35 | "P37": 37, 36 | "P38": 38, 37 | "P39": 39, 38 | "P40": 40, 39 | "P41": 41, 40 | "P42": 42, 41 | "P43": 43, 42 | "P44": 44, 43 | "P45": 45, 44 | "P46": 46 45 | } 46 | } -------------------------------------------------------------------------------- /boards/esp32s2/msr48.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "MSR JacdacIoT 48 v0.2", 4 | "productId": "0x3de1398b", 5 | "log": { 6 | "pinTX": 43 7 | }, 8 | "jacdac": { 9 | "$connector": "Jacdac", 10 | "pin": 17 11 | }, 12 | "led": { 13 | "rgb": [ 14 | { 15 | "pin": 8, 16 | "mult": 250 17 | }, 18 | { 19 | "pin": 7, 20 | "mult": 60 21 | }, 22 | { 23 | "pin": 6, 24 | "mult": 150 25 | } 26 | ] 27 | }, 28 | "i2c": { 29 | "$connector": "Qwiic", 30 | "pinSCL": 10, 31 | "pinSDA": 9 32 | }, 33 | "pins": { 34 | "TX": 37, 35 | "RX": 38, 36 | "P33": 33, 37 | "P34": 34, 38 | "P35": 35, 39 | "P36": 36 40 | }, 41 | "services": [ 42 | { 43 | "service": "power", 44 | "name": "power", 45 | "mode": 0, 46 | "pinEn": 2, 47 | "pinFault": 13, 48 | "faultIgnoreMs": 100 49 | } 50 | ] 51 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /boards/esp32s3/esp32s3_bare.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32-S3 (bare)", 4 | "productId": "0x3e121501", 5 | "$description": "A bare ESP32-S3 board without any pin functions.", 6 | "url": "https://www.espressif.com/en/products/socs/esp32-s3", 7 | "log": { 8 | "pinTX": "P43" 9 | }, 10 | "pins": { 11 | "P0": 0, 12 | "P1": 1, 13 | "P2": 2, 14 | "P3": 3, 15 | "P4": 4, 16 | "P5": 5, 17 | "P6": 6, 18 | "P7": 7, 19 | "P8": 8, 20 | "P9": 9, 21 | "P10": 10, 22 | "P11": 11, 23 | "P12": 12, 24 | "P13": 13, 25 | "P14": 14, 26 | "P15": 15, 27 | "P16": 16, 28 | "P17": 17, 29 | "P18": 18, 30 | "P21": 21, 31 | "P33": 33, 32 | "P34": 34, 33 | "#P35": 35, 34 | "#P36": 36, 35 | "#P37": 37, 36 | "P38": 38, 37 | "P39": 39, 38 | "P40": 40, 39 | "P41": 41, 40 | "P42": 42, 41 | "P43": 43, 42 | "P44": 44, 43 | "P45": 45, 44 | "P46": 46, 45 | "P47": 47, 46 | "P48": 48 47 | } 48 | } -------------------------------------------------------------------------------- /boards/esp32s3/esp32s3_devkit_m.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Espressif ESP32-S3 DevKitM", 4 | "productId": "0x3574d277", 5 | "$description": "ESP32-S3 DevKitM development board. Should also work for DevKitC.", 6 | "url": "https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/hw-reference/esp32s3/user-guide-devkitm-1.html", 7 | "log": { 8 | "pinTX": "P43" 9 | }, 10 | "led": { 11 | "pin": "P48", 12 | "type": 1 13 | }, 14 | "pins": { 15 | "P0": 0, 16 | "P1": 1, 17 | "P2": 2, 18 | "P3": 3, 19 | "P4": 4, 20 | "P5": 5, 21 | "P6": 6, 22 | "P7": 7, 23 | "P8": 8, 24 | "P9": 9, 25 | "P10": 10, 26 | "P11": 11, 27 | "P12": 12, 28 | "P13": 13, 29 | "P14": 14, 30 | "P15": 15, 31 | "P16": 16, 32 | "P17": 17, 33 | "P18": 18, 34 | "P21": 21, 35 | "P33": 33, 36 | "P34": 34, 37 | "P38": 38, 38 | "P39": 39, 39 | "P40": 40, 40 | "P41": 41, 41 | "P42": 42, 42 | "P43": 43, 43 | "P45": 45, 44 | "P46": 46, 45 | "P47": 47, 46 | "P48": 48 47 | } 48 | } -------------------------------------------------------------------------------- /main/flash.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "esp_partition.h" 3 | #include "esp_flash.h" 4 | #include "spi_flash_mmap.h" 5 | 6 | uint32_t flash_size, flash_base; 7 | int32_t flash_offset; 8 | 9 | void flash_init(void) { 10 | const esp_partition_t *part = esp_partition_find_first(0x8A, 0x01, NULL); 11 | JD_ASSERT(part != NULL); 12 | JD_ASSERT((part->address & 0xffff) == 0); 13 | JD_ASSERT((part->size & (JD_FLASH_PAGE_SIZE - 1)) == 0); 14 | 15 | const void *rd_part; 16 | spi_flash_mmap_handle_t map; 17 | CHK(spi_flash_mmap(part->address, part->size, SPI_FLASH_MMAP_DATA, &rd_part, &map)); 18 | 19 | flash_size = part->size; 20 | flash_base = (uint32_t)rd_part; 21 | flash_offset = part->address - flash_base; 22 | 23 | DMESG("fstor at %x -> %p (%ukB)", (unsigned)part->address, rd_part, 24 | (unsigned)(flash_size >> 10)); 25 | } 26 | 27 | static uint32_t flash_addr(void *addr) { 28 | JD_ASSERT(flash_offset != 0); 29 | return (uint32_t)addr + flash_offset; 30 | } 31 | 32 | void flash_erase(void *page_addr) { 33 | CHK(esp_flash_erase_region(NULL, flash_addr(page_addr), JD_FLASH_PAGE_SIZE)); 34 | } 35 | 36 | void flash_program(void *dst, const void *src, uint32_t len) { 37 | CHK(esp_flash_write(NULL, src, flash_addr(dst), len)); 38 | } 39 | 40 | void flash_sync(void) {} 41 | -------------------------------------------------------------------------------- /boards/esp32c3/kittenbot_grapebit_esp32c3.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "KittenBot Grape:Bit ESP32-C3", 4 | "productId": "0x38ccab8d", 5 | "$description": "A round board packed with IMU, buzzer, motor control, 4 RGB LEDs, and Jacdac.", 6 | "url": "https://www.kittenbot.cc/products/kittenbot-grapebit", 7 | "jacdac": { 8 | "$connector": "Jacdac", 9 | "pin": 5 10 | }, 11 | "led": { 12 | "type": 0, 13 | "pin": 8 14 | }, 15 | "i2c": { 16 | "pinSCL": 7, 17 | "pinSDA": 6 18 | }, 19 | "pins": { 20 | "P1": 2, 21 | "P2": 0, 22 | "LED": 10 23 | }, 24 | "$services": [ 25 | { 26 | "name": "buttonA", 27 | "service": "button", 28 | "pin": 21 29 | }, 30 | { 31 | "name": "buttonB", 32 | "service": "button", 33 | "pin": 9 34 | }, 35 | { 36 | "name": "music", 37 | "service": "buzzer", 38 | "pin": 3 39 | }, 40 | { 41 | "name": "M1", 42 | "service": "motor", 43 | "pin1": 1 44 | }, 45 | { 46 | "name": "M2", 47 | "service": "motor", 48 | "pin1": 4 49 | } 50 | ] 51 | } 52 | -------------------------------------------------------------------------------- /main/jdesp.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "jdlow.h" 3 | #include "jdtcp.h" 4 | #include "jd_client.h" 5 | #include "storage/jd_storage.h" 6 | #include "devicescript.h" 7 | #include "services/jd_services.h" 8 | #include "services/interfaces/jd_pins.h" 9 | #include "services/interfaces/jd_adc.h" 10 | #include "services/interfaces/jd_pwm.h" 11 | #include "services/interfaces/jd_flash.h" 12 | #include "interfaces/jd_usb.h" 13 | #include "network/jd_network.h" 14 | 15 | #define CHK ESP_ERROR_CHECK 16 | 17 | typedef struct worker *worker_t; 18 | // needs worker_do_work() called from somewhere 19 | worker_t worker_alloc(void); 20 | // starts task that will call worker_do_work(): 21 | worker_t worker_start(const char *id, uint32_t stack_size); 22 | int worker_run(worker_t w, TaskFunction_t fn, void *arg); 23 | void worker_set_idle(worker_t w, TaskFunction_t fn, void *arg); 24 | int worker_run_wait(worker_t w, TaskFunction_t fn, void *arg); 25 | void worker_do_work(worker_t w); 26 | 27 | int tim_worker_run(TaskFunction_t fn, void *arg); 28 | 29 | bool jd_rx_has_frame(void); 30 | void usb_init(void); 31 | void usb_pre_init(void); 32 | 33 | void log_free_mem(void); 34 | void uart_log_init(void); 35 | void uart_log_write(const void *data0, unsigned size); 36 | void uart_log_dmesg(void); 37 | 38 | extern worker_t main_worker; 39 | 40 | char *extract_property(const char *property_bag, int plen, const char *key); 41 | char *jd_hmac_b64(const char *key, const char **parts); 42 | 43 | void reboot_to_uf2(void); 44 | void flush_dmesg(void); 45 | 46 | void init_sdcard(void); 47 | 48 | void jd_tcpsock_process(void); 49 | void jd_tcpsock_init(void); 50 | 51 | void get_i2c_pins(uint8_t *sda, uint8_t *scl); 52 | void flash_init(void); -------------------------------------------------------------------------------- /main/jd_user_config.h: -------------------------------------------------------------------------------- 1 | #ifndef JD_USER_CONFIG_H 2 | #define JD_USER_CONFIG_H 3 | 4 | #include 5 | #include "../build/config/sdkconfig.h" 6 | 7 | #define JD_DMESG_BUFFER_SIZE 4096 8 | 9 | #define JD_LOG DMESG 10 | #define JD_WR_OVERHEAD 28 11 | 12 | #ifndef NO_JACSCRIPT 13 | #define JD_CLIENT 1 14 | #endif 15 | 16 | #define JD_MS_TIMER 1 17 | #define JD_FREE_SUPPORTED 1 18 | #define JD_ADVANCED_STRING 1 19 | #define JD_LSTORE 1 20 | 21 | #define JD_RAW_FRAME 1 22 | 23 | #define JD_FLASH_PAGE_SIZE 4096 24 | 25 | #define JD_USB_BRIDGE 1 26 | 27 | // probably not so useful on brains... 28 | #define JD_CONFIG_WATCHDOG 0 29 | 30 | void jdesp_wake_main(void); 31 | #define JD_WAKE_MAIN() jdesp_wake_main() 32 | 33 | #define JD_SIMPLE_ALLOC 0 34 | 35 | #if CONFIG_IDF_TARGET_ESP32S2 36 | #define JD_GC_KB 40 37 | #else 38 | #define JD_GC_KB 64 39 | #endif 40 | 41 | #define JD_I2C_HELPERS 1 42 | #define JD_WIFI 1 43 | #define JD_NET_BRIDGE JD_WIFI 44 | 45 | const void *dcfg_base_addr(void); 46 | #define JD_DCFG_BASE_ADDR dcfg_base_addr() 47 | 48 | #if CONFIG_IDF_TARGET_ESP32 49 | #define JD_CONFIG_TEMPERATURE 0 50 | #else 51 | #define JD_CONFIG_TEMPERATURE 1 52 | #endif 53 | 54 | #define JD_FAST _JD_SECTION_ATTR_IMPL(".iram1", __COUNTER__) 55 | #define _JD_COUNTER_STRINGIFY(COUNTER) #COUNTER 56 | #define _JD_SECTION_ATTR_IMPL(SECTION, COUNTER) \ 57 | __attribute__((section(SECTION "." _JD_COUNTER_STRINGIFY(COUNTER)))) 58 | 59 | #define JD_SPI 1 60 | #define JD_I2C 1 61 | #define JD_LED_STRIP 1 62 | 63 | extern uint32_t flash_size, flash_base; 64 | #define JD_FSTOR_TOTAL_SIZE flash_size 65 | #define JD_FSTOR_BASE_ADDR flash_base 66 | #define JD_FSTOR_MAX_DATA_PAGES (512 / 4) 67 | 68 | #endif 69 | -------------------------------------------------------------------------------- /main/worker.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | struct worker { 4 | TaskHandle_t task; 5 | QueueHandle_t queue; 6 | TaskFunction_t fn; 7 | void *arg; 8 | }; 9 | 10 | typedef struct qitem { 11 | TaskFunction_t fn; 12 | void *arg; 13 | } qitem_t; 14 | 15 | void worker_do_work(worker_t w) { 16 | while (1) { 17 | if (w->fn) 18 | w->fn(w->arg); 19 | qitem_t evt; 20 | if (xQueueReceive(w->queue, &evt, 0)) 21 | evt.fn(evt.arg); 22 | else 23 | break; 24 | } 25 | } 26 | 27 | static void worker_main(void *arg) { 28 | worker_t w = (worker_t)arg; 29 | while (1) { 30 | qitem_t evt; 31 | if (xQueueReceive(w->queue, &evt, w->fn ? 20 : 1)) 32 | evt.fn(evt.arg); 33 | if (w->fn) 34 | w->fn(w->arg); 35 | } 36 | } 37 | 38 | worker_t worker_alloc(void) { 39 | worker_t w = (worker_t)calloc(1, sizeof(struct worker)); 40 | w->queue = xQueueCreate(20, sizeof(qitem_t)); 41 | return w; 42 | } 43 | 44 | worker_t worker_start(const char *id, uint32_t stack_size) { 45 | worker_t w = worker_alloc(); 46 | // The main task is at priority 1, so we're higher priority (run "more often"). 47 | // Timer task runs at much higher priority (~20). 48 | xTaskCreatePinnedToCore(worker_main, id, stack_size, w, 2, &w->task, WORKER_CPU); 49 | return w; 50 | } 51 | 52 | void worker_set_idle(worker_t w, TaskFunction_t fn, void *arg) { 53 | w->fn = fn; 54 | w->arg = arg; 55 | } 56 | 57 | int worker_run(worker_t w, TaskFunction_t fn, void *arg) { 58 | qitem_t evt = {fn, arg}; 59 | if (xQueueSendFromISR(w->queue, &evt, NULL) == pdPASS) 60 | return 0; 61 | return -1; 62 | } 63 | 64 | int worker_run_wait(worker_t w, TaskFunction_t fn, void *arg) { 65 | qitem_t evt = {fn, arg}; 66 | if (xQueueSend(w->queue, &evt, 100) == pdPASS) 67 | return 0; 68 | return -1; 69 | } -------------------------------------------------------------------------------- /boards/esp32s2/feather_s2.board.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "../esp32deviceconfig.schema.json", 3 | "devName": "Unexpected Maker FeatherS2 ESP32-S2", 4 | "productId": "0x3126f707", 5 | "$description": "ESP32-S2 based development board in a Feather format.", 6 | "url": "https://unexpectedmaker.com/shop/feathers2-esp32-s2", 7 | "log": { 8 | "pinTX": "TX_D1" 9 | }, 10 | "i2c": { 11 | "pinSCL": "SCL", 12 | "pinSDA": "SDA", 13 | "$connector": "Qwiic" 14 | }, 15 | "led": { 16 | "pin": 40, 17 | "pinCLK": 45, 18 | "type": 2 19 | }, 20 | "$pins": { 21 | "P17": "A0", 22 | "P18": "A1", 23 | "P14": "A2", 24 | "P12": "A3", 25 | "P6": "A4_D24", 26 | "P5": "A5_D25", 27 | "SDO": "MOSI", 28 | "SDI": "MISO", 29 | "P33": "D5", 30 | "P38": "D6", 31 | "P1": "D9", 32 | "P3": "D10", 33 | "P7": "D11", 34 | "P10": "D12", 35 | "P11": "D13" 36 | }, 37 | "pins": { 38 | "A0": 17, 39 | "A1": 18, 40 | "A2": 14, 41 | "A3": 12, 42 | "A4_D24": 6, 43 | "A5_D25": 5, 44 | "SCK": 36, 45 | "MOSI": 35, 46 | "MISO": 37, 47 | "RX_D0": 44, 48 | "TX_D1": 43, 49 | "SDA": 8, 50 | "SCL": 9, 51 | "D5": 33, 52 | "D6": 38, 53 | "D9": 1, 54 | "D10": 3, 55 | "D11": 7, 56 | "D12": 10, 57 | "D13": 11, 58 | "LED0": 13, 59 | "LED_PWR": 21 60 | }, 61 | "sPin": { 62 | "LED_PWR": 1 63 | }, 64 | "$services": [ 65 | { 66 | "service": "button", 67 | "name": "buttonBOOT", 68 | "pin": 0 69 | }, 70 | { 71 | "service": "analog:lightLevel", 72 | "name": "ambientLight", 73 | "pin": 4 74 | } 75 | ] 76 | } -------------------------------------------------------------------------------- /main/utils.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "mbedtls/md.h" 4 | #include "mbedtls/base64.h" 5 | 6 | char *extract_property(const char *property_bag, int plen, const char *key) { 7 | int klen = strlen(key); 8 | for (int ptr = 0; ptr + klen < plen;) { 9 | int nextp = ptr; 10 | while (nextp < plen && property_bag[nextp] != ';') 11 | nextp++; 12 | if (property_bag[ptr + klen] == '=' && memcmp(property_bag + ptr, key, klen) == 0) { 13 | int sidx = ptr + klen + 1; 14 | int rlen = nextp - sidx; 15 | char *r = jd_alloc(rlen + 1); 16 | memcpy(r, property_bag + sidx, rlen); 17 | r[rlen] = 0; 18 | return r; 19 | } 20 | if (nextp < plen) 21 | nextp++; 22 | ptr = nextp; 23 | } 24 | return NULL; 25 | } 26 | 27 | char *jd_hmac_b64(const char *key, const char **parts) { 28 | uint8_t binkey[64]; 29 | size_t klen = 0; 30 | 31 | if (mbedtls_base64_decode(binkey, sizeof(binkey), &klen, (const unsigned char *)key, 32 | strlen(key))) { 33 | DMESG("invalid sign key"); 34 | return NULL; 35 | } 36 | 37 | mbedtls_md_context_t md_ctx; 38 | mbedtls_md_init(&md_ctx); 39 | 40 | const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); 41 | CHK(mbedtls_md_setup(&md_ctx, md_info, 1)); 42 | 43 | if (mbedtls_md_hmac_starts(&md_ctx, binkey, klen) != 0) { 44 | DMESG("invalid key len?"); 45 | mbedtls_md_free(&md_ctx); 46 | return NULL; 47 | } 48 | 49 | for (int i = 0; parts[i]; ++i) { 50 | CHK(mbedtls_md_hmac_update(&md_ctx, (const unsigned char *)parts[i], strlen(parts[i]))); 51 | } 52 | 53 | CHK(mbedtls_md_hmac_finish(&md_ctx, binkey)); 54 | mbedtls_md_free(&md_ctx); 55 | 56 | mbedtls_base64_encode(NULL, 0, &klen, binkey, 32); 57 | char *r = jd_alloc(klen + 1); 58 | CHK(mbedtls_base64_encode((unsigned char *)r, klen + 1, &klen, binkey, 32)); 59 | r[klen] = 0; 60 | return r; 61 | } 62 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: C/C++ CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | tags: 7 | - "v[0-9]*.*.*" 8 | pull_request: 9 | branches: [main] 10 | 11 | jobs: 12 | build: 13 | 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout repo 18 | uses: actions/checkout@v2 19 | with: 20 | submodules: recursive 21 | 22 | - uses: actions/setup-node@v3 23 | with: 24 | node-version: 20 25 | 26 | - run: echo 'TARGET=esp32s2' > Makefile.user 27 | - run: make prep 28 | - name: esp-idf build 29 | uses: espressif/esp-idf-ci-action@main 30 | with: 31 | esp_idf_version: v5.0.5 32 | target: esp32s2 33 | command: make ci-build 34 | - run: make ci-patch 35 | 36 | - run: echo 'TARGET=esp32s3' > Makefile.user 37 | - run: make prep 38 | - name: esp-idf build 39 | uses: espressif/esp-idf-ci-action@main 40 | with: 41 | esp_idf_version: v5.0.5 42 | target: esp32s3 43 | command: make ci-build 44 | - run: make ci-patch 45 | 46 | - run: echo 'TARGET=esp32c3' > Makefile.user 47 | - run: make prep 48 | - name: esp-idf build 49 | uses: espressif/esp-idf-ci-action@main 50 | with: 51 | esp_idf_version: v5.0.5 52 | target: esp32c3 53 | command: make ci-build 54 | - run: make ci-patch 55 | 56 | - run: echo 'TARGET=esp32' > Makefile.user 57 | - run: make prep 58 | - name: esp-idf build 59 | uses: espressif/esp-idf-ci-action@main 60 | with: 61 | esp_idf_version: v5.0.5 62 | target: esp32 63 | command: make ci-build 64 | - run: make ci-patch 65 | 66 | - run: ls -l dist 67 | 68 | - name: Release 69 | uses: softprops/action-gh-release@v1 70 | if: startsWith(github.ref, 'refs/tags/') 71 | with: 72 | body_path: dist/info.md 73 | files: dist/* 74 | 75 | - name: Save artifacts 76 | uses: actions/upload-artifact@v3 77 | with: 78 | name: dist-files 79 | path: dist -------------------------------------------------------------------------------- /scripts/parsestack.js: -------------------------------------------------------------------------------- 1 | let fs = require("fs") 2 | let child_process = require("child_process") 3 | 4 | const buildPath = "build/" 5 | 6 | const js = JSON.parse(fs.readFileSync(buildPath + "compile_commands.json", "utf-8")) 7 | const gdb = js[0].command.replace(/ .*/, "").replace(/-gcc$/, "-gdb") 8 | const elfpath = buildPath + "espjd.elf" 9 | const pref = js[0].directory.replace(/[^\/]*\/build/, "") 10 | 11 | const args = process.argv.slice(2) 12 | let stack = "" 13 | if (args[0]) { 14 | stack = fs.readFileSync(args[0], "utf-8") 15 | } else { 16 | stack = child_process.execSync("pbpaste", { 17 | encoding: "utf8" 18 | }) 19 | } 20 | 21 | 22 | const addr = {} 23 | const addrlist = [] 24 | let addrgdb = "" 25 | 26 | function iterLines(final) { 27 | for (let line of stack.split(/\n/)) { 28 | if (final) console.log(line) 29 | line.replace(/(0x|^|\s)([a-f0-9]{8})([\s,.:]|$)/mig, (_, _p, w) => { 30 | let k = parseInt(w, 16) 31 | if (final && addr[k + ""] != "x") { 32 | console.log(" \x1b[33m0x" + k.toString(16) + ": " + addr[k + ""] + "\x1b[0m") 33 | } 34 | if (!addr[k + ""]) { 35 | addr[k + ""] = "x" 36 | addrlist.push(k) 37 | addrgdb += `info line *${k}\n` 38 | } 39 | }) 40 | } 41 | } 42 | iterLines() 43 | fs.writeFileSync("build/addr.gdb", addrgdb, "utf-8") 44 | const res = child_process.spawnSync(gdb, [elfpath, 45 | "--quiet", "--batch", "--command=build/addr.gdb" 46 | ], { encoding: "utf-8" }) 47 | 48 | const lines = res.stdout.split(/\n/) 49 | for (let i = 0; i < addrlist.length; ++i) { 50 | if (!lines[i]) { 51 | console.log("missing?", res.stderr, res.stdout) 52 | break 53 | } 54 | const k = addrlist[i] + "" 55 | let m = /No line number information available for address 0x[a-f0-9]+ (<.*>)/.exec(lines[i]) 56 | if (m) { 57 | addr[k] = m[1] 58 | continue 59 | } 60 | 61 | if (lines[i].startsWith('No line number information available')) 62 | continue 63 | 64 | m = /Line (\d+) of "([^"]*)"/.exec(lines[i]) 65 | if (m) { 66 | addr[k] = m[2].replace(pref, "") + ":" + m[1] 67 | continue 68 | } 69 | 70 | console.log("WHOOPS", k, lines[i]) 71 | } 72 | iterLines(1) 73 | 74 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ main ] 20 | schedule: 21 | - cron: '23 2 * * 5' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'cpp' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | # Initializes the CodeQL tools for scanning. 45 | - name: Initialize CodeQL 46 | uses: github/codeql-action/init@v1 47 | with: 48 | languages: ${{ matrix.language }} 49 | # If you wish to specify custom queries, you can do so here or in a config file. 50 | # By default, queries listed here will override any specified in a config file. 51 | # Prefix the list here with "+" to use these queries and those in the config file. 52 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 53 | 54 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 55 | # If this step fails, then you should remove it and run the build manually (see below) 56 | - name: Autobuild 57 | uses: github/codeql-action/autobuild@v1 58 | 59 | # ℹ️ Command-line programs to run using the OS shell. 60 | # 📚 https://git.io/JvXDl 61 | 62 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 63 | # and modify them (or add more) to build your code if your project 64 | # uses a compiled language 65 | 66 | #- run: | 67 | # make bootstrap 68 | # make release 69 | 70 | - name: Perform CodeQL Analysis 71 | uses: github/codeql-action/analyze@v1 72 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). 40 | 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DeviceScript Runtime for ESP32 2 | 3 | This repo contains implementation of DeviceScript virtual machine and runtime for ESP32. 4 | See https://microsoft.github.io/devicescript/devices/esp32 . 5 | 6 | ## Building 7 | 8 | Install ESP-IDF. Make sure `IDF_PATH` is set. 9 | You can also install `ccache` to speed up builds. 10 | You will need to run `export.sh` from the IDF folder - the Makefile will remind you. 11 | 12 | To build run `make`. 13 | 14 | To deploy run `make r`. 15 | 16 | ## TODO 17 | 18 | * [ ] test flash jacs storage 19 | * [ ] test sensor watchdog 20 | * [ ] test cloud watchdog 21 | * [ ] add Jacs restart/crash blinks 22 | * [ ] add Jacs user blink 23 | * [ ] some blink for no Jacs program? 24 | * [ ] linker-override panic_restart() - do some blinking in there 25 | * [ ] synchronize timeseries ends to limit number of uploads 26 | * [ ] save current time in some RTC register so it survives reset (for stored cloud uploads) 27 | * [ ] 'failed' response for HF2 send 28 | 29 | * [x] report Wi-Fi RSSI from tsagg 30 | * [x] deal with Discrete from motion sensor 31 | * [ ] user-accessible watchdog in Jacscript 32 | * [x] restart on infinite loop (hw-watchdog) 33 | * [x] impl. watchdogs in tsagg + azureiot 34 | * [x] blink on upload 35 | 36 | * [x] save program in flash 37 | * [ ] multiple Wi-Fi networks saved 38 | 39 | * [x] add HF2 over USB Serial (CDC) 40 | * [x] set "CLIENT" flag in announce 41 | 42 | * [ ] disable self-reports coming from the wire 43 | * [ ] don't forward `restricted` packets to the wire from USB or loopback 44 | * [ ] only accept `restricted` packets from USB or loopback 45 | * [ ] add `restricted` flag in frame flags 46 | 47 | * [x] re-enable wifi service - think about auto-connect? 48 | * [x] implement Azure IoT Hub connection and IoT Hub health service 49 | * [x] implement Jacscript Cloud service using IoT Hub 50 | * [x] add auto-upload function in Jacscript and precompile for common modules 51 | * [x] implement reset_in as hw-watchdog 52 | 53 | ## Contributing 54 | 55 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 56 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 57 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 58 | 59 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 60 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 61 | provided by the bot. You will only need to do this once across all repos using our CLA. 62 | 63 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 64 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 65 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 66 | -------------------------------------------------------------------------------- /scripts/filter-bundle.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const tls = require('tls'); 3 | const net = require('net'); 4 | const https = require('https'); 5 | 6 | const msftTrusted = {} 7 | 8 | // see https://docs.microsoft.com/en-us/security/trusted-root/participants-list 9 | https.get('https://ccadb.my.salesforce-sites.com/microsoft/IncludedCACertificateReportForMSFTCSV', (resp) => { 10 | let data = ''; 11 | resp.on('data', (chunk) => { data += chunk; }); 12 | resp.on('end', () => { 13 | const fields=["Microsoft Status","CA Owner","CA Common Name or Certificate Name","SHA-1 Fingerprint","SHA-256 Fingerprint","Microsoft EKUs","Valid From [GMT]","Valid To [GMT]","Public Key Algorithm","Signature Hash Algorithm"] 14 | for (const ln of data.split(/\n/)) { 15 | const words = JSON.parse("[" + ln + "]") 16 | if (/Server Authentication/i.test(words[5]) && /Included/.test(words[0])) { 17 | msftTrusted[words[4]] = 1 18 | } 19 | } 20 | 21 | // These keys share public key, but for whatever reason were re-issued and 22 | // Mozilla and Microsoft have different versions. 23 | // 24 | // Use https://crt.sh/?q=1A0D20445DE5BA1862D19EF880858CBCE50102B36E8F0A040C3C69E74522FE6E etc 25 | // to see the certs. 26 | 27 | // COMODO Certification Authority 28 | if (msftTrusted["1A0D20445DE5BA1862D19EF880858CBCE50102B36E8F0A040C3C69E74522FE6E"]) 29 | msftTrusted["0C2CD63DF7806FA399EDE809116B575BF87989F06518F9808C860503178BAF66"] = 1 30 | 31 | // Network Solutions Certificate Authority 32 | if (msftTrusted["001686CD181F83A1B1217D305B365C41E3470A78A1D37B134A98CD547B92DAB3"]) 33 | msftTrusted["15F0BA00A3AC7AF3AC884C072B1011A077BD77C097F40164B2F8598ABD83860C"] = 1 34 | 35 | main() 36 | }); 37 | }) 38 | 39 | function certInfo(pem) { 40 | const secureContext = tls.createSecureContext({ 41 | cert: pem 42 | }); 43 | 44 | const secureSocket = new tls.TLSSocket(new net.Socket(), { secureContext }); 45 | 46 | return secureSocket.getCertificate(); 47 | } 48 | 49 | function subj(inf) { 50 | const keys = Object.keys(inf) 51 | keys.reverse() 52 | return keys.map(k => inf[k]).join(", ") 53 | } 54 | 55 | function main() { 56 | 57 | let outp = "" 58 | let cert = "" 59 | for (const ln of fs.readFileSync(process.argv[2], "utf8").split(/\n/)) { 60 | if (ln.startsWith("#")) 61 | outp += ln + "\n" 62 | else if (ln == "-----BEGIN CERTIFICATE-----") { 63 | cert = ln + "\n" 64 | } else if (ln == "-----END CERTIFICATE-----") { 65 | cert += ln + "\n" 66 | const info = certInfo(cert) 67 | const sha = info.fingerprint256.replace(/:/g, "") 68 | if (msftTrusted[sha]) { 69 | outp += "# " + subj(info.subject) + "\n" 70 | outp += "# " + info.valid_to + "\n" 71 | outp += "# " + sha + "\n" 72 | outp += cert + "\n" 73 | } else { 74 | console.log("skipping", sha, subj(info.subject), " (not in MSFT list)") 75 | } 76 | cert = "" 77 | } else if (cert) { 78 | cert += ln + "\n" 79 | } 80 | } 81 | 82 | fs.writeFileSync(process.argv[3], outp) 83 | console.log("written", process.argv[3]) 84 | } 85 | -------------------------------------------------------------------------------- /main/i2c.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "jd_drivers.h" 3 | #include "driver/i2c.h" 4 | #include "hal/i2c_hal.h" 5 | 6 | static int i2c_inst_num; 7 | 8 | static uint8_t i2c_ok; 9 | 10 | int i2c_init_(void) { 11 | uint8_t sda, scl; 12 | 13 | if (i2c_ok) 14 | return 0; 15 | 16 | sda = dcfg_get_pin("i2c.pinSDA"); 17 | scl = dcfg_get_pin("i2c.pinSCL"); 18 | 19 | if (sda == scl) { 20 | DMESG("no I2C"); 21 | return -1; 22 | } 23 | 24 | int khz = dcfg_get_i32("i2c.kHz", 100); 25 | 26 | i2c_inst_num = dcfg_get_i32("i2c.inst", 0); 27 | 28 | i2c_config_t conf = { 29 | .mode = I2C_MODE_MASTER, 30 | .sda_io_num = sda, 31 | .scl_io_num = scl, 32 | .sda_pullup_en = GPIO_PULLUP_ENABLE, 33 | .scl_pullup_en = GPIO_PULLUP_ENABLE, 34 | .master.clk_speed = khz * 1000, 35 | .clk_flags = 0, 36 | }; 37 | 38 | JD_CHK(i2c_param_config(i2c_inst_num, &conf)); 39 | JD_CHK(i2c_set_timeout(i2c_inst_num, I2C_LL_MAX_TIMEOUT - 1)); 40 | JD_CHK(i2c_driver_install(i2c_inst_num, conf.mode, 0, 0, 0)); 41 | 42 | i2c_ok = 1; 43 | 44 | DMESG("i2c OK: sda=%d scl=%d %dkHz", sda, scl, khz); 45 | 46 | return 0; 47 | } 48 | 49 | #define I2C_TRANS_BUF_MINIMUM_SIZE 200 50 | 51 | int i2c_read_ex(uint8_t device_address, void *dst, unsigned len) { 52 | if (!i2c_ok) 53 | return -108; 54 | 55 | esp_err_t err = ESP_OK; 56 | uint8_t buffer[I2C_TRANS_BUF_MINIMUM_SIZE] = {0}; 57 | 58 | i2c_cmd_handle_t handle = i2c_cmd_link_create_static(buffer, sizeof(buffer)); 59 | JD_ASSERT(handle != NULL); 60 | 61 | err = i2c_master_start(handle); 62 | if (err != ESP_OK) 63 | goto end; 64 | 65 | err = i2c_master_write_byte(handle, device_address << 1 | I2C_MASTER_READ, true); 66 | if (err != ESP_OK) 67 | goto end; 68 | 69 | err = i2c_master_read(handle, dst, len, I2C_MASTER_LAST_NACK); 70 | if (err != ESP_OK) 71 | goto end; 72 | 73 | i2c_master_stop(handle); 74 | 75 | err = i2c_master_cmd_begin(i2c_inst_num, handle, 2); 76 | 77 | end: 78 | i2c_cmd_link_delete_static(handle); 79 | return err; 80 | } 81 | 82 | int i2c_write_ex2(uint8_t device_address, const void *src, unsigned len, const void *src2, 83 | unsigned len2, bool repeated) { 84 | if (!i2c_ok) 85 | return -108; 86 | 87 | esp_err_t err = ESP_OK; 88 | uint8_t buffer[I2C_TRANS_BUF_MINIMUM_SIZE] = {0}; 89 | 90 | i2c_cmd_handle_t handle = i2c_cmd_link_create_static(buffer, sizeof(buffer)); 91 | JD_ASSERT(handle != NULL); 92 | 93 | err = i2c_master_start(handle); 94 | if (err != ESP_OK) 95 | goto end; 96 | 97 | err = i2c_master_write_byte(handle, device_address << 1 | I2C_MASTER_WRITE, true); 98 | if (err != ESP_OK) 99 | goto end; 100 | 101 | err = i2c_master_write(handle, src, len, true); 102 | if (err != ESP_OK) 103 | goto end; 104 | 105 | if (len2 != 0) { 106 | err = i2c_master_write(handle, src2, len2, true); 107 | if (err != ESP_OK) 108 | goto end; 109 | } 110 | 111 | if (!repeated) 112 | i2c_master_stop(handle); 113 | 114 | err = i2c_master_cmd_begin(i2c_inst_num, handle, 2); 115 | 116 | end: 117 | i2c_cmd_link_delete_static(handle); 118 | return err; 119 | } 120 | -------------------------------------------------------------------------------- /main/adc.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "esp_adc/adc_oneshot.h" 3 | #include "hal/adc_hal.h" 4 | 5 | #define LOG_TAG "adc" 6 | #include "devs_logging.h" 7 | 8 | #define ADC_ATTEN ADC_ATTEN_DB_11 9 | #define ADC_BITS SOC_ADC_RTC_MAX_BITWIDTH 10 | 11 | #if CONFIG_IDF_TARGET_ESP32 12 | #define CH_OFFSET 32 13 | static uint8_t channels[] = { 14 | ADC1_GPIO32_CHANNEL, ADC1_GPIO33_CHANNEL, ADC1_GPIO34_CHANNEL, ADC1_GPIO35_CHANNEL, 15 | ADC1_GPIO36_CHANNEL, ADC1_GPIO37_CHANNEL, ADC1_GPIO38_CHANNEL, ADC1_GPIO39_CHANNEL, 16 | }; 17 | 18 | #elif CONFIG_IDF_TARGET_ESP32S2 19 | static uint8_t channels[] = { 20 | 0xff, 21 | ADC1_GPIO1_CHANNEL, 22 | ADC1_GPIO2_CHANNEL, 23 | ADC1_GPIO3_CHANNEL, 24 | ADC1_GPIO4_CHANNEL, 25 | ADC1_GPIO5_CHANNEL, 26 | ADC1_GPIO6_CHANNEL, 27 | ADC1_GPIO7_CHANNEL, 28 | ADC1_GPIO8_CHANNEL, 29 | ADC1_GPIO9_CHANNEL, 30 | ADC1_GPIO10_CHANNEL, 31 | }; 32 | 33 | #elif CONFIG_IDF_TARGET_ESP32C3 34 | static uint8_t channels[] = { 35 | ADC1_GPIO0_CHANNEL, ADC1_GPIO1_CHANNEL, ADC1_GPIO2_CHANNEL, 36 | ADC1_GPIO3_CHANNEL, ADC1_GPIO4_CHANNEL, 37 | }; 38 | 39 | #elif CONFIG_IDF_TARGET_ESP32S3 40 | static uint8_t channels[] = { 41 | 0xff, 42 | ADC1_GPIO1_CHANNEL, 43 | ADC1_GPIO2_CHANNEL, 44 | ADC1_GPIO3_CHANNEL, 45 | ADC1_GPIO4_CHANNEL, 46 | ADC1_GPIO5_CHANNEL, 47 | ADC1_GPIO6_CHANNEL, 48 | ADC1_GPIO7_CHANNEL, 49 | ADC1_GPIO8_CHANNEL, 50 | ADC1_GPIO9_CHANNEL, 51 | ADC1_GPIO10_CHANNEL, 52 | }; 53 | 54 | #else 55 | #error "unknown ESP32" 56 | #endif 57 | 58 | static uint32_t inited_channels; 59 | static adc_oneshot_unit_handle_t adc1_handle; 60 | 61 | static int adc_ch(uint8_t pin) { 62 | #ifdef CH_OFFSET 63 | if (pin < CH_OFFSET) 64 | return -1; 65 | pin -= CH_OFFSET; 66 | #endif 67 | if (pin < sizeof(channels) && channels[pin] != 0xff) 68 | return channels[pin]; 69 | return -1; 70 | } 71 | 72 | static void adc_init(void) { 73 | if (adc1_handle) 74 | return; 75 | 76 | adc_oneshot_unit_init_cfg_t init_config1 = { 77 | .unit_id = ADC_UNIT_1, 78 | .ulp_mode = ADC_ULP_MODE_DISABLE, 79 | }; 80 | CHK(adc_oneshot_new_unit(&init_config1, &adc1_handle)); 81 | } 82 | 83 | bool adc_can_read_pin(uint8_t pin) { 84 | return adc_ch(pin) != -1; 85 | } 86 | 87 | uint16_t adc_read_pin(uint8_t pin) { 88 | int ch = adc_ch(pin); 89 | if (ch < 0) 90 | return 0; 91 | if (!(inited_channels & (1 << ch))) { 92 | adc_init(); 93 | inited_channels |= 1 << ch; 94 | adc_oneshot_chan_cfg_t config = { 95 | .bitwidth = ADC_BITS, 96 | .atten = ADC_ATTEN, 97 | }; 98 | CHK(adc_oneshot_config_channel(adc1_handle, ch, &config)); 99 | } 100 | 101 | int res; 102 | CHK(adc_oneshot_read(adc1_handle, ch, &res)); 103 | return res << (16 - ADC_BITS); 104 | } 105 | 106 | #if JD_CONFIG_TEMPERATURE 107 | #include "driver/temperature_sensor.h" 108 | int32_t adc_read_temp(void) { 109 | static temperature_sensor_handle_t temp_handle; 110 | if (!temp_handle) { 111 | temperature_sensor_config_t temp_sensor = TEMPERATURE_SENSOR_CONFIG_DEFAULT(20, 100); 112 | CHK(temperature_sensor_install(&temp_sensor, &temp_handle)); 113 | } 114 | 115 | CHK(temperature_sensor_enable(temp_handle)); 116 | float tsens_out; 117 | CHK(temperature_sensor_get_celsius(temp_handle, &tsens_out)); 118 | CHK(temperature_sensor_disable(temp_handle)); 119 | 120 | return (int)tsens_out; 121 | } 122 | #endif -------------------------------------------------------------------------------- /main/platform.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "esp_timer.h" 4 | #include "esp_event.h" 5 | #include "spi_flash_mmap.h" 6 | #include "esp_private/system_internal.h" 7 | #include "esp_sleep.h" 8 | #include "esp_random.h" 9 | #include "esp_mac.h" 10 | 11 | uint64_t hw_device_id(void) { 12 | static uint64_t addr; 13 | if (!addr) { 14 | uint8_t mac[6]; 15 | esp_efuse_mac_get_default(mac); 16 | addr = ((uint64_t)0xff << 56) | ((uint64_t)mac[5] << 48) | ((uint64_t)mac[4] << 40) | 17 | ((uint64_t)mac[3] << 32) | ((uint64_t)mac[2] << 24) | ((uint64_t)mac[1] << 16) | 18 | ((uint64_t)mac[0] << 8) | ((uint64_t)0xfe << 0); 19 | } 20 | return addr; 21 | } 22 | 23 | void jd_alloc_stack_check(void) {} 24 | 25 | void jd_alloc_init(void) {} 26 | 27 | void log_free_mem(void) { 28 | DMESG("free memory: %u bytes (max block: %u bytes)", 29 | (unsigned)heap_caps_get_free_size(MALLOC_CAP_8BIT), 30 | (unsigned)heap_caps_get_largest_free_block(MALLOC_CAP_8BIT)); 31 | } 32 | 33 | void *jd_alloc(uint32_t size) { 34 | void *r = calloc(size, 1); 35 | if (r == NULL) { 36 | DMESG("OOM! %u bytes", (unsigned)size); 37 | log_free_mem(); 38 | ESP_LOGE("JD", "OOM %u bytes\n", (unsigned)size); 39 | JD_PANIC(); 40 | } 41 | return r; 42 | } 43 | 44 | void jd_free(void *ptr) { 45 | free(ptr); 46 | } 47 | 48 | void *jd_alloc_emergency_area(uint32_t size) { 49 | return calloc(size, 1); 50 | } 51 | 52 | void target_reset(void) { 53 | // ESP_LOGE("JD", "target_reset()\n"); 54 | // reset through deep sleep to make sure the C3 USB is disconnected 55 | esp_sleep_enable_timer_wakeup(20000); 56 | esp_deep_sleep_start(); 57 | // just in case... 58 | esp_restart_noos_dig(); 59 | } 60 | 61 | void target_standby(uint32_t duration_ms) { 62 | esp_sleep_enable_timer_wakeup(duration_ms * 1000LL); 63 | esp_deep_sleep_start(); 64 | } 65 | 66 | IRAM_ATTR void target_wait_us(uint32_t us) { 67 | int64_t later = esp_timer_get_time() + us; 68 | while (esp_timer_get_time() < later) { 69 | ; 70 | } 71 | } 72 | 73 | static portMUX_TYPE global_int_mux = portMUX_INITIALIZER_UNLOCKED; 74 | int int_level; 75 | 76 | IRAM_ATTR void target_disable_irq(void) { 77 | portENTER_CRITICAL_ISR(&global_int_mux); 78 | int_level++; 79 | } 80 | 81 | IRAM_ATTR void target_enable_irq(void) { 82 | int_level--; 83 | portEXIT_CRITICAL_ISR(&global_int_mux); 84 | } 85 | 86 | void hw_panic(void) { 87 | DMESG("HW PANIC!"); 88 | abort(); 89 | } 90 | 91 | void reboot_to_uf2(void) { 92 | ESP_LOGE("JD", "reset to UF2\n"); 93 | 94 | #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) 95 | // call esp_reset_reason() is required for idf.py to properly links esp_reset_reason_set_hint() 96 | (void)esp_reset_reason(); 97 | esp_reset_reason_set_hint((esp_reset_reason_t)0x11F2); 98 | #endif 99 | 100 | esp_restart_noos_dig(); 101 | } 102 | 103 | void jd_crypto_get_random(uint8_t *buf, unsigned size) { 104 | esp_fill_random(buf, size); 105 | } 106 | 107 | extern const char app_fw_version[]; 108 | extern const char app_dev_class_name[]; 109 | 110 | const char *app_get_fw_version(void) { 111 | return app_fw_version; 112 | } 113 | 114 | const void *dcfg_base_addr(void) { 115 | static const void *result = NULL; 116 | if (!result) { 117 | spi_flash_mmap_handle_t map; 118 | CHK(spi_flash_mmap(0, 0x10000, SPI_FLASH_MMAP_DATA, &result, &map)); 119 | result = (const uint8_t *)result + 0x9000; 120 | } 121 | return result; 122 | } 123 | -------------------------------------------------------------------------------- /main/ledstrip.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "devs_internal.h" 3 | #include "led_strip_encoder.h" 4 | #include "driver/rmt_tx.h" 5 | 6 | #define RMT_LED_STRIP_RESOLUTION_HZ \ 7 | 10000000 // 10MHz resolution, 1 tick = 0.1us (led strip needs a high resolution) 8 | 9 | static void setup_strip(uint8_t pin, int mem_block_symbols, rmt_channel_handle_t *led_chan, 10 | rmt_encoder_handle_t *led_encoder) { 11 | rmt_tx_channel_config_t tx_chan_config = { 12 | .clk_src = RMT_CLK_SRC_DEFAULT, // select source clock 13 | .gpio_num = pin, 14 | .mem_block_symbols = mem_block_symbols, 15 | .resolution_hz = RMT_LED_STRIP_RESOLUTION_HZ, 16 | .trans_queue_depth = 17 | 4, // set the number of transactions that can be pending in the background 18 | }; 19 | ESP_ERROR_CHECK(rmt_new_tx_channel(&tx_chan_config, led_chan)); 20 | 21 | if (!*led_encoder) { 22 | led_strip_encoder_config_t encoder_config = { 23 | .resolution = RMT_LED_STRIP_RESOLUTION_HZ, 24 | }; 25 | ESP_ERROR_CHECK(rmt_new_led_strip_encoder(&encoder_config, led_encoder)); 26 | } 27 | 28 | ESP_ERROR_CHECK(rmt_enable(*led_chan)); 29 | } 30 | 31 | static void transmit(rmt_channel_handle_t led_chan, rmt_encoder_handle_t led_encoder, 32 | const uint8_t *data, unsigned size) { 33 | rmt_transmit_config_t tx_config = { 34 | .loop_count = 0, // no transfer loop 35 | }; 36 | ESP_ERROR_CHECK(rmt_transmit(led_chan, led_encoder, data, size, &tx_config)); 37 | } 38 | 39 | void jd_rgbext_link(void) {} 40 | 41 | static rmt_channel_handle_t rgbext_chan; 42 | static rmt_encoder_handle_t rgbext_encoder; 43 | static uint8_t rgbext_buf[3]; 44 | 45 | void jd_rgbext_init(int type, uint8_t pin) { 46 | if (rgbext_chan) 47 | return; 48 | if (type == 1) { 49 | setup_strip(pin, SOC_RMT_MEM_WORDS_PER_CHANNEL, &rgbext_chan, &rgbext_encoder); 50 | } 51 | } 52 | 53 | void jd_rgbext_set(uint8_t r, uint8_t g, uint8_t b) { 54 | if (rgbext_chan) { 55 | rgbext_buf[0] = r; 56 | rgbext_buf[1] = g; 57 | rgbext_buf[2] = b; 58 | transmit(rgbext_chan, rgbext_encoder, rgbext_buf, sizeof(rgbext_buf)); 59 | } 60 | } 61 | 62 | static rmt_channel_handle_t led_chan; 63 | static rmt_encoder_handle_t led_encoder; 64 | static uint8_t led_pin, led_in_use; 65 | static cb_t led_done_fn; 66 | static uint32_t last_ctx_no; 67 | 68 | static void led_strip_done_outside_isr(void *userdata) { 69 | rmt_disable(led_chan); 70 | led_in_use = false; 71 | led_done_fn(); 72 | } 73 | 74 | static bool led_strip_done(rmt_channel_handle_t tx_chan, const rmt_tx_done_event_data_t *edata, 75 | void *user_ctx) { 76 | JD_ASSERT(led_in_use); 77 | tim_worker_run(led_strip_done_outside_isr, NULL); 78 | return false; 79 | } 80 | 81 | int devs_led_strip_send(devs_ctx_t *ctx, uint8_t pin, const uint8_t *data, unsigned size, 82 | cb_t donefn) { 83 | if (led_in_use) 84 | return -100; 85 | 86 | if (!led_chan || led_pin != pin || last_ctx_no != ctx->ctx_seq_no) { 87 | led_pin = pin; 88 | last_ctx_no = ctx->ctx_seq_no; 89 | if (led_chan) { 90 | // free-up any previously allocated channel 91 | CHK(rmt_del_channel(led_chan)); 92 | } 93 | setup_strip(pin, SOC_RMT_MEM_WORDS_PER_CHANNEL, &led_chan, &led_encoder); 94 | rmt_tx_event_callbacks_t cbs = {.on_trans_done = led_strip_done}; 95 | CHK(rmt_tx_register_event_callbacks(led_chan, &cbs, NULL)); 96 | } else { 97 | CHK(rmt_enable(led_chan)); 98 | } 99 | 100 | led_in_use = true; 101 | led_done_fn = donefn; 102 | transmit(led_chan, led_encoder, data, size); 103 | 104 | return 0; 105 | } -------------------------------------------------------------------------------- /main/spi.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "jd_drivers.h" 3 | #include "driver/spi_master.h" 4 | #include "hal/spi_hal.h" 5 | #include "services/interfaces/jd_spi.h" 6 | 7 | #define BLOCK_SIZE 4096 8 | 9 | static int mappin(uint8_t pin) { 10 | if (pin == NO_PIN) 11 | return -1; 12 | return pin; 13 | } 14 | 15 | #define MY_SPI_HOST SPI2_HOST // seems OK on C3, S2 and ESP32 16 | 17 | static spi_device_handle_t spi; 18 | static spi_transaction_t trans; 19 | static bool spi_in_use; 20 | 21 | static void jd_spi_done_cb_outside_isr(spi_transaction_t *transp) { 22 | JD_ASSERT(spi_in_use); 23 | spi_transaction_t *rtrans; 24 | int ret = spi_device_get_trans_result(spi, &rtrans, 0); 25 | JD_ASSERT(ret == 0); 26 | JD_ASSERT(rtrans == transp); 27 | cb_t cb = transp->user; 28 | spi_in_use = false; 29 | cb(); 30 | } 31 | 32 | static void jd_spi_done_cb(spi_transaction_t *transp) { 33 | JD_ASSERT(transp == &trans); 34 | if (transp->user == NULL) 35 | return; // polling transaction 36 | JD_ASSERT(spi_in_use); 37 | tim_worker_run((TaskFunction_t)jd_spi_done_cb_outside_isr, transp); 38 | } 39 | 40 | int jd_spi_init(const jd_spi_cfg_t *cfg) { 41 | if (spi_in_use) 42 | return -100; 43 | 44 | if (spi) { 45 | spi_bus_remove_device(spi); 46 | spi = NULL; 47 | spi_bus_free(MY_SPI_HOST); 48 | } 49 | 50 | spi_bus_config_t buscfg = { 51 | .miso_io_num = mappin(cfg->miso), 52 | .mosi_io_num = mappin(cfg->mosi), 53 | .sclk_io_num = mappin(cfg->sck), 54 | .quadwp_io_num = -1, 55 | .quadhd_io_num = -1, 56 | .max_transfer_sz = BLOCK_SIZE, 57 | }; 58 | 59 | DMESG("SPI init: miso=%d mosi=%d sck=%d hz=%u", buscfg.miso_io_num, buscfg.mosi_io_num, 60 | buscfg.sclk_io_num, (unsigned)cfg->hz); 61 | 62 | spi_device_interface_config_t devcfg = { 63 | .clock_speed_hz = cfg->hz, 64 | .mode = cfg->mode, 65 | .spics_io_num = -1, 66 | .queue_size = 2, 67 | .post_cb = jd_spi_done_cb, 68 | }; 69 | 70 | int r; 71 | 72 | r = spi_bus_initialize(MY_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO); 73 | if (r) 74 | return r; 75 | 76 | r = spi_bus_add_device(MY_SPI_HOST, &devcfg, &spi); 77 | if (r) { 78 | spi = NULL; 79 | spi_bus_free(MY_SPI_HOST); 80 | return r; 81 | } 82 | 83 | return 0; 84 | } 85 | 86 | bool jd_spi_is_ready(void) { 87 | return spi != NULL && !spi_in_use; 88 | } 89 | 90 | unsigned jd_spi_max_block_size(void) { 91 | return BLOCK_SIZE; 92 | } 93 | 94 | static void call_fn(void *f) { 95 | cb_t ff = f; 96 | if (ff) 97 | ff(); 98 | } 99 | 100 | int jd_spi_xfer(const void *txdata, void *rxdata, unsigned numbytes, cb_t done_fn) { 101 | // JD_ASSERT(!target_in_irq()); this can in fact be invoked from done_fn()... 102 | JD_ASSERT(done_fn != NULL); 103 | 104 | if (!jd_spi_is_ready()) 105 | return -1; 106 | 107 | if (numbytes > BLOCK_SIZE) 108 | return -2; 109 | 110 | if (numbytes == 0) 111 | goto sync_ok; 112 | 113 | JD_ASSERT(txdata || rxdata); 114 | 115 | memset(&trans, 0, sizeof(trans)); 116 | trans.length = 8 * numbytes; 117 | trans.tx_buffer = txdata; 118 | trans.rx_buffer = rxdata; 119 | trans.user = done_fn; 120 | 121 | int ret; 122 | 123 | if (numbytes <= 4) { 124 | trans.user = NULL; 125 | ret = spi_device_polling_transmit(spi, &trans); 126 | if (ret == 0) 127 | goto sync_ok; 128 | return ret; 129 | } else { 130 | spi_in_use = true; 131 | ret = spi_device_queue_trans(spi, &trans, 0); 132 | if (ret != 0) 133 | spi_in_use = false; 134 | return ret; 135 | } 136 | 137 | sync_ok: 138 | tim_worker_run(call_fn, done_fn); 139 | return 0; 140 | } 141 | -------------------------------------------------------------------------------- /main/dmesg.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #if JD_DMESG_BUFFER_SIZE > 0 4 | 5 | extern int int_level; 6 | static uint8_t panic_mode_uart; 7 | void panic_print_char(const char c); 8 | void panic_print_str(const char *str); 9 | void panic_print_dec(int d); 10 | 11 | void __real_uart_hal_write_txfifo(void *hal, const uint8_t *buf, uint32_t data_size, 12 | uint32_t *write_size); 13 | void __wrap_uart_hal_write_txfifo(void *hal, const uint8_t *buf, uint32_t data_size, 14 | uint32_t *write_size) { 15 | __real_uart_hal_write_txfifo(hal, buf, data_size, write_size); 16 | if (data_size == 1 && panic_mode_uart) { 17 | jd_usb_panic_print_char(*buf); 18 | jd_lstore_panic_print_char(*buf); 19 | } 20 | } 21 | 22 | // void __real_panic_restart(void); 23 | void __wrap_panic_restart(void) { 24 | jd_lstore_panic_flush(); 25 | jd_usb_panic_print_char('\n'); 26 | target_reset(); // this make sure to reset USB connection state on ESP32-C3 27 | // __real_panic_restart(); 28 | } 29 | 30 | void panic_dump_dmesg(void) { 31 | panic_print_str(LOG_COLOR(LOG_COLOR_RED) "\r\nDMESG:\r\n"); 32 | for (unsigned i = 0; i < codalLogStore.ptr; ++i) { 33 | char c = codalLogStore.buffer[i]; 34 | if (c == '\n') 35 | panic_print_char('\r'); 36 | panic_print_char(c); 37 | } 38 | panic_print_str("END DMESG\r\nInt: "); 39 | panic_print_dec(int_level); 40 | panic_print_str("\r\n" LOG_RESET_COLOR); 41 | } 42 | 43 | void __real_esp_panic_handler(void *); 44 | void __wrap_esp_panic_handler(void *info) { 45 | panic_mode_uart = 1; 46 | jd_usb_panic_start(); 47 | panic_dump_dmesg(); 48 | __real_esp_panic_handler(info); 49 | } 50 | 51 | static void esp_error_check_failed_print(const char *msg, esp_err_t rc, const char *file, int line, 52 | const char *function, const char *expression, 53 | intptr_t addr) { 54 | DMESG("%s failed: esp_err_t %x (%s) at %p", msg, rc, esp_err_to_name(rc), 55 | (void *)esp_cpu_get_call_addr(addr)); 56 | DMESG("file: \"%s\" line %d func: %s expression: %s", file, line, function, expression); 57 | } 58 | 59 | void __wrap__esp_error_check_failed(esp_err_t rc, const char *file, int line, const char *function, 60 | const char *expression) { 61 | esp_error_check_failed_print("ESP_ERROR_CHECK", rc, file, line, function, expression, 62 | (intptr_t)__builtin_return_address(0)); 63 | abort(); 64 | } 65 | 66 | #endif 67 | 68 | #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) 69 | #define LOGGING_TX_PIN 43 70 | #elif defined(CONFIG_IDF_TARGET_ESP32C3) 71 | #define LOGGING_TX_PIN 21 72 | #endif 73 | 74 | #if defined(LOGGING_TX_PIN) 75 | #include "hal/uart_ll.h" 76 | 77 | static uart_dev_t *log_uart; 78 | void uart_log_write(const void *data0, unsigned size) { 79 | if (log_uart) { 80 | const uint8_t *data = data0; 81 | while (size > 0) { 82 | uint16_t fill_len = uart_ll_get_txfifo_len(log_uart); 83 | int n = fill_len; 84 | if (n > size) 85 | n = size; 86 | uart_ll_write_txfifo(log_uart, data, n); 87 | data += n; 88 | size -= n; 89 | vTaskDelay(1); 90 | } 91 | } 92 | } 93 | 94 | void uart_log_init(void) { 95 | int p = dcfg_get_pin("log.pinTX"); 96 | if (p == NO_PIN) { 97 | DMESG("log.pinTX not set"); 98 | } else { 99 | DMESG("log.pinTX at GPIO%d", p); 100 | if (p == LOGGING_TX_PIN) { 101 | log_uart = &UART0; 102 | } else { 103 | DMESG("! only GPIO%d supported for TX", LOGGING_TX_PIN); 104 | } 105 | } 106 | } 107 | 108 | void uart_log_dmesg(void) { 109 | static uint32_t dmesg_ptr; 110 | 111 | if (!log_uart) 112 | return; 113 | 114 | while (uart_ll_get_txfifo_len(log_uart) > 64) { 115 | uint8_t buf[64]; 116 | int n = jd_dmesg_read(buf, sizeof(buf), &dmesg_ptr); 117 | if (n > 0) { 118 | jd_usb_flush_stdout(); 119 | uart_log_write(buf, n); 120 | } else { 121 | break; 122 | } 123 | } 124 | } 125 | 126 | #else 127 | void uart_log_init(void) {} 128 | void uart_log_dmesg(void) {} 129 | void uart_log_write(const void *data0, unsigned size) {} 130 | #endif 131 | -------------------------------------------------------------------------------- /scripts/uf2families.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "0x16573617", 4 | "short_name": "ATMEGA32", 5 | "description": "Microchip (Atmel) ATmega32" 6 | }, 7 | { 8 | "id": "0x1851780a", 9 | "short_name": "SAML21", 10 | "description": "Microchip (Atmel) SAML21" 11 | }, 12 | { 13 | "id": "0x1b57745f", 14 | "short_name": "NRF52", 15 | "description": "Nordic NRF52" 16 | }, 17 | { 18 | "id": "0x1c5f21b0", 19 | "short_name": "ESP32", 20 | "description": "ESP32" 21 | }, 22 | { 23 | "id": "0x1e1f432d", 24 | "short_name": "STM32L1", 25 | "description": "ST STM32L1xx" 26 | }, 27 | { 28 | "id": "0x202e3a91", 29 | "short_name": "STM32L0", 30 | "description": "ST STM32L0xx" 31 | }, 32 | { 33 | "id": "0x21460ff0", 34 | "short_name": "STM32WL", 35 | "description": "ST STM32WLxx" 36 | }, 37 | { 38 | "id": "0x2abc77ec", 39 | "short_name": "LPC55", 40 | "description": "NXP LPC55xx" 41 | }, 42 | { 43 | "id": "0x300f5633", 44 | "short_name": "STM32G0", 45 | "description": "ST STM32G0xx" 46 | }, 47 | { 48 | "id": "0x31d228c6", 49 | "short_name": "GD32F350", 50 | "description": "GD32F350" 51 | }, 52 | { 53 | "id": "0x04240bdf", 54 | "short_name": "STM32L5", 55 | "description": "ST STM32L5xx" 56 | }, 57 | { 58 | "id": "0x4c71240a", 59 | "short_name": "STM32G4", 60 | "description": "ST STM32G4xx" 61 | }, 62 | { 63 | "id": "0x4fb2d5bd", 64 | "short_name": "MIMXRT10XX", 65 | "description": "NXP i.MX RT10XX" 66 | }, 67 | { 68 | "id": "0x53b80f00", 69 | "short_name": "STM32F7", 70 | "description": "ST STM32F7xx" 71 | }, 72 | { 73 | "id": "0x55114460", 74 | "short_name": "SAMD51", 75 | "description": "Microchip (Atmel) SAMD51" 76 | }, 77 | { 78 | "id": "0x57755a57", 79 | "short_name": "STM32F4", 80 | "description": "ST STM32F401" 81 | }, 82 | { 83 | "id": "0x5a18069b", 84 | "short_name": "FX2", 85 | "description": "Cypress FX2" 86 | }, 87 | { 88 | "id": "0x5d1a0a2e", 89 | "short_name": "STM32F2", 90 | "description": "ST STM32F2xx" 91 | }, 92 | { 93 | "id": "0x5ee21072", 94 | "short_name": "STM32F1", 95 | "description": "ST STM32F103" 96 | }, 97 | { 98 | "id": "0x647824b6", 99 | "short_name": "STM32F0", 100 | "description": "ST STM32F0xx" 101 | }, 102 | { 103 | "id": "0x68ed2b88", 104 | "short_name": "SAMD21", 105 | "description": "Microchip (Atmel) SAMD21" 106 | }, 107 | { 108 | "id": "0x6b846188", 109 | "short_name": "STM32F3", 110 | "description": "ST STM32F3xx" 111 | }, 112 | { 113 | "id": "0x6d0922fa", 114 | "short_name": "STM32F407", 115 | "description": "ST STM32F407" 116 | }, 117 | { 118 | "id": "0x6db66082", 119 | "short_name": "STM32H7", 120 | "description": "ST STM32H7xx" 121 | }, 122 | { 123 | "id": "0x70d16653", 124 | "short_name": "STM32WB", 125 | "description": "ST STM32WBxx" 126 | }, 127 | { 128 | "id": "0x7eab61ed", 129 | "short_name": "ESP8266", 130 | "description": "ESP8266" 131 | }, 132 | { 133 | "id": "0x7f83e793", 134 | "short_name": "KL32L2", 135 | "description": "NXP KL32L2x" 136 | }, 137 | { 138 | "id": "0x8fb060fe", 139 | "short_name": "STM32F407VG", 140 | "description": "ST STM32F407VG" 141 | }, 142 | { 143 | "id": "0xada52840", 144 | "short_name": "NRF52840", 145 | "description": "Nordic NRF52840" 146 | }, 147 | { 148 | "id": "0xbfdd4eee", 149 | "short_name": "ESP32S2", 150 | "description": "ESP32-S2" 151 | }, 152 | { 153 | "id": "0xc47e5767", 154 | "short_name": "ESP32S3", 155 | "description": "ESP32-S3" 156 | }, 157 | { 158 | "id": "0xd42ba06c", 159 | "short_name": "ESP32C3", 160 | "description": "ESP32-C3" 161 | }, 162 | { 163 | "id": "0xe48bff56", 164 | "short_name": "RP2040", 165 | "description": "Raspberry Pi RP2040" 166 | }, 167 | { 168 | "id": "0x00ff6919", 169 | "short_name": "STM32L4", 170 | "description": "ST STM32L4xx" 171 | } 172 | ] -------------------------------------------------------------------------------- /main/led_strip.h: -------------------------------------------------------------------------------- 1 | // Copyright 2019 Espressif Systems (Shanghai) PTE LTD 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | #pragma once 15 | 16 | #ifdef __cplusplus 17 | extern "C" { 18 | #endif 19 | 20 | #include "esp_err.h" 21 | 22 | /** 23 | * @brief LED Strip Type 24 | * 25 | */ 26 | typedef struct led_strip_s led_strip_t; 27 | 28 | /** 29 | * @brief LED Strip Device Type 30 | * 31 | */ 32 | typedef void *led_strip_dev_t; 33 | 34 | /** 35 | * @brief Declare of LED Strip Type 36 | * 37 | */ 38 | struct led_strip_s { 39 | /** 40 | * @brief Set RGB for a specific pixel 41 | * 42 | * @param strip: LED strip 43 | * @param index: index of pixel to set 44 | * @param red: red part of color 45 | * @param green: green part of color 46 | * @param blue: blue part of color 47 | * 48 | * @return 49 | * - ESP_OK: Set RGB for a specific pixel successfully 50 | * - ESP_ERR_INVALID_ARG: Set RGB for a specific pixel failed because of invalid parameters 51 | * - ESP_FAIL: Set RGB for a specific pixel failed because other error occurred 52 | */ 53 | esp_err_t (*set_pixel)(led_strip_t *strip, uint32_t index, uint32_t red, uint32_t green, uint32_t blue); 54 | 55 | /** 56 | * @brief Refresh memory colors to LEDs 57 | * 58 | * @param strip: LED strip 59 | * @param timeout_ms: timeout value for refreshing task 60 | * 61 | * @return 62 | * - ESP_OK: Refresh successfully 63 | * - ESP_ERR_TIMEOUT: Refresh failed because of timeout 64 | * - ESP_FAIL: Refresh failed because some other error occurred 65 | * 66 | * @note: 67 | * After updating the LED colors in the memory, a following invocation of this API is needed to flush colors to strip. 68 | */ 69 | esp_err_t (*refresh)(led_strip_t *strip, uint32_t timeout_ms); 70 | 71 | /** 72 | * @brief Clear LED strip (turn off all LEDs) 73 | * 74 | * @param strip: LED strip 75 | * @param timeout_ms: timeout value for clearing task 76 | * 77 | * @return 78 | * - ESP_OK: Clear LEDs successfully 79 | * - ESP_ERR_TIMEOUT: Clear LEDs failed because of timeout 80 | * - ESP_FAIL: Clear LEDs failed because some other error occurred 81 | */ 82 | esp_err_t (*clear)(led_strip_t *strip, uint32_t timeout_ms); 83 | 84 | /** 85 | * @brief Free LED strip resources 86 | * 87 | * @param strip: LED strip 88 | * 89 | * @return 90 | * - ESP_OK: Free resources successfully 91 | * - ESP_FAIL: Free resources failed because error occurred 92 | */ 93 | esp_err_t (*del)(led_strip_t *strip); 94 | }; 95 | 96 | /** 97 | * @brief LED Strip Configuration Type 98 | * 99 | */ 100 | typedef struct { 101 | uint32_t max_leds; /*!< Maximum LEDs in a single strip */ 102 | led_strip_dev_t dev; /*!< LED strip device (e.g. RMT channel, PWM channel, etc) */ 103 | } led_strip_config_t; 104 | 105 | /** 106 | * @brief Default configuration for LED strip 107 | * 108 | */ 109 | #define LED_STRIP_DEFAULT_CONFIG(number, dev_hdl) \ 110 | { \ 111 | .max_leds = number, \ 112 | .dev = dev_hdl, \ 113 | } 114 | 115 | /** 116 | * @brief Install a new ws2812 driver (based on RMT peripheral) 117 | * 118 | * @param config: LED strip configuration 119 | * @return 120 | * LED strip instance or NULL 121 | */ 122 | led_strip_t *led_strip_new_rmt_ws2812(const led_strip_config_t *config); 123 | 124 | /** 125 | * @brief Init the RMT peripheral and LED strip configuration. 126 | * 127 | * @param[in] channel: RMT peripheral channel number. 128 | * @param[in] gpio: GPIO number for the RMT data output. 129 | * @param[in] led_num: number of addressable LEDs. 130 | * @return 131 | * LED strip instance or NULL 132 | */ 133 | led_strip_t * led_strip_init(uint8_t channel, uint8_t gpio, uint16_t led_num); 134 | 135 | /** 136 | * @brief Denit the RMT peripheral. 137 | * 138 | * @param[in] strip: LED strip 139 | * @return 140 | * - ESP_OK 141 | * - ESP_FAIL 142 | */ 143 | esp_err_t led_strip_denit(led_strip_t *strip); 144 | 145 | #ifdef __cplusplus 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /main/sdcard.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "sdmmc_cmd.h" 4 | 5 | #include "hal/gpio_ll.h" 6 | #include "driver/sdspi_host.h" 7 | #include "driver/sdmmc_host.h" 8 | #include "diskio_impl.h" 9 | #include "diskio_sdmmc.h" 10 | 11 | static const char *TAG = "sd"; 12 | 13 | static uint8_t pin_mosi, pin_miso, pin_sck, pin_cs; 14 | 15 | #define SET_SCK() gpio_ll_set_level(&GPIO, pin_sck, 1) 16 | #define CLR_SCK() gpio_ll_set_level(&GPIO, pin_sck, 0) 17 | #define GET_MISO() gpio_ll_get_level(&GPIO, pin_miso) 18 | 19 | #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) 20 | #define SPI_DMA_CHAN host.slot 21 | #elif CONFIG_IDF_TARGET_ESP32C3 22 | #define SPI_DMA_CHAN SPI_DMA_CH_AUTO 23 | #else 24 | #define SPI_DMA_CHAN 1 25 | #endif 26 | 27 | #define SPI_TX_BIT(n) \ 28 | gpio_ll_set_level(&GPIO, pin_mosi, b & (1 << n)); \ 29 | SET_SCK(); \ 30 | CLR_SCK() 31 | 32 | #define SPI_RX_BIT(n) \ 33 | SET_SCK(); \ 34 | if (GET_MISO()) \ 35 | b |= (1 << n); \ 36 | CLR_SCK() 37 | 38 | void spi_bb_init(void) { 39 | pin_setup_analog_input(pin_sck); 40 | pin_setup_output(pin_sck); 41 | pin_setup_analog_input(pin_mosi); 42 | pin_setup_output(pin_mosi); 43 | pin_setup_analog_input(pin_miso); 44 | pin_setup_input(pin_miso, PIN_PULL_UP); 45 | 46 | pin_setup_analog_input(pin_cs); 47 | pin_setup_output(pin_cs); 48 | } 49 | 50 | void spi_bb_tx(const void *data, unsigned len) { 51 | const uint8_t *p = data; 52 | while (len--) { 53 | uint32_t b = *p++; 54 | SPI_TX_BIT(7); 55 | SPI_TX_BIT(6); 56 | SPI_TX_BIT(5); 57 | SPI_TX_BIT(4); 58 | SPI_TX_BIT(3); 59 | SPI_TX_BIT(2); 60 | SPI_TX_BIT(1); 61 | SPI_TX_BIT(0); 62 | } 63 | } 64 | 65 | void spi_bb_set_cs(int val) { 66 | pin_set(pin_cs, val); 67 | } 68 | 69 | int spi_bb_get_miso(void) { 70 | return pin_get(pin_miso); 71 | } 72 | 73 | void spi_bb_rx(void *data, unsigned len) { 74 | uint8_t *p = data; 75 | // keep MOSI high 76 | gpio_ll_set_level(&GPIO, pin_mosi, 1); 77 | while (len--) { 78 | uint32_t b = 0; 79 | SPI_RX_BIT(7); 80 | SPI_RX_BIT(6); 81 | SPI_RX_BIT(5); 82 | SPI_RX_BIT(4); 83 | SPI_RX_BIT(3); 84 | SPI_RX_BIT(2); 85 | SPI_RX_BIT(1); 86 | SPI_RX_BIT(0); 87 | *p++ = b; 88 | } 89 | } 90 | 91 | void panic_dump_dmesg(void); 92 | 93 | void init_sdcard(void) { 94 | esp_err_t ret; 95 | 96 | sdmmc_card_t *card; 97 | 98 | pin_miso = dcfg_get_pin("sd.pinMISO"); 99 | pin_mosi = dcfg_get_pin("sd.pinMOSI"); 100 | pin_sck = dcfg_get_pin("sd.pinSCK"); 101 | pin_cs = dcfg_get_pin("sd.pinCS"); 102 | 103 | if (pin_miso == NO_PIN || pin_mosi == NO_PIN || pin_sck == NO_PIN || pin_cs == NO_PIN) { 104 | ESP_LOGI(TAG, "skipping SD card - no config"); 105 | return; 106 | } 107 | 108 | #if defined(CONFIG_IDF_TARGET_ESP32C3) 109 | // 9 is a boot pin connected to button - we should not actively drive it high 110 | JD_ASSERT(pin_cs != 9 && pin_sck != 9 && pin_mosi != 9); 111 | #endif 112 | 113 | ESP_LOGI(TAG, "Initializing SD card"); 114 | 115 | sdmmc_host_t host = SDSPI_HOST_DEFAULT(); 116 | spi_bus_config_t bus_cfg = { 117 | .mosi_io_num = pin_mosi, 118 | .miso_io_num = pin_miso, 119 | .sclk_io_num = pin_sck, 120 | .quadwp_io_num = -1, 121 | .quadhd_io_num = -1, 122 | .max_transfer_sz = 4000, 123 | }; 124 | 125 | CHK(spi_bus_initialize(host.slot, &bus_cfg, SPI_DMA_CHAN)); 126 | 127 | sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT(); 128 | slot_config.gpio_cs = pin_cs; 129 | slot_config.host_id = host.slot; 130 | 131 | BYTE pdrv = FF_DRV_NOT_USED; 132 | CHK(ff_diskio_get_drive(&pdrv)); 133 | JD_ASSERT(pdrv == 0); 134 | 135 | card = jd_alloc(sizeof(sdmmc_card_t)); 136 | 137 | CHK(host.init()); 138 | 139 | CHK(sdspi_host_init_device(&slot_config, &host.slot)); 140 | 141 | ret = sdmmc_card_init(&host, card); 142 | if (ret != 0) { 143 | jd_free(card); 144 | ESP_LOGW(TAG, "Failed to initialize SD card"); 145 | return; 146 | } 147 | 148 | ff_diskio_register_sdmmc(pdrv, card); 149 | 150 | ESP_LOGI(TAG, "SD card initialized"); 151 | 152 | jd_lstore_init(); 153 | } 154 | -------------------------------------------------------------------------------- /main/jdprotocol.h: -------------------------------------------------------------------------------- 1 | #ifndef __JDPROTOCOL_H 2 | #define __JDPROTOCOL_H 3 | 4 | #include 5 | #include 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif 10 | 11 | // 255 minus size of the serial header, rounded down to 4 12 | #define JD_SERIAL_PAYLOAD_SIZE 236 13 | #define JD_SERIAL_FULL_HEADER_SIZE 16 14 | 15 | #define JD_SERVICE_CLASS_CTRL 0x00000000 16 | 17 | #define JD_SERVICE_NUMBER_CTRL 0x00 18 | #define JD_SERVICE_NUMBER_MASK 0x3f 19 | #define JD_SERVICE_NUMBER_CRC_ACK 0x3f 20 | #define JD_SERVICE_NUMBER_STREAM 0x3e 21 | 22 | #define JD_PIPE_COUNTER_MASK 0x001f 23 | #define JD_PIPE_CLOSE_MASK 0x0020 24 | #define JD_PIPE_METADATA_MASK 0x0040 25 | #define JD_PIPE_PORT_SHIFT 7 26 | 27 | // the COMMAND flag signifies that the device_identifier is the recipent 28 | // (i.e., it's a command for the peripheral); the bit clear means device_identifier is the source 29 | // (i.e., it's a report from peripheral or a broadcast message) 30 | #define JD_FRAME_FLAG_COMMAND 0x01 31 | // an ACK should be issued with CRC of this package upon reception 32 | #define JD_FRAME_FLAG_ACK_REQUESTED 0x02 33 | // the device_identifier contains target service class number 34 | #define JD_FRAME_FLAG_IDENTIFIER_IS_SERVICE_CLASS 0x04 35 | 36 | #define JD_FRAME_SIZE(pkt) ((pkt)->size + 12) 37 | 38 | // Registers 0x001-0x07f - r/w common to all services 39 | // Registers 0x080-0x0ff - r/w defined per-service 40 | // Registers 0x100-0x17f - r/o common to all services 41 | // Registers 0x180-0x1ff - r/o defined per-service 42 | // Registers 0x200-0xeff - custom, defined per-service 43 | // Registers 0xf00-0xfff - reserved for implementation, should not be on the wire 44 | 45 | // this is either binary (0 or non-zero), or can be gradual (eg. brightness of neopixel) 46 | #define JD_REG_INTENSITY 0x01 47 | // the primary value of actuator (eg. servo angle) 48 | #define JD_REG_VALUE 0x02 49 | // enable/disable streaming 50 | #define JD_REG_IS_STREAMING 0x03 51 | // streaming interval in miliseconds 52 | #define JD_REG_STREAMING_INTERVAL 0x04 53 | // for analog sensors 54 | #define JD_REG_LOW_THRESHOLD 0x05 55 | #define JD_REG_HIGH_THRESHOLD 0x06 56 | // limit power drawn; in mA 57 | #define JD_REG_MAX_POWER 0x07 58 | 59 | // eg. one number for light sensor, all 3 coordinates for accelerometer 60 | #define JD_REG_READING 0x101 61 | 62 | #define JD_CMD_GET_REG 0x1000 63 | #define JD_CMD_SET_REG 0x2000 64 | 65 | #define JD_GET(reg) (JD_CMD_GET_REG | (reg)) 66 | #define JD_SET(reg) (JD_CMD_SET_REG | (reg)) 67 | 68 | // Commands 0x000-0x07f - common to all services 69 | // Commands 0x080-0xeff - defined per-service 70 | // Commands 0xf00-0xfff - reserved for implementation 71 | // enumeration data for CTRL, ad-data for other services 72 | #define JD_CMD_ADVERTISEMENT_DATA 0x00 73 | // event from sensor or on broadcast service 74 | #define JD_CMD_EVENT 0x01 75 | // request to calibrate sensor 76 | #define JD_CMD_CALIBRATE 0x02 77 | // request human-readable description of service 78 | #define JD_CMD_GET_DESCRIPTION 0x03 79 | // create a new stream for sending events 80 | #define JD_CMD_SUBSCRIBE 0x04 81 | 82 | // Commands specific to control service 83 | // do nothing 84 | #define JD_CMD_CTRL_NOOP 0x80 85 | // blink led or otherwise draw user's attention 86 | #define JD_CMD_CTRL_IDENTIFY 0x81 87 | // reset device 88 | #define JD_CMD_CTRL_RESET 0x82 89 | // identifies the type of hardware (eg., ACME Corp. Servo X-42 Rev C) 90 | #define JD_REG_CTRL_DEVICE_DESCRIPTION 0x180 91 | // a numeric code for the string above; used to mark firmware images 92 | #define JD_REG_CTRL_DEVICE_CLASS 0x181 93 | // MCU temperature in Celsius 94 | #define JD_REG_CTRL_TEMPERATURE 0x182 95 | // this is very approximate; ADC reading from backward-biasing the identification LED 96 | #define JD_REG_CTRL_LIGHT_LEVEL 0x183 97 | // typically the same as JD_REG_CTRL_DEVICE_CLASS; the bootloader will respond to that code 98 | #define JD_REG_CTRL_BL_DEVICE_CLASS 0x184 99 | 100 | #define JD_PACKED __attribute__((__packed__, aligned(4))) 101 | 102 | struct _jd_packet_t { 103 | uint16_t crc; 104 | uint8_t _size; // of frame data[] 105 | uint8_t flags; 106 | 107 | uint64_t device_identifier; 108 | 109 | uint8_t service_size; 110 | uint8_t service_number; 111 | uint16_t service_command; 112 | 113 | uint8_t data[0]; 114 | } JD_PACKED; 115 | typedef struct _jd_packet_t jd_packet_t; 116 | 117 | struct _jd_frame_t { 118 | uint16_t crc; 119 | uint8_t size; 120 | uint8_t flags; 121 | 122 | uint64_t device_identifier; 123 | 124 | uint8_t data[JD_SERIAL_PAYLOAD_SIZE + 4]; 125 | } JD_PACKED; 126 | typedef struct _jd_frame_t jd_frame_t; 127 | 128 | struct _jd_pipe_cmd_t { 129 | uint64_t device_identifier; 130 | uint16_t port_num; 131 | uint16_t reserved; 132 | } JD_PACKED; 133 | typedef struct _jd_pipe_cmd_t jd_pipe_cmd_t; 134 | 135 | #define JDSPI_MAGIC 0x7ACD 136 | #define JDSPI_MAGIC_NOOP 0xB3CD 137 | 138 | #ifdef __cplusplus 139 | } 140 | #endif 141 | 142 | #endif 143 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .SECONDARY: # this prevents object files from being removed 2 | .DEFAULT_GOAL := all 3 | CLI = node devicescript/cli/devicescript 4 | JDC = devicescript/runtime/jacdac-c 5 | BUILD = build 6 | IDF = idf.py 7 | 8 | _IGNORE0 := $(shell test -f Makefile.user || cp sample-Makefile.user Makefile.user) 9 | ifeq ($(TARGET),) 10 | include Makefile.user 11 | endif 12 | 13 | MON_PORT ?= $(SERIAL_PORT) 14 | ESPTOOL ?= esptool.py 15 | 16 | BL_OFF = $(shell grep CONFIG_BOOTLOADER_OFFSET_IN_FLASH= sdkconfig | sed -e 's/.*=//') 17 | 18 | ifeq ($(TARGET),esp32s2) 19 | GCC_PREF = xtensa-esp32s2-elf 20 | endif 21 | 22 | ifeq ($(TARGET),esp32s3) 23 | GCC_PREF = xtensa-esp32s3-elf 24 | endif 25 | 26 | ifeq ($(TARGET),esp32c3) 27 | GCC_PREF = riscv32-esp-elf 28 | endif 29 | 30 | ifeq ($(TARGET),esp32) 31 | GCC_PREF = xtensa-esp32-elf 32 | endif 33 | 34 | BOARD ?= $(shell basename `ls boards/$(TARGET)/*.board.json | head -1` .board.json) 35 | 36 | ifeq ($(GCC_PREF),) 37 | $(error Define 'TARGET = esp32s2' or similar in Makefile.user) 38 | endif 39 | 40 | prep: devicescript/cli/built/devicescript-cli.cjs sdkconfig.defaults refresh-version 41 | 42 | all: inner-build patch 43 | 44 | inner-build: check-export check-submodule prep 45 | $(IDF) --ccache build 46 | $(MAKE) combine 47 | 48 | ci-build: 49 | $(IDF) --ccache build 50 | $(MAKE) combine 51 | mv $(BUILD) build-$(TARGET) 52 | 53 | ci-patch: 54 | $(MAKE) patch BUILD=build-$(TARGET) 55 | 56 | sdkconfig.defaults: Makefile.user 57 | @if test -f sdkconfig ; then \ 58 | if grep -q 'CONFIG_IDF_TARGET="$(TARGET)"' sdkconfig ; then echo target OK ; \ 59 | else echo cleaning target... ; rm -rf $(BUILD) sdkconfig ; $(MAKE) refresh-version ; fi ; \ 60 | fi 61 | cat boards/$(TARGET)/sdkconfig.$(TARGET) boards/sdkconfig.common > sdkconfig.defaults 62 | for folder in boards/$(TARGET)/ boards/ ; do \ 63 | if test -f $$folder/idf_component.yml ; then cp $$folder/idf_component.yml main/ ; break ; fi ; \ 64 | done 65 | @mkdir -p $(BUILD) 66 | echo "idf_build_set_property(COMPILE_OPTIONS "$(COMPILE_OPTIONS)" APPEND)" > $(BUILD)/options.cmake 67 | 68 | combine: 69 | $(ESPTOOL) --chip $(TARGET) merge_bin \ 70 | -o $(BUILD)/combined.bin \ 71 | --target-offset $(BL_OFF) \ 72 | $(BL_OFF) $(BUILD)/bootloader/bootloader.bin \ 73 | 0x8000 $(BUILD)/partition_table/partition-table.bin \ 74 | 0x10000 $(BUILD)/espjd.bin 75 | 76 | patch: 77 | mkdir -p dist 78 | $(CLI) binpatch --slug microsoft/devicescript-esp32 \ 79 | --bin $(BUILD)/combined.bin --elf $(BUILD)/espjd.elf --generic \ 80 | boards/$(TARGET)/*.board.json $(PATCH_ARGS) 81 | 82 | clean: 83 | rm -rf sdkconfig sdkconfig.defaults $(BUILD) 84 | 85 | vscode: 86 | . $$IDF_PATH/export.sh ; $(IDF) --ccache build 87 | 88 | check-submodule: 89 | @test -f $(JDC)/jacdac/README.md || git submodule update --init --recursive 90 | 91 | check-export: 92 | @if [ "X$$IDF_TOOLS_EXPORT_CMD" = X ] ; then echo Run: ; echo . $$IDF_PATH/export.sh ; exit 1 ; fi 93 | 94 | 95 | devicescript/cli/built/devicescript-cli.cjs: check-submodule 96 | cd devicescript && yarn 97 | cd devicescript && yarn build-fast 98 | 99 | f: flash 100 | r: flash 101 | 102 | flash: all 103 | $(ESPTOOL) --chip $(TARGET) -p $(SERIAL_PORT) write_flash \ 104 | $(BL_OFF) dist/devicescript-$(TARGET)-$(BOARD)-$(BL_OFF).bin 105 | 106 | mon: 107 | . $(IDF_PATH)/export.sh ; $(IDF_PATH)/tools/idf_monitor.py --port $(MON_PORT) --baud 115200 $(BUILD)/espjd.elf 108 | 109 | monf: 110 | . $(IDF_PATH)/export.sh ; $(IDF_PATH)/tools/idf_monitor.py --port $(SERIAL_PORT) --baud 1500000 $(BUILD)/espjd.elf 111 | 112 | monu: 113 | . $(IDF_PATH)/export.sh ; $(IDF_PATH)/tools/idf_monitor.py --port $(SERIAL_PORT) --baud 115200 $(BUILD)/espjd.elf 114 | 115 | mon-2: 116 | $(IDF) monitor 117 | 118 | prep-gdb: 119 | echo > $(BUILD)/gdbinit 120 | echo "target remote :3333" >> $(BUILD)/gdbinit 121 | echo "set remote hardware-watchpoint-limit 2" >> $(BUILD)/gdbinit 122 | 123 | gdb: prep-gdb 124 | echo "mon halt" >> $(BUILD)/gdbinit 125 | $(GCC_PREF)-gdb -x $(BUILD)/gdbinit $(BUILD)/espjd.elf 126 | 127 | rst: 128 | echo "mon reset halt" >> $(BUILD)/gdbinit 129 | echo "flushregs" >> $(BUILD)/gdbinit 130 | echo "thb app_main" >> $(BUILD)/gdbinit 131 | echo "c" >> $(BUILD)/gdbinit 132 | $(GCC_PREF)-gdb -x $(BUILD)/gdbinit $(BUILD)/espjd.elf 133 | 134 | fake-dist: 135 | rm -rf dist/ 136 | $(MAKE) TARGET=esp32 PATCH_ARGS=--fake patch 137 | $(MAKE) TARGET=esp32c3 PATCH_ARGS=--fake patch 138 | $(MAKE) TARGET=esp32s2 PATCH_ARGS=--fake patch 139 | $(MAKE) TARGET=esp32s3 PATCH_ARGS=--fake patch 140 | 141 | update-devs: devicescript/cli/built/devicescript-cli.cjs 142 | node devicescript/scripts/bumparch.mjs --update 143 | 144 | bump: update-devs 145 | node devicescript/scripts/bumparch.mjs 146 | 147 | FW_VERSION = $(shell sh $(JDC)/scripts/git-version.sh) 148 | refresh-version: 149 | @mkdir -p $(BUILD) 150 | echo 'const char app_fw_version[] = "v$(FW_VERSION)";' > $(BUILD)/version-tmp.c 151 | @diff $(BUILD)/version.c $(BUILD)/version-tmp.c >/dev/null 2>/dev/null || \ 152 | (echo "refresh version"; cp $(BUILD)/version-tmp.c $(BUILD)/version.c) 153 | -------------------------------------------------------------------------------- /main/led_strip_encoder.c: -------------------------------------------------------------------------------- 1 | /* 2 | * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 3 | * 4 | * SPDX-License-Identifier: Apache-2.0 5 | */ 6 | 7 | #include "esp_check.h" 8 | #include "led_strip_encoder.h" 9 | 10 | static const char *TAG = "led_encoder"; 11 | 12 | typedef struct { 13 | rmt_encoder_t base; 14 | rmt_encoder_t *bytes_encoder; 15 | rmt_encoder_t *copy_encoder; 16 | int state; 17 | rmt_symbol_word_t reset_code; 18 | } rmt_led_strip_encoder_t; 19 | 20 | static size_t rmt_encode_led_strip(rmt_encoder_t *encoder, rmt_channel_handle_t channel, const void *primary_data, size_t data_size, rmt_encode_state_t *ret_state) 21 | { 22 | rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); 23 | rmt_encoder_handle_t bytes_encoder = led_encoder->bytes_encoder; 24 | rmt_encoder_handle_t copy_encoder = led_encoder->copy_encoder; 25 | rmt_encode_state_t session_state = 0; 26 | rmt_encode_state_t state = 0; 27 | size_t encoded_symbols = 0; 28 | switch (led_encoder->state) { 29 | case 0: // send RGB data 30 | encoded_symbols += bytes_encoder->encode(bytes_encoder, channel, primary_data, data_size, &session_state); 31 | if (session_state & RMT_ENCODING_COMPLETE) { 32 | led_encoder->state = 1; // switch to next state when current encoding session finished 33 | } 34 | if (session_state & RMT_ENCODING_MEM_FULL) { 35 | state |= RMT_ENCODING_MEM_FULL; 36 | goto out; // yield if there's no free space for encoding artifacts 37 | } 38 | // fall-through 39 | case 1: // send reset code 40 | encoded_symbols += copy_encoder->encode(copy_encoder, channel, &led_encoder->reset_code, 41 | sizeof(led_encoder->reset_code), &session_state); 42 | if (session_state & RMT_ENCODING_COMPLETE) { 43 | led_encoder->state = 0; // back to the initial encoding session 44 | state |= RMT_ENCODING_COMPLETE; 45 | } 46 | if (session_state & RMT_ENCODING_MEM_FULL) { 47 | state |= RMT_ENCODING_MEM_FULL; 48 | goto out; // yield if there's no free space for encoding artifacts 49 | } 50 | } 51 | out: 52 | *ret_state = state; 53 | return encoded_symbols; 54 | } 55 | 56 | static esp_err_t rmt_del_led_strip_encoder(rmt_encoder_t *encoder) 57 | { 58 | rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); 59 | rmt_del_encoder(led_encoder->bytes_encoder); 60 | rmt_del_encoder(led_encoder->copy_encoder); 61 | free(led_encoder); 62 | return ESP_OK; 63 | } 64 | 65 | static esp_err_t rmt_led_strip_encoder_reset(rmt_encoder_t *encoder) 66 | { 67 | rmt_led_strip_encoder_t *led_encoder = __containerof(encoder, rmt_led_strip_encoder_t, base); 68 | rmt_encoder_reset(led_encoder->bytes_encoder); 69 | rmt_encoder_reset(led_encoder->copy_encoder); 70 | led_encoder->state = 0; 71 | return ESP_OK; 72 | } 73 | 74 | esp_err_t rmt_new_led_strip_encoder(const led_strip_encoder_config_t *config, rmt_encoder_handle_t *ret_encoder) 75 | { 76 | esp_err_t ret = ESP_OK; 77 | rmt_led_strip_encoder_t *led_encoder = NULL; 78 | ESP_GOTO_ON_FALSE(config && ret_encoder, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument"); 79 | led_encoder = calloc(1, sizeof(rmt_led_strip_encoder_t)); 80 | ESP_GOTO_ON_FALSE(led_encoder, ESP_ERR_NO_MEM, err, TAG, "no mem for led strip encoder"); 81 | led_encoder->base.encode = rmt_encode_led_strip; 82 | led_encoder->base.del = rmt_del_led_strip_encoder; 83 | led_encoder->base.reset = rmt_led_strip_encoder_reset; 84 | // different led strip might have its own timing requirements, following parameter is for WS2812 85 | rmt_bytes_encoder_config_t bytes_encoder_config = { 86 | .bit0 = { 87 | .level0 = 1, 88 | .duration0 = 0.3 * config->resolution / 1000000, // T0H=0.3us 89 | .level1 = 0, 90 | .duration1 = 0.9 * config->resolution / 1000000, // T0L=0.9us 91 | }, 92 | .bit1 = { 93 | .level0 = 1, 94 | .duration0 = 0.9 * config->resolution / 1000000, // T1H=0.9us 95 | .level1 = 0, 96 | .duration1 = 0.3 * config->resolution / 1000000, // T1L=0.3us 97 | }, 98 | .flags.msb_first = 1 // WS2812 transfer bit order: G7...G0R7...R0B7...B0 99 | }; 100 | ESP_GOTO_ON_ERROR(rmt_new_bytes_encoder(&bytes_encoder_config, &led_encoder->bytes_encoder), err, TAG, "create bytes encoder failed"); 101 | rmt_copy_encoder_config_t copy_encoder_config = {}; 102 | ESP_GOTO_ON_ERROR(rmt_new_copy_encoder(©_encoder_config, &led_encoder->copy_encoder), err, TAG, "create copy encoder failed"); 103 | 104 | uint32_t reset_ticks = config->resolution / 1000000 * 50 / 2; // reset code duration defaults to 50us 105 | led_encoder->reset_code = (rmt_symbol_word_t) { 106 | .level0 = 0, 107 | .duration0 = reset_ticks, 108 | .level1 = 0, 109 | .duration1 = reset_ticks, 110 | }; 111 | *ret_encoder = &led_encoder->base; 112 | return ESP_OK; 113 | err: 114 | if (led_encoder) { 115 | if (led_encoder->bytes_encoder) { 116 | rmt_del_encoder(led_encoder->bytes_encoder); 117 | } 118 | if (led_encoder->copy_encoder) { 119 | rmt_del_encoder(led_encoder->copy_encoder); 120 | } 121 | free(led_encoder); 122 | } 123 | return ret; 124 | } 125 | -------------------------------------------------------------------------------- /main/main.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "esp_timer.h" 4 | #include "esp_event.h" 5 | #include "esp_random.h" 6 | #include "esp_task_wdt.h" 7 | #include "services/interfaces/jd_spi.h" 8 | 9 | #ifdef CONFIG_IDF_TARGET_ESP32C3 10 | // #define PIN_BOOT_BTN 9 11 | #else 12 | // this makes the board reset 13 | // #define PIN_BOOT_BTN 0 14 | #endif 15 | 16 | const char *JD_EVENT = "JD_EVENT"; 17 | 18 | worker_t main_worker; 19 | uint32_t now; 20 | static TaskHandle_t main_task; 21 | static int loop_pending; 22 | static esp_timer_handle_t main_loop_tick_timer; 23 | uint16_t tim_max_sleep; 24 | 25 | static void sync_main_loop_timer(void) { 26 | static uint16_t max_sleep; 27 | if (!tim_max_sleep) 28 | tim_max_sleep = 10000; 29 | if (max_sleep != tim_max_sleep) { 30 | esp_timer_stop(main_loop_tick_timer); 31 | max_sleep = tim_max_sleep; 32 | CHK(esp_timer_start_periodic(main_loop_tick_timer, max_sleep)); 33 | } 34 | } 35 | 36 | int target_in_irq(void) { 37 | return main_task != NULL && xTaskGetCurrentTaskHandle() != main_task; 38 | } 39 | 40 | static void post_loop(void *dummy) { 41 | if (!loop_pending) { 42 | loop_pending = 1; 43 | esp_event_post(JD_EVENT, 1, NULL, 0, 0); 44 | } 45 | } 46 | 47 | void jdesp_wake_main(void) { 48 | post_loop(NULL); 49 | } 50 | 51 | void jd_usb_flush_stdout(void) { 52 | fflush(stdout); 53 | } 54 | 55 | static void loop_handler(void *event_handler_arg, esp_event_base_t event_base, int32_t event_id, 56 | void *event_data) { 57 | if (!main_task) { 58 | CHK(esp_task_wdt_add(NULL)); 59 | 60 | main_task = xTaskGetCurrentTaskHandle(); 61 | // this will call app_init_services(), which may try to send something, so we better run it 62 | // from here 63 | jd_init(); 64 | 65 | jd_tcpsock_init(); 66 | 67 | DMESG("loop init done"); 68 | } 69 | 70 | loop_pending = 0; 71 | 72 | static int n; 73 | if (n++ > 50) { 74 | jd_usb_flush_stdout(); 75 | n = 0; 76 | } 77 | 78 | CHK(esp_task_wdt_reset()); 79 | 80 | #if defined(PIN_BOOT_BTN) 81 | if (pin_get(PIN_BOOT_BTN) == 0) 82 | reboot_to_uf2(); 83 | #endif 84 | 85 | jd_process_everything(); 86 | 87 | worker_do_work(main_worker); 88 | 89 | jd_tcpsock_process(); 90 | 91 | uart_log_dmesg(); 92 | 93 | // re-post ourselves immediately if more frames to process 94 | if (jd_rx_has_frame()) 95 | post_loop(NULL); 96 | else 97 | sync_main_loop_timer(); 98 | } 99 | 100 | void app_init_services(void) { 101 | devs_service_full_init(); 102 | 103 | if (i2c_init() == 0) { 104 | jd_scan_all(); 105 | // i2cserv_init(); 106 | } 107 | 108 | #if JD_WIFI 109 | jd_wifi_rssi(); // make sure WiFi module links 110 | #endif 111 | 112 | adc_can_read_pin(0); // link ADC 113 | jd_spi_is_ready(); // link SPI 114 | } 115 | 116 | static int log_writefn(void *cookie, const char *data, int size) { 117 | jd_lstore_append_frag(0, JD_LSTORE_TYPE_LOG, data, size); 118 | jd_dmesg_write(data, size); 119 | return size; 120 | } 121 | 122 | void app_main(void) { 123 | // reboot after 5s without watchdog 124 | esp_task_wdt_config_t wdt_cfg = { 125 | .timeout_ms = 5000, 126 | .trigger_panic = true, 127 | }; 128 | CHK(esp_task_wdt_init(&wdt_cfg)); 129 | // subscribe current task, in case something goes wrong here (unlikely) 130 | CHK(esp_task_wdt_add(NULL)); 131 | 132 | ESP_LOGI("JD", "starting devicescript-esp32 %s", app_get_fw_version()); 133 | DMESG("starting devicescript-esp32 %s", app_get_fw_version()); 134 | 135 | ESP_LOGW("JD", "devname %s", dcfg_get_string("devName", NULL)); 136 | 137 | extern void jd_rgbext_link(void); 138 | jd_rgbext_link(); 139 | 140 | usb_pre_init(); 141 | jd_seed_random(esp_random()); 142 | init_sdcard(); 143 | 144 | #if 0 145 | esp_log_level_set("sdmmc_init", ESP_LOG_VERBOSE); 146 | esp_log_level_set("sdmmc_cmd", ESP_LOG_VERBOSE); 147 | esp_log_level_set("sdspi_transaction", ESP_LOG_VERBOSE); 148 | esp_log_level_set("sdspi_host", ESP_LOG_VERBOSE); 149 | #endif 150 | 151 | _GLOBAL_REENT->_stdout = fwopen(NULL, &log_writefn); 152 | // enable line buffering for this stream (to be similar to the regular UART-based output) 153 | static char stdout_buf[128]; 154 | setvbuf(stdout, stdout_buf, _IOLBF, sizeof(stdout_buf)); 155 | 156 | flash_init(); 157 | 158 | usb_init(); 159 | uart_log_init(); 160 | jd_usb_enable_serial(); 161 | 162 | main_worker = worker_alloc(); 163 | 164 | esp_event_loop_create_default(); 165 | 166 | jd_settings_get_bin("no_such_setting", NULL, 0); // force flash init 167 | 168 | tim_init(); 169 | 170 | jd_rx_init(); 171 | jd_tx_init(); 172 | 173 | uart_init_(); 174 | 175 | esp_timer_create_args_t args; 176 | args.callback = post_loop; 177 | args.arg = NULL; 178 | args.dispatch_method = ESP_TIMER_TASK; 179 | args.name = "10ms"; 180 | CHK(esp_timer_create(&args, &main_loop_tick_timer)); 181 | sync_main_loop_timer(); 182 | 183 | DMESG("app_main mostly done"); 184 | 185 | CHK(esp_event_handler_instance_register(JD_EVENT, 1, loop_handler, NULL, NULL)); 186 | loop_pending = 0; // someone might have tried to post it before, but it would be ignored without 187 | // loop_handler registered 188 | post_loop(NULL); // run the loop for the first time 189 | 190 | // unsubscribe current task before exiting 191 | // the loop_handler should have subscribed itself by now 192 | CHK(esp_task_wdt_delete(NULL)); 193 | } 194 | -------------------------------------------------------------------------------- /boards/esp32archconfig.schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "additionalProperties": false, 4 | "definitions": { 5 | "HexInt": { 6 | "type": [ 7 | "string", 8 | "integer" 9 | ] 10 | } 11 | }, 12 | "properties": { 13 | "#": { 14 | "description": "All fields starting with '#' arg ignored", 15 | "type": "string" 16 | }, 17 | "$schema": { 18 | "type": "string" 19 | }, 20 | "bareUrl": { 21 | "description": "Where to find a UF2 or BIN file with minimal DCFG.\nCan be auto-populated.", 22 | "type": "string" 23 | }, 24 | "binFlashOffset": { 25 | "$ref": "#/definitions/HexInt", 26 | "description": "If specified, will be used as part of output file name." 27 | }, 28 | "binGenericFlashOffset": { 29 | "$ref": "#/definitions/HexInt", 30 | "description": "If specified, this shows where the generic part of BIN file starts." 31 | }, 32 | "dcfgOffset": { 33 | "$ref": "#/definitions/HexInt", 34 | "description": "Where should DCFG be embedded in generic file." 35 | }, 36 | "flashPageSize": { 37 | "description": "Size of a flash page, typically 4096.", 38 | "type": "integer" 39 | }, 40 | "fstorOffset": { 41 | "$ref": "#/definitions/HexInt", 42 | "description": "Offset where FSTOR sits in total flash space." 43 | }, 44 | "fstorPages": { 45 | "description": "Total number of pages in FSTOR.\nOften 32 (for 128k) or 64 (for 256k).", 46 | "type": "integer" 47 | }, 48 | "genericUrl": { 49 | "description": "Where to find a generic (without DCFG) UF2 or BIN file.", 50 | "type": "string" 51 | }, 52 | "id": { 53 | "description": "Short identification of architecture.", 54 | "examples": [ 55 | "rp2040", 56 | "rp2040w", 57 | "esp32c3" 58 | ], 59 | "type": "string" 60 | }, 61 | "name": { 62 | "description": "Full name of architecture.", 63 | "examples": [ 64 | "RP2040 + CYW43 WiFi", 65 | "ESP32-C3" 66 | ], 67 | "type": "string" 68 | }, 69 | "pins": { 70 | "additionalProperties": false, 71 | "description": "Defines a mapping from a pin function", 72 | "properties": { 73 | "#": { 74 | "description": "All fields starting with '#' arg ignored", 75 | "type": "string" 76 | }, 77 | "analogIn": { 78 | "description": "Pins capable of analog input (ADC).", 79 | "type": "string" 80 | }, 81 | "analogOut": { 82 | "description": "Pins capable of true analog output (DAC, not PWM).", 83 | "type": "string" 84 | }, 85 | "boot": { 86 | "description": "Pins used in boot process.", 87 | "type": "string" 88 | }, 89 | "bootUart": { 90 | "description": "Pins used by hardware UART during boot.", 91 | "type": "string" 92 | }, 93 | "debug": { 94 | "description": "Pins used by JTAG or SWD debugger interface.", 95 | "type": "string" 96 | }, 97 | "flash": { 98 | "description": "Pins used by the SPI flash interface.", 99 | "type": "string" 100 | }, 101 | "input": { 102 | "description": "Pins capable of general purpose input.\n`io` pins are automatically added here.", 103 | "type": "string" 104 | }, 105 | "io": { 106 | "description": "Pins with both input and output capability.", 107 | "type": "string" 108 | }, 109 | "output": { 110 | "description": "Pins capable of general purpose output.\n`io` pins are automatically added here.", 111 | "type": "string" 112 | }, 113 | "psram": { 114 | "description": "Pins used by PSRAM interface.", 115 | "type": "string" 116 | }, 117 | "touch": { 118 | "description": "Pins capable of touch input.", 119 | "type": "string" 120 | }, 121 | "usb": { 122 | "description": "Pins used by USB interface.", 123 | "type": "string" 124 | } 125 | }, 126 | "required": [ 127 | "io" 128 | ], 129 | "type": "object" 130 | }, 131 | "repoUrl": { 132 | "description": "Source repo, boards are assumed to sit in `boards//.board.json`\nAuto-populated.", 133 | "type": "string" 134 | }, 135 | "uf2Align": { 136 | "$ref": "#/definitions/HexInt", 137 | "description": "Force alignment of the last page in the patched UF2 file.\nSet to 4096 on RP2040 because of RP2040-E14." 138 | } 139 | }, 140 | "required": [ 141 | "dcfgOffset", 142 | "flashPageSize", 143 | "fstorOffset", 144 | "fstorPages", 145 | "id", 146 | "name" 147 | ], 148 | "type": "object" 149 | } 150 | 151 | -------------------------------------------------------------------------------- /main/wifi_impl.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "freertos/event_groups.h" 3 | #include "esp_wifi.h" 4 | #include "esp_mac.h" 5 | #include "nvs_flash.h" 6 | 7 | #if JD_WIFI 8 | 9 | // #define LOG(...) ESP_LOGI(TAG, __VA_ARGS__); 10 | #define LOG(msg, ...) DMESG("wifi: " msg, ##__VA_ARGS__) 11 | 12 | static const char *TAG = "wifi"; 13 | 14 | int jd_wifi_start_scan(void) { 15 | log_free_mem(); 16 | wifi_scan_config_t scan_config = {0}; 17 | return esp_wifi_scan_start(&scan_config, false); 18 | } 19 | 20 | int jd_wifi_connect(const char *ssid, const char *pw) { 21 | wifi_config_t cfg; 22 | memset(&cfg, 0, sizeof(cfg)); 23 | 24 | if (!pw) 25 | pw = ""; 26 | 27 | strlcpy((char *)cfg.sta.ssid, ssid, sizeof(cfg.sta.ssid)); 28 | strlcpy((char *)cfg.sta.password, pw, sizeof(cfg.sta.password)); 29 | 30 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 31 | ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &cfg)); 32 | ESP_ERROR_CHECK(esp_wifi_connect()); 33 | 34 | return 0; 35 | } 36 | 37 | static void scan_done_handler(void *arg, esp_event_base_t event_base, int32_t event_id, 38 | void *event_data) { 39 | uint16_t sta_number = 0; 40 | esp_wifi_scan_get_ap_num(&sta_number); 41 | 42 | jd_wifi_results_t *res = NULL; 43 | 44 | if (sta_number != 0) { 45 | wifi_ap_record_t *ap_list_buffer = 46 | (wifi_ap_record_t *)jd_alloc(sta_number * sizeof(wifi_ap_record_t)); 47 | 48 | esp_err_t err = esp_wifi_scan_get_ap_records(&sta_number, ap_list_buffer); 49 | 50 | if (err == ESP_OK && sta_number != 0) { 51 | res = jd_alloc(sizeof(jd_wifi_results_t) * sta_number); 52 | 53 | for (int i = 0; i < sta_number; i++) { 54 | jd_wifi_results_t ent; 55 | wifi_ap_record_t *src = &ap_list_buffer[i]; 56 | 57 | ent.reserved = 0; 58 | ent.flags = 0; 59 | 60 | if (src->phy_11b) 61 | ent.flags |= JD_WIFI_APFLAGS_IEEE_802_11B; 62 | if (src->phy_11g) 63 | ent.flags |= JD_WIFI_APFLAGS_IEEE_802_11G; 64 | if (src->phy_11n) 65 | ent.flags |= JD_WIFI_APFLAGS_IEEE_802_11N; 66 | if (src->phy_lr) 67 | ent.flags |= JD_WIFI_APFLAGS_IEEE_802_LONG_RANGE; 68 | if (src->wps) 69 | ent.flags |= JD_WIFI_APFLAGS_WPS; 70 | if (src->second == WIFI_SECOND_CHAN_ABOVE) 71 | ent.flags |= JD_WIFI_APFLAGS_HAS_SECONDARY_CHANNEL_ABOVE; 72 | if (src->second == WIFI_SECOND_CHAN_BELOW) 73 | ent.flags |= JD_WIFI_APFLAGS_HAS_SECONDARY_CHANNEL_BELOW; 74 | if (src->authmode != WIFI_AUTH_OPEN && src->authmode != WIFI_AUTH_WPA2_ENTERPRISE) 75 | ent.flags |= JD_WIFI_APFLAGS_HAS_PASSWORD; 76 | ent.channel = src->primary; 77 | ent.rssi = src->rssi; 78 | memcpy(ent.bssid, src->bssid, 6); 79 | memset(ent.ssid, 0, sizeof(ent.ssid)); 80 | int len = strlen((char *)src->ssid); 81 | if (len > 32) 82 | len = 32; 83 | memcpy(ent.ssid, src->ssid, len); 84 | res[i] = ent; 85 | } 86 | } else { 87 | LOG("failed to read scan results: %d", err); 88 | } 89 | 90 | jd_free(ap_list_buffer); 91 | } 92 | 93 | jd_wifi_scan_done_cb(res, sta_number); 94 | } 95 | 96 | static void got_ip_handler(void *arg, esp_event_base_t event_base, int32_t event_id, 97 | void *event_data) { 98 | ip_event_got_ip_t *ev = event_data; 99 | jd_wifi_got_ip_cb(ev->ip_info.ip.addr); 100 | } 101 | 102 | static void disconnect_handler(void *arg, esp_event_base_t event_base, int32_t event_id, 103 | void *event_data) { 104 | jd_wifi_lost_ip_cb(); 105 | } 106 | 107 | int jd_wifi_init(uint8_t mac_out[6]) { 108 | LOG("starting..."); 109 | 110 | esp_log_level_set(TAG, ESP_LOG_INFO); 111 | esp_efuse_mac_get_default(mac_out); 112 | 113 | esp_err_t ret = nvs_flash_init(); 114 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 115 | ESP_ERROR_CHECK(nvs_flash_erase()); 116 | ret = nvs_flash_init(); 117 | } 118 | JD_CHK(ret); 119 | 120 | ESP_ERROR_CHECK(esp_netif_init()); 121 | 122 | wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT(); 123 | ESP_ERROR_CHECK(esp_wifi_init(&cfg)); 124 | 125 | esp_netif_config_t netif_config = ESP_NETIF_DEFAULT_WIFI_STA(); 126 | esp_netif_t *netif = esp_netif_new(&netif_config); 127 | assert(netif); 128 | ESP_ERROR_CHECK(esp_netif_attach_wifi_station(netif)); 129 | ESP_ERROR_CHECK(esp_wifi_set_default_wifi_sta_handlers()); 130 | 131 | ESP_ERROR_CHECK( 132 | esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_SCAN_DONE, &scan_done_handler, NULL)); 133 | ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_DISCONNECTED, 134 | &disconnect_handler, NULL)); 135 | ESP_ERROR_CHECK( 136 | esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &got_ip_handler, NULL)); 137 | ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM)); 138 | ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); 139 | ESP_ERROR_CHECK(esp_wifi_start()); 140 | 141 | return 0; 142 | } 143 | 144 | int jd_wifi_disconnect(void) { 145 | ESP_ERROR_CHECK(esp_wifi_disconnect()); 146 | return 0; 147 | } 148 | 149 | int jd_wifi_rssi(void) { 150 | wifi_ap_record_t info; 151 | if (esp_wifi_sta_get_ap_info(&info) != 0) 152 | return -128; 153 | return info.rssi; 154 | } 155 | 156 | void jd_wifi_process(void) { 157 | // do nothing 158 | } 159 | 160 | #endif -------------------------------------------------------------------------------- /main/led.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "driver/ledc.h" 4 | #include "hal/ledc_ll.h" 5 | #include "hal/gpio_ll.h" 6 | #include "esp_rom_gpio.h" 7 | 8 | #include "esp_private/periph_ctrl.h" 9 | 10 | #define LEDC_TIMER_DIV_NUM_MAX (0x3FFFF) 11 | 12 | typedef struct timer_info { 13 | uint8_t tim_num; 14 | uint8_t pin; 15 | uint8_t bits; 16 | uint32_t div; 17 | uint32_t period; 18 | } timer_info_t; 19 | 20 | static timer_info_t timers[LEDC_TIMER_MAX]; 21 | 22 | uint8_t cpu_mhz = APB_CLK_FREQ / 1000000; 23 | 24 | static void apply_config(timer_info_t *t) { 25 | ledc_timer_t tim = t->tim_num; 26 | 27 | ledc_ll_set_clock_divider(&LEDC, LEDC_LOW_SPEED_MODE, tim, t->div); 28 | #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(CONFIG_IDF_TARGET_ESP32S3) 29 | ledc_ll_set_clock_source(&LEDC, LEDC_LOW_SPEED_MODE, tim, LEDC_APB_CLK); 30 | #endif 31 | ledc_ll_set_duty_resolution(&LEDC, LEDC_LOW_SPEED_MODE, tim, t->bits); 32 | ledc_ll_ls_timer_update(&LEDC, LEDC_LOW_SPEED_MODE, tim); 33 | 34 | ledc_ll_timer_rst(&LEDC, LEDC_LOW_SPEED_MODE, tim); 35 | ledc_ll_ls_timer_update(&LEDC, LEDC_LOW_SPEED_MODE, tim); 36 | } 37 | 38 | static void init(void) { 39 | if (timers[1].tim_num == 1) 40 | return; 41 | for (int i = 0; i < LEDC_TIMER_MAX; ++i) { 42 | timers[i].tim_num = i; 43 | timers[i].pin = 0xff; 44 | } 45 | periph_module_enable(PERIPH_LEDC_MODULE); 46 | ledc_ll_set_slow_clk_sel(&LEDC, LEDC_SLOW_CLK_APB); 47 | } 48 | 49 | uint8_t jd_pwm_init(uint8_t pin, uint32_t period, uint32_t duty, uint8_t prescaler) { 50 | init(); 51 | 52 | uint32_t period_cycles = period * prescaler; 53 | timer_info_t *t = NULL; 54 | 55 | for (int bits = LEDC_TIMER_BIT_MAX - 1; bits >= 4; bits--) { 56 | uint32_t div = (period_cycles << 8) >> bits; 57 | if (div < 256) 58 | continue; 59 | if (div > LEDC_TIMER_DIV_NUM_MAX) { 60 | DMESG("! PWM too fast"); 61 | hw_panic(); 62 | } 63 | 64 | for (unsigned i = 0; i < LEDC_TIMER_MAX; ++i) { 65 | t = &timers[i]; 66 | if (t->pin == pin) 67 | break; 68 | t = NULL; 69 | } 70 | 71 | if (t == NULL) 72 | for (unsigned i = 0; i < LEDC_TIMER_MAX; ++i) { 73 | t = &timers[i]; 74 | if (t->pin == 0xff) 75 | break; 76 | t = NULL; 77 | } 78 | 79 | if (t == NULL) { 80 | DMESG("! out of LEDC timers"); 81 | hw_panic(); 82 | } 83 | 84 | t->pin = pin; 85 | t->div = div; 86 | t->bits = bits; 87 | t->period = period; 88 | apply_config(t); 89 | 90 | break; 91 | } 92 | 93 | int ch = t->tim_num; // we bind timers to channels 1-1 94 | int tim = t->tim_num; 95 | int pwm_id = t->tim_num + 1; 96 | 97 | jd_pwm_set_duty(pwm_id, duty); 98 | 99 | ledc_ll_bind_channel_timer(&LEDC, LEDC_LOW_SPEED_MODE, ch, tim); 100 | ledc_ll_ls_channel_update(&LEDC, LEDC_LOW_SPEED_MODE, ch); 101 | 102 | jd_pwm_enable(pwm_id, 1); 103 | 104 | return pwm_id; 105 | } 106 | 107 | void jd_pwm_set_duty(uint8_t pwm_id, uint32_t duty) { 108 | JD_ASSERT(pwm_id > 0); 109 | JD_ASSERT(pwm_id <= LEDC_TIMER_MAX); 110 | 111 | timer_info_t *t = &timers[pwm_id - 1]; 112 | int ch = t->tim_num; 113 | 114 | uint32_t max = 1 << t->bits; 115 | duty = max * duty / t->period; 116 | if (duty >= max) 117 | duty = max - 1; 118 | 119 | ledc_ll_set_duty_int_part(&LEDC, LEDC_LOW_SPEED_MODE, ch, duty); 120 | ledc_ll_set_duty_direction(&LEDC, LEDC_LOW_SPEED_MODE, ch, 1); 121 | ledc_ll_set_duty_num(&LEDC, LEDC_LOW_SPEED_MODE, ch, 0); 122 | ledc_ll_set_duty_cycle(&LEDC, LEDC_LOW_SPEED_MODE, ch, 0); 123 | ledc_ll_set_duty_scale(&LEDC, LEDC_LOW_SPEED_MODE, ch, 0); 124 | ledc_ll_ls_channel_update(&LEDC, LEDC_LOW_SPEED_MODE, ch); 125 | 126 | ledc_ll_set_sig_out_en(&LEDC, LEDC_LOW_SPEED_MODE, ch, true); 127 | ledc_ll_set_duty_start(&LEDC, LEDC_LOW_SPEED_MODE, ch, true); 128 | ledc_ll_ls_channel_update(&LEDC, LEDC_LOW_SPEED_MODE, ch); 129 | } 130 | 131 | void jd_pwm_enable(uint8_t pwm_id, bool enabled) { 132 | JD_ASSERT(pwm_id > 0); 133 | JD_ASSERT(pwm_id <= LEDC_TIMER_MAX); 134 | 135 | timer_info_t *t = &timers[pwm_id - 1]; 136 | int ch = t->tim_num; 137 | 138 | pin_setup_output(t->pin); 139 | if (enabled) { 140 | bool output_invert = false; 141 | esp_rom_gpio_connect_out_signal( 142 | t->pin, ledc_periph_signal[LEDC_LOW_SPEED_MODE].sig_out0_idx + ch, output_invert, 0); 143 | } 144 | } 145 | 146 | void pin_set(int pin, int v) { 147 | if ((uint8_t)pin != NO_PIN) 148 | gpio_set_level(pin, v); 149 | } 150 | 151 | void pin_setup_output(int pin) { 152 | if ((uint8_t)pin != NO_PIN) { 153 | gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO); 154 | CHK(gpio_set_direction(pin, GPIO_MODE_OUTPUT)); 155 | esp_rom_gpio_connect_out_signal(pin, SIG_GPIO_OUT_IDX, false, false); 156 | } 157 | } 158 | 159 | int pin_get(int pin) { 160 | if ((uint8_t)pin == NO_PIN) 161 | return -1; 162 | return gpio_get_level(pin); 163 | } 164 | 165 | void pin_setup_input(int pin, int pull) { 166 | if ((uint8_t)pin == NO_PIN) 167 | return; 168 | gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO); 169 | pin_set_pull(pin, pull); 170 | CHK(gpio_set_direction(pin, GPIO_MODE_INPUT)); 171 | } 172 | 173 | void pin_set_pull(int pin, int pull) { 174 | if ((uint8_t)pin == NO_PIN) 175 | return; 176 | 177 | if (pull < 0) { 178 | gpio_pulldown_en(pin); 179 | gpio_pullup_dis(pin); 180 | } else if (pull > 0) { 181 | gpio_pullup_en(pin); 182 | gpio_pulldown_dis(pin); 183 | } else { 184 | gpio_pullup_dis(pin); 185 | gpio_pulldown_dis(pin); 186 | } 187 | } 188 | 189 | void pin_setup_analog_input(int pin) { 190 | if ((uint8_t)pin == NO_PIN) 191 | return; 192 | gpio_ll_iomux_func_sel(GPIO_PIN_MUX_REG[pin], PIN_FUNC_GPIO); 193 | pin_set_pull(pin, PIN_PULL_NONE); 194 | CHK(gpio_set_direction(pin, GPIO_MODE_DISABLE)); 195 | } 196 | 197 | void pwr_enter_no_sleep(void) {} 198 | void pwr_enter_tim(void) {} 199 | void pwr_leave_tim(void) {} 200 | 201 | void pwr_enter_pll(void) {} 202 | void pwr_leave_pll(void) {} 203 | 204 | void power_pin_enable(int en) {} 205 | -------------------------------------------------------------------------------- /main/usb.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | #include "interfaces/jd_usb.h" 3 | #include "esp_mac.h" 4 | 5 | #define LOG(msg, ...) DMESG("USB: " msg, ##__VA_ARGS__) 6 | #define LOGV(msg, ...) ((void)0) 7 | #undef ERROR 8 | #define ERROR(msg, ...) DMESG("USB-ERROR: " msg, ##__VA_ARGS__) 9 | 10 | #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) 11 | 12 | #include "tinyusb.h" 13 | #include "tusb_cdc_acm.h" 14 | 15 | static const char *descriptor_str[USB_STRING_DESCRIPTOR_ARRAY_SIZE] = { 16 | // array of pointer to string descriptors 17 | (char[]){0x09, 0x04}, // 0: is supported language is English (0x0409) 18 | CONFIG_TINYUSB_DESC_MANUFACTURER_STRING, // 1: Manufacturer 19 | "DeviceScript", // 2: Product 20 | "", // 3: Serials -> replaced 21 | 22 | #if CONFIG_TINYUSB_CDC_ENABLED 23 | CONFIG_TINYUSB_DESC_CDC_STRING, // 4: CDC Interface 24 | #else 25 | "", 26 | #endif 27 | 28 | #if CONFIG_TINYUSB_MSC_ENABLED 29 | CONFIG_TINYUSB_DESC_MSC_STRING, // 5: MSC Interface 30 | #else 31 | "", 32 | #endif 33 | 34 | #if CONFIG_TINYUSB_HID_ENABLED 35 | CONFIG_TINYUSB_DESC_HID_STRING // 6: HIDs 36 | #else 37 | "", 38 | #endif 39 | }; 40 | 41 | static uint8_t usb_connected; 42 | 43 | static volatile uint8_t fq_scheduled; 44 | 45 | static void fill_queue(void *dummy) { 46 | fq_scheduled = 0; 47 | while (tud_cdc_n_write_available(TINYUSB_CDC_ACM_0) >= 64) { 48 | uint8_t buf[64]; 49 | int sz = jd_usb_pull(buf); 50 | if (sz > 0) { 51 | tinyusb_cdcacm_write_queue(TINYUSB_CDC_ACM_0, buf, sz); 52 | tud_cdc_n_write_flush(TINYUSB_CDC_ACM_0); 53 | } else { 54 | break; 55 | } 56 | } 57 | } 58 | 59 | void jd_usb_pull_ready(void) { 60 | if (!fq_scheduled) { 61 | fq_scheduled = 1; 62 | tim_worker_run(fill_queue, NULL); 63 | } 64 | } 65 | 66 | void tud_cdc_tx_complete_cb(uint8_t itf) { 67 | jd_usb_pull_ready(); 68 | } 69 | 70 | static void on_cdc_rx(int itf0, cdcacm_event_t *event) { 71 | uint8_t buf[64]; 72 | for (;;) { 73 | size_t rx_size = 0; 74 | esp_err_t ret = tinyusb_cdcacm_read(TINYUSB_CDC_ACM_0, buf, sizeof(buf), &rx_size); 75 | if (ret >= 0 && rx_size > 0) { 76 | jd_usb_push(buf, rx_size); 77 | } else { 78 | break; 79 | } 80 | } 81 | JD_WAKE_MAIN(); 82 | } 83 | 84 | static void on_cdc_line_state_changed(int itf, cdcacm_event_t *event) { 85 | usb_connected = event->line_state_changed_data.dtr && event->line_state_changed_data.rts; 86 | LOG("connected: %d", usb_connected); 87 | } 88 | 89 | void usb_pre_init(void) {} 90 | 91 | void usb_init(void) { 92 | LOG("init"); 93 | tinyusb_config_t tusb_cfg; 94 | memset(&tusb_cfg, 0, sizeof(tusb_cfg)); 95 | 96 | uint8_t mac[6]; 97 | esp_efuse_mac_get_default(mac); 98 | static char macHex[15]; 99 | macHex[0] = 'J'; 100 | macHex[1] = 'D'; 101 | for (int i = 0; i < 6; ++i) { 102 | snprintf(macHex + (2 + i * 2), 3, "%02X", mac[i]); 103 | } 104 | DMESG("USB serial: %s", macHex); 105 | descriptor_str[3] = macHex; 106 | tusb_cfg.string_descriptor = (const char **)descriptor_str; 107 | 108 | ESP_ERROR_CHECK(tinyusb_driver_install(&tusb_cfg)); 109 | 110 | tinyusb_config_cdcacm_t amc_cfg; 111 | memset(&amc_cfg, 0, sizeof(amc_cfg)); 112 | amc_cfg.usb_dev = TINYUSB_USBDEV_0; 113 | amc_cfg.cdc_port = TINYUSB_CDC_ACM_0; 114 | amc_cfg.rx_unread_buf_sz = 64; 115 | amc_cfg.callback_rx = &on_cdc_rx; 116 | amc_cfg.callback_line_state_changed = &on_cdc_line_state_changed; 117 | ESP_ERROR_CHECK(tusb_cdc_acm_init(&amc_cfg)); 118 | 119 | LOG("init done"); 120 | } 121 | 122 | #elif defined(CONFIG_IDF_TARGET_ESP32C3) 123 | 124 | #include "hal/usb_serial_jtag_ll.h" 125 | #include "soc/periph_defs.h" 126 | 127 | static void fill_buffer(void) { 128 | uint8_t buf[64]; 129 | int len = jd_usb_pull(buf); 130 | if (len > 0) { 131 | usb_serial_jtag_ll_write_txfifo(buf, len); 132 | usb_serial_jtag_ll_txfifo_flush(); 133 | usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); 134 | usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); 135 | } else { 136 | usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); 137 | usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); 138 | } 139 | } 140 | 141 | void jd_usb_pull_ready(void) { 142 | target_disable_irq(); 143 | if (usb_serial_jtag_ll_txfifo_writable()) 144 | fill_buffer(); 145 | target_enable_irq(); 146 | } 147 | 148 | static void usb_serial_jtag_isr_handler(void *arg) { 149 | uint32_t st = usb_serial_jtag_ll_get_intsts_mask(); 150 | 151 | if (st & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) { 152 | if (usb_serial_jtag_ll_txfifo_writable()) 153 | fill_buffer(); 154 | else 155 | usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY); // ??? 156 | } 157 | 158 | if (st & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) { 159 | uint8_t buf[64]; 160 | int r = usb_serial_jtag_ll_read_rxfifo(buf, sizeof(buf)); 161 | usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); 162 | usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); 163 | if (r) 164 | jd_usb_push(buf, r); 165 | } 166 | } 167 | 168 | void phy_bbpll_en_usb(bool en); 169 | void usb_pre_init(void) { 170 | phy_bbpll_en_usb(true); 171 | } 172 | 173 | void usb_init(void) { 174 | LOG("init"); 175 | usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | 176 | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); 177 | usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | 178 | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT); 179 | intr_handle_t intr_handle; 180 | CHK(esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0, usb_serial_jtag_isr_handler, NULL, 181 | &intr_handle)); 182 | LOG("init done"); 183 | } 184 | 185 | #elif defined(CONFIG_IDF_TARGET_ESP32) 186 | 187 | #include "driver/uart.h" 188 | #include "esp_private/periph_ctrl.h" 189 | #include "hal/uart_ll.h" 190 | #include "hal/gpio_ll.h" 191 | 192 | // https://www.mischianti.org/2021/02/17/doit-esp32-dev-kit-v1-high-resolution-pinout-and-specs/ 193 | // https://randomnerdtutorials.com/esp32-pinout-reference-gpios/ 194 | 195 | #define TX_PIN 1 196 | #define RX_PIN 3 197 | 198 | static uart_dev_t *uart_hw; 199 | 200 | static JD_FAST void fill_buffer(void) { 201 | int space = UART_FIFO_LEN - uart_hw->status.txfifo_cnt; 202 | if (space < 64) 203 | return; 204 | 205 | uint8_t buf[64]; 206 | int len = jd_usb_pull(buf); 207 | if (len) { 208 | uart_ll_write_txfifo(uart_hw, buf, len); 209 | uart_hw->int_clr.txfifo_empty = 1; 210 | uart_hw->conf1.txfifo_empty_thrhd = 32; 211 | uart_hw->int_ena.txfifo_empty = 1; 212 | } else { 213 | uart_hw->int_clr.txfifo_empty = 1; 214 | uart_hw->int_ena.txfifo_empty = 0; 215 | } 216 | } 217 | 218 | static JD_FAST void read_fifo(void) { 219 | unsigned n; 220 | uint8_t buf[64]; 221 | while (0 != (n = uart_hw->status.rxfifo_cnt)) { 222 | if (n > 64) 223 | n = 64; 224 | uart_ll_read_rxfifo(uart_hw, buf, n); 225 | jd_usb_push(buf, n); 226 | } 227 | } 228 | 229 | static JD_FAST void uart_isr(void *dummy) { 230 | uint32_t uart_intr_status = uart_hw->int_st.val; 231 | 232 | read_fifo(); 233 | fill_buffer(); 234 | 235 | uart_hw->int_clr.val = uart_intr_status; // clear all 236 | } 237 | 238 | void jd_usb_pull_ready(void) { 239 | target_disable_irq(); 240 | fill_buffer(); 241 | target_enable_irq(); 242 | } 243 | 244 | void usb_pre_init(void) {} 245 | void usb_init(void) { 246 | int uart_idx = 1; 247 | uart_hw = UART_LL_GET_HW(uart_idx); 248 | 249 | periph_module_enable(uart_periph_signal[uart_idx].module); 250 | const uart_config_t uart_config = {.baud_rate = 1500000, 251 | .data_bits = UART_DATA_8_BITS, 252 | .parity = UART_PARITY_DISABLE, 253 | .stop_bits = UART_STOP_BITS_1, 254 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 255 | .source_clk = UART_SCLK_DEFAULT}; 256 | CHK(uart_param_config(uart_idx, &uart_config)); 257 | intr_handle_t intr_handle; 258 | CHK(esp_intr_alloc(uart_periph_signal[uart_idx].irq, 0, uart_isr, NULL, &intr_handle)); 259 | 260 | CHK(uart_set_pin(uart_idx, TX_PIN, RX_PIN, -1, -1)); 261 | 262 | uart_intr_config_t uart_intr = { 263 | .intr_enable_mask = UART_INTR_RXFIFO_TOUT | UART_INTR_TXFIFO_EMPTY | UART_INTR_RXFIFO_FULL, 264 | .rxfifo_full_thresh = 64, 265 | .rx_timeout_thresh = 30, // us 266 | .txfifo_empty_intr_thresh = 32}; 267 | CHK(uart_intr_config(uart_idx, &uart_intr)); 268 | } 269 | 270 | void jd_usb_process(void) { 271 | read_fifo(); 272 | // try to fill the output buffer just in case 273 | jd_usb_pull_ready(); 274 | } 275 | 276 | #else 277 | #error "unknown target" 278 | #endif 279 | -------------------------------------------------------------------------------- /main/sock.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "esp_wifi.h" 4 | #include "esp_event.h" 5 | #include "lwip/err.h" 6 | #include "lwip/sockets.h" 7 | #include "lwip/sys.h" 8 | #include "lwip/netdb.h" 9 | #include "lwip/dns.h" 10 | 11 | #include "mbedtls/platform.h" 12 | #include "mbedtls/net_sockets.h" 13 | #include "mbedtls/esp_debug.h" 14 | #include "mbedtls/ssl.h" 15 | #include "mbedtls/entropy.h" 16 | #include "mbedtls/ctr_drbg.h" 17 | #include "mbedtls/error.h" 18 | #include "esp_crt_bundle.h" 19 | 20 | #define LOG(fmt, ...) DMESG("SOCK: " fmt, ##__VA_ARGS__) 21 | #if 1 22 | #define LOGV(...) ((void)0) 23 | #else 24 | #define LOGV LOG 25 | #endif 26 | 27 | #define CHK_ERR(call) CHK(0 != (call)) 28 | 29 | static QueueHandle_t sock_cmds; 30 | static QueueHandle_t sock_events; 31 | static uint8_t sockbuf[1024]; 32 | 33 | typedef struct { 34 | mbedtls_ssl_context ssl; 35 | mbedtls_ctr_drbg_context ctr_drbg; // rng 36 | mbedtls_ssl_config conf; 37 | mbedtls_entropy_context entropy; 38 | mbedtls_net_context server_fd; 39 | bool is_tls; 40 | bool is_connected; 41 | 42 | #if 0 43 | mbedtls_x509_crt cacert; 44 | mbedtls_x509_crt *cacert_ptr; 45 | mbedtls_x509_crt clientcert; 46 | mbedtls_pk_context clientkey; 47 | #endif 48 | } sock_state_t; 49 | 50 | static sock_state_t _tls; 51 | 52 | typedef struct { 53 | unsigned ev; 54 | const void *data; 55 | unsigned size; 56 | } sock_event_t; 57 | 58 | typedef struct { 59 | unsigned cmd; 60 | union { 61 | struct { 62 | char *hostname; 63 | int port; 64 | } open; 65 | struct { 66 | uint8_t *data; 67 | unsigned size; 68 | } write; 69 | }; 70 | } sock_cmd_t; 71 | 72 | static void push_event(unsigned event, const void *data, unsigned size) { 73 | sock_event_t evt = { 74 | .ev = event, 75 | .data = data, 76 | .size = size, 77 | }; 78 | xQueueSend(sock_events, &evt, 20); 79 | } 80 | 81 | static bool needs_io(int ret) { 82 | return (ret == MBEDTLS_ERR_SSL_WANT_READ || ret == MBEDTLS_ERR_SSL_WANT_WRITE); 83 | } 84 | 85 | static void push_error(const char *msg) { 86 | push_event(JD_CONN_EV_ERROR, msg, strlen(msg)); 87 | } 88 | 89 | static int sock_fd; 90 | static void raise_error(const char *msg) { 91 | if (msg) 92 | LOG("err: %s", msg); 93 | else 94 | LOG("close"); 95 | if (sock_fd) { 96 | jd_tcpsock_close(); 97 | if (msg) 98 | push_error(msg); 99 | push_event(JD_CONN_EV_CLOSE, NULL, 0); 100 | } 101 | } 102 | 103 | static int mbedtls_print_error_msg(const char *fn, int error) { 104 | LOG("%s returned -%x", fn, -error); 105 | mbedtls_strerror(error, (char *)sockbuf, sizeof(sockbuf)); 106 | LOG(" %s", sockbuf); 107 | raise_error(fn); 108 | return -1; 109 | } 110 | 111 | static int sock_create_and_connect(const char *hostname, int port) { 112 | struct addrinfo hints = { 113 | .ai_family = AF_UNSPEC, 114 | .ai_socktype = SOCK_STREAM, 115 | }; 116 | struct addrinfo *result; 117 | 118 | bool is_tls = false; 119 | 120 | if (port < 0) { 121 | port = -port; 122 | is_tls = true; 123 | } 124 | 125 | char portbuf[10]; 126 | jd_sprintf(portbuf, sizeof(portbuf), "%d", port); 127 | 128 | int s = getaddrinfo(hostname, portbuf, &hints, &result); 129 | if (s) { 130 | LOG("getaddrinfo %s:%d: %d", hostname, port, s); 131 | push_error("can't resolve host"); 132 | return -1; 133 | } 134 | 135 | int sockfd = -1; 136 | 137 | for (struct addrinfo *rp = result; rp != NULL; rp = rp->ai_next) { 138 | int sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); 139 | if (sfd == -1) 140 | continue; 141 | 142 | if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { 143 | sockfd = sfd; 144 | break; 145 | } 146 | 147 | if (rp->ai_next == NULL) { 148 | LOG("connect %s:%d: %s", hostname, port, strerror(errno)); 149 | push_error("can't connect"); 150 | } 151 | 152 | close(sfd); 153 | } 154 | 155 | freeaddrinfo(result); 156 | 157 | if (sockfd < 0) 158 | return sockfd; 159 | 160 | LOG("connected to %s:%d", hostname, port); 161 | 162 | sock_fd = sockfd; 163 | 164 | if (!is_tls) 165 | return 0; 166 | 167 | sock_state_t *tls = &_tls; 168 | mbedtls_ssl_init(&tls->ssl); 169 | mbedtls_ctr_drbg_init(&tls->ctr_drbg); 170 | mbedtls_ssl_config_init(&tls->conf); 171 | mbedtls_entropy_init(&tls->entropy); 172 | tls->is_tls = true; 173 | tls->server_fd.fd = sockfd; 174 | 175 | int ret; 176 | 177 | if ((ret = mbedtls_ssl_set_hostname(&tls->ssl, hostname)) != 0) 178 | return mbedtls_print_error_msg("mbedtls_ssl_set_hostname", ret); 179 | 180 | if ((ret = mbedtls_ssl_config_defaults(&tls->conf, MBEDTLS_SSL_IS_CLIENT, 181 | MBEDTLS_SSL_TRANSPORT_STREAM, 182 | MBEDTLS_SSL_PRESET_DEFAULT)) != 0) 183 | return mbedtls_print_error_msg("mbedtls_ssl_config_defaults", ret); 184 | 185 | mbedtls_ssl_conf_authmode(&tls->conf, MBEDTLS_SSL_VERIFY_REQUIRED); 186 | esp_crt_bundle_attach(&tls->conf); 187 | 188 | if ((ret = mbedtls_ctr_drbg_seed(&tls->ctr_drbg, mbedtls_entropy_func, &tls->entropy, NULL, 189 | 0)) != 0) 190 | return mbedtls_print_error_msg("mbedtls_ctr_drbg_seed", ret); 191 | 192 | mbedtls_ssl_conf_rng(&tls->conf, mbedtls_ctr_drbg_random, &tls->ctr_drbg); 193 | 194 | // 2-warn 3-debug 4-verbose 195 | // mbedtls_esp_enable_debug_log(&tls->conf, 3); 196 | 197 | if ((ret = mbedtls_ssl_setup(&tls->ssl, &tls->conf)) != 0) 198 | return mbedtls_print_error_msg("mbedtls_ssl_setup", ret); 199 | 200 | mbedtls_ssl_set_bio(&tls->ssl, &tls->server_fd, mbedtls_net_send, mbedtls_net_recv, NULL); 201 | 202 | for (;;) { 203 | ret = mbedtls_ssl_handshake(&tls->ssl); 204 | if (ret == 0) 205 | break; 206 | 207 | if (!needs_io(ret)) 208 | return mbedtls_print_error_msg("mbedtls_ssl_handshake", ret); 209 | 210 | vTaskDelay(10); 211 | } 212 | 213 | mbedtls_net_set_nonblock(&tls->server_fd); 214 | 215 | LOG("TLS handshake completed with %s:%d", hostname, port); 216 | 217 | return 0; 218 | } 219 | 220 | static void process_open(sock_cmd_t *cmd) { 221 | jd_tcpsock_close(); 222 | int r = sock_create_and_connect(cmd->open.hostname, cmd->open.port); 223 | jd_free(cmd->open.hostname); 224 | if (r == 0) { 225 | _tls.is_connected = true; 226 | push_event(JD_CONN_EV_OPEN, NULL, 0); 227 | } 228 | } 229 | 230 | static int forced_write(int fd, const void *buf, size_t nbytes) { 231 | int numread = 0; 232 | while ((int)nbytes > numread) { 233 | int r = write(fd, (const uint8_t *)buf + numread, nbytes - numread); 234 | if (r <= 0) 235 | return r; 236 | numread += r; 237 | if ((int)nbytes > numread) 238 | LOGV("short write: %d", r); 239 | } 240 | return numread; 241 | } 242 | 243 | static int sock_mbedtls_write(sock_state_t *tls, const uint8_t *data, size_t datalen) { 244 | JD_ASSERT(datalen < MBEDTLS_SSL_OUT_CONTENT_LEN); 245 | 246 | size_t written = 0; 247 | size_t write_len = datalen; 248 | while (written < datalen) { 249 | ssize_t ret = mbedtls_ssl_write(&tls->ssl, data + written, write_len); 250 | if (ret <= 0) { 251 | if (ret != 0 && !needs_io(ret)) { 252 | return mbedtls_print_error_msg("mbedtls_ssl_write", ret); 253 | } else { 254 | vTaskDelay(5); 255 | } 256 | } 257 | written += ret; 258 | write_len = datalen - written; 259 | } 260 | return written; 261 | } 262 | 263 | static void process_write(sock_cmd_t *cmd) { 264 | unsigned size = cmd->write.size; 265 | uint8_t *buf = cmd->write.data; 266 | 267 | sock_state_t *tls = &_tls; 268 | 269 | if (tls->is_tls) { 270 | LOGV("wrTLS %u b", size); 271 | sock_mbedtls_write(tls, buf, size); 272 | } else if (sock_fd) { 273 | LOGV("wr %u b", size); 274 | if (forced_write(sock_fd, buf, size) != (int)size) 275 | raise_error("write error"); 276 | } 277 | jd_free(buf); 278 | } 279 | 280 | void jd_tcpsock_close(void) { 281 | if (sock_fd) { 282 | close(sock_fd); 283 | sock_fd = 0; 284 | } 285 | 286 | sock_state_t *tls = &_tls; 287 | tls->is_connected = false; 288 | if (tls->is_tls) { 289 | // mbedtls_ssl_session_reset(&tls->ssl); 290 | mbedtls_entropy_free(&tls->entropy); 291 | mbedtls_ssl_config_free(&tls->conf); 292 | mbedtls_ctr_drbg_free(&tls->ctr_drbg); 293 | mbedtls_ssl_free(&tls->ssl); 294 | tls->is_tls = 0; 295 | } 296 | } 297 | 298 | int jd_tcpsock_new(const char *hostname, int port) { 299 | sock_cmd_t cmd = { 300 | .cmd = JD_CONN_EV_OPEN, 301 | .open = {.hostname = jd_strdup(hostname), .port = port}, 302 | }; 303 | if (xQueueSend(sock_cmds, &cmd, 20) == pdPASS) { 304 | return 0; 305 | } else { 306 | jd_free(cmd.open.hostname); 307 | return -1; 308 | } 309 | } 310 | 311 | int jd_tcpsock_write(const void *buf, unsigned size) { 312 | if (!sock_fd) 313 | return -10; 314 | 315 | uint8_t *copy = jd_alloc(size); 316 | memcpy(copy, buf, size); 317 | sock_cmd_t cmd = {.cmd = JD_CONN_EV_MESSAGE, .write = {.data = copy, .size = size}}; 318 | if (xQueueSend(sock_cmds, &cmd, 0) == pdPASS) { 319 | return 0; 320 | } else { 321 | jd_free(copy); 322 | return -1; 323 | } 324 | } 325 | 326 | static void worker_main(void *arg) { 327 | while (1) { 328 | sock_cmd_t cmd; 329 | if (!xQueueReceive(sock_cmds, &cmd, 20)) 330 | continue; 331 | switch (cmd.cmd) { 332 | case JD_CONN_EV_OPEN: 333 | process_open(&cmd); 334 | break; 335 | case JD_CONN_EV_MESSAGE: 336 | process_write(&cmd); 337 | break; 338 | default: 339 | JD_PANIC(); 340 | } 341 | } 342 | } 343 | 344 | void jd_tcpsock_init(void) { 345 | esp_log_level_set("mbedtls", ESP_LOG_DEBUG); 346 | 347 | // The main task is at priority 1, so we're higher priority (run "more often"). 348 | // Timer task runs at much higher priority (~20). 349 | unsigned stack_size = 4096; 350 | sock_cmds = xQueueCreate(50, sizeof(sock_cmd_t)); 351 | sock_events = xQueueCreate(10, sizeof(sock_event_t)); 352 | TaskHandle_t task; 353 | xTaskCreatePinnedToCore(worker_main, "tcpsock", stack_size, NULL, 2, &task, WORKER_CPU); 354 | } 355 | 356 | void jd_tcpsock_process(void) { 357 | sock_event_t evt; 358 | while (xQueueReceive(sock_events, &evt, 0)) { 359 | jd_tcpsock_on_event(evt.ev, evt.data, evt.size); 360 | } 361 | 362 | sock_state_t *tls = &_tls; 363 | 364 | if (!tls->is_connected) 365 | return; 366 | 367 | for (;;) { 368 | if (tls->is_tls) { 369 | int ret = mbedtls_ssl_read(&tls->ssl, sockbuf, sizeof(sockbuf)); 370 | 371 | if (needs_io(ret)) 372 | return; 373 | 374 | if (ret == MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY || ret == 0) { 375 | raise_error(NULL); 376 | return; 377 | } 378 | 379 | if (ret < 0) { 380 | mbedtls_print_error_msg("mbedtls_ssl_read", ret); 381 | return; 382 | } 383 | 384 | LOGV("rdTLS %d", ret); 385 | jd_tcpsock_on_event(JD_CONN_EV_MESSAGE, sockbuf, ret); 386 | } else { 387 | int r = recv(sock_fd, sockbuf, sizeof(sockbuf), MSG_DONTWAIT); 388 | if (r == 0) { 389 | raise_error(NULL); 390 | return; 391 | } 392 | 393 | if (r > 0) { 394 | LOGV("rd %d", r); 395 | jd_tcpsock_on_event(JD_CONN_EV_MESSAGE, sockbuf, r); 396 | } else { 397 | if (errno == EAGAIN || errno == EWOULDBLOCK) 398 | break; 399 | else { 400 | raise_error("recv error"); 401 | return; 402 | } 403 | } 404 | } 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /scripts/uf2conv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import sys 3 | import struct 4 | import subprocess 5 | import re 6 | import os 7 | import os.path 8 | import argparse 9 | import json 10 | 11 | 12 | UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" 13 | UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected 14 | UF2_MAGIC_END = 0x0AB16F30 # Ditto 15 | 16 | INFO_FILE = "/INFO_UF2.TXT" 17 | 18 | appstartaddr = 0x2000 19 | familyid = 0x0 20 | 21 | 22 | def is_uf2(buf): 23 | w = struct.unpack(" 476: 57 | assert False, "Invalid UF2 data size at " + ptr 58 | newaddr = hd[3] 59 | if (hd[2] & 0x2000) and (currfamilyid == None): 60 | currfamilyid = hd[7] 61 | if curraddr == None or ((hd[2] & 0x2000) and hd[7] != currfamilyid): 62 | currfamilyid = hd[7] 63 | curraddr = newaddr 64 | if familyid == 0x0 or familyid == hd[7]: 65 | appstartaddr = newaddr 66 | padding = newaddr - curraddr 67 | if padding < 0: 68 | assert False, "Block out of order at " + ptr 69 | if padding > 10*1024*1024: 70 | assert False, "More than 10M of padding needed at " + ptr 71 | if padding % 4 != 0: 72 | assert False, "Non-word padding size at " + ptr 73 | while padding > 0: 74 | padding -= 4 75 | outp += b"\x00\x00\x00\x00" 76 | if familyid == 0x0 or ((hd[2] & 0x2000) and familyid == hd[7]): 77 | outp.append(block[32 : 32 + datalen]) 78 | curraddr = newaddr + datalen 79 | if hd[2] & 0x2000: 80 | if hd[7] in families_found.keys(): 81 | if families_found[hd[7]] > newaddr: 82 | families_found[hd[7]] = newaddr 83 | else: 84 | families_found[hd[7]] = newaddr 85 | if prev_flag == None: 86 | prev_flag = hd[2] 87 | if prev_flag != hd[2]: 88 | all_flags_same = False 89 | if blockno == (numblocks - 1): 90 | print("--- UF2 File Header Info ---") 91 | families = load_families() 92 | for family_hex in families_found.keys(): 93 | family_short_name = "" 94 | for name, value in families.items(): 95 | if value == family_hex: 96 | family_short_name = name 97 | print("Family ID is {:s}, hex value is 0x{:08x}".format(family_short_name,family_hex)) 98 | print("Target Address is 0x{:08x}".format(families_found[family_hex])) 99 | if all_flags_same: 100 | print("All block flag values consistent, 0x{:04x}".format(hd[2])) 101 | else: 102 | print("Flags were not all the same") 103 | print("----------------------------") 104 | if len(families_found) > 1 and familyid == 0x0: 105 | outp = [] 106 | appstartaddr = 0x0 107 | return b"".join(outp) 108 | 109 | def convert_to_carray(file_content): 110 | outp = "const unsigned long bindata_len = %d;\n" % len(file_content) 111 | outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {" 112 | for i in range(len(file_content)): 113 | if i % 16 == 0: 114 | outp += "\n" 115 | outp += "0x%02x, " % file_content[i] 116 | outp += "\n};\n" 117 | return bytes(outp, "utf-8") 118 | 119 | def convert_to_uf2(file_content): 120 | global familyid 121 | datapadding = b"" 122 | while len(datapadding) < 512 - 256 - 32 - 4: 123 | datapadding += b"\x00\x00\x00\x00" 124 | numblocks = (len(file_content) + 255) // 256 125 | outp = [] 126 | for blockno in range(numblocks): 127 | ptr = 256 * blockno 128 | chunk = file_content[ptr:ptr + 256] 129 | flags = 0x0 130 | if familyid: 131 | flags |= 0x2000 132 | hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": 212 | drives.append(words[0]) 213 | else: 214 | rootpath = "/media" 215 | if sys.platform == "darwin": 216 | rootpath = "/Volumes" 217 | elif sys.platform == "linux": 218 | try: 219 | tmp = rootpath + "/" + os.environ["USER"] 220 | if os.path.isdir(tmp): 221 | rootpath = tmp 222 | except: 223 | # can get keyerror in docker 224 | pass 225 | for d in os.listdir(rootpath): 226 | drives.append(os.path.join(rootpath, d)) 227 | 228 | 229 | def has_info(d): 230 | try: 231 | return os.path.isfile(d + INFO_FILE) 232 | except: 233 | return False 234 | 235 | return list(filter(has_info, drives)) 236 | 237 | 238 | def board_id(path): 239 | with open(path + INFO_FILE, mode='r') as file: 240 | file_content = file.read() 241 | return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) 242 | 243 | 244 | def list_drives(): 245 | for d in get_drives(): 246 | print(d, board_id(d)) 247 | 248 | 249 | def write_file(name, buf): 250 | with open(name, "wb") as f: 251 | f.write(buf) 252 | print("Wrote %d bytes to %s" % (len(buf), name)) 253 | 254 | 255 | def load_families(): 256 | # The expectation is that the `uf2families.json` file is in the same 257 | # directory as this script. Make a path that works using `__file__` 258 | # which contains the full path to this script. 259 | filename = "uf2families.json" 260 | pathname = os.path.join(os.path.dirname(os.path.abspath(__file__)), filename) 261 | with open(pathname) as f: 262 | raw_families = json.load(f) 263 | 264 | families = {} 265 | for family in raw_families: 266 | families[family["short_name"]] = int(family["id"], 0) 267 | 268 | return families 269 | 270 | 271 | def main(): 272 | global appstartaddr, familyid 273 | def error(msg): 274 | print(msg) 275 | sys.exit(1) 276 | parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') 277 | parser.add_argument('input', metavar='INPUT', type=str, nargs='?', 278 | help='input file (HEX, BIN or UF2)') 279 | parser.add_argument('-b' , '--base', dest='base', type=str, 280 | default="0x2000", 281 | help='set base address of application for BIN format (default: 0x2000)') 282 | parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, 283 | help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') 284 | parser.add_argument('-d' , '--device', dest="device_path", 285 | help='select a device path to flash') 286 | parser.add_argument('-l' , '--list', action='store_true', 287 | help='list connected devices') 288 | parser.add_argument('-c' , '--convert', action='store_true', 289 | help='do not flash, just convert') 290 | parser.add_argument('-D' , '--deploy', action='store_true', 291 | help='just flash, do not convert') 292 | parser.add_argument('-f' , '--family', dest='family', type=str, 293 | default="0x0", 294 | help='specify familyID - number or name (default: 0x0)') 295 | parser.add_argument('-C' , '--carray', action='store_true', 296 | help='convert binary file to a C array, not UF2') 297 | parser.add_argument('-i', '--info', action='store_true', 298 | help='display header information from UF2, do not convert') 299 | args = parser.parse_args() 300 | appstartaddr = int(args.base, 0) 301 | 302 | families = load_families() 303 | 304 | if args.family.upper() in families: 305 | familyid = families[args.family.upper()] 306 | else: 307 | try: 308 | familyid = int(args.family, 0) 309 | except ValueError: 310 | error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) 311 | 312 | if args.list: 313 | list_drives() 314 | else: 315 | if not args.input: 316 | error("Need input file") 317 | with open(args.input, mode='rb') as f: 318 | inpbuf = f.read() 319 | from_uf2 = is_uf2(inpbuf) 320 | ext = "uf2" 321 | if args.deploy: 322 | outbuf = inpbuf 323 | elif from_uf2 and not args.info: 324 | outbuf = convert_from_uf2(inpbuf) 325 | ext = "bin" 326 | elif from_uf2 and args.info: 327 | outbuf = "" 328 | convert_from_uf2(inpbuf) 329 | elif is_hex(inpbuf): 330 | outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) 331 | elif args.carray: 332 | outbuf = convert_to_carray(inpbuf) 333 | ext = "h" 334 | else: 335 | outbuf = convert_to_uf2(inpbuf) 336 | if not args.deploy and not args.info: 337 | print("Converted to %s, output size: %d, start address: 0x%x" % 338 | (ext, len(outbuf), appstartaddr)) 339 | if args.convert or ext != "uf2": 340 | drives = [] 341 | if args.output == None: 342 | args.output = "flash." + ext 343 | else: 344 | drives = get_drives() 345 | 346 | if args.output: 347 | write_file(args.output, outbuf) 348 | else: 349 | if len(drives) == 0: 350 | error("No drive to deploy.") 351 | for d in drives: 352 | print("Flashing %s (%s)" % (d, board_id(d))) 353 | write_file(d + "/NEW.UF2", outbuf) 354 | 355 | 356 | if __name__ == "__main__": 357 | main() 358 | -------------------------------------------------------------------------------- /main/hw-esp32.c: -------------------------------------------------------------------------------- 1 | #include "jdesp.h" 2 | 3 | #include "driver/uart.h" 4 | #include "driver/gpio.h" 5 | #include "esp_private/periph_ctrl.h" 6 | #include "esp_timer.h" 7 | #include "driver/uart.h" 8 | #include "hal/uart_ll.h" 9 | #include "hal/gpio_ll.h" 10 | #include "rom/gpio.h" 11 | 12 | typedef struct jacdac_ctx { 13 | uint8_t pin_num; 14 | uint8_t uart_num; 15 | volatile bool seen_low; 16 | bool rx_ended; 17 | bool in_tx; 18 | volatile uint16_t tx_len; 19 | volatile uint16_t rx_len; 20 | uint16_t data_left; 21 | uint8_t *volatile fifo_buf; 22 | cb_t timer_cb; 23 | uart_dev_t *uart_hw; 24 | 25 | esp_timer_handle_t timer; 26 | intr_handle_t intr_handle; 27 | 28 | volatile bool cb_rx; 29 | volatile bool cb_tx; 30 | volatile bool cb_fall; 31 | esp_timer_handle_t timer0; 32 | 33 | esp_timer_handle_t timer_worker; 34 | worker_t tim_worker; 35 | } jacdac_ctx_t; 36 | 37 | static jacdac_ctx_t context; 38 | 39 | #if !defined(CONFIG_IDF_TARGET_ESP32S3) 40 | #define tx_brk_done_int_clr tx_brk_done 41 | #define tx_brk_done_int_ena tx_brk_done 42 | #define txfifo_empty_int_clr txfifo_empty 43 | #define txfifo_empty_int_ena txfifo_empty 44 | #define brk_det_int_raw brk_det 45 | #endif 46 | 47 | // #define LOG(msg, ...) DMESG("JD: " msg, ##__VA_ARGS__) 48 | #define LOG JD_NOLOG 49 | 50 | // #define PIN_LOG_0 GPIO_NUM_3 51 | // #define PIN_LOG_1 GPIO_NUM_4 52 | 53 | #define UART_EMPTY_THRESH_DEFAULT (10) 54 | #define UART_FULL_THRESH_DEFAULT (120) 55 | #define UART_TOUT_THRESH_DEFAULT (10) 56 | 57 | static IRAM_ATTR void uart_isr(void *); 58 | 59 | static void init_log_pins(void) { 60 | #ifdef PIN_LOG_0 61 | gpio_set_direction(PIN_LOG_0, GPIO_MODE_OUTPUT); 62 | gpio_set_direction(PIN_LOG_1, GPIO_MODE_OUTPUT); 63 | #endif 64 | } 65 | 66 | static void log_pin_pulse(int pinid, int numpulses) { 67 | #ifdef PIN_LOG_0 68 | uint32_t mask = pinid == 0 ? 1 << PIN_LOG_0 : 1 << PIN_LOG_1; 69 | while (numpulses--) { 70 | GPIO.out_w1ts = mask; 71 | GPIO.out_w1tc = mask; 72 | } 73 | #endif 74 | } 75 | 76 | void timer_log(int line, int v) {} 77 | void log_pin_set(int line, int v) {} 78 | 79 | static void jd_timer(void *dummy) { 80 | target_disable_irq(); 81 | cb_t f = context.timer_cb; 82 | if (f) { 83 | context.timer_cb = NULL; 84 | target_enable_irq(); 85 | f(); 86 | } else { 87 | target_enable_irq(); 88 | } 89 | } 90 | 91 | static void jd_timer0(void *dummy) { 92 | LOG("t0 rx=%d tx=%d fall=%d", context.cb_rx, context.cb_tx, context.cb_fall); 93 | if (context.cb_rx) { 94 | context.cb_rx = 0; 95 | jd_rx_completed(0); 96 | } 97 | if (context.cb_tx) { 98 | context.cb_tx = 0; 99 | jd_tx_completed(0); 100 | } 101 | if (context.cb_fall) { 102 | context.cb_fall = 0; 103 | jd_line_falling(); 104 | } 105 | } 106 | 107 | static void jd_tim_worker(void *dummy) { 108 | worker_do_work(context.tim_worker); 109 | } 110 | 111 | int tim_worker_run(TaskFunction_t fn, void *arg) { 112 | int r = worker_run(context.tim_worker, fn, arg); 113 | if (r == 0) { 114 | esp_timer_stop(context.timer_worker); 115 | esp_timer_start_once(context.timer_worker, 0); 116 | } 117 | return r; 118 | } 119 | 120 | void tim_init(void) { 121 | init_log_pins(); 122 | 123 | esp_timer_create_args_t args; 124 | args.callback = (esp_timer_cb_t)jd_timer; 125 | args.arg = NULL; 126 | args.dispatch_method = ESP_TIMER_TASK; 127 | args.name = "JD timeout"; 128 | esp_timer_create(&args, &context.timer); 129 | 130 | args.callback = (esp_timer_cb_t)jd_timer0; 131 | args.name = "JD callback"; 132 | esp_timer_create(&args, &context.timer0); 133 | 134 | args.callback = (esp_timer_cb_t)jd_tim_worker; 135 | args.name = "tim_worker"; 136 | esp_timer_create(&args, &context.timer_worker); 137 | 138 | context.tim_worker = worker_alloc(); 139 | } 140 | 141 | static void schedule_timer0(void) { 142 | esp_timer_stop(context.timer0); 143 | esp_timer_start_once(context.timer0, 0); 144 | } 145 | 146 | uint64_t tim_get_micros(void) { 147 | return esp_timer_get_time(); 148 | } 149 | 150 | void tim_set_timer(int delta, cb_t callback) { 151 | // compensate for overheads 152 | delta -= JD_TIM_OVERHEAD; 153 | if (delta < 20) 154 | delta = 20; 155 | 156 | target_disable_irq(); 157 | if (!!context.timer) { 158 | context.timer_cb = callback; 159 | esp_timer_stop(context.timer); 160 | if (callback) { 161 | esp_timer_start_once(context.timer, delta); 162 | } 163 | } 164 | target_enable_irq(); 165 | } 166 | 167 | static IRAM_ATTR esp_err_t xgpio_set_level(gpio_num_t gpio_num, uint32_t level) { 168 | gpio_ll_set_level(&GPIO, gpio_num, level); 169 | return ESP_OK; 170 | } 171 | 172 | // #define RX_SIG uart_periph_signal[context.uart_num].rx_sig 173 | // #define TX_SIG uart_periph_signal[context.uart_num].tx_sig 174 | 175 | #define RX_SIG uart_periph_signal[context.uart_num].pins[SOC_UART_RX_PIN_IDX].signal 176 | #define TX_SIG uart_periph_signal[context.uart_num].pins[SOC_UART_TX_PIN_IDX].signal 177 | 178 | static IRAM_ATTR void pin_rx(void) { 179 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[context.pin_num], PIN_FUNC_GPIO); 180 | REG_SET_BIT(GPIO_PIN_MUX_REG[context.pin_num], FUN_PU); 181 | PIN_INPUT_ENABLE(GPIO_PIN_MUX_REG[context.pin_num]); 182 | gpio_ll_output_disable(&GPIO, context.pin_num); 183 | gpio_matrix_in(context.pin_num, RX_SIG, 0); 184 | } 185 | 186 | static IRAM_ATTR void pin_tx(void) { 187 | gpio_matrix_in(GPIO_FUNC_IN_HIGH, RX_SIG, 188 | 0); // context.uart_hw 189 | GPIO.pin[context.pin_num].int_type = GPIO_PIN_INTR_DISABLE; 190 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[context.pin_num], PIN_FUNC_GPIO); 191 | xgpio_set_level(context.pin_num, 1); 192 | gpio_matrix_out(context.pin_num, TX_SIG, 0, 0); 193 | } 194 | 195 | static IRAM_ATTR void fill_fifo(void) { 196 | if (!context.tx_len) { 197 | return; 198 | } 199 | 200 | int space = UART_FIFO_LEN - context.uart_hw->status.txfifo_cnt; 201 | if (context.tx_len < space) { 202 | space = context.tx_len; 203 | } 204 | 205 | uart_ll_write_txfifo(context.uart_hw, context.fifo_buf, space); 206 | 207 | context.fifo_buf += space; 208 | context.tx_len -= space; 209 | 210 | if (context.tx_len == 0) { 211 | LOG("txbrk"); 212 | uart_ll_tx_break(context.uart_hw, 14); 213 | context.uart_hw->int_clr.tx_brk_done_int_clr = 1; 214 | context.uart_hw->int_ena.tx_brk_done_int_ena = 1; 215 | } 216 | 217 | context.uart_hw->int_clr.txfifo_empty_int_clr = 1; 218 | context.uart_hw->conf1.txfifo_empty_thrhd = UART_EMPTY_THRESH_DEFAULT; 219 | context.uart_hw->int_ena.txfifo_empty_int_ena = 1; 220 | } 221 | 222 | static IRAM_ATTR void read_fifo(int force) { 223 | uart_dev_t *uart_reg = context.uart_hw; 224 | int rx_fifo_len = uart_reg->status.rxfifo_cnt; 225 | 226 | if (!force && context.fifo_buf == NULL && rx_fifo_len < UART_FULL_THRESH_DEFAULT - 1) { 227 | return; // read not started yet and we're not overflowing 228 | } 229 | if (rx_fifo_len) { 230 | LOG("rxfifo %d", rx_fifo_len); 231 | int n = rx_fifo_len; 232 | int needs_rst = 0; 233 | if (n > context.rx_len) { 234 | n = context.rx_len; 235 | needs_rst = 1; 236 | } 237 | 238 | if (n) { 239 | context.rx_len -= n; 240 | rx_fifo_len -= n; 241 | uart_ll_read_rxfifo(uart_reg, context.fifo_buf, n); 242 | context.fifo_buf += n; 243 | } 244 | 245 | // and drop the rest of data 246 | if (needs_rst) { 247 | uart_ll_rxfifo_rst(uart_reg); 248 | } 249 | } 250 | } 251 | 252 | void uart_init_(void) { 253 | uint8_t pinnum = dcfg_get_pin("jacdac.pin"); 254 | if (pinnum == NO_PIN) { 255 | DMESG("jacdac.pin not defined"); 256 | return; 257 | } 258 | 259 | if (!(1 <= pinnum && pinnum < 32)) { 260 | DMESG("invalid jacdac.pin"); 261 | JD_PANIC(); 262 | } 263 | 264 | context.uart_num = UART_NUM_MAX - 1; 265 | DMESG("Jacdac on UART%d IO%d", context.uart_num, pinnum); 266 | context.uart_hw = UART_LL_GET_HW(context.uart_num); 267 | 268 | context.pin_num = pinnum; 269 | 270 | // uart_mark_used(context.uart_num, true); 271 | 272 | periph_module_enable(uart_periph_signal[context.uart_num].module); 273 | 274 | const uart_config_t uart_config = {.baud_rate = 1000000, 275 | .data_bits = UART_DATA_8_BITS, 276 | .parity = UART_PARITY_DISABLE, 277 | .stop_bits = UART_STOP_BITS_1, 278 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 279 | .source_clk = UART_SCLK_DEFAULT}; 280 | CHK(uart_param_config(context.uart_num, &uart_config)); 281 | CHK(esp_intr_alloc(uart_periph_signal[context.uart_num].irq, 0, (void (*)(void *))uart_isr, 282 | &context, &context.intr_handle)); 283 | 284 | uart_intr_config_t uart_intr = {.intr_enable_mask = 0, 285 | .rxfifo_full_thresh = UART_FULL_THRESH_DEFAULT, 286 | .rx_timeout_thresh = 30, // 30us 287 | .txfifo_empty_intr_thresh = UART_EMPTY_THRESH_DEFAULT}; 288 | CHK(uart_intr_config(context.uart_num, &uart_intr)); 289 | 290 | gpio_config_t cfg = { 291 | .pin_bit_mask = BIT64(context.pin_num), 292 | .mode = GPIO_MODE_INPUT, 293 | .pull_up_en = true, 294 | .pull_down_en = false, 295 | .intr_type = GPIO_INTR_DISABLE, 296 | }; 297 | gpio_config(&cfg); 298 | 299 | uart_disable(); 300 | } 301 | 302 | #define END_RX_FLAGS (UART_RXFIFO_TOUT_INT_ST | UART_BRK_DET_INT_ST | UART_FRM_ERR_INT_ST) 303 | 304 | static IRAM_ATTR void start_bg_rx(void) { 305 | JD_ASSERT(!context.in_tx); 306 | read_fifo(1); // flush any data 307 | context.seen_low = 1; 308 | context.uart_hw->int_ena.val |= END_RX_FLAGS | UART_RXFIFO_FULL_INT_ENA; 309 | if (!context.fifo_buf) { 310 | context.cb_fall = 1; 311 | schedule_timer0(); 312 | } 313 | } 314 | 315 | static void uart_isr(void *dummy) { 316 | log_pin_pulse(0, 1); 317 | 318 | if (!context.intr_handle) 319 | return; 320 | 321 | uart_dev_t *uart_reg = context.uart_hw; 322 | 323 | uint32_t uart_intr_status = uart_reg->int_st.val; 324 | uart_reg->int_clr.val = uart_intr_status; // clear all 325 | 326 | // mask out disabled interrupts 327 | uart_intr_status &= uart_reg->int_ena.val; 328 | 329 | LOG("ISR %x %d", uart_intr_status, context.seen_low); 330 | 331 | read_fifo(0); 332 | 333 | if (!context.seen_low && (uart_intr_status & UART_BRK_DET_INT_ST)) { 334 | log_pin_pulse(0, 2); 335 | start_bg_rx(); 336 | } else if (uart_intr_status & UART_TX_BRK_DONE_INT_ST) { 337 | uart_reg->conf0.txd_brk = 0; 338 | context.in_tx = 0; 339 | uart_disable(); 340 | context.cb_tx = 1; 341 | schedule_timer0(); 342 | } else if (uart_intr_status & UART_TXFIFO_EMPTY_INT_ST) { 343 | uart_reg->int_ena.txfifo_empty_int_ena = 0; 344 | fill_fifo(); 345 | } else if (uart_intr_status & END_RX_FLAGS) { 346 | log_pin_pulse(0, 4); 347 | context.data_left = context.rx_len; 348 | int had_buf = context.fifo_buf != NULL; 349 | LOG("%d end, rx=%d %d", (int)esp_timer_get_time(), context.rx_len, had_buf); 350 | uart_disable(); 351 | if (had_buf) { 352 | log_pin_pulse(0, 5); 353 | context.cb_rx = 1; 354 | schedule_timer0(); 355 | } else { 356 | context.rx_ended = 1; 357 | } 358 | } 359 | } 360 | 361 | static IRAM_ATTR NOINLINE_ATTR void probe_and_set(volatile uint32_t *oe, volatile uint32_t *inp, 362 | uint32_t mask) { 363 | *oe = *inp & mask; 364 | } 365 | 366 | static void tx_race(void) { 367 | // don't reconnect the pin in the middle of the low-pulse 368 | int timeout = 50000; 369 | while (timeout-- > 0 && gpio_get_level(context.pin_num) == 0) { 370 | ; 371 | } 372 | pin_rx(); 373 | if (!context.seen_low) 374 | start_bg_rx(); // some rare race here 375 | } 376 | 377 | #if defined(CONFIG_IDF_TARGET_ESP32C3) 378 | #define GPIO_VAL(x) (GPIO.x.val) 379 | #else 380 | #define GPIO_VAL(x) (GPIO.x) 381 | #endif 382 | 383 | int uart_start_tx(const void *data, uint32_t numbytes) { 384 | if (!context.uart_hw) { 385 | jd_tx_completed(0); 386 | return 0; 387 | } 388 | 389 | if (context.tx_len || context.in_tx) { 390 | JD_PANIC(); 391 | } 392 | 393 | target_disable_irq(); 394 | if (!context.intr_handle || context.seen_low || context.uart_hw->int_raw.brk_det_int_raw) { 395 | LOG("seen low %p %d %p", &context, context.seen_low, context.uart_hw->int_raw.brk_det); 396 | target_enable_irq(); 397 | return -1; 398 | } 399 | 400 | gpio_matrix_in(GPIO_FUNC_IN_HIGH, RX_SIG, 0); // context.uart_hw 401 | PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[context.pin_num], PIN_FUNC_GPIO); 402 | xgpio_set_level(context.pin_num, 0); 403 | 404 | probe_and_set(&GPIO_VAL(enable_w1ts), &GPIO_VAL(in), 1 << context.pin_num); 405 | 406 | if (!(GPIO_VAL(enable) & (1 << context.pin_num))) { 407 | // the line went down in the meantime 408 | tx_race(); 409 | target_enable_irq(); 410 | return -1; 411 | } 412 | 413 | JD_ASSERT(!context.seen_low); 414 | context.in_tx = 1; 415 | 416 | target_wait_us(12); // low pulse is 14us with wait of 12 here 417 | xgpio_set_level(context.pin_num, 1); 418 | 419 | target_wait_us(50); 420 | 421 | pin_tx(); 422 | 423 | context.fifo_buf = (uint8_t *)data; 424 | context.tx_len = numbytes; 425 | 426 | context.uart_hw->int_clr.val = 0xffffffff; 427 | 428 | fill_fifo(); 429 | 430 | target_enable_irq(); 431 | 432 | return 0; 433 | } 434 | 435 | void uart_flush_rx(void) { 436 | target_disable_irq(); 437 | read_fifo(1); 438 | target_enable_irq(); 439 | } 440 | 441 | void uart_start_rx(void *data, uint32_t maxbytes) { 442 | if (context.rx_len || context.tx_len) { 443 | JD_PANIC(); 444 | } 445 | 446 | log_pin_pulse(0, 3); 447 | 448 | target_disable_irq(); 449 | context.fifo_buf = data; 450 | context.rx_len = maxbytes; 451 | target_enable_irq(); 452 | 453 | LOG("ini rx=%d", maxbytes); 454 | 455 | uart_flush_rx(); 456 | 457 | // log_pin_pulse(0, 2); 458 | 459 | if (context.rx_ended) { 460 | target_disable_irq(); 461 | context.seen_low = 0; 462 | context.rx_ended = 0; 463 | context.rx_len = 0; 464 | context.fifo_buf = NULL; 465 | target_enable_irq(); 466 | // log_pin_pulse(0, 2); 467 | jd_rx_completed(0); 468 | } 469 | } 470 | 471 | void uart_disable(void) { 472 | target_disable_irq(); 473 | context.uart_hw->int_clr.val = context.uart_hw->int_st.val; 474 | context.uart_hw->int_ena.val = UART_BRK_DET_INT_ENA; 475 | context.seen_low = 0; 476 | context.cb_fall = 0; 477 | context.rx_len = context.tx_len = 0; 478 | context.fifo_buf = NULL; 479 | context.rx_ended = 0; 480 | read_fifo(1); 481 | pin_rx(); 482 | target_enable_irq(); 483 | log_pin_pulse(1, 1); 484 | } 485 | 486 | int uart_wait_high(void) { 487 | // we already started RX at this point 488 | return 0; 489 | } 490 | --------------------------------------------------------------------------------