Bed monitor
uni · SIT329 · T2 2026A prototype bed monitor for aged care. A small board reads a temperature sensor, a pressure mat and a button, blinks an LED for a fever, and calls a nurse when someone taps the mat five times in two seconds. A dashboard on a laptop shows each bed and logs everything to a database.
Task 6.1D in SIT329, Advanced Embedded Systems, at Deakin. The unit is about programming an ARM Cortex-M0+ through its registers, so nothing in the running program calls digitalWrite, analogRead or attachInterrupt. The timer, ADC and USART classes come from my own earlier tasks in the unit.
The task sheet asked for temperature every 5 minutes, a red LED blinking once a second in a fever, an alarm above 39 C or below 35 C, bed occupancy from a force sensor, a HELP alarm on five taps in two seconds that a nurse can reset, and a dashboard that logs to a database.
Both analogue sensors share one ADC, so a mutex guards it and only the temperature task ever moves the multiplexer. The button interrupt stamps each press and drops the time into a queue for the mat task. Once a second the link task builds a text frame with a checksum. Each byte goes out on D6, comes back on D7 through a jumper wire, and is then passed on over USB. The dashboard checks the checksum, stores the frame and can send commands back.
Four tasks, each woken on a fixed period with vTaskDelayUntil so a late run does not push the next one back. The LED is not a task. A hardware timer interrupt toggles it, so nothing the scheduler does can slow the blink.
The board prints its own setup at boot. Here is part of that banner from 24 Aug 2026. The register values and the baud rate are read back off the hardware, not copied from the source.
link SERCOM0 CTRLA = 0x40200106, CTRLB = 0x30000, BAUD = 63019 asked for 115200 baud, got 115219, error 165 ppm against a limit of 20000 ppm D6 to D7 loopback: up pins A0 thermistor, D4 mat, D6 TxD, D7 RxD, D8 red LED, D2 tap scheduler tick 1000 Hz, priorities 0 to 8, heap free 13912 of 14336 mat 5 at 10 ms, command 4 at 50 ms, link 3 at 1 s, temp 1 at 5 min
The same mat has to report occupancy and catch a call for help. Occupancy only changes after a reading has stayed past its threshold for 3 seconds: on above 400 g, off below 250 g. A tap has to rise and fall back within 400 ms, or it is not a tap.
Here is the rule on the real board. The dashboard stored every frame from this session in SQLite. The mat was held down twice, then tapped quickly.
view data
| seconds | force |
|---|---|
| 0 | 0 g |
| 1 | 0 g |
| 2 | 0 g |
| 3 | 0 g |
| 4 | 0 g |
| 5 | 0 g |
| 6 | 0 g |
| 7 | 0 g |
| 8 | 0 g |
| 9 | 0 g |
| 10 | 0 g |
| 11 | 0 g |
| 12 | 0 g |
| 13 | 319 g |
| 14 | 804 g |
| 15 | 985 g |
| 16 | 1,029 g |
| 17 | 1,038 g |
| 18 | 1,041 g |
| 19 | 1,041 g |
| 20 | 1,041 g |
| 21 | 1,041 g |
| 22 | 1,041 g |
| 23 | 1,041 g |
| 24 | 1,041 g |
| 25 | 1,041 g |
| 26 | 1,041 g |
| 27 | 1,041 g |
| 28 | 1,041 g |
| 29 | 1,041 g |
| 30 | 1,041 g |
| 31 | 121 g |
| 32 | 17 g |
| 33 | 0 g |
| 34 | 0 g |
| 35 | 0 g |
| 36 | 10 g |
| 37 | 206 g |
| 38 | 33 g |
| 39 | 520 g |
| 40 | 894 g |
| 41 | 1,008 g |
| 42 | 1,034 g |
| 43 | 1,040 g |
| 44 | 1,041 g |
| 45 | 1,041 g |
| 46 | 1,041 g |
| 47 | 1,041 g |
| 48 | 1,041 g |
| 49 | 1,041 g |
| 50 | 1,041 g |
| 51 | 1,041 g |
| 52 | 1,041 g |
| 53 | 1,041 g |
| 54 | 1,041 g |
| 55 | 1,041 g |
| 56 | 1,041 g |
| 57 | 1,041 g |
| 58 | 1,041 g |
| 59 | 212 g |
| 60 | 208 g |
| 61 | 47 g |
| 62 | 5 g |
| 63 | 0 g |
| 64 | 0 g |
| 65 | 0 g |
| 66 | 0 g |
| 67 | 124 g |
| 68 | 124 g |
| 69 | 18 g |
| 70 | 0 g |
| 71 | 0 g |
| 72 | 0 g |
| 73 | 0 g |
| 74 | 0 g |
| 75 | 0 g |
| 76 | 0 g |
| 77 | 0 g |
| 78 | 0 g |
| 79 | 0 g |
| 80 | 0 g |
view data
| seconds | taps |
|---|---|
| 0 | 0 |
| 1 | 0 |
| 2 | 0 |
| 3 | 0 |
| 4 | 0 |
| 5 | 0 |
| 6 | 0 |
| 7 | 0 |
| 8 | 0 |
| 9 | 0 |
| 10 | 0 |
| 11 | 0 |
| 12 | 0 |
| 13 | 3 |
| 14 | 3 |
| 15 | 3 |
| 16 | 3 |
| 17 | 3 |
| 18 | 3 |
| 19 | 3 |
| 20 | 3 |
| 21 | 3 |
| 22 | 3 |
| 23 | 3 |
| 24 | 3 |
| 25 | 3 |
| 26 | 3 |
| 27 | 3 |
| 28 | 3 |
| 29 | 3 |
| 30 | 3 |
| 31 | 3 |
| 32 | 3 |
| 33 | 3 |
| 34 | 3 |
| 35 | 3 |
| 36 | 4 |
| 37 | 5 |
| 38 | 6 |
| 39 | 6 |
| 40 | 6 |
| 41 | 6 |
| 42 | 6 |
| 43 | 6 |
| 44 | 6 |
| 45 | 6 |
| 46 | 6 |
| 47 | 6 |
| 48 | 6 |
| 49 | 6 |
| 50 | 6 |
| 51 | 6 |
| 52 | 6 |
| 53 | 6 |
| 54 | 6 |
| 55 | 6 |
| 56 | 6 |
| 57 | 6 |
| 58 | 6 |
| 59 | 6 |
| 60 | 6 |
| 61 | 7 |
| 62 | 7 |
| 63 | 7 |
| 64 | 7 |
| 65 | 7 |
| 66 | 7 |
| 67 | 10 |
| 68 | 14 |
| 69 | 14 |
| 70 | 14 |
| 71 | 14 |
| 72 | 14 |
| 73 | 14 |
| 74 | 14 |
| 75 | 14 |
| 76 | 14 |
| 77 | 14 |
| 78 | 14 |
| 79 | 14 |
| 80 | 14 |
The same alarm in the raw serial stream from 7 Sep. TAPS climbs and ALM goes from 02 (hypothermia) to 06 (hypothermia and HELP).
$AC,SEQ=482,UP=510000,BED=1,T=1934,TB=HYPO,MAT=21,F=0,OCC=0,ALM=02,TAPS=0,ST=27*6E $AC,SEQ=483,UP=511000,BED=1,T=1934,TB=HYPO,MAT=4095,F=120,OCC=0,ALM=02,TAPS=1,ST=27*67 $AC,SEQ=484,UP=512000,BED=1,T=1934,TB=HYPO,MAT=4095,F=357,OCC=0,ALM=02,TAPS=4,ST=27*64 $AC,SEQ=485,UP=513000,BED=1,T=1934,TB=HYPO,MAT=4095,F=405,OCC=0,ALM=06,TAPS=8,ST=27*6C
The SQLite file holds 1,216 frames from the real board, logged on three days: 24 Aug, 7 Sep and 8 Sep 2026. None has the stub bit set, so none was injected. The sequence number jumps six times, each time at a reconnect or a reboot, and never skips within a connection. Within a connection the temperature only changes at uptime 301 s and 3001 s, which is the 5-minute task running on time. The board also reported its own counters after 51 minutes up.
13|2026-09-07T04:47:01.890Z|1|stats|up 3083.0 s, TC3 ran 6166 times (2.00 a second), EIC took 3 edges and dropped 2 as bounce, busy waits adc 0 state 0 link 0, heap free 6704
The monitor logic is plain C with no hardware calls, so it compiles on a laptop and runs against 184 checks. The register classes run against mock registers: 630 more checks. A simulator built from the same C writes 2,761 frames over 46 minutes of virtual time, and 60 dashboard checks parse every one and match the Python checksum to the C one. I re-ran all of it on 27 Oct 2026 and all 874 passed. The board build was 44,484 of 262,144 bytes of flash on 7 Sep.
Calibrate the sensors before building on them. The force numbers still come from two placeholder points, so the grams are not real weights. The thermistor constants were never measured off the part either. And with no mat plugged in, D4 floats: it read as about 450 g, an occupied bed and about seven taps a second, and nothing downstream can tell that from a real reading. A pull-down resistor and a fault check for that case would come first next time.

