├── .vscode ├── c_cpp_properties.json ├── launch.json ├── settings.json └── tasks.json ├── LICENSE ├── README.en.md ├── README.md ├── examples ├── DownloadFromSerial │ └── DownloadFromSerial.ino ├── LaunchExtFlash │ └── LaunchExtFlash.ino └── WriteSampleMenu │ ├── WriteSampleMenu.ino │ └── menu_data.h ├── extflash.py ├── figure ├── arduino_library_manager.png ├── arduino_library_manager_extflashloader.png └── arduino_sketch_examples_extflashloader.png ├── library.json ├── library.properties ├── openocd.tcl └── src ├── ExtFlashLoader.h └── ExtFlashLoader.hpp /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${workspaceFolder}/**", 7 | "${env:HONE}/.arduino15/packages/Seeeduino/tools/arm-none-eabi-gcc/7-2017q4/arm-none-eabi/include", 8 | "${env:HOME}/.arduino15/packages/Seeeduino/tools/CMSIS/**", 9 | "${env:HOME}/.arduino15/packages/Seeeduino/tools/CMSIS-Atmel/**", 10 | "${env:HOME}/.arduino15/packages/Seeeduino/hardware/samd/1.7.5/variants/wio_terminal", 11 | "${env:HOME}/.arduino15/packages/Seeeduino/hardware/samd/1.7.5/**", 12 | "${env:HOME}/.arduino15/libraries/**" 13 | 14 | ], 15 | "forcedInclude": [ 16 | "${env:HOME}/.arduino15/packages/Seeeduino/hardware/samd/1.7.5/cores/arduino/Arduino.h" 17 | ], 18 | "defines": [], 19 | "compilerPath": ".arduino15/packages/Seeeduino/tools/arm-none-eabi-gcc/7-2017q4/bin/arm-none-eabi-gcc", 20 | "cStandard": "c11", 21 | "cppStandard": "c++14", 22 | "intelliSenseMode": "clang-x64" 23 | } 24 | ], 25 | "version": 4 26 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // IntelliSense を使用して利用可能な属性を学べます。 3 | // 既存の属性の説明をホバーして表示します。 4 | // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "(gdb) launch", 9 | "type": "cppdbg", 10 | "request": "launch", 11 | "program": "${workspaceFolder}/sd_updater.Seeeduino.samd.seeed_wio_terminal.elf", 12 | "cwd": "${workspaceFolder}", 13 | "stopAtEntry": true, 14 | "targetArchitecture": "arm", 15 | "MIMode": "gdb", 16 | "setupCommands": [ 17 | { 18 | "description": "gdb の再フォーマットを有効にする", 19 | "text": "-enable-pretty-printing", 20 | "ignoreFailures": true 21 | }, 22 | { 23 | "description": "load symbols", 24 | "text": "symbol-file ${workspaceFolder}/sd_updater.Seeeduino.samd.seeed_wio_terminal.elf" 25 | } 26 | ], 27 | "miDebuggerPath": "/usr/bin/gdb-multiarch", 28 | "miDebuggerServerAddress": "localhost:3333", 29 | "miDebuggerArgs": "", 30 | "debugServerPath": "${env:HOME}/openocd/bin/openocd", 31 | "debugServerArgs": "-s ${env:HOME}/openocd/share/openocd/scripts -f ${workspaceFolder}/openocd.tcl -c \"flash_bin ${workspaceFolder}/sd_updater.Seeeduino.samd.seeed_wio_terminal.bin\" -c \"echo {gdb server started}\"", 32 | "serverStarted": "gdb server started", 33 | "filterStderr": true, 34 | "filterStdout": false, 35 | "logging": { 36 | "engineLogging": true 37 | }, 38 | "preLaunchTask": "build" 39 | } 40 | ] 41 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "definitions.h": "c", 4 | "plib_port.h": "c", 5 | "array": "cpp", 6 | "atomic": "cpp", 7 | "bit": "cpp", 8 | "*.tcc": "cpp", 9 | "cctype": "cpp", 10 | "clocale": "cpp", 11 | "cmath": "cpp", 12 | "cstdarg": "cpp", 13 | "cstddef": "cpp", 14 | "cstdint": "cpp", 15 | "cstdio": "cpp", 16 | "cstdlib": "cpp", 17 | "cwchar": "cpp", 18 | "cwctype": "cpp", 19 | "deque": "cpp", 20 | "unordered_map": "cpp", 21 | "vector": "cpp", 22 | "exception": "cpp", 23 | "algorithm": "cpp", 24 | "functional": "cpp", 25 | "iterator": "cpp", 26 | "memory": "cpp", 27 | "memory_resource": "cpp", 28 | "numeric": "cpp", 29 | "optional": "cpp", 30 | "random": "cpp", 31 | "string": "cpp", 32 | "string_view": "cpp", 33 | "system_error": "cpp", 34 | "tuple": "cpp", 35 | "type_traits": "cpp", 36 | "utility": "cpp", 37 | "fstream": "cpp", 38 | "initializer_list": "cpp", 39 | "iosfwd": "cpp", 40 | "iostream": "cpp", 41 | "istream": "cpp", 42 | "limits": "cpp", 43 | "new": "cpp", 44 | "ostream": "cpp", 45 | "sstream": "cpp", 46 | "stdexcept": "cpp", 47 | "streambuf": "cpp", 48 | "typeinfo": "cpp" 49 | } 50 | } -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | // See https://go.microsoft.com/fwlink/?LinkId=733558 3 | // for the documentation about the tasks.json format 4 | "version": "2.0.0", 5 | "tasks": [ 6 | { 7 | "label": "build", 8 | "type": "shell", 9 | "options": { 10 | "cwd": "${workspaceFolder}" 11 | }, 12 | "command": "${env:HOME}/arduino-cli/bin/arduino-cli compile --optimize-for-debug --fqbn Seeeduino:samd:seeed_wio_terminal --additional-urls https://files.seeedstudio.com/arduino/package_seeeduino_boards_index.json", 13 | "problemMatcher": [ 14 | "$gcc" 15 | ] 16 | } 17 | ] 18 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.en.md: -------------------------------------------------------------------------------- 1 | # External Flash Loader library for Wio Terminal 2 | 3 | ## What Is This? 4 | 5 | This is an Arduino library to access external flash memory connected to ATSAMD51 on Wio Terminal. 6 | 7 | And this Arduino library also contains a sample sketch to flash the menu application, which select and load applications from the TF card, by using the library function. 8 | 9 | Applications can return to the menu app by embedding codes to launch the menu app if the button is pressed. 10 | 11 | The `LaunchExtFlash.ino` sample sketch launches the menu app by resetting the Wio Terminal while the button A is pressed. 12 | 13 | [![ExtFlashLoaderの動作](https://img.youtube.com/vi/lPJtOYFQees/0.jpg)](https://www.youtube.com/watch?v=lPJtOYFQees) 14 | 15 | ## How To Use 16 | 17 | ### How To Install This Library 18 | 19 | Select `ExtFlashLoader` from the library manager of Arduino IDE and install it. 20 | 21 | ![Library Manager](figure/arduino_library_manager.png) 22 | 23 | ![ExtFlashLoader](figure/arduino_library_manager_extflashloader.png) 24 | 25 | After installing the library completes, the sample sketches of `ExtFlashLoader` is added to the sketch example menu of Arduino IDE. 26 | 27 | ![Sample Sketch](figure/arduino_sketch_examples_extflashloader.png) 28 | 29 | ### Writing the menu app 30 | 31 | The menu app must have been written to the external flash to use the menu app. 32 | 33 | Becase the bootloader of Wio Terminal cannot write to the external flash, the user must use `WriteSampleMenu.ino` sample sketch to write the menu app to the external flash. 34 | 35 | `WriteSampleMenu.ino` contains the binary of the menu app and it automatically writes the menu app to the external flash after launched. 36 | 37 | If the sample sketch writes the menu app successfully, then the menu app starts. 38 | 39 | ### How to use the menu app 40 | 41 | The menu app shows the list of applications in `/apps` directory of the TF card. 42 | 43 | Every application must be contained for its dedicated direcory in the `apps` directory. 44 | 45 | ``` 46 | /apps 47 | +- app1 48 | +- app.bin --- app1's binary 49 | +- app.png --- Image file to describe the contents of app1 (Optional) 50 | +- name --- File which contains the name of app1 51 | +- desc --- File which contains the description of app1 (Optional) 52 | +- app2 53 | +- app.bin --- app2's binary 54 | +- app.png --- Image file to describe the contents of app2 (Optional) 55 | +- name --- File which contains the name of app2 56 | +- desc --- File which contains the description of app2 (Optional) 57 | +- hoge 58 | +- app.bin --- hoge's binary 59 | +- app.png --- Image file to describe the contents of hoge (Optional) 60 | +- name --- File which contains the name of hoge 61 | +- desc --- File which contains the description of hoge (Optional) 62 | +- fuga 63 | +- app.bin --- fuga's binary 64 | +- app.png --- Image file to describe the contents of fuga (Optional) 65 | +- name --- File which contains the name of fuga 66 | +- desc --- File which contains the description of fuga (Optional) 67 | ``` 68 | 69 | Store application's binary as `app.bin`. 70 | 71 | `name` is a file which contains application's name. Maximum length of the `name` file is 64 bytes. 72 | 73 | `desc` is a file which contains application's description. Maximum length of the `desc` file is 64 bytes. 74 | 75 | `app.png` is an image file which describes the application. Size of the area to show these image files is 160x120, thus size of the image must be less than 160x120. 76 | 77 | If you insert the TF card that stores the application with the above structure into the Wio Terminal, a list of applications will be displayed. 78 | 79 | Select an application with the up and down of the stick and push the stick to write the selected application to the built-in flash. 80 | 81 | For trial purposes, the sample code of [LovyanGFX](https://github.com/lovyan03/LovyanGFX) with suporting to return to the menu app is available. [You can download it here. ] (https://github.com/ciniml/ExtFlashLoader/releases/download/0.1.0/extflashloader_sample.zip) 82 | 83 | After downloading it, extract the ZIP file, then write the `apps` directory that appears in the root of the TF card. 84 | 85 | ### How To Implement Menu App Support 86 | 87 | The menu app on the external flash cannot be started from the boot loader of Wio Terminal. 88 | 89 | Therefore, it is necessary to add some codes to start the menu app at the time of startup of the main app. 90 | 91 | In the attached sample sketch `LaunchExtFlash.ino`, if the `button A` is pressed at startup, the menu app will be launched. 92 | 93 | ```LaunchExtFlash.ino 94 | #include 95 | #include 96 | #include 97 | 98 | TFT_eSPI tft; 99 | 100 | void setup() { 101 | tft.begin(); 102 | tft.setRotation(3); 103 | tft.fillScreen(0); 104 | 105 | pinMode(WIO_KEY_A, INPUT_PULLUP); 106 | if( digitalRead(WIO_KEY_A) == LOW) { 107 | tft.printf("Launching QSPI application\r\n"); 108 | ExtFlashLoader::ExtFlashLoader loader; 109 | } 110 | 111 | tft.printf("Normal flash application\r\n"); 112 | 113 | Serial.begin(115200); 114 | while(!Serial); 115 | } 116 | void loop() { 117 | 118 | } 119 | ``` 120 | 121 | On the `13th line`, check if the input of button A (`WIO_KEY_A`) is pressed (==`LOW`). 122 | 123 | If button A is pressed, execute `ExtFlashLoader::ExtFlashLoader loader;` and launch the menu app on the external flash. 124 | 125 | You can create an application that supports loading from the TF card by embedding the above code into the application. 126 | 127 | ## License 128 | 129 | The license of the main module `ExtFlashLoader.hpp` is `Boost Software License 1.0`. 130 | You can use the code freely as long as you keep the license code of the source code. 131 | 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # External Flash Loader library for Wio Terminal 2 | 3 | [English page](./README.en.md) 4 | 5 | ## 概要 6 | 7 | Wio TerminalのATSAMD51に接続されている外部フラッシュメモリに対してアクセスするためのArduino用ライブラリです。 8 | 9 | また、ライブラリの機能を用いて、TFカードからアプリケーションを選択してロードするメニューアプリを書き込むためのサンプルスケッチも含みます。 10 | 11 | TFカードからロードされるメインアプリケーションには、ボタンが押されている場合にメニューアプリを起動するコードを埋め込んでおくことにより、メインアプリケーションからメニューアプリへの復帰できるようになります。 12 | 13 | 付属の `LaunchExtFlash.ino` サンプルスケッチでは、Wio TerminalのボタンA を押した状態でリセットすることにより、メニューアプリを起動します。 14 | 15 | [![ExtFlashLoaderの動作](https://img.youtube.com/vi/lPJtOYFQees/0.jpg)](https://www.youtube.com/watch?v=lPJtOYFQees) 16 | 17 | ## 使い方 18 | 19 | ### ライブラリのインストール 20 | 21 | Arduino IDEのライブラリマネージャから `ExtFlashLoader` を選択してインストールします。 22 | 23 | ![ライブラリマネージャ](figure/arduino_library_manager.png) 24 | 25 | ![ExtFlashLoader](figure/arduino_library_manager_extflashloader.png) 26 | 27 | インストールが完了すると、スケッチ例に `ExtFlashLoader` のサンプルスケッチが追加されます。 28 | 29 | ![サンプルスケッチ](figure/arduino_sketch_examples_extflashloader.png) 30 | 31 | ### メニューアプリの書き込み 32 | 33 | メニューアプリを使うためには、外部フラッシュにメニューアプリを書き込んでおく必要があります。 34 | 35 | Wio Terminal標準の機能では外部フラッシュに書き込みを行えないので、`WriteSampleMenu.ino` サンプルスケッチを使ってメニューアプリを外部フラッシュに書き込みます。 36 | 37 | `WriteSampleMenu.ino`はメニューアプリのバイナリを内蔵しており、起動すると外部フラッシュにメニューアプリを自動的に書き込みます。 38 | 39 | 書き込みが成功すると、メニューアプリが起動します。 40 | 41 | ### メニューアプリの使い方 42 | 43 | 付属のメニューアプリは、TFカードの `/apps` ディレクトリにあるアプリケーションを一覧表示するようになっています。 44 | 45 | 各アプリケーションは個別のディレクトリに分けて格納します。 46 | 47 | ``` 48 | /apps 49 | +- app1 50 | +- app.bin --- app1のバイナリ 51 | +- app.png --- app1の内容を表す画像 (オプション) 52 | +- name --- app1の名前を含むテキストファイル 53 | +- desc --- app1の説明文を含むテキストファイル (オプション) 54 | +- app2 55 | +- app.bin --- app2のバイナリ 56 | +- app.png --- app2の内容を表す画像 (オプション) 57 | +- name --- app2の名前を含むテキストファイル 58 | +- desc --- app2の説明文を含むテキストファイル (オプション) 59 | +- hoge 60 | +- app.bin --- hogeのバイナリ 61 | +- app.png --- hogeの内容を表す画像 (オプション) 62 | +- name --- hogeの名前を含むテキストファイル 63 | +- desc --- hogeの説明文を含むテキストファイル (オプション) 64 | +- fuga 65 | +- app.bin --- fugaのバイナリ 66 | +- app.png --- fugaの内容を表す画像 (オプション) 67 | +- name --- fugaの名前を含むテキストファイル 68 | +- desc --- fugaの説明文を含むテキストファイル (オプション) 69 | ``` 70 | 71 | アプリケーションのバイナリを `app.bin`として格納します。 72 | 73 | `name` はアプリケーションの名前を含むテキストファイルです。最大64バイトまでです。 74 | 75 | `desc` はアプリケーションの説明文を含むテキストファイルです。最大64バイトまでです。 76 | 77 | `app.png` はアプリケーションの内容を表す画像ファイルです。表示領域は160x120ですので、それ以下の画像を格納します。 78 | 79 | 上記の構造でアプリケーションを格納したあTFカードをWio Terminalに挿入すると、アプリケーションの一覧をが表示されます。 80 | 81 | スティックの上下でアプリケーションを選択し、スティックを押し込むと、選択中のアプリケーションを内蔵フラッシュに書き込みます。 82 | 83 | 書き込み後、現在は不具合によりアプリケーションを起動できていませんので、電源スイッチをリセット方向にスライドしてリセットをかけてください。 84 | 85 | お試し用に [LovyanGFX](https://github.com/lovyan03/LovyanGFX) のサンプルコードをメニュー対応にしたものを用意してあります。[ここからダウンロード出来ます。](https://github.com/ciniml/ExtFlashLoader/releases/download/0.1.0/extflashloader_sample.zip) 86 | ダウンロードしてZIPファイルを展開し、出てきた `apps` ディレクトリをSDカードのルートに書き込んでください。 87 | 88 | ### アプリケーションのメニューアプリ対応 89 | 90 | Wio Terminal出荷時に書き込まれているブートローダから外部フラッシュ上のメニューアプリを起動することはできません。 91 | 92 | そのため、メインアプリケーション側で起動時に必要に応じてメニューアプリを起動するコードを追加する必要があります。 93 | 94 | 付属のサンプルスケッチ `LaunchExtFlash.ino` では、起動時に `ボタンA` が押されている場合、メニューアプリを起動します。 95 | 96 | ```LaunchExtFlash.ino 97 | #include 98 | #include 99 | #include 100 | 101 | TFT_eSPI tft; 102 | 103 | void setup() { 104 | tft.begin(); 105 | tft.setRotation(3); 106 | tft.fillScreen(0); 107 | 108 | pinMode(WIO_KEY_A, INPUT_PULLUP); 109 | if( digitalRead(WIO_KEY_A) == LOW) { 110 | tft.printf("Launching QSPI application\r\n"); 111 | ExtFlashLoader::ExtFlashLoader loader; 112 | } 113 | 114 | tft.printf("Normal flash application\r\n"); 115 | 116 | Serial.begin(115200); 117 | while(!Serial); 118 | } 119 | void loop() { 120 | 121 | } 122 | ``` 123 | 124 | `13行目`で`WIO_KEY_A`、つまりボタンAの入力がボタンが押されている(==`LOW`)かどうか確認します。 125 | 126 | ボタンAが押されている場合は、 `ExtFlashLoader::ExtFlashLoader loader;` を実行し、外部フラッシュ上のメニューアプリを起動します。 127 | 128 | アプリケーションに上記のコードを埋め込むことにより、TFカードからのロードに対応したアプリケーションを作ることが出来ます。 129 | 130 | ## ライセンス 131 | 132 | ライブラリの本体 `ExtFlashLoader.hpp`のライセンスは、 `Boost Software License 1.0` です。 133 | ソースコードのライセンス表記を残している限り、自由にコードを使用することが出来ます。 134 | 135 | -------------------------------------------------------------------------------- /examples/DownloadFromSerial/DownloadFromSerial.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TFT_eSPI tft; 6 | ExtFlashLoader::QSPIFlash qspiFlash; 7 | ExtFlashLoader::SerialDownloader downloader(qspiFlash); 8 | 9 | void setup() { 10 | tft.begin(); 11 | tft.setRotation(3); 12 | tft.fillScreen(0); 13 | 14 | qspiFlash.initialize(); 15 | qspiFlash.reset(); 16 | auto id = qspiFlash.readId(); 17 | tft.printf("Flash detected ID: %02x, %04x\r\n", id.manufacturer, id.product); 18 | 19 | tft.printf("Waiting for serial connection...\r\n"); 20 | 21 | Serial.begin(115200); 22 | while(!Serial); 23 | } 24 | void loop() { 25 | downloader.run(); 26 | } 27 | -------------------------------------------------------------------------------- /examples/LaunchExtFlash/LaunchExtFlash.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | TFT_eSPI tft; 6 | 7 | void setup() { 8 | tft.begin(); 9 | tft.setRotation(3); 10 | tft.fillScreen(0); 11 | 12 | pinMode(WIO_KEY_A, INPUT_PULLUP); 13 | if( digitalRead(WIO_KEY_A) == LOW) { 14 | tft.printf("Launching QSPI application\r\n"); 15 | ExtFlashLoader::ExtFlashLoader loader; 16 | } 17 | 18 | tft.printf("Normal flash application\r\n"); 19 | 20 | Serial.begin(115200); 21 | while(!Serial); 22 | } 23 | void loop() { 24 | 25 | } -------------------------------------------------------------------------------- /examples/WriteSampleMenu/WriteSampleMenu.ino: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "menu_data.h" 5 | 6 | TFT_eSPI tft; 7 | ExtFlashLoader::QSPIFlash qspiFlash; 8 | 9 | void setup() { 10 | tft.begin(); 11 | tft.setRotation(3); 12 | tft.fillScreen(0); 13 | 14 | qspiFlash.initialize(); 15 | qspiFlash.reset(); 16 | auto id = qspiFlash.readId(); 17 | tft.printf("Flash detected ID: %02x, %04x\r\n", id.manufacturer, id.product); 18 | 19 | tft.printf("Updating external flash...\r\n"); 20 | 21 | auto result = ExtFlashLoader::writeExternalFlash(qspiFlash, 0, menu_data, menu_data_len, [](std::size_t bytes_processed, std::size_t bytes_total, bool verifying){ 22 | tft.drawCentreString(verifying ? "Verifying..." : "Writing...", 160, 100, 0); 23 | tft.fillRect(40, 120, 240*bytes_processed/bytes_total, 32, tft.color565(0, verifying?255:0, 255)); 24 | tft.drawRect(40-1, 120-1, 240+2, 32+2, tft.color565(255, 255, 255)); 25 | return true; 26 | }); 27 | if( result ) { 28 | tft.printf("Launching menu..."); 29 | ExtFlashLoader::runQSPIApplication(qspiFlash, 0x04000000); 30 | } 31 | else { 32 | tft.printf("Failed to load menu app into the external flash"); 33 | } 34 | } 35 | void loop() { 36 | } 37 | -------------------------------------------------------------------------------- /extflash.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import serial 4 | import struct 5 | import intelhex 6 | 7 | port = serial.Serial('/dev/ttyACM0', baudrate=115200) 8 | 9 | class SerialDownloader(object): 10 | HOST_MARKER = 0xa5 11 | DEVICE_MARKER = 0x5a 12 | 13 | CMD_IDENTIFY = 0x00 14 | CMD_BEGINREAD = 0x01 15 | CMD_BEGINWRITE = 0x02 16 | CMD_ERASE = 0x03 17 | CMD_READ = 0x04 18 | CMD_WRITE = 0x05 19 | CMD_RUN = 0x06 20 | 21 | def __init__(self, port: serial.Serial): 22 | self.__port = port 23 | 24 | def waitResponse(self): 25 | while True: 26 | data = self.__port.read() 27 | if data[0] == SerialDownloader.DEVICE_MARKER: 28 | break 29 | 30 | def readResponse(self): 31 | return self.__port.read() 32 | 33 | def makeCommand(self, command:int, length:int, address:int): 34 | return struct.pack(' 65535: 75 | return None 76 | self.writeCommand(SerialDownloader.CMD_READ, length, address) 77 | self.waitResponse() 78 | result = self.__port.read(1) 79 | if result[0] != SerialDownloader.CMD_READ: 80 | return None 81 | return self.__port.read(length) 82 | 83 | def run(self, address:int): 84 | self.writeCommand(SerialDownloader.CMD_RUN, 0, address) 85 | 86 | downloader = SerialDownloader(port) 87 | print('Identifying the target\n') 88 | rom_id = downloader.identify() 89 | print(f'id: {rom_id}') 90 | 91 | import io 92 | with open('/home/kenta/wio_terminal/lcd_backlight_control/lcd_backlight_control.Seeeduino.samd.seeed_wio_terminal.bin', 'rb') as f: 93 | code = f.read() 94 | header = struct.unpack('= 256: 104 | downloader.write(block, code[block:block+256]) 105 | else: 106 | remaining = len(code) - block 107 | block_with_padding = code[block:block+remaining] + bytes([0xff]*(256 - remaining)) 108 | downloader.write(block, block_with_padding) 109 | 110 | # Verify codes 111 | print('Verifying blocks...') 112 | block_size = 512 113 | verified = True 114 | for trial in range(2): 115 | downloader.beginRead() 116 | for block in range(0, len(code), block_size): 117 | remaining = len(code) - block 118 | if remaining > block_size: remaining = block_size 119 | verify = downloader.read(0x04000000 + block, block_size) 120 | for i in range(remaining): 121 | expected = code[block + i] 122 | actual = verify[i] 123 | if expected != actual: 124 | print(f'Verify failed at 0x{(block+i):06X}: expected 0x{expected:02X} actual 0x{actual:02X}') 125 | verified = False 126 | break 127 | 128 | if verified: 129 | verify_header = downloader.read(0x04000000, 8) 130 | header = struct.unpack(' 5 | sentence=Downloads application binary to external flash memory connected to ATSAMD51 MCU and run it. 6 | paragraph=Downloads application binary to external flash memory connected to ATSAMD51 MCU and run it. 7 | category=Other 8 | url=https://github.com/ciniml/ExtFlashLoader 9 | includes=ExtFlashLoader.hpp 10 | architectures=samd -------------------------------------------------------------------------------- /openocd.tcl: -------------------------------------------------------------------------------- 1 | interface ftdi 2 | ftdi_device_desc "Dual RS232" 3 | ftdi_vid_pid 0x0403 0x6010 4 | 5 | ftdi_layout_init 0x0508 0x0f1b 6 | ftdi_layout_signal nTRST -data 0 7 | ftdi_layout_signal nSRST -data 0x0020 8 | 9 | transport select swd 10 | ftdi_layout_signal SWD_EN -data 0 11 | 12 | adapter_nsrst_delay 100 13 | adapter_nsrst_assert_width 100 14 | 15 | set CHIPNAME samd51p19a 16 | source [find target/atsame5x.cfg] 17 | 18 | init 19 | targets 20 | 21 | proc flash_bin {bin_file} { 22 | reset halt 23 | set file_size [file size $bin_file] 24 | set end_addr [expr $file_size + 0x4000] 25 | for {set addr 0x4000} {$addr < $end_addr} {incr addr 0x2000} { 26 | flash erase_address $addr 0x2000 27 | sleep 200 28 | } 29 | flash write_image $bin_file 0x4000 30 | verify_image $bin_file 0x4000 31 | echo "flashing $bin_file complete" 32 | reset halt 33 | } 34 | -------------------------------------------------------------------------------- /src/ExtFlashLoader.h: -------------------------------------------------------------------------------- 1 | #include -------------------------------------------------------------------------------- /src/ExtFlashLoader.hpp: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: BSL-1.0 2 | // Copyright 2020 Kenta IDA 3 | #ifndef EXTFLASHLOADER_HPP__ 4 | #define EXTFLASHLOADER_HPP__ 5 | 6 | #undef min 7 | #include 8 | #include 9 | 10 | #include "sam.h" 11 | #include "variant.h" 12 | 13 | 14 | namespace ExtFlashLoader 15 | { 16 | struct GPIORegs 17 | { 18 | std::uint32_t DIR ; 19 | std::uint32_t DIRCLR; 20 | std::uint32_t DIRSET; 21 | std::uint32_t DIRTGL; 22 | std::uint32_t OUT ; 23 | std::uint32_t OUTCLR; 24 | std::uint32_t OUTSET; 25 | std::uint32_t OUTTGL; 26 | std::uint32_t IN ; 27 | std::uint32_t CTRL ; 28 | std::uint32_t WRCONFIG; 29 | std::uint32_t EVCTRL; 30 | std::uint8_t PMUX[16]; 31 | std::uint8_t PINCFG[32]; 32 | }; 33 | 34 | static volatile GPIORegs* const PORT_GROUP0 = reinterpret_cast(0x41008000 + 0x80*0); 35 | static volatile GPIORegs* const PORT_GROUP1 = reinterpret_cast(0x41008000 + 0x80*1); 36 | static volatile GPIORegs* const PORT_GROUP2 = reinterpret_cast(0x41008000 + 0x80*2); 37 | static volatile GPIORegs* const PORT_GROUP3 = reinterpret_cast(0x41008000 + 0x80*3); 38 | 39 | class QSPIFlash 40 | { 41 | private: 42 | static constexpr const std::uintptr_t baseAddress = 0x42003400; 43 | struct Regs 44 | { 45 | std::uint32_t CTRLA; 46 | std::uint32_t CTRLB; 47 | std::uint32_t BAUD; 48 | std::uint32_t RXDATA; 49 | std::uint32_t TXDATA; 50 | std::uint32_t INTENCLR; 51 | std::uint32_t INTENSET; 52 | std::uint32_t INTFLAG; 53 | std::uint32_t STATUS; 54 | std::uint32_t _Reserved0[3]; 55 | std::uint32_t INSTRADDR; 56 | std::uint32_t INSTRCTRL; 57 | std::uint32_t INSTRFRAME; 58 | std::uint32_t _Reserved1; 59 | std::uint32_t SCRAMBCTRL; 60 | std::uint32_t SCRAMBKEY; 61 | }; 62 | volatile Regs* const regs = reinterpret_cast(baseAddress); 63 | volatile std::uint32_t* const CMCC_MAINT0 = reinterpret_cast(0x41006000 + 0x20); 64 | 65 | bool isMemoryMode = false; 66 | bool isInitialized = false; 67 | 68 | void waitDataRegisterEmpty() 69 | { 70 | while( !(regs->INTFLAG & (1u << 1)) ); 71 | } 72 | void waitTransferComplete() 73 | { 74 | while( !(regs->INTFLAG & (1u << 2) )); 75 | } 76 | void waitReceiveDataFull() 77 | { 78 | while( !(regs->INTFLAG & (1u << 0) )); 79 | } 80 | uint8_t transmit(std::uint8_t value, bool isFirst, bool deassertCS) 81 | { 82 | if( !isFirst ) { 83 | this->waitReceiveDataFull(); 84 | } 85 | std::uint8_t rxdata = regs->RXDATA; 86 | this->waitDataRegisterEmpty(); 87 | regs->TXDATA = value; 88 | this->setLastTransfer(deassertCS); 89 | return rxdata; 90 | } 91 | void setLastTransfer(bool isLastTransfer) 92 | { 93 | regs->CTRLA = ((isLastTransfer ? (1u << 24) : 0) | (1u << 1)); 94 | } 95 | void transmit(const std::uint8_t* txData, std::uint8_t* rxBuffer, std::size_t bytesToTransfer, bool keepCSAsserted = false) 96 | { 97 | if( bytesToTransfer == 0 ) { 98 | return; 99 | } 100 | bytesToTransfer--; 101 | transmit(*txData++, true, bytesToTransfer == 0 && !keepCSAsserted); 102 | if( bytesToTransfer > 0 ) { 103 | if( rxBuffer == nullptr ) { 104 | for(std::size_t bytesTransferred = 0; bytesTransferred < bytesToTransfer - 1; bytesTransferred++ ) { 105 | this->transmit(*txData++, false, false); 106 | } 107 | this->transmit(*txData++, false, !keepCSAsserted); 108 | } 109 | else { 110 | for(std::size_t bytesTransferred = 0; bytesTransferred < bytesToTransfer - 1; bytesTransferred++ ) { 111 | *(rxBuffer++) = this->transmit(*txData++, false, false); 112 | } 113 | *(rxBuffer++) = this->transmit(*txData++, false, !keepCSAsserted); 114 | } 115 | } 116 | this->waitTransferComplete(); 117 | this->waitReceiveDataFull(); 118 | if( rxBuffer != nullptr ) { 119 | *rxBuffer = regs->RXDATA; 120 | } 121 | else { 122 | volatile uint8_t dummy = regs->RXDATA; 123 | (void)(dummy); 124 | } 125 | } 126 | void transmitSingleCommand(std::uint8_t command) 127 | { 128 | this->transmit(&command, nullptr, 1); 129 | } 130 | void setBaudRateRegister() 131 | { 132 | regs->BAUD = (1u << 8) | (10u << 24); 133 | } 134 | public: 135 | struct FlashID 136 | { 137 | std::uint16_t product; 138 | std::uint8_t manufacturer; 139 | }; 140 | void initialize() 141 | { 142 | if( this->isInitialized ) { 143 | return; 144 | } 145 | 146 | PORT_GROUP0->PINCFG[ 8] = 0x01; 147 | PORT_GROUP0->PINCFG[ 9] = 0x01; 148 | PORT_GROUP0->PINCFG[10] = 0x01; 149 | PORT_GROUP0->PINCFG[11] = 0x01; 150 | PORT_GROUP1->PINCFG[10] = 0x01; 151 | PORT_GROUP1->PINCFG[11] = 0x01; 152 | 153 | PORT_GROUP0->PMUX[ 8 >> 1] = 0x77; 154 | PORT_GROUP0->PMUX[10 >> 1] = 0x77; 155 | PORT_GROUP1->PMUX[10 >> 1] = 0x77; 156 | 157 | regs->CTRLA = (1u << 0); // SWRST 158 | 159 | this->setBaudRateRegister(); 160 | regs->CTRLB = (0b01u << 4); // CSMODE=0b01 (LASTXFER) 161 | regs->CTRLA = (1u << 1); 162 | while( !(regs->STATUS & (1u << 1)) ); // Wait until the QSPI is enabled. 163 | this->isInitialized = true; 164 | } 165 | 166 | void reset() 167 | { 168 | // Reset 169 | this->transmitSingleCommand(0x66); // Enable Reset 170 | this->transmitSingleCommand(0x99); // Reset 171 | delayMicroseconds(100); 172 | } 173 | FlashID readId() 174 | { 175 | std::uint8_t buffer[6]; 176 | memset(buffer, 0xff, sizeof(buffer)); 177 | 178 | auto wasMemoryMode = this->isMemoryMode; 179 | this->exitFromMemoryMode(); 180 | 181 | // Release Power-down 182 | buffer[0] = 0xab; 183 | transmit(buffer, nullptr, 5); 184 | // JEDEC ID 185 | buffer[0] = 0x9f; 186 | transmit(buffer, buffer, 4); 187 | 188 | FlashID flashId = { 189 | static_cast((buffer[2] << 8) | buffer[3]), 190 | buffer[1] 191 | }; 192 | 193 | if( wasMemoryMode ) { 194 | this->enterToMemoryMode(); 195 | } 196 | return flashId; 197 | } 198 | void writeEnable() 199 | { 200 | this->transmitSingleCommand(0x06); 201 | } 202 | void writeDisable() 203 | { 204 | this->transmitSingleCommand(0x04); 205 | } 206 | 207 | static constexpr const std::uint8_t STATUS1 = 0x00; 208 | static constexpr const std::uint8_t STATUS2 = 0x30; 209 | static constexpr const std::uint8_t STATUS3 = 0x10; 210 | std::uint8_t readStatus(std::uint8_t statusReg) 211 | { 212 | std::uint8_t buffer[] = {static_cast(statusReg | 0x05), 0x00}; 213 | this->transmit(buffer, buffer, sizeof(buffer)); 214 | return buffer[1]; 215 | } 216 | void writeStatus(std::uint8_t statusReg, std::uint8_t value) 217 | { 218 | std::uint8_t buffer[] = { static_cast(statusReg | 0x01), value}; 219 | this->transmit(buffer, nullptr, sizeof(buffer)); 220 | } 221 | 222 | void enableQuad() 223 | { 224 | std::uint8_t status = this->readStatus(STATUS1); 225 | this->writeStatus(QSPIFlash::STATUS2, status | (1u << 1)); 226 | } 227 | void disableQuad() 228 | { 229 | std::uint8_t status = this->readStatus(STATUS1); 230 | this->writeStatus(QSPIFlash::STATUS2, status & ~(1u << 1)); 231 | } 232 | void eraseSector(std::uint32_t address) 233 | { 234 | std::uint8_t command[] = { 235 | 0x20, 236 | static_cast(address >> 16), 237 | static_cast(address >> 8), 238 | static_cast(address >> 0), 239 | }; 240 | this->transmit(command, nullptr, sizeof(command)); 241 | } 242 | void programPage(std::uint32_t address, const std::uint8_t* data, std::size_t bytesToWrite) 243 | { 244 | if(bytesToWrite > 256 ) { 245 | return; 246 | } 247 | std::uint8_t command[] = { 248 | 0x02, 249 | static_cast(address >> 16), 250 | static_cast(address >> 8), 251 | static_cast(address >> 0), 252 | }; 253 | this->transmit(command, nullptr, sizeof(command), true); 254 | this->transmit(data, nullptr, bytesToWrite, false); 255 | } 256 | void readData(std::uint32_t address, std::uint8_t* buffer, std::size_t bytesToRead) 257 | { 258 | std::uint8_t command[] = { 259 | 0x03, 260 | static_cast(address >> 16), 261 | static_cast(address >> 8), 262 | static_cast(address >> 0), 263 | }; 264 | this->transmit(command, nullptr, sizeof(command), true); 265 | this->transmit(buffer, buffer, bytesToRead, false); 266 | } 267 | 268 | bool waitProgram(std::uint32_t timeout) 269 | { 270 | while( this->readStatus(STATUS1) & 0x01 ); // support timeout 271 | return true; 272 | } 273 | 274 | bool getIsMemoryMode() const { return this->isMemoryMode; } 275 | 276 | void enterToMemoryMode() 277 | { 278 | if( this->isMemoryMode ) { 279 | return; 280 | } 281 | this->enableQuad(); // Enable Quad IO in flash. 282 | regs->CTRLA = 0; 283 | while(regs->STATUS & (1u << 1)); 284 | regs->CTRLB = (1u << 0); // Enable MEMORY mode. 285 | this->setBaudRateRegister(); 286 | regs->INSTRCTRL = 0xeb | (0x00u << 16); // Fast QUAD IO Read, Option word = 0xe0 for continuous read. 287 | // WIDTH=Quad IO, INSTREN, ADDREN, OPTCODEEN, DATAEN, OPTCODELEN=8bits, ADDRLEN=24bits, TFRTYPE=READMEMORY, CRMODE, DUMMYLEN=4 288 | regs->INSTRFRAME = 0x04 | (1u << 4) | (1u << 5) | (1u << 6) | (1u << 7) | (0x03u << 8) | (0x01 << 12) | /*(1u << 14) | */ (4u << 16); 289 | regs->CTRLA = (1u << 1); 290 | while( !(regs->STATUS & (1u << 1)) ); 291 | 292 | *CMCC_MAINT0 = 1; // Invalidate all caches. 293 | __ISB(); 294 | __DSB(); 295 | this->isMemoryMode = true; 296 | } 297 | 298 | void exitFromMemoryMode() 299 | { 300 | if( !this->isMemoryMode ) { 301 | return; 302 | } 303 | regs->CTRLA = (1u << 0); 304 | while(regs->STATUS & (1u << 1)); 305 | this->setBaudRateRegister(); 306 | regs->CTRLB = (0b01u << 4); // CSMODE=0b01 (LASTXFER), Disable memory mode. 307 | regs->CTRLA = (1u << 1); 308 | while(!(regs->STATUS & (1u << 1))); 309 | this->disableQuad(); // Disable Quad IO in flash. 310 | 311 | *CMCC_MAINT0 = 1; // Invalidate all caches. 312 | __ISB(); 313 | __DSB(); 314 | this->isMemoryMode = false; 315 | } 316 | }; 317 | 318 | static __attribute__((noreturn)) void runQSPIApplication(QSPIFlash& qspi, std::uintptr_t address) 319 | { 320 | volatile std::uint8_t* const USB_BASE = reinterpret_cast(0x41000000); 321 | qspi.enterToMemoryMode(); 322 | __disable_irq(); 323 | //Serial.end(); 324 | // Disable USB 325 | *(USB_BASE + 0x00) = 1; // SWRST 326 | while(*(USB_BASE + 0x02) != 0); // SWRST busy wait 327 | // Revert GCLK0 to DFLL 328 | GCLK->GENCTRL[0].reg = GCLK_GENCTRL_SRC(GCLK_GENCTRL_SRC_DFLL) | GCLK_GENCTRL_GENEN; 329 | while ( GCLK->SYNCBUSY.reg & GCLK_SYNCBUSY_GENCTRL0 ); 330 | std::uint32_t* vector_top = reinterpret_cast(address); 331 | auto reset_vector = reinterpret_cast(*(vector_top + 1)); 332 | SCB->VTOR = address; 333 | __DSB(); 334 | auto stack_top = *vector_top; 335 | __asm__("mov r13, %[stack_top]":: [stack_top] "r" (stack_top)); 336 | reset_vector(); 337 | } 338 | 339 | struct ExtFlashLoader 340 | { 341 | static constexpr const std::uintptr_t QspiBaseAddress = 0x04000000; 342 | static constexpr const std::uintptr_t QspiSize = 0x00040000; // 4[MiB] 343 | static constexpr const std::uintptr_t SramTop = 0x20000000; 344 | static constexpr const std::uintptr_t SramSize = 192*1024; // 192[kiB] 345 | QSPIFlash qspi; 346 | ExtFlashLoader() 347 | { 348 | qspi.initialize(); 349 | qspi.reset(); 350 | qspi.enterToMemoryMode(); 351 | std::uint32_t* vector_top = reinterpret_cast(QspiBaseAddress); 352 | std::uint32_t stack_top = vector_top[0]; 353 | std::uint32_t reset_handler = vector_top[1]; 354 | if( stack_top < SramTop || SramTop + SramSize < stack_top ) { 355 | return; // Invalid stack top value. 356 | } 357 | if( reset_handler < QspiBaseAddress + 8 || QspiBaseAddress + QspiSize <= reset_handler ) { 358 | return; // Invalid reset handler address. 359 | } 360 | runQSPIApplication(this->qspi, QspiBaseAddress); 361 | } 362 | }; 363 | 364 | typedef std::function WriteExternalCallback; 365 | static bool writeExternalFlash(QSPIFlash& qspi, std::uintptr_t address, const void* data, std::size_t length, WriteExternalCallback&& callback ) { 366 | constexpr std::size_t PageSize = 256; 367 | constexpr std::size_t SectorSize = 4096; 368 | constexpr std::size_t SectorSizeMask = SectorSize - 1; 369 | auto data_ptr = reinterpret_cast(data); 370 | 371 | auto is_memory_mode = qspi.getIsMemoryMode(); 372 | 373 | qspi.exitFromMemoryMode(); 374 | // Write data 375 | for(std::size_t bytes_written = 0; bytes_written < length; bytes_written += PageSize) { 376 | if( (bytes_written & SectorSizeMask) == 0 ) { 377 | // Erase sector 378 | qspi.writeEnable(); 379 | qspi.eraseSector(address + bytes_written); 380 | qspi.waitProgram(0); 381 | } 382 | // Program bytes 383 | auto bytes_remaining = length - bytes_written; 384 | if( bytes_remaining < PageSize ) { 385 | std::uint8_t page_buffer[PageSize]; 386 | std::copy(data_ptr + bytes_written, data_ptr + length, page_buffer); 387 | std::fill(page_buffer + bytes_remaining, page_buffer + PageSize, 0xff); 388 | qspi.writeEnable(); 389 | qspi.programPage(address + bytes_written, page_buffer, PageSize); 390 | qspi.waitProgram(0); 391 | } 392 | else { 393 | qspi.writeEnable(); 394 | qspi.programPage(address + bytes_written, data_ptr + bytes_written, PageSize); 395 | qspi.waitProgram(0); 396 | } 397 | callback(bytes_written, length, false); 398 | } 399 | // Verify 400 | bool success = true; 401 | qspi.enterToMemoryMode(); 402 | auto qspi_ptr = reinterpret_cast(0x04000000 + address); 403 | for(std::size_t bytes_read = 0; bytes_read < length; bytes_read++, qspi_ptr++, data_ptr++) { 404 | if( (bytes_read & (PageSize-1)) == 0 ) { 405 | callback(bytes_read, length, true); 406 | } 407 | if( *qspi_ptr != *data_ptr ) { 408 | success = false; 409 | break; 410 | } 411 | } 412 | 413 | if( !is_memory_mode ) { 414 | qspi.exitFromMemoryMode(); 415 | } 416 | return success; 417 | } 418 | 419 | class SerialDownloader 420 | { 421 | private: 422 | enum class State 423 | { 424 | Idle, 425 | Response, 426 | Read, 427 | Write, 428 | }; 429 | 430 | State state = State::Idle; 431 | std::uint8_t buffer[256]; 432 | 433 | enum class CommandType : std::uint8_t { 434 | Identify, 435 | BeginRead, 436 | BeginWrite, 437 | Erase, 438 | Read, 439 | Write, 440 | Run, 441 | }; 442 | struct __attribute__((packed)) CommandHeader 443 | { 444 | CommandType command; 445 | std::uint8_t reserved0; 446 | std::uint16_t length; 447 | std::uint32_t address; 448 | }; 449 | 450 | CommandHeader command; 451 | std::uintptr_t address; 452 | std::uint16_t bytes_remaining; 453 | 454 | QSPIFlash& qspi; 455 | 456 | static constexpr std::uint8_t HOST_MARKER = 0xa5; 457 | static constexpr std::uint8_t DEVICE_MARKER = 0x5a; 458 | 459 | public: 460 | SerialDownloader(QSPIFlash& qspi) : qspi(qspi) {} 461 | 462 | void setupResponse() 463 | { 464 | this->address = reinterpret_cast(this->buffer); 465 | this->bytes_remaining = 2; 466 | this->buffer[0] = DEVICE_MARKER; 467 | this->buffer[1] = static_cast(this->command.command); 468 | this->state = State::Response; 469 | } 470 | 471 | void run() 472 | { 473 | switch(this->state) { 474 | case State::Idle: 475 | if( static_cast(Serial.available()) >= sizeof(this->command) + 1) { 476 | std::uint8_t marker; 477 | Serial.readBytes(reinterpret_cast(&marker), 1); 478 | if( marker == HOST_MARKER ) { 479 | Serial.readBytes(reinterpret_cast(&this->command), sizeof(this->command)); 480 | switch(this->command.command) 481 | { 482 | case CommandType::Identify: { 483 | this->bytes_remaining = sizeof(QSPIFlash::FlashID) + 2; 484 | this->buffer[0] = DEVICE_MARKER; 485 | this->buffer[1] = static_cast(this->command.command); 486 | auto id = this->qspi.readId(); 487 | memcpy(this->buffer + 2, &id, sizeof(id)); 488 | this->address = reinterpret_cast(this->buffer); 489 | this->state = State::Response; 490 | break; 491 | } 492 | case CommandType::Read: 493 | this->address = this->command.address; 494 | this->bytes_remaining = this->command.length; 495 | this->state = State::Read; 496 | Serial.write(DEVICE_MARKER); 497 | Serial.write(static_cast(this->command.command)); 498 | this->qspi.enterToMemoryMode(); 499 | break; 500 | case CommandType::Write: 501 | this->address = this->command.address; 502 | this->bytes_remaining = this->command.length; 503 | this->state = State::Write; 504 | this->qspi.exitFromMemoryMode(); 505 | break; 506 | case CommandType::BeginRead: 507 | this->qspi.enterToMemoryMode(); 508 | this->setupResponse(); 509 | break; 510 | case CommandType::BeginWrite: 511 | this->qspi.exitFromMemoryMode(); 512 | this->setupResponse(); 513 | break; 514 | case CommandType::Erase: 515 | this->address = this->command.address; 516 | this->qspi.exitFromMemoryMode(); 517 | this->qspi.writeEnable(); 518 | this->qspi.eraseSector(this->address); 519 | this->qspi.waitProgram(0); 520 | 521 | this->setupResponse(); 522 | break; 523 | case CommandType::Run: { 524 | runQSPIApplication(this->qspi, this->command.address); 525 | break; 526 | } 527 | } 528 | } 529 | } 530 | break; 531 | case State::Response: 532 | case State::Read: 533 | while(this->bytes_remaining > 0) { 534 | std::uint16_t bytes_written = Serial.write(reinterpret_cast(this->address), this->bytes_remaining); 535 | if( bytes_written == 0 ) { 536 | return; 537 | } 538 | this->bytes_remaining -= bytes_written; 539 | this->address += bytes_written; 540 | } 541 | this->state = State::Idle; 542 | break; 543 | case State::Write: 544 | while(this->bytes_remaining > 0) { 545 | std::uint16_t bytes_read = Serial.readBytes(reinterpret_cast(this->buffer + sizeof(this->buffer) - this->bytes_remaining), this->bytes_remaining); 546 | if( bytes_read == 0 ) { 547 | return; 548 | } 549 | this->bytes_remaining -= bytes_read; 550 | } 551 | this->qspi.writeEnable(); 552 | this->qspi.programPage(this->address, this->buffer, sizeof(this->buffer)); 553 | this->qspi.waitProgram(0); 554 | 555 | this->setupResponse(); 556 | break; 557 | } 558 | } 559 | }; 560 | } // namespace ExtFlashLoader 561 | 562 | #endif //EXTFLASHLOADER_HPP__ --------------------------------------------------------------------------------