spirilis 1,265 Posted November 14, 2012 Share Posted November 14, 2012 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: The bottom side of the PCB with the MSP430: 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). 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! bluehash, t0mpr1c3, Rickta59 and 2 others 5 Quote Link to post Share on other sites
bluehash 1,581 Posted November 14, 2012 Share Posted November 14, 2012 Jeez, awesome! Add it to the POTM. abecedarian 1 Quote Link to post Share on other sites
spirilis 1,265 Posted November 14, 2012 Author Share Posted November 14, 2012 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. Quote Link to post Share on other sites
spirilis 1,265 Posted November 14, 2012 Author Share Posted November 14, 2012 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 ;-) 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. Quote Link to post Share on other sites
spirilis 1,265 Posted November 14, 2012 Author Share Posted November 14, 2012 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. Quote Link to post Share on other sites
spirilis 1,265 Posted November 14, 2012 Author Share Posted November 14, 2012 Firing up the smoker with a huge load of charcoal to smoke a pork picnic- HP touchpad running CyanogenMod 9- bluehash 1 Quote Link to post Share on other sites
t0mpr1c3 91 Posted November 15, 2012 Share Posted November 15, 2012 Looks tasty! I like your design choices. I had fun making a PID controlled flowerpot smoker this summer. spirilis 1 Quote Link to post Share on other sites
spirilis 1,265 Posted November 15, 2012 Author Share Posted November 15, 2012 that's pretty sweet! Quote Link to post Share on other sites
spirilis 1,265 Posted November 19, 2012 Author Share Posted November 19, 2012 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. Quote Link to post Share on other sites
bluehash 1,581 Posted November 22, 2012 Share Posted November 22, 2012 http://www.43oh.com/2012/11/happy-thanksgiving-to-our-readers-turkey-inside/ spirilis 1 Quote Link to post Share on other sites
geodave 9 Posted March 18, 2013 Share Posted March 18, 2013 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! spirilis 1 Quote Link to post Share on other sites
roadrunner84 466 Posted March 19, 2013 Share Posted March 19, 2013 @geodave: say Hi in the welcome board. Hi btw. Quote Link to post Share on other sites
spirilis 1,265 Posted May 29, 2013 Author Share Posted May 29, 2013 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... Quote Link to post Share on other sites
spirilis 1,265 Posted November 28, 2013 Author Share Posted November 28, 2013 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: bluehash 1 Quote Link to post Share on other sites
bluehash 1,581 Posted November 30, 2013 Share Posted November 30, 2013 @@spirilis That is a cool photo. Looks like it is about to self-destruct Did you add this to any previous POTMs? Quote Link to post Share on other sites
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.