Protocol rework

This commit is contained in:
ThePetrovich 2026-05-10 15:33:08 +08:00
parent 3e77d34ccc
commit 2b713a3e3a
15 changed files with 1347 additions and 1155 deletions

View file

@ -18,49 +18,55 @@
#include <stdint.h>
#include <util/atomic.h>
#include "adc.h"
#include "config.h"
#include "eeprom.h"
#include "iodefs.h"
#include "rsense.h"
#include "utils.h"
#include "adc.h"
static volatile union __attribute__((packed)) {
uint16_t channels_16[RSENSE_CHANNEL_COUNT];
uint32_t channels_32[RSENSE_CHANNEL_32_COUNT];
} g_detector_counts = {0};
static uint8_t g_spectrum_mode = 0; // 0 = 16-bit, 1 = 32-bit
static uint8_t g_oversampling_bits = config.flags.adc_oversample_bits;
static uint16_t g_read_pointer = 0;
static volatile uint32_t g_counts_cps = 0; /* counts per second accumulator, reset by periodic tick */
static uint32_t g_cps_time = 0;
static uint16_t g_cps_last = 0;
static uint32_t g_total_counts = 0;
static uint32_t g_total_time = 0;
static uint32_t g_flush_time = 0;
static volatile uint16_t g_counts_cpm = 0;
static volatile uint32_t g_counts_delta = 0; /* counts since last CMD_GET_COUNTS */
static uint32_t g_cpm_time = 0;
static uint16_t g_cpm_last = 0;
static struct {
uint8_t index;
uint16_t cps[10]; /* Circular buffer of the last 10 CPS values, updated by periodic tick */
} g_cp10s_data;
static potentiometer_settings_t g_current_pot_settings;
/*
* Drift compensation state.
*
* Three independent reference temperatures:
* det_ref DET_TEMP (SiPM carrier), drives pot_hv via temp_drift.
* hv_ref HV_TEMP (+28V supply), drives pot_hv via vbias_drift.
* amp_ref AMP_TEMP (amplifier), drives pot_amp and pot_det.
*
* pot_hv adjustment = temp_drift*delta_det + vbias_drift*delta_hv (summed).
*/
static uint32_t g_drift_last_ms = 0;
static int16_t g_drift_ref_det = 0; /* DET_TEMP reference in 0.1 °C */
static int16_t g_drift_ref_hv = 0; /* HV_TEMP reference in 0.1 °C */
static int16_t g_drift_ref_amp = 0; /* AMP_TEMP reference in 0.1 °C */
static bool g_drift_ref_valid = false;
bool g_rsense_enabled = false; /* true when HV/AMP are powered on */
static void apply_potentiometer_settings(const potentiometer_settings_t *settings);
static void apply_potentiometer_settings(void);
static void initialize_event_system(void);
static inline void handle_analog_read_interrupt(void);
static void clear_detector_counters(void);
static void clear_channel_data(void);
static void set_potentiometer_values(uint8_t hv, uint8_t amp, uint8_t det);
static void send_temperature_data(void);
static inline void analog_read_interrupt(void);
static void rsense_drift_compensation_tick(void);
void rsense_init(void)
{
eeprom_load_pot_settings(&g_current_pot_settings);
pinMode(HV_EN, OUTPUT);
pinMode(DET_EN, OUTPUT);
pinMode(DET_RST, OUTPUT);
// Disable HV and Detector on startup
digitalWrite(HV_EN, LOW);
digitalWrite(DET_EN, LOW);
@ -72,342 +78,263 @@ void rsense_init(void)
digitalWrite(DET_CS, HIGH);
digitalWrite(AMP_CS, HIGH);
apply_potentiometer_settings(&g_current_pot_settings);
apply_potentiometer_settings();
pinMode(DET_TRIG_PIN, INPUT);
initialize_event_system();
}
/**
* @brief Initialize event system for radiation detection
*/
static void initialize_event_system(void)
{
// Enable event on DET_TRIG_PIN
Event2.set_generator(event::gen2::pin_pc7);
Event2.set_user(event::user::adc0_start);
// Pin events are async, level; convert to sync, edge via CCL
// Logic0.enable = true;
// Logic0.input0 = logic::in::event_a;
// Logic0.input1 = logic::in::masked;
// Logic0.input2 = logic::in::masked;
// Logic0.output = logic::out::enable;
// Logic0.output_swap = logic::out::no_swap;
// Logic0.filter = logic::filter::synchronizer;
// Logic0.edgedetect = logic::edgedetect::enable;
// Logic0.clocksource = logic::clocksource::clk_per;
// Logic0.sequencer = logic::sequencer::disable;
// Logic0.truth = 0b00000010; // A and not previous A
// Logic0.init();
// Route CCL output to ADC
// Event0.set_generator(event::gen::ccl0_out);
// Event0.set_user(event::user::adc0_start);
//Event0.start();
Event2.start();
}
/**
* @brief Handle ADC conversion results for radiation detection
*/
static inline void handle_analog_read_interrupt(void)
static inline void analog_read_interrupt(void)
{
digitalWriteFast(DET_RST, HIGH);
uint16_t channel = ADC0.RES; // read result to clear flag
uint16_t channel = ADC0.RES;
digitalWriteFast(STATUS_LED, LOW);
if (g_spectrum_mode == 0) {
// 16-bit spectrum mode
// 2 extra bits from oversampling + averaging
// shift down to equiv. 11 bits
#if ADC_OVERSAMPLING_BITS == 1
channel = channel >> 1;
#elif ADC_OVERSAMPLING_BITS == 2
channel = channel >> (ADC_OVERSAMPLING_BITS + 1);
#elif ADC_OVERSAMPLING_BITS == 3
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
#else
#error "Unsupported ADC oversampling setting"
#endif
g_detector_counts.channels_16[channel & RSENSE_CHANNEL_16_MASK]++;
} else {
// 32-bit spectrum mode
// 2 extra bits from oversampling + averaging x2
// shift down to equiv. 10 bits
#if ADC_OVERSAMPLING_BITS == 1
channel = channel >> 2;
#elif ADC_OVERSAMPLING_BITS == 2
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
#elif ADC_OVERSAMPLING_BITS == 3
channel = channel >> (ADC_OVERSAMPLING_BITS + 3);
#else
#error "Unsupported ADC oversampling setting"
#endif
g_detector_counts.channels_32[channel & RSENSE_CHANNEL_32_MASK]++;
}
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
g_detector_counts.channels_16[channel & RSENSE_CHANNEL_MASK]++;
g_total_counts++;
g_counts_cpm++;
g_counts_cps++;
g_counts_delta++;
digitalWriteFast(DET_RST, LOW);
}
/**
* @brief Clear all detector counters
*/
static void clear_detector_counters(void)
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
g_total_counts = 0;
g_total_time = 0;
g_flush_time = millis();
g_counts_cpm = 0;
g_cpm_time = millis();
}
}
ISR(ADC0_RESRDY_vect) { analog_read_interrupt(); }
/**
* @brief Clear channel data
* @brief Clamp a signed value into the unsigned interval [lo, hi].
*/
static void clear_channel_data(void)
static uint8_t clamp_u8(int16_t v, uint8_t lo, uint8_t hi)
{
for (int i = 0; i < RSENSE_CHANNEL_COUNT; i++) {
g_detector_counts.channels_16[i] = 0;
}
if (v < (int16_t)lo)
return lo;
if (v > (int16_t)hi)
return hi;
return (uint8_t)v;
}
/**
* @brief Apply potentiometer settings to hardware
* @param settings Pointer to potentiometer settings
*/
static void apply_potentiometer_settings(const potentiometer_settings_t *settings)
static void rsense_drift_compensation_tick(void)
{
if (settings == nullptr)
if (!config.flags.fields.temperature_drift_compensation || !g_rsense_enabled)
return;
// HV potentiometer setup
digitalWrite(HV_CS, LOW);
SPI.transfer(settings->hv_pot);
digitalWrite(HV_CS, HIGH);
delay(1);
uint32_t period = config.temp_drift_compensation.drift_period_ms;
if (period == 0)
period =
300000; /* Default to 5 minutes if not set, to avoid excessive compensation when misconfigured. */
// SiPM Pre-amp gain potentiometer setup
digitalWrite(AMP_CS, LOW);
SPI.transfer(settings->amp_pot);
digitalWrite(AMP_CS, HIGH);
delay(1);
if (millis() - g_drift_last_ms < period)
return;
// Detection threshold potentiometer setup
digitalWrite(DET_CS, LOW);
SPI.transfer(settings->det_pot);
digitalWrite(DET_CS, HIGH);
delay(1);
}
/* Read all three temperature sensors (each ~10 ms due to oversampling) */
int16_t det_temp = adc_to_temperature_c(adc_read_oversampled(DET_TEMP_PIN, 16));
int16_t hv_temp = adc_to_temperature_c(adc_read_oversampled(HV_TEMP_PIN, 16));
int16_t amp_temp = adc_to_temperature_c(adc_read_oversampled(AMP_TEMP_PIN, 16));
/**
* @brief Set new potentiometer values
* @param hv High voltage potentiometer value (0-255)
* @param amp Amplifier potentiometer value (0-255)
* @param det Detector potentiometer value (0-255)
*/
static void set_potentiometer_values(uint8_t hv, uint8_t amp, uint8_t det)
{
digitalWrite(DET_EN, LOW);
digitalWrite(HV_EN, LOW);
delay(5);
// Update current settings and save to EEPROM
g_current_pot_settings.hv_pot = hv;
g_current_pot_settings.amp_pot = amp;
g_current_pot_settings.det_pot = det;
apply_potentiometer_settings(&g_current_pot_settings);
eeprom_save_pot_settings(&g_current_pot_settings);
}
/*******************************************************************************/
/*******************************************************************************/
/* Command handlers */
/*******************************************************************************/
/*******************************************************************************/
void rsense_cmd_telemetry(void)
{
adc_restore_default();
// Send total counts as 4 bytes
Serial.write(g_total_counts & 0xFF);
Serial.write((g_total_counts >> 8) & 0xFF);
Serial.write((g_total_counts >> 16) & 0xFF);
Serial.write((g_total_counts >> 24) & 0xFF);
// 2 bytes of voltage in mV
uint16_t voltage_raw = adc_read_oversampled(V28V0_FB_PIN, 16);
uint16_t voltage_mv = adc_to_voltage_mv(voltage_raw);
Serial.write(voltage_mv & 0xFF);
Serial.write(voltage_mv >> 8);
// 6 bytes of temperature data
send_temperature_data();
// 3 bytes of potentiometer settings
Serial.write(g_current_pot_settings.hv_pot);
Serial.write(g_current_pot_settings.amp_pot);
Serial.write(g_current_pot_settings.det_pot);
Serial.flush();
SERIAL_BUFFER_CLEAR();
adc_enable_fast();
}
/**
* @brief Send temperature sensor data
*/
static void send_temperature_data(void)
{
int16_t hv_temp_c = adc_to_temperature_c(adc_read_oversampled(HV_TEMP_PIN, 16));
int16_t amp_temp_c = adc_to_temperature_c(adc_read_oversampled(AMP_TEMP_PIN, 16));
int16_t det_temp_c = adc_to_temperature_c(adc_read_oversampled(DET_TEMP_PIN, 16));
Serial.write(hv_temp_c & 0xFF);
Serial.write(hv_temp_c >> 8);
Serial.write(amp_temp_c & 0xFF);
Serial.write(amp_temp_c >> 8);
Serial.write(det_temp_c & 0xFF);
Serial.write(det_temp_c >> 8);
}
/* TODO: CRC for parameters */
void rsense_cmd_set_potentiometers(void)
{
// Wait for HV, AMP, DET potentiometer values
while (Serial.available() < 3)
;
uint8_t hv = Serial.read();
uint8_t amp = Serial.read();
uint8_t det = Serial.read();
set_potentiometer_values(hv, amp, det);
SERIAL_SEND_OK();
}
void rsense_cmd_enable(void)
{
digitalWrite(HV_EN, HIGH);
digitalWrite(DET_EN, HIGH);
adc_enable_fast();
SERIAL_SEND_OK();
}
void rsense_cmd_disable(void)
{
digitalWrite(DET_EN, LOW);
digitalWrite(HV_EN, LOW);
adc_restore_default();
SERIAL_SEND_OK();
}
void rsense_cmd_flush(void)
{
clear_detector_counters();
clear_channel_data();
SERIAL_SEND_OK();
}
void rsense_cmd_dump_channels(void)
{
adc_restore_default();
uint16_t read_from = 0;
// Wait for 2 bytes specifying read position
while (Serial.available() < 2)
;
read_from = Serial.read();
read_from |= (uint16_t)Serial.read() << 8;
// Send 2 bytes of read position
Serial.write(g_read_pointer & 0xFF);
Serial.write(g_read_pointer >> 8);
g_read_pointer = read_from;
if (g_read_pointer >= (RSENSE_CHANNEL_32_COUNT - 32)) {
g_read_pointer = RSENSE_CHANNEL_32_COUNT - 32;
if (!g_drift_ref_valid) {
g_drift_ref_det = det_temp;
g_drift_ref_hv = hv_temp;
g_drift_ref_amp = amp_temp;
g_drift_ref_valid = true;
g_drift_last_ms = millis();
return;
}
// Send 2 bytes of CRC16 checksum for the 32 channels
uint16_t crc16 = calculate_crc16_xmodem((uint8_t *)(&g_detector_counts.channels_32[g_read_pointer]), 32 * 4);
Serial.write(crc16 & 0xFF);
Serial.write(crc16 >> 8);
/* Deltas in 0.1°C units */
int16_t delta_det = det_temp - g_drift_ref_det;
int16_t delta_hv = hv_temp - g_drift_ref_hv;
int16_t delta_amp = amp_temp - g_drift_ref_amp;
// Send 32 channel values (128 bytes total)
for (uint16_t i = 0; i < 32 && g_read_pointer < RSENSE_CHANNEL_32_COUNT; i++, g_read_pointer++) {
uint32_t channel_value = g_detector_counts.channels_32[g_read_pointer];
Serial.write(channel_value & 0xFF);
Serial.write((channel_value >> 8) & 0xFF);
Serial.write((channel_value >> 16) & 0xFF);
Serial.write((channel_value >> 24) & 0xFF);
/* Drift coefficients in pot_units per °C, scaled by 256.
* adj = coeff * delta_0.1C / 10 / 256 = coeff * delta_0.1C / 2560 */
bool needs_update = false;
Serial.flush();
SERIAL_BUFFER_CLEAR();
delay(1);
int16_t new_hv = config.pots.pot_hv;
int16_t new_amp = config.pots.pot_amp;
int16_t new_det = config.pots.pot_det;
const temp_drift_compensation_t *dc = &config.temp_drift_compensation;
if (dc->pot_hv_low != dc->pot_hv_high) {
/* pot_hv: SiPM carrier (DET_TEMP) + 28V supply (HV_TEMP) contributions. */
int16_t adj =
(int16_t)(((int32_t)dc->temp_drift * delta_det + (int32_t)dc->vbias_drift * delta_hv) / 2560L);
if (adj != 0) {
new_hv = (int16_t)config.pots.pot_hv + adj;
needs_update = true;
}
}
Serial.flush();
SERIAL_BUFFER_CLEAR();
adc_enable_fast();
}
void rsense_cmd_get_cpm(void)
{
Serial.write(g_cpm_last & 0xFF);
Serial.write(g_cpm_last >> 8);
Serial.flush();
SERIAL_BUFFER_CLEAR();
}
void rsense_cmd_set_configuration(void)
{
// Wait for 1 byte specifying mode
while (Serial.available() < 8)
;
uint8_t mode = Serial.read();
if (mode <= 1) {
g_spectrum_mode = mode;
clear_channel_data();
SERIAL_SEND_OK();
if (dc->pot_amp_low != dc->pot_amp_high) {
int16_t adj = (int16_t)(((int32_t)dc->gain_drift * delta_amp) / 2560L);
if (adj != 0) {
new_amp = (int16_t)config.pots.pot_amp + adj;
needs_update = true;
}
}
// 7 bytes reserved for future use
if (dc->pot_det_low != dc->pot_det_high) {
int16_t adj = (int16_t)(((int32_t)dc->threshold_drift * delta_amp) / 2560L);
if (adj != 0) {
new_det = (int16_t)config.pots.pot_det + adj;
needs_update = true;
}
}
Serial.flush();
SERIAL_BUFFER_CLEAR();
if (!needs_update) {
g_drift_last_ms = millis();
return;
}
config.pots.pot_hv = clamp_u8(new_hv, dc->pot_hv_low, dc->pot_hv_high);
config.pots.pot_amp = clamp_u8(new_amp, dc->pot_amp_low, dc->pot_amp_high);
config.pots.pot_det = clamp_u8(new_det, dc->pot_det_low, dc->pot_det_high);
rsense_apply_potentiometers();
g_drift_ref_det = det_temp;
g_drift_ref_hv = hv_temp;
g_drift_ref_amp = amp_temp;
g_drift_last_ms = millis();
}
void rsense_periodic(void)
{
if (millis() - g_cpm_time > CPM_UPDATE_PERIOD) {
uint32_t elapsed_time = millis() - g_cpm_time;
g_cpm_last = (uint16_t)(((uint32_t)g_counts_cpm * 60UL * 1000UL) / elapsed_time);
g_counts_cpm = 0;
g_cpm_time = millis();
if (millis() - g_cps_time > RSENSE_UPDATE_PERIOD) {
uint32_t elapsed_time = millis() - g_cps_time;
g_cps_last = (uint16_t)(((uint32_t)g_counts_cps * 1000UL) / elapsed_time);
g_counts_cps = 0;
g_cps_time = millis();
/* Update 10-second CPS circular buffer for moving average */
g_cp10s_data.cps[g_cp10s_data.index] = g_cps_last;
g_cp10s_data.index = (g_cp10s_data.index + 1) % 10;
rsense_drift_compensation_tick();
}
}
/**
* @brief ADC conversion complete interrupt handler
* @brief Clock @c value out to one AD5160 selected by @p cs_pin.
*/
ISR(ADC0_RESRDY_vect) { handle_analog_read_interrupt(); }
static inline void write_pot(uint8_t cs_pin, uint8_t value)
{
digitalWrite(cs_pin, LOW);
SPI.transfer(value);
digitalWrite(cs_pin, HIGH);
delay(1);
}
/**
* @brief Push the current @c config.pots values to the AD5160 digital
* potentiometers over SPI. Does not touch power-enable lines;
* see #rsense_apply_potentiometers for the safe re-sequencing version.
*/
static void apply_potentiometer_settings(void)
{
write_pot(HV_CS, config.pots.pot_hv);
write_pot(AMP_CS, config.pots.pot_amp);
write_pot(DET_CS, config.pots.pot_det);
}
void rsense_apply_potentiometers(void)
{
if (!g_rsense_enabled) {
/* System is off — just write SPI registers without power cycling */
apply_potentiometer_settings();
return;
}
/* Power sequence: freeze → disable amp → disable HV → set pots → enable HV → enable amp → unfreeze */
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.INTCTRL &= ~ADC_RESRDY_bm; }
digitalWrite(DET_EN, LOW);
digitalWrite(HV_EN, LOW);
delay(10);
apply_potentiometer_settings();
delay(10);
digitalWrite(HV_EN, HIGH);
delay(100);
digitalWrite(DET_EN, HIGH);
delay(100);
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.INTCTRL |= ADC_RESRDY_bm; }
}
void rsense_enable(void)
{
g_rsense_enabled = true;
digitalWrite(HV_EN, HIGH);
digitalWrite(DET_EN, HIGH);
adc_enable_fast();
}
void rsense_disable(void)
{
g_rsense_enabled = false;
digitalWrite(DET_EN, LOW);
digitalWrite(HV_EN, LOW);
adc_restore_default();
}
void rsense_flush_counters(void)
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
g_total_counts = 0;
g_counts_cps = 0;
g_counts_delta = 0;
g_cps_time = millis();
}
}
void rsense_flush_spectrum(void)
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
for (int i = 0; i < RSENSE_CHANNEL_COUNT; i++) {
g_detector_counts.channels_16[i] = 0;
}
}
rsense_flush_counters();
}
uint32_t rsense_get_total_counts(void)
{
uint32_t v;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { v = g_total_counts; }
return v;
}
uint16_t rsense_get_cps(void) { return g_cps_last; }
uint32_t rsense_get_cp10s(void)
{
uint32_t sum = 0;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
for (int i = 0; i < 10; i++) {
sum += g_cp10s_data.cps[i];
}
}
return sum / 10;
}
uint32_t rsense_get_counts_since_last(void)
{
uint32_t v;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
v = g_counts_delta;
g_counts_delta = 0;
}
return v;
}
const volatile void *rsense_get_spectrum_ptr(void) { return (const volatile void *)&g_detector_counts; }