Protocol rework
This commit is contained in:
parent
3e77d34ccc
commit
2b713a3e3a
15 changed files with 1347 additions and 1155 deletions
176
sbc_fw/adc.cpp
176
sbc_fw/adc.cpp
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* @file adc.cpp
|
||||
* @brief
|
||||
* @brief ATmega4809 ADC0 driver: configuration profiles, oversampling reads,
|
||||
* and conversions to engineering units.
|
||||
*
|
||||
* Created: 27.09.2025 05:06:33
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -13,19 +14,38 @@
|
|||
|
||||
#include "adc.h"
|
||||
#include "config.h"
|
||||
#include "utils.h"
|
||||
#include <Arduino.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
uint8_t adc_flags = 0;
|
||||
/**
|
||||
* @brief Internal driver state.
|
||||
* Bit 0 = fast mode currently enabled.
|
||||
*/
|
||||
static 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));
|
||||
/*
|
||||
* MCP9700: 500 mV at 0 °C, 10 mV/°C, result returned in 0.1 °C units.
|
||||
* Use calibration points when available (adccal3 != 0xFFFF):
|
||||
* adccal0 = ADC reading at GND (offset),
|
||||
* adccal3 = ADC reading at 3.0 V reference.
|
||||
* Falls back to nominal constants when uncalibrated.
|
||||
*/
|
||||
uint16_t cal0 = config.calibration.adccal0;
|
||||
uint16_t cal3 = config.calibration.adccal3;
|
||||
int16_t tempcal = (int16_t)config.calibration.tempcal;
|
||||
|
||||
int32_t mv;
|
||||
if (cal3 != 0xFFFF && cal3 != cal0) {
|
||||
mv = (int32_t)(adc_value - cal0) * ADC_VREF_MV / (int32_t)(cal3 - cal0);
|
||||
} else {
|
||||
uint16_t adc_resolution = ADC_RESOLUTION << config.flags.fields.adc_oversample_bits;
|
||||
mv = (int32_t)adc_value * ADC_VREF_MV / adc_resolution;
|
||||
}
|
||||
|
||||
/* Result in 0.1 °C: (mv - 500 - tempcal_offset) * 10 / 10. */
|
||||
return (int16_t)((mv - MCP9700_OFFSET_MV - tempcal) * 10L / MCP9700_SCALE_MV);
|
||||
}
|
||||
|
||||
uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * VOLTAGE_MV_PER_STEP); }
|
||||
|
|
@ -33,14 +53,11 @@ uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * V
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
@ -50,21 +67,21 @@ void adc_enable_fast(void)
|
|||
{
|
||||
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
|
||||
/* Oversampling factor selected by config.flags.adc_oversample_bits. */
|
||||
ADC0.CTRLB |= (config.flags.fields.adc_oversample_bits << 2);
|
||||
|
||||
ADC0.CTRLC = 0; // reset
|
||||
ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VREFA_gc; // CLK_PER/32, VREFA as reference
|
||||
ADC0.CTRLC = 0;
|
||||
ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VREFA_gc; /* CLK_PER/32, VREFA reference */
|
||||
|
||||
ADC0.EVCTRL |= ADC_STARTEI_bm; // EVSYS input to start conversion
|
||||
ADC0.INTCTRL |= ADC_RESRDY_bm; // Enable interrupt on conversion complete
|
||||
ADC0.EVCTRL |= ADC_STARTEI_bm; /* EVSYS input starts conversion */
|
||||
ADC0.INTCTRL |= ADC_RESRDY_bm; /* result-ready interrupt */
|
||||
|
||||
// Configure VREF, Microchip DS40002015B page 424
|
||||
/* Microchip DS40002015B p. 424. */
|
||||
VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc;
|
||||
|
||||
ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // DET_SIG_PIN
|
||||
ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; /* DET_SIG_PIN */
|
||||
|
||||
adc_flags |= 0x01; // Fast mode enabled
|
||||
adc_flags |= 0x01;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -72,19 +89,21 @@ 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.CTRLC |= ADC_REFSEL_VREFA_gc;
|
||||
|
||||
ADC0.EVCTRL &= ~ADC_STARTEI_bm;
|
||||
ADC0.INTCTRL &= ~ADC_RESRDY_bm;
|
||||
|
||||
adc_flags &= ~0x01; // Fast mode disabled
|
||||
adc_flags &= ~0x01;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Trigger a single conversion and block until the result is ready.
|
||||
*/
|
||||
static uint16_t adc_conversion(void)
|
||||
{
|
||||
ADC0.COMMAND = ADC_STCONV_bm;
|
||||
|
|
@ -93,48 +112,45 @@ static uint16_t adc_conversion(void)
|
|||
return ADC0.RES;
|
||||
}
|
||||
|
||||
static uint16_t adc_read_tempsense(void)
|
||||
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.
|
||||
*/
|
||||
/*
|
||||
* Microchip DS40002015B p. 425 procedure:
|
||||
* 1. Configure VREF to 1.1 V via the VREF peripheral.
|
||||
* 2. Select internal voltage reference (REFSEL = 0x0 in ADCn.CTRLC).
|
||||
* 3. Select the temperature sensor channel via ADCn.MUXPOS.
|
||||
* 4. Set INITDLY ≥ 32 µs × CLK_ADC in ADCn.SAMPCTRL.
|
||||
* 5. Set SAMPLEN ≥ 32 µs × CLK_ADC in ADCn.CTRLC.
|
||||
* 6. Start a conversion to acquire the sensor output voltage.
|
||||
* 7. Process the result as below.
|
||||
*/
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
ADC0.CTRLA |= ADC_ENABLE_bm;
|
||||
|
||||
ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc;
|
||||
|
||||
ADC0.CTRLC = 0; // reset
|
||||
ADC0.CTRLC = 0;
|
||||
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
|
||||
int8_t sigrow_offset = SIGROW.TEMPSENSE1;
|
||||
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
|
||||
|
||||
/* Conversion result with 1.1 V internal reference; result may exceed 16
|
||||
* bits during the multiply (10-bit reading × 8-bit gain). */
|
||||
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 *= sigrow_gain;
|
||||
temp += 0x80; /* Round-half-up before the >>8 division. */
|
||||
temp >>= 8;
|
||||
// Divide result to get Kelvin
|
||||
uint16_t temperature_in_K = temp;
|
||||
uint16_t temperature_in_C = temp - 273; /* Convert from Kelvin to Celsius. */
|
||||
|
||||
adc_restore_default();
|
||||
|
||||
|
|
@ -142,73 +158,5 @@ static uint16_t adc_read_tempsense(void)
|
|||
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();
|
||||
}
|
||||
return temperature_in_C * 10; /* Return in 0.1 °C for consistency */
|
||||
}
|
||||
|
|
|
|||
38
sbc_fw/adc.h
38
sbc_fw/adc.h
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* @file adc.h
|
||||
* @brief
|
||||
* @brief ATmega4809 ADC0 driver and engineering-unit conversions.
|
||||
*
|
||||
* Created: 27.09.2025 05:06:42
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -11,47 +11,51 @@
|
|||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
|
||||
#ifndef ADC_H_
|
||||
#define ADC_H_
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Enable ADC for fast radiation detection
|
||||
* @brief Enable ADC0 in fast event-driven mode for radiation pulse capture.
|
||||
* ADC0 conversions are started by the detector trigger via EVSYS and
|
||||
* completed by the ADC0_RESRDY ISR.
|
||||
*/
|
||||
void adc_enable_fast(void);
|
||||
|
||||
/**
|
||||
* @brief Restore ADC to default settings
|
||||
* @brief Restore ADC0 to its default configuration suitable for blocking
|
||||
* analogRead()-style reads of housekeeping channels.
|
||||
*/
|
||||
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
|
||||
* @brief Convert a raw ADC reading to temperature in 0.1 °C.
|
||||
* Uses calibration values from @c config when available.
|
||||
* @param adc_value Raw 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
|
||||
* @brief Convert a raw ADC reading from the V28 feedback divider to mV.
|
||||
* @param adc_value Raw ADC reading.
|
||||
* @return Voltage at the sensor input 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
|
||||
* @brief Block-read an analog pin with software oversampling.
|
||||
* @param pin Analog pin to read.
|
||||
* @param samples Sample count (1, 4, 16, or 64).
|
||||
* @return Sum of samples right-shifted by log2(samples) to give the average.
|
||||
*/
|
||||
uint16_t adc_read_oversampled(uint8_t pin, uint8_t samples);
|
||||
|
||||
/**
|
||||
* @brief Command to calibrate ADC and read reference values
|
||||
* @brief Read the MCU internal temperature sensor (blocking, ~10 ms).
|
||||
* @return Temperature in Kelvin.
|
||||
*/
|
||||
void adc_cmd_calibrate(void);
|
||||
uint16_t adc_read_tempsense(void);
|
||||
|
||||
#endif // ADC_H_
|
||||
#endif /* ADC_H_ */
|
||||
165
sbc_fw/config.h
165
sbc_fw/config.h
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* @file config.h
|
||||
* @brief System configuration and constants
|
||||
* @brief System configuration, persisted-config struct definition, and
|
||||
* compile-time constants shared between modules.
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -14,86 +15,122 @@
|
|||
#ifndef CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
// Firmware version
|
||||
#define FIRMWARE_VERSION_MAJOR 2
|
||||
#define FIRMWARE_VERSION_MINOR 03
|
||||
#include <stdint.h>
|
||||
|
||||
// Serial communication
|
||||
/* Firmware version */
|
||||
#define FIRMWARE_VERSION_MAJOR 3
|
||||
#define FIRMWARE_VERSION_MINOR 00
|
||||
|
||||
/* Serial communication */
|
||||
#define SERIAL_BAUD_RATE 38400
|
||||
|
||||
// Timing constants
|
||||
/* Timing constants */
|
||||
#define STATUS_LED_BLINK_PERIOD 500U
|
||||
#define MAIN_LOOP_DELAY 10
|
||||
#define CPM_UPDATE_PERIOD 10000U
|
||||
#define RSENSE_UPDATE_PERIOD 1000U
|
||||
|
||||
#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
|
||||
/* Spectrum channel masks and counts */
|
||||
#define RSENSE_CHANNEL_MASK 0x3FFU /* 10-bit channel mask */
|
||||
#define RSENSE_CHANNEL_COUNT 1024
|
||||
|
||||
// Temperature sensor constants (MCP9700)
|
||||
#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
|
||||
/* MCP9700 temperature sensor calibration constants */
|
||||
#define MCP9700_OFFSET_MV 500L /* 500 mV at 0 °C */
|
||||
#define MCP9700_SCALE_MV 10L /* 10 mV per °C */
|
||||
#define ADC_OVERSAMPLING_BITS 2 /* 16x oversampling -> 2 extra bits */
|
||||
#define ADC_VREF_MV 3000L /* 3.0 V reference */
|
||||
#define ADC_RESOLUTION 1024L /* 10-bit ADC */
|
||||
|
||||
// Voltage divider constants
|
||||
#define VOLTAGE_DIVIDER_RATIO 20.0f // 200k and 10k resistors
|
||||
#define VOLTAGE_MV_PER_STEP 14.6484375f // (3.0 * 20) / 4096
|
||||
/* Voltage divider constants */
|
||||
#define VOLTAGE_DIVIDER_RATIO 20.0f /* 200k and 10k resistors */
|
||||
#define VOLTAGE_MV_PER_STEP 14.6484375f /* (3.0 * 20) / 4096 */
|
||||
|
||||
// EEPROM addresses for potentiometer settings
|
||||
#define EEPROM_POT_HV_ADDR 0
|
||||
#define EEPROM_POT_AMP_ADDR 1
|
||||
#define EEPROM_POT_DET_ADDR 2
|
||||
#define EEPROM_MAGIC_ADDR 3
|
||||
#define EEPROM_MAGIC_VALUE 0xA5 // Magic value to check if EEPROM is initialized
|
||||
|
||||
// Default potentiometer values
|
||||
/* Default potentiometer wiper value (mid-scale, 127/255) */
|
||||
#define DEFAULT_POT_VALUE 127
|
||||
|
||||
// Light sensor constants
|
||||
/* Light sensor I2C constants */
|
||||
#define LSENSE_I2C_BASE_ADDR 0x38
|
||||
#define LSENSE_EXPECTED_ID 0xE0
|
||||
#define LSENSE_DATA_SIZE 12
|
||||
|
||||
/**
|
||||
* @brief AD5160 wiper settings for the three digital potentiometers.
|
||||
*/
|
||||
typedef struct potentiometers_t {
|
||||
uint8_t pot_hv; /**< HV regulator wiper. */
|
||||
uint8_t pot_amp; /**< Amplifier gain wiper. */
|
||||
uint8_t pot_det; /**< Detector threshold wiper. */
|
||||
} __attribute__((packed)) potentiometers_t;
|
||||
|
||||
/**
|
||||
* @brief ADC and temperature-sensor calibration coefficients.
|
||||
* Reused as the payload of #CMD_SET_CALIBRATION.
|
||||
*/
|
||||
typedef struct calibration_t {
|
||||
uint16_t adccal0; /**< ADC reading at GND (offset). */
|
||||
uint16_t adccal3; /**< ADC reading at 3.0 V reference. */
|
||||
uint16_t tempcal; /**< Temperature offset correction. */
|
||||
} __attribute__((packed)) calibration_t;
|
||||
|
||||
/**
|
||||
* @brief Runtime feature flags packed into a single byte.
|
||||
* Reused as the payload of #CMD_SET_FLAGS.
|
||||
*/
|
||||
typedef union config_flags_t {
|
||||
struct __attribute__((packed)) {
|
||||
/** Spectrum oversampling: 0/1/2/3 = 1x/4x/16x/64x. */
|
||||
uint8_t spectrum_oversample_bits : 2;
|
||||
uint8_t reserved1 : 1;
|
||||
/** 0 = disable, 1 = enable temperature drift compensation. */
|
||||
uint8_t temperature_drift_compensation : 1;
|
||||
/** ADC oversampling for temperature/voltage: 0/1/2/3 = 1x/4x/16x/64x. */
|
||||
uint8_t adc_oversample_bits : 2;
|
||||
uint8_t reserved2 : 2;
|
||||
} fields;
|
||||
uint8_t value;
|
||||
} __attribute__((packed)) config_flags_t;
|
||||
|
||||
/**
|
||||
* @brief Temperature drift compensation parameters.
|
||||
* Reused as the payload of #CMD_SET_TEMP_DRIFT_COMPENSATION.
|
||||
*
|
||||
* Drift coefficients are in pot_units/255 per °C. When @c pot_X_low equals
|
||||
* @c pot_X_high, drift compensation for that channel is disabled.
|
||||
*
|
||||
* Sensor → potentiometer mapping:
|
||||
* - temp_drift : DET_TEMP (SiPM carrier) → pot_hv
|
||||
* - vbias_drift : HV_TEMP (+28V supply) → pot_hv
|
||||
* - gain_drift : AMP_TEMP (amplifier) → pot_amp
|
||||
* - threshold_drift : AMP_TEMP (amplifier) → pot_det
|
||||
*/
|
||||
typedef struct temp_drift_compensation_t {
|
||||
int16_t temp_drift;
|
||||
int16_t vbias_drift;
|
||||
int16_t gain_drift;
|
||||
int16_t threshold_drift;
|
||||
uint32_t drift_period_ms; /**< Minimum interval between adjustments, in ms. */
|
||||
uint8_t pot_hv_low;
|
||||
uint8_t pot_hv_high;
|
||||
uint8_t pot_amp_low;
|
||||
uint8_t pot_amp_high;
|
||||
uint8_t pot_det_low;
|
||||
uint8_t pot_det_high;
|
||||
} __attribute__((packed)) temp_drift_compensation_t;
|
||||
|
||||
/**
|
||||
* @brief Persistent device configuration.
|
||||
*
|
||||
* Stored in EEPROM with dual-partition wear leveling (see eeprom.cpp).
|
||||
* The pair @c magic1 / @c magic2 (0xA5 / 0x5A) marks a partition as valid.
|
||||
*/
|
||||
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));
|
||||
uint8_t magic1; /**< 0xA5 — valid-partition marker, byte 1. */
|
||||
uint8_t magic2; /**< 0x5A — valid-partition marker, byte 2. */
|
||||
potentiometers_t pots;
|
||||
calibration_t calibration;
|
||||
config_flags_t flags;
|
||||
temp_drift_compensation_t temp_drift_compensation;
|
||||
} __attribute__((packed)) config_t;
|
||||
|
||||
extern config_t config;
|
||||
|
||||
#endif // CONFIG_H
|
||||
#endif /* CONFIG_H */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* @file eeprom.cpp
|
||||
* @brief EEPROM management implementation
|
||||
* @brief EEPROM management with dual-partition wear leveling.
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -18,35 +18,127 @@
|
|||
|
||||
config_t config;
|
||||
|
||||
/*
|
||||
* Each partition occupies sizeof(config_t)+1 bytes:
|
||||
* [0 .. sizeof(config_t)-1] config_t struct
|
||||
* [sizeof(config_t)] generation counter (uint8_t)
|
||||
*/
|
||||
#define PARTITION_SIZE ((uint16_t)(sizeof(config_t) + 1))
|
||||
#define PARTITION_A_ADDR 0
|
||||
#define PARTITION_B_ADDR PARTITION_SIZE
|
||||
#define GEN_OFFSET ((uint16_t)sizeof(config_t))
|
||||
|
||||
/**
|
||||
* @brief Track which partition was last written so saves alternate.
|
||||
* 0 = partition A is current, 1 = partition B is current.
|
||||
*/
|
||||
static uint8_t g_active_partition = 0;
|
||||
|
||||
/**
|
||||
* @brief Check whether the partition at @p base contains a valid magic pair.
|
||||
*/
|
||||
static bool partition_valid(uint16_t base)
|
||||
{
|
||||
uint8_t m1 = EEPROM.read(base + offsetof(config_t, magic1));
|
||||
uint8_t m2 = EEPROM.read(base + offsetof(config_t, magic2));
|
||||
return (m1 == 0xA5 && m2 == 0x5A);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read a config_t struct from the partition at @p base into @p dst.
|
||||
*/
|
||||
static void read_partition(uint16_t base, config_t *dst)
|
||||
{
|
||||
uint8_t *p = (uint8_t *)dst;
|
||||
for (uint16_t i = 0; i < sizeof(config_t); i++) {
|
||||
p[i] = EEPROM.read(base + i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write @p src and a generation byte to the partition at @p base.
|
||||
* Uses EEPROM.update() so unchanged cells incur no wear.
|
||||
*/
|
||||
static void write_partition(uint16_t base, const config_t *src, uint8_t generation)
|
||||
{
|
||||
const uint8_t *p = (const uint8_t *)src;
|
||||
for (uint16_t i = 0; i < sizeof(config_t); i++) {
|
||||
EEPROM.update(base + i, p[i]);
|
||||
}
|
||||
EEPROM.update(base + GEN_OFFSET, generation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Populate @p cfg with safe factory defaults.
|
||||
* Used on first boot or when both partitions are corrupt.
|
||||
*/
|
||||
static void set_defaults(config_t *cfg)
|
||||
{
|
||||
cfg->magic1 = 0xA5;
|
||||
cfg->magic2 = 0x5A;
|
||||
cfg->pots.pot_hv = DEFAULT_POT_VALUE;
|
||||
cfg->pots.pot_amp = DEFAULT_POT_VALUE;
|
||||
cfg->pots.pot_det = DEFAULT_POT_VALUE;
|
||||
cfg->calibration.adccal0 = 0xFFFF;
|
||||
cfg->calibration.adccal3 = 0xFFFF;
|
||||
cfg->calibration.tempcal = 0;
|
||||
cfg->flags.value = 0;
|
||||
/* pot_low == pot_high disables drift compensation per channel. */
|
||||
cfg->temp_drift_compensation.temp_drift = 0;
|
||||
cfg->temp_drift_compensation.vbias_drift = 0;
|
||||
cfg->temp_drift_compensation.gain_drift = 0;
|
||||
cfg->temp_drift_compensation.threshold_drift = 0;
|
||||
cfg->temp_drift_compensation.drift_period_ms = 5000;
|
||||
cfg->temp_drift_compensation.pot_hv_low = DEFAULT_POT_VALUE;
|
||||
cfg->temp_drift_compensation.pot_hv_high = DEFAULT_POT_VALUE;
|
||||
cfg->temp_drift_compensation.pot_amp_low = DEFAULT_POT_VALUE;
|
||||
cfg->temp_drift_compensation.pot_amp_high = DEFAULT_POT_VALUE;
|
||||
cfg->temp_drift_compensation.pot_det_low = DEFAULT_POT_VALUE;
|
||||
cfg->temp_drift_compensation.pot_det_high = DEFAULT_POT_VALUE;
|
||||
}
|
||||
|
||||
void eeprom_init(void)
|
||||
{
|
||||
if (EEPROM.read(EEPROM_MAGIC_ADDR) != EEPROM_MAGIC_VALUE) {
|
||||
potentiometer_settings_t default_settings = {
|
||||
.hv_pot = DEFAULT_POT_VALUE, .amp_pot = DEFAULT_POT_VALUE, .det_pot = DEFAULT_POT_VALUE};
|
||||
bool a_valid = partition_valid(PARTITION_A_ADDR);
|
||||
bool b_valid = partition_valid(PARTITION_B_ADDR);
|
||||
|
||||
eeprom_save_pot_settings(&default_settings);
|
||||
EEPROM.write(EEPROM_MAGIC_ADDR, EEPROM_MAGIC_VALUE);
|
||||
if (a_valid && b_valid) {
|
||||
uint8_t gen_a = EEPROM.read(PARTITION_A_ADDR + GEN_OFFSET);
|
||||
uint8_t gen_b = EEPROM.read(PARTITION_B_ADDR + GEN_OFFSET);
|
||||
/* Signed delta handles wrap-around: B is newer if (gen_b - gen_a) > 0 mod 256. */
|
||||
if ((int8_t)(gen_b - gen_a) > 0) {
|
||||
read_partition(PARTITION_B_ADDR, &config);
|
||||
g_active_partition = 1;
|
||||
} else {
|
||||
read_partition(PARTITION_A_ADDR, &config);
|
||||
g_active_partition = 0;
|
||||
}
|
||||
} else if (a_valid) {
|
||||
read_partition(PARTITION_A_ADDR, &config);
|
||||
g_active_partition = 0;
|
||||
} else if (b_valid) {
|
||||
read_partition(PARTITION_B_ADDR, &config);
|
||||
g_active_partition = 1;
|
||||
} else {
|
||||
set_defaults(&config);
|
||||
write_partition(PARTITION_A_ADDR, &config, 0);
|
||||
g_active_partition = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void eeprom_load_pot_settings(potentiometer_settings_t *settings)
|
||||
void eeprom_save_config(void)
|
||||
{
|
||||
if (settings == nullptr) {
|
||||
return;
|
||||
}
|
||||
/* Write to whichever partition is NOT currently active. */
|
||||
uint8_t target_partition = (g_active_partition == 0) ? 1 : 0;
|
||||
uint16_t target_base = (target_partition == 0) ? PARTITION_A_ADDR : PARTITION_B_ADDR;
|
||||
uint16_t active_base = (g_active_partition == 0) ? PARTITION_A_ADDR : PARTITION_B_ADDR;
|
||||
|
||||
settings->hv_pot = EEPROM.read(EEPROM_POT_HV_ADDR);
|
||||
settings->amp_pot = EEPROM.read(EEPROM_POT_AMP_ADDR);
|
||||
settings->det_pot = EEPROM.read(EEPROM_POT_DET_ADDR);
|
||||
}
|
||||
|
||||
void eeprom_save_pot_settings(const potentiometer_settings_t *settings)
|
||||
{
|
||||
if (settings == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
EEPROM.write(EEPROM_POT_HV_ADDR, settings->hv_pot);
|
||||
EEPROM.write(EEPROM_POT_AMP_ADDR, settings->amp_pot);
|
||||
EEPROM.write(EEPROM_POT_DET_ADDR, settings->det_pot);
|
||||
uint8_t old_gen = EEPROM.read(active_base + GEN_OFFSET);
|
||||
uint8_t new_gen = old_gen + 1;
|
||||
|
||||
config.magic1 = 0xA5;
|
||||
config.magic2 = 0x5A;
|
||||
|
||||
write_partition(target_base, &config, new_gen);
|
||||
g_active_partition = target_partition;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,16 @@
|
|||
/*
|
||||
* @file eeprom.h
|
||||
* @brief EEPROM management for persistent settings
|
||||
* @brief EEPROM management for persistent configuration storage.
|
||||
*
|
||||
* Dual-partition layout in ATmega4809 EEPROM (256 bytes):
|
||||
*
|
||||
* [0 .. sizeof(config_t)] Partition A: config_t + 1-byte generation counter
|
||||
* [sizeof(config_t)+1 .. 2*(sizeof(config_t)+1)-1] Partition B: same layout
|
||||
*
|
||||
* On save, the alternate (older) partition is overwritten. If power fails
|
||||
* mid-write the other partition retains the last good config. The generation
|
||||
* counter wraps around uint8_t; the partition with the higher counter
|
||||
* (mod-256 comparison via signed delta) is the newer one.
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -17,30 +27,16 @@
|
|||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Potentiometer settings structure
|
||||
*/
|
||||
typedef struct {
|
||||
uint8_t hv_pot; ///< High voltage potentiometer value
|
||||
uint8_t amp_pot; ///< SiPM Pre-amp gain potentiometer value
|
||||
uint8_t det_pot; ///< Detection threshold potentiometer value
|
||||
} potentiometer_settings_t;
|
||||
|
||||
/**
|
||||
* @brief Initialize EEPROM settings
|
||||
* Sets default values if EEPROM is uninitialized
|
||||
* @brief Load the global @c config from EEPROM.
|
||||
* Picks the freshest valid partition; writes factory defaults if both
|
||||
* partitions are blank or corrupt.
|
||||
*/
|
||||
void eeprom_init(void);
|
||||
|
||||
/**
|
||||
* @brief Load potentiometer settings from EEPROM
|
||||
* @param settings Pointer to settings structure to populate
|
||||
* @brief Persist the current global @c config to the alternate partition,
|
||||
* bumping the generation counter to flip the active partition.
|
||||
*/
|
||||
void eeprom_load_pot_settings(potentiometer_settings_t *settings);
|
||||
void eeprom_save_config(void);
|
||||
|
||||
/**
|
||||
* @brief Save potentiometer settings to EEPROM
|
||||
* @param settings Pointer to settings structure to save
|
||||
*/
|
||||
void eeprom_save_pot_settings(const potentiometer_settings_t *settings);
|
||||
|
||||
#endif // DET_EEPROM_H
|
||||
#endif /* DET_EEPROM_H */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* @file iodefs.h
|
||||
* @brief
|
||||
* @brief Hardware pin assignments for the SBC firmware.
|
||||
*
|
||||
* Created: 21.09.2025 05:39:48
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -18,32 +18,38 @@
|
|||
#include <avr/io.h>
|
||||
#include <avr/pgmspace.h>
|
||||
|
||||
#define DET_TRIG_PIN 21 // PC7, event input
|
||||
#define DET_READ_PIN A3 // PD3 (ADC3)
|
||||
#define DET_PREAMP_PIN A2 // PD2 (ADC2)
|
||||
/* Detector signal path */
|
||||
#define DET_TRIG_PIN 21 /* PC7, event input */
|
||||
#define DET_READ_PIN A3 /* PD3 (ADC3) */
|
||||
#define DET_PREAMP_PIN A2 /* PD2 (ADC2) */
|
||||
|
||||
#define STATUS_LED 31 // PE1
|
||||
/* Status indicator */
|
||||
#define STATUS_LED 31 /* PE1 */
|
||||
|
||||
#define HV_EN 16 // PC2
|
||||
#define DET_EN 18 // PC4
|
||||
#define DET_RST 20 // PC6
|
||||
/* Power and reset control */
|
||||
#define HV_EN 16 /* PC2 */
|
||||
#define DET_EN 18 /* PC4 */
|
||||
#define DET_RST 20 /* PC6 */
|
||||
|
||||
#define V28V0_FB_PIN A0 // PD0 (ADC0)
|
||||
#define HV_TEMP_PIN A1 // PD1 (ADC1)
|
||||
#define AMP_TEMP_PIN A4 // PD4 (ADC4)
|
||||
#define DET_TEMP_PIN A5 // PD5 (ADC5)
|
||||
/* Housekeeping ADC inputs */
|
||||
#define V28V0_FB_PIN A0 /* PD0 (ADC0) */
|
||||
#define HV_TEMP_PIN A1 /* PD1 (ADC1) */
|
||||
#define AMP_TEMP_PIN A4 /* PD4 (ADC4) */
|
||||
#define DET_TEMP_PIN A5 /* PD5 (ADC5) */
|
||||
|
||||
#define HV_CS 15 // PC1
|
||||
#define AMP_CS 17 // PC3
|
||||
#define DET_CS 19 // PC5
|
||||
/* AD5160 digital potentiometer chip selects */
|
||||
#define HV_CS 15 /* PC1 */
|
||||
#define AMP_CS 17 /* PC3 */
|
||||
#define DET_CS 19 /* PC5 */
|
||||
|
||||
#define SCL0 8 // PB0
|
||||
#define SDA0 9 // PB1
|
||||
/* Light sensor I2C bus pins (software-bitbanged) */
|
||||
#define SCL0 8 /* PB0 */
|
||||
#define SDA0 9 /* PB1 */
|
||||
|
||||
#define SCL1 10 // PB2
|
||||
#define SDA1 11 // PB3
|
||||
#define SCL1 10 /* PB2 */
|
||||
#define SDA1 11 /* PB3 */
|
||||
|
||||
#define SCL2 12 // PB4
|
||||
#define SDA2 13 // PB5
|
||||
#define SCL2 12 /* PB4 */
|
||||
#define SDA2 13 /* PB5 */
|
||||
|
||||
#endif // LSENSE_IODEFS_H
|
||||
#endif /* LSENSE_IODEFS_H */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
/*
|
||||
* @file lsense.cpp
|
||||
* @brief Light sensor implementation
|
||||
* @brief Light sensor implementation: software I2C, detection, and channel
|
||||
* readout for the six TCS-style light sensors on three buses.
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -21,9 +22,6 @@
|
|||
|
||||
static SoftwareI2C g_i2c_buses[NUM_BUSES];
|
||||
|
||||
/**
|
||||
* @brief Light sensor data structure
|
||||
*/
|
||||
typedef union __attribute__((packed)) {
|
||||
struct __attribute__((packed)) {
|
||||
uint16_t red;
|
||||
|
|
@ -39,21 +37,9 @@ typedef union __attribute__((packed)) {
|
|||
static light_sensor_data_t g_sensor_data[NUM_BUSES][SENSORS_PER_BUS];
|
||||
static uint8_t g_sensor_addresses[NUM_BUSES][SENSORS_PER_BUS];
|
||||
|
||||
// Presence counters
|
||||
static uint8_t g_sensors_detected_total = 0;
|
||||
static uint8_t g_sensors_detected_per_bus[NUM_BUSES];
|
||||
|
||||
static void configure_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr);
|
||||
static void read_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr, light_sensor_data_t *sensor_data);
|
||||
static uint8_t detect_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr);
|
||||
static void initialize_i2c_bus(int bus_number, bool pin_order_inverted);
|
||||
static void detect_sensors_on_bus_generic(int bus_number);
|
||||
|
||||
/**
|
||||
* @brief Configure a light sensor for measurement
|
||||
* @param i2c_bus Reference to I2C bus instance
|
||||
* @param sensor_addr LSB of sensor address (0 or 1)
|
||||
*/
|
||||
static void configure_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr)
|
||||
{
|
||||
uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr;
|
||||
|
|
@ -64,17 +50,11 @@ static void configure_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr)
|
|||
|
||||
i2c_bus.beginTransmission(addr);
|
||||
i2c_bus.write(0x41);
|
||||
i2c_bus.write(0b00101101); // IR gain x1, RGB gain x1, 35ms mode
|
||||
i2c_bus.write(0b00010000); // RGB_EN = 1, measurement active
|
||||
i2c_bus.write(0b00101101); /* IR gain x1, RGB gain x1, 35ms mode */
|
||||
i2c_bus.write(0b00010000); /* RGB_EN = 1, measurement active */
|
||||
i2c_bus.endTransmission();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Read data from a light sensor
|
||||
* @param i2c_bus Reference to I2C bus instance
|
||||
* @param sensor_addr LSB of sensor address (0 or 1)
|
||||
* @param sensor_data Pointer to data structure to populate
|
||||
*/
|
||||
static void read_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr, light_sensor_data_t *sensor_data)
|
||||
{
|
||||
if (sensor_data == nullptr)
|
||||
|
|
@ -83,7 +63,7 @@ static void read_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr, light_s
|
|||
uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr;
|
||||
|
||||
i2c_bus.beginTransmission(addr);
|
||||
i2c_bus.write(0x50); // Start from red register
|
||||
i2c_bus.write(0x50);
|
||||
i2c_bus.endTransmission();
|
||||
|
||||
i2c_bus.requestFrom(addr, (uint8_t)LSENSE_DATA_SIZE);
|
||||
|
|
@ -93,37 +73,25 @@ static void read_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr, light_s
|
|||
i2c_bus.endTransmission();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Detect if a light sensor is present
|
||||
* @param i2c_bus Reference to I2C bus instance
|
||||
* @param sensor_addr LSB of sensor address (0 or 1)
|
||||
* @return Manufacturer ID (0xE0 if valid sensor detected, 0 otherwise)
|
||||
*/
|
||||
static uint8_t detect_light_sensor(SoftwareI2C &i2c_bus, uint8_t sensor_addr)
|
||||
{
|
||||
uint8_t response = 0;
|
||||
uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr;
|
||||
|
||||
i2c_bus.beginTransmission(addr);
|
||||
i2c_bus.write(0x92); // Manufacturer ID register
|
||||
i2c_bus.write(0x92);
|
||||
i2c_bus.endTransmission();
|
||||
|
||||
i2c_bus.requestFrom(addr, (uint8_t)1);
|
||||
if (i2c_bus.available()) {
|
||||
response = i2c_bus.read();
|
||||
if (response == 0xFF) {
|
||||
response = 0; // Invalid response
|
||||
response = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize an I2C bus and configure sensors
|
||||
* @param bus_number Bus number (0, 1, or 2)
|
||||
* @param pin_order_inverted Whether to invert SDA/SCL pin order
|
||||
*/
|
||||
static void initialize_i2c_bus(int bus_number, bool pin_order_inverted)
|
||||
{
|
||||
uint8_t sda_pin, scl_pin;
|
||||
|
|
@ -142,29 +110,22 @@ static void initialize_i2c_bus(int bus_number, bool pin_order_inverted)
|
|||
scl_pin = pin_order_inverted ? SDA2 : SCL2;
|
||||
break;
|
||||
default:
|
||||
// Invalid bus number
|
||||
return;
|
||||
}
|
||||
|
||||
SoftwareI2C *i2c_bus = &g_i2c_buses[bus_number];
|
||||
|
||||
i2c_bus->begin(sda_pin, scl_pin);
|
||||
configure_light_sensor(*i2c_bus, 0);
|
||||
configure_light_sensor(*i2c_bus, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Generic function to detect sensors on any I2C bus
|
||||
* @param bus_number Bus number (0, 1, or 2)
|
||||
*/
|
||||
static void detect_sensors_on_bus_generic(int bus_number)
|
||||
static void detect_sensors_on_bus(int bus_number)
|
||||
{
|
||||
if (bus_number < 0 || bus_number >= NUM_BUSES)
|
||||
return;
|
||||
|
||||
g_sensors_detected_per_bus[bus_number] = 0;
|
||||
|
||||
// Try normal pin order first
|
||||
initialize_i2c_bus(bus_number, false);
|
||||
g_sensor_addresses[bus_number][SENSOR_NEG] = detect_light_sensor(g_i2c_buses[bus_number], 0);
|
||||
if (g_sensor_addresses[bus_number][SENSOR_NEG] == LSENSE_EXPECTED_ID) {
|
||||
|
|
@ -177,7 +138,6 @@ static void detect_sensors_on_bus_generic(int bus_number)
|
|||
g_sensors_detected_per_bus[bus_number]++;
|
||||
}
|
||||
|
||||
// If no sensors found, try inverted pin order
|
||||
if (g_sensors_detected_per_bus[bus_number] == 0) {
|
||||
initialize_i2c_bus(bus_number, true);
|
||||
g_sensor_addresses[bus_number][SENSOR_NEG] = detect_light_sensor(g_i2c_buses[bus_number], 0);
|
||||
|
|
@ -193,62 +153,67 @@ static void detect_sensors_on_bus_generic(int bus_number)
|
|||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/* Command handlers */
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
|
||||
void lsense_cmd_presence(void)
|
||||
void lsense_detect_all(void)
|
||||
{
|
||||
g_sensors_detected_total = 0;
|
||||
|
||||
detect_sensors_on_bus_generic(BUS_X);
|
||||
detect_sensors_on_bus_generic(BUS_Y);
|
||||
detect_sensors_on_bus_generic(BUS_Z);
|
||||
|
||||
Serial.write(g_sensors_detected_total);
|
||||
Serial.write(g_sensors_detected_per_bus[BUS_X]);
|
||||
Serial.write(g_sensor_addresses[BUS_X][SENSOR_NEG]);
|
||||
Serial.write(g_sensor_addresses[BUS_X][SENSOR_POS]);
|
||||
Serial.write(g_sensors_detected_per_bus[BUS_Y]);
|
||||
Serial.write(g_sensor_addresses[BUS_Y][SENSOR_NEG]);
|
||||
Serial.write(g_sensor_addresses[BUS_Y][SENSOR_POS]);
|
||||
Serial.write(g_sensors_detected_per_bus[BUS_Z]);
|
||||
Serial.write(g_sensor_addresses[BUS_Z][SENSOR_NEG]);
|
||||
Serial.write(g_sensor_addresses[BUS_Z][SENSOR_POS]);
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
detect_sensors_on_bus(BUS_X);
|
||||
detect_sensors_on_bus(BUS_Y);
|
||||
detect_sensors_on_bus(BUS_Z);
|
||||
}
|
||||
|
||||
void lsense_cmd_read(void)
|
||||
void lsense_get_presence(lsense_presence_t *out)
|
||||
{
|
||||
detect_sensors_on_bus_generic(BUS_X);
|
||||
read_light_sensor(g_i2c_buses[BUS_X], 0, &g_sensor_data[BUS_X][SENSOR_NEG]);
|
||||
read_light_sensor(g_i2c_buses[BUS_X], 1, &g_sensor_data[BUS_X][SENSOR_POS]);
|
||||
|
||||
detect_sensors_on_bus_generic(BUS_Y);
|
||||
read_light_sensor(g_i2c_buses[BUS_Y], 0, &g_sensor_data[BUS_Y][SENSOR_NEG]);
|
||||
read_light_sensor(g_i2c_buses[BUS_Y], 1, &g_sensor_data[BUS_Y][SENSOR_POS]);
|
||||
|
||||
detect_sensors_on_bus_generic(BUS_Z);
|
||||
read_light_sensor(g_i2c_buses[BUS_Z], 0, &g_sensor_data[BUS_Z][SENSOR_NEG]);
|
||||
read_light_sensor(g_i2c_buses[BUS_Z], 1, &g_sensor_data[BUS_Z][SENSOR_POS]);
|
||||
|
||||
Serial.write(g_sensor_data[BUS_X][SENSOR_NEG].data.blue & 0xFF);
|
||||
Serial.write(g_sensor_data[BUS_X][SENSOR_NEG].data.blue >> 8);
|
||||
Serial.write(g_sensor_data[BUS_X][SENSOR_POS].data.blue & 0xFF);
|
||||
Serial.write(g_sensor_data[BUS_X][SENSOR_POS].data.blue >> 8);
|
||||
Serial.write(g_sensor_data[BUS_Y][SENSOR_NEG].data.blue & 0xFF);
|
||||
Serial.write(g_sensor_data[BUS_Y][SENSOR_NEG].data.blue >> 8);
|
||||
Serial.write(g_sensor_data[BUS_Y][SENSOR_POS].data.blue & 0xFF);
|
||||
Serial.write(g_sensor_data[BUS_Y][SENSOR_POS].data.blue >> 8);
|
||||
Serial.write(g_sensor_data[BUS_Z][SENSOR_NEG].data.blue & 0xFF);
|
||||
Serial.write(g_sensor_data[BUS_Z][SENSOR_NEG].data.blue >> 8);
|
||||
Serial.write(g_sensor_data[BUS_Z][SENSOR_POS].data.blue & 0xFF);
|
||||
Serial.write(g_sensor_data[BUS_Z][SENSOR_POS].data.blue >> 8);
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
if (out == nullptr)
|
||||
return;
|
||||
out->total_detected = g_sensors_detected_total;
|
||||
out->bus_x_count = g_sensors_detected_per_bus[BUS_X];
|
||||
out->bus_x_neg_addr = g_sensor_addresses[BUS_X][SENSOR_NEG];
|
||||
out->bus_x_pos_addr = g_sensor_addresses[BUS_X][SENSOR_POS];
|
||||
out->bus_y_count = g_sensors_detected_per_bus[BUS_Y];
|
||||
out->bus_y_neg_addr = g_sensor_addresses[BUS_Y][SENSOR_NEG];
|
||||
out->bus_y_pos_addr = g_sensor_addresses[BUS_Y][SENSOR_POS];
|
||||
out->bus_z_count = g_sensors_detected_per_bus[BUS_Z];
|
||||
out->bus_z_neg_addr = g_sensor_addresses[BUS_Z][SENSOR_NEG];
|
||||
out->bus_z_pos_addr = g_sensor_addresses[BUS_Z][SENSOR_POS];
|
||||
}
|
||||
|
||||
static uint16_t extract_channel(const light_sensor_data_t *d, uint8_t channel)
|
||||
{
|
||||
switch (channel) {
|
||||
case 0:
|
||||
return d->data.red;
|
||||
case 1:
|
||||
return d->data.green;
|
||||
case 2:
|
||||
return d->data.blue;
|
||||
case 3:
|
||||
return d->data.ir;
|
||||
case 4:
|
||||
return d->data.green2;
|
||||
case 5: /* visible: R+G+B+G2 */
|
||||
return (uint16_t)((uint32_t)d->data.red + d->data.green + d->data.blue + d->data.green2);
|
||||
case 6: /* all: R+G+B+G2+IR */
|
||||
return (uint16_t)((uint32_t)d->data.red + d->data.green + d->data.blue + d->data.green2 +
|
||||
d->data.ir);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void lsense_read_channel(uint8_t channel, lsense_channel_values_t *out)
|
||||
{
|
||||
if (out == nullptr)
|
||||
return;
|
||||
|
||||
for (int bus = 0; bus < NUM_BUSES; bus++) {
|
||||
read_light_sensor(g_i2c_buses[bus], 0, &g_sensor_data[bus][SENSOR_NEG]);
|
||||
read_light_sensor(g_i2c_buses[bus], 1, &g_sensor_data[bus][SENSOR_POS]);
|
||||
}
|
||||
|
||||
out->values[0] = extract_channel(&g_sensor_data[BUS_X][SENSOR_NEG], channel);
|
||||
out->values[1] = extract_channel(&g_sensor_data[BUS_X][SENSOR_POS], channel);
|
||||
out->values[2] = extract_channel(&g_sensor_data[BUS_Y][SENSOR_NEG], channel);
|
||||
out->values[3] = extract_channel(&g_sensor_data[BUS_Y][SENSOR_POS], channel);
|
||||
out->values[4] = extract_channel(&g_sensor_data[BUS_Z][SENSOR_NEG], channel);
|
||||
out->values[5] = extract_channel(&g_sensor_data[BUS_Z][SENSOR_POS], channel);
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
/*
|
||||
* @file lsense.h
|
||||
* @brief Light sensor management and command handling
|
||||
* @brief Light sensor management.
|
||||
*
|
||||
* Created: 21.09.2025 05:53:32
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
|
|
@ -14,30 +14,69 @@
|
|||
#ifndef LSENSE_H
|
||||
#define LSENSE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#define NUM_BUSES 3
|
||||
#define NUM_BUSES 3
|
||||
#define SENSORS_PER_BUS 2
|
||||
|
||||
enum {
|
||||
BUS_X = 0, // X-axis (bus 0)
|
||||
BUS_Y = 1, // Y-axis (bus 1)
|
||||
BUS_Z = 2 // Z-axis (bus 2)
|
||||
};
|
||||
|
||||
enum {
|
||||
SENSOR_NEG = 0, // Negative sensor (addresses 0)
|
||||
SENSOR_POS = 1 // Positive sensor (addresses 1)
|
||||
};
|
||||
enum { BUS_X = 0, BUS_Y = 1, BUS_Z = 2 };
|
||||
enum { SENSOR_NEG = 0, SENSOR_POS = 1 };
|
||||
|
||||
/**
|
||||
* @brief Send light sensor presence information via serial
|
||||
* @brief Detection result for the six light sensors across three I2C buses.
|
||||
* Reused as the response payload of #CMD_LIGHT_SENSOR_PRESENCE.
|
||||
*/
|
||||
void lsense_cmd_presence(void);
|
||||
typedef union lsense_presence_t {
|
||||
struct __attribute__((packed)) {
|
||||
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;
|
||||
};
|
||||
uint8_t bytes[10];
|
||||
} __attribute__((packed)) lsense_presence_t;
|
||||
|
||||
/**
|
||||
* @brief Read and send light sensor data via serial
|
||||
* @brief Per-sensor channel values for a single read across all 6 sensors.
|
||||
* Reused as the response payload of #CMD_READ_LIGHT_SENSORS.
|
||||
*/
|
||||
void lsense_cmd_read(void);
|
||||
typedef union lsense_channel_values_t {
|
||||
struct __attribute__((packed)) {
|
||||
uint16_t bus_x_neg_value;
|
||||
uint16_t bus_x_pos_value;
|
||||
uint16_t bus_y_neg_value;
|
||||
uint16_t bus_y_pos_value;
|
||||
uint16_t bus_z_neg_value;
|
||||
uint16_t bus_z_pos_value;
|
||||
};
|
||||
uint16_t values[6];
|
||||
} __attribute__((packed)) lsense_channel_values_t;
|
||||
|
||||
#endif // LSENSE_H
|
||||
/**
|
||||
* @brief Detect all sensors on all three buses.
|
||||
* Populates internal state used by #lsense_get_presence and
|
||||
* #lsense_read_channel.
|
||||
*/
|
||||
void lsense_detect_all(void);
|
||||
|
||||
/**
|
||||
* @brief Fill @p out with detection results from the most recent
|
||||
* #lsense_detect_all call.
|
||||
*/
|
||||
void lsense_get_presence(lsense_presence_t *out);
|
||||
|
||||
/**
|
||||
* @brief Read a spectral channel from all six sensors into @p out.
|
||||
* @param channel 0=red 1=green 2=blue 3=IR 4=green2
|
||||
* 5=visible (R+G+B+G2) 6=all (R+G+B+G2+IR)
|
||||
* @param out Output values, ordered X_neg, X_pos, Y_neg, Y_pos, Z_neg, Z_pos.
|
||||
*/
|
||||
void lsense_read_channel(uint8_t channel, lsense_channel_values_t *out);
|
||||
|
||||
#endif /* LSENSE_H */
|
||||
|
|
|
|||
328
sbc_fw/protocol.cpp
Normal file
328
sbc_fw/protocol.cpp
Normal file
|
|
@ -0,0 +1,328 @@
|
|||
/*
|
||||
* @file protocol.cpp
|
||||
* @brief Protocol framing, CRC, and command handler implementations.
|
||||
*
|
||||
* Created: 07.05.2026
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include "adc.h"
|
||||
#include "config.h"
|
||||
#include "eeprom.h"
|
||||
#include "iodefs.h"
|
||||
#include "lsense.h"
|
||||
#include "protocol.h"
|
||||
#include "rsense.h"
|
||||
|
||||
extern bool g_rsense_enabled; /* from rsense.cpp */
|
||||
|
||||
uint16_t protocol_crc16_update(uint16_t crc, const uint8_t *data, uint16_t len)
|
||||
{
|
||||
if (data == NULL) {
|
||||
return crc;
|
||||
}
|
||||
while (len--) {
|
||||
crc ^= (uint16_t)(*data++) << 8;
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
crc = (crc & 0x8000) ? (crc << 1) ^ 0x1021 : crc << 1;
|
||||
}
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
uint16_t protocol_crc16_xmodem(const uint8_t *data, uint16_t len) { return protocol_crc16_update(0, data, len); }
|
||||
|
||||
/**
|
||||
* @brief Write a frame header (CMD + LEN) to the serial port.
|
||||
* Internal helper, not exported.
|
||||
*/
|
||||
static void send_header_bytes(uint16_t cmd, uint16_t length)
|
||||
{
|
||||
Serial.write((uint8_t)(cmd & 0xFF));
|
||||
Serial.write((uint8_t)(cmd >> 8));
|
||||
Serial.write((uint8_t)(length & 0xFF));
|
||||
Serial.write((uint8_t)(length >> 8));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Write a 16-bit CRC trailer (LSB first) and flush the TX queue.
|
||||
*/
|
||||
static void send_crc_trailer(uint16_t crc)
|
||||
{
|
||||
Serial.write((uint8_t)(crc & 0xFF));
|
||||
Serial.write((uint8_t)(crc >> 8));
|
||||
Serial.flush();
|
||||
}
|
||||
|
||||
void protocol_send_message_v2(uint16_t cmd, const void *frag1, uint16_t len1, const void *frag2, uint16_t len2)
|
||||
{
|
||||
protocol_header_t hdr;
|
||||
hdr.cmd = cmd;
|
||||
hdr.length = (uint16_t)(len1 + len2);
|
||||
|
||||
uint16_t crc = protocol_crc16_update(0, (const uint8_t *)&hdr, sizeof(hdr));
|
||||
crc = protocol_crc16_update(crc, (const uint8_t *)frag1, len1);
|
||||
crc = protocol_crc16_update(crc, (const uint8_t *)frag2, len2);
|
||||
|
||||
send_header_bytes(hdr.cmd, hdr.length);
|
||||
if (frag1 != NULL && len1 > 0) {
|
||||
Serial.write((const uint8_t *)frag1, len1);
|
||||
}
|
||||
if (frag2 != NULL && len2 > 0) {
|
||||
Serial.write((const uint8_t *)frag2, len2);
|
||||
}
|
||||
send_crc_trailer(crc);
|
||||
}
|
||||
|
||||
void protocol_send_message(uint16_t cmd, const void *payload, uint16_t length)
|
||||
{
|
||||
protocol_send_message_v2(cmd, payload, length, NULL, 0);
|
||||
}
|
||||
|
||||
void protocol_send_ack(void) { protocol_send_message(PROTOCOL_RESP_ACK, NULL, 0); }
|
||||
void protocol_send_nak(void) { protocol_send_message(PROTOCOL_RESP_NAK, NULL, 0); }
|
||||
void protocol_send_error(void) { protocol_send_message(PROTOCOL_RESP_ERR, NULL, 0); }
|
||||
|
||||
/**
|
||||
* @brief Validate that a received payload is at least @p expected bytes long.
|
||||
* Sends a NAK on the wire and returns false if the check fails.
|
||||
*/
|
||||
static bool require_payload(uint16_t length, uint16_t expected)
|
||||
{
|
||||
if (length < expected) {
|
||||
protocol_send_nak();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void protocol_version_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
protocol_firmware_version_response_t resp;
|
||||
resp.major = FIRMWARE_VERSION_MAJOR;
|
||||
resp.minor = FIRMWARE_VERSION_MINOR;
|
||||
protocol_send_message(CMD_FIRMWARE_VERSION, &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
void protocol_light_sensor_presence_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
lsense_detect_all();
|
||||
|
||||
protocol_light_sensor_presence_response_t resp;
|
||||
lsense_get_presence(&resp);
|
||||
protocol_send_message(CMD_LIGHT_SENSOR_PRESENCE, &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
void protocol_read_light_sensors_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_read_light_sensors_handler_t))) {
|
||||
return;
|
||||
}
|
||||
protocol_read_light_sensors_handler_t *cmd = (protocol_read_light_sensors_handler_t *)payload;
|
||||
if (cmd->channel > 6) {
|
||||
protocol_send_nak();
|
||||
return;
|
||||
}
|
||||
|
||||
protocol_read_light_sensors_response_t resp;
|
||||
lsense_read_channel(cmd->channel, &resp);
|
||||
protocol_send_message(CMD_READ_LIGHT_SENSORS, &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
void protocol_get_configuration_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
protocol_send_message(CMD_GET_CONFIGURATION, &config, sizeof(config));
|
||||
}
|
||||
|
||||
void protocol_set_configuration_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_set_configuration_handler_t))) {
|
||||
return;
|
||||
}
|
||||
protocol_set_configuration_handler_t *cmd = (protocol_set_configuration_handler_t *)payload;
|
||||
if (cmd->magic1 != 0xA5 || cmd->magic2 != 0x5A) {
|
||||
protocol_send_nak();
|
||||
return;
|
||||
}
|
||||
config = *cmd;
|
||||
eeprom_save_config();
|
||||
rsense_apply_potentiometers();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_set_potentiometers_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_set_potentiometers_handler_t))) {
|
||||
return;
|
||||
}
|
||||
config.pots = *(protocol_set_potentiometers_handler_t *)payload;
|
||||
rsense_apply_potentiometers();
|
||||
eeprom_save_config();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_set_calibration_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_set_calibration_handler_t))) {
|
||||
return;
|
||||
}
|
||||
config.calibration = *(protocol_set_calibration_handler_t *)payload;
|
||||
eeprom_save_config();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_set_flags_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_set_flags_handler_t))) {
|
||||
return;
|
||||
}
|
||||
config.flags = *(protocol_set_flags_handler_t *)payload;
|
||||
eeprom_save_config();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_set_temp_drift_compensation_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_set_temp_drift_compensation_handler_t))) {
|
||||
return;
|
||||
}
|
||||
config.temp_drift_compensation = *(protocol_set_temp_drift_compensation_handler_t *)payload;
|
||||
eeprom_save_config();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_set_drift_compensation_enable_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_set_drift_compensation_enable_handler_t))) {
|
||||
return;
|
||||
}
|
||||
protocol_set_drift_compensation_enable_handler_t *cmd =
|
||||
(protocol_set_drift_compensation_enable_handler_t *)payload;
|
||||
config.flags.fields.temperature_drift_compensation = (cmd->enable != 0) ? 1 : 0;
|
||||
eeprom_save_config();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_get_telemetry_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
|
||||
adc_restore_default();
|
||||
|
||||
protocol_get_telemetry_response_t resp;
|
||||
resp.hv_temp_c = (uint16_t)adc_to_temperature_c(adc_read_oversampled(HV_TEMP_PIN, 16));
|
||||
resp.amp_temp_c = (uint16_t)adc_to_temperature_c(adc_read_oversampled(AMP_TEMP_PIN, 16));
|
||||
resp.sipm_temp_c = (uint16_t)adc_to_temperature_c(adc_read_oversampled(DET_TEMP_PIN, 16));
|
||||
resp.mcu_temp_c = adc_read_tempsense();
|
||||
resp.vbias_mv = adc_to_voltage_mv(adc_read_oversampled(V28V0_FB_PIN, 16));
|
||||
resp.pots = config.pots;
|
||||
resp.cps = rsense_get_cps() / 60;
|
||||
resp.cp10s = rsense_get_cp10s();
|
||||
resp.total_counts = rsense_get_total_counts();
|
||||
resp.flags = (1 << 0) | /* power on ok */
|
||||
((g_rsense_enabled ? 1 : 0) << 1) |
|
||||
((config.flags.fields.temperature_drift_compensation ? 1 : 0) << 2);
|
||||
|
||||
adc_enable_fast();
|
||||
|
||||
protocol_send_message(CMD_GET_TELEMETRY, &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
void protocol_flush_counters_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
rsense_flush_counters();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_flush_spectrum_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
rsense_flush_spectrum();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_rsense_enable_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
rsense_enable();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_rsense_disable_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
rsense_disable();
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_spectrum_freeze_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
/* Disable ADC result-ready interrupt so the ISR stops accumulating. */
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.INTCTRL &= ~ADC_RESRDY_bm; }
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_spectrum_unfreeze_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.INTCTRL |= ADC_RESRDY_bm; }
|
||||
protocol_send_ack();
|
||||
}
|
||||
|
||||
void protocol_get_counts_handler(void *payload, uint16_t length)
|
||||
{
|
||||
(void)payload;
|
||||
(void)length;
|
||||
protocol_get_counts_response_t resp;
|
||||
resp.counts = rsense_get_counts_since_last();
|
||||
protocol_send_message(CMD_GET_COUNTS, &resp, sizeof(resp));
|
||||
}
|
||||
|
||||
void protocol_read_spectrum_chunk_handler(void *payload, uint16_t length)
|
||||
{
|
||||
if (!require_payload(length, sizeof(protocol_read_spectrum_chunk_handler_t))) {
|
||||
return;
|
||||
}
|
||||
protocol_read_spectrum_chunk_handler_t *cmd = (protocol_read_spectrum_chunk_handler_t *)payload;
|
||||
|
||||
if (cmd->offset >= RSENSE_CHANNEL_COUNT * sizeof(uint16_t)) {
|
||||
protocol_send_nak();
|
||||
return;
|
||||
}
|
||||
|
||||
uint16_t available = (RSENSE_CHANNEL_COUNT * sizeof(uint16_t)) - cmd->offset;
|
||||
uint16_t send_len = (cmd->length < available) ? cmd->length : available;
|
||||
|
||||
const uint8_t *spectrum = (const uint8_t *)rsense_get_spectrum_ptr();
|
||||
|
||||
protocol_read_spectrum_chunk_response_t resp_hdr;
|
||||
resp_hdr.offset = cmd->offset;
|
||||
resp_hdr.length = send_len;
|
||||
|
||||
protocol_send_message_v2(CMD_READ_SPECTRUM_DATA, &resp_hdr, sizeof(resp_hdr), spectrum + cmd->offset, send_len);
|
||||
}
|
||||
|
|
@ -1,8 +1,19 @@
|
|||
/*
|
||||
* @file protocol.h
|
||||
* @brief
|
||||
* @brief Serial protocol definitions, command structures and dispatch table.
|
||||
*
|
||||
* Created: 07.05.2026 10:15:36
|
||||
* Frame format: [CMD : 2B][LEN : 2B][PAYLOAD : LEN B][CRC16 : 2B]
|
||||
* - CMD : 16-bit little-endian command identifier.
|
||||
* - LEN : 16-bit little-endian payload length, excluding CRC.
|
||||
* - PAYLOAD : LEN bytes, command-specific.
|
||||
* - CRC16 : CRC16-XMODEM (poly 0x1021, init 0x0000) over CMD+LEN+PAYLOAD.
|
||||
*
|
||||
* Response identifiers:
|
||||
* - 0x414B "AK" : command accepted and executed.
|
||||
* - 0x4E4B "NK" : valid command but cannot execute (bad params, wrong state).
|
||||
* - 0x4552 "ER" : malformed frame (bad CRC, bad length).
|
||||
*
|
||||
* Created: 07.05.2026
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
|
|
@ -15,294 +26,188 @@
|
|||
#define PROTOCOL_H
|
||||
|
||||
#include "config.h"
|
||||
#include "lsense.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
|
||||
*/
|
||||
#define PROTOCOL_RESP_ACK 0x414B /* "AK" */
|
||||
#define PROTOCOL_RESP_NAK 0x4E4B /* "NK" */
|
||||
#define PROTOCOL_RESP_ERR 0x4552 /* "ER" */
|
||||
|
||||
uint16_t protocol_crc16_xmodem(uint8_t *data, uint16_t len);
|
||||
/**
|
||||
* @brief Compute CRC16-XMODEM (poly 0x1021, init 0x0000) over a buffer.
|
||||
* @param data Pointer to input bytes.
|
||||
* @param len Number of bytes to process.
|
||||
* @return Final CRC value.
|
||||
*/
|
||||
uint16_t protocol_crc16_xmodem(const uint8_t *data, uint16_t len);
|
||||
|
||||
/**
|
||||
* @brief Incrementally update a running CRC16-XMODEM with additional bytes.
|
||||
* Pass @c crc=0 for the first call, then chain the result through
|
||||
* further calls. This is the single CRC primitive used by the protocol.
|
||||
* @param crc Current CRC value.
|
||||
* @param data Pointer to input bytes.
|
||||
* @param len Number of bytes to process.
|
||||
* @return Updated CRC value.
|
||||
*/
|
||||
uint16_t protocol_crc16_update(uint16_t crc, const 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);
|
||||
/**
|
||||
* @brief Send a complete framed message: header + payload + CRC.
|
||||
* @param cmd Command identifier.
|
||||
* @param payload Optional payload pointer (may be NULL when @p length is 0).
|
||||
* @param length Payload length in bytes.
|
||||
*/
|
||||
void protocol_send_message(uint16_t cmd, const void *payload, uint16_t length);
|
||||
|
||||
/**
|
||||
* @brief Send a framed message whose payload is split across two fragments.
|
||||
* CRC is computed across header + frag1 + frag2 in order.
|
||||
*/
|
||||
void protocol_send_message_v2(uint16_t cmd, const void *frag1, uint16_t len1, const void *frag2, uint16_t len2);
|
||||
|
||||
/** @brief Send an ACK response (cmd = PROTOCOL_RESP_ACK, no payload). */
|
||||
void protocol_send_ack(void);
|
||||
|
||||
/** @brief Send a NAK response (cmd = PROTOCOL_RESP_NAK, no payload). */
|
||||
void protocol_send_nak(void);
|
||||
|
||||
/** @brief Send an ERR response (cmd = PROTOCOL_RESP_ERR, no payload). */
|
||||
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
|
||||
* X-macro command dispatch table.
|
||||
*
|
||||
* X(EnumName, CmdId, HandlerFn) is the single source of truth for all
|
||||
* supported commands. It is expanded to:
|
||||
* - an enumeration of command IDs (e.g. CMD_FIRMWARE_VERSION),
|
||||
* - prototypes for every handler function,
|
||||
* - the dispatch switch in sbc_fw.ino.
|
||||
* The same X-macro can be re-included on the host/master side to generate
|
||||
* matching send helpers.
|
||||
*/
|
||||
#define CMD_FIRMWARE_VERSION 0x7600
|
||||
#define X_PROTOCOL_handlerS \
|
||||
X(CMD_FIRMWARE_VERSION, 0x7600, protocol_version_handler) \
|
||||
X(CMD_LIGHT_SENSOR_PRESENCE, 0x6C00, protocol_light_sensor_presence_handler) \
|
||||
X(CMD_READ_LIGHT_SENSORS, 0x7200, protocol_read_light_sensors_handler) \
|
||||
X(CMD_GET_CONFIGURATION, 0x6D00, protocol_get_configuration_handler) \
|
||||
X(CMD_SET_CONFIGURATION, 0x7D00, protocol_set_configuration_handler) \
|
||||
X(CMD_SET_POTENTIOMETERS, 0x7D01, protocol_set_potentiometers_handler) \
|
||||
X(CMD_SET_CALIBRATION, 0x7D02, protocol_set_calibration_handler) \
|
||||
X(CMD_SET_FLAGS, 0x7D03, protocol_set_flags_handler) \
|
||||
X(CMD_SET_TEMP_DRIFT_COMPENSATION, 0x7D04, protocol_set_temp_drift_compensation_handler) \
|
||||
X(CMD_SET_DRIFT_COMPENSATION_ENABLE, 0x7D05, protocol_set_drift_compensation_enable_handler) \
|
||||
X(CMD_GET_TELEMETRY, 0x7400, protocol_get_telemetry_handler) \
|
||||
X(CMD_FLUSH_COUNTERS, 0x7401, protocol_flush_counters_handler) \
|
||||
X(CMD_FLUSH_SPECTRUM, 0x7402, protocol_flush_spectrum_handler) \
|
||||
X(CMD_RSENSE_ENABLE, 0x7500, protocol_rsense_enable_handler) \
|
||||
X(CMD_RSENSE_DISABLE, 0x7501, protocol_rsense_disable_handler) \
|
||||
X(CMD_SPECTRUM_FREEZE, 0x7502, protocol_spectrum_freeze_handler) \
|
||||
X(CMD_SPECTRUM_UNFREEZE, 0x7503, protocol_spectrum_unfreeze_handler) \
|
||||
X(CMD_GET_COUNTS, 0x7504, protocol_get_counts_handler) \
|
||||
X(CMD_READ_SPECTRUM_DATA, 0x7505, protocol_read_spectrum_chunk_handler)
|
||||
|
||||
/** @brief Symbolic command IDs generated from #X_PROTOCOL_handlerS. */
|
||||
enum protocol_cmd_id {
|
||||
#define X(Enum, Cmd_id, Handler) Enum = Cmd_id,
|
||||
X_PROTOCOL_handlerS
|
||||
#undef X
|
||||
};
|
||||
|
||||
/* Command handler prototypes generated from X_PROTOCOL_handlerS. */
|
||||
#define X(Enum, Cmd_id, Handler) void Handler(void *payload, uint16_t length);
|
||||
X_PROTOCOL_handlerS
|
||||
#undef X
|
||||
|
||||
/** @brief Response payload for #CMD_FIRMWARE_VERSION. */
|
||||
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);
|
||||
/* Light sensor protocol payloads reuse the lsense module types directly
|
||||
* so handlers can populate them via a single call without field copies. */
|
||||
|
||||
/*
|
||||
* @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)
|
||||
/** @brief Response payload for #CMD_LIGHT_SENSOR_PRESENCE — alias of #lsense_presence_t. */
|
||||
typedef lsense_presence_t protocol_light_sensor_presence_response_t;
|
||||
|
||||
/**
|
||||
* @brief Command payload for #CMD_READ_LIGHT_SENSORS.
|
||||
*
|
||||
* channel: 0=red 1=green 2=blue 3=IR 4=green2
|
||||
* 5=visible (R+G+B+G2) 6=all (R+G+B+G2+IR)
|
||||
*/
|
||||
#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 {
|
||||
typedef struct protocol_read_light_sensors_handler_t {
|
||||
uint8_t channel;
|
||||
} __attribute__((packed)) protocol_read_light_sensors_command_t;
|
||||
} __attribute__((packed)) protocol_read_light_sensors_handler_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;
|
||||
/** @brief Response payload for #CMD_READ_LIGHT_SENSORS — alias of #lsense_channel_values_t. */
|
||||
typedef lsense_channel_values_t protocol_read_light_sensors_response_t;
|
||||
|
||||
void handle_read_light_sensors_command(void *payload, uint16_t length);
|
||||
/* Configuration commands reuse the persisted-config types from config.h
|
||||
* directly. The on-the-wire layout is the corresponding struct, so handlers
|
||||
* can use a single struct assignment in place of field-by-field copies. */
|
||||
|
||||
/*
|
||||
* @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;
|
||||
/** @brief Response payload for #CMD_GET_CONFIGURATION — alias of #config_t. */
|
||||
typedef config_t protocol_get_configuration_response_t;
|
||||
|
||||
void handle_get_configuration_command(void *payload, uint16_t length);
|
||||
/** @brief Command payload for #CMD_SET_CONFIGURATION — alias of #config_t. */
|
||||
typedef config_t protocol_set_configuration_handler_t;
|
||||
|
||||
/*
|
||||
* @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;
|
||||
/** @brief Command payload for #CMD_SET_POTENTIOMETERS — alias of #potentiometers_t. */
|
||||
typedef potentiometers_t protocol_set_potentiometers_handler_t;
|
||||
|
||||
void handle_set_configuration_command(void *payload, uint16_t length);
|
||||
/** @brief Command payload for #CMD_SET_CALIBRATION — alias of #calibration_t. */
|
||||
typedef calibration_t protocol_set_calibration_handler_t;
|
||||
|
||||
/*
|
||||
* @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;
|
||||
/** @brief Command payload for #CMD_SET_FLAGS — alias of #config_flags_t. */
|
||||
typedef config_flags_t protocol_set_flags_handler_t;
|
||||
|
||||
void handle_set_potentiometers_command(void *payload, uint16_t length);
|
||||
/** @brief Command payload for #CMD_SET_TEMP_DRIFT_COMPENSATION — alias of #temp_drift_compensation_t. */
|
||||
typedef temp_drift_compensation_t protocol_set_temp_drift_compensation_handler_t;
|
||||
|
||||
/*
|
||||
* @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;
|
||||
/** @brief Command payload for #CMD_SET_DRIFT_COMPENSATION_ENABLE. */
|
||||
typedef struct protocol_set_drift_compensation_enable_handler_t {
|
||||
uint8_t enable; /* 1 = enable, 0 = disable */
|
||||
} __attribute__((packed)) protocol_set_drift_compensation_enable_handler_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
|
||||
/** @brief Response payload for #CMD_GET_TELEMETRY. */
|
||||
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 hv_temp_c; /**< 0.1 °C — HV_TEMP_PIN, +28V supply sensor. */
|
||||
uint16_t amp_temp_c; /**< 0.1 °C — AMP_TEMP_PIN, amplifier sensor. */
|
||||
uint16_t sipm_temp_c; /**< 0.1 °C — DET_TEMP_PIN, SiPM carrier sensor. */
|
||||
uint16_t mcu_temp_c; /**< Kelvin — MCU internal temperature sensor. */
|
||||
uint16_t vbias_mv;
|
||||
uint8_t hv_pot;
|
||||
uint8_t amp_pot;
|
||||
uint8_t det_pot;
|
||||
uint16_t cps;
|
||||
uint32_t cp10s;
|
||||
potentiometers_t pots;
|
||||
uint16_t cps; /**< counts per second = CPM / 60. */
|
||||
uint32_t cp10s; /**< counts in the last 10 s window. */
|
||||
uint32_t total_counts;
|
||||
uint8_t flags; /**< bit 0: power on ok, bit 1: sensing enabled, bit 2: drift compensation enabled, bit 3: reserved. */
|
||||
} __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
|
||||
/** @brief Response payload for #CMD_GET_COUNTS. */
|
||||
typedef struct protocol_get_counts_response_t {
|
||||
uint32_t counts;
|
||||
uint32_t counts;
|
||||
} __attribute__((packed)) protocol_get_counts_response_t;
|
||||
|
||||
void handle_get_counts_command(void *payload, uint16_t length);
|
||||
/** @brief Command payload for #CMD_READ_SPECTRUM_DATA. */
|
||||
typedef struct protocol_read_spectrum_chunk_handler_t {
|
||||
uint16_t offset; /* byte offset into spectrum data */
|
||||
uint16_t length; /* number of bytes to read */
|
||||
} __attribute__((packed)) protocol_read_spectrum_chunk_handler_t;
|
||||
|
||||
/*
|
||||
* @brief Read spectrum data (arbitrary chunking for large data)
|
||||
/**
|
||||
* @brief Response header for #CMD_READ_SPECTRUM_DATA.
|
||||
* Followed on the wire by @c length bytes of raw spectrum 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
|
||||
uint16_t offset;
|
||||
uint16_t length;
|
||||
} __attribute__((packed)) protocol_read_spectrum_chunk_response_t;
|
||||
void handle_read_spectrum_chunk_command(void *payload, uint16_t length);
|
||||
|
||||
#endif /* PROTOCOL_H */
|
||||
|
|
|
|||
|
|
@ -18,49 +18,55 @@
|
|||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include "adc.h"
|
||||
#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 volatile uint32_t g_counts_cps = 0; /* counts per second accumulator, reset by periodic tick */
|
||||
static uint32_t g_cps_time = 0;
|
||||
static uint16_t g_cps_last = 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 volatile uint32_t g_counts_delta = 0; /* counts since last CMD_GET_COUNTS */
|
||||
|
||||
static uint32_t g_cpm_time = 0;
|
||||
static uint16_t g_cpm_last = 0;
|
||||
static struct {
|
||||
uint8_t index;
|
||||
uint16_t cps[10]; /* Circular buffer of the last 10 CPS values, updated by periodic tick */
|
||||
} g_cp10s_data;
|
||||
|
||||
static potentiometer_settings_t g_current_pot_settings;
|
||||
/*
|
||||
* Drift compensation state.
|
||||
*
|
||||
* Three independent reference temperatures:
|
||||
* det_ref — DET_TEMP (SiPM carrier), drives pot_hv via temp_drift.
|
||||
* hv_ref — HV_TEMP (+28V supply), drives pot_hv via vbias_drift.
|
||||
* amp_ref — AMP_TEMP (amplifier), drives pot_amp and pot_det.
|
||||
*
|
||||
* pot_hv adjustment = temp_drift*delta_det + vbias_drift*delta_hv (summed).
|
||||
*/
|
||||
static uint32_t g_drift_last_ms = 0;
|
||||
static int16_t g_drift_ref_det = 0; /* DET_TEMP reference in 0.1 °C */
|
||||
static int16_t g_drift_ref_hv = 0; /* HV_TEMP reference in 0.1 °C */
|
||||
static int16_t g_drift_ref_amp = 0; /* AMP_TEMP reference in 0.1 °C */
|
||||
static bool g_drift_ref_valid = false;
|
||||
bool g_rsense_enabled = false; /* true when HV/AMP are powered on */
|
||||
|
||||
static void apply_potentiometer_settings(const potentiometer_settings_t *settings);
|
||||
static void apply_potentiometer_settings(void);
|
||||
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);
|
||||
static inline void analog_read_interrupt(void);
|
||||
static void rsense_drift_compensation_tick(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);
|
||||
|
||||
|
|
@ -72,342 +78,263 @@ void rsense_init(void)
|
|||
digitalWrite(DET_CS, HIGH);
|
||||
digitalWrite(AMP_CS, HIGH);
|
||||
|
||||
apply_potentiometer_settings(&g_current_pot_settings);
|
||||
apply_potentiometer_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)
|
||||
static inline void analog_read_interrupt(void)
|
||||
{
|
||||
digitalWriteFast(DET_RST, HIGH);
|
||||
uint16_t channel = ADC0.RES; // read result to clear flag
|
||||
uint16_t channel = ADC0.RES;
|
||||
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]++;
|
||||
}
|
||||
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
|
||||
g_detector_counts.channels_16[channel & RSENSE_CHANNEL_MASK]++;
|
||||
|
||||
g_total_counts++;
|
||||
g_counts_cpm++;
|
||||
g_counts_cps++;
|
||||
g_counts_delta++;
|
||||
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();
|
||||
}
|
||||
}
|
||||
ISR(ADC0_RESRDY_vect) { analog_read_interrupt(); }
|
||||
|
||||
/**
|
||||
* @brief Clear channel data
|
||||
* @brief Clamp a signed value into the unsigned interval [lo, hi].
|
||||
*/
|
||||
static void clear_channel_data(void)
|
||||
static uint8_t clamp_u8(int16_t v, uint8_t lo, uint8_t hi)
|
||||
{
|
||||
for (int i = 0; i < RSENSE_CHANNEL_COUNT; i++) {
|
||||
g_detector_counts.channels_16[i] = 0;
|
||||
}
|
||||
if (v < (int16_t)lo)
|
||||
return lo;
|
||||
if (v > (int16_t)hi)
|
||||
return hi;
|
||||
return (uint8_t)v;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply potentiometer settings to hardware
|
||||
* @param settings Pointer to potentiometer settings
|
||||
*/
|
||||
static void apply_potentiometer_settings(const potentiometer_settings_t *settings)
|
||||
static void rsense_drift_compensation_tick(void)
|
||||
{
|
||||
if (settings == nullptr)
|
||||
if (!config.flags.fields.temperature_drift_compensation || !g_rsense_enabled)
|
||||
return;
|
||||
|
||||
// HV potentiometer setup
|
||||
digitalWrite(HV_CS, LOW);
|
||||
SPI.transfer(settings->hv_pot);
|
||||
digitalWrite(HV_CS, HIGH);
|
||||
delay(1);
|
||||
uint32_t period = config.temp_drift_compensation.drift_period_ms;
|
||||
if (period == 0)
|
||||
period =
|
||||
300000; /* Default to 5 minutes if not set, to avoid excessive compensation when misconfigured. */
|
||||
|
||||
// SiPM Pre-amp gain potentiometer setup
|
||||
digitalWrite(AMP_CS, LOW);
|
||||
SPI.transfer(settings->amp_pot);
|
||||
digitalWrite(AMP_CS, HIGH);
|
||||
delay(1);
|
||||
if (millis() - g_drift_last_ms < period)
|
||||
return;
|
||||
|
||||
// Detection threshold potentiometer setup
|
||||
digitalWrite(DET_CS, LOW);
|
||||
SPI.transfer(settings->det_pot);
|
||||
digitalWrite(DET_CS, HIGH);
|
||||
delay(1);
|
||||
}
|
||||
/* Read all three temperature sensors (each ~10 ms due to oversampling) */
|
||||
int16_t det_temp = adc_to_temperature_c(adc_read_oversampled(DET_TEMP_PIN, 16));
|
||||
int16_t hv_temp = adc_to_temperature_c(adc_read_oversampled(HV_TEMP_PIN, 16));
|
||||
int16_t amp_temp = adc_to_temperature_c(adc_read_oversampled(AMP_TEMP_PIN, 16));
|
||||
|
||||
/**
|
||||
* @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;
|
||||
if (!g_drift_ref_valid) {
|
||||
g_drift_ref_det = det_temp;
|
||||
g_drift_ref_hv = hv_temp;
|
||||
g_drift_ref_amp = amp_temp;
|
||||
g_drift_ref_valid = true;
|
||||
g_drift_last_ms = millis();
|
||||
return;
|
||||
}
|
||||
|
||||
// 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);
|
||||
/* Deltas in 0.1°C units */
|
||||
int16_t delta_det = det_temp - g_drift_ref_det;
|
||||
int16_t delta_hv = hv_temp - g_drift_ref_hv;
|
||||
int16_t delta_amp = amp_temp - g_drift_ref_amp;
|
||||
|
||||
// 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);
|
||||
/* Drift coefficients in pot_units per °C, scaled by 256.
|
||||
* adj = coeff * delta_0.1C / 10 / 256 = coeff * delta_0.1C / 2560 */
|
||||
bool needs_update = false;
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
delay(1);
|
||||
int16_t new_hv = config.pots.pot_hv;
|
||||
int16_t new_amp = config.pots.pot_amp;
|
||||
int16_t new_det = config.pots.pot_det;
|
||||
|
||||
const temp_drift_compensation_t *dc = &config.temp_drift_compensation;
|
||||
|
||||
if (dc->pot_hv_low != dc->pot_hv_high) {
|
||||
/* pot_hv: SiPM carrier (DET_TEMP) + 28V supply (HV_TEMP) contributions. */
|
||||
int16_t adj =
|
||||
(int16_t)(((int32_t)dc->temp_drift * delta_det + (int32_t)dc->vbias_drift * delta_hv) / 2560L);
|
||||
if (adj != 0) {
|
||||
new_hv = (int16_t)config.pots.pot_hv + adj;
|
||||
needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
if (dc->pot_amp_low != dc->pot_amp_high) {
|
||||
int16_t adj = (int16_t)(((int32_t)dc->gain_drift * delta_amp) / 2560L);
|
||||
if (adj != 0) {
|
||||
new_amp = (int16_t)config.pots.pot_amp + adj;
|
||||
needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
// 7 bytes reserved for future use
|
||||
if (dc->pot_det_low != dc->pot_det_high) {
|
||||
int16_t adj = (int16_t)(((int32_t)dc->threshold_drift * delta_amp) / 2560L);
|
||||
if (adj != 0) {
|
||||
new_det = (int16_t)config.pots.pot_det + adj;
|
||||
needs_update = true;
|
||||
}
|
||||
}
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
if (!needs_update) {
|
||||
g_drift_last_ms = millis();
|
||||
return;
|
||||
}
|
||||
|
||||
config.pots.pot_hv = clamp_u8(new_hv, dc->pot_hv_low, dc->pot_hv_high);
|
||||
config.pots.pot_amp = clamp_u8(new_amp, dc->pot_amp_low, dc->pot_amp_high);
|
||||
config.pots.pot_det = clamp_u8(new_det, dc->pot_det_low, dc->pot_det_high);
|
||||
|
||||
rsense_apply_potentiometers();
|
||||
|
||||
g_drift_ref_det = det_temp;
|
||||
g_drift_ref_hv = hv_temp;
|
||||
g_drift_ref_amp = amp_temp;
|
||||
g_drift_last_ms = millis();
|
||||
}
|
||||
|
||||
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();
|
||||
if (millis() - g_cps_time > RSENSE_UPDATE_PERIOD) {
|
||||
uint32_t elapsed_time = millis() - g_cps_time;
|
||||
g_cps_last = (uint16_t)(((uint32_t)g_counts_cps * 1000UL) / elapsed_time);
|
||||
g_counts_cps = 0;
|
||||
g_cps_time = millis();
|
||||
|
||||
/* Update 10-second CPS circular buffer for moving average */
|
||||
g_cp10s_data.cps[g_cp10s_data.index] = g_cps_last;
|
||||
g_cp10s_data.index = (g_cp10s_data.index + 1) % 10;
|
||||
|
||||
rsense_drift_compensation_tick();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ADC conversion complete interrupt handler
|
||||
* @brief Clock @c value out to one AD5160 selected by @p cs_pin.
|
||||
*/
|
||||
ISR(ADC0_RESRDY_vect) { handle_analog_read_interrupt(); }
|
||||
static inline void write_pot(uint8_t cs_pin, uint8_t value)
|
||||
{
|
||||
digitalWrite(cs_pin, LOW);
|
||||
SPI.transfer(value);
|
||||
digitalWrite(cs_pin, HIGH);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Push the current @c config.pots values to the AD5160 digital
|
||||
* potentiometers over SPI. Does not touch power-enable lines;
|
||||
* see #rsense_apply_potentiometers for the safe re-sequencing version.
|
||||
*/
|
||||
static void apply_potentiometer_settings(void)
|
||||
{
|
||||
write_pot(HV_CS, config.pots.pot_hv);
|
||||
write_pot(AMP_CS, config.pots.pot_amp);
|
||||
write_pot(DET_CS, config.pots.pot_det);
|
||||
}
|
||||
|
||||
void rsense_apply_potentiometers(void)
|
||||
{
|
||||
if (!g_rsense_enabled) {
|
||||
/* System is off — just write SPI registers without power cycling */
|
||||
apply_potentiometer_settings();
|
||||
return;
|
||||
}
|
||||
|
||||
/* Power sequence: freeze → disable amp → disable HV → set pots → enable HV → enable amp → unfreeze */
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.INTCTRL &= ~ADC_RESRDY_bm; }
|
||||
digitalWrite(DET_EN, LOW);
|
||||
digitalWrite(HV_EN, LOW);
|
||||
delay(10);
|
||||
apply_potentiometer_settings();
|
||||
delay(10);
|
||||
digitalWrite(HV_EN, HIGH);
|
||||
delay(100);
|
||||
digitalWrite(DET_EN, HIGH);
|
||||
delay(100);
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ADC0.INTCTRL |= ADC_RESRDY_bm; }
|
||||
}
|
||||
|
||||
void rsense_enable(void)
|
||||
{
|
||||
g_rsense_enabled = true;
|
||||
digitalWrite(HV_EN, HIGH);
|
||||
digitalWrite(DET_EN, HIGH);
|
||||
adc_enable_fast();
|
||||
}
|
||||
|
||||
void rsense_disable(void)
|
||||
{
|
||||
g_rsense_enabled = false;
|
||||
digitalWrite(DET_EN, LOW);
|
||||
digitalWrite(HV_EN, LOW);
|
||||
adc_restore_default();
|
||||
}
|
||||
|
||||
void rsense_flush_counters(void)
|
||||
{
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
g_total_counts = 0;
|
||||
g_counts_cps = 0;
|
||||
g_counts_delta = 0;
|
||||
g_cps_time = millis();
|
||||
}
|
||||
}
|
||||
|
||||
void rsense_flush_spectrum(void)
|
||||
{
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
for (int i = 0; i < RSENSE_CHANNEL_COUNT; i++) {
|
||||
g_detector_counts.channels_16[i] = 0;
|
||||
}
|
||||
}
|
||||
rsense_flush_counters();
|
||||
}
|
||||
|
||||
uint32_t rsense_get_total_counts(void)
|
||||
{
|
||||
uint32_t v;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { v = g_total_counts; }
|
||||
return v;
|
||||
}
|
||||
|
||||
uint16_t rsense_get_cps(void) { return g_cps_last; }
|
||||
|
||||
uint32_t rsense_get_cp10s(void)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
for (int i = 0; i < 10; i++) {
|
||||
sum += g_cp10s_data.cps[i];
|
||||
}
|
||||
}
|
||||
return sum / 10;
|
||||
}
|
||||
|
||||
uint32_t rsense_get_counts_since_last(void)
|
||||
{
|
||||
uint32_t v;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
v = g_counts_delta;
|
||||
g_counts_delta = 0;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const volatile void *rsense_get_spectrum_ptr(void) { return (const volatile void *)&g_detector_counts; }
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
/*
|
||||
* @file rsense.h
|
||||
* @brief Radiation sensor management and command handling
|
||||
* @brief Radiation sensor subsystem: counters, spectrum, drift compensation,
|
||||
* and HV/detector power control.
|
||||
*
|
||||
* Created: 21.09.2025 06:01:56
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
|
|
@ -14,58 +15,59 @@
|
|||
#ifndef RSENSE_H
|
||||
#define RSENSE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize radiation sensor subsystem
|
||||
* @brief Configure GPIO, SPI digital potentiometers, and the event system
|
||||
* used by the detector trigger pin to start ADC conversions.
|
||||
*/
|
||||
void rsense_init(void);
|
||||
|
||||
/**
|
||||
* @brief Send telemetry data via serial
|
||||
*/
|
||||
void rsense_cmd_telemetry(void);
|
||||
|
||||
/**
|
||||
* @brief Dump channel data via serial
|
||||
*/
|
||||
void rsense_cmd_dump_channels(void);
|
||||
|
||||
/**
|
||||
* @brief Flush detector counters
|
||||
*/
|
||||
void rsense_cmd_flush(void);
|
||||
|
||||
/**
|
||||
* @brief Enable radiation detection
|
||||
*/
|
||||
void rsense_cmd_enable(void);
|
||||
|
||||
/**
|
||||
* @brief Disable radiation detection
|
||||
*/
|
||||
void rsense_cmd_disable(void);
|
||||
|
||||
/**
|
||||
* @brief Set potentiometer values
|
||||
*/
|
||||
void rsense_cmd_set_potentiometers(void);
|
||||
|
||||
/**
|
||||
* @brief Send counts per minute data
|
||||
*/
|
||||
void rsense_cmd_get_cpm(void);
|
||||
|
||||
/**
|
||||
* @brief Set spectrum mode (16-bit or 32-bit)
|
||||
*/
|
||||
void rsense_cmd_set_configuration(void);
|
||||
|
||||
/**
|
||||
* @brief Periodic tasks for radiation sensor
|
||||
* Updates CPM calculation every 10 seconds
|
||||
* Call from main loop
|
||||
* @brief Periodic housekeeping: CPM accumulation window and drift
|
||||
* compensation tick. Should be called from the main loop.
|
||||
*/
|
||||
void rsense_periodic(void);
|
||||
|
||||
#endif // RSENSE_H
|
||||
/** @brief Reset all CPM/CP10S/total/delta count accumulators. */
|
||||
void rsense_flush_counters(void);
|
||||
|
||||
/** @brief Zero the spectrum histogram and reset all counters. */
|
||||
void rsense_flush_spectrum(void);
|
||||
|
||||
/** @brief Total counts since the last #rsense_flush_counters call. */
|
||||
uint32_t rsense_get_total_counts(void);
|
||||
|
||||
/** @brief Last computed counts-per-minute value. */
|
||||
uint16_t rsense_get_cps(void);
|
||||
|
||||
/**
|
||||
* @brief Returns the count delta since the previous call and resets it.
|
||||
* Used to service CMD_GET_COUNTS without losing pulses.
|
||||
*/
|
||||
uint32_t rsense_get_counts_since_last(void);
|
||||
|
||||
/** @brief Counts in the current 10 s window. */
|
||||
uint32_t rsense_get_cp10s(void);
|
||||
|
||||
/**
|
||||
* @brief Apply @c config.pots to the AD5160s.
|
||||
* When the radiation sensor is powered, performs a freeze →
|
||||
* disable → set → enable → unfreeze sequence to avoid spurious
|
||||
* counts during the wiper update.
|
||||
*/
|
||||
void rsense_apply_potentiometers(void);
|
||||
|
||||
/** @brief Current spectrum binning mode (0 = 16-bit channels, 1 = 32-bit). */
|
||||
uint8_t rsense_get_spectrum_mode(void);
|
||||
|
||||
/** @brief Pointer to the spectrum histogram, for read-only chunked transfer. */
|
||||
const volatile void *rsense_get_spectrum_ptr(void);
|
||||
|
||||
/** @brief Power on the high-voltage and detector rails. */
|
||||
void rsense_enable(void);
|
||||
|
||||
/** @brief Power off the high-voltage and detector rails. */
|
||||
void rsense_disable(void);
|
||||
|
||||
#endif /* RSENSE_H */
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* @file sbc_fw.ino
|
||||
* @brief Main firmware for sensor board controller, YKSA PL/EDU16 RSENSE
|
||||
* @brief Main firmware for sensor board controller, YKSA PL/EDU16 RSENSE.
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
|
|
@ -15,15 +15,126 @@
|
|||
#include <SPI.h>
|
||||
#include <SoftwareI2C.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "eeprom.h"
|
||||
#include "iodefs.h"
|
||||
#include "lsense.h"
|
||||
#include "protocol.h"
|
||||
#include "rsense.h"
|
||||
|
||||
/*
|
||||
* Frame format: [CMD 2B][LEN 2B][PAYLOAD LEN bytes][CRC16 2B].
|
||||
* Maximum payload size chosen to fit the largest command (config_t ~22 bytes);
|
||||
* 256 bytes leaves headroom for future commands.
|
||||
*/
|
||||
#define PROTOCOL_MAX_PAYLOAD 256U
|
||||
#define PROTOCOL_HEADER_SIZE ((uint16_t)sizeof(protocol_header_t))
|
||||
#define PROTOCOL_CRC_SIZE 2U
|
||||
#define PROTOCOL_BUF_SIZE (PROTOCOL_HEADER_SIZE + PROTOCOL_MAX_PAYLOAD + PROTOCOL_CRC_SIZE)
|
||||
|
||||
static uint8_t rx_buf[PROTOCOL_BUF_SIZE];
|
||||
static uint16_t rx_pos = 0;
|
||||
|
||||
/**
|
||||
* @brief Dispatch a fully-validated frame to its registered handler.
|
||||
* Generated from the X-macro table in protocol.h.
|
||||
*/
|
||||
static void dispatch_handler(uint16_t cmd, void *payload, uint16_t length)
|
||||
{
|
||||
switch (cmd) {
|
||||
#define X(Enum, Cmd_id, Handler) \
|
||||
case Cmd_id: \
|
||||
Handler(payload, length); \
|
||||
break;
|
||||
X_PROTOCOL_handlerS
|
||||
#undef X
|
||||
default : protocol_send_error();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Drain the serial RX buffer and assemble/dispatch any complete frames.
|
||||
* On bad CRC, oversized length, or buffer overflow, an ERR is emitted
|
||||
* and the receive state is reset.
|
||||
*/
|
||||
static void protocol_receive(void)
|
||||
{
|
||||
while (Serial.available()) {
|
||||
uint8_t byte = (uint8_t)Serial.read();
|
||||
|
||||
if (rx_pos < PROTOCOL_BUF_SIZE) {
|
||||
rx_buf[rx_pos++] = byte;
|
||||
} else {
|
||||
/* Buffer overflow — discard and reset. */
|
||||
protocol_send_error();
|
||||
rx_pos = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Need a full header before total frame length is known. */
|
||||
if (rx_pos < PROTOCOL_HEADER_SIZE) {
|
||||
continue;
|
||||
}
|
||||
|
||||
protocol_header_t hdr;
|
||||
memcpy(&hdr, rx_buf, sizeof(hdr));
|
||||
|
||||
if (hdr.length > PROTOCOL_MAX_PAYLOAD) {
|
||||
protocol_send_error();
|
||||
rx_pos = 0;
|
||||
continue;
|
||||
}
|
||||
|
||||
uint16_t total = PROTOCOL_HEADER_SIZE + hdr.length + PROTOCOL_CRC_SIZE;
|
||||
if (rx_pos < total) {
|
||||
continue; /* not enough bytes yet */
|
||||
}
|
||||
|
||||
uint16_t rx_crc = (uint16_t)rx_buf[total - 2] | ((uint16_t)rx_buf[total - 1] << 8);
|
||||
uint16_t calc_crc = protocol_crc16_xmodem(rx_buf, total - PROTOCOL_CRC_SIZE);
|
||||
|
||||
if (calc_crc != rx_crc) {
|
||||
protocol_send_error();
|
||||
} else {
|
||||
dispatch_handler(hdr.cmd, &rx_buf[PROTOCOL_HEADER_SIZE], hdr.length);
|
||||
}
|
||||
|
||||
/* Shift any back-to-back trailing bytes to the front of the buffer. */
|
||||
uint16_t extra = rx_pos - total;
|
||||
if (extra > 0) {
|
||||
memmove(rx_buf, rx_buf + total, extra);
|
||||
}
|
||||
rx_pos = extra;
|
||||
}
|
||||
}
|
||||
|
||||
static uint32_t status_led_last_blink = 0;
|
||||
|
||||
/**
|
||||
* @brief Toggle the status LED at #STATUS_LED_BLINK_PERIOD ms intervals.
|
||||
*/
|
||||
static inline void update_status_led(void)
|
||||
{
|
||||
if (millis() - status_led_last_blink > STATUS_LED_BLINK_PERIOD) {
|
||||
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
|
||||
status_led_last_blink = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Bring up the SPI bus shared by the AD5160 digital potentiometers.
|
||||
*/
|
||||
static void initialize_spi(void)
|
||||
{
|
||||
SPI.begin();
|
||||
SPI.setClockDivider(SPI_CLOCK_DIV128); /* 125 kHz @ 16 MHz */
|
||||
SPI.setDataMode(SPI_MODE0); /* AD5160 (Rev C.) */
|
||||
SPI.setBitOrder(MSBFIRST); /* AD5160 (Rev C.), p. 13, fig. 37 */
|
||||
}
|
||||
|
||||
void setup()
|
||||
{
|
||||
pinMode(STATUS_LED, OUTPUT);
|
||||
|
|
@ -38,102 +149,9 @@ void setup()
|
|||
rsense_init();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize SPI interface for potentiometer control
|
||||
*/
|
||||
void initialize_spi(void)
|
||||
{
|
||||
SPI.begin();
|
||||
SPI.setClockDivider(SPI_CLOCK_DIV128); // 125 kHz @ 16 MHz
|
||||
SPI.setDataMode(SPI_MODE0); // AD5160 (Rev C.)
|
||||
SPI.setBitOrder(MSBFIRST); // AD5160 (Rev C.), p. 13, fig. 37
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle incoming serial commands
|
||||
* @param command Command character received
|
||||
*/
|
||||
void handle_serial_command(char command)
|
||||
{
|
||||
switch (command) {
|
||||
case 'v': // Firmware version
|
||||
handle_version_command();
|
||||
break;
|
||||
case 'd': // Detect/ping
|
||||
handle_detect_command();
|
||||
break;
|
||||
case 'l': // Light sensor presence
|
||||
lsense_cmd_presence();
|
||||
break;
|
||||
case 'r': // Read sensors
|
||||
lsense_cmd_read();
|
||||
rsense_cmd_get_cpm();
|
||||
break;
|
||||
case 'c': // Dump 128 bytes of channel data
|
||||
rsense_cmd_dump_channels();
|
||||
break;
|
||||
case 'f': // Flush counters
|
||||
rsense_cmd_flush();
|
||||
break;
|
||||
case 'e': // Enable radiation detection
|
||||
rsense_cmd_enable();
|
||||
break;
|
||||
case 's': // Disable radiation detection
|
||||
rsense_cmd_disable();
|
||||
break;
|
||||
case 'p': // Set potentiometers
|
||||
rsense_cmd_set_potentiometers();
|
||||
break;
|
||||
case 't': // Telemetry
|
||||
rsense_cmd_telemetry();
|
||||
break;
|
||||
case 'm': // Set spectrum mode (16-bit or 32-bit)
|
||||
rsense_cmd_set_configuration();
|
||||
break;
|
||||
default:
|
||||
// Unknown command - ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle firmware version command
|
||||
*/
|
||||
void handle_version_command(void)
|
||||
{
|
||||
Serial.write(FIRMWARE_VERSION_MAJOR);
|
||||
Serial.write(FIRMWARE_VERSION_MINOR);
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle detect/ping command
|
||||
*/
|
||||
void handle_detect_command(void) { SERIAL_SEND_OK(); }
|
||||
|
||||
/**
|
||||
* @brief Update status LED (blink)
|
||||
*/
|
||||
inline void update_status_led(void)
|
||||
{
|
||||
if (millis() - status_led_last_blink > STATUS_LED_BLINK_PERIOD) {
|
||||
digitalWrite(STATUS_LED, !digitalRead(STATUS_LED));
|
||||
status_led_last_blink = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main program loop
|
||||
*/
|
||||
void loop()
|
||||
{
|
||||
// Process incoming serial commands
|
||||
while (Serial.available()) {
|
||||
char command = Serial.read();
|
||||
handle_serial_command(command);
|
||||
}
|
||||
protocol_receive();
|
||||
update_status_led();
|
||||
|
||||
rsense_periodic();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,35 +0,0 @@
|
|||
/*
|
||||
* @file utils.cpp
|
||||
* @brief Utility functions 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 "utils.h"
|
||||
#include "config.h"
|
||||
#include <Arduino.h>
|
||||
|
||||
uint16_t calculate_crc16_xmodem(uint8_t *data, uint16_t len)
|
||||
{
|
||||
uint16_t crc = 0;
|
||||
uint8_t i;
|
||||
|
||||
while (len--) {
|
||||
crc ^= (uint16_t)(*data++) << 8;
|
||||
for (i = 0; i < 8; i++) {
|
||||
if (crc & 0x8000) {
|
||||
crc = (crc << 1) ^ 0x1021;
|
||||
} else {
|
||||
crc <<= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return crc;
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
/*
|
||||
* @file utils.h
|
||||
* @brief Utility functions and helpers
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef UTILS_H
|
||||
#define UTILS_H
|
||||
|
||||
#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
|
||||
* @param len Length of data in bytes
|
||||
* @return CRC16 checksum
|
||||
*/
|
||||
uint16_t calculate_crc16_xmodem(uint8_t *data, uint16_t len);
|
||||
|
||||
#endif // UTILS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue