Compare commits
5 Commits
c87308caca
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 089be9564b | |||
| 2152b8f727 | |||
| bbdcc1e623 | |||
| 8550e74a8a | |||
| ed476473b6 |
+50
-34
@@ -1,67 +1,83 @@
|
|||||||
#include "adc.h"
|
#include "adc.h"
|
||||||
|
|
||||||
int8_t adc_Initialize()
|
#define ADC_CONVERSION_TIMEOUT 1000U
|
||||||
|
|
||||||
|
// The ADC needs a 50-200 kHz clock. At F_CPU = 8 MHz that is a /64 prescaler
|
||||||
|
// (125 kHz); the old /2 ran it at 4 MHz, far out of spec.
|
||||||
|
#define ADC_PRESCALER_64 ((1 << ADPS2) | (1 << ADPS1))
|
||||||
|
|
||||||
|
// The 1.1 V bandgap reference needs time to settle after the mux is switched.
|
||||||
|
#define ADC_SETTLE_US 200
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
//ADEN enabled; ADSC disabled; ADATE disabled; ADIF disabled; ADIE disabled; ADPS VAL_0x01;
|
ADCSRA = (1 << ADEN) | ADC_PRESCALER_64;
|
||||||
ADCSRA = 0x81;
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void adc_Disable()
|
// 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)
|
||||||
{
|
{
|
||||||
ADCSRA &= ~(1 << ADEN);
|
ADCSRA &= ~(1 << ADEN);
|
||||||
|
power_adc_disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void adc_Enable()
|
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;
|
|
||||||
}
|
|
||||||
ADCSRA |= (1 << ADSC);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool adc_IsConversionDone()
|
bool adc_IsConversionDone(void) { return ((ADCSRA & (1 << ADIF))); }
|
||||||
{
|
|
||||||
return ((ADCSRA & (1 << ADIF)));
|
|
||||||
}
|
|
||||||
|
|
||||||
uint16_t adc_GetConversionResult(void)
|
uint16_t adc_GetConversionResult(void)
|
||||||
{
|
{
|
||||||
return (ADCL | ADCH << 8);
|
// ADC reads ADCL then ADCH in the right order. Reading the two volatile
|
||||||
|
// registers in one expression leaves the order unspecified, and taking ADCH
|
||||||
|
// first breaks the data-register lock and corrupts the result.
|
||||||
|
return ADC;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t adc_GetConversion(uint8_t channel)
|
uint16_t adc_GetConversion(uint8_t channel)
|
||||||
{
|
{
|
||||||
|
adc_StartConversion(channel);
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
uint16_t attempts = 0;
|
||||||
|
while (!adc_IsConversionDone()) {
|
||||||
|
if (++attempts > ADC_CONVERSION_TIMEOUT) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
_delay_us(10);
|
||||||
|
}
|
||||||
|
|
||||||
adc_StartConversion(channel);
|
uint16_t res = adc_GetConversionResult();
|
||||||
while (!adc_IsConversionDone());
|
ADCSRA |= (1 << ADIF);
|
||||||
uint16_t res = adc_GetConversionResult();
|
return res;
|
||||||
ADCSRA |= (1 << ADIF);
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-10
@@ -6,27 +6,31 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef ADC_H
|
#ifndef ADC_H
|
||||||
#define ADC_H
|
#define ADC_H
|
||||||
|
#include "defines.h"
|
||||||
#include <avr/io.h>
|
#include <avr/io.h>
|
||||||
#include <stdint.h>
|
#include <avr/power.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
|
#include <stdint.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();
|
int8_t adc_Initialize(void);
|
||||||
void adc_Enable();
|
void adc_Enable(void);
|
||||||
void adc_Disable();
|
void adc_Disable(void);
|
||||||
void adc_StartConversion(uint8_t channel);
|
void adc_StartConversion(uint8_t channel);
|
||||||
bool adc_IsConversionDone();
|
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 */
|
||||||
|
|
||||||
|
|||||||
-1020
File diff suppressed because it is too large
Load Diff
+8
-10
@@ -1,12 +1,10 @@
|
|||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
|
|
||||||
unsigned char DATA_BUFFER_65[64];
|
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;
|
|
||||||
+34
-31
@@ -6,35 +6,49 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#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>
|
||||||
#define F_CPU 8000000UL // 16 MHz clock speed
|
#include <stdlib.h>
|
||||||
|
#ifndef F_CPU
|
||||||
|
#define F_CPU 8000000UL // 8 MHz clock speed; prefer -DF_CPU=8000000UL in the build flags
|
||||||
|
#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
|
||||||
|
|
||||||
extern unsigned char DATA_BUFFER_65[64];
|
#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 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;
|
||||||
char payload[255];
|
char payload[48];
|
||||||
uint8_t success;
|
uint8_t success;
|
||||||
} ndef_message;
|
} ndef_message;
|
||||||
extern ndef_message NDEF_MSG;
|
extern ndef_message NDEF_MSG;
|
||||||
@@ -47,14 +61,13 @@ extern trimmed_string_struct TRIMMED_STRING;
|
|||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t name_len;
|
uint8_t name_len;
|
||||||
char name_str[128];
|
char name_str[16];
|
||||||
char diameter_str[128];
|
char diameter_str[16];
|
||||||
uint8_t diameter_len;
|
uint8_t diameter_len;
|
||||||
uint8_t hashed;
|
uint8_t hashed;
|
||||||
} 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;
|
||||||
@@ -69,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;
|
||||||
@@ -99,17 +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
|
||||||
|
|
||||||
#define WHILE_BREAK(counter, attempts) \
|
|
||||||
counter+=1; \
|
|
||||||
if ((counter+1) > attempts) { break;};
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* DEFINES_H */
|
#endif /* DEFINES_H */
|
||||||
|
|
||||||
|
|||||||
+42
-18
@@ -1,7 +1,22 @@
|
|||||||
|
// 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>
|
||||||
|
|
||||||
void i2c_init()
|
// 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)
|
// Set SCL and SDA as inputs (automatically done by TWI hardware)
|
||||||
TWSR = 0; // Prescaler = 1
|
TWSR = 0; // Prescaler = 1
|
||||||
@@ -13,13 +28,13 @@ void i2c_init()
|
|||||||
uint8_t i2c_start(uint8_t address)
|
uint8_t i2c_start(uint8_t address)
|
||||||
{
|
{
|
||||||
TWCR = (1 << TWSTA) | (1 << TWINT) | (1 << TWEN); // Send START condition
|
TWCR = (1 << TWSTA) | (1 << TWINT) | (1 << TWEN); // Send START condition
|
||||||
while (!(TWCR & (1 << TWINT)))
|
if (!i2c_wait_twint())
|
||||||
; // Wait for TWINT flag to be set
|
return 1;
|
||||||
TWDR = address; // Load address into data register
|
TWDR = address; // Load address into data register
|
||||||
|
|
||||||
TWCR = (1 << TWINT) | (1 << TWEN); // Send address
|
TWCR = (1 << TWINT) | (1 << TWEN); // Send address
|
||||||
while (!(TWCR & (1 << TWINT)))
|
if (!i2c_wait_twint())
|
||||||
; // Wait for TWINT flag to be set
|
return 1;
|
||||||
uint8_t status = TWSR & 0xF8;
|
uint8_t status = TWSR & 0xF8;
|
||||||
if (status != 0x18 && status != 0x40)
|
if (status != 0x18 && status != 0x40)
|
||||||
return 1;
|
return 1;
|
||||||
@@ -36,9 +51,15 @@ uint8_t write_one_byte(uint8_t device_addr, uint8_t register_addr, 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)
|
||||||
{
|
{
|
||||||
I2C_START_WRITE(device_addr);
|
I2C_START_WRITE(device_addr);
|
||||||
i2c_write(register_addr);
|
if (i2c_write(register_addr)) {
|
||||||
|
i2c_stop();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
for (uint8_t i = 0; i < n_bytes; i++) {
|
for (uint8_t i = 0; i < n_bytes; i++) {
|
||||||
i2c_write(data[i]);
|
if (i2c_write(data[i])) {
|
||||||
|
i2c_stop();
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
i2c_stop();
|
i2c_stop();
|
||||||
return 0;
|
return 0;
|
||||||
@@ -106,18 +127,21 @@ read_n_bytes_16bit_addr(uint8_t device_addr, uint16_t register_addr, uint8_t* da
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Stop i2c communication
|
// Stop i2c communication
|
||||||
void i2c_stop()
|
void i2c_stop(void)
|
||||||
{
|
{
|
||||||
TWCR = (1 << TWSTO) | (1 << TWINT) | (1 << TWEN); // Send STOP condition
|
TWCR = (1 << TWSTO) | (1 << TWINT) | (1 << TWEN); // Send STOP condition
|
||||||
while (!(TWCR & (1 << TWSTO)))
|
for (uint16_t attempts = 0; attempts < I2C_TIMEOUT_LOOPS; attempts++) {
|
||||||
; // Wait for STOP to complete
|
if (!(TWCR & (1 << TWSTO))) {
|
||||||
|
return; // STOP complete
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t i2c_read_ack()
|
uint8_t i2c_read_ack(void)
|
||||||
{
|
{
|
||||||
TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWEA);
|
TWCR = (1 << TWEN) | (1 << TWINT) | (1 << TWEA);
|
||||||
while (!(TWCR & (1 << TWINT)))
|
if (!i2c_wait_twint())
|
||||||
; // Wait for TWINT flag to be set
|
return 0xFF;
|
||||||
return TWDR;
|
return TWDR;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,17 +150,17 @@ uint8_t i2c_write(uint8_t data)
|
|||||||
// Load data into TWDR
|
// Load data into TWDR
|
||||||
TWDR = data;
|
TWDR = data;
|
||||||
TWCR = (1 << TWEN) | (1 << TWINT);
|
TWCR = (1 << TWEN) | (1 << TWINT);
|
||||||
while (!(TWCR & (1 << TWINT)))
|
if (!i2c_wait_twint())
|
||||||
; // Wait for TWINT flag set
|
return 1;
|
||||||
if ((TWSR & 0xF8) != TW_MT_DATA_ACK)
|
if ((TWSR & 0xF8) != TW_MT_DATA_ACK)
|
||||||
return 1; // Check ACK
|
return 1; // Check ACK
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t i2c_read_nack()
|
uint8_t i2c_read_nack(void)
|
||||||
{
|
{
|
||||||
TWCR = (1 << TWEN) | (1 << TWINT);
|
TWCR = (1 << TWEN) | (1 << TWINT);
|
||||||
while (!(TWCR & (1 << TWINT)))
|
if (!i2c_wait_twint())
|
||||||
; // Wait for TWINT flag to be set
|
return 0xFF;
|
||||||
return TWDR;
|
return TWDR;
|
||||||
}
|
}
|
||||||
+29
-32
@@ -2,20 +2,6 @@
|
|||||||
#include "uart.h"
|
#include "uart.h"
|
||||||
#include <avr/io.h>
|
#include <avr/io.h>
|
||||||
|
|
||||||
#define I2C_START_WRITE(device_addr) \
|
|
||||||
{ \
|
|
||||||
if (i2c_start((device_addr << 1) | 0x00)) { \
|
|
||||||
return 1; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#define I2C_START_READ(device_addr) \
|
|
||||||
{ \
|
|
||||||
if (i2c_start((device_addr << 1) | 0x01)) { \
|
|
||||||
return 1; \
|
|
||||||
} \
|
|
||||||
}
|
|
||||||
|
|
||||||
#ifndef i2c_H
|
#ifndef i2c_H
|
||||||
#define i2c_H
|
#define i2c_H
|
||||||
|
|
||||||
@@ -24,31 +10,42 @@
|
|||||||
#define TWBR TWBR0
|
#define TWBR TWBR0
|
||||||
#define TWCR TWCR0
|
#define TWCR TWCR0
|
||||||
|
|
||||||
void i2c_init();
|
#define I2C_START_WRITE(device_addr) \
|
||||||
|
{ \
|
||||||
|
if (i2c_start((device_addr << 1) | 0x00)) { \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
#define I2C_START_READ(device_addr) \
|
||||||
|
{ \
|
||||||
|
if (i2c_start((device_addr << 1) | 0x01)) { \
|
||||||
|
return 1; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
// A byte at F_SCL takes well under 100 us; anything past this means the bus is
|
||||||
|
// stuck (peripheral unpowered, SDA held low) and we must not spin forever.
|
||||||
|
#define I2C_TIMEOUT_LOOPS 20000U
|
||||||
|
|
||||||
|
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 i2c_stop(void);
|
||||||
void i2c_scan();
|
uint8_t i2c_read_ack(void);
|
||||||
uint8_t i2c_read_ack();
|
uint8_t i2c_read_nack(void);
|
||||||
uint8_t i2c_read_nack();
|
|
||||||
uint8_t i2c_write(uint8_t data);
|
uint8_t i2c_write(uint8_t data);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+54
-29
@@ -1,54 +1,79 @@
|
|||||||
|
|
||||||
#include "interrupts.h"
|
#include "interrupts.h"
|
||||||
|
|
||||||
void init_pins() {
|
void init_pins(void)
|
||||||
|
{
|
||||||
|
|
||||||
// Set reed switch interrupt pin
|
// 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
|
||||||
|
// floating makes the inputs self-trigger.
|
||||||
SET_PIN_IN(DDRD, DDD3);
|
SET_PIN_IN(DDRD, DDD3);
|
||||||
SET_PIN_LOW(PORTD, PD3);
|
SET_PIN_HIGH(PORTD, PD3);
|
||||||
|
|
||||||
|
SET_PIN_IN(DDRD, DDD2);
|
||||||
|
SET_PIN_HIGH(PORTD, PD2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_up_reed_interrupt() {
|
// Both external interrupts are configured low-level triggered (ISCn1:0 = 00).
|
||||||
// Falling edge interrupt
|
// Edge detection needs the I/O clock, which SLEEP_MODE_PWR_DOWN stops, so a
|
||||||
EICRA |= (1 << ISC11);
|
// falling-edge INT0/INT1 can never wake the MCU. Only level detection is
|
||||||
EICRA &= ~(1 << ISC10);
|
// asynchronous. Each handler masks its own interrupt while the source is still
|
||||||
|
// asserted, so the low level does not retrigger in a loop.
|
||||||
|
|
||||||
// Enable INT1 interrupt
|
void set_up_reed_interrupt(void)
|
||||||
|
{
|
||||||
|
EICRA &= ~((1 << ISC11) | (1 << ISC10));
|
||||||
EIMSK |= (1 << INT1);
|
EIMSK |= (1 << INT1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_up_minute_interrupt() {
|
void set_up_minute_interrupt(void)
|
||||||
EICRA |= (1 << ISC01);
|
{
|
||||||
EICRA &= ~(1 << ISC00);
|
EICRA &= ~((1 << ISC01) | (1 << ISC00));
|
||||||
|
|
||||||
// Enable INT1 interrupt
|
|
||||||
EIMSK |= (1 << INT0);
|
EIMSK |= (1 << INT0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void wdt_isr_enable() {
|
void reed_interrupt_enable(void)
|
||||||
|
{
|
||||||
|
EIFR = (1 << INTF1); // Drop anything latched while we were masked
|
||||||
|
EIMSK |= (1 << INT1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void minute_interrupt_enable(void)
|
||||||
|
{
|
||||||
|
EIFR = (1 << INTF0);
|
||||||
|
EIMSK |= (1 << INT0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void wdt_isr_enable(void)
|
||||||
|
{
|
||||||
|
uint8_t sreg = SREG;
|
||||||
cli();
|
cli();
|
||||||
wdt_reset();
|
wdt_reset();
|
||||||
|
|
||||||
WDTCSR |= (1 << WDCE) | (1 << WDE);
|
// WDRF keeps WDE set, which would block the write below, so it must go
|
||||||
|
// first. The unlock is a single assignment: a read-modify-write does not
|
||||||
|
// open the 4-cycle change window.
|
||||||
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
WDTCSR = (1 << WDCE) | (1 << WDE);
|
||||||
|
|
||||||
WDTCSR = (1 << WDP2) | (1 << WDP0); // WDP[3:0] = 0b101 (2 seconds)
|
// WDP[3:0] = 0b010 -> 64 ms debounce, capping the count at ~15 rev/s;
|
||||||
WDTCSR |= (1 << WDIE); // Enable WDT Interrupt mode
|
// a reed contact settles in a few ms, so this is generous. Interrupt mode
|
||||||
sei();
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
void wdt_isr_disable() {
|
void wdt_isr_disable(void)
|
||||||
|
{
|
||||||
|
uint8_t sreg = SREG;
|
||||||
cli();
|
cli();
|
||||||
|
wdt_reset();
|
||||||
|
|
||||||
WDTCSR |= (1 << WDCE) | (1 << WDE);
|
MCUSR &= ~(1 << WDRF);
|
||||||
|
WDTCSR = (1 << WDCE) | (1 << WDE);
|
||||||
WDTCSR = 0x00;
|
WDTCSR = 0x00;
|
||||||
|
|
||||||
sei();
|
SREG = sreg;
|
||||||
}
|
}
|
||||||
//
|
|
||||||
//void set_debounce_timer_interrupt() {
|
|
||||||
// TCCR0A = 0;
|
|
||||||
// TCCR0B = (1 << CS01) | (1 << CS00);
|
|
||||||
// TIMSK0 = (1 << TOIE0);
|
|
||||||
// TCNT0 = 0;
|
|
||||||
//}
|
|
||||||
|
|||||||
+12
-14
@@ -5,31 +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 set_up_reed_interrupt();
|
|
||||||
// void set_debounce_timer_interrupt();
|
|
||||||
void set_up_minute_interrupt();
|
|
||||||
void wdt_isr_disable();
|
|
||||||
void wdt_isr_enable();
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* INTERRUPTS_H */
|
#endif /* INTERRUPTS_H */
|
||||||
|
|
||||||
|
|||||||
+29
-18
@@ -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)
|
||||||
@@ -35,15 +40,18 @@ void eeprom_write(uint8_t page, unsigned const char* msg, uint8_t msg_len)
|
|||||||
spi_write(EEPROM_WRDI);
|
spi_write(EEPROM_WRDI);
|
||||||
spi_eeprom_select(false);
|
spi_eeprom_select(false);
|
||||||
|
|
||||||
while (1) {
|
// Poll the write-in-progress bit, but give up rather than spin forever if
|
||||||
|
// the EEPROM is unpowered or absent.
|
||||||
|
for (uint16_t attempts = 0; attempts < EEPROM_POLL_TIMEOUT_MS; attempts++) {
|
||||||
spi_eeprom_select(true);
|
spi_eeprom_select(true);
|
||||||
spi_write(EEPROM_RDSR);
|
spi_write(EEPROM_RDSR);
|
||||||
read_value = spi_read();
|
read_value = spi_read();
|
||||||
spi_eeprom_select(false);
|
spi_eeprom_select(false);
|
||||||
if (read_value == 0x00) {
|
if ((read_value & EEPROM_STATUS_WIP) == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
};
|
_delay_ms(1);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
||||||
@@ -63,19 +71,18 @@ void eeprom_read(uint8_t page, unsigned char* msg, uint8_t msg_len)
|
|||||||
spi_eeprom_select(false);
|
spi_eeprom_select(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void delete_last_page()
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t get_last_page()
|
uint8_t get_last_page(void)
|
||||||
{
|
{
|
||||||
memset(DATA_BUFFER_65, 0, 1);
|
memset(DATA_BUFFER_65, 0, 1);
|
||||||
eeprom_read(0, DATA_BUFFER_65, 1);
|
eeprom_read(0, DATA_BUFFER_65, 1);
|
||||||
@@ -127,7 +134,7 @@ tx_rx_data_struct eeprom_read_tx_data(uint8_t page)
|
|||||||
return TX_DATA;
|
return TX_DATA;
|
||||||
}
|
}
|
||||||
|
|
||||||
tx_rx_data_struct read_struct_last_page()
|
tx_rx_data_struct read_struct_last_page(void)
|
||||||
{
|
{
|
||||||
uint8_t page_num = get_last_page();
|
uint8_t page_num = get_last_page();
|
||||||
return eeprom_read_tx_data(page_num);
|
return eeprom_read_tx_data(page_num);
|
||||||
@@ -137,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;
|
||||||
}
|
}
|
||||||
|
|||||||
+12
-7
@@ -26,27 +26,32 @@
|
|||||||
#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
|
||||||
|
|
||||||
|
// A page write takes ~5 ms; past this the device is not responding.
|
||||||
|
#define EEPROM_POLL_TIMEOUT_MS 100U
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
w
|
|
||||||
#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);
|
||||||
void write_page_address(uint8_t page);
|
void write_page_address(uint8_t page);
|
||||||
uint8_t get_last_page();
|
uint8_t get_last_page(void);
|
||||||
tx_rx_data_struct eeprom_read_tx_data(uint8_t page);
|
tx_rx_data_struct eeprom_read_tx_data(uint8_t page);
|
||||||
void delete_last_page();
|
void delete_last_page(void);
|
||||||
uint8_t get_last_page();
|
uint8_t get_last_page(void);
|
||||||
void write_last_page_value(uint8_t page);
|
void write_last_page_value(uint8_t page);
|
||||||
void eeprom_clear_page(uint8_t page);
|
void eeprom_clear_page(uint8_t page);
|
||||||
void write_page_address(uint8_t page);
|
void write_page_address(uint8_t page);
|
||||||
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);
|
||||||
void write_struct_to_last_page(tx_rx_data_struct tx_data);
|
void write_struct_to_last_page(tx_rx_data_struct tx_data);
|
||||||
tx_rx_data_struct read_struct_last_page();
|
tx_rx_data_struct read_struct_last_page(void);
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
+324
-185
@@ -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,275 +29,393 @@
|
|||||||
#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/delay.h>
|
#include <util/delay.h>
|
||||||
#define WAIT_FOREVER \
|
|
||||||
while (1) { \
|
|
||||||
_delay_ms(100); \
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
|
// 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
|
||||||
#define SEND_INTERVAL 15
|
#define SEND_INTERVAL 15
|
||||||
#endif
|
#endif
|
||||||
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];
|
|
||||||
|
|
||||||
RTC_RFM69_STATUS rtc_rfm69_status;
|
#define WHEEL_COUNT_SLOTS 15
|
||||||
|
|
||||||
ISR(INT0_vect) {
|
// Erased EEPROM reads back as 0xFF; anything else is a real spool depth.
|
||||||
cli();
|
#define EEPROM_LAST_PAGE_UNINIT 0xFF
|
||||||
#if DO_UART
|
|
||||||
uart_sendString("\t\t\t\tMINUTE INTERRUPT\n");
|
// The NFC identity is cached; re-read the tag every 4th send (~1 h), so a
|
||||||
#endif
|
// renamed nugget still takes effect without a reset.
|
||||||
increment_minute_index = true;
|
#define TAG_REREAD_SEND_CYCLES 4
|
||||||
// sei();
|
|
||||||
|
// 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
|
||||||
|
|
||||||
|
// How many spooled packets one successful cycle may retry, so a huge backlog
|
||||||
|
// cannot keep the node awake for minutes.
|
||||||
|
#define SPOOL_DRAIN_MAX 10
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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);
|
||||||
|
minute_alarm_fired = true;
|
||||||
|
LOG("\t\t\t\tMINUTE INTERRUPT\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
ISR(INT1_vect) {
|
// Reed switch: one revolution. The magnet holds the reed closed far longer
|
||||||
cli();
|
// 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]++;
|
||||||
|
}
|
||||||
|
reed_is_debouncing = true;
|
||||||
if (!is_debouncing) {
|
EIMSK &= ~(1 << INT1);
|
||||||
total_wheel_counts[index_wheel_count]++;
|
|
||||||
is_debouncing = 1;
|
|
||||||
wdt_isr_enable();
|
wdt_isr_enable();
|
||||||
}
|
}
|
||||||
// sei();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ISR(WDT_vect) {
|
// Debounce window over: allow the next reed pulse to count.
|
||||||
cli();
|
ISR(WDT_vect)
|
||||||
is_debouncing = 0;
|
{
|
||||||
|
reed_is_debouncing = false;
|
||||||
wdt_isr_disable();
|
wdt_isr_disable();
|
||||||
// sei();
|
reed_interrupt_enable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void start_sleeping() {
|
// ---------------------------------------------------------------------------
|
||||||
|
// 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);
|
||||||
rfid_set_i2c_power(false);
|
rfid_set_i2c_power(false);
|
||||||
ldo_set_state(false);
|
ldo_set_state(false);
|
||||||
_delay_ms(10);
|
_delay_ms(10);
|
||||||
sleep_bod_disable();
|
|
||||||
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
|
||||||
sleep_enable();
|
|
||||||
|
// 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();
|
||||||
|
if (!minute_alarm_fired) {
|
||||||
|
sleep_enable();
|
||||||
|
sleep_bod_disable();
|
||||||
|
sei();
|
||||||
|
sleep_cpu();
|
||||||
|
sleep_disable();
|
||||||
|
}
|
||||||
sei();
|
sei();
|
||||||
sleep_cpu();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t get_battery_reading() {
|
// Restore the supplies that sleep_until_interrupt() dropped. Everything on the
|
||||||
|
// I2C bus is dead until this runs.
|
||||||
|
static void wake_peripheral_rails(void)
|
||||||
|
{
|
||||||
|
ldo_set_state(true);
|
||||||
|
rfid_set_i2c_power(true);
|
||||||
|
_delay_ms(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Measurement helpers
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// Battery voltage in millivolts, measured by reading the 1.1 V internal
|
||||||
|
// 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_Enable();
|
||||||
adc_GetConversion(14);
|
adc_GetConversion(ADC_CHANNEL_BANDGAP);
|
||||||
adc_GetConversion(14);
|
adc_GetConversion(ADC_CHANNEL_BANDGAP);
|
||||||
self_value = adc_GetConversion(14);
|
uint16_t raw = adc_GetConversion(ADC_CHANNEL_BANDGAP);
|
||||||
adc_Disable();
|
adc_Disable();
|
||||||
return self_value;
|
|
||||||
|
if (raw == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return (uint16_t)((1100UL * 1023UL) / raw);
|
||||||
}
|
}
|
||||||
|
|
||||||
// i2c Addresses
|
// 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// RTC
|
// Atomically hand out the collected counts and start the next collection
|
||||||
// 0x68 (0xD0 W) (0xD1 R)
|
// 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// NFC
|
// ---------------------------------------------------------------------------
|
||||||
// 0x2D (0x5A W) (0x5B R)
|
// Radio reporting
|
||||||
// 0x53 (0xA6 W) (0xA7 R)
|
// ---------------------------------------------------------------------------
|
||||||
// 0x57 (0xAE W) (0xAF R)
|
|
||||||
|
|
||||||
int main(void) {
|
// 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
|
|
||||||
|
|
||||||
write_last_page_value(0);
|
// Only initialise the spool pointer when it has never been written --
|
||||||
#if DO_UART
|
// clearing it unconditionally would discard every unsent message across a
|
||||||
uart_sendString("Set up last page value for SPI flash\n");
|
// reset.
|
||||||
#endif
|
if (get_last_page() == EEPROM_LAST_PAGE_UNINIT) {
|
||||||
|
write_last_page_value(0);
|
||||||
for (uint8_t c = 0; c < 15; c++) {
|
|
||||||
total_wheel_counts[c] = 0;
|
|
||||||
}
|
}
|
||||||
|
LOG("Set up last page value for SPI flash\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Five long blinks for a successful time sync, five short ones for a failure.
|
||||||
|
static void blink_time_sync_result(bool success)
|
||||||
// Get the nugget's name and wheel diameter
|
{
|
||||||
IDENTIFIER = get_nugget_data();
|
for (uint8_t i = 0; i < 5; i++) {
|
||||||
#if DO_UART
|
led_1_set_state(true);
|
||||||
uart_sendString("Got nugget data from RFID\n");
|
if (success) {
|
||||||
#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) {
|
||||||
cli();
|
sleep_until_interrupt();
|
||||||
|
|
||||||
if (increment_minute_index) {
|
bool minute_elapsed;
|
||||||
#if DO_UART
|
ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
|
||||||
uart_sendString("In minute index\n");
|
{
|
||||||
#endif
|
minute_elapsed = minute_alarm_fired;
|
||||||
increment_minute_index = false;
|
minute_alarm_fired = false;
|
||||||
is_debouncing = 0;
|
}
|
||||||
index_wheel_count += 1;
|
|
||||||
|
|
||||||
rtc_read_interrupt_register();
|
|
||||||
rtc_read_status_register();
|
|
||||||
|
|
||||||
rtc_read_interrupt_register();
|
|
||||||
rtc_read_status_register();
|
|
||||||
|
|
||||||
if (index_wheel_count >= SEND_INTERVAL) {
|
|
||||||
index_wheel_count = 0;
|
|
||||||
ldo_set_state(true);
|
|
||||||
rfm69_init();
|
|
||||||
rfid_set_i2c_power(true);
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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);
|
|
||||||
|
|
||||||
#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);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
}
|
if (minute_elapsed) {
|
||||||
}
|
handle_minute_alarm();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+53
-35
@@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t rtc_set_per_minute_alarm()
|
// Masking out minutes, hours, and day makes alarm 2 match once every minute
|
||||||
|
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() { 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() { 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() { 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() { return write_one_byte(I2C_ADDR, 0x01, 0b00000010); }
|
uint8_t rtc_enable_interrupts(void)
|
||||||
|
|
||||||
uint8_t rtc_read_time_array(uint8_t* data) { return read_n_bytes(I2C_ADDR, 0x06, data, 7); }
|
|
||||||
|
|
||||||
time_struct rtc_read_time()
|
|
||||||
{
|
{
|
||||||
|
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, RTC_REG_SECONDS, data, 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
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)));
|
||||||
@@ -94,15 +111,16 @@ uint8_t rtc_write_time(time_struct tm)
|
|||||||
if (i2c_start((I2C_ADDR << 1) | 0x00))
|
if (i2c_start((I2C_ADDR << 1) | 0x00))
|
||||||
return 1;
|
return 1;
|
||||||
|
|
||||||
i2c_write(0x06);
|
uint8_t err = 0;
|
||||||
i2c_write(DEC2BCD(tm.Second));
|
err |= i2c_write(RTC_REG_SECONDS);
|
||||||
i2c_write(DEC2BCD(tm.Minute));
|
err |= i2c_write(DEC2BCD(tm.Second));
|
||||||
i2c_write(DEC2BCD(tm.Hour));
|
err |= i2c_write(DEC2BCD(tm.Minute));
|
||||||
i2c_write(tm.Wday);
|
err |= i2c_write(DEC2BCD(tm.Hour));
|
||||||
i2c_write(DEC2BCD(tm.Day));
|
err |= i2c_write(tm.Wday);
|
||||||
i2c_write(DEC2BCD(tm.Month));
|
err |= i2c_write(DEC2BCD(tm.Day));
|
||||||
i2c_write(DEC2BCD(y2kYearToTm(tm.Year)));
|
err |= i2c_write(DEC2BCD(tm.Month));
|
||||||
|
err |= i2c_write(DEC2BCD(y2kYearToTm(tm.Year)));
|
||||||
|
|
||||||
i2c_stop();
|
i2c_stop();
|
||||||
return 0;
|
return err ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
+25
-11
@@ -10,7 +10,6 @@
|
|||||||
#include "rfm69.h"
|
#include "rfm69.h"
|
||||||
#include "st25dv.h"
|
#include "st25dv.h"
|
||||||
#include "uart.h"
|
#include "uart.h"
|
||||||
#define I2C_ADDR 0x68
|
|
||||||
|
|
||||||
#ifndef MAX31329_H
|
#ifndef MAX31329_H
|
||||||
#define MAX31329_H
|
#define MAX31329_H
|
||||||
@@ -19,20 +18,35 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#define DEC2BCD(n) (n + (6 * (n / 10)))
|
#define I2C_ADDR 0x68
|
||||||
#define BCD2DEC(n) (n - (6 * (n >> 4)))
|
|
||||||
|
|
||||||
#define tmYearToY2k(Y) ((Y) - 30) // offset is from 2000
|
// MAX31329 register map (the subset this driver touches)
|
||||||
#define y2kYearToTm(Y) ((Y) + 30)
|
#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
|
||||||
|
|
||||||
uint8_t rtc_enable_interrupts();
|
#define RTC_INT_EN_A2IE 0b00000010 // Alarm-2 interrupt enable
|
||||||
uint8_t rtc_set_per_minute_alarm();
|
#define RTC_ALM_MASK_BIT 0x80 // "Don't match this field" bit in each alarm register
|
||||||
|
|
||||||
|
#define DEC2BCD(n) ((n) + (6 * ((n) / 10)))
|
||||||
|
#define BCD2DEC(n) ((n) - (6 * ((n) >> 4)))
|
||||||
|
|
||||||
|
// time_struct.Year is years since 2000, which is exactly what the RTC's 2-digit
|
||||||
|
// year register holds -- no offset. (The old +/-30 round-tripped but stored the
|
||||||
|
// wrong year in the RTC and overflowed BCD above 2069.)
|
||||||
|
#define tmYearToY2k(Y) (Y)
|
||||||
|
#define y2kYearToTm(Y) (Y)
|
||||||
|
|
||||||
|
uint8_t rtc_enable_interrupts(void);
|
||||||
|
uint8_t rtc_set_per_minute_alarm(void);
|
||||||
uint8_t rtc_read_time_array(uint8_t* data);
|
uint8_t rtc_read_time_array(uint8_t* data);
|
||||||
time_struct rtc_read_time();
|
time_struct rtc_read_time(void);
|
||||||
uint8_t rtc_write_time(time_struct tm);
|
uint8_t rtc_write_time(time_struct tm);
|
||||||
uint8_t rtc_set_alarm_config();
|
uint8_t rtc_set_alarm_config(void);
|
||||||
uint8_t rtc_read_status_register();
|
uint8_t rtc_read_status_register(void);
|
||||||
uint8_t rtc_read_interrupt_register();
|
uint8_t rtc_read_interrupt_register(void);
|
||||||
uint8_t rtc_read_register(uint8_t addr);
|
uint8_t rtc_read_register(uint8_t addr);
|
||||||
void uart_print_rtc_time(time_struct td);
|
void uart_print_rtc_time(time_struct td);
|
||||||
RTC_RFM69_STATUS set_time_from_rfm69(identifier_results id_data);
|
RTC_RFM69_STATUS set_time_from_rfm69(identifier_results id_data);
|
||||||
|
|||||||
+54
-19
@@ -1,10 +1,26 @@
|
|||||||
|
// 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"
|
||||||
ndef_message readNDEFText(unsigned char *buf) {
|
// Everything read here comes off an NFC tag that anyone can write, so every
|
||||||
int addr = 0;
|
// length taken from the buffer is bounds-checked before it is used.
|
||||||
NDEF_MSG.success = 0;
|
#define NDEF_NEED(n) \
|
||||||
|
{ \
|
||||||
|
if ((addr + (uint16_t)(n)) > buf_len) { \
|
||||||
|
NDEF_MSG.success = NDEF_ERR_TRUNCATED; \
|
||||||
|
return NDEF_MSG; \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
ndef_message readNDEFText(unsigned char* buf, uint8_t buf_len)
|
||||||
|
{
|
||||||
|
uint16_t addr = 0;
|
||||||
|
NDEF_MSG.success = 0;
|
||||||
|
NDEF_MSG.payload_len = 0;
|
||||||
|
NDEF_MSG.payload[0] = '\0';
|
||||||
|
|
||||||
|
NDEF_NEED(2);
|
||||||
if (buf[0] != NDEF_TLV) {
|
if (buf[0] != NDEF_TLV) {
|
||||||
NDEF_MSG.success = 1;
|
NDEF_MSG.success = 1;
|
||||||
return NDEF_MSG;
|
return NDEF_MSG;
|
||||||
@@ -18,6 +34,7 @@ ndef_message readNDEFText(unsigned char *buf) {
|
|||||||
// int len_field = buf[1];
|
// int len_field = buf[1];
|
||||||
addr = 2;
|
addr = 2;
|
||||||
|
|
||||||
|
NDEF_NEED(3);
|
||||||
// bool is_short_record = (buf[addr] & NDEF_SHORT_RECORD) == NDEF_SHORT_RECORD;
|
// bool is_short_record = (buf[addr] & NDEF_SHORT_RECORD) == NDEF_SHORT_RECORD;
|
||||||
bool has_id_length = (buf[addr] & NDEF_ID_LEN) == NDEF_ID_LEN;
|
bool has_id_length = (buf[addr] & NDEF_ID_LEN) == NDEF_ID_LEN;
|
||||||
uint8_t tnf = buf[addr] & 0x7;
|
uint8_t tnf = buf[addr] & 0x7;
|
||||||
@@ -31,18 +48,18 @@ ndef_message readNDEFText(unsigned char *buf) {
|
|||||||
|
|
||||||
uint8_t id_length = 0;
|
uint8_t id_length = 0;
|
||||||
if (has_id_length) {
|
if (has_id_length) {
|
||||||
|
NDEF_NEED(1);
|
||||||
id_length = buf[addr];
|
id_length = buf[addr];
|
||||||
addr += 1;
|
addr += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t type_value[type_length + 1];
|
// Only the first type byte is ever inspected, so skip the rest rather than
|
||||||
for (uint8_t i = 0; i < type_length; i++) {
|
// copying them into a tag-sized VLA.
|
||||||
type_value[i] = buf[addr];
|
NDEF_NEED(type_length);
|
||||||
addr += 1; // 6
|
uint8_t type_value_0 = (type_length > 0) ? buf[addr] : 0;
|
||||||
}
|
addr += type_length;
|
||||||
|
|
||||||
type_value[type_length] = 0;
|
if (type_value_0 != NDEF_TEXT_RECORD) {
|
||||||
if (type_value[0] != NDEF_TEXT_RECORD) {
|
|
||||||
NDEF_MSG.success = 11;
|
NDEF_MSG.success = 11;
|
||||||
return NDEF_MSG;
|
return NDEF_MSG;
|
||||||
};
|
};
|
||||||
@@ -52,25 +69,43 @@ ndef_message readNDEFText(unsigned char *buf) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (has_id_length && (id_length > 0)) {
|
if (has_id_length && (id_length > 0)) {
|
||||||
|
NDEF_NEED(id_length);
|
||||||
addr += id_length;
|
addr += id_length;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
NDEF_NEED(1);
|
||||||
uint8_t lang_str_len = buf[addr];
|
uint8_t lang_str_len = buf[addr];
|
||||||
|
|
||||||
|
// payload_length covers the language-length byte plus the language code
|
||||||
|
// plus the text. Subtracting without this check wraps a uint8_t to ~250.
|
||||||
|
if (payload_length < ((uint16_t)lang_str_len + 1)) {
|
||||||
|
NDEF_MSG.success = NDEF_ERR_BAD_LENGTH;
|
||||||
|
return NDEF_MSG;
|
||||||
|
}
|
||||||
|
payload_length -= lang_str_len; // Language string
|
||||||
|
payload_length -= 1; // The byte that says how long the language string is
|
||||||
|
|
||||||
|
NDEF_NEED((uint16_t)lang_str_len + 1);
|
||||||
addr += lang_str_len;
|
addr += lang_str_len;
|
||||||
addr += 1;
|
addr += 1;
|
||||||
payload_length -= lang_str_len; // Language string
|
|
||||||
payload_length -= 1; // The byte that says how long the language string is
|
// Leave room for the terminator the UART print and strchr() both rely on.
|
||||||
|
if (payload_length > (sizeof(NDEF_MSG.payload) - 1)) {
|
||||||
|
payload_length = sizeof(NDEF_MSG.payload) - 1;
|
||||||
|
}
|
||||||
|
NDEF_NEED(payload_length);
|
||||||
|
|
||||||
for (uint8_t i = 0; i < (payload_length); i++) {
|
for (uint8_t i = 0; i < (payload_length); i++) {
|
||||||
NDEF_MSG.payload[i] = buf[addr];
|
NDEF_MSG.payload[i] = buf[addr];
|
||||||
addr += 1;
|
addr += 1;
|
||||||
}
|
}
|
||||||
|
NDEF_MSG.payload[payload_length] = '\0';
|
||||||
// NDEF_MSG.payload = payload;
|
|
||||||
NDEF_MSG.payload_len = payload_length;
|
NDEF_MSG.payload_len = payload_length;
|
||||||
#if DO_UART
|
|
||||||
uart_sendString(NDEF_MSG.payload);
|
#if DO_UART
|
||||||
uart_sendString("\n");
|
uart_sendString(NDEF_MSG.payload); // Runtime string, so not LOG()
|
||||||
#endif
|
#endif
|
||||||
|
LOG("\n");
|
||||||
|
|
||||||
return NDEF_MSG;
|
return NDEF_MSG;
|
||||||
};
|
};
|
||||||
+8
-4
@@ -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)
|
||||||
@@ -24,7 +24,11 @@ extern "C" {
|
|||||||
#define NDEF_TEXT_RECORD 0x54
|
#define NDEF_TEXT_RECORD 0x54
|
||||||
#define TNF_KNOWN 0x01
|
#define TNF_KNOWN 0x01
|
||||||
|
|
||||||
ndef_message readNDEFText(unsigned char *buf) ;
|
// readNDEFText failure codes reported through ndef_message.success
|
||||||
|
#define NDEF_ERR_TRUNCATED 13
|
||||||
|
#define NDEF_ERR_BAD_LENGTH 14
|
||||||
|
|
||||||
|
ndef_message readNDEFText(unsigned char* buf, uint8_t buf_len);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-3
@@ -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 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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,20 +4,19 @@
|
|||||||
*
|
*
|
||||||
* Created on December 20, 2024, 3:54 PM
|
* Created on December 20, 2024, 3:54 PM
|
||||||
*/
|
*/
|
||||||
|
#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();
|
#ifdef __cplusplus
|
||||||
|
|
||||||
#ifdef __cplusplus
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif /* POWER_MGMT_H */
|
#endif /* POWER_MGMT_H */
|
||||||
|
|
||||||
|
|||||||
+308
-294
@@ -1,152 +1,25 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
int8_t rssi;
|
// ---------------------------------------------------------------------------
|
||||||
uint8_t i;
|
// 1. Register access over SPI ("_rt" = register transfer)
|
||||||
uint8_t len_payload;
|
// ---------------------------------------------------------------------------
|
||||||
uint32_t msg_hash;
|
|
||||||
uint8_t p_hash_1;
|
|
||||||
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;
|
|
||||||
bool cond_4;
|
|
||||||
bool cond_5;
|
|
||||||
|
|
||||||
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;
|
|
||||||
break;
|
|
||||||
} 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");
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
set_rfm69_rx_mode();
|
|
||||||
set_rfm69_tx_mode();
|
|
||||||
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);
|
|
||||||
|
|
||||||
wait_tx_sent();
|
|
||||||
set_rfm69_rx_mode();
|
|
||||||
}
|
|
||||||
|
|
||||||
tx_rx_data_struct rfm69_read_msg()
|
|
||||||
{
|
|
||||||
memset(RX_DATA.msg, ' ', sizeof(RX_DATA.msg));
|
|
||||||
spi_rfm69_select(true);
|
|
||||||
spi_write(REG_FIFO);
|
|
||||||
|
|
||||||
RX_DATA.len = spi_read() - 4;
|
|
||||||
RX_DATA.to = spi_read();
|
|
||||||
RX_DATA.from = spi_read();
|
|
||||||
RX_DATA.dtype = spi_read();
|
|
||||||
RX_DATA.flags = spi_read();
|
|
||||||
uint8_t len_f = RX_DATA.len;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
void rfm69_set_state(bool state)
|
|
||||||
{
|
|
||||||
SET_PIN_OUT(DDRC, DDC2);
|
|
||||||
if (!state) {
|
|
||||||
SET_PIN_HIGH(PORTC, PC2);
|
|
||||||
} else {
|
|
||||||
SET_PIN_LOW(PORTC, PC2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t spi_read_rfm69_rt(uint8_t reg)
|
uint8_t spi_read_rfm69_rt(uint8_t reg)
|
||||||
{
|
{
|
||||||
spi_rfm69_select(true);
|
spi_rfm69_select(true);
|
||||||
spi_write(reg);
|
spi_write(reg);
|
||||||
uint8_t data_read = spi_read(0xFF);
|
uint8_t data_read = spi_read();
|
||||||
spi_rfm69_select(false);
|
spi_rfm69_select(false);
|
||||||
return data_read;
|
return data_read;
|
||||||
}
|
}
|
||||||
@@ -171,21 +44,255 @@ 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
|
void reset_rfm69(void)
|
||||||
// 15 * per-min +30 57
|
{
|
||||||
// three byte hash check 3
|
rfm69_reset_state(true); // Reset line is active high
|
||||||
|
_delay_ms(10);
|
||||||
|
rfm69_reset_state(false);
|
||||||
|
_delay_ms(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rfm69_mode(uint8_t target_mode)
|
||||||
|
{
|
||||||
|
uint8_t mode = spi_read_rfm69_rt(REG_OP_MODE);
|
||||||
|
mode &= ~VAL_OPMODE_MASK;
|
||||||
|
mode |= (target_mode & VAL_OPMODE_MASK);
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
for (uint16_t attempts = 0; attempts < RFM69_TIMEOUT_MS; attempts++) {
|
||||||
|
if (TX_SENT) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_delay_ms(1);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool wait_rx_payload_ready_timeout(uint16_t attempts)
|
||||||
|
{
|
||||||
|
set_rfm69_rx_mode();
|
||||||
|
// Test the flag before spending the tick, so a payload that arrives on the
|
||||||
|
// last attempt is not thrown away.
|
||||||
|
for (uint16_t counter = 0; counter < attempts; counter++) {
|
||||||
|
if (RX_PAYLOAD_READY) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
_delay_ms(1);
|
||||||
|
}
|
||||||
|
return RX_PAYLOAD_READY != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
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 set_rfm69_tx_mode(void)
|
||||||
|
{
|
||||||
|
set_rfm69_power_amp_boost();
|
||||||
|
set_rfm69_mode(VAL_OPMODE_TX);
|
||||||
|
wait_rfm69_mode_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rfm69_rx_mode(void)
|
||||||
|
{
|
||||||
|
set_rfm69_power_amp_normal();
|
||||||
|
set_rfm69_mode(VAL_OPMODE_RX);
|
||||||
|
wait_rfm69_mode_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rfm69_standby(void)
|
||||||
|
{
|
||||||
|
set_rfm69_power_amp_normal();
|
||||||
|
set_rfm69_mode(VAL_OPMODE_STDBY);
|
||||||
|
wait_rfm69_mode_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_rfm69_sleep(void)
|
||||||
|
{
|
||||||
|
set_rfm69_power_amp_normal();
|
||||||
|
set_rfm69_mode(VAL_OPMODE_SLEEP);
|
||||||
|
wait_rfm69_mode_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
// "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)
|
||||||
|
{
|
||||||
|
s->len = 0;
|
||||||
|
s->to = 255;
|
||||||
|
s->from = 255;
|
||||||
|
s->dtype = 0;
|
||||||
|
s->flags = 0;
|
||||||
|
memset(s->msg, ' ', sizeof(s->msg));
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
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(
|
tx_rx_data_struct generate_wheel_counts_message(
|
||||||
identifier_results idd, time_struct time, uint16_t battery_value, volatile uint16_t counts[15])
|
identifier_results idd, time_struct time, uint16_t battery_value, volatile uint16_t counts[15])
|
||||||
{
|
{
|
||||||
|
|
||||||
reset_txrx_struct(&TX_DATA);
|
reset_txrx_struct(&TX_DATA);
|
||||||
memcpy(TX_DATA.msg, idd.name_str, MIN(10, idd.name_len));
|
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));
|
memcpy(TX_DATA.msg + 10, idd.diameter_str, MIN(10, idd.diameter_len));
|
||||||
@@ -204,7 +311,7 @@ tx_rx_data_struct generate_wheel_counts_message(
|
|||||||
TX_DATA.msg[26 + (2 * idx + 2)] = (counts[idx] >> 8) & 0xFF; // MSB second
|
TX_DATA.msg[26 + (2 * idx + 2)] = (counts[idx] >> 8) & 0xFF; // MSB second
|
||||||
}
|
}
|
||||||
|
|
||||||
msg_hash = hash_3bytes(TX_DATA.msg, 57);
|
uint32_t msg_hash = hash_3bytes(TX_DATA.msg, 57);
|
||||||
TX_DATA.msg[57] = msg_hash & 0xFF;
|
TX_DATA.msg[57] = msg_hash & 0xFF;
|
||||||
TX_DATA.msg[58] = (msg_hash >> 8) & 0xFF;
|
TX_DATA.msg[58] = (msg_hash >> 8) & 0xFF;
|
||||||
TX_DATA.msg[59] = (msg_hash >> 16) & 0xFF;
|
TX_DATA.msg[59] = (msg_hash >> 16) & 0xFF;
|
||||||
@@ -217,53 +324,19 @@ tx_rx_data_struct generate_wheel_counts_message(
|
|||||||
return TX_DATA;
|
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_3bytes(unsigned const char* str, uint8_t str_len)
|
||||||
{
|
{
|
||||||
uint32_t hash = 0;
|
uint32_t hash = 0;
|
||||||
|
|
||||||
for (i = 0; i < str_len; i++) {
|
for (uint8_t i = 0; i < str_len; i++) {
|
||||||
hash = (hash * 31 + str[i]) % 0xFFFFFF;
|
hash = (hash * 31 + str[i]) % 0xFFFFFF;
|
||||||
}
|
}
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
void set_rfm69_power_amp_boost()
|
// Hash a NUL-terminated string into [min, max]; used to derive the node's
|
||||||
{
|
// radio address from its name.
|
||||||
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()
|
|
||||||
{
|
|
||||||
spi_write_rfm69_rt(REG_TEST_PA1, VAL_TEST_PA1_NORMAL);
|
|
||||||
spi_write_rfm69_rt(REG_TEST_PA2, VAL_TEST_PA2_NORMAL);
|
|
||||||
};
|
|
||||||
|
|
||||||
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)
|
|
||||||
{
|
|
||||||
|
|
||||||
uint8_t mode = spi_read_rfm69_rt(REG_OP_MODE);
|
|
||||||
mode &= ~VAL_OPMODE_MASK;
|
|
||||||
mode |= (target_mode & VAL_OPMODE_MASK);
|
|
||||||
spi_write_rfm69_rt(REG_OP_MODE, mode);
|
|
||||||
}
|
|
||||||
|
|
||||||
void wait_tx_sent()
|
|
||||||
{
|
|
||||||
while (TX_NOT_SENT)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t hash(const char* str, uint8_t min, uint8_t max)
|
uint8_t hash(const char* str, uint8_t min, uint8_t max)
|
||||||
{
|
{
|
||||||
unsigned int hash = 0;
|
unsigned int hash = 0;
|
||||||
@@ -276,126 +349,67 @@ uint8_t hash(const char* str, uint8_t min, uint8_t max)
|
|||||||
return (hash % range) + min;
|
return (hash % range) + min;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool wait_rx_payload_ready_timeout(uint16_t attempts)
|
void uart_print_tx_rx_data(tx_rx_data_struct tx_rx_print)
|
||||||
{
|
{
|
||||||
set_rfm69_rx_mode();
|
DATA_BUFFER_7[0] = tx_rx_print.len;
|
||||||
uint16_t counter = 0;
|
DATA_BUFFER_7[1] = tx_rx_print.to;
|
||||||
while (1) {
|
DATA_BUFFER_7[2] = tx_rx_print.from;
|
||||||
_delay_ms(1);
|
DATA_BUFFER_7[3] = tx_rx_print.dtype;
|
||||||
WHILE_BREAK(counter, attempts);
|
DATA_BUFFER_7[4] = tx_rx_print.flags;
|
||||||
if (RX_PAYLOAD_READY) {
|
uart_sendString(" ");
|
||||||
return true;
|
uart_print_uint8_array(DATA_BUFFER_7, 5, "LEN,TO,FROM,DTYPE,FLAGS\n");
|
||||||
break;
|
uart_sendString(" ");
|
||||||
}
|
uart_sendStringArray(tx_rx_print.msg, 20);
|
||||||
}
|
uart_sendChar('\n');
|
||||||
return false;
|
uart_sendString(" ");
|
||||||
|
uart_print_uint8_array(tx_rx_print.msg, tx_rx_print.len, "\n");
|
||||||
;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void wait_rx_payload_ready()
|
// ---------------------------------------------------------------------------
|
||||||
{
|
// 6. Radio configuration
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
while (RX_PAYLOAD_NOT_READY) { };
|
void rfm69_init(void)
|
||||||
}
|
|
||||||
|
|
||||||
void wait_rfm69_mode_ready()
|
|
||||||
{
|
|
||||||
while (MODE_NOT_READY)
|
|
||||||
;
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_rfm69_tx_mode()
|
|
||||||
{
|
|
||||||
set_rfm69_power_amp_boost();
|
|
||||||
set_rfm69_mode(VAL_OPMODE_TX);
|
|
||||||
wait_rfm69_mode_ready();
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_rfm69_rx_mode()
|
|
||||||
{
|
|
||||||
set_rfm69_power_amp_normal();
|
|
||||||
set_rfm69_mode(VAL_OPMODE_RX);
|
|
||||||
wait_rfm69_mode_ready();
|
|
||||||
};
|
|
||||||
|
|
||||||
void set_rfm69_standby()
|
|
||||||
{
|
|
||||||
set_rfm69_power_amp_normal();
|
|
||||||
set_rfm69_mode(VAL_OPMODE_STDBY);
|
|
||||||
wait_rfm69_mode_ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_rfm69_sleep()
|
|
||||||
{
|
|
||||||
set_rfm69_power_amp_normal();
|
|
||||||
set_rfm69_mode(VAL_OPMODE_SLEEP);
|
|
||||||
wait_rfm69_mode_ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_rfm69_idle()
|
|
||||||
{
|
|
||||||
set_rfm69_power_amp_normal();
|
|
||||||
set_rfm69_mode(VAL_OPMODE_STDBY);
|
|
||||||
wait_rfm69_mode_ready();
|
|
||||||
}
|
|
||||||
|
|
||||||
void reset_rfm69()
|
|
||||||
{
|
|
||||||
rfm69_reset_state(true);
|
|
||||||
_delay_ms(10);
|
|
||||||
rfm69_reset_state(false);
|
|
||||||
_delay_ms(10);
|
|
||||||
}
|
|
||||||
|
|
||||||
void set_rfm69_tx_power()
|
|
||||||
{
|
|
||||||
uint8_t PA_LEVEL_SET
|
|
||||||
= VAL_PALEVEL_PA1_ON | VAL_PALEVEL_PA2_ON | ((20 + 14) & VAL_PALEVEL_PA1_OUTPUTPOWER);
|
|
||||||
spi_write_rfm69_rt(REG_PA_LEVEL, PA_LEVEL_SET);
|
|
||||||
}
|
|
||||||
|
|
||||||
void rfm69_init()
|
|
||||||
{
|
{
|
||||||
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(REG_FREQ_DEV_MSB, VAL_FREQ_DEV_MSB);
|
// Start transmitting as soon as the FIFO has data (rfm69_write_msg relies
|
||||||
|
// on filling the FIFO in standby because of this)
|
||||||
spi_write_rfm69_rt(
|
spi_write_rfm69_rt(REG_FIFO_THRESH, VAL_TX_START_FIFO_NOT_EMPTY | VAL_FIFO_LEVEL_INTERRUPT);
|
||||||
REG_FIFO_THRESH,
|
spi_write_rfm69_rt(REG_TEST_DAGC, VAL_TEST_DAGC_DEFAULT); // Fading margin improvement
|
||||||
VAL_TX_START_FIFO_NOT_EMPTY | VAL_FIFO_LEVEL_INTERRUPT); // TX condition
|
|
||||||
spi_write_rfm69_rt(REG_TEST_DAGC,
|
|
||||||
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_OOK); // 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, 0x10); // RegFdevMSB
|
|
||||||
spi_write_rfm69_rt(REG_FDEV_LSB, 0x00); // RegFdevLSB
|
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
|
|||||||
+35
-23
@@ -15,14 +15,21 @@
|
|||||||
#ifndef RFM69_H
|
#ifndef RFM69_H
|
||||||
#define RFM69_H
|
#define RFM69_H
|
||||||
|
|
||||||
#define MODE_NOT_READY !(spi_read_rfm69_rt(REG_IRQ_FLAGS1) & VAL_IRQ_FLAGS1_MODEREADY)
|
|
||||||
#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_NOT_SENT !(spi_read_rfm69_rt(REG_IRQ_FLAGS2) & VAL_IRQ_FLAGS2_TX_SENT)
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#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_FIFO 0x00
|
||||||
#define REG_FREQ_MSB 0x07
|
#define REG_FREQ_MSB 0x07
|
||||||
#define REG_FREQ_MIDDLE_SB 0x08
|
#define REG_FREQ_MIDDLE_SB 0x08
|
||||||
@@ -35,7 +42,7 @@ extern "C" {
|
|||||||
#define REG_DATA_MODUL 0x02
|
#define REG_DATA_MODUL 0x02
|
||||||
#define REG_BITRATE_MSB 0x03
|
#define REG_BITRATE_MSB 0x03
|
||||||
#define REG_BITRATE_LSB 0x04
|
#define REG_BITRATE_LSB 0x04
|
||||||
#define REG_FDEV_MSB 0x06
|
#define REG_FDEV_MSB 0x05
|
||||||
#define REG_FDEV_LSB 0x06
|
#define REG_FDEV_LSB 0x06
|
||||||
#define REG_RX_BW 0x19
|
#define REG_RX_BW 0x19
|
||||||
#define REG_AFC_BW 0x1A
|
#define REG_AFC_BW 0x1A
|
||||||
@@ -48,9 +55,15 @@ extern "C" {
|
|||||||
#define REG_IRQ_FLAGS1 0x27
|
#define REG_IRQ_FLAGS1 0x27
|
||||||
#define REG_IRQ_FLAGS2 0x28
|
#define REG_IRQ_FLAGS2 0x28
|
||||||
#define REG_RSSI_VALUE 0x24
|
#define REG_RSSI_VALUE 0x24
|
||||||
|
#define REG_OCP 0x13
|
||||||
|
|
||||||
#define REG_FREQ_DEV_MSB 0x05
|
// Over-current protection must be off while the PA boost registers are set,
|
||||||
#define VAL_FREQ_DEV_MSB 0x10
|
// 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_TEST_DAGC_DEFAULT 0x30
|
||||||
#define VAL_DATA_PACKET_MODE 0x00
|
#define VAL_DATA_PACKET_MODE 0x00
|
||||||
@@ -58,7 +71,8 @@ extern "C" {
|
|||||||
#define VAL_BITRATE_250kbps_MSB 0x00
|
#define VAL_BITRATE_250kbps_MSB 0x00
|
||||||
#define VAL_BITRATE_250kbps_LSB 0x80
|
#define VAL_BITRATE_250kbps_LSB 0x80
|
||||||
|
|
||||||
#define VAL_DATA_MODUL_OOK 0x01
|
#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_TX_START_FIFO_NOT_EMPTY 0x80
|
||||||
#define VAL_FIFO_LEVEL_INTERRUPT 0x0f
|
#define VAL_FIFO_LEVEL_INTERRUPT 0x0f
|
||||||
|
|
||||||
@@ -104,28 +118,26 @@ extern "C" {
|
|||||||
#define VAL_FREQ_433MHz_LSB 0x00
|
#define VAL_FREQ_433MHz_LSB 0x00
|
||||||
|
|
||||||
DATA_SEND_STATUS send_message(tx_rx_data_struct tx_data);
|
DATA_SEND_STATUS send_message(tx_rx_data_struct tx_data);
|
||||||
void rfm69_set_state(bool state);
|
|
||||||
|
|
||||||
uint8_t spi_read_rfm69_rt(uint8_t reg);
|
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_rt(uint8_t reg, uint8_t val);
|
||||||
uint8_t spi_write_rfm69_multiple_rt(uint8_t reg, const char* vals, uint8_t len);
|
uint8_t spi_write_rfm69_multiple_rt(uint8_t reg, const char* vals, uint8_t len);
|
||||||
void set_rfm69_power_amp_boost();
|
void set_rfm69_power_amp_boost(void);
|
||||||
void set_rfm69_power_amp_normal();
|
void set_rfm69_power_amp_normal(void);
|
||||||
tx_rx_data_struct rfm69_read_msg();
|
tx_rx_data_struct rfm69_read_msg(void);
|
||||||
void reset_txrx_struct(tx_rx_data_struct* s);
|
void reset_txrx_struct(tx_rx_data_struct* s);
|
||||||
void rfm69_write_msg(tx_rx_data_struct txrxd);
|
void rfm69_write_msg(tx_rx_data_struct txrxd);
|
||||||
void set_rfm69_mode(uint8_t mode);
|
void set_rfm69_mode(uint8_t mode);
|
||||||
void wait_rfm69_mode_ready();
|
bool wait_rfm69_mode_ready(void);
|
||||||
void set_rfm69_tx_mode();
|
void set_rfm69_tx_mode(void);
|
||||||
void wait_tx_sent();
|
bool wait_tx_sent(void);
|
||||||
void wait_rx_payload_ready();
|
bool wait_rx_payload_ready(void);
|
||||||
void reset_rfm69();
|
void reset_rfm69(void);
|
||||||
void set_rfm69_rx_mode();
|
void set_rfm69_rx_mode(void);
|
||||||
void set_rfm69_standby();
|
void set_rfm69_standby(void);
|
||||||
void set_rfm69_sleep();
|
void set_rfm69_sleep(void);
|
||||||
void set_rfm69_idle();
|
void set_rfm69_idle(void);
|
||||||
void set_rfm69_tx_power();
|
void rfm69_init(void);
|
||||||
void rfm69_init();
|
|
||||||
bool wait_rx_payload_ready_timeout(uint16_t attempts);
|
bool wait_rx_payload_ready_timeout(uint16_t attempts);
|
||||||
uint8_t hash(const char* str, uint8_t min, uint8_t max);
|
uint8_t hash(const char* str, uint8_t min, uint8_t max);
|
||||||
uint32_t hash_3bytes(unsigned const char* str, uint8_t str_len);
|
uint32_t hash_3bytes(unsigned const char* str, uint8_t str_len);
|
||||||
|
|||||||
+9
-17
@@ -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() {
|
|
||||||
return spi_write(0xFF);
|
|
||||||
}
|
|
||||||
|
|||||||
+2
-3
@@ -1,13 +1,12 @@
|
|||||||
#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();
|
uint8_t spi_read(void);
|
||||||
void spi_rfm69_select(bool state);
|
void spi_rfm69_select(bool state);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
+75
-43
@@ -1,88 +1,120 @@
|
|||||||
|
// 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"
|
||||||
|
|
||||||
identifier_results get_nugget_data() {
|
#define IDENT_NAME_MAX (sizeof(IDENTIFIER.name_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)
|
||||||
|
{
|
||||||
|
if (name_len > IDENT_NAME_MAX) {
|
||||||
|
name_len = IDENT_NAME_MAX;
|
||||||
|
}
|
||||||
|
if (diam_len > IDENT_DIAM_MAX) {
|
||||||
|
diam_len = IDENT_DIAM_MAX;
|
||||||
|
}
|
||||||
|
memcpy(IDENTIFIER.name_str, name, name_len);
|
||||||
|
IDENTIFIER.name_str[name_len] = '\0';
|
||||||
|
IDENTIFIER.name_len = name_len;
|
||||||
|
|
||||||
|
memcpy(IDENTIFIER.diameter_str, diam, diam_len);
|
||||||
|
IDENTIFIER.diameter_str[diam_len] = '\0';
|
||||||
|
IDENTIFIER.diameter_len = diam_len;
|
||||||
|
|
||||||
|
IDENTIFIER.hashed = hash(IDENTIFIER.name_str, 0, 59);
|
||||||
|
}
|
||||||
|
|
||||||
|
identifier_results get_nugget_data(void)
|
||||||
|
{
|
||||||
|
|
||||||
NDEF_MSG = rfid_read_first_ndef_entry();
|
NDEF_MSG = rfid_read_first_ndef_entry();
|
||||||
|
|
||||||
|
// readNDEFText reports parse failures through success; without this check a
|
||||||
|
// missing or malformed tag leaves stale/uninitialised bytes in payload and
|
||||||
|
// we transmit them as the node identity.
|
||||||
|
if (NDEF_MSG.success != 0) {
|
||||||
|
#if DO_UART
|
||||||
|
uart_print_uint8(NDEF_MSG.success, "NDEF parse failed, code ");
|
||||||
|
#endif
|
||||||
|
set_identifier("UNKNOWN", 7, "N/A", 3);
|
||||||
|
return IDENTIFIER;
|
||||||
|
}
|
||||||
|
|
||||||
TRIMMED_STRING = remove_spaces(NDEF_MSG.payload, NDEF_MSG.payload_len);
|
TRIMMED_STRING = remove_spaces(NDEF_MSG.payload, NDEF_MSG.payload_len);
|
||||||
char* delim_ptr = strchr(TRIMMED_STRING.str, ',');
|
char* delim_ptr = strchr(TRIMMED_STRING.str, ',');
|
||||||
if (delim_ptr != NULL) {
|
if (delim_ptr != NULL) {
|
||||||
uint8_t index_comma = delim_ptr - TRIMMED_STRING.str;
|
uint8_t index_comma = (uint8_t)(delim_ptr - TRIMMED_STRING.str);
|
||||||
memcpy(IDENTIFIER.name_str, TRIMMED_STRING.str, index_comma);
|
// The diameter is what follows the comma, so its length is the
|
||||||
memcpy(
|
// remainder of the string -- not the whole string's length, which read
|
||||||
IDENTIFIER.diameter_str, TRIMMED_STRING.str + index_comma + 1, TRIMMED_STRING.length);
|
// off the end of the 21-byte buffer.
|
||||||
IDENTIFIER.name_len = index_comma;
|
uint8_t diam_len = (uint8_t)(TRIMMED_STRING.length - index_comma - 1);
|
||||||
IDENTIFIER.diameter_len = TRIMMED_STRING.length - index_comma;
|
set_identifier(TRIMMED_STRING.str, index_comma, delim_ptr + 1, diam_len);
|
||||||
} else {
|
} else {
|
||||||
memcpy(IDENTIFIER.name_str, TRIMMED_STRING.str, TRIMMED_STRING.length);
|
set_identifier(TRIMMED_STRING.str, (uint8_t)TRIMMED_STRING.length, "N/A", 3);
|
||||||
IDENTIFIER.name_len = TRIMMED_STRING.length;
|
|
||||||
memcpy(IDENTIFIER.diameter_str, "N/A", 3);
|
|
||||||
IDENTIFIER.diameter_len = 3;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
IDENTIFIER.hashed = hash(IDENTIFIER.name_str, 0, 59);
|
|
||||||
|
|
||||||
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)
|
||||||
uint8_t i = 0, j = 0;
|
{
|
||||||
memset(TRIMMED_STRING.str, ' ', 20);
|
const uint8_t max_len = sizeof(TRIMMED_STRING.str) - 1;
|
||||||
while (str[i]) {
|
uint8_t j = 0;
|
||||||
if (str[i] != ' ') {
|
|
||||||
|
memset(TRIMMED_STRING.str, 0, sizeof(TRIMMED_STRING.str));
|
||||||
|
for (uint8_t i = 0; (i < len_str) && str[i]; i++) {
|
||||||
|
if ((str[i] != ' ') && (j < max_len)) {
|
||||||
TRIMMED_STRING.str[j++] = str[i];
|
TRIMMED_STRING.str[j++] = str[i];
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
if (i >= len_str) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callers run strchr() over this, so it has to be terminated.
|
||||||
|
TRIMMED_STRING.str[j] = '\0';
|
||||||
TRIMMED_STRING.length = j;
|
TRIMMED_STRING.length = j;
|
||||||
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() {
|
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);
|
||||||
|
|
||||||
memset(DATA_BUFFER_65, ' ', 64);
|
// static: a 65-byte frame here sat on top of an already deep call chain and
|
||||||
|
// was a large part of the stack overrun.
|
||||||
char DATA_BUFFER_INTERNAL[65];
|
static unsigned char DATA_BUFFER_INTERNAL[NDEF_READ_LEN];
|
||||||
rfid_read_memory(DATA_BUFFER_INTERNAL, 64, 0x0000 + 4);
|
memset(DATA_BUFFER_INTERNAL, 0, sizeof(DATA_BUFFER_INTERNAL));
|
||||||
|
rfid_read_memory(DATA_BUFFER_INTERNAL, NDEF_READ_LEN, 0x0000 + 4);
|
||||||
NDEF_MSG = readNDEFText(DATA_BUFFER_INTERNAL);
|
|
||||||
|
|
||||||
|
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() {
|
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);
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-7
@@ -1,24 +1,25 @@
|
|||||||
#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
|
||||||
|
|
||||||
identifier_results get_nugget_data();
|
// Bytes of tag memory pulled in one go to look for the first NDEF record.
|
||||||
ndef_message rfid_read_first_ndef_entry();
|
#define NDEF_READ_LEN 64
|
||||||
|
|
||||||
|
identifier_results get_nugget_data(void);
|
||||||
|
ndef_message rfid_read_first_ndef_entry(void);
|
||||||
void rfid_set_low_power_down(bool state);
|
void rfid_set_low_power_down(bool state);
|
||||||
void rfid_set_i2c_power(bool state);
|
void rfid_set_i2c_power(bool state);
|
||||||
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);
|
||||||
uint8_t rfid_read_system_register();
|
uint8_t rfid_read_system_register(void);
|
||||||
|
|
||||||
trimmed_string_struct remove_spaces(char* str, uint8_t len_str);
|
trimmed_string_struct remove_spaces(char* str, uint8_t len_str);
|
||||||
|
|
||||||
|
|||||||
+44
-47
@@ -1,55 +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 init_spi(void)
|
||||||
SET_PIN_OUT(DDRC, DDC1); // SCK
|
{
|
||||||
SET_PIN_OUT(DDRE, DDE3); // MOSI
|
SET_PIN_OUT(DDRC, DDC1); // SCK1
|
||||||
SET_PIN_IN(DDRC, DDC0); // MISO_RFM69
|
SET_PIN_OUT(DDRE, DDE3); // MOSI1
|
||||||
SET_PIN_HIGH(PORTC, PC0);
|
SET_PIN_IN(DDRC, DDC0); // MISO1 (driven by the slave; no pull-up)
|
||||||
SPCR1= (1<<SPE1) | (1<<MSTR1); // Enable, Master, f_osc/16
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// 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 a
|
||||||
|
// master.
|
||||||
|
SET_PIN_OUT(DDRE, DDE2);
|
||||||
|
SET_PIN_HIGH(PORTE, PE2);
|
||||||
|
|
||||||
void rfm69_reset_state(bool state) {
|
SPCR1 = (1 << SPE1) | (1 << MSTR1); // Enable, Master, SPR1:0 = 00 -> f_osc/4
|
||||||
SET_PIN_OUT(DDRC, DDC2);
|
}
|
||||||
if (state) {
|
|
||||||
SET_PIN_HIGH(PORTC, PC2);
|
|
||||||
} else {
|
|
||||||
SET_PIN_LOW(PORTC, PC2);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_1_set_state(bool state) {
|
// RFM69 reset line: high holds the radio in reset
|
||||||
SET_PIN_OUT(DDRD, DDD4);
|
void rfm69_reset_state(bool state)
|
||||||
if (state) {
|
{
|
||||||
SET_PIN_HIGH(PORTD, PD4);
|
SET_PIN_OUT(DDRC, DDC2);
|
||||||
} else {
|
SET_PIN_TO(PORTC, PC2, state);
|
||||||
SET_PIN_LOW(PORTD, PD4);
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_2_set_state(bool state) {
|
void led_1_set_state(bool state)
|
||||||
SET_PIN_OUT(DDRD, DDD6);
|
{
|
||||||
if (state) {
|
SET_PIN_OUT(DDRD, DDD4);
|
||||||
SET_PIN_HIGH(PORTD, PD6);
|
SET_PIN_TO(PORTD, PD4, state);
|
||||||
} else {
|
}
|
||||||
SET_PIN_LOW(PORTD, PD6);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void led_3_set_state(bool state) {
|
void led_2_set_state(bool state)
|
||||||
SET_PIN_OUT(DDRD, DDD7);
|
{
|
||||||
if (state) {
|
SET_PIN_OUT(DDRD, DDD6);
|
||||||
SET_PIN_HIGH(PORTD, PD7);
|
SET_PIN_TO(PORTD, PD6, state);
|
||||||
} else {
|
}
|
||||||
SET_PIN_LOW(PORTD, PD7);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void ldo_set_state(bool state) {
|
void led_3_set_state(bool state)
|
||||||
SET_PIN_OUT(DDRC, DDC3);
|
{
|
||||||
if (state) {
|
SET_PIN_OUT(DDRD, DDD7);
|
||||||
SET_PIN_HIGH(PORTC, PC3);
|
SET_PIN_TO(PORTD, PD7, state);
|
||||||
} else {
|
}
|
||||||
SET_PIN_LOW(PORTC, PC3);
|
|
||||||
}
|
// Enable line of the LDO that powers the radio and EEPROM
|
||||||
}
|
void ldo_set_state(bool state)
|
||||||
|
{
|
||||||
|
SET_PIN_OUT(DDRC, DDC3);
|
||||||
|
SET_PIN_TO(PORTC, PC3, state);
|
||||||
|
}
|
||||||
|
|||||||
+17
-20
@@ -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 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 */
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
+6
-3
@@ -6,9 +6,9 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "defines.h"
|
#include "defines.h"
|
||||||
#define UBRR_BAUD F_CPU / 16 / BAUD - 1
|
|
||||||
|
|
||||||
#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>
|
||||||
@@ -22,9 +22,12 @@
|
|||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
void uart_init();
|
#define UBRR_BAUD ((F_CPU) / 16 / (BAUD) - 1)
|
||||||
|
|
||||||
|
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);
|
||||||
@@ -34,7 +37,7 @@ void uart_print_float(float meas, const char* buf);
|
|||||||
void uart_print_binary(unsigned char vin, const char* buf);
|
void uart_print_binary(unsigned char vin, const char* buf);
|
||||||
void uart_print_uint8(uint8_t vin, const char* buf);
|
void uart_print_uint8(uint8_t vin, const char* buf);
|
||||||
void uart_print_uint8_array(uint8_t* array, size_t length, const char* buf);
|
void uart_print_uint8_array(uint8_t* array, size_t length, const char* buf);
|
||||||
void uart_wait_until_sent();
|
void uart_wait_until_sent(void);
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user