Hydro Node Wireless Sensor Network

Wireless Sensor Network Node Design with Code

In 2014 I launched Mayim, LLC (a spinoff from the USDA SCRI-MINDS project) to reduce wasted water being applied to crops when irrigating. Most farmers overwater their crops since to underwater means death to their crops. By accurately controlling irrigation we were able to not only reduce water usage, but also reduce pesticide usage, reduce fertilizer usage, reduce leeching of those chemicals to the environment; all while improving crop quality and reducing disease (and we have many scientific research papers from many farms/universities to back those claims).

Our approach to improving crop quality was to develop a wireless sensor network that was also able to control local irrigation solenoids (and pumps) and sense at each area of crops growing. This allowed for adjusting irrigation automatically based on local weather, current field conditions, and plant growth models. Unfortunately during COVID, Mayim shutdown. However the positive of that is that I can share the below architecture and code with you. This post will mostly just be discussing the “Hydro” node and not the full Sensorweb system that included the basestation and all of its software for monitoring data, analyzing data, and controlling irrigation.

Wireless “Hydro” nodes distributed thoughout the farm collected data from various sensors and/or controlled irrigation near the node. Typically a node would be used to control irrigation to a small area of crops, to a specific type of crop, or to various indicator species. Nodes were able to control irrigation based on a schedule or based on these local sensors (which was great if communications were lost with the basestation). However the real power of this system was that all of the data was sent to a central onsite basestation that allowed for using data from other nodes/sources for “global” irrigation control. This allowed for a local weather station to provide data that could impact if a node would water or not, or for soil moisture sensors in one area to control irrigation solenoids at another location. In addition plant scientists have been developing models for the optimal way to water various plants, some of these models could say irrigate for 30s, wait 2.5 minutes, irrigate for 92s, etc, etc… No human would practically follow those instructions, however since we had local sensors, environmental data, and the like, we were able to implement these models and successfully automate do model based irrigation.

In just about every use case farmers immediately noticed a reduction in water, fertilizer and pesticide usage. At one farm in Georgia there was no real reduction in irrigation water usage, in fact the water used was just about exactly the same between the human controlled areas and the automated control areas. Finally the farmer saw that his irrigation manager would look how much water the automated system applied, and then would apply the same amount of water to the crops under his care. 
So at the end of the day this system still provided significant benefits to that grower.

Node Hardware

We will start by quickly looking at the primary part of the hardware schematic showing the MSP430 processor. More specifically a MSP430F5529IPN is used in this design. The battery operated node in the field used a Digi XSC 900Mhz radio to communicate with a central Basestation that could be located several kilometers away. Onboard the node there were 3 sensor inputs as well as the ability to control 2 DC latching solenoids, 1 AC solenoid, and/or 1 pump.

Business Advice: We combined the DC, AC, and pump controls on a single node so that we would only need a single node in inventory. The incremental cost to put all of that functionality on a single node was minimal. However many customers felt like they were overpaying for a "deluxe" product since they only needed DC, AC OR pump controls; and not all three. However customers had no issue paying the price of a node when we did not populate the parts of the circuit board that the customer did not need and sold it as separate products (arguably not true since we went out of business, but cost was not really our issue).
Hardware Schematic of the microprocessor on the Hydro Node. Other parts of the schematic with power, sensor inputs, solenoid/pump control and the like are not shown here.

Node Firmware

Now that we have had a quick look at the hardware, lets look at some of the key software elements.

In case you try following along at home, this project was made with TI Code Composer Studio v7 and used the msp430_driverlib_2_91_13_01 MSP430F5xx_6xx library (if you are really following along at home that library from TI has a bug in the ADC function calls).

The basic functions of the node was to wake up at a given time interval and check if irrigation was needed. If irrigation was needed various profiles could be run to irrigate crops in a given manner. After the irrigation the node would go into a deep sleep to save power, during the irrigation the node would drop to a lower power mode between cycles again to save power. There was also a switch on the board to overide the sleeping that was useful for debugging, configuration, or for nodes that did not use batteries and used AC power. Commands to change settings, data from sensors on the node, and data from the irrigation were all published via the digi radio.

I am not really adding much content to the code below, I am mostly posting in case any of these code snippits are useful for anyone and also in case anyone is architecture a similiar system. If this code helps you please leave a comment at the bottom of this post. If you use a significant amount of code from here please credit it to this blog

Disclaimer: No warrenties, guarantees, claims that this code does or does not do anything or similiar are provided for this code or anything else in this post.

main.c :

#include <msp430.h>
#include "driverlib.h"
#include <node_core_functions.h>
#include <main.h>
#include <console.h>
#include <radio.h>
#include "systick.h"
#include "rtc.h"
#include "debug.h"
#include "irrigate.h"

#define LED_FLASH_MS 500

// Used for the serial port UART reading
#define MAX_CONSOLE_RX_BUFFER_SIZE 100
volatile char console_input[MAX_CONSOLE_RX_BUFFER_SIZE];
volatile unsigned int console_RXByteCtr = 0;
volatile int console_cnt = 0;

/*
 * Set all ports to init the micro with. This saves power and sets us up.
 */
void init_micro()
{
    rtc_init();
    node_wakeup();
}


// USCI 1 console receiver interrupt
// In code check if console_cnt==1, if yes we should read the console_input buffer.
// After reading reset console_cnt=0 and console_RXByteCtr=0
// The stuff immediately below is to make it compatible with GCC, TI or IAR
#pragma vector=USCI_A1_VECTOR
__interrupt void USCI_A1_ISR(void)
{
    if (console_RXByteCtr == MAX_CONSOLE_RX_BUFFER_SIZE) {
        console_input[console_RXByteCtr-1] = 0;
        console_cnt = 1;

    } else {
        //Check if the UCA1RXBUF is different from 0x0d (which is final character)
        if (UCA1RXBUF != 0x0d) {
            console_input[console_RXByteCtr++] = UCA1RXBUF; //If it is, load received character to current input string element
        } else {
            console_cnt = 1; //If it is not, set cnt so we handle the input string
            console_input[console_RXByteCtr] = 0; //Add null character at the end of input string (on the /r)
        }
    }
}

// Function to publish via the CP2102 USB port
void transmit_console(const char *str) {
    while (*str != 0) { //Do this during current element is not equal to null character
        while (!(UCTXIFG & UCA1IFG)); //Ensure that transmit interrupt flag is set
        UCA1TXBUF = *str++; //Load UCA1TXBUF with current string element
    }       //then go to the next element
}

// Convert integer values to strings to send via UART
//void itoa(long unsigned int value, char* result, int base) {
void itoa(uint16_t value, char* result, int base) {
      // check that the base if valid
      if (base < 2 || base > 36) { *result = '\0';}

      char* ptr = result, *ptr1 = result, tmp_char;
      int tmp_value;

      do {
        tmp_value = value;
        value /= base;
        *ptr++ = "zyxwvutsrqponmlkjihgfedcba9876543210123456789abcdefghijklmnopqrstuvwxyz" [35 + (tmp_value - value * base)];
      } while ( value );

      // Apply negative sign
      if (tmp_value < 0) *ptr++ = '-';
      *ptr-- = '\0';
      while(ptr1 < ptr) {
        tmp_char = *ptr;
        *ptr--= *ptr1;
        *ptr1++ = tmp_char;
      }

}

void main(void)
{
    uint32_t ledLastChangeTime = 0;
    uint8_t  ledState = 0;
    uint32_t alarmCount = (uint32_t) -1;
    uint32_t nextIrrigationTime = 0;

    // Halts the watchdog timer.
    // TODO: Start it up somewhere else
    WDT_A_hold(WDT_A_BASE);

    // Initialize all ports as needed
    init_micro();

    // Print info to the console
    char * compile_date = __DATE__;
    char * compile_time = __TIME__;
    debug_out_str("\r\nMayim Hydro Node(TM)\r\n");
    debug_out_str("Firmware Version: ");
    debug_out_uint(MAYIM_FIRMWARE_VERSION);
    debug_out_str("\r\nFirmware Date: ");
    debug_out_str(compile_date);
    debug_out_str(" ");
    debug_out_str(compile_time);
    debug_out_str("\r\nType help for commands\r\n\r\n");

    irrigate_init();

    radio_init();
    radio_run_boot_state_machine();
    radio_run_wakeup_state_machine();
    alarmCount = rtc_alarm_count();
    nextIrrigationTime = irrigate(get_sensor_average()) + rtc_get_seconds_since_epoch();

    while(1)
    {
        if ((systick_get_tick() - ledLastChangeTime) > LED_FLASH_MS) {
            ledLastChangeTime = systick_get_tick();
            set_led(LED_MAIN, ledState);
            ledState = !ledState;
        }

        // Check if we have any USB console messages waiting for us
        if (console_cnt != 0) {
            parse_console((char*)console_input);
            console_cnt = 0;
            console_RXByteCtr = 0;
        }
        if ( alarmCount != rtc_alarm_count() ) { // make sure the radio only happens when time is up
            irrigate(get_sensor_average());
            // NOTE: comment out the next line if doing console-based irrigation testing.
            radio_run_wakeup_state_machine();
            alarmCount = rtc_alarm_count();
            nextIrrigationTime = irrigate(get_sensor_average()) + rtc_get_seconds_since_epoch();
            debug_out_str("Radio complete: sleep for ");
            debug_out_uint(nextIrrigationTime - rtc_get_seconds_since_epoch());
            debug_out_endl();

        }

        if (node_mode_should_sleep() == 0) { // always awake
            if (radio_always_awake_check_control_packet(ALWAYS_ON_RADIO_CHECK_MS)) {
                nextIrrigationTime = irrigate(get_sensor_average()) + rtc_get_seconds_since_epoch();
            }
            if (rtc_get_seconds_since_epoch()  >= nextIrrigationTime) {
                nextIrrigationTime = irrigate(get_sensor_average()) + rtc_get_seconds_since_epoch();
            }
        } else { // should sleep
            ledState = 0; // off
            set_led(LED_MAIN, ledState);
            ledState = !ledState;
            systick_delay(1); // small delay so system goes to sleep properly
            if (irrigate_currently_active_commands() == 0) {
                node_go_to_sleep();
            } else {
                debug_out_str("  .. ");
                debug_out_uint(nextIrrigationTime - rtc_get_seconds_since_epoch());

                systick_delay(SECONDS_PER_TICK); // a lighter form of sleep during active irrigation
                if (rtc_get_seconds_since_epoch()  >= nextIrrigationTime) {
                    nextIrrigationTime = irrigate(get_sensor_average()) + rtc_get_seconds_since_epoch();
                }
            }
        }
    } // Main while loop
}  // main loop

node_core_functions.c:

#include <msp430.h>
#include <main.h>
#include "driverlib.h"
#include "node_core_functions.h"
#include "spiflash.h"
#include "systick.h"

const int LED_MAIN = BIT0; // P1.0
const int LED_PUMP = BIT1; //P1.1
const int LED_AC_SOLENOID = BIT2; //P1.2
// Did the DC LED's get switched on the board vs schematic?
const int LED_DC_SOLENOID_1 = BIT4; //P1.3
const int LED_DC_SOLENOID_2 = BIT3; //P1.4

#define SENSOR_IN_PORT GPIO_PORT_P2
const unsigned int SENSOR_IN_PIN[4] = { 0, GPIO_PIN6, GPIO_PIN3, GPIO_PIN0};

// Turns LED's on or off
// LED should be one of the const LED_* from above include file.
// State is 0 (off) or 1 (on)
void set_led(int led, int state) {
    if (state == 0) {
        GPIO_setOutputHighOnPin(GPIO_PORT_P1, led); // set LED high to turn it of
    } else {
        GPIO_setOutputLowOnPin(GPIO_PORT_P1, led); // set LED low to turn it on
    }
}

// Enables 3.3v synchronous buck converter for AC solenoid, and sensors
void enable_3V3_bus() {
    GPIO_setOutputHighOnPin(GPIO_PORT_P3, GPIO_PIN1);
}

// Disables 3.3v synchronous buck converter for AC solenoid, and sensors
void disable_3V3_bus() {
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN1);
}

// Enables 12v boost converter for DC solenoid driver
void enable_12v_bus() {
    GPIO_setOutputHighOnPin(GPIO_PORT_P3, GPIO_PIN6);
}

// disables 12v boost converter for DC solenoid driver
void disable_12v_bus() {
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN6);
}

// Enables the pump relay. Node needs to be plugged into wall power for this to work.
void enable_pump() {
    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN2);
    set_led(LED_PUMP, 1);
}

// Disables the pump relay.
void disable_pump() {
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN2);
    set_led(LED_PUMP, 0);
}

// Enables the AC Solenoid port relay
void enable_ac_solenoid() {
    //enable_3V3_bus();    // TODO: check if BUCK_GOOD is high before using
    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN1);
    systick_delay(4*MILLISECONDS_PER_TICK);
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN1);
    //disable_3V3_bus();
    set_led(LED_AC_SOLENOID, 1);
}

// Disables the AC Solenoid port relay
void disable_ac_solenoid() {
    //enable_3V3_bus();    // TODO: check if BUCK_GOOD is high before using
    GPIO_setOutputHighOnPin(GPIO_PORT_P8, GPIO_PIN0);
    systick_delay(3*MILLISECONDS_PER_TICK);
    GPIO_setOutputLowOnPin(GPIO_PORT_P8, GPIO_PIN0);
    //disable_3V3_bus();
    set_led(LED_AC_SOLENOID, 0);
}

// sets state of the DC latching solenoids (1 = enable solenoid, 0 = close solenoid)
void dc_solenoid(int port_1, int port_2) {
    enable_12v_bus();
    P7OUT |= BIT7; // P7.7 set high. To wake the driver up.

    systick_delay(2*MILLISECONDS_PER_TICK); // Driver needs 1.5ms to stabilize
    // Set the pins for the 2 outputs on the driver chip.
    if (port_1 == 1){
        P5OUT |= BIT7; // P5.7 set high
        P7OUT &= ~BIT4; // P7.4 set low
        set_led(LED_DC_SOLENOID_1, 1);
    } else {
        P5OUT &= ~BIT7; // P5.7 set low
        P7OUT |= BIT4; // P7.4 set high
        set_led(LED_DC_SOLENOID_1, 0);
    }

    if (port_2 == 1){
        P7OUT |= BIT6; // P7.6 set high
        P7OUT &= ~BIT5; // P7.5 set low
        set_led(LED_DC_SOLENOID_2, 1);
    } else {
        P7OUT &= ~BIT6; // P7.6 set low
        P7OUT |= BIT5; // P7.5 set high
        set_led(LED_DC_SOLENOID_2, 0);
    }

    // Wait for the solenoid to latch. Then we can disable the driver.
    systick_delay(50*MILLISECONDS_PER_TICK);

    // Set both ports to 1 to brake state to burn up energy in the coil to prevent damaging the chip
    P5OUT |= BIT7; // P5.7 set high
    P7OUT |= BIT4; // P7.4 set high
    P7OUT |= BIT6; // P7.6 set high
    P7OUT |= BIT5; // P7.5 set high

    __delay_cycles(30000); // wait some amount of time...

    // Now sleep the driver and reset ports (to save power)
    P5OUT &= ~BIT7; // P5.7 set low
    P7OUT &= ~BIT4; // P7.4 set low
    P7OUT &= ~BIT6; // P7.6 set low
    P7OUT &= ~BIT5; // P7.5 set low
    P7OUT &= ~BIT7; // P7.7 set low. The sleep pin for the driver
    disable_12v_bus();
}

// Determine if we are in normal mode or the always on mode.
// 0 = always awake, 1 = normal mode
bool node_mode_should_sleep(void) {
    GPIO_setAsInputPin(GPIO_PORT_P3, GPIO_PIN0);
    uint8_t state = GPIO_getInputPinValue(GPIO_PORT_P3, GPIO_PIN0);
    return(!state);
}

// Enable power to the sensors
void enable_sensor_inputs() {
    GPIO_setOutputLowOnPin(GPIO_PORT_P2, GPIO_PIN7 | GPIO_PIN4 | GPIO_PIN1);  // Sensor Input 1 | 2 | 3
}

// Enable power to the sensors
void disable_sensor_inputs() {
    GPIO_setOutputHighOnPin(GPIO_PORT_P2, GPIO_PIN7 | GPIO_PIN4 | GPIO_PIN1);  // Sensor Input 1 | 2 | 3
}

// Disable radio sleep. Sets the sleep pin to awake.
void disable_radio_sleep() {
    GPIO_setOutputLowOnPin(GPIO_PORT_P3, GPIO_PIN2); // set low to wake up the digi radio
}

// Enable radio sleep, allows the radio to go to sleep when transmit is complete.
void enable_sleep_radio() {
    GPIO_setOutputHighOnPin(GPIO_PORT_P3, GPIO_PIN2); // P3.2 set high to put the digi radio to sleep
}

// Reset radio.
void reset_radio() {
    disable_radio_sleep();
    GPIO_setOutputLowOnPin(GPIO_PORT_P6, GPIO_PIN1); // set low to put device in reset
    systick_delay(5*MILLISECONDS_PER_TICK); // minimum reset pulse to radio is 100ns, this should be 5ms
    GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P6, GPIO_PIN1); // set high to take device out of reset
    systick_delay(5*MILLISECONDS_PER_TICK); // minimum reset pulse to radio is 100ns, this should be 5ms
}


// Get data from an analog sensor port
uint16_t get_sensor_data(int port) {
    int channel = 12 + port;
    uint16_t ain = 0;

    // check to see if the port is plugged in
    uint8_t unplugged = GPIO_getInputPinValue(SENSOR_IN_PORT, SENSOR_IN_PIN[port]);

    if ( ! unplugged ) {
        int i, count = 0;;
        // Sample n times and average the results for more accurate data
        for (i = 1; i < 10; i++) {
            ain += read_analog(channel); // Now get the raw analog value
            count++;
        }
        if (count > 1) {
                ain /= count;
        }
    } // else return zero

    return(ain);
}

uint16_t get_sensor_average(void)
{
    int i;
    uint16_t reading;
    uint16_t sum = 0;
    uint16_t count = 0;

    for (i = 1; i < 4; i++) {
        reading = get_sensor_data(i);
        if ( 0 != reading ) {
            sum += reading;
            count++;
        }
    }
    if (count > 1) {
        sum /= count;
    }
    return sum; // this is zero if not readings are valid

}


// Get battery voltage
uint16_t get_battery_voltage() {
    uint16_t ain = read_analog(12);
    return(ain);
}

// Get voltage from the DC barrel jack (should be around 0 or 9)
uint16_t get_dc_power_supply_voltage() {
    uint16_t ain = read_analog(7);
    return(ain);
}

uint16_t valid_analog_channel(int channel)
{
    switch (channel) {
    case 4 :
    case 5 :
    case 6 :
    case 7 :
    case 12 :
    case 13 :
    case 14 :
    case 15 :
        return (1);
    default :
        return (0);
    }
}
// Reads from an analog input (enable, sample and hold, return value)
// Convert to real units with:
// mV = ( Reference in mV / (2**12 - 1)) * ADC  or
// mV = ( 3300 / 4095) * ADC
uint16_t read_analog(int channel) {
    uint16_t data = (uint16_t) -1;
    if (valid_analog_channel(channel)) {
        uint16_t channelMask = 1u << channel;
        //Switch ON ADC12
        ADC12_A_enable(ADC12_A_BASE);

        // Setup sampling timer to sample-and-hold for 16 clock cycles
        ADC12_A_setupSamplingTimer(ADC12_A_BASE,
                                   ADC12_A_CYCLEHOLD_64_CYCLES,
                                   ADC12_A_CYCLEHOLD_4_CYCLES,
                                   ADC12_A_MULTIPLESAMPLESDISABLE);

        // Configure the Input to the Memory Buffer with the specified Reference Voltages
        ADC12_A_configureMemoryParam param = {0};
        param.memoryBufferControlIndex = ADC12_A_MEMORY_0 + channel;
        param.inputSourceSelect = ADC12_A_INPUT_A0 + channel;

        // Use external ADC reference voltage
        //param.positiveRefVoltageSourceSelect = ADC12_A_VREFPOS_AVCC;
        // Use internal ADC reference voltage
        param.positiveRefVoltageSourceSelect = ADC12_A_VREFPOS_INT;
        param.negativeRefVoltageSourceSelect = ADC12_A_VREFNEG_AVSS;
        param.endOfSequence = ADC12_A_ENDOFSEQUENCE;
        ADC12_A_configureMemory(ADC12_A_BASE, &param);

        //Configure internal reference
        //If ref generator busy, WAIT
        while ( REF_ACTIVE == Ref_isRefGenBusy(REF_BASE) ) ;
        //Select internal ref = 2.5V
        Ref_setReferenceVoltage(REF_BASE, REF_VREF2_5V);
        //Internal Reference ON
        Ref_enableReferenceVoltage(REF_BASE);

        ADC12_A_clearInterrupt(ADC12_A_BASE, channelMask);

        // Start a single conversion, no repeating or sequences.
        ADC12_A_startConversion(ADC12_A_BASE,
                                ADC12_A_MEMORY_0 + channel,
                                ADC12_A_SINGLECHANNEL);

        // Wait for the Interrupt Flag to assert
        while( (ADC12_A_getInterruptStatus(ADC12_A_BASE, channelMask)) == 0) { ; }

        // Get the value
        data =  ADC12_A_getResults(ADC12_A_BASE, ADC12_A_MEMORY_0 + channel);

        // Disable the ADC
        ADC12_A_disableConversions(ADC12_A_BASE, ADC12_A_PREEMPTCONVERSION);
        ADC12_A_disable(ADC12_A_BASE);
    }
    return(data);
}

static void node_prepare_for_sleep(void)
{
    _disable_interrupts();

    // set UARTs to low power mode
    UCA0CTL1 |= UCDORM;
    UCA1CTL1 |= UCDORM;
    GPIO_setOutputHighOnPin(GPIO_PORT_P3, GPIO_PIN4);

    GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P2, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P4, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P5, (GPIO_PIN_ALL8 & ~(XTAL_XIN_PIN |XTAL_XOUT_PIN))); // don't set RTC pins to out
    GPIO_setAsOutputPin(GPIO_PORT_P6, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P7, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P8, GPIO_PIN_ALL8);


    int i;
    for ( i = 1 ; i < 4; i++) {
        GPIO_setAsInputPin(SENSOR_IN_PORT, SENSOR_IN_PIN[i]);
    }
    disable_sensor_inputs();
    //disable_pump();
    //disable_ac_solenoid();
    //dc_solenoid(0,0);
    set_led(LED_MAIN, 0);
    enable_sleep_radio();
    systick_stop();
    disable_3V3_bus();
}
void node_wakeup(void)
{
    _disable_interrupts();

    GPIO_setAsOutputPin(GPIO_PORT_P1, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P2, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P3, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P4, GPIO_PIN_ALL8);
    //    GPIO_setAsOutputPin(GPIO_PORT_P5, GPIO_PIN_ALL8); // not port 5, that has the RTC
    GPIO_setAsOutputPin(GPIO_PORT_P5, BIT7); // for solenoid driver
    GPIO_setAsOutputPin(GPIO_PORT_P6, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P7, GPIO_PIN_ALL8);
    GPIO_setAsOutputPin(GPIO_PORT_P8, GPIO_PIN_ALL8);

    // Set sensor and voltage as analog inputs
    GPIO_setAsInputPin(GPIO_PORT_P6, GPIO_PIN4 | GPIO_PIN5 | GPIO_PIN6 | GPIO_PIN7);
    P6SEL |= BIT4 + BIT5 + BIT6 + BIT7;
    GPIO_setAsInputPin(GPIO_PORT_P7, GPIO_PIN0 | GPIO_PIN1 | GPIO_PIN2 | GPIO_PIN3);
    P7SEL |= BIT0 + BIT1 + BIT2 + BIT3;

    // Set digital sensor inputs as GPIO ins with no pullup
    GPIO_setAsInputPin(GPIO_PORT_P1, GPIO_PIN7);
    GPIO_setAsInputPin(GPIO_PORT_P2, GPIO_PIN2 | GPIO_PIN5);

    // Bring UART's out of low power mode
    UCA0CTL1 &= ~UCDORM;
    UCA1CTL1 &= ~UCDORM;

    // Configure serial port clocks
    // Lets start with the Xbee radio
    P3SEL |= BIT3 + BIT4;
    UCA0CTL1 |= UCSWRST;                      // **Put state machine in reset**
    UCA0CTL1 |= UCSSEL__SMCLK;                // SMCLK
    UCA0CTL1 &= ~UCDORM;
    UCA0BR0 = 109;                            // User Guide has baud rate charts
    UCA0BR1 = 0;                              //
    UCA0MCTL = UCBRS_2;                       // UCBRSx=2, no oversampling
    UCA0CTL1 &= ~UCSWRST;                     // **Initialize USCI state machine**
    UCA0IE |= UCRXIE;                         // Enable USCI_A0 RX interrupt

    // Now the CP2102 console serial (to USB port on the board)
    P4SEL |= BIT4 + BIT5;
    UCA1CTL1 |= UCSWRST;                      // **Put state machine in reset**
    UCA1CTL1 |= UCSSEL__SMCLK;                 // SMCLK
    UCA1BR0 = 109;                            // User Guide has baud rate charts
    UCA1BR1 = 0;                              //
    UCA1MCTL = UCBRS_2;                       // UCBRSx=2, no oversampling
    UCA1CTL1 &= ~UCSWRST;                     // **Initialize USCI state machine**
    UCA1IE |= UCRXIE;                         // Enable USCI_A0 RX interrupt

    //Enable 3V3 bus for solenoids, sensors, etc..
    enable_3V3_bus();

    // Initialize ADC12 with ADC12�s built-in oscillator
    ADC12_A_init(ADC12_A_BASE,
                 ADC12_A_SAMPLEHOLDSOURCE_SC,
                 ADC12_A_CLOCKSOURCE_ADC12OSC,
                 ADC12_A_CLOCKDIVIDER_1);
    systick_init();

    // Now enable all interrupts
    _enable_interrupts();

    reset_radio();
    disable_radio_sleep();

    spiflash_init();

    // Set all relay/solenoids to the off position to verify that they are off
    //disable_pump();
    //disable_ac_solenoid();
    //dc_solenoid(0,0);

    // Should status light turn on when awake from sleep?? Good for debugging, bad for power
    //set_led(LED_MAIN, 1);
}
void node_go_to_sleep(void)
{
    node_prepare_for_sleep();
    __bis_SR_register(LPM3_bits + GIE);
    __no_operation();
    node_wakeup();
}

irrigate.c:

#include <string.h>
#include "main.h"
#include "irrigate.h"
#include "rtc.h"
#include "node_core_functions.h"
#include "debug.h"


typedef enum {  CONTROL_PORT_DC1_SOLENOID = 0,
                CONTROL_PORT_DC2_SOLENOID = 1,
                CONTROL_PORT_AC_SOLENOID = 2,
                NUMBER_NON_PUMP_COMMANDS = 3,
                CONTROL_PORT_PUMP = 3,
                NUMBER_COMMANDS = 4,
} tControlPort;

#define CONTROL_PORT_PUMP_MASK           0x80
#define CONTROL_PORT_AC_SOLENOID_MASK    0x04
#define CONTROL_PORT_DC2_SOLENOID_MASK   0x02
#define CONTROL_PORT_DC1_SOLENOID_MASK   0x01
#define CONTROL_PORT_NONE_SELECTED_MASK  0x00

typedef struct {
    tControlMode controlMode;     // off, irrigate on command, irrigate based on sensor
    uint16_t     setpoint;        // only used when control mode is CONTROL_IRRIGATE_BASED_ON_SENSOR
    uint8_t      controlMask;     // what actuator this command covers (should be one solenoid and optionally a pump)
    tPattern     goal;            // when irrigation starts (for whatever reason), this is the goal irrigration pattern (timeOn, timeBetween, pulsePerEven)
    tPattern     remaining;       // where we are in the irrigation pattern
    uint16_t     timeOnSinceLastReportS; // amount of time spent on since last report
} tCommand;

struct {
    tCommand     command[NUMBER_COMMANDS];   // what command have been set, can only have one per actuator
    uint16_t     commandActiveMask;          // which commands are in the process of running right now
    uint32_t     timeOfLastPassS;            // last time the irrigation command was run, it should not run more than once per second
    uint8_t      controlPortStateMask;       // current state of each actuator
} gIrrigate;

void irrigate_print_status(void)
{
    int i;
    debug_out_str("*******************************\r\n");
    debug_out_str("commandActiveMask: 0x");
    debug_out_hex(gIrrigate.commandActiveMask);
    debug_out_str(", controlPortStateMask: 0x");
    debug_out_hex(gIrrigate.controlPortStateMask);
    debug_out_str(", seconds since last pass ");
    debug_out_uint(rtc_get_seconds_since_epoch() - gIrrigate.timeOfLastPassS);
    debug_out_endl();
    //debug_out_str("Control Devices: DC1, DC2, AC, Pump\r\n");
    //debug_out_str("Mode: OFF=0, ON_GLOBAL_COMMAND=1, ON_LOCAL_SENSOR_CONTROL=2 \r\n");
    //debug_out_uint(gIrrigate.command[i].controlMode);

    for ( i = 0 ; i < NUMBER_COMMANDS ; i++ ) {
        //debug_out_uint(i);
        if (i==0) {
            debug_out_str("DC1");
        } else if (i==1) {
            debug_out_str("DC2");
        } else if (i==2) {
                    debug_out_str("AC");
        } else if (i==3) {
                    debug_out_str("Pump");
        }

        debug_out_str(": mode ");

        if (gIrrigate.command[i].controlMode == 0) {
            debug_out_str("Off");
        } else if (gIrrigate.command[i].controlMode == 1) {
            debug_out_str("Global");
        } else if (gIrrigate.command[i].controlMode == 2) {
            debug_out_str("Local");
        } else {
            debug_out_str("INVALID");
        }

        debug_out_str(", setPoint ");
        debug_out_uint(gIrrigate.command[i].setpoint);
        debug_out_str(", controlMask 0x");
        debug_out_hex(gIrrigate.command[i].controlMask);
        debug_out_str(", timeOnSinceLastReport ");
        debug_out_uint(gIrrigate.command[i].timeOnSinceLastReportS);
        if (CONTROL_OFF != gIrrigate.command[i].controlMask) {
            debug_out_str("\r\n\ttimeOn ");
            debug_out_uint(gIrrigate.command[i].remaining.timeOnS);
            debug_out_str(" of ");
            debug_out_uint(gIrrigate.command[i].goal.timeOnS);
            debug_out_str("\r\n\ttimeBetweenS ");
            debug_out_uint(gIrrigate.command[i].remaining.timeBetweenS);
            debug_out_str(" of ");
            debug_out_uint(gIrrigate.command[i].goal.timeBetweenS);
            debug_out_str("\r\n\tpulsesPerEvent ");
            debug_out_uint(gIrrigate.command[i].remaining.pulsesPerEvent);
            debug_out_str(" of ");
            debug_out_uint(gIrrigate.command[i].goal.pulsesPerEvent);
        }
        debug_out_endl();
    }
    debug_out_str("*******************************\r\n");
}


// Copy the pattern in a memory safe way.
static void irrigate_copy_pattern(tPattern *dst, tPattern *src)
{
    dst->timeOnS = src->timeOnS;
    dst->timeBetweenS = src->timeBetweenS;
    dst->pulsesPerEvent = src->pulsesPerEvent;
}

void irrigate_init(void)
{
    // Make sure all control ports are disabled on power up of the node
    //enable_3V3_bus();
    //enable_12v_bus();
    disable_pump();
    disable_ac_solenoid();
    dc_solenoid(0,0);

    gIrrigate.timeOfLastPassS = rtc_get_seconds_since_epoch();
    memset(&gIrrigate, 0, sizeof(gIrrigate));
    gIrrigate.commandActiveMask = CONTROL_PORT_NONE_SELECTED_MASK;
    gIrrigate.controlPortStateMask = 0;
}

// Set the command parameters for a single actuator
static void irrigate_set_parameters_command(tCommand *cmd, uint8_t controlMask,  tControlMode controlMode, uint16_t setpoint, tPattern *pattern)
{
    cmd->controlMode = controlMode;
    cmd->setpoint = setpoint;
    cmd->controlMask = controlMask;
    irrigate_copy_pattern(&(cmd->goal), pattern);
    memset(&(cmd->remaining), 0, sizeof(cmd->remaining));
    // fixme: stop current pattern, clear actuator? or wait until next cycle?
}

// Called from the radio code, set_parameters configures
void irrigate_set_parameters(uint8_t controlPort, tControlMode controlMode, uint16_t setpoint, tPattern *pattern)
{
    uint16_t i;
    gIrrigate.timeOfLastPassS = rtc_get_seconds_since_epoch();

    for ( i = 0 ; i < NUMBER_NON_PUMP_COMMANDS ; i++) {
        uint8_t mask = (1 << i);
       if (controlPort & mask) {
           mask = (controlPort & mask) | (controlPort & CONTROL_PORT_PUMP_MASK);
           irrigate_set_parameters_command( &gIrrigate.command[i], mask, controlMode, setpoint, pattern);
       }
    }
    if ( controlPort == CONTROL_PORT_PUMP_MASK ) { // pump only command!
        irrigate_set_parameters_command( &gIrrigate.command[CONTROL_PORT_PUMP], controlPort, controlMode, setpoint, pattern);
    }
    irrigate_print_status();
}

// modify the actuators according to the controlMask.
// Note that this is a local function, only made public for testing on the console.
// Some good tests include:
//  irrigate_act 00 then irrigate_act 01 then 03, 07, 87, 86, 84, 80, and 00
void irrigate_set_actuators(uint8_t newControlMask)
{

    uint8_t change = gIrrigate.controlPortStateMask  ^ newControlMask; // XOR will show what is different

    debug_out_str("Irrigate_set_actuators: ");
    debug_out_hex(change);
    debug_out_endl();

    if (CONTROL_PORT_AC_SOLENOID_MASK & change) {
        if ( newControlMask & CONTROL_PORT_AC_SOLENOID_MASK ) {
            enable_ac_solenoid();
        } else {
            disable_ac_solenoid();
        }
    }

    if (CONTROL_PORT_PUMP_MASK & change) {
        if ( newControlMask & CONTROL_PORT_PUMP_MASK ) {
            enable_pump();
        } else {
            disable_pump();
        }
    }

    if ( (CONTROL_PORT_DC1_SOLENOID_MASK & change) ||(CONTROL_PORT_DC2_SOLENOID_MASK & change) ) {
        uint8_t dc1 = 0;
        uint8_t dc2 = 0;
        if ( newControlMask & CONTROL_PORT_DC1_SOLENOID_MASK ) {
            dc1 = 1;
        }
        if ( newControlMask & CONTROL_PORT_DC2_SOLENOID_MASK ) {
            dc2 = 1;
        }
        dc_solenoid(dc1, dc2);
    }

    gIrrigate.controlPortStateMask  = newControlMask;
}



// Given a command, determine where it is in its pattern and what its actuator states need to be
// The return values from this function are:
//   * stillAlive: is this command still running
//   * controlMask: what actuators should be based on this command
//   * timeUntilNextActionS: number of seconds until this command needs to change its state
// Note that this is a local function, only made public for testing on the console.
// Some good tests include:
//  cmd_irrigate FIXME

bool irrigate_run_command(uint16_t timePassed, tCommand *cmd, uint8_t *controlMask, uint32_t *timeUntilNextActionS)
{
    bool stillAlive = true;
    if (cmd->remaining.timeOnS > 0) {
        if (cmd->remaining.timeOnS > timePassed) {
            cmd->remaining.timeOnS -= timePassed;
            *controlMask |= cmd->controlMask;
            *timeUntilNextActionS = cmd->remaining.timeOnS;
        } else {
            cmd->remaining.timeOnS = 0;
            timePassed = 0;
            // fall through to the timeBetween state and check for completion
        }
    }

    if (cmd->remaining.timeOnS == 0) {
        if (cmd->remaining.timeBetweenS > timePassed) {
            cmd->remaining.timeBetweenS -= timePassed;
            *timeUntilNextActionS = cmd->remaining.timeBetweenS;
        } else {
            cmd->remaining.timeBetweenS = 0;
            if (cmd->remaining.pulsesPerEvent > 0) {
                cmd->remaining.pulsesPerEvent--;
            }
            if (cmd->remaining.pulsesPerEvent > 0) {
                cmd->remaining.timeOnS = cmd->goal.timeOnS;
                cmd->remaining.timeBetweenS = cmd->goal.timeBetweenS;
                *controlMask |= cmd->controlMask;
                *timeUntilNextActionS = cmd->remaining.timeOnS;
            } else { // done with pattern!
                stillAlive = false;
                *timeUntilNextActionS = UINT32_MAX;
                if ( cmd->controlMode == CONTROL_IRRIGATE_ON_COMMAND) {
                    cmd->controlMode = CONTROL_OFF;
                }
            }
        }
    }
    return stillAlive;
}

uint16_t irrigate_get_on_time(uint8_t controlPort)
{
    uint16_t timeOnSinceLastReportS = gIrrigate.command[controlPort].timeOnSinceLastReportS;
    //gIrrigate.command[controlPort].timeOnSinceLastReportS = 0; // This will reset the timeOn even if base does not get this time
    return timeOnSinceLastReportS;
}

// Reset the total on time as reported by the control status packet
void irrigate_reset_total_on_time(uint8_t controlPort)
{
    gIrrigate.command[controlPort].timeOnSinceLastReportS = 0;
}

// Increment the amount of time on for each actuator. This should be called before
// changing the actuators.
static void irrigate_calculate_time_on(uint16_t timePassed)
{

    if (gIrrigate.controlPortStateMask & CONTROL_PORT_PUMP_MASK) {
        gIrrigate.command[CONTROL_PORT_PUMP].timeOnSinceLastReportS += timePassed;
    }
    if (gIrrigate.controlPortStateMask & CONTROL_PORT_AC_SOLENOID_MASK) {
        gIrrigate.command[CONTROL_PORT_AC_SOLENOID].timeOnSinceLastReportS += timePassed;
    }
    if (gIrrigate.controlPortStateMask & CONTROL_PORT_DC2_SOLENOID_MASK) {
        gIrrigate.command[CONTROL_PORT_DC2_SOLENOID].timeOnSinceLastReportS += timePassed;
    }
    if (gIrrigate.controlPortStateMask & CONTROL_PORT_DC1_SOLENOID_MASK) {
        gIrrigate.command[CONTROL_PORT_DC1_SOLENOID].timeOnSinceLastReportS += timePassed;
    }
}

uint16_t irrigate_look_for_commands_to_start(uint16_t sensorValue)
{
    uint16_t i;

    for ( i = 0; i < NUMBER_COMMANDS ; i++) {
        uint8_t currentMask = (1 << i);
        bool active = currentMask & gIrrigate.commandActiveMask;
        if ( ! active ) {
            active = (gIrrigate.command[i].controlMode == CONTROL_IRRIGATE_ON_COMMAND);
            active = active || ((gIrrigate.command[i].controlMode == CONTROL_IRRIGATE_BASED_ON_SENSOR)
                        && (gIrrigate.command[i].setpoint >= sensorValue));
            if (active) { // newly active
                gIrrigate.commandActiveMask |= currentMask;
                irrigate_copy_pattern(&(gIrrigate.command[i].remaining), &(gIrrigate.command[i].goal));
            }
        }
    }
    return gIrrigate.commandActiveMask;
}

uint8_t irrigate_currently_active_commands(void)
{
    return gIrrigate.commandActiveMask;
}


uint32_t irrigate_run_active_commands(uint32_t timePassed)
{
    uint32_t secondsToSleep = UINT32_MAX;
    uint16_t i;
    bool stillActive;
    uint8_t overallControlMask = 0;

    if (CONTROL_PORT_NONE_SELECTED_MASK != gIrrigate.commandActiveMask) {
        // run active irrigation
        uint32_t timeUntilNextActionS;
        for ( i = 0 ; i < NUMBER_COMMANDS ; i++) {
            uint8_t currentCmdControlMask = 0;
            uint8_t currentMask = (1 << i);
            if (gIrrigate.commandActiveMask & currentMask) {
                stillActive = irrigate_run_command(timePassed, &gIrrigate.command[i], &currentCmdControlMask, &timeUntilNextActionS);
               if ( stillActive ) {
                   overallControlMask |= currentCmdControlMask;
                   if (secondsToSleep > timeUntilNextActionS) {
                       secondsToSleep = timeUntilNextActionS;
                   }
               } else {
                   gIrrigate.commandActiveMask &= ~currentMask; // clear from active
               }
           }
        }

        irrigate_calculate_time_on(timePassed);
        irrigate_set_actuators(overallControlMask);
    }
    irrigate_print_status();

    debug_out_str("secondsToSleep(for irrigation) ");
    debug_out_uint(secondsToSleep);
    debug_out_endl();
    debug_out_endl();


    return secondsToSleep;
}

// Irrigation control function
//   This returns the number of seconds until the next irrigation command needs to run
//   Note that this may be zero if there are no active commands in which case, the system
//   should run based on the configured measurement rate.
uint32_t irrigate(uint16_t sensorValue)
{
    uint32_t secondsToSleep = UINT32_MAX;
    uint32_t timeNow = rtc_get_seconds_since_epoch();

    // look for any commands based on sensor value being less than the given set point
    irrigate_look_for_commands_to_start(sensorValue);

    // At least one command is currently active, run it
    secondsToSleep = irrigate_run_active_commands(timeNow - gIrrigate.timeOfLastPassS);
    gIrrigate.timeOfLastPassS = timeNow;

    return secondsToSleep;

}

radio.c:

#include <stdlib.h>
#include <main.h>
#include <string.h>
#include "radio.h"
#include "radio_packet.h"
#include "systick.h"
#include "node_core_functions.h"
#include "rtc.h"
#include "debug.h"
#include "irrigate.h"

#define MAX_RADIO_TX_BUFFER_SIZE 75 //100
#define MAX_RADIO_RX_BUFFER_SIZE 75 //100
#define MAX_RADIO_RETRIES 3
#define COMMAND_MODE_TRANSMISSION_END 0x0d
#define COMMAND_MODE_TRANSMISSION_START '>'
#define TIME_FUDGE_FACTOR 2 // amount of time in seconds RTC can be ahead of radio

#define RADIO_SERIAL_NUMBER_LENGTH (PACKET_SERIAL_NUMBER_LENGTH+1) // + terminator

uint8_t gRadioSerialNumber[RADIO_SERIAL_NUMBER_LENGTH];
uint8_t gRadioTxBuffer[MAX_RADIO_TX_BUFFER_SIZE];
uint16_t gRadioLastPacketLen = 0; // so we can retry the last packet without knowing the type
uint16_t gBaseConfirmationStayAwakeMs = 0;
uint16_t gRecordId = 0;

static void radio_send_control_packet_confirmation(tControlConfigPacket* packet);
static void radio_fill_buffer_with_packet(uint8_t *buffer, uint8_t* packet, uint16_t packetLen);

volatile struct {
    uint16_t counter;
    uint8_t  terminator;
    uint8_t  terminated;
    uint8_t  started;
    uint8_t  restartCounter;
    uint8_t  buffer[MAX_RADIO_RX_BUFFER_SIZE];
} gRadioRx;

// USCI 0 Xbee receiver interrupt
// In code check if xbee_cnt==1, if yes we should read the xbee_input buffer.
// After reading reset xbee_cnt=0 and xbee_RXByteCtr=0
// The stuff immediately below is to make it compatible with GCC, TI or IAR
#pragma vector=USCI_A0_VECTOR
__interrupt void USCI_A0_ISR(void)
{
    char ch = UCA0RXBUF;
    if (gRadioRx.counter == MAX_RADIO_RX_BUFFER_SIZE) {
        // this is an error: clear buffer and reset
        gRadioRx.started = false;
        gRadioRx.counter = 0;
        gRadioRx.restartCounter++;
    } else {
        if ( ! gRadioRx.started) {
            if (ch == COMMAND_MODE_TRANSMISSION_START) {
                gRadioRx.counter = 0;
                gRadioRx.started = true;
                gRadioRx.buffer[gRadioRx.counter++] = ch;
            } // else, throw away the character as it isn't the start characters
        } else { // we've already started reading, continue until end
           // Check if the UCA0RXBUF is different from 0x0d (which is final character)
            gRadioRx.buffer[gRadioRx.counter++] = ch; //If it is, load received character to current input string element

            // if doesn't SN match toss whole buffer and restart
            if (gRadioRx.counter == sizeof(tPacketHeader)) {
                tPacketHeader *pHeader = (tPacketHeader *)gRadioRx.buffer;
                uint16_t i;
                for ( i = 0 ; i < PACKET_SERIAL_NUMBER_LENGTH ; i++ ) {
                    if (gRadioSerialNumber[i] != pHeader->serialNumber[i]) {
                        gRadioRx.counter == MAX_RADIO_RX_BUFFER_SIZE; // ditch this buffer
                    }
                }
            }

            if(ch == gRadioRx.terminator){
                gRadioRx.terminated = true; //If it is not, set cnt so we handle the input string
            }
        }
    }
}

// Function to publish via the xbee serial port
static void radio_transmit_xbee_string(const uint8_t *str) {
    while (*str != 0) { //Do this during current element is not equal to null character
        while (!(UCTXIFG & UCA0IFG)); //Ensure that transmit interrupt flag is set
        UCA0TXBUF = *str++; //Load UCA0TXBUF with current string element
    }
}

// Function to publish via the xbee serial port
static void radio_transmit_xbee_data(const uint8_t *data, uint32_t dataLen) {
    debug_out_str("PacketTX 0x: ");
    while (dataLen != 0) { 
        while (!(UCTXIFG & UCA0IFG)) {;} // Ensure that transmit interrupt flag is set
        debug_out_hex(*data);
        debug_out_str(" ");
        UCA0TXBUF = *data++; // Load UCA0TXBUF with current string element
        dataLen--;
    }
    debug_out_endl();
}

static uint16_t radio_calculate_crc(uint8_t *buffer, uint32_t bufLen)
{
    uint16_t crc = 0;
    uint32_t i;
    for (i = 0; i < bufLen; i++) {
        crc = ((crc >> 8 ) & 0xFF) | (crc << 8);
        crc ^= buffer[i];
        crc ^= ((crc & 0xFF) >> 4) & 0xFF;
        crc ^= (crc << 12);
        crc ^= ((crc&0xFF) << 5);
    }
    return crc;
}

static void radio_command_xbee(uint8_t *cmdBuffer, uint8_t *rxBuffer, uint32_t *rxBufLen)
{
    gRadioRx.started = true; // don't wait for start of normal packet
    gRadioRx.terminated = false;
    gRadioRx.counter = 0;
    gRadioRx.started = true;
    radio_transmit_xbee_string(cmdBuffer);
    while ( ! gRadioRx.terminated) {
        ;
    }
    if ( *rxBufLen > gRadioRx.counter) {
        *rxBufLen = gRadioRx.counter;
    }
    if (*rxBufLen > 0) {
        memcpy((void*)rxBuffer, (void*)gRadioRx.buffer, *rxBufLen);
    }
    gRadioRx.counter = 0;
}

uint8_t * radio_get_serial_number(void)
{
    return gRadioSerialNumber;
}

static void radio_ready_rx(void)
{
    gRadioRx.started = false;
    gRadioRx.terminated = false;
    gRadioRx.counter = 0;
}

bool radio_init(void)
{
    uint32_t length;
    bool initializationGood = true;
    gRadioRx.counter = 0;
    gRadioRx.started = true;
    gRadioRx.terminated = false;
    gRadioRx.terminator = COMMAND_MODE_TRANSMISSION_END;

    // enter command mode
    reset_radio();

    systick_delay(1100*MILLISECONDS_PER_TICK);
    radio_transmit_xbee_string("+++");
    systick_delay(1100*MILLISECONDS_PER_TICK);
    if ((gRadioRx.counter >= 2) &&
            (gRadioRx.buffer[0] == 'O') &&
            (gRadioRx.buffer[1] == 'K')){
        uint8_t okBuffer[3];
        // get the serial number from the radio
        memset(gRadioSerialNumber, 0, RADIO_SERIAL_NUMBER_LENGTH);
        length = 4;
        radio_command_xbee("ATSH\r\n", gRadioSerialNumber, &length);
        length = 4;
        radio_command_xbee("ATSL\r\n", &gRadioSerialNumber[4], &length);

        length = sizeof(okBuffer);
        radio_command_xbee("ATSM1\r\n", okBuffer, &length); // set sleep mode to use the pin
        length = sizeof(okBuffer);
        radio_command_xbee("ATWR\r\n", okBuffer, &length); // remember these settings
        length = sizeof(okBuffer);
        radio_command_xbee("ATCN\r\n", okBuffer, &length); // exit command mode
    } else {
        debug_out_str("ERROR!! Radio init failed!\r\n");
        initializationGood = false;
    }

    gRadioRx.terminator = PACKET_END_INDICATOR;
    radio_ready_rx();
    return initializationGood;
}

bool radio_wakeup(void)
{
    bool radioGood = radio_init();
    if (radioGood) {
        debug_out_str("Radio Good\r\n");
    }
    return  radioGood;
}

static uint16_t radio_base_confirmation_stay_awake_time(void)
{
    return gBaseConfirmationStayAwakeMs;
}

static bool radio_good_base_confirmation_packet(ePacketType packetType)
{
    tBaseConfirmationPacket packet;
    int i;
    bool goodPacket = true;

    if (gRadioRx.counter == 0) {
        goodPacket = false;
        debug_out_str("Rx Base Conf: nothing heard from base. \r\n");
    }

    if (goodPacket) {
        if (gRadioRx.counter < (sizeof(packet) - 1)) {
            goodPacket = false;
            //debug_out_str("Rx Base Conf: bad size. \r\n");
            debug_out_str("Rx Base Conf: bad size(");
            debug_out_uint(gRadioRx.counter);
            debug_out_str("/");
            debug_out_uint(sizeof(packet) - 1);
            debug_out_str(")\r\n");
        }
    }

    if (goodPacket) {
        memcpy((void*)&packet, (void*)gRadioRx.buffer, sizeof(packet));
        radio_ready_rx();
        uint16_t calcCrc = radio_calculate_crc((uint8_t*) &packet, (sizeof(packet)-sizeof(packet.footer)));
        if (calcCrc != packet.footer.crc) {
            goodPacket = false;
            //debug_out_str("Rx Base Conf: CRC mismatch. \r\n");
            debug_out_str("Rx Base Conf: CRC mismatch. (");
            debug_out_uint(packet.footer.crc);
            debug_out_str("/");
            debug_out_uint(calcCrc);
            debug_out_str(")\r\n");
        }
    }
    if (goodPacket) {
        if (packet.header.type != TYPE_BASE_CONFIRMATION_PACKET) {
            goodPacket = false;
            debug_out_str("Rx Base Conf: header mismatch. \r\n");
        }
    }
    if (goodPacket) {
        for ( i = 0 ; i < PACKET_SERIAL_NUMBER_LENGTH ; i++ ) {
            if (gRadioSerialNumber[i] != packet.header.serialNumber[i]) {
                goodPacket = false;
                debug_out_str("Rx Base Conf: SN mismatch. \r\n");
            }
        }
    }
    if (goodPacket) {
        // FIXME: Ignore type as it is not being filled in
//        if (packet.packetType != packetType) {
//            goodPacket = false;
//            debug_out_str("Rx Base Conf: packetType mismatch. \r\n");
//        }
    }
    if (goodPacket) {
        uint16_t crcIndex = gRadioLastPacketLen-sizeof(tPacketFooter);
        uint16_t lastPacketCrc = gRadioTxBuffer[crcIndex] + (gRadioTxBuffer[crcIndex+1] << 8);
        if (packet.packetCrc != lastPacketCrc) {
            goodPacket = false;
            debug_out_str("Rx Base Conf: last CRC mismatch. \r\n");
        }
    }

    if (goodPacket) {
        uint32_t currentLocalTime = rtc_get_seconds_since_epoch();
        gBaseConfirmationStayAwakeMs = packet.stayAwakeForMs;

        if (packet.time > currentLocalTime) {
            rtc_set_seconds_since_epoch(packet.time);
        } else if (packet.time < currentLocalTime + TIME_FUDGE_FACTOR) {
            rtc_set_seconds_since_epoch(packet.time);
        }
        debug_out_str("Rx Base Conf: good, stay awake: ");
        debug_out_uint(gBaseConfirmationStayAwakeMs);
        debug_out_endl();
    }

    return goodPacket;

}

bool radio_wait_for_base_confirmation(ePacketType packetType)
{
    bool receivedConfirmation = false;
    uint32_t timeStart = systick_get_tick();
    uint32_t timeToWait = 500 + (rand() % 100); // After sending a data message, how long to wait for confirmation

    while ((! gRadioRx.terminated )
            && ( (systick_get_tick() - timeStart) < timeToWait)) {
        systick_delay(MILLISECONDS_PER_TICK); // sleep lightly
    }

    if (gRadioRx.terminated) {
        receivedConfirmation = radio_good_base_confirmation_packet(packetType);
    } else {
        debug_out_str("Rx Base Conf: timeout. \r\n");
    }

    return receivedConfirmation;
}

bool radio_wait_for_base_confirmation_with_retries(ePacketType packetType, uint16_t retriesCounter)
{
    bool receivedConfirmation = radio_wait_for_base_confirmation(packetType);
    while ( ! receivedConfirmation && retriesCounter > 0 ) {
        radio_resend_last_packet();
        receivedConfirmation = radio_wait_for_base_confirmation(packetType);
        retriesCounter--;
    }

    // For control status packets if the basestation gets the packet we should clear the time on value.
    // This lets us still report accurate irrigation even if the base misses some packets.
    uint8_t i;
    if ((packetType == TYPE_CONTROL_STATUS_PACKET) && (receivedConfirmation)) {
        for ( i = 0 ; i < 4; i++ ) {
            irrigate_reset_total_on_time(i);
        }
    }

    return receivedConfirmation;
}


static bool radio_good_config_control_packet(void)
{
    tControlConfigPacket packet;
    int i;
    bool goodPacket = true;

    if (goodPacket) {
        if (gRadioRx.counter < sizeof(packet) -1) {
            goodPacket = false;
            debug_out_str("Rx Control Config: bad size. \r\n");
        }
    }
    if (goodPacket) {
        memcpy((void*)&packet, (void*)gRadioRx.buffer, sizeof(packet));
        radio_ready_rx();
        uint16_t calcCrc = radio_calculate_crc((uint8_t*) &packet, (sizeof(packet)-sizeof(packet.footer)));
        if (calcCrc != packet.footer.crc) {
            goodPacket = false;
            debug_out_str("Rx Control Config: CRC mismatch. \r\n");
        }
    }
    if (goodPacket) {
        if (packet.header.type != TYPE_CONTROL_CONFIG_PACKET) {
            goodPacket = false;
            debug_out_str("Rx Control Config: header mismatch. \r\n");
        }
    }
    if (goodPacket) {
        for ( i = 0 ; i < PACKET_SERIAL_NUMBER_LENGTH ; i++ ) {
            if (gRadioSerialNumber[i] != packet.header.serialNumber[i]) {
                goodPacket = false;
                debug_out_str("Rx Control Config: SN mismatch. \r\n");
            }
        }
    }
    if (goodPacket) {
        tPattern pattern;
        tControlMode controlMode = (tControlMode) packet.controlMode;
        pattern.timeOnS = packet.timeOnSeconds;
        pattern.timeBetweenS = packet.timeBetweenSeconds;
        pattern.pulsesPerEvent = packet.pulsesPerEvent;
        irrigate_set_parameters(packet.controlPort, controlMode, packet.setpoint, &pattern);
        radio_send_control_packet_confirmation(&packet);

        debug_out_str("Rx Control Config: good!\r\n");
    }

    return goodPacket;
}


static bool radio_wait_for_control_packet(uint16_t stayAwakeForMs)
{

    uint32_t timeStart = systick_get_tick();
    uint32_t timeToWait = stayAwakeForMs;
    bool haveData = false;

    while ((! gRadioRx.terminated )
            && ( (systick_get_tick() - timeStart) < timeToWait)) {
        systick_delay(MILLISECONDS_PER_TICK); // sleep lightly
    }
    if (gRadioRx.terminated) {
        haveData = radio_good_config_control_packet();
    } else {
        debug_out_str("Rx control config: timeout (or no control command sent). \r\n");
    }
    return haveData;
}

void radio_run_boot_state_machine(void)
{
    bool baseConfirmation;
    uint16_t stayAwakeForMs;
    radio_send_test_packet();
    baseConfirmation = radio_wait_for_base_confirmation_with_retries(TYPE_TEST_PACKET, MAX_RADIO_RETRIES);
    if (baseConfirmation) {
        stayAwakeForMs = radio_base_confirmation_stay_awake_time();
        debug_out_str("Received base confirmation packet for test packet\r\n");
    }
    radio_wait_for_control_packet(stayAwakeForMs);
}

void radio_run_wakeup_state_machine(void)
{
    bool baseConfirmation;
    uint16_t stayAwakeForMs = 0;
    radio_wakeup();
    radio_send_measurement_packet();
    baseConfirmation = radio_wait_for_base_confirmation_with_retries(TYPE_MEASUREMENT_PACKET, MAX_RADIO_RETRIES);
    if (baseConfirmation) {
        stayAwakeForMs = radio_base_confirmation_stay_awake_time();
        debug_out_str("Received base confirmation packet for measurement packet\r\n");
    }
    if (baseConfirmation) {
        radio_send_control_status_packet();
        baseConfirmation = radio_wait_for_base_confirmation_with_retries(TYPE_CONTROL_STATUS_PACKET, MAX_RADIO_RETRIES);
        if (stayAwakeForMs < radio_base_confirmation_stay_awake_time()) {
            stayAwakeForMs = radio_base_confirmation_stay_awake_time();
        }
    }

    // Listen for a few times in case we get control configuration packets.
    while (stayAwakeForMs > 0) {
        bool gotData = radio_wait_for_control_packet(stayAwakeForMs);
        if (gotData == true) {
            baseConfirmation = radio_wait_for_base_confirmation_with_retries(TYPE_CONTROL_STATUS_PACKET, MAX_RADIO_RETRIES);
            stayAwakeForMs = radio_base_confirmation_stay_awake_time();
        } else {
            stayAwakeForMs = 0;
        }
    }
}

bool radio_always_awake_check_control_packet(uint16_t stayAwakeForMs)
{

    bool gotData;
    while (stayAwakeForMs > 0) {
        gotData = radio_wait_for_control_packet(stayAwakeForMs);
        if (gotData == true) {
            radio_wait_for_base_confirmation_with_retries(TYPE_CONTROL_STATUS_PACKET, MAX_RADIO_RETRIES);
            stayAwakeForMs = radio_base_confirmation_stay_awake_time();
        } else {
            stayAwakeForMs = 0;
        }
    }
    return gotData;
}

void radio_resend_last_packet(void)
{
    uint16_t retriesIndex = gRadioLastPacketLen-sizeof(tPacketFooter)+sizeof(uint16_t);
    if (gRadioLastPacketLen > 0) {
        gRadioTxBuffer[retriesIndex]++;
        radio_transmit_xbee_data(gRadioTxBuffer, gRadioLastPacketLen);
    } // else nothing has been sent so nothing to resend

}

// separate function in case we need to byte swap
static void radio_fill_buffer_with_packet(uint8_t *buffer, uint8_t* packet, uint16_t packetLen)
{
    memcpy(buffer, packet, packetLen);
}
static void radio_fill_packet_header(tPacketHeader *header, ePacketType type, ePacketDirection direction)
{
    header->direction = direction;
    header->type = type;
    memcpy(header->serialNumber, gRadioSerialNumber,PACKET_SERIAL_NUMBER_LENGTH);
}

static void radio_fill_packet_footer(tPacketFooter *footer, void* packet, uint16_t packetLen)
{
    footer->crc = radio_calculate_crc(packet, packetLen);
    footer->tries = 0;
    footer->end = PACKET_END_INDICATOR;
    footer->terminator = COMMAND_MODE_TRANSMISSION_END;
}

static uint16_t radio_fill_test_packet(uint8_t *buffer) {
    tTestPacket packet;
    uint16_t packetLen;

    radio_fill_packet_header(&packet.header, TYPE_TEST_PACKET, DIRECTION_FROM_NODE);

    packet.deviceType = MAYIM_NODE_DEVICE_TYPE;
    packet.firmwareVersion = MAYIM_FIRMWARE_VERSION;
    packet.payload[0] = 'T';
    packet.payload[1] = 'E';
    packet.payload[2] = 'S';
    packet.payload[3] = 'T';

    packetLen = sizeof(packet);
    radio_fill_packet_footer(&packet.footer, (uint8_t*) &packet, packetLen-sizeof(packet.footer));
    radio_fill_buffer_with_packet(buffer,(uint8_t *) &packet, packetLen);
    return packetLen;
}

static uint16_t radio_fill_measurement_packet(uint8_t *buffer) {
    tMeasurementDataPacket packet;
    uint16_t packetLen;

    radio_fill_packet_header(&packet.header, TYPE_MEASUREMENT_PACKET, DIRECTION_FROM_NODE);
    packet.time = rtc_get_seconds_since_epoch();

    enable_sensor_inputs();
    systick_delay(10*MILLISECONDS_PER_TICK);
    packet.sensor1 = get_sensor_data(1);
    packet.sensor2 = get_sensor_data(2);
    packet.sensor3 = get_sensor_data(3);
    disable_sensor_inputs();

    packet.recordId = gRecordId; gRecordId++;
    packet.pendingFlashRecords = 0;  // FIXME: flash is not working yet

    packet.batteryVoltage = get_battery_voltage();
    packet.dcVoltage = get_dc_power_supply_voltage();

    packetLen = sizeof(packet);
    radio_fill_packet_footer(&packet.footer, (uint8_t*) &packet, packetLen-sizeof(packet.footer));
    radio_fill_buffer_with_packet(buffer,(uint8_t *) &packet, packetLen);
    return packetLen;
}
static uint16_t radio_fill_control_packet(uint8_t *buffer) {
    tControlStatusPacket packet;
    uint16_t packetLen;
    uint16_t i;
    uint16_t numberOfActuators = sizeof(packet.totalOnTime)/sizeof(packet.totalOnTime[0]);

    radio_fill_packet_header(&packet.header, TYPE_CONTROL_STATUS_PACKET, DIRECTION_FROM_NODE);
    packet.time = rtc_get_seconds_since_epoch();

    packet.status = irrigate_currently_active_commands();
    packet.error = 0;

    for ( i = 0 ; i < numberOfActuators; i++ ) {
        packet.totalOnTime[i] = irrigate_get_on_time(i);
    }

    packetLen = sizeof(packet);
    radio_fill_packet_footer(&packet.footer, (uint8_t*) &packet, packetLen-sizeof(packet.footer));
    radio_fill_buffer_with_packet(buffer,(uint8_t *) &packet, packetLen);
    return packetLen;
}
static uint16_t radio_fill_control_packet_confirmation(tControlConfigPacket *packet, uint8_t *buffer)
{
    uint16_t packetLen = sizeof(*packet);
    packet->header.direction = DIRECTION_FROM_NODE;
    radio_fill_packet_footer(&(packet->footer), (uint8_t*) packet, packetLen-sizeof(packet->footer));
    radio_fill_buffer_with_packet(buffer,(uint8_t *) packet, packetLen);
    return packetLen;
}



void radio_send_test_packet()
{
    debug_out_str("Tx Test Packet. \r\n");
    gRadioLastPacketLen = radio_fill_test_packet(gRadioTxBuffer);
    radio_transmit_xbee_data(gRadioTxBuffer, gRadioLastPacketLen);
}

void radio_send_measurement_packet(void)
{
    debug_out_str("Tx Data Packet. \r\n");
    gRadioLastPacketLen = radio_fill_measurement_packet(gRadioTxBuffer);
    radio_transmit_xbee_data(gRadioTxBuffer, gRadioLastPacketLen);
}

void radio_send_control_status_packet(void)
{
    debug_out_str("Tx Control Status Packet. \r\n");
    gRadioLastPacketLen = radio_fill_control_packet(gRadioTxBuffer);
    radio_transmit_xbee_data(gRadioTxBuffer, gRadioLastPacketLen);
}
void radio_send_control_packet_confirmation(tControlConfigPacket* packet)
{
    debug_out_str("Tx Control Conf. \r\n");
    gRadioLastPacketLen = radio_fill_control_packet_confirmation(packet, gRadioTxBuffer);
    radio_transmit_xbee_data(gRadioTxBuffer, gRadioLastPacketLen);
}

void radio_set_channel(uint16_t channel)
{
    uint32_t length;
    gRadioRx.counter = 0;
    gRadioRx.started = true;
    gRadioRx.terminated = false;
    gRadioRx.terminator = COMMAND_MODE_TRANSMISSION_END;

    // enter command mode
    reset_radio();

    systick_delay(1100*MILLISECONDS_PER_TICK);
    radio_transmit_xbee_string("+++");
    systick_delay(1100*MILLISECONDS_PER_TICK);
    if ((gRadioRx.counter >= 2) &&
            (gRadioRx.buffer[0] == 'O') &&
            (gRadioRx.buffer[1] == 'K')){
        uint8_t okBuffer[3];

        // Keep track of valid so we only save if valid setting
        int valid=0;

        // Set the channel and subchannel in the radio
        length = sizeof(okBuffer);

        switch (channel){
        case 0:
            radio_command_xbee("ATHP0\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT0\r\n", okBuffer, &length); // set subchannel
            valid=1;
            break;
        case 1:
            radio_command_xbee("ATHP1\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT1\r\n", okBuffer, &length); // set subchannel
            break;
        case 2:
            radio_command_xbee("ATHP2\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT2\r\n", okBuffer, &length); // set subchannel
            valid=1;
            break;
        case 3:
            radio_command_xbee("ATHP3\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT3\r\n", okBuffer, &length); // set subchannel
            valid=1;
            break;
        case 4:
            radio_command_xbee("ATHP4\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT4\r\n", okBuffer, &length); // set subchannel
            valid=1;
            break;
        case 5:
            radio_command_xbee("ATHP5\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT5\r\n", okBuffer, &length); // set subchannel
            valid=1;
            break;
        case 6:
            radio_command_xbee("ATHP6\r\n", okBuffer, &length); // set channel
            radio_command_xbee("ATDT6\r\n", okBuffer, &length); // set subchannel
            valid=1;
            break;
        default:
            debug_out_str("ERROR: Desired radio channel out of range (0-6)\r\n");
            break;
        }

        // Save the settings if above was valid
        if (valid==1) {
            length = sizeof(okBuffer);
            radio_command_xbee("ATWR\r\n", okBuffer, &length); // remember these settings
        }

        // Read back the channels/subchannels
        length = sizeof(okBuffer);
        radio_command_xbee("ATHP\r\n", okBuffer, &length); // set channel
        debug_out_str("Radio Channels(from radio):");
        debug_out_uint(gRadioRx.buffer[0]-48); // -48 to convert from ascii to the int value
        length = sizeof(okBuffer);
        radio_command_xbee("ATDT\r\n", okBuffer, &length); // set subchannel
        debug_out_str(" Subchannel:");
        debug_out_uint(gRadioRx.buffer[0]-48); // -48 to convert from ascii to the int value
        debug_out_str("\r\n");

        // exit command mode
        length = sizeof(okBuffer);
        radio_command_xbee("ATCN\r\n", okBuffer, &length);
    } else {
        debug_out_str("ERROR!! Radio set channel failed to enter cmd mode!\r\n");
    }

    gRadioRx.terminator = PACKET_END_INDICATOR;
    radio_ready_rx();
}

rtc.c:

#include <msp430.h>
#include "driverlib.h"
#include "rtc.h"
#include "main.h"
#include "time.h"


uint32_t gMinAlarm = DEFAULT_WAKE_UP_TIME_MINUTES;
uint32_t gMinuteCounter = 0;
uint32_t gAlarmCounter = 0;

static void rtc_set_next_alarm(void)
{
    Calendar newTime = RTC_A_getCalendarTime(RTC_A_BASE);
    RTC_A_configureCalendarAlarmParam param = {0};
    param.minutesAlarm = (newTime.Minutes + gMinAlarm) % 60;
    param.hoursAlarm = RTC_A_ALARMCONDITION_OFF;
    param.dayOfWeekAlarm = RTC_A_ALARMCONDITION_OFF;
    param.dayOfMonthAlarm = RTC_A_ALARMCONDITION_OFF;
    RTC_A_configureCalendarAlarm(RTC_A_BASE, &param);

}

static void rtc_init_xtal(void)
{
    GPIO_setAsInputPin(XTAL_PORT, XTAL_XIN_PIN | XTAL_XOUT_PIN);
    P5SEL |= BIT4 + BIT5; // XTAL_PORT set alt func

    //Initializes the XT1 crystal oscillator with no timeout
    //In case of failure, code hangs here.
    //For time-out instead of code hang use UCS_turnOnLFXT1WithTimeout()
    UCS_turnOnLFXT1(UCS_XT1_DRIVE_0, UCS_XCAP_3);

    UCS_setExternalClockSource (XTAL_FREQUENCY, 0 );
    //Select XT1 as ACLK source
    UCS_initClockSignal( UCS_ACLK, UCS_XT1CLK_SELECT, UCS_CLOCK_DIVIDER_1);

    // Enable global oscillator fault flag
    SFR_clearInterrupt(SFR_OSCILLATOR_FAULT_INTERRUPT);
    SFR_enableInterrupt(SFR_OSCILLATOR_FAULT_INTERRUPT);

}

void rtc_init(void)
{
    Calendar currentTime;

    //Setup Current Time for Calendar
    currentTime.Seconds    = 0x00;
    currentTime.Minutes    = 0x00;
    currentTime.Hours      = 0x00;
    currentTime.DayOfWeek  = 0x00;
    currentTime.DayOfMonth = 0x00;
    currentTime.Month      = 0x00;
    currentTime.Year       = 2018;

    rtc_init_xtal();

    RTC_A_initCalendar(RTC_A_BASE, &currentTime, RTC_A_FORMAT_BINARY);

    //Specify an interrupt to assert every minute
    RTC_A_setCalendarEvent(RTC_A_BASE,
        RTC_A_CALENDAREVENT_MINUTECHANGE);

    RTC_A_clearInterrupt(RTC_A_BASE, RTCAIFG);
    RTC_A_enableInterrupt(RTC_A_BASE, RTCAIE);

    //Start RTC Clock
    RTC_A_startClock(RTC_A_BASE);
    rtc_set_next_alarm();

}
void rtc_change_alarm_minutes(uint16_t min)
{
    gMinAlarm = min;
    rtc_set_next_alarm();
}

static const unsigned char rtc_days_in_month[] = {
    31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};

static const unsigned short rtc_ydays[2][13] = {
   /* Normal years */
   { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 },
   /* Leap years */
   { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 }
};

#define LEAPS_THRU_END_OF(y) ((y)/4 - (y)/100 + (y)/400)
#define LEAP_YEAR(year) ((!(year % 4) && (year % 100)) || !(year % 400))

/*
+ * The number of days in the month.
+ */
 int rtc_month_days(unsigned int month, unsigned int year)
 {
    return rtc_days_in_month[month] + (LEAP_YEAR(year) && month == 1);
 }
/*
 * The number of days since January 1. (0 to 365)
 */
int rtc_year_days(unsigned int day, unsigned int month, unsigned int year)
{
   return rtc_ydays[LEAP_YEAR(year)][month] + day-1;
}
uint32_t rtc_get_seconds_since_epoch(void)
{
    uint32_t sse;
    Calendar currentTime = RTC_A_getCalendarTime(RTC_A_BASE);
    struct tm timeCal;
    timeCal.tm_sec = currentTime.Seconds;
    timeCal.tm_min = currentTime.Minutes;
    timeCal.tm_hour = currentTime.Hours;
    timeCal.tm_mday = currentTime.DayOfMonth;
    timeCal.tm_mon = currentTime.Month;
    timeCal.tm_year = currentTime.Year - 1970;
    timeCal.tm_wday = currentTime.DayOfWeek;
    timeCal.tm_yday = rtc_year_days(timeCal.tm_mday, timeCal.tm_mon, timeCal.tm_year);
    sse = mktime(&timeCal);
    return sse;
}
void rtc_set_seconds_since_epoch(uint32_t sse)
{
    Calendar newTime;
    struct tm timeCal = *localtime(&sse);
    newTime.Seconds = timeCal.tm_sec;
    newTime.Minutes = timeCal.tm_min;
    newTime.Hours = timeCal.tm_hour;
    newTime.DayOfMonth = timeCal.tm_mday;
    newTime.Month = timeCal.tm_mon;
    newTime.Year = timeCal.tm_year + 1970;
    newTime.DayOfWeek = timeCal.tm_wday;

    RTC_A_initCalendar(RTC_A_BASE, &newTime, RTC_A_FORMAT_BINARY);
    RTC_A_startClock(RTC_A_BASE);

    rtc_set_next_alarm();
}

uint32_t rtc_alarm_count(void)
{
    return gAlarmCounter;
}

#pragma vector=RTC_VECTOR
__interrupt void RTC_A_ISR (void)
{
    switch (__even_in_range(RTCIV,16)){
        case 0: break;  //No interrupts
        case 2:         //RTCRDYIFG
        case 4:         //RTCEVIFG
            // could interrupt every minute here
            break;
        case 6:         //RTCAIFG
            gAlarmCounter++;
            rtc_set_next_alarm();
            __bic_SR_register_on_exit(LPM3_bits);
            break;
        case 8: break;  //RT0PSIFG
        case 10: break; //RT1PSIFG
        case 12: break; //Reserved
        case 14: break; //Reserved
        case 16: break; //Reserved
        default: break;
    }
}

#pragma vector=UNMI_VECTOR
__interrupt void NMI_ISR(void)
{
  uint16_t status;
  do {
    // If it still can't clear the oscillator fault flags after the timeout,
    // trap and wait here.
      status = UCS_clearAllOscFlagsWithTimeout(1000);
  } while(status != 0);
}

systick.c:

#include <msp430.h>
#include "driverlib.h"
#include "systick.h"

#define SYSTICK_MAX_DELAY 60*1000 // one minute
uint32_t gSysTick;

#pragma vector = TIMER1_A0_VECTOR
__interrupt void timer0_ISR(void)
{
    gSysTick++;
    __bic_SR_register_on_exit(LPM3_bits);
}

void systick_init(void)
{
    gSysTick = 0u;

    Timer_A_initUpModeParam initUpModeParamTimerA1 = {
            TIMER_A_CLOCKSOURCE_SMCLK,
            TIMER_A_CLOCKSOURCE_DIVIDER_1,
            1000,
            TIMER_A_TAIE_INTERRUPT_DISABLE,
            TIMER_A_CCIE_CCR0_INTERRUPT_ENABLE,
            TIMER_A_DO_CLEAR,
            false
    };

    Timer_A_initUpMode(TIMER_A1_BASE, &initUpModeParamTimerA1);
    Timer_A_clearTimerInterrupt(TIMER_A1_BASE);
    Timer_A_clearCaptureCompareInterrupt(TIMER_A1_BASE,TIMER_A_CAPTURECOMPARE_REGISTER_0);
    Timer_A_startCounter(TIMER_A1_BASE,TIMER_A_UP_MODE);
}
void systick_stop(void)
{
    Timer_A_stop(TIMER_A1_BASE);
}

uint32_t systick_get_tick(void)
{
    return gSysTick;
}
void systick_delay(uint32_t delay)
{
    uint32_t tickstart = systick_get_tick();
    uint32_t wait = delay;

    /* Add a period to guarantee minimum wait */
    if (wait == 0) {
        wait++;
    }

    while((systick_get_tick() - tickstart) < wait)
    { __bis_SR_register(LPM0_bits + GIE); } // light sleep
}

console.c:

#include <main.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <node_core_functions.h>
#include "systick.h"
#include "spiflash.h"
#include "rtc.h"
#include "radio.h"
#include "debug.h"
#include "irrigate.h"

#define PARAMETER_SEPARATER     (' ')
#define NUM_TEST_PATTERNS      4

static bool console_find_param_n(const char * buffer, const uint8_t parameterNumber, uint32_t *startLocation);
bool console_receive_param_int16(const char * buffer, const uint8_t parameterNumber, int16_t* parameterInt);
bool console_receive_param_uint16_hex(const char * buffer, const uint8_t parameterNumber, uint16_t* parameterInt);

void parse_console(char buffer[100]) {
    // Print the command recieved
    debug_out_str(buffer);
    debug_out_str(": ");

    // Now act on that command
    if (strcmp(buffer,"pump_on") == 0) {
        enable_pump();
        debug_out_str("Pump Turned On.\r\n");
    } else if (strcmp(buffer,"pump_off") == 0) {
        disable_pump();
        debug_out_str("Pump Turned Off.\r\n");
    } else if (strcmp(buffer,"ac_solenoid_on") == 0) {
        enable_ac_solenoid();
        debug_out_str("AC Solenoid Turned On.\r\n");
    } else if (strcmp(buffer,"ac_solenoid_off") == 0) {
        disable_ac_solenoid();
        debug_out_str("AC Solenoid Turned Off.\r\n");
    } else if (strcmp(buffer,"dc_solenoid 0 0") == 0) {
        dc_solenoid(0, 0);
        debug_out_str("DC Solenoid- 1:OFF 2:OFF.\r\n");
    } else if (strcmp(buffer,"dc_solenoid 1 0") == 0) {
        dc_solenoid(1, 0);
        debug_out_str("DC Solenoid- 1:ON 2:OFF.\r\n");
    } else if (strcmp(buffer,"dc_solenoid 0 1") == 0) {
        dc_solenoid(0, 1);
        debug_out_str("DC Solenoid- 1:OFF 2:ON.\r\n");
    } else if (strcmp(buffer,"dc_solenoid 1 1") == 0) {
        dc_solenoid(1, 1);
        debug_out_str("DC Solenoid- 1:ON 2:ON.\r\n");
    } else if (strcmp(buffer,"voltage_battery") == 0) {
        uint16_t value = get_battery_voltage();
        debug_out_str("Battery Voltage: ");
        debug_out_uint(value);
        debug_out_endl();
    } else if (strcmp(buffer,"voltage_dc") == 0) {
        uint16_t value = get_dc_power_supply_voltage();
        debug_out_str("DC Power Supply Voltage: ");
        debug_out_uint(value);
        debug_out_endl();
    } else if (strcmp(buffer,"get_mode") == 0) {
        int value = node_mode_should_sleep();
        if (value == 0)
            debug_out_str("Mode Switch Status: Normal Mode (0).\r\n");
        if (value == 1)
            debug_out_str("Mode Switch Status: Awake Mode (1).\r\n");
    } else if (strcmp(buffer,"sensor_power_on") == 0) {
        enable_sensor_inputs();
        debug_out_str("Sensor Power Enabled.\r\n");
    } else if (strcmp(buffer,"sensor_power_off") == 0) {
        disable_sensor_inputs();
        debug_out_str("Sensor Power Disabled.\r\n");
    } else if (strcmp(buffer,"get_sensor") == 0) {
        debug_out_str("Sensor Data: ");
        enable_sensor_inputs();
        systick_delay(10);
        debug_out_uint(get_sensor_average());
        debug_out_endl();
        disable_sensor_inputs();
    } else if (strcmp(buffer,"get_data") == 0) {
        enable_sensor_inputs();
        systick_delay(10*MILLISECONDS_PER_TICK);
        uint16_t p1 = get_sensor_data(1);
        uint16_t p2 = get_sensor_data(2);
        uint16_t p3 = get_sensor_data(3);
        disable_sensor_inputs();
        debug_out_str("Sensor Data: ");
        debug_out_uint(p1);
        debug_out_str(", ");
        debug_out_uint(p2);
        debug_out_str(" , ");
        debug_out_uint(p3);
        debug_out_endl();
    } else if (strcmp(buffer,"flash_id") == 0) {
        uint32_t id = spiflash_get_flash_id();
        debug_out_str("Flash ID: ");
        debug_out_hex(id);
        debug_out_hex(id >> 16);
        debug_out_endl();
    } else if (strcmp(buffer,"flash_status") == 0) {
        uint32_t stat = spiflash_read_status_register();
        debug_out_str("Flash status: ");
        debug_out_hex(stat);
        debug_out_hex(stat >> 16);
        debug_out_endl();
    } else if (strcmp(buffer,"flash_test") == 0) {
        uint16_t errors = spiflash_test(0x2000);
        debug_out_str("Flash test errors: ");
        debug_out_uint(errors);
        debug_out_endl();
    } else if (strcmp(buffer,"get_clocks") == 0) {
        debug_out_str("AClk ");
        debug_out_uint(UCS_getACLK()/1000);
        debug_out_str("k SMClk ");
        debug_out_uint(UCS_getSMCLK()/1000);
        debug_out_str("k MClk ");
        debug_out_uint(UCS_getMCLK()/1000);
        debug_out_str("k");
        debug_out_endl();
    } else if (strcmp(buffer,"time") == 0) {
        uint32_t time = rtc_get_seconds_since_epoch();
        debug_out_str("Time ");
        debug_out_uint(time>>16);
        debug_out_str(" ");
        debug_out_uint(time&0xFFFF);
        debug_out_endl();
    } else if (strcmp(buffer,"radio_test") == 0) {
        radio_send_test_packet();
        debug_out_str("Sent.\r\n");
    } else if (strcmp(buffer,"radio_data") == 0) {
        radio_send_measurement_packet();
        debug_out_str("Sent.\r\n");
    } else if (strcmp(buffer,"radio_stat") == 0) {
        radio_send_control_status_packet();
        debug_out_str("Sent.\r\n");
    } else if (strcmp(buffer,"radio_re") == 0) {
        radio_resend_last_packet();
        debug_out_str("Sent.\r\n");
    } else if (strcmp(buffer,"radio_sn") == 0) {
        debug_out_str((char*)radio_get_serial_number());
        debug_out_endl();
    } else if (strcmp(buffer,"radio_boot") == 0) {
        radio_run_boot_state_machine();
    } else if (strcmp(buffer,"radio_init") == 0) {
        radio_init();
        debug_out_str((char*)radio_get_serial_number());
        debug_out_str(" at ");
        debug_out_uint(systick_get_tick()/1000);
        debug_out_str(" sec \r\n");
    } else if (strncmp(buffer,"wakeup_time", sizeof("wakeup_time")-1) == 0) {
        int16_t wakeupTime;
        if (true == console_receive_param_int16(buffer, 1,&wakeupTime)) {
            rtc_change_alarm_minutes(wakeupTime);
            debug_out_str("Ok!\r\n");
        } else {
            debug_out_str("Need a parameter to set wake up time.\r\n");
        }
    } else if (strcmp(buffer,"irrigate_print") == 0) {
        irrigate_print_status();
    } else if (strncmp(buffer,"irrigate_start", sizeof("irrigate_start")-1) == 0) {
        uint16_t activeMask;
        uint16_t sensorValue = 0;
        console_receive_param_uint16_hex(buffer, 1,&sensorValue);
        activeMask = irrigate_look_for_commands_to_start(sensorValue);
        debug_out_str("Active command mask: 0x");
        debug_out_hex(activeMask);
        debug_out_endl();
    } else if (strncmp(buffer,"irrigate_act", sizeof("irrigate_act")-1) == 0) {
        uint16_t controlMask;
        if (true == console_receive_param_uint16_hex(buffer, 1,&controlMask)) {
            irrigate_set_actuators(controlMask);
        } else {
            debug_out_str("Need a parameter to set irrigation.\r\n");
        }
    } else if (strncmp(buffer,"irrigate_run", sizeof("irrigate_run")-1) == 0) {
        int16_t timePassed;
        uint32_t secondsToSleep;
        bool good;
        good = console_receive_param_int16(buffer, 1,&timePassed);
        if (good) {
            secondsToSleep = irrigate_run_active_commands(timePassed);
            debug_out_str("Seconds to sleep ");
            debug_out_uint(secondsToSleep);
            debug_out_endl();
        }
        if ( !good ) {
            debug_out_str("Need time parameter to run irrigation command.\r\n");
        }
    } else if (strncmp(buffer,"irrigate_cmd", sizeof("irrigate_cmd")-1) == 0) {
        int16_t mode;
        uint16_t portMask;
        int16_t patternNum;
        tPattern pattern[NUM_TEST_PATTERNS] = {
                                              //  timeOn, timeBetween, pulsesPer
                                               {  60,       20,         4 },
                                               {  125,      80,         3 },
                                               {   20,      30,         2 },
                                               {  500,      120,        1 }
        };

        bool good;
        good = console_receive_param_uint16_hex(buffer, 1, &portMask);
        if (good) {
            good = console_receive_param_int16(buffer, 2, &mode);
        }
        if (good) {
            good = console_receive_param_int16(buffer, 3, &patternNum);
            if (patternNum >= NUM_TEST_PATTERNS) { good = false; }
        }
        if (good) {
            irrigate_set_parameters(portMask, (tControlMode) mode, 100, &pattern[patternNum]);
            debug_out_str("Ok.\r\n");

        }
        if ( !good ) {
            debug_out_str("Need three parameter to set irrigation command.\r\n");
        }
    } else if (strncmp(buffer,"set_radio_channel", sizeof("set_radio_channel")-1) == 0) {
        int16_t channel;
        console_receive_param_int16(buffer, 1, &channel);

        debug_out_str("Setting Channel to: ");
        debug_out_uint(channel);
        debug_out_str("\r\n");

        // Send command. If channel is out of range it will print current channels.
        radio_set_channel(channel);
    } else if (strcmp(buffer,"help") == 0) {
        debug_out_str("COMMANDS\r\n");
        debug_out_str("pump_on - Turn the pump relay on.\r\n");
        debug_out_str("pump_off - Turn the pump relay off.\r\n");
        debug_out_str("ac_solenoid_on - Turn the AC solenoid relay on.\r\n");
        debug_out_str("ac_solenoid_off - Turn the AC solenoid relay off.\r\n");
        debug_out_str("dc_solenoid [port1] [port2] - Set port 1 and 2 parameters to 1 for enable or 0 to disable.\r\n");
        debug_out_str("get_mode - Get the status of the switch.\r\n");
        debug_out_str("voltage_battery - Get the battery voltage.\r\n");
        debug_out_str("voltage_dc - Get the DC barrel jack external voltage.\r\n");
        debug_out_str("sensor_power_on - Enable power to the 3 sensor ports.\r\n");
        debug_out_str("sensor_power_off - Disable power for the 3 sensor ports.\r\n");
        debug_out_str("get_data - Get raw data from the 3 sensor ports.\r\n");
        debug_out_str("get_sensor - Get averaged data from the 3 sensor ports.\r\n");
        debug_out_str("flash_id - Get the flash ID.\r\n");
        debug_out_str("flash_status - Get the flash status register.\r\n");
        debug_out_str("flash_test - Run through simple write read test of flash (this is destructive!).\r\n");
        debug_out_str("get_clocks - get current clock rates.\r\n");
        debug_out_str("time - get current seconds since epoch.\r\n");
        debug_out_str("radio_test - send radio test packet.\r\n");
        debug_out_str("radio_data - send radio data measurement packet.\r\n");
        debug_out_str("radio_stat - send radio config status packet.\r\n");
        debug_out_str("radio_re - resend last packet.\r\n");
        debug_out_str("radio_boot - re-run boot state machine.\r\n");
        debug_out_str("radio_sn - print the radio serial number.\r\n");
        debug_out_str("radio_init - reinit radio and print the radio serial number.\r\n");
        debug_out_str("set_radio_channel [channel] - set new radio channel (0-6)\r\n");
        debug_out_str("wakeup_time [time in min] - set wakeup timer.\r\n");
        debug_out_str("irrigate_print - print irrigation state.\r\n");
        debug_out_str("irrigate_act [controlMask in hex] - set current irrigation state.\r\n");
        debug_out_str("irrigate_start [optional sensor val] - start irrigation commands.\r\n");
        debug_out_str("irrigate_run [timePassedS] [avtiveMask] - run a given port's command.\r\n");
        debug_out_str("irrigate_cmd [portMask] [mode] [pattern] - set a given port's command.\r\n");
        debug_out_str("help - this menu.\r\n");
    } else {


        debug_out_str("Command Not Found. Try help command\r\n");
    }
}


// ConsoleParamFindN
// Find the start location of the nth parametr in the buffer where the command itself is parameter 0
static bool console_find_param_n(const char * buffer, const uint8_t parameterNumber, uint32_t *startLocation)
{
    uint32_t bufferIndex = 0;
    uint32_t parameterIndex = 0;
    bool success;

    while ( ( parameterNumber != parameterIndex ) && ( buffer[bufferIndex] != NULL) )
    {
        if ( PARAMETER_SEPARATER == buffer[bufferIndex] )
        {
            parameterIndex++;
        }
        bufferIndex++;
    }
    if  ( ( buffer[bufferIndex] == NULL) )
    {
        success = false;
    } else {
        *startLocation = bufferIndex;
        success = true;
    }
    return success;
}

// ConsoleReceiveParamInt16
// Identify and obtain a parameter of type int16_t, sent in in decimal, possibly with a negative sign.
// Note that this uses atoi, a somewhat costly function. You may want to replace it, see ConsoleReceiveParamHexUint16
// for some ideas on how to do that.
bool console_receive_param_int16(const char * buffer, const uint8_t parameterNumber, int16_t* parameterInt)
{
    uint32_t startIndex = 0;
    uint32_t i;
    bool success;
    char charVal;
    char str[INT16_MAX_STR_LENGTH];

    success = console_find_param_n(buffer, parameterNumber, &startIndex);
    if (true == success) {
        i = 0;
        charVal = buffer[startIndex + i];
        while ( ( NULL != charVal ) && ( PARAMETER_SEPARATER != charVal )
            && ( i < INT16_MAX_STR_LENGTH ) )
        {
            str[i] = charVal;                   // copy the relevant part
            i++;
            charVal = buffer[startIndex + i];
        }
        if ( i == INT16_MAX_STR_LENGTH)
        {
            success = false;
        }
    }
    if ( true == success )
    {
        str[i] = NULL;
        *parameterInt = atoi(str);
    }
    return success;
}

// ConsoleReceiveParamInt16
// Identify and obtain a parameter of type int16_t, sent in in decimal, possibly with a negative sign.
// Note that this uses atoi, a somewhat costly function. You may want to replace it, see ConsoleReceiveParamHexUint16
// for some ideas on how to do that.
bool console_receive_param_uint16_hex(const char * buffer, const uint8_t parameterNumber, uint16_t* parameterInt)
{
    uint32_t startIndex = 0;
    uint32_t i;
    bool success;
    char charVal;
    char str[INT16_MAX_STR_LENGTH];

    success = console_find_param_n(buffer, parameterNumber, &startIndex);
    if (true == success) {
        i = 0;
        charVal = buffer[startIndex + i];
        while ( ( NULL != charVal ) && ( PARAMETER_SEPARATER != charVal )
            && ( i < INT16_MAX_STR_LENGTH ) )
        {
            str[i] = charVal;                   // copy the relevant part
            i++;
            charVal = buffer[startIndex + i];
        }
        if ( i == INT16_MAX_STR_LENGTH)
        {
            success = false;
        }
    }
    if ( true == success )
    {
        str[i] = NULL;
        *parameterInt = strtol(str, NULL, 16);
    }
    return success;
}

spiflash.c:

#include <msp430.h>
#include <string.h>
#include "driverlib.h"
#include "spiflash.h"
#include "spiflash_mt25Q.h"
#include "systick.h"

#define WRITE_PROTECT_PORT  GPIO_PORT_P3
#define WRITE_PROTECT_PIN   GPIO_PIN7

#define SPI_PORT            GPIO_PORT_P4
#define SPI_CLK             GPIO_PIN3
#define SPI_MISO            GPIO_PIN2
#define SPI_MOSI            GPIO_PIN1
#define SPI_NCS             GPIO_PIN0

#define SPICLK              500000  // SPI clock frequency

#define SPI_PAGE_SIZE       256
#define SPI_MAX_BUF         (SPI_PAGE_SIZE+10) // one page plus some SPI overhead

struct {
    uint8_t  tx[SPI_MAX_BUF];
    uint8_t  rx[SPI_MAX_BUF];
    uint16_t len;
    uint16_t index;
    bool     done;
} gSpiTransfer;

static void spiflash_select_chip(void)
{
    GPIO_setOutputLowOnPin(SPI_PORT, SPI_NCS);
}
static void spiflash_deselect_chip(void)
{
    GPIO_setOutputHighOnPin(SPI_PORT, SPI_NCS);
}
static void spiflash_tx(uint8_t *tx, uint16_t len)
{

    gSpiTransfer.len = len;
    memcpy(gSpiTransfer.tx, tx, gSpiTransfer.len);
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    spiflash_select_chip();
    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);

   while ( ! gSpiTransfer.done ) {;}
   spiflash_deselect_chip();
}

static void spiflash_enable_write(void)
{
    uint8_t cmd = SPI_FLASH_INS_WREN;
    spiflash_tx(&cmd, 1);
    GPIO_setOutputLowOnPin(WRITE_PROTECT_PORT, WRITE_PROTECT_PIN);
}
static void spiflash_disable_write(void)
{
    uint8_t cmd = SPI_FLASH_INS_WRDI;
    spiflash_tx(&cmd, 1);
    GPIO_setOutputHighOnPin(WRITE_PROTECT_PORT, WRITE_PROTECT_PIN);
}

static uint16_t spiflash_put_address_in_txbuf(uint16_t index, uint32_t flashAddr)
{
    gSpiTransfer.tx[index] = flashAddr >> 16;
    index++;
    gSpiTransfer.tx[index] = flashAddr >> 8;
    index++;
    gSpiTransfer.tx[index] = flashAddr;
    index++;
    return index;
}

#pragma vector=USCI_B1_VECTOR
__interrupt void USCI_B1_ISR (void)
{
    switch (__even_in_range(UCB1IV,4)){
        //Vector 2 - RXIFG
        case 2:
            while (!USCI_B_SPI_getInterruptStatus(USCI_B1_BASE,
                       USCI_B_SPI_TRANSMIT_INTERRUPT)) ;

            gSpiTransfer.rx[gSpiTransfer.index] = USCI_B_SPI_receiveData(USCI_B1_BASE);
            gSpiTransfer.index++;

            // If there is more to send, then send next value
            if (gSpiTransfer.index >= gSpiTransfer.len) {
                gSpiTransfer.done = true;
            }
            if ( ! gSpiTransfer.done ) {
                USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);
            }
            break;
        default: break;
    }
}

static bool spiflash_is_busy(void)
{
    uint8_t status = spiflash_read_status_register();
    if (status & SPI_FLASH_WIP) {
        return true;
    } else {
        return false;
    }
}
void spiflash_erase_sector(uint32_t address)
{

    spiflash_enable_write();

    gSpiTransfer.tx[0] = SPI_FLASH_INS_SSE;
    gSpiTransfer.len = spiflash_put_address_in_txbuf(1, address);
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    spiflash_select_chip();
    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);

   while ( ! gSpiTransfer.done ) {;}
   spiflash_deselect_chip();

   while ( spiflash_is_busy() ) {;}

   spiflash_disable_write();
}
void spiflash_write_page(uint32_t address, uint8_t *data, uint16_t *requestedLen)
{
    uint16_t len = *requestedLen;
    uint16_t index;

    spiflash_enable_write();

    gSpiTransfer.tx[0] = SPI_FLASH_INS_PP;
    index = spiflash_put_address_in_txbuf(1, address);
    gSpiTransfer.len = len = index;
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    spiflash_select_chip();
    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);
    while ( ! gSpiTransfer.done ) {;}

    len = *requestedLen;
    if (len > SPI_MAX_BUF)  { len = SPI_MAX_BUF; }

    memcpy(&(gSpiTransfer.tx), data, len);
    gSpiTransfer.len = len;
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);
   while ( ! gSpiTransfer.done ) {;}
   spiflash_deselect_chip();
   while ( spiflash_is_busy() ) {;}

   spiflash_disable_write();
}
void spiflash_read_data(uint32_t address, uint8_t *data, uint16_t *requestedLen)
{
    uint16_t len = *requestedLen;
    uint16_t index;

    gSpiTransfer.tx[0] = SPI_FLASH_INS_READ;
    index = spiflash_put_address_in_txbuf(1, address);
    len = *requestedLen + index;
    if (len > SPI_MAX_BUF)  { len = SPI_MAX_BUF; }
    *requestedLen = len-index;

    memset(&(gSpiTransfer.tx[index]), 0xFF, *requestedLen);
    gSpiTransfer.len = len;
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    spiflash_select_chip();
    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);

   while ( ! gSpiTransfer.done ) {;}
   spiflash_deselect_chip();
   memcpy(data, &gSpiTransfer.rx[index], *requestedLen);
}

uint8_t spiflash_read_status_register(void)
{
    uint8_t statusReg = 0;

    gSpiTransfer.len = 2;
    memset(gSpiTransfer.tx, 0xFF, gSpiTransfer.len);
    gSpiTransfer.tx[0] = SPI_FLASH_INS_RDSR;
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    spiflash_select_chip();
    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);

   while ( ! gSpiTransfer.done ) {;}
   spiflash_deselect_chip();
   statusReg = gSpiTransfer.rx[1];
   return statusReg;
}


uint32_t spiflash_get_flash_id(void)
{
    uint32_t devId = 0;
    uint16_t i;

    gSpiTransfer.len = 4;
    memset(gSpiTransfer.tx, 0xFF, gSpiTransfer.len);
    gSpiTransfer.tx[0] = SPI_FLASH_INS_RDID;
    gSpiTransfer.index = 0;
    gSpiTransfer.done = false;

    spiflash_select_chip();
    USCI_B_SPI_transmitData(USCI_B1_BASE, gSpiTransfer.tx[gSpiTransfer.index]);

   while ( ! gSpiTransfer.done ) {;}
   spiflash_deselect_chip();

   for ( i = 1; i < gSpiTransfer.len; i++) {
       devId <<= 8;
       devId |= gSpiTransfer.rx[i];
   }
   return devId;
}

void spiflash_init(void)
{
    //Set write protect
    GPIO_setOutputHighOnPin(WRITE_PROTECT_PORT, WRITE_PROTECT_PIN);

    GPIO_setAsPeripheralModuleFunctionInputPin(
        SPI_PORT,
        SPI_CLK + SPI_MISO + SPI_MOSI );

    //Initialize Master
    USCI_B_SPI_initMasterParam param = {0};
    param.selectClockSource = USCI_B_SPI_CLOCKSOURCE_SMCLK;
    param.clockSourceFrequency = UCS_getSMCLK();
    param.desiredSpiClock = SPICLK;
    param.msbFirst = USCI_B_SPI_MSB_FIRST;
    param.clockPhase = USCI_B_SPI_PHASE_DATA_CHANGED_ONFIRST_CAPTURED_ON_NEXT;
    param.clockPolarity = USCI_B_SPI_CLOCKPOLARITY_INACTIVITY_HIGH;

    USCI_B_SPI_initMaster(USCI_B1_BASE, &param);


    //Enable SPI module
    USCI_B_SPI_enable(USCI_B1_BASE);

    //Enable Receive interrupt
    USCI_B_SPI_clearInterrupt(USCI_B1_BASE, USCI_B_SPI_RECEIVE_INTERRUPT);
    USCI_B_SPI_enableInterrupt(USCI_B1_BASE, USCI_B_SPI_RECEIVE_INTERRUPT);

    spiflash_get_flash_id(); // dummy call to get chip ready
}

uint8_t gTestData[SPI_PAGE_SIZE];
uint16_t spiflash_test(uint32_t address)
{
    uint16_t i;
    uint16_t len = SPI_PAGE_SIZE;
    uint8_t seed = systick_get_tick() & 0xFF;
    uint8_t errors;

    spiflash_erase_sector(address);

    for (i = 0 ; i < SPI_PAGE_SIZE; i++) {
        gTestData[i] = seed + i;
    }
    spiflash_write_page(address, gTestData, &len);
    spiflash_read_data(0, gTestData, &len); // palate cleanser
    spiflash_read_data(address, gTestData, &len); // real read

    errors = 0;
    for (i = 0 ; i < SPI_PAGE_SIZE; i++) {
        if (gTestData[i] != ((seed + i)&0xFF)) {
            errors++;
        }
    }
   return errors;
}
Liked it? Take a second to support David Kohanbash on Patreon!
Become a patron at Patreon!

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply