High-Speed Spectroscopic Camera for Palomar (HiSPEC) - Telescope Interface Box (TIB) firmware built on Zephyr RTOS using the COO (Caltech Optical Observatories) standardized template.
The HiSPEC-TIB controls optical routing, laser calibration sources, and attenuators for the HiSPEC spectrograph. This firmware runs on a WIZnet W5500-EVB-Pico2 board (RP2350 microcontroller) and provides MQTT-based remote control over Ethernet.
- MEMS Optical Switches: 8 dual-channel fiber switches for beam routing
- Maiman Laser Controllers: Modbus control of calibration laser sources
- Optical Attenuators: DAC-controlled variable optical attenuators with polynomial calibration
- Photodiode Monitoring: ADC-based optical power monitoring at 50Hz
- Watchdog: Automatic recovery from system hangs
- Persistent Settings: NVS-backed configuration storage
# Create and activate virtual environment
python3 -m venv ~/zephyr-venv
source ~/zephyr-venv/bin/activate # On Windows: ~/zephyr-venv/Scripts/activate
# Install west
pip install west
# Create workspace
west init -m https://github.com/mikelangmayr/hispec-tib --mr main hispec-zephyr
cd hispec-zephyr && west update
# Build for W5500-EVB-Pico2
cd hispec-tib
west build -b w5500_evb_pico2/rp2350a/m33 app
west flashThis project uses the lib/coo_commons library for production-ready utilities:
Production-ready MQTT 5.0 wrapper with automatic retry, subscription management, event callbacks, and QoS support (0, 1, 2).
MQTT Command Interface:
- Subscribe topic:
cmd/hsfib-tib/req/# - Response topic: Provided in MQTT 5.0
response_topicproperty - Broker:
jebcontrol.caltech.edu:1883
Complete networking support with connection manager integration (L4 events, DHCP with static IP fallback).
Structured message handling for telemetry encoding, command parsing with hierarchical keys (device/setting), and message type detection (GET/SET/RESPONSE).
Reusable proportional-integral-derivative loops for temperature and motion control.
Usage Example:
#include <coo_commons/mqtt_client.h>
#include <coo_commons/network.h>
// Initialize network and MQTT
coo_network_init(NULL);
coo_network_wait_ready(K_FOREVER);
struct mqtt_client client;
coo_mqtt_init(&client, "hsfib-tib");
coo_mqtt_add_subscription("cmd/hsfib-tib/req/#", MQTT_QOS_2_EXACTLY_ONCE);
coo_mqtt_set_message_callback(on_mqtt_message);
coo_mqtt_connect(&client);Configuration in app/prj.conf:
CONFIG_COO_COMMONS=y
CONFIG_COO_NETWORK=y
CONFIG_COO_MQTT=y
CONFIG_COO_MQTT_BROKER_HOSTNAME="jebcontrol.caltech.edu"
CONFIG_COO_MQTT_BROKER_PORT="1883"
All commands follow a standardized JSON format with MQTT 5.0 properties:
{
"msg_type": "get|set",
"value": "<depends on command>"
}Topic: cmd/hsfib-tib/req/memsroute
{
"msg_type": "set",
"value": ["input_name", "output_name"]
}Topic: cmd/hsfib-tib/req/mems/<name>
{
"msg_type": "set|get",
"value": "state"
}Topic: cmd/hsfib-tib/req/laser###/flux
{
"msg_type": "set|get",
"value": <flux_value>
}Topic: cmd/hsfib-tib/req/atten###/value[dB]
{
"msg_type": "set|get",
"value": <attenuation_in_dB>
}Topic: cmd/hsfib-tib/req/atten###/coeff
{
"msg_type": "set|get",
"value": [coeff0, coeff1, ..., coeffN]
}Topic: cmd/hsfib-tib/req/status
{
"msg_type": "get"
}Response:
{
"version": 1,
"uptime": <seconds>,
"a_laser_is_on": "true|false|error:...",
"time": <unix_timestamp>
}- Main Thread: MQTT event loop, network management, watchdog feeding
- Executor Thread: Command dispatch and execution
- Photodiode Thread: 50Hz optical power sampling
- Photodiode Publisher: 100Hz work queue for telemetry publishing
inbound_queue: MQTT commands → Executoroutbound_queue: Executor/Photodiode → MQTT publisherphotodiode_queue: ADC samples → Publisher
For Hardware:
west build -b w5500_evb_pico2/rp2350a/m33 app
west flashDebug Build:
west build -b w5500_evb_pico2/rp2350a/m33 app -- -DEXTRA_CONF_FILE=debug.confClean Rebuild:
west build -b w5500_evb_pico2/rp2350a/m33 app --pristineHardware is configured via app/boards/w5500_evb_pico2_rp2350a_m33.overlay:
- I2C0: DAC7578 (attenuators), PCAL6416A (GPIO expander for MEMS)
- I2C1: ADS1115 (photodiode ADCs)
- UART1: Modbus for Maiman laser controllers
- SPI0: W5500 Ethernet controller
- Flash: RP2350 internal flash with NVS storage partition
The application demonstrates NVS and watchdog usage:
// Initialize and load settings from flash
settings_subsys_init();
settings_load();
// Save settings (persists across reboots)
settings_save_one("tib/key", &value, sizeof(value));
// Initialize watchdog (5 second timeout)
watchdog_init(&wdt, &wdt_channel);
// Feed periodically in main loop
wdt_feed(wdt, wdt_channel);Settings are stored in the storage_partition defined in the board device tree overlay.
The application uses Zephyr's connection manager for automatic network setup:
conn_mgr_all_if_up(true); // Bring up interfaces with DHCP
coo_network_wait_ready(K_FOREVER); // Wait for L4 connectivityStatic IP Configuration (in app/prj.conf):
CONFIG_NET_CONFIG_MY_IPV4_ADDR="192.168.1.111"
CONFIG_NET_CONFIG_MY_IPV4_NETMASK="255.255.255.0"
CONFIG_NET_CONFIG_MY_IPV4_GW="192.168.1.1"
Follow the Zephyr Getting Started Guide to install:
- West:
pip3 install west - CMake >= 3.20
- Python >= 3.9
- Zephyr SDK
Set environment:
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=~/zephyr-sdk-0.16.xhispec-tib/
├── app/ # HiSPEC-TIB application
│ ├── src/
│ │ ├── main.c # Main application with MQTT loop
│ │ ├── command.c/h # Command parser and dispatcher
│ │ ├── devices.c/h # Device initialization
│ │ ├── attenuator.c/h # Attenuator control via DAC
│ │ ├── maiman.c/h # Maiman laser Modbus driver
│ │ ├── photodiode.c/h # Photodiode ADC monitoring
│ │ └── mems_switching.c/h # MEMS switch routing logic
│ ├── boards/
│ │ └── w5500_evb_pico2_rp2350a_m33.overlay # Hardware config
│ └── prj.conf # Kconfig options
├── lib/
│ └── coo_commons/ # Shared COO library (PID, MQTT, network, JSON)
├── include/
│ └── coo_commons/ # COO commons public headers
├── drivers/ # Custom drivers (blink LED, sensors)
├── boards/ # Custom board definitions
├── tests/ # Integration tests
├── doc/ # Doxygen + Sphinx documentation
└── .github/workflows/ # CI with Zephyr builds
Build fails with "Zephyr not found":
cd hispec-zephyr
west update
west list # Verify configuration"No CMAKE_C_COMPILER could be found":
export ZEPHYR_TOOLCHAIN_VARIANT=zephyr
export ZEPHYR_SDK_INSTALL_DIR=~/zephyr-sdk-0.16.xSettings don't persist:
- Verify
CONFIG_SETTINGS_NVS=yin prj.conf - Check board overlay has
storage_partitiondefined - Try clean build:
west build -b w5500_evb_pico2/rp2350a/m33 app --pristine
Enable verbose logging:
west build -b w5500_evb_pico2/rp2350a/m33 app -- -DEXTRA_CONF_FILE=debug.conf
# Or in prj.conf: CONFIG_LOG_DEFAULT_LEVEL=4Network not connecting:
- Check Ethernet cable connection
- Verify W5500 SPI configuration in device tree
- Check DHCP server or configure static IP
- Enable network debug:
CONFIG_NET_LOG_LEVEL_DBG=y
Build API and user documentation locally:
cd doc
pip install -r requirements.txt
doxygen # API docs → _build_doxygen/html/index.html
make html # User docs → _build_sphinx/html/index.htmlThis project was migrated from the original tib-zephyr repository to use the COO standardized template. Key improvements:
- Replaced custom MQTT client with
coo_commons/mqtt_clientfor better maintainability - Added coo_commons/network helper for simplified network initialization
- Integrated watchdog for automatic recovery from hangs
- Added NVS settings for persistent configuration (stub implementation, ready for expansion)
- Standardized project structure following COO template conventions
- Improved documentation with consistent formatting and examples
- UUID generation for unique device identification
- Expand settings persistence for device calibration data
- Verify DHCP → static IP fallback behavior across link failures
- Test MQTT reconnection across network disruptions
SPDX-License-Identifier: Apache-2.0
Copyright (c) 2025 Caltech Optical Observatories