Why I Started Using This Tool
The SW alive heartbeat solved the
“is my program still running?” question, but only while I was watching the terminal. As soon
as I unplugged the serial cable and left the DE10-Nano running on its own, I was back to guessing. I wanted
a signal I could check with a glance from across the room, and the board already had the perfect one: a
single green LED, HPS_LED0, wired directly to the ARM processor. So the next task in the
project is HW alive, a heartbeat you can see.
What It Does
This episode is the header, hwalive.h. It follows the same shape as
swalive.h: an include
guard, the includes it needs, a control struct, and HWAlive_start /
HWAlive_stop declared with (void). The differences all come from touching real
hardware. Here's the whole file:
#ifndef HWALIVE_H
#define HWALIVE_H
#include <pthread.h>
#include <time.h>
#include "mytype.h"
/* HPS_LED0 through the Linux LED class (sysfs). */
#define HWALIVE_LED_DIR "/sys/class/leds/hps_led0"
#define HWALIVE_LED_BRIGHTNESS HWALIVE_LED_DIR "/brightness"
#define HWALIVE_LED_TRIGGER HWALIVE_LED_DIR "/trigger"
#define HWALIVE_INTERVAL_SEC 1u /* toggle once a second */
typedef struct hwalive_s
{
pthread_t thread_id;
volatile BOOLEAN running;
UINT32 interval_sec; /* time between LED toggles */
INT32 led_fd; /* open brightness file, -1 when closed */
BOOLEAN led_on; /* current LED state */
time_t start_time; /* CLOCK_MONOTONIC seconds at start */
UINT32 cycle_count; /* number of toggles so far */
} HWAlive;
INT32 HWAlive_start(void);
void HWAlive_stop(void);
#endif /* HWALIVE_H */
Test the LED from the shell first
Before writing any C, I checked that the LED actually responds, straight from a root shell on the board.
Linux exposes each LED as a folder of plain text files under /sys/class/leds:
# see which trigger is driving the LED (the active one is in [brackets])
cat /sys/class/leds/hps_led0/trigger
# take control away from the kernel
echo none > /sys/class/leds/hps_led0/trigger
# on, then off
echo 1 > /sys/class/leds/hps_led0/brightness
echo 0 > /sys/class/leds/hps_led0/brightness
If the LED lights up and goes dark, the C code only has to do the same thing: write
none to trigger once, then write 1 and 0 to
brightness in a loop. If /sys/class/leds/hps_led0 doesn't exist on your image,
ls /sys/class/leds will show you what the LED is called there.
The LED path is a #define
HWALIVE_LED_DIR holds the sysfs folder, and the other two macros build the file paths from
it. Writing two string literals next to each other, like HWALIVE_LED_DIR "/brightness", makes
the compiler glue them into one string, so there's no runtime string building. Switching to a different LED
or a different SD card image means changing one line at the top of the header.
The blink interval
Like SW alive, the timing lives in one place: HWALIVE_INTERVAL_SEC sets the default, and the
struct keeps it in interval_sec. At 1u, the LED toggles once a second, one
second on and one second off. That's slow enough to read from across the room and fast enough that you
can tell a blinking LED from a stuck one within a couple of seconds. The u suffix makes the
constant unsigned to match the UINT32 field.
The struct: four new fields
thread_id and running do exactly the same job as in SW alive. The rest is new:
-
led_fd: an open file descriptor for thebrightnessfile, and-1when it's closed. Opening it once in Start means Start can fail early with a clear error if the LED isn't there, and the blink loop doesn't have to reopen the file twice a second. It's anINT32becauseopen()returns a signed value and uses-1for failure. led_on: remembers whether the LED is currently on, so each cycle just flips it and writes the new value.start_time: when the task started, taken fromCLOCK_MONOTONIClike the updated SW alive task. That's why the header now includes<time.h>.cycle_count: how many times the LED has toggled, handy for a status line or for checking the loop kept its pace.
What Start and Stop will do
-
HWAlive_startwritesnoneto the LED'striggerso the kernel stops driving it, opensbrightnessintoled_fd, records the start time, and creates the thread. It returns non-zero if any step fails. -
HWAlive_stopclearsrunning, joins the thread, turns the LED off, and closes the file, settingled_fdback to-1.
Hardware outlives your threads
This was the new lesson. When a thread ends, it's gone, and so is everything it was doing. An LED stays however you left it. If the program exits while the LED happens to be on, it stays on forever, which looks exactly like a healthy board with a frozen program. So a hardware task has to clean up after itself, and Stop always leaves the LED off.
Why not use the kernel's heartbeat trigger?
cat trigger lists a heartbeat option, and
echo heartbeat > trigger gives you a lovely double-pulse blink with zero lines of C. The
catch is that the kernel does the blinking. It proves the kernel is alive, and it keeps blinking
happily after my program has crashed. The whole point of HW alive is that the LED only blinks while my own
thread is running, so if the program dies, the LED stops.
Final Verdict
Copying the SW alive pattern made this header quick to write, and that's the point of having a pattern. The
new lesson was that hardware outlives your threads. When a thread ends it's gone, but an LED stays however
you left it, so a hardware task has to clean up after itself. I also learned why I can't just use the
kernel's built-in heartbeat trigger: it proves the kernel is alive, not my program. If you're on a
DE10-Nano, try the echo commands first. Seeing that little LED respond to a line in the shell
makes the C code much less mysterious.