/* * @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" #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 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 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 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 & 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]++; } 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 < RSENSE_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) { 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; } // 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 < 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); Serial.flush(); SERIAL_BUFFER_CLEAR(); delay(1); } 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(); } // 7 bytes reserved for future use 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(); }