STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_main.cpp
Go to the documentation of this file.
1// End-to-end tests for Idle sniff behaviour (SetRxDutyCycle, DS 13.1.7).
2//
3// Three properties under test:
4// 1. idle_arm_wake: a long-preamble Arm is always heard in sniff mode.
5// 2. Average radio current in Idle is under 0.3 mA.
6// 3. The radio is re-armed (listening) after every received packet.
7//
8// fake_air models the listen windows; Sx1262Setup is exercised against the
9// same TxChip fake used in test_sx1262_setup.
10
11#include <string.h>
12
15#include "fake_air.h"
16#include "unity.h"
17
18using namespace radio_link;
19
20// Minimal fake chip (same pattern as TxChip in test_sx1262_setup).
21namespace {
22
23class IdleChip : public SpiBus {
24public:
25 static constexpr uint8_t kStatus = 0x24; // STBY_RC, no error
26 static constexpr unsigned kMaxCmds = 128;
27
28 void reset() {
29 never_ready = false;
30 n_cmds = 0;
31 device_errors = 0;
32 tx_clamp = 0xC8;
33 tx_modulation = 0x01;
34 sync[0] = sync[1] = 0;
35 }
36
38 uint16_t device_errors;
40 unsigned n_cmds;
41 uint8_t cmds[kMaxCmds][12];
42 uint16_t lens[kMaxCmds];
43
44 int find(uint8_t opcode, int from = 0) const {
45 for (unsigned i = static_cast<unsigned>(from); i < n_cmds; ++i) {
46 if (cmds[i][0] == opcode)
47 return static_cast<int>(i);
48 }
49 return -1;
50 }
51
52 bool wait_busy() override {
53 return !never_ready;
54 }
55 void transfer(const uint8_t* tx, uint8_t* rx, uint16_t len) override {
56 if (n_cmds < kMaxCmds) {
57 memcpy(cmds[n_cmds], tx, len < 12 ? len : 12);
58 lens[n_cmds++] = len;
59 }
60 memset(rx, 0xEE, len);
61 rx[1] = kStatus;
62 const uint16_t addr =
63 len >= 3 ? static_cast<uint16_t>(tx[1] << 8 | tx[2]) : 0;
64 switch (tx[0]) {
65 case 0x1D: // ReadRegister
66 rx[4] = addr == 0x08D8 ? tx_clamp
67 : addr == 0x0889 ? tx_modulation
68 : 0;
69 break;
70 case 0x0D: // WriteRegister
71 if (addr == 0x08D8)
72 tx_clamp = tx[3];
73 if (addr == 0x0889)
74 tx_modulation = tx[3];
75 if (addr == 0x0740)
76 memcpy(sync, tx + 3, 2);
77 break;
78 case 0x17: // GetDeviceErrors
79 rx[2] = static_cast<uint8_t>(device_errors >> 8);
80 rx[3] = static_cast<uint8_t>(device_errors);
81 break;
82 case 0x12: // GetIrqStatus — no TX in idle tests, always idle
83 rx[2] = rx[3] = 0;
84 break;
85 default:
86 break;
87 }
88 }
89};
90
91class IdleControl : public RadioControl {
92public:
93 bool rx_switch = false;
94 void set_rx_switch(bool receive) override {
95 rx_switch = receive;
96 }
97 void delay_ms(uint32_t) override {
98 }
99};
100
103
104} // namespace
105
106void setUp(void) {
107 chip.reset();
108 control = IdleControl();
109 fake_air_configure(33, 1000);
110}
111void tearDown(void) {
112}
113
114// 1. A long-preamble Arm (> sleep period) is always heard regardless of when
115// in the duty cycle the ground starts transmitting.
117 // SF9 / 125 kHz: symbol time ≈ 4.096 ms. 260 symbols ≈ 1065 ms > 1000 ms
118 // sleep.
119 const uint32_t preamble_ms = 1065;
120 const uint32_t period = 33 + 1000;
121 for (uint32_t offset = 0; offset < period; offset += 50) {
122 TEST_ASSERT_TRUE(fake_air_packet_heard(offset, preamble_ms));
123 }
124}
125
126// A short preamble (default 8 symbols ≈ 33 ms) can be missed when it falls
127// entirely within the sleep window. This test must fail if the guard is removed
128// (i.e., if short preambles were always allowed for Arm).
130 // Start 10 ms into the sleep window; 33 ms preamble ends at offset 43 ms,
131 // still inside sleep (33 + 10 = 43 < 33 + 1000).
132 TEST_ASSERT_FALSE(fake_air_packet_heard(33 + 10, 33));
133 // Middle of sleep: definitely missed.
134 TEST_ASSERT_FALSE(fake_air_packet_heard(500, 33));
135}
136
137// 2. Average radio current in Idle is under 0.3 mA.
139 // SX1262 DS p.16: 4.6 mA in LoRa 125 kHz RX (DC-DC), 1.2 µA in warm sleep.
140 const float avg = fake_air_average_current_ma(4.6f, 1.2f);
141 // ≈ 4.6 * 33/1033 + 0.0012 * 1000/1033 ≈ 0.147 mA
142 TEST_ASSERT_TRUE(avg < 0.3f);
143 TEST_ASSERT_FLOAT_WITHIN(0.05f, 0.147f, avg);
144}
145
146// 3. After a packet arrives the radio is re-armed (SetRxDutyCycle sent again),
147// so it does not go deaf. If rearm_rx() is never called the radio stays in
148// STDBY_RC and misses every subsequent packet.
151 TEST_ASSERT_EQUAL(SetupStep::Ok, radio.configure());
152 TEST_ASSERT_TRUE(radio.start_rx_duty_cycle(33, 1000));
153
154 // Simulate three RxDone events: each must restart the duty cycle.
155 for (int i = 0; i < 3; i++) {
156 chip.n_cmds = 0;
157 TEST_ASSERT_TRUE(radio.rearm_rx());
158 TEST_ASSERT_TRUE(chip.find(0x94) >= 0);
159 }
160}
161
162// If rearm_rx() is skipped the chip stays in STDBY_RC and no SetRxDutyCycle
163// is sent — this test demonstrates the failure mode.
166 radio.configure();
167 radio.start_rx_duty_cycle(33, 1000);
168 chip.n_cmds = 0;
169
170 // Caller deliberately does NOT call rearm_rx() after RxDone.
171 // No new 0x94 should appear.
172 TEST_ASSERT_EQUAL(-1, chip.find(0x94));
173}
174
175int main(void) {
176 UNITY_BEGIN();
182 return UNITY_END();
183}
int find(uint8_t opcode, int from=0) const
Definition test_main.cpp:44
void transfer(const uint8_t *tx, uint8_t *rx, uint16_t len) override
Definition test_main.cpp:55
void set_rx_switch(bool receive) override
Definition test_main.cpp:94
float fake_air_average_current_ma(float rx_ma, float sleep_ua)
Definition fake_air.c:33
void fake_air_configure(uint32_t rx_ms, uint32_t sleep_ms)
Definition fake_air.c:6
bool fake_air_packet_heard(uint32_t cycle_offset_ms, uint32_t preamble_ms)
Definition fake_air.c:11
int main()
static void test_average_idle_current_under_0_3_ma(void)
static void test_short_preamble_missed_in_sleep_window(void)
void setUp(void)
void tearDown(void)
static void test_radio_rearmed_after_every_rxdone(void)
static void test_idle_arm_wake_long_preamble_always_heard(void)
static void test_no_rearm_means_no_duty_cycle_command(void)
static FakeChip chip
Definition test_main.cpp:12