diff --git a/Documentation/Fabrication/MPRLS_pressure_sensor/README.md b/Documentation/Fabrication/MPRLS_pressure_sensor/README.md new file mode 100644 index 0000000..e499544 --- /dev/null +++ b/Documentation/Fabrication/MPRLS_pressure_sensor/README.md @@ -0,0 +1,90 @@ +# FLipMouse MPRLS Pressure Sensor Board + +**Fabrication Note v1.0, AsTeRICS Foundation** + +![FLipMouseLogo](./img/flipmouseLogo.png) + +# Scope + +The FLipMouse uses a pressure sensor to detect any sip and puff input on the mouthpiece. + +Version 2 uses a MPXV7007GP pressure sensor, which provides an analog value, exactly half of the supply voltage when idle. + +From 2022 onwards, this sensor is getting VERY expensive (~11$ 2021; ~35$ 2022) and hard to buy (~52 weeks lead time). + +Version 3 of the mouse supports 2 different types of sensors: + +* MPXV7007GP (same sensor as in FMv2) +* MPRLS0025PA00001A + +There are major differences between these two: + +* MPRLS uses I2C +* MPRLS is an absolute pressure sensor, needs calibration on FM startup +* MPRLS is not hand-solder friendly + +Because this sensor is basically impossible to solder with a solder iron. We still want to ship this sensor with assembled devices as well as construction kits, therefore we designed a small adapter PCB which needs to be __reflow soldered__ before further use. + +# Preparation + +## Material + +| Nr. | Description | Source | Image | +| ---- | ------------------------ | -------------------------------------- | ----- | +| 1 | MPRLS adapter PCB | | | +| 2 | 3 parts as stated in BOM | | | + +__Note: Material will be referenced in square brackets: []__ + +## Tools + +| Nr. | Description | Source | Image | +| ---- | --------------------------------- | ------------------------------------------------------------ | ------------------------------------------------- | +| 1 | Reflow solder equipment | Fablab, or university. You need a reflow oven, a stencil, solder paste, tweezers | | +| 2 | Testbench for MPRLS | | ![Picture of sensor testbench](img/testbench.jpg) | +| 3 | Permanent marker (red/blue/green) | DYI store, paper store (possibly any store) | | + +__Note: Tools will be referenced in curly brackets: {}__ + +
+# Procedure + +__Prepare PCB:__ + +1. Mount the stencil on your holder _{1}_ +1. Apply solder paste on PCB(s) _[1]_ + +__Place parts: (note: guaranteed information is available in the KiCAD project, this is for reference only)__ + +1. Place the MPRLS sensor, pin one has a small triangle _{2}_ +2. Place both capacitors (1nF / 100nF) _{2}_ + +__Solder:__ + +1. Follow the instructions for your reflow solder equipment to solder the PCBs + +__Testing:__ + +1. This sensor can be prone to solder errors, especially if you are not experienced in reflow soldering & paste application +2. Follow the test procedure for __EVERY__ soldered sensor PCB + + + +# Testing + +Sensor testing is done via the testbench for sensors, which is based on a Teensy. + +1. Attach the testbench to a USB port (micro USB cable) +2. Wait for finished initialisation, `Insert device` should be shown on the display +3. Attach each sensor under test on the left pin header (4pins): ![sensor connected on the testbench](img/sensorattached.jpg) +4. Hold down the sensor for the test to provide sufficient contact without soldering +5. The display should show `Found dev @0x18`, `MPRLS pressure`. __If not:__ This sensor is broken +6. Wait for the tester to run a test cycle +7. The display should show now: `Fin, avg.: xxx`, the actual value depends on your ambient pressure, but should be in the __range of 400-600__. __If not:__ this sensor is broken. +8. Every character on the __second line__ should be a __`+` symbol__. __If not:__ Either there was a bad contact (press the sensor against the pins and re-run the test) or this sensor is broken (if you tried it more than once). +9. If the test finished successfully, __mark this sensor__ with a permanent marker _{3}_. +10. Repeat the procedure with another sensor from __step 3__. + +## Documentation + +For each produced batch, fill out one document __template_MPRLS_production.ots__ and save it as: `mprls_.ods`(e.g.: mprls_20221118.ods) diff --git a/Documentation/Fabrication/MPRLS_pressure_sensor/README.pdf b/Documentation/Fabrication/MPRLS_pressure_sensor/README.pdf new file mode 100644 index 0000000..0ef4a02 Binary files /dev/null and b/Documentation/Fabrication/MPRLS_pressure_sensor/README.pdf differ diff --git a/Documentation/Fabrication/MPRLS_pressure_sensor/img/flipmouseLogo.png b/Documentation/Fabrication/MPRLS_pressure_sensor/img/flipmouseLogo.png new file mode 100644 index 0000000..d6f4892 Binary files /dev/null and b/Documentation/Fabrication/MPRLS_pressure_sensor/img/flipmouseLogo.png differ diff --git a/Documentation/Fabrication/MPRLS_pressure_sensor/img/sensorattached.jpg b/Documentation/Fabrication/MPRLS_pressure_sensor/img/sensorattached.jpg new file mode 100644 index 0000000..5a4718b Binary files /dev/null and b/Documentation/Fabrication/MPRLS_pressure_sensor/img/sensorattached.jpg differ diff --git a/Documentation/Fabrication/MPRLS_pressure_sensor/img/testbench.jpg b/Documentation/Fabrication/MPRLS_pressure_sensor/img/testbench.jpg new file mode 100644 index 0000000..90564ef Binary files /dev/null and b/Documentation/Fabrication/MPRLS_pressure_sensor/img/testbench.jpg differ diff --git a/Documentation/Fabrication/MPRLS_pressure_sensor/template_MPRLS_production.ots b/Documentation/Fabrication/MPRLS_pressure_sensor/template_MPRLS_production.ots new file mode 100644 index 0000000..9d2b7ec Binary files /dev/null and b/Documentation/Fabrication/MPRLS_pressure_sensor/template_MPRLS_production.ots differ diff --git a/Documentation/Fabrication/mouthpiece/README.md b/Documentation/Fabrication/mouthpiece/README.md new file mode 100644 index 0000000..10aaa5a --- /dev/null +++ b/Documentation/Fabrication/mouthpiece/README.md @@ -0,0 +1,103 @@ +# FLipMouse - Mouthpiece + +**Fabrication Note v1.0, AsTeRICS Foundation** + +![FLipMouseLogo](./img/flipmouseLogo.png) + +# Scope + +The FLipMouse needs a stick in front to be actuated via finger or lip movements. From 2022 on, the AsTeRICS Foundation produces the mouthpieces in-house. + +Basic components of the mouthpiece are: + +1. Acryllic pipe, variable length +2. Male Luer Lock adapter to M6 +3. Filter + +The acryllic pipe (Ø10/6mm) is glued to the Luer Lock adapter with epoxy resin. + +![Fully assembled mouthpiece with filter](./img/mouthpiece.png) + + + +# Preparation + +## Material + +| Nr. | Description | Source | Image | +| ---- | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 1 | Male Luer Lock to M6 adapter | https://www.droh.de/produkt/2836-luer-lock-adapter-mit-m6-gewinde-mannlich | ![A male Luer Lock to M6 thread adapter](./img/maleluer.png) | +| 2 | Female Luer Lock to pipe adapter. __Note:__ this part is either already in the holder OR it can be taken from the FLipMouse construction kit. | https://www.droh.de/produkt/1752-luer-lock-adapter-mit-m6-gewinde-fur-schlauche-mit-3-mm-id-und-zur-geratemontage-weiblich | ![A female Luer Lock to 3mm pipe adapter](./img/femaleluer.png) | +| 3 | Acryllic pipe (Ø10/6mm) | https://acrylhaus.com/Acrylglasrohr-rund-XT-transparent-R-10-6mm-Aussen-Innen-1000mm | ![Acryllic pipe, 10mm outer and 6mm inner diameter](./img/acryllic.png) | +| 4 | Cigarette filters, 6mm diameter | Local tobacco store | ![6mm cigarette filter](./img/filter.png) | +| 5 | Epoxy resin, UHU 300 or equivalent | DIY-store | ![UHU epoxy](./img/epoxy.png) | + +__Note: Material will be referenced in square brackets: []__ + +## Tools + +| Nr. | Description | Source | Image | +| ---- | ---------------------- | -------------------------------------- | ------------------------------------------------------------ | +| 1 | Mixing pan & toothpick | Included in the epoxy kit or DIY-store | ![A analog film case with a toothpick](./img/mixingpan.png) | +| 2 | Miter saw | DIY-store | ![Mitre saw](./img/mitresaw.jpg) | +| 3 | Heat gun | DIY-store | ![A heat gun in an original case with different accessories](./img/heatgun.jpg) | +| 4 | Mouthpiece holder | 3D printed, .stl file in this folder. | ![Holding tool with 15 places for Luer Lock adapters and 2 holes to screw the holder to a surface](./img/holder.png) | +| 5 | Sand paper, 240grit | DIY-store | | + +__Note: Tools will be referenced in curly brackets: {}__ + +
+# Procedure + +__Cut the acryllic pipe:__ + +1. Adjust the mitre saw _{2}_ to the necessary length: 50, 100, 120 or 150mm +2. Cut as many pipes _[3]_ as you want to produce +3. Use the heat gun _{3}_ at **500°C** and heat up both ends for ~**5s** (removing the burr) + + + +__Prepare holder and epoxy resin:__ + +1. Place as many female luer lock adapters _[2]_ in the holder _{4}_ as you want to produce. Take care that both hex nuts are in the holder. +2. Attach a male luer lock adapter _[1]_ on each female adapter by pushing and turning 180°. +3. Mix epoxy resin 50:50 in the mixing pan (TBA: give here a weight for a full holder) + + + +__Glueing:__ + +1. Use the toothpick to apply epoxy resin on the male luer lock's _[1]_ M6 thread. __Note:__ do NOT apply too much and start applying directly on the thread. +2. Push the acryllic pipe _[3]_ on the luer lock adapter _[1]_. __Note:__ make sure you have clean hands. +3. Repeat for all luer lock adapters +4. Use paper or a tissue to remove any surplus epoxy. + + + +`Wait for 12h` + + + +__Finalize:__ + +1. Us the sand paper _{5}_ to make a 45° / 1mm chamfer on the outside of the mouthpiece; remove any burr inside the acryllic pipe (either with sand paper, a big drill with ~10mm or a countersunk head) +1. Use the heat gun _{3}_ at ~500°C and heat up the top for ~5 to have rounded edges +2. Put a filter _[4]_ into the acryllic pipe +3. TBA: how to mark the mouthpieces with a chargenumber? +4. Unscrew the luer locks, the female luer lock can be put into the construction kit. + + + +# Testing + +## Tools + + + +## Procedure + + + +## Documentation + +For each produced batch, fill out one document __template_mouthpiece_production.ots__ and save it as: `mouthpiece_production_.ods`(e.g.: mouthpiece_production_20221118.ods) \ No newline at end of file diff --git a/Documentation/Fabrication/mouthpiece/README.pdf b/Documentation/Fabrication/mouthpiece/README.pdf new file mode 100644 index 0000000..e20d975 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/README.pdf differ diff --git a/Documentation/Fabrication/mouthpiece/img/acryllic.png b/Documentation/Fabrication/mouthpiece/img/acryllic.png new file mode 100644 index 0000000..0ac8683 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/acryllic.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/epoxy.png b/Documentation/Fabrication/mouthpiece/img/epoxy.png new file mode 100644 index 0000000..4509636 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/epoxy.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/femaleluer.png b/Documentation/Fabrication/mouthpiece/img/femaleluer.png new file mode 100644 index 0000000..27bcd9a Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/femaleluer.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/filter.png b/Documentation/Fabrication/mouthpiece/img/filter.png new file mode 100644 index 0000000..2d011c9 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/filter.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/flipmouseLogo.png b/Documentation/Fabrication/mouthpiece/img/flipmouseLogo.png new file mode 100644 index 0000000..d6f4892 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/flipmouseLogo.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/heatgun.jpg b/Documentation/Fabrication/mouthpiece/img/heatgun.jpg new file mode 100644 index 0000000..db43792 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/heatgun.jpg differ diff --git a/Documentation/Fabrication/mouthpiece/img/holder.png b/Documentation/Fabrication/mouthpiece/img/holder.png new file mode 100644 index 0000000..468bd1e Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/holder.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/maleluer.png b/Documentation/Fabrication/mouthpiece/img/maleluer.png new file mode 100644 index 0000000..7a4f310 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/maleluer.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/mitresaw.jpg b/Documentation/Fabrication/mouthpiece/img/mitresaw.jpg new file mode 100644 index 0000000..7a46448 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/mitresaw.jpg differ diff --git a/Documentation/Fabrication/mouthpiece/img/mixingpan.png b/Documentation/Fabrication/mouthpiece/img/mixingpan.png new file mode 100644 index 0000000..67137b8 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/mixingpan.png differ diff --git a/Documentation/Fabrication/mouthpiece/img/mouthpiece.png b/Documentation/Fabrication/mouthpiece/img/mouthpiece.png new file mode 100644 index 0000000..264d993 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/img/mouthpiece.png differ diff --git a/Documentation/Fabrication/mouthpiece/mouthpiece_production_20220917.ods b/Documentation/Fabrication/mouthpiece/mouthpiece_production_20220917.ods new file mode 100644 index 0000000..c9cf5a8 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/mouthpiece_production_20220917.ods differ diff --git a/Documentation/Fabrication/mouthpiece/template_mouthpiece_production.ots b/Documentation/Fabrication/mouthpiece/template_mouthpiece_production.ots new file mode 100644 index 0000000..739e628 Binary files /dev/null and b/Documentation/Fabrication/mouthpiece/template_mouthpiece_production.ots differ diff --git a/Documentation/Fabrication/rp2040_preparation/FM.uf2 b/Documentation/Fabrication/rp2040_preparation/FM.uf2 new file mode 100644 index 0000000..7124e16 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/FM.uf2 differ diff --git a/Documentation/Fabrication/rp2040_preparation/README.md b/Documentation/Fabrication/rp2040_preparation/README.md new file mode 100644 index 0000000..acfe174 --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/README.md @@ -0,0 +1,90 @@ +# FLipMouse/Pad Arduino Initialisation + +**Fabrication Note v1.0, AsTeRICS Foundation** + +![FLipMouseLogo](./img/flipmouseLogo.png) + +# Scope + +FLipMouse and FLipPad use from version 3 onward an __Arduino Nano RP2040 Connect__ platform. + +The big advantage over the TeensyLC from version 2, this controller has much more RAM & ROM and already contains the ESP32, which is used for the Bluetooth connectivity. + +To fully use the update procedures from the WebGUI, the Arduino needs to be programmed initially: + +* __esp32_addon_bootloader:__ Bootloader Code to update the ESP32 BLE Mouse/Keyboard firmware without esptool.py +* __esp32_mouse_keyboard:__ Program the BLE HID over GATT firmware +* __FLipMouse/FLipPad firmware:__ Program the RP2040 with the current FLipMouse / FLipPad Firmware + +# Preparation + +## Material + +| Nr. | Description | Source | Image | +| ---- | ---------------------------------- | ------------------------------------------------------------ | ------------------------------------------------------------ | +| 1 | Arduino Nano RP2040 Connect | https://store.arduino.cc/products/arduino-nano-rp2040-connect | ![A new Arduino Nano RP2040 connect](./img/rp2040_unmarked.png) | + +__Note: Material will be referenced in square brackets: []__ + +## Tools / Requirements + +| Nr. | Description | Source | +| ---- | --------------------------------- | ------------------------------------------------------------ | +| 1 | Terminal + Python3 | Install python according to your OS | +| 2 | esptool | Install via pip: `pip install esptool` (if it cannot be executed from a terminal, run this command as root) | +| 2 | Permanent marker (red/blue/green) | DYI store, paper store (possibly any store) | + +__Note: Tools will be referenced in curly brackets: {}__ + +
+# Procedure + +1. Attach the Arduino Nano RP2040 Connect _[1]_ to the computer +2. Open a terminal +3. Call the script: `rp2040_prepare.py -t -p ` + +_-t_ Select the firmware to be flashed, either FP for FLipPad, FM for FLipMouse of FB for FABI (FP & FB are not finished yet) + +_-p_ Select a serial port which should be flashed (normally COMxx on Windows, /dev/ttyxxx on Linux) + +# Testing + +Not available, if procedure is followed, the software is flashed correctly: + +* __Blue blinking__ LED for a flashed Bluetooth firmware + +## Documentation + +For each produced batch, fill out one document __template_arduino_init_production.ots__ and save it as: `arduino_init_.ods`(e.g.: arduino_init_20221118.ods) + + +## Updating the firmware builds to be flashed + + + +1. Replace `bootloader.bin`,`esp32_addon_bootloader.bin`, `ota_initial_data.bin` and `partition-table.bin`with a current build from: _esp32_addon_bootloader/build/esp32_addon_bootloader.bin_ +2. Replace `mousekeyboard.bin` with a current build from: _esp32_mouse_keyboard/build/esp32_mouse_keyboard.bin_ +3. Replace `FM.uf2`, `FP.uf2`, `FB.uf2` with current builds from the FLipWare/FabiWare repositories (build with Arduino and select _Sketch->Export compiled binary_) +4. Replace `serialflasher1.uf2` with a new build from the serialflasher1.ino sketch from this directory (build with Arduino and select _Sketch->Export compiled binary_) +5. Replace `serialflasher2.uf2` with a new build from the serialflasher2.ino sketch from this directory (build with Arduino and select _Sketch->Export compiled binary_) +7. __Write down the current GIT tags (releases) or commit numbers for the builds to _VERSIONS.md_ __ + + + +## Insights + + + +This tool performs following steps: + +1. Flashing the serialflasher1.uf2 firmware calling `uf2conv.py`tool +2. Flashing the esp32_addon_bootloader firmware with `esptool.py` +3. Flashing the serialflasher2.uf2 firmware calling `uf2conv.py` tool +4. Flash the esp32_mouse_keyboard_firmware (included in rp2040_prepare.py) +5. Reset the Arduino to UF2 download mode by opening the given serial port with 1200Baud and closing it +6. Flashing the FM/FB/FP.uf2 firmware calling `uf2conv.py`tool +7. Verifying that everything worked by sending "AT BC $ID" to the serial port, which returns the version of the BLE module. This is printed on the command line. +8. Start over (wait until serial port is removed and a new one is detected) + + + diff --git a/Documentation/Fabrication/rp2040_preparation/README.pdf b/Documentation/Fabrication/rp2040_preparation/README.pdf new file mode 100644 index 0000000..46fc8ff Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/README.pdf differ diff --git a/Documentation/Fabrication/rp2040_preparation/VERSIONS.md b/Documentation/Fabrication/rp2040_preparation/VERSIONS.md new file mode 100644 index 0000000..cb52f59 --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/VERSIONS.md @@ -0,0 +1,21 @@ +# Versions for intermediate serial/usb passthrough sketches + +serialflasher1.uf2 no version code, first commit +serialflasher2.uf2 no version code, first commit + +# Versions for esp32_addon_bootloader binary files + +__Note:__ You need to update all of these files! + +esp32_addon_bootloader.bin 5bd3413 (RP2040 branch) +ota_data_initial.bin 5bd3413 (RP2040 branch) +bootloader.bin 5bd3413 (RP2040 branch) +partition-table.bin 5bd3413 (RP2040 branch) + +# Versions for the esp32_mouse_keyboard binary file + +esp32_mouse_keyboard.bin fa59627 (RP2040 branch) + +# Versions for FLipWare + +FM.uf 712338a (RP2040 branch) diff --git a/Documentation/Fabrication/rp2040_preparation/bootloader.bin b/Documentation/Fabrication/rp2040_preparation/bootloader.bin new file mode 100644 index 0000000..58123a1 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/bootloader.bin differ diff --git a/Documentation/Fabrication/rp2040_preparation/esp32_addon_bootloader.bin b/Documentation/Fabrication/rp2040_preparation/esp32_addon_bootloader.bin new file mode 100644 index 0000000..7c68565 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/esp32_addon_bootloader.bin differ diff --git a/Documentation/Fabrication/rp2040_preparation/esp32_mouse_keyboard.bin b/Documentation/Fabrication/rp2040_preparation/esp32_mouse_keyboard.bin new file mode 100644 index 0000000..5c8780e Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/esp32_mouse_keyboard.bin differ diff --git a/Documentation/Fabrication/rp2040_preparation/flash.bin b/Documentation/Fabrication/rp2040_preparation/flash.bin new file mode 100644 index 0000000..7ffcc2e Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/flash.bin differ diff --git a/Documentation/Fabrication/rp2040_preparation/img/flipmouseLogo.png b/Documentation/Fabrication/rp2040_preparation/img/flipmouseLogo.png new file mode 100644 index 0000000..d6f4892 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/img/flipmouseLogo.png differ diff --git a/Documentation/Fabrication/rp2040_preparation/img/rp2040_marked.png b/Documentation/Fabrication/rp2040_preparation/img/rp2040_marked.png new file mode 100644 index 0000000..5664cf6 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/img/rp2040_marked.png differ diff --git a/Documentation/Fabrication/rp2040_preparation/img/rp2040_unmarked.png b/Documentation/Fabrication/rp2040_preparation/img/rp2040_unmarked.png new file mode 100644 index 0000000..d2b2da3 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/img/rp2040_unmarked.png differ diff --git a/Documentation/Fabrication/rp2040_preparation/ota_data_initial.bin b/Documentation/Fabrication/rp2040_preparation/ota_data_initial.bin new file mode 100644 index 0000000..b4033a7 --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/ota_data_initial.bin @@ -0,0 +1 @@ +ÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿÿ \ No newline at end of file diff --git a/Documentation/Fabrication/rp2040_preparation/partition-table.bin b/Documentation/Fabrication/rp2040_preparation/partition-table.bin new file mode 100644 index 0000000..e8ef43e Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/partition-table.bin differ diff --git a/Documentation/Fabrication/rp2040_preparation/rp2040_preparation.py b/Documentation/Fabrication/rp2040_preparation/rp2040_preparation.py new file mode 100644 index 0000000..2bbf07a --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/rp2040_preparation.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python3 + +# rp2040_preparation.py + +# Python tool for initial flashing of +# * esp32_addon_bootloader +# * esp32_mouse_keyboard +# * FLipMouse / FLipWare / FabiWare firmwares +# Please refer to README.md for documentation + +# Copyright 2022 Benjamin Aigner + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# + +import argparse +import serial +import io +import sys +import os +import time +import subprocess + +devices = { + 'FM': "FLipMouse", + 'FP': "FLipPad", + 'FB': "FABI" +} + +#subprocess.run('python D:/your_script.py') + +def main(): + def error(msg): + print(msg) + sys.exit(1) + parser = argparse.ArgumentParser(description='Prepare Arduino Nano RP2040 connect for FLipMouse, FLipPad or FABI') + parser.add_argument('-d' , '--device', dest="device", + help='select a device type to be flashed (FM, FP or FB)') + parser.add_argument('-s', '--serial', dest='serial', help='Serial port to reset before upload') + parser.add_argument('-o', '--start_with', dest='startwith', help='Start with this step') + args = parser.parse_args() + + if not args.device: + error("Error: Need a device type (e.g. -d FM)") + elif not args.device.upper() in devices: + error("Error: Need a correct device type (e.g. -d FM)") + else: + global current_device + current_device = args.device.upper() + + if not args.serial: + error("Error: Need a serial port (e.g. COM2 or /dev/ttyACM0)") + else: + global current_serial + current_serial = args.serial + + global startwith + if args.startwith: + startwith = int(args.startwith) + print("Starting with step " + args.startwith) + else: + startwith = 0 + + while 1: + print("Flashing " + devices[args.device] + " firmware to " + args.serial) + + # 1.) serialflasher1.uf2 -> esptool.py compatible serial interface to the ESP32, ESP32 is in download mode + if startwith <= 1: + print("Part 1 ###############################################") + proc = subprocess.run(["python3","uf2conv.py","serialflasher1.uf2","-s", current_serial,"-D"], capture_output=True, text=True) + print(proc.stdout) + if proc.returncode != 0: + print("Error flashing UF2 'serialflasher1.uf2'") + exit + time.sleep(5) + + # 2.) esptool.py -> flash the bootloader + if startwith <= 2: + print("Part 2 ###############################################") + retry = 0 + while 1: + # this was the output from idf.py build: + # esptool.py -p (PORT) -b 460800 --before no_reset --after hard_reset --chip esp32 write_flash --flash_mode dio --flash_size detect --flash_freq 40m + # 0x1000 build/bootloader/bootloader.bin 0x8000 build/partition_table/partition-table.bin 0xd000 build/ota_data_initial.bin 0x10000 build/esp32_addon_bootloader.bin + proc = subprocess.run(["esptool.py","--port", current_serial,"--baud", "115200", "--before", "no_reset", "--after", "no_reset", "--chip", "esp32", "write_flash", \ + "--flash_mode", "dio", "--flash_size", "detect", "--flash_freq", "40m", \ + "0x1000", "bootloader.bin", "0x8000", "partition-table.bin", "0xd000", "ota_data_initial.bin", "0x10000", "esp32_addon_bootloader.bin"], capture_output=True, text=True) + print(proc.stdout) + print(proc.stderr) + if proc.returncode != 0: + print("Error flashing esp32_addon_bootloader.bin") + retry = retry + 1 + if retry > 10: + exit + else: + break + time.sleep(1) + + # 3.) serialflasher2.uf2 -> usb to serial passthrough sketch with 500k, compatible to FM/FP/FB ESP32 update mode + if startwith <= 3: + print("Part 3 ###############################################") + proc = subprocess.run(["python3","uf2conv.py","serialflasher2.uf2","-s", current_serial,"-D"], capture_output=True, text=True) + print(proc.stdout) + if proc.returncode != 0: + print("Error flashing UF2 'serialflasher1.uf2'") + exit + + + # 4.) wait for "OTA:ready" signal + if startwith <= 4: + print("Part 4 ###############################################") + retry = 0 + while 1: + try: + ser = serial.Serial(current_serial, timeout=1) + break + except Exception: + retry = retry + 1 + if retry > 10: + exit + else: + time.sleep(0.5) + + # avoid a Windows quirk: the Arduino does not respond to any data, if the DTR line is not set + if os.name == 'nt': + ser.dtr = True + ser.flushInput() + tries = 0 + while 1: + try: + line = ser.readline() + print(str(line)) + line = line.decode() + if line == "OTA:ready\r\n": + print("Got OTA:ready") + break + else: + tries = tries + 1 + if tries > 20: + error("Not receiving OTA:ready") + break + except Exception: + continue + + # 5.) flash esp32_mouse_keyboard.bin with update.py code + if startwith <= 5: + print("Part 5 ###############################################") + if not ser: + retry = 0 + while 1: + try: + ser = serial.Serial(current_serial, timeout=1) + break + except Exception: + retry = retry + 1 + if retry > 10: + exit + else: + time.sleep(0.5) + # as long as there is something available in the input file + with open('esp32_mouse_keyboard.bin', "rb") as input: + while True: + # read in 128B chunks + data = input.read(128) + #datalen += len(data) + #print(datalen) + ser.write(data) + ser.flush() + # if this is the last block (less than 128B) + if len(data) < 128: + # finished... + print("Finished") + break + #sleep for 10ms + time.sleep(10 / 1000) + # any feedback? + if ser.in_waiting: + line = ser.read(100) + print(line.decode()) + ser.flush() + + tries = 0 + while 1: + try: + line = ser.readline() + print(str(line)) + line = line.decode() + if line == "OTA:$FINISHED\r\n": + print("Got OTA:$FINISHED\r\n") + break + else: + tries = tries + 1 + if tries > 20: + error("Not receiving OTA:$FINISHED") + break + except Exception: + continue + ser.close() + + # 6.) FM/FB/FP.uf2 -> selected device firmware + if startwith <= 6: + print("Part 6 ###############################################") + proc = subprocess.run(["python3","uf2conv.py",current_device + ".uf2","-s", current_serial,"-D"], capture_output=True, text=True) + print(proc.stdout) + if proc.returncode != 0: + print(proc.stderr) + print("Error flashing UF2 '"+current_device+".uf2'") + exit + + # 7.) test output of "AT BC $ID" to confirm working firmware for RP2040 & ESP32 + if startwith <= 7: + print("Part 7 ###############################################") + retry = 0 + while 1: + try: + ser = serial.Serial(current_serial, timeout=1) + break + except Exception: + retry = retry + 1 + if retry > 10: + exit + else: + time.sleep(0.5) + #set device name depending on selected module + ser.write(str("AT BC $NAME "+devices[current_device] + "\r\n").encode()) + print(ser.readline().decode()) + retry = 0 + while 1: + ser.write(str("AT BC $ID\r\n").encode()) + line = ser.readline().decode() + if line.startswith("ESP32"): + break + else: + retry = retry + 1 + if retry > 15: + print("Error getting ESP32 revision number") + break + time.sleep(1) + ser.close() + + print("Finished - waiting 5s before starting new cycle") + # TODO: endless cycles? + break + time.sleep(5) + +if __name__ == "__main__": + main() diff --git a/Documentation/Fabrication/rp2040_preparation/serialflasher1.uf2 b/Documentation/Fabrication/rp2040_preparation/serialflasher1.uf2 new file mode 100644 index 0000000..9bc15a6 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/serialflasher1.uf2 differ diff --git a/Documentation/Fabrication/rp2040_preparation/serialflasher1/serialflasher1.ino b/Documentation/Fabrication/rp2040_preparation/serialflasher1/serialflasher1.ino new file mode 100644 index 0000000..9c8f4d2 --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/serialflasher1/serialflasher1.ino @@ -0,0 +1,56 @@ +/* + * RP2040 - ESP32 serial passthrough + * + * Resets the ESP32 into download mode when flashed (115k2 Baud) + */ + + +#define ORANGE_LED_PIN 6 + +#define ESP32_RESET_PIN 3 +#define ESP32_GPIO0_PIN 2 + +#define LED_TOGGLE_TIME 20000 + + +void setup() { + //LED output + pinMode(ORANGE_LED_PIN, OUTPUT); + //ESP32 reset / download pins + pinMode(ESP32_RESET_PIN, OUTPUT); + pinMode(ESP32_GPIO0_PIN, OUTPUT); + + //enable both serial interfaces + Serial.begin(115200); + Serial2.begin(115200); + + //enable ESP32 into download mode + digitalWrite(ESP32_GPIO0_PIN,LOW); // connect GPIO0 to GND + digitalWrite(ESP32_RESET_PIN,LOW); // reset ESP32 + delay(100); + digitalWrite(ESP32_RESET_PIN,HIGH); + delay(100); + digitalWrite(ESP32_GPIO0_PIN,HIGH); +} + +long cnt=0; +long bypass_reset=0; + +// the loop function runs over and over again forever +void loop() { + // toggle LED + + if (!(cnt++ % LED_TOGGLE_TIME)) + digitalWrite(ORANGE_LED_PIN,!digitalRead(ORANGE_LED_PIN)); + + // pass serial transfer to Serial2 (ESP) + + if (Serial.available()) { // If anything comes in Serial (USB), + Serial2.write(Serial.read()); // read it and send it out Serial1 (pins 0 & 1) + } + + if (Serial2.available()) { // If anything comes in Serial1 (pins 0 & 1) + Serial.write(Serial2.read()); // read it and send it out Serial (USB) + } + +} diff --git a/Documentation/Fabrication/rp2040_preparation/serialflasher1/serialflasher1.ino.arduino_nano_connect.uf2 b/Documentation/Fabrication/rp2040_preparation/serialflasher1/serialflasher1.ino.arduino_nano_connect.uf2 new file mode 100644 index 0000000..9bc15a6 Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/serialflasher1/serialflasher1.ino.arduino_nano_connect.uf2 differ diff --git a/Documentation/Fabrication/rp2040_preparation/serialflasher2.uf2 b/Documentation/Fabrication/rp2040_preparation/serialflasher2.uf2 new file mode 100644 index 0000000..acdf7ae Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/serialflasher2.uf2 differ diff --git a/Documentation/Fabrication/rp2040_preparation/serialflasher2/serialflasher2.ino b/Documentation/Fabrication/rp2040_preparation/serialflasher2/serialflasher2.ino new file mode 100644 index 0000000..7ed9d4b --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/serialflasher2/serialflasher2.ino @@ -0,0 +1,54 @@ +/* + * RP2040 - ESP32 serial passthrough + * + * Enables the ESP32 and passes serial data through (500kBaud) + */ + + +#define ORANGE_LED_PIN 6 + +#define ESP32_RESET_PIN 3 +#define ESP32_GPIO0_PIN 2 + +#define LED_TOGGLE_TIME 20000 + + +void setup() { + //LED output + pinMode(ORANGE_LED_PIN, OUTPUT); + //ESP32 reset / download pins + pinMode(ESP32_RESET_PIN, OUTPUT); + pinMode(ESP32_GPIO0_PIN, OUTPUT); + + //enable both serial interfaces + Serial.begin(500000); + Serial2.begin(500000); + + //enable ESP32 into normal mode + digitalWrite(ESP32_GPIO0_PIN,HIGH); // connect GPIO0 to VCC + digitalWrite(ESP32_RESET_PIN,LOW); // reset ESP32 + delay(10); + digitalWrite(ESP32_RESET_PIN,HIGH); +} + +long cnt=0; +long bypass_reset=0; + +// the loop function runs over and over again forever +void loop() { + // toggle LED + + if (!(cnt++ % LED_TOGGLE_TIME)) + digitalWrite(ORANGE_LED_PIN,!digitalRead(ORANGE_LED_PIN)); + + // pass serial transfer to Serial2 (ESP) + + if (Serial.available()) { // If anything comes in Serial (USB), + Serial2.write(Serial.read()); // read it and send it out Serial1 (pins 0 & 1) + } + + if (Serial2.available()) { // If anything comes in Serial1 (pins 0 & 1) + Serial.write(Serial2.read()); // read it and send it out Serial (USB) + } + +} diff --git a/Documentation/Fabrication/rp2040_preparation/serialflasher2/serialflasher2.ino.arduino_nano_connect.uf2 b/Documentation/Fabrication/rp2040_preparation/serialflasher2/serialflasher2.ino.arduino_nano_connect.uf2 new file mode 100644 index 0000000..acdf7ae Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/serialflasher2/serialflasher2.ino.arduino_nano_connect.uf2 differ diff --git a/Documentation/Fabrication/rp2040_preparation/template_arduino_init_production.ots b/Documentation/Fabrication/rp2040_preparation/template_arduino_init_production.ots new file mode 100644 index 0000000..9d2b7ec Binary files /dev/null and b/Documentation/Fabrication/rp2040_preparation/template_arduino_init_production.ots differ diff --git a/Documentation/Fabrication/rp2040_preparation/uf2conv.py b/Documentation/Fabrication/rp2040_preparation/uf2conv.py new file mode 100644 index 0000000..2905929 --- /dev/null +++ b/Documentation/Fabrication/rp2040_preparation/uf2conv.py @@ -0,0 +1,401 @@ +#!/usr/bin/env python3 + +# UF2 upload utility taken from https://github.com/microsoft/uf2/blob/master/utils/uf2conv.py + +# Microsoft UF2 +# The MIT License (MIT) +# Copyright (c) Microsoft Corporation +# All rights reserved. + +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to deal +# in the Software without restriction, including without limitation the rights +# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +# copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: + +# The above copyright notice and this permission notice shall be included in all +# copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +# SOFTWARE. + +import sys +import struct +import subprocess +import re +import os +import os.path +import argparse +import time +import glob + +toolspath = os.path.dirname(os.path.realpath(__file__)).replace('\\', '/') # CWD in UNIX format +try: + sys.path.insert(0, toolspath + "/pyserial") # Add pyserial dir to search path + import serial # If this fails, we can't continue and will bomb below +except Exception: + sys.stderr.write("pyserial directory not found next to this upload.py tool.\n") + sys.exit(1) + +UF2_MAGIC_START0 = 0x0A324655 # "UF2\n" +UF2_MAGIC_START1 = 0x9E5D5157 # Randomly selected +UF2_MAGIC_END = 0x0AB16F30 # Ditto + +families = { + 'SAMD21': 0x68ed2b88, + 'SAML21': 0x1851780a, + 'SAMD51': 0x55114460, + 'NRF52': 0x1b57745f, + 'STM32F0': 0x647824b6, + 'STM32F1': 0x5ee21072, + 'STM32F2': 0x5d1a0a2e, + 'STM32F3': 0x6b846188, + 'STM32F4': 0x57755a57, + 'STM32F7': 0x53b80f00, + 'STM32G0': 0x300f5633, + 'STM32G4': 0x4c71240a, + 'STM32H7': 0x6db66082, + 'STM32L0': 0x202e3a91, + 'STM32L1': 0x1e1f432d, + 'STM32L4': 0x00ff6919, + 'STM32L5': 0x04240bdf, + 'STM32WB': 0x70d16653, + 'STM32WL': 0x21460ff0, + 'ATMEGA32': 0x16573617, + 'MIMXRT10XX': 0x4FB2D5BD, + 'LPC55': 0x2abc77ec, + 'GD32F350': 0x31D228C6, + 'ESP32S2': 0xbfdd4eee, + 'RP2040': 0xe48bff56 +} + +INFO_FILE = "/INFO_UF2.TXT" + +appstartaddr = 0x2000 +familyid = 0x0 + + +def is_uf2(buf): + w = struct.unpack(" 476: + assert False, "Invalid UF2 data size at " + ptr + newaddr = hd[3] + if curraddr == None: + appstartaddr = newaddr + curraddr = newaddr + padding = newaddr - curraddr + if padding < 0: + assert False, "Block out of order at " + ptr + if padding > 10*1024*1024: + assert False, "More than 10M of padding needed at " + ptr + if padding % 4 != 0: + assert False, "Non-word padding size at " + ptr + while padding > 0: + padding -= 4 + outp += b"\x00\x00\x00\x00" + outp.append(block[32 : 32 + datalen]) + curraddr = newaddr + datalen + return b"".join(outp) + +def convert_to_carray(file_content): + outp = "const unsigned long bindata_len = %d;\n" % len(file_content) + outp += "const unsigned char bindata[] __attribute__((aligned(16))) = {" + for i in range(len(file_content)): + if i % 16 == 0: + outp += "\n" + outp += "0x%02x, " % file_content[i] + outp += "\n};\n" + return bytes(outp, "utf-8") + +def convert_to_uf2(file_content): + global familyid + datapadding = b"" + while len(datapadding) < 512 - 256 - 32 - 4: + datapadding += b"\x00\x00\x00\x00" + numblocks = (len(file_content) + 255) // 256 + outp = [] + for blockno in range(numblocks): + ptr = 256 * blockno + chunk = file_content[ptr:ptr + 256] + flags = 0x0 + if familyid: + flags |= 0x2000 + hd = struct.pack(b"= 3 and words[1] == "2" and words[2] == "FAT": + drives.append(words[0]) + else: + rootpath = "/run/media" + if not os.path.isdir(rootpath): + rootpath = "/media" + if not os.path.isdir(rootpath): + rootpath = "/opt/media" + if sys.platform == "darwin": + rootpath = "/Volumes" + elif sys.platform == "linux": + tmp = rootpath + "/" + os.environ["USER"] + if os.path.isdir(tmp): + rootpath = tmp + for d in os.listdir(rootpath): + drives.append(os.path.join(rootpath, d)) + + if (len(drives) == 0) and (sys.platform == "linux"): + globexpr = "/dev/disk/by-id/usb-RPI_RP2*-part1" + rpidisk = glob.glob(globexpr) + if len(rpidisk) > 0: + try: + cmd = ["udisksctl", "mount", "--block-device", os.path.realpath(rpidisk[0])] + proc_out = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if proc_out.returncode == 0: + stdoutput = proc_out.stdout.decode("UTF-8") + match = re.search(r'Mounted\s+.*\s+at\s+([^\.\r\n]*)', stdoutput) + if match: + drives = [match.group(1)] + except Exception as ex: + print("Exception executing udisksctl. Exception: {}".format(ex)) + # If it fails, no problem since it was a heroic attempt + + def has_info(d): + try: + return os.path.isfile(d + INFO_FILE) + except: + return False + + return list(filter(has_info, drives)) + + +def board_id(path): + with open(path + INFO_FILE, mode='r') as file: + file_content = file.read() + return re.search("Board-ID: ([^\r\n]*)", file_content).group(1) + + +def list_drives(): + for d in get_drives(): + print(d, board_id(d)) + + +def write_file(name, buf): + with open(name, "wb") as f: + f.write(buf) + print("Wrote %d bytes to %s" % (len(buf), name)) + + +def main(): + global appstartaddr, familyid + def error(msg): + print(msg) + sys.exit(1) + parser = argparse.ArgumentParser(description='Convert to UF2 or flash directly.') + parser.add_argument('input', metavar='INPUT', type=str, nargs='?', + help='input file (HEX, BIN or UF2)') + parser.add_argument('-b' , '--base', dest='base', type=str, + default="0x2000", + help='set base address of application for BIN format (default: 0x2000)') + parser.add_argument('-o' , '--output', metavar="FILE", dest='output', type=str, + help='write output to named file; defaults to "flash.uf2" or "flash.bin" where sensible') + parser.add_argument('-d' , '--device', dest="device_path", + help='select a device path to flash') + parser.add_argument('-l' , '--list', action='store_true', + help='list connected devices') + parser.add_argument('-c' , '--convert', action='store_true', + help='do not flash, just convert') + parser.add_argument('-D' , '--deploy', action='store_true', + help='just flash, do not convert') + parser.add_argument('-f' , '--family', dest='family', type=str, + default="0x0", + help='specify familyID - number or name (default: 0x0)') + parser.add_argument('-C' , '--carray', action='store_true', + help='convert binary file to a C array, not UF2') + parser.add_argument('-s', '--serial', dest='serial', help='Serial port to reset before upload') + args = parser.parse_args() + appstartaddr = int(args.base, 0) + + if args.family.upper() in families: + familyid = families[args.family.upper()] + else: + try: + familyid = int(args.family, 0) + except ValueError: + error("Family ID needs to be a number or one of: " + ", ".join(families.keys())) + + if args.serial: + if str(args.serial).startswith("/dev/tty") or str(args.serial).startswith("COM") or str(args.serial).startswith("/dev/cu"): + try: + print("Resetting " + str(args.serial)) + try: + ser = serial.Serial(args.serial, 1200) + ser.dtr = False + except: + pass # Ignore error in the case it is already in upload mode + except: + pass + if args.list: + list_drives() + else: + if not args.input: + error("Need input file") + with open(args.input, mode='rb') as f: + inpbuf = f.read() + from_uf2 = is_uf2(inpbuf) + ext = "uf2" + if args.deploy: + outbuf = inpbuf + elif from_uf2: + outbuf = convert_from_uf2(inpbuf) + ext = "bin" + elif is_hex(inpbuf): + outbuf = convert_from_hex_to_uf2(inpbuf.decode("utf-8")) + elif args.carray: + outbuf = convert_to_carray(inpbuf) + ext = "h" + else: + outbuf = convert_to_uf2(inpbuf) + print("Converting to %s, output size: %d, start address: 0x%x" % + (ext, len(outbuf), appstartaddr)) + if args.convert or ext != "uf2": + drives = [] + if args.output == None: + args.output = "flash." + ext + else: + now = time.time() + drives = [] + while (time.time() - now < 10.0) and (len(drives) == 0): + time.sleep(0.5) # Avoid 100% CPU use while waiting for drive to appear + drives = get_drives() + + if args.output: + write_file(args.output, outbuf) + else: + if len(drives) == 0: + error("No drive to deploy.") + for d in drives: + print("Flashing %s (%s)" % (d, board_id(d))) + write_file(d + "/NEW.UF2", outbuf) + + +if __name__ == "__main__": + main() diff --git a/FLipWare/FlipWare.h b/FLipWare/FlipWare.h index 2fee56b..de1f4c2 100644 --- a/FLipWare/FlipWare.h +++ b/FLipWare/FlipWare.h @@ -43,6 +43,7 @@ // V3.2: changed pinning to PCB v3.2 // V3.00: changed platform to Arduino Nano RP2040 Connect +// V2.12.1: fixed keystring buffer problem // V2.12: improved modularisation and source code documentation, added LC-display support and elliptical deadzone // V2.11: eeprom access optimization and support for deletion / update of individual slots // V2.10: code size reduction: using floating point math, removed debug level control via AT E0, AT E1 and AT E2 diff --git a/FLipWare/buttons.cpp b/FLipWare/buttons.cpp index c1e7bc3..785f772 100644 --- a/FLipWare/buttons.cpp +++ b/FLipWare/buttons.cpp @@ -42,8 +42,8 @@ char * getButtonKeystring(int num) { char * str = keystringBuffer; for (int i=0;i +
# Hardware and Features