Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e13b323
refactor: Rename laser constants from wavelength-based to port-based …
hongquanli Jan 27, 2026
8cb1b1c
fix: Correct D3/D4 port-to-source-code mapping
hongquanli Jan 27, 2026
340c6b0
docs: Fix misleading comments in firmware constants
hongquanli Jan 27, 2026
9868e1d
docs: Rename "Laser Ports" to "Illumination Control TTL Ports"
hongquanli Jan 27, 2026
3fe45ef
refactor: Rename firmware pin constants to port-based names
hongquanli Jan 27, 2026
fc7280d
fix: Address PR review issues - consistent naming and accurate comments
hongquanli Jan 27, 2026
c866822
fix: Update YAML config to match corrected D3/D4 port mapping
hongquanli Jan 27, 2026
5cfc3c4
feat(firmware): Add multi-port illumination control
hongquanli Jan 28, 2026
b4d5b04
feat(firmware): Add illumination utility headers
hongquanli Jan 28, 2026
aa53069
test(firmware): Add comprehensive illumination control tests
hongquanli Jan 28, 2026
1da03a5
feat(python): Add multi-port illumination control
hongquanli Jan 28, 2026
fdbca36
test(python): Add comprehensive multi-port illumination tests
hongquanli Jan 28, 2026
554a772
refactor: Use centralized source code mapping in core.py
hongquanli Jan 28, 2026
7f1aa68
fix: Detect firmware version early during Microcontroller init
hongquanli Jan 28, 2026
117c04c
fix: Add port validation and consistent wait behavior
hongquanli Jan 28, 2026
4dd66e2
test: Add round-trip tests for D3/D4 legacy↔new mapping
hongquanli Jan 28, 2026
7e74579
docs: Document illumination_intensity_factor scaling behavior
hongquanli Jan 28, 2026
a91c5ec
docs: Add non-blocking notes to Microcontroller multi-port methods
hongquanli Jan 28, 2026
1d4f483
fix: Address PR review comments for multi-port illumination
hongquanli Jan 28, 2026
f25d454
docs: Add class docstring explaining legacy vs multi-port illuminatio…
hongquanli Jan 28, 2026
5a7b3de
docs: Add illumination control documentation
hongquanli Jan 28, 2026
769265a
docs: Add port index column to hardware ports table
hongquanli Jan 28, 2026
f37b88c
fix: Handle D3/D5 ports in ImageDisplayWindow.display_image()
hongquanli Jan 28, 2026
c31ad9c
refactor: Remove dead code ImageArrayDisplayWindow
hongquanli Jan 28, 2026
5273dd9
Revert "refactor: Remove dead code ImageArrayDisplayWindow"
hongquanli Jan 28, 2026
d570053
Merge branch 'master' into refactor/laser-port-naming
hongquanli Jan 28, 2026
d8c9d6f
Update illumination control documentation
hongquanli Jan 28, 2026
c67b382
fix: Add multi-port illumination commands to firmware validator
hongquanli Jan 28, 2026
6b04a55
Merge branch 'master' into refactor/laser-port-naming
hongquanli Jan 28, 2026
c0c14c3
feat: Add multi-camera support with LiveController camera switching
hongquanli Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions firmware/controller/src/commands/commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,13 @@ void init_callbacks()
cmd_map[TURN_OFF_ILLUMINATION] = &callback_turn_off_illumination;
cmd_map[SET_ILLUMINATION] = &callback_set_illumination;
cmd_map[SET_ILLUMINATION_LED_MATRIX] = &callback_set_illumination_led_matrix;
// Multi-port illumination commands (firmware v1.0+)
cmd_map[SET_PORT_INTENSITY] = &callback_set_port_intensity;
cmd_map[TURN_ON_PORT] = &callback_turn_on_port;
cmd_map[TURN_OFF_PORT] = &callback_turn_off_port;
cmd_map[SET_PORT_ILLUMINATION] = &callback_set_port_illumination;
cmd_map[SET_MULTI_PORT_MASK] = &callback_set_multi_port_mask;
cmd_map[TURN_OFF_ALL_PORTS] = &callback_turn_off_all_ports;
cmd_map[ACK_JOYSTICK_BUTTON_PRESSED] = &callback_ack_joystick_button_pressed;
cmd_map[ANALOG_WRITE_ONBOARD_DAC] = &callback_analog_write_onboard_dac;
cmd_map[SET_DAC80508_REFDIV_GAIN] = &callback_set_dac80508_defdiv_gain;
Expand Down
67 changes: 67 additions & 0 deletions firmware/controller/src/commands/light_commands.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,70 @@ void callback_set_illumination_intensity_factor()
if (factor > 100) factor = 100;
illumination_intensity_factor = float(factor) / 100;
}

/***************************************************************************************************/
/*************************** Multi-port illumination commands (v1.0+) ******************************/
/***************************************************************************************************/

// Command byte layout: [cmd_id, 34, port, intensity_hi, intensity_lo, 0, 0, crc]
void callback_set_port_intensity()
{
int port_index = buffer_rx[2];
uint16_t intensity = (uint16_t(buffer_rx[3]) << 8) | uint16_t(buffer_rx[4]);
set_port_intensity(port_index, intensity);
}

// Command byte layout: [cmd_id, 35, port, 0, 0, 0, 0, crc]
void callback_turn_on_port()
{
int port_index = buffer_rx[2];
turn_on_port(port_index);
}

// Command byte layout: [cmd_id, 36, port, 0, 0, 0, 0, crc]
void callback_turn_off_port()
{
int port_index = buffer_rx[2];
turn_off_port(port_index);
}

// Command byte layout: [cmd_id, 37, port, intensity_hi, intensity_lo, on_flag, 0, crc]
void callback_set_port_illumination()
{
int port_index = buffer_rx[2];
uint16_t intensity = (uint16_t(buffer_rx[3]) << 8) | uint16_t(buffer_rx[4]);
bool turn_on = buffer_rx[5] != 0;

set_port_intensity(port_index, intensity);
if (turn_on)
turn_on_port(port_index);
else
turn_off_port(port_index);
}

// Command byte layout: [cmd_id, 38, mask_hi, mask_lo, on_hi, on_lo, 0, crc]
// port_mask: which ports to update (bit 0 = D1, bit 15 = D16)
// on_mask: for selected ports, which to turn ON (1) vs OFF (0)
void callback_set_multi_port_mask()
{
uint16_t port_mask = (uint16_t(buffer_rx[2]) << 8) | uint16_t(buffer_rx[3]);
uint16_t on_mask = (uint16_t(buffer_rx[4]) << 8) | uint16_t(buffer_rx[5]);

for (int i = 0; i < NUM_ILLUMINATION_PORTS; i++)
{
if (port_mask & (1 << i)) // If this port is selected
{
if (on_mask & (1 << i))
turn_on_port(i);
else
turn_off_port(i);
}
// Ports not in port_mask are left unchanged
}
}

// Command byte layout: [cmd_id, 39, 0, 0, 0, 0, 0, crc]
void callback_turn_off_all_ports()
{
turn_off_all_ports();
}
8 changes: 8 additions & 0 deletions firmware/controller/src/commands/light_commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,12 @@ void callback_set_illumination();
void callback_set_illumination_led_matrix();
void callback_set_illumination_intensity_factor();

// Multi-port illumination commands (firmware v1.0+)
void callback_set_port_intensity();
void callback_turn_on_port();
void callback_turn_off_port();
void callback_set_port_illumination();
void callback_set_multi_port_mask();
void callback_turn_off_all_ports();

#endif // LIGHT_COMMANDS_H
8 changes: 8 additions & 0 deletions firmware/controller/src/constants.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,14 @@

#include "constants_protocol.h"

/***************************************************************************************************/
/**************************************** Firmware Version *****************************************/
/***************************************************************************************************/
// Version is sent in response byte 22 as nibble-encoded: high nibble = major, low nibble = minor
// Version 1.0 = first version with multi-port illumination support
#define FIRMWARE_VERSION_MAJOR 1
#define FIRMWARE_VERSION_MINOR 0

#include "def/def_v1.h"

#include "tmc/TMC4361A_TMC2660_Utils.h"
Expand Down
12 changes: 12 additions & 0 deletions firmware/controller/src/constants_protocol.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,18 @@ static const int SEND_HARDWARE_TRIGGER = 30;
static const int SET_STROBE_DELAY = 31;
static const int SET_AXIS_DISABLE_ENABLE = 32;
static const int SET_TRIGGER_MODE = 33;

// Multi-port illumination commands (firmware v1.0+)
// Separate commands (matches existing SET_ILLUMINATION pattern)
static const int SET_PORT_INTENSITY = 34; // Set DAC intensity for specific port only
static const int TURN_ON_PORT = 35; // Turn on GPIO for specific port
static const int TURN_OFF_PORT = 36; // Turn off GPIO for specific port
// Combined command (convenience)
static const int SET_PORT_ILLUMINATION = 37; // Set intensity + on/off in one command
// Multi-port commands
static const int SET_MULTI_PORT_MASK = 38; // Set on/off for multiple ports (partial update)
static const int TURN_OFF_ALL_PORTS = 39; // Turn off all illumination ports

static const int SET_PIN_LEVEL = 41;
static const int INITFILTERWHEEL_W2 = 252;
static const int INITFILTERWHEEL = 253;
Expand Down
110 changes: 110 additions & 0 deletions firmware/controller/src/functions.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,12 @@ CRGB matrix[NUM_LEDS] = {0};
void turn_on_illumination()
{
illumination_is_on = true;

// Update per-port state for D1-D5 sources (backward compatibility with new multi-port tracking)
int port_index = illumination_source_to_port_index(illumination_source);
if (port_index >= 0)
illumination_port_is_on[port_index] = true;

switch (illumination_source)
{
case ILLUMINATION_SOURCE_LED_ARRAY_FULL:
Expand Down Expand Up @@ -263,6 +269,11 @@ void turn_on_illumination()

void turn_off_illumination()
{
// Update per-port state for D1-D5 sources (backward compatibility with new multi-port tracking)
int port_index = illumination_source_to_port_index(illumination_source);
if (port_index >= 0)
illumination_port_is_on[port_index] = false;
Comment on lines 270 to +275
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Firmware legacy turn_off_illumination() only turns off the currently selected legacy source pin, but with multi-port illumination it’s now possible for other ports to remain ON (e.g., after SET_MULTI_PORT_MASK / TURN_ON_PORT). This conflicts with the Python SimSerial/tests which assume TURN_OFF_ILLUMINATION is a global shutdown. Consider making legacy TURN_OFF_ILLUMINATION call turn_off_all_ports() (or otherwise guarantee all ports are OFF) to avoid leaving illumination outputs active unintentionally and to keep firmware/simulation behavior consistent.

Copilot uses AI. Check for mistakes.

switch(illumination_source)
{
case ILLUMINATION_SOURCE_LED_ARRAY_FULL:
Expand Down Expand Up @@ -317,6 +328,12 @@ void set_illumination(int source, uint16_t intensity)
{
illumination_source = source;
illumination_intensity = intensity * illumination_intensity_factor;

// Update per-port intensity for D1-D5 sources (backward compatibility with new multi-port tracking)
int port_index = illumination_source_to_port_index(source);
if (port_index >= 0)
illumination_port_intensity[port_index] = intensity;

switch (source)
{
case ILLUMINATION_D1:
Expand Down Expand Up @@ -349,6 +366,99 @@ void set_illumination_led_matrix(int source, uint8_t r, uint8_t g, uint8_t b)
turn_on_illumination(); //update the illumination
}

/***************************************************************************************************/
/********************************** Multi-port illumination control ********************************/
/***************************************************************************************************/

// illumination_source_to_port_index() is provided by utils/illumination_mapping.h
// to ensure firmware and tests use the same mapping logic.

// Gets GPIO pin for port index (0-15), returns -1 for unsupported ports
int port_index_to_pin(int port_index)
{
switch (port_index)
{
case 0: return PIN_ILLUMINATION_D1;
case 1: return PIN_ILLUMINATION_D2;
case 2: return PIN_ILLUMINATION_D3;
case 3: return PIN_ILLUMINATION_D4;
case 4: return PIN_ILLUMINATION_D5;
// Ports 5-15 can be added here as hardware expands
default: return -1;
}
}

// Gets DAC channel for port index (0-15), returns -1 for unsupported ports
static int port_index_to_dac_channel(int port_index)
{
// Currently ports 0-4 map directly to DAC channels 0-4
if (port_index >= 0 && port_index < 5)
return port_index;
return -1;
}

void turn_on_port(int port_index)
{
if (port_index < 0 || port_index >= NUM_ILLUMINATION_PORTS)
return;

int pin = port_index_to_pin(port_index);
if (pin < 0)
return;

if (INTERLOCK_OK())
{
digitalWrite(pin, HIGH);
illumination_port_is_on[port_index] = true;
}
}

void turn_off_port(int port_index)
{
if (port_index < 0 || port_index >= NUM_ILLUMINATION_PORTS)
return;

int pin = port_index_to_pin(port_index);
if (pin < 0)
return;

digitalWrite(pin, LOW);
illumination_port_is_on[port_index] = false;
}

// Set DAC intensity for a specific port without changing on/off state.
// The intensity is scaled by illumination_intensity_factor before being sent to DAC.
// The unscaled value is stored in illumination_port_intensity[] for reference.
void set_port_intensity(int port_index, uint16_t intensity)
{
if (port_index < 0 || port_index >= NUM_ILLUMINATION_PORTS)
return;

int dac_channel = port_index_to_dac_channel(port_index);
if (dac_channel < 0)
return;

uint16_t scaled_intensity = intensity * illumination_intensity_factor;
set_DAC8050x_output(dac_channel, scaled_intensity);
illumination_port_intensity[port_index] = intensity; // Store unscaled for reference
}

void turn_off_all_ports()
{
for (int i = 0; i < NUM_ILLUMINATION_PORTS; i++)
{
int pin = port_index_to_pin(i);
if (pin >= 0)
{
digitalWrite(pin, LOW);
illumination_port_is_on[i] = false;
}
}
// Also turn off LED matrix if it was on
clear_matrix(matrix);
illumination_is_on = false;
}

void ISR_strobeTimer()
{
for (int camera_channel = 0; camera_channel < 4; camera_channel++)
Expand Down
11 changes: 11 additions & 0 deletions firmware/controller/src/functions.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

#include "constants.h"
#include "globals.h"
#include "utils/illumination_mapping.h"

#include <Arduino.h>
#include <SPI.h>
Expand Down Expand Up @@ -54,6 +55,16 @@ void set_illumination(int source, uint16_t intensity);
void set_illumination_led_matrix(int source, uint8_t r, uint8_t g, uint8_t b);
void ISR_strobeTimer();

// Multi-port illumination control
// illumination_source_to_port_index() is provided by utils/illumination_mapping.h
// Gets GPIO pin for port index, returns -1 for invalid port
int port_index_to_pin(int port_index);
// Per-port control functions (interlock checked for turn_on)
void turn_on_port(int port_index);
void turn_off_port(int port_index);
void set_port_intensity(int port_index, uint16_t intensity);
void turn_off_all_ports();

/***************************************************************************************************/
/******************************************* joystick **********************************************/
/***************************************************************************************************/
Expand Down
9 changes: 9 additions & 0 deletions firmware/controller/src/globals.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,17 @@ bool enable_filterwheel_w2 = false;
/***************************************************************************************************/
int illumination_source = 0;
uint16_t illumination_intensity = 65535;
// Illumination intensity scaling factor - scales DAC output for different hardware:
// 0.6 = Squid LEDs (0-1.5V output range)
// 0.8 = Squid laser engine (0-2V output range)
// 1.0 = Full range (0-2.5V output, when DAC gain is 1 instead of 2)
// This factor is applied to ALL illumination commands (legacy and multi-port).
float illumination_intensity_factor = 0.6;
uint8_t led_matrix_r = 0;
uint8_t led_matrix_g = 0;
uint8_t led_matrix_b = 0;
bool illumination_is_on = false;

// Multi-port illumination control (all ports off and at zero intensity by default)
bool illumination_port_is_on[NUM_ILLUMINATION_PORTS] = {false};
uint16_t illumination_port_intensity[NUM_ILLUMINATION_PORTS] = {0};
5 changes: 5 additions & 0 deletions firmware/controller/src/globals.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,9 @@ extern uint8_t led_matrix_g;
extern uint8_t led_matrix_b;
extern bool illumination_is_on;

// Multi-port illumination control (supports up to 16 ports D1-D16)
#define NUM_ILLUMINATION_PORTS 16
extern bool illumination_port_is_on[NUM_ILLUMINATION_PORTS];
extern uint16_t illumination_port_intensity[NUM_ILLUMINATION_PORTS];

#endif // GLOBALS_H
3 changes: 3 additions & 0 deletions firmware/controller/src/serial_communication.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,9 @@ void send_position_update()
buffer_tx[18] &= ~ (1 << BIT_POS_JOYSTICK_BUTTON); // clear the joystick button bit
buffer_tx[18] = buffer_tx[18] | joystick_button_pressed << BIT_POS_JOYSTICK_BUTTON;

// Firmware version in byte 22: high nibble = major, low nibble = minor
buffer_tx[22] = (FIRMWARE_VERSION_MAJOR << 4) | (FIRMWARE_VERSION_MINOR & 0x0F);

// Calculate and fill out the checksum. NOTE: This must be after all other buffer_tx modifications are done!
uint8_t checksum = crc8ccitt(buffer_tx, MSG_LENGTH - 1);
buffer_tx[MSG_LENGTH - 1] = checksum;
Expand Down
59 changes: 59 additions & 0 deletions firmware/controller/src/utils/illumination_mapping.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* Illumination port mapping utilities.
*
* Pure C++ utility functions for mapping between legacy illumination source
* codes and port indices. No Arduino/hardware dependencies.
*/

#ifndef ILLUMINATION_MAPPING_H
#define ILLUMINATION_MAPPING_H

#include "../constants_protocol.h"

// Number of illumination ports supported
#define NUM_ILLUMINATION_PORTS 16

/**
* Map legacy illumination source code to port index.
*
* Legacy source codes are non-sequential for historical API compatibility:
* D1 = 11, D2 = 12, D3 = 14, D4 = 13, D5 = 15
*
* Port indices are sequential: 0=D1, 1=D2, 2=D3, 3=D4, 4=D5
*
* @param source Legacy illumination source code (11-15)
* @return Port index (0-4), or -1 for unknown source codes
*/
inline int illumination_source_to_port_index(int source)
{
switch (source)
{
case ILLUMINATION_D1: return 0; // 11 -> 0
case ILLUMINATION_D2: return 1; // 12 -> 1
case ILLUMINATION_D3: return 2; // 14 -> 2 (non-sequential!)
case ILLUMINATION_D4: return 3; // 13 -> 3 (non-sequential!)
case ILLUMINATION_D5: return 4; // 15 -> 4
default: return -1; // Unknown source
}
}

/**
* Map port index to legacy illumination source code.
*
* @param port_index Port index (0-4)
* @return Legacy source code (11-15), or -1 for invalid port index
*/
inline int port_index_to_illumination_source(int port_index)
{
switch (port_index)
{
case 0: return ILLUMINATION_D1; // 0 -> 11
case 1: return ILLUMINATION_D2; // 1 -> 12
case 2: return ILLUMINATION_D3; // 2 -> 14
case 3: return ILLUMINATION_D4; // 3 -> 13
case 4: return ILLUMINATION_D5; // 4 -> 15
default: return -1; // Invalid port
}
}

#endif // ILLUMINATION_MAPPING_H
Loading
Loading