STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
idle.c
Go to the documentation of this file.
1#include "idle.h"
2
3#include <string.h>
4
5/* PA0 = VDD * 10k / (22k + 10k) = VDD * 0.3125, buffered, 16-bit ADC with a
6 * 3.32 V reference (DEVICE_CHECKS.md on
7 * pranay/self-test-confirmed-requirements, #137).
8 * mV = counts / 65535 * 3320 / 0.3125 = counts * 10624 / 65535.
9 * 65535 * 10624 fits in uint32_t, so this is exact integer math. */
10#define IDLE_ADC_FULL_SCALE 65535U
11#define IDLE_MV_AT_FULL_SCALE 10624U
12
13uint16_t idle_counts_to_mv(uint32_t raw_counts) {
14 if (raw_counts > IDLE_ADC_FULL_SCALE) {
15 return 0U;
16 }
17 /* Round to nearest. */
18 uint32_t mv =
19 (raw_counts * IDLE_MV_AT_FULL_SCALE + IDLE_ADC_FULL_SCALE / 2U) /
21 return (uint16_t)mv;
22}
23
24static bool config_valid(const idle_config_t* c) {
25 if (c->heartbeat_interval_ms < 1000U ||
26 c->heartbeat_interval_ms > (uint32_t)INT32_MAX) {
27 return false;
28 }
30 return false;
31 }
32 if (c->callback_budget_ms == 0U) {
33 return false;
34 }
35 if (c->battery_fail_mv == 0U || c->battery_fail_mv >= c->battery_warn_mv) {
36 return false;
37 }
38 /* Recovery levels must stay below the 16-bit range. */
39 if ((uint32_t)c->battery_warn_mv + c->battery_hysteresis_mv > 0xFFFFU) {
40 return false;
41 }
42 return true;
43}
44
45static bool port_valid(const idle_port_t* p) {
46 return p->read_battery != NULL && p->on_usb_power != NULL &&
47 p->send_heartbeat != NULL && p->poll_command != NULL;
48}
49
51 const idle_port_t* port, void* context) {
52 if (idle == NULL) {
53 return false;
54 }
55 memset(idle, 0, sizeof(*idle));
56 if (config == NULL || port == NULL || !config_valid(config) ||
57 !port_valid(port)) {
58 return false;
59 }
60 idle->config = *config;
61 idle->port = *port;
62 idle->context = context;
63 idle->last.previous_send = SELF_TEST_PENDING;
64 idle->last.battery = IDLE_BATT_UNKNOWN;
65 idle->battery_band = IDLE_BATT_UNKNOWN;
66 idle->initialized = true;
67 return true;
68}
69
70bool idle_enter(idle_t* idle, uint32_t now_ms) {
71 if (idle == NULL || !idle->initialized) {
72 return false;
73 }
74 /* First wake after entry sends a heartbeat immediately. */
75 idle->next_heartbeat_ms = now_ms;
76 idle->entered = true;
77 return true;
78}
79
80/* Plain thresholds, used when there is no OK/WARN/FAIL history. */
81static idle_battery_state_t band_fresh(const idle_config_t* c, uint16_t mv) {
82 if (mv < c->battery_fail_mv) {
83 return IDLE_BATT_FAIL;
84 }
85 if (mv < c->battery_warn_mv) {
86 return IDLE_BATT_WARN;
87 }
88 return IDLE_BATT_OK;
89}
90
91/*
92 * Getting worse is immediate; getting better needs the reading to clear the
93 * threshold by the hysteresis margin. This stops a pack sitting near 7.0 V
94 * from flipping OK/WARN on every heartbeat.
95 */
97 idle_battery_state_t previous,
98 uint16_t mv) {
99 uint32_t warn_recover =
100 (uint32_t)c->battery_warn_mv + c->battery_hysteresis_mv;
101 uint32_t fail_recover =
102 (uint32_t)c->battery_fail_mv + c->battery_hysteresis_mv;
103 switch (previous) {
104 case IDLE_BATT_WARN:
105 if (mv < c->battery_fail_mv) {
106 return IDLE_BATT_FAIL;
107 }
108 return mv >= warn_recover ? IDLE_BATT_OK : IDLE_BATT_WARN;
109 case IDLE_BATT_FAIL:
110 if (mv >= warn_recover) {
111 return IDLE_BATT_OK;
112 }
113 return mv >= fail_recover ? IDLE_BATT_WARN : IDLE_BATT_FAIL;
114 case IDLE_BATT_OK:
115 default:
116 return band_fresh(c, mv);
117 }
118}
119
120static void read_battery(idle_t* idle, uint32_t now_ms) {
121 self_test_battery_t sample = idle->port.read_battery(
122 idle->context, now_ms, idle->config.callback_budget_ms);
123
124 if (sample.reply.result != SELF_TEST_PASS || !sample.valid ||
126 idle->last.supply_mv = 0U;
127 idle->last.battery = IDLE_BATT_INVALID;
128 return;
129 }
130
131 uint16_t mv = idle_counts_to_mv(sample.raw_counts);
132 idle->last.supply_mv = mv;
133
134 /* On USB, PA0 measures USB supply, not the pack. Never classify it as
135 * battery health; keep the hysteresis memory for when USB is removed. */
136 if (idle->port.on_usb_power(idle->context)) {
137 idle->last.battery = IDLE_BATT_USB;
138 return;
139 }
140
141 idle->battery_band = band_next(&idle->config, idle->battery_band, mv);
142 idle->last.battery = idle->battery_band;
143}
144
145static void send_heartbeat(idle_t* idle, uint32_t now_ms) {
146 idle->last.sequence += 1U; /* wraps modulo 2^32 by design */
147 idle->last.uptime_ms = now_ms;
148
149 self_test_reply_t reply = idle->port.send_heartbeat(
150 idle->context, &idle->last, idle->config.callback_budget_ms);
151
152 /* No retry within Idle: retries cost battery and the next heartbeat is
153 * only one interval away. Failures are counted and reported instead. */
154 idle->last.previous_send = reply.result;
155 if (reply.result == SELF_TEST_PASS) {
156 idle->last.consecutive_send_failures = 0U;
157 } else if (idle->last.consecutive_send_failures < UINT8_MAX) {
158 idle->last.consecutive_send_failures += 1U;
159 }
160}
161
162/* Signed distance from a to b in modular milliseconds (valid for |gap| < 2^31).
163 */
164static int32_t ms_until(uint32_t now_ms, uint32_t target_ms) {
165 return (int32_t)(target_ms - now_ms);
166}
167
170 /* Without idle_enter() the schedule is unset; after ~24 days of uptime
171 * that would read as a heartbeat weeks away. Refuse instead. */
172 if (idle == NULL || !idle->initialized || !idle->entered) {
173 return result;
174 }
175
176 /* 1. Commands first: an Arm request must not wait behind a heartbeat. */
177 for (unsigned i = 0; i < IDLE_MAX_COMMANDS_PER_WAKE; ++i) {
178 idle_command_t cmd = idle->port.poll_command(idle->context);
179 if (cmd == IDLE_CMD_NONE) {
180 break;
181 }
182 if (cmd == IDLE_CMD_ARM) {
184 return result;
185 }
186 /* IDLE_CMD_DISARM (or anything unknown): already disarmed, ignore. */
187 }
188
189 /* 2. Heartbeat when due. Time decides, not the wake source, so a radio
190 * wake never delays the next heartbeat and an early LSI wake within
191 * tolerance still counts as on time. */
192 int32_t remaining = ms_until(now_ms, idle->next_heartbeat_ms);
193 if (remaining <= (int32_t)idle->config.wake_tolerance_ms) {
194 /* Battery before transmit: LoRa TX current would sag the reading. */
195 read_battery(idle, now_ms);
196 send_heartbeat(idle, now_ms);
197 result.heartbeat_sent = true;
198 /* Schedule from now, not from the missed deadline: a long gap
199 * produces one heartbeat, not a burst of catch-up heartbeats. */
200 idle->next_heartbeat_ms = now_ms + idle->config.heartbeat_interval_ms;
201 remaining = (int32_t)idle->config.heartbeat_interval_ms;
202 }
203
204 /* 3. Sleep until the next heartbeat, rounded up to whole RTC seconds. */
205 uint32_t sleep_s = ((uint32_t)remaining + 999U) / 1000U;
206 result.event = IDLE_EVENT_SLEEP;
207 result.sleep_s = sleep_s == 0U ? 1U : sleep_s;
208 return result;
209}
210
212 if (idle == NULL || !idle->initialized) {
213 return NULL;
214 }
215 return &idle->last;
216}
static const emmc_port_t port
Definition emmc_stm32.c:210
static bool config_valid
Definition main.cpp:66
int32_t p
Definition flight_e2e.c:78
#define NULL
Definition usbd_def.h:44
idle_result_t idle_on_wake(idle_t *idle, uint32_t now_ms)
Definition idle.c:168
const idle_heartbeat_t * idle_last_heartbeat(const idle_t *idle)
Definition idle.c:211
#define IDLE_ADC_FULL_SCALE
Definition idle.c:10
#define IDLE_MV_AT_FULL_SCALE
Definition idle.c:11
static idle_battery_state_t band_next(const idle_config_t *c, idle_battery_state_t previous, uint16_t mv)
Definition idle.c:96
static void send_heartbeat(idle_t *idle, uint32_t now_ms)
Definition idle.c:145
bool idle_enter(idle_t *idle, uint32_t now_ms)
Definition idle.c:70
uint16_t idle_counts_to_mv(uint32_t raw_counts)
Definition idle.c:13
bool idle_init(idle_t *idle, const idle_config_t *config, const idle_port_t *port, void *context)
Definition idle.c:50
static bool port_valid(const idle_port_t *p)
Definition idle.c:45
static int32_t ms_until(uint32_t now_ms, uint32_t target_ms)
Definition idle.c:164
static void read_battery(idle_t *idle, uint32_t now_ms)
Definition idle.c:120
static idle_battery_state_t band_fresh(const idle_config_t *c, uint16_t mv)
Definition idle.c:81
@ IDLE_EVENT_ARM_REQUESTED
Definition idle.h:38
@ IDLE_EVENT_SLEEP
Definition idle.h:35
@ IDLE_EVENT_ERROR
Definition idle.h:41
#define IDLE_MAX_COMMANDS_PER_WAKE
Definition idle.h:21
idle_command_t
Definition idle_port.h:25
@ IDLE_CMD_NONE
Definition idle_port.h:26
@ IDLE_CMD_ARM
Definition idle_port.h:27
idle_battery_state_t
Definition idle_port.h:31
@ IDLE_BATT_FAIL
Definition idle_port.h:35
@ IDLE_BATT_INVALID
Definition idle_port.h:37
@ IDLE_BATT_USB
Definition idle_port.h:36
@ IDLE_BATT_OK
Definition idle_port.h:33
@ IDLE_BATT_WARN
Definition idle_port.h:34
@ IDLE_BATT_UNKNOWN
Definition idle_port.h:32
static idle_t idle
Definition idle_task.c:26
static const idle_config_t config
Definition idle_task.c:17
json_pointer result
Definition json.hpp:14064
static self_test_reply_t reply(self_test_result_t result, int32_t diagnostic)
@ SELF_TEST_PENDING
@ SELF_TEST_PASS
uint32_t callback_budget_ms
Definition idle.h:27
uint16_t battery_fail_mv
Definition idle.h:29
uint32_t wake_tolerance_ms
Definition idle.h:26
uint16_t battery_hysteresis_mv
Definition idle.h:30
uint32_t heartbeat_interval_ms
Definition idle.h:24
uint16_t battery_warn_mv
Definition idle.h:28
Definition idle.h:51
self_test_reply_t reply
self_test_result_t result