Sunday, April 14, 2013

Temperature Graph using Electric Imp and TMP102 over I2C

Hey I have a blog I forgot about...

Why another temperature monitor?

My main purpose was to just get started with creating wireless embedded low-power devices.  I hadn't used a soldering iron in over a decade and I needed something easy and fun.

So let's show my boss that my brain overheats at over 75 degrees Fahrenheit.

I quickly needed a way to track temperature.  Ideally the device should be wireless and low power to liberate myself from the desktop.  I had bought an Arduino Uno R3 a while ago but I didn't like that it needed a serial connection.  So I decided to try the Electric Imp even though it's a closed system.  

The prototype uses a TMP102 temperature sensor since I wanted a cheap high resolution sensor on a breakout board.  I also wanted to learn I2C since the digital bus design is very appealing for future devices with many sensors.  Spark Fun Electronics here in Boulder Colorado has some wonderful things that got me started.

Hardware

The wiring ending up very simple, with the sensor intentionally wired up almost directly to the Imp Breakout board.  You could potentially solder them onto each other but I'd like to save the Impee for future quick work.


The line-up between the Imp and the TMP102 worked out because the way I soldered the pins.  As you can see in the code below I configured pin 1+2 to be the I2C connection.

TMP102: SCL and SDA?

However, the I2C connection has a clock (SCL) and data (SDA) and I had to head over to a forum to learn that SCL is pin1 and SDA is pin 2.  That should be easy info on the Imp documentation wiki.  The next pin over is pin 5 which lines up with VCC on the TMP102.  They are both 3.3v ICs and I hoped that the Imp could drive a low-power sensor.  I was right but I really should check the amp requirements.

TMP102: ADD0

I thought I could leave the ADD0 pin on the TMP102 alone but it does need to be grounded.  TMP102 allows two sensors on the I2C bus and grounding it is the way to lock it to the lower address 0x48.

Blinking LED

The project had to have a blinking LED.  All projects do...  This one was my debugging tool.  The black bar you can see is the 330 resistor bank since... it looks nicer than a resistor...  The wires make the LED connected to pin 7 and the code blinks the LED every time it takes a temperature reading.

Software

Electric Imp is a strange beast.  The SD card form factor makes you think you can program it using a computer with and SD reader.  You can't.  It won't hurt it inserting into an SD reader but it won't even power up.  Instead you need a breakout board that supply power and an ID chip that uniquely identifies your Impee.

The Imp is connected to the internet over WiFi.  This is also how you program it. Yep.  No sketches or uploading over USB.  You program on their webpage and upload to the Imp.  It's strange programming with the Imp next to you, even "connected" with USB but just for power

When you hit "Run Code" it actually uploads the new code over the web back to your Imp over your home WiFi.  Debugging is the same strange thing.  I see serial output on the Web page but those strings are actually transmitted over the web by the Imp and then BACK to my computer.  That's a lot of travelling for embedded debugging.

Imp: Planner

The main screen allows you to see all your Imps and visually associate debug input and outputs using dragging of connectors.  The screen below is the currently running temperature monitor:


The blue box represents the Imp along with its current show message using:
server.show(format("%3.1f%c (%dv)", temperature, unit, volt));

The green boxes are output that allow easy display of output values or just counting the number of updates the Imp has provided.  It's a push architecture displayed above.  The "Cosm 200" green boxes will be dealt with at the end. They are the bridges to graphing the temperature.

Imp: Code

Programming is done by clicking the edit link in the blue box which brings us to the "code" tab:


The Log tab is showing output in real-time when you use server.log() such as:
server.log(format("[%d,%d] %d %dv", degrees, data[1], steps, volt));

TMP102: Negative temperature

The only tricky part was reading negative temperatures correctly.  The imp sat in my freezer for an hour at least...  I'm serious.

The TMP102 returns temperature as two bytes.  The first byte is the Two's Complement of the temperature in Celsius  This byte is probably enough for most people but it required a little tinkering to compute the rest of the temperature without my full C++ toolbox.  I'm sure there is a better way of doing it in the Imp's Squirrel language but I ran out of time and it works.

TMP102: 12 bits of temperature?

The sensor is rated for 0.5°C accuracy and the second byte carries this extra data.  The sensor actually returns 12-bit aka 0.0625°C resolution but doesn't promise that accuracy.  However looking at page 4 on the full TMP102 spec sheet it shows the following graph:


This means most of the data at room temperature is MUCH more accurate than 0.5°C.  It looks like most readings are +- 0.05°C and many are probably even more accurate.

BTW, The TMP102 has a lot of cool capabilities such as continuous temperature reading, sleep modes, high and low thresholds and more.

Graphs

The purpose was to show management that development was overheating so we needed fancy graphs and historical records.  Fortunately Cosm is a great resource for this and Electric Imp has a direct bridge over to Cosm.


Connecting out Imp temperature data feed over to Cosm is simply a matter of adding a Cosm node and configuring it with the correct Cosm API key and feed number.  I did add two feeds from the Imp for fun and practice over at https://cosm.com/feeds/123369


Fun project and management is already working on installing blinds and fixing the AC system, three days after the graph became visible.  let me know if there are any correction needed above.

Next project will be a cheaper version of wireless temperature using Arduino and a 434Mhz transmitter.



Code for the Imp below:

/** Temperature monitor using TMP102 chip over I2C */
// Temperature Sensor Class for TMP102
class TemperatureSensor
{
    i2cPort = null;
    i2cAddress = null;
    constructor(port, add0)
    {
        if (port == I2C_12)
        {
            // Configure I2C bus on pins 1 & 2
            hardware.configure(I2C_12);
            i2cPort = hardware.i2c12;
        }
        else if (port == I2C_89)
        {
            // Configure I2C bus on pins 8 & 9
            hardware.configure(I2C_89);
            i2cPort = hardware.i2c89;
        }
        else
        {
            server.log("Invalid I2C port specified.");
        }
        i2cPort.configure(CLOCK_SPEED_100_KHZ);
        i2cAddress = (add0 ? 0x49 : 0x48) << 1;
        server.log(format("i2cAddress=%x", i2cAddress));
        // Start continuous conversion
        i2cPort.write(i2cAddress, "");
        i2cPort.write(i2cAddress, "0");
        imp.sleep(0.05);
    }
    // Retrieve temperature (from local sensor) in deg F
    function getTemperature(unit)
    {
        local data = i2cPort.read(i2cAddress, "\x00", 2);
        if (data == null)
        {
            server.show("Error reading TMP102");
            return null;
        }

        // Remove two's complement if negative temperature
        local degrees = data[0];
        if (degrees & 0x80)
            degrees = -((degrees - 1) ^ 0xFF);
            
        // Calculate no of steps. Each step is 0.0625 degrees centigrades
        local steps = degrees * 16 + (data[1] >> 4);
        
        local temperature = steps * 0.0625;
        if (unit == 'F')
            temperature = temperature * 9 / 5 + 32;
        
        local volt = hardware.voltage();
        server.log(format("[%d,%d] %d %dv", degrees, data[1], steps, volt));
        server.show(format("%3.1f%c (%dv)", temperature, unit, volt));
        return temperature;
    }
}

// Instantiate the sensor
local sensor = TemperatureSensor(I2C_12, false);
// Output port to send temperature readings
local output = OutputPort("Temperature", "number");
local iq = OutputPort("IQ", "number");
local power = OutputPort("volt", "number");

// Temperature unit
local unit = 'F';
// Capture and log a temperature reading every 5s
function capture()
{
     // Blink LED
    hardware.pin7.configure(DIGITAL_OUT);
    hardware.pin7.write(1);
    imp.sleep(0.1);
    hardware.pin7.write(0);
    
    // Set timer for the next capture
    imp.wakeup(60.0, capture);
    // Output the temperature in F
    local temp = sensor.getTemperature(unit);
    if (temp != null)
    {
        output.set(math.floor(temp*10.0)/10.0);
        iq.set(math.floor(100.0 + 20.0 * math.atan(40.0-temp/2.0)));
        power.set(hardware.voltage());
    }
}
// Register with the server
imp.configure(format("Temperature Logger (%c)", unit), [], [output,iq,power]);


// Configure and turn on temperature chip
hardware.pin5.configure(DIGITAL_OUT);
hardware.pin5.write(1);

// Start capturing temperature
capture();
// End of code.