High Voltage Programmer

I’ve been experimenting with using non-standard pins on an ATtiny85. Using XTAL1 and XTAL2 is simple if you run off the internal oscillator at 8MHz/3.3v but I want to also use the RESET pin as D5/A0.

When you re-purpose the RESET pin, you lose the ability to program using an ISP, so we have to use a high voltage serial programmer (HVSP) which basically applies 12v to the RESET pin to set the fuses.

I redesigned the stripboard from here for use with perfboard and used a 2N2222 transistor as I have plenty of them, as well as a DC barrel jack for the 12v supply.

The hv_rescue.ino sketch which you load onto an Uno is as follows, I modified it slightly to compile nicely without needing the IDE:

// AVR High-voltage Serial Programmer
// Originally created by Paul Willoughby 03/20/2010
// http://www.rickety.us/2010/03/arduino-avr-high-voltage-serial-programmer/
// Inspired by Jeff Keyzer http://mightyohm.com
// Serial Programming routines from ATtiny25/45/85 datasheet

// Desired fuse configuration
#define  HFUSE  0xDF   // Defaults for ATtiny25/45/85
#define  LFUSE  0x62

//#define  HFUSE  0x5F   // reset as gpio5
//#define  LFUSE  0xE2   // 8mhz internal oscillator

// For Attiny13 use
// #define HFUSE 0xFF
// #define LFUSE 0x6A  

#define  RST     13    // Output to level shifter for !RESET from transistor to Pin 1
#define  CLKOUT  12    // Connect to Serial Clock Input (SCI) Pin 2
#define  DATAIN  11    // Connect to Serial Data Output (SDO) Pin 7
#define  INSTOUT 10    // Connect to Serial Instruction Input (SII) Pin 6
#define  DATAOUT  9    // Connect to Serial Data Input (SDI) Pin 5 
#define  VCC      8    // Connect to VCC Pin 8

int inByte = 0;         // incoming serial byte Computer
int inData = 0;         // incoming serial byte AVR

void establishContact() {
  while (Serial.available() <= 0) {
    Serial.println("Enter a character to continue");   // send an initial string
    delay(1000);
  }
}

int shiftOut2(uint8_t dataPin, uint8_t dataPin1, uint8_t clockPin, uint8_t bitOrder, byte val, byte val1)
{
	int i;
        int inBits = 0;
        //Wait until DATAIN goes high
        while (!digitalRead(DATAIN));
        
        //Start bit
        digitalWrite(DATAOUT, LOW);
        digitalWrite(INSTOUT, LOW);
        digitalWrite(clockPin, HIGH);
  	digitalWrite(clockPin, LOW);
        
	for (i = 0; i < 8; i++)  {
                
		if (bitOrder == LSBFIRST) {
			digitalWrite(dataPin, !!(val & (1 << i)));
                        digitalWrite(dataPin1, !!(val1 & (1 << i)));
                }
		else {
			digitalWrite(dataPin, !!(val & (1 << (7 - i))));
                        digitalWrite(dataPin1, !!(val1 & (1 << (7 - i))));
                }
                inBits <<=1;
                inBits |= digitalRead(DATAIN);
                digitalWrite(clockPin, HIGH);
		digitalWrite(clockPin, LOW);
                
	}

        
        //End bits
        digitalWrite(DATAOUT, LOW);
        digitalWrite(INSTOUT, LOW);
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
        digitalWrite(clockPin, HIGH);
        digitalWrite(clockPin, LOW);
        
        return inBits;
}

void readFuses(){
     //Read lfuse
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x04, 0x4C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x68);
    inData = shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6C);
    Serial.print("lfuse reads as ");
    Serial.println(inData, HEX);
    
    //Read hfuse
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x04, 0x4C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x7A);
    inData = shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x7E);
    Serial.print("hfuse reads as ");
    Serial.println(inData, HEX);
    
    //Read efuse
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x04, 0x4C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6A);
    inData = shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6E);
    Serial.print("efuse reads as ");
    Serial.println(inData, HEX);
    Serial.println(); 
}

void setup()
{
  // Set up control lines for HV parallel programming
  pinMode(VCC, OUTPUT);
  pinMode(RST, OUTPUT);
  pinMode(DATAOUT, OUTPUT);
  pinMode(INSTOUT, OUTPUT);
  pinMode(CLKOUT, OUTPUT);
  pinMode(DATAIN, OUTPUT);  // configured as input when in programming mode
  
  // Initialize output pins as needed
  digitalWrite(RST, HIGH);  // Level shifter is inverting, this shuts off 12V
  
  // start serial port at 9600 bps:
  Serial.begin(9600);
  
  establishContact();  // send a byte to establish contact until receiver responds 
  
}

void loop()
{
  // if we get a valid byte, run:
  if (Serial.available() > 0) {
    // get incoming byte:
    inByte = Serial.read();
    Serial.println(byte(inByte));
    Serial.println("Entering programming Mode\n");

    // Initialize pins to enter programming mode
    pinMode(DATAIN, OUTPUT);  //Temporary
    digitalWrite(DATAOUT, LOW);
    digitalWrite(INSTOUT, LOW);
    digitalWrite(DATAIN, LOW);
    digitalWrite(RST, HIGH);  // Level shifter is inverting, this shuts off 12V
    
    // Enter High-voltage Serial programming mode
    digitalWrite(VCC, HIGH);  // Apply VCC to start programming process
    delayMicroseconds(20);
    digitalWrite(RST, LOW);   //Turn on 12v
    delayMicroseconds(10);
    pinMode(DATAIN, INPUT);   //Release DATAIN
    delayMicroseconds(300);
    
    //Programming mode
    
    readFuses();
    
    //Write hfuse
    Serial.println("Writing hfuse");
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x40, 0x4C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, HFUSE, 0x2C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x74);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x7C);
    
    //Write lfuse
    Serial.println("Writing lfuse\n");
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x40, 0x4C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, LFUSE, 0x2C);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x64);
    shiftOut2(DATAOUT, INSTOUT, CLKOUT, MSBFIRST, 0x00, 0x6C);

    readFuses();    
    
    Serial.println("Exiting programming Mode\n");
    digitalWrite(CLKOUT, LOW);
    digitalWrite(VCC, LOW);
    digitalWrite(RST, HIGH);   //Turn off 12v
  }
}

I then uploaded the following sketch to the ATtiny85 using make ispload, as you can see its using D5 to blink an LED:

const int led = 5;

void setup()
{
    pinMode(led, OUTPUT);
}

void loop()
{
    digitalWrite(led, HIGH);
    delay(1000);
    digitalWrite(led, LOW);
    delay(1000);
}

The Makefile is as follows, I used the latest IDE and the ATTinyCore as it supports more chips and I wanted to experiment with something other than the usual arduino-tiny or attiny-master with IDE 1.0.5:

ARDUINO_DIR = $(HOME)/arduino-1.8.5

ISP_PROG       = usbasp
AVRDUDE_OPTS   = -v
ALTERNATE_CORE = ATTinyCore
BOARD_TAG      = attinyx5
BOARD_SUB      = 85
F_CPU          = 8000000L
ISP_HIGH_FUSE  = 0x5F
ISP_LOW_FUSE   = 0xE2

include $(HOME)/Arduino-Makefile/Arduino.mk

The important bit is to then edit the hv_rescue.ino file to set the fuses:

#define  HFUSE  0x5F   // reset as gpio5
#define  LFUSE  0xE2   // 8mhz internal oscillator

Then upload that to the Uno, connect the HVSP board with the ATtiny85 mounted, and press a key to set the fuses. If you don’t edit the script it’ll just reset the fuses to their defaults of 0xDF and 0x62.

The order is important, as you can’t ISP the chip after you’ve set the fuse to disable RESET, so you can’t use make set_fuses for example, you have to use the HVSP to set the fuses, after ISP’ing the sketch. If you want to edit the sketch, you’ll have to reset the fuses, then ISP the sketch and set the fuses back again, so its not something you want to do often – maybe develop using a regular GPIO pin like D2, then when you’re ready switch to D5.

I made a PR to fix that – so you make ispload to upload the sketch, then make set_fuses to set the fuses, after which you need to reset them using the HVSP.

You can then blink six LED’s using an ATTiny85 (the RESET pin has lower power output) with just VCC and GND connected (no crystal etc.) using for example:

#include <avr/io.h>

void setup()
{
    // set d0-d5 as outputs
    DDRB = B11111111;
}

void loop()
{
    // set all high
    PORTB = B11111111;
    delay(500);

    // invert all
    PORTB ^= PORTB;
    delay(500);
}

There are other more complicated sketches around which can auto-detect the chip and prompt for the fuses to set, but they seem to require a button and a 12v DC-DC boost converter, as per the Rescue Shield

ArduinoOTA Solution for ESP8266

I’ve wasted about three days trying to get OTA upgrades to work on my ESP8266 boards. They take 1-2 OTA’s and then don’t even boot into the sketch. Tried my Gizwits WiFi Witty and ESP12F on a breakout board with an LDO as described in my earlier post. Then I tried my old NodeMCUv2 board and it worked fine, all of the time.

Turns out we need moar powah!

The HT7833 LDO on the white breakout boards from the earlier post, are supposed to be able to put out 500mA so I guess its not a current problem but a voltage one, when measuring what gets through to the ESP its just over 3.3v. I bypassed the LDO and fed a 3.7v LiPo to VCC and it works fine now!

Whilst playing around trying to get it to work I added the 4x 10k resistors and a 100nF capacitor across the ESP’s VCC/GND pins as well as a 470uF capacitor across the power rails feeding the LDO, as per here. Didn’t seem to make any difference.

On a related note, the pinout of those white boards is insane. Turns out the VCC pin on the white board is where you should feed the supply into the LDO. It’s connected to VIN (middle pin). But bizarrely VCC on the ESP is not fed from there, its fed from a via from the LDO’s VOUT (right pin) and it seems CH_PD is fed from that via the leftmost 10k resistor on the breakout. So don’t power other components or connect other ESP pins to VCC as it’ll be running 8v or whatever you’re inputting!

I removed the LDO and had to replace the middle 000 resistor (solder blob) so that the breakout VCC goes to the ESP VCC.

So essentially if you want to use the LDO (I’d advise against it) you have to remove the 000 resistor and feed the LDO up to 8v via the breakout’s VCC pin, then run a jumper wire from VOUT on the LDO to the ESP’s VCC pin. I used the leftmost via as seen from the top, near VCC/GPIO13 pins rather than running a wire over and under the breakout.

If you don’t want to use the LDO, leave the breakout as it was and feed 3.7v into the VCC pin.

Update: I found that you can power the breakout from an external ~3.5v power source (with short wires) as long as you add:

  • 470uF capacitor on power rail
  • 0.1uF capacitor on power rail near to the VCC/GND pins
  • 10k pullup on RST pin
  • 10k pullup on GPIO0
  • 10k pullup on GPIO2
  • No pulldown on GPIO15 it already has one
  • No pullup on CH_PD it already has one

You can just about manage OTA’s this way but more than one sensor or LED and the thing falls over again! Nodemcu’s just seem much more robust.

I’ve received my free Amazon Echo – wow I need to get another when they’re £80 again! They are amazing! I’ve been using the fauxmoESP library to control the LED on an ESP8266, sooooo easy.

Here’s my sketch, including OTA code. Note to generate an MD5SUM to use as your OTA password, you need to call echo -n "otapassphrase" | md5sum to ensure you don’t get a newline! Then pass espota.py the plaintext string.

#include <ESP8266WiFi.h>
#include <ESP8266mDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include "fauxmoESP.h"

// init vars
const int RED = 15;
const int GREEN = 12;
const int BLUE = 13;

// constructor
fauxmoESP fauxmo;

void callback(uint8_t device_id, const char * device_name, bool state)
{
    Serial.print("Device ");
    Serial.print(device_name);
    Serial.print(" state: ");
    if (state)
    {
        Serial.println("ON");
        analogWrite(RED, random(0,1023));
        analogWrite(GREEN, random(0,1023));
        analogWrite(BLUE, random(0,1023));
    }
    else
    {
        Serial.println("OFF");
        analogWrite(RED, 0);
        analogWrite(GREEN, 0);
        analogWrite(BLUE, 0);
    }
}

void setup()
{
    // configure led
    pinMode(RED, OUTPUT);
    pinMode(GREEN, OUTPUT);
    pinMode(BLUE, OUTPUT);
    analogWrite(RED, 512);
    analogWrite(GREEN, 0);
    analogWrite(BLUE, 0);

    // debug
    Serial.begin(115200);
    Serial.setDebugOutput(false);
    Serial.println("After connection, ask Alexa to 'turn pixel on' or 'off'");

    // wifi
    WiFi.mode(WIFI_STA);
    WiFi.begin("myssid", "mypassword");
    WiFi.config(IPAddress(192, 168, 1, 2), IPAddress(192, 168, 1, 1), 
         IPAddress(255, 255, 255, 0), IPAddress(8,8,8,8));

    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.print(".");
    }

    // ota
    ArduinoOTA.setPasswordHash("b7e82cbfc5bf5f1a44d8e5e526e2f1fe");
    ArduinoOTA.begin();

    // fauxmo
    fauxmo.addDevice("pixel");
    fauxmo.onMessage(callback);
}

void loop()
{
    fauxmo.handle();
    ArduinoOTA.handle();
}

I’ve improved my ESP Makefile to handle OTA and debugging etc (mind the word-wrapping on the fqbn line)

ARDUINO_PATH = $(HOME)/arduino-1.8.3
SKETCHBOOK   = $(HOME)/arduino16
ESPTOOL		 = $(SKETCHBOOK)/hardware/esp8266com/esp8266/tools/esptool/esptool
ESPOTA		 = $(SKETCHBOOK)/hardware/esp8266com/esp8266/tools/espota.py
SKETCH 		 = $(notdir $(CURDIR)).ino
TARGET_DIR   = $(CURDIR)/build-esp8266
MONITOR_PORT = /dev/ttyUSB0
OTA_IP		 = 192.168.1.2
OTA_PASSWD	 = otapassphrase
#DEBUG		 = ,Debug=Serial,DebugLevel=all_____
DEBUG		 = 

all:
	@ mkdir -p $(TARGET_DIR)

	$(ARDUINO_PATH)/arduino-builder -compile -logger=machine \
	-hardware "$(ARDUINO_PATH)/hardware" \
	-hardware "$(SKETCHBOOK)/hardware" \
	-tools "$(ARDUINO_PATH)/tools-builder" \
	-tools "$(ARDUINO_PATH)/hardware/tools/avr" \
	-built-in-libraries "$(ARDUINO_PATH)/libraries" \
	-libraries "$(SKETCHBOOK)/libraries" \
	-fqbn=esp8266com:esp8266:generic:CpuFrequency=80,CrystalFreq=26,FlashFreq=40,
        FlashMode=dio,UploadSpeed=115200,FlashSize=4M3M,ResetMethod=nodemcu$(DEBUG) \
	-ide-version=10803 \
	-build-path "$(TARGET_DIR)" \
	-warnings=none \
	-prefs=build.warn_data_percentage=75 \
	-verbose "$(SKETCH)"

flash:
	$(ESPTOOL) -v -cd nodemcu -cb 115200 -cp $(MONITOR_PORT) -ca 0x00000 -cf $(TARGET_DIR)/$(SKETCH).bin

ota:
	$(ESPOTA) -i $(OTA_IP) -I 192.168.1.3 -a $(OTA_PASSWD) -f $(TARGET_DIR)/$(SKETCH).bin

clean:
	rm -rf $(TARGET_DIR)

monitor:
	screen $(MONITOR_PORT) 115200

Once you’ve discovered your device using the Alexa app (or go to echo.amazon.com then “Smart Home” > Devices > Discover) you just say “Alexa, turn pixel on”.

Big Update

Its been quite a while since my last post, so here’s a big one…..

I’ve been doing a lot of 3D printing since I received my FlashForge Creator Pro 2016. I learned OpenSCAD and FreeCAD, my designs are here. I’ve made various enclosures for Arduino’s and Raspberry Pi’s, as well as USBASP cases, voltmeter cases, solder tip holders, calliper battery savers, resistor forming tools, soldering helping hands, bottle openers, trolley tokens, SD card holders, PIR cases, Dremel accessories, various 3D printer upgrades and the usual calibration prints.

Yesterday I upgraded Dad’s Lenovo B570 laptop to Ubuntu 16.04 from 12.04, which was fun due to its broken EFI setup – basically it has a BIOS and not SecureBoot but the installer sees it as using GPT+EFI instead of a simple MBR. Weirdness such as failure to boot from USB and then failure to boot from HDD once I’d installed from DVD ensued. Boot-Repair fixed the problem, from what I can see it installed some dummy EFI files in the EFI partition, which the 12.04 install obviously nuked. Had to disable hardware acceleration in Chrome to prevent Flash flickering, but otherwise it seems to be working fine.

Today I modified an SG90 servo for continuous rotation, basically removed the wiper on the potentiometer and replaced it with a couple of SMD resistors, and cut the tab off of the largest gear. I found that there is no trimpot to adjust the zero position on my servo so I altered my code to send 100 instead of 90 for “stop”, which I found using some trial and error. Values lower than 100 move clockwise (the lower the number, the faster movement) and greater than 100 moves anti-clockwise (the higher, the faster).

When you write a value to the servo, you’re no longer giving it a degree value, you’re setting a pusle width. So instead of “rotate to 40 degrees” you’re saying “run for 1200uS”, just like a DC motor with H-bridge, which is essentially what is inside a servo. The upside is that the servo can rotate 360 degrees, the downside is that you don’t know where its rotated to as the microcontroller receives no feedback.

My test code is below – its moves it left, right, fast, slow and stop.

continuous_servo.ino

#include <Servo.h>

// create servo object
Servo myservo;

// speeds for my sg90
#define FASTCW  45
#define SLOWCW  90
#define STOP    100
#define FASTACW 135
#define SLOWACW 110

void setup()
{
    // attach servo to D9
    myservo.attach(9);
}

void loop()
{
    myservo.write(FASTCW);
    delay(2000);

    myservo.write(STOP);
    delay(1000);

    myservo.write(FASTACW);
    delay(2000);

    myservo.write(STOP);
    delay(1000);

    myservo.write(SLOWCW);
    delay(2000);

    myservo.write(STOP);
    delay(1000);

    myservo.write(SLOWACW);
    delay(2000);
    
    myservo.write(STOP);
    delay(1000);
}

Makefile

BOARD_TAG = uno
MONITOR_PORT = /dev/ttyUSB0
include /usr/share/arduino/Arduino.mk

I’ve also been making custom power cables lately, such as a USB to DC jack for giving 5v to older projects such as a 555 astable from a powerbank. It seems that microUSB should be the new go-to connector for power instead of 2.1×5.5mm DC jacks these days. I’ve actually got some USB-to-DIP breakout boards as well as the TP4056 Li-On charger boards. I’ve also got some USB boost regulators on the way that apparently can output up to 28v or something silly. My next custom cable will likely be banana plugs to pin headers as its a pain trying to put a male pin header on a screw terminal.

Finally I had a play with a MCP23017 port expander which I was thinking of using when my ESP8266 projects don’t have enough pins – like my RTC clock could have done with 1-2 more buttons. But it seems like a lot of wiring e.g. 8 pins before you’ve even added any I/O, and a lot of calls to the Wire library e.g. 4 calls just to send an output like lighting an LED, so I think I’ll hold out for ESP32’s to be generally available before doing any more pin-intensive projects.

Auto-BST for LED Clock

I’ve finally got around to adding British Summer Time detection to my LED matrix clock, I’ve also reduced flash wear and connection time by using the new persistent() method from the WiFi library. I’m still saving the UTC time to the RTC module, just displaying +/- the hour. The LiFePO4 battery is still putting out over 3.1v after 2 months.

The final code is below:

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "LedControl.h"
#include <Wire.h>
#include <RTClib.h>

// ntp server pool
IPAddress timeServerIP;

// ntp time stamp is in the first 48 bytes of the message
const int NTP_PACKET_SIZE = 48;

// buffer to hold incoming and outgoing packets
byte packetBuffer[NTP_PACKET_SIZE];

// create a udp instance
WiFiUDP udp;

// data, clock, cs, numdevices
LedControl lc = LedControl(D7,D5,D6,4);

// ds3231 constuctor
RTC_DS3231 RTC;

const int num[10][8] = {
    {0x00,0x78,0xcc,0xec,0xfc,0xdc,0xcc,0x78}, // zero
    {0x00,0xfc,0x30,0x30,0x30,0x30,0xf0,0x30}, // one
    {0x00,0xfc,0xcc,0x60,0x38,0x0c,0xcc,0x78}, // two
    {0x00,0x78,0xcc,0x0c,0x38,0x0c,0xcc,0x78}, // three
    {0x00,0x0c,0x0c,0xfe,0xcc,0x6c,0x3c,0x1c}, // four
    {0x00,0x78,0xcc,0x0c,0x0c,0xf8,0xc0,0xfc}, // five
    {0x00,0x78,0xcc,0xcc,0xf8,0xc0,0x60,0x38}, // six
    {0x00,0x60,0x60,0x30,0x18,0x0c,0xcc,0xfc}, // seven
    {0x00,0x78,0xcc,0xcc,0x78,0xcc,0xcc,0x78}, // eight
    {0x00,0x70,0x18,0x0c,0x7c,0xcc,0xcc,0x78}  // nine
};

void drawNum(int number, int display)
{
    lc.setColumn(display, 0, num[number][0]);
    lc.setColumn(display, 1, num[number][1]);
    lc.setColumn(display, 2, num[number][2]);
    lc.setColumn(display, 3, num[number][3]);
    lc.setColumn(display, 4, num[number][4]);
    lc.setColumn(display, 5, num[number][5]);
    lc.setColumn(display, 6, num[number][6]);
    lc.setColumn(display, 7, num[number][7]);
}

bool isBST(int year, int month, int day, int hour)
{
    // bst begins at 01:00 gmt on the last sunday of march
    // and ends at 01:00 gmt (02:00 bst) on the last sunday of october

    // january, february, and november are out
    if (month < 3 || month > 10) { return false; }

    // april to september are in
    if (month > 3 && month < 10) { return true; }

    // last sunday of march
    int lastMarSunday =  (31 - (5* year /4 + 4) % 7);

    // last sunday of october
    int lastOctSunday = (31 - (5 * year /4 + 1) % 7);

    // in march we are bst if its past 1am gmt on the last sunday in the month
    if (month == 3)
    {
        if (day > lastMarSunday)
        {
            return true;
        }

        if (day < lastMarSunday)
        {
            return false;
        }

        if (hour < 1)
        {
            return false;
        }

        return true;
    }

    // in october we must be before 1am gmt (2am bst) on the last sunday to be bst
    if (month == 10)
    {
        if (day < lastOctSunday)
        {
            return true;
        }

        if (day > lastOctSunday)
        {
            return false;
        }

        if (hour >= 1)
        {
            return false;
        }

        return true;
    }
}

// send an ntp request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
    // set all bytes in the buffer to 0
    memset(packetBuffer, 0, NTP_PACKET_SIZE);

    packetBuffer[0] = 0b11100011;   // li, version, mode
    packetBuffer[1] = 0;            // stratum, or type of clock
    packetBuffer[2] = 6;            // polling interval
    packetBuffer[3] = 0xEC;         // peer clock precision

    // 8 bytes of zero for root delay & root dispersion
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;

    // all ntp fields have been given values, send request
    udp.beginPacket(address, 123);
    udp.write(packetBuffer, NTP_PACKET_SIZE);
    udp.endPacket();
}

void displayDate(unsigned long unixtime)
{
    // power up led matrices
    for (int i=0; i<4; i++)
    {
        lc.shutdown(i,false); // come out of powersaving
        lc.setIntensity(i,5); // set brightness 0-15
        lc.clearDisplay(i);   // clear display
    }

    // handle british summer time
    DateTime nowntp = unixtime;
    int myhour;
    if (isBST(nowntp.year(), nowntp.month(), nowntp.day(), nowntp.hour()))
    {
        myhour = ((unixtime % 86400L) / 3600) + 1; // bst
    }
    else
    {
        myhour = (unixtime % 86400L) / 3600; // utc
    }

    // print hour to led
    if (myhour == 24)
    {
        drawNum(0,0);
        drawNum(0,1);
    }
    else
    {
        drawNum(myhour/10,0);
        drawNum(myhour%10,1);
    }

    // print minute to led
    int myminute = (unixtime % 3600) / 60;
    if (myminute < 10)
    {
        drawNum(0,2);
    }
    else
    {
        drawNum(myminute/10,2);
    }
    drawNum(myminute%10,3);
}

void gotoSleep()
{
    for (int i=0; i<4; i++)
    {
        lc.clearDisplay(i);
        lc.setIntensity(i,0);
        lc.shutdown(i,true);
    }

    // latch cs pin and pause to work around display0 being stuck high on sleep problem
    digitalWrite(D6, LOW);
    delay(2000);

    // sleep mcu forever
    ESP.deepSleep(0);
}

void setup()
{
    // setup ds3231
    Wire.begin();
    RTC.begin();

    // display rtc time on led matrices
    DateTime now = RTC.now();
    displayDate(now.unixtime());

    // connect to wifi network
    if (WiFi.SSID() != "myssid") {
        WiFi.begin("myssid", "mypassword");
        WiFi.persistent(true);
        WiFi.setAutoConnect(true);
        WiFi.setAutoReconnect(true);
    }

    // static ip, gateway, netmask
    WiFi.config(IPAddress(192, 168, 1, 2), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));

    // connect - could drain battery if never connects to wifi
    int wifitries = 0;
    while (WiFi.status() != WL_CONNECTED)
    {
        // give it a moment
        delay(1000);

        // only try 5 times before sleeping
        wifitries++;
        if (wifitries >5)
        {
            gotoSleep();
        }
    }

    udp.begin(2390);

    // get a random server from the pool
    WiFi.hostByName("europe.pool.ntp.org", timeServerIP);

    int cb = 0;
    int ntptries = 0;
    while (!cb)
    {
        // send an ntp packet to a time server
        sendNTPpacket(timeServerIP);

        // wait to see if a reply is available
        delay(3000);
        cb = udp.parsePacket();

        // only try 5 times before sleeping
        ntptries++;
        if (ntptries >5)
        {
            gotoSleep();
        }
    }

    // we've received a packet, read the data into the buffer
    udp.read(packetBuffer, NTP_PACKET_SIZE);

    // the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. first, extract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);

    // combine the four bytes (two words) into a long integer
    // this is ntp time (seconds since jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;

    // unix time starts on jan 1 1970. in seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;

    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;

    // update ds3231 from ntp unixtime
    RTC.adjust(DateTime(epoch));

    // display ntp time on led matrices
    displayDate(epoch);

    // ntp has a dot rtc does not
    lc.setLed(3, 7, 0, true);

    // wait five seconds before shutting down
    delay(5000);
    gotoSleep();
}

void loop()
{
}

LED Matrix Alarm Clock Update

I’ve been working some more on my 8×8 LED matrix clock.

I’ve added a DS1307 RTC chip, which has to run from the 5v pin on the NodeMCU and use 10k pullup resistors between 3.3v and the SCL/SDA pins on the NodeMCU and DS1307. I’m using the Adafruit fork of RTCLib as it has better ESP8266 support and uses unixtime. I’m using this chip as I’ve got a load of them and already have a veroboard setup with the coincell and crystal.

I’ve also added a button, which when pressed reads the time from the RTC and displays it, then it fetches the NTP time over the internet and updates the display (and RTC). Also a single pixel is lit when the display is showing NTP time, so I can differentiate between RTC and NTP. I’ve done it this way as sometimes it can take up to 10secs to fetch the NTP time, and I really wanted something to display instantly. The battery-backed RTC means it will still show accurate time if there is no internet access. The display sleeps after 5secs.

I’m not sure if I’m going to replace the NodeMCU with just a bare ESP-12E on some breadboard fed by a LiFePO4 battery, or stick with the whole NodeMCU powered from a USB mains adaptor. The 5v supply is quite handy, and means I don’t need to add a step-up just for the DS1307.

Update: I’ve ordered some DS3231 RTC modules which run from 3.3v, although I must remember to cut the trace or remove the diode/resistor trickle charge circuit as I’ll be using them with non-rechargeable CR2032 batteries. So I can remove the pullup resistors and run the whole clock from 3.3v

I’ve also moved the button to the RST pin and implemented an infinite deepSleep(0); so that the MCU and display sleeps until you reset the MCU. I’ve yet to measure the current consumption.

For the 8×8 LED matrices I’m using my fork of the LedControl library. I’m considering soldering the pin headers a bit differently than they currently are – maybe facing inwards on the underside, rather than sticking out of the top and bottom.

The pinout for the NodeMCU is a bit weird when using Arduino instead of NodeLua, here is the pin mapping, my setup is:

DIN : D7 (GPIO-13)
CLK : D5 (GPIO-14)
CS  : D3 (GPIO-0)
VCC : 3V3
GND : GND

SCL : D1 (GPIO-5), pullup resistor goes to 3.3v
SDA : D2 (GPIO-4), pullup resistor goes to 3.3v
5v  : VIN

Button : RST, other side goes to GND

Here’s a video of it in action before I added the RTC – notice it takes a couple of seconds for the NTP request after the button is pressed:

The code is:

#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include "LedControl.h"
#include <Wire.h>
#include <RTClib.h>

// local port to listen for udp packets
unsigned int localPort = 2390;

// ntp server pool
IPAddress timeServerIP;
const char* ntpServerName = "europe.pool.ntp.org";

// ntp time stamp is in the first 48 bytes of the message
const int NTP_PACKET_SIZE = 48;

// buffer to hold incoming and outgoing packets
byte packetBuffer[NTP_PACKET_SIZE];

// create a udp instance
WiFiUDP udp;

// data, clock, cs, numdevices
LedControl lc = LedControl(D7,D5,D3,4);

// ds1307 constuctor
RTC_DS1307 RTC;

const int num[10][8] = {
    {0x00,0x78,0xcc,0xec,0xfc,0xdc,0xcc,0x78}, // zero
    {0x00,0xfc,0x30,0x30,0x30,0x30,0xf0,0x30}, // one
    {0x00,0xfc,0xcc,0x60,0x38,0x0c,0xcc,0x78}, // two
    {0x00,0x78,0xcc,0x0c,0x38,0x0c,0xcc,0x78}, // three
    {0x00,0x0c,0x0c,0xfe,0xcc,0x6c,0x3c,0x1c}, // four
    {0x00,0x78,0xcc,0x0c,0x0c,0xf8,0xc0,0xfc}, // five
    {0x00,0x78,0xcc,0xcc,0xf8,0xc0,0x60,0x38}, // six
    {0x00,0x60,0x60,0x30,0x18,0x0c,0xcc,0xfc}, // seven
    {0x00,0x78,0xcc,0xcc,0x78,0xcc,0xcc,0x78}, // eight
    {0x00,0x70,0x18,0x0c,0x7c,0xcc,0xcc,0x78}  // nine
};

void drawNum(int number, int display)
{
    lc.setColumn(display, 0, num[number][0]);
    lc.setColumn(display, 1, num[number][1]);
    lc.setColumn(display, 2, num[number][2]);
    lc.setColumn(display, 3, num[number][3]);
    lc.setColumn(display, 4, num[number][4]);
    lc.setColumn(display, 5, num[number][5]);
    lc.setColumn(display, 6, num[number][6]);
    lc.setColumn(display, 7, num[number][7]);
}

// send an ntp request to the time server at the given address
unsigned long sendNTPpacket(IPAddress& address)
{
    Serial.println("Sending NTP packet...");

    // set all bytes in the buffer to 0
    memset(packetBuffer, 0, NTP_PACKET_SIZE);

    packetBuffer[0] = 0b11100011;   // li, version, mode
    packetBuffer[1] = 0;            // stratum, or type of clock
    packetBuffer[2] = 6;            // polling interval
    packetBuffer[3] = 0xEC;         // peer clock precision

    // 8 bytes of zero for root delay & root dispersion
    packetBuffer[12] = 49;
    packetBuffer[13] = 0x4E;
    packetBuffer[14] = 49;
    packetBuffer[15] = 52;

    // all ntp fields have been given values, send request
    udp.beginPacket(address, 123);
    udp.write(packetBuffer, NTP_PACKET_SIZE);
    udp.endPacket();
}

void displayDate(unsigned long unixtime)
{
    // power up led matrices
    for (int i=0; i<4; i++)
    {
        lc.shutdown(i,false); // come out of powersaving
        lc.setIntensity(i,8); // set brightness 0-15
        lc.clearDisplay(i);   // clear display
    }

    // print hour to led
    int myhour = (unixtime % 86400L) / 3600;
    drawNum(myhour/10,0);
    drawNum(myhour%10,1);

    // print minute to led
    int myminute = (unixtime % 3600) / 60;
    if (myminute < 10)
    {
        drawNum(0,2);
    }
    else
    {
        drawNum(myminute/10,2);
    }
    drawNum(myminute%10,3);
}

void gotoSleep()
{
    for (int i=0; i<4; i++)
    {
        lc.clearDisplay(i);
        lc.setIntensity(i,0);
        lc.shutdown(i,true);
    }

    // latch cs pin and pause to work around display0 being stuck high on sleep problem
    digitalWrite(D3, LOW);
    delay(2000);

    // sleep mcu forever
    ESP.deepSleep(0);
}

void setup()
{
    // setup ds1307
    Wire.begin();
    RTC.begin();

    // display rtc time on led matrices
    DateTime now = RTC.now();
    displayDate(now.unixtime());

    // debug
    Serial.begin(9600);

    // connect to wifi network
    WiFi.begin("ssid", "password");

    // static ip, gateway, netmask
    WiFi.config(IPAddress(192, 168, 1, 2), IPAddress(192, 168, 1, 1), IPAddress(255, 255, 255, 0));

    // connect - could drain battery if never connects to wifi
    int wifitries = 0;
    while (WiFi.status() != WL_CONNECTED)
    {
        // give it a moment
        delay(1000);

        // only try 5 times before sleeping
        wifitries++;
        Serial.print("Wifi connect tries: ");
        Serial.println(wifitries);
        if (wifitries >5)
        {
            Serial.println("Gave up on wifi");
            gotoSleep();
        }
    }

    Serial.println("Starting UDP");
    udp.begin(localPort);
    Serial.print("Local port: ");
    Serial.println(udp.localPort());

    // get a random server from the pool
    WiFi.hostByName(ntpServerName, timeServerIP);

    int cb = 0;
    int ntptries = 0;
    while (!cb)
    {
        // send an ntp packet to a time server
        sendNTPpacket(timeServerIP);

        // wait to see if a reply is available
        delay(3000);
        cb = udp.parsePacket();

        // only try 5 times before sleeping
        ntptries++;
        Serial.print("NTP connect tries: ");
        Serial.println(ntptries);
        if (ntptries >5)
        {
            Serial.println("Gave up on NTP");
            gotoSleep();
        }
    }

    // we've received a packet, read the data from it
    Serial.print("Packet received, length=");
    Serial.println(cb);

    // read the packet into the buffer
    udp.read(packetBuffer, NTP_PACKET_SIZE);

    // the timestamp starts at byte 40 of the received packet and is four bytes,
    // or two words, long. first, esxtract the two words:
    unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
    unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);

    // combine the four bytes (two words) into a long integer
    // this is ntp time (seconds since jan 1 1900):
    unsigned long secsSince1900 = highWord << 16 | lowWord;
    Serial.print("Seconds since Jan 1 1900 = ");
    Serial.println(secsSince1900);

    // now convert ntp time into everyday time:
    Serial.print("Unix time = ");

    // unix time starts on jan 1 1970. in seconds, that's 2208988800:
    const unsigned long seventyYears = 2208988800UL;

    // subtract seventy years:
    unsigned long epoch = secsSince1900 - seventyYears;

    // print unix time:
    Serial.println(epoch);

    // update ds1307 from ntp unixtime
    RTC.adjust(DateTime(epoch));
    Serial.println("Updated RTC");

    // utc is the time at gmt
    Serial.print("The UTC time is ");

    // print the hour - 86400 equals secs per day
    Serial.print((epoch % 86400L) / 3600);
    Serial.print(':');

    // in the first 10 minutes of each hour, we'll want a leading '0'
    if (((epoch % 3600) / 60) < 10)
    {
        Serial.print('0');
    }

    // print the minute (3600 equals secs per minute)
    Serial.print((epoch % 3600) / 60);
    Serial.print(':');

    // in the first 10 seconds of each minute, we'll want a leading '0'
    if ((epoch % 60) < 10)
    {
        Serial.print('0');
    }

    // print the second
    Serial.println(epoch % 60);
    Serial.println("");

    // display ntp time on led matrices
    displayDate(epoch);

    // ntp has a dot rtc does not
    lc.setLed(3, 7, 0, true);

    // wait five seconds before shutting down
    delay(5000);
    gotoSleep();
}

void loop()
{
}

Edit: finished video.