POVON 16 CHannel Household Submetering System

POVON home energy monitor Part 1: 20 Channel System

This is the story of POVON: my abandoned home energy monitor project and startup. I’m documenting my work here for the reference and benefit of others working on similar devices.

Back in 2015, myself and a coworker joined forces in an attempt to enter the “home energy monitor” market. We wanted to make and sell small home energy monitors which would live inside a household breaker-box and continuously report measurements to our web-app over the internet via WiFi or cellular connection. Unfortunately the project was more difficult than we imagined, and eventually died from neglect. I was solely responsible for the hardware design and manufacture of our systems, and two main hardware “units” were developed. This part, Part 1, will detail our 20 Channel sub-metering hardware.

 

Warning: The text below is a shortened version of a detailed report I wrote on this project for an engineering class back before I graduated. You can download the full report, which includes more details regarding the current transformer and other component selections, here.

 

20 Channel Circuit Level Home Energy Monitor Description and Overview

Our initial goal was to monitor power consumption in different parts of the house, and we quickly realized every household circuit would need to be monitored. After some research, small clip on current transformers, or CT’s, looked to be the best sensor for our application. Using CT’s, current draw and thus power on each circuit can be measured. The CT’s would be installed on the wires immediately leaving the circuit breakers in the standard household breaker box. CT’s work great for this because they’re completely isolated and nothing needs to be disconnected to install them. We chose Dechang Electronics model SCT-006 30A split core current transformers as our standard CT, but also implemented circuitry for up to five larger CT’s (SCT-010) for monitoring of larger 240VAC appliances. The Open Energy Monitor project has a fantastic page explaining how to connect CT’s to a micro-controller ADC pin. Alternatively, download and read my paper linked above for this.

Connectivity

Two telemetry methods were selected to report the power data back to the internet server. The first and main method was the use of the ESP8266 by Espressif, a small microcontroller with built in Wi-Fi capability. The second method is via a GSM cellular modem, specifically the SIMCOM SIM900 quad-band module. Both of these devices were selected due to their low cost (~$5 for the ESP8266 and ~$20 for the SIM900) as well as their ease of use and thorough online documentation and open source libraries. As an additional feature and the 3rd wireless device on the system, a small UHF data radio/modem (RFM69W) was added to allow for the creation of a Home Area Network (HAN).

Brains

A Teensy 3.1 microcontroller was chosen as the brains of the system because the onboard MK20DX256 processor contains a decent Analog Digital Converter (ADC) capable of 2MSPS at 12 bit resolution. Furthermore, the MCU has an internal analog multiplexer allowing up to 21 pins to be routed to the ADC – the exact amount we need! (remember, we need one extra to sample the line voltage waveform!) Looking back, we should have used external discrete ADCs to measure the signals from the CTs in order to reduce interference across the PCB and obtain more accurate power measurements.

Mechanics

Physical and electrical design of the home energy monitor necessitated a PCB. I spun up the PCB with Eagle and had it manufactured by my favorite board-house, PCBWay. Cost was about $30 for 10 PCBs including shipping. I also made a laser cut acrylic case for the entire system. Power was supplied by a 12V AC to AC adapter – this allows the line voltage waveform to be sampled by the MCU safely for use in the power calculations. After this ADC connection, the 12V AC is rectified, filtered, and regulated down to proper levels for all board components.

Unfortunately most of the code for this project was lost, though I’ll definitely update this page when/if I find it. This home energy monitor was installed and tested in my apartment as well as my business partner’s. We had it reporting to a server running an instance of the Open Energy Monitor web-app Emoncms successfully. Initial results were decent – readings were between 5% and 10% accurate on all 20 CT’s before any adjustments or calibrations. Please checkout the pics for more details:

Feel free to use the Eagle PCB files here (.zip warning)

Part 2 of this post, when I have time to write it up, will detail the Mini Energy Monitor

 

Flip Digit Clock Completed

Flip Display Clock

This is the official build log and walk-through for my recently completed Flip Display Clock! You can see a video of me explaining this clock here as well as a link to the associated reddit post here.

So, why design and build a Flip Display Clock? Simple – I thought it was a cool thing to make, it presents an interesting electronics challenge, and I love retro displays of all types (I’ve already built several Nixie Clocks). I was originally inspired by a reddit post of someone trying to use the same style flip digits to make their own clock; I couldn’t miss out on the fun so I purchased 6 7-segment 1″ Ferranti-Packard electromechanical displays from eBay.

These displays are electrically interesting because each segment has it’s own electromagnet which must be given a 19V 1msec ~300mA pulse in order to flip the little piece of plastic on the front from black to white. Furthermore, the coils do not have a center tap meaning that the polarity across the coils must be truly reversed to change the piece of plastic again from white to black. As each digit has 7 segments (so 7 electromagnets), and we want a 4 digit clock, the dumbest way to design the control circuitry would be to build 28 independent H-bridges. Now, a H-bridge will take a minimum of 4 transistors; all the sudden we are up to 112 individual transistors needing a ton of I/O on our MCU just to drive a 4 digit clock!

After a fair amount of thought and some recommendations from other engineers, I figured out how to multiplex the digits efficiently. They are arranged in a classic matrix configuration so I can select one of 4 digits to flip and then one of 7 segments within that digit to flip. The control electronics, only needing 11 sets now, are half-bridges (half an H-bridge). Check out schematics below.

I built this clock without the diodes at first and it didn’t work. Only after some intense debugging did I realize that the diodes were needed to prevent power from energizing other segments in completely different digits than the one I was trying to control. Also, if you’re wondering why 2N2222 transistors were needed on the high side fets – this is necessary to correctly turn them on and off because they’re not ground referenced and we are making them switch 19V which is much higher than our I/O residing at 3.3V. Look up H-bridge design for more details on why this is so.

After figuring out all that needed to be done, I designed the entire circuit in Eagle PCB. The rest of the clock system is fairly straight forward. An Atmega328P micro-controller (Read Arduino) is the brains of the operation. I used a MCP23017 I/O expander to gain the necessary number of I/O needed to control all 11 half bridges described earlier (22 I/O in total for the half-bridges); this part connects to the 328P via I2C. Also on the same I2C bus is a DS3231 Real Time Clock chip with a rechargeable lithium backup battery. The only other large system on board is the 12V to 19V boost converter based on the TPS61175 chip from TI and designed with TI’s AMAZING WEBENCH software. Seriously, why would you ever design a buck or boost converter without WEBENCH in 2018?

Assembly was done with my trusty DIY reflow toaster oven (I may write another post about this oven later) and a solder stencil I ordered along with the PCB from PCBway. 10 PCB’s and the solder stencil were roughly $70 including DHL shipping and they arrived in about 1 week. The pictures below show the application of solderpaste to the PCB using the stencil. After that step, each component (ordered from DigiKey) is placed with tweezers onto the PCB before the whole thing goes in the oven. I highly recommend this technique for soldering PCBs – I’ve had fantastic success with it so far. In fact, I just soldered a 6 pin BGA chip for a different project with these methods and it worked perfectly!

After soldering, the PCB’s look close to professional IMHO. I only had to do a couple touch ups here and there with the hot air gun. Once you’ve assembled a board like this, make sure you power it up slowly with a current limited PSU and check each voltage rail on the board as you go in case there’s a short or other problem you missed.

The next step was building a case. I designed an acrylic enclosure with the help of MakerCase and fine tuned the details by editing the file in AutoCAD. Once designed, I used my university’s laser cutter to cut each piece from Acrylic.

The final hardware:

Next up is software! I wrote the entirety of embedded code for this project in C/C++ with the help of the Arduino ecosystem and libraries. All development was done in the Visual Studio Code IDE with the PlatformIO plugin. If you haven’t used PlatformIO, you’re missing out! My code is attached at the bottom of this post as a zip file (too lazy to make a git repo atm) and the main program is presented here directly as well. I won’t do a very detailed walk through of my code because it’s pretty self explanatory and I tried to comment it well. At a high level, the main loop continually checks if the digits need to be updated by polling the RTC for the time and comparing it with the previously displayed time. You can also see I just used giant case statements to map out the digit I/O as it was simple and fast. Only one segment can ever be flipped at once with this clock design, but they flip very fast so it’s not a problem. The code is dependent on a few other libraries, so if you try it, make sure to get those.

// ****************************************
// Flip Clock Code V1.0
// Started: 10/26/17
// Coyt Barringer
// ****************************************


// ****************************************
// dependencies
// ****************************************
#include <Arduino.h>


//library for I2C
#include <Wire.h>


#include <DS3231.h>


//library for MCP23017 I/O expander chip
#include "Adafruit_MCP23017.h"





// ****************************************
// PIN definitions clock
// ****************************************
#define BACK_LED 13
#define WHITE_LEDS 10

//#define FRONT_TOP_LED
//#define FRONT_BOTTOM_LED
#define BOOST_ENABLE A0
#define BUTTON_A A3
#define BUTTON_B A2

#define ONET 9
#define ONEB 2
#define TWOT 7
#define TWOB 8
#define THREET 5
#define THREEB 6
#define FOURT 3
#define FOURB 4

#define AT 0
#define AB 8
#define BT 1
#define BB 9
#define CT 2
#define CB 10
#define DT 3
#define DB 11
#define ET 4
#define EB 12
#define FT 5
#define FB 13
#define GT 6
#define GB 14

#define night 22 //defines hour out of 24 hours where clock switches to night mode
#define morning 8 //defines hour out of 24 hours where clock switches to day mode

// ****************************************
// Global Variables
// ****************************************


// ****************************************
// Objects
// ****************************************
Adafruit_MCP23017 mcp;

DS3231 clock;
RTCDateTime dt;

int lasthour = 0;
int lastminute = 0;
long lastMillis = 0;
int hour = 0;
int minute = 0;
int ledMem;
bool nightFlag = false;

// ****************************************
// flipDigitTwoSegmentADown();
// ****************************************
void flipDigitTwoSegmentADown(){
  //digitalWrite(BOOST_ENABLE,HIGH);
  delay(1);
  mcp.digitalWrite(AB,HIGH);
  digitalWrite(TWOT,HIGH);
  delay(1);
  mcp.digitalWrite(AB,LOW);
  digitalWrite(TWOT,LOW);
  delay(1);
  //digitalWrite(BOOST_ENABLE,LOW);
}

// ****************************************
// flipDigitTwoSegmentAUp();
// ****************************************
void flipDigitTwoSegmentAUp(){
  //digitalWrite(BOOST_ENABLE,HIGH);
  delay(1);
  mcp.digitalWrite(AT,HIGH);
  digitalWrite(TWOB,HIGH);
  delay(1);
  mcp.digitalWrite(AT,LOW);
  digitalWrite(TWOB,LOW);
  delay(1);
  //digitalWrite(BOOST_ENABLE,LOW);
}


// ****************************************
// Function to flip any segment of any digit to white or black
// white = bool true
// black = bool false
// ****************************************
void flip(int digit, int segment, bool dir){

  if(dir){
    //flip to white
    switch(digit){
      case 1:
        //flip certain segment
        switch(segment){
          case 1:
            mcp.digitalWrite(AB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(AB,LOW);
            break;
          case 2:
            mcp.digitalWrite(BB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(BB,LOW);
            break;
          case 3:
            mcp.digitalWrite(CB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(CB,LOW);
            break;
          case 4:
            mcp.digitalWrite(DB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(DB,LOW);
            break;
          case 5:
            mcp.digitalWrite(EB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(EB,LOW);
            break;
          case 6:
            mcp.digitalWrite(FB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(FB,LOW);
            break;
          case 7:
            mcp.digitalWrite(GB,HIGH);
            digitalWrite(ONET,HIGH);
            delay(1);
            digitalWrite(ONET,LOW);
            mcp.digitalWrite(GB,LOW);
            break;
          default:
            break;
        }

        break;
      case 2:
        //flip certain segment
          switch(segment){
          case 1:
            mcp.digitalWrite(AB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(AB,LOW);
            break;
          case 2:
            mcp.digitalWrite(BB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(BB,LOW);
            break;
          case 3:
            mcp.digitalWrite(CB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(CB,LOW);
            break;
          case 4:
            mcp.digitalWrite(DB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(DB,LOW);
            break;
          case 5:
            mcp.digitalWrite(EB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(EB,LOW);
            break;
          case 6:
            mcp.digitalWrite(FB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(FB,LOW);
            break;
          case 7:
            mcp.digitalWrite(GB,HIGH);
            digitalWrite(TWOT,HIGH);
            delay(1);
            digitalWrite(TWOT,LOW);
            mcp.digitalWrite(GB,LOW);
            break;
          default:
            break;
        }
        break;
      case 3:
        //flip certain segment
          switch(segment){
          case 1:
            mcp.digitalWrite(AB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(AB,LOW);
            break;
          case 2:
            mcp.digitalWrite(BB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(BB,LOW);
            break;
          case 3:
            mcp.digitalWrite(CB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(CB,LOW);
            break;
          case 4:
            mcp.digitalWrite(DB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(DB,LOW);
            break;
          case 5:
            mcp.digitalWrite(EB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(EB,LOW);
            break;
          case 6:
            mcp.digitalWrite(FB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(FB,LOW);
            break;
          case 7:
            mcp.digitalWrite(GB,HIGH);
            digitalWrite(THREET,HIGH);
            delay(1);
            digitalWrite(THREET,LOW);
            mcp.digitalWrite(GB,LOW);
            break;
          default:
            break;
        }
        break;
      case 4:
        //flip certain segment
          switch(segment){
          case 1:
            mcp.digitalWrite(AB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(AB,LOW);
            break;
          case 2:
            mcp.digitalWrite(BB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(BB,LOW);
            break;
          case 3:
            mcp.digitalWrite(CB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(CB,LOW);
            break;
          case 4:
            mcp.digitalWrite(DB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(DB,LOW);
            break;
          case 5:
            mcp.digitalWrite(EB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(EB,LOW);
            break;
          case 6:
            mcp.digitalWrite(FB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(FB,LOW);
            break;
          case 7:
            mcp.digitalWrite(GB,HIGH);
            digitalWrite(FOURT,HIGH);
            delay(1);
            digitalWrite(FOURT,LOW);
            mcp.digitalWrite(GB,LOW);
            break;
          default:
            break;
        }
        break;
      default:
       //do nothing
       break;
    }
  }

  else{
    //flip to black
    switch(digit){
      case 1:
        //flip certain segment
        switch(segment){
          case 1:
            mcp.digitalWrite(AT,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(AT,LOW);
            break;
          case 2:
            mcp.digitalWrite(BT,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(BT,LOW);
            break;
          case 3:
            mcp.digitalWrite(CT,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(CT,LOW);
            break;
          case 4:
            mcp.digitalWrite(DT,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(DT,LOW);
            break;
          case 5:
            mcp.digitalWrite(ET,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(ET,LOW);
            break;
          case 6:
            mcp.digitalWrite(FT,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(FT,LOW);
            break;
          case 7:
            mcp.digitalWrite(GT,HIGH);
            digitalWrite(ONEB,HIGH);
            delay(1);
            digitalWrite(ONEB,LOW);
            mcp.digitalWrite(GT,LOW);
            break;
          default:
            break;
        }
        break;
      case 2:
        //flip certain segment
        switch(segment){
          case 1:
            mcp.digitalWrite(AT,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(AT,LOW);
            break;
          case 2:
            mcp.digitalWrite(BT,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(BT,LOW);
            break;
          case 3:
            mcp.digitalWrite(CT,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(CT,LOW);
            break;
          case 4:
            mcp.digitalWrite(DT,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(DT,LOW);
            break;
          case 5:
            mcp.digitalWrite(ET,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(ET,LOW);
            break;
          case 6:
            mcp.digitalWrite(FT,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(FT,LOW);
            break;
          case 7:
            mcp.digitalWrite(GT,HIGH);
            digitalWrite(TWOB,HIGH);
            delay(1);
            digitalWrite(TWOB,LOW);
            mcp.digitalWrite(GT,LOW);
            break;
          default:
            break;
          }
        break;
      case 3:
        //flip certain segment
        switch(segment){
          case 1:
            mcp.digitalWrite(AT,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(AT,LOW);
            break;
          case 2:
            mcp.digitalWrite(BT,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(BT,LOW);
            break;
          case 3:
            mcp.digitalWrite(CT,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(CT,LOW);
            break;
          case 4:
            mcp.digitalWrite(DT,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(DT,LOW);
            break;
          case 5:
            mcp.digitalWrite(ET,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(ET,LOW);
            break;
          case 6:
            mcp.digitalWrite(FT,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(FT,LOW);
            break;
          case 7:
            mcp.digitalWrite(GT,HIGH);
            digitalWrite(THREEB,HIGH);
            delay(1);
            digitalWrite(THREEB,LOW);
            mcp.digitalWrite(GT,LOW);
            break;
          default:
            break;
          }
        break;
      case 4:
        //flip certain segment
        switch(segment){
          case 1:
            mcp.digitalWrite(AT,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(AT,LOW);
            break;
          case 2:
            mcp.digitalWrite(BT,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(BT,LOW);
            break;
          case 3:
            mcp.digitalWrite(CT,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(CT,LOW);
            break;
          case 4:
            mcp.digitalWrite(DT,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(DT,LOW);
            break;
          case 5:
            mcp.digitalWrite(ET,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(ET,LOW);
            break;
          case 6:
            mcp.digitalWrite(FT,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(FT,LOW);
            break;
          case 7:
            mcp.digitalWrite(GT,HIGH);
            digitalWrite(FOURB,HIGH);
            delay(1);
            digitalWrite(FOURB,LOW);
            mcp.digitalWrite(GT,LOW);
            break;
          default:
            break;
          }
        break;

      default:
        break;
       //do nothing
    }
  }

}

// ****************************************
// Function to flip all segments to white
// ****************************************
void allWhite(){
  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(1,i,true);
  }
  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(2,i,true);
  }
  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(3,i,true);
  }
  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(4,i,true);
  }
}

// ****************************************
// Function to flip all segments to black
// ****************************************
void allBlack(){
  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(1,i,false);
  }

  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(2,i,false);
  }

  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(3,i,false);
  }

  delay(1);
  for(int i = 1; i <= 7; i++){
    delay(1);
    flip(4,i,false);
  }
}

// ****************************************
// Function to make a cool transition - ONE
// ****************************************
void coolSequenceOne(){
  int delayOne = 50;

  //allBlack();

  delay(delayOne);
  flip(1,6,true);
  delay(delayOne);
  flip(1,1,true);
  delay(delayOne);
  flip(2,1,true);
  delay(delayOne);
  flip(3,1,true);
  delay(delayOne);
  flip(4,1,true);
  delay(delayOne);
  flip(4,2,true);
  delay(delayOne);
  flip(4,3,true);
  delay(delayOne);
  flip(4,4,true);
  delay(delayOne);
  flip(3,4,true);
  delay(delayOne);
  flip(2,4,true);
  delay(delayOne);
  flip(1,4,true);
  delay(delayOne);
  flip(1,5,true);

  delay(delayOne);

  flip(1,6,false);
  delay(delayOne);
  flip(1,1,false);
  delay(delayOne);
  flip(2,1,false);
  delay(delayOne);
  flip(3,1,false);
  delay(delayOne);
  flip(4,1,false);
  delay(delayOne);
  flip(4,2,false);
  delay(delayOne);
  flip(4,3,false);
  delay(delayOne);
  flip(4,4,false);
  delay(delayOne);
  flip(3,4,false);
  delay(delayOne);
  flip(2,4,false);
  delay(delayOne);
  flip(1,4,false);
  delay(delayOne);
  flip(1,5,false);

  delay(delayOne);
}


// ****************************************
// Function to write a number from 0 to 9 on any of the digits!
// ****************************************

void displayNumber(int digit, int number){

  //makes selected digit all black before we can change the actual number on it
  for(int i = 1; i <= 7 ;i++){
    flip(digit,i,false);
  }

  switch(number){
    case 0:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,true);
      break;
    case 1:
      flip(digit,2,true);
      flip(digit,3,true);
      break;
    case 2:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,7,true);
      break;
    case 3:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,7,true);
      break;
    case 4:
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 5:
      flip(digit,1,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 6:
      flip(digit,1,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 7:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      break;
    case 8:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 9:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
  }


}


// ****************************************
// Function to write a number from 0 to 9 on any of the digits!
// ****************************************

void displayNumberStyle2(int digit, int number){

  switch(number){
    case 0:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,true);
      flip(digit,7,false);
      break;
    case 1:
      flip(digit,1,false);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,false);
      flip(digit,5,false);
      flip(digit,6,false);
      flip(digit,7,false);
      break;
    case 2:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,false);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,false);
      flip(digit,7,true);
      break;
    case 3:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,false);
      flip(digit,6,false);
      flip(digit,7,true);
      break;
    case 4:
      flip(digit,1,false);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,false);
      flip(digit,5,false);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 5:
      flip(digit,1,true);
      flip(digit,2,false);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,false);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 6:
      flip(digit,1,true);
      flip(digit,2,false);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 7:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,false);
      flip(digit,5,false);
      flip(digit,6,false);
      flip(digit,7,false);
      break;
    case 8:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,true);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
    case 9:
      flip(digit,1,true);
      flip(digit,2,true);
      flip(digit,3,true);
      flip(digit,4,true);
      flip(digit,5,false);
      flip(digit,6,true);
      flip(digit,7,true);
      break;
  }


}





// ****************************************
// Setup
// ****************************************
void setup() {

    pinMode(BOOST_ENABLE, OUTPUT);
    pinMode(BACK_LED, OUTPUT);
    pinMode(BUTTON_A, INPUT);
    pinMode(BUTTON_B, INPUT);
    pinMode(WHITE_LEDS, OUTPUT);

    //keep boost converter off
    digitalWrite(BOOST_ENABLE, LOW);
    digitalWrite(WHITE_LEDS, HIGH);

    //set all DIGIT pins to outputs
    pinMode(ONET, OUTPUT);
    pinMode(ONEB, OUTPUT);
    pinMode(TWOT, OUTPUT);
    pinMode(TWOB, OUTPUT);
    pinMode(THREET, OUTPUT);
    pinMode(THREEB, OUTPUT);
    pinMode(FOURT, OUTPUT);
    pinMode(FOURB, OUTPUT);

    //set initial matrix pin outputs so everything is OFF!
    digitalWrite(ONET, LOW);
    digitalWrite(ONEB, LOW);
    digitalWrite(TWOT, LOW);
    digitalWrite(TWOB, LOW);
    digitalWrite(THREET, LOW);
    digitalWrite(THREEB, LOW);
    digitalWrite(FOURT, LOW);
    digitalWrite(FOURB, LOW);


    Serial.begin(115200);
    delay(10);

    // Initialize DS3231
    Serial.println("Initialize DS3231");;
    clock.begin();

    // Set clock time on first reprogram
    // make sure to comment the below out so we don't reset time every time the clock restarts!!!
    //clock.setDateTime(2017,11,23,10,11,00);


    //flash LEDs a few times to indicate startup sequence
    for(int i = 0; i <= 10; i++){ digitalWrite(BACK_LED, LOW); delay(200); digitalWrite(BACK_LED, HIGH); delay(200); } //enable I/O expander mcp.begin(); //default address 0 delay(100); //set all I/O expander pins to outputs! mcp.pinMode(0, OUTPUT); //GPA0 mcp.pinMode(1, OUTPUT); //GPA1 mcp.pinMode(2, OUTPUT); //GPA2 mcp.pinMode(3, OUTPUT); //GPA3 mcp.pinMode(4, OUTPUT); //GPA4 mcp.pinMode(5, OUTPUT); //GPA5 mcp.pinMode(6, OUTPUT); //GPA6 mcp.pinMode(7, OUTPUT); //GPA7 //LED1 //BOTTOM LED mcp.pinMode(8, OUTPUT); //GPB0 mcp.pinMode(9, OUTPUT); //GPB1 mcp.pinMode(10, OUTPUT); //GPB2 mcp.pinMode(11, OUTPUT); //GPB3 mcp.pinMode(12, OUTPUT); //GPB4 mcp.pinMode(13, OUTPUT); //GPB5 mcp.pinMode(14, OUTPUT); //GPB6 mcp.pinMode(15, OUTPUT); //GPB7 //LED2 //TOP LED //turn all segment transistors OFF mcp.digitalWrite(AT, LOW); mcp.digitalWrite(AB, LOW); mcp.digitalWrite(BT, LOW); mcp.digitalWrite(BB, LOW); mcp.digitalWrite(CT, LOW); mcp.digitalWrite(CB, LOW); mcp.digitalWrite(DT, LOW); mcp.digitalWrite(DB, LOW); mcp.digitalWrite(ET, LOW); mcp.digitalWrite(EB, LOW); mcp.digitalWrite(FT, LOW); mcp.digitalWrite(FB, LOW); mcp.digitalWrite(GT, LOW); mcp.digitalWrite(GB, LOW); mcp.digitalWrite(7, HIGH); mcp.digitalWrite(15, HIGH); delay(50); digitalWrite(BOOST_ENABLE, HIGH); delay(50); lasthour = dt.hour; lastminute = dt.minute; lastMillis = millis(); //do two extra cool sequences at startup allBlack(); coolSequenceOne(); coolSequenceOne(); } // **************************************** // Forever Loop // **************************************** void loop() { // ********** //this loop will happen every second or whatever is specified in the if statement // ********** if((millis() - lastMillis) >= 1000){

      //update time from the RTC chip
      dt = clock.getDateTime();

      hour = dt.hour;

      //24 hour to 12 hour conversion
      if(hour > 12){
        hour = hour - 12;
      }

      minute = dt.minute;


      //blink led every second
      //will not blink if we are in night mode
      if(ledMem == HIGH || nightFlag == true){
      mcp.digitalWrite(7, LOW);
      mcp.digitalWrite(15,LOW);
      ledMem = LOW;
      }

      else{
      mcp.digitalWrite(7, HIGH);
      mcp.digitalWrite(15,HIGH);
      ledMem = HIGH;
      }
      //end blink led every second


      lastMillis = millis();
    }


    // **********
    // set time: if button is pressed increment minute val or rollover and update time in RTC
    // **********
    if(digitalRead(BUTTON_A) == 1){
      //coolSequenceOne();

      int tempMinute = 0;

      //update time from the RTC chip
      dt = clock.getDateTime();

      tempMinute = dt.minute;

      //this statement handles rollover of the time setting
      if((tempMinute + 1) > 60){
        tempMinute = 1;
      }
      else{
        tempMinute++;
      }


      clock.setDateTime(dt.year,dt.month,dt.day,dt.hour,tempMinute,dt.second);

      //update time from the RTC chip
      dt = clock.getDateTime();

      minute = dt.minute;


      delay(100);

    }

    // **********
    // set time: if button is pressed increment hour val or rollover and update time in RTC
    // **********
    if(digitalRead(BUTTON_B) == 1){

      int tempHour = 0;

      //update time from the RTC chip
      dt = clock.getDateTime();

      tempHour = dt.hour;

      //backled will be ON if we are in PM
      if(tempHour > 12){
        digitalWrite(BACK_LED, HIGH);
      }
      else{
        digitalWrite(BACK_LED, LOW);
      }

      //this statement handles rollover of the time setting
      if((tempHour + 1) > 24){
        tempHour = 1;
      }

      else{
        tempHour++;
      }

      clock.setDateTime(dt.year,dt.month,dt.day,tempHour,dt.minute,dt.second);

      //update time from the RTC chip
      dt = clock.getDateTime();

      hour = dt.hour;

      //24 hour to 12 hour conversion
      if(hour > 12){
        hour = hour - 12;
      }

      delay(100);

    }


    // **********
    //update of hour digits
    // **********
    if(hour != lasthour){
      lasthour = hour;

      //run cool routine to make display more interesting and transition on each hour!

      if(nightFlag == false){
        allBlack();
        coolSequenceOne();
        allBlack();
      }



      //update minutes after coolSequenceOne or else it'll take one minute for any numbers to show on minute digits
      displayNumberStyle2(3,(minute/10));
      displayNumberStyle2(4,(minute%10));

      if(dt.hour == 0){
        displayNumberStyle2(1,1);
        displayNumberStyle2(2,2);
      }
      else{
        displayNumberStyle2(1,(hour/10));
        displayNumberStyle2(2,(hour%10));
      }

      //red backled will be ON if we are in PM
      if(dt.hour > 12){
        digitalWrite(BACK_LED, HIGH);
      }
      else{
        digitalWrite(BACK_LED, LOW);
      }

      //test for night mode and set back leds accordingly
      if(dt.hour >= night || dt.hour <= morning){
        digitalWrite(WHITE_LEDS, LOW);
        nightFlag = true;
      }
      else{
        digitalWrite(WHITE_LEDS, HIGH);
        nightFlag = false;
      }

    }


    // **********
    //update of minute digits
    // **********
    if(minute != lastminute){
      lastminute = minute;

      displayNumberStyle2(3,(minute/10));
      displayNumberStyle2(4,(minute%10));
    }


}






//asdf

Flip Digit Clock Code & Libraries (Direct .ZIP)

Flip Digit Clock Complete Schematics (PDF)

Flip Digit Clock Board Layout (PDF)

Flip Digit Clock Eagle PCB Files & BOM (.ZIP)

Flip Digit Clock pcb GERBERS for PCBWay (.ZIP)

AudoCad Drawing for Laser Cut Case (.ZIP)

Laser Cut Case Ponoko size P2 File (.ZIP)

Feel free to post any questions regarding this project!

 

 

Programming STM32F103 Blue Pill using USB Bootloader and PlatformIO

This is the infamous Blue Pill board – a $2 ARM STM32F103 development board with all the capabilities of a Teensy 3.x at a fraction of the price of an Arduino. So what’s the catch?

I’ll tell you – software support.

A couple weeks ago I decided to invest some time learning this platform because I was sick of paying 20+ dollars for a Teensy. While the PJRC platforms are fantastic, they are expensive and need a proprietary boot loader in order to work. I want a small and powerful arm chip which I can integrate INTO my own PCBs and the Teensy does not easily or cheaply allow this. The Blue Pill and it’s derivatives appear to be just the thing I need!

But how do you use one of these things? It should be simple! Just buy one, connect it to your computer over USB, and upload code from the Arduino IDE or PlatformIO just like you would with an Arduino!… WRONG! It appears the Blue Pills are not quite there yet. The main problem is lack of a USB boot loader installed at the factory as well as poor documentation and poor support in PlatformIO.

As a side note, if you are not using the PlatformIO framework with either ATOM or Visual Studio Code as your prototype embedded development environment then you need to get with the program! Just download VS Code and install the PlatformIO plugin – it’s fantastic! Way better than Arduino IDE!

Now, I’ll tell you exactly how to get your blue pill working like an Arduino:

  1. Get a Blue Pill STM32F103C8T6 board AND EITHER a 3.3V USB/UART adapter, a knockoff ST-LINK programmer, or a REAL ARM programmer such as a J-Link. You only need one of those three things. I’ve verified this tutorial works fine with all 3.
  2. Change resistor R10 from 10k Ohms to 1.5k Ohms on the Blue pill board. This is an 0603 size surface mount resistor on the bottom side of the Blue Pill. Read up elsewhere on this problem and make sure you need to do it for your specific board. I know it’s a PITA, but just do it – it’s not that hard and will make USB work properly.
  3. Flash the latest USB bootloader onto your Blue Pill STM32F103
    1. You need to get the latest STM32duino bootloader from rogerclarkmelbourne’s GITHUB here: https://github.com/rogerclarkmelbourne/STM32duino-bootloader Go into the “binaries” folder and download the bootloader.bin file that matches the LED pin on your actual Blue Pill board! My Blue Pill boards have the LED on pin PC13 so I selected the file “generic_boot20_PC13.bin”.
    2. OPTION 1: Use J-Flash Lite program with your J-LINK ARM programmer to flash the bootloader file to the Blue Pill
      1. Wire up the Programmer as I did in my picture below, or look up how to connect your specific ARM programmer to an ARM processor over SWD pins.
      2. In J-Flash lite, select the “STM32F103CB” processor from the dropdown, select the bootloader.bin file from earlier, and hit write. J-Flash Lite should say success. Plug in your Blue Pill to PC via USB and it should register as a com port or something and the led should blink a few times.
    3. OPTION 2: The Blue Pill does come from factory with a UART bootloader pre-programmed to operate on a couple of the pins. This allows you to upload the USB bootloader from earlier with just a USB to UART adapter. This is more annoying because you must use a weird Python Program. See this dude’s tutorial for info. 
    4. OPTION 3: Use the knockoff STLINK V2 programmer you purchased to burn the bootloader. Honestly I lied and haven’t done this method yet, but you should just need to download some STLINK flasher app and upload our USB bootloader to the Blue Pill just as we did with J-Flash in option 1.
    5.  You’re USB bootloader should now be on the Blue Pill STM32F103 – make sure both jumpers on Blue Pill are set to default of 0 and the LED should blink fast after pressing reset and then continue blinking slowly to indicate no user program is found yet.
  4. Install DFU drivers to allow Windows to program the Blue Pill
    1. Go to rogerclarkmelbourne’s github repo for Arduino_STM32 here: https://github.com/rogerclarkmelbourne/Arduino_STM32
    2. Download the repo, extract all the stuff, and go into “/drivers/win”, and run the “install_drivers.bat” as administrator. This seems super sketchy but it actually worked for me every time.
  5. Make sure you have GIT and JAVA installed on your PC – trust me – PlatformIO needs both to work with the Blue Pill apparently
  6. Make sure you have the PlatformIO plugin installed and updated in either ATOM or Visual Studio Code
  7. Make a NEW project using PlatformIO and selecting board: bluepill & framework:Arduino
  8. Edit platformio.ino file with a couple custom settings
    1. Change the text after platform to be “platform = https://github.com/platformio/platform-ststm32.git” This is why we needed GIT – it will allow PlatformIO to pull the most recent framework and board info directly from GITHUB.
    2. Add or Change “upload_port = anything”
    3. Add or Change “upload_protocol = dfu”
    4. See my screenshot of completed PlatformIO.ini
  9. You should now be able to upload a sketch/program from PlatformIO onto the Blue Pill Board with ONLY a USB cable!
    1. If you’re testing with Arduino “Blink” style program, make sure to change the LED pin from 13 or whatever to PB13 for the Blue Pill!
    2. You’ll likely need to press the reset button on the Blue Pill to get the program to upload

I really hope this helps some people out. This took more hours than I’d care to admit to figure out. Please let me know if anything needs to be changed or updated. I’ll try to keep it current – everything here worked as of 12/16/17.

My worst FAIL or how to destroy a $500 radio

     A year or two have passed so I figured I’m finally ready to share one of the worst FAILs of my young EE life.

     It all started with a dream… A dream to tap into the Intermediate Frequency line inside my beloved FT-817. If it worked, I would have been able to use the FT-817 as a HF upconverter for a RTL SDR dongle. In conjunction with my tablet, I would have had a super cool portable “panadapter” setup with my FT-817. A waterfall display on the tablet would show me every signal inside the FT-817’s receive bandwidth at the same time!

     For this to work, I needed to install a buffer amp and filter between the IF line in the FT-817 and an SMA connector I install in the radio case. The RTL SDR will reside just outside the FT-817. I bought the amp and filter kit from w1ghz and was all ready to go. 1st things 1st… drill a hole in the FT-817 case to install the SMA connector.

     Stupidly, I didn’t remove the main PCB inside the FT-817 as I drilled through the back side of it’s aluminum case. In a split second the drill bit propelled through the case completeing the hole and taking out several SMD components as it screeched across the FT-817 main PCB! All in all, the drill completely took out the CXA1611 FM/AM radio chip and it’s traces on the PCB.

     Over the following year, I attempted two seperate repairs of the chip and traces. Unfortunately, the radio has never functioned like it once did, and I hardly use it anymore due to this accident.

     Lessons learned: think before you drill holes in expensive radios!

 

Score of the Year! Free Multi-kW HF Linear Amplifier!

Ahhhh how I love attending a college with active research labs decades older than me!

Where else could I score a free Multi-kW HF Linear Amplifier!?!?!?!

Anyway, this find is most definitely my find of the year. This SERVER RACK SIZED amp was a CW (continuous wave) RF source for a Sputtering System in one of the materials labs at my university. Every once in a while these old research labs get some money and decide to renovate. When they do this they discard old equipment in certain areas for recycling. Many times this equipment is in perfect shape and very valuable if you happen to know what your looking for… and when and where to find it.

This is where I come in. Over the past 10 or so years that I’ve been hanging around my college I’ve become an expert in dumpster diving this electronic junk. This amplifier is simply my most recent find and possibly my most valuable one to date.

So why am I excited about this specific piece of equipment?

  1. It’s a MASSIVE AMPLIFIER! It can boost an RF signal 15 to 17 times with an output of several kW!
  2. It can be easily modified to work on Ham Radio Frequencies! (I’m a Ham!)
  3. It’s worth A LOT in parts alone! (similar Ham Amps are several $1000’s)
  4. Nearly everything to make it run is present and intact

Now for the nitty gritty…

This Amp consists of 4 main components all housed in a convienient rack with wheels.

  1. The power supply (High voltage system, tube filament transformers, etc…)
  2. Control system (relays, metering circuitry, etc…)
  3. RF exciter (100w Sine Wave Generator @ 13? Mhz ISM)
  4. Main RF Deck

The RF Deck is the most important component. This is the heart of the amplifier. It uses a 3cx3000a7 Triode Vacuum Tube with a rated plate dissipation of 4000 watts. The Amp is in a common zero-bias grounded grid configuration with a tuned input network and a Pi-L output network. If the 3cx3000a7 tube is good, it alone is worth almost $1k.

Here is a hand drawn schematic of the Main RF Deck…because I’m awesome

20140801_174226

 

Getting this Amp up and running is now one of my primary projects.

The Goal: Turn this Amplifier into a legal limit (will easily go above 🙂 Ham Radio amplifier for the HF bands.

Here are some more pictures until I finish writing this post: