#include "adc.h" #include "defines.h" #include "interrupts.h" #include "m95128.h" #include "max31329.h" #include "ndef.h" #include "power_mgmt.h" #include "rfm69.h" #include "st25dv.h" #include "states.h" #if DO_UART #include "uart.h" #endif #include #include #include #include #include #include #include #include #if ITERATING #define SEND_INTERVAL 1 #else #define SEND_INTERVAL 15 #endif #define WHEEL_COUNT_SLOTS 15 // Erased EEPROM reads back as 0xFF; anything else is a real page count. #define EEPROM_LAST_PAGE_UNINIT 0xFF uint16_t self_value; 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[WHEEL_COUNT_SLOTS]; RTC_RFM69_STATUS rtc_rfm69_status; ISR(INT0_vect) { // The RTC holds INTB low until its flag registers are read, and this is a // level-triggered interrupt, so mask it here and let main re-arm it once // the RTC has released the line. EIMSK &= ~(1 << INT0); #if DO_UART uart_sendString("\t\t\t\tMINUTE INTERRUPT\n"); #endif increment_minute_index = true; } ISR(INT1_vect) { #if ITERATING increment_minute_index = true; #endif #if DO_UART uart_sendString("\t\t\t\tREED INTERRUPT\n"); #endif if (!is_debouncing) { if (index_wheel_count < WHEEL_COUNT_SLOTS) { total_wheel_counts[index_wheel_count]++; } is_debouncing = 1; // Mask INT1 for the debounce window: the magnet holds the reed closed // (and the pin low) for far longer than one revolution's worth of // bounce, and a level-triggered interrupt would retrigger continuously. EIMSK &= ~(1 << INT1); wdt_isr_enable(); } } ISR(WDT_vect) { is_debouncing = 0; wdt_isr_disable(); reed_interrupt_enable(); } void start_sleeping(void) { spi_eeprom_select(false); spi_rfm69_select(false); rfid_set_low_power_down(true); rfid_set_i2c_power(false); ldo_set_state(false); _delay_ms(10); set_sleep_mode(SLEEP_MODE_PWR_DOWN); cli(); // Don't sleep through work that arrived while we were dropping the rails. // Testing the flag with interrupts off, then sei() immediately before // sleep_cpu(), is the avr-libc idiom that closes that race -- and // sleep_bod_disable() is a timed sequence, so it belongs here and not // before sleep_enable() where it had no effect at all. if (!increment_minute_index) { sleep_enable(); sleep_bod_disable(); sei(); sleep_cpu(); sleep_disable(); } sei(); } uint16_t get_battery_reading(void) { adc_Enable(); adc_GetConversion(14); adc_GetConversion(14); self_value = adc_GetConversion(14); adc_Disable(); return self_value; } // i2c Addresses // RTC // 0x68 (0xD0 W) (0xD1 R) // NFC // 0x2D (0x5A W) (0x5B R) // 0x53 (0xA6 W) (0xA7 R) // 0x57 (0xAE W) (0xAF R) int main(void) { ldo_set_state(true); _delay_ms(10); init_pins(); #if DO_UART uart_init(); uart_sendString("---- STARTING ----\n"); uart_wait_until_sent(); #endif i2c_init(); init_spi(); adc_Initialize(); set_up_reed_interrupt(); set_up_minute_interrupt(); #if DO_UART uart_sendString("Set up AVR interrupts\n"); #endif rtc_set_per_minute_alarm(); rtc_set_alarm_config(); rtc_enable_interrupts(); rtc_read_interrupt_register(); rtc_read_status_register(); #if DO_UART uart_sendString("Set up RTC interrupts\n"); #endif rfm69_init(); #if DO_UART uart_sendString("Initialized RFM69\n"); #endif // Only initialise the spool pointer when it has never been written -- // clearing it unconditionally would discard every unsent message across a // reset. if (get_last_page() == EEPROM_LAST_PAGE_UNINIT) { write_last_page_value(0); } #if DO_UART uart_sendString("Set up last page value for SPI flash\n"); #endif for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) { total_wheel_counts[c] = 0; } // Get the nugget's name and wheel diameter IDENTIFIER = get_nugget_data(); #if DO_UART uart_sendString("Got nugget data from RFID\n"); #endif // Request time from radio rtc_rfm69_status = set_time_from_rfm69(IDENTIFIER); if (rtc_rfm69_status == RTC_RFM69_SET_TIME_SUCCESS) { for (int i = 0; i < 5; i++) { led_1_set_state(true); _delay_ms(90); led_1_set_state(false); _delay_ms(10); } } else { for (int i = 0; i < 5; i++) { led_1_set_state(true); _delay_ms(10); led_1_set_state(false); _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 get_battery_reading(); while (1) { spi_rfm69_select(false); spi_eeprom_select(false); start_sleeping(); // Short critical sections around the shared variables only. The old // blanket cli() stayed in force through the whole radio/EEPROM // sequence, so every reed pulse in that multi-second window was lost. bool minute_elapsed; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { minute_elapsed = increment_minute_index; increment_minute_index = false; } if (minute_elapsed) { #if DO_UART uart_sendString("In minute index\n"); #endif uint8_t slot; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { if (index_wheel_count < WHEEL_COUNT_SLOTS) { index_wheel_count += 1; } slot = index_wheel_count; } // The I2C rail has to be back up before we touch the RTC: sleeping // dropped both the LDO and the tag's supply. ldo_set_state(true); rfid_set_i2c_power(true); _delay_ms(1); rtc_read_interrupt_register(); rtc_read_status_register(); // Reading the flags releases INTB, so INT0 can safely be re-armed. minute_interrupt_enable(); if (slot >= SEND_INTERVAL) { ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { index_wheel_count = 0; } rfm69_init(); rfid_set_low_power_down(false); _delay_ms(1); IDENTIFIER = get_nugget_data(); // Request time from radio if we don't have a good time stamp yet if (rtc_rfm69_status == RTC_RFM69_SET_TIME_FAILED) { rtc_rfm69_status = set_time_from_rfm69(IDENTIFIER); } // Snapshot and clear the counters in one critical section so // a reed pulse landing mid-packet is neither lost nor double // counted. uint16_t counts_snapshot[WHEEL_COUNT_SLOTS]; ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { for (uint8_t c = 0; c < WHEEL_COUNT_SLOTS; c++) { counts_snapshot[c] = total_wheel_counts[c]; total_wheel_counts[c] = 0; } } // Generate wheel counts message reset_txrx_struct(&TX_DATA); TX_DATA = generate_wheel_counts_message( IDENTIFIER, rtc_read_time(), get_battery_reading(), counts_snapshot); #if DO_UART uart_sendString("TX DATA Sent\n"); uart_print_tx_rx_data(TX_DATA); #endif DATA_SEND_STATUS result = send_message(TX_DATA); if (result == DATA_NOT_SENT) { #if DO_UART uart_sendString(" TX DATA not sent, writing to SPI\n"); #endif write_struct_to_last_page(TX_DATA); } if ((result == DATA_SEND_SUCCESS) && (get_last_page() > 0)) { reset_txrx_struct(&TX_DATA); TX_DATA = read_struct_last_page(); TX_DATA.flags = MSG_RESENT_COUNTS; _delay_ms(250); result = send_message(TX_DATA); #if DO_UART uart_sendString("TX DATA From SPI Memory\n"); uart_print_tx_rx_data(TX_DATA); #endif // Only drop the spooled page once it is actually // acknowledged, otherwise a failed retry loses the data. if (result == DATA_SEND_SUCCESS) { delete_last_page(); } #if DO_UART else { uart_sendString(" SPI not sent\n"); } #endif } } } } }