diff --git a/prawnblaster/CMakeLists.txt b/prawnblaster/CMakeLists.txt index 230f630..fed3b9e 100644 --- a/prawnblaster/CMakeLists.txt +++ b/prawnblaster/CMakeLists.txt @@ -1,22 +1,21 @@ 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) @@ -24,11 +23,8 @@ pico_generate_pio_header(prawnblasteroverclock ${CMAKE_CURRENT_LIST_DIR}/pseudoc 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) \ No newline at end of file +pico_add_extra_outputs(prawnblasteroverclock) diff --git a/prawnblaster/fast_serial.c b/prawnblaster/fast_serial.c new file mode 100644 index 0000000..add1531 --- /dev/null +++ b/prawnblaster/fast_serial.c @@ -0,0 +1,199 @@ +#include "tusb.h" +#include "pico/unique_id.h" + +#include + +#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 - 1){ + 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(); + } + buffer[buffer_idx] = '\0'; // Null terminate string + 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; +} diff --git a/prawnblaster/fast_serial.h b/prawnblaster/fast_serial.h new file mode 100644 index 0000000..1b9b827 --- /dev/null +++ b/prawnblaster/fast_serial.h @@ -0,0 +1,76 @@ +/* + 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) +// Adds null terminator to buffer after read completes (reserving one byte in buffer for this) +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(); +} diff --git a/prawnblaster/prawnblaster.cpp b/prawnblaster/prawnblaster.cpp index 6366f7f..078585b 100644 --- a/prawnblaster/prawnblaster.cpp +++ b/prawnblaster/prawnblaster.cpp @@ -18,11 +18,11 @@ ####################################################################### */ -#include #include #include "pico/stdlib.h" #include "pico/multicore.h" +#include "pico/bootrom.h" #include "hardware/dma.h" #include "hardware/pio.h" #include "hardware/pll.h" @@ -32,10 +32,14 @@ #include "pseudoclock.pio.h" +extern "C"{ +#include "fast_serial.h" +} + #ifndef PRAWNBLASTER_OVERCLOCK -const char VERSION[16] = "1.0.1"; +const char VERSION[16] = "1.1.0"; #else -const char VERSION[16] = "1.0.1-overclock"; +const char VERSION[16] = "1.1.0-overclock"; #endif //PRAWNBLASTER_OVERCLOCK int DEBUG; @@ -48,7 +52,8 @@ unsigned int instructions[60008]; // max_waits + 4 unsigned int waits[404]; -char readstring[256] = ""; +#define SERIAL_BUFFER_SIZE 256 +char readstring[SERIAL_BUFFER_SIZE] = ""; // This contains the number of clock cycles for a half period, which is currently 5 (there are 5 ASM instructions) const unsigned int non_loop_path_length = 5; @@ -153,7 +158,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, { // Divide by 2 to put it back in terms of "half_period reps" instructions // Subtract off two to remove the stop instruction from the count - printf("Too many instructions to send to pseudoclock %d (%d > %d)\n", config->sm, (words_to_send - 2) / 2, max_words / 2); + fast_serial_printf("Too many instructions to send to pseudoclock %d (%d > %d)\r\n", config->sm, (words_to_send - 2) / 2, max_words / 2); } return false; } @@ -164,7 +169,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, if (DEBUG) { // subtract off one to remove the stop instruction from the wait count - printf("Too many waits to send to pseudoclock %d (%d > %d)\n", config->sm, wait_count - 1, max_waits_per_pseudoclock); + fast_serial_printf("Too many waits to send to pseudoclock %d (%d > %d)\r\n", config->sm, wait_count - 1, max_waits_per_pseudoclock); } return false; } @@ -177,7 +182,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, if (DEBUG) { - printf("Pseudoclock %d has no instructions. It will not run this time.\n", config->sm); + fast_serial_printf("Pseudoclock %d has no instructions. It will not run this time.\r\n", config->sm); } return true; @@ -190,7 +195,7 @@ bool configure_pseudoclock_pio_sm(pseudoclock_config *config, uint prog_offset, // Subtract off two to remove the stop instruction from the count // wait count: // subtract off one to remove the stop instruction from the wait count - printf("Will send %d instructions containing %d waits to pseudoclock %d\n", (words_to_send - 2) / 2, wait_count - 1, config->sm); + fast_serial_printf("Will send %d instructions containing %d waits to pseudoclock %d\r\n", (words_to_send - 2) / 2, wait_count - 1, config->sm); } // Claim the POI @@ -349,12 +354,12 @@ void free_pseudoclock_pio_sm(pseudoclock_config *config) // Drain the FIFOs if (DEBUG) { - printf("Draining instruction FIFO\n"); + fast_serial_printf("Draining instruction FIFO\r\n"); } pio_sm_drain_tx_fifo(config->pio, config->sm); if (DEBUG) { - printf("Draining wait FIFO\n"); + fast_serial_printf("Draining wait FIFO\r\n"); } // drain rx fifo while (pio_sm_get_rx_fifo_level(config->pio, config->sm) > 0) @@ -363,7 +368,7 @@ void free_pseudoclock_pio_sm(pseudoclock_config *config) } if (DEBUG) { - printf("Pseudoclock program aborted\n"); + fast_serial_printf("Pseudoclock program aborted\r\n"); } } @@ -373,7 +378,7 @@ void free_pseudoclock_pio_sm(pseudoclock_config *config) if (DEBUG) { - printf("Draining TX FIFO\n"); + fast_serial_printf("Draining TX FIFO\r\n"); } // drain the tx FIFO to be safe pio_sm_drain_tx_fifo(config->pio, config->sm); @@ -539,7 +544,7 @@ void core1_entry() { if (DEBUG) { - printf("Failed to configure pseudoclock %d\n. Aborting.", i); + fast_serial_printf("Failed to configure pseudoclock %d\r\n. Aborting.", i); } break; } @@ -558,7 +563,7 @@ void core1_entry() set_status(ABORTED); if (DEBUG) { - printf("Core1 loop ended\n"); + fast_serial_printf("Core1 loop ended\r\n"); } continue; } @@ -587,7 +592,7 @@ void core1_entry() { if (DEBUG) { - printf("Tight loop for pseudoclock %d beginning\n", i); + fast_serial_printf("Tight loop for pseudoclock %d beginning\r\n", i); } while (dma_channel_is_busy(pseudoclock_configs[i].instructions_dma_channel) && get_status() != ABORT_REQUESTED) { @@ -595,7 +600,7 @@ void core1_entry() } if (DEBUG) { - printf("Tight loop for pseudoclock waits %d beginning\n", i); + fast_serial_printf("Tight loop for pseudoclock waits %d beginning\r\n", i); } while (dma_channel_is_busy(pseudoclock_configs[i].waits_dma_channel) && get_status() != ABORT_REQUESTED) { @@ -603,7 +608,7 @@ void core1_entry() } if (DEBUG) { - printf("Tight loops done for pseudoclock %d\n", i); + fast_serial_printf("Tight loops done for pseudoclock %d\r\n", i); } } } @@ -618,7 +623,7 @@ void core1_entry() { if (DEBUG) { - printf("Aborting pseudoclock program\n"); + fast_serial_printf("Aborting pseudoclock program\r\n"); } set_status(ABORTING); @@ -628,7 +633,7 @@ void core1_entry() { if (DEBUG) { - printf("Pseudoclock program complete\n"); + fast_serial_printf("Pseudoclock program complete\r\n"); } set_status(TRANSITION_TO_STOP); @@ -655,48 +660,7 @@ void core1_entry() if (DEBUG) { - printf("Core1 loop ended\n"); - } - } -} - -void readline() -{ - int i = 0; - char c; - int crfound = 0; - while (true) - { - char c = getchar(); - if (c == '\r') - { - crfound = 1; - } - else if (c == '\n') - { - if (crfound == 1) - { - readstring[i] = '\0'; - return; - } - else - { - readstring[i] = '\n'; - i++; - } - } - else if (crfound) - { - crfound = 0; - readstring[i] = '\r'; - i++; - readstring[i] = c; - i++; - } - else - { - readstring[i] = c; - i++; + fast_serial_printf("Core1 loop ended\r\n"); } } } @@ -724,14 +688,14 @@ void measure_freqs(void) uint f_clk_adc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_ADC); uint f_clk_rtc = frequency_count_khz(CLOCKS_FC0_SRC_VALUE_CLK_RTC); - printf("pll_sys = %dkHz\n", f_pll_sys); - printf("pll_usb = %dkHz\n", f_pll_usb); - printf("rosc = %dkHz\n", f_rosc); - printf("clk_sys = %dkHz\n", f_clk_sys); - printf("clk_peri = %dkHz\n", f_clk_peri); - printf("clk_usb = %dkHz\n", f_clk_usb); - printf("clk_adc = %dkHz\n", f_clk_adc); - printf("clk_rtc = %dkHz\n", f_clk_rtc); + fast_serial_printf("pll_sys = %dkHz\r\n", f_pll_sys); + fast_serial_printf("pll_usb = %dkHz\r\n", f_pll_usb); + fast_serial_printf("rosc = %dkHz\r\n", f_rosc); + fast_serial_printf("clk_sys = %dkHz\r\n", f_clk_sys); + fast_serial_printf("clk_peri = %dkHz\r\n", f_clk_peri); + fast_serial_printf("clk_usb = %dkHz\r\n", f_clk_usb); + fast_serial_printf("clk_adc = %dkHz\r\n", f_clk_adc); + fast_serial_printf("clk_rtc = %dkHz\r\n", f_clk_rtc); } void resus_callback(void) @@ -745,50 +709,40 @@ void resus_callback(void) // update clock status clock_status = INTERNAL; - - // Reconfigure uart as clocks have changed - stdio_init_all(); - if (DEBUG) - { - printf("Resus event fired\n"); - } - - // Wait for uart output to finish - uart_default_tx_wait_blocking(); } void loop() { - readline(); + fast_serial_read_until(readstring, 256, '\n'); int local_status = get_status(); if (strncmp(readstring, "version", 7) == 0) { - printf("version: %s\n", VERSION); + fast_serial_printf("version: %s\r\n", VERSION); } else if (strncmp(readstring, "status", 6) == 0) { - printf("run-status:%d clock-status:%d\n", local_status, clock_status); + fast_serial_printf("run-status:%d clock-status:%d\r\n", local_status, clock_status); } else if (strncmp(readstring, "debug on", 8) == 0) { DEBUG = 1; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else if (strncmp(readstring, "debug off", 9) == 0) { DEBUG = 0; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else if (strncmp(readstring, "getfreqs", 8) == 0) { measure_freqs(); - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else if (strncmp(readstring, "abort", 5) == 0) { if (local_status != RUNNING && local_status != TRANSITION_TO_RUNNING) { - printf("Can only abort when status is 1 or 2 (transitioning to running or running)"); + fast_serial_printf("Can only abort when status is 1 or 2 (transitioning to running or running)\r\n"); } else { @@ -801,7 +755,7 @@ void loop() gpio_put(OUT_PINS[i], 0); } // Should be done! - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "getwait", 7) == 0) @@ -812,19 +766,19 @@ void loop() int waits_per_pseudoclock = (max_waits / num_pseudoclocks_in_use) + 1; if (parsed < 2) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else if (addr >= waits_per_pseudoclock) { - printf("invalid address\n"); + fast_serial_printf("invalid address\r\n"); } else if (addr >= get_num_processed_waits(pseudoclock)) { - printf("wait not yet available\n"); + fast_serial_printf("wait not yet available\r\n"); } else { @@ -840,13 +794,13 @@ void loop() // We multiply by two here to counteract the divide by two when storing (see below) wait_remaining *= 2; } - printf("%u\n", wait_remaining); + fast_serial_printf("%u\r\n", wait_remaining); } } // Prevent manual mode commands from running during buffered execution else if (local_status != ABORTED && local_status != STOPPED) { - printf("Cannot execute command %s during buffered execution. Check status first and wait for it to return 0 or 5 (stopped or aborted).\n", readstring); + fast_serial_printf("Cannot execute command %s during buffered execution. Check status first and wait for it to return 0 or 5 (stopped or aborted).\r\n", readstring); } // Set number of pseudoclocks else if (strncmp(readstring, "setnumpseudoclocks", 17) == 0) @@ -855,11 +809,11 @@ void loop() int parsed = sscanf(readstring, "%*s %u %u", &num_pseudoclocks); if (parsed < 1) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (num_pseudoclocks < 1 || num_pseudoclocks > 4) { - printf("The number of pseudoclocks must be between 1 and 4 (inclusive)\n"); + fast_serial_printf("The number of pseudoclocks must be between 1 and 4 (inclusive)\r\n"); } else { @@ -877,7 +831,7 @@ void loop() instructions[i] = 0; } num_pseudoclocks_in_use = num_pseudoclocks; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "setinpin", 8) == 0) @@ -887,28 +841,28 @@ void loop() int parsed = sscanf(readstring, "%*s %u %u", &pseudoclock, &pin_no); if (parsed < 2) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else if (pin_no == OUT_PINS[0] || pin_no == OUT_PINS[1] || pin_no == OUT_PINS[2] || pin_no == OUT_PINS[3]) { - printf("IN pin cannot be the same as one of the OUT pins\n"); + fast_serial_printf("IN pin cannot be the same as one of the OUT pins\r\n"); } else if (pin_no < 0 || pin_no > 19) { - printf("IN pin must be between 0 and 19 (inclusive)\n"); + fast_serial_printf("IN pin must be between 0 and 19 (inclusive)\r\n"); } else if (pin_no == IN_PINS[pseudoclock]) { - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else { IN_PINS[pseudoclock] = pin_no; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "setoutpin", 9) == 0) @@ -918,32 +872,32 @@ void loop() int parsed = sscanf(readstring, "%*s %u %u", &pseudoclock, &pin_no); if (parsed < 2) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else if (pin_no == IN_PINS[0] || pin_no == IN_PINS[1] || pin_no == IN_PINS[2] || pin_no == IN_PINS[3]) { - printf("OUT pin cannot be the same as one of the IN pins\n"); + fast_serial_printf("OUT pin cannot be the same as one of the IN pins\r\n"); } else if (pin_no != 25 && (pin_no < 0 || pin_no > 19)) { - printf("OUT pin must be between 0 and 19 (inclusive) or 25 (LED for debugging)\n"); + fast_serial_printf("OUT pin must be between 0 and 19 (inclusive) or 25 (LED for debugging)\r\n"); } else if (pin_no == OUT_PINS[pseudoclock]) { - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else if (pin_no == OUT_PINS[0] || pin_no == OUT_PINS[1] || pin_no == OUT_PINS[2] || pin_no == OUT_PINS[3]) { - printf("OUT pin cannot be the same as one of the other OUT pins\n"); + fast_serial_printf("OUT pin cannot be the same as one of the other OUT pins\r\n"); } else { OUT_PINS[pseudoclock] = pin_no; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "getoutpin", 9) == 0) @@ -952,21 +906,21 @@ void loop() int parsed = sscanf(readstring, "%*s %u", &pseudoclock); if (parsed < 1) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else { if (OUT_PINS[pseudoclock] == INVALID_PIN_NUMBER) { - printf("default\n"); + fast_serial_printf("default\r\n"); } else { - printf("%u\n", OUT_PINS[pseudoclock]); + fast_serial_printf("%u\r\n", OUT_PINS[pseudoclock]); } } } @@ -976,21 +930,21 @@ void loop() int parsed = sscanf(readstring, "%*s %u", &pseudoclock); if (parsed < 1) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else { if (IN_PINS[pseudoclock] == INVALID_PIN_NUMBER) { - printf("default\n"); + fast_serial_printf("default\r\n"); } else { - printf("%u\n", IN_PINS[pseudoclock]); + fast_serial_printf("%u\r\n", IN_PINS[pseudoclock]); } } } @@ -1001,17 +955,17 @@ void loop() int parsed = sscanf(readstring, "%*s %u %u", &src, &freq); if (parsed < 2) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else { if (DEBUG) { - printf("Got request mode=%u, freq=%u MHz\n", src, freq / MHZ); + fast_serial_printf("Got request mode=%u, freq=%u MHz\r\n", src, freq / MHZ); } if (src > 2) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else { @@ -1020,7 +974,7 @@ void loop() // Do validation checking on values provided if (freq > 133 * MHZ) { - printf("Invalid clock frequency specified\n"); + fast_serial_printf("Invalid clock frequency specified\r\n"); return; } #endif //PRAWNBLASTER_OVERCLOCK @@ -1030,12 +984,12 @@ void loop() { if (set_sys_clock_khz(freq / 1000, false)) { - printf("ok\n"); + fast_serial_printf("ok\r\n"); clock_status = INTERNAL; } else { - printf("Failure. Cannot exactly achieve that clock frequency."); + fast_serial_printf("Failure. Cannot exactly achieve that clock frequency.\r\n"); } } else @@ -1043,7 +997,7 @@ void loop() clock_configure_gpin(clk_sys, (src == 2 ? 22 : 20), freq, freq); // update clock status clock_status = EXTERNAL; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } } @@ -1054,16 +1008,16 @@ void loop() int parsed = sscanf(readstring, "%*s %u", &core); if (parsed < 1) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (core != 0 && core != 1) { - printf("You must specify either 0 for PIO0 or 1 for PIO1\n"); + fast_serial_printf("You must specify either 0 for PIO0 or 1 for PIO1\r\n"); } else { pio_to_use = core == 0 ? pio0 : pio1; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "hwstart", 7) == 0) @@ -1078,7 +1032,7 @@ void loop() multicore_fifo_push_blocking(1); // update status set_status(TRANSITION_TO_RUNNING); - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else if ((strncmp(readstring, "start", 5) == 0)) { @@ -1092,7 +1046,7 @@ void loop() multicore_fifo_push_blocking(0); // update status set_status(TRANSITION_TO_RUNNING); - printf("ok\n"); + fast_serial_printf("ok\r\n"); } // TODO: update this to support pseudoclock selection else if (strncmp(readstring, "set ", 4) == 0) @@ -1105,15 +1059,15 @@ void loop() int address_offset = pseudoclock * (max_instructions * 2 / num_pseudoclocks_in_use + 2); if (parsed < 4) { - printf("invalid request\n"); + fast_serial_printf("invalid request\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else if (addr >= max_instructions) { - printf("invalid address\n"); + fast_serial_printf("invalid address\r\n"); } else if (reps == 0) { @@ -1123,7 +1077,7 @@ void loop() { // It's a stop instruction instructions[address_offset + addr * 2 + 1] = 0; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else if (half_period >= 6) { @@ -1134,26 +1088,26 @@ void loop() // the timeout is accurate. // The wait loop conatins two ASM instructions, so we divide by 2 here. instructions[address_offset + addr * 2 + 1] = (half_period - 4) / 2; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } else { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } } else if (half_period < (non_loop_path_length)) { - printf("half-period too short\n"); + fast_serial_printf("half-period too short\r\n"); } else if (reps < 1) { - printf("reps must be at least one\n"); + fast_serial_printf("reps must be at least one\r\n"); } else { instructions[address_offset + addr * 2] = reps; instructions[address_offset + addr * 2 + 1] = half_period - non_loop_path_length; - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "get ", 4) == 0) @@ -1164,15 +1118,15 @@ void loop() int address_offset = pseudoclock * (max_instructions * 2 / num_pseudoclocks_in_use + 2); if (parsed < 2) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else if (addr >= max_instructions) { - printf("invalid address\n"); + fast_serial_printf("invalid address\r\n"); } else { @@ -1193,7 +1147,159 @@ void loop() half_period += 4; } } - printf("%u %u\n", half_period, reps); + fast_serial_printf("%u %u\r\n", half_period, reps); + } + } + else if (strncmp(readstring, "setb ", 5) == 0) + { + // set a large block of instructions encoded in a binary blob of fixed length. + unsigned int start_addr; + unsigned int inst_count; + unsigned int pseudoclock; + int parsed = sscanf(readstring, "%*s %u %u %u", &pseudoclock, &start_addr, &inst_count); + int address_offset = pseudoclock * (max_instructions * 2 / num_pseudoclocks_in_use + 2); + if (parsed < 3) + { + fast_serial_printf("invalid request\n"); + } + else if (pseudoclock < 0 || pseudoclock > 3) + { + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); + } + else if (start_addr + inst_count >= max_instructions) + { + fast_serial_printf("Invalid address and/or too many instructions (%d + %d).\r\n", start_addr, inst_count); + } + else + { + fast_serial_printf("ready\r\n"); + // It takes 8 bytes to describe an instruction: 4 bytes for reps, 4 bytes for half period + uint32_t inst_per_buffer = SERIAL_BUFFER_SIZE / 8; + unsigned int addr = start_addr; + + // Variables to track errors + uint32_t reps_error_count = 0; + uint32_t last_reps_error_idx = 0; + uint32_t half_period_error_count = 0; + uint32_t last_half_period_error_idx = 0; + + while(inst_count > inst_per_buffer){ + fast_serial_read(readstring, 8*inst_per_buffer); + for (int i = 0; i < inst_per_buffer; i++) + { + uint32_t reps = ((readstring[8*i + 7] << 24) + | (readstring[8*i + 6] << 16) + | (readstring[8*i + 5] << 8) + | (readstring[8*i + 4])); + uint32_t half_period = ((readstring[8*i + 3] << 24) + | (readstring[8*i + 2] << 16) + | (readstring[8*i + 1] << 8) + | (readstring[8*i + 0])); + + if (reps == 0) + { + // This indicates either a stop or a wait instruction + instructions[address_offset + addr * 2] = 0; + if (half_period == 0) + { + // It's a stop instruction + instructions[address_offset + addr * 2 + 1] = 0; + addr++; + } + else if (half_period >= 6) + { + // It's a wait instruction. See "set" command for why we do this. + instructions[address_offset + addr * 2 + 1] = (half_period - 4) / 2; + addr++; + } + else + { + reps_error_count++; + last_reps_error_idx = (address_offset + addr * 2 + 1) / 2; + } + } + else if (half_period < (non_loop_path_length)) + { + fast_serial_printf("half-period too short\r\n"); + half_period_error_count++; + last_half_period_error_idx = (address_offset + addr * 2 + 1) / 2; + } + else + { + instructions[address_offset + addr * 2] = reps; + instructions[address_offset + addr * 2 + 1] = half_period - non_loop_path_length; + addr++; + } + } + inst_count -= inst_per_buffer; + } + // In this if statement, we read a final serial buffer and load it into instructions. + if(inst_count > 0) + { + fast_serial_read(readstring, 8*inst_count); + for(int i = 0; i < inst_count; i++) + { + uint32_t reps = ((readstring[8*i + 7] << 24) + | (readstring[8*i + 6] << 16) + | (readstring[8*i + 5] << 8) + | (readstring[8*i + 4])); + uint32_t half_period = ((readstring[8*i + 3] << 24) + | (readstring[8*i + 2] << 16) + | (readstring[8*i + 1] << 8) + | (readstring[8*i + 0])); + + if (reps == 0) + { + // This indicates either a stop or a wait instruction + instructions[address_offset + addr * 2] = 0; + if (half_period == 0) + { + // It's a stop instruction + instructions[address_offset + addr * 2 + 1] = 0; + addr++; + } + else if (half_period >= 6) + { + // It's a wait instruction. See "set" command for why we do this. + instructions[address_offset + addr * 2 + 1] = (half_period - 4) / 2; + addr++; + } + else + { + reps_error_count++; + last_reps_error_idx = (address_offset + addr * 2 + 1) / 2; + } + } + else if (half_period < (non_loop_path_length)) + { + half_period_error_count++; + last_half_period_error_idx = (address_offset + addr * 2 + 1) / 2; + fast_serial_printf("half-period too short\r\n"); + } + else + { + instructions[address_offset + addr * 2] = reps; + instructions[address_offset + addr * 2 + 1] = half_period - non_loop_path_length; + addr++; + } + } + } + if (reps_error_count == 0 && half_period_error_count == 0) + { + fast_serial_printf("ok\r\n"); + } + else + { + if (reps_error_count > 0) + { + fast_serial_printf("Invalid half-period for wait in %d instructions, most recent error at instruction %d. Skipping these instructions.\r\n", reps_error_count, last_reps_error_idx); + } + if (reps_error_count > 0) + { + fast_serial_printf("Too short half-period in %d instructions, most recent error at instruction %d. Skipping these instructions.\r\n", half_period_error_count, last_half_period_error_idx); + + } + } } } else if (strncmp(readstring, "go high", 7) == 0) @@ -1202,17 +1308,17 @@ void loop() int parsed = sscanf(readstring, "%*s %*s %u", &pseudoclock); if (parsed < 1) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else { configure_gpio(); gpio_put(OUT_PINS[pseudoclock], 1); - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } else if (strncmp(readstring, "go low", 6) == 0) @@ -1221,22 +1327,26 @@ void loop() int parsed = sscanf(readstring, "%*s %*s %u", &pseudoclock); if (parsed < 1) { - printf("invalid request\n"); + fast_serial_printf("invalid request\r\n"); } else if (pseudoclock < 0 || pseudoclock > 3) { - printf("The specified pseudoclock must be between 0 and 3 (inclusive)\n"); + fast_serial_printf("The specified pseudoclock must be between 0 and 3 (inclusive)\r\n"); } else { configure_gpio(); gpio_put(OUT_PINS[pseudoclock], 0); - printf("ok\n"); + fast_serial_printf("ok\r\n"); } } + else if (strncmp(readstring, "program", 7) == 0) + { + reset_usb_boot(0, 0); + } else { - printf("invalid request: %s\n", readstring); + fast_serial_printf("invalid request: %s\r\n", readstring); } } @@ -1268,7 +1378,7 @@ int main() // Temp output 48MHZ clock for debug clock_gpio_init(21, CLOCKS_CLK_GPOUT0_CTRL_AUXSRC_VALUE_CLK_USB, 1); - stdio_init_all(); + fast_serial_init(); multicore_launch_core1(core1_entry); multicore_fifo_pop_blocking(); diff --git a/prawnblaster/tusb_config.h b/prawnblaster/tusb_config.h new file mode 100644 index 0000000..c0c3e77 --- /dev/null +++ b/prawnblaster/tusb_config.h @@ -0,0 +1,35 @@ +#ifndef _TUSB_CONFIG_H_ +#define _TUSB_CONFIG_H_ + +#define BOARD_DEVICE_RHPORT_SPEED OPT_MODE_FULL_SPEED // RP2040 only supports full speed + +#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_MEM_SECTION +#define CFG_TUSB_MEM_SECTION +#endif + +#ifndef CFG_TUSB_MEM_ALIGN +#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#endif + +#ifndef CFG_TUD_ENDPOINT0_SIZE +#define CFG_TUD_ENDPOINT0_SIZE 64 +#endif + +#define CFG_TUD_HID 0 +#define CFG_TUD_CDC 1 +#define CFG_TUD_MSC 0 +#define CFG_TUD_MIDI 0 +#define CFG_TUD_VENDOR 0 + +#define CFG_TUD_CDC_RX_BUFSIZE 64 +#define CFG_TUD_CDC_TX_BUFSIZE 64 + +#endif diff --git a/readme.md b/readme.md index 882e2e2..c6a75fd 100644 --- a/readme.md +++ b/readme.md @@ -75,6 +75,7 @@ Note: the commands are only read if terminated with `\r\n`. * `hwstart`: Triggers the execution of the instruction set(s), but only after first detecting logical high on the trigger input(s). * `set `: Sets the values of instruction number `addr` for the pseudoclock `pseudoclock` (pseudoclock is zero indexed). `addr` starts at `0`. `half-period` is specified in clock cycles and must be at least `5` (and less than 2^32) for a normal instruction. `reps` should be `1` or more (and less than 2^32) for a normal instruction and indicates how many times the pulse should repeat. Special instructions can be specified with `reps=0`. A stop (end execution) instruction is specified by setting both `reps` and `half-period` to `0`. A wait instruction is specified by `reps=0` and `half-period=` where the wait-timeout/half-period must be at least 6 clock cycles. Two waits in a row (sequential PrawnBlaster instructions) will trigger an indefinite wait should the first timeout expire (the second wait timeout is ignored and the length of this wait is not logged). See below (FAQ) for details on the requirements for trigger pulse lengths. * `get `: Gets the half-period and reps of the instruction at `addr` for the pseudoclock `pseudoclock` (pseudoclock is zero indexed). Return values are integers, separated by a space, in the same format as `set`. +* `setb `: Sets the values of instructions number `start addr` through `start addr + instruction count` for the pseudoclock `pseudoclock` (pseudoclock is zero indexed). `addr` starts at `0`. After this command is sent, PrawnBlaster reads `instruction count` 8 byte packets and decodes them into instruction values. The first 4 bytes of each packet are `reps` and the second 4 bytes are `half period`, each encoded as an unsigned little-Endian 32 bit integer. Instructions are then processed the same way as `set` (including stop instructions and wait instructions). * `go high `: Forces the GPIO output high for the pseudoclock `pseudoclock` (pseudoclock is zero indexed). This is useful for debugging. * `go low `: Forces the GPIO output low for the pseudoclock `pseudoclock` (pseudoclock is zero indexed). This is useful for debugging. * `setinpin `: Configures which GPIO to use for the pseudoclock `pseudoclock` trigger input (pseudoclock is zero indexed). Defaults to GPIO 0, 2, 4, and 6 for pseudoclocks 0, 1, 2 and 3 respectively. Should be between 0 and 19 inclusive. Trigger inputs can be shared between pseudoclocks (e.g. `setinpin 0 10` followed by `setinpin 1 10` is valid). Note that different defaults may be used if you explicitly assign the default for another use via `setinpin` or `setoutpin`. See FAQ below for more details. @@ -83,6 +84,7 @@ Note: the commands are only read if terminated with `\r\n`. * `getoutpin `: Gets the currently set trigger output pin for pseudoclock `pseudoclock`. Returns either an integer corresponding to the set pin or `default` to indicate it will use try and use the default pin as defined above for `setoutpin`. See FAQ below for more details on what happens if it can't use the default. * `debug `: Turns on extra debug messages printed over serial. `state` should be `on` or `off` (no string quotes required). * `setpio `: Sets whether the PrawnBlaster should use pio0 or pio1 in the RP2040 chip (both have 4 state machines). Defaults to `0` (pio0) on powerup. May be useful if your particular board shows different timing behaviour (on the sub 10ns scale) between the PIO cores and you care about this level of precision. Otherwise you can leave this as the default. +* `program`: Equivalent to disconnecting the Pico, holding down the "bootsel" button, and reconnecting the Pico. Places the Pico into firmware flashing mode; the PrawnBlaster serial port should disappear and the Pico should mount as a mass storage device. ## Reconfiguring the internal clock. The clock frequency (and even source) can be reconfigured at runtime (it is initially set to 100 MHz on every boot).