Skip to content

Commit

Permalink
Switch to fast serial.
Browse files Browse the repository at this point in the history
Should be backwards compatible, except that commands ending in just newline (\n) are accepted in addition to commands ending in carriage return + newline (\r\n).
  • Loading branch information
carterturn committed Feb 3, 2024
1 parent 3896b1a commit 23fb9c1
Show file tree
Hide file tree
Showing 7 changed files with 414 additions and 158 deletions.
Binary file modified build/prawnblaster/prawnblaster.uf2
Binary file not shown.
Binary file modified build/prawnblaster/prawnblasteroverclock.uf2
Binary file not shown.
18 changes: 7 additions & 11 deletions prawnblaster/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,34 +1,30 @@
add_executable(prawnblaster
prawnblaster.cpp
fast_serial.c
)

pico_generate_pio_header(prawnblaster ${CMAKE_CURRENT_LIST_DIR}/pseudoclock.pio)


# Pull in our pico_stdlib which aggregates commonly used features
target_link_libraries(prawnblaster pico_stdlib hardware_pio pico_multicore hardware_clocks hardware_dma)

# enable usb output, disable uart output
pico_enable_stdio_usb(prawnblaster 1)
pico_enable_stdio_uart(prawnblaster 0)
target_link_libraries(prawnblaster pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board)
target_include_directories(prawnblaster PRIVATE .)

# create map/bin/hex/uf2 file etc.
pico_add_extra_outputs(prawnblaster)

add_executable(prawnblasteroverclock
prawnblaster.cpp
fast_serial.c
)

pico_generate_pio_header(prawnblasteroverclock ${CMAKE_CURRENT_LIST_DIR}/pseudoclock.pio)

set_target_properties(prawnblasteroverclock PROPERTIES COMPILE_DEFINITIONS PRAWNBLASTER_OVERCLOCK=1)

# Pull in our pico_stdlib which aggregates commonly used features
target_link_libraries(prawnblasteroverclock pico_stdlib hardware_pio pico_multicore hardware_clocks hardware_dma)

# enable usb output, disable uart output
pico_enable_stdio_usb(prawnblasteroverclock 1)
pico_enable_stdio_uart(prawnblasteroverclock 0)
target_link_libraries(prawnblasteroverclock pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board)
target_include_directories(prawnblasteroverclock PRIVATE .)

# create map/bin/hex/uf2 file etc.
pico_add_extra_outputs(prawnblasteroverclock)
pico_add_extra_outputs(prawnblasteroverclock)
198 changes: 198 additions & 0 deletions prawnblaster/fast_serial.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
#include "tusb.h"
#include "pico/unique_id.h"

#include <stdarg.h>

#include "fast_serial.h"

/*
Serial functions
These are mostly thin wrappers around TinyUSB functions;
they are provided to simplify the API.
*/

// Read bytes (blocks until buffer_size is reached)
uint32_t fast_serial_read(const char * buffer, uint32_t buffer_size){
uint32_t buffer_idx = 0;
while(buffer_idx < buffer_size){
uint32_t buffer_avail = buffer_size - buffer_idx;
uint32_t read_avail = fast_serial_read_available();

if(read_avail > 0){
if(buffer_avail > read_avail){
buffer_avail = read_avail;
}

buffer_idx += fast_serial_read_atomic(buffer + buffer_idx, buffer_avail);
}

fast_serial_task();
}
return buffer_size;
}

// Read bytes until terminator reached (blocks until terminator or buffer_size is reached)
uint32_t fast_serial_read_until(char * buffer, uint32_t buffer_size, char until){
uint32_t buffer_idx = 0;
while(buffer_idx < buffer_size){
while(fast_serial_read_available() > 0){
int32_t next_char = tud_cdc_read_char();

buffer[buffer_idx] = next_char;
buffer_idx++;
if(next_char == until){
break;
}
}

if(buffer_idx > 0 && buffer[buffer_idx-1] == until){
break;
}
fast_serial_task();
}
return buffer_idx;
}

// Write bytes (without flushing, so limited to 64 bytes)
uint32_t fast_serial_write(const char * buffer, uint32_t buffer_size){
uint32_t buffer_idx = 0;
while(buffer_idx < buffer_size){
uint32_t write_avail = fast_serial_write_available();

if(write_avail > 0){
if(buffer_size - buffer_idx < write_avail){
write_avail = buffer_size - buffer_idx;
}

buffer_idx += fast_serial_write_atomic(buffer + buffer_idx, write_avail);
}
fast_serial_task();
fast_serial_write_flush();
}
return buffer_size;
}

int fast_serial_printf(const char * format, ...){
va_list va;
va_start(va, format);
char printf_buffer[128];
int ret = vsnprintf(printf_buffer, 128, format, va);
va_end(va);
if(ret <= 0){
return ret;
}
return fast_serial_write(printf_buffer, strnlen(printf_buffer, 128));
}

/*
USB callbacks
*/

void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts){}
void tud_cdc_rx_cb(uint8_t itf){}

/*
USB descriptor setup
We use the same VID, PID and ID as the Pi Pico would normally use.
*/
tusb_desc_device_t const desc_device = {
.bLength = sizeof(tusb_desc_device_t),
.bDescriptorType = TUSB_DESC_DEVICE,
.bcdUSB = 0x0200,
.bDeviceClass = TUSB_CLASS_MISC,
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
.bDeviceProtocol = MISC_PROTOCOL_IAD,
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,

.idVendor = 0x2E8A,
.idProduct = 0x000A,
.bcdDevice = 0x0100,

.iManufacturer = 0x01,
.iProduct = 0x02,
.iSerialNumber = 0x03,

.bNumConfigurations = 0x01
};

uint8_t const * tud_descriptor_device_cb(){
return (uint8_t const *) &desc_device;
}

enum{
ITF_NUM_CDC = 0,
ITF_NUM_CDC_DATA,
ITF_NUM_TOTAL
};

#define EPNUM_CDC_NOTIF 0x81
#define EPNUM_CDC_OUT 0x02
#define EPNUM_CDC_IN 0x82

#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN)

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, 0x00, 100),
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64)
};

uint8_t const * tud_descriptor_configuration_cb(uint8_t index){
return desc_configuration;
}

enum {
STRID_LANGID = 0,
STRID_MANUFACTURER,
STRID_PRODUCT,
STRID_SERIAL,
};

static char usb_serial_str[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1];

char const* string_desc_arr [] ={
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
"Raspberry Pi", // 1: Manufacturer
"Pico", // 2: Product
usb_serial_str, // 3: Serials, should use chip ID
"Board CDC", // 4: CDC Interface
};

static uint16_t _desc_str[32];

uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid){
uint8_t chr_count;

if(!usb_serial_str[0]){
pico_get_unique_board_id_string(usb_serial_str, sizeof(usb_serial_str));
}

if(index == 0){
memcpy(&_desc_str[1], string_desc_arr[0], 2);
chr_count = 1;
}
else{
if(!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))){
return NULL;
}

const char* str = string_desc_arr[index];

// Cap at max char
chr_count = (uint8_t) 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] = (uint16_t) ((TUSB_DESC_STRING << 8 ) | (2*chr_count + 2));

return _desc_str;
}
75 changes: 75 additions & 0 deletions prawnblaster/fast_serial.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
Faster serial functions
fast_serial_read/fast_serial_read_until are blocking functions
designed to receive data over a USB serial connection as fast as possible
(hopefully at the limit of the drivers).
fast_serial_read is generally much faster (~4x) than fast_serial_read_until,
as it can read larger blocks of data without having to scan for the terminating character.
Therefore, it is recommended to use fixed size blocks for large transmissions.
fast_serial_write is a blocking function
designed to send data over a USB serial connection as fast as possible
(again hopefully at the limit of the drivers).
The remaining functions are thin wrappers around TinyUSB functions;
they are provided to simplify the API.
Basic usage:
Call fast_serial_init()
In the main processing loop, call fast_serial_task.
Place calls to fast_serial_read/fast_serial_read_until and fast_serial_write where appropriate.
*/
#include "tusb.h"

// Initialize the USB stack
static inline bool fast_serial_init(){
return tusb_init();
}

// Get number of bytes available to read
static inline uint32_t fast_serial_read_available(){
return tud_cdc_available();
}

// Get number of bytes available to write
static inline uint32_t fast_serial_write_available(){
return tud_cdc_write_available();
}

// Read up to 64 bytes
static inline uint32_t fast_serial_read_atomic(char * buffer, uint32_t buffer_size){
return tud_cdc_read(buffer, buffer_size);
}

// Read bytes (blocks until buffer_size is reached)
uint32_t fast_serial_read(const char * buffer, uint32_t buffer_size);

// Read bytes until terminator reached (blocks until terminator or buffer_size is reached)
uint32_t fast_serial_read_until(char * buffer, uint32_t buffer_size, char until);

// Clear read FIFO (without reading it)
static inline void fast_serial_read_flush(){
tud_cdc_read_flush();
}

// Write bytes (without flushing, so limited to 64 bytes)
static inline uint32_t fast_serial_write_atomic(const char * buffer, uint32_t buffer_size){
return tud_cdc_write(buffer, buffer_size);
}

// Write bytes (without flushing)
uint32_t fast_serial_write(const char * buffer, uint32_t buffer_size);

// print via fast_serial_write
int fast_serial_printf(const char * format, ...);

// Force write of data. Returns number of bytes written.
static inline uint32_t fast_serial_write_flush(){
return tud_cdc_write_flush();
}

// Must be called regularly from main loop
static inline void fast_serial_task(){
tud_task();
}
Loading

0 comments on commit 23fb9c1

Please sign in to comment.