Jump to content

[ ENDED ] 43oh-Stellarisiti Nov 2013- Jan 2014 Project Of The Month

Recommended Posts


Hello All,

After consulting with the members of the 43oh forum, I'd like to open the contest to Stellarisiti members too. There are alot of prizes to be won from sponsors. All you have to do is document your project here in the Stellarisiti forum projects section and provide a brief description of your project in this 43oh POTM thread. Unfortunately, you will have to regster at 43oh to do this as I've not integrated the membership between the two sites yet.



(1) Logic 8 Analyzer-----------------------------------------------------Saleae
(1) Mastech MS8211N 2000 Count Handheld Digital Multimeter--Saelig
(1) PanaVise Mount Package--------------------------------------------Panavise
(1) 2" Pervasive Displays EPD - Kit-------------------------------------Pervasive Displays
(1) DLP-7970ABP NFC/RFID Reader BoosterPack------------------DLP Design
(1) $20 gift card Sparkfun/Amazon/Adafruit ---------------------------Abecederian
(1) Your choice of Design, Laser Cut by 43oh Member Fred------Fred
(2) Stellaris Launchpad Kits.---------------------------------------------Bluehash


Last Date for entries
31st Jan, 2014
Submitting your entry
To submit your entry, make an entry into this thread with the following:
1 - A small description of your project.
2 - A picture or video of your setup
3 - Code.
4 - Schematic and board files.
5 - You will also need to make a copy of the entry under the Projects section. This will enable members to ask you questions and comment on your project. If you are from the Stellarisiti forum, describe your project in the Projects section at Stellarisiti.
A day after the contest ends, a poll will be created with all the project entries. Only members of the forum will be allowed to vote. Voting will run for a week.
Prize Distribution
1. Winners can choose their prize from the pool in the order they win it.
2. If there is a tie between multiple winners, the entry than made the earliest in this project thread will have priority in choosing a prize.
Simple Rules
- You must be a member of the 43oh/Stellarisiti forum at least a week before your submission.
- One entry from each member will be permitted.
- Your design has to be open source. if you can, select a license from here or here. 43oh will not claim any ownership.
- Your project can be anything based around the MSP430/Tiva-C/Stellaris controllers. You may interface the MSP430 to another controller. But try to keep the above listed as the main controller.
- You may reuse code from anywhere as long as you give credit to the original author.
- You must submit code and schematics of your project with a proper description.
- You can submit your MSP430 project, if it was created before the annoucement of the contest.
- You must have at least 5 posts in the forums, for your entry to be considered when the voting begins.
- Previous entries in other 43oh contests will not be permitted.

Link to post
Share on other sites
  • 3 weeks later...

This is my first microcontroller project (aside from a couple of Hello World's after I received my Tiva), but I've been programming professionally for more than 30 years -- less so in the last decade.

Tic Tac Toe, called noughts and crosses elsewhere in the world, is a pretty simple strategy game, and I thought it would be well suited for a first project.

tic tac toe by joelfinkle, on Flickr


There are 18 LEDs (9 each of red and green), not counting the onboard RGB (used for status).  While there are plenty of pins on the Tiva, it seemed worth learning Charlieplexing.

That provided an additional challenge, getting LEDs to line up nicely in the limited space of a standard breadboard, while getting in all the wires.  I mostly succeeded, although the grid is a little skewed.


The project uses a 4x4 keypad bought off Amazon for under $2 shipped (not realizing it would take weeks to arrive from China).

There are two slider switches used to determine whether there is a computer player, and whether the computer is first or second.  I could probably modify it to have both players be computer, but given that the strategy is deterministic (nothing random), it would be pretty dull -- every game would end the same way.


I decided to program using Energia, mainly because I had trouble getting the TI dev environment to work, and I'm lazy.

I'd looked at [roadrunner]'s charlieplexing module, but it added a lot of unneeded complexity, and I settled for a simple tick routine that changed which cell of the tic tac toe board it was looking at each time around the loop routine.

i plan on upgrading the code to use more direct access to the pins (like [roadrunner] does), but thought I'd get it to work on a more pure Wiring program first.


The strategy is clearly laid out in the code, using the suggestion of http://www.instructables.com/id/How-to-make-a-TIC-TAC-TOE-game-using-C/all/?lang=zh'>this Instructables post of storing a game state as empty=2, player 1=3 and player 2=5, and multiplying cell values together to get a state (winning is 27 or 125).  I enhanced it to look for fork and fork-blocking moves.


The onboard LED is blinked at intervals (also done using a routine called from loop, but could be an interrupt in future versions), with different colors to indicate player's turn, invalid move, win of player 1 or 2 and cat's game (draw).



// Tic Tac Toe for Tiva - Energia Implementation
// (CC) Creative Commons Attribution Non-Commercial 2013 Joel Finkle
// Recipients of this code may copy, distribute, display, and perform the work and make derivative works based on it only for noncommercial purposes
// Hardware requirements:
//   TI Tiva C development board
//   18 LEDs, 9 each of red and green
//   4x4 keypad
//   Five 330-ohm resistors
//   Two slider switches
//   Breadboard and wires

#include <Keypad.h>

// Game state globals
int curPos = 0;
int curScanRow = 0;
int curPlayer = 0; // start with player 1
int blinkType = 0; // 0 = no blink, 1 = error, 2 = P1 win, 3 = P2 win, 4 = P1 turn, 5 = p2 turn, 6 = Cat's Game
int blinkCtr = -1;
int blinkTimer = 0;
long blinker = -1000;
int game[9] = {2,2,2,2,2,2,2,2,2}; // 2 = neither, 3 = P1, 5 = P2 for items 0-8
int humans[2] = {1,1};
int movesmade = 0;
boolean stillplaying = true;

// Constants for the value of a given move
#define WINMOVE 0
#define BLOCKMOVE 1
#define FORKMOVE 2
#define CENTERMOVE 4
#define OPPOMOVE 5
#define CORNERMOVE 6
#define EDGEMOVE 7
#define NOMOVE 999

// Charlieplexed LED Data
#define PIN_COUNT 5
int pinlist[PIN_COUNT] = {PE_1,PE_2,PE_3,PE_4,PE_5};
// pinpairs provides a shortcut for charlieplexing, 
// since the alternate players use the same pair of pins with reversed polarity.
// Each of the nine cells indicates the pair of pins in pinlist
// with the direction indicated by the player.
// Pins were chosen to all be in one set, for future changes to use register operations
// instead of the Wiring routines
int pinpairs[3][3][2] = {
  { { 0, 1 }, { 1, 2 }, { 2, 3 } },
  { { 3, 4 }, { 4, 0 }, { 0, 2 } },
  { { 2, 4 }, { 4, 1 }, { 1, 3 } }

// Keypad data
const byte ROWS = 4; // use 4X4 keypad for both instances
const byte COLS = 4;
char keys[ROWS][COLS] = {
// Pins chosen below were the only set of eight in as row that don't have another function
// so that the keypad's header could be plugged directly into the Tiva board
byte rowPins[ROWS] = {38,37,36,35}; //connect to the row pinouts of the keypad
byte colPins[COLS] = {34,33,32,31}; //co/nnect to the column pinouts of the keypad
Keypad keypad( makeKeymap(keys), rowPins, colPins, ROWS, COLS );

// Setup - set the initial game state, other than the data initialization above
void setup()

// setup code here, to run once:
  int i = 0;
  for (i=0;i<PIN_COUNT;i++) { pinMode(pinlist[i], INPUT); }
  pinMode(PA_3, INPUT); // Play against computer?
  pinMode(PA_2, INPUT); // Which player is computer?
  pinMode(RED_LED, OUTPUT); // all three of these for statuses
  pinMode(BLUE_LED, OUTPUT);
  if (digitalRead(PA_3) == LOW) { // Yes, play against computer
    humans[digitalRead(PA_2) == HIGH ? 0 : 1] = 0; // set either player 1 or player 2 as computer player

  updateBlink(blinkCtr, blinkType, blinkTimer);
// Player 1's turn
  blinkType = 4; // 0 = no blink, 1 = error, 2 = P1 win, 3 = P2 win, 4 = P1 turn, 5 = p2 turn
  blinkCtr = 6;
  blinkTimer = 250;    
  updateBlink(blinkCtr, blinkType, blinkTimer);

// loop - main event loop
void loop()
  int move = NOMOVE;
  // Give time to the charlieplex display
  if (curPos > 8) { curPos = 0; }
  // Give time to the blinker handler
  updateBlink(blinkCtr, blinkType, blinkTimer);
  // Whose turn is it?  Fetch a move
  if (stillplaying) {
    if (humans[curPlayer]) {
      move = humanplayer();
    } else {
      move = computerplayer();

  // If a move occurred (which should always happen if it's the computer's turn, but might not have yet for human)...
  if (move != NOMOVE) {
    // Was it a winning move?
    if (move == WINMOVE) { // game over
      blinkType = 2 + curPlayer;
      blinkCtr = 10;
      blinkTimer = 250;
      stillplaying = false;
    } else {
      // If not a winning move, update the game state
      // cats game
      if (movesmade > 8) { 
        blinkType = 6;
        blinkCtr = 6;
        blinkTimer = 250; 
        stillplaying = false;
      // or need another move
      } else {

// humanplayer - Fetch a move from the keypad
int humanplayer() {
  char key = keypad.getKey();
  int button = -1;
  int move = NOMOVE;
  // The keypad is a 16-key pad, ignore right column (A-D) and bottom row (* 0 #)
  if (key >= '1' && key <= '9') { button = (byte)key - 49; } // subtract 1 extra to make it 0-based

  // If we got real input...
  if (button >= 0) {
    // Make sure the position isn't already used
    if (game[button] != 2) {
      // error -- already in use
      blinkCtr = 4; // three times on and off
      blinkType = 1;
      blinkTimer = 250; // milliseconds
      move = NOMOVE;
    // Then update the game board, and check whether it's a winning move
    } else {
      game[button] = curPlayer ? 5 : 3; // set to 5 for player 2 (value 1), 3 for player 1 (value 0)
      // did they win?
      move = bestmove(button, curPlayer, 0);
    return move;
  } else {
    return NOMOVE;

// computerplayer - calculate best move
int computerplayer() {
  int best = NOMOVE;
  int bestpos = -1;
  for (int j=0; j<9; j++) {
    // Since this can be time consuming, keep the screen updated
    if (curPos > 8) { curPos = 0; }
    updateBlink(blinkCtr, blinkType, blinkTimer);
    // end of screen update
    // Only check the value of a move where we actually can move
    if (game[j] == 2) { // spot is empty
      int thismove = bestmove(j, curPlayer, 1);
      // If this move is better than other ones we've chosen, keep it
      if (thismove < best) { // lower values are better
        bestpos = j;
        best = thismove;
  // Update board and return the quality of the move (which indicates winning)
  game[bestpos] = curPlayer ? 5 : 3; // set to 5 for player 2 (value 1), 3 for player 1 (value 0)
  return best;

// switchplayer - blink for the other guy
void switchplayer() {
// change players -- this shamefully works on globals
  curPlayer ^= 1; // XOR toggles between 0 and 1
  blinkType = 4 + curPlayer; // 4 = P1 turn, 5 = p2 turn
  blinkCtr = 6;
  blinkTimer = 250;

// tickDisplay -- give time to the Charlieplex
void tickDisplay( int position) { // display the next LED
  if (game[position] != 2) { // there's something there
    displaySquare( position % 3, position / 3, (game[position])); // pass position value to permit clearing
  if (game[position] == 8) { game[position] = 2; } // reset if needed

// displaySquare -- update the output lines for the particular cell
// TODO: switch to using internal registers instead of Wiring routines, for speed
void displaySquare( int row, int col, int value) {
  int player = 0;
  switch (value) {
    case (3) : player = 0;
    case (5) : player = 1;
  // set all pins to low and input to ensure that we don't have old junk sitting around
  for (int i = 0; i < PIN_COUNT; i++) {  digitalWrite(pinlist[i], LOW); pinMode( pinlist[i], INPUT);}
  // set particular pins to output, and raise one of them high
  if (value != 8) { // don't do this if clearing
    pinMode(pinlist[pinpairs[row][col][0]], OUTPUT);
    pinMode(pinlist[pinpairs[row][col][1]], OUTPUT);
    digitalWrite( pinlist[pinpairs[row][col][player]], HIGH);

// updateBlink - change the blinker periodically.
// Todo: Change to interrupt-driven blinker
void updateBlink( int &bCount, int bType, int bTimer) {
  if (bCount < 0) { 
    blinker = 0;
  // If bTimer expired then
  if (blinker + bTimer < millis()) { // timer expired 
  //    a) Toggle LED based on bType
    switch (bType) {
      case (0) : // nothing
        digitalWrite(RED_LED, LOW);
        digitalWrite(GREEN_LED, LOW);
        digitalWrite(BLUE_LED, LOW);
      case (1) : // error - yellow
        digitalWrite(RED_LED, (bCount & 1) ? LOW : HIGH);
        digitalWrite(GREEN_LED, (bCount & 1) ? LOW : HIGH);

      case (2) : // P1 Win - alternate red and blue
        digitalWrite(RED_LED, (bCount & 1) ? LOW : HIGH);
        digitalWrite(BLUE_LED, (bCount & 1) ? HIGH : LOW);
      case (3) : // P2 win - alternate green and blue
        digitalWrite(GREEN_LED, (bCount & 1) ? LOW : HIGH);
        digitalWrite(BLUE_LED, (bCount & 1) ? HIGH : LOW);
      case (4) : // P1 turn - red
        digitalWrite(RED_LED, (bCount & 1) ? LOW : HIGH);
      case (5) : // P2 turn - green
        digitalWrite(GREEN_LED, (bCount & 1) ? LOW : HIGH);
      case (6) : // Cat's Game - alternate yellow and blue
        digitalWrite(RED_LED, (bCount & 1) ? LOW : HIGH);
        digitalWrite(GREEN_LED, (bCount & 1) ? LOW : HIGH);
        digitalWrite(BLUE_LED, (bCount & 1) ? HIGH : LOW);
    //     Decrement bCount
    if (bCount < 0) {
      digitalWrite(GREEN_LED, LOW);
      digitalWrite(RED_LED, LOW);
      digitalWrite(BLUE_LED, LOW);
      blinker = -1;
    } else {
    //    c) reset bTimer
      blinker = millis();

Strategy: Wikipedia suggests the following priority:
1) Win: If the player has two in a row, they can place a third to get three in a row. (same as posswin)
2) Block: If the [opponent] has two in a row, the player must play the third themself to block the opponent.
3) Fork: Create an opportunity where the player has two threats to win (two non-blocked lines of 2).
4) Blocking an opponent's fork:
Option 1: The player should create two in a row to force the opponent into defending, as long as it doesn't result in them creating a fork. 
  For example, if "X" has a corner, "O" has the center, and "X" has the opposite corner as well, 
  "O" must not play a corner in order to win. (Playing a corner in this scenario creates a fork for "X" to win.)
Option 2: If there is a configuration where the opponent can fork, the player should block that fork.
5) Center: A player marks the center. (If it is the first move of the game, playing on a corner gives "O" more opportunities to make a mistake and may therefore be the better choice; 
   however, it makes no difference between perfect players.)
6) Opposite corner: If the opponent is in the corner, the player plays the opposite corner.
7) Empty corner: The player plays in a corner square.
8) Empty side: The player plays in a middle square on any of the 4 sides.

The bestmove routine here uses 2 for an empty cell, 3 for a player-one cell, 5 for a player 2 cell, and calculates the product of three cells in a row
There are four possible results from any cell: 
  Three in a row, 
  three in a column, 
  three in a sinister diagonal (top right to bottom left), 
  three in a dexter diagonal (top left to bottom right)
Possible products for any set of 3 cells (row, col, sinister, dexter) a*b*c:
8    Nothing there (don't care)
12   One cell of player 1 (low priority)
18   Two of player 1, one empty = could indicate a fork if there's another
20   One cell of player 2 (low priority)
27   Three of player 1 = winner
30   One Player 1, One Player 2, One Empty = could indicate an opponent fork that could be blocked, if there's another
45   Two of player 1, one Player 2 = block for player 2
50   Two of player 2, one empty = forkable
75   Two of player 2, one Player 1 = block for player 1
125  Three of player 2 = winner
999  Invalid (i.e. looking for sinister when in any cell except the right to left diagonal [2,4,6])

So for each give empty square, look at the set of row, col, sin, dex
a) if there's an 27/125 (for player 1 or 2), choose it and win
 if there are any 75/45, choose it and block
c) if there are two 18/50, choose it and fork
d) if there are two 30, choose it and prevent fork
e) if given square is a corner, and opposite corner is not self, choose it
f) if given square is corner, take it
g) if given square is edge, take it


// bestmove - use above strategy to find the best possible move
// TODO: make the first move more random?  You can still force a win or cats from any cell
int bestmove(int cell, int player, boolean testcell) {
  int row = int(cell / 3) * 3;
  int col = cell % 3;
  int oldCell = game[cell];
  int forkcnt = 0;
  int forkblkcnt = 0;
  int thismove = NOMOVE;
  boolean block = false;
  int forks = 0;
  int forkblocks = 0;

  // If testcell is true, we're looking for a best move, versus just whether we won
  // in which case, pretend the move is made by filling the cell
  if (testcell) { game[cell] = (player ? 5 : 3); }
  // Check the four directions: Row, Col, Dexter, Sinister
  for (int dir=0; dir<4; dir++) {
    switch (dir) {
      case 0: // row
        thismove = game[row] * game[row+1] * game[row+2];
      case 1: // column
        thismove = game[col] * game[col+3] * game[col+6];
      case 2: // dexter diagonal
        if (cell % 4 == 0) { // cells 0, 4 or 8
          thismove = game[0] * game[4] * game[8];
        } else {
          thismove = NOMOVE;
      case 3: // sinister diagonal
        if (cell == 2 || cell == 4 || cell == 6) { // no cute calculation here
          thismove = game[2] * game[4] * game[6];
        } else {
          thismove = NOMOVE;

    // evaluate results
    if (thismove == 125 || thismove == 27) { // winner
      if (testcell) { game[cell] = oldCell; }
      return WINMOVE; // no need to keep testing, we've got a winner
    } else if ((thismove == 75 && player == 0) || (thismove == 45 && player == 1)) { // blocker, keep track
      block = true;
    } else if (thismove == 18 || thismove == 50) { // possible fork
    } else if (thismove == 30) { // possible forkblock

  // now that we've looked at all 4, what's the best outcome of this spot (we've already returned if we would win)
  // BLOCK
  if (block) { 
    thismove = BLOCKMOVE;
  // FORK if two directions found
  } else if (forks > 1) {
    thismove = FORKMOVE;
  // FORK BLOCK if two directions found
  } else if (forkblocks > 1) {
    thismove = FORKBLOCKMOVE;
  } else if (cell == 4) {
    thismove = CENTERMOVE;
  } else if (cell % 2 == 0) { // all the corners are even, plus center which we already counted
    if (game[3*((row+2) & 3) + ((col+2) & 3)] != 2) {
      thismove = OPPOMOVE;
    } else {
    // CORNER
      thismove = CORNERMOVE;
  // EDGE
  } else {
    thismove = EDGEMOVE;

  // Reset the test cell
  if (testcell) { game[cell] = oldCell; };
  return thismove;

Schematic -- if anyone has a Fritzing file for the Tiva/Stellaris, I'd appreciate it.  I had to hack the SVG files for the Launchpad headers to get this to work, and it's still a little funky.

Link to post
Share on other sites

("Your project can be anything based around the MSP430. You may interface the MSP430 to another controller. But try to keep it as the main controller.")


So just to be sure I understand - while members of Stellarisiti may enter, it is still limited to projects based around the MSP430 chips?

(i.e., we can't enter things based on TI's LM3, Stellaris or Tiva)



Link to post
Share on other sites

("Your project can be anything based around the MSP430. You may interface the MSP430 to another controller. But try to keep it as the main controller.")


So just to be sure I understand - while members of Stellarisiti may enter, it is still limited to projects based around the MSP430 chips?

(i.e., we can't enter things based on TI's LM3, Stellaris or Tiva)

Yes.. I've updated the rules. Thanks @@igor.

Link to post
Share on other sites

This is a FM synthesizer made for the Stellarpad using the AD9850 function generator.



The FM synthesis is teorethicaly the same as the all-known frequency modoulation used for radio communications, but in this case the frequencies are suited to stay at the audio range, i.e. from 20Hz to 20kHz. Basically The AD9850 is used as a sine wave generator, and the microcontroller create each cycle a sample of ANOTHER sine wave, this one goes from 5Hz to 100Hz more or less. We can modulate this two sinewaves, the AD signal being the carrier and the stellaris signal the baseband. The outcoming spectrum can be represented by the Bessel functions for each harmonic, making some interesting sounds for electronic music. Attaching two potentiometers we can control the baseband sine frequency and its amplitude, thus controlling the modulation index.

This is a recording I made with this project (is there any way to display souncloud embed on the forum?):


The first seconds were non-modulated waves and then I pressed a note (C3 if I recall correctly) and played with the potentiometers..


This is a simplfied schematic of the project.




The complete schematic of each module can be found respectively here:


AD9850 module: http://www.analog.com/static/imported-files/data_sheets/AD9850.pdf

MIDI boosterpack by RobG: http://forum.43oh.com/topic/1773-midi-booster-pack/page-3#entry23763



And finally, this is the main code for this project:


// FM Synthesizer for Stellaris Launchpad and AD9850
// Coded by Gonzalo Recio

#include "inc/hw_types.h"
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "inc/hw_gpio.h"
#include "inc/hw_sysctl.h"
#include "driverlib/pin_map.h"
#include "driverlib/uart.h"
#include "inc/hw_ints.h"
#include "driverlib/interrupt.h"
#include "driverlib/adc.h"

#include "math.h"
#include "midi.h"
#include "STELLARIS_AD9850.h"

#define PI 3.141592
#define GPIO_PB0_U1RX 0x00010001
#define GPIO_PB1_U1TX 0x00010401

// Global variables
unsigned long message;
unsigned long note, velocity;
int on=0, off=0;
int flagON = 0, flagOFF = 0;
unsigned short i = 0, z,y;
float t = 0;
float freq = 0;
unsigned long ulADC0_Value[4];

int main(void){

    //50MHz clock

    //enable UART for MIDI T/R
    GPIOPinConfigure(GPIO_PB0_U1RX); //B0 receptor
    GPIOPinConfigure(GPIO_PB1_U1TX); //B1 transmitter

    //enable AD9850

//ADC0 config
ADCHardwareOversampleConfigure(ADC0_BASE, 4);
ADCSequenceStepConfigure(ADC0_BASE, 1 , 0 , ADC_CTL_CH8 );
ADCSequenceStepConfigure(ADC0_BASE, 1 , 1 , ADC_CTL_CH9 | ADC_CTL_IE | ADC_CTL_END);
ADCSequenceEnable(ADC0_BASE, 1);
ADCIntClear(ADC0_BASE, 1);

    //Begin the interrupt detection


        if (on == off){
     ADCProcessorTrigger(ADC0_BASE, 1 );
     while(!ADCIntStatus(ADC0_BASE, 1 , false)){ }
     ADCIntClear(ADC0_BASE, 1 );
     ADCSequenceDataGet(ADC0_BASE, 1 , ulADC0_Value);

            AD9850_Osc(freq + y*sin(t), 0);

        z=(ulADC0_Value[0] / 4 ) + 1;
        i=(i + 1) % z;
        //timescale between 0 and 2*pi
        t=((float)i / (float)z ) * 2 * PI;


void UARTIntHandler(void){

    unsigned long ulStatus = UARTIntStatus(UART1_BASE, true); //get interrupt status. RX or RT?
    UARTIntClear(UART1_BASE, ulStatus);                      //clear the asserted interrupts

    while(UARTCharsAvail(UART1_BASE)){                          //loop while there are chars
        message = UARTCharGetNonBlocking(UART1_BASE);

        if(flagON){                            // if the last message was a note ON, then this message is
            on++;                            // the note code.
            if (note != message){
                note = message;
                freq = code2Freq(note);
        else if(flagOFF){

        flagON = isNoteOn(message);
        flagOFF = isNoteOff(message);


The codes for the AD9850 library were also made by me, you can find them in this other thread.
The MIDI library used in this project is here midi.c, midi.h. I was also the author for this one.

If you don't have a MIDI boosterpack I've made also a no-MIDI version, the on-board switches trigger two different notes. You still need to attach a potentiometer to E4 and E5 though. You can find this version HERE.

Link to post
Share on other sites



A very nice contest. I would like to submit my project which I began during the summer holidays, the MP3-thing. Sadly, I had no time to work on it for a long time, however this is not a dead project and I am currently doing some bugfixes and plan to add a feature or two tommorow. Would it be OK to submit this project anyway?

Link to post
Share on other sites

I would like to enter my Kitchen Roast Thermocouple Monitor project!


This was a low-budget gift for my wife I put together in the last 3 days before christmas.  I chose the Stellaris LaunchPad simply because I had no firm plans for it, and I have two Tiva-C LaunchPads that succeed it.  The application itself would be much better fit for an MSP430 (and could be implemented with battery power too) but alas, it was a good test of my last-minute resourcefulness :D


First, pics!


We start with the TI Stellaris LaunchPad.



Then home-etch a PCB using the laser toner-transfer process and ferric chloride, with some MG Liquid Tin for a nice tin protective coating:




This board features the MAX31855 Thermocouple Amplifier chipset along with a Nordic nRF24L01+ PA+LNA board with external antenna.  The latter is used to report temperatures to my Linux server, which posts them on a webpage every 30 seconds so my wife can view the state of her roast remotely with her iPad.


Then add @@bluehash 's first attempt at the Nokia 1202 BoosterPack, unfortunately the LCD on this board had its white backing removed (noob mistake I have not repeated since) so the LCD has a weird background effect to it-



Stack them all together:



I am not good with woodworking, but I made an exception here and worked hard with a circular saw and dremel out in my garage.  Used boiled linseed oil as a simple coating on the outside of the enclosure, milled out the holes with a drill & dremel...



It could definitely use some strain relief for the cord, I'll probably wrap it around the bottom female headers of the Stellaris LaunchPad at some point:



Anyway, my wife used it to make fudge and to make a rib roast for Christmas and it turned out perfectly!  This unit is expandable, meaning I can pop the top and replace/re-etch the boosterpacks as I see fit.  If she asks, I may implement more than one MAX31855 and possibly home-etch a Nokia 1202 boosterpack with pushbuttons with some implement to allow her to press them from the top wooden cover.


Board files for the custom MAX31855+nRF24L01+ PCB: Thermocouple_BPak40_draft1.zip

Nokia 1202 boosterpack from 43oh: http://forum.43oh.com/topic/3724-43oh-nokia-1202-lcd-display-boosterpack/




Used Energia 11 for this one.  First off, 2 libraries:

Nokia1202 - https://github.com/spirilis/Nokia1202

Enrf24 - https://github.com/spirilis/Enrf24


Nokia1202 requires 9-bit SPI, while I have an alternative Energia SPI lib for MSP430, I didn't have one for Stellaris/Tiva-C.

It required one update.  Updated energia-0101E0011/hardware/lm4f/libraries/SPI/SPI.cpp and SPI.h files: SPI_lm4f_9bit.zip


Energia sketch:

#include <ste2007.h>
#include <Nokia1202.h>
#include <Enrf24.h>
#include <nRF24L01.h>
#include <SPI.h>

struct MAX31855_DATUM {
  uint32_t rawData;
  int16_t tcDecimalC;
  int16_t tcC;
  int16_t tcF;
  int16_t ambDecimalC;
  int16_t ambC;
  int16_t ambF;
  uint8_t faultBits;

void max31855_read(struct MAX31855_DATUM *);

#define TC_CS PA_3
#define nRF24_CE PC_7
#define nRF24_CSN PE_0
#define nRF24_IRQ PD_6
#define LCD_BL PA_4
#define LCD_CS PA_5
#define LCD_RST PA_6
#define RADIO_CHANNEL 10
#define RADIO_ID 20
// 30-seconds between polls
#define WAIT_TIME 30

const uint8_t radio_basestation[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x01};

Nokia1202 lcd(LCD_CS);
Enrf24 radio(nRF24_CE, nRF24_CSN, nRF24_IRQ);
void setup()
  // put your setup code here, to run once:
  //Serial.setBufferSize(512, 64);

  Serial.println("Init TC CS, SPI-");  
  pinMode(TC_CS, OUTPUT); digitalWrite(TC_CS, HIGH);
  radio.begin(250000, RADIO_CHANNEL);
  radio.setCRC(true, true);

  // Manually reset LCD
  Serial.println("Resetting LCD-");
  pinMode(LCD_BL, OUTPUT); digitalWrite(LCD_BL, HIGH);
  pinMode(LCD_RST, OUTPUT); digitalWrite(LCD_RST, LOW);
  digitalWrite(LCD_RST, HIGH);
  Serial.println("Init LCD-");
  lcd.begin();  // Cursor enabled, contrast at medium, tabstop=4
  Serial.println("Writing splash screen-");
  lcd.print("Kitchen Roast!\nTemp Reporter\n\n");
  lcd.print("From Eric with\nLove \n\n");
  lcd.print("Web address:\nsisko.lan/roast");
  Serial.println("LCD splash screen delay complete-");

void loop()
  uint8_t nrfmsg[32], cursor = 0;
  struct MAX31855_DATUM tc;
  uint32_t lastupdate;

  // Update display
  Serial.println("LCD update-");
  lcd.println("Kitchen Roast!");
  lcd.print("Temp: "); lcd.print(tc.tcF); lcd.print('\x7f'); lcd.println("F");
  lcd.print("     ("); lcd.print(tc.tcC); lcd.print('\x7f'); lcd.println("C)");

  if (tc.faultBits) {
    lcd.setCursor(0, 7);
    if (tc.faultBits & 0x01)
      lcd.print("DISCON ");
    if (tc.faultBits & 0x06)
      lcd.print("SHORT ");
  lcd.setCursor(0, 6);

  Serial.println("nRF24 radio transmit-");
  nrfmsg[0] = 0x10;  // PROTO = THERMOCOUPLE UPDATE
  nrfmsg[1] = 0x06;
  nrfmsg[2] = RADIO_ID;
  nrfmsg[3] = tc.tcC & 0x00FF;
  nrfmsg[4] = tc.tcC >> 8;
  nrfmsg[5] = tc.ambC & 0x00FF;
  nrfmsg[6] = tc.ambC >> 8;
  nrfmsg[7] = tc.faultBits;
  radio.write(nrfmsg, 8);
  Serial.print("Waiting "); Serial.print(WAIT_TIME); Serial.println(" seconds before next update-");
  lastupdate = millis();
  while ( (millis() - lastupdate) < 1000*WAIT_TIME ) {
    lcd.setCursor(15, cursor);
    cursor ^= 1;

void max31855_read(struct MAX31855_DATUM *datum)
  uint32_t tcread;
  int16_t tmp;
  digitalWrite(TC_CS, LOW);
  tcread = SPI.transfer(0) << 24;
  tcread |= SPI.transfer(0) << 16;
  tcread |= SPI.transfer(0) << 8;
  tcread |= SPI.transfer(0);
  digitalWrite(TC_CS, HIGH);
  datum->rawData = tcread;
  // Thermocouple temp, degrees C and F
  tmp = (tcread & 0xFFFC0000) >> 18;
  if (tmp & 0x2000)
    tmp |= 0xC000;  // sign-extend
  // tmp = TC deg C decimal
  datum->tcDecimalC = tmp;
  datum->tcC = tmp / 4;
  datum->tcF = ((tmp * 9) / 5 + (32*4)) / 4;
  // Ambient temp, degrees C and F
  tmp = (tcread & 0x0000FFF0) >> 4;
  if (tmp & 0x0800)
    tmp |= 0xF000;  // sign-extend
  // tmp = Amb deg C decimal
  datum->ambDecimalC = tmp;
  datum->ambC = tmp / 16;
  datum->ambF = ((tmp * 9) / 5 + (32*16)) / 16;
  // Fault information
  datum->faultBits = tcread & 0x00000007;

Link to post
Share on other sites

Be aware that the contest has been extended until the end of January 2014 (see updates on the thread on 43oh forum).

Also, be sure to use the thread on 43oh to make your submissions (at least according to the leading article, submitting it in this thread may not count, or at least may make it

more of a headache for the forum maintainers.)

Link to post
Share on other sites

Be aware that the contest has been extended until the end of January 2014 (see updates on the thread on 43oh forum).

Also, be sure to use the thread on 43oh to make your submissions (at least according to the leading article, submitting it in this thread may not count, or at least may make it

more of a headache for the forum maintainers.)

Thanks igor. I'm keeping track of them. Also changed the date.

Link to post
Share on other sites
  • 2 weeks later...

Join the conversation

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

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...