blob: e5d61c189412a154abd0f4cd25babdc1ad4fa2dc [file] [log] [blame]
#include <pico/stdlib.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <hardware/pio.h>
#include <hardware/dma.h>
#include <hardware/irq.h>
#include <hardware/clocks.h>
#include "autobaud.h"
#include "probe_config.h"
#include "autobaud.pio.h"
// DMA buffer size
#define BUF_SIZE 1024
// Size of hash table for sample occurrence counts
#define HASH_TBL_SIZE 500
// Minimum sample occurrence ratio to consider a baud rate value valid
#define MIN_FREQUENCY 0.05f
// PIO clock frequency in Hz
#define PIO_CLOCK_FREQUENCY 125000000
// DMA IRQ for autobaud
#define DMA_AUTOBAUD_IRQ 0
// Priority for DMA IRQ handler
#define DMA_AUTOBAUD_IRQ_PRIORITY PICO_SHARED_IRQ_HANDLER_DEFAULT_ORDER_PRIORITY
typedef struct {
int key;
int count;
} Entry;
typedef struct {
Entry *entries;
size_t size;
} HashTable;
// PIO instance
static PIO pio;
// PIO state machine
static int sm = -1;
// PIO program offset
static int offset = -1;
// UART RX GPIO 1 pin
static const uint rx_pin = PROBE_UART_RX;
// Frequency hash table to store samples occurance
static HashTable *freq_table;
// Estimated baud rate and validity
static float baud;
static float validity;
// shortest bit duration in PIO cycles
static uint32_t min_cycles_count = UINT32_MAX;
// longest bit duration in PIO cycles
static uint32_t max_cycles_count = 0;
static uint32_t total_samples; // total samples seen
static uint32_t bit_time_sum; // sum of 1-bit times
static uint32_t bit_time_count; // total 1-bit times
static uint32_t outlier_count; // total of 1-bit times outliers
// DMA channels to read RX PIO line
static int ctrl_chan = -1;
static int data_chan = -1;
// DMA ring buffer storing PIO RX FIFO data
static uint32_t rx_buffer[BUF_SIZE] __attribute__((aligned(4096)));
// DMA control channel reads this value to reload transfer count
static const uint32_t dma_reload_count = BUF_SIZE;
static uintptr_t last_write_addr = (uintptr_t)rx_buffer;
static uintptr_t curr_write_addr = (uintptr_t)rx_buffer;
volatile bool autobaud_running = false;
volatile bool autobaud_stopped = true;
// Queue to hold new baud rate estimates
QueueHandle_t baudQueue;
TaskHandle_t autobaud_taskhandle;
uint32_t hash(uint32_t x, size_t size) {
x = ((x >> 16) ^ x) * 0x45d9f3bu;
x = ((x >> 16) ^ x) * 0x45d9f3bu;
x = (x >> 16) ^ x;
return x % size;
}
HashTable *create_table(size_t size) {
HashTable *table = malloc(sizeof(HashTable));
if (!table) return NULL;
table->size = size;
table->entries = calloc(size, sizeof(Entry));
if (!table->entries) {
free(table);
return NULL;
}
return table;
}
void insert(HashTable *table, int key) {
uint32_t idx = hash(key, table->size);
while (table->entries[idx].key != 0) {
if (table->entries[idx].key == key) {
table->entries[idx].count++;
return;
}
idx = (idx + 1) % table->size;
}
table->entries[idx].key = key;
table->entries[idx].count = 1;
}
int get_count(HashTable *table, int key) {
uint32_t idx = hash(key, table->size);
while (table->entries[idx].key != 0) {
if (table->entries[idx].key == key) {
return table->entries[idx].count;
}
idx = (idx + 1) % table->size;
}
return 0;
}
void free_table(HashTable *table) {
free(table->entries);
free(table);
}
void __isr dma_handler() {
// Clear DMA interrupt
if ((data_chan >= 0) && dma_irqn_get_channel_status(DMA_AUTOBAUD_IRQ, data_chan)) {
dma_irqn_acknowledge_channel(DMA_AUTOBAUD_IRQ, data_chan);
}
}
bool dma_configure(PIO pio, uint sm) {
// Configure two DMA channels for continuous RX FIFO monitoring:
// - data_chan: Continuously reads from PIO RX FIFO into circular buffer
// - ctrl_chan: Triggers data_chan to restart when it completes a transfer
ctrl_chan = dma_claim_unused_channel(true);
if (ctrl_chan < 0) return false;
data_chan = dma_claim_unused_channel(true);
if (data_chan < 0) return false;
dma_channel_config data_cfg = dma_channel_get_default_config(data_chan);
channel_config_set_transfer_data_size(&data_cfg, DMA_SIZE_32);
channel_config_set_read_increment(&data_cfg, false);
channel_config_set_write_increment(&data_cfg, true);
channel_config_set_dreq(&data_cfg, pio_get_dreq(pio, sm, false)); // Trigger when PIO RX FIFO has data to read
channel_config_set_chain_to(&data_cfg, ctrl_chan); // Chain to control channel when transfer completes
channel_config_set_ring(&data_cfg, true, 12); // Ring buffer size 2^12 = 4096 bytes (1024 uint32_t)
dma_channel_configure(
data_chan,
&data_cfg,
rx_buffer,
&pio->rxf[sm],
BUF_SIZE,
false
);
dma_channel_config ctrl_cfg = dma_channel_get_default_config(ctrl_chan);
channel_config_set_transfer_data_size(&ctrl_cfg, DMA_SIZE_32);
channel_config_set_read_increment(&ctrl_cfg, false);
channel_config_set_write_increment(&ctrl_cfg, false);
dma_channel_configure(
ctrl_chan,
&ctrl_cfg,
&(dma_hw->ch[data_chan].al1_transfer_count_trig), // Destination: data channel's transfer count register
&dma_reload_count, // Source: reload transfer count value (BUF_SIZE)
1,
false
);
// Enable DMA interrupt on data channel completion
irq_add_shared_handler(dma_get_irq_num(DMA_AUTOBAUD_IRQ), dma_handler, DMA_AUTOBAUD_IRQ_PRIORITY);
irq_set_enabled(dma_get_irq_num(DMA_AUTOBAUD_IRQ), true);
dma_irqn_set_channel_enabled(DMA_AUTOBAUD_IRQ, data_chan, true);
dma_channel_start(data_chan);
return true;
}
// Compare rounded integer parts of baud rates
// Tolerance of 0.5% in detected versus set
inline bool baud_changed(float new_baud, float baud) {
uint32_t hi = (uint32_t)(baud * 1.005f);
uint32_t lo = (uint32_t)(baud * 0.995f);
uint32_t new = (uint32_t)new_baud;
return (new > hi || new < lo);
}
// Processes new DMA samples read from the PIO RX FIFO. Each DMA sample
// represents a timestamp (or cycle count) between edges on the input signal.
// Accumulates these durations, filters noise, and estimates baud rate.
uint estimate_baud_rate() {
uint old_progress = total_samples;
// Get current DMA write address
curr_write_addr = dma_hw->ch[data_chan].write_addr;
// Convert absolute addresses to buffer indices
size_t curr_index = ((curr_write_addr) - (uintptr_t)rx_buffer) / sizeof(rx_buffer[0]);
size_t last_index = (last_write_addr - (uintptr_t)rx_buffer) / sizeof(rx_buffer[0]);
for (size_t i = last_index; i != curr_index; i = (i + 1) % BUF_SIZE) {
uint32_t raw = rx_buffer[i];
uint32_t curr_cycles_count = (UINT32_MAX - raw) * 2;
insert(freq_table, curr_cycles_count);
total_samples++;
if (curr_cycles_count > max_cycles_count)
max_cycles_count = curr_cycles_count;
float freq = (float) get_count(freq_table, curr_cycles_count) / (float) total_samples;
// if sample is seen at least 5% of all samples,
// it is assumed it's not a noisy value
if (freq < MIN_FREQUENCY) continue;
if (curr_cycles_count < min_cycles_count) {
min_cycles_count = curr_cycles_count;
bit_time_sum = 0;
bit_time_count = 0;
outlier_count = 0;
continue;
}
// If current duration is within +10% of min_cycles, treat it as a "1-bit period"
if ((curr_cycles_count - min_cycles_count) < ((float)min_cycles_count * 0.1f)) {
bit_time_sum += curr_cycles_count;
bit_time_count++;
// 1-bit period should not be less than 1/9th of the longest period
if (curr_cycles_count < (max_cycles_count / 9))
outlier_count++;
// Calculate baud from average of 1-bit times
float avg_bit_time = (float) bit_time_sum / (float) bit_time_count;
float new_baud = PIO_CLOCK_FREQUENCY / avg_bit_time;
// If baud has changed, send updated baud information to cdc_thread
if (baud_changed(new_baud, baud)) {
float completeness = 1.0f - expf(-(float) total_samples / 40.0f);
float noise_ratio = (float) outlier_count / (float) bit_time_count;
float consistency = 1.0f - fminf(noise_ratio * 2.0f, 1.0f);
float validity = completeness * consistency;
if (validity > 0.6f) {
baud = new_baud;
BaudInfo_t new_baud_info;
new_baud_info.baud = (uint32_t)roundf(baud);
new_baud_info.validity = validity;
xQueueOverwrite(baudQueue, &new_baud_info);
}
}
}
}
last_write_addr = curr_write_addr;
return total_samples - old_progress;
}
void autobaud_deinit() {
// Disable DMA IRQ and channels
if (data_chan >= 0) {
dma_irqn_set_channel_enabled(DMA_AUTOBAUD_IRQ, data_chan, false);
dma_irqn_acknowledge_channel(DMA_AUTOBAUD_IRQ, data_chan);
dma_channel_unclaim(data_chan);
data_chan = -1;
}
if (ctrl_chan >= 0) {
dma_channel_unclaim(ctrl_chan);
ctrl_chan = -1;
}
irq_remove_handler(dma_get_irq_num(DMA_AUTOBAUD_IRQ), dma_handler);
if (!irq_has_shared_handler(dma_get_irq_num(DMA_AUTOBAUD_IRQ))) {
irq_set_enabled(dma_get_irq_num(DMA_AUTOBAUD_IRQ), false);
}
// Remove PIO program
if (pio && (sm >= 0)) {
pio_sm_set_enabled(pio, sm, false);
pio_sm_unclaim(pio, sm);
}
if (pio && (offset >= 0)) {
pio_remove_program(pio, &autobaud_program, offset);
}
if (freq_table) {
free_table(freq_table);
freq_table = NULL;
}
if (baudQueue) {
vQueueDelete(baudQueue);
baudQueue = NULL;
}
// Reset state
pio = NULL;
sm = offset = -1;
baud = validity = 0.0f;
min_cycles_count = UINT32_MAX;
max_cycles_count = 0;
total_samples = bit_time_sum = bit_time_count = outlier_count = 0;
last_write_addr = (uintptr_t)rx_buffer;
curr_write_addr = (uintptr_t)rx_buffer;
}
bool autobaud_init() {
pio = pio0;
// Claim a free PIO state machine
sm = pio_claim_unused_sm(pio, true);
if (sm < 0)
return false;
// Add PIO program
offset = pio_add_program(pio, &autobaud_program);
if (offset < 0) {
autobaud_deinit();
return false;
}
float div = (float)clock_get_hz(clk_sys) / PIO_CLOCK_FREQUENCY;
autobaud_program_init(pio, sm, offset, rx_pin, div);
pio_sm_set_enabled(pio, sm, true);
// Create hash table to keep count of sample occurance
freq_table = create_table(HASH_TBL_SIZE);
if (!freq_table) {
autobaud_deinit();
return false;
}
// Create queue to send baud information to cdc_thread
baudQueue = xQueueCreate(1, sizeof(BaudInfo_t));
if (!baudQueue) {
autobaud_deinit();
return false;
}
// Set up DMA to continuously write PIO RX data into RAM
if (!dma_configure(pio, sm)) {
autobaud_deinit();
return false;
}
return true;
}
void autobaud_start() {
xTaskNotify(autobaud_taskhandle, AUTOBAUD_CMD_START, eSetValueWithOverwrite);
}
void autobaud_wait_stop() {
while (!autobaud_stopped)
xTaskNotify(autobaud_taskhandle, AUTOBAUD_CMD_STOP, eSetValueWithOverwrite);
}
// FreeRTOS thread running if MAGIC_BAUD was set by host
void autobaud_thread(void * param) {
TickType_t wake = xTaskGetTickCount();
uint32_t cmd = AUTOBAUD_CMD_NONE;
uint processed;
while (true) {
if (!autobaud_running) {
// Idle state: thread is blocked here until start command is received
xTaskNotifyWait(0, 0xFFFFFFFFu, &cmd, portMAX_DELAY);
if (cmd == AUTOBAUD_CMD_START) {
if (autobaud_init()) {
autobaud_running = true;
autobaud_stopped = false;
}
}
} else {
// Check if host requested autobaud termination
if (xTaskNotifyWait(0, 0xFFFFFFFFu, &cmd, 0) == pdTRUE) {
if (cmd == AUTOBAUD_CMD_STOP) {
autobaud_running = false;
autobaud_deinit();
autobaud_stopped = true;
continue;
}
}
processed = estimate_baud_rate();
if (!processed)
xTaskDelayUntil(&wake, 1);
}
}
}