Happy Fools’ day

Having too much spare time at hand, I put together the VK Couples Testing site featured in xkcd‘s Suspicion comic.

Yet another DIY Geiger Counter

After seeing a bunch online I decided I had to make one. Code and schematics are a bit messy, but available on request.

Board in Box

Board with Probes

One count, as seen on my new scope

Graphics for the Nokia 3310 LCD and AVR

There is a lot of info and code on talking to the Phillips PCD8544 display driver with AVR (and other micros). There is also a guide on hackaday for exporting images for the lcd. And there is this plugin for exporting bitmaps from gimp for another type of display.

I combined ideas and code from the above and came up with a fairly painless procedure for displaying fancy graphics on the teeny tiny but notorious Nokia 3310 display.

step I: draw

Using gimp and your ninja designer skills, draw your icon or graphics. Aim at a final product with height a multiple of 8 and any width (below 84 px). I made a template with the correct settings for the display

Image -> New …

create_blank

Note the different X and Y resolutions — pixels on the display are not square! I measured the physical dimensions myself with a caliper, so the figures may be a little off. The next useful step is to adjust and display a grid

Image -> Configure Grid …

configure-grid

View -> uncheck “Dot for dot” to enable the custom aspect ratio.

View -> Show Grid should be obvious.

8×6 px is the size of a character on this lcd with the “standard” font that you are probably using too. Anyway, width should be whatever floats your boat, but the height of 8 px is extremely helpful. You should be looking at something like this:

lcd-blank

The above screenshot shows an 8×8 grid with the actual aspect ratio of the display. You should be able to see that those are not squares.

step II: export

I based my export plugin on the work of Raul Aguaviva, he wrote an export plugin for ks0108.

Download it, place it in ~/.gimp-2.6/plug-ins/, restart Gimp.

Convert your image to indexed mode, 1-bit palette, remove the alpha chanel (if you have one).

Image -> Export -> AVR 3310…

export-settings

Click OK. If nothing bursts into flames or frogs don’t start falling from the sky you should be able to find something similar in the file:

#ifndef _TEST
#define _TEST

#include <stdint.h>
#include <avr/pgmspace.h>

// Rows: 2 Cols: 12
static const uint8_t icon_bulb[] PROGMEM = {
  /* cols */ 0x0c, /* rows */ 0x02,
  0xf0, 0x18, 0x0c, 0xc4, 0x22, 0xc2, 0x22, 0xc2, 0x24, 0x0c, 0x18, 0xf0,
  0x01, 0x03, 0x06, 0x7c, 0xf8, 0xf8, 0xf8, 0xf8, 0x7c, 0x06, 0x03, 0x01
};
#endif /* _TEST */

step III: display

I render the images with this function

void display_draw_image_P(const uint8_t * ptr, uint8_t xpos, uint8_t ypos) {
    uint8_t rows, cols, xi, yi;
    cols = pgm_read_byte(ptr);
    rows = pgm_read_byte(ptr+1);
    for (yi=0; yi < rows; yi++) {
        lcd_goto_xy(xpos, ypos+yi);
        for (xi=0; xi < cols; xi++) {
            lcd_data(pgm_read_byte(ptr + 2 + yi*cols + xi));
        }
    }
}

To use it successfully, replace calls to lcd_goto_xy() and lcd_data() with calls to your driver. That’s it!

Here’s another snipped, to show you some actual usage

static const uint8_t soft_open_normal[] PROGMEM = { 0x00, 0xfe, 0xff };
static const uint8_t soft_open_selected[] PROGMEM = { 0xfe, 0xff, 0xff };
static const uint8_t soft_close_normal[] PROGMEM = { 0xff, 0xfe, 0x00 };
static const uint8_t soft_close_selected[] PROGMEM = { 0xff, 0xff, 0xfe };
/**
 * Returns the width of the softbutton in pixels
 */
int8_t display_draw_softbutton_P(PGM_P label, uint8_t x, uint8_t flags) {
    lcd_goto_xy(x, 5);

    if (flags & DISPLAY_ITEM_SELECTED) {
        lcd_data_arr_P(soft_open_selected, 3);

        char chr;
        while ( (chr = pgm_read_byte(label)) ) {
            lcd_data(0xff);
            for (uint8_t col=0; col < 5; col++) {
                lcd_data( ~pgm_read_byte(FONT_SMALL_CHAR_P(chr) + col) | 0x01);
            }
            label++;
        }

        lcd_data(0xff);
        lcd_data_arr_P(soft_close_selected, 3);

    } else {

// and so on ...

And here’s a bad picture of something unfinished

unfinished-screen

RH measurement with resistive sensor and AVR

Needing to measure relative humidity, I went to a local electronic components store and asked for a sensor. All they had was the H25K5A (datasheet). As it turns out, this is the cheapest type of relative humidity sensor, and a real pain to read with a microcontroller. Well, not really that hard, but there is not much info on the web and it took a while to figure out. I am going to outline my method and proceedings below with the hope that some lost soul will someday find this work and thank me in their mind.

The first thing there is to know about resistive humidity sensors is they don’t like DC. Any DC current passing through them will cause irreversible damage to the sensor because of electrolysis going on inside its sensitive guts. AC only please, no DC bias whatsoever! The second thing is it has a significant amount of stray capacitance which needs to be taken into account, but its actual value is not mentioned anywhere in the datasheet. Thank you, China and company, for your high standards.

After a couple of days of Google searches, I found this gem. It is written in very poor English, it misses out on quite a few details … generally sounds like bullshit. But the procedure described in it actually works! The really cool thing about it is that no external components are required, except for a resistor. It takes 2 IO pins of the mcu and an ADC input:

Basic RH measurement circuit

Basic RH measurement circuit

The IO pins alternate their levels during measurement. After each transition, it takes some time (on the order of μs) for the voltage to stabilize — that’s when ADC reading must be fired. Measurements can be taken in only one state, or in both states of the IO pins. So I decided to measure both V1 and V2:

IO and ADC voltages

IO and ADC voltages

Resistance of the sensor — R(rh) — can be calculated from the ADC readings, granted Vcc = Vref, where Vref is the reference voltage for ADC:

R(rh) = (Vcc x R1)/V1 - R1
R(rh) = (V2 x R1)/(Vcc - V2)

Actually, that second equation will fail at V2 = 0, but it’s also true that Vcc = V1 + V2. So, take a bunch of readings for both states, average each out, then have

V1,average = (V1 + (Vcc – V2)) / 2

This approach gives the maximum number of ADC readings for the time current passes through the sensor. Did I mention that even with pure AC these sensors eventually give up? People say that you should pass the least possible amount of current though them.

At this point I built a prototype and it worked! The sum of averaged ADC reads for the 2 IO states was 1023±3 (10-bit ADC) — fair enough. The numbers reacted pretty well to me breathing into the sensor too. Now, how do I get from a number between 3 and 1020 to an actual figure for relative humidity in percent? Pretty simple — just take the calibration data from the datasheet, reformat it into data points, go to zunzun.com and stare in dismay. That website blew my mind, but more on it later.

First, let’s take a look at the correlation between ADC values and R(rh):

ADC vs. R(rh) value for different values of R1

ADC vs. R(rh) value for different values of R1

The dynamic range of my sensor is from around 2KOhm to about 21MOhm. That’s quite some range! Using a simple voltage divider will obviously not yield good results in terms of accuracy, but especially in terms of resolution at the far ends of the range — whichever one line on the graph I pick, there will be regions where my device will interpret a single least-significant bit change as a huge difference in relative humidity. To solve this, I do not use one voltage divider, but 3. Actually, more can be added, I just like the number 3:

Improved RH measurement circuit

This allows good dynamic range to be achieved by using either one of IO2, IO3 and IO4 for the opposite end of the voltage divider. So I find the derivatives of the above plots, find the points where they intersect and define the interval in which each curve is optimal.

ADC value expressed as function of R(rh)
ADC(R(rh)) = 1023 * R1 / (R(rh) + R1)

First Derivative
ADC'(R(rh)) = 1023 * R1 / (R(rh) + R1)^2

Find points where derivatives intersect

Solve ADC(R1=3)'(R(rh)) = ADC(R1=30)'(R(rh)) -> R(rh) = 3 * sqrt(10)
Solve ADC(R1=30)'(R(rh)) = ADC(R1=300)'(R(rh)) -> R(rh) = 30 * sqrt(10)

Therefore, resistances below ~9.48 KOhm should be measured with the 3KOhm bridge, such above 94.8KOhm should be measured with the 300KOhm bridge, everything else falls in the middle.

The next step is to get that value and turn it into RH%. Did I mention the sensor is heavily temperature-dependent? I should have, but you should have known if you looked at the datasheet. Adding a thermometer to an AVR-based device is fairly simple, I’ll just outline what I did next:

Pasted the reference values table from the datasheet into a spreadsheet, aligned it, transformed it into one-data-point-per-row format, copy/paste into zunzun.com’s 3D Function Finder. After it was done I had a piece of code I could readily paste into my program. Cool. The only problem is that the best interpolation gives ±15% error, and that is a lot. My plan is to slice the data into 3 temperature regions (or maybe slice it by resistance) and see if individual interpolations for each will yield better results.

This is a work in progress, part of a project I intend to release as open hardware. There’s a lot more to be done though, so stay tuned if you are interested. As always, comments are welcome, except in Russian.

Update: making some progress on the project, current code can be seen here, rendered schematics and links are also available.

First attempt at an Eclipse-compatible, flexible Arduino AVR Makefile

I’ve been playing with an Arduino board for a couple of months now (mostly during the weekends). Arduino is a great device — especially because it’s so simple to work with, both in terms of software and hardware. However, being a programmer, I felt sick about having to use the Arduino IDE. I’m using Eclipse in my dayjob, and though I don’t depend on it, I sure appreciate the time and effort a true IDE saves.

I was playing with an ATtiny26L a year or two ago, and even back then I was using Eclipse. There was no avr plugin, but still, configuring a C/C++ project was worth it. Now, that I had an Arduino board I wanted to use the core libraries for it inside Eclipse. The Eclipse tutorial did work at first, but broke badly with 0015. Additionally, the method described there did not allow for modifications of the libraries, which sometimes proves necessary.

So, I came up with my own method. Here’s a very summarized howto:

  • Install Eclipse, the AVR plugin for Eclipse, and make sure you have gcc-avr, avr-libc and all the necessary tools to build code for the AVR target.
  • In Eclipse, create a new C++ project using the “AVR Cross-target application” template.
  • Copy the hardware/cores/arduino directory from your Arduino IDE inside your newly created C++ project. Then go to the project properties -> AVR and configure your Target and Programmer.
  • In the project properties dialog, click on C/C++ Build and uncheck “Generate Makefiles automatically”
  • In the Build directory field, enter ${workspace_loc:/YourProjectName/build} Replace YourProjectName with the actual name of the project :)
  • Create the build directory inside your project, create a Makefile inside it, and paste the following:
# Makefile for building small AVR executables, supports C and C++ code
# Author: Kiril Zyapkov
 
SOURCE_DIRS = . arduino
INCLUDE_DIRS = arduino
MMCU = atmega168
F_CPU = 16000000UL
SRC_ROOT = $(CURDIR)/..
BUILD_DIR = $(CURDIR)
 
CFLAGS = -Wall -g2 -gstabs -Os -fpack-struct -fshort-enums -ffunction-sections \
 -fdata-sections -ffreestanding -funsigned-char -funsigned-bitfields \
 -mmcu=$(MMCU) -DF_CPU=$(F_CPU) $(INCLUDE_DIRS:%=-I$(SRC_ROOT)/%)
 
CXXFLAGS = -Wall -g2 -gstabs -Os -fpack-struct -fshort-enums -ffunction-sections \
 -fdata-sections -ffreestanding -funsigned-char -funsigned-bitfields \
 -fno-exceptions -mmcu=$(MMCU) -DF_CPU=$(F_CPU) $(INCLUDE_DIRS:%=-I$(SRC_ROOT)/%)
 
LDFLAGS = -Os -Wl,-gc-sections -mmcu=$(MMCU) #-Wl,--relax
 
TARGET = $(notdir $(realpath $(SRC_ROOT)))
CC = avr-gcc
CXX = avr-g++
OBJCOPY = avr-objcopy
OBJDUMP = avr-objdump
AR  = avr-ar
SIZE = avr-size
 
SRC = $(wildcard $(SOURCE_DIRS:%=$(SRC_ROOT)/%/*.c))
 
CXXSRC = $(wildcard $(SOURCE_DIRS:%=$(SRC_ROOT)/%/*.cpp))
 
OBJ = $(SRC:$(SRC_ROOT)/%.c=$(BUILD_DIR)/%.o) $(CXXSRC:$(SRC_ROOT)/%.cpp=$(BUILD_DIR)/%.o)
 
DEPS = $(OBJ:%.o=%.d)
 
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.c
	$(CC) $(CFLAGS) -c $&lt; -o $@
 
$(BUILD_DIR)/%.o: $(SRC_ROOT)/%.cpp
	$(CXX) $(CXXFLAGS) -c $&lt; -o $@
 
$(TARGET).a: $(OBJ)
	$(AR) rcs $(TARGET).a $?
 
$(BUILD_DIR)/%.d: $(SRC_ROOT)/%.c
	mkdir -p $(dir $@)
	$(CC) $(CFLAGS) -MM -MF $@ $&lt;
 
$(BUILD_DIR)/%.d: $(SRC_ROOT)/%.cpp
	mkdir -p $(dir $@)
	$(CXX) $(CXXFLAGS) -MM -MF $@ $&lt;
 
$(TARGET).elf: $(TARGET).a
	$(CXX) $(LDFLAGS) $&lt; -o $@
 
$(TARGET).hex: $(TARGET).elf
	$(OBJCOPY) -R .eeprom -O ihex $&lt;  $@
 
all: $(TARGET).hex printsize
 
clean:
	$(RM) $(DEPS) $(OBJ) $(TARGET).*
 
printsize:
	avr-size --format=avr --mcu=$(MMCU) $(TARGET).elf
 
.PHONY: all clean printsize
 
#include automatically generated dependencies
-include $(DEPS)

This is my first Makefile, so it’s probably stupid, ineffective, buggy and cryptic. Feel free to let me know :) I did not use the one that comes with the core libraries since I could not understand it all, plus it does stuff I don’t need (pre-processing to make an ‘Arduino language’ sketch into legal C/C++ for instance).

Here’s a blank sketch that you can try compiling:

#include "WProgram.h"
void setup(void);
void loop(void);
 
// implement void(), setup() and any other functions below
 
// the 'standard' main function for Arduino
void main() __attribute__ ((noreturn));
void main(void) {
    init();
    setup();
    for (;;)
        loop();
}

Place the above in a .cpp file in the project and click the “Build” button. If you configured the programmer details, uploading should work as well.