Friday, June 29, 2012

AVR-stick as a temperature logger

[Image]
I've stumbled recently upon a little gem called AVR-Stick and bought two of them for about 7€ per stick. I also had a thermoregulated fan controller salvaged from a recycled computer. I decided to desolder a thermistor from that controller and put it onto the stick to use it as a USB temperature logger. Stick's ATtiny85 has an internal temperature sensor too but it should be calibrated otherwise its precision is ±10°C. Thermistor that I'm using is equivalent to the NTCLE100E3103JB0 and its resistance is 10K at 25°C. Any other NTC thermistor that has resistance around 10K at room temperature could also be used but it should be checked if manufacturer specifies Steinhart-Hart equation coefficients for it.


Logger's schematic could be as simple as this voltage divider:

[Image]

but there are two problems with it:

  • thermistor produces an error if there's constant current through it. Although current is small still it produces error as big as 2-3 °C.
  • to reduce ADC conversion errors it would be better to use ATtiny85's internal reference voltages. Available reference voltages are 2.56V and 1.1V so the voltage divider on the picture above should be fed for example with 2.5V to not oversaturate the ADC.
The first problem is solved by controlling thermistor current by a ATtiny85's pin. To solve second problem I was thinking of using a 2.7V zener diode but I could only find a 500mW one. To set such diode's operation point 185mA would be needed but ATtiny85 can only supply 40mA from a pin. As the result I came up with the following schematic:

[Image]

This picture only shows elements I've added to the original design of the ARV-Stick. Switch is added to activate the bootloader but it isn't used because bootloader that I'm using activates application code automatically after 5 sec and it requires no switch.

Now the AVR-Stick had problems too:
  • the PCB is too thin so the USB contacts were not working.
  • the Stick's firmware was a waste.
I solved the first problem with a stripe cut from an old credit card. The resulting thickness was too high so I used a sandpaper to make that stripe thinner. The firmware is described below.

The Bootloader


First of all I needed a bootloader to be flashed onto the AVR-Stick because I didn't want to keep programmer's header mounted. BootloaderHID project doesn't support ATtiny's chips so I had to use this bootloader instead. I've only changed programmer string in the Makefile and also adjusted the HARDWARE_CONFIG define to reflect the hardware I'm using. Both changed files are stored in the bootloader folder of this github repository. After the bootloader was flashed I needed to install a windows driver for the USBasp programmer and to get patched AVRDude.exe as described on the bootloader's page.

The Firmware


I wanted the AVR-Stick to send temperature measures back to the host when the Caps Lock is pressed twice there. Original firmware does not support USB HID boot report protocol (USB HID boot report protocol has nothing to do with the bootloader) therefore it does not react on the status report messages sent by the OS. That's why I dumped the original firmware and put a new one (check the firmware section of the github repository).

To map an ADC reading to a temperature I'm using a precalculated table.Using Steinhart-Hart equation coefficients from thermistor's datasheet I have calculated Rt with 1°C step. When Rt is known for a particular temperature the ADC reading is equal to:


[Image]

where Vst is the voltage produced by the voltage divider. In this formula I assume that the USB voltage is 5V. If it is not the case then there will be a constant error produced.

As far as resistors values aren't precise and because of the Rt+10K resistance attached in parallel to one of the 430R resistors the real value of the Vst will be lower than the value calculated by the formula. Additionally it will vary a little with the temperature changes. I measured Vst with a multimeter and it was 2.33V.

Additionally internal reference voltage Vref isn't very precise. With measured voltage of 1.31V on an input pin the ADC value was 576. Which means that:

[Image]

and Kref:

[Image]


Delta column in the spreadsheet shows linear pieces that I'm using to construct a temperature reference table. To make it more convenient I've written a script that extracts values from C,F,G columns and fills J,K,L ones. The script is not shared with the spreadsheet so I'm publishing it here:
function createReportTable() {
  var sheet1 = SpreadsheetApp.getActiveSpreadsheet().getSheetByName("Sheet1");
  if (sheet1 == null)
    return;
  
  var tempCol = sheet1.getRange("C:C");
  var adcCol  = sheet1.getRange("F:F");
  var diffCol = sheet1.getRange("G:G");
  
  var tbl = sheet1.getRange("J:L");
  
  var i = sheet1.getFrozenRows()+1; // first row index
  var j = i; // new table index
  var i0 = i; // saved index 
  do
  {
    var step = Math.round(diffCol.getCell(i, 1).getValue());
    while (step == Math.round(diffCol.getCell(i, 1).getValue()))
      ++i;
    
    tbl.getCell(j, 1).setValue(Math.round(adcCol.getCell(i0, 1).getValue()));
    tbl.getCell(j, 2).setValue(Math.round(diffCol.getCell(i0, 1).getValue()));
    tbl.getCell(j, 3).setValue(Math.round(tempCol.getCell(i0, 1).getValue()));
    
    ++j;
    i0 = i;
    
  } while (diffCol.getCell(i, 1).getValue().toString()!="")
  
}

it simply finds places where the delta value changes and writes adc, delta, temp, into a new raw.

To convert an ADC reading to a temperature in the firmware I'm looking for a place in the table such as:

[Image]

and then temperature is calculated as:

[Image]

(check type definitions in the firmware/temptable.h)

Command Line Interface (Linux)


I also needed a utility to read temperature automatically without pressing anything on a keyboard because I'm attaching the logger to a router with OpenWRT installed. Utility (readavrstick) was tested with Xubunt 12.04 but it also should work with other distributions if linux/input.h exist (find the source code in the cli section of the github repository). To match router's architecture I have cross compiled the code using this description.

Showing temperature on a DPF


I've already mentioned DPF in one of my previous posts. I've changed the layout to show the temperature readings too. lcd4linux could execute readavrstick directly but I'm also using collectd daemon to record temperature values into a database. As far as readavrstick grabs HID keyboard device no other readavrstick instances should be running at the same time. The collectd script reads temperature and stores it in a file that is accessed by the lcd4linux (check a script below)

The resulting layout picture:

[Image]
I wanted to use the same bounding box for the clock and temperature by changing Image widget's visibility flag but there' s a bug in lcd4linux that it still shows a black box even if the visibility flag is set to 0. 
(Animated image is created using this site. This was the only site I've found that does not change images palette, does not compress original images and does not put any advertisement banners into the final image )

 

Showing temperature on a OpenWRT luci_statistics page


I've also written about collectd-mod-exec module usage here. The information there is a bit outdated because luci_statistics has changed. Now to make collectd exec module work it is necessary to create a /usr/lib/lua/luci/statistics/rrdtool/definitions/exec.lua with the following content:

root@Buffalo:~# cat /usr/lib/lua/luci/statistics/rrdtool/definitions/exec.lua
module("luci.statistics.rrdtool.definitions.exec", package.seeall)

function rrdargs(graph, plugin, plugin_instance)

        -- within the exec.lua's rrdargs() function you can decide
        -- for which rrd you've been called. For the temperature.rrd,
        --  plugin will be "exec" and plugin_instance will be "room-temperature"
        
        --
        -- temperature diagram
        --
        if "room-temperature" == plugin_instance then
                return {
                        title = "%H: Temperature",
                        vlabel = "Celsius",
                        data = {
                                types = { "temperature" },
                                options = {
                                        temperature = {
                                                title  = "Room temperature",
                                                color  = "ff0000"
                                        }
                                }
                        }
                }
        end
end

The idea here is to return proper data type definitions for particular exec script running. For example to collect room temperature I'm using the following simple script:

root@Buffalo:~# cat /mnt/sd/bin/roomtempcollect.sh 
#!/bin/sh

logger "*** roomtempcollect script is started ***"

HOST=$COLLECTD_HOSTNAME
INTERVAL=$COLLECTD_INTERVAL

[ -z "$INTERVAL" ] && INTERVAL=5
[ -z "$HOST" ] && HOST=Buffalo
INTERVAL=$(awk -v i=$INTERVAL 'BEGIN{print int(i)}')
while sleep $INTERVAL; do
  value=$(/mnt/sd/bin/readavrstick)
  [ -n "$value" ] && echo "$value" > /tmp/roomtemp.txt # for lcd4linux
  echo "PUTVAL \"$HOST/exec-room-temperature/temperature\" interval=$INTERVAL N:${value}"
done


what follows the exec- on the line 15 is the name of the plugin_instance that I'm checking in the exec.lua script above and in this case it is room-temperature. This is the only logging script that I'm currently running so the exec.lua is simple. If I would run more logging scripts I'd have to update the exec.lua script for each of them.

This is how room temperature graph looks like on the luci_staistics page:

[Image]

The only problem left is that udev (or hotplug) creates nodes in /dev with read/write permissions for root only and readavrstick shall have permission to write too. As far as collectd refuses to start scripts with root credentials I've written a small helper script for OpenWRT hotplugging mechanism to adjust permissions for the avr-stick device on the fly when it is attached to the router:
root@Buffalo:~# cat /etc/hotplug.d/input/20-avrstick 
#!/bin/sh

local vendor
local product

case "$ACTION" in
    add)
        vendor=$(cat /sys/${DEVPATH}/device/id/vendor) 
        product=$(cat /sys/${DEVPATH}/device/id/product) 
        [ "2420" -eq "$vendor" -a "0001" -eq "$product" ] && {
            chmod 666 /dev/${DEVNAME}       
        }
    ;;
    remove)
        # nothing to do
    ;;
esac

Conclusion



For this project I've only used parts that I had already and I didn't want to buy anything extra. Precision of the logger could be better if I bought a specialized IC with a serial interface to measure temperature. Still it was fun to do everything myself and I've learned something that Atmel does not put into their datasheets.

[Image]

No comments: