From 3e77d34ccc905d5e9a6c72971bed7918d2655bf8 Mon Sep 17 00:00:00 2001 From: ThePetrovich Date: Thu, 7 May 2026 23:21:11 +0800 Subject: [PATCH] Freeze --- sbc_fw/adc.cpp | 214 ++++++++++++++++++++++++++++++++ sbc_fw/adc.h | 57 +++++++++ sbc_fw/config.h | 70 +++++++---- sbc_fw/eeprom.cpp | 2 + sbc_fw/protocol.h | 308 ++++++++++++++++++++++++++++++++++++++++++++++ sbc_fw/rsense.cpp | 101 ++++----------- sbc_fw/rsense.h | 2 +- sbc_fw/sbc_fw.ino | 2 +- sbc_fw/utils.cpp | 24 +--- sbc_fw/utils.h | 35 ++---- 10 files changed, 669 insertions(+), 146 deletions(-) create mode 100644 sbc_fw/adc.cpp create mode 100644 sbc_fw/adc.h create mode 100644 sbc_fw/protocol.h diff --git a/sbc_fw/adc.cpp b/sbc_fw/adc.cpp new file mode 100644 index 0000000..cfa6b36 --- /dev/null +++ b/sbc_fw/adc.cpp @@ -0,0 +1,214 @@ +/* + * @file adc.cpp + * @brief + * + * Created: 27.09.2025 05:06:33 + * Author: ThePetrovich + * + * Copyright YKSA - Sakha Aerospace Systems, LLC. + * See the LICENSE file for details. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#include "adc.h" +#include "config.h" +#include "utils.h" +#include +#include + +uint8_t adc_flags = 0; + +int16_t adc_to_temperature_c(uint16_t adc_value) +{ + // MCP9700: 500 mV at 0°C, 10 mV per degree, vref = 3.0V + // Result in 0.1°C units + uint16_t adc_resolution = ADC_RESOLUTION + << config.flags.adc_oversample_bits; // Adjust resolution based on oversampling + return (int16_t)((adc_value * ADC_VREF_MV / adc_resolution - config.tempcal)); +} + +uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * VOLTAGE_MV_PER_STEP); } + +uint16_t adc_read_oversampled(uint8_t pin, uint8_t samples) +{ + uint32_t sum = 0; + + // unrolled loop + uint8_t shift = ((samples == 64) ? 3 : (samples == 16) ? 2 : (samples == 4) ? 1 : 0); + + for (uint8_t i = 0; i < samples; i++) { + sum += analogRead(pin); + } + + return (uint16_t)(sum >> shift); +} + +void adc_enable_fast(void) +{ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + ADC0.CTRLA |= ADC_ENABLE_bm; + + // Enable oversampling x16 for 12-bit result + ADC0.CTRLB |= (config.flags.adc_oversample_bits << 2); // 2, 4, or 6 for 4x, 16x, or 64x oversampling + + ADC0.CTRLC = 0; // reset + ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VREFA_gc; // CLK_PER/32, VREFA as reference + + ADC0.EVCTRL |= ADC_STARTEI_bm; // EVSYS input to start conversion + ADC0.INTCTRL |= ADC_RESRDY_bm; // Enable interrupt on conversion complete + + // Configure VREF, Microchip DS40002015B page 424 + VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc; + + ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // DET_SIG_PIN + + adc_flags |= 0x01; // Fast mode enabled + } +} + +void adc_restore_default(void) +{ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + // Disable oversampling + ADC0.CTRLB &= ~ADC_SAMPNUM_gm; + + ADC0.CTRLC &= ~ADC_REFSEL_gm; + ADC0.CTRLC |= ADC_REFSEL_VREFA_gc; // VREFA as reference + + ADC0.EVCTRL &= ~ADC_STARTEI_bm; + ADC0.INTCTRL &= ~ADC_RESRDY_bm; + + adc_flags &= ~0x01; // Fast mode disabled + } +} + +static uint16_t adc_conversion(void) +{ + ADC0.COMMAND = ADC_STCONV_bm; + while (!(ADC0.INTFLAGS & ADC_RESRDY_bm)) + ; + return ADC0.RES; +} + +static uint16_t adc_read_tempsense(void) +{ + /* DS40002015B-page 425 + 1. Configure the internal voltage reference to 1.1V by configuring the VREF peripheral. + 2. Select the internal voltage reference by writing the REFSEL bits in ADCn.CTRLC to 0x0. + 3. Select the ADC temperature sensor channel by configuring the MUXPOS register + (ADCn.MUXPOS). This enables the temperature sensor. + 5. In ADCn.SAMPCTRL select INITDLY ≥ 32 µs × CLK_ADC + 6. In ADCn.CTRLC select SAMPLEN ≥ 32 µs × CLK_ADC + 7. Acquire the temperature sensor output voltage by starting a conversion. + 8. Process the measurement result as described below. + */ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + ADC0.CTRLA |= ADC_ENABLE_bm; + + ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc; + + ADC0.CTRLC = 0; // reset + ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_INTREF_gc; + + // Configure VREF, Microchip DS40002015B page 424 + VREF.CTRLA |= VREF_ADC0REFSEL_1V1_gc; + + // Select temperature sensor channel + ADC0.MUXPOS = ADC_MUXPOS_TEMPSENSE_gc; + } + + delay(10); + + int8_t sigrow_offset = SIGROW.TEMPSENSE1; // Read signed value from signature row + uint8_t sigrow_gain = SIGROW.TEMPSENSE0; + // Read unsigned value from signature row + uint16_t adc_reading = adc_conversion() >> 4; + // ADC conversion result with 1.1 V internal reference + uint32_t temp = adc_reading - sigrow_offset; + temp *= sigrow_gain; // Result might overflow 16 bit variable (10bit+8bit) + temp += 0x80; + // Add 1/2 to get correct rounding on division below + temp >>= 8; + // Divide result to get Kelvin + uint16_t temperature_in_K = temp; + + adc_restore_default(); + + if (adc_flags & 0x01) { + adc_enable_fast(); + } + + return temperature_in_K; +} + +void adc_cmd_calibrate(void) +{ + ATOMIC_BLOCK(ATOMIC_RESTORESTATE) + { + ADC0.EVCTRL &= ~ADC_STARTEI_bm; // Start event input + ADC0.INTCTRL &= ~ADC_RESRDY_bm; + + ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc; // 16 samples for oversampling to get 12-bit result from 10-bit ADC + + ADC0.CTRLC = 0; // reset + ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VDDREF_gc; // CLK_PER/32, VDD as reference + + // Configure VREF, Microchip DS40002015B page 424 + VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc; + } + + delay(10); // Wait for VREF to stabilize + + // set MUX to gnd and read offset + ADC0.MUXPOS = ADC_MUXPOS_GND_gc; + delay(1); + uint16_t offset = adc_conversion() >> 2; // 12-bit result + + Serial.write(offset & 0xFF); + Serial.write(offset >> 8); + + if (config.adccal0 == 0xFFFF) { + config.adccal0 = offset; + } + + // set MUX to external AREF and read 3.0V value + ADC0.MUXPOS = ADC_MUXPOS_AIN7_gc; // AREF pin + delay(1); + + uint16_t aref = adc_conversion() >> 2; // 12-bit result + + Serial.write(aref & 0xFF); + Serial.write(aref >> 8); + + if (config.adccal3 == 0xFFFF) { + config.adccal3 = aref; + } + + // set MUX to DACREF0 and read + ADC0.MUXPOS = ADC_MUXPOS_DACREF_gc; + delay(1); + + uint16_t dacref = adc_conversion() >> 2; // 12-bit result + Serial.write(dacref & 0xFF); + Serial.write(dacref >> 8); + + uint16_t tempsense = adc_read_tempsense(); + Serial.write(tempsense & 0xFF); + Serial.write(tempsense >> 8); + + Serial.write(SIGROW.TEMPSENSE1); + Serial.write(SIGROW.TEMPSENSE0); + + Serial.flush(); + SERIAL_BUFFER_CLEAR(); + + adc_restore_default(); + + if (adc_flags & 0x01) { + adc_enable_fast(); + } +} diff --git a/sbc_fw/adc.h b/sbc_fw/adc.h new file mode 100644 index 0000000..b8d60e5 --- /dev/null +++ b/sbc_fw/adc.h @@ -0,0 +1,57 @@ +/* + * @file adc.h + * @brief + * + * Created: 27.09.2025 05:06:42 + * Author: ThePetrovich + * + * Copyright YKSA - Sakha Aerospace Systems, LLC. + * See the LICENSE file for details. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + + +#ifndef ADC_H_ +#define ADC_H_ + +#include + +/** + * @brief Enable ADC for fast radiation detection + */ +void adc_enable_fast(void); + +/** + * @brief Restore ADC to default settings + */ +void adc_restore_default(void); + +/** + * @brief Convert ADC reading to temperature in 0.1°C + * @param adc_value 12-bit ADC reading + * @return Temperature in 0.1°C + */ +int16_t adc_to_temperature_c(uint16_t adc_value); + +/** + * @brief Convert ADC reading to voltage in mV + * @param adc_value ADC reading + * @return Voltage in mV + */ +uint16_t adc_to_voltage_mv(uint16_t adc_value); + +/** + * @brief Read ADC with oversampling + * @param pin Analog pin to read + * @param samples Number of samples for oversampling + * @return Averaged ADC value + */ +uint16_t adc_read_oversampled(uint8_t pin, uint8_t samples); + +/** + * @brief Command to calibrate ADC and read reference values + */ +void adc_cmd_calibrate(void); + +#endif // ADC_H_ \ No newline at end of file diff --git a/sbc_fw/config.h b/sbc_fw/config.h index e240d5c..2d44580 100644 --- a/sbc_fw/config.h +++ b/sbc_fw/config.h @@ -20,36 +20,23 @@ // Serial communication #define SERIAL_BAUD_RATE 38400 -#define SERIAL_BUFFER_CLEAR() \ - do { \ - while (Serial.available()) { \ - Serial.read(); \ - } \ - } while (0) -#define SERIAL_SEND_OK() \ - do { \ - Serial.print("ok\n"); \ - Serial.flush(); \ - SERIAL_BUFFER_CLEAR(); \ - } while (0) // Timing constants #define STATUS_LED_BLINK_PERIOD 500U #define MAIN_LOOP_DELAY 10 #define CPM_UPDATE_PERIOD 10000U -// ADC and detector settings -#define ADC_OVERSAMPLING_BITS 2 // 16x oversampling -> 2 extra bits -#define ADC_CHANNEL_16_MASK 0x7FFU // 11-bit channel mask -#define ADC_CHANNEL_32_MASK 0x3FFU // 10-bit channel mask -#define DETECTOR_CHANNEL_COUNT 2048 -#define DETECTOR_CHANNEL_32_COUNT 1024 +#define RSENSE_CHANNEL_16_MASK 0x7FFU // 11-bit channel mask +#define RSENSE_CHANNEL_32_MASK 0x3FFU // 10-bit channel mask +#define RSENSE_CHANNEL_COUNT 2048 +#define RSENSE_CHANNEL_32_COUNT 1024 // Temperature sensor constants (MCP9700) -#define TEMP_SENSOR_OFFSET_MV 500L // 500 mV at 0°C -#define TEMP_SENSOR_SCALE_MV 10L // 10 mV per degree -#define ADC_VREF_MV 3000L // 3.0V reference -#define ADC_RESOLUTION 4096L // 12-bit effective resolution +#define MCP9700_OFFSET_MV 500L // 500 mV at 0°C +#define MCP9700_SCALE_MV 10L // 10 mV per degree +#define ADC_OVERSAMPLING_BITS 2 // 16x oversampling -> 2 extra bits +#define ADC_VREF_MV 3000L // 3.0V reference +#define ADC_RESOLUTION 1024L // 10-bit ADC // Voltage divider constants #define VOLTAGE_DIVIDER_RATIO 20.0f // 200k and 10k resistors @@ -70,4 +57,43 @@ #define LSENSE_EXPECTED_ID 0xE0 #define LSENSE_DATA_SIZE 12 +typedef struct config_t { + uint8_t magic1; // 0xA5 to indicate valid config + uint8_t magic2; // 0x5A to indicate valid config + uint8_t pot_hv; + uint8_t pot_amp; + uint8_t pot_det; + struct { + uint16_t adccal0; // ADC offset at GND + uint16_t adccal3; // ADC reading at 3.0V reference + uint16_t tempcal; + uint16_t tempdrift_mvK; // SiPM temperature drift compensation in mV/K, normally around 20 mV/K + float gainlow; // gain value for pot_amp = 0 + float gainhigh; // gain value for pot_amp = 255 + } calibration __attribute__((packed)); + union { + struct { + uint8_t spectrum_oversample_bits + : 2; // 0, 1, 2, or 3 for 1x, 4x, 16x, or 64x oversampling, only for spectrum measurement + uint8_t spectrum_channel_bits : 1; // 0 for 16-bit channels, 1 for 32-bit channels + uint8_t temperature_drift_compensation + : 1; // 0 to disable, 1 to enable temperature compensation + uint8_t adc_oversample_bits : 2; // 0, 1, 2, or 3 for 1x, 4x, 16x, or 64x ADC oversampling for + // temperature and voltage measurements + uint8_t reserved : 2; + } fields __attribute__((packed)); + uint8_t value; + } flags __attribute__((packed)); + struct { + uint8_t tempdrift_pot_hv_low; + uint8_t tempdrift_pot_hv_high; + uint8_t tempdrift_pot_amp_low; + uint8_t tempdrift_pot_amp_high; + uint8_t tempdrift_pot_det_low; + uint8_t tempdrift_pot_det_high; + } temp_drift_compensation_ranges __attribute__((packed)); +} __attribute__((packed)) config_t; + +extern config_t config; + #endif // CONFIG_H diff --git a/sbc_fw/eeprom.cpp b/sbc_fw/eeprom.cpp index 65452a3..c44d5ac 100644 --- a/sbc_fw/eeprom.cpp +++ b/sbc_fw/eeprom.cpp @@ -16,6 +16,8 @@ #include #include +config_t config; + void eeprom_init(void) { if (EEPROM.read(EEPROM_MAGIC_ADDR) != EEPROM_MAGIC_VALUE) { diff --git a/sbc_fw/protocol.h b/sbc_fw/protocol.h new file mode 100644 index 0000000..52c0073 --- /dev/null +++ b/sbc_fw/protocol.h @@ -0,0 +1,308 @@ +/* + * @file protocol.h + * @brief + * + * Created: 07.05.2026 10:15:36 + * Author: ThePetrovich + * + * Copyright YKSA - Sakha Aerospace Systems, LLC. + * See the LICENSE file for details. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +#ifndef PROTOCOL_H +#define PROTOCOL_H + +#include "config.h" +#include + +/* + * @details + * General Protocol Description: + * - All commands are initiated by the host + * - Command format: [CMD (2 bytes)][Length (2 bytes)][Payload (variable)][CRC16 (2 bytes)] + * - CMD: 2 bytes command identifier (e.g., 'HV' for set potentiometers) + * - Length: 2 bytes unsigned integer indicating the length of the payload in bytes + * - Payload: Variable length data specific to the command + * - CRC16: 2 bytes CRC16-XMODEM checksum of the CMD, Length, and Payload fields + * - Response: For commands that require a response, the device will send back a similar structured + * message with the appropriate CMD and payload. Otherwise, it should send an acknowledgment + * (e.g., 'ACK') or an error response if the command is invalid. + * - If a valid command is received, but cannot be executed (e.g. locked state, parameters out of range), + * the device should send a command failure response (e.g., 'NAK') without performing any action. + * - Error Handling: If a command is malformed (e.g., incorrect CRC, invalid length), the device will + * ignore the command and should send an error response (e.g., 'ERR') to the host. + * The device should not perform any action if the command is invalid. + * Example Command: Set Potentiometers + * - CMD: 'HV' (0x48 0x56) + * - Length: 3 (0x00 0x03) + * - Payload: [HV_POT (1 byte)][AMP_POT (1 byte)][DET_POT (1 byte)] + * - CRC16: Calculated over 'HV', Length, and Payload + * Response: 'ACK' if successful, 'NAK' if parameters are out of safe range, 'ERR' if command is malformed + */ + +uint16_t protocol_crc16_xmodem(uint8_t *data, uint16_t len); + +typedef struct protocol_header_t { + uint16_t cmd; + uint16_t length; +} __attribute__((packed)) protocol_header_t; + +void protocol_send_header(uint16_t cmd, uint16_t length); + +void protocol_send_ack(void); +void protocol_send_nak(void); +void protocol_send_error(void); + +void protocol_send_message(uint16_t cmd, void *payload, uint16_t length); + +/* + * @brief Firmware version command and response structure + * @param[out] major Firmware major version + * @param[out] minor Firmware minor version + */ +#define CMD_FIRMWARE_VERSION 0x7600 +typedef struct protocol_firmware_version_response_t { + uint8_t major; + uint8_t minor; +} __attribute__((packed)) protocol_firmware_version_response_t; + +void handle_version_command(void *payload, uint16_t length); + +/* + * @brief Light sensor presence command and response structure + * @param[out] total_detected Total number of sensors detected across all buses + * @param[out] bus_x_count Number of sensors detected on bus X + * @param[out] bus_x_neg_addr I2C address of negative sensor on bus X (0 if not present) + * @param[out] bus_x_pos_addr I2C address of positive sensor on bus X (0 if not present) + * @param[out] bus_y_count Number of sensors detected on bus Y + * @param[out] bus_y_neg_addr I2C address of negative sensor on bus Y (0 if not present) + * @param[out] bus_y_pos_addr I2C address of positive sensor on bus Y (0 if not present) + * @param[out] bus_z_count Number of sensors detected on bus Z + * @param[out] bus_z_neg_addr I2C address of negative sensor on bus Z (0 if not present) + * @param[out] bus_z_pos_addr I2C address of positive sensor on bus Z (0 if not present) + */ +#define CMD_LIGHT_SENSOR_PRESENCE 0x6C00 +typedef struct protocol_light_sensor_presence_response_t { + uint8_t total_detected; + uint8_t bus_x_count; + uint8_t bus_x_neg_addr; + uint8_t bus_x_pos_addr; + uint8_t bus_y_count; + uint8_t bus_y_neg_addr; + uint8_t bus_y_pos_addr; + uint8_t bus_z_count; + uint8_t bus_z_neg_addr; + uint8_t bus_z_pos_addr; +} __attribute__((packed)) protocol_light_sensor_presence_response_t; + +void handle_light_sensor_presence_command(void *payload, uint16_t length); + +/* + * @brief Read light sensor data command and response structure + * @param[in] channel Spectral channel (0 = red, 1 = green, 2 = blue, 3 = IR, 4 = green2, 5 = composite visible, 6 = + * composite all) + * @param[out] bus_x_neg_channel_value Channel value for negative sensor on bus X + * @param[out] bus_x_pos_channel_value Channel value for positive sensor on bus X + * @param[out] bus_y_neg_channel_value Channel value for negative sensor on bus Y + * @param[out] bus_y_pos_channel_value Channel value for positive sensor on bus Y + * @param[out] bus_z_neg_channel_value Channel value for negative sensor on bus Z + * @param[out] bus_z_pos_channel_value Channel value for positive sensor on bus Z + */ +#define CMD_READ_LIGHT_SENSORS 0x7200 +typedef struct protocol_read_light_sensors_command_t { + uint8_t channel; +} __attribute__((packed)) protocol_read_light_sensors_command_t; + +typedef struct protocol_read_light_sensors_response_t { + uint16_t bus_x_neg_channel_value; + uint16_t bus_x_pos_channel_value; + uint16_t bus_y_neg_channel_value; + uint16_t bus_y_pos_channel_value; + uint16_t bus_z_neg_channel_value; + uint16_t bus_z_pos_channel_value; +} __attribute__((packed)) protocol_read_light_sensors_response_t; + +void handle_read_light_sensors_command(void *payload, uint16_t length); + +/* + * @brief Get configuration command and response structure + * @param[out] config Current device configuration + */ +#define CMD_GET_CONFIGURATION 0x6D00 +typedef struct protocol_get_configuration_response_t { + config_t config; +} __attribute__((packed)) protocol_get_configuration_response_t; + +void handle_get_configuration_command(void *payload, uint16_t length); + +/* + * @brief Set configuration command and response structure + * @param[in] config New device configuration to apply + */ +#define CMD_SET_CONFIGURATION 0x7D00 +typedef struct protocol_set_configuration_command_t { + config_t config; +} __attribute__((packed)) protocol_set_configuration_command_t; + +void handle_set_configuration_command(void *payload, uint16_t length); + +/* + * @brief Set potentiometers command and response structure + * @param[in] hv_pot High voltage potentiometer value + * @param[in] amp_pot Amplifier potentiometer value + * @param[in] det_pot Detector potentiometer value + */ +#define CMD_SET_POTENTIOMETERS 0x7D01 +typedef struct protocol_set_potentiometers_command_t { + uint8_t hv_pot; + uint8_t amp_pot; + uint8_t det_pot; +} __attribute__((packed)) protocol_set_potentiometers_command_t; + +void handle_set_potentiometers_command(void *payload, uint16_t length); + +/* + * @brief Set calibration data command and response structure + * @param[in] adccal0 ADC offset at GND + * @param[in] adccal3 ADC reading at 3.0V reference + * @param[in] tempcal Temperature calibration value + * @param[in] tempdrift_mvK Temperature drift compensation in mV/K + */ +#define CMD_SET_CALIBRATION 0x7D02 +typedef struct protocol_set_calibration_command_t { + uint16_t adccal0; + uint16_t adccal3; + uint16_t tempcal; + uint16_t tempdrift_mvK; +} __attribute__((packed)) protocol_set_calibration_command_t; + +void handle_set_calibration_command(void *payload, uint16_t length); + +/* + * @brief Set flags command and response structure + * @param[in] flags Configuration flags to set (e.g., oversampling, channel mode, temperature compensation) + */ +#define CMD_SET_FLAGS 0x7D03 +typedef struct protocol_set_flags_command_t { + uint8_t flags; +} __attribute__((packed)) protocol_set_flags_command_t; + +/* +* @brief Set temperature drift compensation ranges command and response structure +* @param[in] tempdrift_pot_hv_low Low threshold for high voltage potentiometer +* @param[in] tempdrift_pot_hv_high High threshold for high voltage potentiometer +* @param[in] tempdrift_pot_amp_low Low threshold for amplifier potentiometer +* @param[in] tempdrift_pot_amp_high High threshold for amplifier potentiometer +* @param[in] tempdrift_pot_det_low Low threshold for detector potentiometer +* @param[in] tempdrift_pot_det_high High threshold for detector potentiometer +*/ +#define CMD_SET_TEMP_DRIFT_COMPENSATION_RANGES 0x7D04 +typedef struct protocol_set_temp_drift_compensation_ranges_command_t { + uint8_t tempdrift_pot_hv_low; + uint8_t tempdrift_pot_hv_high; + uint8_t tempdrift_pot_amp_low; + uint8_t tempdrift_pot_amp_high; + uint8_t tempdrift_pot_det_low; + uint8_t tempdrift_pot_det_high; +} __attribute__((packed)) protocol_set_temp_drift_compensation_ranges_command_t; + +void handle_set_flags_command(void *payload, uint16_t length); + +/* + * @brief Get telemetry command and response structure + * @param[out] hv_temp_c High voltage component temperature in 0.1°C + * @param[out] amp_temp_c Amplifier component temperature in 0.1°C + * @param[out] det_temp_c Detector component temperature in 0.1°C + * @param[out] mcu_temp_c MCU temperature in 0.1°C + * @param[out] vbias_mv SiPM bias voltage in mV + * @param[out] hv_pot Current high voltage potentiometer setting + * @param[out] amp_pot Current amplifier potentiometer setting + * @param[out] det_pot Current detector potentiometer setting + * @param[out] cps Current counts per second (CPM/60) + * @param[out] cp10s Counts in the last 10 seconds + * @param[out] total_counts Total counts since last reset + */ +#define CMD_GET_TELEMETRY 0x7400 +typedef struct protocol_get_telemetry_response_t { + uint16_t hv_temp_c; + uint16_t amp_temp_c; + uint16_t det_temp_c; + uint16_t mcu_temp_c; + uint16_t vbias_mv; + uint8_t hv_pot; + uint8_t amp_pot; + uint8_t det_pot; + uint16_t cps; + uint32_t cp10s; + uint32_t total_counts; +} __attribute__((packed)) protocol_get_telemetry_response_t; + +void handle_get_telemetry_command(void *payload, uint16_t length); + +/* + * @brief Flush counters command + * @details Resets total counts and 10-second counts to zero + */ +#define CMD_FLUSH_COUNTERS 0x7401 +void handle_flush_counters_command(void *payload, uint16_t length); + +/* + * @brief Flush spectrum command + * @details Clears all channel data and resets total counts to zero + */ +#define CMD_FLUSH_SPECTRUM 0x7402 +void handle_flush_spectrum_command(void *payload, uint16_t length); + +/* + * @brief Turn on the spectrometer circutry and enable fast ADC mode for radiation detection + */ +#define CMD_RSENSE_ENABLE 0x7500 +void handle_rsense_enable_command(void *payload, uint16_t length); + +/* + * @brief Turn off the spectrometer circutry and disable fast ADC mode for radiation detection + */ +#define CMD_RSENSE_DISABLE 0x7501 +void handle_rsense_disable_command(void *payload, uint16_t length); + +/* + * @brief Freeze spectrum data for reading + */ +#define CMD_SPECTRUM_FREEZE 0x7502 +void handle_spectrum_freeze_command(void *payload, uint16_t length); + +/* + * @brief Unfreeze spectrum data after reading + */ +#define CMD_SPECTRUM_UNFREEZE 0x7503 +void handle_spectrum_unfreeze_command(void *payload, uint16_t length); + +/* +* @brief Get counts since last request +*/ +#define CMD_GET_COUNTS 0x7504 +typedef struct protocol_get_counts_response_t { + uint32_t counts; +} __attribute__((packed)) protocol_get_counts_response_t; + +void handle_get_counts_command(void *payload, uint16_t length); + +/* + * @brief Read spectrum data (arbitrary chunking for large data) + */ +#define CMD_READ_SPECTRUM_DATA 0x7505 +typedef struct protocol_read_spectrum_chunk_command_t { + uint16_t offset; + uint16_t length; +} __attribute__((packed)) protocol_read_spectrum_chunk_command_t; + +typedef struct protocol_read_spectrum_chunk_response_t { + uint16_t offset; + uint16_t length; + // Followed by 'length' bytes of spectrum data +} __attribute__((packed)) protocol_read_spectrum_chunk_response_t; +void handle_read_spectrum_chunk_command(void *payload, uint16_t length); + +#endif /* PROTOCOL_H */ diff --git a/sbc_fw/rsense.cpp b/sbc_fw/rsense.cpp index 6744221..571416b 100644 --- a/sbc_fw/rsense.cpp +++ b/sbc_fw/rsense.cpp @@ -23,13 +23,15 @@ #include "iodefs.h" #include "rsense.h" #include "utils.h" +#include "adc.h" static volatile union __attribute__((packed)) { - uint16_t channels_16[DETECTOR_CHANNEL_COUNT]; - uint32_t channels_32[DETECTOR_CHANNEL_32_COUNT]; + 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; @@ -44,8 +46,6 @@ 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); @@ -110,61 +110,6 @@ static void initialize_event_system(void) 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 */ @@ -188,7 +133,7 @@ static inline void handle_analog_read_interrupt(void) #error "Unsupported ADC oversampling setting" #endif - g_detector_counts.channels_16[channel & ADC_CHANNEL_16_MASK]++; + g_detector_counts.channels_16[channel & RSENSE_CHANNEL_16_MASK]++; } else { // 32-bit spectrum mode // 2 extra bits from oversampling + averaging x2 @@ -203,7 +148,7 @@ static inline void handle_analog_read_interrupt(void) #error "Unsupported ADC oversampling setting" #endif - g_detector_counts.channels_32[channel & ADC_CHANNEL_32_MASK]++; + g_detector_counts.channels_32[channel & RSENSE_CHANNEL_32_MASK]++; } g_total_counts++; @@ -231,7 +176,7 @@ static void clear_detector_counters(void) */ static void clear_channel_data(void) { - for (int i = 0; i < DETECTOR_CHANNEL_COUNT; i++) { + for (int i = 0; i < RSENSE_CHANNEL_COUNT; i++) { g_detector_counts.channels_16[i] = 0; } } @@ -294,7 +239,7 @@ static void set_potentiometer_values(uint8_t hv, uint8_t amp, uint8_t det) void rsense_cmd_telemetry(void) { - restore_default_adc(); + adc_restore_default(); // Send total counts as 4 bytes Serial.write(g_total_counts & 0xFF); @@ -303,7 +248,7 @@ void rsense_cmd_telemetry(void) 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_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); @@ -318,7 +263,7 @@ void rsense_cmd_telemetry(void) Serial.flush(); SERIAL_BUFFER_CLEAR(); - enable_fast_adc(); + adc_enable_fast(); } /** @@ -326,9 +271,9 @@ void rsense_cmd_telemetry(void) */ 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)); + 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); @@ -359,7 +304,7 @@ void rsense_cmd_enable(void) digitalWrite(HV_EN, HIGH); digitalWrite(DET_EN, HIGH); - enable_fast_adc(); + adc_enable_fast(); SERIAL_SEND_OK(); } @@ -369,7 +314,7 @@ void rsense_cmd_disable(void) digitalWrite(DET_EN, LOW); digitalWrite(HV_EN, LOW); - restore_default_adc(); + adc_restore_default(); SERIAL_SEND_OK(); } @@ -383,7 +328,7 @@ void rsense_cmd_flush(void) void rsense_cmd_dump_channels(void) { - restore_default_adc(); + adc_restore_default(); uint16_t read_from = 0; @@ -398,8 +343,8 @@ void rsense_cmd_dump_channels(void) 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; + 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 @@ -408,7 +353,7 @@ void rsense_cmd_dump_channels(void) 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++) { + 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); @@ -422,7 +367,7 @@ void rsense_cmd_dump_channels(void) Serial.flush(); SERIAL_BUFFER_CLEAR(); - enable_fast_adc(); + adc_enable_fast(); } void rsense_cmd_get_cpm(void) @@ -433,10 +378,10 @@ void rsense_cmd_get_cpm(void) SERIAL_BUFFER_CLEAR(); } -void rsense_cmd_set_mode(void) +void rsense_cmd_set_configuration(void) { // Wait for 1 byte specifying mode - while (Serial.available() < 1) + while (Serial.available() < 8) ; uint8_t mode = Serial.read(); @@ -446,6 +391,8 @@ void rsense_cmd_set_mode(void) SERIAL_SEND_OK(); } + // 7 bytes reserved for future use + Serial.flush(); SERIAL_BUFFER_CLEAR(); } diff --git a/sbc_fw/rsense.h b/sbc_fw/rsense.h index 2f6c5e3..46f7cbf 100644 --- a/sbc_fw/rsense.h +++ b/sbc_fw/rsense.h @@ -59,7 +59,7 @@ void rsense_cmd_get_cpm(void); /** * @brief Set spectrum mode (16-bit or 32-bit) */ -void rsense_cmd_set_mode(void); +void rsense_cmd_set_configuration(void); /** * @brief Periodic tasks for radiation sensor diff --git a/sbc_fw/sbc_fw.ino b/sbc_fw/sbc_fw.ino index d365375..87f7478 100644 --- a/sbc_fw/sbc_fw.ino +++ b/sbc_fw/sbc_fw.ino @@ -88,7 +88,7 @@ void handle_serial_command(char command) rsense_cmd_telemetry(); break; case 'm': // Set spectrum mode (16-bit or 32-bit) - rsense_cmd_set_mode(); + rsense_cmd_set_configuration(); break; default: // Unknown command - ignore diff --git a/sbc_fw/utils.cpp b/sbc_fw/utils.cpp index acd22fc..e75aaaa 100644 --- a/sbc_fw/utils.cpp +++ b/sbc_fw/utils.cpp @@ -32,26 +32,4 @@ uint16_t calculate_crc16_xmodem(uint8_t *data, uint16_t len) } return crc; -} - -int16_t adc_to_temperature_c(uint16_t adc_value) -{ - // MCP9700: 500 mV at 0°C, 10 mV per degree, vref = 3.0V - // Result in 0.1°C units - return (int16_t)((adc_value * ADC_VREF_MV / ADC_RESOLUTION - TEMP_SENSOR_OFFSET_MV)); -} - -uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * VOLTAGE_MV_PER_STEP); } - -uint16_t read_adc_oversampled(uint8_t pin, uint8_t samples) -{ - uint32_t sum = 0; - - uint8_t shift = ((samples == 64) ? 3 : (samples == 16) ? 2 : (samples == 4) ? 1 : 0); - - for (uint8_t i = 0; i < samples; i++) { - sum += analogRead(pin); - } - - return (uint16_t)(sum >> shift); -} +} \ No newline at end of file diff --git a/sbc_fw/utils.h b/sbc_fw/utils.h index cd37faf..b1d5a39 100644 --- a/sbc_fw/utils.h +++ b/sbc_fw/utils.h @@ -16,6 +16,19 @@ #include +#define SERIAL_BUFFER_CLEAR() \ + do { \ + while (Serial.available()) { \ + Serial.read(); \ + } \ + } while (0) +#define SERIAL_SEND_OK() \ + do { \ + Serial.print("ok\n"); \ + Serial.flush(); \ + SERIAL_BUFFER_CLEAR(); \ + } while (0) + /** * @brief Calculate CRC16 using XMODEM polynomial * @param data Pointer to data buffer @@ -24,26 +37,4 @@ */ uint16_t calculate_crc16_xmodem(uint8_t *data, uint16_t len); -/** - * @brief Convert ADC reading to temperature in 0.1°C - * @param adc_value 12-bit ADC reading - * @return Temperature in 0.1°C - */ -int16_t adc_to_temperature_c(uint16_t adc_value); - -/** - * @brief Convert ADC reading to voltage in mV - * @param adc_value ADC reading - * @return Voltage in mV - */ -uint16_t adc_to_voltage_mv(uint16_t adc_value); - -/** - * @brief Read ADC with oversampling - * @param pin Analog pin to read - * @param samples Number of samples for oversampling - * @return Averaged ADC value - */ -uint16_t read_adc_oversampled(uint8_t pin, uint8_t samples); - #endif // UTILS_H