Jump to content
spirilis

Charcoal Grill / Smoker Monitor

Recommended Posts

Ok so this is my inaugural MSP430 project, i.e. the first legitimately useful thing I've built since buying a LaunchPad.

 

One of my bosses at work asked me, after I babbled on about my Arduino work et al, "So what would it take for me to get text messages when my Big Green Egg is done cooking my pork shoulder roast?"

 

As it turns out, there are commercial solutions out there which will not only monitor your grill & roast temperatures--but also CONTROL them through some sort of fan mounted on a custom fan shroud that replaces the air inlet draft door. These solutions all cost $300+, usually $400-600 for the automated control ones, so I figured there's room for improvement on the price point and DIY'ability of this.

These solutions also seem to require AC power (or AC adapter) of some sort, and I didn't look to see if there was much weatherproofing of that.

 

So I devised a couple requirements of my own:

1. This solution must be an appliance, permanently installed and able to withstand the elements. Maryland doesn't get anything too extreme, maybe 5F (-15C) temps in the depths of winter and upwards of 105F (40C) temps at the peak of summer. Variable rain, snow, ice. I maintain this requirement because I hate dragging gadgets out to plug in/set up when I already have a zillion small tasks in my head.

2. This solution needs to come in at under $200 for everything.

3. There should be an expansion capability built in for adding other stuff--like the ability to control a draft fan, or the ability to show the grill + meat temperatures on some sort of portable display (besides a cellphone)

4. There needs to be Internet capability, even if it's through a stationary PC or Mac that talks to a base station MSP430.

 

What I came up with was a solution based on the MSP430 Value Line chips, Nordic Semiconductor nRF24L01+ transceivers and Maxim MAX31855 digital thermocouple readers. A base station composed of a LaunchPad board with Nordic nRF24L01+ boosterpack (bonus also includes FTDI 6-pin pinout for high speed serial) comprises the internal half of the solution, and software running on a PC/Mac (currently a Linux server) does the dirty Internet work.

 

THERMOCOUPLE SENDER UNIT

The exterior unit that is hardened to the elements/etc. is a custom PCB (OSHpark.com) with an MSP430G2452 TSSOP chip, two MAX31855's, two screw terminals, a Nordic nRF24L01+ board bought off eBay for $2 and some power regulation hardware to take two AA batteries and generate a fairly clean 3.3V rail (required by the MAX31855's, it can't go down to 1.8-2.0V like the MSP430 and nRF24 can...).

The board was designed to fit inside an enclosure sold by Polycase -- the WC-22F (http://www.polycase.com/wc-22f) -- with a cable gland installed in the side to feed the thermocouple leads while maintaining the watertightness of the enclosure.

 

Pic of the external unit installed:

post-15991-0-08933200-1352863016_thumb.jpg

 

The bottom side of the PCB with the MSP430:

post-15991-0-21840100-1352863082_thumb.jpg

 

Schematic - DipTrace_Schematic_-_GrillMonitor_draft6.pdf

Gerber files (OSHpark ready to use) - GrillMon_ExtSenderUnit_43oh.zip

 

Thermocouples I ended up using - http://www.auberins....e5b2d83c4c4407a

 

 

 

GRILL MONITOR BASE STATION

The main requirements for the base station is some way to talk to a PC and some way to receive the RF information from the external sending unit. A $4.30 LaunchPad board works fine for this with a custom boosterpack to attach a nRF24L01+ breakout board. I tossed in an FTDI pinout just in case I was having trouble with the LaunchPad's application UART, which ended up being the case (the Linux driver I found for cdc-acm kernel panics my server after a day or 2 of use).

 

Pic of the LP + boosterpack (note- I used an nRF24L01+ module with an RP-SMA antenna, it cost around $12 vs the $2 for the PCB trace version).

post-15991-0-80135800-1352864220_thumb.jpg

 

Schematic of the BoosterPack - DipTrace_Schematic_-_GrillMonitor_BaseStn_draft4.pdf

Gerber files (OSHpark ready to use) - GrillMon_BaseStation_43oh.zip

 

 

 

CODE

 

I went ahead and took a current snapshot of my work (I am using git, but, don't have it up on github yet) and here it is:

http://spirilis.net/...urce-0.1.tar.gz

 

Note this includes 2 libraries I wrote, "msprf24" -- http://github.com/spirilis/msprf24 and mspuartcli -- http://github.com/spirilis/mspuartcli

The latter library I wrote in a hurry, debugged it a bit here and there, but frankly it could use some improvement (e.g. toss the print functions and just use oPossum's printf() implementation). It does work however!

 

I'll go ahead and include some of the main code for the external sensor unit:

 

tcsender.h -- header info for tcsender main prog

/* tcsender.h
*
* Main program - exported global variables and #define constants
*/

#ifndef TCSENDER_H
#define TCSENDER_H



/* Sleep interval between thermocouple reads; in units of 46ms */
// Set to 2 minutes (2608*46ms)
#define SLEEP_INTERVAL_BETWEEN_TC_READS 2608
/* RX window after a thermocouple read; a back-window to receive GPIO updates from the base station */
// Set to ~322ms (7*46ms)
#define SLEEP_INTERVAL_RX_BACKWINDOW 7
// Interval between LED test polls
#define SLEEP_INTERVAL_TEST_POLLS 4
// Number of attempts to send packet (multiply this times 30ms each, which constitutes 15 retries each)
#define RETRY_ATTEMPTS 32

// nRF24L01+ RF channel & speed
#define RF_CHANNEL 10
#define RF_SPEED RF24_SPEED_MIN

// Protocol device IDs (not RFaddr, used to uniquely identify different thermocouple devices)
#define DEVICE_ID 0x01
#define DEVICE_ID_TC1 0x01
#define DEVICE_ID_TC2 0x02

// Test LEDs
#define LED_RED_PORTOUT P2OUT
#define LED_RED_PORT BIT6

#define LED_GREEN_PORTOUT P2OUT
#define LED_GREEN_PORT BIT7

// Thermocouple control & SPI
#define TC_MOSFET_PORTOUT P2OUT
#define TC_MOSFET_PORT BIT0

#define TC1_CS_PORTOUT P1OUT
#define TC1_CS_PORT BIT3

#define TC2_CS_PORTOUT P1OUT
#define TC2_CS_PORT BIT4

// External GPIO ports
#define GPIO_1_PORTOUT P2OUT
#define GPIO_1_PORT BIT1
#define GPIO_2_PORTOUT P2OUT
#define GPIO_2_PORT BIT3

#define EXTSPI_CS1_PORTOUT P2OUT
#define EXTSPI_CS1_PORT BIT2
#define EXTSPI_CS2_PORTOUT P2OUT
#define EXTSPI_CS2_PORT BIT4

// Test mode jumper input pin
#define TEST_MODE_PORTIN P2IN
#define TEST_MODE_PORT BIT5


#endif

 

TCsender main() function -- main.c:



/* TCsender
*
* Grill Monitor/Remote Thermocouple Sensor Unit
* Main code
*
* Provides data from 2 thermocouples over an nRF24L01+ transceiver to
*   a Base Station which should be listening.  Base Station is also
*   relied upon for RF test data, and in TEST mode this unit will frequently
*   request Test LED information from the base to reflect on this unit's red LED.
*
* Designed for the Texas Instruments MSP430 G2452 Value Line microcontroller
*   with a Nordic Semiconductor nRF24L01+ attached to the USI port.
*
* Thermocouple data is accessed via a pair of Maxim MAX31855K digital thermocouple
*   amplifiers accessed over the same USI SPI port; power to the MAX31855's are enabled
*   or disabled using a MOSFET controlled by one of the MSP430's pins.
*
* Utilizes VLOCLK as a timer for scheduling thermocouple-read events and Test LED
*   packet requests during TEST mode.
*/

#include <msp430.h>
#include "tcsender.h"
#include "packet_processor.h"
#include "nrf_userconfig.h"
#include "msprf24.h"
#include <sys/cdefs.h>
#include <string.h>

const char tcsender_rxaddr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x02};
const char tcsender_dummyaddr[] = {0x00, 0x00, 0x00, 0x00, 0x00};

const char basestation_rxaddr[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01};

volatile unsigned int sleep_counter, rx_waitack;
unsigned int tcread_count, txfail_count;
char rfbuf[32];   // Sharing this with test_mode_main



// Function prototypes
void test_mode_main();
void thermocouple_wakeup();
void thermocouple_sleep();
char thermocouple_read(char tcid, struct packet_task *taskbuf);
void debug_packet(struct packet_task *taskbuf);
void wdt_suspend();
void wdt_unsuspend();


// Main program
int main()
{
   char task_rxwindow_after_txack, txretry_count, txretry_latch;
   char do_lpm;
   int i;
   char pipeid, pktlen;

   WDTCTL = WDTPW | WDTHOLD;
   DCOCTL = CALDCO_16MHZ;
   BCSCTL1 = CALBC1_16MHZ;
   BCSCTL2 = DIVS_2;         // SMCLK = DCOCLK/4, or 4MHz
                 /* nRF24L01+ supports up to 10MHz, but the MAX31855
                  * only supports 5MHz SPI.
                  */
   BCSCTL3 = LFXT1S_2;       // ACLK = VLOCLK
   while (BCSCTL3 & LFXT1OF)
       ;  // Wait until the VLOCLK is up, running & stable

   /* Initialize/reset all I/O ports */
   P1DIR = 0x00;
   P1OUT = 0x00;
   P1SEL = 0x00;
   P1SEL2 = 0x00;
   P1REN = 0x00;

   P1IFG = 0x00;
   P1IE = 0x00;
   P2DIR = 0x00;
   P2OUT = 0x00;
   P2SEL = 0x00;
   P2SEL2 = 0x00;
   P2REN = 0x00;
   P2IFG = 0x00;
   P2IE = 0x00;

   /* Init I/O ports for application use */
   // Test LEDs
   P2DIR |= LED_RED_PORT | LED_GREEN_PORT;

   // Thermocouple Vcc MOSFET & SPI CS lines--turn on initially so nRF24 init can occur
   P2DIR |= TC_MOSFET_PORT;
   TC_MOSFET_PORTOUT &= ~TC_MOSFET_PORT;   // LOW=on
   P1DIR |= TC1_CS_PORT | TC2_CS_PORT;
   TC1_CS_PORTOUT |= TC1_CS_PORT;
   TC2_CS_PORTOUT |= TC2_CS_PORT;

   // External GPIO & SPI CS ports
   P2DIR |= GPIO_1_PORT | GPIO_2_PORT | EXTSPI_CS1_PORT | EXTSPI_CS2_PORT;

   // Test mode jumper input pin
   P2OUT |= TEST_MODE_PORT;    // Pull-up enabled
   P2REN |= TEST_MODE_PORT;
   P2IES |= TEST_MODE_PORT;    // Interrupt on falling-edge
   P2IE |= TEST_MODE_PORT;

   // nRF24L01+ configuration & initialization
   rf_crc = RF24_EN_CRC | RF24_CRCO; // CRC enabled, 16-bit
   rf_addr_width      = 5;
   rf_speed_power     = RF_SPEED | RF24_POWER_MAX;  // RF_SPEED from tcsender.h
   rf_channel         = RF_CHANNEL;  // This #define is located in tcsender.h
   msprf24_init();
   msprf24_open_pipe(0, 1);  // Open pipe#0 with Enhanced ShockBurst for receiving Auto-ACKs

   msprf24_open_pipe(1, 1);  // Pipe#1 is the primary receiver for base->slave traffic
   msprf24_set_pipe_packetsize(0, 0);  // Dynamic packet sizes
   msprf24_set_pipe_packetsize(1, 0);  // Dynamic packet sizes
   w_rx_addr(0, (char*)tcsender_dummyaddr);
   w_rx_addr(1, (char*)tcsender_rxaddr);  // For receiving base->slave packets

   // Now that the nRF24 module has been initialized, we can shut off the TC amps.
   thermocouple_sleep();

   // Set up WDT interval timer as a scheduler
   sleep_counter = 0;
   WDTCTL = WDT_ADLY_16;  // 46ms per overflow with VLOCLK
   IFG1 &= ~WDTIFG;
   IE1 |= WDTIE;

   // Main loop
   task_rxwindow_after_txack = 0;
   rx_waitack = 0;
   txretry_count = 0;
   txretry_latch = 0;
   tcread_count = 0;
   txfail_count = 0;
   while (1) {
       do_lpm = 1;

       // Handle RF acknowledgements & RX packets
       if (rf_irq & RF24_IRQ_FLAGGED) {
           msprf24_get_irq_reason();

           // Handle TX acknowledgements
           if (rf_irq & RF24_IRQ_TX) {
               // Acknowledge
               msprf24_irq_clear(RF24_IRQ_TX);
               flush_tx();

               // Load dummy address into pipe#0
               w_rx_addr(0, (char*)tcsender_dummyaddr);


               // Shut off green LED to indicate we're finished
               LED_GREEN_PORTOUT &= ~LED_GREEN_PORT;
               txretry_count = 0;  // Reset this
               txretry_latch = 0;
           }

           if (rf_irq & RF24_IRQ_TXFAILED) {
               // Acknowledge
               msprf24_irq_clear(RF24_IRQ_TXFAILED);

               // If we have not exhausted our txretry_count's, resubmit the packet.
               if (txretry_count) {
                   txretry_count--;
                   if (!txretry_latch) {
                       txretry_latch = 1;
                       tx_reuse_lastpayload();
                   }
                   pulse_ce();
               } else {
                   flush_tx();
                   w_rx_addr(0, (char*)tcsender_dummyaddr);
                   LED_GREEN_PORTOUT &= ~LED_GREEN_PORT;
                   txretry_latch = 0;
               }
               txfail_count++;
           }

           // Handle RX packets
           if (rf_irq & RF24_IRQ_RX) {
               pktlen = r_rx_peek_payload_size();
               pipeid = r_rx_payload(pktlen, rfbuf);
               msprf24_irq_clear(RF24_IRQ_RX);

               /* Ignore any 0-byte receives, and we only care
                * about packets coming for pipe#1
                */

               if (pktlen && pipeid == 1) {
                   for (i=0; i<pktlen; i++) {
                       if (rfbuf[i]) {
                           // Only process this packet if the length does not send us past the buffer!
                           // (otherwise this would make an easy buffer overflow attack vector)
                           if ((i + (unsigned char)rfbuf[i+1] + 1) < pktlen) {
                               packet_processor((unsigned char)rfbuf[i],
                                        (unsigned char)rfbuf[i+1],
                                        &rfbuf[i+2]);
                               i += (unsigned char)rfbuf[i+1] + 1;
                           }
                       }
                   }
               }
               rx_waitack = 1;
           }

           /* If a TX acknowledgement occurred and we just sent thermocouple data, enable
            * a short RX backwindow
            */
           if (task_rxwindow_after_txack && (rf_irq & (RF24_IRQ_TX|RF24_IRQ_TXFAILED)) && !txretry_count) {
               sleep_counter = SLEEP_INTERVAL_RX_BACKWINDOW;
               msprf24_activate_rx();
               // Switch on green LED to indicate we're actively using RF
               LED_GREEN_PORTOUT |= LED_GREEN_PORT;
           }
           do_lpm = 0;    // Prefer to always loop once more after handling nRF24 events
       }

       // Check for TEST mode (active=LOW)
       if ( !(TEST_MODE_PORTIN & TEST_MODE_PORT) ) {
           // Mandatory 92ms debounce period
           int old_sleep_counter = sleep_counter - 2;
           sleep_counter = 2;
           while(sleep_counter)
               LPM3;
           if ( !(TEST_MODE_PORTIN & TEST_MODE_PORT) ) {

               flush_tx();
               flush_rx();
               thermocouple_wakeup();
               test_mode_main();
               sleep_counter = 0;  // Force an immediate thermocouple read upon leaving Test Mode
           } else {
               if (old_sleep_counter > 0)
                   /* Restore deep-sleep counter in case the TEST line gets flappy so we don't
                    * waste too much power with this crap
                    */
                   sleep_counter = old_sleep_counter;
           }
       }

       // Did we just finish a long sleep-between-TC-read?
       if (!sleep_counter) {
           // Is this the end of the RX-backwindow-after-TX?
           if (task_rxwindow_after_txack) {
               msprf24_powerdown();
               flush_tx();
               flush_rx();
               msprf24_irq_clear(RF24_IRQ_MASK);
               LED_GREEN_PORTOUT &= ~LED_GREEN_PORT;  // Shut off green LED to indicate we're not listening anymore
               thermocouple_sleep();  /* This is the safest time to shut off the TC amplifiers, since we're
                           * done all SPI I/O now.
                           */

               task_rxwindow_after_txack = 0;
               sleep_counter = SLEEP_INTERVAL_BETWEEN_TC_READS;
           } else {
               // Wake everyone up and perform a TC read!
               thermocouple_wakeup();
               msprf24_standby();

               tcread_count++;

               // Perform TC read, append TX packet tasks

               struct packet_task tcpkt;

               if (thermocouple_read(DEVICE_ID_TC1, &tcpkt))
                   packet_task_append(&tcpkt);
               if (thermocouple_read(DEVICE_ID_TC2, &tcpkt))
                   packet_task_append(&tcpkt);
               debug_packet(&tcpkt);
                   packet_task_append(&tcpkt);

               // TC packet will be sent shortly after this with packet_process_txqueue()

               /* Flag the nRF24 IRQ handler that we should enable a 322ms RX window
                * once this transmission has acknowledged.
                */
               task_rxwindow_after_txack = 1;
               // Attempt to retransmit for at least 1/2 second before giving up
               txretry_count = RETRY_ATTEMPTS;
           }
       }

       if (packet_task_next() != NULL) {
           if (!rx_waitack) {
               if (packet_process_txqueue())
                   // Switch on Green LED to indicate we have a transmission in progress
                   LED_GREEN_PORTOUT |= LED_GREEN_PORT;
           }
       }

       if (do_lpm) {
           LPM3;
       }
   }

   return 0;  // This should cause a reset, but it should never be reached.
}

volatile unsigned int tx_count;


void test_mode_main()
{
   char do_lpm;
   int i;
   struct packet_task txpkt, *pt;
   char pipeid, pktlen;

   LED_RED_PORTOUT &= ~LED_RED_PORT;
   LED_GREEN_PORTOUT &= ~LED_GREEN_PORT;
   tx_count = 0;
   sleep_counter = 0;

   // Alternative main loop for when the TEST jumper is active. (active=LOW)
   while ( !(TEST_MODE_PORTIN & TEST_MODE_PORT) ) {
       do_lpm = 1;

       if (rf_irq & RF24_IRQ_FLAGGED) {
           msprf24_get_irq_reason();
           if (rf_irq & (RF24_IRQ_TX|RF24_IRQ_TXFAILED)) {
               msprf24_irq_clear(RF24_IRQ_TX|RF24_IRQ_TXFAILED);
               flush_tx();
               w_rx_addr(0, (char*)tcsender_dummyaddr);
               msprf24_activate_rx();
               LED_GREEN_PORTOUT &= ~LED_GREEN_PORT;
               tx_count++;  // just used for debugging
           }

           if (rf_irq & RF24_IRQ_RX) {
               // Read packet & process
               pktlen = r_rx_peek_payload_size();
               pipeid = r_rx_payload(pktlen, rfbuf);
               msprf24_irq_clear(RF24_IRQ_RX);

               /* Ignore any 0-byte receives, and we only care
                * about packets coming for pipe#1
                */

               i = 0;
               if (pktlen && pipeid == 1) {
                   for (i=0; i<pktlen; i++) {
                       if (rfbuf[i]) {
                           // Only process this packet if the length does not send us past the buffer!
                           // (otherwise this would make an easy buffer overflow attack vector)
                           if ((i + (unsigned char)rfbuf[i+1] + 1) < pktlen) {
                               packet_processor((unsigned char)rfbuf[i],
                                        (unsigned char)rfbuf[i+1],
                                        &rfbuf[i+2]);
                               i += (unsigned char)rfbuf[i+1] + 1;
                           }
                       }
                   }
               }
           }
           do_lpm = 0;
       }

       if (!sleep_counter) {
           // Send Test LED Request packet
           txpkt.program = 0x02;
           txpkt.size = 6;
           memcpy(&(txpkt.rfaddr[0]), basestation_rxaddr, 5);
           txpkt.data[0] = DEVICE_ID;    
           memcpy(&(txpkt.data[1]), tcsender_rxaddr, 5);

           packet_task_append(&txpkt);

           sleep_counter = SLEEP_INTERVAL_TEST_POLLS;
       }

       if (!(rf_irq & RF24_IRQ_FLAGGED) && (pt = packet_task_next()) != NULL) {
           if (packet_process_txqueue())
               LED_GREEN_PORTOUT |= LED_GREEN_PORT;
       }


       if (do_lpm)
           LPM3;
   }
   msprf24_standby();  // Leave the calling function in a powered-on standby state
   msprf24_irq_clear(RF24_IRQ_MASK);  // Clear any outstanding TX packets, queues or IRQs
   flush_tx();
   flush_rx();
   packet_init_tasklist();
   LED_RED_PORTOUT &= ~LED_RED_PORT;
   LED_GREEN_PORTOUT &= ~LED_GREEN_PORT;
}

void thermocouple_wakeup()
{
   // Activate Thermocouple Amplifiers
   TC_MOSFET_PORTOUT &= ~TC_MOSFET_PORT;
   TC1_CS_PORTOUT |= TC1_CS_PORT;
   TC2_CS_PORTOUT |= TC2_CS_PORT;

   // Wait over 200ms for the first valid results to come in  (BLOCKING SLEEP)
   sleep_counter = 6;
   while (sleep_counter)
       LPM3;
}

void thermocouple_sleep()
{
   // Deactivate thermocouple amplifiers
   TC_MOSFET_PORTOUT |= TC_MOSFET_PORT;

   // Shut off SPI CS lines to avoid parasitic power
   TC1_CS_PORTOUT &= ~TC1_CS_PORT;
   TC2_CS_PORTOUT &= ~TC2_CS_PORT;
   /* Note that the SPI SCLK pin will still provide some parasitic power so long as we're
    * doing SPI communication.  Cleanest way to resolve this is to avoid calling this function
    * until we're done all SPI I/O and are ready to go into deep sleep for a while.
    */

}

char thermocouple_read(char tcid, struct packet_task *taskbuf)
{
   unsigned int tcread[2];
   int temp;
   unsigned char portbit;

   switch (tcid) {
       case DEVICE_ID_TC1:
           portbit = TC1_CS_PORT;
           break;
       case DEVICE_ID_TC2:
           portbit = TC2_CS_PORT;
           break;
       default:
           return 0;
   }

   taskbuf->program = 0x10;
   taskbuf->size = 6;
   taskbuf->data[0] = tcid;
   memcpy(&taskbuf->rfaddr[0], basestation_rxaddr, 5);

   // SPI I/O
   TC1_CS_PORTOUT &= ~portbit;
   tcread[0] = spi_transfer16(0xFFFF);
   tcread[1] = spi_transfer16(0xFFFF);
   TC1_CS_PORTOUT |= portbit;

   // Interpret numbers into degrees C for TC temp
   temp = (int) ((tcread[0] & 0xFFF0) >> 4);
   if (temp & 0x0800)
       temp |= 0xF000;  // Properly sign-extend negative values
   memcpy(&(taskbuf->data[1]), &temp, sizeof(int));

   // Interpret numbers into degrees C for Ambient temp

   temp = (int) ((tcread[1] & 0xFF00) >> 8);
   if (temp & 0x0080)
       temp |= 0xFF00;  // Properly sign-extend negative values
   memcpy(&(taskbuf->data[3]), &temp, sizeof(int));

   // Interpret fault codes
   taskbuf->data[5] = 0;
   if (tcread[1] & 0x0001)  // Open circuit
       taskbuf->data[5] = 1;
   if (tcread[1] & 0x0002)  // Short to GND
       taskbuf->data[5] = 3;
   if (tcread[1] & 0x0004)  // Short to +Vcc
       taskbuf->data[5] = 2;

   return 1;
}

void debug_packet(struct packet_task *taskbuf)
{
   unsigned char *devidptr = (unsigned char *) &(taskbuf->data[0]);  /* making sure the (unsigned) DEVID isn't clobbered by the
                                      *   signed char nature of the data[] array
                                      */
   *devidptr = DEVICE_ID;
   memcpy(&(taskbuf->data[1]), &tcread_count, 2);  // # times we've woken up to read/xmit the thermocouple data
   memcpy(&(taskbuf->data[3]), &txfail_count, 2);  // # times we've seen a TXFAILED IRQ
   memcpy(&(taskbuf->data[5]), &devidptr, 2);      // Stack address of the devidptr variable created here, to watch/detect memory leaks

   taskbuf->program = 0xFE;
   taskbuf->size = 7;
   memcpy(&taskbuf->rfaddr[0], basestation_rxaddr, 5);
}

void wdt_suspend()
{
   IE1 &= ~WDTIE;
}


void wdt_unsuspend()
{
   IE1 |= WDTIE;
}

// WDT overflow/timer
#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR(void)
{
   IFG1 &= ~WDTIFG;
   if (rx_waitack) {
       rx_waitack--;
       if (!rx_waitack)
           __bic_SR_register_on_exit(LPM3_bits);
   }
   if (sleep_counter)
       sleep_counter--;
   else
       __bic_SR_register_on_exit(LPM3_bits);
}

// PORT2 interrupt ISR
// Note that our nRF24L01+ IRQ is attached to PORT1, so the msprf24 library is not providing
// this ISR, thus we should provide our own.
#pragma vector=PORT2_VECTOR
__interrupt void P2_ISR(void)
{
   if (P2IFG & TEST_MODE_PORT) {
       P2IFG &= ~TEST_MODE_PORT;
       __bic_SR_register_on_exit(LPM3_bits);
   }
}

 

One interesting thing to note here is how I handled the MAX31855's power consumption during idle--there's a small signal FET gating Vcc on both chips controlled by one of the G2452's I/O pins, however turning off Vcc doesn't solve the problem since it can grab parasitic current from the SPI lines as well as the Chip Select lines. So I have to make sure that any time the chip is in a state where SPI I/O may occur, the thermocouple chips are up and running, and only right before I go into extended low-power LPM3 sleep should I shut off the Vcc (and SPI CS lines for the chips).

 

 

As for the OTA binary protocol, I came up with my own little scheme which is detailed in the code tarball under PROTOCOL.txt. From the G2452 side though, here's the code that interprets/processes incoming packets:

 

packet_processor.h


/* packet_processor.h
*
* Handle slave node packets (TX or RX)
*
* Headers & reference documentation
* Struct for managing packet task list
*/

/* Command/packet reference:
* +---------+-----------+----------------+
* | PROGRAM | PACKETLEN | PACKETCONTENTS |
* +---------+-----------+----------------+
*
* Programs:
* 0x01 : Hello (slave->base)
* 0x02 : LEDtest request (slave->base)
* 0x03 : LEDtest reply (base->slave)
* 0x04 : GPIO set (base->slave)
* 0x05 : GPIO get request (base->slave)
* 0x06 : GPIO get reply (slave->base)
* 0x10 : Thermocouple status (slave->base)
*/

#define PACKET_TASKLIST_DEPTH 3

// Packet TX task; a single-linked list
struct packet_task {
   unsigned char active;
   unsigned char program;
   unsigned char size;
   char rfaddr[5];
   char data[16];
};

extern struct packet_task txtasks[PACKET_TASKLIST_DEPTH];  // Task list

void packet_init_tasklist();

char packet_task_append(struct packet_task *task);
struct packet_task *packet_task_next();

char packet_processor(unsigned char program, unsigned char size, char *data); // Processes an RX packet, one at a time, returning 1 if successful

char packet_process_txqueue();        /* Submits a TX packet stuffing as many packets with the same RF address as can fit
                    * inside a 32-byte packet; returns 1 if successful or 0 if no packets found in the task
                    * queue.
                    * This function runs msprf24_activate_tx() and the MCU should enter LPM sleep shortly after
                    * successful completion of this function, handling IRQ upon wakeup.  After waking up it's
                    * prudent to clear RX pipe#0's address (w_rx_addr(0, <some_dummy_array_of_zeroes>)) to avoid
                    * inadvertently capturing other traffic destined to that remote module if we enter RX mode later.
                    */

 

packet_processor.c

/* packet_processor.c
* Handle slave node packets (TX or RX)
*/

#include <msp430.h>
#include "tcsender.h"
#include "packet_processor.h"
#include "msprf24.h"
#include <sys/cdefs.h>
#include <string.h>
#include <stdlib.h>

struct packet_task txtasks[PACKET_TASKLIST_DEPTH];

char packet_task_append(struct packet_task *task)
{
   int i=0;

   if (task == NULL)
       return 0;  // Invalid request
   while (txtasks[i].active && i < PACKET_TASKLIST_DEPTH)
       i++;
   if (i == PACKET_TASKLIST_DEPTH)
       return 0;  // No free slots in the tasklist queue
   memcpy(&(txtasks[i]), task, sizeof(struct packet_task));
   txtasks[i].active = 1;

   return 1;
}

struct packet_task *packet_task_next()
{
   int i;

   for (i=0; i < PACKET_TASKLIST_DEPTH; i++) {
       if (txtasks[i].active)
           return &txtasks[i];

   }
   return NULL;
}

void packet_init_tasklist()
{
   int i;

   for (i=0; i < PACKET_TASKLIST_DEPTH; i++) {
       txtasks[i].active = 0;
       txtasks[i].program = 0;
       txtasks[i].size = 0;
   }
}

/* Process RX packet payloads
*
* Some commands may result in TX replies, which are appended to the
* packet task list.
*/
char packet_processor(unsigned char program, unsigned char size, char *data)
{
   unsigned char *udata = (unsigned char*)data;

   switch (program) {
       case 0x03:  // LED test reply
           // Data: ID(1), LED_EN(1)
           if (size != 2)
               return 0;  // Packet length invalid
           if (udata[0] != DEVICE_ID)
               return 0;  // Packet was sent to the wrong device
           if (udata[1])
               LED_RED_PORTOUT |= LED_RED_PORT;
           else
               LED_RED_PORTOUT &= ~LED_RED_PORT;
           return 1;


       default:
           return 0;
   }
   return 1;
}

/* Process all outstanding TX packet payloads of a similar RF address */
char packet_process_txqueue()
{
   char packet[32], *curaddr;
   int plen=0;
   struct packet_task *ct;

   // No point in sending a packet until the TX queue is empty
   if ( msprf24_queue_state() & RF24_QUEUE_TXFULL )
       return 0;

   if ((ct = packet_task_next()) == NULL)
       return 0;
   curaddr = ct->rfaddr;
   do {
       if (ct->active && !memcmp(ct->rfaddr, curaddr, 5)) {
           if ( (ct->size + 2 + plen) < 32 ) {
               packet[plen++] = ct->program;
               packet[plen++] = ct->size;
               memcpy(&packet[plen], ct->data, ct->size);
               plen += ct->size;
               ct->active = 0;  // Delete task; it's handled
           }
           if (ct->size > 16)  // Cull any invalid/faulty-sized packets
               ct->active = 0;
       }
       ct = packet_task_next();
   } while (ct != NULL);
   if (!plen)
       return 0;


   w_tx_addr(curaddr);
   w_rx_addr(0, curaddr);
   w_tx_payload(plen, packet);

   // Let'er rip
   msprf24_activate_tx();
   return 1;
}

 

That is all for now (no idea if this post is exceeding any limits for that matter...) but I'll post more details as questions come!

Share this post


Link to post
Share on other sites

Heh wow that turned tabs into double-tabs I think...

 

Anyway a couple more things to note--GPIO support isn't enabled yet (for the base station to twiddle the general purpose I/O ports that break out to the EXPANSION header) but that's how the unit will eventually support controlling a fan. Nor is external SPI support enabled yet (and I haven't yet come up with a protocol packet format for doing that).

 

In theory another device with an nRF24L01+ should be able to listen on the base station's RX address, but with Enhanced ShockBurst (auto-ack) disabled, and intercept the temperature updates from this unit--thus enabling the creation of a portable display for the data.

Share this post


Link to post
Share on other sites

Ok some more pics of the external unit...

The Baltimore Ravens logo was my boss's recommendation, he saw the purple PCBs coming out of oshpark and said he really wanted to see a Ravens logo on one of those ... haha ... The gerbers I published up above don't have that in them, btw ;-)

 

post-15991-0-03476600-1352866352_thumb.jpg

post-15991-0-60757000-1352866369_thumb.jpg

 

And here's some examples of the Base Station CLI (it gives an "OK" prompt after each command, with the cursor below that):

help
Available commands:
? - Print help for all commands
help - Print help for all commands
ledset <status> - Set Test LED status
0 = Off
1 = On
2 = Blink
ledtime <interval> - Set Test LED blink interval
interval: Time between blinks in milliseconds, rounded up
to the nearest 46ms.
signature - Print base station signature, used to identify base station
among multiple serial ports.
nrfirq - List any nRF24L01+ IRQs currently outstanding (debugging)
nrfretrans - List latest retransmitted packets + lost packet counter, then reset both.
nrfstate - Print current operational mode of nRF24L01+ transceiver
nrfchattiness - Perform nRF24 Chattiness/Interference test
nrfchanscan - Perform nRF24 Chattiness scan on all 126 channels
THIS MAY TAKE A WHILE AND CLOBBER INCOMING DATA!!
nrfchannel [chan] - Print or change current RF channel (0-125)
debug [0|1] - Enable debug mode for nRF24 IRQs, including hex dumps of RX packets
and dump any DEBUG packets (program=0xFE) received by slaves.
OK
debug
debug: Currently enabled
OK
signature
SIGNATURE BASESTATION NRF24 0.1
OK
nrfstate
nRF24 State: PRX
nRF24 Channel: 10
nRF24 Speed: 250Kbps
nRF24 Transmit Power: 0dBm
nRF24 TX queues: EMPTY
nRF24 RX queues: EMPTY
OK


TC1 temp 0 degC 32 degF ambient -1 degC 31 degF
TC2 temp -1 degC 31 degF ambient -1 degC 31 degF
DEBUG1 3D 09 00 00 F0 02
BaseStn: RX IRQ packet=10 06 01 00 00 FF FF 00 10 06 02 FF FF FF FF 00 FE 07 01 3D 09 00 00 F0 02

 

The TC1/TC2/DEBUG1/BaseStn text arrives spontaneously, whenever the external unit sends data. So a PC application making use of this will have to open the serial port and continually poll for this data.

 

The DEBUG1 data contains three 16-bit unsigned integers--# of total thermocouple reads performed, # of total TXFAILED nRF24 IRQs handled, and the pointer address of some buffer in the debug_packet() function (alerts me of any strange memory leaks, although they shouldn't occur since that's all in the stack...)

So since the last time I fiddled with it, it's performed 0x093D transmits (2365, or about 78 hours).

 

 

I currently just run a simple shell script that dumps the data (along with some timestamps) to a text file:

#!/bin/bash

LOGFILE="grillmon.txt"

function trap_kill_sleep() {
kill $(cat grillmon.sleep.pid)
rm -f grillmon.sleep.pid
kill $(ps auxwww | grep 'sleep 1800' | grep -v grep | awk '{print $2}')
}

trap trap_kill_sleep SIGINT

(echo $BASHPID > grillmon.sleep.pid
while [ 1 ]; do
date +"%s %a %b %e %T %Z %Y" >> $LOGFILE
sleep 1800
done) &

sudo stty -F /dev/ttyUSB0 115200 raw
cat /dev/ttyUSB0 | tee -a $LOGFILE

 

And another script in cron runs every minute to update an html page (actually updates it every 10 seconds)-


#!/bin/bash

for i in 1 2 3 4 5; do
TC1="$(tail ~/grillmon.txt | grep TC1 | tail -1 | awk '{print $5, $10}')"
TC2="$(tail ~/grillmon.txt | grep TC2 | tail -1 | awk '{print $5, $10}')"

cat <<EOF > ~/public_html/grill.html
<html>
<head>
<title>Grill Monitor Temperature</title>
<meta http-equiv="refresh" content="20">
</head>
<body>
<table style="font-size: 50px; padding-left: 10px; padding-right: 10px">
<thead style="text-align: left">
<th>Sensor</th>
<th>Temp (F)</th>
</thead>
<tr>
<td style="padding-right: 20px">Grill dome:</td>
<td>$(echo $TC2 | awk '{print $1}')</td>
</tr>
<tr>
<td style="padding-right: 20px">Ambient temp:</td>
<td>$(echo $TC2 | awk '{print $2}')</td>
</tr>
<tr>
<td style="padding-right: 20px">Roast temp:</td>
<td>$(echo $TC1 | awk '{print $1}')</td>
</tr>
</table>
</html>
EOF
sleep 10
done

 

All this will be replaced with a single Perl application soon.

Share this post


Link to post
Share on other sites

Also the TC sender unit has some silicone dielectric grease wiped on the various pins of the ICs and passives, and so far it's survived many frosts and Hurricane Sandy! I have high hopes that it'll last. Just curious how long the batteries really will last; I did a quick check at the battery terminals once and saw about 8.5uA average draw during idle, so it should last a good while. That Boost Regulator (TPS61221) can continue running until it's seeing <0.7V which with two AA batteries in series.... would have to be legitimately dead.

Share this post


Link to post
Share on other sites

Writing code for a passive grill monitor "display" based on the Oasis SPI LED displays...

 

Main "meat" of the code so far (packet_processor.c):


/* Process RX packet payloads
*/
char packet_processor(unsigned char program, unsigned char size, char *data)
{
   int tempF;
   unsigned char *udata = (unsigned char*)data;

   switch (program) {
       case 0x10:             // Thermocouple status (slave->base)
           // Data: ID(1), TCtemp(2), AmbTemp(2), Fault(1)
           if (size != 6)
               return 0;  // Packet length invalid

           volatile unsigned char *disp_portout;
           unsigned char disp_bit;

           int tcary[2];
           memcpy(&tcary, data+1, sizeof(int)*2);

           switch (udata[0]) {  // ID
               case TC_ID_GRILLDOME:
                   disp_portout = LEDDISPLAY_GRILLDOME_PORTOUT;
                   disp_bit = LEDDISPLAY_GRILLDOME_PORT;
                   break;
               case TC_ID_MEAT:
                   disp_portout = LEDDISPLAY_MEAT_PORTOUT;
                   disp_bit = LEDDISPLAY_MEAT_PORT;
                   break;
               default:  // Ignore remainder if this isn't a TC we care about.
                   return 0;
           }
           tempF = 32 + (tcary[0] * 9) / 5;

           if (!udata[5])  // No faults?
               oasisdisp_print_int(disp_portout, disp_bit, tempF);
           else
               oasisdisp_print_word(disp_portout, disp_bit, 0xDEAD);


           return 1;

       default:
           return 0;
   }
   return 1;
}

 

This unit has no need to transmit so I left the process_txqueue() crap out. Working on main() now to tie it all together. It will have a red SMD LED too indicating whether the data is too "stale" (>5 minutes with no updates). Probably going to do this toner-transfer (I keep telling myself that and then don't do it, but this should be simple enough to pull off...) and I intend to use 2xAA batteries + TPS61221 boost converter just like the TCsender unit outside.

Share this post


Link to post
Share on other sites

Beautiful work spirilis!  I love seeing these smoker projects!  I am sure you have seen the Arduino based "Linkmeter" PID BBQ controller project.  I have a v3.0 of that controller which uses a Linksys router.  The newer version (v4.0) sits on top of a Raspberry Pi.  Here is the link:  http://tvwbb.com/forumdisplay.php?85-LinkMeter-v2-Homebrew-BBQ-Controller

 

I just started playing with the Launchpad and MSP430G2553 about three months ago and I like it much more than the Arduino platform.  Mainly because the Arduino is hard to debug for a non-programmer.  The debugging capabilities in CSS are great for figuring out what you are doing wrong!  I have managed to get my MSP430 to send comma delimited over the UART to my PI which logs the data in a JSON and CSV file.  I am running an Apache server on the Pi which I use to display the JSON data using the javascript Flot charting library. I just got a couple of these nrf24L0+ for $1.43 a piece shipped from ebay.  I got Ike's example going pretty quickly.  I am hoping I can figure out how I can use your library to extend my UART so I can have a remote sensor node for my garden.  We will see how that goes.  It would be nice to see a non-Energia UART example...when/if you ever get some time.  I have learned a lot from guys like you who take time to write libraries and blogs about how to do cool things with microcontrollers.  It really is a public service and I really appreciate your effort!      

Share this post


Link to post
Share on other sites

FYI- I made some changes/bugfixes to the code over the months, can't remember what all, so I'm going to upload the current .zip on my Linux server at home for the code.

grillmon-052013.zip

 

I do have the initial code for an LED numeric display-based monitor that I'll have up in my kitchen at some point once I get off my butt and etch the boards...

Share this post


Link to post
Share on other sites

Still working... I've noticed some odd flakiness where it sometimes checks out for periods at a time, but that always happens during the daytime.  I'd expect it during the nighttime when it's colder and the batteries might not work as well... Could be RF related somehow I suppose.

 

Otherwise, still kicking on the same pair of AA batteries, and it just helped me smoke a turkey:

post-15991-0-75995400-1385655523_thumb.jpg

Share this post


Link to post
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.


×
×
  • Create New...