Initial Commit

This commit is contained in:
Hrvoje Cavrak 2023-12-24 23:22:05 +01:00
parent 5896e50802
commit c10f971038
60 changed files with 52447 additions and 0 deletions

81
CMakeLists.txt Normal file
View File

@ -0,0 +1,81 @@
cmake_minimum_required(VERSION 3.13)
set(PICO_SDK_FETCH_FROM_GIT off)
set(PICO_BOARD=pico)
include(pico_sdk_import.cmake)
set(CMAKE_CXX_FLAGS "-Ofast -Wall -mcpu=cortex-m0plus -mtune=cortex-m0plus")
project(deskhop_project C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
pico_sdk_init()
set(PICO_PIO_USB_DIR ${CMAKE_CURRENT_LIST_DIR}/Pico-PIO-USB)
add_library(Pico-PIO-USB STATIC
${PICO_PIO_USB_DIR}/src/pio_usb.c
${PICO_PIO_USB_DIR}/src/pio_usb_host.c
${PICO_PIO_USB_DIR}/src/usb_crc.c
)
pico_generate_pio_header(Pico-PIO-USB ${PICO_PIO_USB_DIR}/src/usb_tx.pio)
pico_generate_pio_header(Pico-PIO-USB ${PICO_PIO_USB_DIR}/src/usb_rx.pio)
target_link_libraries(Pico-PIO-USB PRIVATE
pico_stdlib
pico_multicore
hardware_pio
hardware_dma
)
target_include_directories(Pico-PIO-USB PRIVATE ${PICO_PIO_USB_DIR})
set(COMMON_SOURCES
${CMAKE_CURRENT_LIST_DIR}/src/usb_descriptors.c
${CMAKE_CURRENT_LIST_DIR}/src/utils.c
${CMAKE_CURRENT_LIST_DIR}/src/handlers.c
${CMAKE_CURRENT_LIST_DIR}/src/setup.c
${CMAKE_CURRENT_LIST_DIR}/src/keyboard.c
${CMAKE_CURRENT_LIST_DIR}/src/mouse.c
${CMAKE_CURRENT_LIST_DIR}/src/led.c
${CMAKE_CURRENT_LIST_DIR}/src/uart.c
${CMAKE_CURRENT_LIST_DIR}/src/usb.c
${CMAKE_CURRENT_LIST_DIR}/src/main.c
)
set(COMMON_INCLUDES
${CMAKE_CURRENT_LIST_DIR}/src
${PICO_PIO_USB_DIR}/src
)
set(COMMON_LINK_LIBRARIES
pico_stdlib
hardware_uart
hardware_gpio
tinyusb_device
pico_multicore
Pico-PIO-USB
)
# Pico A - Keyboard
add_executable(board_A)
target_sources(board_A PUBLIC ${COMMON_SOURCES})
target_compile_definitions(board_A PRIVATE BOARD_ROLE=0)
target_include_directories(board_A PUBLIC ${COMMON_INCLUDES})
target_link_libraries(board_A PUBLIC ${COMMON_LINK_LIBRARIES})
pico_enable_stdio_usb(board_A 0)
pico_add_extra_outputs(board_A)
# Pico B - Mouse
add_executable(board_B)
target_compile_definitions(board_B PRIVATE BOARD_ROLE=1)
target_sources(board_B PUBLIC ${COMMON_SOURCES})
target_include_directories(board_B PUBLIC ${COMMON_INCLUDES})
target_link_libraries(board_B PUBLIC ${COMMON_LINK_LIBRARIES})
pico_enable_stdio_usb(board_B 0)
pico_add_extra_outputs(board_B)

View File

@ -0,0 +1,26 @@
set(lib_name pico_pio_usb)
add_library(${lib_name} INTERFACE)
set(dir ${CMAKE_CURRENT_LIST_DIR}/src)
pico_generate_pio_header(${lib_name} ${dir}/usb_tx.pio)
pico_generate_pio_header(${lib_name} ${dir}/usb_rx.pio)
target_sources(${lib_name} INTERFACE
${dir}/pio_usb.c
${dir}/pio_usb_device.c
${dir}/pio_usb_host.c
${dir}/usb_crc.c
)
target_link_libraries(${lib_name} INTERFACE
pico_stdlib
pico_multicore
hardware_pio
hardware_dma
)
target_include_directories(${lib_name} INTERFACE ${dir})
# enable all warnings
target_compile_options(${lib_name} INTERFACE -Wall -Wextra)

475
Pico-PIO-USB/src/pio_usb.c Normal file
View File

@ -0,0 +1,475 @@
/**
* Copyright (c) 2021 sekigon-gonnoc
*/
#pragma GCC push_options
#pragma GCC optimize("-O3")
#include <stdio.h>
#include <stdint.h>
#include <string.h> // memcpy
#include "hardware/clocks.h"
#include "hardware/dma.h"
#include "hardware/pio.h"
#include "hardware/sync.h"
#include "pico/bootrom.h"
#include "pico/stdlib.h"
#include "pio_usb.h"
#include "usb_definitions.h"
#include "pio_usb_ll.h"
#include "usb_crc.h"
#include "usb_tx.pio.h"
#include "usb_rx.pio.h"
#define UNUSED_PARAMETER(x) (void)x
usb_device_t pio_usb_device[PIO_USB_DEVICE_CNT];
pio_port_t pio_port[1];
root_port_t pio_usb_root_port[PIO_USB_ROOT_PORT_CNT];
endpoint_t pio_usb_ep_pool[PIO_USB_EP_POOL_CNT];
//--------------------------------------------------------------------+
// Bus functions
//--------------------------------------------------------------------+
static void __no_inline_not_in_flash_func(send_pre)(const pio_port_t *pp) {
uint8_t data[] = {USB_SYNC, USB_PID_PRE};
// send PRE token in full-speed
pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, false);
for (uint i = 0; i < USB_TX_EOP_DISABLER_LEN; ++i) {
uint16_t instr = pp->fs_tx_pre_program->instructions[i + USB_TX_EOP_OFFSET];
pp->pio_usb_tx->instr_mem[pp->offset_tx + i + USB_TX_EOP_OFFSET] = instr;
}
SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_fs_tx);
dma_channel_transfer_from_buffer_now(pp->tx_ch, data, 2);
pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, true);
pp->pio_usb_tx->irq |= IRQ_TX_ALL_MASK; // clear complete flag
pp->pio_usb_tx->irq_force |= IRQ_TX_EOP_MASK; // disable eop
while ((pp->pio_usb_tx->irq & IRQ_TX_COMP_MASK) == 0) {
continue;
}
// change bus speed to low-speed
pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, false);
for (uint i = 0; i < USB_TX_EOP_DISABLER_LEN; ++i) {
uint16_t instr = pp->fs_tx_program->instructions[i + USB_TX_EOP_OFFSET];
pp->pio_usb_tx->instr_mem[pp->offset_tx + i + USB_TX_EOP_OFFSET] = instr;
}
SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_ls_tx);
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
SM_SET_CLKDIV_MAXSPEED(pp->pio_usb_rx, pp->sm_rx);
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_eop, false);
SM_SET_CLKDIV(pp->pio_usb_rx, pp->sm_eop, pp->clk_div_ls_rx);
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_eop, true);
}
void __not_in_flash_func(pio_usb_bus_usb_transfer)(const pio_port_t *pp,
uint8_t *data, uint16_t len) {
if (pp->need_pre) {
send_pre(pp);
}
dma_channel_transfer_from_buffer_now(pp->tx_ch, data, len);
pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, true);
pp->pio_usb_tx->irq |= IRQ_TX_ALL_MASK; // clear complete flag
while ((pp->pio_usb_tx->irq & IRQ_TX_ALL_MASK) == 0) {
continue;
}
}
void __no_inline_not_in_flash_func(pio_usb_bus_send_handshake)(
const pio_port_t *pp, uint8_t pid) {
uint8_t data[] = {USB_SYNC, pid};
pio_usb_bus_usb_transfer(pp, data, sizeof(data));
}
void __no_inline_not_in_flash_func(pio_usb_bus_send_token)(const pio_port_t *pp,
uint8_t token,
uint8_t addr,
uint8_t ep_num) {
uint8_t packet[4] = {USB_SYNC, token, 0, 0};
uint16_t dat = ((uint16_t)(ep_num & 0xf) << 7) | (addr & 0x7f);
uint8_t crc = calc_usb_crc5(dat);
packet[2] = dat & 0xff;
packet[3] = (crc << 3) | ((dat >> 8) & 0x1f);
pio_usb_bus_usb_transfer(pp, packet, sizeof(packet));
}
void __no_inline_not_in_flash_func(pio_usb_bus_prepare_receive)(const pio_port_t *pp) {
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx);
pio_sm_restart(pp->pio_usb_rx, pp->sm_rx);
pio_sm_exec(pp->pio_usb_rx, pp->sm_rx, pp->rx_reset_instr);
pio_sm_exec(pp->pio_usb_rx, pp->sm_rx, pp->rx_reset_instr2);
}
void __no_inline_not_in_flash_func(pio_usb_bus_start_receive)(const pio_port_t *pp) {
pp->pio_usb_rx->ctrl |= (1 << pp->sm_rx);
pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK;
}
uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp) {
int16_t t = 240;
int16_t idx = 0;
while (t--) {
if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) {
uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24;
pp->usb_rx_buffer[idx++] = data;
if (idx == 2) {
break;
}
}
}
if (t > 0) {
while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
continue;
}
}
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
return pp->usb_rx_buffer[1];
}
int __no_inline_not_in_flash_func(pio_usb_bus_receive_packet_and_handshake)(
pio_port_t *pp, uint8_t handshake) {
uint16_t crc = 0xffff;
uint16_t crc_prev = 0xffff;
uint16_t crc_prev2 = 0xffff;
uint16_t crc_receive = 0xffff;
uint16_t crc_receive_inverse;
bool crc_match = false;
int16_t t = 240;
uint16_t idx = 0;
while (t--) {
if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) {
uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24;
pp->usb_rx_buffer[idx++] = data;
if (idx == 2) {
break;
}
}
}
// timing critical start
if (t > 0) {
if (handshake == USB_PID_ACK) {
while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) {
uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24;
crc_prev2 = crc_prev;
crc_prev = crc;
crc = update_usb_crc16(crc, data);
pp->usb_rx_buffer[idx++] = data;
crc_receive = (crc_receive >> 8) | (data << 8);
crc_receive_inverse = crc_receive ^ 0xffff;
crc_match = (crc_receive_inverse == crc_prev2);
}
}
if (idx >= 4 && crc_match) {
pio_usb_bus_send_handshake(pp, USB_PID_ACK);
// timing critical end
return idx - 4;
}
} else {
// just discard received data since we NAK/STALL anyway
while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
continue;
}
pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx);
pio_usb_bus_send_handshake(pp, handshake);
}
}
return -1;
}
static __always_inline void add_pio_host_rx_program(PIO pio,
const pio_program_t *program,
const pio_program_t *debug_program,
uint *offset, int debug_pin) {
if (debug_pin < 0) {
*offset = pio_add_program(pio, program);
} else {
*offset = pio_add_program(pio, debug_program);
}
}
static void __no_inline_not_in_flash_func(initialize_host_programs)(
pio_port_t *pp, const pio_usb_configuration_t *c, root_port_t *port) {
pp->offset_tx = pio_add_program(pp->pio_usb_tx, pp->fs_tx_program);
usb_tx_fs_program_init(pp->pio_usb_tx, pp->sm_tx, pp->offset_tx,
port->pin_dp, port->pin_dm);
add_pio_host_rx_program(pp->pio_usb_rx, &usb_nrzi_decoder_program,
&usb_nrzi_decoder_debug_program, &pp->offset_rx,
c->debug_pin_rx);
usb_rx_fs_program_init(pp->pio_usb_rx, pp->sm_rx, pp->offset_rx, port->pin_dp,
port->pin_dm, c->debug_pin_rx);
pp->rx_reset_instr = pio_encode_jmp(pp->offset_rx);
pp->rx_reset_instr2 = pio_encode_set(pio_x, 0);
add_pio_host_rx_program(pp->pio_usb_rx, &usb_edge_detector_program,
&usb_edge_detector_debug_program, &pp->offset_eop,
c->debug_pin_eop);
eop_detect_fs_program_init(pp->pio_usb_rx, c->sm_eop, pp->offset_eop,
port->pin_dp, port->pin_dm, true,
c->debug_pin_eop);
usb_tx_configure_pins(pp->pio_usb_tx, pp->sm_tx, port->pin_dp, port->pin_dm);
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_rx, port->pin_dp);
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_eop, port->pin_dm);
pio_sm_set_in_pins(pp->pio_usb_rx, pp->sm_eop, port->pin_dp);
}
static void configure_tx_channel(uint8_t ch, PIO pio, uint sm) {
dma_channel_config conf = dma_channel_get_default_config(ch);
channel_config_set_read_increment(&conf, true);
channel_config_set_write_increment(&conf, false);
channel_config_set_transfer_data_size(&conf, DMA_SIZE_8);
channel_config_set_dreq(&conf, pio_get_dreq(pio, sm, true));
dma_channel_set_config(ch, &conf, false);
dma_channel_set_write_addr(ch, &pio->txf[sm], false);
}
static void apply_config(pio_port_t *pp, const pio_usb_configuration_t *c,
root_port_t *port) {
pp->pio_usb_tx = c->pio_tx_num == 0 ? pio0 : pio1;
pp->sm_tx = c->sm_tx;
pp->tx_ch = c->tx_ch;
pp->pio_usb_rx = c->pio_rx_num == 0 ? pio0 : pio1;
pp->sm_rx = c->sm_rx;
pp->sm_eop = c->sm_eop;
port->pin_dp = c->pin_dp;
if (c->pinout == PIO_USB_PINOUT_DPDM) {
port->pin_dm = c->pin_dp + 1;
pp->fs_tx_program = &usb_tx_dpdm_program;
pp->fs_tx_pre_program = &usb_tx_pre_dpdm_program;
pp->ls_tx_program = &usb_tx_dmdp_program;
} else {
port->pin_dm = c->pin_dp - 1;
pp->fs_tx_program = &usb_tx_dmdp_program;
pp->fs_tx_pre_program = &usb_tx_pre_dmdp_program;
pp->ls_tx_program = &usb_tx_dpdm_program;
}
pp->debug_pin_rx = c->debug_pin_rx;
pp->debug_pin_eop = c->debug_pin_eop;
pio_sm_claim(pp->pio_usb_tx, pp->sm_tx);
pio_sm_claim(pp->pio_usb_rx, pp->sm_rx);
pio_sm_claim(pp->pio_usb_rx, pp->sm_eop);
}
static void port_pin_drive_setting(const root_port_t *port) {
gpio_set_slew_rate(port->pin_dp, GPIO_SLEW_RATE_FAST);
gpio_set_slew_rate(port->pin_dm, GPIO_SLEW_RATE_FAST);
gpio_set_drive_strength(port->pin_dp, GPIO_DRIVE_STRENGTH_12MA);
gpio_set_drive_strength(port->pin_dm, GPIO_DRIVE_STRENGTH_12MA);
}
void pio_usb_bus_init(pio_port_t *pp, const pio_usb_configuration_t *c,
root_port_t *root) {
memset(root, 0, sizeof(root_port_t));
pp->pio_usb_tx = c->pio_tx_num == 0 ? pio0 : pio1;
dma_claim_mask(1<<c->tx_ch);
configure_tx_channel(c->tx_ch, pp->pio_usb_tx, c->sm_tx);
apply_config(pp, c, root);
initialize_host_programs(pp, c, root);
port_pin_drive_setting(root);
root->initialized = true;
root->dev_addr = 0;
}
//--------------------------------------------------------------------+
// Application API
//--------------------------------------------------------------------+
endpoint_t *pio_usb_get_endpoint(usb_device_t *device, uint8_t idx) {
uint8_t ep_id = device->endpoint_id[idx];
if (ep_id == 0) {
return NULL;
} else if (ep_id >= 1) {
return &pio_usb_ep_pool[ep_id - 1];
}
return NULL;
}
int __no_inline_not_in_flash_func(pio_usb_get_in_data)(endpoint_t *ep,
uint8_t *buffer,
uint8_t len) {
if (ep->has_transfer || ep->is_tx) {
return -1;
}
if (ep->new_data_flag) {
len = len < ep->actual_len ? len : ep->actual_len;
memcpy(buffer, (void *)ep->buffer, len);
ep->new_data_flag = false;
return pio_usb_ll_transfer_start(ep, ep->buffer, ep->size) ? len : -1;
}
return -1;
}
int __no_inline_not_in_flash_func(pio_usb_set_out_data)(endpoint_t *ep,
const uint8_t *buffer,
uint8_t len) {
if (ep->has_transfer || !ep->is_tx) {
return -1;
}
return pio_usb_ll_transfer_start(ep, (uint8_t *)buffer, len) ? 0 : -1;
}
//--------------------------------------------------------------------+
// Low Level Function
//--------------------------------------------------------------------+
void __no_inline_not_in_flash_func(pio_usb_ll_configure_endpoint)(
endpoint_t *ep, uint8_t const *desc_endpoint) {
const endpoint_descriptor_t *d = (const endpoint_descriptor_t *)desc_endpoint;
ep->size = d->max_size[0] | (d->max_size[1] << 8);
ep->ep_num = d->epaddr;
ep->attr = d->attr;
ep->interval = d->interval;
ep->interval_counter = 0;
ep->data_id = 0;
}
static inline __force_inline void prepare_tx_data(endpoint_t *ep) {
uint16_t const xact_len = pio_usb_ll_get_transaction_len(ep);
ep->buffer[0] = USB_SYNC;
ep->buffer[1] = (ep->data_id == 1)
? USB_PID_DATA1
: USB_PID_DATA0; // USB_PID_SETUP also DATA0
memcpy(ep->buffer + 2, ep->app_buf, xact_len);
uint16_t const crc16 = calc_usb_crc16(ep->app_buf, xact_len);
ep->buffer[2 + xact_len] = crc16 & 0xff;
ep->buffer[2 + xact_len + 1] = crc16 >> 8;
}
bool __no_inline_not_in_flash_func(pio_usb_ll_transfer_start)(endpoint_t *ep,
uint8_t *buffer,
uint16_t buflen) {
if (ep->has_transfer) {
return false;
}
ep->app_buf = buffer;
ep->total_len = buflen;
ep->actual_len = 0;
if (ep->is_tx) {
prepare_tx_data(ep);
} else {
ep->new_data_flag = false;
}
ep->transfer_started = false;
ep->transfer_aborted = false;
ep->has_transfer = true;
return true;
}
bool __no_inline_not_in_flash_func(pio_usb_ll_transfer_continue)(
endpoint_t *ep, uint16_t xferred_bytes) {
ep->app_buf += xferred_bytes;
ep->actual_len += xferred_bytes;
ep->data_id ^= 1;
if ((xferred_bytes < ep->size) || (ep->actual_len >= ep->total_len)) {
// complete if all bytes transferred or short packet
pio_usb_ll_transfer_complete(ep, PIO_USB_INTS_ENDPOINT_COMPLETE_BITS);
return false;
} else {
if (ep->is_tx) {
prepare_tx_data(ep);
}
return true;
}
}
void __no_inline_not_in_flash_func(pio_usb_ll_transfer_complete)(
endpoint_t *ep, uint32_t flag) {
root_port_t *rport = PIO_USB_ROOT_PORT(ep->root_idx);
uint32_t const ep_mask = (1u << (ep - pio_usb_ep_pool));
rport->ints |= flag;
if (flag == PIO_USB_INTS_ENDPOINT_COMPLETE_BITS) {
rport->ep_complete |= ep_mask;
if (!ep->is_tx) {
ep->new_data_flag = true;
}
} else if (flag == PIO_USB_INTS_ENDPOINT_ERROR_BITS) {
rport->ep_error |= ep_mask;
} else if (flag == PIO_USB_INTS_ENDPOINT_STALLED_BITS) {
rport->ep_stalled |= ep_mask;
} else {
// something wrong
}
ep->has_transfer = false;
}
int pio_usb_host_add_port(uint8_t pin_dp, PIO_USB_PINOUT pinout) {
for (int idx = 0; idx < PIO_USB_ROOT_PORT_CNT; idx++) {
root_port_t *root = PIO_USB_ROOT_PORT(idx);
if (!root->initialized) {
root->pin_dp = pin_dp;
if (pinout == PIO_USB_PINOUT_DPDM) {
root->pin_dm = pin_dp + 1;
} else {
root->pin_dm = pin_dp - 1;
}
gpio_pull_down(pin_dp);
gpio_pull_down(root->pin_dm);
pio_gpio_init(pio_port[0].pio_usb_tx, pin_dp);
pio_gpio_init(pio_port[0].pio_usb_tx, root->pin_dm);
gpio_set_inover(pin_dp, GPIO_OVERRIDE_INVERT);
gpio_set_inover(root->pin_dm, GPIO_OVERRIDE_INVERT);
pio_sm_set_pindirs_with_mask(pio_port[0].pio_usb_tx, pio_port[0].sm_tx, 0,
(1 << pin_dp) | (1 << root->pin_dm));
port_pin_drive_setting(root);
root->initialized = true;
return 0;
}
}
return -1;
}
#pragma GCC pop_options

View File

@ -0,0 +1,37 @@
#pragma once
#include "pio_usb_configuration.h"
#include "usb_definitions.h"
#ifdef __cplusplus
extern "C" {
#endif
// Host functions
usb_device_t *pio_usb_host_init(const pio_usb_configuration_t *c);
int pio_usb_host_add_port(uint8_t pin_dp, PIO_USB_PINOUT pinout);
void pio_usb_host_task(void);
void pio_usb_host_stop(void);
void pio_usb_host_restart(void);
uint32_t pio_usb_host_get_frame_number(void);
// Call this every 1ms when skip_alarm_pool is true.
void pio_usb_host_frame(void);
// Device functions
usb_device_t *pio_usb_device_init(const pio_usb_configuration_t *c,
const usb_descriptor_buffers_t *buffers);
void pio_usb_device_task(void);
// Common functions
endpoint_t *pio_usb_get_endpoint(usb_device_t *device, uint8_t idx);
int pio_usb_get_in_data(endpoint_t *ep, uint8_t *buffer, uint8_t len);
int pio_usb_set_out_data(endpoint_t *ep, const uint8_t *buffer, uint8_t len);
// Misc functions
int pio_usb_kbd_set_leds(usb_device_t *device, uint8_t port, uint8_t value);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,52 @@
#pragma once
typedef enum {
PIO_USB_PINOUT_DPDM = 0, // DM = DP+1
PIO_USB_PINOUT_DMDP, // DM = DP-1
} PIO_USB_PINOUT;
typedef struct {
uint8_t pin_dp;
uint8_t pio_tx_num;
uint8_t sm_tx;
uint8_t tx_ch;
uint8_t pio_rx_num;
uint8_t sm_rx;
uint8_t sm_eop;
void* alarm_pool;
int8_t debug_pin_rx;
int8_t debug_pin_eop;
bool skip_alarm_pool;
PIO_USB_PINOUT pinout;
} pio_usb_configuration_t;
#ifndef PIO_USB_DP_PIN_DEFAULT
#define PIO_USB_DP_PIN_DEFAULT 0
#endif
#define PIO_USB_TX_DEFAULT 0
#define PIO_SM_USB_TX_DEFAULT 0
#define PIO_USB_DMA_TX_DEFAULT 0
#define PIO_USB_RX_DEFAULT 1
#define PIO_SM_USB_RX_DEFAULT 0
#define PIO_SM_USB_EOP_DEFAULT 1
#define PIO_USB_DEBUG_PIN_NONE (-1)
#define PIO_USB_DEFAULT_CONFIG \
{ \
PIO_USB_DP_PIN_DEFAULT, PIO_USB_TX_DEFAULT, PIO_SM_USB_TX_DEFAULT, \
PIO_USB_DMA_TX_DEFAULT, PIO_USB_RX_DEFAULT, PIO_SM_USB_RX_DEFAULT, \
PIO_SM_USB_EOP_DEFAULT, NULL, PIO_USB_DEBUG_PIN_NONE, \
PIO_USB_DEBUG_PIN_NONE, false, PIO_USB_PINOUT_DPDM \
}
#define PIO_USB_EP_POOL_CNT 32
#define PIO_USB_DEV_EP_CNT 16
#define PIO_USB_DEVICE_CNT 1
#define PIO_USB_HUB_PORT_CNT 8
#define PIO_USB_ROOT_PORT_CNT 2
#define PIO_USB_EP_SIZE 64

View File

@ -0,0 +1,498 @@
/**
* Copyright (c) 2021 sekigon-gonnoc
* Ha Thach (thach@tinyusb.org)
*/
#pragma GCC push_options
#pragma GCC optimize("-O3")
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include "pio_usb.h"
#include "pio_usb_ll.h"
#include "usb_crc.h"
#include "usb_rx.pio.h"
#include "usb_tx.pio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
static uint8_t new_devaddr = 0;
static uint8_t ep0_crc5_lut[16];
static __unused usb_descriptor_buffers_t descriptor_buffers;
static void __no_inline_not_in_flash_func(update_ep0_crc5_lut)(uint8_t addr) {
uint16_t dat;
uint8_t crc;
for (int epnum = 0; epnum < 16; epnum++) {
dat = (addr) | (epnum << 7);
crc = calc_usb_crc5(dat);
ep0_crc5_lut[epnum] = (crc << 3) | ((epnum >> 1) & 0x07);
}
}
static __always_inline void restart_usb_reveiver(pio_port_t *pp) {
pio_sm_exec(pp->pio_usb_rx, pp->sm_rx, pp->rx_reset_instr);
pio_sm_exec(pp->pio_usb_rx, pp->sm_rx, pp->rx_reset_instr2);
pio_sm_restart(pp->pio_usb_rx, pp->sm_rx);
pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK;
}
static __always_inline int8_t device_receive_token(uint8_t *buffer,
uint8_t dev_addr) {
pio_port_t *pp = PIO_USB_PIO_PORT(0);
uint8_t idx = 0;
uint8_t addr;
uint8_t ep;
bool match = false;
static uint8_t eplut[2][8] = {{0, 2, 4, 6, 8, 10, 12, 14},
{1, 3, 5, 7, 9, 11, 13, 15}};
uint8_t *current_lut;
if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) {
buffer[idx++] = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24;
if ((idx == 3) && (buffer[1] != USB_PID_SOF)) {
addr = buffer[2] & 0x7f;
current_lut = &eplut[buffer[2] >> 7][0];
match = dev_addr == addr ? true : false;
}
}
}
} else {
// host is probably timeout. Ignore this packets.
pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx);
}
restart_usb_reveiver(pp);
if (match) {
ep = current_lut[buffer[3] & 0x07];
if (ep0_crc5_lut[ep] == buffer[3]) {
return ep;
} else {
return -1;
}
}
return -1;
}
static void __no_inline_not_in_flash_func(usb_device_packet_handler)(void) {
static uint8_t token_buf[64];
pio_port_t *pp = PIO_USB_PIO_PORT(0);
root_port_t *rport = PIO_USB_ROOT_PORT(0);
//
// time critical start
//
int8_t ep_num = device_receive_token(token_buf, rport->dev_addr);
if (token_buf[1] == USB_PID_IN) {
if (ep_num < 0) {
return;
}
static uint8_t hand_shake_token[2] = {USB_SYNC, USB_PID_STALL};
endpoint_t *ep = PIO_USB_ENDPOINT((ep_num << 1) | 0x01);
uint16_t const xact_len = pio_usb_ll_get_transaction_len(ep);
pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, false);
volatile bool has_transfer = ep->has_transfer;
if (has_transfer) {
dma_channel_transfer_from_buffer_now(pp->tx_ch, ep->buffer, xact_len + 4);
} else if (ep->stalled) {
hand_shake_token[1] = USB_PID_STALL;
dma_channel_transfer_from_buffer_now(pp->tx_ch, hand_shake_token, 2);
} else {
hand_shake_token[1] = USB_PID_NAK;
dma_channel_transfer_from_buffer_now(pp->tx_ch, hand_shake_token, 2);
}
pp->pio_usb_tx->irq = IRQ_TX_ALL_MASK; // clear complete flag
while ((pp->pio_usb_tx->irq & IRQ_TX_ALL_MASK) == 0) {
continue;
}
if (has_transfer) {
pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK;
irq_clear(pp->device_rx_irq_num);
pio_usb_bus_start_receive(pp);
// wait for ack
pio_usb_bus_wait_handshake(pp);
pio_usb_bus_start_receive(pp);
irq_clear(pp->device_rx_irq_num);
//
// time critical end
//
if (ep->ep_num == 0x80 && new_devaddr > 0) {
rport->dev_addr = new_devaddr;
new_devaddr = 0;
update_ep0_crc5_lut(rport->dev_addr);
}
pio_usb_ll_transfer_continue(ep, xact_len);
} else {
pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK;
irq_clear(pp->device_rx_irq_num);
pio_usb_bus_start_receive(pp);
//
// time critical end
//
}
} else if (token_buf[1] == USB_PID_OUT) {
if (ep_num < 0) {
return;
}
endpoint_t *ep = PIO_USB_ENDPOINT(ep_num << 1);
uint8_t hanshake = ep->stalled
? USB_PID_STALL
: (ep->has_transfer ? USB_PID_ACK : USB_PID_NAK);
int res = pio_usb_bus_receive_packet_and_handshake(pp, hanshake);
pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx);
restart_usb_reveiver(pp);
irq_clear(pp->device_rx_irq_num);
if (ep->has_transfer) {
if (res >= 0) {
memcpy(ep->app_buf, pp->usb_rx_buffer + 2, res);
pio_usb_ll_transfer_continue(ep, res);
}
}
} else if (token_buf[1] == USB_PID_SETUP) {
if (ep_num < 0) {
return;
}
int res = pio_usb_bus_receive_packet_and_handshake(pp, USB_PID_ACK);
pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx);
restart_usb_reveiver(pp);
irq_clear(pp->device_rx_irq_num);
if (res >= 0) {
rport->setup_packet = pp->usb_rx_buffer + 2;
rport->ints |= PIO_USB_INTS_SETUP_REQ_BITS;
// DATA1 for both data and status stage
PIO_USB_ENDPOINT(0)->has_transfer = PIO_USB_ENDPOINT(1)->has_transfer = false;
PIO_USB_ENDPOINT(0)->data_id = PIO_USB_ENDPOINT(1)->data_id = 1;
PIO_USB_ENDPOINT(0)->stalled = PIO_USB_ENDPOINT(1)->stalled = false;
}
} else if (token_buf[1] == USB_PID_SOF) {
// SOF interrupt
}
token_buf[0] = 0; // clear received token
token_buf[1] = 0;
if (rport->ints) {
pio_usb_device_irq_handler(0);
}
}
usb_device_t *pio_usb_device_init(const pio_usb_configuration_t *c,
const usb_descriptor_buffers_t *buffers) {
pio_port_t *pp = PIO_USB_PIO_PORT(0);
root_port_t *rport = PIO_USB_ROOT_PORT(0);
usb_device_t *dev = &pio_usb_device[0];
pio_usb_bus_init(pp, c, rport);
rport->mode = PIO_USB_MODE_DEVICE;
memset(dev, 0, sizeof(*dev));
for (int i = 0; i < PIO_USB_DEV_EP_CNT; i++) {
dev->endpoint_id[i] = 2 * (i + 1); // only index IN endpoint
}
update_ep0_crc5_lut(rport->dev_addr);
float const cpu_freq = (float)clock_get_hz(clk_sys);
pio_calculate_clkdiv_from_float(cpu_freq / 48000000,
&pp->clk_div_fs_tx.div_int,
&pp->clk_div_fs_tx.div_frac);
pio_calculate_clkdiv_from_float(cpu_freq / 96000000,
&pp->clk_div_fs_rx.div_int,
&pp->clk_div_fs_rx.div_frac);
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_rx, rport->pin_dp);
SM_SET_CLKDIV_MAXSPEED(pp->pio_usb_rx, pp->sm_rx);
pio_sm_set_jmp_pin(pp->pio_usb_rx, pp->sm_eop, rport->pin_dm);
pio_sm_set_in_pins(pp->pio_usb_rx, pp->sm_eop, rport->pin_dp);
SM_SET_CLKDIV(pp->pio_usb_rx, pp->sm_eop, pp->clk_div_fs_rx);
descriptor_buffers = *buffers;
pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, true);
pio_usb_bus_prepare_receive(pp);
pp->pio_usb_rx->ctrl |= (1 << pp->sm_rx);
pp->pio_usb_rx->irq |= IRQ_RX_ALL_MASK;
// configure PIOx_IRQ_0 to detect packet receive start
pio_set_irqn_source_enabled(pp->pio_usb_rx, 0, pis_interrupt0 + IRQ_RX_START,
true);
pp->device_rx_irq_num = (pp->pio_usb_rx == pio0) ? PIO0_IRQ_0 : PIO1_IRQ_0;
irq_set_exclusive_handler(pp->device_rx_irq_num, usb_device_packet_handler);
irq_set_enabled(pp->device_rx_irq_num, true);
return dev;
}
//--------------------------------------------------------------------+
// Device Controller functions
//--------------------------------------------------------------------+
void pio_usb_device_set_address(uint8_t dev_addr) {
new_devaddr = dev_addr;
}
bool __no_inline_not_in_flash_func(pio_usb_device_endpoint_open)(
uint8_t const *desc_endpoint) {
const endpoint_descriptor_t *d = (const endpoint_descriptor_t *)desc_endpoint;
endpoint_t *ep = pio_usb_device_get_endpoint_by_address(d->epaddr);
pio_usb_ll_configure_endpoint(ep, desc_endpoint);
ep->root_idx = 0;
ep->dev_addr = 0; // not used
ep->need_pre = 0;
ep->is_tx = (d->epaddr & 0x80) ? true : false; // device: endpoint in is tx
return true;
}
bool pio_usb_device_transfer(uint8_t ep_address, uint8_t *buffer,
uint16_t buflen) {
endpoint_t *ep = pio_usb_device_get_endpoint_by_address(ep_address);
return pio_usb_ll_transfer_start(ep, buffer, buflen);
}
//--------------------------------------------------------------------+
// USB Device Stack
//--------------------------------------------------------------------+
static int8_t ep0_desc_request_type = -1;
static uint16_t ep0_desc_request_len;
static uint8_t ep0_desc_request_idx;
static void __no_inline_not_in_flash_func(prepare_ep0_data)(uint8_t *data,
uint8_t len) {
// 0: control out (rx), 1 : control in (tx)
endpoint_t *ep = &pio_usb_ep_pool[1];
pio_usb_ll_transfer_start(ep, data, len);
if (len) {
// there is data, prepare for status as well
pio_usb_ll_transfer_start(&pio_usb_ep_pool[0], NULL, 0);
}
}
static void __no_inline_not_in_flash_func(prepare_ep0_rx)(uint8_t *data,
uint8_t len) {
// 0: control out (rx), 1 : control in (tx)
endpoint_t *ep = &pio_usb_ep_pool[0];
pio_usb_ll_transfer_start(ep, data, len);
if (len) {
// there is data, prepare for status as well
pio_usb_ll_transfer_start(&pio_usb_ep_pool[1], NULL, 0);
}
}
void pio_usb_device_task(void) {
switch (ep0_desc_request_type) {
case DESC_TYPE_CONFIG: {
uint16_t req_len = ep0_desc_request_len;
uint16_t desc_len =
descriptor_buffers.config[2] | (descriptor_buffers.config[3] << 8);
req_len = req_len > desc_len ? desc_len : req_len;
prepare_ep0_data((uint8_t *)descriptor_buffers.config, req_len);
ep0_desc_request_type = -1;
} break;
case DESC_TYPE_STRING: {
const uint16_t *str =
(uint16_t *)&descriptor_buffers.string[ep0_desc_request_idx];
prepare_ep0_data((uint8_t *)str, str[0] & 0xff);
ep0_desc_request_type = -1;
} break;
case DESC_TYPE_HID_REPORT:{
prepare_ep0_data(
(uint8_t *)descriptor_buffers.hid_report[ep0_desc_request_idx],
ep0_desc_request_len);
ep0_desc_request_type = -1;
}
default:
break;
}
root_port_t *rport = PIO_USB_ROOT_PORT(0);
uint32_t se0_time_us =0;
while (pio_usb_bus_get_line_state(rport) == PORT_PIN_SE0) {
busy_wait_us_32(1);
se0_time_us++;
if (se0_time_us == 1000) {
memset(pio_usb_ep_pool, 0, sizeof(pio_usb_ep_pool));
rport->dev_addr = 0;
update_ep0_crc5_lut(rport->dev_addr);
// init endpoint control in/out
PIO_USB_ENDPOINT(0)->size = 64;
PIO_USB_ENDPOINT(0)->ep_num = 0;
PIO_USB_ENDPOINT(0)->is_tx = false;
PIO_USB_ENDPOINT(1)->size = 64;
PIO_USB_ENDPOINT(1)->ep_num = 0x80;
PIO_USB_ENDPOINT(1)->is_tx = true;
// TODO should be reset end, this is reset start only
rport->ep_complete = rport->ep_stalled = rport->ep_error = 0;
rport->ints |= PIO_USB_INTS_RESET_END_BITS;
pio_port_t *pp = PIO_USB_PIO_PORT(0);
restart_usb_reveiver(pp);
}
}
}
static void __no_inline_not_in_flash_func(configure_all_endpoints)(uint8_t const *desc) {
uint8_t const *desc_end = desc + (descriptor_buffers.config[2] |
(descriptor_buffers.config[3] << 8));
while (desc < desc_end) {
if (desc[1] == DESC_TYPE_ENDPOINT) {
pio_usb_device_endpoint_open(desc);
}
desc += desc[0];
}
}
static int __no_inline_not_in_flash_func(process_device_setup_stage)(uint8_t *buffer) {
int res = -1;
const usb_setup_packet_t *packet = (usb_setup_packet_t *)buffer;
if (packet->request_type == USB_REQ_DIR_IN) {
if (packet->request == 0x06) {
if (packet->value_msb == DESC_TYPE_DEVICE) {
prepare_ep0_data((uint8_t *)descriptor_buffers.device, 18);
res = 0;
} else if (packet->value_msb == DESC_TYPE_CONFIG) {
ep0_desc_request_len = (packet->length_lsb | (packet->length_msb << 8));
ep0_desc_request_type = DESC_TYPE_CONFIG;
res = 0;
} else if (packet->value_msb == DESC_TYPE_STRING) {
if (descriptor_buffers.string != NULL) {
ep0_desc_request_idx = packet->value_lsb;
ep0_desc_request_type = DESC_TYPE_STRING;
res = 0;
}
}
}
} else if (packet->request_type == USB_REQ_DIR_OUT) {
if (packet->request == 0x05) {
// set address
new_devaddr = packet->value_lsb;
prepare_ep0_data(NULL, 0);
res = 0;
} else if (packet->request == 0x09) {
// set configuration
configure_all_endpoints(descriptor_buffers.config);
prepare_ep0_data(NULL, 0);
res = 0;
}
} else if (packet->request_type == (USB_REQ_DIR_IN | USB_REQ_REC_IFACE)) {
if (packet->request == 0x06 && packet->value_msb == DESC_TYPE_HID_REPORT) {
// get hid report desc
ep0_desc_request_len = (packet->length_lsb | (packet->length_msb << 8));
ep0_desc_request_idx = packet->index_lsb;
ep0_desc_request_type = DESC_TYPE_HID_REPORT;
res = 0;
}
} else if (packet->request_type == (USB_REQ_TYP_CLASS | USB_REQ_REC_IFACE)) {
if (packet->request == 0x09) {
// set hid report
static __unused uint8_t received_hid_report[8]; // not used
prepare_ep0_rx(received_hid_report, 8);
res = 0;
} else if (packet->request == 0x0A) {
// set hid idle request
prepare_ep0_data(NULL, 0);
res = 0;
} else if (packet->request == 0x0B) {
// set hid protocol request
prepare_ep0_data(NULL, 0);
res = 0;
}
} else if (packet->request_type == (USB_REQ_REC_EP)) {
prepare_ep0_data(NULL, 0);
res = 0;
}
return res;
}
// IRQ Handler
static void __no_inline_not_in_flash_func(__pio_usb_device_irq_handler)(uint8_t root_idx) {
root_port_t *root = PIO_USB_ROOT_PORT(root_idx);
usb_device_t *dev = &pio_usb_device[0];
uint32_t const ints = root->ints;
if (ints & PIO_USB_INTS_RESET_END_BITS) {
memset(dev, 0, sizeof(*dev));
for (int i = 0; i < PIO_USB_DEV_EP_CNT; i++) {
dev->endpoint_id[i] = 2 * (i + 1); // only index IN endpoint
}
}
if (ints & PIO_USB_INTS_SETUP_REQ_BITS) {
process_device_setup_stage(root->setup_packet);
dev->control_pipe.stage = STAGE_DATA;
}
if (ints & PIO_USB_INTS_ENDPOINT_COMPLETE_BITS) {
const uint32_t ep_all = root->ep_complete;
// control out
if (ep_all & 0x01) {
if (dev->control_pipe.stage == STAGE_STATUS) {
dev->control_pipe.stage = STAGE_COMPLETE;
} else if (dev->control_pipe.stage == STAGE_DATA) {
dev->control_pipe.stage = STAGE_STATUS;
prepare_ep0_data(NULL, 0);
}
}
// control in
if (ep_all & 0x02) {
if (dev->control_pipe.stage == STAGE_STATUS) {
dev->control_pipe.stage = STAGE_COMPLETE;
}
}
// clear all
root->ep_complete &= ~ep_all;
}
// clear all
root->ints &= ~ints;
}
// weak alias to __pio_usb_device_irq_handler
void pio_usb_device_irq_handler(uint8_t root_id) __attribute__ ((weak, alias("__pio_usb_device_irq_handler")));
#pragma GCC pop_options

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,197 @@
/**
* Copyright (c) 2021 sekigon-gonnoc
* Ha Thach (thach@tinyusb.org)
*/
#pragma once
#include "hardware/pio.h"
#include "pio_usb_configuration.h"
#include "usb_definitions.h"
enum {
PIO_USB_INTS_CONNECT_POS = 0,
PIO_USB_INTS_DISCONNECT_POS,
PIO_USB_INTS_RESET_END_POS,
PIO_USB_INTS_SETUP_REQ_POS,
PIO_USB_INTS_SOF_POS,
PIO_USB_INTS_ENDPOINT_COMPLETE_POS,
PIO_USB_INTS_ENDPOINT_ERROR_POS,
PIO_USB_INTS_ENDPOINT_STALLED_POS,
};
#define PIO_USB_INTS_CONNECT_BITS (1u << PIO_USB_INTS_CONNECT_POS)
#define PIO_USB_INTS_DISCONNECT_BITS (1u << PIO_USB_INTS_DISCONNECT_POS)
#define PIO_USB_INTS_RESET_END_BITS (1u << PIO_USB_INTS_RESET_END_POS)
#define PIO_USB_INTS_SETUP_REQ_BITS (1u << PIO_USB_INTS_SETUP_REQ_POS)
#define PIO_USB_INTS_SOF_BITS (1u << PIO_USB_INTS_SOF_POS)
#define PIO_USB_INTS_ENDPOINT_COMPLETE_BITS \
(1u << PIO_USB_INTS_ENDPOINT_COMPLETE_POS)
#define PIO_USB_INTS_ENDPOINT_ERROR_BITS (1u << PIO_USB_INTS_ENDPOINT_ERROR_POS)
#define PIO_USB_INTS_ENDPOINT_STALLED_BITS \
(1u << PIO_USB_INTS_ENDPOINT_STALLED_POS)
typedef enum {
PORT_PIN_SE0 = 0b00,
PORT_PIN_FS_IDLE = 0b01,
PORT_PIN_LS_IDLE = 0b10,
PORT_PIN_SE1 = 0b11,
} port_pin_status_t;
typedef struct {
uint16_t div_int;
uint8_t div_frac;
} pio_clk_div_t;
typedef struct {
PIO pio_usb_tx; // could not set to volatile
uint sm_tx;
uint offset_tx;
uint tx_ch;
PIO pio_usb_rx; // could not set to volatile
uint sm_rx;
uint offset_rx;
uint sm_eop;
uint offset_eop;
uint rx_reset_instr;
uint rx_reset_instr2;
uint device_rx_irq_num;
int8_t debug_pin_rx;
int8_t debug_pin_eop;
const pio_program_t *fs_tx_program;
const pio_program_t *fs_tx_pre_program;
const pio_program_t *ls_tx_program;
pio_clk_div_t clk_div_fs_tx;
pio_clk_div_t clk_div_fs_rx;
pio_clk_div_t clk_div_ls_tx;
pio_clk_div_t clk_div_ls_rx;
bool need_pre;
uint8_t usb_rx_buffer[128];
} pio_port_t;
//--------------------------------------------------------------------+
//
//--------------------------------------------------------------------+
enum {
PIO_USB_MODE_INVALID = 0,
PIO_USB_MODE_DEVICE,
PIO_USB_MODE_HOST,
};
extern usb_device_t pio_usb_device[PIO_USB_DEVICE_CNT];
extern root_port_t pio_usb_root_port[PIO_USB_ROOT_PORT_CNT];
#define PIO_USB_ROOT_PORT(_idx) (pio_usb_root_port + (_idx))
extern endpoint_t pio_usb_ep_pool[PIO_USB_EP_POOL_CNT];
#define PIO_USB_ENDPOINT(_idx) (pio_usb_ep_pool + (_idx))
extern pio_port_t pio_port[1];
#define PIO_USB_PIO_PORT(_idx) (pio_port + (_idx))
//--------------------------------------------------------------------+
// Bus functions
//--------------------------------------------------------------------+
#define IRQ_TX_EOP_MASK (1 << usb_tx_dpdm_IRQ_EOP)
#define IRQ_TX_COMP_MASK (1 << usb_tx_dpdm_IRQ_COMP)
#define IRQ_TX_ALL_MASK (IRQ_TX_EOP_MASK | IRQ_TX_COMP_MASK)
#define IRQ_RX_COMP_MASK (1 << IRQ_RX_EOP)
#define IRQ_RX_ALL_MASK \
((1 << IRQ_RX_EOP) | (1 << IRQ_RX_BS_ERR) | (1 << IRQ_RX_START) | \
(1 << DECODER_TRIGGER))
#define SM_SET_CLKDIV(pio, sm, div) \
pio_sm_set_clkdiv_int_frac(pio, sm, div.div_int, div.div_frac)
#define SM_SET_CLKDIV_MAXSPEED(pio, sm) \
pio_sm_set_clkdiv_int_frac(pio, sm, 1, 0)
void pio_usb_bus_init(pio_port_t *pp, const pio_usb_configuration_t *c,
root_port_t *root);
void pio_usb_bus_start_receive(const pio_port_t *pp);
void pio_usb_bus_prepare_receive(const pio_port_t *pp);
int pio_usb_bus_receive_packet_and_handshake(pio_port_t *pp, uint8_t handshake);
void pio_usb_bus_usb_transfer(const pio_port_t *pp, uint8_t *data,
uint16_t len);
uint8_t pio_usb_bus_wait_handshake(pio_port_t *pp);
void pio_usb_bus_send_handshake(const pio_port_t *pp, uint8_t pid);
void pio_usb_bus_send_token(const pio_port_t *pp, uint8_t token, uint8_t addr,
uint8_t ep_num);
static __always_inline port_pin_status_t
pio_usb_bus_get_line_state(root_port_t *root) {
uint8_t dp = gpio_get(root->pin_dp) ? 0 : 1;
uint8_t dm = gpio_get(root->pin_dm) ? 0 : 1;
return (dm << 1) | dp;
}
//--------------------------------------------------------------------+
// Low Level functions
//--------------------------------------------------------------------+
void pio_usb_ll_configure_endpoint(endpoint_t *ep,
uint8_t const *desc_endpoint);
bool pio_usb_ll_transfer_start(endpoint_t *ep, uint8_t *buffer,
uint16_t buflen);
bool pio_usb_ll_transfer_continue(endpoint_t *ep, uint16_t xferred_bytes);
void pio_usb_ll_transfer_complete(endpoint_t *ep, uint32_t flag);
static inline __force_inline uint16_t
pio_usb_ll_get_transaction_len(endpoint_t *ep) {
uint16_t remaining = ep->total_len - ep->actual_len;
return (remaining < ep->size) ? remaining : ep->size;
}
//--------------------------------------------------------------------
// Host Controller functions
//--------------------------------------------------------------------
// Host IRQ Handler
void pio_usb_host_irq_handler(uint8_t root_idx);
void pio_usb_host_port_reset_start(uint8_t root_idx);
void pio_usb_host_port_reset_end(uint8_t root_idx);
void pio_usb_host_close_device(uint8_t root_idx, uint8_t device_address);
bool pio_usb_host_endpoint_open(uint8_t root_idx, uint8_t device_address,
uint8_t const *desc_endpoint, bool need_pre);
bool pio_usb_host_send_setup(uint8_t root_idx, uint8_t device_address,
uint8_t const setup_packet[8]);
bool pio_usb_host_endpoint_transfer(uint8_t root_idx, uint8_t device_address,
uint8_t ep_address, uint8_t *buffer,
uint16_t buflen);
bool pio_usb_host_endpoint_abort_transfer(uint8_t root_idx, uint8_t device_address,
uint8_t ep_address);
//--------------------------------------------------------------------
// Device Controller functions
//--------------------------------------------------------------------
// Device IRQ Handler
void pio_usb_device_irq_handler(uint8_t root_idx);
void pio_usb_device_set_address(uint8_t dev_addr);
bool pio_usb_device_endpoint_open(uint8_t const *desc_endpoint);
bool pio_usb_device_transfer(uint8_t ep_address, uint8_t *buffer,
uint16_t buflen);
static inline __force_inline endpoint_t *
pio_usb_device_get_endpoint_by_address(uint8_t ep_address) {
// index = 2*num + dir e.g out1, in1, out2, in2
uint8_t const ep_idx = ((ep_address & 0x7f) << 1) | (ep_address >> 7);
return PIO_USB_ENDPOINT(ep_idx);
}

View File

@ -0,0 +1,63 @@
#include "usb_crc.h"
const uint8_t __not_in_flash("crc5_tbl") crc5_tbl[32] = {
0x00, 0x0b, 0x16, 0x1d, 0x05, 0x0e, 0x13, 0x18, 0x0a, 0x01,
0x1c, 0x17, 0x0f, 0x04, 0x19, 0x12, 0x14, 0x1f, 0x02, 0x09,
0x11, 0x1a, 0x07, 0x0c, 0x1e, 0x15, 0x08, 0x03, 0x1b, 0x10,
0x0d, 0x06,
};
uint8_t __not_in_flash_func(calc_usb_crc5)(uint16_t data) {
data = data ^ 0x1f;
const uint8_t lsb = (data >> 1) & 0x1f;
const uint8_t msb = (data >> 6) & 0x1f;
uint8_t crc = (data & 1) ? 0x14 : 0x00;
crc = crc5_tbl[(lsb ^ crc)];
crc = crc5_tbl[(msb ^ crc)];
return crc ^ 0x1f;
}
// Place to RAM
const uint16_t __not_in_flash("crc_tbl") crc16_tbl[256] = {
0x0000, 0xc0c1, 0xc181, 0x0140, 0xc301, 0x03c0, 0x0280, 0xc241, 0xc601,
0x06c0, 0x0780, 0xc741, 0x0500, 0xc5c1, 0xc481, 0x0440, 0xcc01, 0x0cc0,
0x0d80, 0xcd41, 0x0f00, 0xcfc1, 0xce81, 0x0e40, 0x0a00, 0xcac1, 0xcb81,
0x0b40, 0xc901, 0x09c0, 0x0880, 0xc841, 0xd801, 0x18c0, 0x1980, 0xd941,
0x1b00, 0xdbc1, 0xda81, 0x1a40, 0x1e00, 0xdec1, 0xdf81, 0x1f40, 0xdd01,
0x1dc0, 0x1c80, 0xdc41, 0x1400, 0xd4c1, 0xd581, 0x1540, 0xd701, 0x17c0,
0x1680, 0xd641, 0xd201, 0x12c0, 0x1380, 0xd341, 0x1100, 0xd1c1, 0xd081,
0x1040, 0xf001, 0x30c0, 0x3180, 0xf141, 0x3300, 0xf3c1, 0xf281, 0x3240,
0x3600, 0xf6c1, 0xf781, 0x3740, 0xf501, 0x35c0, 0x3480, 0xf441, 0x3c00,
0xfcc1, 0xfd81, 0x3d40, 0xff01, 0x3fc0, 0x3e80, 0xfe41, 0xfa01, 0x3ac0,
0x3b80, 0xfb41, 0x3900, 0xf9c1, 0xf881, 0x3840, 0x2800, 0xe8c1, 0xe981,
0x2940, 0xeb01, 0x2bc0, 0x2a80, 0xea41, 0xee01, 0x2ec0, 0x2f80, 0xef41,
0x2d00, 0xedc1, 0xec81, 0x2c40, 0xe401, 0x24c0, 0x2580, 0xe541, 0x2700,
0xe7c1, 0xe681, 0x2640, 0x2200, 0xe2c1, 0xe381, 0x2340, 0xe101, 0x21c0,
0x2080, 0xe041, 0xa001, 0x60c0, 0x6180, 0xa141, 0x6300, 0xa3c1, 0xa281,
0x6240, 0x6600, 0xa6c1, 0xa781, 0x6740, 0xa501, 0x65c0, 0x6480, 0xa441,
0x6c00, 0xacc1, 0xad81, 0x6d40, 0xaf01, 0x6fc0, 0x6e80, 0xae41, 0xaa01,
0x6ac0, 0x6b80, 0xab41, 0x6900, 0xa9c1, 0xa881, 0x6840, 0x7800, 0xb8c1,
0xb981, 0x7940, 0xbb01, 0x7bc0, 0x7a80, 0xba41, 0xbe01, 0x7ec0, 0x7f80,
0xbf41, 0x7d00, 0xbdc1, 0xbc81, 0x7c40, 0xb401, 0x74c0, 0x7580, 0xb541,
0x7700, 0xb7c1, 0xb681, 0x7640, 0x7200, 0xb2c1, 0xb381, 0x7340, 0xb101,
0x71c0, 0x7080, 0xb041, 0x5000, 0x90c1, 0x9181, 0x5140, 0x9301, 0x53c0,
0x5280, 0x9241, 0x9601, 0x56c0, 0x5780, 0x9741, 0x5500, 0x95c1, 0x9481,
0x5440, 0x9c01, 0x5cc0, 0x5d80, 0x9d41, 0x5f00, 0x9fc1, 0x9e81, 0x5e40,
0x5a00, 0x9ac1, 0x9b81, 0x5b40, 0x9901, 0x59c0, 0x5880, 0x9841, 0x8801,
0x48c0, 0x4980, 0x8941, 0x4b00, 0x8bc1, 0x8a81, 0x4a40, 0x4e00, 0x8ec1,
0x8f81, 0x4f40, 0x8d01, 0x4dc0, 0x4c80, 0x8c41, 0x4400, 0x84c1, 0x8581,
0x4540, 0x8701, 0x47c0, 0x4680, 0x8641, 0x8201, 0x42c0, 0x4380, 0x8341,
0x4100, 0x81c1, 0x8081, 0x4040};
uint16_t calc_usb_crc16(const uint8_t *data, uint16_t len) {
uint16_t crc = 0xffff;
for (int idx = 0; idx < len; idx++) {
crc = (crc >> 8) ^ crc16_tbl[(crc ^ data[idx]) & 0xff];
}
return crc ^ 0xffff;
}

View File

@ -0,0 +1,15 @@
#pragma once
#include <stdint.h>
#include "pico/stdlib.h"
// Calc CRC5-USB of 11bit data
uint8_t calc_usb_crc5(uint16_t data);
// Calc CRC16-USB of array
uint16_t calc_usb_crc16(const uint8_t *data, uint16_t len);
extern const uint16_t crc16_tbl[256];
static inline uint16_t __time_critical_func(update_usb_crc16)(uint16_t crc, uint8_t data) {
crc = (crc >> 8) ^ crc16_tbl[(crc ^ data) & 0xff];
return crc;
}

View File

@ -0,0 +1,345 @@
#pragma once
#include <stdint.h>
#include "pio_usb_configuration.h"
typedef enum {
CONTROL_NONE,
CONTROL_IN,
CONTROL_OUT,
CONTROL_COMPLETE,
CONTROL_ERROR,
} control_transfer_operation_t;
typedef enum {
EP_IN = 0x80,
EP_OUT = 0x00,
} ep_type_t;
typedef enum {
STAGE_SETUP,
STAGE_DATA,
STAGE_IN,
STAGE_OUT,
STAGE_STATUS,
STAGE_COMPLETE,
STAGE_ERROR
} setup_transfer_stage_t;
typedef enum {
STAGE_IDLE,
STAGE_DEVICE,
STAGE_CONFIG,
STAGE_CONFIG2,
STAGE_INTERFACE,
STAGE_ENDPOINT
} usb_enumeration_stage_t;
typedef struct {
uint8_t *tx_address;
uint8_t tx_length;
} packet_info_t;
typedef struct {
volatile uint8_t data_in_num;
volatile uint16_t buffer_idx;
volatile uint8_t *rx_buffer;
volatile packet_info_t setup_packet;
volatile packet_info_t out_data_packet;
volatile int16_t request_length;
volatile control_transfer_operation_t operation;
volatile setup_transfer_stage_t stage;
} control_pipe_t;
typedef struct {
volatile uint8_t root_idx;
volatile uint8_t dev_addr;
bool need_pre;
bool is_tx; // Host out or Device in
volatile uint8_t ep_num;
volatile bool new_data_flag;
volatile uint16_t size;
volatile uint8_t attr;
volatile uint8_t interval;
volatile uint8_t interval_counter;
volatile uint8_t data_id; // data0 or data1
volatile bool stalled;
volatile bool has_transfer;
volatile bool transfer_started;
volatile bool transfer_aborted;
uint8_t buffer[64 + 4];
uint8_t *app_buf;
uint16_t total_len;
uint16_t actual_len;
} endpoint_t;
typedef enum {
EVENT_NONE,
EVENT_CONNECT,
EVENT_DISCONNECT,
EVENT_HUB_PORT_CHANGE,
} usb_device_event_t;
typedef struct struct_usb_device_t usb_device_t;
typedef struct struct_root_port_t {
volatile bool initialized;
volatile bool addr0_exists;
volatile uint8_t pin_dp;
volatile uint8_t pin_dm;
volatile usb_device_event_t event;
usb_device_t *root_device;
volatile bool is_fullspeed;
volatile bool connected;
volatile bool suspended;
uint8_t mode;
// register interface
volatile uint32_t ints; // interrupt status
volatile uint32_t ep_complete;
volatile uint32_t ep_error;
volatile uint32_t ep_stalled;
// device only
uint8_t dev_addr;
uint8_t *setup_packet;
} root_port_t;
struct struct_usb_device_t {
volatile bool connected;
volatile bool enumerated;
volatile usb_device_event_t event;
volatile uint8_t address;
volatile uint16_t vid;
volatile uint16_t pid;
volatile uint8_t device_class;
volatile bool is_fullspeed;
volatile bool is_root;
control_pipe_t control_pipe;
uint8_t endpoint_id[PIO_USB_DEV_EP_CNT];
uint8_t child_devices[PIO_USB_HUB_PORT_CNT];
struct struct_usb_device_t *parent_device;
uint8_t parent_port;
root_port_t *root;
};
enum {
USB_SYNC = 0x80,
USB_PID_OUT = 0xe1,
USB_PID_IN = 0x69,
USB_PID_SOF = 0xa5,
USB_PID_SETUP = 0x2d,
USB_PID_DATA0 = 0xc3,
USB_PID_DATA1 = 0x4b,
USB_PID_ACK = 0xd2,
USB_PID_NAK = 0x5a,
USB_PID_STALL = 0x1e,
USB_PID_PRE = 0x3c,
USB_CRC16_PLACE = 0,
};
enum {
DESC_TYPE_DEVICE = 0x01,
DESC_TYPE_CONFIG = 0x02,
DESC_TYPE_STRING = 0x03,
DESC_TYPE_INTERFACE = 0x04,
DESC_TYPE_ENDPOINT = 0x05,
DESC_TYPE_HID = 0x21,
DESC_TYPE_HID_REPORT = 0x22,
};
enum {
CLASS_HID = 0x03,
CLASS_HUB = 0x09,
};
enum {
EP_ATTR_CONTROL = 0x00,
EP_ATTR_ISOCHRONOUS = 0x01,
EP_ATTR_BULK = 0x02,
EP_ATTR_INTERRUPT = 0x03,
EP_ATTR_ENUMERATING = 0x80
};
typedef struct {
// uint8_t sync;
// uint8_t pid;
uint8_t request_type;
uint8_t request;
uint8_t value_lsb;
uint8_t value_msb;
uint8_t index_lsb;
uint8_t index_msb;
uint8_t length_lsb;
uint8_t length_msb;
// uint8_t crc16[2];
} usb_setup_packet_t;
typedef struct {
uint8_t length;
uint8_t type;
uint8_t bcd_usb[2];
uint8_t device_class;
uint8_t subclass;
uint8_t protocol;
uint8_t max_packet_size;
uint8_t vid[2];
uint8_t pid[2];
uint8_t bcd_device[2];
uint8_t manufacture;
uint8_t product;
uint8_t serial;
uint8_t num_configu;
} device_descriptor_t;
typedef struct {
uint8_t length;
uint8_t type;
uint8_t string[62];
} __attribute__((aligned(2))) string_descriptor_t;
typedef struct {
uint8_t length;
uint8_t type;
uint8_t inum;
uint8_t altsetting;
uint8_t numep;
uint8_t iclass;
uint8_t isubclass;
uint8_t iprotocol;
uint8_t iface;
} interface_descriptor_t;
typedef struct {
uint8_t length;
uint8_t type;
uint8_t epaddr;
uint8_t attr;
uint8_t max_size[2];
uint8_t interval;
} endpoint_descriptor_t;
typedef struct {
uint8_t length;
uint8_t type;
uint8_t bcd_hid[2];
uint8_t contry_code;
uint8_t num_desc;
uint8_t desc_type;
uint8_t desc_len[2];
} hid_descriptor_t;
typedef struct configuration_descriptor_tag {
uint8_t length;
uint8_t type;
uint8_t total_length_lsb;
uint8_t total_length_msb;
uint8_t num_interfaces;
uint8_t configuration_value;
uint8_t configuration;
uint8_t attributes;
uint8_t max_power;
} configuration_descriptor_t;
typedef struct {
uint8_t lenght;
uint8_t type;
uint8_t port_num;
uint8_t chara_lsb;
uint8_t chara_msb;
uint8_t pow_on_time;
uint8_t current;
uint8_t removable;
} hub_descriptor_t;
typedef struct {
uint16_t port_status;
uint16_t port_change;
} hub_port_status_t;
enum {
HUB_SET_PORT_RESET = 4,
HUB_SET_PORT_POWER = 8,
HUB_CLR_PORT_CONNECTION = 16,
HUB_CLR_PORT_ENABLE = 17,
HUB_CLR_PORT_SUSPEND = 18,
HUB_CLR_PORT_RESET = 20,
};
enum {
HUB_STAT_PORT_CONNECTION = (1 << 0),
HUB_STAT_PORT_ENABLE = (1 << 1),
HUB_STAT_PORT_SUSPEND = (1 << 2),
HUB_STAT_PORT_OC = (1 << 3),
HUB_STAT_PORT_RESET = (1 << 4),
HUB_STAT_PORT_POWER = (1 << 8),
HUB_STAT_PORT_LOWSPEED = (1 << 9),
};
enum {
HUB_CHANGE_PORT_CONNECTION = (1 << 0),
HUB_CHANGE_PORT_ENABLE = (1 << 1),
HUB_CHANGE_PORT_SUSPEND = (1 << 2),
HUB_CHANGE_PORT_OC = (1 << 3),
HUB_CHANGE_PORT_RESET = (1 << 4),
};
enum {
USB_REQ_DIR_IN = 0x80,
USB_REQ_DIR_OUT = 0x00,
USB_REQ_TYP_STANDARD = 0x00,
USB_REQ_TYP_CLASS = 0x20,
USB_REQ_TYP_VENDOR = 0x40,
USB_REQ_REC_DEVICE = 0x00,
USB_REQ_REC_IFACE = 0x01,
USB_REQ_REC_EP = 0x02,
USB_REQ_REC_OTHER = 0x03,
};
#define GET_DEVICE_DESCRIPTOR_REQ_DEFAULT \
{ USB_REQ_DIR_IN, 0x06, 0, 0x01, 0, 0, 0x12, 0 }
#define GET_CONFIGURATION_DESCRIPTOR_REQ_DEFAULT \
{ USB_REQ_DIR_IN, 0x06, 0, 0x02, 0, 0, 0x09, 0 }
#define SET_CONFIGURATION_REQ_DEFAULT \
{ USB_REQ_DIR_OUT, 0x09, 0, 0, 0, 0, 0, 0 }
#define SET_ADDRESS_REQ_DEFAULT \
{ USB_REQ_DIR_OUT, 0x5, 0x02, 0, 0, 0, 0, 0 }
#define SET_HID_IDLE_REQ_DEFAULT \
{ USB_REQ_TYP_CLASS | USB_REQ_REC_IFACE, 0x0A, 0, 0, 0, 0, 0, 0 }
#define GET_HID_REPORT_DESCRIPTOR_DEFAULT \
{ USB_REQ_DIR_IN | USB_REQ_REC_IFACE, 0x06, 0, 0x22, 0, 0, 0xff, 0 }
#define SET_REPORT_REQ_DEFAULT \
{ USB_REQ_TYP_CLASS | USB_REQ_REC_IFACE, 0x09, 0, 0x02, 0, 0, 0, 0 }
#define GET_HUB_DESCRPTOR_REQUEST \
{ \
USB_REQ_DIR_IN | USB_REQ_TYP_CLASS | USB_REQ_REC_DEVICE, 0x06, 0, 0x29, 0, \
0, 8, 0 \
}
#define GET_HUB_PORT_STATUS_REQUEST \
{ \
USB_REQ_DIR_IN | USB_REQ_TYP_CLASS | USB_REQ_REC_OTHER, 0, 0, 0, 0, 0, 4, \
0 \
}
#define SET_HUB_FEATURE_REQUEST \
{ \
USB_REQ_DIR_OUT | USB_REQ_TYP_CLASS | USB_REQ_REC_OTHER, 0x03, 0, 0, 0, 0, \
0, 0 \
}
#define CLEAR_HUB_FEATURE_REQUEST \
{ \
USB_REQ_DIR_OUT | USB_REQ_TYP_CLASS | USB_REQ_REC_OTHER, 0x01, 0, 0, 0, 0, \
0, 0 \
}
typedef struct {
const uint8_t *device;
const uint8_t *config;
const uint8_t **hid_report;
const string_descriptor_t *string;
} usb_descriptor_buffers_t;

242
Pico-PIO-USB/src/usb_rx.pio Normal file
View File

@ -0,0 +1,242 @@
; Copyright (c) 2021-2022 sekigon-gonnoc
.define public IRQ_RX_BS_ERR 1 ; bit stuffinc error
.define public IRQ_RX_EOP 2 ; eop detect flag
.define public IRQ_RX_START 3 ; packet start flag
.define public DECODER_TRIGGER 4
.define BIT_REPEAT_COUNT 6 ; bit repeat counter
.define db0 0
.define db1 1
; USB signal edge and eop detector
; 17 instruction
; FS(12M) LS(1.5M)
; Run at 96MHz 12MHz
; jmp_pin d- d+
; in_pin d+ d-
; both d+/d- pin should be input invert overrideed
.program usb_edge_detector
eop:
irq wait IRQ_RX_EOP
start:
jmp pin start ; Wait fall edge
irq IRQ_RX_START [1]
.wrap_target
pin_still_low:
irq DECODER_TRIGGER [1] ; Trigger NRZI decoder
; Resync on rising edge
pin_low:
jmp pin pin_went_high
pin_went_low:
jmp pin pin_went_high
jmp pin pin_went_high
jmp pin pin_went_high
jmp pin pin_went_high
jmp pin pin_went_high
.wrap
pin_still_high:
mov x, isr [2]
jmp x-- eop ; Jump to eop if jmp_pin and in_pin are high because both inputs are inverted
; Jump to here on rising edge
pin_went_high:
mov isr, null
in pins, 1 ; Capture the pin to check eop.
irq DECODER_TRIGGER ; Trigger NRZI decoder
jmp pin pin_still_high
jmp pin_went_low ; To adjust interval of decoder trigger, jump to pin_went_low (not pin_low)
.program usb_edge_detector_debug
.side_set 1
eop:
irq wait IRQ_RX_EOP side db0
start:
jmp pin start side db1 ; Wait fall edge
irq IRQ_RX_START [1] side db0
.wrap_target
pin_still_low:
irq DECODER_TRIGGER [1] side db0 ; Trigger NRZI decoder
; Resync on rising edge
pin_low:
jmp pin pin_went_high side db1
pin_went_low:
jmp pin pin_went_high side db1
jmp pin pin_went_high side db1
jmp pin pin_went_high side db1
jmp pin pin_went_high side db1
jmp pin pin_went_high side db1
.wrap
pin_still_high:
mov x, isr [2] side db1
jmp x-- eop side db1 ; Jump to eop if jmp_pin and in_pin are high because both inputs are inverted
; Jump to here on rising edge
pin_went_high:
mov isr, null side db1
in pins, 1 side db0 ; Capture the pin to check eop.
irq DECODER_TRIGGER side db0 ; Trigger NRZI decoder
jmp pin pin_still_high side db0
jmp pin_went_low side db1 ; To adjust interval of decoder trigger, jump to pin_went_low (not pin_low)
; USB NRZI data decoder
; 15 instruction
; FS(12M) LS(1.5M)
; Run at as fast as possible
; jmp_pin d+ d-
; both d+/d- pin should be input invert overrideed
; Fill OSR by 1 and set 0 to x before runnning this program
.program usb_nrzi_decoder
start:
; set x, 0
.wrap_target
set_y:
set y, BIT_REPEAT_COUNT
irq_wait:
wait 1 irq DECODER_TRIGGER ; wait signal from edge detector
jmp PIN pin_high
pin_low:
jmp !y flip ; ignore stuff bit, without error check
jmp !x K1
K2:
; x==1
in null, 1
jmp flip
K1:
; x==0
in osr, 1
jmp y-- irq_wait
pin_high:
jmp !y flip ; ignore stuff bit, without error check
jmp !x J1
J2:
; x==1
in x, 1
jmp y-- irq_wait
J1:
; x==0
in null, 1
flip:
mov x, ~x
.wrap
.program usb_nrzi_decoder_debug
.side_set 1 opt
start:
; set x, 0 side db0
.wrap_target
set_y:
set y, BIT_REPEAT_COUNT
irq_wait:
wait 1 irq DECODER_TRIGGER ; wait signal from edge detector
jmp PIN pin_high
pin_low:
jmp !y flip side db0 ; ignore stuff bit, without error check
jmp !x K1 side db0
K2:
; x==1
in null, 1
jmp flip
K1:
; x==0
in osr, 1
jmp y-- irq_wait
pin_high:
jmp !y flip side db1 ; ignore stuff bit, without error check
jmp !x J1 side db1
J2:
; x==1
in x, 1
jmp y-- irq_wait
J1:
; x==0
in null, 1
flip:
mov x, ~x
.wrap
% c-sdk {
#include "hardware/clocks.h"
static __always_inline void pio_sm_set_jmp_pin(PIO pio, uint sm, uint jmp_pin) {
pio->sm[sm].execctrl =
(pio->sm[sm].execctrl & ~PIO_SM0_EXECCTRL_JMP_PIN_BITS) |
(jmp_pin << PIO_SM0_EXECCTRL_JMP_PIN_LSB);
}
static inline void usb_rx_fs_program_init(PIO pio, uint sm, uint offset, uint pin_dp, uint pin_dm, int pin_debug) {
if (pin_dp < pin_dm) {
pio_sm_set_consecutive_pindirs(pio, sm, pin_dp, 2, false);
} else {
pio_sm_set_consecutive_pindirs(pio, sm, pin_dm, 2, false);
}
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dm);
gpio_set_inover(pin_dp, GPIO_OVERRIDE_INVERT);
gpio_set_inover(pin_dm, GPIO_OVERRIDE_INVERT);
pio_sm_config c;
if (pin_debug < 0) {
c = usb_nrzi_decoder_program_get_default_config(offset);
} else {
c = usb_nrzi_decoder_debug_program_get_default_config(offset);
pio_sm_set_pins_with_mask(pio, sm, 0, 1 << pin_debug);
pio_sm_set_pindirs_with_mask(pio, sm, 1 << pin_debug, 1 << pin_debug);
pio_gpio_init(pio, pin_debug);
sm_config_set_sideset_pins(&c, pin_debug);
}
sm_config_set_in_pins(&c, pin_dp); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin_dp); // for JMP
// Shift to right, autopull enabled, 8bit
sm_config_set_in_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_exec(pio, sm, pio_encode_mov_not(pio_osr, pio_null));
pio_sm_set_enabled(pio, sm, false);
}
static inline void eop_detect_fs_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, uint pin_dm, bool is_fs, int pin_debug) {
pio_sm_config c;
if (pin_debug < 0) {
c = usb_edge_detector_program_get_default_config(offset);
} else {
c = usb_edge_detector_debug_program_get_default_config(offset);
pio_sm_set_pins_with_mask(pio, sm, 0, 1 << pin_debug);
pio_sm_set_pindirs_with_mask(pio, sm, 1 << pin_debug, 1 << pin_debug);
pio_gpio_init(pio, pin_debug);
sm_config_set_sideset_pins(&c, pin_debug);
}
sm_config_set_in_pins(&c, pin_dp); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin_dm); // for JMP
sm_config_set_in_shift(&c, false, false, 8);
float div;
if (is_fs) {
div = (float)clock_get_hz(clk_sys) / (96000000);
} else {
div = (float)clock_get_hz(clk_sys) / (12000000);
}
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + 1, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@ -0,0 +1,247 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
#define IRQ_RX_BS_ERR 1
#define IRQ_RX_EOP 2
#define IRQ_RX_START 3
#define DECODER_TRIGGER 4
// ----------------- //
// usb_edge_detector //
// ----------------- //
#define usb_edge_detector_wrap_target 3
#define usb_edge_detector_wrap 9
static const uint16_t usb_edge_detector_program_instructions[] = {
0xc022, // 0: irq wait 2
0x00c1, // 1: jmp pin, 1
0xc103, // 2: irq nowait 3 [1]
// .wrap_target
0xc104, // 3: irq nowait 4 [1]
0x00cc, // 4: jmp pin, 12
0x00cc, // 5: jmp pin, 12
0x00cc, // 6: jmp pin, 12
0x00cc, // 7: jmp pin, 12
0x00cc, // 8: jmp pin, 12
0x00cc, // 9: jmp pin, 12
// .wrap
0xa226, // 10: mov x, isr [2]
0x0040, // 11: jmp x--, 0
0xa0c3, // 12: mov isr, null
0x4001, // 13: in pins, 1
0xc004, // 14: irq nowait 4
0x00ca, // 15: jmp pin, 10
0x0005, // 16: jmp 5
};
#if !PICO_NO_HARDWARE
static const struct pio_program usb_edge_detector_program = {
.instructions = usb_edge_detector_program_instructions,
.length = 17,
.origin = -1,
};
static inline pio_sm_config usb_edge_detector_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_edge_detector_wrap_target, offset + usb_edge_detector_wrap);
return c;
}
#endif
// ----------------------- //
// usb_edge_detector_debug //
// ----------------------- //
#define usb_edge_detector_debug_wrap_target 3
#define usb_edge_detector_debug_wrap 9
static const uint16_t usb_edge_detector_debug_program_instructions[] = {
0xc022, // 0: irq wait 2 side 0
0x10c1, // 1: jmp pin, 1 side 1
0xc103, // 2: irq nowait 3 side 0 [1]
// .wrap_target
0xc104, // 3: irq nowait 4 side 0 [1]
0x10cc, // 4: jmp pin, 12 side 1
0x10cc, // 5: jmp pin, 12 side 1
0x10cc, // 6: jmp pin, 12 side 1
0x10cc, // 7: jmp pin, 12 side 1
0x10cc, // 8: jmp pin, 12 side 1
0x10cc, // 9: jmp pin, 12 side 1
// .wrap
0xb226, // 10: mov x, isr side 1 [2]
0x1040, // 11: jmp x--, 0 side 1
0xb0c3, // 12: mov isr, null side 1
0x4001, // 13: in pins, 1 side 0
0xc004, // 14: irq nowait 4 side 0
0x00ca, // 15: jmp pin, 10 side 0
0x1005, // 16: jmp 5 side 1
};
#if !PICO_NO_HARDWARE
static const struct pio_program usb_edge_detector_debug_program = {
.instructions = usb_edge_detector_debug_program_instructions,
.length = 17,
.origin = -1,
};
static inline pio_sm_config usb_edge_detector_debug_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_edge_detector_debug_wrap_target, offset + usb_edge_detector_debug_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#endif
// ---------------- //
// usb_nrzi_decoder //
// ---------------- //
#define usb_nrzi_decoder_wrap_target 0
#define usb_nrzi_decoder_wrap 14
static const uint16_t usb_nrzi_decoder_program_instructions[] = {
// .wrap_target
0xe046, // 0: set y, 6
0x20c4, // 1: wait 1 irq, 4
0x00c9, // 2: jmp pin, 9
0x006e, // 3: jmp !y, 14
0x0027, // 4: jmp !x, 7
0x4061, // 5: in null, 1
0x000e, // 6: jmp 14
0x40e1, // 7: in osr, 1
0x0081, // 8: jmp y--, 1
0x006e, // 9: jmp !y, 14
0x002d, // 10: jmp !x, 13
0x4021, // 11: in x, 1
0x0081, // 12: jmp y--, 1
0x4061, // 13: in null, 1
0xa029, // 14: mov x, !x
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program usb_nrzi_decoder_program = {
.instructions = usb_nrzi_decoder_program_instructions,
.length = 15,
.origin = -1,
};
static inline pio_sm_config usb_nrzi_decoder_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_nrzi_decoder_wrap_target, offset + usb_nrzi_decoder_wrap);
return c;
}
#endif
// ---------------------- //
// usb_nrzi_decoder_debug //
// ---------------------- //
#define usb_nrzi_decoder_debug_wrap_target 0
#define usb_nrzi_decoder_debug_wrap 14
static const uint16_t usb_nrzi_decoder_debug_program_instructions[] = {
// .wrap_target
0xe046, // 0: set y, 6
0x20c4, // 1: wait 1 irq, 4
0x00c9, // 2: jmp pin, 9
0x106e, // 3: jmp !y, 14 side 0
0x1027, // 4: jmp !x, 7 side 0
0x4061, // 5: in null, 1
0x000e, // 6: jmp 14
0x40e1, // 7: in osr, 1
0x0081, // 8: jmp y--, 1
0x186e, // 9: jmp !y, 14 side 1
0x182d, // 10: jmp !x, 13 side 1
0x4021, // 11: in x, 1
0x0081, // 12: jmp y--, 1
0x4061, // 13: in null, 1
0xa029, // 14: mov x, !x
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program usb_nrzi_decoder_debug_program = {
.instructions = usb_nrzi_decoder_debug_program_instructions,
.length = 15,
.origin = -1,
};
static inline pio_sm_config usb_nrzi_decoder_debug_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_nrzi_decoder_debug_wrap_target, offset + usb_nrzi_decoder_debug_wrap);
sm_config_set_sideset(&c, 2, true, false);
return c;
}
#include "hardware/clocks.h"
static __always_inline void pio_sm_set_jmp_pin(PIO pio, uint sm, uint jmp_pin) {
pio->sm[sm].execctrl =
(pio->sm[sm].execctrl & ~PIO_SM0_EXECCTRL_JMP_PIN_BITS) |
(jmp_pin << PIO_SM0_EXECCTRL_JMP_PIN_LSB);
}
static inline void usb_rx_fs_program_init(PIO pio, uint sm, uint offset, uint pin_dp, uint pin_dm, int pin_debug) {
if (pin_dp < pin_dm) {
pio_sm_set_consecutive_pindirs(pio, sm, pin_dp, 2, false);
} else {
pio_sm_set_consecutive_pindirs(pio, sm, pin_dm, 2, false);
}
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dm);
gpio_set_inover(pin_dp, GPIO_OVERRIDE_INVERT);
gpio_set_inover(pin_dm, GPIO_OVERRIDE_INVERT);
pio_sm_config c;
if (pin_debug < 0) {
c = usb_nrzi_decoder_program_get_default_config(offset);
} else {
c = usb_nrzi_decoder_debug_program_get_default_config(offset);
pio_sm_set_pins_with_mask(pio, sm, 0, 1 << pin_debug);
pio_sm_set_pindirs_with_mask(pio, sm, 1 << pin_debug, 1 << pin_debug);
pio_gpio_init(pio, pin_debug);
sm_config_set_sideset_pins(&c, pin_debug);
}
sm_config_set_in_pins(&c, pin_dp); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin_dp); // for JMP
// Shift to right, autopull enabled, 8bit
sm_config_set_in_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX);
pio_sm_init(pio, sm, offset, &c);
pio_sm_exec(pio, sm, pio_encode_mov_not(pio_osr, pio_null));
pio_sm_set_enabled(pio, sm, false);
}
static inline void eop_detect_fs_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, uint pin_dm, bool is_fs, int pin_debug) {
pio_sm_config c;
if (pin_debug < 0) {
c = usb_edge_detector_program_get_default_config(offset);
} else {
c = usb_edge_detector_debug_program_get_default_config(offset);
pio_sm_set_pins_with_mask(pio, sm, 0, 1 << pin_debug);
pio_sm_set_pindirs_with_mask(pio, sm, 1 << pin_debug, 1 << pin_debug);
pio_gpio_init(pio, pin_debug);
sm_config_set_sideset_pins(&c, pin_debug);
}
sm_config_set_in_pins(&c, pin_dp); // for WAIT, IN
sm_config_set_jmp_pin(&c, pin_dm); // for JMP
sm_config_set_in_shift(&c, false, false, 8);
float div;
if (is_fs) {
div = (float)clock_get_hz(clk_sys) / (96000000);
} else {
div = (float)clock_get_hz(clk_sys) / (12000000);
}
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset + 1, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif

279
Pico-PIO-USB/src/usb_tx.pio Normal file
View File

@ -0,0 +1,279 @@
; Copyright (c) 2021-2023 sekigon-gonnoc
.define public USB_TX_EOP_OFFSET 4
.define public USB_TX_EOP_DISABLER_LEN 4
; USB NRZI transmitter
; Run at 48 MHz for full-spped
; Run at 6 MHz for low-spped
; autopull enabled
.program usb_tx_dpdm
.side_set 2 opt
; J for fs, K for ls
.define FJ_LK 0b01
; K for fs, J for ls
.define FK_LJ 0b10
.define SE0 0b00
.define BR 5 ; bit repeat limit
.define public IRQ_COMP 0 ; complete flag bit
.define public IRQ_EOP 1 ; EOP start flag bit
start:
set y, BR side FJ_LK
set pindirs, 0b11
.wrap_target
check_eop1:
jmp !osre load_bit1
nop [1]
send_eop:
irq IRQ_EOP side SE0 [3] ; To catch quik ACK, mark as complete here
nop [3]
nop side FJ_LK
set pindirs, 0b00 [3]
irq wait IRQ_COMP
jmp start
load_bit1:
out x, 1
jmp !x low1
high1:
jmp y-- check_eop1 side FJ_LK
nop [2] ; bit stuffing
low1:
set y BR side FK_LJ
check_eop2:
jmp !osre load_bit2
jmp send_eop [1]
load_bit2:
out x, 1
jmp !x low2
high2:
jmp y-- check_eop2 side FK_LJ
nop [2] ; bit stuffing
low2:
set y BR side FJ_LK
.wrap
; USB transmitter for PRE packet (No EOP)
; Run at 48 MHz for full-spped
; autopull enabled
.program usb_tx_pre_dpdm
.side_set 2 opt
.define J 0b01
.define K 0b10
.define SE0 0b00
.define BR 5 ; bit repeat limit
.define public IRQ_COMP 0 ; complete flag bit
.define public IRQ_EOP 1 ; EOP start flag bit
start:
set y, BR side J
set pindirs, 0b11
.wrap_target
check_eop1:
jmp !osre load_bit1
nop [1]
send_eop:
irq IRQ_EOP side J [3]
set pindirs, 0b00
nop ; to align program size
nop ; to align program size
irq wait IRQ_COMP
jmp start
load_bit1:
out x, 1
jmp !x low1
high1:
jmp y-- check_eop1 side J
nop [2] ; bit stuffing
low1:
set y BR side K
check_eop2:
jmp !osre load_bit2
jmp send_eop [1]
load_bit2:
out x, 1
jmp !x low2
high2:
jmp y-- check_eop2 side K
nop [2] ; bit stuffing
low2:
set y BR side J
.wrap
; USB NRZI transmitter
; Run at 48 MHz for full-spped
; Run at 6 MHz for low-spped
; autopull enabled
.program usb_tx_dmdp
.side_set 2 opt
.define FK_LJ 0b10
.define FJ_LK 0b01
.define SE0 0b00
.define BR 5 ; bit repeat limit
.define public IRQ_COMP 0 ; complete flag bit
.define public IRQ_EOP 1 ; EOP start flag bit
start:
set y, BR side FK_LJ
set pindirs, 0b11
.wrap_target
check_eop1:
jmp !osre load_bit1
nop [1]
send_eop:
irq IRQ_EOP side SE0 [3]
nop [3]
nop side FK_LJ
set pindirs, 0b00 [3]
irq wait IRQ_COMP
jmp start
load_bit1:
out x, 1
jmp !x low1
high1:
jmp y-- check_eop1 side FK_LJ
nop [2] ; bit stuffing
low1:
set y BR side FJ_LK
check_eop2:
jmp !osre load_bit2
jmp send_eop [1]
load_bit2:
out x, 1
jmp !x low2
high2:
jmp y-- check_eop2 side FJ_LK
nop [2] ; bit stuffing
low2:
set y BR side FK_LJ
.wrap
; USB transmitter for PRE packet (No EOP)
; Run at 48 MHz for full-spped
; autopull enabled
.program usb_tx_pre_dmdp
.side_set 2 opt
.define J 0b10
.define K 0b01
.define SE0 0b00
.define BR 5 ; bit repeat limit
.define public IRQ_COMP 0 ; complete flag bit
.define public IRQ_EOP 1 ; EOP start flag bit
start:
set y, BR side J
set pindirs, 0b11
.wrap_target
check_eop1:
jmp !osre load_bit1
nop [1]
send_eop:
irq IRQ_EOP side J [3]
set pindirs, 0b00
nop ; to align program size
nop ; to align program size
irq wait IRQ_COMP
jmp start
load_bit1:
out x, 1
jmp !x low1
high1:
jmp y-- check_eop1 side J
nop [2] ; bit stuffing
low1:
set y BR side K
check_eop2:
jmp !osre load_bit2
jmp send_eop [1]
load_bit2:
out x, 1
jmp !x low2
high2:
jmp y-- check_eop2 side K
nop [2] ; bit stuffing
low2:
set y BR side J
.wrap
% c-sdk {
#include "hardware/clocks.h"
static void __no_inline_not_in_flash_func(usb_tx_configure_pins)(PIO pio, uint sm, uint pin_dp, uint pin_dm) {
if (pin_dp < pin_dm) {
pio_sm_set_out_pins(pio, sm, pin_dp, 2);
pio_sm_set_set_pins(pio, sm, pin_dp, 2);
pio_sm_set_sideset_pins(pio, sm, pin_dp);
} else {
pio_sm_set_out_pins(pio, sm, pin_dm, 2);
pio_sm_set_set_pins(pio, sm, pin_dm, 2);
pio_sm_set_sideset_pins(pio, sm, pin_dm);
}
}
static inline void usb_tx_fs_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, uint pin_dm) {
pio_sm_set_pins_with_mask(pio, sm, (1 << pin_dp), ((1 << pin_dp) | (1 << pin_dm)));
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dm);
pio_gpio_init(pio, pin_dp);
pio_gpio_init(pio, pin_dm);
pio_sm_config c = usb_tx_dpdm_program_get_default_config(offset);
// shifts to left, autopull, 8bit
sm_config_set_out_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// run at 48MHz
// clk_sys should be multiply of 12MHz
float div = (float)clock_get_hz(clk_sys) / (48000000UL);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
usb_tx_configure_pins(pio, sm, pin_dp, pin_dm);
pio_sm_set_enabled(pio, sm, true);
}
static inline void usb_tx_ls_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, uint pin_dm) {
pio_sm_set_pins_with_mask(pio, sm, (1 << pin_dm), ((1 << pin_dp) | (1 << pin_dm)));
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dm);
pio_gpio_init(pio, pin_dp);
pio_gpio_init(pio, pin_dm);
pio_sm_config c = usb_tx_dmdp_program_get_default_config(offset);
// shifts to left, autopull, 8bit
sm_config_set_out_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// run at 6MHz
// clk_sys should be multiply of 12MHz
float div = (float)clock_get_hz(clk_sys) / (6000000UL);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
usb_tx_configure_pins(pio, sm, pin_dp, pin_dm);
pio_sm_set_enabled(pio, sm, true);
}
%}

View File

@ -0,0 +1,273 @@
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
#define USB_TX_EOP_OFFSET 4
#define USB_TX_EOP_DISABLER_LEN 4
// ----------- //
// usb_tx_dpdm //
// ----------- //
#define usb_tx_dpdm_wrap_target 2
#define usb_tx_dpdm_wrap 21
#define usb_tx_dpdm_IRQ_COMP 0
#define usb_tx_dpdm_IRQ_EOP 1
static const uint16_t __not_in_flash("tx_program") usb_tx_dpdm_program_instructions[] = {
0xf445, // 0: set y, 5 side 1
0xe083, // 1: set pindirs, 3
// .wrap_target
0x00ea, // 2: jmp !osre, 10
0xa142, // 3: nop [1]
0xd301, // 4: irq nowait 1 side 0 [3]
0xa342, // 5: nop [3]
0xb442, // 6: nop side 1
0xe380, // 7: set pindirs, 0 [3]
0xc020, // 8: irq wait 0
0x0000, // 9: jmp 0
0x6021, // 10: out x, 1
0x002e, // 11: jmp !x, 14
0x1482, // 12: jmp y--, 2 side 1
0xa242, // 13: nop [2]
0xf845, // 14: set y, 5 side 2
0x00f1, // 15: jmp !osre, 17
0x0104, // 16: jmp 4 [1]
0x6021, // 17: out x, 1
0x0035, // 18: jmp !x, 21
0x188f, // 19: jmp y--, 15 side 2
0xa242, // 20: nop [2]
0xf445, // 21: set y, 5 side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program __not_in_flash("tx_program") usb_tx_dpdm_program = {
.instructions = usb_tx_dpdm_program_instructions,
.length = 22,
.origin = -1,
};
static inline pio_sm_config usb_tx_dpdm_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_tx_dpdm_wrap_target, offset + usb_tx_dpdm_wrap);
sm_config_set_sideset(&c, 3, true, false);
return c;
}
#endif
// --------------- //
// usb_tx_pre_dpdm //
// --------------- //
#define usb_tx_pre_dpdm_wrap_target 2
#define usb_tx_pre_dpdm_wrap 21
#define usb_tx_pre_dpdm_IRQ_COMP 0
#define usb_tx_pre_dpdm_IRQ_EOP 1
static const uint16_t __not_in_flash("tx_program") usb_tx_pre_dpdm_program_instructions[] = {
0xf445, // 0: set y, 5 side 1
0xe083, // 1: set pindirs, 3
// .wrap_target
0x00ea, // 2: jmp !osre, 10
0xa142, // 3: nop [1]
0xd701, // 4: irq nowait 1 side 1 [3]
0xe080, // 5: set pindirs, 0
0xa042, // 6: nop
0xa042, // 7: nop
0xc020, // 8: irq wait 0
0x0000, // 9: jmp 0
0x6021, // 10: out x, 1
0x002e, // 11: jmp !x, 14
0x1482, // 12: jmp y--, 2 side 1
0xa242, // 13: nop [2]
0xf845, // 14: set y, 5 side 2
0x00f1, // 15: jmp !osre, 17
0x0104, // 16: jmp 4 [1]
0x6021, // 17: out x, 1
0x0035, // 18: jmp !x, 21
0x188f, // 19: jmp y--, 15 side 2
0xa242, // 20: nop [2]
0xf445, // 21: set y, 5 side 1
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program __not_in_flash("tx_program") usb_tx_pre_dpdm_program = {
.instructions = usb_tx_pre_dpdm_program_instructions,
.length = 22,
.origin = -1,
};
static inline pio_sm_config usb_tx_pre_dpdm_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_tx_pre_dpdm_wrap_target, offset + usb_tx_pre_dpdm_wrap);
sm_config_set_sideset(&c, 3, true, false);
return c;
}
#endif
// ----------- //
// usb_tx_dmdp //
// ----------- //
#define usb_tx_dmdp_wrap_target 2
#define usb_tx_dmdp_wrap 21
#define usb_tx_dmdp_IRQ_COMP 0
#define usb_tx_dmdp_IRQ_EOP 1
static const uint16_t __not_in_flash("tx_program") usb_tx_dmdp_program_instructions[] = {
0xf845, // 0: set y, 5 side 2
0xe083, // 1: set pindirs, 3
// .wrap_target
0x00ea, // 2: jmp !osre, 10
0xa142, // 3: nop [1]
0xd301, // 4: irq nowait 1 side 0 [3]
0xa342, // 5: nop [3]
0xb842, // 6: nop side 2
0xe380, // 7: set pindirs, 0 [3]
0xc020, // 8: irq wait 0
0x0000, // 9: jmp 0
0x6021, // 10: out x, 1
0x002e, // 11: jmp !x, 14
0x1882, // 12: jmp y--, 2 side 2
0xa242, // 13: nop [2]
0xf445, // 14: set y, 5 side 1
0x00f1, // 15: jmp !osre, 17
0x0104, // 16: jmp 4 [1]
0x6021, // 17: out x, 1
0x0035, // 18: jmp !x, 21
0x148f, // 19: jmp y--, 15 side 1
0xa242, // 20: nop [2]
0xf845, // 21: set y, 5 side 2
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program __not_in_flash("tx_program") usb_tx_dmdp_program = {
.instructions = usb_tx_dmdp_program_instructions,
.length = 22,
.origin = -1,
};
static inline pio_sm_config usb_tx_dmdp_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_tx_dmdp_wrap_target, offset + usb_tx_dmdp_wrap);
sm_config_set_sideset(&c, 3, true, false);
return c;
}
#endif
// --------------- //
// usb_tx_pre_dmdp //
// --------------- //
#define usb_tx_pre_dmdp_wrap_target 2
#define usb_tx_pre_dmdp_wrap 21
#define usb_tx_pre_dmdp_IRQ_COMP 0
#define usb_tx_pre_dmdp_IRQ_EOP 1
static const uint16_t __not_in_flash("tx_program") usb_tx_pre_dmdp_program_instructions[] = {
0xf845, // 0: set y, 5 side 2
0xe083, // 1: set pindirs, 3
// .wrap_target
0x00ea, // 2: jmp !osre, 10
0xa142, // 3: nop [1]
0xdb01, // 4: irq nowait 1 side 2 [3]
0xe080, // 5: set pindirs, 0
0xa042, // 6: nop
0xa042, // 7: nop
0xc020, // 8: irq wait 0
0x0000, // 9: jmp 0
0x6021, // 10: out x, 1
0x002e, // 11: jmp !x, 14
0x1882, // 12: jmp y--, 2 side 2
0xa242, // 13: nop [2]
0xf445, // 14: set y, 5 side 1
0x00f1, // 15: jmp !osre, 17
0x0104, // 16: jmp 4 [1]
0x6021, // 17: out x, 1
0x0035, // 18: jmp !x, 21
0x148f, // 19: jmp y--, 15 side 1
0xa242, // 20: nop [2]
0xf845, // 21: set y, 5 side 2
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program __not_in_flash("tx_program") usb_tx_pre_dmdp_program = {
.instructions = usb_tx_pre_dmdp_program_instructions,
.length = 22,
.origin = -1,
};
static inline pio_sm_config usb_tx_pre_dmdp_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + usb_tx_pre_dmdp_wrap_target, offset + usb_tx_pre_dmdp_wrap);
sm_config_set_sideset(&c, 3, true, false);
return c;
}
#include "hardware/clocks.h"
static void __no_inline_not_in_flash_func(usb_tx_configure_pins)(PIO pio, uint sm, uint pin_dp, uint pin_dm) {
if (pin_dp < pin_dm) {
pio_sm_set_out_pins(pio, sm, pin_dp, 2);
pio_sm_set_set_pins(pio, sm, pin_dp, 2);
pio_sm_set_sideset_pins(pio, sm, pin_dp);
} else {
pio_sm_set_out_pins(pio, sm, pin_dm, 2);
pio_sm_set_set_pins(pio, sm, pin_dm, 2);
pio_sm_set_sideset_pins(pio, sm, pin_dm);
}
}
static inline void usb_tx_fs_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, uint pin_dm) {
pio_sm_set_pins_with_mask(pio, sm, (1 << pin_dp), ((1 << pin_dp) | (1 << pin_dm)));
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dm);
pio_gpio_init(pio, pin_dp);
pio_gpio_init(pio, pin_dm);
pio_sm_config c = usb_tx_dpdm_program_get_default_config(offset);
// shifts to left, autopull, 8bit
sm_config_set_out_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// run at 48MHz
// clk_sys should be multiply of 12MHz
float div = (float)clock_get_hz(clk_sys) / (48000000UL);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
usb_tx_configure_pins(pio, sm, pin_dp, pin_dm);
pio_sm_set_enabled(pio, sm, true);
}
static inline void usb_tx_ls_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, uint pin_dm) {
pio_sm_set_pins_with_mask(pio, sm, (1 << pin_dm), ((1 << pin_dp) | (1 << pin_dm)));
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dm);
pio_gpio_init(pio, pin_dp);
pio_gpio_init(pio, pin_dm);
pio_sm_config c = usb_tx_dmdp_program_get_default_config(offset);
// shifts to left, autopull, 8bit
sm_config_set_out_shift(&c, true, true, 8);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
// run at 6MHz
// clk_sys should be multiply of 12MHz
float div = (float)clock_get_hz(clk_sys) / (6000000UL);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
usb_tx_configure_pins(pio, sm, pin_dp, pin_dm);
pio_sm_set_enabled(pio, sm, true);
}
#endif

162
README.md Normal file
View File

@ -0,0 +1,162 @@
# DeskHop - Fast Desktop Switching
Did you ever notice how, in the crazy world of tech, there's always that one quirky little project trying to solve a problem so niche that its only competitors might be a left-handed screwdriver and a self-hiding alarm clock?
I use two different computers in my daily workflow and share a single keyboard/mouse pair between them. Trying several USB switching boxes found on Amazon made me realize they all suffer from similar issues - it takes a while to switch, the process is quite clumsy when trying to find the button and frankly, it just doesn't get any better with time.
All I wanted was a way to use a keyboard shortcut to quickly switch outputs, paired with the ability to do the same by magically moving the mouse pointer between monitors. This project enables you to do both, even if your computers run different operating systems!
![Image](img/case_and_board_s.png)
## Features
- Completely **free and open source**
- No noticeable delay when switching
- Simply drag the mouse pointer between computers
- No software installed
- Affordable and obtainable components (<15)
- 3D printable snap-fit case
- Full Galvanic isolation between your outputs
- Works with Linux, macOS and Windows
------
## How it works
The device acts as an intermediary between your keyboard/mouse and the computer, establishing and maintaining connections with both computers at once. Then it chooses where to forward your mouse and keystrokes to, depending on your selection.
## Mouse
To get the mouse cursor to magically jump across, the mouse hid report descriptor was changed to use absolute coordinates and then the mouse reports (that still come in relative movements) accumulate internally, keeping the accurate tally on the position.
When you try to leave the monitor area in the direction of the other monitor, it keeps the Y coordinate and swaps the maximum X for a minimum X, then flips the outputs. This ensures that the cursor seamlessly appears at the same height on the other monitor, enhancing the perception of a smooth transition.
![Image](img/deskhop-demo.gif)
<p align="center"> Dragging the mouse from Mac to Linux automatically switches outputs.
</p>
-----
The actual switch happens at the very moment when one arrow stops moving and the other one starts.
## Keyboard
Acting as a USB Host and querying your keyboard periodically, it looks for a preconfigured hotkey in the hid report (usually Caps Lock for me). When found, it will forward all subsequent characters to the other output.
To have a visual indication which output you are using at any given moment, you can repurpose keyboard LEDs and have them provide the necessary feedback.
It also remembers the LED state for each computer, so you can pick up exactly how you left it.
![Image](img/demo-typing.gif)
## How to build
```
mkdir build
cd build
PICO_BOARD=pico PICO_SDK_PATH=/your/sdk/path cmake ..
make
```
## Using pre-built images
Alternatively, you can use the [pre-built images](binaries/). Take the Pico board that goes to slot A on the PCB and hold the on-board button while connecting the cable.
It should appear as a USB drive on your system. Copy the corresponding board_A.uf2 file there and repeat the same with B.
## Upgrading firmware
Option 1 - Open the case, hold the button while connecting each Pico and copy the right uf2 to it.
Option 2 - Switch both Picos to BOOTSEL mode by using a special key combination (**hold down** all of these keys): ```Right Shift, P, H, X, Left Shift```
This will make the Picos enter the bootloader upgrade mode and act as USB flash drives. Now you can drag-and-drop the .uf2 files to them (you might need to plug in your mouse directly).
## Misc features
Ever tried to move that YT video slider to a specific position but your mouse moves too jumpy and suddenly you are moving your hand super-carefully like you're 5 and playing "Operation" all over again?
**Holding right ALT** while moving the mouse will slow it down considerably, enabling you to get the finer precision work done and still have your mouse moving quickly otherwise.
## Hardware
[The circuit](schematics/DeskHop.pdf) is based on two Raspberry Pi Pico boards, chosen because they are cheap (4.10 € / pc), can be hand soldered and most suppliers have them in stock.
The Picos are connected using UART and separated by an Analog Devices ADuM1201 dual-channel digital isolator (~3€).
While they normally don't have support for dual USB, thanks to an [amazing project](https://github.com/sekigon-gonnoc/Pico-PIO-USB) where USB is implemented using the programmable IO wizardry found in RP2040, there is support for it to act both as an USB host and device.
## PCB
To keep things as simple as possible for DIY builds, the traces were kept on one side and the number of parts kept to a theoretical minimum.
![Image](img/pcb_render_s.png)
USB D+/D- differential lines should be identical in length, but they are slightly asymmetrical on purpose to counter the length difference on the corresponding GPIO traces PICO PCB itself, so the overall lengths should match.
Zd (differential impedance) is aimed as 90 ohm (managed to get ~107, close enough :)).
The thickness is designed to be 1.6 mm for snap-fit to work as expected.
Planned changes:
- Add 15 ohm resistors for D+ / D- lines
- Add decoupling capacitance near VBUS line on USB-A connector (~100 uF)
- Add an ESD protection device near the USB connector
## Case
Since I'm not very good with 3d, the case is [simple and basic](case/) but does the job. It should be easy to print, uses ~33g of filament and takes a couple of hours.
Horizontal PCB movements are countered by pegs sliding through holes and vertical movements by snap-fit lugs on the side - no screws required.
Micro USB connectors on both boards are offset from the side of the case, so slightly larger holes should allow for cables to reach in.
The lid is of a snap-fit design, with a screwdriver slot for opening. The markings on top are recessed and can be finished with e.g. crayons to give better contrast (or simply left as-is).
![Image](img/deskhop-case.gif)
## Bill of materials
| Component | Qty | Unit Price / € | Price / €|
|--------------------|-----|----------------|----------|
| Raspberry Pi Pico | 2 | 4.10 | 8.20 |
| ADuM1201BRZ | 1 | 2.59 | 2.59 |
| Cap 1206 SMD 100nF | 2 | 0.09 | 0.18 |
| USB-A PCB conn. | 2 | 0.20 | 0.40 |
| Headers 2.54 1x03 | 2 | 0.08 | 0.16 |
| | | | |
| | | Total | 11.53 |
Additional steps:
- making the PCB ([Gerber provided](pcb/), JLC does it for a few bucks, choose 1.6 mm thickness)
- 3d printing the case ([stl files provided](case/), ~33g filament)
## FAQ
1. I just have two Picos, can I do without a PCB and isolator?
*Sure. Having an isolator is recommended but it should work without it.*
2. What happens if I have two different resolutions on my monitors?
*The mouse movement is done in abstract coordinate space and your computer figures out how that corresponds with the physical screen, so it should just work.*
3. Where can I buy it?
*I'm not selling anything, this is just a personal, non-commercial hobby project.*
## Shortcomings
- Code needs cleanup, some refactoring etc.
- Occasional bugs and weird behavior.
- Not tested with a wide variety of devices, I don't know how it will work with your hardware.
- Pico-PIO-USB was patched to support controlling keyboard LEDs, normally this would be handled by TinyUSB in host mode.
## Disclaimer
I kindly request that anyone attempting to build this project understands and acknowledges that I am not liable for any injuries, damages, or other consequences. Your safety is important, and I encourage you to approach this project carefully, taking necessary precautions and assuming personal responsibility for your well-being throughout the process. Please don't get electrocuted, burned, stressed or angry. Have fun and enjoy!
Happy switchin'!

BIN
binaries/board_A.uf2 Normal file

Binary file not shown.

BIN
binaries/board_B.uf2 Normal file

Binary file not shown.

BIN
case/bottom.stl Normal file

Binary file not shown.

BIN
case/top.stl Normal file

Binary file not shown.

BIN
img/case_and_board.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

BIN
img/case_and_board_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 KiB

BIN
img/demo-typing.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB

BIN
img/deskhop-case.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 KiB

BIN
img/deskhop-demo.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

BIN
img/pcb_render.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

BIN
img/pcb_render_s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 392 KiB

17781
pcb/DeskHop.kicad_pcb Normal file

File diff suppressed because it is too large Load Diff

77
pcb/DeskHop.kicad_prl Normal file
View File

@ -0,0 +1,77 @@
{
"board": {
"active_layer": 0,
"active_layer_preset": "All Layers",
"auto_track_width": true,
"hidden_netclasses": [],
"hidden_nets": [],
"high_contrast_mode": 0,
"net_color_mode": 1,
"opacity": {
"images": 0.6,
"pads": 1.0,
"tracks": 1.0,
"vias": 1.0,
"zones": 0.6
},
"selection_filter": {
"dimensions": true,
"footprints": true,
"graphics": true,
"keepouts": true,
"lockedItems": false,
"otherItems": true,
"pads": true,
"text": true,
"tracks": true,
"vias": true,
"zones": true
},
"visible_items": [
0,
1,
2,
3,
4,
5,
8,
9,
10,
11,
12,
13,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
28,
29,
30,
32,
33,
34,
35,
36,
39,
40
],
"visible_layers": "fffffff_ffffffff",
"zone_display_mode": 0
},
"meta": {
"filename": "DeskHop.kicad_prl",
"version": 3
},
"project": {
"files": []
}
}

506
pcb/DeskHop.kicad_pro Normal file
View File

@ -0,0 +1,506 @@
{
"board": {
"3dviewports": [],
"design_settings": {
"defaults": {
"board_outline_line_width": 0.049999999999999996,
"copper_line_width": 0.19999999999999998,
"copper_text_italic": false,
"copper_text_size_h": 1.5,
"copper_text_size_v": 1.5,
"copper_text_thickness": 0.3,
"copper_text_upright": false,
"courtyard_line_width": 0.049999999999999996,
"dimension_precision": 4,
"dimension_units": 3,
"dimensions": {
"arrow_length": 1270000,
"extension_offset": 500000,
"keep_text_aligned": true,
"suppress_zeroes": false,
"text_position": 0,
"units_format": 1
},
"fab_line_width": 0.09999999999999999,
"fab_text_italic": false,
"fab_text_size_h": 1.0,
"fab_text_size_v": 1.0,
"fab_text_thickness": 0.15,
"fab_text_upright": false,
"other_line_width": 0.09999999999999999,
"other_text_italic": false,
"other_text_size_h": 1.0,
"other_text_size_v": 1.0,
"other_text_thickness": 0.15,
"other_text_upright": false,
"pads": {
"drill": 0.0,
"height": 5.4,
"width": 5.4
},
"silk_line_width": 0.12,
"silk_text_italic": false,
"silk_text_size_h": 1.0,
"silk_text_size_v": 1.0,
"silk_text_thickness": 0.15,
"silk_text_upright": false,
"zones": {
"45_degree_only": false,
"min_clearance": 0.508
}
},
"diff_pair_dimensions": [
{
"gap": 0.0,
"via_gap": 0.0,
"width": 0.0
}
],
"drc_exclusions": [],
"meta": {
"filename": "board_design_settings.json",
"version": 2
},
"rule_severities": {
"annular_width": "error",
"clearance": "error",
"connection_width": "warning",
"copper_edge_clearance": "error",
"copper_sliver": "warning",
"courtyards_overlap": "error",
"diff_pair_gap_out_of_range": "error",
"diff_pair_uncoupled_length_too_long": "error",
"drill_out_of_range": "error",
"duplicate_footprints": "warning",
"extra_footprint": "warning",
"footprint": "error",
"footprint_type_mismatch": "error",
"hole_clearance": "error",
"hole_near_hole": "error",
"invalid_outline": "error",
"isolated_copper": "warning",
"item_on_disabled_layer": "error",
"items_not_allowed": "error",
"length_out_of_range": "error",
"lib_footprint_issues": "warning",
"lib_footprint_mismatch": "warning",
"malformed_courtyard": "error",
"microvia_drill_out_of_range": "error",
"missing_courtyard": "ignore",
"missing_footprint": "warning",
"net_conflict": "warning",
"npth_inside_courtyard": "ignore",
"padstack": "error",
"pth_inside_courtyard": "ignore",
"shorting_items": "error",
"silk_edge_clearance": "warning",
"silk_over_copper": "warning",
"silk_overlap": "warning",
"skew_out_of_range": "error",
"solder_mask_bridge": "error",
"starved_thermal": "error",
"text_height": "warning",
"text_thickness": "warning",
"through_hole_pad_without_hole": "error",
"too_many_vias": "error",
"track_dangling": "warning",
"track_width": "error",
"tracks_crossing": "error",
"unconnected_items": "error",
"unresolved_variable": "error",
"via_dangling": "warning",
"zones_intersect": "error"
},
"rule_severitieslegacy_courtyards_overlap": true,
"rule_severitieslegacy_no_courtyard_defined": false,
"rules": {
"allow_blind_buried_vias": false,
"allow_microvias": false,
"max_error": 0.005,
"min_clearance": 0.0,
"min_connection": 0.0,
"min_copper_edge_clearance": 0.024999999999999998,
"min_hole_clearance": 0.25,
"min_hole_to_hole": 0.25,
"min_microvia_diameter": 0.19999999999999998,
"min_microvia_drill": 0.09999999999999999,
"min_resolved_spokes": 2,
"min_silk_clearance": 0.0,
"min_text_height": 0.7999999999999999,
"min_text_thickness": 0.08,
"min_through_hole_diameter": 0.3,
"min_track_width": 0.19999999999999998,
"min_via_annular_width": 0.049999999999999996,
"min_via_diameter": 0.39999999999999997,
"solder_mask_to_copper_clearance": 0.0,
"use_height_for_length_calcs": true
},
"teardrop_options": [
{
"td_allow_use_two_tracks": true,
"td_curve_segcount": 5,
"td_on_pad_in_zone": false,
"td_onpadsmd": true,
"td_onroundshapesonly": false,
"td_ontrackend": false,
"td_onviapad": true
}
],
"teardrop_parameters": [
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_round_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_rect_shape",
"td_width_to_size_filter_ratio": 0.9
},
{
"td_curve_segcount": 0,
"td_height_ratio": 1.0,
"td_length_ratio": 0.5,
"td_maxheight": 2.0,
"td_maxlen": 1.0,
"td_target_name": "td_track_end",
"td_width_to_size_filter_ratio": 0.9
}
],
"track_widths": [
0.0,
1.0
],
"via_dimensions": [
{
"diameter": 0.0,
"drill": 0.0
}
],
"zones_allow_external_fillets": false,
"zones_use_no_outline": true
},
"layer_presets": [],
"viewports": []
},
"boards": [],
"cvpcb": {
"equivalence_files": []
},
"erc": {
"erc_exclusions": [],
"meta": {
"version": 0
},
"pin_map": [
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
1,
0,
1,
2
],
[
0,
1,
0,
0,
0,
0,
1,
1,
2,
1,
1,
2
],
[
0,
0,
0,
0,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
2
],
[
1,
1,
1,
1,
1,
0,
1,
1,
1,
1,
1,
2
],
[
0,
0,
0,
1,
0,
0,
1,
0,
0,
0,
0,
2
],
[
0,
2,
1,
2,
0,
0,
1,
0,
2,
2,
2,
2
],
[
0,
2,
0,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
0,
2,
1,
1,
0,
0,
1,
0,
2,
0,
0,
2
],
[
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2,
2
]
],
"rule_severities": {
"bus_definition_conflict": "error",
"bus_entry_needed": "error",
"bus_to_bus_conflict": "error",
"bus_to_net_conflict": "error",
"conflicting_netclasses": "error",
"different_unit_footprint": "error",
"different_unit_net": "error",
"duplicate_reference": "error",
"duplicate_sheet_names": "error",
"endpoint_off_grid": "warning",
"extra_units": "error",
"global_label_dangling": "warning",
"hier_label_mismatch": "error",
"label_dangling": "error",
"lib_symbol_issues": "warning",
"missing_bidi_pin": "warning",
"missing_input_pin": "warning",
"missing_power_pin": "error",
"missing_unit": "warning",
"multiple_net_names": "warning",
"net_not_bus_member": "warning",
"no_connect_connected": "warning",
"no_connect_dangling": "warning",
"pin_not_connected": "error",
"pin_not_driven": "error",
"pin_to_pin": "warning",
"power_pin_not_driven": "error",
"similar_labels": "warning",
"simulation_model_issue": "error",
"unannotated": "error",
"unit_value_mismatch": "error",
"unresolved_variable": "error",
"wire_dangling": "error"
}
},
"libraries": {
"pinned_footprint_libs": [],
"pinned_symbol_libs": []
},
"meta": {
"filename": "DeskHop.kicad_pro",
"version": 1
},
"net_settings": {
"classes": [
{
"bus_width": 12,
"clearance": 0.2,
"diff_pair_gap": 0.25,
"diff_pair_via_gap": 0.25,
"diff_pair_width": 0.2,
"line_style": 0,
"microvia_diameter": 0.3,
"microvia_drill": 0.1,
"name": "Default",
"pcb_color": "rgba(0, 0, 0, 0.000)",
"schematic_color": "rgba(0, 0, 0, 0.000)",
"track_width": 0.5,
"via_diameter": 0.8,
"via_drill": 0.4,
"wire_width": 6
}
],
"meta": {
"version": 3
},
"net_colors": null,
"netclass_assignments": null,
"netclass_patterns": []
},
"pcbnew": {
"last_paths": {
"gencad": "",
"idf": "",
"netlist": "DeskHop.net",
"specctra_dsn": "",
"step": "DeskHop_v3_fusion.step",
"vrml": ""
},
"page_layout_descr_file": ""
},
"schematic": {
"annotate_start_num": 0,
"drawing": {
"dashed_lines_dash_length_ratio": 12.0,
"dashed_lines_gap_length_ratio": 3.0,
"default_line_thickness": 6.0,
"default_text_size": 50.0,
"field_names": [],
"intersheets_ref_own_page": false,
"intersheets_ref_prefix": "",
"intersheets_ref_short": false,
"intersheets_ref_show": false,
"intersheets_ref_suffix": "",
"junction_size_choice": 3,
"label_size_ratio": 0.25,
"pin_symbol_size": 0.0,
"text_offset_ratio": 0.08
},
"legacy_lib_dir": "",
"legacy_lib_list": [],
"meta": {
"version": 1
},
"net_format_name": "Pcbnew",
"ngspice": {
"fix_include_paths": true,
"fix_passive_vals": false,
"meta": {
"version": 0
},
"model_mode": 0,
"workbook_filename": ""
},
"page_layout_descr_file": "",
"plot_directory": "",
"spice_adjust_passive_values": false,
"spice_current_sheet_as_root": false,
"spice_external_command": "spice \"%I\"",
"spice_model_current_sheet_as_root": true,
"spice_save_all_currents": false,
"spice_save_all_voltages": false,
"subpart_first_id": 65,
"subpart_id_separator": 0
},
"sheets": [
[
"fa1c3e0e-91b1-4532-8b22-6d396d71c75b",
""
]
],
"text_variables": {}
}

1852
pcb/DeskHop.kicad_sch Normal file

File diff suppressed because it is too large Load Diff

3641
pcb/Gerber/DeskHop-B_Cu.gbr Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Soldermask,Bot*%
%TF.FilePolarity,Negative*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%ADD10O,1.800000X1.800000*%
%ADD11O,1.500000X1.500000*%
%ADD12C,3.100000*%
%ADD13R,1.600000X1.500000*%
%ADD14C,1.600000*%
%ADD15C,3.000000*%
%ADD16R,1.700000X1.700000*%
%ADD17O,1.700000X1.700000*%
%ADD18C,5.400000*%
G04 APERTURE END LIST*
D10*
%TO.C,U1*%
X82619000Y-44834000D03*
D11*
X82919000Y-47864000D03*
X87769000Y-47864000D03*
D10*
X88069000Y-44834000D03*
%TD*%
%TO.C,U2*%
X46160523Y-44818249D03*
D11*
X46460523Y-47848249D03*
X51310523Y-47848249D03*
D10*
X51610523Y-44818249D03*
%TD*%
D12*
%TO.C,H3*%
X67310000Y-45212000D03*
%TD*%
D13*
%TO.C,J4*%
X45776000Y-101574000D03*
D14*
X48276000Y-101574000D03*
X50276000Y-101574000D03*
X52776000Y-101574000D03*
D15*
X42706000Y-104284000D03*
X55846000Y-104284000D03*
%TD*%
D16*
%TO.C,J3*%
X95667537Y-99795757D03*
D17*
X95667537Y-102335757D03*
X95667537Y-104875757D03*
%TD*%
D12*
%TO.C,H2*%
X96774000Y-111506000D03*
D18*
X96774000Y-111506000D03*
%TD*%
D13*
%TO.C,J1*%
X81082000Y-101574000D03*
D14*
X83582000Y-101574000D03*
X85582000Y-101574000D03*
X88082000Y-101574000D03*
D15*
X78012000Y-104284000D03*
X91152000Y-104284000D03*
%TD*%
D12*
%TO.C,H1*%
X37846000Y-111506000D03*
D18*
X37846000Y-111506000D03*
%TD*%
D16*
%TO.C,J2*%
X60731042Y-99717876D03*
D17*
X60731042Y-102257876D03*
X60731042Y-104797876D03*
%TD*%
M02*

View File

@ -0,0 +1,15 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Paste,Bot*%
%TF.FilePolarity,Positive*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 APERTURE END LIST*
M02*

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Profile,NP*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
%TA.AperFunction,Profile*%
%ADD10C,0.050000*%
%TD*%
G04 APERTURE END LIST*
D10*
X33773403Y-41085124D02*
X100689573Y-41085124D01*
X100689573Y-115566375D01*
X33773403Y-115566375D01*
X33773403Y-41085124D01*
M02*

8669
pcb/Gerber/DeskHop-F_Cu.gbr Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,230 @@
%TF.GenerationSoftware,KiCad,Pcbnew,7.0.9*%
%TF.CreationDate,2023-12-24T22:12:45+01:00*%
%TF.ProjectId,DeskHop,4465736b-486f-4702-9e6b-696361645f70,rev?*%
%TF.SameCoordinates,Original*%
%TF.FileFunction,Soldermask,Top*%
%TF.FilePolarity,Negative*%
%FSLAX46Y46*%
G04 Gerber Fmt 4.6, Leading zero omitted, Abs format (unit mm)*
G04 Created by KiCad (PCBNEW 7.0.9) date 2023-12-24 22:12:45*
%MOMM*%
%LPD*%
G01*
G04 APERTURE LIST*
G04 Aperture macros list*
%AMRoundRect*
0 Rectangle with rounded corners*
0 $1 Rounding radius*
0 $2 $3 $4 $5 $6 $7 $8 $9 X,Y pos of 4 corners*
0 Add a 4 corners polygon primitive as box body*
4,1,4,$2,$3,$4,$5,$6,$7,$8,$9,$2,$3,0*
0 Add four circle primitives for the rounded corners*
1,1,$1+$1,$2,$3*
1,1,$1+$1,$4,$5*
1,1,$1+$1,$6,$7*
1,1,$1+$1,$8,$9*
0 Add four rect primitives between the rounded corners*
20,1,$1+$1,$2,$3,$4,$5,0*
20,1,$1+$1,$4,$5,$6,$7,0*
20,1,$1+$1,$6,$7,$8,$9,0*
20,1,$1+$1,$8,$9,$2,$3,0*%
G04 Aperture macros list end*
%ADD10O,1.800000X1.800000*%
%ADD11O,1.500000X1.500000*%
%ADD12R,3.500000X1.700000*%
%ADD13R,1.700000X3.500000*%
%ADD14C,3.100000*%
%ADD15C,5.400000*%
%ADD16RoundRect,0.250000X-0.412500X-0.650000X0.412500X-0.650000X0.412500X0.650000X-0.412500X0.650000X0*%
%ADD17R,1.600000X1.500000*%
%ADD18C,1.600000*%
%ADD19C,3.000000*%
%ADD20R,1.700000X1.700000*%
%ADD21O,1.700000X1.700000*%
%ADD22RoundRect,0.150000X-0.825000X-0.150000X0.825000X-0.150000X0.825000X0.150000X-0.825000X0.150000X0*%
%ADD23RoundRect,0.250000X0.412500X0.650000X-0.412500X0.650000X-0.412500X-0.650000X0.412500X-0.650000X0*%
G04 APERTURE END LIST*
D10*
%TO.C,U1*%
X82619000Y-44834000D03*
D11*
X82919000Y-47864000D03*
X87769000Y-47864000D03*
D10*
X88069000Y-44834000D03*
D12*
X75554000Y-44704000D03*
X75554000Y-47244000D03*
X75554000Y-49784000D03*
X75554000Y-52324000D03*
X75554000Y-54864000D03*
X75554000Y-57404000D03*
X75554000Y-59944000D03*
X75554000Y-62484000D03*
X75554000Y-65024000D03*
X75554000Y-67564000D03*
X75554000Y-70104000D03*
X75554000Y-72644000D03*
X75554000Y-75184000D03*
X75554000Y-77724000D03*
X75554000Y-80264000D03*
X75554000Y-82804000D03*
X75554000Y-85344000D03*
X75554000Y-87884000D03*
X75554000Y-90424000D03*
X75554000Y-92964000D03*
X95134000Y-92964000D03*
X95134000Y-90424000D03*
X95134000Y-87884000D03*
X95134000Y-85344000D03*
X95134000Y-82804000D03*
X95134000Y-80264000D03*
X95134000Y-77724000D03*
X95134000Y-75184000D03*
X95134000Y-72644000D03*
X95134000Y-70104000D03*
X95134000Y-67564000D03*
X95134000Y-65024000D03*
X95134000Y-62484000D03*
X95134000Y-59944000D03*
X95134000Y-57404000D03*
X95134000Y-54864000D03*
X95134000Y-52324000D03*
X95134000Y-49784000D03*
X95134000Y-47244000D03*
X95134000Y-44704000D03*
D13*
X82804000Y-93634000D03*
X85344000Y-93634000D03*
X87884000Y-93634000D03*
%TD*%
D10*
%TO.C,U2*%
X46160523Y-44818249D03*
D11*
X46460523Y-47848249D03*
X51310523Y-47848249D03*
D10*
X51610523Y-44818249D03*
D12*
X39095523Y-44688249D03*
X39095523Y-47228249D03*
X39095523Y-49768249D03*
X39095523Y-52308249D03*
X39095523Y-54848249D03*
X39095523Y-57388249D03*
X39095523Y-59928249D03*
X39095523Y-62468249D03*
X39095523Y-65008249D03*
X39095523Y-67548249D03*
X39095523Y-70088249D03*
X39095523Y-72628249D03*
X39095523Y-75168249D03*
X39095523Y-77708249D03*
X39095523Y-80248249D03*
X39095523Y-82788249D03*
X39095523Y-85328249D03*
X39095523Y-87868249D03*
X39095523Y-90408249D03*
X39095523Y-92948249D03*
X58675523Y-92948249D03*
X58675523Y-90408249D03*
X58675523Y-87868249D03*
X58675523Y-85328249D03*
X58675523Y-82788249D03*
X58675523Y-80248249D03*
X58675523Y-77708249D03*
X58675523Y-75168249D03*
X58675523Y-72628249D03*
X58675523Y-70088249D03*
X58675523Y-67548249D03*
X58675523Y-65008249D03*
X58675523Y-62468249D03*
X58675523Y-59928249D03*
X58675523Y-57388249D03*
X58675523Y-54848249D03*
X58675523Y-52308249D03*
X58675523Y-49768249D03*
X58675523Y-47228249D03*
X58675523Y-44688249D03*
D13*
X46345523Y-93618249D03*
X48885523Y-93618249D03*
X51425523Y-93618249D03*
%TD*%
D14*
%TO.C,H3*%
X67310000Y-45212000D03*
D15*
X67310000Y-45212000D03*
%TD*%
D16*
%TO.C,C2*%
X69041691Y-74877665D03*
X72166691Y-74877665D03*
%TD*%
D17*
%TO.C,J4*%
X45776000Y-101574000D03*
D18*
X48276000Y-101574000D03*
X50276000Y-101574000D03*
X52776000Y-101574000D03*
D19*
X42706000Y-104284000D03*
X55846000Y-104284000D03*
%TD*%
D20*
%TO.C,J3*%
X95667537Y-99795757D03*
D21*
X95667537Y-102335757D03*
X95667537Y-104875757D03*
%TD*%
D14*
%TO.C,H2*%
X96774000Y-111506000D03*
D15*
X96774000Y-111506000D03*
%TD*%
D17*
%TO.C,J1*%
X81082000Y-101574000D03*
D18*
X83582000Y-101574000D03*
X85582000Y-101574000D03*
X88082000Y-101574000D03*
D19*
X78012000Y-104284000D03*
X91152000Y-104284000D03*
%TD*%
D14*
%TO.C,H1*%
X37846000Y-111506000D03*
D15*
X37846000Y-111506000D03*
%TD*%
D22*
%TO.C,U4*%
X64753070Y-82571110D03*
X64753070Y-83841110D03*
X64753070Y-85111110D03*
X64753070Y-86381110D03*
X69703070Y-86381110D03*
X69703070Y-85111110D03*
X69703070Y-83841110D03*
X69703070Y-82571110D03*
%TD*%
D20*
%TO.C,J2*%
X60731042Y-99717876D03*
D21*
X60731042Y-102257876D03*
X60731042Y-104797876D03*
%TD*%
D23*
%TO.C,C1*%
X65572536Y-74929999D03*
X62447536Y-74929999D03*
%TD*%
M02*

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,27 @@
M48
; DRILL file {KiCad 7.0.9} date Sunday, December 24, 2023 at 10:12:50PM
; FORMAT={-:-/ absolute / inch / decimal}
; #@! TF.CreationDate,2023-12-24T22:12:50+01:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.9
; #@! TF.FileFunction,NonPlated,1,2,NPTH
FMAT,2
INCH
; #@! TA.AperFunction,NonPlated,NPTH,ComponentDrill
T1C0.0591
; #@! TA.AperFunction,NonPlated,NPTH,ComponentDrill
T2C0.0709
%
G90
G05
T1
X1.8292Y-1.8838
X2.0201Y-1.8838
X3.2645Y-1.8844
X3.4555Y-1.8844
T2
X1.8173Y-1.7645
X2.0319Y-1.7645
X3.2527Y-1.7651
X3.4673Y-1.7651
T0
M30

View File

@ -0,0 +1,46 @@
M48
; DRILL file {KiCad 7.0.9} date Sunday, December 24, 2023 at 10:12:50PM
; FORMAT={-:-/ absolute / inch / decimal}
; #@! TF.CreationDate,2023-12-24T22:12:50+01:00
; #@! TF.GenerationSoftware,Kicad,Pcbnew,7.0.9
; #@! TF.FileFunction,Plated,1,2,PTH
FMAT,2
INCH
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T1C0.0374
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T2C0.0394
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T3C0.0906
; #@! TA.AperFunction,Plated,PTH,ComponentDrill
T4C0.1063
%
G90
G05
T1
X1.8022Y-3.999
X1.9006Y-3.999
X1.9794Y-3.999
X2.0778Y-3.999
X3.1922Y-3.999
X3.2906Y-3.999
X3.3694Y-3.999
X3.4678Y-3.999
T2
X2.391Y-3.9259
X2.391Y-4.0259
X2.391Y-4.1259
X3.7664Y-3.929
X3.7664Y-4.029
X3.7664Y-4.129
T3
X1.6813Y-4.1057
X2.1987Y-4.1057
X3.0713Y-4.1057
X3.5887Y-4.1057
T4
X1.49Y-4.39
X2.65Y-1.78
X3.81Y-4.39
T0
M30

View File

@ -0,0 +1,127 @@
{
"Header": {
"GenerationSoftware": {
"Vendor": "KiCad",
"Application": "Pcbnew",
"Version": "7.0.9"
},
"CreationDate": "2023-12-24T22:12:45+01:00"
},
"GeneralSpecs": {
"ProjectId": {
"Name": "DeskHop",
"GUID": "4465736b-486f-4702-9e6b-696361645f70",
"Revision": "rev?"
},
"Size": {
"X": 66.9662,
"Y": 74.5313
},
"LayerNumber": 2,
"BoardThickness": 1.6,
"Finish": "None"
},
"DesignRules": [
{
"Layers": "Outer",
"PadToPad": 0.0,
"PadToTrack": 0.0,
"TrackToTrack": 0.2,
"MinLineWidth": 0.5,
"TrackToRegion": 0.508,
"RegionToRegion": 0.508
}
],
"FilesAttributes": [
{
"Path": "DeskHop-F_Cu.gbr",
"FileFunction": "Copper,L1,Top",
"FilePolarity": "Positive"
},
{
"Path": "DeskHop-B_Cu.gbr",
"FileFunction": "Copper,L2,Bot",
"FilePolarity": "Positive"
},
{
"Path": "DeskHop-F_Paste.gbr",
"FileFunction": "SolderPaste,Top",
"FilePolarity": "Positive"
},
{
"Path": "DeskHop-B_Paste.gbr",
"FileFunction": "SolderPaste,Bot",
"FilePolarity": "Positive"
},
{
"Path": "DeskHop-F_Silkscreen.gbr",
"FileFunction": "Legend,Top",
"FilePolarity": "Positive"
},
{
"Path": "DeskHop-B_Silkscreen.gbr",
"FileFunction": "Legend,Bot",
"FilePolarity": "Positive"
},
{
"Path": "DeskHop-F_Mask.gbr",
"FileFunction": "SolderMask,Top",
"FilePolarity": "Negative"
},
{
"Path": "DeskHop-B_Mask.gbr",
"FileFunction": "SolderMask,Bot",
"FilePolarity": "Negative"
},
{
"Path": "DeskHop-Edge_Cuts.gbr",
"FileFunction": "Profile",
"FilePolarity": "Positive"
}
],
"MaterialStackup": [
{
"Type": "Legend",
"Name": "Top Silk Screen"
},
{
"Type": "SolderPaste",
"Name": "Top Solder Paste"
},
{
"Type": "SolderMask",
"Thickness": 0.01,
"Name": "Top Solder Mask"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "F.Cu"
},
{
"Type": "Dielectric",
"Thickness": 1.51,
"Material": "FR4",
"Name": "F.Cu/B.Cu",
"Notes": "Type: dielectric layer 1 (from F.Cu to B.Cu)"
},
{
"Type": "Copper",
"Thickness": 0.035,
"Name": "B.Cu"
},
{
"Type": "SolderMask",
"Thickness": 0.01,
"Name": "Bottom Solder Mask"
},
{
"Type": "SolderPaste",
"Name": "Bottom Solder Paste"
},
{
"Type": "Legend",
"Name": "Bottom Silk Screen"
}
]
}

62
pico_sdk_import.cmake Executable file
View File

@ -0,0 +1,62 @@
# 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 ()
set(PICO_SDK_PATH "${PICO_SDK_PATH}" CACHE PATH "Path to the Raspberry Pi Pico SDK")
set(PICO_SDK_FETCH_FROM_GIT "${PICO_SDK_FETCH_FROM_GIT}" CACHE BOOL "Set to ON to fetch copy of SDK from git if not otherwise locatable")
set(PICO_SDK_FETCH_FROM_GIT_PATH "${PICO_SDK_FETCH_FROM_GIT_PATH}" CACHE FILEPATH "location to download SDK")
if (NOT PICO_SDK_PATH)
if (PICO_SDK_FETCH_FROM_GIT)
include(FetchContent)
set(FETCHCONTENT_BASE_DIR_SAVE ${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 ()
FetchContent_Declare(
pico_sdk
GIT_REPOSITORY https://github.com/raspberrypi/pico-sdk
GIT_TAG master
)
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})
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_BINARY_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
schematics/DeskHop.pdf Normal file

Binary file not shown.

80
src/handlers.c Normal file
View File

@ -0,0 +1,80 @@
#include "main.h"
/**=================================================== *
* ============ Hotkey Handler Routines ============ *
* =================================================== */
void output_toggle_hotkey_handler(device_state_t* state) {
state->active_output ^= 1;
switch_output(state->active_output);
};
void fw_upgrade_hotkey_handler(device_state_t* state) {
send_value(ENABLE, FIRMWARE_UPGRADE_MSG);
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
};
void mouse_zoom_hotkey_handler(device_state_t* state) {
if (state->mouse_zoom)
return;
send_value(ENABLE, MOUSE_ZOOM_MSG);
state->mouse_zoom = true;
};
void all_keys_released_handler(device_state_t* state) {
if (state->mouse_zoom) {
state->mouse_zoom = false;
send_value(DISABLE, MOUSE_ZOOM_MSG);
}
};
/**==================================================== *
* ========== UART Message Handling Routines ======== *
* ==================================================== */
void handle_keyboard_uart_msg(uart_packet_t* packet, device_state_t* state) {
if (state->active_output == ACTIVE_OUTPUT_B) {
hid_keyboard_report_t* report = (hid_keyboard_report_t*)packet->data;
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, report->modifier, report->keycode);
state->last_activity[ACTIVE_OUTPUT_B] = time_us_64();
}
}
void handle_mouse_abs_uart_msg(uart_packet_t* packet, device_state_t* state) {
if (state->active_output == ACTIVE_OUTPUT_A) {
const hid_abs_mouse_report_t* mouse_report = (hid_abs_mouse_report_t*)packet->data;
tud_hid_abs_mouse_report(REPORT_ID_MOUSE, mouse_report->buttons, mouse_report->x,
mouse_report->y, mouse_report->wheel, 0);
state->last_activity[ACTIVE_OUTPUT_A] = time_us_64();
}
}
void handle_output_select_msg(uart_packet_t* packet, device_state_t* state) {
state->active_output = packet->data[0];
update_leds(state);
}
void handle_fw_upgrade_msg(void) {
reset_usb_boot(1 << PICO_DEFAULT_LED_PIN, 0);
}
void handle_mouse_zoom_msg(uart_packet_t* packet, device_state_t* state) {
state->mouse_zoom = packet->data[0];
}
void handle_set_report_msg(uart_packet_t* packet, device_state_t* state) {
// Only board B sends LED state through this message type
state->keyboard_leds[ACTIVE_OUTPUT_B] = packet->data[0];
update_leds(state);
}
// Update output variable, set LED on/off and notify the other board
void switch_output(uint8_t new_output) {
global_state.active_output = new_output;
update_leds(&global_state);
send_value(new_output, OUTPUT_SELECT_MSG);
}

101
src/keyboard.c Normal file
View File

@ -0,0 +1,101 @@
#include "main.h"
/* ==================================================== *
* Hotkeys to trigger actions via the keyboard
* ==================================================== */
hotkey_combo_t hotkeys[] = {
// Main keyboard switching hotkey
{.modifier = 0,
.keys = {HOTKEY_TOGGLE},
.key_count = 1,
.action_handler = &output_toggle_hotkey_handler},
// Holding down right ALT slows the mouse down
{.modifier = KEYBOARD_MODIFIER_RIGHTALT,
.keys = {},
.key_count = 0,
.action_handler = &mouse_zoom_hotkey_handler},
// Hold down left shift + right shift + P + H + X ==> firmware upgrade mode
{.modifier = KEYBOARD_MODIFIER_RIGHTSHIFT | KEYBOARD_MODIFIER_LEFTSHIFT,
.keys = {HID_KEY_P, HID_KEY_H, HID_KEY_X},
.key_count = 3,
.action_handler = &fw_upgrade_hotkey_handler}
};
/* ==================================================== *
* Parse and interpret the keys pressed on the keyboard
* ==================================================== */
bool no_keys_are_pressed(const hid_keyboard_report_t* report) {
if (report->modifier != 0)
return false;
for (int n = 0; n < KEYS_IN_USB_REPORT; n++) {
if (report->keycode[n] != 0)
return false;
}
return true;
}
void process_keyboard_report(uint8_t* raw_report, int length, device_state_t* state) {
hid_keyboard_report_t* keyboard_report = (hid_keyboard_report_t*)raw_report;
if (length < KBD_REPORT_LENGTH)
return;
// Go through the list of hotkeys, check if any are pressed, then execute their handler
for (int n = 0; n < sizeof(hotkeys) / sizeof(hotkeys[0]); n++) {
if (keypress_check(hotkeys[n], keyboard_report)) {
hotkeys[n].action_handler(state);
return;
}
}
// If no keys are pressed anymore, take care of checking and deactivating stuff
if (no_keys_are_pressed(keyboard_report)) {
all_keys_released_handler(state);
}
// If keys need to go to output B, send them through UART, otherwise send a HID report directly
if (state->active_output == ACTIVE_OUTPUT_B) {
send_packet(raw_report, KEYBOARD_REPORT_MSG, KBD_REPORT_LENGTH);
} else {
tud_hid_keyboard_report(REPORT_ID_KEYBOARD, keyboard_report->modifier,
keyboard_report->keycode);
state->last_activity[ACTIVE_OUTPUT_A] = time_us_64();
}
}
/* ============================================================ *
* Check if a specific key combination is present in the report
* ============================================================ */
bool keypress_check(hotkey_combo_t keypress, const hid_keyboard_report_t* report) {
int matches = 0;
// We expect all modifiers specified to be detected in the report
if (keypress.modifier != (report->modifier & keypress.modifier))
return false;
for (int n = 0; n < keypress.key_count; n++) {
for (int j = 0; j < KEYS_IN_USB_REPORT; j++) {
if (keypress.keys[n] == report->keycode[j]) {
matches++;
break;
}
}
// If any of the keys are not found, we can bail out early.
if (matches < n + 1) {
return false;
}
}
// Getting here means all of the keys were found.
return true;
}

12
src/led.c Normal file
View File

@ -0,0 +1,12 @@
#include "main.h"
/**==================================================== *
* ========== Update pico and keyboard LEDs ========== *
* ==================================================== */
void update_leds(device_state_t* state) {
gpio_put(GPIO_LED_PIN, state->active_output == BOARD_ROLE);
if (BOARD_ROLE == KEYBOARD_PICO_A)
pio_usb_kbd_set_leds(state->usb_device, 0, state->keyboard_leds[state->active_output]);
}

51
src/main.c Normal file
View File

@ -0,0 +1,51 @@
#include "main.h"
/********* Global Variable **********/
device_state_t global_state = {0};
/**================================================== *
* ============== Main Program Loops ============== *
* ================================================== */
void main(void) {
// Wait for the board to settle
sleep_ms(10);
global_state.usb_device = initial_setup();
// Initial state, A is the default output
switch_output(ACTIVE_OUTPUT_A);
while (true) {
// USB device task, needs to run as often as possible
tud_task();
// If we are not yet connected to the PC, don't bother with host
// If host task becomes too slow, move it to the second core
if (global_state.tud_connected) {
// Execute HOST task periodically
pio_usb_host_task();
// Query devices and handle reports
if (global_state.usb_device && global_state.usb_device->connected) {
check_endpoints(&global_state);
}
}
// Verify core1 is still running and if so, reset watchdog timer
kick_watchdog();
}
}
void core1_main() {
uart_packet_t in_packet = {0};
while (true) {
// Update the timestamp, so core0 can figure out if we're dead
global_state.core1_last_loop_pass = time_us_64();
receive_char(&in_packet, &global_state);
}
}
/* ======= End of Main Program Loops ======= */

196
src/main.h Normal file
View File

@ -0,0 +1,196 @@
#pragma once
#include "pico/stdlib.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "hardware/watchdog.h"
#include "pico/bootrom.h"
#include "pico/multicore.h"
#include "pico/stdlib.h"
#include "pio_usb.h"
#include "tusb.h"
#include "usb_descriptors.h"
#include "user_config.h"
/********* Misc definitions **********/
#define KEYBOARD_PICO_A 0
#define MOUSE_PICO_B 1
#define ACTIVE_OUTPUT_A 0
#define ACTIVE_OUTPUT_B 1
#define ENABLE 1
#define DISABLE 0
/********* Pinout definitions **********/
#define PIO_USB_DP_PIN 14 // D+ is pin 14, D- is pin 15
#define GPIO_LED_PIN 25 // LED is connected to pin 25 on a PICO
#if BOARD_ROLE == MOUSE_PICO_B
#define SERIAL_TX_PIN 16
#define SERIAL_RX_PIN 17
#elif BOARD_ROLE == KEYBOARD_PICO_A
#define SERIAL_TX_PIN 12
#define SERIAL_RX_PIN 13
#endif
/********* Serial port definitions **********/
#define SERIAL_UART uart0
#define SERIAL_BAUDRATE 115200
#define SERIAL_DATA_BITS 8
#define SERIAL_STOP_BITS 1
#define SERIAL_PARITY UART_PARITY_NONE
/********* Watchdog definitions **********/
#define WATCHDOG_TIMEOUT 500 // In milliseconds => needs to be reset every 500 ms
#define WATCHDOG_PAUSE_ON_DEBUG 1 // When using a debugger, disable watchdog
#define CORE1_HANG_TIMEOUT_US 500000 // In microseconds, wait up to 0.5s to declare core1 dead
/********* Protocol definitions *********
*
* - every packet starts with 0xAA 0x55 for easy re-sync
* - then a 1 byte packet type is transmitted
* - 8 bytes of packet data follows, fixed length for simplicity
* - 1 checksum byte ends the packet
* - checksum includes **only** the packet data
* - checksum is simply calculated by XORing all bytes together
*/
enum packet_type_e : uint8_t {
KEYBOARD_REPORT_MSG = 1,
MOUSE_REPORT_MSG = 2,
OUTPUT_SELECT_MSG = 3,
FIRMWARE_UPGRADE_MSG = 4,
MOUSE_ZOOM_MSG = 5,
KBD_SET_REPORT_MSG = 6,
};
/*
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Start1 | Start2 | Type | Packet data | Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| 1 | 1 | 1 | 8 | 1 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*/
/* Data structure defining packets of information transferred */
typedef struct {
uint8_t type; // Enum field describing the type of packet
uint8_t data[8]; // Data goes here (type + payload + checksum)
uint8_t checksum; // Checksum, a simple XOR-based one
} uart_packet_t;
/********* Packet parameters **********/
#define START1 0xAA
#define START2 0x55
#define START_LENGTH 2
#define TYPE_LENGTH 1
#define PACKET_DATA_LENGTH 8 // For simplicity, all packet types are the same length
#define CHECKSUM_LENGTH 1
#define PACKET_LENGTH (TYPE_LENGTH + PACKET_DATA_LENGTH + CHECKSUM_LENGTH)
#define RAW_PACKET_LENGTH (START_LENGTH + PACKET_LENGTH)
#define KEYS_IN_USB_REPORT 6
#define KBD_REPORT_LENGTH 8
#define MOUSE_REPORT_LENGTH 7
/********* Screen **********/
#define MAX_SCREEN_COORD 32767
// -------------------------------------------------------+
typedef void (*action_handler_t)();
typedef struct {
uint8_t modifier; // Which modifier is pressed
uint8_t keys[6]; // Which keys need to be pressed
uint8_t key_count; // How many keys are pressed
action_handler_t action_handler; // What to execute when the key combination is detected
} hotkey_combo_t;
typedef struct TU_ATTR_PACKED {
uint8_t buttons;
int16_t x;
int16_t y;
int8_t wheel;
int8_t pan;
} hid_abs_mouse_report_t;
typedef enum { IDLE, READING_PACKET, PROCESSING_PACKET } receiver_state_t;
typedef struct {
usb_device_t* usb_device; // USB device structure (keyboard or mouse)
uint8_t keyboard_leds[2]; // State of keyboard LEDs (index 0 = A, index 1 = B)
uint64_t last_activity[2]; // Timestamp of the last input activity (-||-)
receiver_state_t receiver_state; // Storing the state for the simple receiver state machine
uint64_t core1_last_loop_pass; // Timestamp of last core1 loop execution
uint8_t active_output; // Currently selected output (0 = A, 1 = B)
int16_t mouse_x; // Store and update the location of our mouse pointer
int16_t mouse_y;
bool tud_connected; // True when TinyUSB device successfully connects
bool mouse_zoom; // True when "mouse zoom" is enabled
} device_state_t;
/******* USB Host *********/
void process_mouse_report(uint8_t*, int, device_state_t*);
void check_endpoints(device_state_t* state);
/********* Setup **********/
usb_device_t* initial_setup(void);
void serial_init(void);
void core1_main(void);
/********* Keyboard **********/
bool keypress_check(hotkey_combo_t, const hid_keyboard_report_t*);
void process_keyboard_report(uint8_t*, int, device_state_t*);
/********* Mouse **********/
bool tud_hid_abs_mouse_report(uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal);
/********* UART **********/
void receive_char(uart_packet_t*, device_state_t*);
void send_packet(const uint8_t*, enum packet_type_e, int);
void send_value(const uint8_t, enum packet_type_e);
/********* LEDs **********/
void update_leds(device_state_t*);
/********* Checksum **********/
uint8_t calc_checksum(const uint8_t*, int);
bool verify_checksum(const uart_packet_t*);
/********* Watchdog **********/
void kick_watchdog(void);
/********* Handlers **********/
void output_toggle_hotkey_handler(device_state_t*);
void fw_upgrade_hotkey_handler(device_state_t*);
void mouse_zoom_hotkey_handler(device_state_t*);
void all_keys_released_handler(device_state_t*);
void handle_keyboard_uart_msg(uart_packet_t*, device_state_t*);
void handle_mouse_abs_uart_msg(uart_packet_t*, device_state_t*);
void handle_output_select_msg(uart_packet_t*, device_state_t*);
void handle_fw_upgrade_msg(void);
void handle_mouse_zoom_msg(uart_packet_t*, device_state_t*);
void handle_set_report_msg(uart_packet_t*, device_state_t*);
void switch_output(uint8_t);
/********* Global variables (don't judge) **********/
extern device_state_t global_state;

72
src/mouse.c Normal file
View File

@ -0,0 +1,72 @@
#include "main.h"
int get_mouse_offset(int8_t movement) {
// Holding a special hotkey enables mouse to slow down as much as possible
// when you need that extra precision
if (global_state.mouse_zoom)
return movement * MOUSE_SPEED_FACTOR >> 2;
else
return movement * MOUSE_SPEED_FACTOR;
}
void keep_cursor_on_screen(int16_t* position, const int8_t* movement) {
int16_t offset = get_mouse_offset(*movement);
// Lowest we can go is 0
if (*position + offset < 0)
*position = 0;
// Highest we can go is MAX_SCREEN_COORD
else if (*position + offset > MAX_SCREEN_COORD)
*position = MAX_SCREEN_COORD;
// We're still on screen, all good
else
*position += offset;
}
void check_mouse_switch(const hid_mouse_report_t* mouse_report, device_state_t* state) {
// End of screen right switches screen B->A
if ((state->mouse_x + mouse_report->x) > MAX_SCREEN_COORD &&
state->active_output == ACTIVE_OUTPUT_B) {
state->mouse_x = 0;
switch_output(ACTIVE_OUTPUT_A);
return;
}
// End of screen left switches screen A->B
if ((state->mouse_x + mouse_report->x) < 0 && state->active_output == ACTIVE_OUTPUT_A) {
state->mouse_x = MAX_SCREEN_COORD;
switch_output(ACTIVE_OUTPUT_B);
return;
}
}
void process_mouse_report(uint8_t* raw_report, int len, device_state_t* state) {
hid_mouse_report_t* mouse_report = (hid_mouse_report_t*)raw_report;
// We need to enforce the cursor doesn't go off-screen, that would be bad.
keep_cursor_on_screen(&state->mouse_x, &mouse_report->x);
keep_cursor_on_screen(&state->mouse_y, &mouse_report->y);
if (state->active_output == ACTIVE_OUTPUT_A) {
hid_abs_mouse_report_t abs_mouse_report;
abs_mouse_report.buttons = mouse_report->buttons;
abs_mouse_report.x = state->mouse_x;
abs_mouse_report.y = state->mouse_y;
abs_mouse_report.wheel = mouse_report->wheel;
abs_mouse_report.pan = 0;
send_packet((const uint8_t*)&abs_mouse_report, MOUSE_REPORT_MSG, MOUSE_REPORT_LENGTH);
} else {
tud_hid_abs_mouse_report(REPORT_ID_MOUSE, mouse_report->buttons, state->mouse_x,
state->mouse_y, mouse_report->wheel, 0);
state->last_activity[ACTIVE_OUTPUT_B] = time_us_64();
}
// We use the mouse to switch outputs, the logic is in check_mouse_switch()
check_mouse_switch(mouse_report, state);
}

79
src/setup.c Normal file
View File

@ -0,0 +1,79 @@
/**================================================== *
* ============= Initial Board Setup ============== *
* ================================================== */
#include "main.h"
/* ================================================== *
* Perform initial UART setup
* ================================================== */
void serial_init() {
/* Set up our UART with a default baudrate. */
uart_init(SERIAL_UART, SERIAL_BAUDRATE);
/* Set UART flow control CTS/RTS. We don't have these - turn them off.*/
uart_set_hw_flow(SERIAL_UART, false, false);
/* Set our data format */
uart_set_format(SERIAL_UART, SERIAL_DATA_BITS, SERIAL_STOP_BITS, SERIAL_PARITY);
/* Turn of CRLF translation */
uart_set_translate_crlf(SERIAL_UART, false);
/* We do want FIFO, will help us have fewer interruptions */
uart_set_fifo_enabled(SERIAL_UART, true);
/* Set the RX/TX pins, they differ based on the device role (A or B, check
/* schematics) */
gpio_set_function(SERIAL_TX_PIN, GPIO_FUNC_UART);
gpio_set_function(SERIAL_RX_PIN, GPIO_FUNC_UART);
}
/* ================================================== *
* PIO USB configuration, D+ pin 14, D- pin 15
* ================================================== */
usb_device_t* pio_usb_init(void) {
static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG;
config.pin_dp = 14;
config.alarm_pool = (void*)alarm_pool_create(2, 1);
return pio_usb_host_init(&config);
}
/* ================================================== *
* Perform initial board/usb setup
* ================================================== */
usb_device_t* initial_setup(void) {
/* PIO USB requires a clock multiple of 12 MHz, setting to 120 MHz */
set_sys_clock_khz(120000, true);
/* Init and enable the on-board LED GPIO as output */
gpio_init(GPIO_LED_PIN);
gpio_set_dir(GPIO_LED_PIN, GPIO_OUT);
/* Initialize and configure UART */
serial_init();
/* Initialize and configure TinyUSB */
tusb_init();
/* Setup RP2040 Core 1 */
multicore_reset_core1();
multicore_launch_core1(core1_main);
/* Initialize and configure PIO USB */
usb_device_t* pio_usb_device = pio_usb_init();
/* Update the core1 initial pass timestamp before enabling the watchdog */
global_state.core1_last_loop_pass = time_us_64();
/* Setup the watchdog so we reboot and recover from a crash */
watchdog_enable(WATCHDOG_TIMEOUT, WATCHDOG_PAUSE_ON_DEBUG);
return pio_usb_device;
}
/* ========== End of Initial Board Setup ========== */

111
src/tusb_config.h Normal file
View File

@ -0,0 +1,111 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#ifndef _TUSB_CONFIG_H_
#define _TUSB_CONFIG_H_
#ifdef __cplusplus
extern "C" {
#endif
//--------------------------------------------------------------------
// COMMON CONFIGURATION
//--------------------------------------------------------------------
// defined by board.mk
#ifndef CFG_TUSB_MCU
#error CFG_TUSB_MCU must be defined
#endif
// RHPort number used for device can be defined by board.mk, default to port 0
#ifndef BOARD_DEVICE_RHPORT_NUM
#define BOARD_DEVICE_RHPORT_NUM 0
#endif
// RHPort max operational speed can defined by board.mk
// Default to Highspeed for MCU with internal HighSpeed PHY (can be port specific), otherwise FullSpeed
#ifndef BOARD_DEVICE_RHPORT_SPEED
#if (CFG_TUSB_MCU == OPT_MCU_LPC18XX || CFG_TUSB_MCU == OPT_MCU_LPC43XX || CFG_TUSB_MCU == OPT_MCU_MIMXRT10XX || \
CFG_TUSB_MCU == OPT_MCU_NUC505 || CFG_TUSB_MCU == OPT_MCU_CXD56 || CFG_TUSB_MCU == OPT_MCU_SAMX7X)
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_HIGH_SPEED
#else
#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED
#endif
#endif
// Device mode with rhport and speed defined by board.mk
#if BOARD_DEVICE_RHPORT_NUM == 0
#define CFG_TUSB_RHPORT0_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#elif BOARD_DEVICE_RHPORT_NUM == 1
#define CFG_TUSB_RHPORT1_MODE (OPT_MODE_DEVICE | BOARD_DEVICE_RHPORT_SPEED)
#else
#error "Incorrect RHPort configuration"
#endif
#ifndef CFG_TUSB_OS
#define CFG_TUSB_OS OPT_OS_NONE
#endif
// CFG_TUSB_DEBUG is defined by compiler in DEBUG build
// #define CFG_TUSB_DEBUG 0
/* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment.
* Tinyusb use follows macros to declare transferring memory so that they can be put
* into those specific section.
* e.g
* - CFG_TUSB_MEM SECTION : __attribute__ (( section(".usb_ram") ))
* - CFG_TUSB_MEM_ALIGN : __attribute__ ((aligned(4)))
*/
#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
//--------------------------------------------------------------------
#ifndef CFG_TUD_ENDPOINT0_SIZE
#define CFG_TUD_ENDPOINT0_SIZE 64
#endif
//------------- CLASS -------------//
#define CFG_TUD_HID 1
#define CFG_TUD_CDC 0
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 0
// HID buffer size Should be sufficient to hold ID (if any) + Data
#define CFG_TUD_HID_EP_BUFSIZE 16
#ifdef __cplusplus
}
#endif
#endif /* _TUSB_CONFIG_H_ */

104
src/uart.c Normal file
View File

@ -0,0 +1,104 @@
#include "main.h"
/**================================================== *
* =============== Sending Packets ================ *
* ================================================== */
void send_packet(const uint8_t* data, enum packet_type_e packet_type, int length) {
uint8_t raw_packet[RAW_PACKET_LENGTH] = {[0] = START1,
[1] = START2,
[2] = packet_type,
/* [3-10] is data, defaults to 0 */
[11] = calc_checksum(data, length)};
if (length > 0)
memcpy(&raw_packet[START_LENGTH + TYPE_LENGTH], data, length);
uart_write_blocking(SERIAL_UART, raw_packet, RAW_PACKET_LENGTH);
}
void send_value(const uint8_t value, enum packet_type_e packet_type) {
const uint8_t data[8] = {[0] = value};
send_packet((uint8_t*)&data, packet_type, 1);
}
/**================================================== *
* =============== Parsing Packets ================ *
* ================================================== */
void process_packet(uart_packet_t* packet, device_state_t* state) {
if (!verify_checksum(packet)) {
return;
}
switch (packet->type) {
case KEYBOARD_REPORT_MSG:
handle_keyboard_uart_msg(packet, state);
break;
case MOUSE_REPORT_MSG:
handle_mouse_abs_uart_msg(packet, state);
break;
case OUTPUT_SELECT_MSG:
handle_output_select_msg(packet, state);
break;
case FIRMWARE_UPGRADE_MSG:
handle_fw_upgrade_msg();
break;
case MOUSE_ZOOM_MSG:
handle_mouse_zoom_msg(packet, state);
break;
case KBD_SET_REPORT_MSG:
handle_set_report_msg(packet, state);
break;
}
}
/**================================================== *
* ============== Receiving Packets =============== *
* ================================================== */
void receive_char(uart_packet_t* packet, device_state_t* state) {
uint8_t* raw_packet = (uint8_t*)packet;
static int count = 0;
switch (state->receiver_state) {
case IDLE:
if (uart_is_readable(SERIAL_UART)) {
raw_packet[0] = raw_packet[1]; // Remember the previous byte received
raw_packet[1] = uart_getc(SERIAL_UART); // ... and try to match packet start
// If we found 0xAA 0x55, we're in sync and can move on to read/process the packet
if (raw_packet[0] == START1 && raw_packet[1] == START2) {
state->receiver_state = READING_PACKET;
}
}
break;
case READING_PACKET:
if (uart_is_readable(SERIAL_UART)) {
raw_packet[count++] = uart_getc(SERIAL_UART);
// Check if a complete packet is received
if (count >= PACKET_LENGTH) {
state->receiver_state = PROCESSING_PACKET;
}
}
break;
case PROCESSING_PACKET:
process_packet(packet, state);
// Cleanup and return to IDLE when done
count = 0;
state->receiver_state = IDLE;
break;
}
}

90
src/usb.c Normal file
View File

@ -0,0 +1,90 @@
#include "main.h"
/**================================================== *
* ========== Query endpoints for reports ========= *
* ================================================== */
void check_endpoints(device_state_t* state) {
uint8_t raw_report[64];
// Iterate through all endpoints and check for data
for (int ep_idx = 0; ep_idx < PIO_USB_DEV_EP_CNT; ep_idx++) {
endpoint_t* ep = pio_usb_get_endpoint(state->usb_device, ep_idx);
if (ep == NULL) {
continue;
}
int len = pio_usb_get_in_data(ep, raw_report, sizeof(raw_report));
if (len > 0) {
if (BOARD_ROLE == KEYBOARD_PICO_A)
process_keyboard_report(raw_report, len, state);
else
process_mouse_report(raw_report, len, state);
}
}
}
/**================================================== *
* =========== TinyUSB Device Callbacks =========== *
* ================================================== */
uint16_t tud_hid_get_report_cb(uint8_t instance,
uint8_t report_id,
hid_report_type_t report_type,
uint8_t* buffer,
uint16_t reqlen) {
return 0;
}
/**
* Computer controls our LEDs by sending USB SetReport messages with a payload
* of just 1 byte and report type output. It's type 0x21 (USB_REQ_DIR_OUT |
* USB_REQ_TYP_CLASS | USB_REQ_REC_IFACE) Request code for SetReport is 0x09,
* report type is 0x02 (HID_REPORT_TYPE_OUTPUT). We get a set_report callback
* from TinyUSB device HID and then figure out what to do with the LEDs.
*/
void tud_hid_set_report_cb(uint8_t instance,
uint8_t report_id,
hid_report_type_t report_type,
uint8_t const* buffer,
uint16_t bufsize) {
if (report_id == REPORT_ID_KEYBOARD && bufsize == 1 && report_type == HID_REPORT_TYPE_OUTPUT) {
/**
* If we are using caps lock LED to indicate the chosen output, we will
* override whatever is sent through the SetReport message.
*/
uint8_t leds = buffer[0];
if (KBD_LED_AS_INDICATOR) {
leds = leds & 0xFD; // 1111 1101 (Clear Caps Lock bit)
if (global_state.active_output)
leds |= KEYBOARD_LED_CAPSLOCK;
}
global_state.keyboard_leds[global_state.active_output] = leds;
// If we are board B, we need to set this information to the other one since that one
// has the keyboard connected to it (and LEDs you can turn on :-))
if (BOARD_ROLE == MOUSE_PICO_B)
send_value(leds, KBD_SET_REPORT_MSG);
// If we are board A, update LEDs directly
else
update_leds(&global_state);
}
}
// Invoked when device is mounted
void tud_mount_cb(void)
{
global_state.tud_connected = true;
}
// Invoked when device is unmounted
void tud_umount_cb(void)
{
global_state.tud_connected = false;
}

237
src/usb_descriptors.c Normal file
View File

@ -0,0 +1,237 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
*/
#include "main.h"
#include "usb_descriptors.h"
#include "tusb.h"
//--------------------------------------------------------------------+
// Device Descriptors
//--------------------------------------------------------------------+
tusb_desc_device_t const desc_device = {.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.idVendor = 0x1209,
.idProduct = 0x0007, // Project will apply for its own PID
.bcdDevice = 0x0100,
.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,
.bNumConfigurations = 0x01};
// Invoked when received GET DEVICE DESCRIPTOR
// Application return pointer to descriptor
uint8_t const* tud_descriptor_device_cb(void) {
return (uint8_t const*)&desc_device;
}
//--------------------------------------------------------------------+
// HID Report Descriptor
//--------------------------------------------------------------------+
uint8_t const desc_hid_report[] = {TUD_HID_REPORT_DESC_KEYBOARD(HID_REPORT_ID(REPORT_ID_KEYBOARD)),
TUD_HID_REPORT_DESC_ABSMOUSE(HID_REPORT_ID(REPORT_ID_MOUSE))};
// Invoked when received GET HID REPORT DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const* tud_hid_descriptor_report_cb(uint8_t instance) {
(void)instance;
return desc_hid_report;
}
bool tud_hid_n_abs_mouse_report(uint8_t instance,
uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal) {
hid_abs_mouse_report_t report = {
.buttons = buttons, .x = x, .y = y, .wheel = vertical, .pan = horizontal};
return tud_hid_n_report(instance, report_id, &report, sizeof(report));
}
bool tud_hid_abs_mouse_report(uint8_t report_id,
uint8_t buttons,
int16_t x,
int16_t y,
int8_t vertical,
int8_t horizontal) {
return tud_hid_n_abs_mouse_report(0, report_id, buttons, x, y, vertical, horizontal);
}
//--------------------------------------------------------------------+
// Configuration Descriptor
//--------------------------------------------------------------------+
enum { ITF_NUM_HID, ITF_NUM_TOTAL };
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_HID_DESC_LEN)
#define EPNUM_HID 0x81
uint8_t const desc_configuration[] = {
// Config number, interface count, string index, total length, attribute, power in mA
TUD_CONFIG_DESCRIPTOR(1,
ITF_NUM_TOTAL,
0,
CONFIG_TOTAL_LEN,
TUSB_DESC_CONFIG_ATT_REMOTE_WAKEUP,
100),
// Interface number, string index, protocol, report descriptor len, EP In address, size &
// polling interval
TUD_HID_DESCRIPTOR(ITF_NUM_HID,
0,
HID_ITF_PROTOCOL_NONE,
sizeof(desc_hid_report),
EPNUM_HID,
CFG_TUD_HID_EP_BUFSIZE,
5)};
#if TUD_OPT_HIGH_SPEED
// Per USB specs: high speed capable device must report device_qualifier and
// other_speed_configuration
// other speed configuration
uint8_t desc_other_speed_config[CONFIG_TOTAL_LEN];
// device qualifier is mostly similar to device descriptor since we don't change configuration based
// on speed
tusb_desc_device_qualifier_t const desc_device_qualifier = {
.bLength = sizeof(tusb_desc_device_qualifier_t),
.bDescriptorType = TUSB_DESC_DEVICE_QUALIFIER,
.bcdUSB = USB_BCD,
.bDeviceClass = 0x00,
.bDeviceSubClass = 0x00,
.bDeviceProtocol = 0x00,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
.bNumConfigurations = 0x01,
.bReserved = 0x00};
// Invoked when received GET DEVICE QUALIFIER DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete. device_qualifier descriptor describes information about a high-speed capable device
// that would change if the device were operating at the other speed. If not highspeed capable stall
// this request.
uint8_t const* tud_descriptor_device_qualifier_cb(void) {
return (uint8_t const*)&desc_device_qualifier;
}
// Invoked when received GET OTHER SEED CONFIGURATION DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete Configuration descriptor in the other speed e.g if high speed then this is for full
// speed and vice versa
uint8_t const* tud_descriptor_other_speed_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
// other speed config is basically configuration with type = OHER_SPEED_CONFIG
memcpy(desc_other_speed_config, desc_configuration, CONFIG_TOTAL_LEN);
desc_other_speed_config[1] = TUSB_DESC_OTHER_SPEED_CONFIG;
// this example use the same configuration for both high and full speed mode
return desc_other_speed_config;
}
#endif // highspeed
// Invoked when received GET CONFIGURATION DESCRIPTOR
// Application return pointer to descriptor
// Descriptor contents must exist long enough for transfer to complete
uint8_t const* tud_descriptor_configuration_cb(uint8_t index) {
(void)index; // for multiple configurations
// This example use the same configuration for both high and full speed mode
return desc_configuration;
}
//--------------------------------------------------------------------+
// String Descriptors
//--------------------------------------------------------------------+
// String Descriptor Index
enum {
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
};
// array of pointer to string descriptors
char const* string_desc_arr[] = {
(const char[]){0x09, 0x04}, // 0: is supported language is English (0x0409)
"Hrvoje Cavrak", // 1: Manufacturer
"DeskHop Switch", // 2: Product
"31415926535", // 3: Serials, should use chip ID
};
static uint16_t _desc_str[32];
// Invoked when received GET STRING DESCRIPTOR request
// Application return pointer to descriptor, whose contents must exist long enough for transfer to
// complete
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 {
// 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
if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0])))
return NULL;
const char* str = string_desc_arr[index];
// Cap at max char
chr_count = strlen(str);
if (chr_count > 31)
chr_count = 31;
// Convert ASCII string into UTF-16
for (uint8_t i = 0; i < chr_count; i++) {
_desc_str[1 + i] = str[i];
}
}
// first byte is length (including header), second byte is string type
_desc_str[0] = (TUSB_DESC_STRING << 8) | (2 * chr_count + 2);
return _desc_str;
}

86
src/usb_descriptors.h Normal file
View File

@ -0,0 +1,86 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Ha Thach (tinyusb.org)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#ifndef USB_DESCRIPTORS_H_
#define USB_DESCRIPTORS_H_
enum
{
REPORT_ID_KEYBOARD = 1,
REPORT_ID_MOUSE,
REPORT_ID_CONSUMER_CONTROL,
REPORT_ID_GAMEPAD,
REPORT_ID_COUNT
};
#define TUD_HID_REPORT_DESC_ABSMOUSE(...) \
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_MOUSE ) ,\
HID_COLLECTION ( HID_COLLECTION_APPLICATION ) ,\
/* Report ID if any */\
__VA_ARGS__ \
HID_USAGE ( HID_USAGE_DESKTOP_POINTER ) ,\
HID_COLLECTION ( HID_COLLECTION_PHYSICAL ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_BUTTON ) ,\
HID_USAGE_MIN ( 1 ) ,\
HID_USAGE_MAX ( 5 ) ,\
HID_LOGICAL_MIN ( 0 ) ,\
HID_LOGICAL_MAX ( 1 ) ,\
/* Left, Right, Middle, Backward, Forward buttons */ \
HID_REPORT_COUNT( 5 ) ,\
HID_REPORT_SIZE ( 1 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
/* 3 bit padding */ \
HID_REPORT_COUNT( 1 ) ,\
HID_REPORT_SIZE ( 3 ) ,\
HID_INPUT ( HID_CONSTANT ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_DESKTOP ) ,\
/* X, Y absolute position [0, 32767] */ \
HID_USAGE ( HID_USAGE_DESKTOP_X ) ,\
HID_USAGE ( HID_USAGE_DESKTOP_Y ) ,\
HID_LOGICAL_MIN ( 0x00 ) ,\
HID_LOGICAL_MAX_N( 0x7FFF, 2 ) ,\
HID_REPORT_SIZE ( 16 ) ,\
HID_REPORT_COUNT ( 2 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_ABSOLUTE ) ,\
/* Vertical wheel scroll [-127, 127] */ \
HID_USAGE ( HID_USAGE_DESKTOP_WHEEL ) ,\
HID_LOGICAL_MIN ( 0x81 ) ,\
HID_LOGICAL_MAX ( 0x7f ) ,\
HID_REPORT_COUNT( 1 ) ,\
HID_REPORT_SIZE ( 8 ) ,\
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ) ,\
HID_USAGE_PAGE ( HID_USAGE_PAGE_CONSUMER ), \
/* Horizontal wheel scroll [-127, 127] */ \
HID_USAGE_N ( HID_USAGE_CONSUMER_AC_PAN, 2 ), \
HID_LOGICAL_MIN ( 0x81 ), \
HID_LOGICAL_MAX ( 0x7f ), \
HID_REPORT_COUNT( 1 ), \
HID_REPORT_SIZE ( 8 ), \
HID_INPUT ( HID_DATA | HID_VARIABLE | HID_RELATIVE ), \
HID_COLLECTION_END , \
HID_COLLECTION_END \
#endif /* USB_DESCRIPTORS_H_ */

41
src/user_config.h Normal file
View File

@ -0,0 +1,41 @@
/**===================================================== *
* ========== Keyboard LED Output Indicator ========== *
* ===================================================== *
*
* If you are willing to give up on using the keyboard LEDs for their original purpose,
* you can use them as a convenient way to indicate which output is selected.
*
* KBD_LED_AS_INDICATOR set to 0 will use the keyboard LEDs as normal.
* KBD_LED_AS_INDICATOR set to 1 will use the Caps Lock LED as indicator.
*
* */
#define KBD_LED_AS_INDICATOR 1
/**===================================================== *
* =========== Hotkey for output switching =========== *
* ===================================================== *
*
* Everyone is different, I prefer to use caps lock because I HATE SHOUTING :)
* You might prefer something else. Pick something from the list found at:
*
* https://github.com/hathach/tinyusb/blob/master/src/class/hid/hid.h
*
* defined as HID_KEY_<something>
*
* */
#define HOTKEY_TOGGLE HID_KEY_CAPS_LOCK
/**================================================== *
* ============== Mouse Speed Factor ============== *
* ==================================================
*
* This affects how fast the mouse moves.
*
* MOUSE_SPEED_FACTOR: [1-128], higher values will make very little sense,
* 16 works well for my mouse, but the option to adjust is here if you need it.
*
* */
#define MOUSE_SPEED_FACTOR 16

37
src/utils.c Normal file
View File

@ -0,0 +1,37 @@
#include "main.h"
/**================================================== *
* ============== Checksum Functions ============== *
* ================================================== */
uint8_t calc_checksum(const uint8_t* data, int length) {
uint8_t checksum = 0;
for (int i = 0; i < length; i++) {
checksum ^= data[i];
}
return checksum;
}
bool verify_checksum(const uart_packet_t* packet) {
uint8_t checksum = calc_checksum(packet->data, PACKET_DATA_LENGTH);
return checksum == packet->checksum;
}
/**================================================== *
* ============== Watchdog Functions ============== *
* ================================================== */
void kick_watchdog(void) {
// Read the timer AFTER duplicating the core1 timestamp,
// so it doesn't get updated in the meantime.
uint64_t core1_last_loop_pass = global_state.core1_last_loop_pass;
uint64_t current_time = time_us_64();
// If core1 stops updating the timestamp, we'll stop kicking the watchog and reboot
if (current_time - core1_last_loop_pass < CORE1_HANG_TIMEOUT_US)
watchdog_update();
}