From 609ea52497449bb914bf96eeccef6429a3506065 Mon Sep 17 00:00:00 2001 From: Santosh Bala Date: Tue, 28 Jul 2026 17:46:15 +0530 Subject: [PATCH 1/5] feat: add pattern generator SCPI commands --- CMakeLists.txt | 2 + src/application/pattern_generator_commands.c | 196 +++++++++++++++++++ src/application/pattern_generator_commands.h | 34 ++++ src/application/protocol/common.c | 28 +++ src/application/protocol/pg.c | 146 ++++++++++++++ 5 files changed, 406 insertions(+) create mode 100644 src/application/pattern_generator_commands.c create mode 100644 src/application/pattern_generator_commands.h create mode 100644 src/application/protocol/pg.c diff --git a/CMakeLists.txt b/CMakeLists.txt index d5b91de4..e9dc5171 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,12 +19,14 @@ target_sources(pslab_pico PRIVATE src/application/protocol/la.c src/application/protocol/dso.c src/application/protocol/mso.c + src/application/protocol/pg.c src/application/communication_commands.c src/application/gateway/i2c_commands.c src/application/gateway/uart_commands.c src/application/logic_analyser_commands.c src/application/dso_commands.c src/application/mixed_signal_commands.c + src/application/pattern_generator_commands.c src/system/instrument/dso.c src/system/instrument/mixed_signal.c src/system/pattern_generator.c diff --git a/src/application/pattern_generator_commands.c b/src/application/pattern_generator_commands.c new file mode 100644 index 00000000..c5e61d17 --- /dev/null +++ b/src/application/pattern_generator_commands.c @@ -0,0 +1,196 @@ +#include "application/pattern_generator_commands.h" + +#include + +#include "system/pattern_generator.h" + +enum { + PG_DEFAULT_PIN_BASE = 16, + PG_DEFAULT_PIN_COUNT = 1, + PG_DEFAULT_RATE_HZ = 1000, + PG_MAX_PIN_COUNT = 8, + PG_MAX_PATTERN_WORDS = 16384, + PG_MAX_RATE_HZ = 75000000, +}; + +static PatternGenerator pg; +static bool pg_initialized; +static uint32_t pattern_buffer[PG_MAX_PATTERN_WORDS]; + +static struct { + uint32_t pin_base; + uint32_t pin_count; + uint32_t rate_hz; + uint32_t pattern_words; + PatternGeneratorMode mode; +} state = { + .pin_base = PG_DEFAULT_PIN_BASE, + .pin_count = PG_DEFAULT_PIN_COUNT, + .rate_hz = PG_DEFAULT_RATE_HZ, + .pattern_words = 0, + .mode = PATTERN_GENERATOR_MODE_ONCE, +}; + +static bool config_is_valid(void) +{ + return state.pin_count >= 1 && state.pin_count <= PG_MAX_PIN_COUNT && + state.pin_base + state.pin_count <= 30 && state.rate_hz >= 1 && + state.rate_hz <= PG_MAX_RATE_HZ; +} + +static bool apply_config(void) +{ + if (!config_is_valid()) { + return false; + } + + PatternGeneratorConfig config = { + .pin_base = state.pin_base, + .pin_count = state.pin_count, + .rate_hz = state.rate_hz, + }; + + if (pg_initialized) { + return pattern_generator_configure(&pg, &config); + } + + pg_initialized = pattern_generator_init(&pg, &config); + return pg_initialized; +} + +void pg_reset_state(void) +{ + pg_stop(); + if (pg_initialized) { + pattern_generator_deinit(&pg); + } + + pg_initialized = false; + state.pin_base = PG_DEFAULT_PIN_BASE; + state.pin_count = PG_DEFAULT_PIN_COUNT; + state.rate_hz = PG_DEFAULT_RATE_HZ; + state.pattern_words = 0; + state.mode = PATTERN_GENERATOR_MODE_ONCE; + memset(pattern_buffer, 0, sizeof(pattern_buffer)); +} + +void pg_task(void) +{ + if (pg_initialized) { + pattern_generator_task(&pg); + } +} + +bool pg_set_pins(uint32_t pin_base, uint32_t pin_count) +{ + if (pin_count < 1 || pin_count > PG_MAX_PIN_COUNT || + pin_base + pin_count > 30 || pg_is_running()) { + return false; + } + + uint32_t old_pin_base = state.pin_base; + uint32_t old_pin_count = state.pin_count; + state.pin_base = pin_base; + state.pin_count = pin_count; + + if (pg_initialized && !apply_config()) { + state.pin_base = old_pin_base; + state.pin_count = old_pin_count; + return false; + } + + return true; +} + +bool pg_set_rate(uint32_t rate_hz) +{ + if (rate_hz < 1 || rate_hz > PG_MAX_RATE_HZ || pg_is_running()) { + return false; + } + + uint32_t old_rate_hz = state.rate_hz; + state.rate_hz = rate_hz; + + if (pg_initialized && !apply_config()) { + state.rate_hz = old_rate_hz; + return false; + } + + return true; +} + +bool pg_set_mode_once(void) +{ + if (pg_is_running()) { + return false; + } + + state.mode = PATTERN_GENERATOR_MODE_ONCE; + return true; +} + +bool pg_set_mode_loop(void) +{ + if (pg_is_running()) { + return false; + } + + state.mode = PATTERN_GENERATOR_MODE_LOOP; + return true; +} + +bool pg_upload_data(uint8_t const *data, size_t len) +{ + if (!data || len == 0 || (len % sizeof(uint32_t)) != 0 || pg_is_running()) { + return false; + } + + size_t word_count = len / sizeof(uint32_t); + if (word_count > PG_MAX_PATTERN_WORDS) { + return false; + } + + memcpy(pattern_buffer, data, len); + state.pattern_words = (uint32_t)word_count; + return true; +} + +bool pg_start(void) +{ + if (state.pattern_words == 0) { + return false; + } + + if (!pg_initialized && !apply_config()) { + return false; + } + + return pattern_generator_start( + &pg, + pattern_buffer, + state.pattern_words, + state.mode + ); +} + +void pg_stop(void) +{ + if (pg_initialized) { + pattern_generator_stop(&pg); + } +} + +uint32_t pg_get_pin_base(void) { return state.pin_base; } + +uint32_t pg_get_pin_count(void) { return state.pin_count; } + +uint32_t pg_get_rate(void) { return state.rate_hz; } + +bool pg_get_mode_loop(void) { return state.mode == PATTERN_GENERATOR_MODE_LOOP; } + +uint32_t pg_get_pattern_words(void) { return state.pattern_words; } + +bool pg_is_running(void) +{ + return pg_initialized && pattern_generator_is_running(&pg); +} diff --git a/src/application/pattern_generator_commands.h b/src/application/pattern_generator_commands.h new file mode 100644 index 00000000..678c9538 --- /dev/null +++ b/src/application/pattern_generator_commands.h @@ -0,0 +1,34 @@ +#ifndef PATTERN_GENERATOR_COMMANDS_H +#define PATTERN_GENERATOR_COMMANDS_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +void pg_reset_state(void); +void pg_task(void); + +bool pg_set_pins(uint32_t pin_base, uint32_t pin_count); +bool pg_set_rate(uint32_t rate_hz); +bool pg_set_mode_once(void); +bool pg_set_mode_loop(void); +bool pg_upload_data(uint8_t const *data, size_t len); +bool pg_start(void); +void pg_stop(void); + +uint32_t pg_get_pin_base(void); +uint32_t pg_get_pin_count(void); +uint32_t pg_get_rate(void); +bool pg_get_mode_loop(void); +uint32_t pg_get_pattern_words(void); +bool pg_is_running(void); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/application/protocol/common.c b/src/application/protocol/common.c index d78a642e..cbaf26c9 100644 --- a/src/application/protocol/common.c +++ b/src/application/protocol/common.c @@ -20,6 +20,7 @@ #include "application/dso_commands.h" #include "application/logic_analyser_commands.h" #include "application/mixed_signal_commands.h" +#include "application/pattern_generator_commands.h" #include "application/protocol/bus/i2c.h" #include "application/protocol/bus/uart.h" #include "platform/platform.h" @@ -108,6 +109,18 @@ extern scpi_result_t scpi_cmd_read_mso_analog_q(scpi_t *context); extern scpi_result_t scpi_cmd_status_mso_q(scpi_t *context); extern scpi_result_t scpi_cmd_metadata_mso_q(scpi_t *context); +// Forward declarations of pattern generator functions needed by common +extern scpi_result_t scpi_cmd_pattern_generator_pins(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_pins_q(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_rate(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_rate_q(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_mode(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_mode_q(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_data(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_start(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_stop(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_status_q(scpi_t *context); + static scpi_result_t scpi_cmd_la_wifi_read_q(scpi_t *context); static scpi_result_t scpi_cmd_dso_wifi_read_q(scpi_t *context); static scpi_result_t scpi_cmd_mso_wifi_read_q(scpi_t *context); @@ -210,6 +223,7 @@ static scpi_result_t protocol_reset(scpi_t *context) la_reset_state(); dso_commands_reset(); mso_commands_reset(); + pg_reset_state(); return SCPI_RES_OK; } @@ -356,6 +370,18 @@ static scpi_command_t const g_SCPI_COMMANDS[] = { { "MSO:METadata?", scpi_cmd_metadata_mso_q }, { "MSO:WIFI:READ?", scpi_cmd_mso_wifi_read_q }, + // Digital pattern generator commands + { "PG:CONFigure:PINS", scpi_cmd_pattern_generator_pins }, + { "PG:CONFigure:PINS?", scpi_cmd_pattern_generator_pins_q }, + { "PG:CONFigure:RATE", scpi_cmd_pattern_generator_rate }, + { "PG:CONFigure:RATE?", scpi_cmd_pattern_generator_rate_q }, + { "PG:CONFigure:MODE", scpi_cmd_pattern_generator_mode }, + { "PG:CONFigure:MODE?", scpi_cmd_pattern_generator_mode_q }, + { "PG:DATA", scpi_cmd_pattern_generator_data }, + { "PG:STARt", scpi_cmd_pattern_generator_start }, + { "PG:STOP", scpi_cmd_pattern_generator_stop }, + { "PG:STATus?", scpi_cmd_pattern_generator_status_q }, + // Built-in test signal commands { "TEST:SQUare", scpi_cmd_test_square }, { "TEST:SQUare?", scpi_cmd_test_square_q }, @@ -632,6 +658,8 @@ void protocol_task(void) return; } + pg_task(); + // Step USB task usb_cdc_task(); diff --git a/src/application/protocol/pg.c b/src/application/protocol/pg.c new file mode 100644 index 00000000..c8deda0b --- /dev/null +++ b/src/application/protocol/pg.c @@ -0,0 +1,146 @@ +/** + * @file pg.c + * @brief Digital pattern generator SCPI commands implementation + */ + +#include +#include +#include + +#include "scpi/error.h" +#include "scpi/scpi.h" + +#include "application/pattern_generator_commands.h" + +static scpi_result_t result_ok(void) { return SCPI_RES_OK; } + +static scpi_result_t result_illegal_parameter(scpi_t *context) +{ + SCPI_ErrorPush(context, SCPI_ERROR_ILLEGAL_PARAMETER_VALUE); + return SCPI_RES_ERR; +} + +static scpi_result_t result_execution_error(scpi_t *context) +{ + SCPI_ErrorPush(context, SCPI_ERROR_EXECUTION_ERROR); + return SCPI_RES_ERR; +} + +scpi_result_t scpi_cmd_pattern_generator_pins(scpi_t *context) +{ + uint32_t pin_base = 0; + uint32_t pin_count = 0; + + if (!SCPI_ParamUInt32(context, &pin_base, TRUE) || + !SCPI_ParamUInt32(context, &pin_count, TRUE)) { + SCPI_ErrorPush(context, SCPI_ERROR_MISSING_PARAMETER); + return SCPI_RES_ERR; + } + + return pg_set_pins(pin_base, pin_count) ? result_ok() + : result_illegal_parameter(context); +} + +scpi_result_t scpi_cmd_pattern_generator_pins_q(scpi_t *context) +{ + char pins[32]; + snprintf( + pins, + sizeof(pins), + "%lu,%lu", + (unsigned long)pg_get_pin_base(), + (unsigned long)pg_get_pin_count() + ); + SCPI_ResultText(context, pins); + return SCPI_RES_OK; +} + +scpi_result_t scpi_cmd_pattern_generator_rate(scpi_t *context) +{ + uint32_t rate_hz = 0; + if (!SCPI_ParamUInt32(context, &rate_hz, TRUE)) { + SCPI_ErrorPush(context, SCPI_ERROR_MISSING_PARAMETER); + return SCPI_RES_ERR; + } + + return pg_set_rate(rate_hz) ? result_ok() + : result_illegal_parameter(context); +} + +scpi_result_t scpi_cmd_pattern_generator_rate_q(scpi_t *context) +{ + SCPI_ResultUInt32(context, pg_get_rate()); + return SCPI_RES_OK; +} + +scpi_result_t scpi_cmd_pattern_generator_mode(scpi_t *context) +{ + enum { PG_MODE_ONCE, PG_MODE_LOOP }; + scpi_choice_def_t const choices[] = { + { "ONCE", PG_MODE_ONCE }, + { "LOOP", PG_MODE_LOOP }, + SCPI_CHOICE_LIST_END + }; + int32_t choice = -1; + + if (!SCPI_ParamChoice(context, choices, &choice, TRUE)) { + SCPI_ErrorPush(context, SCPI_ERROR_MISSING_PARAMETER); + return SCPI_RES_ERR; + } + + if (choice == PG_MODE_LOOP) { + return pg_set_mode_loop() ? result_ok() + : result_illegal_parameter(context); + } + + return pg_set_mode_once() ? result_ok() : result_illegal_parameter(context); +} + +scpi_result_t scpi_cmd_pattern_generator_mode_q(scpi_t *context) +{ + SCPI_ResultText(context, pg_get_mode_loop() ? "LOOP" : "ONCE"); + return SCPI_RES_OK; +} + +scpi_result_t scpi_cmd_pattern_generator_data(scpi_t *context) +{ + char const *data = NULL; + size_t len = 0; + + if (!SCPI_ParamArbitraryBlock(context, &data, &len, TRUE)) { + SCPI_ErrorPush(context, SCPI_ERROR_MISSING_PARAMETER); + return SCPI_RES_ERR; + } + + return pg_upload_data((uint8_t const *)data, len) + ? result_ok() + : result_illegal_parameter(context); +} + +scpi_result_t scpi_cmd_pattern_generator_start(scpi_t *context) +{ + return pg_start() ? result_ok() : result_execution_error(context); +} + +scpi_result_t scpi_cmd_pattern_generator_stop(scpi_t *context) +{ + (void)context; + pg_stop(); + return SCPI_RES_OK; +} + +scpi_result_t scpi_cmd_pattern_generator_status_q(scpi_t *context) +{ + char status[64]; + snprintf( + status, + sizeof(status), + "%u,%lu,%lu,%lu", + pg_is_running() ? 1u : 0u, + (unsigned long)pg_get_rate(), + (unsigned long)pg_get_pin_count(), + (unsigned long)pg_get_pattern_words() + ); + SCPI_ResultText(context, status); + return SCPI_RES_OK; +} From 8cbc7aec8013d7796a2887a01e840873f164fe00 Mon Sep 17 00:00:00 2001 From: Santosh Bala Date: Sun, 2 Aug 2026 00:51:57 +0530 Subject: [PATCH 2/5] fix: clk div and pg_init --- src/application/pattern_generator_commands.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/application/pattern_generator_commands.c b/src/application/pattern_generator_commands.c index c5e61d17..d3a4b5bf 100644 --- a/src/application/pattern_generator_commands.c +++ b/src/application/pattern_generator_commands.c @@ -7,7 +7,7 @@ enum { PG_DEFAULT_PIN_BASE = 16, PG_DEFAULT_PIN_COUNT = 1, - PG_DEFAULT_RATE_HZ = 1000, + PG_DEFAULT_RATE_HZ = 2000, PG_MAX_PIN_COUNT = 8, PG_MAX_PATTERN_WORDS = 16384, PG_MAX_RATE_HZ = 75000000, @@ -58,6 +58,15 @@ static bool apply_config(void) return pg_initialized; } +static void mark_unconfigured(void) +{ + if (pg_initialized) { + pattern_generator_deinit(&pg); + } + + pg_initialized = false; +} + void pg_reset_state(void) { pg_stop(); @@ -96,6 +105,7 @@ bool pg_set_pins(uint32_t pin_base, uint32_t pin_count) if (pg_initialized && !apply_config()) { state.pin_base = old_pin_base; state.pin_count = old_pin_count; + mark_unconfigured(); return false; } @@ -113,6 +123,7 @@ bool pg_set_rate(uint32_t rate_hz) if (pg_initialized && !apply_config()) { state.rate_hz = old_rate_hz; + mark_unconfigured(); return false; } From 3abb8ab268059ef4a648432dd12fb69e29f560fc Mon Sep 17 00:00:00 2001 From: Santosh Bala Date: Sun, 2 Aug 2026 01:00:25 +0530 Subject: [PATCH 3/5] docs: add pattern generator commands --- README.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/README.md b/README.md index 19a5f3ce..2cb033fc 100644 --- a/README.md +++ b/README.md @@ -230,6 +230,28 @@ Available commands: bytes per transfer. `TRANsact?` sends the write block followed by a repeated start read, which is the common register read pattern for I2C sensors. +## Digital Pattern Generator + +The digital pattern generator outputs packed 32-bit pattern words through the +PIO/DMA pattern output backend. It defaults to GPIO16, one output pin, +2000 samples per second, and `ONCE` mode. + +Available commands: + +- `PG:CONFigure:PINS ,` +- `PG:CONFigure:PINS?` +- `PG:CONFigure:RATE ` +- `PG:CONFigure:RATE?` +- `PG:CONFigure:MODE ` +- `PG:CONFigure:MODE?` +- `PG:DATA ` +- `PG:STARt` +- `PG:STOP` +- `PG:STATus?` + +`PG:DATA` uses a SCPI arbitrary block containing little-endian `uint32_t` +pattern words. `PG:STATus?` returns `running,rate_hz,pin_count,pattern_words`. + ## Build Configure from the project root: From 4ceee9b71334e7b29539eef7e1024a90ae1de176 Mon Sep 17 00:00:00 2001 From: Santosh Bala Date: Tue, 4 Aug 2026 02:02:26 +0530 Subject: [PATCH 4/5] feat: pack pattern generator samples --- README.md | 7 +++++-- src/platform/pattern_output_ll.c | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 2cb033fc..ec99b84b 100644 --- a/README.md +++ b/README.md @@ -249,8 +249,11 @@ Available commands: - `PG:STOP` - `PG:STATus?` -`PG:DATA` uses a SCPI arbitrary block containing little-endian `uint32_t` -pattern words. `PG:STATus?` returns `running,rate_hz,pin_count,pattern_words`. +`PG:DATA` uses a SCPI arbitrary block containing little-endian packed +`uint32_t` pattern words. Each word stores `floor(32 / pin_count)` consecutive +samples; each sample consumes `pin_count` bits, starting from the least +significant bits. Unused high bits in each word are ignored. `PG:STATus?` +returns `running,rate_hz,pin_count,pattern_words`. ## Build diff --git a/src/platform/pattern_output_ll.c b/src/platform/pattern_output_ll.c index b8d1bbf7..abb24ad0 100644 --- a/src/platform/pattern_output_ll.c +++ b/src/platform/pattern_output_ll.c @@ -9,7 +9,7 @@ #include "platform/platform.h" enum { - PATTERN_OUTPUT_INSTRUCTIONS_PER_SAMPLE = 2, + PATTERN_OUTPUT_INSTRUCTIONS_PER_SAMPLE = 1, }; static float const PATTERN_OUTPUT_MAX_CLKDIV = 65536.0f; @@ -19,6 +19,11 @@ static PIO config_pio(PatternOutputLLConfig const *config) return config && config->pio ? (PIO)config->pio : pio0; } +static uint32_t samples_per_word(uint32_t pin_count) +{ + return pin_count == 0 ? 0 : 32u / pin_count; +} + void pattern_output_ll_default_config( PatternOutputLLConfig *config, uint32_t pin_base, @@ -128,6 +133,12 @@ bool pattern_output_ll_configure( return false; } + uint32_t pull_threshold = + config->pin_count * samples_per_word(config->pin_count); + if (pull_threshold == 0 || pull_threshold > 32) { + return false; + } + if (pg->initialized && pattern_output_ll_is_busy(pg)) { return false; } @@ -155,11 +166,11 @@ bool pattern_output_ll_configure( sm_config_set_out_pins(&sm_config, config->pin_base, config->pin_count); sm_config_set_wrap( &sm_config, - pg->program_offset, + pg->program_offset + 1, pg->program_offset + 1 ); sm_config_set_clkdiv(&sm_config, clk_div); - sm_config_set_out_shift(&sm_config, true, false, 32); + sm_config_set_out_shift(&sm_config, true, true, pull_threshold); sm_config_set_fifo_join(&sm_config, PIO_FIFO_JOIN_TX); pio_sm_init(pio, config->sm, pg->program_offset, &sm_config); pio_sm_set_consecutive_pindirs( @@ -252,6 +263,7 @@ bool pattern_output_ll_start( pio_sm_set_enabled(pio, pg->config.sm, false); pio_sm_clear_fifos(pio, pg->config.sm); pio_sm_restart(pio, pg->config.sm); + pio_sm_exec(pio, pg->config.sm, pio_encode_jmp(pg->program_offset)); pg->loop_enabled = loop; dma_channel_config dma_config = From 6465cf3ca1b0d185a3fdaf396527f85bc0d3de79 Mon Sep 17 00:00:00 2001 From: Santosh Bala Date: Tue, 4 Aug 2026 02:12:38 +0530 Subject: [PATCH 5/5] feat: track pattern generator underruns --- README.md | 3 ++ src/application/pattern_generator_commands.c | 5 ++ src/application/pattern_generator_commands.h | 1 + src/application/protocol/common.c | 2 + src/application/protocol/pg.c | 6 +++ src/platform/pattern_output_ll.c | 49 ++++++++++++++++++++ src/platform/pattern_output_ll.h | 3 ++ src/system/pattern_generator.c | 17 ++++++- src/system/pattern_generator.h | 1 + 9 files changed, 85 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ec99b84b..4ca93a77 100644 --- a/README.md +++ b/README.md @@ -248,12 +248,15 @@ Available commands: - `PG:STARt` - `PG:STOP` - `PG:STATus?` +- `PG:UNDerrun?` `PG:DATA` uses a SCPI arbitrary block containing little-endian packed `uint32_t` pattern words. Each word stores `floor(32 / pin_count)` consecutive samples; each sample consumes `pin_count` bits, starting from the least significant bits. Unused high bits in each word are ignored. `PG:STATus?` returns `running,rate_hz,pin_count,pattern_words`. +`PG:UNDerrun?` returns the number of PIO TX underrun/stall events observed by +the pattern generator backend. ## Build diff --git a/src/application/pattern_generator_commands.c b/src/application/pattern_generator_commands.c index d3a4b5bf..f6afd3b9 100644 --- a/src/application/pattern_generator_commands.c +++ b/src/application/pattern_generator_commands.c @@ -201,6 +201,11 @@ bool pg_get_mode_loop(void) { return state.mode == PATTERN_GENERATOR_MODE_LOOP; uint32_t pg_get_pattern_words(void) { return state.pattern_words; } +uint32_t pg_get_underruns(void) +{ + return pg_initialized ? pattern_generator_get_underruns(&pg) : 0; +} + bool pg_is_running(void) { return pg_initialized && pattern_generator_is_running(&pg); diff --git a/src/application/pattern_generator_commands.h b/src/application/pattern_generator_commands.h index 678c9538..b636c501 100644 --- a/src/application/pattern_generator_commands.h +++ b/src/application/pattern_generator_commands.h @@ -25,6 +25,7 @@ uint32_t pg_get_pin_count(void); uint32_t pg_get_rate(void); bool pg_get_mode_loop(void); uint32_t pg_get_pattern_words(void); +uint32_t pg_get_underruns(void); bool pg_is_running(void); #ifdef __cplusplus diff --git a/src/application/protocol/common.c b/src/application/protocol/common.c index cbaf26c9..07a19762 100644 --- a/src/application/protocol/common.c +++ b/src/application/protocol/common.c @@ -120,6 +120,7 @@ extern scpi_result_t scpi_cmd_pattern_generator_data(scpi_t *context); extern scpi_result_t scpi_cmd_pattern_generator_start(scpi_t *context); extern scpi_result_t scpi_cmd_pattern_generator_stop(scpi_t *context); extern scpi_result_t scpi_cmd_pattern_generator_status_q(scpi_t *context); +extern scpi_result_t scpi_cmd_pattern_generator_underrun_q(scpi_t *context); static scpi_result_t scpi_cmd_la_wifi_read_q(scpi_t *context); static scpi_result_t scpi_cmd_dso_wifi_read_q(scpi_t *context); @@ -381,6 +382,7 @@ static scpi_command_t const g_SCPI_COMMANDS[] = { { "PG:STARt", scpi_cmd_pattern_generator_start }, { "PG:STOP", scpi_cmd_pattern_generator_stop }, { "PG:STATus?", scpi_cmd_pattern_generator_status_q }, + { "PG:UNDerrun?", scpi_cmd_pattern_generator_underrun_q }, // Built-in test signal commands { "TEST:SQUare", scpi_cmd_test_square }, diff --git a/src/application/protocol/pg.c b/src/application/protocol/pg.c index c8deda0b..7a60943b 100644 --- a/src/application/protocol/pg.c +++ b/src/application/protocol/pg.c @@ -144,3 +144,9 @@ scpi_result_t scpi_cmd_pattern_generator_status_q(scpi_t *context) SCPI_ResultText(context, status); return SCPI_RES_OK; } + +scpi_result_t scpi_cmd_pattern_generator_underrun_q(scpi_t *context) +{ + SCPI_ResultUInt32(context, pg_get_underruns()); + return SCPI_RES_OK; +} diff --git a/src/platform/pattern_output_ll.c b/src/platform/pattern_output_ll.c index abb24ad0..619d2756 100644 --- a/src/platform/pattern_output_ll.c +++ b/src/platform/pattern_output_ll.c @@ -19,6 +19,34 @@ static PIO config_pio(PatternOutputLLConfig const *config) return config && config->pio ? (PIO)config->pio : pio0; } +static uint32_t txstall_mask(uint32_t sm) +{ + return 1u << (PIO_FDEBUG_TXSTALL_LSB + sm); +} + +static void clear_txstall(PatternOutputLL const *pg) +{ + PIO pio = config_pio(&pg->config); + pio->fdebug = txstall_mask(pg->config.sm); +} + +static bool consume_txstall(PatternOutputLL *pg) +{ + PIO pio = config_pio(&pg->config); + uint32_t mask = txstall_mask(pg->config.sm); + if ((pio->fdebug & mask) == 0) { + return false; + } + + pio->fdebug = mask; + if (!pg->loop_enabled) { + return false; + } + + pg->underrun_count++; + return true; +} + static uint32_t samples_per_word(uint32_t pin_count) { return pin_count == 0 ? 0 : 32u / pin_count; @@ -264,6 +292,7 @@ bool pattern_output_ll_start( pio_sm_clear_fifos(pio, pg->config.sm); pio_sm_restart(pio, pg->config.sm); pio_sm_exec(pio, pg->config.sm, pio_encode_jmp(pg->program_offset)); + clear_txstall(pg); pg->loop_enabled = loop; dma_channel_config dma_config = @@ -306,6 +335,7 @@ bool pattern_output_ll_start( } dma_channel_start((uint)pg->dma_chan); + clear_txstall(pg); pio_sm_set_enabled(pio, pg->config.sm, true); return true; } @@ -342,3 +372,22 @@ bool pattern_output_ll_is_busy(PatternOutputLL const *pg) dma_channel_is_busy((uint)pg->ctrl_dma_chan) || !pio_sm_is_tx_fifo_empty(pio, pg->config.sm); } + +void pattern_output_ll_task(PatternOutputLL *pg) +{ + if (!pg || !pg->initialized) { + return; + } + + (void)consume_txstall(pg); +} + +uint32_t pattern_output_ll_get_underruns(PatternOutputLL *pg) +{ + if (!pg || !pg->initialized) { + return 0; + } + + (void)consume_txstall(pg); + return pg->underrun_count; +} diff --git a/src/platform/pattern_output_ll.h b/src/platform/pattern_output_ll.h index 888c5e46..5794ca3d 100644 --- a/src/platform/pattern_output_ll.h +++ b/src/platform/pattern_output_ll.h @@ -24,6 +24,7 @@ typedef struct { uint32_t program_offset; uintptr_t restart_read_addr; uint32_t saved_bus_priority; + uint32_t underrun_count; uint16_t program_instructions[2]; bool program_loaded; bool loop_enabled; @@ -54,5 +55,7 @@ bool pattern_output_ll_start( ); void pattern_output_ll_stop(PatternOutputLL *pg); bool pattern_output_ll_is_busy(PatternOutputLL const *pg); +void pattern_output_ll_task(PatternOutputLL *pg); +uint32_t pattern_output_ll_get_underruns(PatternOutputLL *pg); #endif diff --git a/src/system/pattern_generator.c b/src/system/pattern_generator.c index c178547a..bb18bb6f 100644 --- a/src/system/pattern_generator.c +++ b/src/system/pattern_generator.c @@ -112,8 +112,12 @@ void pattern_generator_stop(PatternGenerator *pg) void pattern_generator_task(PatternGenerator *pg) { - if (!pg || !pg->initialized || !pg->running || - pattern_output_ll_is_busy(&pg->platform)) { + if (!pg || !pg->initialized) { + return; + } + + pattern_output_ll_task(&pg->platform); + if (!pg->running || pattern_output_ll_is_busy(&pg->platform)) { return; } @@ -124,3 +128,12 @@ bool pattern_generator_is_running(PatternGenerator const *pg) { return pg && pg->running && pattern_output_ll_is_busy(&pg->platform); } + +uint32_t pattern_generator_get_underruns(PatternGenerator *pg) +{ + if (!pg || !pg->initialized) { + return 0; + } + + return pattern_output_ll_get_underruns(&pg->platform); +} diff --git a/src/system/pattern_generator.h b/src/system/pattern_generator.h index 47218f85..efa6f935 100644 --- a/src/system/pattern_generator.h +++ b/src/system/pattern_generator.h @@ -41,6 +41,7 @@ bool pattern_generator_start( void pattern_generator_stop(PatternGenerator *pg); void pattern_generator_task(PatternGenerator *pg); bool pattern_generator_is_running(PatternGenerator const *pg); +uint32_t pattern_generator_get_underruns(PatternGenerator *pg); struct PatternGenerator { PatternGeneratorConfig config;