Files
wheel_rfm69_counter/avr_code/i2c.c
T
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

164 lines
4.0 KiB
C

#include "i2c.h"
#include <util/twi.h>
// Every one of these loops used to spin forever. The peripheral rail is cut
// before sleeping, so a device that is slow or absent on wake would otherwise
// hang the firmware with no watchdog reset armed.
static bool i2c_wait_twint(void)
{
for (uint16_t attempts = 0; attempts < I2C_TIMEOUT_LOOPS; attempts++) {
if (TWCR & (1 << TWINT)) {
return true;
}
}
return false;
}
void i2c_init(void)
{
// Set SCL and SDA as inputs (automatically done by TWI hardware)
TWSR = 0; // Prescaler = 1
TWBR = (uint8_t)((F_CPU / F_SCL - 16) / 2); // Set bitrate register
TWCR = (1 << TWEN);
}
// Start i2c communication
uint8_t i2c_start(uint8_t address)
{
TWCR = (1 << TWSTA) | (1 << TWINT) | (1 << TWEN); // Send START condition
if (!i2c_wait_twint())
return 1;
TWDR = address; // Load address into data register
TWCR = (1 << TWINT) | (1 << TWEN); // Send address
if (!i2c_wait_twint())
return 1;
uint8_t status = TWSR & 0xF8;
if (status != 0x18 && status != 0x40)
return 1;
return 0;
// return (TWSR & 0xF8); // Return the status code (check if ACK
// received)
}
uint8_t write_one_byte(uint8_t device_addr, uint8_t register_addr, uint8_t data)
{
return write_n_bytes(device_addr, register_addr, &data, 1);
}
uint8_t write_n_bytes(uint8_t device_addr, uint8_t register_addr, uint8_t* data, uint8_t n_bytes)
{
I2C_START_WRITE(device_addr);
if (i2c_write(register_addr)) {
i2c_stop();
return 1;
}
for (uint8_t i = 0; i < n_bytes; i++) {
if (i2c_write(data[i])) {
i2c_stop();
return 1;
}
}
i2c_stop();
return 0;
}
uint8_t read_one_byte_no_err_register(uint8_t device_addr, uint8_t register_addr)
{
uint8_t data;
read_one_byte(device_addr, register_addr, &data);
return data;
}
uint8_t read_one_byte(uint8_t device_addr, uint8_t register_addr, uint8_t* data)
{
return read_n_bytes(device_addr, register_addr, data, 1);
}
uint8_t read_n_bytes(uint8_t device_addr, uint8_t register_addr, uint8_t* data, uint8_t n_bytes)
{
I2C_START_WRITE(device_addr);
i2c_write(register_addr);
I2C_START_READ(device_addr);
for (uint8_t i = 0; i < (n_bytes - 1); i++) {
data[i] = i2c_read_ack();
}
data[n_bytes - 1] = i2c_read_nack();
i2c_stop();
return 0;
}
uint8_t read_one_byte_16bit_addr_no_err_register(uint8_t device_addr, uint16_t register_addr)
{
uint8_t data;
read_one_byte_16bit_addr(device_addr, register_addr, &data);
return data;
}
uint8_t read_one_byte_16bit_addr(uint8_t device_addr, uint16_t register_addr, uint8_t* data)
{
return read_n_bytes_16bit_addr(device_addr, register_addr, data, 1);
}
uint8_t
read_n_bytes_16bit_addr(uint8_t device_addr, uint16_t register_addr, uint8_t* data, uint8_t n_bytes)
{
I2C_START_WRITE(device_addr);
// i2c_write(0xAE);
i2c_write(register_addr >> 8);
i2c_write(register_addr & 0xFF);
I2C_START_READ(device_addr);
for (uint8_t i = 0; i < (n_bytes - 1); i++) {
data[i] = i2c_read_ack();
}
data[n_bytes - 1] = i2c_read_nack();
i2c_stop();
return 0;
}
// Stop i2c communication
void i2c_stop(void)
{
TWCR = (1 << TWSTO) | (1 << TWINT) | (1 << TWEN); // Send STOP condition
for (uint16_t attempts = 0; attempts < I2C_TIMEOUT_LOOPS; attempts++) {
if (!(TWCR & (1 << TWSTO))) {
return; // STOP complete
}
}
}
uint8_t i2c_read_ack(void)
{
TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWEA);
if (!i2c_wait_twint())
return 0xFF;
return TWDR;
}
uint8_t i2c_write(uint8_t data)
{
// Load data into TWDR
TWDR = data;
TWCR = (1 << TWEN) | (1 << TWINT);
if (!i2c_wait_twint())
return 1;
if ((TWSR & 0xF8) != TW_MT_DATA_ACK)
return 1; // Check ACK
return 0;
}
uint8_t i2c_read_nack(void)
{
TWCR = (1 << TWEN) | (1 << TWINT);
if (!i2c_wait_twint())
return 0xFF;
return TWDR;
}