diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json index 5fd50860..b6b1e0c5 100644 --- a/.vscode/c_cpp_properties.json +++ b/.vscode/c_cpp_properties.json @@ -9,18 +9,11 @@ "includePath": [ ".", "build", - "build/genhdr", - "driver", "micropython", - "micropython/extmod/**", "micropython/lib/cmsis/inc", - "micropython/lib/libm_dbl", - "micropython/lib/libm", - "micropython/lib/re1.5", - "micropython/py", - "micropython/shared/**", "micropython/shared/readline", "modules", + "modules/libvgrs/src", "monocle-core", "nrfx", "nrfx/drivers", diff --git a/.vscode/tasks.json b/.vscode/tasks.json index ca38523a..5a85d950 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -12,7 +12,7 @@ ], "presentation": { "echo": false, - "reveal": "always", + "reveal": "silent", "focus": false, "panel": "shared", "showReuseMessage": false, @@ -24,7 +24,7 @@ }, }, { - "label": "Build & flash", + "label": "Build & Flash Chip", "type": "shell", "command": "make -j`getconf _NPROCESSORS_ONLN` flash", "problemMatcher": [ @@ -43,6 +43,34 @@ "isDefault": false }, }, + { + "label": "Erase & Unlock Chip", + "type": "shell", + "command": "make recover", + "problemMatcher": [], + "presentation": { + "echo": false, + "reveal": "always", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + }, + }, + { + "label": "Clean", + "type": "shell", + "command": "make clean", + "problemMatcher": [], + "presentation": { + "echo": false, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + }, + }, { "label": "Release", "type": "shell", @@ -64,18 +92,18 @@ }, }, { - "label": "Clean", + "label": "RTT Debug Console", "type": "shell", - "command": "make clean", + "command": "JLinkRTTClient", "problemMatcher": [], "presentation": { "echo": false, "reveal": "always", "focus": false, - "panel": "shared", + "panel": "dedicated", "showReuseMessage": false, "clear": true }, - } + }, ] } \ No newline at end of file diff --git a/Makefile b/Makefile index 87ade339..2d81806f 100644 --- a/Makefile +++ b/Makefile @@ -209,10 +209,13 @@ build/application.elf: $(OBJ) $(Q)$(SIZE) $@ flash: build/application.hex - nrfjprog --program softdevice/*.hex --chiperase -f nrf52 --verify - nrfjprog --program $< -f nrf52 --verify + nrfjprog -q --program softdevice/*.hex --chiperase + nrfjprog -q --program build/application.hex nrfjprog --reset -f nrf52 +recover: + nrfjprog --recover + release: clean build/application.hex nrfutil settings generate --family NRF52 --application build/application.hex --application-version 0 --bootloader-version 0 --bl-settings-version 2 build/settings.hex mergehex -m build/settings.hex build/application.hex softdevice/s132_nrf52_7.3.0_softdevice.hex bootloader/build/nrf52832_xxaa_s132.hex -o build/monocle-micropython-$(BUILD_VERSION).hex diff --git a/README.md b/README.md index 83d8ad1f..ba2f099c 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,8 @@ # MicroPython for Monocle -A custom deployment of MicroPython designed specifically for Monocle. Read the [docs](https://docs.brilliant.xyz). +A custom deployment of MicroPython designed specifically for Monocle. Check out the user docs [here](https://docs.brilliant.xyz). -## Flash you Monocle - - -1. Download the latest `.hex` file from the [releases page](https://github.com/brilliantlabsAR/monocle-micropython/releases). - -1. Ensure you have the [nRF Command Line Tools](https://www.nordicsemi.com/Products/Development-tools/nrf-command-line-tools) installed. - -1. Connect your debugger as described [here](https://docs.brilliant.xyz/monocle/monocle/#manually-programming). - -1. Flash your device using the command: - -```sh -nrfjprog --program *.hex --chiperase -f nrf52 --verify --reset -``` - ---- +For those of you who want to modify the standard firmware, keep on reading. ## Getting started with development @@ -25,29 +10,59 @@ nrfjprog --program *.hex --chiperase -f nrf52 --verify --reset 1. Ensure you have the [nRF Command Line Tools](https://www.nordicsemi.com/Products/Development-tools/nrf-command-line-tools) installed. -1. Clone this repository, submodules and build: +1. Clone this repository along with submodules and build the mpy-cross toolchain: ```sh - # Clone and jump into this repo git clone https://github.com/brilliantlabsAR/monocle-micropython.git cd monocle-micropython - # Initialize the submodules git submodule update --init git -C micropython submodule update --init lib/micropython-lib - # Build mpy-cross toolchain make -C micropython/mpy-cross ``` -1. You can now close the terminal and open the project in [VSCode](https://code.visualstudio.com). +1. You should now be able to build the project by calling `make` from the `monocle-micropython` folder. + + ```sh + make + ``` - There are three build tasks already configured and ready for use. Access them by pressing `Ctrl-Shift-P` (`Cmd-Shift-P` on MacOS) → `Tasks: Run Task`. +1. Before flashing an nRF5340, you may need to unlock the chip first. + + ```sh + nrfjprog --recover + ``` - 1. Build (`Ctrl-B` / `Cmd-B`) - 1. Build & flash +1. You should then be able to flash the device. + + ```sh + make flash + ``` + +### Debugging + +1. Open the project in [VSCode](https://code.visualstudio.com). + + There are some build tasks already configured within `.vscode/tasks.json`. Access them by pressing `Ctrl-Shift-P` (`Cmd-Shift-P` on MacOS) → `Tasks: Run Task`. + + 1. Build + 1. Build & Flash Chip + 1. Erase & Unlock Chip 1. Clean +1. Connect your debugger as described [here](https://docs.brilliant.xyz/monocle/monocle/#manually-programming). + +1. You many need to unlock the device by using the `Erase Chip` task before programming or debugging. + +1. To enable IntelliSense, be sure to select the correct compiler from within VSCode. `Ctrl-Shift-P` (`Cmd-Shift-P` on MacOS) → `C/C++: Select IntelliSense Configuration` → `Use arm-none-eabi-gcc`. + +1. Install the [Cortex-Debug](https://marketplace.visualstudio.com/items?itemName=marus25.cortex-debug) extension for VSCode in order to enable debugging. + +1. A debugging launch is already configured within `.vscode/launch.json`. Run the `J-Link` launch configuration from the `Run and Debug` panel, or press `F5`. The project will automatically build and flash before launching. + +1. To monitor the logs, run the task `RTT Console` and ensure the `J-Link` launch configuration is running. + ### Generating final release `.hex` and DFU `.zip` files 1. Download and install [nrfutil](https://www.nordicsemi.com/Products/Development-tools/nRF-Util) including the `nrf5sdk-tools` package: diff --git a/config-tables.h b/config-tables.h index 10290a64..faa69947 100644 --- a/config-tables.h +++ b/config-tables.h @@ -228,7 +228,7 @@ const camera_config_t camera_config[] = { {0x4602, 0x02}, // JPEG output width (high byte) {0x4603, 0x80}, // JPEG output width (low byte) {0x4604, 0x80}, // JPEG output width (low byte) - {0x3820, 0x47}, // Flip image vertically + {0x3820, 0x40}, // Don't flip the image {0x3821, 0x27}, // TIMING_TC_REG_21 - enable JPEG, binning, flip image vertically {0x3002, 0x00}, // SYSTEM_RESET_2 - set everything on {0x3814, 0x31}, // Timing Y increment diff --git a/micropython b/micropython index 7d66ae60..a18d62e0 160000 --- a/micropython +++ b/micropython @@ -1 +1 @@ -Subproject commit 7d66ae603d91157069d7cdee065919a61f678fb9 +Subproject commit a18d62e06727e0c424da1f43f80a5b0cb76bcc04 diff --git a/modules/camera.c b/modules/camera.c index bd44a364..f4d6a39f 100644 --- a/modules/camera.c +++ b/modules/camera.c @@ -23,6 +23,7 @@ */ #include "nrf_gpio.h" +#include "nrfx_systick.h" #include "py/runtime.h" STATIC mp_obj_t camera_sleep(void) @@ -35,6 +36,7 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_0(camera_sleep_obj, camera_sleep); STATIC mp_obj_t camera_wake(void) { nrf_gpio_pin_write(CAMERA_SLEEP_PIN, false); + nrfx_systick_delay_ms(5); return mp_const_none; } STATIC MP_DEFINE_CONST_FUN_OBJ_0(camera_wake_obj, camera_wake); diff --git a/modules/camera.py b/modules/camera.py index 59e7e2a4..a5fc018e 100644 --- a/modules/camera.py +++ b/modules/camera.py @@ -40,7 +40,6 @@ def capture(): _camera.wake() - time.sleep_ms(1) fpga.write(0x1003, b"") while fpga.read(0x1000, 1) == b"2": time.sleep_us(10) diff --git a/modules/fpga.c b/modules/fpga.c index 0dfde056..882d3fc3 100644 --- a/modules/fpga.c +++ b/modules/fpga.c @@ -103,73 +103,11 @@ STATIC mp_obj_t fpga_run(size_t n_args, const mp_obj_t *args) } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(fpga_run_obj, 0, 1, fpga_run); -STATIC mp_obj_t fpga_version(void) -{ - uint8_t target_device[8]; - uint8_t application_version[13]; - uint8_t chip_revision[8]; - - memset(target_device, 0, sizeof(target_device)); - memset(application_version, 0, sizeof(application_version)); - memset(chip_revision, 0, sizeof(chip_revision)); - - uint8_t target_device_address[2] = {(uint8_t)(0x0001 >> 8), (uint8_t)0x0001}; - uint8_t application_version_address[2] = {(uint8_t)(0x0002 >> 8), (uint8_t)0x0002}; - uint8_t chip_revision_address[2] = {(uint8_t)(0x0003 >> 8), (uint8_t)0x0003}; - - monocle_spi_write(FPGA, target_device_address, 2, true); - monocle_spi_read(FPGA, target_device, 4, false); - - monocle_spi_write(FPGA, application_version_address, 2, true); - monocle_spi_read(FPGA, application_version, 12, false); - - monocle_spi_write(FPGA, chip_revision_address, 2, true); - monocle_spi_read(FPGA, chip_revision, 4, false); - - // In case the chip is off, or has no image, nothing will be returned - if (target_device[0] == 0xff) - { - strcpy((char *)target_device, "unknown"); - } - if (application_version[0] == 0xff) - { - strcpy((char *)application_version, "unknown"); - } - if (chip_revision[0] == 0xff) - { - strcpy((char *)chip_revision, "unknown"); - } - - // In case of legacy revC images, the register will return 'f' characters - if (memcmp(chip_revision, "ffff", 4) == 0) - { - strcpy((char *)chip_revision, "revC"); - } - - mp_obj_t return_dict = mp_obj_new_dict(0); - - mp_obj_dict_store(return_dict, - MP_OBJ_NEW_QSTR(MP_QSTR_target_device), - mp_obj_new_str((char *)target_device, strlen((char *)target_device))); - - mp_obj_dict_store(return_dict, - MP_OBJ_NEW_QSTR(MP_QSTR_application_version), - mp_obj_new_str((char *)application_version, strlen((char *)application_version))); - - mp_obj_dict_store(return_dict, - MP_OBJ_NEW_QSTR(MP_QSTR_chip_revision), - mp_obj_new_str((char *)chip_revision, strlen((char *)chip_revision))); - - return return_dict; -} -STATIC MP_DEFINE_CONST_FUN_OBJ_0(fpga_version_obj, fpga_version); - STATIC const mp_rom_map_elem_t fpga_module_globals_table[] = { {MP_ROM_QSTR(MP_QSTR_read), MP_ROM_PTR(&fpga_read_obj)}, {MP_ROM_QSTR(MP_QSTR_write), MP_ROM_PTR(&fpga_write_obj)}, {MP_ROM_QSTR(MP_QSTR_run), MP_ROM_PTR(&fpga_run_obj)}, - {MP_ROM_QSTR(MP_QSTR_version), MP_ROM_PTR(&fpga_version_obj)}, }; STATIC MP_DEFINE_CONST_DICT(fpga_module_globals, fpga_module_globals_table); diff --git a/mpconfigport.h b/mpconfigport.h index 47274312..9b1349e5 100644 --- a/mpconfigport.h +++ b/mpconfigport.h @@ -52,6 +52,7 @@ #define MICROPY_ERROR_REPORTING (MICROPY_ERROR_REPORTING_DETAILED) #define MICROPY_PY_ASYNCIO (1) +#define MICROPY_PY_IO_IOBASE (1) #define MICROPY_MODULE_WEAK_LINKS (1) diff --git a/tools/camera_test_script.py b/tools/camera_test_script.py new file mode 100755 index 00000000..0d4f15a0 --- /dev/null +++ b/tools/camera_test_script.py @@ -0,0 +1,103 @@ +import asyncio +import sys + +from bleak import BleakClient, BleakScanner +from bleak.backends.characteristic import BleakGATTCharacteristic +from bleak.backends.device import BLEDevice +from bleak.backends.scanner import AdvertisementData + +from PIL import Image +import io + +REPL_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" +REPL_TX_CHAR_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" +REPL_RX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" + +DATA_SERVICE_UUID = "e5700001-7bac-429a-b4ce-57ff900f479d" +DATA_TX_CHAR_UUID = "e5700002-7bac-429a-b4ce-57ff900f479d" +DATA_RX_CHAR_UUID = "e5700003-7bac-429a-b4ce-57ff900f479d" + +file = [ + """import bluetooth""", + """import camera""", + """import time""", + """camera.capture()""", + """time.sleep_ms(250)""", + """def send(data):""", + """ while True:""", + """ try:""", + """ bluetooth.send(data)""", + """ break""", + """ except OSError:""", + """ pass""", + """send("START")""", + """while True:""", + """ image_data = camera.read(bluetooth.max_length())""", + """ if image_data == None:""", + """ break""", + """ send(image_data)""", + """send("END")""", +] + +image_data = b"" + + +async def main(): + def match_repl_uuid(device: BLEDevice, adv: AdvertisementData): + return REPL_SERVICE_UUID in adv.service_uuids + + def repl_rx(_: BleakGATTCharacteristic, data: bytearray): + # print(data.decode("utf-8").strip("\r\n")) # Uncomment for debug + pass + + def data_rx(_: BleakGATTCharacteristic, data: bytearray): + global image_data + if data == b"START": + print("Downloading image data\n") + + elif data == b"END": + print("Saving image") + image = Image.open(io.BytesIO(bytes(image_data))) + image.show() + print("Done") + sys.exit(0) + + else: + image_data += data + print(f"\033[FDownloaded {len(image_data)} bytes") + + print("Searching for device") + + device = await BleakScanner.find_device_by_filter(match_repl_uuid) + + if device is None: + print("No device found") + sys.exit(1) + + print("Connected") + + async with BleakClient(device) as c: + await c.start_notify(REPL_RX_CHAR_UUID, repl_rx) + await c.start_notify(DATA_RX_CHAR_UUID, data_rx) + + repl = c.services.get_service(REPL_SERVICE_UUID) + repl_tx = repl.get_characteristic(REPL_TX_CHAR_UUID) + + await c.write_gatt_char(repl_tx, b"\x03\x01") + await c.write_gatt_char(repl_tx, b"f=open('main.py', 'w')\x04") + + await c.write_gatt_char(repl_tx, b"f.write('''") + + for line in file: + await c.write_gatt_char(repl_tx, line.encode() + b"\n") + + await c.write_gatt_char(repl_tx, b"''')\x04") + + await c.write_gatt_char(repl_tx, b"f.close()\x04") + await c.write_gatt_char(repl_tx, b"\x04") + + while True: + await asyncio.sleep(1) + + +asyncio.run(main()) diff --git a/tools/serial_console.py b/tools/serial_console.py old mode 100755 new mode 100644 index 0e10f13b..90f61310 --- a/tools/serial_console.py +++ b/tools/serial_console.py @@ -23,17 +23,17 @@ UART_SERVICE_UUID = "6e400001-b5a3-f393-e0a9-e50e24dcca9e" -UART_RX_CHAR_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" -UART_TX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" +UART_TX_CHAR_UUID = "6e400002-b5a3-f393-e0a9-e50e24dcca9e" +UART_RX_CHAR_UUID = "6e400003-b5a3-f393-e0a9-e50e24dcca9e" DATA_SERVICE_UUID = "e5700001-7bac-429a-b4ce-57ff900f479d" -DATA_RX_CHAR_UUID = "e5700002-7bac-429a-b4ce-57ff900f479d" -DATA_TX_CHAR_UUID = "e5700003-7bac-429a-b4ce-57ff900f479d" +DATA_TX_CHAR_UUID = "e5700002-7bac-429a-b4ce-57ff900f479d" +DATA_RX_CHAR_UUID = "e5700003-7bac-429a-b4ce-57ff900f479d" register_uuids({ DATA_SERVICE_UUID: "Monocle Raw Serivce", - DATA_TX_CHAR_UUID: "Monocle Raw TX", DATA_RX_CHAR_UUID: "Monocle Raw RX", + DATA_TX_CHAR_UUID: "Monocle Raw TX", }) # You can get this function and more from the ``more-itertools`` package. @@ -90,21 +90,21 @@ def prompt(): sys.exit(1) else: sys.stderr.write(f"connected\n") - sys.stderr.write('Ctrl-D: Reboot in normal mode\n") - sys.stderr.write('Ctrl-\\: Reboot in safe mode\n") - sys.stderr.write('Ctrl-C: Cancel ongoing script\n") - sys.stderr.write('Ctrl-E, "code", Ctrl-D: paste mode\n") + sys.stderr.write('Ctrl-D: Reboot in normal mode\n') + sys.stderr.write('Ctrl-\\: Reboot in safe mode\n') + sys.stderr.write('Ctrl-C: Cancel ongoing script\n') + sys.stderr.write('Ctrl-E, "code", Ctrl-D: paste mode\n') sys.stderr.write('Ctrl-V, "text", Enter: send to raw Bluetooth service\n') async with BleakClient(device, disconnected_callback=handle_disconnect) as client: - await client.start_notify(UART_TX_CHAR_UUID, handle_repl_rx) - await client.start_notify(DATA_TX_CHAR_UUID, handle_data_rx) + await client.start_notify(UART_RX_CHAR_UUID, handle_repl_rx) + await client.start_notify(DATA_RX_CHAR_UUID, handle_data_rx) loop = asyncio.get_running_loop() repl = client.services.get_service(UART_SERVICE_UUID) data = client.services.get_service(DATA_SERVICE_UUID) - repl_rx_char = repl.get_characteristic(UART_RX_CHAR_UUID) - data_rx_char = data.get_characteristic(DATA_RX_CHAR_UUID) + repl_tx_char = repl.get_characteristic(UART_TX_CHAR_UUID) + data_tx_char = data.get_characteristic(DATA_TX_CHAR_UUID) # set the terminal to raw I/O: no buffering if sys.stdin.isatty(): @@ -119,9 +119,9 @@ def prompt(): sys.stderr.write(f'TX: ') sys.stderr.flush() line = await loop.run_in_executor(None, prompt) - await client.write_gatt_char(data_rx_char, line.rstrip()) + await client.write_gatt_char(data_tx_char, line.rstrip()) else: - await client.write_gatt_char(repl_rx_char, ch) + await client.write_gatt_char(repl_tx_char, ch) if __name__ == "__main__":