Initial commit
This commit is contained in:
commit
db8f8408af
12 changed files with 1354 additions and 0 deletions
57
.gitignore
vendored
Normal file
57
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
# Ignore build artifacts from Arduino IDE, Arduino CLI and PlatformIO
|
||||
build/
|
||||
tmp/
|
||||
output/
|
||||
.pio/
|
||||
.pioenvs/
|
||||
.platformio/
|
||||
*.hex
|
||||
*.elf
|
||||
*.bin
|
||||
*.eep
|
||||
*.map
|
||||
*.o
|
||||
*.obj
|
||||
*.a
|
||||
*.d
|
||||
*.lst
|
||||
*.sym
|
||||
|
||||
# Project-specific generated files
|
||||
*.log
|
||||
*.tmp
|
||||
|
||||
# IDE/editor configs
|
||||
.vscode/
|
||||
.idea/
|
||||
*.suo
|
||||
*.user
|
||||
*.userprefs
|
||||
*.sln
|
||||
*.proj
|
||||
*.project
|
||||
*.classpath
|
||||
.settings/
|
||||
.vscode/*
|
||||
.idea/*
|
||||
|
||||
# OS files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Arduino cache and global state
|
||||
.arduino15/
|
||||
.arduino/
|
||||
.arduino-builder/
|
||||
|
||||
# Optional: ignore compiled library objects but keep source (adjust if storing built libs)
|
||||
**/lib/**/*.o
|
||||
**/libraries/**/*.o
|
||||
|
||||
# Keep your libraries and sketches under version control
|
||||
!libraries/
|
||||
!**/*.ino
|
||||
!**/*.pde
|
||||
|
||||
# tooling
|
||||
*.exe
|
||||
73
config.h
Normal file
73
config.h
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* @file config.h
|
||||
* @brief System configuration and constants
|
||||
*
|
||||
* 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 CONFIG_H
|
||||
#define CONFIG_H
|
||||
|
||||
// Firmware version
|
||||
#define FIRMWARE_VERSION_MAJOR 2
|
||||
#define FIRMWARE_VERSION_MINOR 03
|
||||
|
||||
// Serial communication
|
||||
#define SERIAL_BAUD_RATE 38400
|
||||
#define SERIAL_BUFFER_CLEAR() \
|
||||
do { \
|
||||
while (Serial.available()) { \
|
||||
Serial.read(); \
|
||||
} \
|
||||
} while (0)
|
||||
#define SERIAL_SEND_OK() \
|
||||
do { \
|
||||
Serial.print("ok\n"); \
|
||||
Serial.flush(); \
|
||||
SERIAL_BUFFER_CLEAR(); \
|
||||
} while (0)
|
||||
|
||||
// Timing constants
|
||||
#define STATUS_LED_BLINK_PERIOD 500U
|
||||
#define MAIN_LOOP_DELAY 10
|
||||
#define CPM_UPDATE_PERIOD 10000U
|
||||
|
||||
// ADC and detector settings
|
||||
#define ADC_OVERSAMPLING_BITS 2 // 16x oversampling -> 2 extra bits
|
||||
#define ADC_CHANNEL_16_MASK 0x7FFU // 11-bit channel mask
|
||||
#define ADC_CHANNEL_32_MASK 0x3FFU // 10-bit channel mask
|
||||
#define DETECTOR_CHANNEL_COUNT 2048
|
||||
#define DETECTOR_CHANNEL_32_COUNT 1024
|
||||
|
||||
// Temperature sensor constants (MCP9700)
|
||||
#define TEMP_SENSOR_OFFSET_MV 500L // 500 mV at 0°C
|
||||
#define TEMP_SENSOR_SCALE_MV 10L // 10 mV per degree
|
||||
#define ADC_VREF_MV 3000L // 3.0V reference
|
||||
#define ADC_RESOLUTION 4096L // 12-bit effective resolution
|
||||
|
||||
// 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
|
||||
#define DEFAULT_POT_VALUE 127
|
||||
|
||||
// Light sensor constants
|
||||
#define LSENSE_I2C_BASE_ADDR 0x38
|
||||
#define LSENSE_EXPECTED_ID 0xE0
|
||||
#define LSENSE_DATA_SIZE 12
|
||||
|
||||
#endif // CONFIG_H
|
||||
50
eeprom.cpp
Normal file
50
eeprom.cpp
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* @file eeprom.cpp
|
||||
* @brief EEPROM management 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 "eeprom.h"
|
||||
#include "config.h"
|
||||
#include <Arduino.h>
|
||||
#include <EEPROM.h>
|
||||
|
||||
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};
|
||||
|
||||
eeprom_save_pot_settings(&default_settings);
|
||||
EEPROM.write(EEPROM_MAGIC_ADDR, EEPROM_MAGIC_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
void eeprom_load_pot_settings(potentiometer_settings_t *settings)
|
||||
{
|
||||
if (settings == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
46
eeprom.h
Normal file
46
eeprom.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* @file eeprom.h
|
||||
* @brief EEPROM management for persistent settings
|
||||
*
|
||||
* 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 DET_EEPROM_H
|
||||
#define DET_EEPROM_H
|
||||
|
||||
#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
|
||||
*/
|
||||
void eeprom_init(void);
|
||||
|
||||
/**
|
||||
* @brief Load potentiometer settings from EEPROM
|
||||
* @param settings Pointer to settings structure to populate
|
||||
*/
|
||||
void eeprom_load_pot_settings(potentiometer_settings_t *settings);
|
||||
|
||||
/**
|
||||
* @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
|
||||
49
iodefs.h
Normal file
49
iodefs.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* @file iodefs.h
|
||||
* @brief
|
||||
*
|
||||
* Created: 21.09.2025 05:39:48
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef LSENSE_IODEFS_H
|
||||
#define LSENSE_IODEFS_H
|
||||
|
||||
#include <avr/interrupt.h>
|
||||
#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)
|
||||
|
||||
#define STATUS_LED 31 // PE1
|
||||
|
||||
#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)
|
||||
|
||||
#define HV_CS 15 // PC1
|
||||
#define AMP_CS 17 // PC3
|
||||
#define DET_CS 19 // PC5
|
||||
|
||||
#define SCL0 8 // PB0
|
||||
#define SDA0 9 // PB1
|
||||
|
||||
#define SCL1 10 // PB2
|
||||
#define SDA1 11 // PB3
|
||||
|
||||
#define SCL2 12 // PB4
|
||||
#define SDA2 13 // PB5
|
||||
|
||||
#endif // LSENSE_IODEFS_H
|
||||
254
lsense.cpp
Normal file
254
lsense.cpp
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
/*
|
||||
* @file lsense.cpp
|
||||
* @brief Light sensor implementation
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <SoftwareI2C.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "iodefs.h"
|
||||
#include "lsense.h"
|
||||
|
||||
static SoftwareI2C g_i2c_buses[NUM_BUSES];
|
||||
|
||||
/**
|
||||
* @brief Light sensor data structure
|
||||
*/
|
||||
typedef union __attribute__((packed)) {
|
||||
struct __attribute__((packed)) {
|
||||
uint16_t red;
|
||||
uint16_t green;
|
||||
uint16_t blue;
|
||||
uint16_t reserved;
|
||||
uint16_t ir;
|
||||
uint16_t green2;
|
||||
} data;
|
||||
uint8_t bytes[LSENSE_DATA_SIZE];
|
||||
} light_sensor_data_t;
|
||||
|
||||
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;
|
||||
|
||||
i2c_bus.beginTransmission(addr);
|
||||
i2c_bus.write(0x41);
|
||||
i2c_bus.endTransmission();
|
||||
|
||||
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.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)
|
||||
return;
|
||||
|
||||
uint8_t addr = LSENSE_I2C_BASE_ADDR | sensor_addr;
|
||||
|
||||
i2c_bus.beginTransmission(addr);
|
||||
i2c_bus.write(0x50); // Start from red register
|
||||
i2c_bus.endTransmission();
|
||||
|
||||
i2c_bus.requestFrom(addr, (uint8_t)LSENSE_DATA_SIZE);
|
||||
for (uint8_t i = 0; i < LSENSE_DATA_SIZE; i++) {
|
||||
sensor_data->bytes[i] = i2c_bus.read();
|
||||
}
|
||||
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.endTransmission();
|
||||
|
||||
i2c_bus.requestFrom(addr, (uint8_t)1);
|
||||
if (i2c_bus.available()) {
|
||||
response = i2c_bus.read();
|
||||
if (response == 0xFF) {
|
||||
response = 0; // Invalid response
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
switch (bus_number) {
|
||||
case BUS_X:
|
||||
sda_pin = pin_order_inverted ? SCL1 : SDA1;
|
||||
scl_pin = pin_order_inverted ? SDA1 : SCL1;
|
||||
break;
|
||||
case BUS_Y:
|
||||
sda_pin = pin_order_inverted ? SCL0 : SDA0;
|
||||
scl_pin = pin_order_inverted ? SDA0 : SCL0;
|
||||
break;
|
||||
case BUS_Z:
|
||||
sda_pin = pin_order_inverted ? SCL2 : SDA2;
|
||||
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)
|
||||
{
|
||||
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) {
|
||||
g_sensors_detected_total++;
|
||||
g_sensors_detected_per_bus[bus_number]++;
|
||||
}
|
||||
g_sensor_addresses[bus_number][SENSOR_POS] = detect_light_sensor(g_i2c_buses[bus_number], 1);
|
||||
if (g_sensor_addresses[bus_number][SENSOR_POS] == LSENSE_EXPECTED_ID) {
|
||||
g_sensors_detected_total++;
|
||||
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);
|
||||
if (g_sensor_addresses[bus_number][SENSOR_NEG] == LSENSE_EXPECTED_ID) {
|
||||
g_sensors_detected_total++;
|
||||
g_sensors_detected_per_bus[bus_number]++;
|
||||
}
|
||||
g_sensor_addresses[bus_number][SENSOR_POS] = detect_light_sensor(g_i2c_buses[bus_number], 1);
|
||||
if (g_sensor_addresses[bus_number][SENSOR_POS] == LSENSE_EXPECTED_ID) {
|
||||
g_sensors_detected_total++;
|
||||
g_sensors_detected_per_bus[bus_number]++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/* Command handlers */
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
|
||||
void lsense_cmd_presence(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();
|
||||
}
|
||||
|
||||
void lsense_cmd_read(void)
|
||||
{
|
||||
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();
|
||||
}
|
||||
43
lsense.h
Normal file
43
lsense.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* @file lsense.h
|
||||
* @brief Light sensor management and command handling
|
||||
*
|
||||
* Created: 21.09.2025 05:53:32
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef LSENSE_H
|
||||
#define LSENSE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
#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)
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Send light sensor presence information via serial
|
||||
*/
|
||||
void lsense_cmd_presence(void);
|
||||
|
||||
/**
|
||||
* @brief Read and send light sensor data via serial
|
||||
*/
|
||||
void lsense_cmd_read(void);
|
||||
|
||||
#endif // LSENSE_H
|
||||
466
rsense.cpp
Normal file
466
rsense.cpp
Normal file
|
|
@ -0,0 +1,466 @@
|
|||
/*
|
||||
* @file rsense.cpp
|
||||
* @brief Radiation sensor implementation
|
||||
*
|
||||
* Created: 21.09.2025
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include <Event.h>
|
||||
#include <Logic.h>
|
||||
#include <SPI.h>
|
||||
#include <stdint.h>
|
||||
#include <util/atomic.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "eeprom.h"
|
||||
#include "iodefs.h"
|
||||
#include "rsense.h"
|
||||
#include "utils.h"
|
||||
|
||||
static volatile union __attribute__((packed)) {
|
||||
uint16_t channels_16[DETECTOR_CHANNEL_COUNT];
|
||||
uint32_t channels_32[DETECTOR_CHANNEL_32_COUNT];
|
||||
} g_detector_counts = {0};
|
||||
|
||||
static uint8_t g_spectrum_mode = 0; // 0 = 16-bit, 1 = 32-bit
|
||||
|
||||
static uint16_t g_read_pointer = 0;
|
||||
static uint32_t g_total_counts = 0;
|
||||
static uint32_t g_total_time = 0;
|
||||
static uint32_t g_flush_time = 0;
|
||||
static volatile uint16_t g_counts_cpm = 0;
|
||||
|
||||
static uint32_t g_cpm_time = 0;
|
||||
static uint16_t g_cpm_last = 0;
|
||||
|
||||
static potentiometer_settings_t g_current_pot_settings;
|
||||
|
||||
static void apply_potentiometer_settings(const potentiometer_settings_t *settings);
|
||||
static void initialize_event_system(void);
|
||||
static void enable_fast_adc(void);
|
||||
static void restore_default_adc(void);
|
||||
static inline void handle_analog_read_interrupt(void);
|
||||
static void clear_detector_counters(void);
|
||||
static void clear_channel_data(void);
|
||||
static void set_potentiometer_values(uint8_t hv, uint8_t amp, uint8_t det);
|
||||
static void send_temperature_data(void);
|
||||
|
||||
void rsense_init(void)
|
||||
{
|
||||
eeprom_load_pot_settings(&g_current_pot_settings);
|
||||
|
||||
pinMode(HV_EN, OUTPUT);
|
||||
pinMode(DET_EN, OUTPUT);
|
||||
pinMode(DET_RST, OUTPUT);
|
||||
|
||||
// Disable HV and Detector on startup
|
||||
digitalWrite(HV_EN, LOW);
|
||||
digitalWrite(DET_EN, LOW);
|
||||
|
||||
pinMode(HV_CS, OUTPUT);
|
||||
pinMode(DET_CS, OUTPUT);
|
||||
pinMode(AMP_CS, OUTPUT);
|
||||
|
||||
digitalWrite(HV_CS, HIGH);
|
||||
digitalWrite(DET_CS, HIGH);
|
||||
digitalWrite(AMP_CS, HIGH);
|
||||
|
||||
apply_potentiometer_settings(&g_current_pot_settings);
|
||||
|
||||
pinMode(DET_TRIG_PIN, INPUT);
|
||||
|
||||
initialize_event_system();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Initialize event system for radiation detection
|
||||
*/
|
||||
static void initialize_event_system(void)
|
||||
{
|
||||
// Enable event on DET_TRIG_PIN
|
||||
Event2.set_generator(event::gen2::pin_pc7);
|
||||
Event2.set_user(event::user::adc0_start);
|
||||
|
||||
// Pin events are async, level; convert to sync, edge via CCL
|
||||
// Logic0.enable = true;
|
||||
// Logic0.input0 = logic::in::event_a;
|
||||
// Logic0.input1 = logic::in::masked;
|
||||
// Logic0.input2 = logic::in::masked;
|
||||
// Logic0.output = logic::out::enable;
|
||||
// Logic0.output_swap = logic::out::no_swap;
|
||||
// Logic0.filter = logic::filter::synchronizer;
|
||||
// Logic0.edgedetect = logic::edgedetect::enable;
|
||||
// Logic0.clocksource = logic::clocksource::clk_per;
|
||||
// Logic0.sequencer = logic::sequencer::disable;
|
||||
// Logic0.truth = 0b00000010; // A and not previous A
|
||||
// Logic0.init();
|
||||
|
||||
// Route CCL output to ADC
|
||||
// Event0.set_generator(event::gen::ccl0_out);
|
||||
// Event0.set_user(event::user::adc0_start);
|
||||
|
||||
//Event0.start();
|
||||
Event2.start();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Enable ADC for fast radiation detection
|
||||
*/
|
||||
static void enable_fast_adc(void)
|
||||
{
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
ADC0.CTRLA |= ADC_ENABLE_bm;
|
||||
|
||||
// Enable oversampling x16 for 12-bit result
|
||||
#if ADC_OVERSAMPLING_BITS == 1
|
||||
ADC0.CTRLB |= ADC_SAMPNUM_ACC4_gc;
|
||||
#elif ADC_OVERSAMPLING_BITS == 2
|
||||
ADC0.CTRLB |= ADC_SAMPNUM_ACC16_gc;
|
||||
#elif ADC_OVERSAMPLING_BITS == 3
|
||||
ADC0.CTRLB |= ADC_SAMPNUM_ACC64_gc;
|
||||
#else
|
||||
#error "Unsupported ADC oversampling setting"
|
||||
#endif
|
||||
|
||||
ADC0.CTRLC = 0; // reset
|
||||
ADC0.CTRLC |= ADC_PRESC_DIV32_gc | ADC_REFSEL_VDDREF_gc; // CLK_PER/32, VDD as reference
|
||||
|
||||
// Enable interrupt on conversion complete
|
||||
ADC0.EVCTRL |= ADC_STARTEI_bm; // Start event input
|
||||
ADC0.INTCTRL |= ADC_RESRDY_bm;
|
||||
|
||||
// Configure VREF, Microchip DS40002015B page 424
|
||||
VREF.CTRLA |= VREF_ADC0REFSEL_4V34_gc;
|
||||
|
||||
// Select ADC channel
|
||||
ADC0.MUXPOS = ADC_MUXPOS_AIN3_gc; // DET_SIG_PIN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Restore ADC to default settings
|
||||
*/
|
||||
static void restore_default_adc(void)
|
||||
{
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
// Disable oversampling
|
||||
ADC0.CTRLB &= ~ADC_SAMPNUM_gm;
|
||||
|
||||
// Set reference back to VREFA
|
||||
ADC0.CTRLC &= ~ADC_REFSEL_gm;
|
||||
ADC0.CTRLC |= ADC_REFSEL_VREFA_gc; // VREFA as reference
|
||||
|
||||
// Disable interrupt on conversion complete
|
||||
ADC0.EVCTRL &= ~ADC_STARTEI_bm; // Start event input
|
||||
ADC0.INTCTRL &= ~ADC_RESRDY_bm;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handle ADC conversion results for radiation detection
|
||||
*/
|
||||
static inline void handle_analog_read_interrupt(void)
|
||||
{
|
||||
digitalWriteFast(DET_RST, HIGH);
|
||||
uint16_t channel = ADC0.RES; // read result to clear flag
|
||||
digitalWriteFast(STATUS_LED, LOW);
|
||||
|
||||
if (g_spectrum_mode == 0) {
|
||||
// 16-bit spectrum mode
|
||||
// 2 extra bits from oversampling + averaging
|
||||
// shift down to equiv. 11 bits
|
||||
#if ADC_OVERSAMPLING_BITS == 1
|
||||
channel = channel >> 1;
|
||||
#elif ADC_OVERSAMPLING_BITS == 2
|
||||
channel = channel >> (ADC_OVERSAMPLING_BITS + 1);
|
||||
#elif ADC_OVERSAMPLING_BITS == 3
|
||||
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
|
||||
#else
|
||||
#error "Unsupported ADC oversampling setting"
|
||||
#endif
|
||||
|
||||
g_detector_counts.channels_16[channel & ADC_CHANNEL_16_MASK]++;
|
||||
} else {
|
||||
// 32-bit spectrum mode
|
||||
// 2 extra bits from oversampling + averaging x2
|
||||
// shift down to equiv. 10 bits
|
||||
#if ADC_OVERSAMPLING_BITS == 1
|
||||
channel = channel >> 2;
|
||||
#elif ADC_OVERSAMPLING_BITS == 2
|
||||
channel = channel >> (ADC_OVERSAMPLING_BITS + 2);
|
||||
#elif ADC_OVERSAMPLING_BITS == 3
|
||||
channel = channel >> (ADC_OVERSAMPLING_BITS + 3);
|
||||
#else
|
||||
#error "Unsupported ADC oversampling setting"
|
||||
#endif
|
||||
|
||||
g_detector_counts.channels_32[channel & ADC_CHANNEL_32_MASK]++;
|
||||
}
|
||||
|
||||
g_total_counts++;
|
||||
g_counts_cpm++;
|
||||
digitalWriteFast(DET_RST, LOW);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear all detector counters
|
||||
*/
|
||||
static void clear_detector_counters(void)
|
||||
{
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||
{
|
||||
g_total_counts = 0;
|
||||
g_total_time = 0;
|
||||
g_flush_time = millis();
|
||||
g_counts_cpm = 0;
|
||||
g_cpm_time = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Clear channel data
|
||||
*/
|
||||
static void clear_channel_data(void)
|
||||
{
|
||||
for (int i = 0; i < DETECTOR_CHANNEL_COUNT; i++) {
|
||||
g_detector_counts.channels_16[i] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Apply potentiometer settings to hardware
|
||||
* @param settings Pointer to potentiometer settings
|
||||
*/
|
||||
static void apply_potentiometer_settings(const potentiometer_settings_t *settings)
|
||||
{
|
||||
if (settings == nullptr)
|
||||
return;
|
||||
|
||||
// HV potentiometer setup
|
||||
digitalWrite(HV_CS, LOW);
|
||||
SPI.transfer(settings->hv_pot);
|
||||
digitalWrite(HV_CS, HIGH);
|
||||
delay(1);
|
||||
|
||||
// SiPM Pre-amp gain potentiometer setup
|
||||
digitalWrite(AMP_CS, LOW);
|
||||
SPI.transfer(settings->amp_pot);
|
||||
digitalWrite(AMP_CS, HIGH);
|
||||
delay(1);
|
||||
|
||||
// Detection threshold potentiometer setup
|
||||
digitalWrite(DET_CS, LOW);
|
||||
SPI.transfer(settings->det_pot);
|
||||
digitalWrite(DET_CS, HIGH);
|
||||
delay(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Set new potentiometer values
|
||||
* @param hv High voltage potentiometer value (0-255)
|
||||
* @param amp Amplifier potentiometer value (0-255)
|
||||
* @param det Detector potentiometer value (0-255)
|
||||
*/
|
||||
static void set_potentiometer_values(uint8_t hv, uint8_t amp, uint8_t det)
|
||||
{
|
||||
digitalWrite(DET_EN, LOW);
|
||||
digitalWrite(HV_EN, LOW);
|
||||
delay(5);
|
||||
|
||||
// Update current settings and save to EEPROM
|
||||
g_current_pot_settings.hv_pot = hv;
|
||||
g_current_pot_settings.amp_pot = amp;
|
||||
g_current_pot_settings.det_pot = det;
|
||||
|
||||
apply_potentiometer_settings(&g_current_pot_settings);
|
||||
|
||||
eeprom_save_pot_settings(&g_current_pot_settings);
|
||||
}
|
||||
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
/* Command handlers */
|
||||
/*******************************************************************************/
|
||||
/*******************************************************************************/
|
||||
|
||||
void rsense_cmd_telemetry(void)
|
||||
{
|
||||
restore_default_adc();
|
||||
|
||||
// Send total counts as 4 bytes
|
||||
Serial.write(g_total_counts & 0xFF);
|
||||
Serial.write((g_total_counts >> 8) & 0xFF);
|
||||
Serial.write((g_total_counts >> 16) & 0xFF);
|
||||
Serial.write((g_total_counts >> 24) & 0xFF);
|
||||
|
||||
// 2 bytes of voltage in mV
|
||||
uint16_t voltage_raw = read_adc_oversampled(V28V0_FB_PIN, 16);
|
||||
uint16_t voltage_mv = adc_to_voltage_mv(voltage_raw);
|
||||
Serial.write(voltage_mv & 0xFF);
|
||||
Serial.write(voltage_mv >> 8);
|
||||
|
||||
// 6 bytes of temperature data
|
||||
send_temperature_data();
|
||||
|
||||
// 3 bytes of potentiometer settings
|
||||
Serial.write(g_current_pot_settings.hv_pot);
|
||||
Serial.write(g_current_pot_settings.amp_pot);
|
||||
Serial.write(g_current_pot_settings.det_pot);
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
enable_fast_adc();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Send temperature sensor data
|
||||
*/
|
||||
static void send_temperature_data(void)
|
||||
{
|
||||
int16_t hv_temp_c = adc_to_temperature_c(read_adc_oversampled(HV_TEMP_PIN, 16));
|
||||
int16_t amp_temp_c = adc_to_temperature_c(read_adc_oversampled(AMP_TEMP_PIN, 16));
|
||||
int16_t det_temp_c = adc_to_temperature_c(read_adc_oversampled(DET_TEMP_PIN, 16));
|
||||
|
||||
Serial.write(hv_temp_c & 0xFF);
|
||||
Serial.write(hv_temp_c >> 8);
|
||||
Serial.write(amp_temp_c & 0xFF);
|
||||
Serial.write(amp_temp_c >> 8);
|
||||
Serial.write(det_temp_c & 0xFF);
|
||||
Serial.write(det_temp_c >> 8);
|
||||
}
|
||||
|
||||
/* TODO: CRC for parameters */
|
||||
void rsense_cmd_set_potentiometers(void)
|
||||
{
|
||||
// Wait for HV, AMP, DET potentiometer values
|
||||
while (Serial.available() < 3)
|
||||
;
|
||||
|
||||
uint8_t hv = Serial.read();
|
||||
uint8_t amp = Serial.read();
|
||||
uint8_t det = Serial.read();
|
||||
|
||||
set_potentiometer_values(hv, amp, det);
|
||||
|
||||
SERIAL_SEND_OK();
|
||||
}
|
||||
|
||||
void rsense_cmd_enable(void)
|
||||
{
|
||||
digitalWrite(HV_EN, HIGH);
|
||||
digitalWrite(DET_EN, HIGH);
|
||||
|
||||
enable_fast_adc();
|
||||
|
||||
SERIAL_SEND_OK();
|
||||
}
|
||||
|
||||
void rsense_cmd_disable(void)
|
||||
{
|
||||
digitalWrite(DET_EN, LOW);
|
||||
digitalWrite(HV_EN, LOW);
|
||||
|
||||
restore_default_adc();
|
||||
|
||||
SERIAL_SEND_OK();
|
||||
}
|
||||
|
||||
void rsense_cmd_flush(void)
|
||||
{
|
||||
clear_detector_counters();
|
||||
clear_channel_data();
|
||||
SERIAL_SEND_OK();
|
||||
}
|
||||
|
||||
void rsense_cmd_dump_channels(void)
|
||||
{
|
||||
restore_default_adc();
|
||||
|
||||
uint16_t read_from = 0;
|
||||
|
||||
// Wait for 2 bytes specifying read position
|
||||
while (Serial.available() < 2)
|
||||
;
|
||||
read_from = Serial.read();
|
||||
read_from |= (uint16_t)Serial.read() << 8;
|
||||
|
||||
// Send 2 bytes of read position
|
||||
Serial.write(g_read_pointer & 0xFF);
|
||||
Serial.write(g_read_pointer >> 8);
|
||||
|
||||
g_read_pointer = read_from;
|
||||
if (g_read_pointer >= (DETECTOR_CHANNEL_32_COUNT - 32)) {
|
||||
g_read_pointer = DETECTOR_CHANNEL_32_COUNT - 32;
|
||||
}
|
||||
|
||||
// Send 2 bytes of CRC16 checksum for the 32 channels
|
||||
uint16_t crc16 = calculate_crc16_xmodem((uint8_t *)(&g_detector_counts.channels_32[g_read_pointer]), 32 * 4);
|
||||
Serial.write(crc16 & 0xFF);
|
||||
Serial.write(crc16 >> 8);
|
||||
|
||||
// Send 32 channel values (128 bytes total)
|
||||
for (uint16_t i = 0; i < 32 && g_read_pointer < DETECTOR_CHANNEL_32_COUNT; i++, g_read_pointer++) {
|
||||
uint32_t channel_value = g_detector_counts.channels_32[g_read_pointer];
|
||||
Serial.write(channel_value & 0xFF);
|
||||
Serial.write((channel_value >> 8) & 0xFF);
|
||||
Serial.write((channel_value >> 16) & 0xFF);
|
||||
Serial.write((channel_value >> 24) & 0xFF);
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
delay(1);
|
||||
}
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
enable_fast_adc();
|
||||
}
|
||||
|
||||
void rsense_cmd_get_cpm(void)
|
||||
{
|
||||
Serial.write(g_cpm_last & 0xFF);
|
||||
Serial.write(g_cpm_last >> 8);
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
}
|
||||
|
||||
void rsense_cmd_set_mode(void)
|
||||
{
|
||||
// Wait for 1 byte specifying mode
|
||||
while (Serial.available() < 1)
|
||||
;
|
||||
uint8_t mode = Serial.read();
|
||||
|
||||
if (mode <= 1) {
|
||||
g_spectrum_mode = mode;
|
||||
clear_channel_data();
|
||||
SERIAL_SEND_OK();
|
||||
}
|
||||
|
||||
Serial.flush();
|
||||
SERIAL_BUFFER_CLEAR();
|
||||
}
|
||||
|
||||
void rsense_periodic(void)
|
||||
{
|
||||
if (millis() - g_cpm_time > CPM_UPDATE_PERIOD) {
|
||||
uint32_t elapsed_time = millis() - g_cpm_time;
|
||||
g_cpm_last = (uint16_t)(((uint32_t)g_counts_cpm * 60UL * 1000UL) / elapsed_time);
|
||||
g_counts_cpm = 0;
|
||||
g_cpm_time = millis();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief ADC conversion complete interrupt handler
|
||||
*/
|
||||
ISR(ADC0_RESRDY_vect) { handle_analog_read_interrupt(); }
|
||||
71
rsense.h
Normal file
71
rsense.h
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* @file rsense.h
|
||||
* @brief Radiation sensor management and command handling
|
||||
*
|
||||
* Created: 21.09.2025 06:01:56
|
||||
* Author: ThePetrovich
|
||||
*
|
||||
* Copyright YKSA - Sakha Aerospace Systems, LLC.
|
||||
* See the LICENSE file for details.
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-3-Clause
|
||||
*/
|
||||
|
||||
#ifndef RSENSE_H
|
||||
#define RSENSE_H
|
||||
|
||||
#include <Arduino.h>
|
||||
|
||||
/**
|
||||
* @brief Initialize radiation sensor subsystem
|
||||
*/
|
||||
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_mode(void);
|
||||
|
||||
/**
|
||||
* @brief Periodic tasks for radiation sensor
|
||||
* Updates CPM calculation every 10 seconds
|
||||
* Call from main loop
|
||||
*/
|
||||
void rsense_periodic(void);
|
||||
|
||||
#endif // RSENSE_H
|
||||
139
sbc_fw.ino
Normal file
139
sbc_fw.ino
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
/*
|
||||
* @file sbc_fw.ino
|
||||
* @brief Main firmware for sensor board controller, YKSA PL/EDU16 RSENSE
|
||||
*
|
||||
* 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 <Event.h>
|
||||
#include <SPI.h>
|
||||
#include <SoftwareI2C.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "eeprom.h"
|
||||
#include "iodefs.h"
|
||||
#include "lsense.h"
|
||||
#include "rsense.h"
|
||||
|
||||
static uint32_t status_led_last_blink = 0;
|
||||
|
||||
void setup()
|
||||
{
|
||||
pinMode(STATUS_LED, OUTPUT);
|
||||
|
||||
Serial.pins(0, 1);
|
||||
Serial.begin(SERIAL_BAUD_RATE);
|
||||
|
||||
initialize_spi();
|
||||
|
||||
eeprom_init();
|
||||
|
||||
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_mode();
|
||||
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);
|
||||
}
|
||||
update_status_led();
|
||||
|
||||
rsense_periodic();
|
||||
}
|
||||
57
utils.cpp
Normal file
57
utils.cpp
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
/*
|
||||
* @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;
|
||||
}
|
||||
|
||||
int16_t adc_to_temperature_c(uint16_t adc_value)
|
||||
{
|
||||
// MCP9700: 500 mV at 0°C, 10 mV per degree, vref = 3.0V
|
||||
// Result in 0.1°C units
|
||||
return (int16_t)((adc_value * ADC_VREF_MV / ADC_RESOLUTION - TEMP_SENSOR_OFFSET_MV));
|
||||
}
|
||||
|
||||
uint16_t adc_to_voltage_mv(uint16_t adc_value) { return (uint16_t)(adc_value * VOLTAGE_MV_PER_STEP); }
|
||||
|
||||
uint16_t read_adc_oversampled(uint8_t pin, uint8_t samples)
|
||||
{
|
||||
uint32_t sum = 0;
|
||||
|
||||
uint8_t shift = ((samples == 64) ? 3 : (samples == 16) ? 2 : (samples == 4) ? 1 : 0);
|
||||
|
||||
for (uint8_t i = 0; i < samples; i++) {
|
||||
sum += analogRead(pin);
|
||||
}
|
||||
|
||||
return (uint16_t)(sum >> shift);
|
||||
}
|
||||
49
utils.h
Normal file
49
utils.h
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* @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>
|
||||
|
||||
/**
|
||||
* @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);
|
||||
|
||||
/**
|
||||
* @brief Convert ADC reading to temperature in 0.1°C
|
||||
* @param adc_value 12-bit ADC reading
|
||||
* @return Temperature in 0.1°C
|
||||
*/
|
||||
int16_t adc_to_temperature_c(uint16_t adc_value);
|
||||
|
||||
/**
|
||||
* @brief Convert ADC reading to voltage in mV
|
||||
* @param adc_value ADC reading
|
||||
* @return Voltage in mV
|
||||
*/
|
||||
uint16_t adc_to_voltage_mv(uint16_t adc_value);
|
||||
|
||||
/**
|
||||
* @brief Read ADC with oversampling
|
||||
* @param pin Analog pin to read
|
||||
* @param samples Number of samples for oversampling
|
||||
* @return Averaged ADC value
|
||||
*/
|
||||
uint16_t read_adc_oversampled(uint8_t pin, uint8_t samples);
|
||||
|
||||
#endif // UTILS_H
|
||||
Loading…
Add table
Add a link
Reference in a new issue