edu16_sbc/sbc_fw/rsense.cpp
2026-05-10 15:33:08 +08:00

340 lines
8.6 KiB
C++

/*
* @file rsense.cpp
* @brief Radiation sensor implementation
*
* Created: 21.09.2025
* Author: ThePetrovich
*
* Copyright YKSA - Sakha Aerospace Systems, LLC.
* See the LICENSE file for details.
*
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <Arduino.h>
#include <Event.h>
#include <Logic.h>
#include <SPI.h>
#include <stdint.h>
#include <util/atomic.h>
#include "adc.h"
#include "config.h"
#include "eeprom.h"
#include "iodefs.h"
#include "rsense.h"
static volatile union __attribute__((packed)) {
uint16_t channels_16[RSENSE_CHANNEL_COUNT];
} g_detector_counts = {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 volatile uint32_t g_counts_delta = 0; /* counts since last CMD_GET_COUNTS */
static struct {
uint8_t index;
uint16_t cps[10]; /* Circular buffer of the last 10 CPS values, updated by periodic tick */
} g_cp10s_data;
/*
* 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(void);
static void initialize_event_system(void);
static inline void analog_read_interrupt(void);
static void rsense_drift_compensation_tick(void);
void rsense_init(void)
{
pinMode(HV_EN, OUTPUT);
pinMode(DET_EN, OUTPUT);
pinMode(DET_RST, OUTPUT);
digitalWrite(HV_EN, LOW);
digitalWrite(DET_EN, LOW);
pinMode(HV_CS, OUTPUT);
pinMode(DET_CS, OUTPUT);
pinMode(AMP_CS, OUTPUT);
digitalWrite(HV_CS, HIGH);
digitalWrite(DET_CS, HIGH);
digitalWrite(AMP_CS, HIGH);
apply_potentiometer_settings();
pinMode(DET_TRIG_PIN, INPUT);
initialize_event_system();
}
static void initialize_event_system(void)
{
Event2.set_generator(event::gen2::pin_pc7);
Event2.set_user(event::user::adc0_start);
Event2.start();
}
static inline void analog_read_interrupt(void)
{
digitalWriteFast(DET_RST, HIGH);
uint16_t channel = ADC0.RES;
digitalWriteFast(STATUS_LED, LOW);
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
g_detector_counts.channels_16[channel & RSENSE_CHANNEL_MASK]++;
g_total_counts++;
g_counts_cps++;
g_counts_delta++;
digitalWriteFast(DET_RST, LOW);
}
ISR(ADC0_RESRDY_vect) { analog_read_interrupt(); }
/**
* @brief Clamp a signed value into the unsigned interval [lo, hi].
*/
static uint8_t clamp_u8(int16_t v, uint8_t lo, uint8_t hi)
{
if (v < (int16_t)lo)
return lo;
if (v > (int16_t)hi)
return hi;
return (uint8_t)v;
}
static void rsense_drift_compensation_tick(void)
{
if (!config.flags.fields.temperature_drift_compensation || !g_rsense_enabled)
return;
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. */
if (millis() - g_drift_last_ms < period)
return;
/* 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));
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;
}
/* 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;
/* 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;
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;
}
}
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;
}
}
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;
}
}
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_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 Clock @c value out to one AD5160 selected by @p cs_pin.
*/
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; }