/* * @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 #include #include #include #include #include #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(); }