Fix wake-from-sleep, RAM overrun, and peripheral hangs in AVR firmware
Three defects prevented the board from working at all: - INT0/INT1 were falling-edge triggered. Edge detection needs the I/O clock, which SLEEP_MODE_PWR_DOWN stops, so neither the reed switch nor the RTC alarm could wake the MCU. Both are now low-level triggered (the only asynchronous mode), and each handler masks its own interrupt while the source is still asserted so the low level cannot retrigger. The reed and RTC pins also get their pull-ups; they were explicitly driven low. - Statics were 1440 B of 2048 with a 538 B main frame, so the first NFC read ran the stack into .data. Shrank the oversized buffers and made the NFC scratch buffer static: statics 1440 -> 1038 B, main frame -> 204 B. - The FIFO was filled after entering TX mode with TxStart = FifoNotEmpty, so transmission began before the payload was loaded. Load in standby. Memory safety: clamp the unvalidated RX length (len - 4 underflowed to >=252 into a 60-byte buffer), fix writes one byte past DATA_BUFFER_65, fix the diameter copy length in st25dv.c, NUL-terminate remove_spaces, and bounds-check the NDEF parser (dropping its tag-sized VLA and its unchecked payload_length decrements). Hangs: add bail-outs to every peripheral poll loop - RFM69 mode/TX/RX waits, the EEPROM WIP poll, all six I2C TWINT spins, and the ADC. The LDO is cut before sleeping, so a slow peripheral hung the firmware with no watchdog armed. Correctness: boot no longer wipes the EEPROM spool; the replayed packet is sent once and deleted only on success; short ATOMIC_BLOCK sections replace the blanket cli() that lost reed pulses during the radio window; the I2C rail comes up before the RTC is touched; sleep_bod_disable() moves into the timed sequence with the sleep race closed; sei() no longer runs inside ISRs; REG_FDEV_MSB was 0x06 twice so deviation was 0; ADC uses return ADC and a /64 prescaler; SS1 is an output before SPE is set. VAL_DATA_MODUL_OOK was misnamed rather than wrong - 0x01 lands in ModulationShaping, not ModulationType - so the register value is unchanged and on-air behavior still matches the base station. Verified: builds clean under -Wall -Wextra on both gnu17 and c23. Not yet run on hardware. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YVJKatfeMJjAmuH9KYiLuv
This commit is contained in:
+94
-49
@@ -19,80 +19,96 @@
|
||||
#include <avr/sleep.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <util/atomic.h>
|
||||
#include <util/delay.h>
|
||||
#define WAIT_FOREVER \
|
||||
while (1) { \
|
||||
_delay_ms(100); \
|
||||
};
|
||||
|
||||
|
||||
#if ITERATING
|
||||
#define SEND_INTERVAL 1
|
||||
#else
|
||||
#define SEND_INTERVAL 15
|
||||
#endif
|
||||
|
||||
#define WHEEL_COUNT_SLOTS 15
|
||||
|
||||
// Erased EEPROM reads back as 0xFF; anything else is a real page count.
|
||||
#define EEPROM_LAST_PAGE_UNINIT 0xFF
|
||||
|
||||
uint16_t self_value;
|
||||
tx_rx_data_struct CRAP;
|
||||
uint16_t main_counter;
|
||||
volatile uint8_t is_debouncing = 0;
|
||||
volatile bool increment_minute_index = false;
|
||||
volatile bool increment_wheel_count = false;
|
||||
volatile uint8_t index_wheel_count = 0;
|
||||
volatile uint16_t total_wheel_counts[15];
|
||||
volatile uint16_t total_wheel_counts[WHEEL_COUNT_SLOTS];
|
||||
|
||||
RTC_RFM69_STATUS rtc_rfm69_status;
|
||||
|
||||
ISR(INT0_vect) {
|
||||
cli();
|
||||
// The RTC holds INTB low until its flag registers are read, and this is a
|
||||
// level-triggered interrupt, so mask it here and let main re-arm it once
|
||||
// the RTC has released the line.
|
||||
EIMSK &= ~(1 << INT0);
|
||||
#if DO_UART
|
||||
uart_sendString("\t\t\t\tMINUTE INTERRUPT\n");
|
||||
#endif
|
||||
increment_minute_index = true;
|
||||
// sei();
|
||||
}
|
||||
|
||||
ISR(INT1_vect) {
|
||||
cli();
|
||||
|
||||
#if ITERATING
|
||||
increment_minute_index = true;
|
||||
#endif
|
||||
|
||||
|
||||
#if DO_UART
|
||||
uart_sendString("\t\t\t\tREED INTERRUPT\n");
|
||||
#endif
|
||||
|
||||
|
||||
if (!is_debouncing) {
|
||||
total_wheel_counts[index_wheel_count]++;
|
||||
if (index_wheel_count < WHEEL_COUNT_SLOTS) {
|
||||
total_wheel_counts[index_wheel_count]++;
|
||||
}
|
||||
is_debouncing = 1;
|
||||
// Mask INT1 for the debounce window: the magnet holds the reed closed
|
||||
// (and the pin low) for far longer than one revolution's worth of
|
||||
// bounce, and a level-triggered interrupt would retrigger continuously.
|
||||
EIMSK &= ~(1 << INT1);
|
||||
wdt_isr_enable();
|
||||
}
|
||||
// sei();
|
||||
}
|
||||
|
||||
ISR(WDT_vect) {
|
||||
cli();
|
||||
is_debouncing = 0;
|
||||
wdt_isr_disable();
|
||||
// sei();
|
||||
reed_interrupt_enable();
|
||||
}
|
||||
|
||||
void start_sleeping() {
|
||||
void start_sleeping(void) {
|
||||
spi_eeprom_select(false);
|
||||
spi_rfm69_select(false);
|
||||
rfid_set_low_power_down(true);
|
||||
rfid_set_i2c_power(false);
|
||||
ldo_set_state(false);
|
||||
_delay_ms(10);
|
||||
sleep_bod_disable();
|
||||
|
||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||
sleep_enable();
|
||||
|
||||
cli();
|
||||
// Don't sleep through work that arrived while we were dropping the rails.
|
||||
// Testing the flag with interrupts off, then sei() immediately before
|
||||
// sleep_cpu(), is the avr-libc idiom that closes that race -- and
|
||||
// sleep_bod_disable() is a timed sequence, so it belongs here and not
|
||||
// before sleep_enable() where it had no effect at all.
|
||||
if (!increment_minute_index) {
|
||||
sleep_enable();
|
||||
sleep_bod_disable();
|
||||
sei();
|
||||
sleep_cpu();
|
||||
sleep_disable();
|
||||
}
|
||||
sei();
|
||||
sleep_cpu();
|
||||
}
|
||||
|
||||
uint16_t get_battery_reading() {
|
||||
uint16_t get_battery_reading(void) {
|
||||
adc_Enable();
|
||||
adc_GetConversion(14);
|
||||
adc_GetConversion(14);
|
||||
@@ -149,12 +165,17 @@ int main(void) {
|
||||
uart_sendString("Initialized RFM69\n");
|
||||
#endif
|
||||
|
||||
write_last_page_value(0);
|
||||
// Only initialise the spool pointer when it has never been written --
|
||||
// clearing it unconditionally would discard every unsent message across a
|
||||
// reset.
|
||||
if (get_last_page() == EEPROM_LAST_PAGE_UNINIT) {
|
||||
write_last_page_value(0);
|
||||
}
|
||||
#if DO_UART
|
||||
uart_sendString("Set up last page value for SPI flash\n");
|
||||
#endif
|
||||
|
||||
for (uint8_t c = 0; c < 15; c++) {
|
||||
for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) {
|
||||
total_wheel_counts[c] = 0;
|
||||
}
|
||||
|
||||
@@ -203,27 +224,43 @@ int main(void) {
|
||||
spi_eeprom_select(false);
|
||||
|
||||
start_sleeping();
|
||||
cli();
|
||||
|
||||
if (increment_minute_index) {
|
||||
// Short critical sections around the shared variables only. The old
|
||||
// blanket cli() stayed in force through the whole radio/EEPROM
|
||||
// sequence, so every reed pulse in that multi-second window was lost.
|
||||
bool minute_elapsed;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
minute_elapsed = increment_minute_index;
|
||||
increment_minute_index = false;
|
||||
}
|
||||
|
||||
if (minute_elapsed) {
|
||||
#if DO_UART
|
||||
uart_sendString("In minute index\n");
|
||||
#endif
|
||||
increment_minute_index = false;
|
||||
is_debouncing = 0;
|
||||
index_wheel_count += 1;
|
||||
uint8_t slot;
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
if (index_wheel_count < WHEEL_COUNT_SLOTS) {
|
||||
index_wheel_count += 1;
|
||||
}
|
||||
slot = index_wheel_count;
|
||||
}
|
||||
|
||||
// The I2C rail has to be back up before we touch the RTC: sleeping
|
||||
// dropped both the LDO and the tag's supply.
|
||||
ldo_set_state(true);
|
||||
rfid_set_i2c_power(true);
|
||||
_delay_ms(1);
|
||||
|
||||
rtc_read_interrupt_register();
|
||||
rtc_read_status_register();
|
||||
|
||||
rtc_read_interrupt_register();
|
||||
rtc_read_status_register();
|
||||
// Reading the flags releases INTB, so INT0 can safely be re-armed.
|
||||
minute_interrupt_enable();
|
||||
|
||||
if (index_wheel_count >= SEND_INTERVAL) {
|
||||
index_wheel_count = 0;
|
||||
ldo_set_state(true);
|
||||
if (slot >= SEND_INTERVAL) {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { index_wheel_count = 0; }
|
||||
rfm69_init();
|
||||
rfid_set_i2c_power(true);
|
||||
rfid_set_low_power_down(false);
|
||||
_delay_ms(1);
|
||||
IDENTIFIER = get_nugget_data();
|
||||
@@ -233,11 +270,21 @@ int main(void) {
|
||||
rtc_rfm69_status = set_time_from_rfm69(IDENTIFIER);
|
||||
}
|
||||
|
||||
// Snapshot and clear the counters in one critical section so
|
||||
// a reed pulse landing mid-packet is neither lost nor double
|
||||
// counted.
|
||||
uint16_t counts_snapshot[WHEEL_COUNT_SLOTS];
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) {
|
||||
counts_snapshot[c] = total_wheel_counts[c];
|
||||
total_wheel_counts[c] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate wheel counts message
|
||||
reset_txrx_struct(&TX_DATA);
|
||||
get_battery_reading();
|
||||
TX_DATA = generate_wheel_counts_message(
|
||||
IDENTIFIER, rtc_read_time(), get_battery_reading(), total_wheel_counts);
|
||||
IDENTIFIER, rtc_read_time(), get_battery_reading(), counts_snapshot);
|
||||
|
||||
#if DO_UART
|
||||
uart_sendString("TX DATA Sent\n");
|
||||
@@ -253,28 +300,26 @@ int main(void) {
|
||||
write_struct_to_last_page(TX_DATA);
|
||||
}
|
||||
|
||||
for (uint8_t c = 0; c < 15; c++) {
|
||||
total_wheel_counts[c] = 0;
|
||||
}
|
||||
if ((result == DATA_SEND_SUCCESS) && (get_last_page() > 0)) {
|
||||
reset_txrx_struct(&TX_DATA);
|
||||
TX_DATA = read_struct_last_page();
|
||||
TX_DATA.flags = MSG_RESENT_COUNTS;
|
||||
_delay_ms(250);
|
||||
result = send_message(TX_DATA);
|
||||
#if DO_UART
|
||||
if (result == DATA_NOT_SENT) {
|
||||
uart_sendString(" SPI not sent\n");
|
||||
}
|
||||
#endif
|
||||
|
||||
send_message(TX_DATA);
|
||||
delete_last_page();
|
||||
#if DO_UART
|
||||
uart_sendString("TX DATA From SPI Memory\n");
|
||||
uart_print_tx_rx_data(TX_DATA);
|
||||
#endif
|
||||
|
||||
// Only drop the spooled page once it is actually
|
||||
// acknowledged, otherwise a failed retry loses the data.
|
||||
if (result == DATA_SEND_SUCCESS) {
|
||||
delete_last_page();
|
||||
}
|
||||
#if DO_UART
|
||||
else {
|
||||
uart_sendString(" SPI not sent\n");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user