The problem
In an EV, the vehicle control unit must know quickly if the BMS, motor controller or charger has stopped communicating, because acting on stale data is dangerous. Heartbeat messages and timeouts are the standard way to detect this. This project builds the mechanism and measures how fast and how reliably it detects different failure types.
Objectives
- Define a heartbeat message with node ID, state and alive counter.
- Send heartbeats at a fixed period from each node.
- Detect missing, frozen-counter and restarted nodes on the monitor.
- Drive a safe reaction (relay open, alarm) on node loss.
- Measure detection time for each failure type.
What you need
From the EV Embedded Development Kit
- STM32F103C8T6 microcontroller unit (Arm Cortex-M3)
- CAN bus interface with screw terminal
- RGB LEDs and a buzzer
- General output relay (NO / COM / NC)
- 16×2 LCD with contrast control
- 4×3 matrix keypad
Software
- STM32CubeIDE
- Embedded C with STM32 HAL
Extra parts
- A second DIYguru EV Embedded Development Kit — Node-loss detection needs at least two nodes on the bus; the board has a single CAN node. The second kit acts as the monitored node (a USB-CAN adapter running a Python heartbeat script could stand in).
- Twisted-pair cable with two 120 Ω termination resistors — To connect the two screw terminals as a short, terminated bus, unless termination is already fitted on the boards.
Key concepts
- Heartbeat
- A small message a node sends periodically to say it is alive and in which state. Its absence is the signal of a problem.
- Timeout
- The time after which a missing heartbeat counts as lost, usually a few periods so that one delayed frame does not cause a false alarm.
- Alive counter
- A value that increments in every heartbeat. If frames keep arriving but the counter stops changing, the sending software has frozen even though its CAN hardware is still running.
- Safe state
- The predefined reaction when a node is lost, for example opening a relay or limiting power, chosen so that the system stays safe without that node.
Step-by-step guide
- 1
Define the heartbeat
Choose IDs such as 0x700 plus the node number, a 100 ms period, and a 2-byte payload: node state (boot, operational, fault) and a 4-bit or 8-bit alive counter. Define detection rules: lost if no heartbeat for 300 ms, frozen if the counter has not changed for three frames, restarted if the state returns to boot or the counter jumps back to zero. Define the reaction for each.
Checkpoint: A specification table of messages, timeouts, detection rules and reactions.
- 2
Wire and configure the bus
Connect the two kits' CAN screw terminals with twisted pair and check about 60 Ω between CAN_H and CAN_L with both unpowered. Enable the CAN, LED, buzzer, relay, LCD and keypad DIP switches. Configure bxCAN at 500 kbit/s with the same timing on both boards and test each in loopback mode first. Set up both nodes with a shared header of IDs and timing so they cannot disagree.
Checkpoint: Each board receives its own frames in loopback; after wiring, both receive each other's test frames.
- 3
Send heartbeats
On each node, a timer interrupt every 100 ms sets a flag; the main loop builds the heartbeat with the current state and counter and sends it with HAL_CAN_AddTxMessage. Send a boot heartbeat immediately after start-up. Add keypad keys on the monitored node to simulate failures: stop sending, freeze the counter, or reset the node. Make sure the counter wraps cleanly from its maximum back to zero and that the monitor treats this as normal, not as a restart.
Checkpoint: The other node logs heartbeats at 100 ms intervals and the simulated failures change what it receives.
- 4
Build the monitor
On the monitor node, filter for heartbeat IDs and in the receive callback store arrival time, state and counter per node. In a 10 ms check task, compare time since last heartbeat with the timeout, and track the counter. Implement a per-node state machine: UNKNOWN, ALIVE, LOST, FROZEN, RESTARTED. Keep the timing check independent of the receive interrupt, so a node that stops sending is detected even when no frames arrive at all. Show the time since the last heartbeat on the LCD as a debug value.
Checkpoint: The LCD shows ALIVE for the other node and changes state when each failure is simulated.
- 5
Implement the reaction
On LOST or FROZEN, open the general output relay as the safe-state action, light the red LED and sound the buzzer. On RESTARTED, show an amber warning and require the node to send operational heartbeats again before returning to ALIVE. Make recovery deliberate: close the relay only after the node has been alive for a set time and a key is pressed.
Checkpoint: Every simulated failure opens the relay and recovery requires the defined conditions.
- 6
Handle the monitor's own bus problems
The monitor might be the node that fails. Enable bus-off and error interrupts and treat a bus-off state as loss of all nodes, entering the safe state. Show error counters on a debug screen. Test by disconnecting the monitor's own CAN wires. Also detect the case where the monitor sends but never receives anything, which can indicate a wiring fault on its receive side. Decide how long the monitor stays in the safe state after the bus returns, and document that choice.
Checkpoint: Disconnecting the monitor from the bus puts it in the safe state rather than leaving the relay closed.
- 7
Measure detection times
Put a GPIO marker on the monitored node at the instant a failure is triggered and another on the monitor when it detects it; measure the time between them with an oscilloscope, or with a common timer if both markers go to one board. Repeat each failure type twenty times and report minimum, average and maximum detection time. Explain the spread you see from the heartbeat period and the check-task period.
Checkpoint: A table of detection times for lost, frozen and restarted nodes, all within the specified limits.
- 8
Check false alarms
Run for at least an hour without failures, while also loading the bus with other traffic from the second kit, and count any false detections. Then shorten the timeout to two periods and repeat, to see how close to the edge the design is. Choose and justify the final timeout. Log every detection with its time and type, so any false alarm can be traced to what was happening on the bus at that moment.
Checkpoint: Zero false alarms in an hour at the chosen timeout, with the trade-off against detection time explained.
- 9
Document
Write up the heartbeat specification, state machine, reactions, detection-time and false-alarm results, wiring and firmware structure. Compare your approach briefly with the heartbeat concept in CANopen. Include a timing diagram that shows a heartbeat stopping, the timeout counting down and the relay opening. Add the wiring diagram, DIP-switch settings and the shared header file. Close with a short section on how the scheme would extend to a vehicle with several ECUs, including how timeouts would be set for nodes with different heartbeat periods.
Checkpoint: A report linking every requirement to a test result.
Testing & validation
- Heartbeat period measured on the receiving node.
- Detection time for each failure type over repeated trials.
- False-alarm count over an hour with background traffic.
- Safe state on monitor bus-off.
Expected results
A working heartbeat and node-loss detection system with measured detection times, a safe-state reaction through the relay and documented false-alarm performance.
Take it further
- Add more nodes using a USB-CAN adapter that emulates several heartbeats.
- Log node-loss events with RTC timestamps to SPI flash.
- Implement the CANopen heartbeat consumer format.
- Add a checksum to protect the heartbeat payload.
Viva and review questions
Why use a timeout of several periods?
One frame can be delayed by arbitration or lost to an error and retransmitted; waiting several periods avoids false alarms while still detecting a real loss quickly.
What does an alive counter detect that a timeout does not?
A node whose CAN hardware keeps sending the same frame while its application software has stopped.
Why should the monitor enter the safe state on its own bus-off?
Without the bus it cannot know whether other nodes are alive, so it must assume the worst.