First prototype

This commit is contained in:
sekigon-gonnoc 2021-12-18 23:32:54 +09:00
parent c4c96cc03c
commit 4d952cb1d4
13 changed files with 1742 additions and 0 deletions

5
.gitignore vendored Normal file
View file

@ -0,0 +1,5 @@
**/build/
tool/
.cache/
.vscode/

View file

@ -1,2 +1,45 @@
# Pico-PIO-USB
USB FS host using PIO of raspberry pi pico (RP2040).
You can add additional USB port to RP2040.
🚧 **This library is WIP. API may be changed drastically in future.** 🚧
## Demo
https://user-images.githubusercontent.com/43873124/146642806-bdf34af6-4342-4a95-bfca-229cdc4bdca2.mp4
## Project status
|Planned Features|Status|
|-|-|
|FS Host|✔|
|LS Host|🚧|
|Hub support|🚧|
|FS Device|🚧|
## Examples
`capture_hid_report.c` is a sample program which print HID reports received from device.
```bash
cd example
mkdir build
cd build
cmake ..
make
# Copy UF2 file to RPiPico and open serial port
```
Another sample program for split keyboard with QMK
[https://github.com/sekigon-gonnoc/qmk_firmware/tree/rp2040/keyboards/pico_pico_usb](https://github.com/sekigon-gonnoc/qmk_firmware/tree/rp2040/keyboards/pico_pico_usb)
## Resource Usage
- Two PIO
- One PIO is for USB transmitter using 22 instruction and one state machine
- Another PIO is for USB receiver using 32 instruction (No space for other program)
- Two GPIO for D+/D- (Series 22ohm resitors are better)
- 10KB ROM and RAM

22
example/CMakeLists.txt Executable file
View file

@ -0,0 +1,22 @@
cmake_minimum_required(VERSION 3.13)
include (pico_sdk_import.cmake)
project(pio_usb)
pico_sdk_init()
set(target_name capture_hid_report)
add_executable(${target_name})
pico_enable_stdio_usb(${target_name} 1)
pico_generate_pio_header(${target_name} ${CMAKE_CURRENT_LIST_DIR}/../usb_tx.pio)
pico_generate_pio_header(${target_name} ${CMAKE_CURRENT_LIST_DIR}/../usb_rx.pio)
target_sources(${target_name} PRIVATE capture_hid_report.c ../pio_usb.c ../usb_crc.c)
target_link_options(${target_name} PRIVATE -Xlinker --print-memory-usage)
target_compile_options(${target_name} PRIVATE -Wall -Wextra)
target_include_directories(${target_name} PRIVATE ../)
target_link_libraries(${target_name} PRIVATE pico_stdlib pico_multicore hardware_pio hardware_dma)
pico_add_extra_outputs(${target_name})

View file

@ -0,0 +1,64 @@
#include <stdio.h>
#include <string.h>
#include "pico/stdlib.h"
#include "pico/multicore.h"
#include "pico/bootrom.h"
#include "pio_usb.h"
static usb_device_t *usb_device = NULL;
void core1_main() {
sleep_ms(10);
// To run USB SOF interrupt in core1, create alarm pool in core1.
static pio_usb_configuration_t config = PIO_USB_DEFAULT_CONFIG;
config.alarm_pool = (void*)alarm_pool_create(2, 1);
usb_device = pio_usb_init(&config);
while (true) {
pio_usb_task();
}
}
int main() {
// default 125MHz is not appropreate. Sysclock should be multiple of 12MHz.
set_sys_clock_khz(120000, true);
stdio_init_all();
printf("hello!");
sleep_ms(10);
multicore_reset_core1();
// all USB task run in core1
multicore_launch_core1(core1_main);
while (true) {
if (usb_device != NULL && usb_device->enumerated) {
// Print received packet to EPs
for (int ep_idx = 0; ep_idx < PIO_USB_EP_CNT; ep_idx++) {
endpoint_t *ep = &usb_device->endpoint[ep_idx];
if ((ep->ep_num & EP_IN) && ep->new_data_flag) {
// Copy received data to local buffer
uint8_t temp[64], len;
len = ep->packet_len;
memcpy(temp, (const void*)ep->buffer, len);
// notify to USB task
ep->new_data_flag = false;
printf("%04x:%04x EP 0x%02x:\t", usb_device->vid, usb_device->pid,
ep->ep_num);
for (size_t i = 0; i < len; i++) {
printf("%02x ", temp[i]);
}
printf("\n");
}
}
}
sleep_us(10);
}
}

62
example/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})

898
pio_usb.c Normal file
View file

@ -0,0 +1,898 @@
/**
* 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/multicore.h"
#include "pico/stdlib.h"
#include "pio_usb.h"
#include "usb_definitions.h"
#include "usb_crc.h"
#include "usb_tx.pio.h"
#include "usb_rx.pio.h"
#define UNUSED_PARAMETER(x) (void)x
#define IRQ_TX_COMP_MASK (1 << usb_tx_IRQ_COMP)
#define IRQ_RX_COMP_MASK (1 << IRQ_RX_EOP)
#define IRQ_RX_ALL_MASK ((1 << IRQ_RX_EOP) | (1 << IRQ_RX_BS_ERR))
PIO pio_usb_tx; // colud not set to static
static volatile uint sm_tx;
static volatile uint tx_ch;
PIO pio_usb_rx; // colud not set to static
static volatile uint sm_rx;
static volatile uint offset_rx;
static volatile uint offset_eop;
static volatile uint rx_reset_instr;
static volatile uint pin_dp;
static volatile uint pin_dm;
static usb_device_t usb_device;
static usb_setup_packet_t get_device_descriptor_report = GET_DEVICE_DESCRIPTOR_REQ_DEFAULT;
static usb_setup_packet_t get_configuration_descriptor_report = GET_CONFIGURATION_DESCRIPTOR_REQ_DEFAULT;
static usb_setup_packet_t set_usb_configuration_report = SET_CONFIGURATION_REQ_DEFAULT;
static usb_setup_packet_t set_address_request = SET_ADDRESS_REQ_DEFAULT;
static usb_setup_packet_t get_hid_report_descrpitor_request = GET_HID_REPORT_DESCRIPTOR_DEFAULT;
static usb_setup_packet_t set_hid_idle_request = SET_HID_IDLE_REQ_DEFAULT;
static uint8_t usb_rx_buffer[128];
static void __not_in_flash_func(usb_transfer)(uint8_t *data, uint16_t len) {
dma_channel_transfer_from_buffer_now(tx_ch, data, len);
pio_sm_set_enabled(pio_usb_tx, sm_tx, true);
pio0->irq |= IRQ_TX_COMP_MASK; // clear complete flag
while ((pio0->irq & IRQ_TX_COMP_MASK) == 0) {
continue;
}
pio_sm_set_enabled(pio_usb_tx, sm_tx, false);
dma_channel_abort(tx_ch);
}
void __no_inline_not_in_flash_func(send_setup_packet)(uint8_t addr, uint8_t ep_num) {
static uint8_t packet[] = {USB_SYNC, USB_PID_SETUP, 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);
usb_transfer(packet, sizeof(packet));
}
void __no_inline_not_in_flash_func(send_out_token)(uint8_t addr, uint8_t ep_num) {
static uint8_t packet[] = {USB_SYNC, USB_PID_OUT, 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);
usb_transfer(packet, sizeof(packet));
}
static void __no_inline_not_in_flash_func(send_ack)(void) {
static uint8_t data[] = {USB_SYNC, USB_PID_ACK};
// usb_transfer(data, sizeof(data));
dma_channel_transfer_from_buffer_now(tx_ch, data, 2);
pio_sm_set_enabled(pio_usb_tx, sm_tx, true);
pio0->irq |= IRQ_TX_COMP_MASK; // clear complete flag
while ((pio0->irq & IRQ_TX_COMP_MASK) == 0) {
continue;
}
pio_sm_set_enabled(pio_usb_tx, sm_tx, false);
dma_channel_abort(tx_ch);
}
void __no_inline_not_in_flash_func(send_nak)(void) {
uint8_t data[] = {USB_SYNC, USB_PID_NAK};
usb_transfer(data, sizeof(data));
}
static void __no_inline_not_in_flash_func(prepare_receive)(void) {
pio_sm_set_enabled(pio_usb_rx, sm_rx, false);
pio_sm_clear_fifos(pio_usb_rx, sm_rx);
pio_sm_restart(pio_usb_rx, sm_rx);
pio_sm_exec(pio_usb_rx, sm_rx, rx_reset_instr);
}
static void __no_inline_not_in_flash_func(start_receive)(void) {
pio_usb_rx->ctrl |= (1 << sm_rx);
pio_usb_rx->irq |= IRQ_RX_ALL_MASK;
}
void __no_inline_not_in_flash_func(data_transfer)(uint8_t * tx_data_address, uint8_t tx_data_len)
{
prepare_receive();
usb_transfer(tx_data_address, tx_data_len);
start_receive();
}
void __no_inline_not_in_flash_func(control_setup_transfer)(uint8_t * tx_data_address, uint8_t tx_data_len)
{
prepare_receive();
send_setup_packet(usb_device.address, 0);
usb_transfer(tx_data_address, tx_data_len);
start_receive();
}
void __no_inline_not_in_flash_func(control_out_transfer)(uint8_t * tx_data_address, uint8_t tx_data_len)
{
prepare_receive();
send_out_token(usb_device.address, 0);
usb_transfer(tx_data_address, tx_data_len);
start_receive();
}
packet_info_t __no_inline_not_in_flash_func(calc_in_token)(uint8_t addr, uint8_t ep_num) {
static uint8_t packet[] = {USB_SYNC, USB_PID_IN, 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);
packet_info_t packet_info = {.tx_address = packet, .tx_length = sizeof(packet)};
return packet_info;
}
static void __no_inline_not_in_flash_func(wait_handshake)(void) {
int16_t t = 120;
int16_t idx = 0;
while (t--) {
if (pio_sm_get_rx_fifo_level(pio_usb_rx, sm_rx)) {
uint8_t data = pio_sm_get(pio_usb_rx, sm_rx) >> 24;
usb_rx_buffer[idx++] = data;
if (idx == 2) {
break;
}
}
}
if (t > 0) {
while ((pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
continue;
}
}
pio_sm_set_enabled(pio_usb_rx, sm_rx, false);
}
static int __no_inline_not_in_flash_func(usb_out_transaction)(uint8_t addr, endpoint_t * ep) {
int res = -1;
prepare_receive();
send_out_token(addr, ep->ep_num & 0xf);
usb_transfer((uint8_t*)ep->buffer, ep->packet_len);
start_receive();
wait_handshake();
if (usb_rx_buffer[1] == USB_PID_ACK) {
res = 0;
ep->data_id ^= 1;
ep->new_data_flag = false;
} else {
res = -1;
}
pio_sm_set_enabled(pio_usb_rx, sm_rx, false);
usb_rx_buffer[0] = 0;
usb_rx_buffer[1] = 0;
return res;
}
static int __no_inline_not_in_flash_func(receive_packet_and_ack)(void) {
uint16_t crc = 0xffff;
uint16_t crc_prev = 0xffff;
uint16_t crc_prev2 = 0xffff;
uint16_t crc_receive = 0xffff;
bool crc_match = false;
int16_t t = 120;
uint16_t idx = 0;
while (t--) {
if (pio_sm_get_rx_fifo_level(pio_usb_rx, sm_rx)) {
uint8_t data = pio_sm_get(pio_usb_rx, sm_rx) >> 24;
usb_rx_buffer[idx++] = data;
if (idx == 2) {
break;
}
}
}
// timing critical start
if (t > 0) {
while ((pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
if (pio_sm_get_rx_fifo_level(pio_usb_rx, sm_rx)) {
uint8_t data = pio_sm_get(pio_usb_rx, sm_rx) >> 24;
crc_prev2 = crc_prev;
crc_prev = crc;
crc = update_usb_crc16(crc, data);
usb_rx_buffer[idx++] = data;
crc_receive = (crc_receive >> 8) | (data << 8);
crc_match = ((crc_receive ^ 0xffff) == crc_prev2);
}
}
}
if (idx >= 4 && crc_match) {
send_ack();
// timing critical end
return idx - 4;
}
return -1;
}
static int __no_inline_not_in_flash_func(usb_in_transaction)(uint8_t addr, endpoint_t * ep) {
int res = -1;
uint8_t expect_token = ep->data_id == 0 ? USB_PID_DATA0 : USB_PID_DATA1;
packet_info_t in_token;
in_token = calc_in_token(addr, ep->ep_num & 0x0f);
data_transfer(in_token.tx_address, in_token.tx_length);
int receive_len = receive_packet_and_ack();
if (receive_len >= 0) {
if (usb_rx_buffer[1] == expect_token) {
memcpy((void *)(ep->buffer), &usb_rx_buffer[2], receive_len);
ep->packet_len = receive_len;
ep->data_id ^= 1;
ep->new_data_flag = true;
}
res = 0;
} else {
res = -1;
if ((pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
res = -2;
}
}
pio_sm_set_enabled(pio_usb_rx, sm_rx, false);
usb_rx_buffer[0] = 0;
usb_rx_buffer[1] = 0;
return res;
}
static int __no_inline_not_in_flash_func(usb_control_out_transfer)(control_pipe_t * pipe) {
int res = 0;
if (pipe->stage == STAGE_SETUP) {
control_setup_transfer(pipe->setup_packet.tx_address, pipe->setup_packet.tx_length);
wait_handshake();
if (usb_rx_buffer[0] == USB_SYNC && usb_rx_buffer[1] == USB_PID_ACK) {
if (pipe->out_data_packet.tx_address != NULL) {
pipe->stage = STAGE_OUT;
} else {
pipe->stage = STAGE_STATUS;
}
usb_rx_buffer[1] = 0; // reset buffer
}
} else if (pipe->stage == STAGE_OUT) {
control_out_transfer(pipe->out_data_packet.tx_address,
pipe->out_data_packet.tx_length);
wait_handshake();
if (usb_rx_buffer[0] == USB_SYNC && usb_rx_buffer[1] == USB_PID_ACK) {
pipe->stage = STAGE_STATUS;
}
usb_rx_buffer[1] = 0;
} else if (pipe->stage == STAGE_STATUS) {
packet_info_t in_token;
in_token = calc_in_token(usb_device.address, 0);
data_transfer(in_token.tx_address, in_token.tx_length);
volatile int16_t t = 120;
volatile int16_t idx = 0;
while (t--) {
if (pio_sm_get_rx_fifo_level(pio_usb_rx, sm_rx)) {
usb_rx_buffer[idx++] = pio_sm_get(pio_usb_rx, sm_rx) >> 24;
if (idx == 2) {
break;
}
}
}
if (t > 0) {
while ((pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
if (pio_sm_get_rx_fifo_level(pio_usb_rx, sm_rx)) {
usb_rx_buffer[idx++] = pio_sm_get(pio_usb_rx, sm_rx) >> 24;
}
}
if (usb_rx_buffer[1] == USB_PID_DATA1) {
send_ack();
pipe->stage = STAGE_COMPLETE;
}
}
pio_sm_set_enabled(pio_usb_rx, sm_rx, false);
usb_rx_buffer[1] = 0;
}
return res;
}
static uint8_t __no_inline_not_in_flash_func(usb_control_in_transfer)(control_pipe_t * pipe) {
static uint8_t data_in_num = 1;
static int16_t buffer_idx = 0;
uint8_t expect_token = data_in_num == 1 ? USB_PID_DATA1 : USB_PID_DATA0;
if (pipe->stage == STAGE_SETUP) {
control_setup_transfer(pipe->setup_packet.tx_address, pipe->setup_packet.tx_length);
wait_handshake();
if (usb_rx_buffer[0] == USB_SYNC && usb_rx_buffer[1] == USB_PID_ACK) {
pipe->stage = STAGE_IN;
usb_rx_buffer[1] = 0; // reset buffer
data_in_num = 1;
buffer_idx = 0;
}
} else if (pipe->stage == STAGE_IN) {
packet_info_t in_token;
in_token = calc_in_token(usb_device.address, 0);
data_transfer(in_token.tx_address, in_token.tx_length);
int receive_len = receive_packet_and_ack();
if (receive_len >= 0) {
if (usb_rx_buffer[1] == expect_token) {
memcpy((void *)(pipe->rx_buffer + buffer_idx), &usb_rx_buffer[2],
receive_len);
buffer_idx += receive_len;
if (pipe->request_length <= buffer_idx) {
pipe->stage = STAGE_STATUS;
}
data_in_num ^= 1; // togle data num
}
}
pio_sm_set_enabled(pio_usb_rx, sm_rx, false);
usb_rx_buffer[1] = 0; // reset buffer
}
else if (pipe->stage == STAGE_STATUS) {
uint8_t status_packet[] = {USB_SYNC, USB_PID_DATA1, 0, 0};
control_out_transfer(status_packet, sizeof(status_packet));
wait_handshake();
if (usb_rx_buffer[0] == USB_SYNC && usb_rx_buffer[1] == USB_PID_ACK) {
pipe->stage = STAGE_COMPLETE;
usb_rx_buffer[1] = 0; // reset buffer
} else if (usb_rx_buffer[0] == USB_SYNC && usb_rx_buffer[1] == USB_PID_NAK) {
pipe->stage = STAGE_STATUS;
usb_rx_buffer[1] = 0; // reset buffer
} else {
pipe->stage = STAGE_ERROR;
usb_rx_buffer[1] = 0; // reset buffer
}
}
return pipe->stage;
}
static bool __no_inline_not_in_flash_func(connection_check)(void) {
if (gpio_get(pin_dp) == 0 && gpio_get(pin_dm) == 0) {
busy_wait_us_32(1);
if (gpio_get(pin_dp) == 0 && gpio_get(pin_dm) == 0) {
// device disconnect
if (usb_device.connected) {
usb_device.connected = false;
usb_device.event = EVENT_DISCONNECT;
}
return false;
}
}
return true;
}
static bool __no_inline_not_in_flash_func(sof_timer)(repeating_timer_t *_rt) {
static uint8_t sof_packet[4] = {USB_SYNC, USB_PID_SOF, 0x00, 0x10};
static uint8_t sof_count = 0;
static uint8_t reset_count = 0;
UNUSED_PARAMETER(_rt);
if (usb_device.connected && connection_check()) {
usb_transfer(sof_packet, sizeof(sof_packet));
switch (usb_device.control_pipe.operation)
{
case CONTROL_NONE:
break;
case CONTROL_IN:
usb_control_in_transfer(&usb_device.control_pipe);
if (usb_device.control_pipe.stage == STAGE_COMPLETE)
{
usb_device.control_pipe.stage = STAGE_SETUP;
usb_device.control_pipe.operation = CONTROL_COMPLETE;
}
else if (usb_device.control_pipe.stage == STAGE_ERROR)
{
usb_device.control_pipe.stage = STAGE_SETUP;
usb_device.control_pipe.operation = CONTROL_ERROR;
}
break;
case CONTROL_OUT:
usb_control_out_transfer(&usb_device.control_pipe);
if (usb_device.control_pipe.stage == STAGE_COMPLETE)
{
usb_device.control_pipe.stage = STAGE_SETUP;
usb_device.control_pipe.operation = CONTROL_COMPLETE;
}
else if (usb_device.control_pipe.stage == STAGE_ERROR)
{
usb_device.control_pipe.stage = STAGE_SETUP;
usb_device.control_pipe.operation = CONTROL_ERROR;
}
break;
default:
break;
}
for (int ep_idx = 0; ep_idx < PIO_USB_EP_CNT; ep_idx++) {
endpoint_t * ep = &usb_device.endpoint[ep_idx];
if (!ep->ep_num || !ep->is_interrupt) {
continue;
}
if (ep->interval_counter > 0) {
ep->interval_counter--;
continue;
}
if (ep->ep_num & EP_IN) {
int res = usb_in_transaction(usb_device.address, ep);
ep->interval_counter = ep->interval - 1;
if (res == 0) {
// ep->interval_counter = ep->interval;
} else if (res <= -2) {
// fatal
usb_device.connected = false;
return true;
}
} else {
if (ep->new_data_flag) {
int res = usb_out_transaction(usb_device.address, ep);
ep->interval_counter = ep->interval - 1;
if (res == 0) {
ep->interval_counter = ep->interval - 1;
}
}
}
}
} else {
const uint pin_tx = 0;
if (reset_count == 0 && gpio_get(pin_dp) == 1 && gpio_get(pin_dm) == 0) {
reset_count = 80;
}
if (reset_count > 3) {
pio_sm_set_pins_with_mask(pio_usb_tx, sm_tx, (0b00 << pin_tx),
(0b11u << pin_tx));
pio_sm_set_pindirs_with_mask(pio_usb_tx, sm_tx, (0b11u << pin_tx),
(0b11u << pin_tx));
} else if (reset_count > 2) {
pio_sm_set_pindirs_with_mask(pio_usb_tx, sm_tx, (0b00u << pin_tx),
(0b11u << pin_tx));
} else if (reset_count > 0) {
if (gpio_get(pin_dp) == 1 && gpio_get(pin_dm) == 0) {
if (!usb_device.connected){
usb_device.connected = true;
usb_device.event = EVENT_CONNECT;
}
}
}
if (reset_count) {
reset_count--;
}
}
sof_count = (sof_count + 1) & 0x1f;
sof_packet[2] = sof_count & 0xff;
sof_packet[3] = (calc_usb_crc5(sof_count) << 3) | (sof_count >> 8);
return true;
}
static void update_packet_crc16(usb_setup_packet_t * packet) {
uint16_t crc16 = calc_usb_crc16(&packet->request_type,
sizeof(*packet) - 4);
packet->crc16[0] = crc16 & 0xff;
packet->crc16[1] = crc16 >> 8;
}
static int __no_inline_not_in_flash_func(control_out_protocol)(
uint8_t *setup_data, uint16_t setup_length, uint8_t *out_data,
uint16_t out_length) {
int res = 0;
if (usb_device.control_pipe.operation == CONTROL_NONE) {
usb_device.control_pipe.setup_packet.tx_address = setup_data;
usb_device.control_pipe.setup_packet.tx_length = setup_length;
usb_device.control_pipe.out_data_packet.tx_address = out_data;
usb_device.control_pipe.out_data_packet.tx_length = out_length;
usb_device.control_pipe.operation = CONTROL_OUT;
} else {
return -1;
}
const uint64_t timeout = 5000 * 1000; // 5s
uint64_t start_time = time_us_64();
while (usb_device.control_pipe.operation == CONTROL_OUT &&
time_us_64() - start_time < timeout) {
if (!usb_device.connected) {
usb_device.control_pipe.operation = CONTROL_NONE;
return -1;
}
}
if (time_us_64() - start_time >= timeout) {
printf("control out[timeout]\n");
res = -2;
} else if (usb_device.control_pipe.operation == CONTROL_ERROR) {
printf("control out[error]\n");
res = -1;
} else if (usb_device.control_pipe.operation == CONTROL_COMPLETE) {
printf("control out[complete]\n");
res = 0;
}
usb_device.control_pipe.operation = CONTROL_NONE;
return res;
}
static int __no_inline_not_in_flash_func(control_in_protocol)(uint8_t *data,
uint16_t length) {
int res = 0;
if (usb_device.control_pipe.operation == CONTROL_NONE) {
usb_device.control_pipe.setup_packet.tx_address = data;
usb_device.control_pipe.setup_packet.tx_length = length;
usb_device.control_pipe.operation = CONTROL_IN;
} else {
return -1;
}
const uint64_t timeout = 5000 * 1000; // 5s
uint64_t start_time = time_us_64();
while (usb_device.control_pipe.operation == CONTROL_IN &&
time_us_64() - start_time < timeout) {
if (!usb_device.connected) {
usb_device.control_pipe.operation = CONTROL_NONE;
return -1;
}
}
if (time_us_64() - start_time >= timeout) {
printf("control in[timeout]\n");
res = -2;
} else if (usb_device.control_pipe.operation == CONTROL_ERROR) {
printf("control in[error]\n");
res = -1;
} else if (usb_device.control_pipe.operation == CONTROL_COMPLETE) {
printf("control in[complete]\n");
res = 0;
}
usb_device.control_pipe.operation = CONTROL_NONE;
return res;
}
int __no_inline_not_in_flash_func(pio_usb_set_out_data)(endpoint_t *ep,
const uint8_t *buffer,
uint8_t len) {
if (ep->new_data_flag || !ep->is_interrupt || (ep->ep_num & EP_IN)) {
return -1;
}
ep->buffer[0] = USB_SYNC;
ep->buffer[1] = ep->data_id == 0 ? USB_PID_DATA0 : USB_PID_DATA1;
memcpy((uint8_t *)&ep->buffer[2], buffer, len);
uint16_t crc = calc_usb_crc16(buffer, len);
ep->buffer[2 + len] = crc & 0xff;
ep->buffer[2 + len + 1] = (crc >> 8) & 0xff;
ep->packet_len = len + 4;
ep->new_data_flag = true;
return 0;
}
static int enumerate_device(void) {
int res = 0;
usb_device.control_pipe.request_length = 18;
update_packet_crc16(&get_device_descriptor_report);
res = control_in_protocol((uint8_t *)&get_device_descriptor_report,
sizeof(get_device_descriptor_report));
if (res != 0) {
return res;
}
const device_descriptor_t * desc = (device_descriptor_t*)usb_device.control_pipe.rx_buffer;
usb_device.vid = desc->vid[0] | (desc->vid[1] << 8);
usb_device.pid = desc->pid[0] | (desc->pid[1] << 8);
set_address_request.value_lsb = 1;
set_address_request.value_msb = 0;
update_packet_crc16(&set_address_request);
res = control_out_protocol((uint8_t *)&set_address_request,
sizeof(set_address_request), NULL, 0);
if (res != 0) {
return res;
}
usb_device.address = 1;
get_configuration_descriptor_report.length_lsb = 9;
get_configuration_descriptor_report.length_msb = 0;
usb_device.control_pipe.request_length = 9;
update_packet_crc16(&get_configuration_descriptor_report);
res = control_in_protocol((uint8_t *)&get_configuration_descriptor_report,
sizeof(get_configuration_descriptor_report));
if (res != 0) {
return res;
}
get_configuration_descriptor_report.length_lsb =
((configuration_descriptor_t *)(usb_device.control_pipe.rx_buffer))
->total_length_lsb;
get_configuration_descriptor_report.length_msb =
((configuration_descriptor_t *)(usb_device.control_pipe.rx_buffer))
->total_length_msb;
usb_device.control_pipe.request_length =
get_configuration_descriptor_report.length_lsb |
(get_configuration_descriptor_report.index_msb << 8);
update_packet_crc16(&get_configuration_descriptor_report);
res = control_in_protocol((uint8_t *)&get_configuration_descriptor_report,
sizeof(get_configuration_descriptor_report));
if (res != 0) {
return res;
}
uint8_t configuration_descrptor_data[512];
int16_t configuration_descrptor_length =
usb_device.control_pipe.request_length > 512
? 512
: usb_device.control_pipe.request_length;
memcpy(configuration_descrptor_data,
(const void *)usb_device.control_pipe.rx_buffer,
configuration_descrptor_length);
set_usb_configuration_report.value_lsb =
((configuration_descriptor_t *)(usb_device.control_pipe.rx_buffer))
->configuration_value;
update_packet_crc16(&set_usb_configuration_report);
res = control_out_protocol((uint8_t *)&set_usb_configuration_report,
sizeof(set_usb_configuration_report), NULL, 0);
if (res != 0) {
return res;
}
volatile uint8_t ep_idx = 0;
volatile uint8_t interface = 0;
volatile uint8_t class = 0;
uint8_t *descriptor = configuration_descrptor_data;
while (configuration_descrptor_length > 0) {
switch (descriptor[1]) {
case DESC_TYPE_INTERFACE: {
const interface_descriptor_t *d =
(const interface_descriptor_t *)descriptor;
printf(
"inum:%d, altsetting:%d, numep:%d, iclass:%d, isubclass:%d, "
"iprotcol:%d, iface:%d\n",
d->inum, d->altsetting, d->numep, d->iclass, d->isubclass,
d->iprotocol, d->iface);
interface = d->inum;
class = d->iclass;
} break;
case DESC_TYPE_ENDPOINT: {
const endpoint_descriptor_t *d =
(const endpoint_descriptor_t *)descriptor;
printf("\t\t\tepaddr:0x%02x, attr:%d, size:%d, interval:%d\n", d->epaddr,
d->attr, d->max_size[0] | (d->max_size[1] << 8), d->interval);
if (class == CLASS_HID && d->attr == EP_ATTR_INTERRUPT) {
volatile endpoint_t *ep = &usb_device.endpoint[ep_idx];
ep->interval = d->interval;
ep->interval_counter = 0;
ep->size = d->max_size[0] | (d->max_size[1] << 8);
ep->is_interrupt = true;
ep->ep_num = d->epaddr;
ep_idx++;
}
} break;
case DESC_TYPE_HID: {
const hid_descriptor_t *d = (const hid_descriptor_t *)descriptor;
printf(
"\tbcdHID:%x.%x, country:%d, desc num:%d, desc_type:%d, "
"desc_size:%d\n",
d->bcd_hid[1], d->bcd_hid[0], d->contry_code, d->num_desc,
d->desc_type, d->desc_len[0] | (d->desc_len[1] << 8));
set_hid_idle_request.value_lsb = interface;
set_hid_idle_request.value_msb = 0;
update_packet_crc16(&set_hid_idle_request);
control_out_protocol((uint8_t *)&set_hid_idle_request,
sizeof(set_hid_idle_request), NULL, 0);
get_hid_report_descrpitor_request.index_lsb = interface;
get_hid_report_descrpitor_request.index_msb = 0;
get_hid_report_descrpitor_request.length_lsb = d->desc_len[0];
get_hid_report_descrpitor_request.length_msb = d->desc_len[1];
uint16_t desc_len = d->desc_len[0] | (d->desc_len[1] << 8);
usb_device.control_pipe.request_length = desc_len;
update_packet_crc16(&get_hid_report_descrpitor_request);
control_in_protocol((uint8_t *)&get_hid_report_descrpitor_request,
sizeof(get_hid_report_descrpitor_request));
printf("\t\tReport descriptor:");
for (int i = 0; i < desc_len; i++) {
printf("%02x ", usb_device.control_pipe.rx_buffer[i]);
}
printf("\n");
stdio_flush();
} break;
default:
break;
}
configuration_descrptor_length -= descriptor[0];
descriptor += descriptor[0];
}
return res;
}
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 repeating_timer_t sof_rt;
static pio_usb_configuration_t current_config;
static bool timer_active;
usb_device_t *pio_usb_init(const pio_usb_configuration_t *c) {
pio_usb_tx = c->pio_tx_num == 0 ? pio0 : pio1;
sm_tx = c->sm_tx;
tx_ch = c->tx_ch;
pio_usb_rx = c->pio_rx_num == 0 ? pio0 : pio1;
sm_rx = c->sm_rx;
pin_dp = c->pin_dp;
pin_dm = c->pin_dp + 1;
uint offset_tx = pio_add_program(pio_usb_tx, &usb_tx_program);
usb_tx_program_init(pio_usb_tx, sm_tx, offset_tx, pin_dp);
configure_tx_channel(tx_ch, pio_usb_tx, sm_tx);
if (c->debug_pin_rx < 0) {
offset_rx = pio_add_program(pio_usb_rx, &usb_rx_program);
} else {
offset_rx = pio_add_program(pio_usb_rx, &usb_rx_debug_program);
}
usb_rx_program_init(pio_usb_rx, sm_rx, offset_rx, pin_dp, c->debug_pin_rx);
rx_reset_instr = pio_encode_jmp(offset_rx);
if (c->debug_pin_eop < 0) {
offset_eop = pio_add_program(pio_usb_rx, &eop_detect_program);
} else {
offset_eop = pio_add_program(pio_usb_rx, &eop_detect_debug_program);
}
eop_detect_program_init(pio_usb_rx, c->sm_eop, offset_eop, pin_dp,
c->debug_pin_eop);
if (c->alarm_pool != NULL) {
alarm_pool_add_repeating_timer_us(c->alarm_pool, -1000, sof_timer, NULL,
&sof_rt);
} else {
add_repeating_timer_us(-1000, sof_timer, NULL, &sof_rt);
}
current_config = *c;
timer_active = true;
return &usb_device;
}
static volatile bool cancel_timer;
static volatile bool start_timer;
static uint32_t int_stat;
void pio_usb_stop(void) {
cancel_timer = true;
while (cancel_timer) {
continue;
}
}
void pio_usb_restart(void) {
start_timer = true;
while (start_timer) {
continue;
}
}
void __no_inline_not_in_flash_func(pio_usb_task)(void) {
if (usb_device.event == EVENT_CONNECT) {
usb_device.event = EVENT_NONE;
printf("Connected\n");
int res = enumerate_device();
if (res == 0) {
usb_device.enumerated = true;
} else {
usb_device.connected = false;
usb_device.event = EVENT_DISCONNECT;
}
} else if (usb_device.event == EVENT_DISCONNECT) {
usb_device.event = EVENT_NONE;
printf("Disconnect\n");
memset(&usb_device, 0, sizeof(usb_device));
usb_device.enumerated = false;
}
if (cancel_timer) {
int_stat = save_and_disable_interrupts();
cancel_repeating_timer(&sof_rt);
memset(&usb_device, 0, sizeof(usb_device));
timer_active = false;
cancel_timer = false;
}
if (start_timer && !timer_active) {
if (current_config.alarm_pool != NULL) {
alarm_pool_add_repeating_timer_us(current_config.alarm_pool, -1000,
sof_timer, NULL, &sof_rt);
} else {
add_repeating_timer_us(-1000, sof_timer, NULL, &sof_rt);
}
restore_interrupts(int_stat);
timer_active = true;
start_timer = false;
}
}
#pragma GCC pop_options

11
pio_usb.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#include "pio_usb_configuration.h"
#include "usb_definitions.h"
usb_device_t *pio_usb_init(const pio_usb_configuration_t *c);
void pio_usb_task(void);
int pio_usb_set_out_data(endpoint_t *ep, const uint8_t *buffer, uint8_t len);
void pio_usb_stop(void);
void pio_usb_restart(void);

38
pio_usb_configuration.h Normal file
View file

@ -0,0 +1,38 @@
#pragma once
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;
} pio_usb_configuration_t;
#define PIO_USB_DP_PIN_DEFAULT 0
#define PIO_USB_DM_PIN_DEFAULT (PIO_USB_DP_PIN_DEFAULT + 1)
#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 \
}
#define PIO_USB_EP_CNT 16

63
usb_crc.c Normal file
View file

@ -0,0 +1,63 @@
#include "usb_crc.h"
uint8_t calc_usb_crc5(uint16_t data) {
const uint8_t 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,
};
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;
}

15
usb_crc.h Normal file
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;
}

225
usb_definitions.h Normal file
View file

@ -0,0 +1,225 @@
#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_IN,
STAGE_OUT,
STAGE_STATUS,
STAGE_COMPLETE,
STAGE_ERROR
} setup_transfer_stage_t;
typedef enum {
STAGE_DEVICE,
STAGE_CONFIG,
STAGE_CONFIG2,
STAGE_INTERFACE,
STAGE_ENDPOINT
} usb_communication_stage_t;
typedef struct {
uint8_t *tx_address;
uint8_t tx_length;
} packet_info_t;
typedef struct {
volatile uint8_t rx_buffer[512];
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 ep_num;
volatile uint8_t size;
volatile uint8_t buffer[64 + 4];
volatile uint8_t packet_len;
volatile bool new_data_flag;
volatile bool is_interrupt;
volatile uint8_t interval;
volatile uint8_t interval_counter;
volatile uint8_t data_id; // data0 or data1
} endpoint_t;
typedef enum {
EVENT_NONE,
EVENT_CONNECT,
EVENT_DISCONNECT,
} usb_device_event_t;
typedef struct {
volatile bool connected;
volatile bool enumerated;
volatile usb_device_event_t event;
volatile uint8_t address;
volatile uint16_t vid;
volatile uint16_t pid;
control_pipe_t control_pipe;
endpoint_t endpoint[PIO_USB_EP_CNT];
} usb_device_t;
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_CRC16_PLACE = 0,
};
enum {
DESC_TYPE_STRING = 0x03,
DESC_TYPE_INTERFACE = 0x04,
DESC_TYPE_ENDPOINT = 0x05,
DESC_TYPE_HID = 0x21,
};
enum {
CLASS_HID = 0x03,
};
enum {
EP_ATTR_INTERRUPT = 0x03,
};
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 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;
} 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;
#define GET_DEVICE_DESCRIPTOR_REQ_DEFAULT \
{ \
USB_SYNC, USB_PID_DATA0, 0x80, 0x06, 0, 0x01, 0, 0, 0x12, 0, { \
USB_CRC16_PLACE, USB_CRC16_PLACE \
} \
}
#define GET_CONFIGURATION_DESCRIPTOR_REQ_DEFAULT \
{ \
USB_SYNC, USB_PID_DATA0, 0x80, 0x06, 0, 0x02, 0, 0, 0x09, 0, { \
USB_CRC16_PLACE, USB_CRC16_PLACE \
} \
}
#define SET_CONFIGURATION_REQ_DEFAULT \
{ \
USB_SYNC, USB_PID_DATA0, 0x00, 0x09, 0, 0, 0, 0, 0, 0, { \
USB_CRC16_PLACE, USB_CRC16_PLACE \
} \
}
#define SET_HID_IDLE_REQ_DEFAULT \
{ \
USB_SYNC, USB_PID_DATA0, 0x21, 0x0A, 0, 0, 0, 0, 0, 0, { \
USB_CRC16_PLACE, USB_CRC16_PLACE \
} \
}
#define GET_HID_REPORT_DESCRIPTOR_DEFAULT \
{ \
USB_SYNC, USB_PID_DATA0, 0x81, 0x06, 0, 0x22, 0, 0, 0xff, 0, { \
USB_CRC16_PLACE, USB_CRC16_PLACE \
} \
}
#define SET_ADDRESS_REQ_DEFAULT \
{ \
USB_SYNC, USB_PID_DATA0, 0, 0x5, 0x02, 0, 0, 0, 0, 0, { \
USB_CRC16_PLACE, USB_CRC16_PLACE \
} \
}

211
usb_rx.pio Normal file
View file

@ -0,0 +1,211 @@
; Copyright (c) 2021 sekigon-gonnoc
; USB FS NRZI receiver
; Run 48 MHz, autopull
.define public IRQ_RX_BS_ERR 1 ; bit stuffinc error
.define public IRQ_RX_EOP 2 ; eop detect flag
.define J 0b01
.define K 0b10
.define SE0 0b00
.define BR 5 ; bit repeat counter
.define db0 0
.define db1 1
; USB FS receive program run at 96MHz
; shift to left, autopull enable
; pin0: dp, pin1: dm
; bit is captured from only dp. dm is used to detect eop
.program usb_rx
.wrap_target
start:
set y, BR
wait 0 pin 0
read1:
jmp PIN J1
K1:
set x, 0 [1]
in x, 1 [1]
set y, BR
jmp read2 [1]
J1:
set x, 1 [2]
in x, 1
jmp y-- read1 [2]
set y, BR
jmp PIN bs_err
jmp read2 [5] ; ignore bitstuff
read2:
jmp PIN J2
K2:
set x, 1 [3]
in x, 1 [1]
jmp y-- read2
set y, BR
jmp read1 [6] ; ignore bitstuff
J2:
set x, 0 [3]
in x, 1
set y, BR
jmp read1
eop:
bs_err:
irq wait IRQ_RX_BS_ERR
.wrap
; USB receive program run at 96MHz with debug pin
; shift to left, autopull enable
; pin0: dp, pin1: dm
; bit is captured from only dp. dm is used to detect eop
.program usb_rx_debug
.side_set 1
.wrap_target
start:
set y, BR side db0
wait 0 pin 0 side db0
read1:
jmp PIN J1 side db1
K1:
;mov x, pins side db1 ; "miv x, pins" does not work as intend
;jmp !x eop side db1; SE0
set x, 0 [1] side db0
in x, 1 [1] side db1
set y, BR side db1
jmp read2 [1] side db1
J1:
set x, 1 [2] side db0
in x, 1 side db1
jmp y-- read1 [2] side db1
set y, BR side db1
jmp PIN bs_err side db1
jmp read2 [5] side db1; ignore bitstuff
read2:
jmp PIN J2 side db1
K2:
; mov x, pins side db1 ; "miv x, pins" does not work as intend
; jmp !x eop side db1; SE0
set x, 1 [3] side db0
in x, 1 [1] side db1
jmp y-- read2 side db1
set y, BR side db1
jmp read1 [6] side db1 ; ignore bitstuff
J2:
set x, 0 [3] side db0
in x, 1 side db1
set y, BR side db1
jmp read1 side db1
eop:
bs_err:
irq wait IRQ_RX_BS_ERR side db0
.wrap
; EOP detect program
; run at 96MHz
; autopull disable
.program eop_detect
.wrap_target
start:
set y, 1
mov isr, null
wait_se0:
in pins, 2 ; read dp/dm
mov x, isr
jmp x-- start ; check se0
jmp y-- wait_se0
irq wait IRQ_RX_EOP ; eop is detected
.wrap
; EOP detect program with debug out
; run at 96MHz
; autopull disable
.program eop_detect_debug
.side_set 1
.wrap_target
start:
set y, 1 side db0;
mov isr, null side db0
wait_se0:
in pins, 2 side db0 ; read dp/dm
mov x, isr side db0
jmp x-- start side db0 ; check se0
jmp y-- wait_se0 side db0 ;
irq wait IRQ_RX_EOP side db1 ; eop is detected
.wrap
% c-sdk {
#include "hardware/clocks.h"
static inline void usb_rx_program_init(PIO pio, uint sm, uint offset, uint pin_dp, int pin_debug) {
pio_sm_set_consecutive_pindirs(pio, sm, pin_dp, 2, false);
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dp + 1); // dm
pio_sm_config c;
if (pin_debug < 0) {
c = usb_rx_program_get_default_config(offset);
} else {
c = usb_rx_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);
// Run at 96Mhz
// system clock should be multiple of 12MHz
float div = (float)clock_get_hz(clk_sys) / (96000000);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, false);
}
static inline void eop_detect_program_init(PIO pio, uint sm, uint offset,
uint pin_dp, int pin_debug) {
pio_sm_config c;
if (pin_debug < 0) {
c = eop_detect_program_get_default_config(offset);
} else {
c = eop_detect_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
sm_config_set_in_shift(&c, false, false, 8);
float div = (float)clock_get_hz(clk_sys) / (96000000);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}

85
usb_tx.pio Normal file
View file

@ -0,0 +1,85 @@
; Copyright (c) 2021 sekigon-gonnoc
; USB FS NRZI transmitter
; Run 48 MHz, autopull
.program usb_tx
.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
start:
set y, BR side J
set pindirs, 0b11
.wrap_target
check_eop1:
jmp !osre load_bit1
nop [1]
send_eop:
nop side SE0 [3]
nop [3]
nop side J
set pindirs, 0b00
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 inline void usb_tx_program_init(PIO pio, uint sm, uint offset,
uint pin_dp) {
pio_sm_set_pins_with_mask(pio, sm, (0b01 << pin_dp), (0b11 << pin_dp));
gpio_pull_down(pin_dp);
gpio_pull_down(pin_dp + 1); // dm
pio_gpio_init(pio, pin_dp);
pio_gpio_init(pio, pin_dp + 1); // dm
pio_sm_config c = usb_tx_program_get_default_config(offset);
// shifts to left, autopull, 8bit
sm_config_set_out_shift(&c, true, true, 8);
sm_config_set_out_pins(&c, pin_dp, 2);
sm_config_set_set_pins(&c, pin_dp, 2);
sm_config_set_sideset_pins(&c, pin_dp);
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);
pio_sm_set_enabled(pio, sm, true);
}
%}