40 Commits

Author SHA1 Message Date
cecio
ae08d29897 PCB 1.3 added 2026-04-15 22:05:32 +02:00
cecio
f86712a8ed minor change in RAM disk read/write handling 2026-04-04 23:23:09 +02:00
cecio
b81e83e7e6 updated README 2026-03-30 18:23:57 +02:00
cecio
55e05a6a1e updated README 2026-03-26 21:43:41 +01:00
cecio
9bc181d14f update 2026-03-26 21:31:57 +01:00
cecio
028227edfd 1.0.0 2026-03-26 21:19:28 +01:00
cecio
8369117d73 updated README 2025-05-31 22:00:14 +02:00
cecio
13422f4c8c bump to release 0.21.1 2025-05-31 21:51:57 +02:00
cecio
c1618bc201 bump to release 0.21.0 2025-02-02 16:27:37 +01:00
cecio
d756e20eb4 new HID count and freq adjustment 2025-02-02 16:25:26 +01:00
cecio
1387103199 adjusted versions and freq 2025-02-02 16:24:30 +01:00
cecio
f4e9ed918b updates 2024-12-22 22:34:12 +01:00
cecio
c551015cd7 added comment to PIWATCH 2024-12-22 22:26:48 +01:00
cecio
08e047811a bump to release 0.20.0 2024-12-22 22:13:53 +01:00
cecio
113158f9ad Update README.md 2024-11-27 21:55:22 +01:00
cecio
251db8f30d bump to release 0.19.1 2024-10-27 22:07:03 +01:00
cecio
384684bdf4 bump to release 0.19.0 2024-09-03 13:45:11 +02:00
LiPolymer
35f4a00326 Fix Document Mistake(#50) (#51)
* Add Notification for R7&R8 of USBPipe

* Document improvements
2024-08-04 12:53:44 +02:00
cecio
59815ce63d Update README.md 2024-07-19 00:27:25 +02:00
cecio
7aee69d8e1 Update README.md 2024-07-18 21:38:13 +02:00
cecio
64f44c1a1c update version 2024-07-07 12:31:05 +02:00
cecio
7cfedce646 bump to release 0.18.2 2024-07-07 12:23:07 +02:00
cecio
f6de47e149 bump to release 0.18.1 2024-05-19 22:23:59 +02:00
cecio
4d730aecdb Update README.md 2024-05-05 22:25:51 +02:00
cecio
44e98ad924 Update README.md 2024-05-05 22:18:23 +02:00
cecio
7d218c974d commented PIWATCH define for default compilation 2024-05-05 11:54:09 +02:00
cecio
f93f2b8fcb bump to release 0.18.0 2024-05-05 11:47:41 +02:00
cecio
5ae2c31403 Update README.md 2024-04-24 01:20:34 +02:00
cecio
f96689ad6b Update README.md 2024-04-20 00:19:07 +02:00
cecio
896a9b8e26 added enclosure for 64 screen 2024-04-20 00:12:14 +02:00
cecio
c9d6a29ae9 Update README.md 2024-04-18 22:15:05 +02:00
cecio
4b87082395 Update README.md 2024-04-18 22:12:45 +02:00
LiPolymer
977296e820 Add USBpipe PCB (#33)
* Add smart_usb_pipe Gerber file

* Update PCB files and BOM

* Update README.md

* Fix README.md

* Update README.md

* Update README.md

* Translate BOM into English and add ods formart

* Replace PDF with PNG and SVG
2024-04-14 11:03:00 +02:00
cecio
6b048cfd95 bump to release 0.17.0 2024-04-11 21:25:54 +02:00
cecio
a33ffa2411 mods to README 2024-03-26 11:51:06 +01:00
cecio
9f4be5b8cd warning added to README 2024-03-26 11:37:09 +01:00
cecio
27e0285aae Update README.md 2024-03-25 21:27:47 +01:00
cecio
07a18dadd6 bump to release 0.16.0 2024-03-25 21:24:52 +01:00
cecio
805d361da8 Update README.md 2024-03-10 00:45:12 +01:00
cecio
815b0434c2 bump to release 0.15.1 2024-02-18 22:34:28 +01:00
57 changed files with 7100 additions and 733 deletions

1
.gitignore vendored
View File

@@ -52,3 +52,4 @@ Mkfile.old
dkms.conf
USBvalve_out
build

6
.gitmodules vendored Normal file
View 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
View 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)

View File

@@ -1,40 +0,0 @@
#
# To Build:
# docker build -t usbvalve/arduino-cli .
#
# To Run:
# docker run --rm --name usbvalve -v $PWD:/mnt usbvalve/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 \
&& arduino-cli lib install "Adafruit TinyUSB Library" \
&& arduino-cli lib install "ssd1306" \
&& arduino-cli lib install "Pico PIO USB" \
&& arduino-cli lib install "XxHash_arduino" \
&& arduino-cli lib install "GFX Library for Arduino" \
&& arduino-cli lib install "SSD1306Ascii"
# 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 --board-options \"usbstack=tinyusb\" --board-options \"freq=120\" --output-dir \"/mnt/USBvalve_out\" \"\$1\"" >> /app/entrypoint.sh \
&& chmod +x /app/entrypoint.sh
ENTRYPOINT ["/app/entrypoint.sh"]

59
Dockerfile.sdk Normal file
View 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"]

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 336 KiB

128
README.md
View File

@@ -1,10 +1,20 @@
<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 have 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*
<p float="left">
<img src="https://github.com/cecio/USBvalve/blob/main/pictures/versions.png" alt="The two models" width="30%" height="30%" />
<img src="https://github.com/cecio/USBvalve/blob/main/pictures/USBvalve_PIWATCH.png" alt="The Watch" width="26%" height="26%" />
<img src="https://github.com/cecio/USBvalve/blob/main/pictures/version1_2.png" alt="1.2" width="26%" height="26%" />
</p>
I'm sure that, like me, you were asked to put your USB drive in an *unknown* device...and then the doubt:
@@ -34,24 +44,24 @@ If you prefer videos, you can also have a look to my [Insomni'hack Presentation]
## USBvalve Watch
Starting from version `0.15.0` a new *Pi Pico Watch* version is supported. To compile the new version you have to uncomment the `#define PIWATCH` line at the beginning of the code or use the pre-compiled firmware provided. The hardware is a RP2040-based 1.28-inch TFT display and watch board. You can find some more info [here](https://www.raspberrypi.com/news/how-to-build-your-own-raspberry-pi-watch/).
Starting from version `0.15.0` a new *Pi Pico Watch* version is supported. To compile the new version you have to uncomment the `#define PIWATCH` line at the beginning of the code. The hardware is a RP2040-based 1.28-inch TFT display and watch board. You can find some more info [here](https://www.raspberrypi.com/news/how-to-build-your-own-raspberry-pi-watch/).
This is also fully compatible with the [Waveshare RP2040-LCD-1.28](https://www.waveshare.com/wiki/RP2040-LCD-1.28).
## Repository Structure
`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
`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
`pictures`: images and resources used in this doc
`STL`: STL files for enclosure. In `1.1` and `1.2` folders there are full enclosures (thanks to [WhistleMaster](https://github.com/WhistleMaster)). If you want something lighter to protect the LCD you can go with `USBvalve_sliding_cover.stl`.
`STL`: STL files for enclosure. In `1.1` and `1.2` folders there are full enclosures (thanks to [WhistleMaster](https://github.com/WhistleMaster)). In folders `1.2_64` and `1.2_64_simple` there are enclosures for the 128x64 screen (thanks to [rtmq0227](https://github.com/rtmq0227)). If you want something lighter to protect the LCD you can go with `USBvalve_sliding_cover.stl`.
## Build USBvalve
@@ -59,13 +69,29 @@ This is also fully compatible with the [Waveshare RP2040-LCD-1.28](https://www.w
If you want to build your own, you need:
- A Raspberry Pi Pico (or another RP2040 based board, like Arduino Nano RP2040)
- an I2C OLED screen 128x64 or 128x32
- A Raspberry Pi Pico 1 or 2 (or another RP2040 based board)
- an I2C OLED screen 128x64 or 128x32 (**SSD1306**)
- (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
### 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.
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
> 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.
Almost all the job is done directly on the board by the software, so you just need to arrange the connection with the OLED for output.
Starting from version 0.8.0 of the firmware, **USBvalve** can detect HID devices (used to detect *BADUSB*). This require an additional USB port behaving as Host. If you are not interested in this, you can use the old instructions [in docs folder](https://github.com/cecio/USBvalve/blob/main/docs/BUILDING-1.1.md) and use PCB version `1.1`. Otherwise go ahead with PCB version `1.2` (we have version for USB-A or USB-B, see folder).
@@ -124,6 +150,28 @@ The mapping is the following:
If you want to use the DEBUG functions, you can also place a header on the 3 SWD PINs at the bottom of the board.
#### With USBpipe PCB
> [!CAUTION]
> This PCB is for experienced electronic makers
> **DON'T USE IT IF YOU AREN'T SURE YOU CAN HANDLE IT!**
> [!NOTE]
> R7 and R8 aren't actually connected to anything. They are added for circuit debugging purposes.
> So they don't show up in the BOM
<img src="./pictures/USB_pipe_finished_oled.jpg" alt="USBpipe PCB" width="15%" height="15%"/>
<img src="./pictures/USB_pipe_using.jpg" alt="USBpipe" width="15%" height="15%"/>
[USBpipe](https://github.com/LiPolymer/smartUSBPipe) is a dedicated PCB for this project.
You can find everything you need in `./PCB/USBpipe/` folder.
<img src="./pictures/USB_pipe_front.png" alt="Front" width="20%" height="20%"/>
<img src="./pictures/USB_pipe_back.png" alt="Back" width="20%" height="20%"/>
### Flash Firmware
To flash the firmware, follow these steps:
@@ -140,10 +188,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 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
// Anti-Detection settings.
// 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
@@ -154,12 +202,11 @@ I grouped most of the variables you may want to modify in this section ([see Doc
// 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 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
@@ -168,21 +215,45 @@ I grouped most of the variables you may want to modify in this section ([see Doc
### 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.2.1`
- `Adafruit TinyUSB Library` version `2.4.1`, `Pico-PIO-USB` version `0.5.2`, Board `Raspberry Pi RP2040 (3.6.3)` setting Tools=>CPU Speed at `120MHz` and Tools=>USB Stack to `Adafruit TinyUSB`
- `ssd1306` OLED library version `1.8.3`
Basic recompile instructions:
```
export PICO_SDK_PATH=</path/to/pico-sdk>
git clone --recursive https://github.com/cecio/USBvalve.git
cd USBvalve
mkdir build && cd build
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.
#### 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. Enter the following commands in the main `USBvalve` folder:
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:
```
docker build -t usbvalve/arduino-cli .
docker run --rm --name usbvalve -v $PWD:/mnt usbvalve/arduino-cli /mnt/USBvalve
# 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
```
The firmware will be placed with extension `uf2` in folder `USBvalve_out`.
@@ -190,3 +261,18 @@ The firmware will be placed with extension `uf2` in folder `USBvalve_out`.
### Contribute
If you have ideas or improvements in your mind, I encourage you to open an issue so that we can improve the project together! Thanks!
### Support
If you have question or need support you can open an `Issue` here or reach me out on Twitter/X [@red5heep](https://twitter.com/red5heep)
### Community versions
The Community created some forks implementing support for other boards, or other modifications. **Thank you to everyone** who contributed to the development of **USBvalve**.
Here below an unofficial/incomplete/unsupported list:
- [USBvalve-tbfa-Mod](https://github.com/TryBreakFixAgain/USBvalve-tbfa-Mod)
## SAFETY WARNING
> [!WARNING]
> I've received a lot of questions about **USBvalve** and *USB killer devices*. **USBvalve** is not built to test these devices, it has not any kind of insulation or protection, so if you have the suspect you are dealing with one of these devices, test it with something else, NOT with **USBvalve** or you may damage the device, yourself or objects near to you.

BIN
STL/1.2_64/renclosure.stl Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -1,668 +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 "SSD1306AsciiWire.h"
#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_HEIGHT 32 // 64 or 32 depending on the OLED
#define OLED_LINES (OLED_HEIGHT / 8)
SSD1306AsciiWire display;
#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.15.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;
//
// 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() {
// 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
Wire.begin();
Wire.setClock(400000L);
#if OLED_HEIGHT == 64
#if RST_PIN >= 0
display.begin(&Adafruit128x64, I2C_ADDRESS, RST_PIN);
#else
display.begin(&Adafruit128x64, I2C_ADDRESS);
#endif
#else
#if RST_PIN >= 0
display.begin(&Adafruit128x32, I2C_ADDRESS, RST_PIN);
#else
display.begin(&Adafruit128x32, I2C_ADDRESS);
#endif
#endif
#endif
#if defined(PIWATCH)
// gfx->setTextSize(tsb);
gfx->setTextSize(1);
gfx->setTextColor(MAGENTA);
#else
display.setFont(Adafruit5x7);
display.setScrollMode(SCROLL_MODE_AUTO);
#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
}
}
}
// 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
set_sys_clock_khz(144000, true);
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 (autorun == true) {
printout("\n[+] AUTORUN (R)");
autorun = false;
}
if (deleted == true && deleted_reported == false) {
printout("\n[!] DELETING");
deleted = false;
deleted_reported = true;
}
if (written == true && written_reported == false) {
printout("\n[!] WRITING");
written = false;
written_reported = true;
}
if (hid_sent == true && hid_reported == false) {
printout("\n[!!] HID Sending data");
hid_sent = false;
hid_reported = true;
}
if (BOOTSEL) {
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);
}
}
#else
void printout(const char *str)
{
display.print(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.clear();
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) {
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");
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");
}
}
// Invoked when device with hid interface is un-mounted
void tuh_hid_umount_cb(uint8_t dev_addr, uint8_t instance) {
SerialTinyUSB.printf("HID device address = %d, instance = %d unmounted\r\n", dev_addr, instance);
// Reset HID sent flag
hid_sent = false;
hid_reported = false;
}
// 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) {
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);
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);
break;
default:
// Generic report: for the time being we use kbd for this as well
process_kbd_report((hid_keyboard_report_t const*)report);
break;
}
if (!tuh_hid_receive_report(dev_addr, instance)) {
SerialTinyUSB.println("Error: cannot request to receive report");
}
}
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

60
build_all.sh Executable file
View 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

View File

@@ -12,7 +12,7 @@
#ifndef 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, 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,

108
data/font6x8.h Normal file
View 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.

Binary file not shown.

1
lib/pico-pio-usb Submodule

Submodule lib/pico-pio-usb added at 3c1eec341a

1
lib/xxhash Submodule

Submodule lib/xxhash added at e626a72bc2

66
pico_sdk_import.cmake Normal file
View 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/USB_pipe_back.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 408 KiB

BIN
pictures/USB_pipe_front.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

BIN
pictures/USB_pipe_using.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 745 KiB

BIN
pictures/reset_button.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

BIN
pictures/version1_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

59
src/CMakeLists.txt Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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;
}

166
src/usb_device.c Normal file
View File

@@ -0,0 +1,166 @@
/*
* 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;
}
// Return data from ramdisk, or zeros for blocks beyond it.
// The disk reports FAKE_DISK_BLOCK_NUM sectors but only
// DISK_BLOCK_NUM exist in RAM — the rest must read as empty.
if (lba < DISK_BLOCK_NUM) {
memcpy(buffer, msc_disk[lba], bufsize);
} else {
memset(buffer, 0, bufsize);
}
serial_printf("Read LBA: %u Size: %u\r\n", (unsigned)lba, (unsigned)bufsize);
if (lba < DISK_BLOCK_NUM) {
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;
}
// Only write to real disk blocks; silently discard beyond ramdisk
if (lba < DISK_BLOCK_NUM) {
memcpy(msc_disk[lba], buffer, bufsize);
}
serial_printf("Write LBA: %u Size: %u\r\n", (unsigned)lba, (unsigned)bufsize);
if (lba < DISK_BLOCK_NUM) {
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
View 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
View 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
View 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