Nixie Tube Energy Meter

December 28, 2015

Featured on Hack a Day

Having recently bought a house, project time has been a bit thin on the ground. As a standard terrace house, the consumer unit and electricity meter were in the entrance hallway, exposed and looking a bit naff. I liked the look of the meter so I quickly created a box that allowed the meter to poke through and leave access to the fuses.

The box covering did the job but felt a bit cumbersome with all that spare space; it needed something else to give it more purpose. An energy meter was the obvious thing but I didn’t want a garish LCD or 7 segment display, it need to match the blown glass electricity meter… …nixie tubes!

The Nixie Module runs off 5V and SPI
The Nixie Module runs off 5V and SPI making the project quick to get off the ground

Design

I’ve wanted to use nixie tubes in a project for a long time – almost tempted by the bog-standard clock – so once I had the vision on this one I set about it in zero time! I’m afraid I didn’t design the driving circuit myself, instead I went for this very tidy Nixie Module. Working off 5V and SPI it is nice and quick to get a project off the ground. It is expensive but the build quality and time saved designing something else made it a worthy investment. An Arduino would be the microcontroller but I wanted the meter to provide some form of data stream for a web based energy history. To make it an IoT, I a paired ESP8266 with it. I used both together because the Arduino ADC has a better resolution and has been tried and tested.

Display

An OpenSCAD box meant an OpenSCAD panel. I also modelled the nixie tube for verification.
An OpenSCAD box meant an OpenSCAD panel. I also modelled the nixie tube for verification.

Considering the expense of the Nixie module and the irony of a high energy impact energy meter not lost on me (the tubes draw around 300mA each), I opted to use only two tubes; I was going to require SI notification anyway for full scale display. Handily, the tubes have colon points on the right-hand side (for clock use), which I designed as indicators for x10 and x1. With both SI units and x10 x1 decimals, one can display from 0 to 100MW – albeit with only 2 significant figures (considering the accuracy of a current transformer system like this, that isn’t a problem).

The Nixie library and arrangement of the SI indicators made displaying the power very simple

void printPwr(uint16_t Pwr) {
    tube.printf("%02d",Pwr);
    SI_PORT |= (SI_OFF_MASK & SI_WATT_MASK);

    if ((Pwr >= 100) && (Pwr < 1000)) { tube.setColon(1,(Colon) Lower); } else if ((Pwr >= 1000) && (Pwr < 10000)) { SI_PORT &= SI_KILO_MASK; tube.setColon(0,(Colon) Upper); } else if (Pwr >= 10000) {
        SI_PORT &= SI_MEGA_MASK;
        tube.setColon(1,(Colon) Upper);
    } else {
        tube.setColon(1,(Colon) None);
    }

    // set kilowatt hours
    if (display == (rolling_t) hour)
        SI_PORT &= SI_HOUR_MASK;

    tube.display();
}

Power Measurement

The power measurement comes by way of SCT-013 clip-on current transformer with a current sensing resistor in parallel, biased by VREF/2, then sampled by the Arduino ADC. Open Energy Monitor have plenty of detail on this, which I followed and a useful library, that I used. In its most basic form, the library samples the ADC reading over a number of samples (in phase with 50Hz mains) and returns the RMS current. With the RMS current and assumed RMS voltage (230V) using \(P = V * I\), the apparent power is calculated.

Apparent power is the assumed power and is what a resistive load will draw. Real power is what we’re billed for and that takes into account non-linear voltage/current relationships and phase lag created by other loads such as motors (which feed power back at certain points in the cycle) and DC converters. The differences are again best summarised by Open Energy Monitor.

Building the system I noticed that the bell (already in the box near the consumer unit) uses a step-down transformer, offering outputs or 3, 5 or 8V. I could adapt the system to sample this AC voltage too, to create a more accurate reading rather than using the assumed 230V RMS. I’m not too bothered though, I mainly want an indicative meter than also looks good! There is already a wide amount of error in the system: transformer tolerance, resistor tolerance, voltage bias drift, ADC resolution etc.

Control

I didn’t want any buttons on the panel and I also wanted to generate reports of power usage. For this, I integrated and EPS8266 using standard AT firmware to act as a TCP packet relay over serial. I found the ESP8266wifi library suited this usage but required a blocking function call to check for incoming connections/messages – not suitable for updating the display in a timely manor, ADC sampling or any task other than a pure TCP client!

I forked the code with functions that poll the ESP8266 serial buffer instead, checking new connections and maintaining a multi-client connection structure, such that when there are connections, messages can be checked or data sent.

When a connection is made and message recieved, a simple command interpreter then acts upon it:

void processCommand(WifiMessage msg) {
    // return buffer
    char espBuf[MSG_BUFFER_MAX];
    // scanf holders
    int set;
    char str[16];

    // Get command and setting
    sscanf(msg.message,"%15s %d",str,&set);
    /* swSerial.print(str);*/
    /* swSerial.println(set);*/

    // Stream JSON
    if ( !strcmp_P(str,STRM) ) {
        snprintf(espBuf,sizeof(espBuf),json,avg[0],avg[1],avg[2],avg[3]);
        wifi.send(msg.channel,espBuf);
    }
    else if ( !strcmp_P(str,ROLL) ) {
        snprintf(espBuf,sizeof(espBuf),json,roll[0],roll[1],roll[2],roll[3]);
        wifi.send(msg.channel,espBuf,false);
        snprintf(espBuf,sizeof(espBuf),json,count[0],count[1],count[2],count[3]);
        wifi.send(msg.channel,espBuf,true);
    }
    // Change colour
    else if ( !strcmp_P(str,COLOUR) ) {
        tube.setBackgroundColor((Color) set);
        tube.display();
        wifi.send(msg.channel,msg.message);
    }
    // Reset rolling counts
    else if ( !strcmp_P(str,RESET) ) {
        memset(roll, 0, ( (sizeof(roll)/sizeof(roll[0])) * sizeof(roll[0]) ) );
        memset(count, 0, ( (sizeof(count)/sizeof(count[0])) * sizeof(count[0]) ) );
        wifi.send(msg.channel,"COUNT OK");
    }
    // Change Nixie display var
    else if ( !strcmp_P(str,DISP) ) {
        display = (rolling_t) set;
        wifi.send(msg.channel,msg.message);
    }
    // Set ADC calibration
    else if ( !strcmp_P(str,CAL) ) {
        EEPROM.write(EEPROM_CAL,set);
        wifi.send(msg.channel,msg.message);
    }
    // Return what we are
    else if ( !strcmp_P(str,IDN) ) {
        wifi.send(msg.channel,"JBR ENERGY MONITOR V0.1");
    }
    // Reset system by temp enable watchdog
    else if ( !strcmp_P(str,RST) ) {
        wifi.send(msg.channel,"SYSTEM RESET...");
        // soft reset by reseting PC
        asm volatile (" jmp 0");
    }
    // Unknown command
    else {
        wifi.send(msg.channel,"ERR");
    }
}

I have commands to stream JSON formated data, with a plan to have a locally running Raspberry Pi Node.js server, requesting the JSON stream and updating a database/presenting a rolling usage graph. That will be something for another project log. The tubes have RGB backlights, so there are commands to change the colour and also change the display between sec, min, hour or even day rolling average.

Result

The affect of turning a kettle on and off (~2kW)

If you want to make one yourself or part of the design, the code and OpenSCAD designs are in this Github

UPDATED: See the wireless version