Skip to content

Commit 783634d

Browse files
authored
Merge pull request #25 from carterturn/carterturn-prawnblaster-fast-serial
Switch to faster serial
2 parents a2fb893 + 536b8de commit 783634d

File tree

6 files changed

+579
-161
lines changed

6 files changed

+579
-161
lines changed

prawnblaster/CMakeLists.txt

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,30 @@
11
add_executable(prawnblaster
22
prawnblaster.cpp
3+
fast_serial.c
34
)
45

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

78

89
# Pull in our pico_stdlib which aggregates commonly used features
9-
target_link_libraries(prawnblaster pico_stdlib hardware_pio pico_multicore hardware_clocks hardware_dma)
10-
11-
# enable usb output, disable uart output
12-
pico_enable_stdio_usb(prawnblaster 1)
13-
pico_enable_stdio_uart(prawnblaster 0)
10+
target_link_libraries(prawnblaster pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board)
11+
target_include_directories(prawnblaster PRIVATE .)
1412

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

1816
add_executable(prawnblasteroverclock
1917
prawnblaster.cpp
18+
fast_serial.c
2019
)
2120

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

2423
set_target_properties(prawnblasteroverclock PROPERTIES COMPILE_DEFINITIONS PRAWNBLASTER_OVERCLOCK=1)
2524

2625
# Pull in our pico_stdlib which aggregates commonly used features
27-
target_link_libraries(prawnblasteroverclock pico_stdlib hardware_pio pico_multicore hardware_clocks hardware_dma)
28-
29-
# enable usb output, disable uart output
30-
pico_enable_stdio_usb(prawnblasteroverclock 1)
31-
pico_enable_stdio_uart(prawnblasteroverclock 0)
26+
target_link_libraries(prawnblasteroverclock pico_stdlib hardware_pio pico_multicore pico_unique_id hardware_clocks hardware_dma tinyusb_device tinyusb_board)
27+
target_include_directories(prawnblasteroverclock PRIVATE .)
3228

3329
# create map/bin/hex/uf2 file etc.
34-
pico_add_extra_outputs(prawnblasteroverclock)
30+
pico_add_extra_outputs(prawnblasteroverclock)

prawnblaster/fast_serial.c

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#include "tusb.h"
2+
#include "pico/unique_id.h"
3+
4+
#include <stdarg.h>
5+
6+
#include "fast_serial.h"
7+
8+
/*
9+
Serial functions
10+
11+
These are mostly thin wrappers around TinyUSB functions;
12+
they are provided to simplify the API.
13+
*/
14+
15+
// Read bytes (blocks until buffer_size is reached)
16+
uint32_t fast_serial_read(const char * buffer, uint32_t buffer_size){
17+
uint32_t buffer_idx = 0;
18+
while(buffer_idx < buffer_size){
19+
uint32_t buffer_avail = buffer_size - buffer_idx;
20+
uint32_t read_avail = fast_serial_read_available();
21+
22+
if(read_avail > 0){
23+
if(buffer_avail > read_avail){
24+
buffer_avail = read_avail;
25+
}
26+
27+
buffer_idx += fast_serial_read_atomic(buffer + buffer_idx, buffer_avail);
28+
}
29+
30+
fast_serial_task();
31+
}
32+
return buffer_size;
33+
}
34+
35+
// Read bytes until terminator reached (blocks until terminator or buffer_size is reached)
36+
uint32_t fast_serial_read_until(char * buffer, uint32_t buffer_size, char until){
37+
uint32_t buffer_idx = 0;
38+
while(buffer_idx < buffer_size - 1){
39+
while(fast_serial_read_available() > 0){
40+
int32_t next_char = tud_cdc_read_char();
41+
42+
buffer[buffer_idx] = next_char;
43+
buffer_idx++;
44+
if(next_char == until){
45+
break;
46+
}
47+
}
48+
49+
if(buffer_idx > 0 && buffer[buffer_idx-1] == until){
50+
break;
51+
}
52+
fast_serial_task();
53+
}
54+
buffer[buffer_idx] = '\0'; // Null terminate string
55+
return buffer_idx;
56+
}
57+
58+
// Write bytes (without flushing, so limited to 64 bytes)
59+
uint32_t fast_serial_write(const char * buffer, uint32_t buffer_size){
60+
uint32_t buffer_idx = 0;
61+
while(buffer_idx < buffer_size){
62+
uint32_t write_avail = fast_serial_write_available();
63+
64+
if(write_avail > 0){
65+
if(buffer_size - buffer_idx < write_avail){
66+
write_avail = buffer_size - buffer_idx;
67+
}
68+
69+
buffer_idx += fast_serial_write_atomic(buffer + buffer_idx, write_avail);
70+
}
71+
fast_serial_task();
72+
fast_serial_write_flush();
73+
}
74+
return buffer_size;
75+
}
76+
77+
int fast_serial_printf(const char * format, ...){
78+
va_list va;
79+
va_start(va, format);
80+
char printf_buffer[128];
81+
int ret = vsnprintf(printf_buffer, 128, format, va);
82+
va_end(va);
83+
if(ret <= 0){
84+
return ret;
85+
}
86+
return fast_serial_write(printf_buffer, strnlen(printf_buffer, 128));
87+
}
88+
89+
/*
90+
USB callbacks
91+
*/
92+
93+
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts){}
94+
void tud_cdc_rx_cb(uint8_t itf){}
95+
96+
/*
97+
USB descriptor setup
98+
99+
We use the same VID, PID and ID as the Pi Pico would normally use.
100+
*/
101+
tusb_desc_device_t const desc_device = {
102+
.bLength = sizeof(tusb_desc_device_t),
103+
.bDescriptorType = TUSB_DESC_DEVICE,
104+
.bcdUSB = 0x0200,
105+
.bDeviceClass = TUSB_CLASS_MISC,
106+
.bDeviceSubClass = MISC_SUBCLASS_COMMON,
107+
.bDeviceProtocol = MISC_PROTOCOL_IAD,
108+
.bMaxPacketSize0 = CFG_TUD_ENDPOINT0_SIZE,
109+
110+
.idVendor = 0x2E8A,
111+
.idProduct = 0x000A,
112+
.bcdDevice = 0x0100,
113+
114+
.iManufacturer = 0x01,
115+
.iProduct = 0x02,
116+
.iSerialNumber = 0x03,
117+
118+
.bNumConfigurations = 0x01
119+
};
120+
121+
uint8_t const * tud_descriptor_device_cb(){
122+
return (uint8_t const *) &desc_device;
123+
}
124+
125+
enum{
126+
ITF_NUM_CDC = 0,
127+
ITF_NUM_CDC_DATA,
128+
ITF_NUM_TOTAL
129+
};
130+
131+
#define EPNUM_CDC_NOTIF 0x81
132+
#define EPNUM_CDC_OUT 0x02
133+
#define EPNUM_CDC_IN 0x82
134+
135+
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN)
136+
137+
uint8_t const desc_configuration[] = {
138+
// Config number, interface count, string index, total length, attribute, power in mA
139+
TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0x00, 100),
140+
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
141+
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 4, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64)
142+
};
143+
144+
uint8_t const * tud_descriptor_configuration_cb(uint8_t index){
145+
return desc_configuration;
146+
}
147+
148+
enum {
149+
STRID_LANGID = 0,
150+
STRID_MANUFACTURER,
151+
STRID_PRODUCT,
152+
STRID_SERIAL,
153+
};
154+
155+
static char usb_serial_str[PICO_UNIQUE_BOARD_ID_SIZE_BYTES * 2 + 1];
156+
157+
char const* string_desc_arr [] ={
158+
(const char[]) { 0x09, 0x04 }, // 0: is supported language is English (0x0409)
159+
"Raspberry Pi", // 1: Manufacturer
160+
"Pico", // 2: Product
161+
usb_serial_str, // 3: Serials, should use chip ID
162+
"Board CDC", // 4: CDC Interface
163+
};
164+
165+
static uint16_t _desc_str[32];
166+
167+
uint16_t const* tud_descriptor_string_cb(uint8_t index, uint16_t langid){
168+
uint8_t chr_count;
169+
170+
if(!usb_serial_str[0]){
171+
pico_get_unique_board_id_string(usb_serial_str, sizeof(usb_serial_str));
172+
}
173+
174+
if(index == 0){
175+
memcpy(&_desc_str[1], string_desc_arr[0], 2);
176+
chr_count = 1;
177+
}
178+
else{
179+
if(!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))){
180+
return NULL;
181+
}
182+
183+
const char* str = string_desc_arr[index];
184+
185+
// Cap at max char
186+
chr_count = (uint8_t) strlen(str);
187+
if ( chr_count > 31 ) chr_count = 31;
188+
189+
// Convert ASCII string into UTF-16
190+
for(uint8_t i = 0; i < chr_count; i++){
191+
_desc_str[1+i] = str[i];
192+
}
193+
}
194+
195+
// first byte is length (including header), second byte is string type
196+
_desc_str[0] = (uint16_t) ((TUSB_DESC_STRING << 8 ) | (2*chr_count + 2));
197+
198+
return _desc_str;
199+
}

prawnblaster/fast_serial.h

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
Faster serial functions
3+
4+
fast_serial_read/fast_serial_read_until are blocking functions
5+
designed to receive data over a USB serial connection as fast as possible
6+
(hopefully at the limit of the drivers).
7+
fast_serial_read is generally much faster (~4x) than fast_serial_read_until,
8+
as it can read larger blocks of data without having to scan for the terminating character.
9+
Therefore, it is recommended to use fixed size blocks for large transmissions.
10+
11+
fast_serial_write is a blocking function
12+
designed to send data over a USB serial connection as fast as possible
13+
(again hopefully at the limit of the drivers).
14+
15+
The remaining functions are thin wrappers around TinyUSB functions;
16+
they are provided to simplify the API.
17+
18+
Basic usage:
19+
Call fast_serial_init()
20+
In the main processing loop, call fast_serial_task.
21+
Place calls to fast_serial_read/fast_serial_read_until and fast_serial_write where appropriate.
22+
*/
23+
#include "tusb.h"
24+
25+
// Initialize the USB stack
26+
static inline bool fast_serial_init(){
27+
return tusb_init();
28+
}
29+
30+
// Get number of bytes available to read
31+
static inline uint32_t fast_serial_read_available(){
32+
return tud_cdc_available();
33+
}
34+
35+
// Get number of bytes available to write
36+
static inline uint32_t fast_serial_write_available(){
37+
return tud_cdc_write_available();
38+
}
39+
40+
// Read up to 64 bytes
41+
static inline uint32_t fast_serial_read_atomic(char * buffer, uint32_t buffer_size){
42+
return tud_cdc_read(buffer, buffer_size);
43+
}
44+
45+
// Read bytes (blocks until buffer_size is reached)
46+
uint32_t fast_serial_read(const char * buffer, uint32_t buffer_size);
47+
48+
// Read bytes until terminator reached (blocks until terminator or buffer_size is reached)
49+
// Adds null terminator to buffer after read completes (reserving one byte in buffer for this)
50+
uint32_t fast_serial_read_until(char * buffer, uint32_t buffer_size, char until);
51+
52+
// Clear read FIFO (without reading it)
53+
static inline void fast_serial_read_flush(){
54+
tud_cdc_read_flush();
55+
}
56+
57+
// Write bytes (without flushing, so limited to 64 bytes)
58+
static inline uint32_t fast_serial_write_atomic(const char * buffer, uint32_t buffer_size){
59+
return tud_cdc_write(buffer, buffer_size);
60+
}
61+
62+
// Write bytes (without flushing)
63+
uint32_t fast_serial_write(const char * buffer, uint32_t buffer_size);
64+
65+
// print via fast_serial_write
66+
int fast_serial_printf(const char * format, ...);
67+
68+
// Force write of data. Returns number of bytes written.
69+
static inline uint32_t fast_serial_write_flush(){
70+
return tud_cdc_write_flush();
71+
}
72+
73+
// Must be called regularly from main loop
74+
static inline void fast_serial_task(){
75+
tud_task();
76+
}

0 commit comments

Comments
 (0)