🛠 Transformer la carte en clavier USB HID avec TinyUSB
Objectives
The RP2040 integrates a USB 1.1 full-speed device AND host controller (datasheet, key features) — no external chip required. With TinyUSB (the USB stack embedded in the Pico SDK, github.com/hathach/tinyusb), your generic board can impersonate a HID keyboard: pressing the GP2 button sends the "A" key to any PC, without drivers. This is the basis for hardware shortcuts, presentation triggers, and other serial→HID bridges.
Hardware
- 1 × generic RP2040 board with the USB pin connected (the Zero exposes USB via the original connector)
- 1 × button between GP2 and GND
- Pico SDK ≥ 1.5 with the
pico_sdk_initlib + TinyUSB: copy thepico_sdk/examples/lib_pico_stdlibtemplate then addpico_sdk_initandtinyusb_board/tinyusb_deviceviapico_sdk_init+include(../../lib/tinyusb/src/tinyusb.cmake)— the simple way is to copy the official exampletinyusb/01_device_hellofrom the raspberrypi/pico-examples repository and switch to a00_hid_keyboard-style (HID) setup; TinyUSB installation docs: raspberrypi/pico-sdk lib/tinyusb
Complete Code
// hid_clavier.c — squelette HID clavier (inspiré de la pile TinyUSB)
#include "pico/stdlib.h"
#include "tusb.h"
#define BTN 2
// descripteur de rapport clavier standard (boot protocol, 8 octets)
#define REPORT_ID_KEYBOARD 1
static const uint8_t hid_desc[] = TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD));
const uint8_t desc_cfg[TUD_CONFIG_SIZE(1) + TUD_HID_DESC_SIZE(1)] = {
TUD_CONFIG_DESCRIPTOR(1, 1, 0, TUD_CONFIG_DESC_LEN, 0, 100),
TUD_HID_DESCRIPTOR(0, 0, HID_ITF_PROTOCOL_KEYBOARD, sizeof(hid_desc),
0x81, sizeof(hid_desc), 10), // EP 0x81, 10 ms
};
uint8_t const *tud_hid_descriptor_report_cb(uint8_t itf) { (void)itf; return hid_desc; }
uint16_t tud_hid_get_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t t, uint8_t *b, uint16_t len) { (void)itf;(void)report_id;(void)t;(void)b;(void)len; return 0; }
void tud_hid_set_report_cb(uint8_t itf, uint8_t report_id, hid_report_type_t t, uint8_t const *b, uint16_t len) { (void)itf;(void)report_id;(void)t;(void)b;(void)len; }
int main(void) {
gpio_init(BTN); gpio_set_dir(BTN, GPIO_IN); gpio_pull_up(BTN);
tusb_init;
uint8_t prev = 1;
while (true) {
tud_task; // fait tourner la pile USB
uint8_t now = gpio_get(BTN);
if (tuh_mounted(0) || tud_mounted) {
if (now != prev) { // front : envoyer touche 'A' puis relâcher
hid_keyboard_report_t press = {0, 0, {HID_KEY_A, 0, 0, 0, 0, 0}, 0};
hid_keyboard_report_t rel = {0, 0, {0, 0, 0, 0, 0, 0, 0}, 0};
tud_hid_report(REPORT_ID_KEYBOARD, &press, sizeof(press));
sleep_ms(10);
tud_hid_report(REPORT_ID_KEYBOARD, &rel, sizeof(rel));
prev = now;
}
}
}
}
Expected Verification
Plug in the board: the PC sees a keyboard device (no serial port, no driver). Open a text editor, press GP2: an "a" is typed. lsusb (Linux) or Device Manager (Windows) lists a HID device. This proves that the complete USB device enumeration (descriptors, endpoint 0x81, keyboard protocol) is working.
Common Errors
- Coding the application before
tud_taskruns: without regular calls to the stack, enumeration fails and the PC displays "unknown device". - Forgetting the release report: the PC sees a key held down (infinite repetition of "a") — always send press then release.
- Report descriptor inconsistent with the sent frame: Windows is strict, the device is rejected — keep the standard descriptor
TUD_HID_REPORT_DESC_KEYBOARD. - Confusing
tud_mounted(we are a device, the host opened us) andtuh_mounted(we are a host): in simple device mode, only the first one makes sense. - The board "disappears" when you also plug in the USB debug: two native USB controllers on the same PC, two enumerations — one cable is enough for testing.
- Boot protocol vs full report: some BIOSes only speak the 8-byte boot protocol — the standard descriptor above is compatible with both.
- Only 12 Mbit/s: the RP2040's USB is full-speed (12 Mbit/s max), more than enough for HID, never for a webcam.
Other tutorials for this board
- Piloter la LED RGB embarquée WS2812 d'une carte RP2040 générique
- Avoir une console série sur une carte sans pont USB (UART0)
- Jouer une mélodie avec un buzzer passif et le PWM
- Afficher sur un OLED I2C SSD1306 et déboguer le bus
- Logger sur carte microSD en SPI (mode carte)
- Interruptions GPIO et alarmes matérielles avec le Pico SDK
Code examples for this board
clignotante.pyPythonbouton-interruption.pyPythonpwm-servo.pyPythonlecture-adc.pyPythonneopixel.pyPythonoled-i2c.pyPython