This commit is contained in:
ThePetrovich 2026-05-07 23:21:11 +08:00
parent 70798b6bf7
commit 3e77d34ccc
10 changed files with 669 additions and 146 deletions

214
sbc_fw/adc.cpp Normal file
View file

@ -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 <Arduino.h>
#include <util/atomic.h>
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();
}
}

57
sbc_fw/adc.h Normal file
View file

@ -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 <stdint.h>
/**
* @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_

View file

@ -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 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 4096L // 12-bit effective resolution
#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

View file

@ -16,6 +16,8 @@
#include <Arduino.h>
#include <EEPROM.h>
config_t config;
void eeprom_init(void)
{
if (EEPROM.read(EEPROM_MAGIC_ADDR) != EEPROM_MAGIC_VALUE) {

308
sbc_fw/protocol.h Normal file
View file

@ -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 <stdint.h>
/*
* @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 */

View file

@ -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();
}

View file

@ -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

View file

@ -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

View file

@ -33,25 +33,3 @@ 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);
}

View file

@ -16,6 +16,19 @@
#include <stdint.h>
#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