Health badge

Electronics

I bought a 128×128 SPI Nokia 5110 display (The ILI9163)  to hook up to an Arduino.  I used an Arduino Mini which runs at 3.3v, since the display uses 3.3v logic.

Then wired it up like:

- LED       -->     +3.3V  -  (5V would work too) 
- SCK       -->     Pin 13 -  Sclk,  (3.3V level)
- SDA       -->     Pin 11 -  Mosi,  (3.3V level)
- A0        -->     Pin 10 -  DC or RS pin (3.3V level)
- RST       -->     Pin 9  -  Or just connect to +3.3V
- CS        -->     Pin 8  -  CS pin (3.3V level)
- Gnd       -->     Gnd
- Vcc       -->     +3.3V

(Note that SDA and SCK pins aren’t optional, because we’re using the arduino SPI hardware to drive this. The DC and CS pins are changeable if you change the code)

layout1_bb

And found a library to use this:

cd /libraries/
git clone https://github.com/adafruit/Adafruit-GFX-Library
git clone https://github.com/sumotoy/TFT_ILI9163C

I had to then edit TFT_ILI9163C/TFT_ILI9163C.h to comment out the #define __144_RED_PCB__ , and uncomment the #define __144_BLACK_PCB__ since my PCB was black. Without this change I got corruption on the top 1/4 of the screen.

Now to get something up and running, using the above pin definition:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <TFT_ILI9163C.h>

/* SPI hardware runs on:
 MOSI:  11
 SCK:   13
 */
#define __CS 8
#define __DC 10
#define __RESET 9

TFT_ILI9163C tft = TFT_ILI9163C(__CS, __DC, __RESET);

const uint16_t WIDTH = 128;
const uint16_t HEIGHT = 128;

void setup(void) {
  tft.begin();
  tft.drawFastHLine(0, 0, WIDTH, 0xFFE0);
}

void loop() {
}

This now gives us a black sceen with a yellow line. Woohoo!

Now, I have a bunch of images that I got an artist to draw, that look like:

face10

I have 10 of these, and their raw size would be 128 * 128 * 4 * 10 = 655KB. Far too large for our 30KB of available flash memory on the nano!

But given the simplicity of the images, we can use a primitive compression algorithm and instead store these as Run Length Encoded. E.g. store as them as pixel index color followed by the number of pixels of that color in a line. We turn to trusty python to do this:

import os,sys
import itertools
from PIL import Image

rleBytes = 0
colorbits = 3

if len(sys.argv) != 3:
    print sys.argv[0], " <input file.png> <output file.h>"
    sys.exit()

def writeHeader(f):
    f.write("/* A generated file for a run length encoded image of " + sys.argv[1] + " */\n\n")
    f.write("#ifndef PIXEL_VALUE_TO_COLOR_INDEX\n")
    f.write("#define PIXEL_VALUE_TO_COLOR_INDEX(x) ((x) >> " + str(8-colorbits) + ")\n")
    f.write("#define PIXEL_VALUE_TO_RUN_LENGTH(x) ((x) & 0b" + ("1" *(8-colorbits)) + ")\n")
    f.write("#endif\n\n")
    f.write("namespace " + os.path.splitext(sys.argv[2])[0] + " {\n")
def writeFooter(f):
    f.write("}")

def writePalette(f, image):
    palette = image.im.getpalette()
    palette = map(ord, palette[:3*2**colorbits]) # This is now an array of bytes of the rgb565 values of the palette
    f.write("/* The RGB565 value for color 0 is in palette[0] and so on */\n")
    # convert from RGB888 to RGB565
    palette565 = [((palette[x] * 2**5 / 256) << 11) + ((palette[x+1] * 2**6 / 256) << 5) + ((palette[x+2] * 2**5/256)) for x in xrange(0, len(palette), 3)]
    f.write("const uint16_t palette[] PROGMEM = {" + ','.join(hex(x) for x in palette565) + "};\n\n")

def writePixel(pixelIndexColor, count):
    global rleBytes
    f.write(hex((pixelIndexColor << (8 - colorbits)) + count) + "," )
    rleBytes += 1

def writePixels(f):
    f.write("/* The first (MSB) " + str(colorbits) + "bits of each byte represent the color index, and the last " + str(8-colorbits) + " bits represent the number of pixels of that color */\n")
    f.write("const unsigned char pixels[] PROGMEM = {")
    (width, height) = image.size

    lastColors = []
    lastColor = -1
    lastColorCount = 0
    for y in xrange(height):
        for x in xrange(width):
            color = pix[x,y]
            if lastColorCount +1 < 2**(8 - colorbits) and (lastColorCount == 0 or color == lastColor):
                lastColorCount = lastColorCount + 1
            else:
                writePixel(lastColor, lastColorCount)
                lastColorCount = 1
            lastColor = color
        # To make the decoder easier, truncate at the end of a row
        # This adds about 5% to the total file size though
        writePixel(lastColor, lastColorCount)
        lastColorCount = 0
    f.write("};\n\n");


image = Image.open(sys.argv[1])
image = image.convert("P", palette=Image.ADAPTIVE, colors=2**colorbits)
#image.save("tmp.png")
pix = image.load()

f = open(sys.argv[2], 'w')
writeHeader(f)
writePalette(f, image)
writePixels(f)
writeFooter(f)
f.close()

print "Summary: Total Bytes: ", rleBytes

The result is that the 10 pictures take up 14kb. This doesn’t produce an optimal image, but it’s a decent starting point. This generates header files like:

/* A generated file for a run length encoded image of face1.png */

#ifndef PIXEL_VALUE_TO_COLOR_INDEX
#define PIXEL_VALUE_TO_COLOR_INDEX(x) ((x) >> 5)
#define PIXEL_VALUE_TO_RUN_LENGTH(x) ((x) & 0b11111)
#endif

namespace face1 {
/* The RGB565 value for color 0 is in palette[0] and so on */
const uint16_t palette[] PROGMEM = {0xef0b,0xf4a3,0x0,0xdb63,0xffff,0xa302,0xe54a,0x5181};

/* The first (MSB) 3 bits of each byte represent the color index, and the last 5 bits represent the number of pixels of that color */
const unsigned char pixels[] PROGMEM = {0x5f,0x56,0xe2...}
}

Which represents the image:

tmp

Which isn’t too bad for a first go. (This was generated by adding a image.save('tmp.png')" in the python code after converting the image).

Now we need an arduino sketch to pull this all in:

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <TFT_ILI9163C.h>
#include "face1.h"
#include "face2.h"
#include "face3.h"
#include "face4.h"
#include "face5.h"
#include "face6.h"
#include "face7.h"
#include "face8.h"
#include "face9.h"
#include "face10.h"

const unsigned char *pixels[] = { face1::pixels, face2::pixels, face3::pixels, face4::pixels, face5::pixels, face6::pixels, face7::pixels, face8::pixels, face9::pixels, face10::pixels } ;
const unsigned char numpixels[] = { sizeof(face1::pixels), sizeof(face2::pixels), sizeof(face3::pixels), sizeof(face4::pixels), sizeof(face5::pixels), sizeof(face6::pixels), sizeof(face7::pixels), sizeof(face8::pixels), sizeof(face9::pixels), sizeof(face10::pixels) };
const uint16_t *palette[] = { face1::palette, face2::palette, face3::palette, face4::palette, face5::palette, face6::palette, face7::palette, face8::palette, face9::palette, face10::palette } ;


/*
 We are using 4 wire SPI here, so:
 MOSI:  11
 SCK:   13
 the rest of pin below:
 */
#define __CS 8
#define __DC 10
#define __RESET 9
TFT_ILI9163C tft = TFT_ILI9163C(__CS, __DC, __RESET);

const uint16_t width = 128;
const uint16_t height = 128;

void drawFace(int i);

void setup(void) {
  tft.begin();
}


void drawImage(const unsigned char *pixels, const unsigned char numPixels, const uint16_t *palette) {
  int16_t x = 0;
  int16_t y = 0;
  for( int i = 0; i < sizeof(face1::pixels); ++i) {
    unsigned char pixel = pgm_read_byte_near(pixels + i);
    /* The first (MSB) 3 of each byte represent the color index, and the last 5 bits represent the number of pixels of that color */
    unsigned char colorIndex = PIXEL_VALUE_TO_COLOR_INDEX(pixel);
    int16_t count = PIXEL_VALUE_TO_RUN_LENGTH(pixel);
    uint16_t color = pgm_read_word_near(palette + colorIndex);
    tft.drawFastHLine(x,y,count+1,color);
    x += count;
    if(x >= width) {
      x = 0;
      y+=1;
    }
  }
}

void loop() {
  for(int i = 1; i <= 10; ++i)
    drawFace(i);
}

void drawFace(int i) {
  drawImage(pixels[i-1], numpixels[i-1], palette[i-1]);
}

Pretty straight forward.

Using 4 bits per color uses up slightly too much space: 34,104 bytes (111%) of program storage.
Using 3 bits per color introduces slight artifcats, but presumably removable if we tweak the images, but it fits with plenty of room to space:

Sketch uses 20,796 bytes (67%) of program storage space. Maximum is 30,720 bytes.
Global variables use 134 bytes (6%) of dynamic memory, leaving 1,914 bytes for local variables. Maximum is 2,048 bytes.

The result is this:

Note that this is actually updating at maximum speed. There is room for improvement in the adafruit graphics code however, so I may have a go at improving this if I need faster updates. It might be nice to faster updates anyway in order to minimize battery use.

Android Phone App

I wanted to keep the Android app side of it as clean and simple as possible. There’s not much to document regarding the GUI side of things – it’s a pretty straightforward Android app.

The idea here is to connect via bluetooth to both the arduino and to a Healthband. It measures the heart rate and, more importantly, the very slightly changes in time between the heart pulses. By the knowing the users age, sex and weight, we can calculate the amount of stress that they are currently experiencing. We can then display that in the app, and send that information via Bluetooth Low Energy to our arduino heart badge.

Screenshot_2015-05-29-23-49-29

Screenshot_2015-05-29-23-51-15

It perhaps needs some tweaking – I’m not really that angry, honest!  Lots of polishing is still needed, but the barebones are starting to come together!

Leave a comment