Compare commits

..

3 Commits

Author SHA1 Message Date
thebears 089be9564b fixes 2026-08-31 23:12:28 -04:00
thebears 2152b8f727 Merge remote-tracking branch 'origin/main' 2026-08-31 23:04:25 -04:00
thebears bbdcc1e623 Refactor firmware for clarity; no functional changes
- main.c: file-header comment describing the hardware and operation, logic
  split into named phases (sleep_until_interrupt, wake_peripheral_rails,
  take_counts_snapshot, send_wheel_counts_report, handle_minute_alarm,
  init_all_hardware); shared state renamed to say what it is and made static.
- rfm69.c: reorganized into six labeled sections; cond_1/2/3 and hash scratch
  globals replaced by a reply_acknowledges() helper with clear locals; packet
  layout and every init register write documented.
- LOG() macro (compiled out when DO_UART is off) replaces the #if DO_UART
  blocks that obscured the logic.
- Drivers: file-header comments; named RTC_REG_*/RTC_ALM_MASK_BIT constants;
  EEPROM spool scheme documented; ADC_CHANNEL_BANDGAP named; repeated pin
  if/else helpers collapsed to SET_PIN_TO().
- Removed unused globals/buffers and commented-out code; ran clang-format
  with the project style.

Register writes and radio protocol are byte-identical. Builds clean under
-Wall -Wextra on gnu17 and c23; flash 13028 -> 12830 B, static RAM
1038 -> 999 B.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YVJKatfeMJjAmuH9KYiLuv
2026-08-31 23:04:00 -04:00
27 changed files with 936 additions and 1801 deletions
+37 -41
View File
@@ -11,10 +11,10 @@
int8_t adc_Initialize(void) int8_t adc_Initialize(void)
{ {
//REFS VAL_0x01; ADLAR disabled; MUX adc0; // REFS VAL_0x01; ADLAR disabled; MUX adc0;
ADMUX = 0x40; ADMUX = 0x40;
//ACME disabled; ADTS VAL_0x00; // ACME disabled; ADTS VAL_0x00;
ADCSRB = 0x00; ADCSRB = 0x00;
ADCSRA = (1 << ADEN) | ADC_PRESCALER_64; ADCSRA = (1 << ADEN) | ADC_PRESCALER_64;
@@ -22,66 +22,62 @@ int8_t adc_Initialize(void)
return 0; return 0;
} }
// Power (PRR clock gate) and ADEN are managed together, so the ADC draws
// nothing between readings. Enable rewrites the full config because register
// access is unreliable while the clock is gated.
void adc_Disable(void) void adc_Disable(void)
{ {
ADCSRA &= ~(1 << ADEN); ADCSRA &= ~(1 << ADEN);
power_adc_disable();
} }
void adc_Enable(void) void adc_Enable(void)
{ {
ADCSRA |= (1 << ADEN); power_adc_enable();
ADCSRA = (1 << ADEN) | ADC_PRESCALER_64;
} }
void adc_StartConversion(uint8_t channel) void adc_StartConversion(uint8_t channel)
{ {
if (channel == 0) if (channel == 0) {
{
ADMUX=0b01000000; ADMUX = 0b01000000;
} else if (channel == ADC_CHANNEL_BANDGAP) {
// ADMUX=0b01001110;
ADMUX = (0x01 << REFS0) | (0 << ADLAR) | (0x0e << MUX0);
} else {
ADMUX &= ~0x0f;
ADMUX |= channel;
} }
else if (channel == 14) _delay_us(ADC_SETTLE_US);
{ ADCSRA |= (1 << ADSC);
// ADMUX=0b01001110;
ADMUX=(0x01 << REFS0) | (0<<ADLAR) | (0x0e << MUX0);
}
else
{
ADMUX &= ~0x0f;
ADMUX |= channel;
}
_delay_us(ADC_SETTLE_US);
ADCSRA |= (1 << ADSC);
} }
bool adc_IsConversionDone(void) bool adc_IsConversionDone(void) { return ((ADCSRA & (1 << ADIF))); }
{
return ((ADCSRA & (1 << ADIF)));
}
uint16_t adc_GetConversionResult(void) uint16_t adc_GetConversionResult(void)
{ {
// ADC reads ADCL then ADCH in the right order. Reading the two volatile // ADC reads ADCL then ADCH in the right order. Reading the two volatile
// registers in one expression leaves the order unspecified, and taking ADCH // registers in one expression leaves the order unspecified, and taking ADCH
// first breaks the data-register lock and corrupts the result. // first breaks the data-register lock and corrupts the result.
return ADC; return ADC;
} }
uint16_t adc_GetConversion(uint8_t channel) uint16_t adc_GetConversion(uint8_t channel)
{ {
adc_StartConversion(channel); adc_StartConversion(channel);
// A conversion is 13 ADC clocks (~104 us at 125 kHz); bail out rather than // A conversion is 13 ADC clocks (~104 us at 125 kHz); bail out rather than
// hang if the ADC is disabled or its clock is gated off. // hang if the ADC is disabled or its clock is gated off.
uint16_t attempts = 0; uint16_t attempts = 0;
while (!adc_IsConversionDone()) { while (!adc_IsConversionDone()) {
if (++attempts > ADC_CONVERSION_TIMEOUT) { if (++attempts > ADC_CONVERSION_TIMEOUT) {
return 0; return 0;
} }
_delay_us(10); _delay_us(10);
} }
uint16_t res = adc_GetConversionResult(); uint16_t res = adc_GetConversionResult();
ADCSRA |= (1 << ADIF); ADCSRA |= (1 << ADIF);
return res; return res;
} }
+7 -5
View File
@@ -6,17 +6,20 @@
*/ */
#ifndef ADC_H #ifndef ADC_H
#define ADC_H #define ADC_H
#include "defines.h" #include "defines.h"
#include <avr/io.h> #include <avr/io.h>
#include <avr/power.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <util/delay.h> #include <util/delay.h>
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
// Mux channel 14 is the internal 1.1 V bandgap, used to infer battery voltage.
#define ADC_CHANNEL_BANDGAP 14
int8_t adc_Initialize(void); int8_t adc_Initialize(void);
void adc_Enable(void); void adc_Enable(void);
@@ -26,9 +29,8 @@ bool adc_IsConversionDone(void);
uint16_t adc_GetConversionResult(void); uint16_t adc_GetConversionResult(void);
uint16_t adc_GetConversion(uint8_t channel); uint16_t adc_GetConversion(uint8_t channel);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
#endif /* ADC_H */ #endif /* ADC_H */
+8 -10
View File
@@ -1,12 +1,10 @@
#include "defines.h" #include "defines.h"
unsigned char DATA_BUFFER_65[65]; unsigned char DATA_BUFFER_65[65];
uint8_t DATA_BUFFER_7[7]; uint8_t DATA_BUFFER_7[7];
// uint8_t DATA_BUFFER_254[255]; ndef_message NDEF_MSG;
unsigned char DATA_BUFFER_20[20]; trimmed_string_struct TRIMMED_STRING;
ndef_message NDEF_MSG; identifier_results IDENTIFIER;
trimmed_string_struct TRIMMED_STRING; tx_rx_data_struct TX_DATA;
identifier_results IDENTIFIER; tx_rx_data_struct RX_DATA;
tx_rx_data_struct TX_DATA; time_struct TIME;
tx_rx_data_struct RX_DATA;
time_struct TIME;
+27 -22
View File
@@ -6,33 +6,45 @@
*/ */
#ifndef DEFINES_H #ifndef DEFINES_H
#define DEFINES_H #define DEFINES_H
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include "stdint.h" #include "stdint.h"
#include <stdbool.h> #include <stdbool.h>
#include <stdlib.h>
#include <stdio.h> #include <stdio.h>
#include <stdlib.h>
#ifndef F_CPU #ifndef F_CPU
#define F_CPU 8000000UL // 8 MHz clock speed; prefer -DF_CPU=8000000UL in the build flags #define F_CPU 8000000UL // 8 MHz clock speed; prefer -DF_CPU=8000000UL in the build flags
#endif #endif
#define BAUD 38400 #define BAUD 38400
#define F_SCL 200000UL #define F_SCL 200000UL
// Build switches: DO_UART compiles in serial logging, ITERATING is a bench
// mode that reports every minute instead of every 15.
#define DO_UART true #define DO_UART true
#define ITERATING false #define ITERATING false
#define MIN(a,b) (((a)<(b))?(a):(b)) // Serial log line, compiled out entirely when DO_UART is off. Takes a string
#define MAX(a,b) (((a)>(b))?(a):(b)) // LITERAL only: PSTR keeps the text in flash instead of copying it into RAM at
// boot. The caller's file must include uart.h (directly or via another driver
// header). Use uart_sendString() for runtime strings.
#if DO_UART
#define LOG(msg) uart_sendString_P(PSTR(msg))
#else
#define LOG(msg) ((void)0)
#endif
#define MIN(a, b) (((a) < (b)) ? (a) : (b))
#define MAX(a, b) (((a) > (b)) ? (a) : (b))
// Shared scratch buffers (RAM is tight: 2 KB total). DATA_BUFFER_65 holds one
// EEPROM page plus a terminator; DATA_BUFFER_7 holds one RTC time readout.
extern unsigned char DATA_BUFFER_65[65]; extern unsigned char DATA_BUFFER_65[65];
extern uint8_t DATA_BUFFER_7[7]; extern uint8_t DATA_BUFFER_7[7];
//extern uint8_t DATA_BUFFER_254[255];
extern unsigned char DATA_BUFFER_20[20];
typedef struct { typedef struct {
uint8_t payload_len; uint8_t payload_len;
@@ -56,7 +68,6 @@ typedef struct {
} identifier_results; } identifier_results;
extern identifier_results IDENTIFIER; extern identifier_results IDENTIFIER;
typedef struct { typedef struct {
uint8_t len; uint8_t len;
uint8_t to; uint8_t to;
@@ -71,26 +82,23 @@ extern tx_rx_data_struct RX_DATA;
typedef struct { typedef struct {
uint8_t Second; // 0-59 uint8_t Second; // 0-59
uint8_t Minute; // 0-59 uint8_t Minute; // 0-59
uint8_t Hour; // 0-23 uint8_t Hour; // 0-23
uint8_t Wday; // Day of week, 1-7 (1 = Sunday) uint8_t Wday; // Day of week, 1-7 (1 = Sunday)
uint8_t Day; // 1-31 uint8_t Day; // 1-31
uint8_t Month; // 1-12 uint8_t Month; // 1-12
uint8_t Year; // Full year (e.g., 2024) uint8_t Year; // Full year (e.g., 2024)
} time_struct; } time_struct;
extern time_struct TIME; extern time_struct TIME;
typedef enum { typedef enum {
RTC_RFM69_SET_TIME_SUCCESS = 1, RTC_RFM69_SET_TIME_SUCCESS = 1,
RTC_RFM69_SET_TIME_FAILED = 2, RTC_RFM69_SET_TIME_FAILED = 2,
} RTC_RFM69_STATUS; } RTC_RFM69_STATUS;
typedef enum // Goes into ID typedef enum // Goes into ID
{ DATA_SEND_SUCCESS = 1, { DATA_SEND_SUCCESS = 1,
DATA_NOT_SENT = 2 } DATA_SEND_STATUS; DATA_NOT_SENT = 2 } DATA_SEND_STATUS;
typedef enum // Goes into ID typedef enum // Goes into ID
{ MSG_TYPE_STRING = 1, { MSG_TYPE_STRING = 1,
MSG_TYPE_BINARY = 2 } MSG_DATA_TYPE; MSG_TYPE_BINARY = 2 } MSG_DATA_TYPE;
@@ -101,13 +109,10 @@ typedef enum // Goes into flags
MSG_SEND_COUNTS = 31, MSG_SEND_COUNTS = 31,
MSG_RECV_COUNTS_SUCCESS = 32, MSG_RECV_COUNTS_SUCCESS = 32,
MSG_RECV_COUNTS_FAIL = 33, MSG_RECV_COUNTS_FAIL = 33,
MSG_RESENT_COUNTS = 34} MSG_REQUEST_TYPE_FLAG; MSG_RESENT_COUNTS = 34 } MSG_REQUEST_TYPE_FLAG;
#ifdef __cplusplus
#ifdef __cplusplus
} }
#endif #endif
#endif /* DEFINES_H */ #endif /* DEFINES_H */
+2
View File
@@ -1,3 +1,5 @@
// Blocking TWI (I2C) master for the RTC and the NFC tag.
#include "i2c.h" #include "i2c.h"
#include <util/twi.h> #include <util/twi.h>
+17 -23
View File
@@ -10,18 +10,18 @@
#define TWBR TWBR0 #define TWBR TWBR0
#define TWCR TWCR0 #define TWCR TWCR0
#define I2C_START_WRITE(device_addr) \ #define I2C_START_WRITE(device_addr) \
{ \ { \
if (i2c_start((device_addr << 1) | 0x00)) { \ if (i2c_start((device_addr << 1) | 0x00)) { \
return 1; \ return 1; \
} \ } \
} }
#define I2C_START_READ(device_addr) \ #define I2C_START_READ(device_addr) \
{ \ { \
if (i2c_start((device_addr << 1) | 0x01)) { \ if (i2c_start((device_addr << 1) | 0x01)) { \
return 1; \ return 1; \
} \ } \
} }
// A byte at F_SCL takes well under 100 us; anything past this means the bus is // A byte at F_SCL takes well under 100 us; anything past this means the bus is
@@ -31,23 +31,17 @@
void i2c_init(void); void i2c_init(void);
uint8_t i2c_start(uint8_t address); uint8_t i2c_start(uint8_t address);
uint8_t write_one_byte(uint8_t device_addr, uint8_t register_addr, uint8_t write_one_byte(uint8_t device_addr, uint8_t register_addr, uint8_t data);
uint8_t data); uint8_t write_n_bytes(uint8_t device_addr, uint8_t register_addr, uint8_t* data, uint8_t n_bytes);
uint8_t write_n_bytes(uint8_t device_addr, uint8_t register_addr, uint8_t *data,
uint8_t n_bytes);
uint8_t read_one_byte_16bit_addr_no_err_register(uint8_t device_addr, uint16_t register_addr); uint8_t read_one_byte_16bit_addr_no_err_register(uint8_t device_addr, uint16_t register_addr);
uint8_t read_one_byte_16bit_addr(uint8_t device_addr, uint16_t register_addr, uint8_t read_one_byte_16bit_addr(uint8_t device_addr, uint16_t register_addr, uint8_t* data);
uint8_t *data); uint8_t read_n_bytes_16bit_addr(
uint8_t read_n_bytes_16bit_addr(uint8_t device_addr, uint16_t register_addr, uint8_t *data, uint8_t device_addr, uint16_t register_addr, uint8_t* data, uint8_t n_bytes);
uint8_t n_bytes);
uint8_t read_one_byte_no_err_register(uint8_t device_addr, uint8_t register_addr); uint8_t read_one_byte_no_err_register(uint8_t device_addr, uint8_t register_addr);
uint8_t read_one_byte(uint8_t device_addr, uint8_t register_addr, uint8_t read_one_byte(uint8_t device_addr, uint8_t register_addr, uint8_t* data);
uint8_t *data); uint8_t read_n_bytes(uint8_t device_addr, uint8_t register_addr, uint8_t* data, uint8_t n_bytes);
uint8_t read_n_bytes(uint8_t device_addr, uint8_t register_addr, uint8_t *data,
uint8_t n_bytes);
void i2c_stop(void); void i2c_stop(void);
uint8_t i2c_read_ack(void); uint8_t i2c_read_ack(void);
+19 -10
View File
@@ -1,7 +1,8 @@
#include "interrupts.h" #include "interrupts.h"
void init_pins(void) { void init_pins(void)
{
// The reed switch (PD3/INT1) switches to ground and the RTC alarm output // The reed switch (PD3/INT1) switches to ground and the RTC alarm output
// (PD2/INT0) is open-drain, so both need the internal pull-up. Leaving them // (PD2/INT0) is open-drain, so both need the internal pull-up. Leaving them
@@ -19,27 +20,32 @@ void init_pins(void) {
// asynchronous. Each handler masks its own interrupt while the source is still // asynchronous. Each handler masks its own interrupt while the source is still
// asserted, so the low level does not retrigger in a loop. // asserted, so the low level does not retrigger in a loop.
void set_up_reed_interrupt(void) { void set_up_reed_interrupt(void)
{
EICRA &= ~((1 << ISC11) | (1 << ISC10)); EICRA &= ~((1 << ISC11) | (1 << ISC10));
EIMSK |= (1 << INT1); EIMSK |= (1 << INT1);
} }
void set_up_minute_interrupt(void) { void set_up_minute_interrupt(void)
{
EICRA &= ~((1 << ISC01) | (1 << ISC00)); EICRA &= ~((1 << ISC01) | (1 << ISC00));
EIMSK |= (1 << INT0); EIMSK |= (1 << INT0);
} }
void reed_interrupt_enable(void) { void reed_interrupt_enable(void)
{
EIFR = (1 << INTF1); // Drop anything latched while we were masked EIFR = (1 << INTF1); // Drop anything latched while we were masked
EIMSK |= (1 << INT1); EIMSK |= (1 << INT1);
} }
void minute_interrupt_enable(void) { void minute_interrupt_enable(void)
{
EIFR = (1 << INTF0); EIFR = (1 << INTF0);
EIMSK |= (1 << INT0); EIMSK |= (1 << INT0);
} }
void wdt_isr_enable(void) { void wdt_isr_enable(void)
{
uint8_t sreg = SREG; uint8_t sreg = SREG;
cli(); cli();
wdt_reset(); wdt_reset();
@@ -50,14 +56,17 @@ void wdt_isr_enable(void) {
MCUSR &= ~(1 << WDRF); MCUSR &= ~(1 << WDRF);
WDTCSR = (1 << WDCE) | (1 << WDE); WDTCSR = (1 << WDCE) | (1 << WDE);
// WDP[3:0] = 0b011 -> 0.125 s. Interrupt mode only (WDE clear), so an // WDP[3:0] = 0b010 -> 64 ms debounce, capping the count at ~15 rev/s;
// expiry wakes us to clear the debounce instead of resetting the part. // a reed contact settles in a few ms, so this is generous. Interrupt mode
WDTCSR = (1 << WDIE) | (1 << WDP1) | (1 << WDP0); // only (WDE clear): an expiry wakes us to clear the debounce instead of
// resetting the part.
WDTCSR = (1 << WDIE) | (1 << WDP1);
SREG = sreg; // Restore, never blanket-sei(): these run inside an ISR SREG = sreg; // Restore, never blanket-sei(): these run inside an ISR
} }
void wdt_isr_disable(void) { void wdt_isr_disable(void)
{
uint8_t sreg = SREG; uint8_t sreg = SREG;
cli(); cli();
wdt_reset(); wdt_reset();
+12 -15
View File
@@ -5,32 +5,29 @@
* Created on December 18, 2024, 12:48 PM * Created on December 18, 2024, 12:48 PM
*/ */
#include <avr/io.h>
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include <avr/io.h>
#include <avr/sleep.h> #include <avr/sleep.h>
#include <avr/wdt.h> #include <avr/wdt.h>
#include "states.h" #include "states.h"
#ifndef INTERRUPTS_H #ifndef INTERRUPTS_H
#define INTERRUPTS_H #define INTERRUPTS_H
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void init_pins(void);
void set_up_reed_interrupt(void);
void set_up_minute_interrupt(void);
void reed_interrupt_enable(void);
void minute_interrupt_enable(void);
void wdt_isr_disable(void);
void wdt_isr_enable(void);
#ifdef __cplusplus
void init_pins(void);
void set_up_reed_interrupt(void);
void set_up_minute_interrupt(void);
void reed_interrupt_enable(void);
void minute_interrupt_enable(void);
void wdt_isr_disable(void);
void wdt_isr_enable(void);
#ifdef __cplusplus
} }
#endif #endif
#endif /* INTERRUPTS_H */ #endif /* INTERRUPTS_H */
+20 -12
View File
@@ -1,16 +1,21 @@
// M95128 SPI EEPROM used as a LIFO spool for unacknowledged radio packets.
//
// Page 0, byte 0: depth of the spool (the highest page currently in use;
// 0 means empty)
// Pages 1..N: one 64-byte packet each -- 60 msg bytes followed by
// flags, from, to, dtype
//
// write_struct_to_last_page() pushes, read_struct_last_page() peeks, and
// delete_last_page() pops.
#include "m95128.h" #include "m95128.h"
uint8_t read_value; uint8_t read_value;
uint8_t old_last_page; // EEPROM chip select (PB0, active low)
uint8_t new_last_page;
void spi_eeprom_select(bool state) void spi_eeprom_select(bool state)
{ {
SET_PIN_OUT(DDRB, DDB0); SET_PIN_OUT(DDRB, DDB0);
if (!state) { SET_PIN_TO(PORTB, PB0, !state);
SET_PIN_HIGH(PORTB, PB0);
} else {
SET_PIN_LOW(PORTB, PB0);
}
} }
void eeprom_write(uint8_t page, unsigned const char* msg, uint8_t msg_len) void eeprom_write(uint8_t page, unsigned const char* msg, uint8_t msg_len)
@@ -68,13 +73,12 @@ void eeprom_read(uint8_t page, unsigned char* msg, uint8_t msg_len)
void delete_last_page(void) void delete_last_page(void)
{ {
old_last_page = get_last_page(); uint8_t old_last_page = get_last_page();
if (old_last_page == 0) // If we have nothing, no need to delete anything if (old_last_page == 0) // If we have nothing, no need to delete anything
{ {
return; return;
} }
new_last_page = old_last_page - 1; write_last_page_value(old_last_page - 1);
write_last_page_value(new_last_page);
eeprom_clear_page(old_last_page); eeprom_clear_page(old_last_page);
} }
@@ -140,8 +144,12 @@ void write_struct_to_last_page(tx_rx_data_struct tx_data_in)
{ {
uint8_t page_val = get_last_page(); uint8_t page_val = get_last_page();
uint8_t next_page_val; uint8_t next_page_val;
if (page_val == 255) { if (page_val >= EEPROM_MAX_PAGE) {
next_page_val = 1; // Spool full (~2.6 days of failed sends). Overwrite the newest spooled
// packet: one packet is lost either way, and this keeps the depth
// truthful -- the old wrap to page 1 stranded 254 packets the counter
// no longer admitted to.
next_page_val = EEPROM_MAX_PAGE;
} else { } else {
next_page_val = page_val + 1; next_page_val = page_val + 1;
} }
+4 -2
View File
@@ -26,6 +26,9 @@
#define EEPROM_RDLS 0b10000011 // 0x83 #define EEPROM_RDLS 0b10000011 // 0x83
#define EEPROM_LID 0b10000010 // 0x82 #define EEPROM_LID 0b10000010 // 0x82
#define PAGE_SIZE 64 #define PAGE_SIZE 64
// M95128 is 16 KB = 256 x 64-byte pages; page 0 holds the spool depth, so
// pages 1..255 hold packets.
#define EEPROM_MAX_PAGE 255
#define EEPROM_STATUS_WIP 0x01 #define EEPROM_STATUS_WIP 0x01
// A page write takes ~5 ms; past this the device is not responding. // A page write takes ~5 ms; past this the device is not responding.
@@ -34,8 +37,7 @@
extern "C" { extern "C" {
#endif #endif
void void spi_eeprom_select(bool state);
spi_eeprom_select(bool state);
void eeprom_write(uint8_t page, unsigned const char* msg, uint8_t msg_len); void eeprom_write(uint8_t page, unsigned const char* msg, uint8_t msg_len);
void eeprom_read(uint8_t page, unsigned char* msg, uint8_t msg_len); void eeprom_read(uint8_t page, unsigned char* msg, uint8_t msg_len);
void eeprom_write_tx_data(uint8_t page, tx_rx_data_struct tx_data); void eeprom_write_tx_data(uint8_t page, tx_rx_data_struct tx_data);
+299 -205
View File
@@ -1,3 +1,24 @@
// Wheel revolution counter
// ========================
//
// Battery-powered node that counts exercise-wheel revolutions and reports them
// over an RFM69 radio.
//
// Hardware (ATmega328PB @ 8 MHz):
// PD3/INT1 reed switch, closes to ground once per wheel revolution
// PD2/INT0 MAX31329 RTC alarm output (open drain), fires once per minute
// SPI1 RFM69 packet radio + M95128 EEPROM (used as a retry spool)
// I2C MAX31329 RTC + ST25DV NFC tag (holds "<name>,<diameter>")
//
// Operation:
// 1. Sleep in power-down. A reed pulse increments the count for the
// current minute slot; an RTC alarm advances to the next slot.
// 2. Every SEND_INTERVAL minutes, pack the per-minute counts into one
// radio packet (name, diameter, battery, timestamp, counts, hash).
// 3. If the base station does not acknowledge, spool the packet to
// EEPROM; whenever a live packet is acknowledged, retry one spooled
// packet.
#include "adc.h" #include "adc.h"
#include "defines.h" #include "defines.h"
#include "interrupts.h" #include "interrupts.h"
@@ -8,20 +29,18 @@
#include "rfm69.h" #include "rfm69.h"
#include "st25dv.h" #include "st25dv.h"
#include "states.h" #include "states.h"
#if DO_UART
#include "uart.h" #include "uart.h"
#endif
#include <avr/interrupt.h> #include <avr/interrupt.h>
#include <avr/io.h> #include <avr/io.h>
#include <avr/power.h>
#include <avr/sleep.h> #include <avr/sleep.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdio.h>
#include <util/atomic.h> #include <util/atomic.h>
#include <util/delay.h> #include <util/delay.h>
// How many minute slots are collected before a packet is sent. ITERATING is a
// bench-test mode that sends every minute (and lets the reed switch stand in
// for the minute alarm).
#if ITERATING #if ITERATING
#define SEND_INTERVAL 1 #define SEND_INTERVAL 1
#else #else
@@ -30,59 +49,87 @@
#define WHEEL_COUNT_SLOTS 15 #define WHEEL_COUNT_SLOTS 15
// Erased EEPROM reads back as 0xFF; anything else is a real page count. // Erased EEPROM reads back as 0xFF; anything else is a real spool depth.
#define EEPROM_LAST_PAGE_UNINIT 0xFF #define EEPROM_LAST_PAGE_UNINIT 0xFF
uint16_t self_value; // The NFC identity is cached; re-read the tag every 4th send (~1 h), so a
volatile uint8_t is_debouncing = 0; // renamed nugget still takes effect without a reset.
volatile bool increment_minute_index = false; #define TAG_REREAD_SEND_CYCLES 4
volatile bool increment_wheel_count = false;
volatile uint8_t index_wheel_count = 0;
volatile uint16_t total_wheel_counts[WHEEL_COUNT_SLOTS];
RTC_RFM69_STATUS rtc_rfm69_status; // Re-request the time daily even when the RTC is running, to bound its drift.
#define TIME_RESYNC_SEND_CYCLES 96 // 96 x 15 min = 24 h
ISR(INT0_vect) { // How many spooled packets one successful cycle may retry, so a huge backlog
// The RTC holds INTB low until its flag registers are read, and this is a // cannot keep the node awake for minutes.
// level-triggered interrupt, so mask it here and let main re-arm it once #define SPOOL_DRAIN_MAX 10
// the RTC has released the line.
// ---------------------------------------------------------------------------
// State shared with the interrupt handlers
// ---------------------------------------------------------------------------
static volatile bool minute_alarm_fired = false; // Set by INT0, consumed by main loop
static volatile bool reed_is_debouncing = false; // Set by INT1, cleared by WDT expiry
static volatile uint8_t minute_slot = 0; // Which wheel_counts[] slot is being filled
static volatile uint16_t wheel_counts[WHEEL_COUNT_SLOTS]; // Revolutions per minute slot
// Whether we ever got a valid timestamp from the base station
static RTC_RFM69_STATUS time_sync_status;
static uint8_t sends_since_tag_read = 0;
static uint8_t sends_since_time_sync = 0;
// ---------------------------------------------------------------------------
// Interrupt handlers
//
// Both external interrupts are low-level triggered (the only mode that can
// wake the MCU from power-down), so each handler must mask itself while its
// source still holds the line low. See interrupts.c.
// ---------------------------------------------------------------------------
// RTC minute alarm. The RTC holds INTB low until main reads its flag
// registers, so mask INT0 here; main re-arms it after clearing the flags.
ISR(INT0_vect)
{
EIMSK &= ~(1 << INT0); EIMSK &= ~(1 << INT0);
#if DO_UART minute_alarm_fired = true;
uart_sendString("\t\t\t\tMINUTE INTERRUPT\n"); LOG("\t\t\t\tMINUTE INTERRUPT\n");
#endif
increment_minute_index = true;
} }
ISR(INT1_vect) { // Reed switch: one revolution. The magnet holds the reed closed far longer
// than one bounce, so mask INT1 for a WDT-timed debounce window; the WDT
// handler below re-arms it.
ISR(INT1_vect)
{
#if ITERATING #if ITERATING
increment_minute_index = true; minute_alarm_fired = true;
#endif #endif
LOG("\t\t\t\tREED INTERRUPT\n");
#if DO_UART if (!reed_is_debouncing) {
uart_sendString("\t\t\t\tREED INTERRUPT\n"); if (minute_slot < WHEEL_COUNT_SLOTS) {
#endif wheel_counts[minute_slot]++;
if (!is_debouncing) {
if (index_wheel_count < WHEEL_COUNT_SLOTS) {
total_wheel_counts[index_wheel_count]++;
} }
is_debouncing = 1; reed_is_debouncing = true;
// 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); EIMSK &= ~(1 << INT1);
wdt_isr_enable(); wdt_isr_enable();
} }
} }
ISR(WDT_vect) { // Debounce window over: allow the next reed pulse to count.
is_debouncing = 0; ISR(WDT_vect)
{
reed_is_debouncing = false;
wdt_isr_disable(); wdt_isr_disable();
reed_interrupt_enable(); reed_interrupt_enable();
} }
void start_sleeping(void) { // ---------------------------------------------------------------------------
// Power management
// ---------------------------------------------------------------------------
// Cut power to every peripheral and enter power-down until the reed switch,
// the RTC alarm, or the debounce watchdog wakes us.
static void sleep_until_interrupt(void)
{
spi_eeprom_select(false); spi_eeprom_select(false);
spi_rfm69_select(false); spi_rfm69_select(false);
rfid_set_low_power_down(true); rfid_set_low_power_down(true);
@@ -92,13 +139,13 @@ void start_sleeping(void) {
set_sleep_mode(SLEEP_MODE_PWR_DOWN); set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// avr-libc sleep idiom: test for pending work with interrupts off, and
// sei() only immediately before sleep_cpu() (the next instruction always
// executes before any pending interrupt), so an alarm that fired while the
// rails were dropping cannot be slept through. sleep_bod_disable() is a
// timed 3-cycle sequence and must sit directly before sleep_cpu().
cli(); cli();
// Don't sleep through work that arrived while we were dropping the rails. if (!minute_alarm_fired) {
// 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_enable();
sleep_bod_disable(); sleep_bod_disable();
sei(); sei();
@@ -108,62 +155,203 @@ void start_sleeping(void) {
sei(); sei();
} }
uint16_t get_battery_reading(void) { // Restore the supplies that sleep_until_interrupt() dropped. Everything on the
adc_Enable(); // I2C bus is dead until this runs.
adc_GetConversion(14); static void wake_peripheral_rails(void)
adc_GetConversion(14); {
self_value = adc_GetConversion(14); ldo_set_state(true);
adc_Disable(); rfid_set_i2c_power(true);
return self_value; _delay_ms(1);
} }
// i2c Addresses // ---------------------------------------------------------------------------
// Measurement helpers
// ---------------------------------------------------------------------------
// RTC // Battery voltage in millivolts, measured by reading the 1.1 V internal
// 0x68 (0xD0 W) (0xD1 R) // bandgap against the AVcc (battery) reference: Vcc = 1100 mV * 1023 / raw.
// The first conversions after enabling the ADC read low, so take three and
// keep the last. Returns 0 when the ADC fails, which the base station can
// recognise as "no reading".
static uint16_t read_battery_millivolts(void)
{
adc_Enable();
adc_GetConversion(ADC_CHANNEL_BANDGAP);
adc_GetConversion(ADC_CHANNEL_BANDGAP);
uint16_t raw = adc_GetConversion(ADC_CHANNEL_BANDGAP);
adc_Disable();
// NFC if (raw == 0) {
// 0x2D (0x5A W) (0x5B R) return 0;
// 0x53 (0xA6 W) (0xA7 R) }
// 0x57 (0xAE W) (0xAF R) return (uint16_t)((1100UL * 1023UL) / raw);
}
int main(void) { // Spread nodes out: a name-hash-derived delay (0-236 ms) before transmitting
// keeps two nodes that woke on the same RTC second from colliding on every
// single cycle.
static void tx_backoff_delay(void)
{
for (uint8_t i = 0; i < IDENTIFIER.hashed; i++) {
_delay_ms(4);
}
}
// Atomically hand out the collected counts and start the next collection
// period, so a reed pulse landing mid-copy is neither lost nor double-counted.
static void take_counts_snapshot(uint16_t snapshot[WHEEL_COUNT_SLOTS])
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) {
snapshot[c] = wheel_counts[c];
wheel_counts[c] = 0;
}
minute_slot = 0;
}
}
// ---------------------------------------------------------------------------
// Radio reporting
// ---------------------------------------------------------------------------
// Build and send the periodic counts packet. Unacknowledged packets go to the
// EEPROM spool; each acknowledged send buys retries of spooled packets.
static void send_wheel_counts_report(void)
{
rfm69_init();
// Reading the tag costs an I2C transaction and a tag power-up, so use the
// cached identity and only re-read about once an hour -- or immediately, if
// the last read failed to parse.
sends_since_tag_read++;
if ((sends_since_tag_read >= TAG_REREAD_SEND_CYCLES) || (NDEF_MSG.success != 0)) {
IDENTIFIER = get_nugget_data();
sends_since_tag_read = 0;
}
// Sync time when we never got it, and re-sync daily to bound RTC drift.
if (sends_since_time_sync < 255) {
sends_since_time_sync++;
}
if ((time_sync_status == RTC_RFM69_SET_TIME_FAILED)
|| (sends_since_time_sync >= TIME_RESYNC_SEND_CYCLES)) {
time_sync_status = set_time_from_rfm69(IDENTIFIER);
if (time_sync_status == RTC_RFM69_SET_TIME_SUCCESS) {
sends_since_time_sync = 0;
}
}
uint16_t counts_snapshot[WHEEL_COUNT_SLOTS];
take_counts_snapshot(counts_snapshot);
reset_txrx_struct(&TX_DATA);
TX_DATA = generate_wheel_counts_message(
IDENTIFIER, rtc_read_time(), read_battery_millivolts(), counts_snapshot);
LOG("TX DATA Sent\n");
#if DO_UART
uart_print_tx_rx_data(TX_DATA);
#endif
tx_backoff_delay();
DATA_SEND_STATUS result = send_message(TX_DATA);
if (result == DATA_NOT_SENT) {
LOG(" TX DATA not sent, writing to SPI\n");
write_struct_to_last_page(TX_DATA);
return;
}
// The base station is listening -- drain the spool while sends keep
// succeeding, capped at SPOOL_DRAIN_MAX per cycle. At one per cycle a long
// outage took days to catch up.
for (uint8_t drained = 0; (drained < SPOOL_DRAIN_MAX) && (get_last_page() > 0); drained++) {
reset_txrx_struct(&TX_DATA);
TX_DATA = read_struct_last_page();
TX_DATA.flags = MSG_RESENT_COUNTS;
_delay_ms(250);
tx_backoff_delay();
result = send_message(TX_DATA);
LOG("TX DATA From SPI Memory\n");
#if DO_UART
uart_print_tx_rx_data(TX_DATA);
#endif
// Only drop the spooled page once it is actually acknowledged;
// deleting on failure would lose the data.
if (result != DATA_SEND_SUCCESS) {
LOG(" SPI not sent\n");
break;
}
delete_last_page();
}
}
// One RTC alarm has fired: clear it, advance the minute slot, and send a
// report if a full period has been collected.
static void handle_minute_alarm(void)
{
LOG("In minute index\n");
uint8_t slots_filled;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
if (minute_slot < WHEEL_COUNT_SLOTS) {
minute_slot += 1;
}
slots_filled = minute_slot;
}
wake_peripheral_rails();
// Reading the RTC flag registers releases the (level-triggered) INTB line,
// after which INT0 can safely be re-armed.
rtc_read_interrupt_register();
rtc_read_status_register();
minute_interrupt_enable();
if (slots_filled >= SEND_INTERVAL) {
send_wheel_counts_report();
}
}
// ---------------------------------------------------------------------------
// Start-up
// ---------------------------------------------------------------------------
static void init_all_hardware(void)
{
ldo_set_state(true); ldo_set_state(true);
_delay_ms(10); _delay_ms(10);
init_pins(); init_pins();
#if DO_UART #if DO_UART
uart_init(); uart_init();
uart_sendString("---- STARTING ----\n"); LOG("---- STARTING ----\n");
uart_wait_until_sent(); uart_wait_until_sent();
#endif #endif
i2c_init(); i2c_init();
init_spi(); init_spi();
adc_Initialize(); adc_Initialize();
// Gate the clocks of everything unused; adc_Enable() lifts the ADC's gate
// for the duration of each battery reading.
shutdown_all_peripherals();
set_up_reed_interrupt(); set_up_reed_interrupt();
set_up_minute_interrupt(); set_up_minute_interrupt();
#if DO_UART LOG("Set up AVR interrupts\n");
uart_sendString("Set up AVR interrupts\n");
#endif
rtc_set_per_minute_alarm(); rtc_set_per_minute_alarm();
rtc_set_alarm_config(); rtc_set_alarm_config();
rtc_enable_interrupts(); rtc_enable_interrupts();
rtc_read_interrupt_register(); rtc_read_interrupt_register(); // Clear any alarm already pending
rtc_read_status_register(); rtc_read_status_register();
#if DO_UART LOG("Set up RTC interrupts\n");
uart_sendString("Set up RTC interrupts\n");
#endif
rfm69_init(); rfm69_init();
#if DO_UART LOG("Initialized RFM69\n");
uart_sendString("Initialized RFM69\n");
#endif
// Only initialise the spool pointer when it has never been written -- // Only initialise the spool pointer when it has never been written --
// clearing it unconditionally would discard every unsent message across a // clearing it unconditionally would discard every unsent message across a
@@ -171,157 +359,63 @@ int main(void) {
if (get_last_page() == EEPROM_LAST_PAGE_UNINIT) { if (get_last_page() == EEPROM_LAST_PAGE_UNINIT) {
write_last_page_value(0); write_last_page_value(0);
} }
#if DO_UART LOG("Set up last page value for SPI flash\n");
uart_sendString("Set up last page value for SPI flash\n"); }
#endif
for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) { // Five long blinks for a successful time sync, five short ones for a failure.
total_wheel_counts[c] = 0; static void blink_time_sync_result(bool success)
} {
for (uint8_t i = 0; i < 5; i++) {
led_1_set_state(true);
if (success) {
// Get the nugget's name and wheel diameter
IDENTIFIER = get_nugget_data();
#if DO_UART
uart_sendString("Got nugget data from RFID\n");
#endif
// Request time from radio
rtc_rfm69_status = set_time_from_rfm69(IDENTIFIER);
if (rtc_rfm69_status == RTC_RFM69_SET_TIME_SUCCESS) {
for (int i = 0; i < 5; i++) {
led_1_set_state(true);
_delay_ms(90); _delay_ms(90);
led_1_set_state(false); } else {
_delay_ms(10); _delay_ms(10);
} }
} else { led_1_set_state(false);
for (int i = 0; i < 5; i++) { if (success) {
led_1_set_state(true);
_delay_ms(10); _delay_ms(10);
led_1_set_state(false); } else {
_delay_ms(90); _delay_ms(90);
} }
} }
#if DO_UART }
if (rtc_rfm69_status == RTC_RFM69_SET_TIME_FAILED) {
uart_sendString("Failed to get time \n");
} else {
uart_sendString("Success in get time \n");
};
#endif int main(void)
{
init_all_hardware();
for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) {
wheel_counts[c] = 0;
}
get_battery_reading(); // The nugget's name and wheel diameter live on the NFC tag
while (1) { IDENTIFIER = get_nugget_data();
LOG("Got nugget data from RFID\n");
// Ask the base station for the current time and load it into the RTC
time_sync_status = set_time_from_rfm69(IDENTIFIER);
blink_time_sync_result(time_sync_status == RTC_RFM69_SET_TIME_SUCCESS);
if (time_sync_status == RTC_RFM69_SET_TIME_SUCCESS) {
LOG("Success in get time \n");
} else {
LOG("Failed to get time \n");
}
spi_rfm69_select(false); read_battery_millivolts(); // Throwaway read to settle the ADC path
spi_eeprom_select(false);
start_sleeping(); while (1) {
sleep_until_interrupt();
// 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; bool minute_elapsed;
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
minute_elapsed = increment_minute_index; {
increment_minute_index = false; minute_elapsed = minute_alarm_fired;
minute_alarm_fired = false;
} }
if (minute_elapsed) { if (minute_elapsed) {
#if DO_UART handle_minute_alarm();
uart_sendString("In minute index\n");
#endif
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();
// Reading the flags releases INTB, so INT0 can safely be re-armed.
minute_interrupt_enable();
if (slot >= SEND_INTERVAL) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { index_wheel_count = 0; }
rfm69_init();
rfid_set_low_power_down(false);
_delay_ms(1);
IDENTIFIER = get_nugget_data();
// Request time from radio if we don't have a good time stamp yet
if (rtc_rfm69_status == RTC_RFM69_SET_TIME_FAILED) {
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);
TX_DATA = generate_wheel_counts_message(
IDENTIFIER, rtc_read_time(), get_battery_reading(), counts_snapshot);
#if DO_UART
uart_sendString("TX DATA Sent\n");
uart_print_tx_rx_data(TX_DATA);
#endif
DATA_SEND_STATUS result = send_message(TX_DATA);
if (result == DATA_NOT_SENT) {
#if DO_UART
uart_sendString(" TX DATA not sent, writing to SPI\n");
#endif
write_struct_to_last_page(TX_DATA);
}
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
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
}
}
} }
} }
} }
+40 -23
View File
@@ -1,5 +1,6 @@
// MAX31329 RTC driver, plus the over-the-radio time sync that seeds it.
#include "max31329.h" #include "max31329.h"
bool result;
RTC_RFM69_STATUS set_time_from_rfm69(identifier_results id_data) RTC_RFM69_STATUS set_time_from_rfm69(identifier_results id_data)
{ {
@@ -14,13 +15,13 @@ RTC_RFM69_STATUS set_time_from_rfm69(identifier_results id_data)
TX_DATA.dtype = MSG_TYPE_STRING; TX_DATA.dtype = MSG_TYPE_STRING;
rfm69_write_msg(TX_DATA); rfm69_write_msg(TX_DATA);
result = wait_rx_payload_ready_timeout(100);
if (result) { if (wait_rx_payload_ready_timeout(100)) {
RX_DATA = rfm69_read_msg(); RX_DATA = rfm69_read_msg();
if (RX_DATA.flags == 2) { if (RX_DATA.flags == MSG_RESP_CTIME) {
// TIME.Second = rx_data.msg[0]; // Seed the seconds from the name hash instead of the reply so
// nodes don't all wake and transmit in the same instant.
TIME.Second = id_data.hashed; TIME.Second = id_data.hashed;
TIME.Minute = RX_DATA.msg[1]; TIME.Minute = RX_DATA.msg[1];
@@ -30,51 +31,67 @@ RTC_RFM69_STATUS set_time_from_rfm69(identifier_results id_data)
TIME.Year = RX_DATA.msg[5]; TIME.Year = RX_DATA.msg[5];
TIME.Wday = RX_DATA.msg[6]; TIME.Wday = RX_DATA.msg[6];
rtc_write_time(TIME); // Only claim success once the time actually landed in the RTC,
return RTC_RFM69_SET_TIME_SUCCESS; // so a failed write is retried next cycle.
if (rtc_write_time(TIME) == 0) {
return RTC_RFM69_SET_TIME_SUCCESS;
}
LOG("RTC write failed\n");
} }
} else {
// uart_sendString("Did not get RX\n");
} }
return RTC_RFM69_SET_TIME_FAILED; return RTC_RFM69_SET_TIME_FAILED;
} }
// Masking out minutes, hours, and day makes alarm 2 match once every minute
uint8_t rtc_set_per_minute_alarm(void) uint8_t rtc_set_per_minute_alarm(void)
{ {
DATA_BUFFER_7[0] = RTC_ALM_MASK_BIT;
DATA_BUFFER_7[1] = RTC_ALM_MASK_BIT;
DATA_BUFFER_7[2] = RTC_ALM_MASK_BIT;
DATA_BUFFER_7[0] = 0x80; return write_n_bytes(I2C_ADDR, RTC_REG_ALM2_MIN, DATA_BUFFER_7, 3);
DATA_BUFFER_7[1] = 0x80;
DATA_BUFFER_7[2] = 0x80;
return write_n_bytes(I2C_ADDR, 0x13, DATA_BUFFER_7, 3);
} }
void uart_print_rtc_time(time_struct td) void uart_print_rtc_time(time_struct td)
{ {
char str_rtc[26]; char str_rtc[26];
snprintf( snprintf(
str_rtc, sizeof(str_rtc), "%u/%02u/%02u %u:%02u:%02u", 2000 + td.Year, str_rtc, sizeof(str_rtc), "%u/%02u/%02u %u:%02u:%02u", 2000 + td.Year, td.Month, td.Day,
td.Month, td.Day, td.Hour, td.Minute, td.Second); td.Hour, td.Minute, td.Second);
uart_sendString(str_rtc); uart_sendString(str_rtc);
uart_sendString("\n"); uart_sendString("\n");
} }
uint8_t rtc_read_register(uint8_t addr) { return read_one_byte_no_err_register(I2C_ADDR, addr); } uint8_t rtc_read_register(uint8_t addr) { return read_one_byte_no_err_register(I2C_ADDR, addr); }
uint8_t rtc_read_status_register(void) { return rtc_read_register(0x00); } uint8_t rtc_read_status_register(void) { return rtc_read_register(RTC_REG_STATUS); }
uint8_t rtc_read_interrupt_register(void) { return rtc_read_register(0x01); } uint8_t rtc_read_interrupt_register(void) { return rtc_read_register(RTC_REG_INT_EN); }
uint8_t rtc_set_alarm_config(void) { return write_one_byte(I2C_ADDR, 0x04, 0b00001010); } uint8_t rtc_set_alarm_config(void) { return write_one_byte(I2C_ADDR, RTC_REG_CONFIG2, 0b00001010); }
uint8_t rtc_enable_interrupts(void) { return write_one_byte(I2C_ADDR, 0x01, 0b00000010); } uint8_t rtc_enable_interrupts(void)
{
return write_one_byte(I2C_ADDR, RTC_REG_INT_EN, RTC_INT_EN_A2IE);
}
uint8_t rtc_read_time_array(uint8_t* data) { return read_n_bytes(I2C_ADDR, 0x06, data, 7); } uint8_t rtc_read_time_array(uint8_t* data)
{
return read_n_bytes(I2C_ADDR, RTC_REG_SECONDS, data, 7);
}
time_struct rtc_read_time(void) time_struct rtc_read_time(void)
{ {
if (rtc_read_time_array(DATA_BUFFER_7)) {
// RTC unreachable: mark every field with an unmistakably invalid value
// rather than transmitting whatever was read last. The base station
// sees month 0xFF and knows the timestamp is unusable.
LOG("RTC read failed\n");
TIME.Second = TIME.Minute = TIME.Hour = 0xFF;
TIME.Wday = TIME.Day = TIME.Month = TIME.Year = 0xFF;
return TIME;
}
rtc_read_time_array(DATA_BUFFER_7);
TIME.Second = BCD2DEC(DATA_BUFFER_7[0]); TIME.Second = BCD2DEC(DATA_BUFFER_7[0]);
TIME.Minute = BCD2DEC(DATA_BUFFER_7[1]); TIME.Minute = BCD2DEC(DATA_BUFFER_7[1]);
TIME.Hour = BCD2DEC((DATA_BUFFER_7[2] & ~(1 << 6))); TIME.Hour = BCD2DEC((DATA_BUFFER_7[2] & ~(1 << 6)));
@@ -95,7 +112,7 @@ uint8_t rtc_write_time(time_struct tm)
return 1; return 1;
uint8_t err = 0; uint8_t err = 0;
err |= i2c_write(0x06); err |= i2c_write(RTC_REG_SECONDS);
err |= i2c_write(DEC2BCD(tm.Second)); err |= i2c_write(DEC2BCD(tm.Second));
err |= i2c_write(DEC2BCD(tm.Minute)); err |= i2c_write(DEC2BCD(tm.Minute));
err |= i2c_write(DEC2BCD(tm.Hour)); err |= i2c_write(DEC2BCD(tm.Hour));
+10
View File
@@ -20,6 +20,16 @@ extern "C" {
#define I2C_ADDR 0x68 #define I2C_ADDR 0x68
// MAX31329 register map (the subset this driver touches)
#define RTC_REG_STATUS 0x00 // Reading clears the alarm flags / releases INTB
#define RTC_REG_INT_EN 0x01
#define RTC_REG_CONFIG2 0x04
#define RTC_REG_SECONDS 0x06 // Start of the 7-byte BCD time block
#define RTC_REG_ALM2_MIN 0x13 // Start of the 3-byte alarm-2 block
#define RTC_INT_EN_A2IE 0b00000010 // Alarm-2 interrupt enable
#define RTC_ALM_MASK_BIT 0x80 // "Don't match this field" bit in each alarm register
#define DEC2BCD(n) ((n) + (6 * ((n) / 10))) #define DEC2BCD(n) ((n) + (6 * ((n) / 10)))
#define BCD2DEC(n) ((n) - (6 * ((n) >> 4))) #define BCD2DEC(n) ((n) - (6 * ((n) >> 4)))
+10 -7
View File
@@ -1,4 +1,6 @@
// Minimal NDEF parser: finds the first record in a raw tag dump and accepts
// only a short text record, whose text becomes the node's "name,diameter"
// identity string.
#include "ndef.h" #include "ndef.h"
// Everything read here comes off an NFC tag that anyone can write, so every // Everything read here comes off an NFC tag that anyone can write, so every
@@ -11,7 +13,8 @@
} \ } \
} }
ndef_message readNDEFText(unsigned char *buf, uint8_t buf_len) { ndef_message readNDEFText(unsigned char* buf, uint8_t buf_len)
{
uint16_t addr = 0; uint16_t addr = 0;
NDEF_MSG.success = 0; NDEF_MSG.success = 0;
NDEF_MSG.payload_len = 0; NDEF_MSG.payload_len = 0;
@@ -80,7 +83,7 @@ ndef_message readNDEFText(unsigned char *buf, uint8_t buf_len) {
return NDEF_MSG; return NDEF_MSG;
} }
payload_length -= lang_str_len; // Language string payload_length -= lang_str_len; // Language string
payload_length -= 1; // The byte that says how long the language string is payload_length -= 1; // The byte that says how long the language string is
NDEF_NEED((uint16_t)lang_str_len + 1); NDEF_NEED((uint16_t)lang_str_len + 1);
addr += lang_str_len; addr += lang_str_len;
@@ -99,10 +102,10 @@ ndef_message readNDEFText(unsigned char *buf, uint8_t buf_len) {
NDEF_MSG.payload[payload_length] = '\0'; NDEF_MSG.payload[payload_length] = '\0';
NDEF_MSG.payload_len = payload_length; NDEF_MSG.payload_len = payload_length;
#if DO_UART #if DO_UART
uart_sendString(NDEF_MSG.payload); uart_sendString(NDEF_MSG.payload); // Runtime string, so not LOG()
uart_sendString("\n"); #endif
#endif LOG("\n");
return NDEF_MSG; return NDEF_MSG;
}; };
+4 -4
View File
@@ -13,10 +13,10 @@ extern "C" {
#endif #endif
#include "defines.h" #include "defines.h"
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include "uart.h" #include "uart.h"
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#define NDEF_TLV 0x03 #define NDEF_TLV 0x03
#define NDEF_SHORT_RECORD (1 << 4) #define NDEF_SHORT_RECORD (1 << 4)
@@ -28,7 +28,7 @@ extern "C" {
#define NDEF_ERR_TRUNCATED 13 #define NDEF_ERR_TRUNCATED 13
#define NDEF_ERR_BAD_LENGTH 14 #define NDEF_ERR_BAD_LENGTH 14
ndef_message readNDEFText(unsigned char *buf, uint8_t buf_len); ndef_message readNDEFText(unsigned char* buf, uint8_t buf_len);
#ifdef __cplusplus #ifdef __cplusplus
} }
+14 -3
View File
@@ -1,14 +1,25 @@
// One-shot clock gating for every peripheral this firmware never uses (and
// the ADC, which adc_Enable()/adc_Disable() power up only around a reading).
// In use and left alone: SPI1 (radio + EEPROM), TWI0 (RTC + NFC tag), and
// USART0 when serial logging is compiled in.
#include "power_mgmt.h" #include "power_mgmt.h"
void shutdown_all_peripherals(void) { void shutdown_all_peripherals(void)
{
power_adc_disable(); power_adc_disable();
power_timer0_disable(); power_timer0_disable();
power_timer1_disable(); power_timer1_disable();
power_timer2_disable(); power_timer2_disable();
power_timer3_disable(); power_timer3_disable();
power_usart1_disable();
#ifdef power_spi0_disable
power_spi0_disable();
#endif
#ifdef power_twi1_disable
power_twi1_disable();
#endif
#if !DO_UART #if !DO_UART
power_usart0_disable(); power_usart0_disable();
power_usart1_disable();
#endif #endif
} }
+5 -7
View File
@@ -7,18 +7,16 @@
#include "defines.h" // for DO_UART, which shutdown_all_peripherals() tests #include "defines.h" // for DO_UART, which shutdown_all_peripherals() tests
#include <avr/power.h> #include <avr/power.h>
#ifndef POWER_MGMT_H #ifndef POWER_MGMT_H
#define POWER_MGMT_H #define POWER_MGMT_H
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void shutdown_all_peripherals(void);
void shutdown_all_peripherals(void); #ifdef __cplusplus
#ifdef __cplusplus
} }
#endif #endif
#endif /* POWER_MGMT_H */ #endif /* POWER_MGMT_H */
+288 -270
View File
@@ -1,142 +1,19 @@
// RFM69 packet radio driver, plus the node's send-with-acknowledgement
// protocol and the wheel-counts packet builder.
//
// Layout of this file:
// 1. Register access over SPI
// 2. Mode control and status waits
// 3. Raw packet write/read (FIFO)
// 4. Acknowledged send protocol
// 5. Wheel-counts packet builder and hashes
// 6. Radio configuration (rfm69_init)
#include "rfm69.h" #include "rfm69.h"
uint32_t msg_hash; // ---------------------------------------------------------------------------
uint8_t p_hash_1; // 1. Register access over SPI ("_rt" = register transfer)
uint8_t p_hash_2; // ---------------------------------------------------------------------------
uint8_t p_hash_3;
uint8_t c_hash_1;
uint8_t c_hash_2;
uint8_t c_hash_3;
bool cond_1;
bool cond_2;
bool cond_3;
DATA_SEND_STATUS send_message(tx_rx_data_struct tx_data)
{
rfm69_write_msg(tx_data);
p_hash_1 = tx_data.msg[57];
p_hash_2 = tx_data.msg[58];
p_hash_3 = tx_data.msg[59];
for (uint8_t i = 0; i < 10; i++) {
bool result = wait_rx_payload_ready_timeout(50);
if (result) {
RX_DATA = rfm69_read_msg();
#if DO_UART
uart_sendString("RX DATA\n");
uart_print_tx_rx_data(RX_DATA);
#endif
c_hash_1 = RX_DATA.msg[0];
c_hash_2 = RX_DATA.msg[1];
c_hash_3 = RX_DATA.msg[2];
cond_1 = (p_hash_1 == c_hash_1) && (p_hash_2 == c_hash_2) && (p_hash_3 == c_hash_3);
cond_2 = (tx_data.from == RX_DATA.to) && (tx_data.to == RX_DATA.from);
cond_3 = cond_1 && cond_2;
if (cond_3 && (RX_DATA.flags == MSG_RECV_COUNTS_SUCCESS) && (RX_DATA.msg[3] == 0xFF)) {
#if DO_UART
uart_sendString(" RX DATA SUCCESS\n");
#endif
return DATA_SEND_SUCCESS;
}
#if DO_UART
else if (
cond_3 && (RX_DATA.flags == MSG_RECV_COUNTS_FAIL) && (RX_DATA.msg[3] == 0x00)) {
uart_sendString(" RX DATA FAILED\n");
} else {
uart_sendString(" RX DATA ANOTHER ERROR\n");
}
#endif
}
}
return DATA_NOT_SENT;
}
void uart_print_tx_rx_data(tx_rx_data_struct tx_rx_print)
{
// uart_print_uint8(tx_rx_print.len, "LEN; ");
// uart_print_uint8(tx_rx_print.to, "TO;");
// uart_print_uint8(tx_rx_print.from, "FROM;");
// uart_print_uint8(tx_rx_print.dtype, "DTYPE;");
// uart_print_uint8(tx_rx_print.flags, "FLAGS;");
DATA_BUFFER_7[0] = tx_rx_print.len;
DATA_BUFFER_7[1] = tx_rx_print.to;
DATA_BUFFER_7[2] = tx_rx_print.from;
DATA_BUFFER_7[3] = tx_rx_print.dtype;
DATA_BUFFER_7[4] = tx_rx_print.flags;
uart_sendString(" ");
uart_print_uint8_array(DATA_BUFFER_7, 5, "LEN,TO,FROM,DTYPE,FLAGS\n");
uart_sendString(" ");
uart_sendStringArray(tx_rx_print.msg, 20);
uart_sendChar('\n');
uart_sendString(" ");
uart_print_uint8_array(tx_rx_print.msg, tx_rx_print.len, "\n");
}
void rfm69_write_msg(tx_rx_data_struct txrxd)
{
// TxStart is configured as FifoNotEmpty, so the radio begins transmitting
// the moment the first byte lands. Fill the FIFO from standby and only then
// switch to TX, otherwise the packet goes out ahead of its own payload.
set_rfm69_standby();
spi_rfm69_select(true);
spi_write(REG_FIFO | RFM69_SPI_WRITE);
if (txrxd.len > (60)) {
txrxd.len = 60;
}
spi_write(txrxd.len + 4);
spi_write(txrxd.to);
spi_write(txrxd.from);
spi_write(txrxd.dtype);
spi_write(txrxd.flags);
for (uint8_t x = 0; x < txrxd.len; x++) {
spi_write(txrxd.msg[x]);
}
spi_rfm69_select(false);
set_rfm69_tx_mode();
wait_tx_sent();
set_rfm69_rx_mode();
}
tx_rx_data_struct rfm69_read_msg(void)
{
memset(RX_DATA.msg, ' ', sizeof(RX_DATA.msg));
spi_rfm69_select(true);
spi_write(REG_FIFO);
uint8_t raw_len = spi_read();
RX_DATA.to = spi_read();
RX_DATA.from = spi_read();
RX_DATA.dtype = spi_read();
RX_DATA.flags = spi_read();
// The length byte comes off the air and is not trustworthy: below 4 it
// underflows to ~252, above 60 it walks off the end of msg[].
uint8_t len_f = (raw_len < 4) ? 0 : (uint8_t)(raw_len - 4);
if (len_f > sizeof(RX_DATA.msg)) {
len_f = sizeof(RX_DATA.msg);
}
RX_DATA.len = len_f;
for (uint8_t idx_f = 0; idx_f < len_f; idx_f++) {
RX_DATA.msg[idx_f] = spi_read();
}
spi_rfm69_select(false);
set_rfm69_idle();
return RX_DATA;
}
uint8_t spi_read_rfm69_rt(uint8_t reg) uint8_t spi_read_rfm69_rt(uint8_t reg)
{ {
@@ -167,95 +44,40 @@ uint8_t spi_write_rfm69_multiple_rt(uint8_t reg, const char* vals, uint8_t len)
return data_init; return data_init;
} }
// name (max 10), 10 // ---------------------------------------------------------------------------
// diameter (max 10), 20 // 2. Mode control and status waits
// battery_value 16-bit, 22 //
// time_reading (min) 23 // Every wait has a bail-out: an absent or unpowered radio must not hang the
// time_reading (hour) 24 // firmware, since no watchdog reset is armed.
// time_reading (day) 25 // ---------------------------------------------------------------------------
// time_reading (month) 26
// time_reading (year) 27
// 15 * per-min +30 57
// three byte hash check 3
tx_rx_data_struct generate_wheel_counts_message( void reset_rfm69(void)
identifier_results idd, time_struct time, uint16_t battery_value, volatile uint16_t counts[15])
{ {
rfm69_reset_state(true); // Reset line is active high
reset_txrx_struct(&TX_DATA); _delay_ms(10);
memcpy(TX_DATA.msg, idd.name_str, MIN(10, idd.name_len)); rfm69_reset_state(false);
memcpy(TX_DATA.msg + 10, idd.diameter_str, MIN(10, idd.diameter_len)); _delay_ms(10);
TX_DATA.msg[20] = battery_value & 0xFF;
TX_DATA.msg[21] = (battery_value >> 8) & 0xFF;
TX_DATA.msg[22] = time.Minute;
TX_DATA.msg[23] = time.Hour;
TX_DATA.msg[24] = time.Day;
TX_DATA.msg[25] = time.Month;
TX_DATA.msg[26] = time.Year;
for (uint8_t idx = 0; idx < 15; idx++) {
TX_DATA.msg[26 + (2 * idx + 1)] = counts[idx] & 0xFF; // LSB first
TX_DATA.msg[26 + (2 * idx + 2)] = (counts[idx] >> 8) & 0xFF; // MSB second
}
msg_hash = hash_3bytes(TX_DATA.msg, 57);
TX_DATA.msg[57] = msg_hash & 0xFF;
TX_DATA.msg[58] = (msg_hash >> 8) & 0xFF;
TX_DATA.msg[59] = (msg_hash >> 16) & 0xFF;
TX_DATA.len = sizeof(TX_DATA.msg);
TX_DATA.flags = MSG_SEND_COUNTS;
TX_DATA.from = idd.hashed;
TX_DATA.to = 255;
TX_DATA.dtype = MSG_TYPE_BINARY;
return TX_DATA;
}
uint32_t hash_3bytes(unsigned const char* str, uint8_t str_len)
{
uint32_t hash = 0;
for (uint8_t i = 0; i < str_len; i++) {
hash = (hash * 31 + str[i]) % 0xFFFFFF;
}
return hash;
}
void set_rfm69_power_amp_boost(void)
{
spi_write_rfm69_rt(REG_OCP, VAL_OCP_OFF);
spi_write_rfm69_rt(REG_TEST_PA1, VAL_TEST_PA1_BOOST);
spi_write_rfm69_rt(REG_TEST_PA2, VAL_TEST_PA2_BOOST);
}
void set_rfm69_power_amp_normal(void)
{
spi_write_rfm69_rt(REG_TEST_PA1, VAL_TEST_PA1_NORMAL);
spi_write_rfm69_rt(REG_TEST_PA2, VAL_TEST_PA2_NORMAL);
spi_write_rfm69_rt(REG_OCP, VAL_OCP_ON);
}
void reset_txrx_struct(tx_rx_data_struct* s)
{
s->len = 0;
s->to = 255;
s->from = 255;
s->dtype = 0;
s->flags = 0;
memset(s->msg, ' ', 60);
} }
void set_rfm69_mode(uint8_t target_mode) void set_rfm69_mode(uint8_t target_mode)
{ {
uint8_t mode = spi_read_rfm69_rt(REG_OP_MODE); uint8_t mode = spi_read_rfm69_rt(REG_OP_MODE);
mode &= ~VAL_OPMODE_MASK; mode &= ~VAL_OPMODE_MASK;
mode |= (target_mode & VAL_OPMODE_MASK); mode |= (target_mode & VAL_OPMODE_MASK);
spi_write_rfm69_rt(REG_OP_MODE, mode); spi_write_rfm69_rt(REG_OP_MODE, mode);
} }
bool wait_rfm69_mode_ready(void)
{
for (uint16_t attempts = 0; attempts < RFM69_TIMEOUT_MS; attempts++) {
if (MODE_READY) {
return true;
}
_delay_ms(1);
}
return false;
}
bool wait_tx_sent(void) bool wait_tx_sent(void)
{ {
for (uint16_t attempts = 0; attempts < RFM69_TIMEOUT_MS; attempts++) { for (uint16_t attempts = 0; attempts < RFM69_TIMEOUT_MS; attempts++) {
@@ -267,18 +89,6 @@ bool wait_tx_sent(void)
return false; return false;
} }
uint8_t hash(const char* str, uint8_t min, uint8_t max)
{
unsigned int hash = 0;
while (*str) {
hash = (hash * 31) + (unsigned char)(*str);
str++;
}
unsigned int range = max - min + 1;
return (hash % range) + min;
}
bool wait_rx_payload_ready_timeout(uint16_t attempts) bool wait_rx_payload_ready_timeout(uint16_t attempts)
{ {
set_rfm69_rx_mode(); set_rfm69_rx_mode();
@@ -293,20 +103,22 @@ bool wait_rx_payload_ready_timeout(uint16_t attempts)
return RX_PAYLOAD_READY != 0; return RX_PAYLOAD_READY != 0;
} }
bool wait_rx_payload_ready(void) bool wait_rx_payload_ready(void) { return wait_rx_payload_ready_timeout(RFM69_TIMEOUT_MS); }
// The PA boost registers are only allowed during TX; OCP must be off for the
// +20 dBm path, per the datasheet's high-power sequence.
void set_rfm69_power_amp_boost(void)
{ {
return wait_rx_payload_ready_timeout(RFM69_TIMEOUT_MS); spi_write_rfm69_rt(REG_OCP, VAL_OCP_OFF);
spi_write_rfm69_rt(REG_TEST_PA1, VAL_TEST_PA1_BOOST);
spi_write_rfm69_rt(REG_TEST_PA2, VAL_TEST_PA2_BOOST);
} }
bool wait_rfm69_mode_ready(void) void set_rfm69_power_amp_normal(void)
{ {
for (uint16_t attempts = 0; attempts < RFM69_TIMEOUT_MS; attempts++) { spi_write_rfm69_rt(REG_TEST_PA1, VAL_TEST_PA1_NORMAL);
if (MODE_READY) { spi_write_rfm69_rt(REG_TEST_PA2, VAL_TEST_PA2_NORMAL);
return true; spi_write_rfm69_rt(REG_OCP, VAL_OCP_ON);
}
_delay_ms(1);
}
return false;
} }
void set_rfm69_tx_mode(void) void set_rfm69_tx_mode(void)
@@ -337,61 +149,267 @@ void set_rfm69_sleep(void)
wait_rfm69_mode_ready(); wait_rfm69_mode_ready();
} }
void set_rfm69_idle(void) // "Idle" between packets is just standby
void set_rfm69_idle(void) { set_rfm69_standby(); }
// ---------------------------------------------------------------------------
// 3. Raw packet write/read
//
// On-air packet layout (variable-length mode, CRC on):
// [len] [to] [from] [dtype] [flags] [msg bytes ...]
// where len counts everything after itself, so msg length + 4 header bytes.
// ---------------------------------------------------------------------------
#define PACKET_HEADER_LEN 4
void reset_txrx_struct(tx_rx_data_struct* s)
{ {
set_rfm69_power_amp_normal(); s->len = 0;
set_rfm69_mode(VAL_OPMODE_STDBY); s->to = 255;
wait_rfm69_mode_ready(); s->from = 255;
s->dtype = 0;
s->flags = 0;
memset(s->msg, ' ', sizeof(s->msg));
} }
void reset_rfm69(void) void rfm69_write_msg(tx_rx_data_struct txrxd)
{ {
rfm69_reset_state(true); // TxStart is configured as FifoNotEmpty, so the radio begins transmitting
_delay_ms(10); // the moment the first byte lands. Fill the FIFO from standby and only then
rfm69_reset_state(false); // switch to TX, otherwise the packet goes out ahead of its own payload.
_delay_ms(10); set_rfm69_standby();
if (txrxd.len > sizeof(txrxd.msg)) {
txrxd.len = sizeof(txrxd.msg);
}
spi_rfm69_select(true);
spi_write(REG_FIFO | RFM69_SPI_WRITE);
spi_write(txrxd.len + PACKET_HEADER_LEN);
spi_write(txrxd.to);
spi_write(txrxd.from);
spi_write(txrxd.dtype);
spi_write(txrxd.flags);
for (uint8_t x = 0; x < txrxd.len; x++) {
spi_write(txrxd.msg[x]);
}
spi_rfm69_select(false);
set_rfm69_tx_mode();
wait_tx_sent();
set_rfm69_rx_mode();
} }
tx_rx_data_struct rfm69_read_msg(void)
{
memset(RX_DATA.msg, ' ', sizeof(RX_DATA.msg));
spi_rfm69_select(true);
spi_write(REG_FIFO);
uint8_t raw_len = spi_read();
RX_DATA.to = spi_read();
RX_DATA.from = spi_read();
RX_DATA.dtype = spi_read();
RX_DATA.flags = spi_read();
// The length byte comes off the air and is not trustworthy: below 4 it
// underflows to ~252, above 60 it walks off the end of msg[].
uint8_t msg_len = (raw_len < PACKET_HEADER_LEN) ? 0 : (uint8_t)(raw_len - PACKET_HEADER_LEN);
if (msg_len > sizeof(RX_DATA.msg)) {
msg_len = sizeof(RX_DATA.msg);
}
RX_DATA.len = msg_len;
for (uint8_t idx = 0; idx < msg_len; idx++) {
RX_DATA.msg[idx] = spi_read();
}
spi_rfm69_select(false);
set_rfm69_idle();
return RX_DATA;
}
// ---------------------------------------------------------------------------
// 4. Acknowledged send protocol
//
// Every counts packet ends in a 3-byte hash of its payload. The base station
// echoes that hash back in its reply, so a reply is accepted only when the
// echoed hash matches what we sent and the addresses are ours reversed.
// ---------------------------------------------------------------------------
#define SEND_ACK_ATTEMPTS 10
#define ACK_WAIT_MS 50
static bool reply_acknowledges(const tx_rx_data_struct* tx_data)
{
bool hash_echo_matches = (tx_data->msg[57] == RX_DATA.msg[0])
&& (tx_data->msg[58] == RX_DATA.msg[1]) && (tx_data->msg[59] == RX_DATA.msg[2]);
bool addresses_are_ours_reversed
= (tx_data->from == RX_DATA.to) && (tx_data->to == RX_DATA.from);
return hash_echo_matches && addresses_are_ours_reversed;
}
DATA_SEND_STATUS send_message(tx_rx_data_struct tx_data)
{
rfm69_write_msg(tx_data);
for (uint8_t attempt = 0; attempt < SEND_ACK_ATTEMPTS; attempt++) {
if (!wait_rx_payload_ready_timeout(ACK_WAIT_MS)) {
continue;
}
RX_DATA = rfm69_read_msg();
LOG("RX DATA\n");
#if DO_UART
uart_print_tx_rx_data(RX_DATA);
#endif
if (!reply_acknowledges(&tx_data)) {
LOG(" RX DATA ANOTHER ERROR\n");
continue;
}
if ((RX_DATA.flags == MSG_RECV_COUNTS_SUCCESS) && (RX_DATA.msg[3] == 0xFF)) {
LOG(" RX DATA SUCCESS\n");
return DATA_SEND_SUCCESS;
}
LOG(" RX DATA FAILED\n");
}
return DATA_NOT_SENT;
}
// ---------------------------------------------------------------------------
// 5. Wheel-counts packet builder and hashes
//
// 60-byte msg layout:
// [0..9] name (padded)
// [10..19] wheel diameter (padded)
// [20..21] battery voltage in millivolts, little endian (0 = read failed)
// [22..26] timestamp: minute, hour, day, month, year
// [27..56] 15 x uint16 per-minute counts, little endian
// [57..59] 24-bit hash of bytes 0..56
// ---------------------------------------------------------------------------
tx_rx_data_struct generate_wheel_counts_message(
identifier_results idd, time_struct time, uint16_t battery_value, volatile uint16_t counts[15])
{
reset_txrx_struct(&TX_DATA);
memcpy(TX_DATA.msg, idd.name_str, MIN(10, idd.name_len));
memcpy(TX_DATA.msg + 10, idd.diameter_str, MIN(10, idd.diameter_len));
TX_DATA.msg[20] = battery_value & 0xFF;
TX_DATA.msg[21] = (battery_value >> 8) & 0xFF;
TX_DATA.msg[22] = time.Minute;
TX_DATA.msg[23] = time.Hour;
TX_DATA.msg[24] = time.Day;
TX_DATA.msg[25] = time.Month;
TX_DATA.msg[26] = time.Year;
for (uint8_t idx = 0; idx < 15; idx++) {
TX_DATA.msg[26 + (2 * idx + 1)] = counts[idx] & 0xFF; // LSB first
TX_DATA.msg[26 + (2 * idx + 2)] = (counts[idx] >> 8) & 0xFF; // MSB second
}
uint32_t msg_hash = hash_3bytes(TX_DATA.msg, 57);
TX_DATA.msg[57] = msg_hash & 0xFF;
TX_DATA.msg[58] = (msg_hash >> 8) & 0xFF;
TX_DATA.msg[59] = (msg_hash >> 16) & 0xFF;
TX_DATA.len = sizeof(TX_DATA.msg);
TX_DATA.flags = MSG_SEND_COUNTS;
TX_DATA.from = idd.hashed;
TX_DATA.to = 255;
TX_DATA.dtype = MSG_TYPE_BINARY;
return TX_DATA;
}
// 24-bit payload checksum carried in the last three message bytes
uint32_t hash_3bytes(unsigned const char* str, uint8_t str_len)
{
uint32_t hash = 0;
for (uint8_t i = 0; i < str_len; i++) {
hash = (hash * 31 + str[i]) % 0xFFFFFF;
}
return hash;
}
// Hash a NUL-terminated string into [min, max]; used to derive the node's
// radio address from its name.
uint8_t hash(const char* str, uint8_t min, uint8_t max)
{
unsigned int hash = 0;
while (*str) {
hash = (hash * 31) + (unsigned char)(*str);
str++;
}
unsigned int range = max - min + 1;
return (hash % range) + min;
}
void uart_print_tx_rx_data(tx_rx_data_struct tx_rx_print)
{
DATA_BUFFER_7[0] = tx_rx_print.len;
DATA_BUFFER_7[1] = tx_rx_print.to;
DATA_BUFFER_7[2] = tx_rx_print.from;
DATA_BUFFER_7[3] = tx_rx_print.dtype;
DATA_BUFFER_7[4] = tx_rx_print.flags;
uart_sendString(" ");
uart_print_uint8_array(DATA_BUFFER_7, 5, "LEN,TO,FROM,DTYPE,FLAGS\n");
uart_sendString(" ");
uart_sendStringArray(tx_rx_print.msg, 20);
uart_sendChar('\n');
uart_sendString(" ");
uart_print_uint8_array(tx_rx_print.msg, tx_rx_print.len, "\n");
}
// ---------------------------------------------------------------------------
// 6. Radio configuration
// ---------------------------------------------------------------------------
void rfm69_init(void) void rfm69_init(void)
{ {
reset_rfm69(); reset_rfm69();
_delay_ms(100); _delay_ms(100);
set_rfm69_idle(); set_rfm69_idle();
// Carrier: 434.0 MHz (see the VAL_FREQ_* derivation in rfm69.h)
spi_write_rfm69_rt(REG_FREQ_MSB, VAL_FREQ_433MHz_MSB); spi_write_rfm69_rt(REG_FREQ_MSB, VAL_FREQ_433MHz_MSB);
spi_write_rfm69_rt(REG_FREQ_MIDDLE_SB, VAL_FREQ_433MHz_MID_SB); spi_write_rfm69_rt(REG_FREQ_MIDDLE_SB, VAL_FREQ_433MHz_MID_SB);
spi_write_rfm69_rt(REG_FREQ_LSB, VAL_FREQ_433MHz_LSB); spi_write_rfm69_rt(REG_FREQ_LSB, VAL_FREQ_433MHz_LSB);
spi_write_rfm69_rt( // Start transmitting as soon as the FIFO has data (rfm69_write_msg relies
REG_FIFO_THRESH, // on filling the FIFO in standby because of this)
VAL_TX_START_FIFO_NOT_EMPTY | VAL_FIFO_LEVEL_INTERRUPT); // TX condition spi_write_rfm69_rt(REG_FIFO_THRESH, VAL_TX_START_FIFO_NOT_EMPTY | VAL_FIFO_LEVEL_INTERRUPT);
spi_write_rfm69_rt(REG_TEST_DAGC, spi_write_rfm69_rt(REG_TEST_DAGC, VAL_TEST_DAGC_DEFAULT); // Fading margin improvement
VAL_TEST_DAGC_DEFAULT); // Fading margin improvement
// 2-byte sync word shared with the base station
char sync_words[] = { 0x2d, 0xd4 }; char sync_words[] = { 0x2d, 0xd4 };
spi_write_rfm69_multiple_rt(REG_SYNC_VALUE_1, sync_words, 2); spi_write_rfm69_multiple_rt(REG_SYNC_VALUE_1, sync_words, 2);
spi_write_rfm69_rt(REG_SYNC_CONFIG, VAL_SYNCWORDS_ON | VAL_SYNCWORDS_SIZE_2_BYTES); spi_write_rfm69_rt(REG_SYNC_CONFIG, VAL_SYNCWORDS_ON | VAL_SYNCWORDS_SIZE_2_BYTES);
spi_write_rfm69_rt(REG_DATA_MODUL, // FSK packet mode, Gaussian shaping, 250 kbps, 25 kHz deviation
VAL_DATA_PACKET_MODE | VAL_DATA_MODUL_FSK
| VAL_MODUL_SHAPING_GAUSS_BT_1_0); // RegDataModul
spi_write_rfm69_rt(REG_BITRATE_MSB,
VAL_BITRATE_250kbps_MSB); // RegBitrateMSB
spi_write_rfm69_rt(REG_BITRATE_LSB,
VAL_BITRATE_250kbps_LSB); // RegbBitrateLSB
spi_write_rfm69_rt(REG_FDEV_MSB, VAL_FDEV_MSB); // RegFdevMSB (0x05)
spi_write_rfm69_rt(REG_FDEV_LSB, VAL_FDEV_LSB); // RegFdevLSB (0x06)
spi_write_rfm69_rt(REG_RX_BW, 0xE0); // RegRxBw
spi_write_rfm69_rt(REG_AFC_BW, 0xE0); // RegAfcBw
spi_write_rfm69_rt( spi_write_rfm69_rt(
REG_PACKET_CONFIG_1, REG_DATA_MODUL, VAL_DATA_PACKET_MODE | VAL_DATA_MODUL_FSK | VAL_MODUL_SHAPING_GAUSS_BT_1_0);
VAL_PACKET_VARIABLE_LENGTH | VAL_PACKET_WHITENING | VAL_PACKET_CRCON); // RegPacketConfig1 spi_write_rfm69_rt(REG_BITRATE_MSB, VAL_BITRATE_250kbps_MSB);
spi_write_rfm69_rt(REG_BITRATE_LSB, VAL_BITRATE_250kbps_LSB);
spi_write_rfm69_rt(REG_FDEV_MSB, VAL_FDEV_MSB);
spi_write_rfm69_rt(REG_FDEV_LSB, VAL_FDEV_LSB);
spi_write_rfm69_rt(REG_PREAMBLE_MSB, 0x00); // RegPreambleMSB // Widest RX/AFC bandwidth settings
spi_write_rfm69_rt(REG_PREAMBLE_LSB, 0x04); // RegPreambleLSB spi_write_rfm69_rt(REG_RX_BW, 0xE0);
spi_write_rfm69_rt(REG_AFC_BW, 0xE0);
spi_write_rfm69_rt(REG_PA_LEVEL, // Variable-length packets with whitening and CRC
VAL_PA_PA1_ON | VAL_PA_PA2_ON | VAL_PA_20dB); // RegPaLevel spi_write_rfm69_rt(
REG_PACKET_CONFIG_1, VAL_PACKET_VARIABLE_LENGTH | VAL_PACKET_WHITENING | VAL_PACKET_CRCON);
// 4-byte preamble
spi_write_rfm69_rt(REG_PREAMBLE_MSB, 0x00);
spi_write_rfm69_rt(REG_PREAMBLE_LSB, 0x04);
// Both PA stages on, maximum output power
spi_write_rfm69_rt(REG_PA_LEVEL, VAL_PA_PA1_ON | VAL_PA_PA2_ON | VAL_PA_20dB);
} }
+9 -17
View File
@@ -1,26 +1,18 @@
#include "spi.h" #include "spi.h"
// RFM69 chip select (SS1/PE2, active low)
void spi_rfm69_select(bool state) void spi_rfm69_select(bool state)
{ {
SET_PIN_OUT(DDRE, DDE2); SET_PIN_OUT(DDRE, DDE2);
if (!state) { SET_PIN_TO(PORTE, PE2, !state);
SET_PIN_HIGH(PORTE, PE2);
} else {
SET_PIN_LOW(PORTE, PE2);
}
} }
uint8_t spi_write(uint8_t data)
{
SPDR1 = data; // Load data into the SPI data register
while (!(SPSR1 & (1 << SPIF1))) { }; // Wait for transmission to complete
return SPDR1; // Return received data
}
uint8_t spi_write(uint8_t data) { uint8_t spi_read(void) { return spi_write(0xFF); }
SPDR1 = data; // Load data into the SPI data register
while (!(SPSR1 & (1 << SPIF1))) {
}; // Wait for transmission to complete
return SPDR1; // Return received data
}
uint8_t spi_read(void) {
return spi_write(0xFF);
}
+1 -2
View File
@@ -1,11 +1,10 @@
#include <avr/io.h>
#include "defines.h" #include "defines.h"
#include "states.h" #include "states.h"
#include <avr/io.h>
#include <stdbool.h> #include <stdbool.h>
#ifndef SPI_H #ifndef SPI_H
#define SPI_H #define SPI_H
uint8_t spi_write(uint8_t data); uint8_t spi_write(uint8_t data);
uint8_t spi_read(void); uint8_t spi_read(void);
void spi_rfm69_select(bool state); void spi_rfm69_select(bool state);
+23 -19
View File
@@ -1,9 +1,13 @@
// ST25DV NFC tag driver. The tag holds a single NDEF text record of the form
// "<name>,<wheel diameter>", which get_nugget_data() parses into IDENTIFIER.
#include "st25dv.h" #include "st25dv.h"
#define IDENT_NAME_MAX (sizeof(IDENTIFIER.name_str) - 1) #define IDENT_NAME_MAX (sizeof(IDENTIFIER.name_str) - 1)
#define IDENT_DIAM_MAX (sizeof(IDENTIFIER.diameter_str) - 1) #define IDENT_DIAM_MAX (sizeof(IDENTIFIER.diameter_str) - 1)
static void set_identifier(const char* name, uint8_t name_len, const char* diam, uint8_t diam_len) { static void set_identifier(const char* name, uint8_t name_len, const char* diam, uint8_t diam_len)
{
if (name_len > IDENT_NAME_MAX) { if (name_len > IDENT_NAME_MAX) {
name_len = IDENT_NAME_MAX; name_len = IDENT_NAME_MAX;
} }
@@ -21,7 +25,8 @@ static void set_identifier(const char* name, uint8_t name_len, const char* diam,
IDENTIFIER.hashed = hash(IDENTIFIER.name_str, 0, 59); IDENTIFIER.hashed = hash(IDENTIFIER.name_str, 0, 59);
} }
identifier_results get_nugget_data(void) { identifier_results get_nugget_data(void)
{
NDEF_MSG = rfid_read_first_ndef_entry(); NDEF_MSG = rfid_read_first_ndef_entry();
@@ -52,7 +57,8 @@ identifier_results get_nugget_data(void) {
return IDENTIFIER; return IDENTIFIER;
} }
trimmed_string_struct remove_spaces(char* str, uint8_t len_str) { trimmed_string_struct remove_spaces(char* str, uint8_t len_str)
{
const uint8_t max_len = sizeof(TRIMMED_STRING.str) - 1; const uint8_t max_len = sizeof(TRIMMED_STRING.str) - 1;
uint8_t j = 0; uint8_t j = 0;
@@ -69,16 +75,15 @@ trimmed_string_struct remove_spaces(char* str, uint8_t len_str) {
return TRIMMED_STRING; return TRIMMED_STRING;
} }
void rfid_set_low_power_down(bool state) { // LPD pin: high puts the tag's I2C interface into low-power mode
void rfid_set_low_power_down(bool state)
{
SET_PIN_OUT(DDRD, DDD5); SET_PIN_OUT(DDRD, DDD5);
if (state) { SET_PIN_TO(PORTD, PD5, state);
SET_PIN_HIGH(PORTD, PD5);
} else {
SET_PIN_LOW(PORTD, PD5);
}
} }
ndef_message rfid_read_first_ndef_entry(void) { ndef_message rfid_read_first_ndef_entry(void)
{
rfid_set_low_power_down(false); rfid_set_low_power_down(false);
rfid_set_i2c_power(true); rfid_set_i2c_power(true);
_delay_ms(1); _delay_ms(1);
@@ -91,26 +96,25 @@ ndef_message rfid_read_first_ndef_entry(void) {
NDEF_MSG = readNDEFText(DATA_BUFFER_INTERNAL, NDEF_READ_LEN); NDEF_MSG = readNDEFText(DATA_BUFFER_INTERNAL, NDEF_READ_LEN);
rfid_set_low_power_down(true); rfid_set_low_power_down(true);
rfid_set_i2c_power(false); rfid_set_i2c_power(false);
return NDEF_MSG; return NDEF_MSG;
} }
uint8_t rfid_read_system_register(void) { uint8_t rfid_read_system_register(void)
{
return read_one_byte_16bit_addr_no_err_register(I2C_SYSTEM_ADDR, 0x0000); return read_one_byte_16bit_addr_no_err_register(I2C_SYSTEM_ADDR, 0x0000);
} }
void rfid_set_i2c_power(bool state) { // Switched supply for the tag's I2C side
void rfid_set_i2c_power(bool state)
{
SET_PIN_OUT(DDRE, DDE0); SET_PIN_OUT(DDRE, DDE0);
if (state) { SET_PIN_TO(PORTE, PE0, state);
SET_PIN_HIGH(PORTE, PE0);
} else {
SET_PIN_LOW(PORTE, PE0);
}
} }
uint8_t rfid_read_memory(uint8_t* data, uint8_t num_bytes, uint16_t address) { uint8_t rfid_read_memory(uint8_t* data, uint8_t num_bytes, uint16_t address)
{
return read_n_bytes_16bit_addr(I2C_USER_ADDR, address, data, num_bytes); return read_n_bytes_16bit_addr(I2C_USER_ADDR, address, data, num_bytes);
} }
+2 -4
View File
@@ -1,15 +1,13 @@
#include "i2c.h"
#include "defines.h" #include "defines.h"
#include "i2c.h"
#include "ndef.h" #include "ndef.h"
#include "rfm69.h"
#include "states.h" #include "states.h"
#include <stdbool.h> #include <stdbool.h>
#include "rfm69.h"
#include <util/delay.h> #include <util/delay.h>
#ifndef ST25DV_H #ifndef ST25DV_H
#define ST25DV_H #define ST25DV_H
#define I2C_SYSTEM_ADDR 0x57 #define I2C_SYSTEM_ADDR 0x57
#define I2C_USER_ADDR 0x53 #define I2C_USER_ADDR 0x53
+43 -52
View File
@@ -1,61 +1,52 @@
// GPIO helpers: every board control line lives here (except the two chip
// selects, which stay with their SPI drivers). Each helper sets the pin's
// direction on every call so it works no matter what ran before it.
#include "states.h" #include "states.h"
void init_spi(void) { void init_spi(void)
SET_PIN_OUT(DDRC, DDC1); // SCK1 {
SET_PIN_OUT(DDRE, DDE3); // MOSI1 SET_PIN_OUT(DDRC, DDC1); // SCK1
SET_PIN_IN(DDRC, DDC0); // MISO1 (driven by the slave; no pull-up) SET_PIN_OUT(DDRE, DDE3); // MOSI1
SET_PIN_IN(DDRC, DDC0); // MISO1 (driven by the slave; no pull-up)
// SS1 must be an output before SPE is set. If it is left as an input and // SS1 must be an output before SPE is set. If it is left as an input and
// reads low, the hardware clears MSTR and the port silently stops being // reads low, the hardware clears MSTR and the port silently stops being a
// a master. // master.
SET_PIN_OUT(DDRE, DDE2); SET_PIN_OUT(DDRE, DDE2);
SET_PIN_HIGH(PORTE, PE2); SET_PIN_HIGH(PORTE, PE2);
SPCR1 = (1 << SPE1) | (1 << MSTR1); // Enable, Master, SPR1:0 = 00 -> f_osc/4 SPCR1 = (1 << SPE1) | (1 << MSTR1); // Enable, Master, SPR1:0 = 00 -> f_osc/4
} }
// RFM69 reset line: high holds the radio in reset
void rfm69_reset_state(bool state)
{
SET_PIN_OUT(DDRC, DDC2);
SET_PIN_TO(PORTC, PC2, state);
}
void rfm69_reset_state(bool state) { void led_1_set_state(bool state)
SET_PIN_OUT(DDRC, DDC2); {
if (state) { SET_PIN_OUT(DDRD, DDD4);
SET_PIN_HIGH(PORTC, PC2); SET_PIN_TO(PORTD, PD4, state);
} else { }
SET_PIN_LOW(PORTC, PC2);
}
}
void led_1_set_state(bool state) { void led_2_set_state(bool state)
SET_PIN_OUT(DDRD, DDD4); {
if (state) { SET_PIN_OUT(DDRD, DDD6);
SET_PIN_HIGH(PORTD, PD4); SET_PIN_TO(PORTD, PD6, state);
} else { }
SET_PIN_LOW(PORTD, PD4);
}
}
void led_2_set_state(bool state) { void led_3_set_state(bool state)
SET_PIN_OUT(DDRD, DDD6); {
if (state) { SET_PIN_OUT(DDRD, DDD7);
SET_PIN_HIGH(PORTD, PD6); SET_PIN_TO(PORTD, PD7, state);
} else { }
SET_PIN_LOW(PORTD, PD6);
}
}
void led_3_set_state(bool state) { // Enable line of the LDO that powers the radio and EEPROM
SET_PIN_OUT(DDRD, DDD7); void ldo_set_state(bool state)
if (state) { {
SET_PIN_HIGH(PORTD, PD7); SET_PIN_OUT(DDRC, DDC3);
} else { SET_PIN_TO(PORTC, PC3, state);
SET_PIN_LOW(PORTD, PD7); }
}
}
void ldo_set_state(bool state) {
SET_PIN_OUT(DDRC, DDC3);
if (state) {
SET_PIN_HIGH(PORTC, PC3);
} else {
SET_PIN_LOW(PORTC, PC3);
}
}
+17 -20
View File
@@ -6,39 +6,36 @@
*/ */
#ifndef STATES_H #ifndef STATES_H
#define STATES_H #define STATES_H
#include <stdbool.h>
#include <avr/io.h> #include <avr/io.h>
#include <stdbool.h>
#define SET_PIN_OUT(DDR, PIN) ((DDR) |= (1 << (PIN))) // Set pin as output #define SET_PIN_OUT(DDR, PIN) ((DDR) |= (1 << (PIN))) // Set pin as output
#define SET_PIN_IN(DDR, PIN) ((DDR) &= ~(1 << (PIN))) // Set pin as input #define SET_PIN_IN(DDR, PIN) ((DDR) &= ~(1 << (PIN))) // Set pin as input
#define SET_PIN_HIGH(PORT, PIN) ((PORT) |= (1 << (PIN))) // Set pin high #define SET_PIN_HIGH(PORT, PIN) ((PORT) |= (1 << (PIN))) // Set pin high
#define SET_PIN_LOW(PORT, PIN) ((PORT) &= ~(1 << (PIN))) // Set pin low #define SET_PIN_LOW(PORT, PIN) ((PORT) &= ~(1 << (PIN))) // Set pin low
// Drive a pin to a boolean level
#define SET_PIN_TO(PORT, PIN, level) ((level) ? SET_PIN_HIGH(PORT, PIN) : SET_PIN_LOW(PORT, PIN))
#ifdef __cplusplus
#ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
void init_spi(void); void init_spi(void);
void rfm69_reset_state(bool state) ; void rfm69_reset_state(bool state);
void led_1_set_state(bool state); void led_1_set_state(bool state);
void led_2_set_state(bool state); void led_2_set_state(bool state);
void led_3_set_state(bool state); void led_3_set_state(bool state);
void ldo_set_state(bool state); void ldo_set_state(bool state);
#ifdef __cplusplus
#ifdef __cplusplus
} }
#endif #endif
#endif /* STATES_H */ #endif /* STATES_H */
+8
View File
@@ -57,6 +57,14 @@ void uart_sendString(const char* str)
} }
} }
void uart_sendString_P(const char* progmem_str)
{
char c;
while ((c = pgm_read_byte(progmem_str++)) != '\0') {
uart_sendChar(c);
}
}
void uart_print_uint16(uint16_t meas, const char* buf) void uart_print_uint16(uint16_t meas, const char* buf)
{ {
snprintf(array_internal, sizeof(array_internal), "%u", meas); snprintf(array_internal, sizeof(array_internal), "%u", meas);
+2
View File
@@ -8,6 +8,7 @@
#include "defines.h" #include "defines.h"
#include <avr/io.h> #include <avr/io.h>
#include <avr/pgmspace.h>
#include <stdbool.h> #include <stdbool.h>
#include <stdint.h> #include <stdint.h>
#include <stdio.h> #include <stdio.h>
@@ -26,6 +27,7 @@ extern "C" {
void uart_init(void); void uart_init(void);
void uart_sendChar(char c); void uart_sendChar(char c);
void uart_sendString(const char* str); void uart_sendString(const char* str);
void uart_sendString_P(const char* progmem_str); // For strings kept in flash (PSTR)
void uart_sendStringArray(unsigned char str[], uint8_t len); void uart_sendStringArray(unsigned char str[], uint8_t len);
void uart_print_uint16(uint16_t meas, const char* buf); void uart_print_uint16(uint16_t meas, const char* buf);
void uart_print_hex(unsigned char vin, const char* buf); void uart_print_hex(unsigned char vin, const char* buf);