Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
28 changes: 28 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,34 @@ 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 <first_gpio>,<pin_count>`
- `PG:CONFigure:PINS?`
- `PG:CONFigure:RATE <rate_hz>`
- `PG:CONFigure:RATE?`
- `PG:CONFigure:MODE <ONCE|LOOP>`
- `PG:CONFigure:MODE?`
- `PG:DATA <arbitrary_block>`
- `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

Configure from the project root:
Expand Down
212 changes: 212 additions & 0 deletions src/application/pattern_generator_commands.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
#include "application/pattern_generator_commands.h"

#include <string.h>

#include "system/pattern_generator.h"

enum {
PG_DEFAULT_PIN_BASE = 16,
PG_DEFAULT_PIN_COUNT = 1,
PG_DEFAULT_RATE_HZ = 2000,
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;
Comment on lines +43 to +44

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Align configuration validation limits with low-level constraints to improve error consistency

config_is_valid only enforces rate_hz <= PG_MAX_RATE_HZ, while pattern_output_ll_configure additionally requires clk_div >= 1.0f based on the actual peripheral clock. As a result, some rates that pass this check will still be rejected later with less clear SCPI errors.

To make failures consistent and predictable, either derive PG_MAX_RATE_HZ from PLATFORM_get_peripheral_clock_speed (or a documented worst-case), or replicate the clk_div >= 1 constraint here so all invalid configs are rejected at the same layer with a clear, shared limit.

Suggested implementation:

static bool config_is_valid(void)
{
    /* Ensure configuration constraints match low-level limits so invalid
     * configurations are rejected consistently before calling into the
     * pattern_output_ll layer.
     */
    const uint32_t peripheral_clk_hz = PLATFORM_get_peripheral_clock_speed();
    const float clk_div = (float)peripheral_clk_hz / (float)state.rate_hz;

    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 &&
           clk_div >= 1.0f;
}
#include <string.h>

#include "system/pattern_generator.h"
#include "system/platform.h"

To keep this check perfectly aligned with pattern_output_ll_configure, ensure that the clk_div computation here matches whatever that function uses (e.g., if it divides by additional factors such as prescalers or pattern word widths, mirror that logic instead of the simple peripheral_clk_hz / state.rate_hz used above). If PLATFORM_get_peripheral_clock_speed is already declared via another header in this translation unit, you can omit the additional #include "system/platform.h" to avoid redundant includes.

}

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;
}

static void mark_unconfigured(void)
{
if (pg_initialized) {
pattern_generator_deinit(&pg);
}

pg_initialized = false;
}

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()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't pg_initialized be set to false here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have made changes to set it to false

state.pin_base = old_pin_base;
state.pin_count = old_pin_count;
mark_unconfigured();
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;
mark_unconfigured();
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; }

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);
}
35 changes: 35 additions & 0 deletions src/application/pattern_generator_commands.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#ifndef PATTERN_GENERATOR_COMMANDS_H
#define PATTERN_GENERATOR_COMMANDS_H

#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>

#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);
uint32_t pg_get_underruns(void);
bool pg_is_running(void);

#ifdef __cplusplus
}
#endif

#endif
30 changes: 30 additions & 0 deletions src/application/protocol/common.c
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -108,6 +109,19 @@ 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);
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);
static scpi_result_t scpi_cmd_mso_wifi_read_q(scpi_t *context);
Expand Down Expand Up @@ -210,6 +224,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;
}

Expand Down Expand Up @@ -356,6 +371,19 @@ 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 },
{ "PG:UNDerrun?", scpi_cmd_pattern_generator_underrun_q },

// Built-in test signal commands
{ "TEST:SQUare", scpi_cmd_test_square },
{ "TEST:SQUare?", scpi_cmd_test_square_q },
Expand Down Expand Up @@ -632,6 +660,8 @@ void protocol_task(void)
return;
}

pg_task();

// Step USB task
usb_cdc_task();

Expand Down
Loading