High-performance thermal tyre and brake temperature monitoring for Adafruit RP2040 CAN Bus Feather
Real-time thermal imaging at 10 fps with automatic tyre detection, brake pad temperature monitoring, and CAN bus output for vehicle integration.
- CAN Bus Output - Configurable CAN 2.0A (125K-1M kbps) for vehicle integration
- Multi-Board Support - Up to 4 boards (one per wheel) on same CAN bus
- Brake Temps - MCP9601 Type-K thermocouple support for brake pads
- Fast - 10 fps thermal imaging with automatic tyre detection
- Reliable - Automatic sensor recovery, watchdog protection
- Safe - Integer overflow protection, bounds checking, flash verification
- Visual Feedback - NeoPixel status LED with color-coded states
| Component | Qty | Link |
|---|---|---|
| Adafruit RP2040 CAN Bus Feather | 1 per wheel | Pimoroni |
| MLX90640 Thermal Camera Breakout | 1 per wheel | Pimoroni |
| MCP9601 Thermocouple Amplifier (optional) | 0-2 per wheel | Pimoroni |
| Type-K Thermocouple (optional) | 0-2 per wheel | - |
| STEMMA QT / Qwiic cables | as needed | - |
All three boards have STEMMA QT / Qwiic connectors - just daisy-chain with cables, no soldering for I2C.
I2C (STEMMA QT cables, no soldering):
- Feather QT port → MCP9601 (inner) → MCP9601 (outer) → MLX90640
Power and CAN (soldering required):
Feather Terminal Block Vehicle
---------------------- -------
CAN-H → CAN-H
CAN-L → CAN-L
GND → GND
Feather USB Header Power Source
------------------ ------------
5V pad → +12V via regulator or USB power
Add 120Ω termination resistor between CAN-H and CAN-L if sensor is at end of bus.
| Device | Address |
|---|---|
| MLX90640 thermal camera | 0x33 |
| MCP9601 inner brake temp | 0x65 |
| MCP9601 outer brake temp | 0x66 |
# Clone and build
git clone https://github.com/SamSkjord/pico-tyre-temp.git
cd pico-tyre-temp
git checkout canbus-feather
# Build (sets up toolchain paths automatically)
./build.sh
# Output: build/thermal_tyre_canbus.uf2- Hold BOOTSEL button and plug in USB
- Copy
build/thermal_tyre_canbus.uf2to RPI-RP2 drive - Feather reboots and starts running
Each sensor must be configured for its wheel position before installation.
-
Connect sensor to computer via USB
-
Open serial terminal (115200 baud):
screen /dev/tty.usbmodem* 115200 # macOS/Linux
-
Configure wheel position:
WHEEL=0 # Front Left (CAN base 0x100) WHEEL=1 # Front Right (CAN base 0x120) WHEEL=2 # Rear Left (CAN base 0x140) WHEEL=3 # Rear Right (CAN base 0x160) -
Verify configuration:
STATUSOutput shows wheel ID, CAN base ID, and board serial number.
-
Optional settings:
EMISSIVITY=95 # Tyre emissivity (default 95 = 0.95) MODE=0 # 0=tyre detect, 4/8/16=fixed channels BROADCAST=1 # 1=auto 10Hz, 0=on-request only CANSPEED=500 # CAN bus speed (125/250/500/1000 kbps)
Configuration persists in flash across power cycles.
When setting up multiple sensors:
- Label each board with its wheel position before configuration
- Configure one at a time via USB before installation
- Record board serial numbers (shown in STATUS output) for remote configuration later
Once installed, sensors can be reconfigured via CAN without USB access.
-
Discover sensors - Send CONFIG_REQUEST (0x7F1):
CAN TX: ID=0x7F1 Data=[0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]Each sensor responds with CONFIG_RESPONSE (0x7F2) containing its wheel ID and serial.
-
Change wheel ID - Send CONFIG_SET_WHEEL (0x7F0):
CAN TX: ID=0x7F0 Data=[serial0, serial1, serial2, serial3, new_wheel_id, 0, 0, 0]Where serial0-3 are the first 4 bytes of the target board's serial number.
-
Change other settings - Similar format for 0x7F4-0x7F6:
- CONFIG_SET_EMISSIVITY (0x7F4): [...serial..., emissivity, 0, 0, 0]
- CONFIG_SET_MODE (0x7F5): [...serial..., mode, 0, 0, 0]
- CONFIG_SET_BROADCAST (0x7F6): [...serial..., broadcast, 0, 0, 0]
After configuration, verify CAN output:
- Connect CAN analyser to vehicle bus
- Load
pico_tyre_temp.dbcfor signal decoding - Check messages appear at correct base ID for wheel position:
- FL: 0x100-0x11C
- FR: 0x120-0x13C
- RL: 0x140-0x15C
- RR: 0x160-0x17C
To reset a sensor to defaults (wheel 0, emissivity 95%, auto broadcast):
RESET
| Wheel | ID | CAN Base | USB Command |
|---|---|---|---|
| Front Left | 0 | 0x100 | WHEEL=0 |
| Front Right | 1 | 0x120 | WHEEL=1 |
| Rear Left | 2 | 0x140 | WHEEL=2 |
| Rear Right | 3 | 0x160 | WHEEL=3 |
A DBC file (pico_tyre_temp.dbc) is included for use with CAN analysis tools (Vector CANalyzer, SavvyCAN, etc).
Each wheel has a unique base CAN ID with message offsets:
| Message | Offset | Rate | Content |
|---|---|---|---|
| TYRE_TEMPS | +0x00 | 10Hz | Left/Centre/Right temps, lateral gradient (detect mode) |
| TYRE_DETECTION | +0x01 | 10Hz | Detection status, confidence, width (detect mode) |
| BRAKE_TEMPS | +0x02 | 10Hz | Inner/outer brake temps, status |
| STATUS | +0x10 | 1Hz | FPS, firmware version, frame number |
| RAW_CH_A | +0x18 | 10Hz | Channels 0-3 temps (channel modes) |
| RAW_CH_B | +0x19 | 10Hz | Channels 4-7 temps (8/16 channel modes) |
| RAW_CH_C | +0x1A | 10Hz | Channels 8-11 temps (16 channel mode) |
| RAW_CH_D | +0x1B | 10Hz | Channels 12-15 temps (16 channel mode) |
| FRAME_DATA | +0x1C | On-demand | Full 768-pixel thermal frame (256 segments) |
| CAN ID | Message | Direction |
|---|---|---|
| 0x7F0 | CONFIG_SET_WHEEL | RX |
| 0x7F1 | CONFIG_REQUEST | RX |
| 0x7F2 | CONFIG_RESPONSE | TX |
| 0x7F3 | FRAME_REQUEST | RX |
| 0x7F4 | CONFIG_SET_EMISSIVITY | RX |
| 0x7F5 | CONFIG_SET_MODE | RX |
| 0x7F6 | CONFIG_SET_BROADCAST | RX |
| 0x7F7 | DATA_REQUEST | RX |
| 0x7F8 | CONFIG_SET_CANSPEED | RX |
| CAN ID | Message |
|---|---|
| 0x100 | Tyre temperatures |
| 0x101 | Tyre detection |
| 0x102 | Brake temperatures |
| 0x110 | Status |
| 0x11C | Frame data segments |
All multi-byte values are little-endian. Temperatures in tenths of °C (int16).
TYRE_TEMPS (8 bytes)
Byte 0-1: Left median temp (int16, tenths °C)
Byte 2-3: Centre median temp (int16, tenths °C)
Byte 4-5: Right median temp (int16, tenths °C)
Byte 6-7: Lateral gradient (int16, tenths °C)
BRAKE_TEMPS (8 bytes)
Byte 0-1: Inner brake temp (int16, tenths °C)
Byte 2-3: Outer brake temp (int16, tenths °C)
Byte 4: Inner sensor status (0=OK, 1=Disconnected, 2=Error, 3=Not Found)
Byte 5: Outer sensor status
Byte 6-7: Reserved
Request a full 768-pixel thermal frame by sending FRAME_REQUEST (0x7F3):
Byte 0: Target wheel ID (0-3, or 0xFF for any)
Byte 1-7: Reserved
The board responds with 256 FRAME_DATA segments (Base + 0x1C):
Byte 0-1: Segment index (0-255, uint16)
Byte 2-3: Pixel N+0 temp (int16, tenths °C)
Byte 4-5: Pixel N+1 temp (int16, tenths °C)
Byte 6-7: Pixel N+2 temp (int16, tenths °C)
Pixels are sent in row-major order (24 rows x 32 columns = 768 pixels). At 8 segments per main loop iteration (~10Hz), full transfer takes ~3.2 seconds.
Configure boards remotely via CAN (requires board serial number from CONFIG_RESPONSE):
CONFIG_SET_EMISSIVITY (0x7F4)
Byte 0-3: Target board serial (first 4 bytes)
Byte 4: Emissivity (10-100)
Byte 5-7: Reserved
CONFIG_SET_MODE (0x7F5)
Byte 0-3: Target board serial (first 4 bytes)
Byte 4: Channel mode (0=detect, 4, 8, or 16)
Byte 5-7: Reserved
CONFIG_SET_BROADCAST (0x7F6)
Byte 0-3: Target board serial (first 4 bytes)
Byte 4: Broadcast mode (0=request, 1=auto)
Byte 5-7: Reserved
CONFIG_SET_CANSPEED (0x7F8)
Byte 0-3: Target board serial (first 4 bytes)
Byte 4-5: CAN speed in kbps (125, 250, 500, or 1000, uint16 LE)
Byte 6-7: Reserved
Note: CAN speed change requires reboot to take effect.
CONFIG_RESPONSE (0x7F2) - sent in reply to CONFIG_REQUEST (0x7F1):
Byte 0: Wheel ID
Byte 1-4: Board serial (first 4 bytes)
Byte 5: Firmware version
Byte 6: Emissivity (0-100)
Byte 7: Channel mode
| Color | Meaning |
|---|---|
| Green | Normal operation |
| Yellow | Warning (high gradient, thermocouple issue) |
| Red | Sensor error (MLX90640 disconnected) |
| Blue | CAN bus error |
| Purple | Initialization/configuration mode |
| White flash | Heartbeat (every second) |
WHEEL=n Set wheel ID (0-3)
EMISSIVITY=n Set emissivity (10-100, e.g., 95 = 0.95)
MODE=n Set channel mode (0=detect, 4, 8, or 16)
BROADCAST=n Set broadcast mode (0=on-request, 1=auto)
CANSPEED=n Set CAN bus speed (125/250/500/1000 kbps, requires reboot)
STATUS Show current configuration
RESET Reset to defaults
HELP Show available commands
The sensor supports two types of operation: automatic tyre detection (mode 0) or fixed channel modes (4/8/16).
Automatically detects tyre edges and reports left/centre/right zone temperatures. Best when the sensor has a clear view of the full tyre width with background on both sides.
CAN messages at 10Hz: TYRE_TEMPS, TYRE_DETECTION, BRAKE_TEMPS
Divides the sensor's 32 columns into equal groups spanning the full tyre width. Each channel reports the median temperature from the middle 4 rows (rows 10-13) of its column group. Use these modes when sensor mounting doesn't suit automatic detection.
4-channel mode - 8 columns per channel:
Columns: 0-7 8-15 16-23 24-31
|--------|--------|--------|--------|
Channel: 0 1 2 3
|------------- RAW_CH_A -------------|
CAN messages at 10Hz: RAW_CH_A, BRAKE_TEMPS
8-channel mode - 4 columns per channel:
Columns: 0-3 4-7 8-11 12-15 16-19 20-23 24-27 28-31
|------|------|------|------|------|------|------|------|
Channel: 0 1 2 3 4 5 6 7
|-------- RAW_CH_A ---------|--------- RAW_CH_B --------|
CAN messages at 10Hz: RAW_CH_A, RAW_CH_B, BRAKE_TEMPS
16-channel mode - 2 columns per channel:
Columns: 0-1 2-3 4-5 6-7 | 8-9 10-11 12-13 14-15 | 16-17 18-19 20-21 22-23 | 24-25 26-27 28-29 30-31
Channel: 0 1 2 3 | 4 5 6 7 | 8 9 10 11 | 12 13 14 15
|--- RAW_CH_A ----||---- RAW_CH_B --------||---- RAW_CH_C ----------||---- RAW_CH_D --------|
CAN messages at 10Hz: RAW_CH_A, RAW_CH_B, RAW_CH_C, RAW_CH_D, BRAKE_TEMPS
| Mode | Columns/Channel | CAN Messages (10Hz) |
|---|---|---|
| 0 (detect) | Auto zones | TYRE_TEMPS, TYRE_DETECTION, BRAKE_TEMPS |
| 4 | 8 | RAW_CH_A, BRAKE_TEMPS |
| 8 | 4 | RAW_CH_A, RAW_CH_B, BRAKE_TEMPS |
| 16 | 2 | RAW_CH_A-D, BRAKE_TEMPS |
All modes also send STATUS at 1Hz.
| Mode | Description |
|---|---|
| 1 (Auto) | Continuous 10Hz transmission (default) |
| 0 (Request) | Only transmit when DATA_REQUEST (0x7F7) received |
In request mode, send DATA_REQUEST with target wheel ID to trigger a single data transmission:
Byte 0: Target wheel ID (0-3, or 0xFF for all)
Byte 1-7: Reserved
- Watchdog timer - 2s timeout, auto-recovery from hangs
- Sensor recovery - Automatic re-initialization on failure (max 10 attempts)
- Integer overflow protection - Safe temperature conversions
- Flash verification - Confirms writes before proceeding
- Bounds checking - Validates all inputs and array accesses
| Component | Path | Notes |
|---|---|---|
| Pico SDK | ~/pico-sdk |
Clone from raspberrypi/pico-sdk |
| ARM Toolchain | ~/arm-toolchain/arm-gnu-toolchain-14.2.rel1-* |
Official ARM release |
Note: Homebrew's arm-none-eabi-gcc won't work due to missing newlib.
export PATH="$HOME/arm-toolchain/arm-gnu-toolchain-14.2.rel1-darwin-arm64-arm-none-eabi/bin:$PATH"
export PICO_SDK_PATH="$HOME/pico-sdk"
./tools/download_mlx_library.sh # First time only
rm -rf build && mkdir build && cd build
cmake .. && make -j4pico-tyre-temp/
├── main.c # Main application loop
├── thermal_algorithm.c/h # Tyre detection algorithm
├── can_bus.cpp/h # CAN controller driver (C++)
├── config.c/h # Flash configuration storage
├── status_led.c/h # NeoPixel driver (PIO)
├── mcp9601.c/h # Thermocouple driver
├── communication.c/h # USB serial output
├── ws2812.pio # PIO program for NeoPixel
├── build.sh # Build script
├── CMakeLists.txt # CMake configuration
├── CLAUDE.md # Developer documentation
└── mlx90640/ # Melexis driver library
"ERROR: Could not detect MLX90640 sensor"
- Check wiring (3.3V, not 5V!)
- Verify 4.7kΩ pull-ups on SDA/SCL
- Check I2C address (0x33)
CAN messages not appearing
- Verify CAN-H/CAN-L wiring
- Check 120Ω termination
- Confirm baud rate matches receiver (default 500 kbps, check with STATUS command)
Red LED stays on
- Sensor error - check MLX90640 connection
- System will attempt recovery every 5 seconds
Blue LED
- CAN bus error - check wiring and termination
- May indicate bus-off condition
- New hardware - Adafruit RP2040 CAN Bus Feather
- CAN output - 500 kbps CAN 2.0A replaces I2C slave
- Multi-board - Support for 4 boards on same bus
- Brake temps - MCP9601 thermocouple support
- NeoPixel LED - Color-coded status indication
- Safety features - Overflow protection, sensor recovery, watchdog
- Raspberry Pi Pico with I2C slave output
- See
mainbranch for original version
MIT License
- MLX90640 driver from Melexis
- CAN driver based on pico-mcp2515
- Built with Raspberry Pi Pico SDK