mirror of
https://github.com/cecio/USBvalve.git
synced 2026-04-22 17:01:22 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
028227edfd | ||
|
|
8369117d73 | ||
|
|
13422f4c8c |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -52,3 +52,4 @@ Mkfile.old
|
|||||||
dkms.conf
|
dkms.conf
|
||||||
|
|
||||||
USBvalve_out
|
USBvalve_out
|
||||||
|
build
|
||||||
|
|||||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[submodule "lib/pico-pio-usb"]
|
||||||
|
path = lib/pico-pio-usb
|
||||||
|
url = https://github.com/sekigon-gonnoc/Pico-PIO-USB.git
|
||||||
|
[submodule "lib/xxhash"]
|
||||||
|
path = lib/xxhash
|
||||||
|
url = https://github.com/Cyan4973/xxHash.git
|
||||||
18
CMakeLists.txt
Normal file
18
CMakeLists.txt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
cmake_minimum_required(VERSION 3.13)
|
||||||
|
|
||||||
|
# Include the Pico SDK import helper
|
||||||
|
include(pico_sdk_import.cmake)
|
||||||
|
|
||||||
|
project(USBvalve C CXX ASM)
|
||||||
|
|
||||||
|
set(CMAKE_C_STANDARD 11)
|
||||||
|
set(CMAKE_CXX_STANDARD 17)
|
||||||
|
|
||||||
|
# Initialize the Pico SDK
|
||||||
|
pico_sdk_init()
|
||||||
|
|
||||||
|
# Add pico-pio-usb library
|
||||||
|
add_subdirectory(lib/pico-pio-usb)
|
||||||
|
|
||||||
|
# Add application
|
||||||
|
add_subdirectory(src)
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#
|
|
||||||
# To Build:
|
|
||||||
# docker build -t usbvalve-pico1/arduino-cli -f Dockerfile.pico1 .
|
|
||||||
#
|
|
||||||
# To Run:
|
|
||||||
# docker run --rm --name usbvalve -v $PWD:/mnt usbvalve-pico1/arduino-cli /mnt/USBvalve
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM ubuntu:22.04
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# OS setup
|
|
||||||
RUN apt-get update -y \
|
|
||||||
&& apt-get install -y git wget python3 \
|
|
||||||
&& apt-get autoremove -y \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
# arduino-cli setup
|
|
||||||
RUN cd /app \
|
|
||||||
&& git clone --recursive https://github.com/arduino/arduino-cli.git \
|
|
||||||
&& cd arduino-cli \
|
|
||||||
&& ./install.sh \
|
|
||||||
&& export PATH=$PATH:/app/arduino-cli/bin \
|
|
||||||
&& arduino-cli --additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json core search 2040 \
|
|
||||||
&& arduino-cli --additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json core install rp2040:rp2040@4.4.2 \
|
|
||||||
&& arduino-cli lib install "Adafruit TinyUSB Library@3.4.2" \
|
|
||||||
&& arduino-cli lib install "Adafruit SSD1306@2.5.13" \
|
|
||||||
&& arduino-cli lib install "Pico PIO USB@0.6.1" \
|
|
||||||
&& arduino-cli lib install "XxHash_arduino@2.1.0" \
|
|
||||||
&& arduino-cli lib install "GFX Library for Arduino@1.5.3"
|
|
||||||
|
|
||||||
# Compilation setup
|
|
||||||
RUN echo "#!/bin/bash" > /app/entrypoint.sh \
|
|
||||||
&& echo "export PATH=\$PATH:/app/arduino-cli/bin" >> /app/entrypoint.sh \
|
|
||||||
&& echo "arduino-cli compile --fqbn rp2040:rp2040:rpipico --build-property \"build.extra_flags=-DCFG_TUD_CDC=1\" --board-options \"usbstack=tinyusb\" --board-options \"freq=240\" --output-dir \"/mnt/USBvalve_out\" \"\$1\"" >> /app/entrypoint.sh \
|
|
||||||
&& chmod +x /app/entrypoint.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
#
|
|
||||||
# To Build:
|
|
||||||
# docker build -t usbvalve-pico2/arduino-cli -f Dockerfile.pico2 .
|
|
||||||
#
|
|
||||||
# To Run:
|
|
||||||
# docker run --rm --name usbvalve -v $PWD:/mnt usbvalve-pico2/arduino-cli /mnt/USBvalve
|
|
||||||
#
|
|
||||||
|
|
||||||
FROM ubuntu:22.04
|
|
||||||
WORKDIR /app
|
|
||||||
|
|
||||||
# OS setup
|
|
||||||
RUN apt-get update -y \
|
|
||||||
&& apt-get install -y git wget python3 \
|
|
||||||
&& apt-get autoremove -y \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
|
||||||
|
|
||||||
# arduino-cli setup
|
|
||||||
RUN cd /app \
|
|
||||||
&& git clone --recursive https://github.com/arduino/arduino-cli.git \
|
|
||||||
&& cd arduino-cli \
|
|
||||||
&& ./install.sh \
|
|
||||||
&& export PATH=$PATH:/app/arduino-cli/bin \
|
|
||||||
&& arduino-cli --additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json core search 2040 \
|
|
||||||
&& arduino-cli --additional-urls https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json core install rp2040:rp2040@4.4.2 \
|
|
||||||
&& arduino-cli lib install "Adafruit TinyUSB Library@3.4.2" \
|
|
||||||
&& arduino-cli lib install "Adafruit SSD1306@2.5.13" \
|
|
||||||
&& arduino-cli lib install "Pico PIO USB@0.6.1" \
|
|
||||||
&& arduino-cli lib install "XxHash_arduino@2.1.0" \
|
|
||||||
&& arduino-cli lib install "GFX Library for Arduino@1.5.3"
|
|
||||||
|
|
||||||
# Compilation setup
|
|
||||||
RUN echo "#!/bin/bash" > /app/entrypoint.sh \
|
|
||||||
&& echo "export PATH=\$PATH:/app/arduino-cli/bin" >> /app/entrypoint.sh \
|
|
||||||
&& echo "arduino-cli compile --fqbn rp2040:rp2040:rpipico2 --build-property \"build.extra_flags=-DCFG_TUD_CDC=1\" --board-options \"usbstack=tinyusb\" --board-options \"freq=240\" --output-dir \"/mnt/USBvalve_out\" \"\$1\"" >> /app/entrypoint.sh \
|
|
||||||
&& chmod +x /app/entrypoint.sh
|
|
||||||
|
|
||||||
ENTRYPOINT ["/app/entrypoint.sh"]
|
|
||||||
59
Dockerfile.sdk
Normal file
59
Dockerfile.sdk
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
#
|
||||||
|
# To Build:
|
||||||
|
# docker build -t usbvalve-sdk -f Dockerfile.sdk .
|
||||||
|
#
|
||||||
|
# To Run (default: pico, SSD1306 OLED 128x32):
|
||||||
|
# docker run --rm -v $PWD:/mnt usbvalve-sdk
|
||||||
|
#
|
||||||
|
# Options (via environment variables):
|
||||||
|
# BOARD=pico|pico2 Board target (default: pico)
|
||||||
|
# OLED_HEIGHT=32|64 OLED display height (default: 32)
|
||||||
|
# PIWATCH=1 Use GC9A01 round TFT instead of SSD1306
|
||||||
|
# USE_BOOTSEL=1 Enable BOOTSEL button (disrupts Low Speed USB)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# docker run --rm -e BOARD=pico2 -v $PWD:/mnt usbvalve-sdk
|
||||||
|
# docker run --rm -e PIWATCH=1 -v $PWD:/mnt usbvalve-sdk
|
||||||
|
# docker run --rm -e BOARD=pico2 -e OLED_HEIGHT=64 -v $PWD:/mnt usbvalve-sdk
|
||||||
|
#
|
||||||
|
|
||||||
|
FROM ubuntu:22.04
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
# OS setup + ARM toolchain + CMake
|
||||||
|
RUN apt-get update -y \
|
||||||
|
&& apt-get install -y git cmake gcc-arm-none-eabi libnewlib-arm-none-eabi \
|
||||||
|
build-essential libstdc++-arm-none-eabi-newlib python3 \
|
||||||
|
&& apt-get autoremove -y \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*
|
||||||
|
|
||||||
|
# Clone Pico SDK
|
||||||
|
RUN cd /app \
|
||||||
|
&& git clone --depth 1 https://github.com/raspberrypi/pico-sdk.git \
|
||||||
|
&& cd pico-sdk \
|
||||||
|
&& git submodule update --init
|
||||||
|
|
||||||
|
ENV PICO_SDK_PATH=/app/pico-sdk
|
||||||
|
|
||||||
|
# Build script
|
||||||
|
RUN echo '#!/bin/bash' > /app/entrypoint.sh \
|
||||||
|
&& echo 'set -e' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'BOARD="${BOARD:-pico}"' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'if [ -d /mnt/USBvalve-sdk ]; then cd /mnt/USBvalve-sdk; elif [ -f /mnt/src/main.c ]; then cd /mnt; else echo "Error: mount the repo root or USBvalve-sdk dir to /mnt"; exit 1; fi' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'rm -rf build && mkdir build && cd build' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'CMAKE_ARGS="-DPICO_BOARD=$BOARD"' >> /app/entrypoint.sh \
|
||||||
|
&& echo '[ -n "$OLED_HEIGHT" ] && CMAKE_ARGS="$CMAKE_ARGS -DOLED_HEIGHT=$OLED_HEIGHT"' >> /app/entrypoint.sh \
|
||||||
|
&& echo '[ -n "$PIWATCH" ] && CMAKE_ARGS="$CMAKE_ARGS -DPIWATCH=$PIWATCH"' >> /app/entrypoint.sh \
|
||||||
|
&& echo '[ -n "$USE_BOOTSEL" ] && CMAKE_ARGS="$CMAKE_ARGS -DUSE_BOOTSEL=$USE_BOOTSEL"' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'echo "Building for $BOARD with: $CMAKE_ARGS"' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'cmake $CMAKE_ARGS ..' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'make -j$(nproc)' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'SUFFIX="$BOARD"' >> /app/entrypoint.sh \
|
||||||
|
&& echo '[ -n "$PIWATCH" ] && SUFFIX="${SUFFIX}-piwatch"' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'mkdir -p /mnt/USBvalve_out' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'cp src/USBvalve.uf2 /mnt/USBvalve_out/USBvalve-sdk-${SUFFIX}.uf2' >> /app/entrypoint.sh \
|
||||||
|
&& echo 'echo "Build complete: USBvalve_out/USBvalve-sdk-${SUFFIX}.uf2"' >> /app/entrypoint.sh \
|
||||||
|
&& chmod +x /app/entrypoint.sh
|
||||||
|
|
||||||
|
ENTRYPOINT ["/app/entrypoint.sh"]
|
||||||
88
README.md
88
README.md
@@ -1,5 +1,13 @@
|
|||||||
<h1><img width="300" alt="logo, landscape, dark text, transparent background" src="https://github.com/cecio/USBvalve/blob/main/pictures/USBvalve_logo_scaled.png"></a></h1>
|
<h1><img width="300" alt="logo, landscape, dark text, transparent background" src="https://github.com/cecio/USBvalve/blob/main/pictures/USBvalve_logo_scaled.png"></a></h1>
|
||||||
|
|
||||||
|
> [!NOTE]
|
||||||
|
> **USBvalve Version 1.0.0**
|
||||||
|
> A complete rewrite of the application has been done:
|
||||||
|
> - moved away from Arduino IDE environment, now the code is written for the [Pi Pico SDK](https://github.com/raspberrypi/pico-sdk)
|
||||||
|
> - dependencies on external libraries has been reduced a lot
|
||||||
|
> - USB host support for Low Speed devices is now more robust (ATTiny85, EvilCrow, etc)
|
||||||
|
> - hardware and functionalities are almost unchanged, see the [notes](https://github.com/cecio/USBvalve#notes-about-bootsel-and-version--100) below for details
|
||||||
|
|
||||||
### *Expose USB activity on the fly*
|
### *Expose USB activity on the fly*
|
||||||
|
|
||||||
<p float="left">
|
<p float="left">
|
||||||
@@ -42,11 +50,11 @@ This is also fully compatible with the [Waveshare RP2040-LCD-1.28](https://www.w
|
|||||||
|
|
||||||
`docs`: documentation about the project, with a presentation where you can have a look to all the features
|
`docs`: documentation about the project, with a presentation where you can have a look to all the features
|
||||||
|
|
||||||
`firmware`: pre-built firmware for the Raspberry Pi Pico. You can just use these and flash them on the board. I prepared the two versions for 32 and 64 OLED versions
|
`firmware`: pre-built firmwares for the Raspberry Pi Pico. You can just use these and flash them on the board. We have several different versions, for 32 or 64 lines OLEDs, for Pico 1 or Pico2, Pi Pico Watch, etc
|
||||||
|
|
||||||
`PCB`: Gerber file if you want to print the custom PCB . It's not mandatory, you can use your own or build it on a breadboard
|
`PCB`: Gerber file if you want to print the custom PCB . It's not mandatory, you can use your own or build it on a breadboard
|
||||||
|
|
||||||
`USBvalve`: sources, if you want to modify and build the firmware by yourself
|
`src` and `data`: sources, if you want to modify and build the firmware yourself
|
||||||
|
|
||||||
`utils`: some utilities you may use to build a custom FS
|
`utils`: some utilities you may use to build a custom FS
|
||||||
|
|
||||||
@@ -60,11 +68,25 @@ This is also fully compatible with the [Waveshare RP2040-LCD-1.28](https://www.w
|
|||||||
|
|
||||||
If you want to build your own, you need:
|
If you want to build your own, you need:
|
||||||
|
|
||||||
- A Raspberry Pi Pico 1 or 2 (or another RP2040 based board, like Arduino Nano RP2040)
|
- A Raspberry Pi Pico 1 or 2 (or another RP2040 based board)
|
||||||
- an I2C OLED screen 128x64 or 128x32 (SSD1306)
|
- an I2C OLED screen 128x64 or 128x32 (**SSD1306**)
|
||||||
- (optional) a **USBvalve** PCB or a breadboard
|
- (optional) a **USBvalve** PCB or a breadboard
|
||||||
- (optional) a 3D printed spacer to isolate the screen from the board (https://www.thingiverse.com/thing:4748043), but you can use a piece of electrical tape instead
|
- (optional) a 3D printed spacer to isolate the screen from the board (https://www.thingiverse.com/thing:4748043), but you can use a piece of electrical tape instead
|
||||||
|
|
||||||
|
### Notes about BOOTSEL and version >= 1.0.0
|
||||||
|
|
||||||
|
In the `0.x.x` versions, BOOTSEL was used to reset the device or print the number of HID events (press > 2s).
|
||||||
|
The polling of BOOTSEL was creating some issues to the *BADUSB* detection so this was removed from version `1.0.0` and replaced with the following two options:
|
||||||
|
- solder a button between `GP0` and `GND` (pads 1 and 3, see pic below to see an example) to have the same functions
|
||||||
|
- use the commands `r` (reset) and `h` (HID events) from the serial monitor
|
||||||
|
|
||||||
|
<img src="https://github.com/cecio/USBvalve/blob/main/pictures/reset_button.jpg" width="35%" height="35%" />
|
||||||
|
|
||||||
|
~~In `PCB` you'll find also a version `1.3` of the board, with some holes on pads 1 and 3 to facilitate the mount of the button. But as you can see, you can also use your old version~~. (still waiting the new printed PCB to test it before publishing)
|
||||||
|
|
||||||
|
If you are not using the *BADUSB* functions or if you prefer to have less coverage on detection but keep BOOTSEL usage, I'm also providing a firmware created with *bootsel* enabled (see folder and releases).
|
||||||
|
|
||||||
|
|
||||||
### Building instructions
|
### Building instructions
|
||||||
|
|
||||||
> Thanks to [Tz1rf](https://github.com/Tz1rf) we also have two great videos: one explaining the [building](https://youtu.be/7ymk8hD7-Hc) process step-by-step, and another showing how to [upload firmware](https://youtu.be/Tp8xvrlqxUY) and use the tool.
|
> Thanks to [Tz1rf](https://github.com/Tz1rf) we also have two great videos: one explaining the [building](https://youtu.be/7ymk8hD7-Hc) process step-by-step, and another showing how to [upload firmware](https://youtu.be/Tp8xvrlqxUY) and use the tool.
|
||||||
@@ -165,10 +187,10 @@ It's done!
|
|||||||
|
|
||||||
I don't know if it will ever be the case, but you may want to customize the firmware in order to avoid detection done by *USBvalve-aware* malware :-)
|
I don't know if it will ever be the case, but you may want to customize the firmware in order to avoid detection done by *USBvalve-aware* malware :-)
|
||||||
|
|
||||||
I grouped most of the variables you may want to modify in this section ([see Dockerfile below for rebuilding](https://github.com/cecio/USBvalve#dockerfile))
|
I grouped most of the variables you may want to modify in this section of `usb_config.h` ([see Dockerfile below for rebuilding](https://github.com/cecio/USBvalve#dockerfile))
|
||||||
|
|
||||||
```C
|
```C
|
||||||
// Anti-Detection settings.
|
// USB anti-detection settings
|
||||||
//
|
//
|
||||||
// Set USB IDs strings and numbers, to avoid possible detections.
|
// Set USB IDs strings and numbers, to avoid possible detections.
|
||||||
// Remember that you can cusotmize FAKE_DISK_BLOCK_NUM as well
|
// Remember that you can cusotmize FAKE_DISK_BLOCK_NUM as well
|
||||||
@@ -179,39 +201,55 @@ I grouped most of the variables you may want to modify in this section ([see Doc
|
|||||||
// Example:
|
// Example:
|
||||||
// 0x0951 0x16D5 VENDORID_STR: Kingston PRODUCTID_STR: DataTraveler
|
// 0x0951 0x16D5 VENDORID_STR: Kingston PRODUCTID_STR: DataTraveler
|
||||||
//
|
//
|
||||||
#define USB_VENDORID 0x0951 // This override the Pi Pico default 0x2E8A
|
#define USB_VENDORID 0x0951
|
||||||
#define USB_PRODUCTID 0x16D5 // This override the Pi Pico default 0x000A
|
#define USB_PRODUCTID 0x16D5
|
||||||
#define USB_DESCRIPTOR "DataTraveler" // This override the Pi Pico default "Pico"
|
#define USB_DESCRIPTOR "DataTraveler"
|
||||||
#define USB_MANUF "Kingston" // This override the Pi Pico default "Raspberry Pi"
|
#define USB_MANUF "Kingston"
|
||||||
#define USB_SERIAL "123456789A" // This override the Pi Pico default. Disabled by default. \
|
#define USB_SERIAL "123456789A"
|
||||||
// See "setSerialDescriptor" in setup() if needed
|
#define USB_VENDORID_STR "Kingston" // Up to 8 chars
|
||||||
#define USB_VENDORID_STR "Kingston" // Up to 8 chars
|
#define USB_PRODUCTID_STR "DataTraveler" // Up to 16 chars
|
||||||
#define USB_PRODUCTID_STR "DataTraveler" // Up to 16 chars
|
#define USB_VERSION_STR "1.0" // Up to 4 chars
|
||||||
#define USB_VERSION_STR "1.0" // Up to 4 chars
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
### Building your firmware
|
### Building your firmware
|
||||||
|
|
||||||
Obviously you can also build your own firmware. To build the *standard* one I used:
|
Obviously you can also build your own firmware. To build you need the *Raspberry Pi Pico SDK* and this repoository.
|
||||||
|
|
||||||
- Arduino IDE `2.3.4`
|
Basic recompile instructions:
|
||||||
- `Adafruit TinyUSB Library` version `3.4.2`, `Pico-PIO-USB` version `0.6.1`, Board `Raspberry Pi RP2040 (4.4.0)` setting Tools=>CPU Speed at `133MHz` and Tools=>USB Stack to `Adafruit TinyUSB`
|
```
|
||||||
- `Adafruit_SSD1306` OLED library version `2.5.13`
|
export PICO_SDK_PATH=</path/to/pico-sdk>
|
||||||
|
mkdir build && cd build
|
||||||
Remember to add `https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json` in the `Additional Board Manager URLs` to install the proper board. Also, starting from `TinyUSB` version `3.4.2` is necessary to force the following macro setting `DCFG_TUD_CDC=1`. I strongly suggest you to use the provided *Dockerfiles* (see below).
|
cmake -DPICO_BOARD=pico .. # or pico2 for standard build
|
||||||
|
make -j$(nproc)
|
||||||
|
# Output: build/src/USBvalve.uf2
|
||||||
|
```
|
||||||
|
|
||||||
If you want to re-create a new fake filesystem, you may want to have a look to the `utils` folder, where I placed some utilities to build a new one.
|
If you want to re-create a new fake filesystem, you may want to have a look to the `utils` folder, where I placed some utilities to build a new one.
|
||||||
|
|
||||||
#### Dockerfile
|
#### Dockerfile
|
||||||
|
|
||||||
If you want to build your own firmware, after you customized it, I provide a `Dockerfile` which builds a complete **Arduino** environment and compile the firmware. I added them for both `Pico` version 1 and 2.
|
If you want to build your own firmware, after you customized it, I provide a `Dockerfile` which builds a complete building environment and compile the firmware.
|
||||||
|
|
||||||
Enter the following commands in the main `USBvalve` folder to build for Pico `v1`:
|
Enter the following commands in the main `USBvalve` folder to build:
|
||||||
|
|
||||||
```
|
```
|
||||||
docker build -t usbvalve-pico1/arduino-cli -f Dockerfile.pico1 .
|
# To Build:
|
||||||
docker run --rm --name usbvalve -v $PWD:/mnt usbvalve-pico1/arduino-cli /mnt/USBvalve
|
# docker build -t usbvalve-sdk -f Dockerfile.sdk .
|
||||||
|
#
|
||||||
|
# To Run (default: pico, SSD1306 OLED 128x32):
|
||||||
|
# docker run --rm -v $PWD:/mnt usbvalve-sdk
|
||||||
|
#
|
||||||
|
# Options (via environment variables):
|
||||||
|
# BOARD=pico|pico2 Board target (default: pico)
|
||||||
|
# OLED_HEIGHT=32|64 OLED display height (default: 32)
|
||||||
|
# PIWATCH=1 Use GC9A01 round TFT instead of SSD1306
|
||||||
|
# USE_BOOTSEL=1 Enable BOOTSEL button (disrupts Low Speed USB)
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# docker run --rm -e BOARD=pico2 -v $PWD:/mnt usbvalve-sdk
|
||||||
|
# docker run --rm -e PIWATCH=1 -v $PWD:/mnt usbvalve-sdk
|
||||||
|
# docker run --rm -e BOARD=pico2 -e OLED_HEIGHT=64 -v $PWD:/mnt usbvalve-sdk
|
||||||
```
|
```
|
||||||
|
|
||||||
The firmware will be placed with extension `uf2` in folder `USBvalve_out`.
|
The firmware will be placed with extension `uf2` in folder `USBvalve_out`.
|
||||||
|
|||||||
@@ -1,798 +0,0 @@
|
|||||||
/*********************************************************************
|
|
||||||
|
|
||||||
USBvalve
|
|
||||||
|
|
||||||
written by Cesare Pizzi
|
|
||||||
This project extensively reuse code done by Adafruit and TinyUSB.
|
|
||||||
Please support them!
|
|
||||||
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
/*********************************************************************
|
|
||||||
Adafruit invests time and resources providing this open source code,
|
|
||||||
please support Adafruit and open-source hardware by purchasing
|
|
||||||
products from Adafruit!
|
|
||||||
|
|
||||||
MIT license, check LICENSE for more information
|
|
||||||
Copyright (c) 2019 Ha Thach for Adafruit Industries
|
|
||||||
All text above, and the splash screen below must be included in
|
|
||||||
any redistribution
|
|
||||||
*********************************************************************/
|
|
||||||
|
|
||||||
// Uncomment the following to compile for the RP2040 based TFT round display
|
|
||||||
// https://www.raspberrypi.com/news/how-to-build-your-own-raspberry-pi-watch/
|
|
||||||
//#define PIWATCH
|
|
||||||
|
|
||||||
#include <pio_usb.h>
|
|
||||||
#include "Adafruit_TinyUSB.h"
|
|
||||||
#include <XxHash_arduino.h>
|
|
||||||
#include <pico/stdlib.h>
|
|
||||||
|
|
||||||
#if defined(PIWATCH)
|
|
||||||
|
|
||||||
#include <Arduino_GFX_Library.h>
|
|
||||||
#include "background.h"
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#include <SPI.h>
|
|
||||||
#include <Wire.h>
|
|
||||||
#include <Adafruit_GFX.h>
|
|
||||||
#include <Adafruit_SSD1306.h>
|
|
||||||
|
|
||||||
// LED Pin. If solid GREEN everything is OK, otherwise it will be put OFF
|
|
||||||
#define LED_PIN 25
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
|
||||||
// BADUSB detector section
|
|
||||||
//
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Requirements:
|
|
||||||
* - [Pico-PIO-USB](https://github.com/sekigon-gonnoc/Pico-PIO-USB) library
|
|
||||||
* - 2 consecutive GPIOs: D+ is defined by HOST_PIN_DP (gpio2), D- = D+ +1 (gpio3)
|
|
||||||
* - CPU Speed must be either 120 or 240 Mhz. Selected via "Menu -> CPU Speed"
|
|
||||||
*/
|
|
||||||
|
|
||||||
#define HOST_PIN_DP 14 // Pin used as D+ for host, D- = D+ + 1
|
|
||||||
#define LANGUAGE_ID 0x0409 // English
|
|
||||||
|
|
||||||
// USB Host object
|
|
||||||
Adafruit_USBH_Host USBHost;
|
|
||||||
|
|
||||||
// END of BADUSB detector section
|
|
||||||
|
|
||||||
// Define vars for OLED screen
|
|
||||||
#if defined(PIWATCH)
|
|
||||||
|
|
||||||
#define GFX_DC 8
|
|
||||||
#define GFX_CS 9
|
|
||||||
#define GFX_MOSI 11
|
|
||||||
#define GFX_CLK 10
|
|
||||||
#define GFX_RST 12
|
|
||||||
#define GFX_MISO 12
|
|
||||||
#define GFX_BL 25 // Backlight
|
|
||||||
|
|
||||||
Arduino_DataBus *bus = new Arduino_RPiPicoSPI(GFX_DC, GFX_CS, GFX_CLK, GFX_MOSI, GFX_MISO, spi1 /* spi */);
|
|
||||||
Arduino_GFX *gfx = new Arduino_GC9A01(bus, GFX_RST, 1 /* rotation */, true /* IPS */);
|
|
||||||
|
|
||||||
#else
|
|
||||||
|
|
||||||
#define I2C_ADDRESS 0x3C // 0X3C+SA0 - 0x3C or 0x3D
|
|
||||||
#define RST_PIN -1 // Define proper RST_PIN if required.
|
|
||||||
#define OLED_WIDTH 128
|
|
||||||
#define OLED_HEIGHT 32 // 64 or 32 depending on the OLED
|
|
||||||
|
|
||||||
Adafruit_SSD1306 display(OLED_WIDTH, OLED_HEIGHT, &Wire, RST_PIN);
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Define the dimension of RAM DISK. We have a "real" one (for which
|
|
||||||
// a real array is created) and a "fake" one, presented to the OS
|
|
||||||
#define DISK_BLOCK_NUM 0x150
|
|
||||||
#define FAKE_DISK_BLOCK_NUM 0x800
|
|
||||||
#define DISK_BLOCK_SIZE 0x200
|
|
||||||
#include "ramdisk.h"
|
|
||||||
|
|
||||||
Adafruit_USBD_MSC usb_msc;
|
|
||||||
|
|
||||||
// Eject button to demonstrate medium is not ready e.g SDCard is not present
|
|
||||||
// whenever this button is pressed and hold, it will report to host as not ready
|
|
||||||
#if defined(ARDUINO_SAMD_CIRCUITPLAYGROUND_EXPRESS) || defined(ARDUINO_NRF52840_CIRCUITPLAY)
|
|
||||||
#define BTN_EJECT 4 // Left Button
|
|
||||||
bool activeState = true;
|
|
||||||
|
|
||||||
#elif defined(ARDUINO_FUNHOUSE_ESP32S2)
|
|
||||||
#define BTN_EJECT BUTTON_DOWN
|
|
||||||
bool activeState = true;
|
|
||||||
|
|
||||||
#elif defined PIN_BUTTON1
|
|
||||||
#define BTN_EJECT PIN_BUTTON1
|
|
||||||
bool activeState = false;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
//
|
|
||||||
// USBvalve globals
|
|
||||||
//
|
|
||||||
#define VERSION "USBvalve - 0.21.0"
|
|
||||||
boolean readme = false;
|
|
||||||
boolean autorun = false;
|
|
||||||
boolean written = false;
|
|
||||||
boolean deleted = false;
|
|
||||||
boolean written_reported = false;
|
|
||||||
boolean deleted_reported = false;
|
|
||||||
boolean hid_sent = false;
|
|
||||||
boolean hid_reported = false;
|
|
||||||
uint hid_event_num = 0;
|
|
||||||
|
|
||||||
static spin_lock_t *lock;
|
|
||||||
|
|
||||||
//
|
|
||||||
// Anti-Detection settings.
|
|
||||||
//
|
|
||||||
// Set USB IDs strings and numbers, to avoid possible detections.
|
|
||||||
// Remember that you can cusotmize FAKE_DISK_BLOCK_NUM as well
|
|
||||||
// for the same reason. Also DISK_LABEL in ramdisk.h can be changed.
|
|
||||||
//
|
|
||||||
// You can see here for inspiration: https://the-sz.com/products/usbid/
|
|
||||||
//
|
|
||||||
// Example:
|
|
||||||
// 0x0951 0x16D5 VENDORID_STR: Kingston PRODUCTID_STR: DataTraveler
|
|
||||||
//
|
|
||||||
#define USB_VENDORID 0x0951 // This override the Pi Pico default 0x2E8A
|
|
||||||
#define USB_PRODUCTID 0x16D5 // This override the Pi Pico default 0x000A
|
|
||||||
#define USB_DESCRIPTOR "DataTraveler" // This override the Pi Pico default "Pico"
|
|
||||||
#define USB_MANUF "Kingston" // This override the Pi Pico default "Raspberry Pi"
|
|
||||||
#define USB_SERIAL "123456789A" // This override the Pi Pico default. Disabled by default. \
|
|
||||||
// See "setSerialDescriptor" in setup() if needed
|
|
||||||
#define USB_VENDORID_STR "Kingston" // Up to 8 chars
|
|
||||||
#define USB_PRODUCTID_STR "DataTraveler" // Up to 16 chars
|
|
||||||
#define USB_VERSION_STR "1.0" // Up to 4 chars
|
|
||||||
|
|
||||||
#define BLOCK_AUTORUN 102 // Block where Autorun.inf file is saved
|
|
||||||
#define BLOCK_README 100 // Block where README.txt file is saved
|
|
||||||
#define MAX_DUMP_BYTES 16 // Used by the dump of the debug facility: do not increase this too much
|
|
||||||
#define BYTES_TO_HASH 512 * 2 // Number of bytes of the RAM disk used to check consistency
|
|
||||||
#define BYTES_TO_HASH_OFFSET 7 // Starting sector to check for consistency (FAT_DIRECTORY is 7)
|
|
||||||
|
|
||||||
// Burned hash to check consistency
|
|
||||||
uint valid_hash = 2362816530;
|
|
||||||
|
|
||||||
// Core 0 Setup: will be used for the USB mass device functions
|
|
||||||
void setup() {
|
|
||||||
// Initialize the spinlock
|
|
||||||
lock = spin_lock_instance(0);
|
|
||||||
|
|
||||||
// Change all the USB Pico settings
|
|
||||||
TinyUSBDevice.setID(USB_VENDORID, USB_PRODUCTID);
|
|
||||||
TinyUSBDevice.setProductDescriptor(USB_DESCRIPTOR);
|
|
||||||
TinyUSBDevice.setManufacturerDescriptor(USB_MANUF);
|
|
||||||
// This could be used to change the serial number as well
|
|
||||||
// TinyUSBDevice.setSerialDescriptor(USB_SERIAL);
|
|
||||||
|
|
||||||
#if defined(ARDUINO_ARCH_MBED) && defined(ARDUINO_ARCH_RP2040)
|
|
||||||
// Manual begin() is required on core without built-in support for TinyUSB such as
|
|
||||||
// - mbed rp2040
|
|
||||||
TinyUSB_Device_Init(0);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Set disk vendor id, product id and revision with string up to 8, 16, 4 characters respectively
|
|
||||||
usb_msc.setID(USB_VENDORID_STR, USB_PRODUCTID_STR, USB_VERSION_STR);
|
|
||||||
|
|
||||||
// Set disk size (using the "fake" size)
|
|
||||||
usb_msc.setCapacity(FAKE_DISK_BLOCK_NUM, DISK_BLOCK_SIZE);
|
|
||||||
|
|
||||||
// Set the callback functions
|
|
||||||
usb_msc.setReadWriteCallback(msc_read_callback, msc_write_callback, msc_flush_callback);
|
|
||||||
|
|
||||||
// Set Lun ready (RAM disk is always ready)
|
|
||||||
usb_msc.setUnitReady(true);
|
|
||||||
|
|
||||||
#ifdef BTN_EJECT
|
|
||||||
pinMode(BTN_EJECT, activeState ? INPUT_PULLDOWN : INPUT_PULLUP);
|
|
||||||
usb_msc.setReadyCallback(msc_ready_callback);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Check consistency of RAM FS
|
|
||||||
// Add 11 bytes to skip the DISK_LABEL from the hashing
|
|
||||||
// The startup of the USB has been moved before initialization of the
|
|
||||||
// screen because sometimes it inserts some delay preventing
|
|
||||||
// proper initialization of the mass device
|
|
||||||
uint computed_hash;
|
|
||||||
computed_hash = XXH32(msc_disk[BYTES_TO_HASH_OFFSET] + 11, BYTES_TO_HASH, 0);
|
|
||||||
if (computed_hash == valid_hash) {
|
|
||||||
usb_msc.begin();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Screen Init
|
|
||||||
#if defined(PIWATCH)
|
|
||||||
gfx->begin();
|
|
||||||
pinMode(GFX_BL, OUTPUT);
|
|
||||||
digitalWrite(GFX_BL, HIGH); // Backlight on
|
|
||||||
gfx->fillScreen(BLACK); // Clear screen
|
|
||||||
gfx->draw16bitRGBBitmap(10,0,background,210,210); // Draw background
|
|
||||||
delay(2000);
|
|
||||||
#else
|
|
||||||
#if RST_PIN >= 0
|
|
||||||
display.begin(SSD1306_SWITCHCAPVCC, I2C_ADDRESS, RST_PIN);
|
|
||||||
#else
|
|
||||||
display.begin(SSD1306_SWITCHCAPVCC, I2C_ADDRESS);
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(PIWATCH)
|
|
||||||
gfx->setTextSize(1);
|
|
||||||
gfx->setTextColor(MAGENTA);
|
|
||||||
#else
|
|
||||||
display.setTextSize(1);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
cls(); // Clear display
|
|
||||||
|
|
||||||
// Now outputs the result of the check
|
|
||||||
if (computed_hash == valid_hash) {
|
|
||||||
printout("\n[+] Selftest: OK");
|
|
||||||
} else {
|
|
||||||
printout("\n[!] Selftest: KO");
|
|
||||||
printout("\n[!] Stopping...");
|
|
||||||
while (1) {
|
|
||||||
delay(1000); // Loop forever
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#if !defined(PIWATCH)
|
|
||||||
// Set up led PIN
|
|
||||||
gpio_init(LED_PIN);
|
|
||||||
gpio_set_dir(LED_PIN, GPIO_OUT);
|
|
||||||
gpio_put(LED_PIN, 1);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Core 1 Setup: will be used for the USB host functions for BADUSB detector
|
|
||||||
void setup1() {
|
|
||||||
// Set a custom clock (multiple of 12Mhz) to achieve maximum compatibility for HID
|
|
||||||
// Differntiated between Pico 1 and Pico 2
|
|
||||||
#ifdef PICO_RP2350
|
|
||||||
set_sys_clock_khz(144000, true);
|
|
||||||
#elif defined PICO_RP2040
|
|
||||||
set_sys_clock_khz(240000, true);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
|
|
||||||
pio_cfg.pin_dp = HOST_PIN_DP;
|
|
||||||
USBHost.configure_pio_usb(1, &pio_cfg);
|
|
||||||
|
|
||||||
// run host stack on controller (rhport) 1
|
|
||||||
// Note: For rp2040 pico-pio-usb, calling USBHost.begin() on core1 will have most of the
|
|
||||||
// host bit-banging processing works done in core1
|
|
||||||
USBHost.begin(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main Core0 loop: managing display
|
|
||||||
void loop() {
|
|
||||||
|
|
||||||
if (readme == true) {
|
|
||||||
printout("\n[!] README (R)");
|
|
||||||
readme = false;
|
|
||||||
#if !defined(PIWATCH)
|
|
||||||
gpio_put(LED_PIN, 0); // Turn Off LED
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autorun == true) {
|
|
||||||
printout("\n[+] AUTORUN (R)");
|
|
||||||
autorun = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deleted == true && deleted_reported == false) {
|
|
||||||
printout("\n[!] DELETING");
|
|
||||||
deleted = false;
|
|
||||||
deleted_reported = true;
|
|
||||||
#if !defined(PIWATCH)
|
|
||||||
gpio_put(LED_PIN, 0); // Turn Off LED
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (written == true && written_reported == false) {
|
|
||||||
printout("\n[!] WRITING");
|
|
||||||
written = false;
|
|
||||||
written_reported = true;
|
|
||||||
#if !defined(PIWATCH)
|
|
||||||
gpio_put(LED_PIN, 0); // Turn Off LED
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hid_sent == true && hid_reported == false) {
|
|
||||||
printout("\n[!!] HID Sending data");
|
|
||||||
hid_sent = false;
|
|
||||||
hid_reported = true;
|
|
||||||
#if !defined(PIWATCH)
|
|
||||||
gpio_put(LED_PIN, 0); // Turn Off LED
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (BOOTSEL) {
|
|
||||||
uint32_t press_start = to_ms_since_boot(get_absolute_time());
|
|
||||||
while (BOOTSEL) {
|
|
||||||
sleep_ms(10);
|
|
||||||
}
|
|
||||||
uint32_t press_end = to_ms_since_boot(get_absolute_time());
|
|
||||||
uint32_t press_duration = press_end - press_start;
|
|
||||||
|
|
||||||
if (press_duration > 2000) { // Press duration > 2sec
|
|
||||||
// Print the number of HID events detected so far
|
|
||||||
char outstr[22];
|
|
||||||
snprintf(outstr, 21, "\n[+] HID Evt# %d", hid_event_num);
|
|
||||||
printout(outstr);
|
|
||||||
} else {
|
|
||||||
printout("\n[+] RESETTING");
|
|
||||||
swreset();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Main Core1 loop: managing USB Host
|
|
||||||
void loop1() {
|
|
||||||
USBHost.task();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback invoked when received READ10 command.
|
|
||||||
// Copy disk's data to buffer (up to bufsize) and
|
|
||||||
// return number of copied bytes (must be multiple of block size).
|
|
||||||
// This happens only for the "real" size of disk
|
|
||||||
int32_t msc_read_callback(uint32_t lba, void* buffer, uint32_t bufsize) {
|
|
||||||
|
|
||||||
// Check for README.TXT
|
|
||||||
if (lba == BLOCK_README) {
|
|
||||||
readme = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for AUTORUN.INF
|
|
||||||
if (lba == BLOCK_AUTORUN) {
|
|
||||||
autorun = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are declaring a bigger size than what is actually allocated, so
|
|
||||||
// this is protecting our memory integrity
|
|
||||||
if (lba < DISK_BLOCK_NUM - 1) {
|
|
||||||
uint8_t const* addr = msc_disk[lba];
|
|
||||||
memcpy(buffer, addr, bufsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
SerialTinyUSB.print("Read LBA: ");
|
|
||||||
SerialTinyUSB.print(lba);
|
|
||||||
SerialTinyUSB.print(" Size: ");
|
|
||||||
SerialTinyUSB.println(bufsize);
|
|
||||||
if (lba < DISK_BLOCK_NUM - 1) {
|
|
||||||
hexDump(msc_disk[lba], MAX_DUMP_BYTES);
|
|
||||||
}
|
|
||||||
SerialTinyUSB.flush();
|
|
||||||
|
|
||||||
return bufsize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback invoked when received WRITE10 command.
|
|
||||||
// Process data in buffer to disk's storage and
|
|
||||||
// return number of written bytes (must be multiple of block size).
|
|
||||||
// This happens only for the "real" size of disk
|
|
||||||
int32_t msc_write_callback(uint32_t lba, uint8_t* buffer, uint32_t bufsize) {
|
|
||||||
|
|
||||||
// Check for file deletion at Block 7
|
|
||||||
// The first char of filename is replaced with 0xE5, we are going
|
|
||||||
// to check for it
|
|
||||||
if (lba == 7) {
|
|
||||||
if (buffer[32] == 0xE5 || buffer[64] == 0xE5 || buffer[160] == 0xE5) {
|
|
||||||
deleted = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This check for writing of space. The LBA > 10 is set to avoid some
|
|
||||||
// false positives, in particular on Windows Systems
|
|
||||||
if (lba > 10) {
|
|
||||||
written = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We are declaring a bigger size than what is actually allocated, so
|
|
||||||
// this is protecting our memory integrity
|
|
||||||
if (lba < DISK_BLOCK_NUM - 1) {
|
|
||||||
// Writing buffer to "disk"
|
|
||||||
uint8_t* addr = msc_disk[lba];
|
|
||||||
memcpy(addr, buffer, bufsize);
|
|
||||||
}
|
|
||||||
|
|
||||||
SerialTinyUSB.print("Write LBA: ");
|
|
||||||
SerialTinyUSB.print(lba);
|
|
||||||
SerialTinyUSB.print(" Size: ");
|
|
||||||
SerialTinyUSB.println(bufsize);
|
|
||||||
if (lba < DISK_BLOCK_NUM - 1) {
|
|
||||||
hexDump(msc_disk[lba], MAX_DUMP_BYTES);
|
|
||||||
}
|
|
||||||
SerialTinyUSB.flush();
|
|
||||||
|
|
||||||
return bufsize;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Callback invoked when WRITE10 command is completed (status received and accepted by host).
|
|
||||||
// used to flush any pending cache.
|
|
||||||
void msc_flush_callback(void) {
|
|
||||||
// Nothing to do
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifdef BTN_EJECT
|
|
||||||
// Invoked when received Test Unit Ready command.
|
|
||||||
// return true allowing host to read/write this LUN e.g SD card inserted
|
|
||||||
bool msc_ready_callback(void) {
|
|
||||||
// button not active --> medium ready
|
|
||||||
return digitalRead(BTN_EJECT) != activeState;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(PIWATCH)
|
|
||||||
void printout(const char *str)
|
|
||||||
{
|
|
||||||
int y;
|
|
||||||
|
|
||||||
y = gfx->getCursorY();
|
|
||||||
// Check if we reached the end of the printable area
|
|
||||||
if (y > 120) {
|
|
||||||
cls();
|
|
||||||
y = gfx->getCursorY();
|
|
||||||
}
|
|
||||||
gfx->setCursor(70, y+10);
|
|
||||||
|
|
||||||
// Skip the newline at the beginning if used
|
|
||||||
if (str[0] == '\n') {
|
|
||||||
gfx->print(str+1);
|
|
||||||
} else {
|
|
||||||
gfx->print(str);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output on serial device
|
|
||||||
SerialTinyUSB.println(str);
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
|
|
||||||
void scrollUp(uint8_t pixels) {
|
|
||||||
// Read the current content of the display, shift it up by 'pixels' rows
|
|
||||||
display.startscrollright(0x00, 0x07); // Dummy values to initiate scroll
|
|
||||||
display.stopscroll(); // Immediately stop to manually shift pixels in memory
|
|
||||||
for (int i = 0; i < display.height() - pixels; i++) {
|
|
||||||
for (int j = 0; j < display.width(); j++) {
|
|
||||||
uint8_t color = display.getPixel(j, i + pixels);
|
|
||||||
display.drawPixel(j, i, color);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clear the freed space after scrolling
|
|
||||||
display.fillRect(0, display.height() - pixels, display.width(), pixels, SSD1306_BLACK);
|
|
||||||
// Refresh the display to show the changes
|
|
||||||
display.display();
|
|
||||||
}
|
|
||||||
|
|
||||||
void checkAndScroll() {
|
|
||||||
// Assumes text height of 8 pixels, but check for 16 because newline is not used
|
|
||||||
if ((display.getCursorY() + 16) > display.height()) {
|
|
||||||
// Scroll up by 8 pixels
|
|
||||||
scrollUp(8);
|
|
||||||
display.setCursor(0, display.getCursorY() - 8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void printout(const char *str)
|
|
||||||
{
|
|
||||||
checkAndScroll();
|
|
||||||
display.print(str);
|
|
||||||
display.display();
|
|
||||||
|
|
||||||
// Output on serial device
|
|
||||||
SerialTinyUSB.println(str);
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(PIWATCH)
|
|
||||||
// Clear display
|
|
||||||
void cls(void) {
|
|
||||||
gfx->fillRoundRect(60, 70, 140, 75, 10, gfx->color565(0, 0, 0));
|
|
||||||
gfx->setCursor(70, 80);
|
|
||||||
gfx->print(VERSION);
|
|
||||||
gfx->setCursor(70, 90);
|
|
||||||
gfx->print("-----------------");
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
// Clear display
|
|
||||||
void cls(void) {
|
|
||||||
display.clearDisplay();
|
|
||||||
display.setTextColor(SSD1306_WHITE);
|
|
||||||
display.setCursor(0, 0);
|
|
||||||
printout(VERSION);
|
|
||||||
printout("\n-----------------");
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// HexDump
|
|
||||||
void hexDump(unsigned char* data, size_t size) {
|
|
||||||
char asciitab[17];
|
|
||||||
size_t i, j;
|
|
||||||
asciitab[16] = '\0';
|
|
||||||
|
|
||||||
for (i = 0; i < size; ++i) {
|
|
||||||
|
|
||||||
SerialTinyUSB.print(data[i] >> 4, HEX);
|
|
||||||
SerialTinyUSB.print(data[i] & 0x0F, HEX);
|
|
||||||
|
|
||||||
if ((data)[i] >= ' ' && (data)[i] <= '~') {
|
|
||||||
asciitab[i % 16] = (data)[i];
|
|
||||||
} else {
|
|
||||||
asciitab[i % 16] = '.';
|
|
||||||
}
|
|
||||||
if ((i + 1) % 8 == 0 || i + 1 == size) {
|
|
||||||
SerialTinyUSB.print(" ");
|
|
||||||
if ((i + 1) % 16 == 0) {
|
|
||||||
SerialTinyUSB.println(asciitab);
|
|
||||||
} else if (i + 1 == size) {
|
|
||||||
asciitab[(i + 1) % 16] = '\0';
|
|
||||||
if ((i + 1) % 16 <= 8) {
|
|
||||||
SerialTinyUSB.print(" ");
|
|
||||||
}
|
|
||||||
for (j = (i + 1) % 16; j < 16; ++j) {
|
|
||||||
SerialTinyUSB.print(" ");
|
|
||||||
}
|
|
||||||
SerialTinyUSB.print("| ");
|
|
||||||
SerialTinyUSB.println(asciitab);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SerialTinyUSB.println();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reset the Pico
|
|
||||||
void swreset() {
|
|
||||||
watchdog_enable(1500, 1);
|
|
||||||
while (1)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
|
||||||
// BADUSB detector section
|
|
||||||
//
|
|
||||||
|
|
||||||
static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII };
|
|
||||||
|
|
||||||
// Invoked when device with hid interface is mounted
|
|
||||||
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* desc_report, uint16_t desc_len) {
|
|
||||||
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
|
|
||||||
uint16_t vid, pid;
|
|
||||||
const char* protocol_str[] = { "None", "Keyboard", "Mouse" };
|
|
||||||
|
|
||||||
// Read the HID protocol
|
|
||||||
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
|
||||||
|
|
||||||
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
|
||||||
|
|
||||||
printout("\n[!!] HID Device");
|
|
||||||
#if !defined(PIWATCH)
|
|
||||||
gpio_put(LED_PIN, 0); // Turn Off LED
|
|
||||||
#endif
|
|
||||||
|
|
||||||
SerialTinyUSB.printf("HID device address = %d, instance = %d mounted\r\n", dev_addr, instance);
|
|
||||||
SerialTinyUSB.printf("VID = %04x, PID = %04x\r\n", vid, pid);
|
|
||||||
SerialTinyUSB.printf("HID Interface Protocol = %s\r\n", protocol_str[itf_protocol]);
|
|
||||||
|
|
||||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
|
||||||
SerialTinyUSB.printf("Error: cannot request to receive report\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoked when device with hid interface is un-mounted
|
|
||||||
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
SerialTinyUSB.printf("HID device address = %d, instance = %d unmounted\r\n", dev_addr, instance);
|
|
||||||
|
|
||||||
// Reset HID sent flag
|
|
||||||
hid_sent = false;
|
|
||||||
hid_reported = false;
|
|
||||||
hid_event_num = 0;
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoked when received report from device
|
|
||||||
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance, uint8_t const* report, uint16_t len) {
|
|
||||||
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
|
|
||||||
static bool kbd_printed = false;
|
|
||||||
static bool mouse_printed = false;
|
|
||||||
|
|
||||||
// Used in main loop to write output to OLED
|
|
||||||
hid_sent = true;
|
|
||||||
|
|
||||||
// Read the HID protocol
|
|
||||||
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
|
||||||
|
|
||||||
switch (itf_protocol) {
|
|
||||||
case HID_ITF_PROTOCOL_KEYBOARD:
|
|
||||||
if (kbd_printed == false) {
|
|
||||||
SerialTinyUSB.println("HID received keyboard report");
|
|
||||||
kbd_printed = true;
|
|
||||||
mouse_printed = false;
|
|
||||||
}
|
|
||||||
process_kbd_report((hid_keyboard_report_t const*)report);
|
|
||||||
hid_event_num++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case HID_ITF_PROTOCOL_MOUSE:
|
|
||||||
if (kbd_printed == false) {
|
|
||||||
SerialTinyUSB.println("HID receive mouse report");
|
|
||||||
mouse_printed = true;
|
|
||||||
kbd_printed = false;
|
|
||||||
}
|
|
||||||
process_mouse_report((hid_mouse_report_t const*)report);
|
|
||||||
hid_event_num++;
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Generic report: for the time being we use kbd for this as well
|
|
||||||
process_kbd_report((hid_keyboard_report_t const*)report);
|
|
||||||
hid_event_num++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
|
||||||
SerialTinyUSB.println("Error: cannot request to receive report");
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline bool find_key_in_report(hid_keyboard_report_t const* report, uint8_t keycode) {
|
|
||||||
for (uint8_t i = 0; i < 6; i++) {
|
|
||||||
if (report->keycode[i] == keycode) return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_kbd_report(hid_keyboard_report_t const* report) {
|
|
||||||
// Previous report to check key released
|
|
||||||
static hid_keyboard_report_t prev_report = { 0, 0, { 0 } };
|
|
||||||
|
|
||||||
for (uint8_t i = 0; i < 6; i++) {
|
|
||||||
if (report->keycode[i]) {
|
|
||||||
if (find_key_in_report(&prev_report, report->keycode[i])) {
|
|
||||||
// Exist in previous report means the current key is holding
|
|
||||||
} else {
|
|
||||||
// Not existed in previous report means the current key is pressed
|
|
||||||
|
|
||||||
// Check for modifiers. It looks that in specific cases, they are not correctly recognized (probably
|
|
||||||
// for timing issues in fast input)
|
|
||||||
bool const is_shift = report->modifier & (KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT);
|
|
||||||
uint8_t ch = keycode2ascii[report->keycode[i]][is_shift ? 1 : 0];
|
|
||||||
|
|
||||||
bool const is_gui = report->modifier & (KEYBOARD_MODIFIER_LEFTGUI | KEYBOARD_MODIFIER_RIGHTGUI);
|
|
||||||
if (is_gui == true) SerialTinyUSB.printf("GUI+");
|
|
||||||
|
|
||||||
bool const is_alt = report->modifier & (KEYBOARD_MODIFIER_LEFTALT | KEYBOARD_MODIFIER_RIGHTALT);
|
|
||||||
if (is_alt == true) SerialTinyUSB.printf("ALT+");
|
|
||||||
|
|
||||||
// Check for "special" keys
|
|
||||||
check_special_key(report->keycode[i]);
|
|
||||||
|
|
||||||
// Finally, print out the decoded char
|
|
||||||
SerialTinyUSB.printf("%c", ch);
|
|
||||||
if (ch == '\r') SerialTinyUSB.print("\n"); // New line for enter
|
|
||||||
|
|
||||||
fflush(stdout); // flush right away, else nanolib will wait for newline
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
prev_report = *report;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void check_special_key(uint8_t code) {
|
|
||||||
|
|
||||||
if (code == HID_KEY_ARROW_RIGHT) SerialTinyUSB.print("<ARROWRIGHT>");
|
|
||||||
if (code == HID_KEY_ARROW_LEFT) SerialTinyUSB.print("<ARROWLEFT>");
|
|
||||||
if (code == HID_KEY_ARROW_DOWN) SerialTinyUSB.print("<ARROWDOWN>");
|
|
||||||
if (code == HID_KEY_ARROW_UP) SerialTinyUSB.print("<ARROWUP>");
|
|
||||||
if (code == HID_KEY_HOME) SerialTinyUSB.print("<HOME>");
|
|
||||||
if (code == HID_KEY_KEYPAD_1) SerialTinyUSB.print("<KEYPAD_1>");
|
|
||||||
if (code == HID_KEY_KEYPAD_2) SerialTinyUSB.print("<KEYPAD_2>");
|
|
||||||
if (code == HID_KEY_KEYPAD_3) SerialTinyUSB.print("<KEYPAD_3>");
|
|
||||||
if (code == HID_KEY_KEYPAD_4) SerialTinyUSB.print("<KEYPAD_4>");
|
|
||||||
if (code == HID_KEY_KEYPAD_5) SerialTinyUSB.print("<KEYPAD_5>");
|
|
||||||
if (code == HID_KEY_KEYPAD_6) SerialTinyUSB.print("<KEYPAD_6>");
|
|
||||||
if (code == HID_KEY_KEYPAD_7) SerialTinyUSB.print("<KEYPAD_7>");
|
|
||||||
if (code == HID_KEY_KEYPAD_8) SerialTinyUSB.print("<KEYPAD_8>");
|
|
||||||
if (code == HID_KEY_KEYPAD_9) SerialTinyUSB.print("<KEYPAD_9>");
|
|
||||||
if (code == HID_KEY_KEYPAD_0) SerialTinyUSB.print("<KEYPAD_0>");
|
|
||||||
if (code == HID_KEY_F1) SerialTinyUSB.print("<F1>");
|
|
||||||
if (code == HID_KEY_F2) SerialTinyUSB.print("<F2>");
|
|
||||||
if (code == HID_KEY_F3) SerialTinyUSB.print("<F3>");
|
|
||||||
if (code == HID_KEY_F4) SerialTinyUSB.print("<F4>");
|
|
||||||
if (code == HID_KEY_F5) SerialTinyUSB.print("<F5>");
|
|
||||||
if (code == HID_KEY_F6) SerialTinyUSB.print("<F6>");
|
|
||||||
if (code == HID_KEY_F7) SerialTinyUSB.print("<F7>");
|
|
||||||
if (code == HID_KEY_F8) SerialTinyUSB.print("<F8>");
|
|
||||||
if (code == HID_KEY_F9) SerialTinyUSB.print("<F9>");
|
|
||||||
if (code == HID_KEY_F10) SerialTinyUSB.print("<F10>");
|
|
||||||
if (code == HID_KEY_F11) SerialTinyUSB.print("<F11>");
|
|
||||||
if (code == HID_KEY_F12) SerialTinyUSB.print("<F12>");
|
|
||||||
if (code == HID_KEY_PRINT_SCREEN) SerialTinyUSB.print("<PRNT>");
|
|
||||||
if (code == HID_KEY_SCROLL_LOCK) SerialTinyUSB.print("<SCRLL>");
|
|
||||||
if (code == HID_KEY_PAUSE) SerialTinyUSB.print("<PAUSE>");
|
|
||||||
if (code == HID_KEY_INSERT) SerialTinyUSB.print("<INSERT>");
|
|
||||||
if (code == HID_KEY_PAGE_UP) SerialTinyUSB.print("<PAGEUP>");
|
|
||||||
if (code == HID_KEY_DELETE) SerialTinyUSB.print("<DEL>");
|
|
||||||
if (code == HID_KEY_END) SerialTinyUSB.print("<END>");
|
|
||||||
if (code == HID_KEY_PAGE_DOWN) SerialTinyUSB.print("<PAGEDOWN>");
|
|
||||||
if (code == HID_KEY_NUM_LOCK) SerialTinyUSB.print("<ARROWRIGHT>");
|
|
||||||
if (code == HID_KEY_KEYPAD_DIVIDE) SerialTinyUSB.print("<KEYPAD_DIV>");
|
|
||||||
if (code == HID_KEY_KEYPAD_MULTIPLY) SerialTinyUSB.print("<KEYPAD_MUL>");
|
|
||||||
if (code == HID_KEY_KEYPAD_SUBTRACT) SerialTinyUSB.print("<KEYPAD_SUB>");
|
|
||||||
if (code == HID_KEY_KEYPAD_ADD) SerialTinyUSB.print("<KEYPAD_ADD>");
|
|
||||||
if (code == HID_KEY_KEYPAD_DECIMAL) SerialTinyUSB.print("<KEYPAD_DECIMAL>");
|
|
||||||
}
|
|
||||||
|
|
||||||
static void process_mouse_report(hid_mouse_report_t const* report) {
|
|
||||||
static hid_mouse_report_t prev_report = { 0 };
|
|
||||||
|
|
||||||
//------------- button state -------------//
|
|
||||||
uint8_t button_changed_mask = report->buttons ^ prev_report.buttons;
|
|
||||||
if (button_changed_mask & report->buttons) {
|
|
||||||
SerialTinyUSB.printf("MOUSE: %c%c%c ",
|
|
||||||
report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-',
|
|
||||||
report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-',
|
|
||||||
report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-');
|
|
||||||
}
|
|
||||||
|
|
||||||
cursor_movement(report->x, report->y, report->wheel);
|
|
||||||
}
|
|
||||||
|
|
||||||
void cursor_movement(int8_t x, int8_t y, int8_t wheel) {
|
|
||||||
SerialTinyUSB.printf("(%d %d %d)\r\n", x, y, wheel);
|
|
||||||
}
|
|
||||||
|
|
||||||
// END of BADUSB detector section
|
|
||||||
|
|
||||||
//
|
|
||||||
// OTHER Host devices detection section
|
|
||||||
//
|
|
||||||
|
|
||||||
// Invoked when a device with MassStorage interface is mounted
|
|
||||||
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
printout("\n[++] Mass Device");
|
|
||||||
SerialTinyUSB.printf("Mass Device attached, address = %d\r\n", dev_addr);
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoked when a device with MassStorage interface is unmounted
|
|
||||||
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
SerialTinyUSB.printf("Mass Device unmounted, address = %d\r\n", dev_addr);
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoked when a device with CDC (Communication Device Class) interface is mounted
|
|
||||||
void tuh_cdc_mount_cb(uint8_t idx) {
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
printout("\n[++] CDC Device");
|
|
||||||
SerialTinyUSB.printf("CDC Device attached, idx = %d\r\n", idx);
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invoked when a device with CDC (Communication Device Class) interface is unmounted
|
|
||||||
void tuh_cdc_umount_cb(uint8_t idx) {
|
|
||||||
uint32_t lock_num = spin_lock_blocking(lock);
|
|
||||||
SerialTinyUSB.printf("CDC Device unmounted, idx = %d\r\n", idx);
|
|
||||||
spin_unlock(lock, lock_num);
|
|
||||||
}
|
|
||||||
|
|
||||||
// END of OTHER Host devices detector section
|
|
||||||
60
build_all.sh
Executable file
60
build_all.sh
Executable file
@@ -0,0 +1,60 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
FW_DIR="$SCRIPT_DIR/firmware"
|
||||||
|
BUILD_DIR="$SCRIPT_DIR/build"
|
||||||
|
|
||||||
|
# Extract version from usb_config.h
|
||||||
|
FW_VERSION=$(grep '#define VERSION' "$SCRIPT_DIR/src/usb_config.h" | sed 's/.*"\(.*\)"/\1/' | sed 's/USBvalve - //')
|
||||||
|
|
||||||
|
echo "Firmware version: $FW_VERSION"
|
||||||
|
mkdir -p "$FW_DIR"
|
||||||
|
|
||||||
|
build() {
|
||||||
|
local name="$1"
|
||||||
|
shift
|
||||||
|
echo "=========================================="
|
||||||
|
echo "Building: $name"
|
||||||
|
echo "CMake args: $@"
|
||||||
|
echo "=========================================="
|
||||||
|
rm -rf "$BUILD_DIR"
|
||||||
|
mkdir "$BUILD_DIR"
|
||||||
|
cd "$BUILD_DIR"
|
||||||
|
cmake "$@" ..
|
||||||
|
make -j$(nproc)
|
||||||
|
cp src/USBvalve.uf2 "$FW_DIR/${name}.uf2"
|
||||||
|
echo "-> $FW_DIR/${name}.uf2"
|
||||||
|
echo ""
|
||||||
|
}
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico1-oled32" \
|
||||||
|
-DPICO_BOARD=pico -DOLED_HEIGHT=32
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico1-oled64" \
|
||||||
|
-DPICO_BOARD=pico -DOLED_HEIGHT=64
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico1-oled32-bootsel" \
|
||||||
|
-DPICO_BOARD=pico -DOLED_HEIGHT=32 -DUSE_BOOTSEL=1
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico1-oled64-bootsel" \
|
||||||
|
-DPICO_BOARD=pico -DOLED_HEIGHT=64 -DUSE_BOOTSEL=1
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico2-oled32" \
|
||||||
|
-DPICO_BOARD=pico2 -DOLED_HEIGHT=32
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico2-oled64" \
|
||||||
|
-DPICO_BOARD=pico2 -DOLED_HEIGHT=64
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico2-oled32-bootsel" \
|
||||||
|
-DPICO_BOARD=pico2 -DOLED_HEIGHT=32 -DUSE_BOOTSEL=1
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico2-oled64-bootsel" \
|
||||||
|
-DPICO_BOARD=pico2 -DOLED_HEIGHT=64 -DUSE_BOOTSEL=1
|
||||||
|
|
||||||
|
build "USBvalve-${FW_VERSION}-pico1-piwatch-bootsel" \
|
||||||
|
-DPICO_BOARD=pico -DPIWATCH=1 -DUSE_BOOTSEL=1
|
||||||
|
|
||||||
|
echo "=========================================="
|
||||||
|
echo "All builds complete. Firmware files:"
|
||||||
|
ls -la "$FW_DIR"/*.uf2
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
#ifndef BACKGROUND_H_
|
#ifndef BACKGROUND_H_
|
||||||
#define BACKGROUND_H_
|
#define BACKGROUND_H_
|
||||||
|
|
||||||
static const uint16_t background[44100] PROGMEM = {
|
static const uint16_t background[44100] = {
|
||||||
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0021, 0x0021, 0x0041, 0x0042, 0x0042, 0x0062, 0x0883, 0x0883, 0x08a4, 0x00a4, 0x08c5, 0x08e5, 0x0105, 0x0926, 0x0967, 0x0946, 0x0020, 0x0000, 0x0042, 0x194a, 0x19ac, 0x198c, 0x198c, 0x118c, 0x198b, 0x0085, 0x0043, 0x0043, 0x0000, 0x0001, 0x08a5, 0x0043, 0x00a5, 0x08e8, 0x0908, 0x0909, 0x0909, 0x0909, 0x0929, 0x0929, 0x0929, 0x0929, 0x0929, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x08e8, 0x00a6, 0x0044, 0x0042, 0x0b0c, 0x1d14,
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0021, 0x0021, 0x0041, 0x0042, 0x0042, 0x0062, 0x0883, 0x0883, 0x08a4, 0x00a4, 0x08c5, 0x08e5, 0x0105, 0x0926, 0x0967, 0x0946, 0x0020, 0x0000, 0x0042, 0x194a, 0x19ac, 0x198c, 0x198c, 0x118c, 0x198b, 0x0085, 0x0043, 0x0043, 0x0000, 0x0001, 0x08a5, 0x0043, 0x00a5, 0x08e8, 0x0908, 0x0909, 0x0909, 0x0909, 0x0929, 0x0929, 0x0929, 0x0929, 0x0929, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x08e8, 0x00a6, 0x0044, 0x0042, 0x0b0c, 0x1d14,
|
||||||
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0021, 0x0021, 0x0021, 0x0021, 0x0042, 0x0062, 0x0062, 0x0063, 0x0063, 0x00a4, 0x08c4, 0x00c4, 0x08e5, 0x0105, 0x0906, 0x0947, 0x0967, 0x0083, 0x0000, 0x0001, 0x08a5, 0x198b, 0x118c, 0x118c, 0x118b, 0x116b, 0x0044, 0x0043, 0x0043, 0x0000, 0x0000, 0x0001, 0x08a6, 0x0885, 0x0064, 0x08e7, 0x0909, 0x0909, 0x0909, 0x0929, 0x0929, 0x0929, 0x0929, 0x0929, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0908, 0x08c7, 0x0085, 0x00a3, 0x130c, 0x1cf4, 0x1d56, 0x1d15, 0x0b0e,
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0021, 0x0021, 0x0021, 0x0021, 0x0021, 0x0042, 0x0062, 0x0062, 0x0063, 0x0063, 0x00a4, 0x08c4, 0x00c4, 0x08e5, 0x0105, 0x0906, 0x0947, 0x0967, 0x0083, 0x0000, 0x0001, 0x08a5, 0x198b, 0x118c, 0x118c, 0x118b, 0x116b, 0x0044, 0x0043, 0x0043, 0x0000, 0x0000, 0x0001, 0x08a6, 0x0885, 0x0064, 0x08e7, 0x0909, 0x0909, 0x0909, 0x0929, 0x0929, 0x0929, 0x0929, 0x0929, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0908, 0x08c7, 0x0085, 0x00a3, 0x130c, 0x1cf4, 0x1d56, 0x1d15, 0x0b0e,
|
||||||
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0020, 0x0021, 0x0021, 0x0021, 0x0021, 0x0042, 0x0042, 0x0062, 0x0063, 0x0883, 0x0883, 0x0084, 0x08a4, 0x00c4, 0x00c5, 0x0906, 0x0906, 0x0926, 0x0947, 0x0926, 0x0000, 0x0000, 0x0042, 0x08e7, 0x196b, 0x118b, 0x118b, 0x08e8, 0x0063, 0x0063, 0x0021, 0x0000, 0x0000, 0x0000, 0x0000, 0x08a5, 0x10e7, 0x0884, 0x08c6, 0x0908, 0x0909, 0x0909, 0x0909, 0x0929, 0x0929, 0x0929, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x08e8, 0x00a5, 0x01a8, 0x1b8f, 0x1cd4, 0x1d56, 0x1cb4, 0x0aae, 0x096b, 0x094b, 0x094a,
|
0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0020, 0x0020, 0x0021, 0x0021, 0x0021, 0x0021, 0x0042, 0x0042, 0x0062, 0x0063, 0x0883, 0x0883, 0x0084, 0x08a4, 0x00c4, 0x00c5, 0x0906, 0x0906, 0x0926, 0x0947, 0x0926, 0x0000, 0x0000, 0x0042, 0x08e7, 0x196b, 0x118b, 0x118b, 0x08e8, 0x0063, 0x0063, 0x0021, 0x0000, 0x0000, 0x0000, 0x0000, 0x08a5, 0x10e7, 0x0884, 0x08c6, 0x0908, 0x0909, 0x0909, 0x0909, 0x0929, 0x0929, 0x0929, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x0909, 0x08e8, 0x00a5, 0x01a8, 0x1b8f, 0x1cd4, 0x1d56, 0x1cb4, 0x0aae, 0x096b, 0x094b, 0x094a,
|
||||||
108
data/font6x8.h
Normal file
108
data/font6x8.h
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
#ifndef FONT6X8_H
|
||||||
|
#define FONT6X8_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 6x8 pixel font for ASCII characters 0x20 (space) through 0x7E (~).
|
||||||
|
* Each character is 6 bytes wide, 8 pixels tall.
|
||||||
|
* Bit 0 = top pixel, bit 7 = bottom pixel (LSB at top).
|
||||||
|
*/
|
||||||
|
|
||||||
|
static const unsigned char font6x8[][6] = {
|
||||||
|
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }, // 0x20 ' '
|
||||||
|
{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 }, // 0x21 '!'
|
||||||
|
{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 }, // 0x22 '"'
|
||||||
|
{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 }, // 0x23 '#'
|
||||||
|
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 }, // 0x24 '$'
|
||||||
|
{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 }, // 0x25 '%'
|
||||||
|
{ 0x36, 0x49, 0x55, 0x22, 0x50, 0x00 }, // 0x26 '&'
|
||||||
|
{ 0x00, 0x05, 0x03, 0x00, 0x00, 0x00 }, // 0x27 '''
|
||||||
|
{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 }, // 0x28 '('
|
||||||
|
{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 }, // 0x29 ')'
|
||||||
|
{ 0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00 }, // 0x2A '*'
|
||||||
|
{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 }, // 0x2B '+'
|
||||||
|
{ 0x00, 0x50, 0x30, 0x00, 0x00, 0x00 }, // 0x2C ','
|
||||||
|
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 }, // 0x2D '-'
|
||||||
|
{ 0x00, 0x60, 0x60, 0x00, 0x00, 0x00 }, // 0x2E '.'
|
||||||
|
{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 }, // 0x2F '/'
|
||||||
|
{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 }, // 0x30 '0'
|
||||||
|
{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 }, // 0x31 '1'
|
||||||
|
{ 0x42, 0x61, 0x51, 0x49, 0x46, 0x00 }, // 0x32 '2'
|
||||||
|
{ 0x21, 0x41, 0x45, 0x4B, 0x31, 0x00 }, // 0x33 '3'
|
||||||
|
{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 }, // 0x34 '4'
|
||||||
|
{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 }, // 0x35 '5'
|
||||||
|
{ 0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00 }, // 0x36 '6'
|
||||||
|
{ 0x01, 0x71, 0x09, 0x05, 0x03, 0x00 }, // 0x37 '7'
|
||||||
|
{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 }, // 0x38 '8'
|
||||||
|
{ 0x06, 0x49, 0x49, 0x29, 0x1E, 0x00 }, // 0x39 '9'
|
||||||
|
{ 0x00, 0x36, 0x36, 0x00, 0x00, 0x00 }, // 0x3A ':'
|
||||||
|
{ 0x00, 0x56, 0x36, 0x00, 0x00, 0x00 }, // 0x3B ';'
|
||||||
|
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 }, // 0x3C '<'
|
||||||
|
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 }, // 0x3D '='
|
||||||
|
{ 0x41, 0x22, 0x14, 0x08, 0x00, 0x00 }, // 0x3E '>'
|
||||||
|
{ 0x02, 0x01, 0x51, 0x09, 0x06, 0x00 }, // 0x3F '?'
|
||||||
|
{ 0x32, 0x49, 0x79, 0x41, 0x3E, 0x00 }, // 0x40 '@'
|
||||||
|
{ 0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00 }, // 0x41 'A'
|
||||||
|
{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 }, // 0x42 'B'
|
||||||
|
{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 }, // 0x43 'C'
|
||||||
|
{ 0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00 }, // 0x44 'D'
|
||||||
|
{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 }, // 0x45 'E'
|
||||||
|
{ 0x7F, 0x09, 0x09, 0x01, 0x01, 0x00 }, // 0x46 'F'
|
||||||
|
{ 0x3E, 0x41, 0x41, 0x51, 0x32, 0x00 }, // 0x47 'G'
|
||||||
|
{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 }, // 0x48 'H'
|
||||||
|
{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 }, // 0x49 'I'
|
||||||
|
{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 }, // 0x4A 'J'
|
||||||
|
{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 }, // 0x4B 'K'
|
||||||
|
{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 }, // 0x4C 'L'
|
||||||
|
{ 0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00 }, // 0x4D 'M'
|
||||||
|
{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 }, // 0x4E 'N'
|
||||||
|
{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 }, // 0x4F 'O'
|
||||||
|
{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 }, // 0x50 'P'
|
||||||
|
{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 }, // 0x51 'Q'
|
||||||
|
{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 }, // 0x52 'R'
|
||||||
|
{ 0x46, 0x49, 0x49, 0x49, 0x31, 0x00 }, // 0x53 'S'
|
||||||
|
{ 0x01, 0x01, 0x7F, 0x01, 0x01, 0x00 }, // 0x54 'T'
|
||||||
|
{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 }, // 0x55 'U'
|
||||||
|
{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 }, // 0x56 'V'
|
||||||
|
{ 0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00 }, // 0x57 'W'
|
||||||
|
{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 }, // 0x58 'X'
|
||||||
|
{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 }, // 0x59 'Y'
|
||||||
|
{ 0x61, 0x51, 0x49, 0x45, 0x43, 0x00 }, // 0x5A 'Z'
|
||||||
|
{ 0x00, 0x00, 0x7F, 0x41, 0x41, 0x00 }, // 0x5B '['
|
||||||
|
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 }, // 0x5C '\'
|
||||||
|
{ 0x41, 0x41, 0x7F, 0x00, 0x00, 0x00 }, // 0x5D ']'
|
||||||
|
{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 }, // 0x5E '^'
|
||||||
|
{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 }, // 0x5F '_'
|
||||||
|
{ 0x00, 0x01, 0x02, 0x04, 0x00, 0x00 }, // 0x60 '`'
|
||||||
|
{ 0x20, 0x54, 0x54, 0x54, 0x78, 0x00 }, // 0x61 'a'
|
||||||
|
{ 0x7F, 0x48, 0x44, 0x44, 0x38, 0x00 }, // 0x62 'b'
|
||||||
|
{ 0x38, 0x44, 0x44, 0x44, 0x20, 0x00 }, // 0x63 'c'
|
||||||
|
{ 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00 }, // 0x64 'd'
|
||||||
|
{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 }, // 0x65 'e'
|
||||||
|
{ 0x08, 0x7E, 0x09, 0x01, 0x02, 0x00 }, // 0x66 'f'
|
||||||
|
{ 0x08, 0x14, 0x54, 0x54, 0x3C, 0x00 }, // 0x67 'g'
|
||||||
|
{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 }, // 0x68 'h'
|
||||||
|
{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 }, // 0x69 'i'
|
||||||
|
{ 0x20, 0x40, 0x44, 0x3D, 0x00, 0x00 }, // 0x6A 'j'
|
||||||
|
{ 0x00, 0x7F, 0x10, 0x28, 0x44, 0x00 }, // 0x6B 'k'
|
||||||
|
{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 }, // 0x6C 'l'
|
||||||
|
{ 0x7C, 0x04, 0x18, 0x04, 0x78, 0x00 }, // 0x6D 'm'
|
||||||
|
{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 }, // 0x6E 'n'
|
||||||
|
{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 }, // 0x6F 'o'
|
||||||
|
{ 0x7C, 0x14, 0x14, 0x14, 0x08, 0x00 }, // 0x70 'p'
|
||||||
|
{ 0x08, 0x14, 0x14, 0x18, 0x7C, 0x00 }, // 0x71 'q'
|
||||||
|
{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 }, // 0x72 'r'
|
||||||
|
{ 0x48, 0x54, 0x54, 0x54, 0x20, 0x00 }, // 0x73 's'
|
||||||
|
{ 0x04, 0x3F, 0x44, 0x40, 0x20, 0x00 }, // 0x74 't'
|
||||||
|
{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 }, // 0x75 'u'
|
||||||
|
{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 }, // 0x76 'v'
|
||||||
|
{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 }, // 0x77 'w'
|
||||||
|
{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 }, // 0x78 'x'
|
||||||
|
{ 0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00 }, // 0x79 'y'
|
||||||
|
{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 }, // 0x7A 'z'
|
||||||
|
{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 }, // 0x7B '{'
|
||||||
|
{ 0x00, 0x00, 0x7F, 0x00, 0x00, 0x00 }, // 0x7C '|'
|
||||||
|
{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 }, // 0x7D '}'
|
||||||
|
{ 0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00 }, // 0x7E '~'
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // FONT6X8_H
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
firmware/USBvalve-1.0.0-pico1-piwatch-bootsel.uf2
Normal file
BIN
firmware/USBvalve-1.0.0-pico1-piwatch-bootsel.uf2
Normal file
Binary file not shown.
BIN
firmware/USBvalve-1.0.0-pico2-oled32-bootsel.uf2
Normal file
BIN
firmware/USBvalve-1.0.0-pico2-oled32-bootsel.uf2
Normal file
Binary file not shown.
BIN
firmware/USBvalve-1.0.0-pico2-oled32.uf2
Normal file
BIN
firmware/USBvalve-1.0.0-pico2-oled32.uf2
Normal file
Binary file not shown.
BIN
firmware/USBvalve-1.0.0-pico2-oled64-bootsel.uf2
Normal file
BIN
firmware/USBvalve-1.0.0-pico2-oled64-bootsel.uf2
Normal file
Binary file not shown.
BIN
firmware/USBvalve-1.0.0-pico2-oled64.uf2
Normal file
BIN
firmware/USBvalve-1.0.0-pico2-oled64.uf2
Normal file
Binary file not shown.
1
lib/pico-pio-usb
Submodule
1
lib/pico-pio-usb
Submodule
Submodule lib/pico-pio-usb added at 3c1eec341a
1
lib/xxhash
Submodule
1
lib/xxhash
Submodule
Submodule lib/xxhash added at e626a72bc2
66
pico_sdk_import.cmake
Normal file
66
pico_sdk_import.cmake
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# This is a copy of <PICO_SDK_PATH>/external/pico_sdk_import.cmake
|
||||||
|
|
||||||
|
# This can be dropped into an external project to help locate this SDK
|
||||||
|
# It should be include()ed prior to project()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_PATH} AND (NOT PICO_SDK_PATH))
|
||||||
|
set(PICO_SDK_PATH $ENV{PICO_SDK_PATH})
|
||||||
|
message("Using PICO_SDK_PATH from environment ('${PICO_SDK_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT} AND (NOT PICO_SDK_FETCH_FROM_GIT))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT $ENV{PICO_SDK_FETCH_FROM_GIT})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT from environment ('${PICO_SDK_FETCH_FROM_GIT}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_PATH} AND (NOT PICO_SDK_FETCH_FROM_GIT_PATH))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_PATH $ENV{PICO_SDK_FETCH_FROM_GIT_PATH})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_PATH from environment ('${PICO_SDK_FETCH_FROM_GIT_PATH}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (DEFINED ENV{PICO_SDK_FETCH_FROM_GIT_TAG} AND (NOT PICO_SDK_FETCH_FROM_GIT_TAG))
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG $ENV{PICO_SDK_FETCH_FROM_GIT_TAG})
|
||||||
|
message("Using PICO_SDK_FETCH_FROM_GIT_TAG from environment ('${PICO_SDK_FETCH_FROM_GIT_TAG}')")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (NOT PICO_SDK_PATH)
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT)
|
||||||
|
include(FetchContent)
|
||||||
|
set(FETCHCONTENT_BASE_DIR_SAVE ${FETCHCONTENT_BASE_DIR} CACHE STRING "Saves FETCHCONTENT_BASE_DIR")
|
||||||
|
if (PICO_SDK_FETCH_FROM_GIT_PATH)
|
||||||
|
get_filename_component(FETCHCONTENT_BASE_DIR "${PICO_SDK_FETCH_FROM_GIT_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
endif ()
|
||||||
|
if (NOT PICO_SDK_FETCH_FROM_GIT_TAG)
|
||||||
|
set(PICO_SDK_FETCH_FROM_GIT_TAG "master")
|
||||||
|
endif ()
|
||||||
|
FetchContent_Declare(
|
||||||
|
pico_sdk
|
||||||
|
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
|
||||||
|
GIT_TAG ${PICO_SDK_FETCH_FROM_GIT_TAG}
|
||||||
|
)
|
||||||
|
if (NOT pico_sdk)
|
||||||
|
message("Downloading Raspberry Pi Pico SDK")
|
||||||
|
FetchContent_Populate(pico_sdk)
|
||||||
|
set(PICO_SDK_PATH ${pico_sdk_SOURCE_DIR})
|
||||||
|
endif ()
|
||||||
|
set(FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR_SAVE} CACHE STRING "Restores FETCHCONTENT_BASE_DIR" FORCE)
|
||||||
|
else ()
|
||||||
|
message(FATAL_ERROR
|
||||||
|
"SDK location was not specified. Please set PICO_SDK_PATH or set PICO_SDK_FETCH_FROM_GIT to on to fetch from git."
|
||||||
|
)
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
get_filename_component(PICO_SDK_PATH "${PICO_SDK_PATH}" REALPATH BASE_DIR "${CMAKE_SOURCE_DIR}")
|
||||||
|
if (NOT EXISTS ${PICO_SDK_PATH})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' not found")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_INIT_CMAKE_FILE ${PICO_SDK_PATH}/pico_sdk_init.cmake)
|
||||||
|
if (NOT EXISTS ${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
|
message(FATAL_ERROR "Directory '${PICO_SDK_PATH}' does not appear to contain the Raspberry Pi Pico SDK")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
set(PICO_SDK_PATH ${PICO_SDK_PATH} CACHE PATH "Path to the Raspberry Pi Pico SDK" FORCE)
|
||||||
|
|
||||||
|
include(${PICO_SDK_INIT_CMAKE_FILE})
|
||||||
BIN
pictures/reset_button.jpg
Normal file
BIN
pictures/reset_button.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
59
src/CMakeLists.txt
Normal file
59
src/CMakeLists.txt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Match the working pico-pio-usb host_hid_to_device_cdc example build pattern
|
||||||
|
|
||||||
|
set(target_name USBvalve)
|
||||||
|
add_executable(${target_name})
|
||||||
|
|
||||||
|
target_sources(${target_name} PRIVATE
|
||||||
|
main.c
|
||||||
|
usb_device.c
|
||||||
|
usb_descriptors.c
|
||||||
|
usb_host.c
|
||||||
|
hid_handler.c
|
||||||
|
display.c
|
||||||
|
serial_output.c
|
||||||
|
../lib/xxhash/xxhash.c
|
||||||
|
# PIO USB TinyUSB portable drivers (same as the example)
|
||||||
|
${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/dcd_pio_usb.c
|
||||||
|
${PICO_TINYUSB_PATH}/src/portable/raspberrypi/pio_usb/hcd_pio_usb.c
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage)
|
||||||
|
target_compile_options(${target_name} PRIVATE -Wall -Wextra)
|
||||||
|
target_compile_definitions(${target_name} PRIVATE PIO_USB_USE_TINYUSB)
|
||||||
|
|
||||||
|
# OLED display height: 32 (default) or 64
|
||||||
|
# Usage: cmake -DOLED_HEIGHT=64 ..
|
||||||
|
if(NOT DEFINED OLED_HEIGHT)
|
||||||
|
set(OLED_HEIGHT 32)
|
||||||
|
endif()
|
||||||
|
target_compile_definitions(${target_name} PRIVATE OLED_HEIGHT=${OLED_HEIGHT})
|
||||||
|
|
||||||
|
# PIWATCH: GC9A01 round TFT display instead of SSD1306 OLED
|
||||||
|
# Usage: cmake -DPIWATCH=1 ..
|
||||||
|
if(PIWATCH)
|
||||||
|
target_compile_definitions(${target_name} PRIVATE PIWATCH)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Optional: enable BOOTSEL button support (disrupts Low Speed USB detection)
|
||||||
|
# Usage: cmake -DUSE_BOOTSEL=1 ..
|
||||||
|
if(USE_BOOTSEL)
|
||||||
|
target_compile_definitions(${target_name} PRIVATE USE_BOOTSEL)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(${target_name} PRIVATE
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../data
|
||||||
|
${CMAKE_CURRENT_LIST_DIR}/../lib/xxhash
|
||||||
|
)
|
||||||
|
|
||||||
|
target_link_libraries(${target_name} PRIVATE
|
||||||
|
pico_stdlib
|
||||||
|
pico_pio_usb
|
||||||
|
tinyusb_device
|
||||||
|
tinyusb_host
|
||||||
|
hardware_i2c
|
||||||
|
hardware_spi
|
||||||
|
hardware_watchdog
|
||||||
|
)
|
||||||
|
|
||||||
|
pico_add_extra_outputs(${target_name})
|
||||||
633
src/display.c
Normal file
633
src/display.c
Normal file
@@ -0,0 +1,633 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - Display Driver
|
||||||
|
*
|
||||||
|
* Two implementations selected at compile time:
|
||||||
|
* - Default: SSD1306 OLED via I2C (128x32 or 128x64)
|
||||||
|
* - PIWATCH: GC9A01 round TFT via SPI (240x240)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "serial_output.h"
|
||||||
|
#include "usb_config.h"
|
||||||
|
#include "font6x8.h"
|
||||||
|
|
||||||
|
#define FONT_W 6
|
||||||
|
#define FONT_H 8
|
||||||
|
|
||||||
|
#ifdef PIWATCH
|
||||||
|
|
||||||
|
//====================================================================
|
||||||
|
// PIWATCH: GC9A01 round TFT (240x240) via SPI
|
||||||
|
//====================================================================
|
||||||
|
|
||||||
|
#include "hardware/spi.h"
|
||||||
|
#include "background.h"
|
||||||
|
|
||||||
|
// GC9A01 pin definitions
|
||||||
|
#define GFX_DC 8
|
||||||
|
#define GFX_CS 9
|
||||||
|
#define GFX_CLK 10
|
||||||
|
#define GFX_MOSI 11
|
||||||
|
#define GFX_RST 12
|
||||||
|
#define GFX_BL 25 // Backlight (shared with LED_PIN on non-PIWATCH)
|
||||||
|
|
||||||
|
#define TFT_WIDTH 240
|
||||||
|
#define TFT_HEIGHT 240
|
||||||
|
|
||||||
|
// Text area for event messages
|
||||||
|
#define TEXT_X 70
|
||||||
|
#define TEXT_Y_START 80
|
||||||
|
#define TEXT_Y_MAX 120
|
||||||
|
#define TEXT_AREA_X 60
|
||||||
|
#define TEXT_AREA_Y 70
|
||||||
|
#define TEXT_AREA_W 140
|
||||||
|
#define TEXT_AREA_H 75
|
||||||
|
|
||||||
|
// Cursor state
|
||||||
|
static uint16_t cursor_x = TEXT_X;
|
||||||
|
static uint16_t cursor_y = TEXT_Y_START;
|
||||||
|
|
||||||
|
// Text color (magenta in RGB565)
|
||||||
|
#define COLOR_MAGENTA 0xF81F
|
||||||
|
#define COLOR_BLACK 0x0000
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Low-level SPI helpers
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static void gc9a01_cmd(uint8_t cmd) {
|
||||||
|
gpio_put(GFX_DC, 0); // Command mode
|
||||||
|
gpio_put(GFX_CS, 0);
|
||||||
|
spi_write_blocking(spi1, &cmd, 1);
|
||||||
|
gpio_put(GFX_CS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gc9a01_data(const uint8_t *data, size_t len) {
|
||||||
|
gpio_put(GFX_DC, 1); // Data mode
|
||||||
|
gpio_put(GFX_CS, 0);
|
||||||
|
spi_write_blocking(spi1, data, len);
|
||||||
|
gpio_put(GFX_CS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gc9a01_data8(uint8_t val) {
|
||||||
|
gc9a01_data(&val, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gc9a01_data16(uint16_t val) {
|
||||||
|
uint8_t buf[2] = {val >> 8, val & 0xFF};
|
||||||
|
gc9a01_data(buf, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void gc9a01_set_window(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1) {
|
||||||
|
gc9a01_cmd(0x2A); // Column address set
|
||||||
|
gc9a01_data16(x0);
|
||||||
|
gc9a01_data16(x1);
|
||||||
|
gc9a01_cmd(0x2B); // Row address set
|
||||||
|
gc9a01_data16(y0);
|
||||||
|
gc9a01_data16(y1);
|
||||||
|
gc9a01_cmd(0x2C); // Memory write
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// GC9A01 Init
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
void display_init(void) {
|
||||||
|
// Init SPI at 40MHz
|
||||||
|
spi_init(spi1, 40000000);
|
||||||
|
gpio_set_function(GFX_CLK, GPIO_FUNC_SPI);
|
||||||
|
gpio_set_function(GFX_MOSI, GPIO_FUNC_SPI);
|
||||||
|
|
||||||
|
// Init control pins
|
||||||
|
gpio_init(GFX_CS);
|
||||||
|
gpio_set_dir(GFX_CS, GPIO_OUT);
|
||||||
|
gpio_put(GFX_CS, 1);
|
||||||
|
|
||||||
|
gpio_init(GFX_DC);
|
||||||
|
gpio_set_dir(GFX_DC, GPIO_OUT);
|
||||||
|
|
||||||
|
gpio_init(GFX_RST);
|
||||||
|
gpio_set_dir(GFX_RST, GPIO_OUT);
|
||||||
|
|
||||||
|
gpio_init(GFX_BL);
|
||||||
|
gpio_set_dir(GFX_BL, GPIO_OUT);
|
||||||
|
|
||||||
|
// Hardware reset
|
||||||
|
gpio_put(GFX_RST, 1);
|
||||||
|
sleep_ms(10);
|
||||||
|
gpio_put(GFX_RST, 0);
|
||||||
|
sleep_ms(10);
|
||||||
|
gpio_put(GFX_RST, 1);
|
||||||
|
sleep_ms(120);
|
||||||
|
|
||||||
|
// GC9A01 initialization sequence
|
||||||
|
gc9a01_cmd(0xEF);
|
||||||
|
gc9a01_cmd(0xEB); gc9a01_data8(0x14);
|
||||||
|
gc9a01_cmd(0xFE); // Inter register enable 1
|
||||||
|
gc9a01_cmd(0xEF); // Inter register enable 2
|
||||||
|
|
||||||
|
gc9a01_cmd(0xEB); gc9a01_data8(0x14);
|
||||||
|
gc9a01_cmd(0x84); gc9a01_data8(0x40);
|
||||||
|
gc9a01_cmd(0x85); gc9a01_data8(0xFF);
|
||||||
|
gc9a01_cmd(0x86); gc9a01_data8(0xFF);
|
||||||
|
gc9a01_cmd(0x87); gc9a01_data8(0xFF);
|
||||||
|
gc9a01_cmd(0x88); gc9a01_data8(0x0A);
|
||||||
|
gc9a01_cmd(0x89); gc9a01_data8(0x21);
|
||||||
|
gc9a01_cmd(0x8A); gc9a01_data8(0x00);
|
||||||
|
gc9a01_cmd(0x8B); gc9a01_data8(0x80);
|
||||||
|
gc9a01_cmd(0x8C); gc9a01_data8(0x01);
|
||||||
|
gc9a01_cmd(0x8D); gc9a01_data8(0x01);
|
||||||
|
gc9a01_cmd(0x8E); gc9a01_data8(0xFF);
|
||||||
|
gc9a01_cmd(0x8F); gc9a01_data8(0xFF);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xB6); // Display function control
|
||||||
|
uint8_t b6[] = {0x00, 0x00};
|
||||||
|
gc9a01_data(b6, 2);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x36); // Memory access control: MV+BGR (rotation=1)
|
||||||
|
gc9a01_data8(0x28);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x3A); // Pixel format: 16-bit RGB565
|
||||||
|
gc9a01_data8(0x05);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x90);
|
||||||
|
uint8_t d90[] = {0x08, 0x08, 0x08, 0x08};
|
||||||
|
gc9a01_data(d90, 4);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xBD); gc9a01_data8(0x06);
|
||||||
|
gc9a01_cmd(0xBC); gc9a01_data8(0x00);
|
||||||
|
gc9a01_cmd(0xFF);
|
||||||
|
uint8_t dff[] = {0x60, 0x01, 0x04};
|
||||||
|
gc9a01_data(dff, 3);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xC3); gc9a01_data8(0x13); // Voltage reg 1a
|
||||||
|
gc9a01_cmd(0xC4); gc9a01_data8(0x13); // Voltage reg 1b
|
||||||
|
gc9a01_cmd(0xC9); gc9a01_data8(0x22);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xBE); gc9a01_data8(0x11);
|
||||||
|
gc9a01_cmd(0xE1); gc9a01_data8(0x10);
|
||||||
|
gc9a01_cmd(0xDF);
|
||||||
|
uint8_t ddf[] = {0x21, 0x0C, 0x02};
|
||||||
|
gc9a01_data(ddf, 3);
|
||||||
|
|
||||||
|
// Gamma
|
||||||
|
gc9a01_cmd(0xF0);
|
||||||
|
uint8_t gp[] = {0x45, 0x09, 0x08, 0x08, 0x26, 0x2A};
|
||||||
|
gc9a01_data(gp, 6);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xF1);
|
||||||
|
uint8_t gn[] = {0x43, 0x70, 0x72, 0x36, 0x37, 0x6F};
|
||||||
|
gc9a01_data(gn, 6);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xF2);
|
||||||
|
uint8_t g2p[] = {0x45, 0x09, 0x08, 0x08, 0x26, 0x2A};
|
||||||
|
gc9a01_data(g2p, 6);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xF3);
|
||||||
|
uint8_t g2n[] = {0x43, 0x70, 0x72, 0x36, 0x37, 0x6F};
|
||||||
|
gc9a01_data(g2n, 6);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xED);
|
||||||
|
uint8_t ded[] = {0x1B, 0x0B};
|
||||||
|
gc9a01_data(ded, 2);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xAE); gc9a01_data8(0x77);
|
||||||
|
gc9a01_cmd(0xCD); gc9a01_data8(0x63);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x70);
|
||||||
|
uint8_t d70[] = {0x07, 0x07, 0x04, 0x0E, 0x0F, 0x09, 0x07, 0x08, 0x03};
|
||||||
|
gc9a01_data(d70, 9);
|
||||||
|
|
||||||
|
gc9a01_cmd(0xE8); gc9a01_data8(0x34);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x62);
|
||||||
|
uint8_t d62[] = {0x18, 0x0D, 0x71, 0xED, 0x70, 0x70,
|
||||||
|
0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70};
|
||||||
|
gc9a01_data(d62, 12);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x63);
|
||||||
|
uint8_t d63[] = {0x18, 0x11, 0x71, 0xF1, 0x70, 0x70,
|
||||||
|
0x18, 0x13, 0x71, 0xF3, 0x70, 0x70};
|
||||||
|
gc9a01_data(d63, 12);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x64);
|
||||||
|
uint8_t d64[] = {0x28, 0x29, 0xF1, 0x01, 0xF1, 0x00, 0x07};
|
||||||
|
gc9a01_data(d64, 7);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x66);
|
||||||
|
uint8_t d66[] = {0x3C, 0x00, 0xCD, 0x67, 0x45, 0x45, 0x10, 0x00, 0x00, 0x00};
|
||||||
|
gc9a01_data(d66, 10);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x67);
|
||||||
|
uint8_t d67[] = {0x00, 0x3C, 0x00, 0x00, 0x00, 0x01, 0x54, 0x10, 0x32, 0x98};
|
||||||
|
gc9a01_data(d67, 10);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x74);
|
||||||
|
uint8_t d74[] = {0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00};
|
||||||
|
gc9a01_data(d74, 7);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x98);
|
||||||
|
uint8_t d98[] = {0x3E, 0x07};
|
||||||
|
gc9a01_data(d98, 2);
|
||||||
|
|
||||||
|
gc9a01_cmd(0x35); // Tearing effect line on
|
||||||
|
gc9a01_cmd(0x21); // Display inversion on (IPS)
|
||||||
|
gc9a01_cmd(0x11); // Sleep out
|
||||||
|
sleep_ms(120);
|
||||||
|
gc9a01_cmd(0x29); // Display on
|
||||||
|
sleep_ms(20);
|
||||||
|
|
||||||
|
// Fill screen black
|
||||||
|
gc9a01_set_window(0, 0, TFT_WIDTH - 1, TFT_HEIGHT - 1);
|
||||||
|
gpio_put(GFX_DC, 1);
|
||||||
|
gpio_put(GFX_CS, 0);
|
||||||
|
for (uint32_t i = 0; i < TFT_WIDTH * TFT_HEIGHT; i++) {
|
||||||
|
uint8_t buf[2] = {0, 0};
|
||||||
|
spi_write_blocking(spi1, buf, 2);
|
||||||
|
}
|
||||||
|
gpio_put(GFX_CS, 1);
|
||||||
|
|
||||||
|
// Draw background bitmap (210x210 at offset 10,0)
|
||||||
|
gc9a01_set_window(10, 0, 10 + 210 - 1, 210 - 1);
|
||||||
|
gpio_put(GFX_DC, 1);
|
||||||
|
gpio_put(GFX_CS, 0);
|
||||||
|
for (uint32_t i = 0; i < 44100; i++) {
|
||||||
|
uint16_t pixel = background[i];
|
||||||
|
uint8_t buf[2] = {pixel >> 8, pixel & 0xFF};
|
||||||
|
spi_write_blocking(spi1, buf, 2);
|
||||||
|
}
|
||||||
|
gpio_put(GFX_CS, 1);
|
||||||
|
|
||||||
|
sleep_ms(2000);
|
||||||
|
|
||||||
|
// Backlight on
|
||||||
|
gpio_put(GFX_BL, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// TFT drawing helpers
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static void tft_fill_rect(uint16_t x, uint16_t y, uint16_t w, uint16_t h, uint16_t color) {
|
||||||
|
if (x >= TFT_WIDTH || y >= TFT_HEIGHT) return;
|
||||||
|
if (x + w > TFT_WIDTH) w = TFT_WIDTH - x;
|
||||||
|
if (y + h > TFT_HEIGHT) h = TFT_HEIGHT - y;
|
||||||
|
|
||||||
|
gc9a01_set_window(x, y, x + w - 1, y + h - 1);
|
||||||
|
gpio_put(GFX_DC, 1);
|
||||||
|
gpio_put(GFX_CS, 0);
|
||||||
|
uint8_t hi = color >> 8;
|
||||||
|
uint8_t lo = color & 0xFF;
|
||||||
|
for (uint32_t i = 0; i < (uint32_t)w * h; i++) {
|
||||||
|
uint8_t buf[2] = {hi, lo};
|
||||||
|
spi_write_blocking(spi1, buf, 2);
|
||||||
|
}
|
||||||
|
gpio_put(GFX_CS, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void tft_draw_char(uint16_t x, uint16_t y, char c, uint16_t color) {
|
||||||
|
if (c < 0x20 || c > 0x7E) return;
|
||||||
|
uint8_t idx = c - 0x20;
|
||||||
|
|
||||||
|
// Draw character pixel by pixel using individual 1x1 rects
|
||||||
|
for (uint8_t col = 0; col < FONT_W; col++) {
|
||||||
|
uint8_t line = font6x8[idx][col];
|
||||||
|
for (uint8_t row = 0; row < FONT_H; row++) {
|
||||||
|
if (line & (1 << row)) {
|
||||||
|
gc9a01_set_window(x + col, y + row, x + col, y + row);
|
||||||
|
gc9a01_data16(color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Display interface implementation
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
void display_clear(void) {
|
||||||
|
cursor_x = TEXT_X;
|
||||||
|
cursor_y = TEXT_Y_START;
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_update(void) {
|
||||||
|
// TFT updates are immediate, no framebuffer to flush
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_set_cursor(uint8_t x, uint8_t y) {
|
||||||
|
cursor_x = x;
|
||||||
|
cursor_y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_get_cursor_y(void) {
|
||||||
|
return (uint8_t)cursor_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_width(void) {
|
||||||
|
return TFT_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_height(void) {
|
||||||
|
return TFT_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_print(const char* str) {
|
||||||
|
while (*str) {
|
||||||
|
if (*str == '\n') {
|
||||||
|
cursor_x = TEXT_X;
|
||||||
|
cursor_y += FONT_H + 2;
|
||||||
|
} else if (*str == '\r') {
|
||||||
|
cursor_x = TEXT_X;
|
||||||
|
} else {
|
||||||
|
tft_draw_char(cursor_x, cursor_y, *str, COLOR_MAGENTA);
|
||||||
|
cursor_x += FONT_W;
|
||||||
|
}
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_get_pixel(uint8_t x, uint8_t y) {
|
||||||
|
(void)x; (void)y;
|
||||||
|
return 0; // Not used for TFT
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_draw_pixel(uint8_t x, uint8_t y, uint8_t color) {
|
||||||
|
gc9a01_set_window(x, y, x, y);
|
||||||
|
gc9a01_data16(color ? COLOR_MAGENTA : COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) {
|
||||||
|
tft_fill_rect(x, y, w, h, color ? COLOR_MAGENTA : COLOR_BLACK);
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_scroll_up(uint8_t pixels) {
|
||||||
|
(void)pixels;
|
||||||
|
// TFT: no pixel scroll, just clear text area on overflow
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_check_and_scroll(void) {
|
||||||
|
if (cursor_y > TEXT_Y_MAX) {
|
||||||
|
cls();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printout(const char* str) {
|
||||||
|
display_check_and_scroll();
|
||||||
|
|
||||||
|
// Skip leading newline for TFT
|
||||||
|
if (str[0] == '\n') {
|
||||||
|
cursor_x = TEXT_X;
|
||||||
|
cursor_y += FONT_H + 2;
|
||||||
|
display_print(str + 1);
|
||||||
|
} else {
|
||||||
|
display_print(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Also output on CDC serial
|
||||||
|
serial_println(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cls(void) {
|
||||||
|
// Clear the text area with a black rounded rect
|
||||||
|
tft_fill_rect(TEXT_AREA_X, TEXT_AREA_Y, TEXT_AREA_W, TEXT_AREA_H, COLOR_BLACK);
|
||||||
|
cursor_x = TEXT_X;
|
||||||
|
cursor_y = TEXT_Y_START;
|
||||||
|
display_print(VERSION);
|
||||||
|
cursor_x = TEXT_X;
|
||||||
|
cursor_y = TEXT_Y_START + FONT_H + 2;
|
||||||
|
display_print("-----------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // !PIWATCH
|
||||||
|
|
||||||
|
//====================================================================
|
||||||
|
// Default: SSD1306 OLED via I2C (128x32 or 128x64)
|
||||||
|
//====================================================================
|
||||||
|
|
||||||
|
#include "hardware/i2c.h"
|
||||||
|
|
||||||
|
// Framebuffer
|
||||||
|
#define FB_SIZE (OLED_WIDTH * OLED_HEIGHT / 8)
|
||||||
|
static uint8_t framebuffer[FB_SIZE];
|
||||||
|
|
||||||
|
// Cursor state
|
||||||
|
static uint8_t cursor_x = 0;
|
||||||
|
static uint8_t cursor_y = 0;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Low-level I2C helpers
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static void ssd1306_cmd(uint8_t cmd) {
|
||||||
|
uint8_t buf[2] = {0x00, cmd}; // Co=0, D/C#=0 (command)
|
||||||
|
i2c_write_blocking(i2c0, I2C_ADDRESS, buf, 2, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Init
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
void display_init(void) {
|
||||||
|
// Init I2C at 400kHz
|
||||||
|
i2c_init(i2c0, 400000);
|
||||||
|
gpio_set_function(I2C_SDA_PIN, GPIO_FUNC_I2C);
|
||||||
|
gpio_set_function(I2C_SCL_PIN, GPIO_FUNC_I2C);
|
||||||
|
gpio_pull_up(I2C_SDA_PIN);
|
||||||
|
gpio_pull_up(I2C_SCL_PIN);
|
||||||
|
|
||||||
|
// Wait for display to power up
|
||||||
|
sleep_ms(100);
|
||||||
|
|
||||||
|
// SSD1306 initialization sequence (works for 128x32 and 128x64)
|
||||||
|
ssd1306_cmd(0xAE); // Display off
|
||||||
|
|
||||||
|
ssd1306_cmd(0xD5); // Set display clock divide ratio
|
||||||
|
ssd1306_cmd(0x80); // Suggested ratio
|
||||||
|
|
||||||
|
ssd1306_cmd(0xA8); // Set multiplex ratio
|
||||||
|
ssd1306_cmd(OLED_HEIGHT - 1);
|
||||||
|
|
||||||
|
ssd1306_cmd(0xD3); // Set display offset
|
||||||
|
ssd1306_cmd(0x00); // No offset
|
||||||
|
|
||||||
|
ssd1306_cmd(0x40); // Set start line to 0
|
||||||
|
|
||||||
|
ssd1306_cmd(0x8D); // Charge pump
|
||||||
|
ssd1306_cmd(0x14); // Enable charge pump
|
||||||
|
|
||||||
|
ssd1306_cmd(0x20); // Memory addressing mode
|
||||||
|
ssd1306_cmd(0x00); // Horizontal addressing mode
|
||||||
|
|
||||||
|
ssd1306_cmd(0xA1); // Segment re-map (column 127 mapped to SEG0)
|
||||||
|
ssd1306_cmd(0xC8); // COM output scan direction (remapped)
|
||||||
|
|
||||||
|
ssd1306_cmd(0xDA); // Set COM pins hardware configuration
|
||||||
|
#if OLED_HEIGHT == 64
|
||||||
|
ssd1306_cmd(0x12); // Alternate COM pin config for 128x64
|
||||||
|
#else
|
||||||
|
ssd1306_cmd(0x02); // Sequential COM pin config for 128x32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ssd1306_cmd(0x81); // Set contrast
|
||||||
|
ssd1306_cmd(0x8F); // Medium contrast
|
||||||
|
|
||||||
|
ssd1306_cmd(0xD9); // Set pre-charge period
|
||||||
|
ssd1306_cmd(0xF1);
|
||||||
|
|
||||||
|
ssd1306_cmd(0xDB); // Set VCOMH deselect level
|
||||||
|
ssd1306_cmd(0x40);
|
||||||
|
|
||||||
|
ssd1306_cmd(0xA4); // Entire display ON (follow RAM)
|
||||||
|
ssd1306_cmd(0xA6); // Normal display (not inverted)
|
||||||
|
|
||||||
|
ssd1306_cmd(0xAF); // Display on
|
||||||
|
|
||||||
|
// Clear framebuffer and update
|
||||||
|
memset(framebuffer, 0, FB_SIZE);
|
||||||
|
display_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Framebuffer operations
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
void display_clear(void) {
|
||||||
|
memset(framebuffer, 0, FB_SIZE);
|
||||||
|
cursor_x = 0;
|
||||||
|
cursor_y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_update(void) {
|
||||||
|
// Set column address range
|
||||||
|
ssd1306_cmd(0x21); // Column address
|
||||||
|
ssd1306_cmd(0); // Start
|
||||||
|
ssd1306_cmd(OLED_WIDTH - 1); // End
|
||||||
|
|
||||||
|
// Set page address range
|
||||||
|
ssd1306_cmd(0x22); // Page address
|
||||||
|
ssd1306_cmd(0); // Start
|
||||||
|
ssd1306_cmd((OLED_HEIGHT / 8) - 1); // End
|
||||||
|
|
||||||
|
// Send framebuffer data
|
||||||
|
for (uint16_t i = 0; i < FB_SIZE; i += 128) {
|
||||||
|
uint8_t buf[129];
|
||||||
|
buf[0] = 0x40; // Co=0, D/C#=1 (data)
|
||||||
|
uint16_t chunk = (FB_SIZE - i < 128) ? (FB_SIZE - i) : 128;
|
||||||
|
memcpy(&buf[1], &framebuffer[i], chunk);
|
||||||
|
i2c_write_blocking(i2c0, I2C_ADDRESS, buf, chunk + 1, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_set_cursor(uint8_t x, uint8_t y) {
|
||||||
|
cursor_x = x;
|
||||||
|
cursor_y = y;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_get_cursor_y(void) {
|
||||||
|
return cursor_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_width(void) {
|
||||||
|
return OLED_WIDTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_height(void) {
|
||||||
|
return OLED_HEIGHT;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t display_get_pixel(uint8_t x, uint8_t y) {
|
||||||
|
if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return 0;
|
||||||
|
uint16_t byte_idx = x + (y / 8) * OLED_WIDTH;
|
||||||
|
uint8_t bit_idx = y & 7;
|
||||||
|
return (framebuffer[byte_idx] >> bit_idx) & 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_draw_pixel(uint8_t x, uint8_t y, uint8_t color) {
|
||||||
|
if (x >= OLED_WIDTH || y >= OLED_HEIGHT) return;
|
||||||
|
uint16_t byte_idx = x + (y / 8) * OLED_WIDTH;
|
||||||
|
uint8_t bit_idx = y & 7;
|
||||||
|
if (color) {
|
||||||
|
framebuffer[byte_idx] |= (1 << bit_idx);
|
||||||
|
} else {
|
||||||
|
framebuffer[byte_idx] &= ~(1 << bit_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color) {
|
||||||
|
for (uint8_t j = y; j < y + h && j < OLED_HEIGHT; j++) {
|
||||||
|
for (uint8_t i = x; i < x + w && i < OLED_WIDTH; i++) {
|
||||||
|
display_draw_pixel(i, j, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Text rendering
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static void draw_char(uint8_t x, uint8_t y, char c) {
|
||||||
|
if (c < 0x20 || c > 0x7E) return;
|
||||||
|
uint8_t idx = c - 0x20;
|
||||||
|
for (uint8_t col = 0; col < FONT_W; col++) {
|
||||||
|
uint8_t line = font6x8[idx][col];
|
||||||
|
for (uint8_t row = 0; row < FONT_H; row++) {
|
||||||
|
if (line & (1 << row)) {
|
||||||
|
display_draw_pixel(x + col, y + row, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_print(const char* str) {
|
||||||
|
while (*str) {
|
||||||
|
if (*str == '\n') {
|
||||||
|
cursor_x = 0;
|
||||||
|
cursor_y += FONT_H;
|
||||||
|
} else if (*str == '\r') {
|
||||||
|
cursor_x = 0;
|
||||||
|
} else {
|
||||||
|
if (cursor_x + FONT_W > OLED_WIDTH) {
|
||||||
|
cursor_x = 0;
|
||||||
|
cursor_y += FONT_H;
|
||||||
|
}
|
||||||
|
draw_char(cursor_x, cursor_y, *str);
|
||||||
|
cursor_x += FONT_W;
|
||||||
|
}
|
||||||
|
str++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// High-level display functions
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void display_scroll_up(uint8_t pixels) {
|
||||||
|
for (uint8_t y = 0; y < OLED_HEIGHT - pixels; y++) {
|
||||||
|
for (uint8_t x = 0; x < OLED_WIDTH; x++) {
|
||||||
|
uint8_t color = display_get_pixel(x, y + pixels);
|
||||||
|
display_draw_pixel(x, y, color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
display_fill_rect(0, OLED_HEIGHT - pixels, OLED_WIDTH, pixels, 0);
|
||||||
|
display_update();
|
||||||
|
}
|
||||||
|
|
||||||
|
void display_check_and_scroll(void) {
|
||||||
|
if ((cursor_y + 16) > OLED_HEIGHT) {
|
||||||
|
display_scroll_up(8);
|
||||||
|
cursor_y -= 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void printout(const char* str) {
|
||||||
|
display_check_and_scroll();
|
||||||
|
display_print(str);
|
||||||
|
display_update();
|
||||||
|
|
||||||
|
// Also output on CDC serial
|
||||||
|
serial_println(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
void cls(void) {
|
||||||
|
display_clear();
|
||||||
|
display_set_cursor(0, 0);
|
||||||
|
printout(VERSION);
|
||||||
|
printout("\n-----------------");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // PIWATCH
|
||||||
51
src/display.h
Normal file
51
src/display.h
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
#ifndef DISPLAY_H
|
||||||
|
#define DISPLAY_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// Initialize display (SSD1306 I2C or GC9A01 SPI depending on build config)
|
||||||
|
void display_init(void);
|
||||||
|
|
||||||
|
// Clear entire framebuffer
|
||||||
|
void display_clear(void);
|
||||||
|
|
||||||
|
// Write framebuffer to display
|
||||||
|
void display_update(void);
|
||||||
|
|
||||||
|
// Set text cursor position
|
||||||
|
void display_set_cursor(uint8_t x, uint8_t y);
|
||||||
|
|
||||||
|
// Get current cursor Y position
|
||||||
|
uint8_t display_get_cursor_y(void);
|
||||||
|
|
||||||
|
// Get display dimensions
|
||||||
|
uint8_t display_width(void);
|
||||||
|
uint8_t display_height(void);
|
||||||
|
|
||||||
|
// Print string at current cursor (wraps, advances cursor)
|
||||||
|
void display_print(const char* str);
|
||||||
|
|
||||||
|
// Get pixel value at (x, y)
|
||||||
|
uint8_t display_get_pixel(uint8_t x, uint8_t y);
|
||||||
|
|
||||||
|
// Set pixel at (x, y) to color (0=black, 1=white)
|
||||||
|
void display_draw_pixel(uint8_t x, uint8_t y, uint8_t color);
|
||||||
|
|
||||||
|
// Fill rectangle with color
|
||||||
|
void display_fill_rect(uint8_t x, uint8_t y, uint8_t w, uint8_t h, uint8_t color);
|
||||||
|
|
||||||
|
// High-level functions
|
||||||
|
|
||||||
|
// Scroll display up by N pixels
|
||||||
|
void display_scroll_up(uint8_t pixels);
|
||||||
|
|
||||||
|
// Check if scroll is needed and perform it
|
||||||
|
void display_check_and_scroll(void);
|
||||||
|
|
||||||
|
// Print message to display + serial
|
||||||
|
void printout(const char* str);
|
||||||
|
|
||||||
|
// Clear display and show version header
|
||||||
|
void cls(void);
|
||||||
|
|
||||||
|
#endif // DISPLAY_H
|
||||||
190
src/hid_handler.c
Normal file
190
src/hid_handler.c
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - HID Report Handler
|
||||||
|
*
|
||||||
|
* Decodes keyboard and mouse HID reports.
|
||||||
|
* Called from Core 1 uses tud_cdc_write() directly (no serial_print
|
||||||
|
* wrappers, which gate on tud_cdc_connected() and may block).
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "hid_handler.h"
|
||||||
|
|
||||||
|
// Keycode to ASCII lookup table from TinyUSB
|
||||||
|
static uint8_t const keycode2ascii[128][2] = { HID_KEYCODE_TO_ASCII };
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Internal helpers
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
static inline bool find_key_in_report(hid_keyboard_report_t const* report, uint8_t keycode) {
|
||||||
|
for (uint8_t i = 0; i < 6; i++) {
|
||||||
|
if (report->keycode[i] == keycode) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void check_special_key(uint8_t code, bool *flush) {
|
||||||
|
const char *str = NULL;
|
||||||
|
|
||||||
|
switch (code) {
|
||||||
|
case HID_KEY_ARROW_RIGHT: str = "<ARROWRIGHT>"; break;
|
||||||
|
case HID_KEY_ARROW_LEFT: str = "<ARROWLEFT>"; break;
|
||||||
|
case HID_KEY_ARROW_DOWN: str = "<ARROWDOWN>"; break;
|
||||||
|
case HID_KEY_ARROW_UP: str = "<ARROWUP>"; break;
|
||||||
|
case HID_KEY_HOME: str = "<HOME>"; break;
|
||||||
|
case HID_KEY_KEYPAD_1: str = "<KEYPAD_1>"; break;
|
||||||
|
case HID_KEY_KEYPAD_2: str = "<KEYPAD_2>"; break;
|
||||||
|
case HID_KEY_KEYPAD_3: str = "<KEYPAD_3>"; break;
|
||||||
|
case HID_KEY_KEYPAD_4: str = "<KEYPAD_4>"; break;
|
||||||
|
case HID_KEY_KEYPAD_5: str = "<KEYPAD_5>"; break;
|
||||||
|
case HID_KEY_KEYPAD_6: str = "<KEYPAD_6>"; break;
|
||||||
|
case HID_KEY_KEYPAD_7: str = "<KEYPAD_7>"; break;
|
||||||
|
case HID_KEY_KEYPAD_8: str = "<KEYPAD_8>"; break;
|
||||||
|
case HID_KEY_KEYPAD_9: str = "<KEYPAD_9>"; break;
|
||||||
|
case HID_KEY_KEYPAD_0: str = "<KEYPAD_0>"; break;
|
||||||
|
case HID_KEY_F1: str = "<F1>"; break;
|
||||||
|
case HID_KEY_F2: str = "<F2>"; break;
|
||||||
|
case HID_KEY_F3: str = "<F3>"; break;
|
||||||
|
case HID_KEY_F4: str = "<F4>"; break;
|
||||||
|
case HID_KEY_F5: str = "<F5>"; break;
|
||||||
|
case HID_KEY_F6: str = "<F6>"; break;
|
||||||
|
case HID_KEY_F7: str = "<F7>"; break;
|
||||||
|
case HID_KEY_F8: str = "<F8>"; break;
|
||||||
|
case HID_KEY_F9: str = "<F9>"; break;
|
||||||
|
case HID_KEY_F10: str = "<F10>"; break;
|
||||||
|
case HID_KEY_F11: str = "<F11>"; break;
|
||||||
|
case HID_KEY_F12: str = "<F12>"; break;
|
||||||
|
case HID_KEY_PRINT_SCREEN: str = "<PRNT>"; break;
|
||||||
|
case HID_KEY_SCROLL_LOCK: str = "<SCRLL>"; break;
|
||||||
|
case HID_KEY_PAUSE: str = "<PAUSE>"; break;
|
||||||
|
case HID_KEY_INSERT: str = "<INSERT>"; break;
|
||||||
|
case HID_KEY_PAGE_UP: str = "<PAGEUP>"; break;
|
||||||
|
case HID_KEY_DELETE: str = "<DEL>"; break;
|
||||||
|
case HID_KEY_END: str = "<END>"; break;
|
||||||
|
case HID_KEY_PAGE_DOWN: str = "<PAGEDOWN>"; break;
|
||||||
|
case HID_KEY_NUM_LOCK: str = "<NUMLOCK>"; break;
|
||||||
|
case HID_KEY_KEYPAD_DIVIDE: str = "<KEYPAD_DIV>"; break;
|
||||||
|
case HID_KEY_KEYPAD_MULTIPLY: str = "<KEYPAD_MUL>"; break;
|
||||||
|
case HID_KEY_KEYPAD_SUBTRACT: str = "<KEYPAD_SUB>"; break;
|
||||||
|
case HID_KEY_KEYPAD_ADD: str = "<KEYPAD_ADD>"; break;
|
||||||
|
case HID_KEY_KEYPAD_DECIMAL: str = "<KEYPAD_DECIMAL>"; break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (str) {
|
||||||
|
tud_cdc_write(str, strlen(str));
|
||||||
|
*flush = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Public API
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void process_kbd_report(uint8_t const* report, uint16_t len) {
|
||||||
|
static uint8_t prev_keycodes[6] = {0};
|
||||||
|
static uint8_t prev_modifier = 0;
|
||||||
|
bool flush = false;
|
||||||
|
|
||||||
|
// Extract modifier and keycodes depending on report length.
|
||||||
|
// 9 bytes: [reportID, modifier, reserved, key0..key5] (report ID prefix)
|
||||||
|
// 8 bytes: [modifier, reserved, key0..key5] (standard boot protocol)
|
||||||
|
// 2 bytes: [modifier, key0] (short, some Low Speed)
|
||||||
|
uint8_t modifier;
|
||||||
|
uint8_t keycodes[6] = {0};
|
||||||
|
uint8_t num_keys;
|
||||||
|
|
||||||
|
if (len <= 2) {
|
||||||
|
// Short report: byte 0 = modifier, byte 1 = single keycode
|
||||||
|
modifier = report[0];
|
||||||
|
if (len == 2) keycodes[0] = report[1];
|
||||||
|
num_keys = 1;
|
||||||
|
} else if (len == 9) {
|
||||||
|
// 9-byte report: byte 0 = report ID, then standard 8-byte layout
|
||||||
|
modifier = report[1];
|
||||||
|
// report[2] is reserved
|
||||||
|
num_keys = 6;
|
||||||
|
for (uint8_t i = 0; i < 6; i++) {
|
||||||
|
keycodes[i] = report[3 + i];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Standard 8-byte boot protocol report
|
||||||
|
modifier = report[0];
|
||||||
|
// report[1] is reserved
|
||||||
|
num_keys = (len - 2 > 6) ? 6 : (uint8_t)(len - 2);
|
||||||
|
for (uint8_t i = 0; i < num_keys; i++) {
|
||||||
|
keycodes[i] = report[2 + i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < num_keys; i++) {
|
||||||
|
uint8_t keycode = keycodes[i];
|
||||||
|
if (keycode) {
|
||||||
|
// Check if key was already in previous report (held)
|
||||||
|
bool found = false;
|
||||||
|
for (uint8_t j = 0; j < 6; j++) {
|
||||||
|
if (prev_keycodes[j] == keycode) { found = true; break; }
|
||||||
|
}
|
||||||
|
if (found) continue;
|
||||||
|
|
||||||
|
// New key press
|
||||||
|
bool const is_shift = modifier &
|
||||||
|
(KEYBOARD_MODIFIER_LEFTSHIFT | KEYBOARD_MODIFIER_RIGHTSHIFT);
|
||||||
|
uint8_t ch = keycode2ascii[keycode][is_shift ? 1 : 0];
|
||||||
|
|
||||||
|
bool const is_ctrl = modifier &
|
||||||
|
(KEYBOARD_MODIFIER_LEFTCTRL | KEYBOARD_MODIFIER_RIGHTCTRL);
|
||||||
|
if (is_ctrl) {
|
||||||
|
tud_cdc_write("CTRL+", 5);
|
||||||
|
flush = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const is_gui = modifier &
|
||||||
|
(KEYBOARD_MODIFIER_LEFTGUI | KEYBOARD_MODIFIER_RIGHTGUI);
|
||||||
|
if (is_gui) {
|
||||||
|
tud_cdc_write("GUI+", 4);
|
||||||
|
flush = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool const is_alt = modifier &
|
||||||
|
(KEYBOARD_MODIFIER_LEFTALT | KEYBOARD_MODIFIER_RIGHTALT);
|
||||||
|
if (is_alt) {
|
||||||
|
tud_cdc_write("ALT+", 4);
|
||||||
|
flush = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
check_special_key(keycode, &flush);
|
||||||
|
|
||||||
|
if (ch) {
|
||||||
|
if (ch == '\r') {
|
||||||
|
tud_cdc_write("\r\n", 2);
|
||||||
|
} else {
|
||||||
|
tud_cdc_write(&ch, 1);
|
||||||
|
}
|
||||||
|
flush = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flush) tud_cdc_write_flush();
|
||||||
|
prev_modifier = modifier;
|
||||||
|
memcpy(prev_keycodes, keycodes, 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
void process_mouse_report(hid_mouse_report_t const* report) {
|
||||||
|
static hid_mouse_report_t prev_report = {0};
|
||||||
|
|
||||||
|
char l = report->buttons & MOUSE_BUTTON_LEFT ? 'L' : '-';
|
||||||
|
char m = report->buttons & MOUSE_BUTTON_MIDDLE ? 'M' : '-';
|
||||||
|
char r = report->buttons & MOUSE_BUTTON_RIGHT ? 'R' : '-';
|
||||||
|
|
||||||
|
char tempbuf[32];
|
||||||
|
int count = sprintf(tempbuf, "%c%c%c %d %d %d\r\n", l, m, r, report->x, report->y, report->wheel);
|
||||||
|
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
|
||||||
|
prev_report = *report;
|
||||||
|
}
|
||||||
12
src/hid_handler.h
Normal file
12
src/hid_handler.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef HID_HANDLER_H
|
||||||
|
#define HID_HANDLER_H
|
||||||
|
|
||||||
|
#include "tusb.h"
|
||||||
|
|
||||||
|
// Process a keyboard HID report (len needed for short-report devices)
|
||||||
|
void process_kbd_report(uint8_t const* report, uint16_t len);
|
||||||
|
|
||||||
|
// Process a mouse HID report
|
||||||
|
void process_mouse_report(hid_mouse_report_t const* report);
|
||||||
|
|
||||||
|
#endif // HID_HANDLER_H
|
||||||
290
src/main.c
Normal file
290
src/main.c
Normal file
@@ -0,0 +1,290 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - Pure Pico SDK Implementation
|
||||||
|
*
|
||||||
|
* USB security tool: acts as USB Mass Storage Device (Core 0) while
|
||||||
|
* simultaneously running USB Host via PIO USB (Core 1) to detect
|
||||||
|
* BadUSB/HID attacks.
|
||||||
|
*
|
||||||
|
* Copyright (c) Cesare Pizzi
|
||||||
|
* MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include "hardware/gpio.h"
|
||||||
|
#include "hardware/watchdog.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "usb_config.h"
|
||||||
|
#include "usb_device.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
#include "display.h"
|
||||||
|
#include "serial_output.h"
|
||||||
|
|
||||||
|
#ifdef USE_BOOTSEL
|
||||||
|
#include "pico/bootrom.h"
|
||||||
|
#include "hardware/structs/ioqspi.h"
|
||||||
|
#include "hardware/structs/sio.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Shared state (cross-core, volatile. ARM word writes are atomic)
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
volatile bool flag_readme = false;
|
||||||
|
volatile bool flag_autorun = false;
|
||||||
|
volatile bool flag_written = false;
|
||||||
|
volatile bool flag_deleted = false;
|
||||||
|
volatile bool flag_written_reported = false;
|
||||||
|
volatile bool flag_deleted_reported = false;
|
||||||
|
volatile bool flag_hid_sent = false;
|
||||||
|
volatile bool flag_hid_reported = false;
|
||||||
|
volatile bool flag_hid_mounted = false;
|
||||||
|
volatile bool flag_msc_mounted = false;
|
||||||
|
volatile bool flag_cdc_mounted = false;
|
||||||
|
volatile uint32_t hid_event_num = 0;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Software reset via watchdog
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static void swreset(void) {
|
||||||
|
watchdog_enable(1500, 1);
|
||||||
|
while (1) {
|
||||||
|
tight_loop_contents();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_BOOTSEL
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// BOOTSEL button check (multi-core safe)
|
||||||
|
//
|
||||||
|
// WARNING: Reading BOOTSEL temporarily disconnects flash (QSPI CS
|
||||||
|
// override), which crashes any core executing from flash. This
|
||||||
|
// disrupts PIO USB on Core 1 and causes unreliable Low Speed device
|
||||||
|
// detection. Only enable this on boards without an external button.
|
||||||
|
//
|
||||||
|
// Core 1 must call multicore_lockout_victim_init() at startup.
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static bool __no_inline_not_in_flash_func(bootsel_pressed_raw)(void) {
|
||||||
|
const uint CS_PIN_INDEX = 1;
|
||||||
|
|
||||||
|
hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl,
|
||||||
|
GPIO_OVERRIDE_LOW << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
|
||||||
|
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
|
||||||
|
|
||||||
|
for (volatile int i = 0; i < 1000; i++);
|
||||||
|
|
||||||
|
bool pressed = !(sio_hw->gpio_hi_in & (1u << CS_PIN_INDEX));
|
||||||
|
|
||||||
|
hw_write_masked(&ioqspi_hw->io[CS_PIN_INDEX].ctrl,
|
||||||
|
GPIO_OVERRIDE_NORMAL << IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_LSB,
|
||||||
|
IO_QSPI_GPIO_QSPI_SS_CTRL_OEOVER_BITS);
|
||||||
|
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool bootsel_pressed(void) {
|
||||||
|
multicore_lockout_start_blocking();
|
||||||
|
uint32_t flags = save_and_disable_interrupts();
|
||||||
|
bool pressed = bootsel_pressed_raw();
|
||||||
|
restore_interrupts(flags);
|
||||||
|
multicore_lockout_end_blocking();
|
||||||
|
return pressed;
|
||||||
|
}
|
||||||
|
#endif // USE_BOOTSEL
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Main (Core 0)
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
int main(void) {
|
||||||
|
// Sysclock must be a multiple of 12MHz for PIO USB.
|
||||||
|
#if defined(PICO_RP2350)
|
||||||
|
set_sys_clock_khz(144000, true);
|
||||||
|
#else
|
||||||
|
set_sys_clock_khz(240000, true);
|
||||||
|
#endif
|
||||||
|
sleep_ms(10);
|
||||||
|
|
||||||
|
// Launch Core 1 for USB host BEFORE device init.
|
||||||
|
// Core 1 handles PIO USB host. Must start first so PIO IRQs
|
||||||
|
// are registered on Core 1.
|
||||||
|
multicore_reset_core1();
|
||||||
|
multicore_launch_core1(core1_entry);
|
||||||
|
|
||||||
|
// Init device stack on native USB (rhport 0)
|
||||||
|
tud_init(0);
|
||||||
|
|
||||||
|
// Run ramdisk self-test
|
||||||
|
bool selftest_ok = usb_device_selftest();
|
||||||
|
|
||||||
|
// Initialize display
|
||||||
|
display_init();
|
||||||
|
cls();
|
||||||
|
|
||||||
|
if (selftest_ok) {
|
||||||
|
printout("\n[+] Selftest: OK");
|
||||||
|
} else {
|
||||||
|
printout("\n[!] Selftest: KO");
|
||||||
|
printout("\n[!] Stopping...");
|
||||||
|
while (1) {
|
||||||
|
sleep_ms(1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PIWATCH
|
||||||
|
// Set up LED pin (PIWATCH uses GPIO 25 for backlight)
|
||||||
|
gpio_init(LED_PIN);
|
||||||
|
gpio_set_dir(LED_PIN, GPIO_OUT);
|
||||||
|
gpio_put(LED_PIN, 1); // LED on = OK
|
||||||
|
|
||||||
|
// Set up external reset button (active-low with pull-up)
|
||||||
|
gpio_init(BUTTON_PIN);
|
||||||
|
gpio_set_dir(BUTTON_PIN, GPIO_IN);
|
||||||
|
gpio_pull_up(BUTTON_PIN);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Core 0 main loop
|
||||||
|
while (true) {
|
||||||
|
// Process TinyUSB device tasks (required for CDC serial + MSC)
|
||||||
|
tud_task();
|
||||||
|
|
||||||
|
// Flush CDC writes from Core 1 callbacks
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
|
||||||
|
// Check flags and report events
|
||||||
|
if (flag_readme) {
|
||||||
|
printout("\n[!] README (R)");
|
||||||
|
flag_readme = false;
|
||||||
|
#ifndef PIWATCH
|
||||||
|
gpio_put(LED_PIN, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_autorun) {
|
||||||
|
printout("\n[+] AUTORUN (R)");
|
||||||
|
flag_autorun = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_deleted && !flag_deleted_reported) {
|
||||||
|
printout("\n[!] DELETING");
|
||||||
|
flag_deleted = false;
|
||||||
|
flag_deleted_reported = true;
|
||||||
|
#ifndef PIWATCH
|
||||||
|
gpio_put(LED_PIN, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_written && !flag_written_reported) {
|
||||||
|
printout("\n[!] WRITING");
|
||||||
|
flag_written = false;
|
||||||
|
flag_written_reported = true;
|
||||||
|
#ifndef PIWATCH
|
||||||
|
gpio_put(LED_PIN, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_hid_mounted) {
|
||||||
|
printout("\n[!!] HID Device");
|
||||||
|
flag_hid_mounted = false;
|
||||||
|
#ifndef PIWATCH
|
||||||
|
gpio_put(LED_PIN, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_msc_mounted) {
|
||||||
|
printout("\n[++] Mass Device");
|
||||||
|
flag_msc_mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_cdc_mounted) {
|
||||||
|
printout("\n[++] CDC Device");
|
||||||
|
flag_cdc_mounted = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flag_hid_sent && !flag_hid_reported) {
|
||||||
|
printout("\n[!!] HID Sending data");
|
||||||
|
flag_hid_sent = false;
|
||||||
|
flag_hid_reported = true;
|
||||||
|
#ifndef PIWATCH
|
||||||
|
gpio_put(LED_PIN, 0);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef PIWATCH
|
||||||
|
// External button on GPIO 0 (active-low, debounced)
|
||||||
|
if (!gpio_get(BUTTON_PIN)) {
|
||||||
|
sleep_ms(50); // debounce
|
||||||
|
if (!gpio_get(BUTTON_PIN)) {
|
||||||
|
uint32_t press_start = to_ms_since_boot(get_absolute_time());
|
||||||
|
while (!gpio_get(BUTTON_PIN)) {
|
||||||
|
sleep_ms(10);
|
||||||
|
}
|
||||||
|
uint32_t press_duration = to_ms_since_boot(get_absolute_time()) - press_start;
|
||||||
|
|
||||||
|
if (press_duration > 2000) {
|
||||||
|
// Long press: show HID event count
|
||||||
|
char outstr[32];
|
||||||
|
snprintf(outstr, sizeof(outstr), "\n[+] HID Evt# %u", (unsigned)hid_event_num);
|
||||||
|
printout(outstr);
|
||||||
|
} else {
|
||||||
|
// Short press: reset
|
||||||
|
printout("\n[+] RESETTING");
|
||||||
|
swreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // !PIWATCH
|
||||||
|
|
||||||
|
// CDC serial commands
|
||||||
|
// 'r'/'R' = reset, 'h'/'H' = HID event count
|
||||||
|
if (tud_cdc_available()) {
|
||||||
|
char buf[64];
|
||||||
|
uint32_t count = tud_cdc_read(buf, sizeof(buf));
|
||||||
|
for (uint32_t i = 0; i < count; i++) {
|
||||||
|
switch (buf[i]) {
|
||||||
|
case 'r': case 'R':
|
||||||
|
printout("\n[+] RESETTING");
|
||||||
|
swreset();
|
||||||
|
break;
|
||||||
|
case 'h': case 'H': {
|
||||||
|
char outstr[32];
|
||||||
|
snprintf(outstr, sizeof(outstr), "\n[+] HID Evt# %u", (unsigned)hid_event_num);
|
||||||
|
printout(outstr);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef USE_BOOTSEL
|
||||||
|
// BOOTSEL polling (disabled by default, disrupts Low Speed USB).
|
||||||
|
// Only enable via -DUSE_BOOTSEL for boards without external button.
|
||||||
|
static uint32_t last_bootsel_check = 0;
|
||||||
|
uint32_t now = to_ms_since_boot(get_absolute_time());
|
||||||
|
if (now - last_bootsel_check > 1500) {
|
||||||
|
last_bootsel_check = now;
|
||||||
|
if (bootsel_pressed()) {
|
||||||
|
uint32_t press_start = to_ms_since_boot(get_absolute_time());
|
||||||
|
while (bootsel_pressed()) {
|
||||||
|
sleep_ms(10);
|
||||||
|
}
|
||||||
|
uint32_t press_duration = to_ms_since_boot(get_absolute_time()) - press_start;
|
||||||
|
|
||||||
|
if (press_duration > 2000) {
|
||||||
|
char outstr[32];
|
||||||
|
snprintf(outstr, sizeof(outstr), "\n[+] HID Evt# %u", (unsigned)hid_event_num);
|
||||||
|
printout(outstr);
|
||||||
|
} else {
|
||||||
|
printout("\n[+] RESETTING");
|
||||||
|
swreset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // USE_BOOTSEL
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
71
src/serial_output.c
Normal file
71
src/serial_output.c
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - CDC Serial Output
|
||||||
|
*
|
||||||
|
* Wraps TinyUSB CDC for printf-style serial output.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "serial_output.h"
|
||||||
|
|
||||||
|
void serial_printf(const char* fmt, ...) {
|
||||||
|
char buf[256];
|
||||||
|
va_list args;
|
||||||
|
va_start(args, fmt);
|
||||||
|
int len = vsnprintf(buf, sizeof(buf), fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
|
||||||
|
if (len > 0) {
|
||||||
|
if (tud_cdc_connected()) {
|
||||||
|
tud_cdc_write(buf, (uint32_t)len);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_print(const char* str) {
|
||||||
|
if (tud_cdc_connected()) {
|
||||||
|
tud_cdc_write(str, (uint32_t)strlen(str));
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void serial_println(const char* str) {
|
||||||
|
serial_print(str);
|
||||||
|
serial_print("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
void hex_dump(const unsigned char* data, size_t size) {
|
||||||
|
char asciitab[17];
|
||||||
|
asciitab[16] = '\0';
|
||||||
|
|
||||||
|
for (size_t i = 0; i < size; ++i) {
|
||||||
|
serial_printf("%02X", data[i]);
|
||||||
|
|
||||||
|
if (data[i] >= ' ' && data[i] <= '~') {
|
||||||
|
asciitab[i % 16] = (char)data[i];
|
||||||
|
} else {
|
||||||
|
asciitab[i % 16] = '.';
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((i + 1) % 8 == 0 || i + 1 == size) {
|
||||||
|
serial_print(" ");
|
||||||
|
if ((i + 1) % 16 == 0) {
|
||||||
|
serial_println(asciitab);
|
||||||
|
} else if (i + 1 == size) {
|
||||||
|
asciitab[(i + 1) % 16] = '\0';
|
||||||
|
if ((i + 1) % 16 <= 8) {
|
||||||
|
serial_print(" ");
|
||||||
|
}
|
||||||
|
for (size_t j = (i + 1) % 16; j < 16; ++j) {
|
||||||
|
serial_print(" ");
|
||||||
|
}
|
||||||
|
serial_print("| ");
|
||||||
|
serial_println(asciitab);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
serial_print("\r\n");
|
||||||
|
}
|
||||||
18
src/serial_output.h
Normal file
18
src/serial_output.h
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
#ifndef SERIAL_OUTPUT_H
|
||||||
|
#define SERIAL_OUTPUT_H
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
// Printf to CDC serial
|
||||||
|
void serial_printf(const char* fmt, ...) __attribute__((format(printf, 1, 2)));
|
||||||
|
|
||||||
|
// Print string to CDC serial
|
||||||
|
void serial_print(const char* str);
|
||||||
|
|
||||||
|
// Print string + newline to CDC serial
|
||||||
|
void serial_println(const char* str);
|
||||||
|
|
||||||
|
// Hex dump to CDC serial
|
||||||
|
void hex_dump(const unsigned char* data, size_t size);
|
||||||
|
|
||||||
|
#endif // SERIAL_OUTPUT_H
|
||||||
72
src/tusb_config.h
Normal file
72
src/tusb_config.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#ifndef TUSB_CONFIG_H
|
||||||
|
#define TUSB_CONFIG_H
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Common Configuration
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
#define CFG_TUSB_OS OPT_OS_PICO
|
||||||
|
|
||||||
|
#ifndef CFG_TUSB_MEM_SECTION
|
||||||
|
#define CFG_TUSB_MEM_SECTION
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef CFG_TUSB_MEM_ALIGN
|
||||||
|
#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4)))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Device Configuration (rhport 0 = native USB)
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
#define CFG_TUD_ENABLED 1
|
||||||
|
|
||||||
|
#ifndef CFG_TUD_ENDPOINT0_SIZE
|
||||||
|
#define CFG_TUD_ENDPOINT0_SIZE 64
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Device classes
|
||||||
|
#define CFG_TUD_CDC 1
|
||||||
|
#define CFG_TUD_MSC 1
|
||||||
|
#define CFG_TUD_HID 0
|
||||||
|
#define CFG_TUD_MIDI 0
|
||||||
|
#define CFG_TUD_VENDOR 0
|
||||||
|
|
||||||
|
// CDC FIFO sizes
|
||||||
|
#define CFG_TUD_CDC_RX_BUFSIZE 256
|
||||||
|
#define CFG_TUD_CDC_TX_BUFSIZE 256
|
||||||
|
|
||||||
|
// MSC buffer size
|
||||||
|
#define CFG_TUD_MSC_EP_BUFSIZE 512
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Host Configuration (rhport 1 = PIO USB)
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
#define CFG_TUH_ENABLED 1
|
||||||
|
#define CFG_TUH_RPI_PIO_USB 1
|
||||||
|
|
||||||
|
// Host classes
|
||||||
|
#define CFG_TUH_HID 4
|
||||||
|
#define CFG_TUH_MSC 1
|
||||||
|
#define CFG_TUH_CDC 1
|
||||||
|
#define CFG_TUH_HUB 0
|
||||||
|
|
||||||
|
// Max devices
|
||||||
|
#define CFG_TUH_DEVICE_MAX 1
|
||||||
|
#define CFG_TUH_ENUMERATION_BUFSIZE 256
|
||||||
|
|
||||||
|
// HID buffer
|
||||||
|
#define CFG_TUH_HID_EPIN_BUFSIZE 64
|
||||||
|
#define CFG_TUH_HID_EPOUT_BUFSIZE 64
|
||||||
|
|
||||||
|
// CDC FIFO sizes for host
|
||||||
|
#define CFG_TUH_CDC_RX_BUFSIZE 64
|
||||||
|
#define CFG_TUH_CDC_TX_BUFSIZE 64
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // TUSB_CONFIG_H
|
||||||
62
src/usb_config.h
Normal file
62
src/usb_config.h
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
#ifndef USB_CONFIG_H
|
||||||
|
#define USB_CONFIG_H
|
||||||
|
|
||||||
|
// Project version
|
||||||
|
#define VERSION "USBvalve - 1.0.0"
|
||||||
|
|
||||||
|
// USB anti-detection settings
|
||||||
|
//
|
||||||
|
// Set USB IDs strings and numbers, to avoid possible detections.
|
||||||
|
// Remember that you can cusotmize FAKE_DISK_BLOCK_NUM as well
|
||||||
|
// for the same reason. Also DISK_LABEL in ramdisk.h can be changed.
|
||||||
|
//
|
||||||
|
// You can see here for inspiration: https://the-sz.com/products/usbid/
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// 0x0951 0x16D5 VENDORID_STR: Kingston PRODUCTID_STR: DataTraveler
|
||||||
|
//
|
||||||
|
#define USB_VENDORID 0x0951
|
||||||
|
#define USB_PRODUCTID 0x16D5
|
||||||
|
#define USB_DESCRIPTOR "DataTraveler"
|
||||||
|
#define USB_MANUF "Kingston"
|
||||||
|
#define USB_SERIAL "123456789A"
|
||||||
|
#define USB_VENDORID_STR "Kingston" // Up to 8 chars
|
||||||
|
#define USB_PRODUCTID_STR "DataTraveler" // Up to 16 chars
|
||||||
|
#define USB_VERSION_STR "1.0" // Up to 4 chars
|
||||||
|
|
||||||
|
// Disk configuration
|
||||||
|
#define DISK_BLOCK_NUM 0x150
|
||||||
|
#define FAKE_DISK_BLOCK_NUM 0x800
|
||||||
|
#define DISK_BLOCK_SIZE 0x200
|
||||||
|
|
||||||
|
// Block locations
|
||||||
|
#define BLOCK_AUTORUN 102
|
||||||
|
#define BLOCK_README 100
|
||||||
|
|
||||||
|
// Debug
|
||||||
|
#define MAX_DUMP_BYTES 16
|
||||||
|
|
||||||
|
// Hash validation
|
||||||
|
#define BYTES_TO_HASH (512 * 2)
|
||||||
|
#define BYTES_TO_HASH_OFFSET 7
|
||||||
|
#define VALID_HASH 2362816530U
|
||||||
|
|
||||||
|
// GPIO pins
|
||||||
|
#define HOST_PIN_DP 14
|
||||||
|
#define LED_PIN 25
|
||||||
|
#define BUTTON_PIN 0 // External reset button (active-low, pulled up)
|
||||||
|
|
||||||
|
// Display
|
||||||
|
#define I2C_SDA_PIN 4
|
||||||
|
#define I2C_SCL_PIN 5
|
||||||
|
#define I2C_ADDRESS 0x3C
|
||||||
|
#define OLED_WIDTH 128
|
||||||
|
// OLED_HEIGHT is set via CMake: -DOLED_HEIGHT=32 (default) or -DOLED_HEIGHT=64
|
||||||
|
#ifndef OLED_HEIGHT
|
||||||
|
#define OLED_HEIGHT 32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// HID
|
||||||
|
#define LANGUAGE_ID 0x0409
|
||||||
|
|
||||||
|
#endif // USB_CONFIG_H
|
||||||
107
src/usb_descriptors.c
Normal file
107
src/usb_descriptors.c
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - USB Descriptors for TinyUSB
|
||||||
|
*
|
||||||
|
* Composite device: CDC (serial) + MSC (mass storage)
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "usb_config.h"
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Device Descriptor
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static tusb_desc_device_t const desc_device = {
|
||||||
|
.bLength = sizeof(tusb_desc_device_t),
|
||||||
|
.bDescriptorType = TUSB_DESC_DEVICE,
|
||||||
|
.bcdUSB = 0x0200, // USB 2.0
|
||||||
|
.bDeviceClass = TUSB_CLASS_MISC,
|
||||||
|
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
|
||||||
|
.bDeviceProtocol = MISC_PROTOCOL_IAD,
|
||||||
|
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
|
||||||
|
.idVendor = USB_VENDORID,
|
||||||
|
.idProduct = USB_PRODUCTID,
|
||||||
|
.bcdDevice = 0x0100,
|
||||||
|
.iManufacturer = 0x01,
|
||||||
|
.iProduct = 0x02,
|
||||||
|
.iSerialNumber = 0x03,
|
||||||
|
.bNumConfigurations = 0x01
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t const* tud_descriptor_device_cb(void) {
|
||||||
|
return (uint8_t const*)&desc_device;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Configuration Descriptor
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
enum {
|
||||||
|
ITF_NUM_CDC = 0,
|
||||||
|
ITF_NUM_CDC_DATA,
|
||||||
|
ITF_NUM_MSC,
|
||||||
|
ITF_NUM_TOTAL
|
||||||
|
};
|
||||||
|
|
||||||
|
#define EPNUM_CDC_NOTIF 0x81
|
||||||
|
#define EPNUM_CDC_OUT 0x02
|
||||||
|
#define EPNUM_CDC_IN 0x82
|
||||||
|
#define EPNUM_MSC_OUT 0x03
|
||||||
|
#define EPNUM_MSC_IN 0x83
|
||||||
|
|
||||||
|
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_MSC_DESC_LEN)
|
||||||
|
|
||||||
|
static uint8_t const desc_configuration[] = {
|
||||||
|
// Config descriptor
|
||||||
|
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
|
||||||
|
// CDC: Interface Association + CDC Control + CDC Data
|
||||||
|
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
|
||||||
|
// MSC
|
||||||
|
TUD_MSC_DESCRIPTOR(ITF_NUM_MSC, 5, EPNUM_MSC_OUT, EPNUM_MSC_IN, 64),
|
||||||
|
};
|
||||||
|
|
||||||
|
uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
|
||||||
|
(void)index;
|
||||||
|
return desc_configuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// String Descriptors
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
static char const* string_desc_arr[] = {
|
||||||
|
[0] = (const char[]){0x09, 0x04}, // English
|
||||||
|
[1] = USB_MANUF, // Manufacturer
|
||||||
|
[2] = USB_DESCRIPTOR, // Product
|
||||||
|
[3] = USB_SERIAL, // Serial
|
||||||
|
[4] = "USBvalve CDC", // CDC Interface
|
||||||
|
[5] = "USBvalve MSC", // MSC Interface
|
||||||
|
};
|
||||||
|
|
||||||
|
static uint16_t _desc_str[33];
|
||||||
|
|
||||||
|
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid) {
|
||||||
|
(void)langid;
|
||||||
|
|
||||||
|
uint8_t chr_count;
|
||||||
|
|
||||||
|
if (index == 0) {
|
||||||
|
memcpy(&_desc_str[1], string_desc_arr[0], 2);
|
||||||
|
chr_count = 1;
|
||||||
|
} else {
|
||||||
|
if (index >= sizeof(string_desc_arr) / sizeof(string_desc_arr[0])) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* str = string_desc_arr[index];
|
||||||
|
chr_count = strlen(str);
|
||||||
|
if (chr_count > 31) chr_count = 31;
|
||||||
|
|
||||||
|
for (uint8_t i = 0; i < chr_count; i++) {
|
||||||
|
_desc_str[1 + i] = str[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// First word is length (including header) and descriptor type
|
||||||
|
_desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2);
|
||||||
|
|
||||||
|
return _desc_str;
|
||||||
|
}
|
||||||
164
src/usb_device.c
Normal file
164
src/usb_device.c
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - USB Mass Storage Device (Core 0)
|
||||||
|
*
|
||||||
|
* Implements TinyUSB MSC callbacks for a fake FAT12 disk.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "usb_config.h"
|
||||||
|
#include "usb_device.h"
|
||||||
|
#include "serial_output.h"
|
||||||
|
#include "ramdisk.h"
|
||||||
|
|
||||||
|
#include "xxhash.h"
|
||||||
|
|
||||||
|
// Shared flags (accessed from both cores via spinlock)
|
||||||
|
extern volatile bool flag_readme;
|
||||||
|
extern volatile bool flag_autorun;
|
||||||
|
extern volatile bool flag_written;
|
||||||
|
extern volatile bool flag_deleted;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Init
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
void usb_device_init(void) {
|
||||||
|
// TinyUSB device init is done via tusb_init() in main
|
||||||
|
}
|
||||||
|
|
||||||
|
bool usb_device_selftest(void) {
|
||||||
|
// Hash the FAT directory area to verify ramdisk integrity
|
||||||
|
// Add 11 bytes to skip the DISK_LABEL from the hashing
|
||||||
|
XXH32_hash_t computed = XXH32(msc_disk[BYTES_TO_HASH_OFFSET] + 11, BYTES_TO_HASH, 0);
|
||||||
|
return (computed == VALID_HASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// MSC Callbacks
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Invoked when host sends SCSI INQUIRY
|
||||||
|
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8],
|
||||||
|
uint8_t product_id[16], uint8_t product_rev[4]) {
|
||||||
|
(void)lun;
|
||||||
|
|
||||||
|
const char vid[] = USB_VENDORID_STR;
|
||||||
|
const char pid[] = USB_PRODUCTID_STR;
|
||||||
|
const char rev[] = USB_VERSION_STR;
|
||||||
|
|
||||||
|
memcpy(vendor_id, vid, strlen(vid));
|
||||||
|
memcpy(product_id, pid, strlen(pid));
|
||||||
|
memcpy(product_rev, rev, strlen(rev));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when host asks for disk capacity
|
||||||
|
void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size) {
|
||||||
|
(void)lun;
|
||||||
|
*block_count = FAKE_DISK_BLOCK_NUM;
|
||||||
|
*block_size = DISK_BLOCK_SIZE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked on READ10 command
|
||||||
|
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset,
|
||||||
|
void* buffer, uint32_t bufsize) {
|
||||||
|
(void)lun;
|
||||||
|
(void)offset;
|
||||||
|
|
||||||
|
// Check for README.TXT access
|
||||||
|
if (lba == BLOCK_README) {
|
||||||
|
flag_readme = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for AUTORUN.INF access
|
||||||
|
if (lba == BLOCK_AUTORUN) {
|
||||||
|
flag_autorun = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect memory: only read from real disk blocks
|
||||||
|
if (lba < DISK_BLOCK_NUM - 1) {
|
||||||
|
uint8_t const* addr = msc_disk[lba];
|
||||||
|
memcpy(buffer, addr, bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
serial_printf("Read LBA: %u Size: %u\r\n", (unsigned)lba, (unsigned)bufsize);
|
||||||
|
if (lba < DISK_BLOCK_NUM - 1) {
|
||||||
|
hex_dump(msc_disk[lba], MAX_DUMP_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int32_t)bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked on WRITE10 command
|
||||||
|
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset,
|
||||||
|
uint8_t* buffer, uint32_t bufsize) {
|
||||||
|
(void)lun;
|
||||||
|
(void)offset;
|
||||||
|
|
||||||
|
// Check for file deletion at Block 7
|
||||||
|
if (lba == 7) {
|
||||||
|
if (buffer[32] == 0xE5 || buffer[64] == 0xE5 || buffer[160] == 0xE5) {
|
||||||
|
flag_deleted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for file writes (LBA > 10 to avoid false positives)
|
||||||
|
if (lba > 10) {
|
||||||
|
flag_written = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Protect memory: only write to real disk blocks
|
||||||
|
if (lba < DISK_BLOCK_NUM - 1) {
|
||||||
|
uint8_t* addr = msc_disk[lba];
|
||||||
|
memcpy(addr, buffer, bufsize);
|
||||||
|
}
|
||||||
|
|
||||||
|
serial_printf("Write LBA: %u Size: %u\r\n", (unsigned)lba, (unsigned)bufsize);
|
||||||
|
if (lba < DISK_BLOCK_NUM - 1) {
|
||||||
|
hex_dump(msc_disk[lba], MAX_DUMP_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int32_t)bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when WRITE10 completes
|
||||||
|
void tud_msc_write10_complete_cb(uint8_t lun) {
|
||||||
|
(void)lun;
|
||||||
|
// Nothing to do - no persistent storage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked on SCSI commands that are not READ10/WRITE10
|
||||||
|
int32_t tud_msc_scsi_cb(uint8_t lun, uint8_t const scsi_cmd[16],
|
||||||
|
void* buffer, uint16_t bufsize) {
|
||||||
|
int32_t resplen = 0;
|
||||||
|
|
||||||
|
switch (scsi_cmd[0]) {
|
||||||
|
case SCSI_CMD_TEST_UNIT_READY:
|
||||||
|
// Disk is always ready
|
||||||
|
resplen = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SCSI_CMD_START_STOP_UNIT: {
|
||||||
|
// Handle eject
|
||||||
|
uint8_t const start = scsi_cmd[4] & 0x01;
|
||||||
|
uint8_t const loej = scsi_cmd[4] & 0x02;
|
||||||
|
if (loej && !start) {
|
||||||
|
// Eject requested - we just ignore it
|
||||||
|
}
|
||||||
|
resplen = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Unknown SCSI command - set sense data for unsupported command
|
||||||
|
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
|
||||||
|
resplen = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resplen;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unit ready check
|
||||||
|
bool tud_msc_test_unit_ready_cb(uint8_t lun) {
|
||||||
|
(void)lun;
|
||||||
|
return true; // RAM disk is always ready
|
||||||
|
}
|
||||||
12
src/usb_device.h
Normal file
12
src/usb_device.h
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
#ifndef USB_DEVICE_H
|
||||||
|
#define USB_DEVICE_H
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
|
||||||
|
// Initialize USB device (MSC + CDC)
|
||||||
|
void usb_device_init(void);
|
||||||
|
|
||||||
|
// Check ramdisk hash consistency. Returns true if valid.
|
||||||
|
bool usb_device_selftest(void);
|
||||||
|
|
||||||
|
#endif // USB_DEVICE_H
|
||||||
162
src/usb_host.c
Normal file
162
src/usb_host.c
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
/*
|
||||||
|
* USBvalve - USB Host (Core 1)
|
||||||
|
*
|
||||||
|
* PIO USB host for detecting HID/MSC/CDC devices.
|
||||||
|
* Runs entirely on Core 1.
|
||||||
|
*
|
||||||
|
* Uses tud_cdc_write() for serial output (same as working example).
|
||||||
|
* Core 0 handles display updates via flags.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include "pico/stdlib.h"
|
||||||
|
#include "pico/multicore.h"
|
||||||
|
#include "hardware/clocks.h"
|
||||||
|
#include "pio_usb.h"
|
||||||
|
#include "tusb.h"
|
||||||
|
#include "usb_config.h"
|
||||||
|
#include "usb_host.h"
|
||||||
|
#include "hid_handler.h"
|
||||||
|
|
||||||
|
// Shared flags (defined in main.c)
|
||||||
|
extern volatile bool flag_hid_sent;
|
||||||
|
extern volatile bool flag_hid_reported;
|
||||||
|
extern volatile bool flag_hid_mounted;
|
||||||
|
extern volatile bool flag_msc_mounted;
|
||||||
|
extern volatile bool flag_cdc_mounted;
|
||||||
|
extern volatile uint32_t hid_event_num;
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// Host Init
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
void usb_host_init(void) {
|
||||||
|
pio_usb_configuration_t pio_cfg = PIO_USB_DEFAULT_CONFIG;
|
||||||
|
pio_cfg.pin_dp = HOST_PIN_DP;
|
||||||
|
|
||||||
|
tuh_configure(1, TUH_CFGID_RPI_PIO_USB_CONFIGURATION, &pio_cfg);
|
||||||
|
tuh_init(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void core1_entry(void) {
|
||||||
|
sleep_ms(10);
|
||||||
|
|
||||||
|
// Allow Core 0 to temporarily pause us (for safe BOOTSEL reading).
|
||||||
|
// The lockout handler runs from RAM, so flash can be disconnected.
|
||||||
|
multicore_lockout_victim_init();
|
||||||
|
|
||||||
|
usb_host_init();
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
tuh_task();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// TinyUSB Host HID Callbacks
|
||||||
|
// Uses tud_cdc_write() for serial (same pattern as working example).
|
||||||
|
// No I2C / display calls, those happen on Core 0 via flags.
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void tuh_hid_mount_cb(uint8_t dev_addr, uint8_t instance,
|
||||||
|
uint8_t const* desc_report, uint16_t desc_len) {
|
||||||
|
(void)desc_report;
|
||||||
|
(void)desc_len;
|
||||||
|
|
||||||
|
const char* protocol_str[] = {"None", "Keyboard", "Mouse"};
|
||||||
|
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||||
|
|
||||||
|
uint16_t vid, pid;
|
||||||
|
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
||||||
|
|
||||||
|
char tempbuf[256];
|
||||||
|
int count = sprintf(tempbuf, "[%04x:%04x][%u] HID Interface%u, Protocol = %s\r\n",
|
||||||
|
vid, pid, dev_addr, instance, protocol_str[itf_protocol]);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
|
||||||
|
flag_hid_mounted = true;
|
||||||
|
|
||||||
|
// Request reports for all protocols, some keyboards report as "None"
|
||||||
|
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||||
|
tud_cdc_write_str("Error: cannot request report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
|
||||||
|
char tempbuf[64];
|
||||||
|
int count = sprintf(tempbuf, "[%u] HID Interface%u unmounted\r\n", dev_addr, instance);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
|
||||||
|
flag_hid_sent = false;
|
||||||
|
flag_hid_reported = false;
|
||||||
|
hid_event_num = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tuh_hid_report_received_cb(uint8_t dev_addr, uint8_t instance,
|
||||||
|
uint8_t const* report, uint16_t len) {
|
||||||
|
flag_hid_sent = true;
|
||||||
|
|
||||||
|
uint8_t const itf_protocol = tuh_hid_interface_protocol(dev_addr, instance);
|
||||||
|
|
||||||
|
switch (itf_protocol) {
|
||||||
|
case HID_ITF_PROTOCOL_KEYBOARD:
|
||||||
|
process_kbd_report(report, len);
|
||||||
|
hid_event_num++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case HID_ITF_PROTOCOL_MOUSE:
|
||||||
|
process_mouse_report((hid_mouse_report_t const*)report);
|
||||||
|
hid_event_num++;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Generic HID (protocol "None"): treat as keyboard
|
||||||
|
process_kbd_report(report, len);
|
||||||
|
hid_event_num++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tuh_hid_receive_report(dev_addr, instance)) {
|
||||||
|
tud_cdc_write_str("Error: cannot request report\r\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
// TinyUSB Host MSC/CDC Callbacks
|
||||||
|
//--------------------------------------------------------------------
|
||||||
|
|
||||||
|
void tuh_msc_mount_cb(uint8_t dev_addr) {
|
||||||
|
uint16_t vid, pid;
|
||||||
|
tuh_vid_pid_get(dev_addr, &vid, &pid);
|
||||||
|
|
||||||
|
char tempbuf[64];
|
||||||
|
int count = sprintf(tempbuf, "[%04x:%04x][%u] MSC mounted\r\n", vid, pid, dev_addr);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
|
||||||
|
flag_msc_mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tuh_msc_umount_cb(uint8_t dev_addr) {
|
||||||
|
char tempbuf[64];
|
||||||
|
int count = sprintf(tempbuf, "[%u] MSC unmounted\r\n", dev_addr);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tuh_cdc_mount_cb(uint8_t idx) {
|
||||||
|
char tempbuf[64];
|
||||||
|
int count = sprintf(tempbuf, "[%u] CDC mounted\r\n", idx);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
|
||||||
|
flag_cdc_mounted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void tuh_cdc_umount_cb(uint8_t idx) {
|
||||||
|
char tempbuf[64];
|
||||||
|
int count = sprintf(tempbuf, "[%u] CDC unmounted\r\n", idx);
|
||||||
|
tud_cdc_write(tempbuf, count);
|
||||||
|
tud_cdc_write_flush();
|
||||||
|
}
|
||||||
10
src/usb_host.h
Normal file
10
src/usb_host.h
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
#ifndef USB_HOST_H
|
||||||
|
#define USB_HOST_H
|
||||||
|
|
||||||
|
// Initialize USB host on Core 1 (PIO USB)
|
||||||
|
void usb_host_init(void);
|
||||||
|
|
||||||
|
// Core 1 entry point
|
||||||
|
void core1_entry(void);
|
||||||
|
|
||||||
|
#endif // USB_HOST_H
|
||||||
Reference in New Issue
Block a user