Files
thebears ed476473b6 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
2026-08-31 22:54:56 -04:00

154 lines
4.5 KiB
C

/*
* File: rfm69.h
* Author: thebears
*
* Created on December 11, 2024, 2:05 PM
*/
#include "defines.h"
#include "spi.h"
#include "states.h"
#include "uart.h"
#include <avr/io.h>
#include <stdbool.h>
#include <string.h>
#include <util/delay.h>
#ifndef RFM69_H
#define RFM69_H
#ifdef __cplusplus
extern "C" {
#endif
#define MODE_READY (spi_read_rfm69_rt(REG_IRQ_FLAGS1) & VAL_IRQ_FLAGS1_MODEREADY)
#define MODE_NOT_READY (!MODE_READY)
#define RX_PAYLOAD_READY (spi_read_rfm69_rt(REG_IRQ_FLAGS2) & VAL_IRQ_FLAGS2_RX_PAYLOADREADY)
#define RX_PAYLOAD_NOT_READY (!RX_PAYLOAD_READY)
#define TX_SENT (spi_read_rfm69_rt(REG_IRQ_FLAGS2) & VAL_IRQ_FLAGS2_TX_SENT)
#define TX_NOT_SENT (!TX_SENT)
// Bail-out for every RFM69 poll loop: an absent or unpowered radio must not
// hang the firmware, since no watchdog reset is armed.
#define RFM69_TIMEOUT_MS 100U
#define REG_FIFO 0x00
#define REG_FREQ_MSB 0x07
#define REG_FREQ_MIDDLE_SB 0x08
#define REG_FREQ_LSB 0x09
#define REG_FIFO_THRESH 0x3c
#define REG_TEST_DAGC 0x6f
#define REG_SYNC_CONFIG 0x2e
#define REG_SYNC_VALUE_1 0x2f
#define REG_OP_MODE 0x01
#define REG_DATA_MODUL 0x02
#define REG_BITRATE_MSB 0x03
#define REG_BITRATE_LSB 0x04
#define REG_FDEV_MSB 0x05
#define REG_FDEV_LSB 0x06
#define REG_RX_BW 0x19
#define REG_AFC_BW 0x1A
#define REG_PACKET_CONFIG_1 0x37
#define REG_PREAMBLE_MSB 0x2C
#define REG_PREAMBLE_LSB 0x2D
#define REG_PA_LEVEL 0x11
#define REG_TEST_PA1 0x5A
#define REG_TEST_PA2 0x5C
#define REG_IRQ_FLAGS1 0x27
#define REG_IRQ_FLAGS2 0x28
#define REG_RSSI_VALUE 0x24
#define REG_OCP 0x13
// Over-current protection must be off while the PA boost registers are set,
// per the datasheet's high-power (+20 dBm) sequence.
#define VAL_OCP_OFF 0x0F
#define VAL_OCP_ON 0x1A
#define VAL_FDEV_MSB 0x10
#define VAL_FDEV_LSB 0x00
#define VAL_TEST_DAGC_DEFAULT 0x30
#define VAL_DATA_PACKET_MODE 0x00
#define VAL_BITRATE_250kbps_MSB 0x00
#define VAL_BITRATE_250kbps_LSB 0x80
#define VAL_DATA_MODUL_FSK 0x00 // RegDataModul ModulationType is bits 4:3
#define VAL_MODUL_SHAPING_GAUSS_BT_1_0 0x01
#define VAL_TX_START_FIFO_NOT_EMPTY 0x80
#define VAL_FIFO_LEVEL_INTERRUPT 0x0f
#define VAL_IRQ_FLAGS1_MODEREADY 0x80
#define VAL_IRQ_FLAGS2_TX_SENT 0x08
#define VAL_IRQ_FLAGS2_RX_PAYLOADREADY 0x04
#define VAL_OPMODE_MASK 0x1c
#define VAL_OPMODE_SLEEP 0x00
#define VAL_OPMODE_STDBY 0x04
#define VAL_OPMODE_TX 0x0c
#define VAL_OPMODE_RX 0x10
#define VAL_SYNCWORDS_ON 0x80
#define VAL_SYNCWORDS_SIZE_2_BYTES 0x08
#define VAL_PACKET_VARIABLE_LENGTH 0x80
#define VAL_PACKET_WHITENING 0x40
#define VAL_PACKET_CRCON 0x10
#define VAL_PA_PA1_ON 0x40
#define VAL_PA_PA2_ON 0x20
#define VAL_PA_20dB 0x1F
#define VAL_PALEVEL_PA1_ON 0x40
#define VAL_PALEVEL_PA2_ON 0x20
#define VAL_PALEVEL_PA1_OUTPUTPOWER 0x1f
#define VAL_TEST_PA1_NORMAL 0x55
#define VAL_TEST_PA2_NORMAL 0x70
#define VAL_TEST_PA1_BOOST 0x5d
#define VAL_TEST_PA2_BOOST 0x7c
#define RFM69_SPI_WRITE 0x80
// Target frequency is 433 MHz.
// Equation is 32MHz/2^19 = 61.03515625. 433 MHz/61.03515625 = 7,094,272
// 7,094,272 = 01101100 01000000 00000000 = 0x6C 0x40 0x00
// Target frequency is 434.0 MHz.
// Equation is 32MHz/2^19 = 61.03515625. 434 MHz/61.03515625 = 7,110,656
// 7,110,656 = 01101100 01000000 00000000 = 0x6C 0x80 0x00
#define VAL_FREQ_433MHz_MSB 0x6c
#define VAL_FREQ_433MHz_MID_SB 0x80
#define VAL_FREQ_433MHz_LSB 0x00
DATA_SEND_STATUS send_message(tx_rx_data_struct tx_data);
uint8_t spi_read_rfm69_rt(uint8_t reg);
uint8_t spi_write_rfm69_rt(uint8_t reg, uint8_t val);
uint8_t spi_write_rfm69_multiple_rt(uint8_t reg, const char* vals, uint8_t len);
void set_rfm69_power_amp_boost(void);
void set_rfm69_power_amp_normal(void);
tx_rx_data_struct rfm69_read_msg(void);
void reset_txrx_struct(tx_rx_data_struct* s);
void rfm69_write_msg(tx_rx_data_struct txrxd);
void set_rfm69_mode(uint8_t mode);
bool wait_rfm69_mode_ready(void);
void set_rfm69_tx_mode(void);
bool wait_tx_sent(void);
bool wait_rx_payload_ready(void);
void reset_rfm69(void);
void set_rfm69_rx_mode(void);
void set_rfm69_standby(void);
void set_rfm69_sleep(void);
void set_rfm69_idle(void);
void rfm69_init(void);
bool wait_rx_payload_ready_timeout(uint16_t attempts);
uint8_t hash(const char* str, uint8_t min, uint8_t max);
uint32_t hash_3bytes(unsigned const char* str, uint8_t str_len);
void uart_print_tx_rx_data(tx_rx_data_struct tx_rx_print);
tx_rx_data_struct generate_wheel_counts_message(
identifier_results idd, time_struct time, uint16_t battery_value, volatile uint16_t counts[15]);
#ifdef __cplusplus
}
#endif
#endif /* RFM69_H */