466 lines
12 KiB
C++
466 lines
12 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 "config.h"
|
|
#include "eeprom.h"
|
|
#include "iodefs.h"
|
|
#include "rsense.h"
|
|
#include "utils.h"
|
|
|
|
static volatile union __attribute__((packed)) {
|
|
uint16_t channels_16[DETECTOR_CHANNEL_COUNT];
|
|
uint32_t channels_32[DETECTOR_CHANNEL_32_COUNT];
|
|
} g_detector_counts = {0};
|
|
|
|
static uint8_t g_spectrum_mode = 0; // 0 = 16-bit, 1 = 32-bit
|
|
|
|
static uint16_t g_read_pointer = 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 uint32_t g_cpm_time = 0;
|
|
static uint16_t g_cpm_last = 0;
|
|
|
|
static potentiometer_settings_t g_current_pot_settings;
|
|
|
|
static void apply_potentiometer_settings(const potentiometer_settings_t *settings);
|
|
static void initialize_event_system(void);
|
|
static void enable_fast_adc(void);
|
|
static void restore_default_adc(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);
|
|
|
|
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);
|
|
|
|
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(&g_current_pot_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 Enable ADC for fast radiation detection
|
|
*/
|
|
static void enable_fast_adc(void)
|
|
{
|
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
|
{
|
|
ADC0.CTRLA |= ADC_ENABLE_bm;
|
|
|
|
// Enable oversampling x16 for 12-bit result
|
|
#if ADC_OVERSAMPLING_BITS == 1
|
|
ADC0.CTRLB |= ADC_SAMPNUM_ACC4_gc;
|
|
#elif ADC_OVERSAMPLING_BITS == 2
|
|
ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc;
|
|
#elif ADC_OVERSAMPLING_BITS == 3
|
|
ADC0.CTRLB |= ADC_SAMPNUM_ACC64_gc;
|
|
#else
|
|
#error "Unsupported ADC oversampling setting"
|
|
#endif
|
|
|
|
ADC0.CTRLC = 0; // reset
|
|
ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VDDREF_gc; // CLK_PER/32, VDD as reference
|
|
|
|
// Enable interrupt on conversion complete
|
|
ADC0.EVCTRL |= ADC_STARTEI_bm; // Start event input
|
|
ADC0.INTCTRL |= ADC_RESRDY_bm;
|
|
|
|
// Configure VREF, Microchip DS40002015B page 424
|
|
VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc;
|
|
|
|
// Select ADC channel
|
|
ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // DET_SIG_PIN
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Restore ADC to default settings
|
|
*/
|
|
static void restore_default_adc(void)
|
|
{
|
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
|
{
|
|
// Disable oversampling
|
|
ADC0.CTRLB &= ~ADC_SAMPNUM_gm;
|
|
|
|
// Set reference back to VREFA
|
|
ADC0.CTRLC &= ~ADC_REFSEL_gm;
|
|
ADC0.CTRLC |= ADC_REFSEL_VREFA_gc; // VREFA as reference
|
|
|
|
// Disable interrupt on conversion complete
|
|
ADC0.EVCTRL &= ~ADC_STARTEI_bm; // Start event input
|
|
ADC0.INTCTRL &= ~ADC_RESRDY_bm;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Handle ADC conversion results for radiation detection
|
|
*/
|
|
static inline void handle_analog_read_interrupt(void)
|
|
{
|
|
digitalWriteFast(DET_RST, HIGH);
|
|
uint16_t channel = ADC0.RES; // read result to clear flag
|
|
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 & ADC_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 & ADC_CHANNEL_32_MASK]++;
|
|
}
|
|
|
|
g_total_counts++;
|
|
g_counts_cpm++;
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Clear channel data
|
|
*/
|
|
static void clear_channel_data(void)
|
|
{
|
|
for (int i = 0; i < DETECTOR_CHANNEL_COUNT; i++) {
|
|
g_detector_counts.channels_16[i] = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Apply potentiometer settings to hardware
|
|
* @param settings Pointer to potentiometer settings
|
|
*/
|
|
static void apply_potentiometer_settings(const potentiometer_settings_t *settings)
|
|
{
|
|
if (settings == nullptr)
|
|
return;
|
|
|
|
// HV potentiometer setup
|
|
digitalWrite(HV_CS, LOW);
|
|
SPI.transfer(settings->hv_pot);
|
|
digitalWrite(HV_CS, HIGH);
|
|
delay(1);
|
|
|
|
// SiPM Pre-amp gain potentiometer setup
|
|
digitalWrite(AMP_CS, LOW);
|
|
SPI.transfer(settings->amp_pot);
|
|
digitalWrite(AMP_CS, HIGH);
|
|
delay(1);
|
|
|
|
// Detection threshold potentiometer setup
|
|
digitalWrite(DET_CS, LOW);
|
|
SPI.transfer(settings->det_pot);
|
|
digitalWrite(DET_CS, HIGH);
|
|
delay(1);
|
|
}
|
|
|
|
/**
|
|
* @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)
|
|
{
|
|
restore_default_adc();
|
|
|
|
// 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 = read_adc_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();
|
|
enable_fast_adc();
|
|
}
|
|
|
|
/**
|
|
* @brief Send temperature sensor data
|
|
*/
|
|
static void send_temperature_data(void)
|
|
{
|
|
int16_t hv_temp_c = adc_to_temperature_c(read_adc_oversampled(HV_TEMP_PIN, 16));
|
|
int16_t amp_temp_c = adc_to_temperature_c(read_adc_oversampled(AMP_TEMP_PIN, 16));
|
|
int16_t det_temp_c = adc_to_temperature_c(read_adc_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);
|
|
|
|
enable_fast_adc();
|
|
|
|
SERIAL_SEND_OK();
|
|
}
|
|
|
|
void rsense_cmd_disable(void)
|
|
{
|
|
digitalWrite(DET_EN, LOW);
|
|
digitalWrite(HV_EN, LOW);
|
|
|
|
restore_default_adc();
|
|
|
|
SERIAL_SEND_OK();
|
|
}
|
|
|
|
void rsense_cmd_flush(void)
|
|
{
|
|
clear_detector_counters();
|
|
clear_channel_data();
|
|
SERIAL_SEND_OK();
|
|
}
|
|
|
|
void rsense_cmd_dump_channels(void)
|
|
{
|
|
restore_default_adc();
|
|
|
|
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 >= (DETECTOR_CHANNEL_32_COUNT - 32)) {
|
|
g_read_pointer = DETECTOR_CHANNEL_32_COUNT - 32;
|
|
}
|
|
|
|
// 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);
|
|
|
|
// Send 32 channel values (128 bytes total)
|
|
for (uint16_t i = 0; i < 32 && g_read_pointer < DETECTOR_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);
|
|
|
|
Serial.flush();
|
|
SERIAL_BUFFER_CLEAR();
|
|
delay(1);
|
|
}
|
|
|
|
Serial.flush();
|
|
SERIAL_BUFFER_CLEAR();
|
|
enable_fast_adc();
|
|
}
|
|
|
|
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_mode(void)
|
|
{
|
|
// Wait for 1 byte specifying mode
|
|
while (Serial.available() < 1)
|
|
;
|
|
uint8_t mode = Serial.read();
|
|
|
|
if (mode <= 1) {
|
|
g_spectrum_mode = mode;
|
|
clear_channel_data();
|
|
SERIAL_SEND_OK();
|
|
}
|
|
|
|
Serial.flush();
|
|
SERIAL_BUFFER_CLEAR();
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief ADC conversion complete interrupt handler
|
|
*/
|
|
ISR(ADC0_RESRDY_vect) { handle_analog_read_interrupt(); }
|