3 Commits

Author SHA1 Message Date
kenji 84aa9a2e74 add Pico 2 W firmware 2025-08-18 08:09:28 -04:00
kenji cf17420973 update VID/PID/serial 2025-08-12 11:30:03 -04:00
kenji 692c5af8c9 code cleanup & fix compile with pico-sdk 2.2.0 2025-08-12 07:49:12 -04:00
9 changed files with 119 additions and 109 deletions
+7 -2
View File
@@ -1,7 +1,10 @@
cmake_minimum_required(VERSION 3.13) cmake_minimum_required(VERSION 3.13)
set(PROJECT webkeyboard) set(PROJECT webkeyboard)
set(PICO_SDK_PATH /home/kenji/programming/pico/c/pico-sdk) set(PICO_SDK_PATH /home/kenji/programming/pico/c/pico-sdk)
set(PICO_BOARD pico_w) if (NOT DEFINED PICO_BOARD)
set(PICO_BOARD pico_w)
endif()
#set(PICO_BOARD pico2_w)
include (${PICO_SDK_PATH}/external/pico_sdk_import.cmake) include (${PICO_SDK_PATH}/external/pico_sdk_import.cmake)
project(${PROJECT} C CXX ASM) project(${PROJECT} C CXX ASM)
@@ -37,7 +40,8 @@ target_sources(${PROJECT} PRIVATE
) )
pico_enable_stdio_usb(${PROJECT} 1) pico_enable_stdio_usb(${PROJECT} 1)
pico_enable_stdio_uart(${PROJECT} 1)
set_target_properties(${PROJECT} PROPERTIES OUTPUT_NAME "${PROJECT}-${PICO_BOARD}")
pico_add_extra_outputs(${PROJECT}) pico_add_extra_outputs(${PROJECT})
@@ -49,6 +53,7 @@ target_link_libraries(${PROJECT}
pico_stdlib pico_stdlib
pico_multicore pico_multicore
pico_mbedtls pico_mbedtls
tinyusb_board
tinyusb_device tinyusb_device
) )
+13 -12
View File
@@ -2,11 +2,11 @@
## About ## About
This project turns a Raspberry Pi Pico W into a web-based USB HID keyboard This project turns a Raspberry Pi Pico W or Raspberry Pi Pico 2 W into a
device. Users can access a webpage served over HTTP to enter keyboard input web-based USB HID keyboard device. Users can access a webpage served over HTTP
onto the host device of the Raspberry Pi Pico W. Input keys can be entered to enter keyboard input onto the host device of the Raspberry Pi Pico (2) W.
through a keyboard on the client device or by pressing on the keys on the Input keys can be entered through a keyboard on the client device or by
graphical interface served on the webpage. pressing on the keys on the graphical interface served on the webpage.
![Web interface for Web Keyboard](web-ui.jpg) ![Web interface for Web Keyboard](web-ui.jpg)
@@ -14,18 +14,19 @@ graphical interface served on the webpage.
[Installation and demo video](https://youtu.be/uORnxt5DLTw) [Installation and demo video](https://youtu.be/uORnxt5DLTw)
Download the webkeyboard.uf2 file from the latest Download the appropriate firmware file for your board, either
webkeyboard-pico_w.uf2 or webkeyboard-pico2_w.uf2, from the latest
[release](https://git.kkozai.com/kenji/webkeyboard/releases) and flash [release](https://git.kkozai.com/kenji/webkeyboard/releases) and flash
onto the Raspberry Pi Pico W by holding down the BOOTSEL button while plugging onto the Raspberry Pi Pico (2) W by holding down the BOOTSEL button while
into your computer so that it appears as a USB drive, then transfer the plugging into your computer so that it appears as a USB drive, then transfer the
firmware file onto the Pico W. After unmounting, connect the Raspberry Pi Pico firmware file onto the Pico (2) W. After unmounting, connect the Raspberry Pi
W with a micro USB cable to the host computer or other device. Pico (2) W with a micro USB cable to the host computer or other device.
On initial boot, the Raspberry Pi Pico W will enter Access Point mode with On initial boot, the Raspberry Pi Pico (2) W will enter Access Point mode with
SSID "picokb" and password "password". Connect to this network with another SSID "picokb" and password "password". Connect to this network with another
device and open a browser window to http://192.168.0.1. device and open a browser window to http://192.168.0.1.
Go to the "Configure W-Fi" page and enter the SSID and password to the Go to the "Configure W-Fi" page and enter the SSID and password to the
network that you would like the Raspberry Pi Pico W to connect. Optionally, network that you would like the Raspberry Pi Pico (2) W to connect. Optionally,
also enter a hostname for the device and static IP information. Hit the "Save" also enter a hostname for the device and static IP information. Hit the "Save"
button to save the network information to device flash. button to save the network information to device flash.
+3 -2
View File
@@ -1,5 +1,5 @@
#ifndef MBEDTLS_CONFIG_EXAMPLES_COMMON_H #ifndef MBEDTLS_CONFIG_H
#define MBEDTLS_CONFIG_EXAMPLES_COMMON_H #define MBEDTLS_CONFIG_H
/* Workaround for some mbedtls source files using INT_MAX without including limits.h */ /* Workaround for some mbedtls source files using INT_MAX without including limits.h */
#include <limits.h> #include <limits.h>
@@ -11,6 +11,7 @@
#define MBEDTLS_ALLOW_PRIVATE_ACCESS #define MBEDTLS_ALLOW_PRIVATE_ACCESS
#define MBEDTLS_HAVE_TIME #define MBEDTLS_HAVE_TIME
#define MBEDTLS_PLATFORM_MS_TIME_ALT
#define MBEDTLS_CIPHER_MODE_CBC #define MBEDTLS_CIPHER_MODE_CBC
#define MBEDTLS_ECP_DP_SECP192R1_ENABLED #define MBEDTLS_ECP_DP_SECP192R1_ENABLED
+2
View File
@@ -7,6 +7,8 @@
#include "parse_keys.h" #include "parse_keys.h"
#include "usb_descriptors.h" #include "usb_descriptors.h"
static unsigned char boot_buf[8];
// take a list of Javascript keys representing pressed keys and turn into // take a list of Javascript keys representing pressed keys and turn into
// a 8 byte USB boot keyboard report format // a 8 byte USB boot keyboard report format
void parse_key_list(char * keys) { void parse_key_list(char * keys) {
-2
View File
@@ -3,7 +3,6 @@
#define MOUSE_SPEED 10 #define MOUSE_SPEED 10
static unsigned char boot_buf[8];
void parse_key_list(char * keys); void parse_key_list(char * keys);
void parse_mouse_list(char * keys); void parse_mouse_list(char * keys);
@@ -12,7 +11,6 @@ uint8_t parse_mod(char * key);
typedef struct { char * key; uint8_t val; } keycode_dict; typedef struct { char * key; uint8_t val; } keycode_dict;
// modifier key bit codes // modifier key bit codes
#define KEY_MOD_LCTRL 0x01 #define KEY_MOD_LCTRL 0x01
#define KEY_MOD_LSHIFT 0x02 #define KEY_MOD_LSHIFT 0x02
+17 -32
View File
@@ -25,20 +25,10 @@
*/ */
#include "tusb.h" #include "tusb.h"
#include "bsp/board_api.h"
#include "usb_descriptors.h" #include "usb_descriptors.h"
/* A combination of interfaces must have a unique product id, since PC will save device driver after the first plug.
* Same VID/PID with different interface e.g MSC (first), then CDC (later) will possibly cause system error on PC.
*
* Auto ProductID layout's Bitmap:
* [MSB] HID | MSC | CDC [LSB]
*/
#define _PID_MAP(itf, n) ( (CFG_TUD_##itf) << (n) )
#define USB_PID (0x4000 | _PID_MAP(CDC, 0) | _PID_MAP(MSC, 1) | _PID_MAP(HID, 2) | \
_PID_MAP(MIDI, 3) | _PID_MAP(VENDOR, 4) )
#define USB_VID 0xCafe
#define USB_BCD 0x0200
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
// Device Descriptors // Device Descriptors
@@ -80,13 +70,6 @@ uint8_t const * tud_descriptor_device_cb(void)
// Configuration Descriptor // Configuration Descriptor
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
#define EPNUM_CDC_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82
#define EPNUM_HID 0x83
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_DESC_LEN)
uint8_t const desc_hid_report[] = uint8_t const desc_hid_report[] =
{ {
TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD)), TUD_HID_REPORT_DESC_KEYBOARD( HID_REPORT_ID(REPORT_ID_KEYBOARD)),
@@ -103,7 +86,7 @@ uint8_t const desc_fs_configuration[] =
// Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval // Interface number, string index, protocol, report descriptor len, EP In address, size & polling interval
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64),
TUD_HID_DESCRIPTOR(ITF_NUM_HID, 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 5), TUD_HID_DESCRIPTOR(ITF_NUM_HID, 5, HID_ITF_PROTOCOL_NONE, sizeof(desc_hid_report), EPNUM_HID, CFG_TUD_HID_EP_BUFSIZE, 8),
}; };
// Invoked when received GET CONFIGURATION DESCRIPTOR // Invoked when received GET CONFIGURATION DESCRIPTOR
@@ -126,12 +109,12 @@ char const* string_desc_arr [] =
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409) (const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"Raspberry Pi", // 1: Manufacturer "Raspberry Pi", // 1: Manufacturer
"Pico Web Keyboard", // 2: Product "Pico Web Keyboard", // 2: Product
"1234567890123456789", // 3: Serials, should use chip ID NULL, // 3: Serials, should use chip ID
"Pico Web Keyboard CDC", // 4: CDC Interface "Pico Web Keyboard CDC", // 4: CDC
"Pico Web Keyboard HID", // 5: HID Keyboard Interface "Pico Web Keyboard HID", // 5: HID
}; };
static uint16_t _desc_str[32]; static uint16_t _desc_str[32+1];
// Invoked when received GET STRING DESCRIPTOR request // Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to complete // Application return pointer to descriptor, whose contents must exist long enough for transfer to complete
@@ -141,28 +124,31 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
uint8_t chr_count; uint8_t chr_count;
if ( index == 0) switch (index) {
{ case 0: // langid
memcpy(&_desc_str[1], string_desc_arr[0], 2); memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1; chr_count = 1;
}else break;
{ case 3: // serial
chr_count = board_usb_get_serial(_desc_str+1, 32);
break;
default:
// Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors.
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors
if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL; if ( !(index < sizeof(string_desc_arr)/sizeof(string_desc_arr[0])) ) return NULL;
const char* str = string_desc_arr[index]; char* str = string_desc_arr[index];
// Cap at max char // Cap at max char
chr_count = (uint8_t) strlen(str); chr_count = (uint8_t) strlen(str);
if ( chr_count > 31 ) chr_count = 31; if ( chr_count > 31 ) chr_count = 31;
// Convert ASCII string into UTF-16 // Convert ASCII string into UTF-16
for(uint8_t i=0; i<chr_count; i++) for(uint8_t i=0; i<chr_count; i++) {
{
_desc_str[1+i] = str[i]; _desc_str[1+i] = str[i];
} }
break;
} }
// first byte is length (including header), second byte is string type // first byte is length (including header), second byte is string type
@@ -171,7 +157,6 @@ uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid)
return _desc_str; return _desc_str;
} }
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
// Device HID // Device HID
//--------------------------------------------------------------------+ //--------------------------------------------------------------------+
+10
View File
@@ -41,5 +41,15 @@ enum
ITF_NUM_TOTAL ITF_NUM_TOTAL
}; };
#define USB_PID 0x0EBD
#define USB_VID 0xCEC0
#define USB_BCD 0x0200
#define EPNUM_CDC_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82
#define EPNUM_HID 0x83
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_DESC_LEN)
#endif /* USB_DESCRIPTORS_H_ */ #endif /* USB_DESCRIPTORS_H_ */
+23 -1
View File
@@ -22,6 +22,21 @@ static tWSOpenHandler ws_open_cb = NULL;
static struct ws_state * ws_connections; static struct ws_state * ws_connections;
static uint8_t ws_num_conns = 0; static uint8_t ws_num_conns = 0;
static struct ws_state* ws_state_alloc(void);
static void ws_state_init(struct ws_state *wss);
static void ws_state_free(struct ws_state *wss);
static void ws_server_init_pcb( struct altcp_pcb *pcb, uint16_t port);
static err_t ws_accept(void *arg, struct altcp_pcb *pcb, err_t err);
static err_t ws_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t ws_sent(void *arg, struct altcp_pcb *pcb, uint16_t len);
static void ws_err (void *arg, err_t err);
static err_t ws_close_conn(struct altcp_pcb *pcb, struct ws_state *wss);
static err_t ws_close_or_abort_conn(struct altcp_pcb *pcb, struct ws_state *wss, uint8_t abort_conn);
static err_t ws_poll(void *arg, struct altcp_pcb *pcb);
static err_t ws_handshake(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p);
static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p);
static err_t ws_send(struct ws_state *wss, uint8_t *data, uint16_t len);
// allocate memory for ws_state instance // allocate memory for ws_state instance
static struct ws_state * ws_state_alloc(void) { static struct ws_state * ws_state_alloc(void) {
struct ws_state *ret = WS_ALLOC_WS_STATE(); struct ws_state *ret = WS_ALLOC_WS_STATE();
@@ -338,6 +353,7 @@ static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p
uint8_t masked = data[1] & 0x80; uint8_t masked = data[1] & 0x80;
uint16_t msg_len = data[1] & 0x7F; uint16_t msg_len = data[1] & 0x7F;
uint8_t *msg; uint8_t *msg;
switch (msg_len) { switch (msg_len) {
case 126: // next two bytes are length case 126: // next two bytes are length
memcpy(&msg_len, &data[2], 2); memcpy(&msg_len, &data[2], 2);
@@ -347,7 +363,8 @@ static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p
break; break;
case 127: // next four bytes are length case 127: // next four bytes are length
// lwIP's pbuf only handles 16-bit lengths, so error // lwIP's pbuf only handles 16-bit lengths, so error
return ERR_ARG; LWIP_DEBUGF(WS_DEBUG, ("ws_read: received 64-bit length %u\n", msg_len));
return ERR_MEM;
//memcpy(&msg_len, &data[2], 4); //memcpy(&msg_len, &data[2], 4);
//if (len >= 10) { //if (len >= 10) {
@@ -387,6 +404,11 @@ static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p
memset(buf, 0x00, sizeof(buf)); memset(buf, 0x00, sizeof(buf));
} }
if (buf_len + msg_len > WS_BUFFER_SIZE) {
LWIP_DEBUGF(WS_DEBUG, ("ws_read: message exceeds buffer size %u+%u\n", buf_len, msg_len));
return ERR_MEM;
}
memcpy(&buf[buf_len], msg, msg_len); memcpy(&buf[buf_len], msg, msg_len);
buf_len += msg_len; buf_len += msg_len;
-14
View File
@@ -28,21 +28,7 @@ struct ws_state {
typedef void (* tWSHandler ) (uint8_t *data, uint16_t len); typedef void (* tWSHandler ) (uint8_t *data, uint16_t len);
typedef void (* tWSOpenHandler ) (struct ws_state * wss); typedef void (* tWSOpenHandler ) (struct ws_state * wss);
static struct ws_state* ws_state_alloc(void);
static void ws_state_init(struct ws_state *wss);
static void ws_state_free(struct ws_state *wss);
void ws_server_init(void); void ws_server_init(void);
static void ws_server_init_pcb( struct altcp_pcb *pcb, uint16_t port);
static err_t ws_accept(void *arg, struct altcp_pcb *pcb, err_t err);
static err_t ws_recv(void *arg, struct altcp_pcb *pcb, struct pbuf *p, err_t err);
static err_t ws_sent(void *arg, struct altcp_pcb *pcb, uint16_t len);
static void ws_err (void *arg, err_t err);
static err_t ws_close_conn(struct altcp_pcb *pcb, struct ws_state *wss);
static err_t ws_close_or_abort_conn(struct altcp_pcb *pcb, struct ws_state *wss, uint8_t abort_conn);
static err_t ws_poll(void *arg, struct altcp_pcb *pcb);
static err_t ws_handshake(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p);
static err_t ws_read(struct altcp_pcb *pcb, struct ws_state *wss, struct pbuf *p);
static err_t ws_send(struct ws_state *wss, uint8_t *data, uint16_t len);
void ws_send_all(uint8_t *data, uint16_t len); void ws_send_all(uint8_t *data, uint16_t len);
void ws_set_receive_handler( tWSHandler ws_handler); void ws_set_receive_handler( tWSHandler ws_handler);
void ws_set_open_handler( tWSOpenHandler ws_handler); void ws_set_open_handler( tWSOpenHandler ws_handler);