Thursday, 5 July 2018

A Long Goodbye for Slackware


I first heard of Linux in the winter of 1995. I was writing a device driver for Microsoft's Windows NT intending to knock it into shape for industrial use. And complaining about the poor quality of the Windows code. Like all the time. I think just to shut me up, my friend (and boss) Wiljan Derks told me about this Finnish student who was writing a UNIX operating system all on his own. He called it Linux, just because his name was Linus Torvalds.

Linus Torvalds 1995 Amsterdam

I escaped on home leave back to balmy Malaysia in February 1996, and there in the sleepy backwater of Ipoh was this book on Linux, 'Slackware Unleashed'. At the back was glued a CDROM with Slackware release 1.1. It was an omen: I bought the only copy immediately.

And it has been Slackware for 22 years From the first install using 12 floppy disks (cdrom drives were hard to find then) on a suitcase-sized Zeos 80486DX2 souped up to a dizzying 66MHz 12MB DRAM to a tiny unassuming Raspberry Pi Zero W blazing along at 1GHz and 512MB.

Patrick Volkerding, Slackware's Benevolent Dictator for Life
Incredibly, through all these years, just like Linux, Slackware is run by one person: Patrick Volkerding.

Initially I ran a number of operating systems: Windows NT, Slackware and SuSE. But by 2004 I dropped Windows, which I had used since 1984. When Novell bought SuSE I dropped that too, and it had only been Slackware since then.

Slackware has always been slow to release new versions. The emphasis has always been stability, which is perfect for me: I had always been using a mainline Linux as an embedded device, and Slackware was a lot less work.

True it required a much bigger footprint, but hardware got better all the time and since elevator projects had a development time of two years anyway, all it needed was a leap of faith: start development immediately using a desktop and bet on the embedded hardware being able to run Slackware by the time I needed it.

Happily, in 14 years Slackware and Moore's Law has not let me down. Slackware progressed from an Advantech Industrial PC to the Via EPIA to the Intel-based fanless boxes with aplomb.

Advantech IPC
Via EPIA
Quanmax Qbox: Intel-based fanless CPU

But then came the ARM-based CPUs like the Beagleboard and the Raspberry Pi. Now a mainline Linux can fit in the palm of your hand.
Beagleboard
Raspberry Pi 3

All this required a fair amount of updates. Despite its manual nature we could cope with Slackware updates, until now: browser vulnerabilities sometimes needed three or more upgrades a year. Browsers like Firefox and Chrome were huge applications and a real pain to upgrade manually, so Debian's siren call beckoned.

When the last two installs of Raspbian went without a hitch, it is time to test my main development laptops on Debian. I started with a spare laptop: an ancient Acer Aspire 5050. Debian 9 'Stretch' installed and ran with great ease. 

Debian 9 'Stretch' on an ancient Acer Aspire 5050

True the speakers did not work and the graphics were slower than my tiny Raspberry Pi Zero W's, but Firefox is up to to the minute, so I think this is the start of Slackware's long goodbye.


On second thought, it is more like au revoir, Slackware.
 

Monday, 2 July 2018

Give your old audio amplifiers a new lease of life with this RM11.90 bluetooth audio receiver


Chinese no-name Bluetooth Audio Receiver
Over the years we kept upgrading the living room audio systems: first turntable gave way to the cassette tape deck, which then changed to the CD player, then mp3 player. This resulted in a few orphaned amplifiers and speakers, especially if they have odd impedances like 4 or 6 Ohms.

These days we play most of our music from smartphones; why not convert them to bluetooth speakers? I used this RM11.90 sgrobot bluetooth audio receiver.

Note the LED, resistor and capacitor
It came with some loose parts: an LED, 100 Ohm resistor and an electrolytic capacitor. Somehow it reminded me of excess bluetooth audio receiver modules off a PCB mainboard for a bluetooth speaker or somesuch now sold off cheaply. The IC markings C7THN5004 did not come up on an Internet search; they look like custom markings. Never mind, better this than discarding it in the municipal landfill.

gameinstance.com has a good write-up on it:




I soldered the parts on it; it literally took one minute.


The pinouts are printed on the PCB, but here they are anyway:



3rd pin from top: left speaker, 4th pin: right speaker
I powered it from my trusty D-link USB3 hub, which puts out a whopping 2A at 5V.

To test, I connected it to yet another one of my orphaned PC analog speakers with integrated audio amplifier. I used my Raspberry Pi Zero W with a brand-new version of Raspbian (Debian 'stretch') and omxplayer. It worked without fuss.  It came up as WIN-668 and paired without asking for a PIN.

I had less luck with my Raspberry Pi B+. It had built-in analog and HDMI audio and omxplayer just would not work out of the box. I'm sure that is fixable, but that is another blog post.

As usual, if you can't be bothered with this DIY malarkey, you can buy it ready-made for RM16.90:
RM29: Bluetooth audio receiver. The USB connector is for power only
Once the speakers are bluetooth-enabled, especially combined with the Raspberry Pi Zero W, it becomes and Internet of Things (IoT) device. Now applications like your very own DIY Google Home becomes possible.

Not bad for something one step from the rubbish heap. Happy Trails.

Wednesday, 27 June 2018

Raspberry Pi Zero W as WiFi Repeater

No extra WiFi dongles used. The dongle shown is a wireless keyboard used for development only
The ESP-01S WiFi Repeater works well for IoT use. It can be daisy-chained maybe 5 times for a maximum range of 200m, but I found it a little slow for normal use. In particular youtube stuttered even on an old Rolling Stones video in black and white.

It would be nice to see how the Raspberry Pi Zero W fared as a WiFi repeater. At RM40 (the microUSB cable is RM2) from cytron, it is about double the price of the ESP-01S Repeater and close to the RM52 retail price for a D-Link DMG-112A.

Even so the Raspberry Pi WiFi Repeater will come out ahead if it can also be used as an IoT device, such as a WiFi autogate remote controller.

Albert Chaharbakshi: had last word on the Raspi WiFi Repeater


I could not find anything to add to Albert Chaharbakhshi's classy post.

Well, except this youtube video to prove his post really works. Even the Raspberry Pi website's version requires another WiFi interface or at least a wired Ethernet interface to work.

Youtube video of Raspi Zero W WiFi repeater feeding a laptop with a video stream
The Pi is shown connected to a monitor and keyboard but is set for "headless" operation with a static IP address. A laptop was able to play a reasonable youtube video (Porcelain Black's Naughty Naughty) at 480p with no difficulty.

There you have it, a practically commercial grade WiFi Repeater. Happy Trails.


Tuesday, 19 June 2018

ESP8266 IoT Low-cost WiFi Repeater

Left: NodeMCU EPS-12E, right: ESP-01S with CH340 adapter
The ESP8266, especially the ESP-01S board can be used as a low-cost WiFi repeater. Add a CH340 USB serial port adapter(RM6.50) for the ESP-01S (RM14.90) and it is still only RM21.40.

A WiFi Range Extender like the D-Link DMG-112A can be bought for RM45, but the ESP8266 is especially cost-effective if the WiFi repeater software can be included in your ESP8266 IoT device. Such an IoT device can be meshed with others to form a WiFi net, extending the IoT cloud's reach.

D-Link DMG-112A WiFi Range Extender is only RM45
Hasten then to Martin Ger's superlative site.

I used Expressive's esptool.py successfully with all three of my programmers, the CH340 ESP-01S adapter, my makeshift ESP-01S programmer, as well as the built-in programmer in the NodeMCU ESP-12E Lua.

I had already upgraded my Slackware 14.2's python to version 2.7.11. This made installing esptools.py easy. Just do:

# pip install esptool

Download's Martin Ger's firmware/0x00000.bin and firmware/0x10000.bin from here. I started with the NodeMCU ESP-12E:

$esptool.py --port /dev/ttyUSB0 write_flash -fs 4MB -ff 80m -fm dio 0x00000 ./0x00000.bin 0x10000 ./0x10000.bin
esptool.py v2.4.1
Serial port /dev/ttyUSB0
Connecting....
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
MAC: 60:01:94:70:54:1b
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash params set to 0x024f
Compressed 37072 bytes to 25809...
Wrote 37072 bytes (25809 compressed) at 0x00000000 in 2.3 seconds (effective 126
.9 kbit/s)...
Hash of data verified.
Compressed 262916 bytes to 178828...
Wrote 262916 bytes (178828 compressed) at 0x00010000 in 15.8 seconds (effective
133.0 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

And that was it; it reset the ESP-12E and the firmware started without fuss.

My ESP-01S took a lot longer as the programming baud rate is only 9600. Remember to put the ESP-01S into 'program' mode manually:

$esptool.py --baud 9600 --port /dev/ttyUSB1 write_flash -fs 4MB -ff 80m -fm dio 0x00000 ./0x00000.bin 0x10000 ./0x10000.bin
esptool.py v2.4.1
Serial port /dev/ttyUSB1
Connecting....
Detecting chip type... ESP8266
Chip is ESP8266EX
Features: WiFi
MAC: ec:fa:bc:1c:c6:0d
Uploading stub...
Running stub...
Stub running...
Configuring flash size...
Flash params set to 0x024f
Compressed 38048 bytes to 26400...
Wrote 38048 bytes (26400 compressed) at 0x00000000 in 27.8 seconds (effective 11
.0 kbit/s)...
Hash of data verified.
Compressed 265860 bytes to 181152...
Wrote 265860 bytes (181152 compressed) at 0x00010000 in 190.5 seconds (effective
 11.2 kbit/s)...
Hash of data verified.

Leaving...
Hard resetting via RTS pin...

Power-cycle (or reset) the ESP-01S to start the firmware running.

I had no problems accessing the WiFi repeater Access Point. It is a little slow, and my test youtube video stuttered a bit, but it played to completion. For IoT use, especially the built-in MQTT, it should be sufficient.

Happy Trails.


Tuesday, 12 June 2018

Raspberry Pi Zero W as IoT Bluetooth Gateway Part3: Replacing Slackware with Raspbian

Something's missing ... yes the USB bluetooth dongle

Nowadays I use the iterative development method. Following Part 1 and Part 2, we used the Raspberry Pi Zero W as an IoT Bluetooth Gateway daily for about six months. It was more secure than using the smartphone to directly pair to the autogate bluetooth module HC-06 - the PIN is only 4 digits and easily brute-forced.. Using a WiFi repeater, its range was much better. We could now reliably operate the autogate without getting out of the car.

In addition the Pi Zero W gateway hogged the HC-06 all the time, and prevented any intruder from pairing to it. To gain access the intruder would now have to brute-force the WPA2 passphrase in the WiFi repeater, a much tougher opponent.

It also got turned on and off a lot to avoid being damaged by lightning, and sometimes it would fail to start up properly. This is usually due to HC-06 connectivity issues- perhaps it was not unpaired properly or was later that usual being discovered. This allowed the startup to be adjusted using my bash script in /etc/rc.d/rc.local.

Now that it was stable enough it was time to improve it further. From Part 2, Slackware 14.2 did not recognize the Raspberry Pi Zero W's builtin bluetooth module. Perhaps the Raspbian kernel I used was not current enough or perhaps my Slackware installation lacked something, but having to use an RM32 bluetooth dongle with an RM42.20 Raspberry Pi Zero W rankled.

I downloaded the Raspberry Pi NOOBS, and following the installation guide, installed the latest Raspbian OS into by Pi Zero W. Make sure to set up the WiFi connection to your broadband. The builtin bluetooth controller worked first time and had no trouble working with the HC-06 in the autogate. I repeated the procedure in Part 1, and upgraded the firmware/BIOS in the Slackware 14.1 image, but the builtin bluetooth controller did not come up.

Now I could have gone further and upgraded the Linux kernel as well, but perhaps it is time to work with Raspbian/Debian for a while. At least I would not have to keep up with the updates.

To use Raspbian for the IoT Bluetooth Gateway, I needed pymodbus, apache, and php. In Slackware, pymodbus needed upgrades to python 2.7, pip and pysetuptools. Checked in Raspbian:

root@raspi-0-w-2:/root# pip --version
pip 9.0.1 from /usr/lib/python2.7/dist-packages (python 2.7)
root@raspi-0-w-2:/root# python --version
Python 2.7.13

Now that was a pleasant surprise: no upgrades necessary. Now for pysetuptools: Make sure your WiFi connection is working before you try this:

root@raspi-0-w-2:/root# apt-get upgrade python-setuptools
Reading package lists... Done
Building dependency tree
Reading state information... Done
python-setuptools is already the newest version (33.1.1-1).
python-setuptools set to manually installed.
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.

Again, no upgrades necessary. Might as well go ahead with pymodbus:

root@raspi-0-w-2:/root# pip install -U pymodbus

Installed successfully, no fuss. Now my Modbus server program needed twisted, so:

root@raspi-0-w-2:/root# pip install twisted

No problems there. Also required is cryptography:

root@raspi-0-w-2:/root# pip install cryptography

Now for apache. Before that just to be sure I checked for upgrades to Raspbian:

root@raspi-0-w-2:/root# apt-get update

No upgrades necessary- I am beginning to like Debian. Now for apache:

root@raspi-0-w-2:/root# apt-get install apache2 -y

No fuss, apache ran right out of the box (check using http://localhost/index.html):

root@raspi-0-w-2:/root# find /var -name index.html
/var/www/html/index.html

root@raspi-0-w-2:/root# ps -ef
root     20567     2  0 14:14 ?        00:00:00 [kworker/0:3]
root     21396     1  0 14:16 ?        00:00:00 /usr/sbin/apache2 -k start
www-data 21398 21396  0 14:16 ?        00:00:00 /usr/sbin/apache2 -k start
www-data 21399 21396  0 14:16 ?        00:00:00 /usr/sbin/apache2 -k start
root     21723  1705  0 14:18 pts/1    00:00:00 ps -ef

Similarly, php just worked, striaght from the box:

root@raspi-0-w-2:/root# apt-get install libapache2-mod-php php

The web server was linked to pymodbus using the same method as before. Now Raspbian defaults to using dynamic IP, and ssh disabled, which is not very useful if you are using the Pi in "headless" (ie without monitor, mouse or keyboard). ssh is easily enabled via the Raspbian desktop or using raspi-config. To get it to use static IP simply add the following lines to /etc/dhcpcd.conf:

interface wlan0
static ip_address=192.168.1.2/24
static routers=192.168.1.1
static domain_name_servers=8.8.8.8

Lastly Raspbian uses the much improved bluetoothctl, which is an interactive program and not easily included in a bash script like bluez. Luckily Linux has just the thing we need: 'expect' can be used to fool bluetoothctl into thinking it is interacting with a human via the keyboard.

root@raspi-0-w-2:/root# apt-get install expect

Put it in a little bash script and you have a much more robust startup script than simple-agent:

root@raspi-0-w-2:/root# cat ./hc-06.sh
#!/usr/bin/expect -f

set prompt "#"
set address [lindex $argv 0]

spawn sudo bluetoothctl -a
expect -re $prompt
send "remove $address\r"
sleep 1
expect -re $prompt
send "scan on\r"
send_user "\nSleeping\r"
sleep 5
send_user "\nDone waiting for controller\r"
expect "Controller"
send_user "\nSleeping ... waiting for autogate\r"
sleep 5
send_user "\nDone waiting for autogate\r"
expect "HC-06"
send "scan off\r"
send "trust $address\r"
sleep 2
send "pair $address\r"
sleep 2
send "0000\r"
sleep 3
send_user "\nShould be paired now.\r"
send "quit\r"
expect eof

Note: replace '0000' with your password and launch the script thus:

root@raspi-0-w-2:/root# ./hc-06.sh 12:34:56:78:9A:BC

Lastly bind to your bluetooth device to get the serial port /dev/rfcomm0:
rfcomm bind 0 12:34:56:78:9A:BC 1

And you are ready for the pymodbus program, which I launched in the background and captured its output to a log file:

root@raspi-0-w-2:/root# python ./autogate_server.py >> ./autogate.log 2>> ./autogate.log &

Raspbian turned out to be quite pleasant to use, but then it is Linux, so we expect nothing less. 

Happy Trails.

Wednesday, 30 May 2018

Using the CH340 USB dongle as ESP-01S Programmer

Note the add-on push-button switch

Some time ago I bought a cheap (RM6.50) CH340-based USB to serial 3V3 TTL dongle with a socket for the ESP-01S. It was described as a development board, and I assumed it will also program the ESP-01.


That assumption turned out to be quite unwise, as 'programming' was not mentioned:


It most certainly would not program an ESP-01S. What is needed was a switch to pull GPIO0 low when the ESP-01S is powered up or reset. AndyS19 has a nice writeup on this.

ESP-01S pinout

I happened to have a cheap (RM0.80) small push-button switch very popular here for use in car, autogate or garage remotes. The distance between the leads (ie pitch) almost exactly fits the pins GPIO0 and GND of the CH340 ESP-01S USB dongle, on the solder-side of the PCB.





Push-button switch for remotes


Solder-side of the CH340 ESP-01S adapter

I simply glued the switch to the PCB and soldered the leads onto the GND and GPIO0 header pins, and presto, I have an ESP-01S programmer.



To program, hold down the button as you insert the dongle in your laptop. Release the button and fire up your Arduino sketch. Use the 'ESP8266 Generic' setting. Some ESP-01S I bought would only work with 9600 baud, others are fine with 115200.

It will be especially handy to have an ESP8266 board to run my code on those train rides on my daily commute.

Life is good. Happy Trails.












Tuesday, 8 May 2018

IoT Porch Light using ESP8266 and Arduino Relay PCB

WiFi control of Porch :Light
Warning: this project contains live mains voltages that can be lethal. There can be a fire risk especially if you use generic no-name China parts like I have. Do not use generic parts unless you first test them under load and disassemble them. Some generic parts are under-specified and can be hazardous. Do not mount the lamp on flammable surfaces like wood or plastic.

Following related posts on ESP-01S, the ESP-12E Arduino sketch, this post shows the final assembly of the light panel and the 5V power supply.

The light panel is a generic "Surface LED Down Light Panel Lamp" from cpeelectrical which I bought for RM43, mainly because there is room inside the enclosure to mount the ESP-1S and relay PCB.

Top: ESP-01S with relay module. Middle: 230Vac to 5Vdc power supply. Bottom: 18W LED Driver



For the ESP-01S power supply I used an old smartphone charger. It was marked as 'output 5.7V 800mA' but when used I measured 5.05V which is just as well as the ESP-01S only takes 5V. It is too large to be mounted inside the lamp, so I removed the PCB and mounted it without its enclosure.

Old Android smartphone charger, disassembled. Note the lack of a controller IC
The power supply was extremely simple: all the components were discrete devices (ie no IC) and just an oscillator at the primary side, and a single rectifier diodes at the primary and secondary. Still, it had been used for some 3 years sealed in its plastic enclosure with no ventilation holes and should be reliable enough for this purpose.

5V Charger tested on ESP-01S
If you do not have an old charger, you can buy them here in Malaysia for RM6 or more. It is better to buy branded chargers like Samsung, as generic chargers have been known to catch fire.

The ESP-01S drew 170mA during WiFi access, and 60mA more when operating the relay. In practice it drew somewhat more than 230mA on power up so that 800mA at 5V came in handy. For easy installation I had the relay come on on power up. This also acts as a manual override 'ON' from the light AC mains power switch. 

The final part, the LED Driver came with the lamp. It took in 85-230Vac (any lower and it shuts down) and puts out a stonking 60Vdc at no load, but settled down to 50Vdc at 330mA when the lamp is turned on. 

LED driver 18W 230V


Now the relay can be wired up to switch the DC or AC side of the LED driver. I opted to switch the AC side- hopefully it saves a little power as the primary side will not be always on.

LED Driver. Note the IC controller on the primary side
For enclosures for the ESP-01S and the mains 5V power, I used the casings from 20W LED drivers. The entire LED driver costs only RM6 each and I had a couple of faulty ones left over from other LED lamps. You must enclose them: all 3 boards have 230V mains voltages that must be insulated. Here in Malaysia insects get into electrical fittings, especially light fittings including bulbs.

There you have it: an IoT lamp, switched from your smartphone or desktop browser. Happy Trails.

Friday, 27 April 2018

A clock that never needs to be set- ESP8266 WiFi-synchronized LED Clock with MAX7219

"Time, it needs time ..." Scorpions, Still Loving You
I like LED clocks. Waking up at night, you just need to glance at it for the time. No fumbling with the light switch, or peering at the radioactive hands of a mechanical clock. That way I have a much better chance of going back to sleep.

LED Clock


A smartphone works just the same except I have to keep moving it and it may not be there when I need the time. An LCD clock is much less legible. Or if the backlight is turned up, it is too bright as a bedside clock.

The trouble with an LED clock is they are not expensive enough to be made very accurate. Typically a quartz clock will be off by some 4 minutes a year. It gets worse if they lose mains power. They are battery-backed, and they keep the time, but with much less accuracy. Over a few power outtages, you can get an outrageously-wrong clock.

Ideally, my LED clock should synchronize itself over the Internet, much like my smartphone. Now if I can keep the large LED segment displays and replace the  electronics I would get my ideal clock.

The ESP8266 with a MAX7219 are perfect for this. The idea is to get the system working, then remove the MAX7219 7-segment displays and wire it to the much larger LEDs in the clock. There should be no trouble fitting an ESP-12E and MAX7219 into the clock.  

I would need a battery-backed 5V for the ESP8266. A lithium power bank circuit would do quite nicely. There is no need to set time as the unit will automatically correct itself once WiFi is available.

Earlier, I had a MAX7219 working with an ESP-12E NodeMCU ESP8266. The Arduino IDE has sample code for the WiFi time, ESP8266 NTPClient. I just need to paste my MAX7219 SPI code into it. No wiring needed.

ESP-12E ESP8266 Nodemcu with MAX7219


And it was so. In just one hour I have a self-correcting WiFi LED clock. The battery-backed power circuit still need to be designed and tested, the software may well need to be changed, and the ESP8266 does not boot up unless the MAX7219 is first disconnected, and last but not least it whole thing needs to be stuffed into the LED clock. 

It was over so quickly, it was only later I found there is already an existing ESP8266 project that does exactly that, courtesy of sebouh.

An Internet of Things for Industry sometimes require accurate reliable time, especially when it is part of a distributed control system. This would be a cheap way of adding NTP to an embedded control system.

There you have it, ESP8266 WiFi-synchronized LED Clock with MAX7219. Happy Trails.

/*
 * TimeNTP_ESP8266WiFi.ino
 * Example showing time sync to NTP time source
 *
 * This sketch uses the ESP8266WiFi library
 */

#include <TimeLib.h>
#include <ESP8266WiFi.h>
#include <WiFiUdp.h>
#include <SPI.h>

class ESP_MAX7219
{
private:
    uint8_t _ss_pin;
    void SS_High()
    {
        digitalWrite(_ss_pin, HIGH);
    }
    void SS_Low()
    {
        digitalWrite(_ss_pin, LOW);
    }

public:
    ESP_MAX7219(uint8_t pin):_ss_pin(pin) {}
    void begin()
    {
        pinMode(_ss_pin, OUTPUT);
        SS_Low();
        delay(1000);
        SS_High(); // Default to high
    }

    void writeData(const uint8_t MSB, const uint8_t LSB)
    {
        SS_Low();
        SPI.transfer(MSB);
        SPI.transfer(LSB);
        SS_High();
    }
};

ESP_MAX7219 esp(SS);


void send(const uint8_t msb, const uint8_t lsb)
{
  esp.writeData(msb, lsb);
  delay(10);
}
void init_MAX7219(void)
{
    send(0x0F, 0x00);
    delay(100);
    Serial.println("Test mode off");
    send(0x09, 0xFF);
    Serial.println("BCD Mode all 8 digits");
    delay(100);
    send(0x0A, 0x03);
    Serial.println("Quarter brightness");
    delay(100);
    send(0x0B, 0x07);
    Serial.println("8 Digit Scan");
    delay(100);
    send(0x0C, 0x01); // Normal Operation
    Serial.println("Shutdown off");
    delay(100);
}

uint8_t put_bcd(const uint8_t bcd)
{
  if (bcd>9)
    return 0x00;
  send(0x08, bcd);
  delay(10);
  return 0x01;
}

static const uint8_t bcd100_table[] = {
  0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
  0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19,
  0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29,
  0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39,
  0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
  0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59,
  0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
  0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79,
  0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
  0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99,
};

uint8_t put_bcd100(const uint8_t bcd)
{
  uint8_t c;

  if (bcd>99)
    return 0x00;
  c= bcd100_table[bcd];
  send(0x01, (c & 0x0F)); // Display LSN
  delay(10);
  send(0x02, (c>>4) & 0x0F); // Display MSN
  delay(10);
  return 0x01;
}
uint8_t put_bcd10000(const uint8_t bcd)
{
  uint8_t c;

  if (bcd>99)
    return 0x00;
  c= bcd100_table[bcd];
  send(0x03, (c & 0x0F)); // Display LSN
  delay(10);
  send(0x04, (c>>4) & 0x0F); // Display MSN
  delay(10);
  return 0x01;
}

uint8_t put_bcd1000000(const uint8_t bcd)
{
  uint8_t c;

  if (bcd>99)
    return 0x00;
  c= bcd100_table[bcd];
  send(0x05, (c & 0x0F)); // Display LSN
  delay(10);
  send(0x06, (c>>4) & 0x0F); // Display MSN
  delay(10);
  return 0x01;
}

void blank_display(void)
{
  send(0x01, 0x0F);
  delay(10);
  send(0x02, 0x0F);
  delay(10);
  send(0x03, 0x0F);
  delay(10);
  send(0x04, 0x0F);
  delay(10);
  send(0x05, 0x0F);
  delay(10);
  send(0x06, 0x0F);
  delay(10);
  send(0x07, 0x0F);
  delay(10);
  send(0x08, 0x0F);
  delay(10);
}
const char ssid[] = "****************";  //  your network SSID (name)
const char pass[] = "*****************";       // your network password
// NTP Servers:
static const char ntpServerName[] = "pool.ntp.org";
//static const char ntpServerName[] = "time.nist.gov";
//static const char ntpServerName[] = "time-a.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-b.timefreq.bldrdoc.gov";
//static const char ntpServerName[] = "time-c.timefreq.bldrdoc.gov";

const int timeZone = +8;     // Central European Time
//const int timeZone = -5;  // Eastern Standard Time (USA)
//const int timeZone = -4;  // Eastern Daylight Time (USA)
//const int timeZone = -8;  // Pacific Standard Time (USA)
//const int timeZone = -7;  // Pacific Daylight Time (USA)


WiFiUDP Udp;
unsigned int localPort = 2390;  // local port to listen for UDP packets

time_t getNtpTime();
void digitalClockDisplay();
void printDigits(int digits);
void sendNTPpacket(IPAddress &address);

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println();
  Serial.println("TimeNTP Example");

delay(3000);
  Serial.println("Initializing SPI");
  SPI.begin(); // Delay sending output to MAX7219 to prevent it leaching power
  esp.begin();
  Serial.println("Initializing MAX7219 - 1st attempt");
  init_MAX7219();
  blank_display();
  put_bcd100(1);
  delay(3000);
  Serial.println("Initializing MAX7219- 2nd attempt");
  init_MAX7219();
  blank_display();
  put_bcd100(2);
  delay(3000);
  Serial.println("Initializing MAX7219- 3rd attempt");
  init_MAX7219();
  blank_display();
  put_bcd100(3);
  delay(3000);
  Serial.print("Connecting to ");
  Serial.println(ssid);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, pass);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("IP number assigned by DHCP is ");
  Serial.println(WiFi.localIP());
  Serial.println("Starting UDP");
  Udp.begin(localPort);
  Serial.print("Local port: ");
  Serial.println(Udp.localPort());
  Serial.println("waiting for sync");
  setSyncProvider(getNtpTime);
  setSyncInterval(300);

}

time_t prevDisplay = 0; // when the digital clock was displayed

uint8_t hours = 0;
uint8_t mins = 0;
uint8_t loop_ctr = 0;
void loop()
{
  if (timeStatus() != timeNotSet) {
    if (now() != prevDisplay) { //update the display only if time has changed
      prevDisplay = now();
  hours = hour();
  mins = minute();
  loop_ctr = second();
  put_bcd100(loop_ctr);
  put_bcd10000(mins);
  put_bcd1000000(hours);
    }
  }
}

void digitalClockDisplay()
{
  // digital clock display of the time
  Serial.print(hour());
  printDigits(minute());
  printDigits(second());
  Serial.print(" ");
  Serial.print(day());
  Serial.print(".");
  Serial.print(month());
  Serial.print(".");
  Serial.print(year());
  Serial.println();
}
{
  // utility for digital clock display: prints preceding colon and leading 0
  Serial.print(":");
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}

/*-------- NTP code ----------*/

const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message
byte packetBuffer[NTP_PACKET_SIZE]; //buffer to hold incoming & outgoing packets

time_t getNtpTime()
{
  IPAddress ntpServerIP; // NTP server's ip address

  while (Udp.parsePacket() > 0) ; // discard any previously received packets
  Serial.println("Transmit NTP Request");
  // get a random server from the pool
  WiFi.hostByName(ntpServerName, ntpServerIP);
  Serial.print(ntpServerName);
  Serial.print(": ");
  Serial.println(ntpServerIP);
  sendNTPpacket(ntpServerIP);
  uint32_t beginWait = millis();
  while (millis() - beginWait < 1500) {
    int size = Udp.parsePacket();
    if (size >= NTP_PACKET_SIZE) {
      Serial.println("Receive NTP Response");
      Udp.read(packetBuffer, NTP_PACKET_SIZE);  // read packet into the buffer
      unsigned long secsSince1900;
      // convert four bytes starting at location 40 to a long integer
      secsSince1900 =  (unsigned long)packetBuffer[40] << 24;
      secsSince1900 |= (unsigned long)packetBuffer[41] << 16;
      secsSince1900 |= (unsigned long)packetBuffer[42] << 8;
      secsSince1900 |= (unsigned long)packetBuffer[43];
      return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
    }
  }
  Serial.println("No NTP Response :-(");
  return 0; // return 0 if unable to get the time
}

// send an NTP request to the time server at the given address
void sendNTPpacket(IPAddress &address)
{
  // set all bytes in the buffer to 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // Initialize values needed to form NTP request
  // (see URL above for details on the packets)
  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, now
  // you can send a packet requesting a timestamp:
  Udp.beginPacket(address, 123); //NTP requests are to port 123
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  Udp.endPacket();
}

Wednesday, 25 April 2018

The SPI who came in from the cold

The film was based on John Le Carre's novel of the same name
“Don't give it to them all at once, make them work for it. Confuse them with detail, leave things out, go back on your tracks" -  John le CarrĂ©The Spy Who Came In from the Cold

I had always wanted a wall clock which self-corrects from the Internet using NTP. A few weeks ago I bought a cheap MAX7219 8-digit 7-segment display. It seems a perfect match for the ESP8266, which will access NTP from my home WiFi. The MAX7219 uses SPI which should link to the ESP-12E and can then be scaled up to wall clock size.

Straightforward project, perfect for IoT. It started well- I linked two ESP-12E using SPI and the sample programs in Arduino IDE. That worked a treat. I may not even need to learn the Lua programming language. Then real life intervened.

It did not work. The Lua sample code had worked when I used ESP-12Es as master and slave. Did I get a bad MAX7219? It certainly was a cheap clone from a no-name China supplier. Was it the level translator, the Lua program or the ESP-12E? 

It was weird- at times it seems to start working (the display comes on) only when I power it off.It seemed to work even when the MAX7219 did not have power - the MAX7219 seemed to be able to leech enough power from its signal lines. Randomly disconnecting a signal cable sometimes produced a fleeting glimpse of a working display.

But after three days, I had to admit- I was stuck. But what is really needed is an admission that the easy way failed. 
I needed to know whether it is a problem with the MAX7129 or the software.One way was to get the MAX7219 working with different software. The other would be to buy another MAX7219 module, preferably from a different supplier. To rule out the ESP-12E's flakey start I need a different controller. 

I had SPI source code for the PIC18F14K50 CPU from Microchip- it came with their development kit. I even have a finished PIC18F14K50 prototype PCB I designed myself some years ago but had not gotten round to testing it.


Testing the SPI PIC18F14K50. Being USB devices they came up in Linux as /dev/ttyACM0 and /dev/ttyACM1

The SPI PIC18F14K50 and their software (sample code from the Microchip MCC18 compiler) did not work either. Time to back off SPI. The PIC18F14K50's SPI pins can be reprogrammed to use the I2C protocol. Maybe I will have better luck - at least it will prove I have working PIC18F14K50.

Fell back to I2C. Did not work either! It is starting to look like I am going backwards. Time to bring up the Death Star, my oscilloscope. Nothing gets by the oscilloscope. I love my scope, but just setting it up takes one day. It forces me to take things real slow, step by steady step, soldering test points, checking voltages pin by pin. Oh, and you now have to read the datasheets. Ugh. Buzzkill. Time to RTFM

And true enough, the scope showed problem is the MCC18 code. It does not work. That is quickly fixed, and now I2C between two PIC18F14K50 work. The SPI worked soon after, but the datasheets said there is no real standard for SPI, there can be 4 variants. The timing cycles for the PIC18F14K50 and the MAX7219 needs to be carefully examined for a match.

The 4 SPI modes supported by the PIC18F14K50
Luckily, there is a match: the second mode in the PIC18F14K50 matches:

MAX7219 Timing Diagram. 

Set up the PIC18F14K50 with the MAX7219. It did not work! In fact the symptoms are much the same as the ESP-12E, the display flashes working on power down, the MAX7219 starting up without power. But this time I have the scope. Turned out the PCB labelling was wrong- SPI MISO and MOSI were swapped round. And more MCC18 code needed to be replaced, but eventually it worked:



MAX7219 working with the PIC18F14K50

It's time to get back to the source of my grief- the software in the ESP-12E sample code. Now this is not a reflection on the software quality or the author- SPI simply had too many variants for the code to work right off the bat. Plus the author was interfacing to his SPI device using 32-bit SPI cycles. The MAX7219 needed 16-bit cycles.


Bottom: working SPI  on ESP-12E and MAX7219. Top: the mighty Death Star

You can get lucky most times and have your project work just using Internet resources. Indeed, you should and must use the optimistic approach first. But sometimes you just have to get out the Death Star and do the slow but steady steamroller. 



Later, I found the LEDControl already has working software for the ESP8266. It had not come up with my Google searches becasue it did not mention SPI, just the MAX7219.

The ESP-12E SPI interface to MAX7219 has had twists and turns worthy of a John Le Carre movie. Such is life. Happy Trails. 

Tuesday, 24 April 2018

Using SPI to interface the ESP-12E to the MAX7219 LED display

From top: ESP-12E Nodemcu, middle: Level Converter, bottom: MAX7219 8-digit 7-segment display
You will need an ESP-12E NodeMCU ESP8266, a 4-channel level converter and a MAX7219 7-segment LED display.

 Wire up as follows:
   GPIO    NodeMCU   Name  |   MAX7219
   ===================================
     15       D8                SS       |   CS
     13       D7                MOSI  |   DIN
     12       D6                MISO  |   Not connected
     14       D5                SCK    |   CLK

Since the MAX7219 is a 5V device and the ESP-12E a 3.3V you will need a level converter in between. I used one based on 4 MOSFETs, covered in some detail here.

The MAX7219 draws quite a bit of power so it is advisable to have a separate power source from the ESP-12E. I used a 3A D-Link USB 3.0 hub.

I based my program on the sample code in the NodeMCU Lua sample code 'SPISlave_SafeMaster'  that came with the Arduino IDE.

With the level converter connected, the ESP-12E would not program, so I ended up having to unplug the 3.3V side of the level converter before programming.

The program merely initializes the MAX7219, displays '12345678', then triggers the MAX7219's built-in test function (lights up everything with maximum brightness). The built-in test requires a relatively high-ampere 5V supply.

The program is a Lua sketch, and requires the Arduino IDE. I used a custom-built Slackware Linux version, but there are stock Linux and Windoes versions that work the same.

The program is:

    SPI Safe Master Demo Sketch
    Connect the SPI Master device to the following pins on the esp8266:

    GPIO    NodeMCU   Name  |   Uno
   ===================================
     15       D8       SS   |   D10
     13       D7      MOSI  |   D11
     12       D6      MISO  |   D12
     14       D5      SCK   |   D13

    Note: If the ESP is booting at a moment when the SPI Master has the Select line HIGH (deselected)
    the ESP8266 WILL FAIL to boot!
    This sketch tries to go around this issue by only pulsing the Slave Select line to reset the command
    and keeping the line LOW all other time.

*/
#include <SPI.h>

class ESP_MAX7219
{
private:
    uint8_t _ss_pin;
    void SS_High()
    {
        digitalWrite(_ss_pin, HIGH);
    }
    void SS_Low()
    {
        digitalWrite(_ss_pin, LOW);
    }
 
public:
    ESP_MAX7219(uint8_t pin):_ss_pin(pin) {}
    void begin()
    {
        pinMode(_ss_pin, OUTPUT);
        SS_High(); // Default to high
    }

    void writeData(const uint8_t MSB, const uint8_t LSB)
    {
        SS_Low();
        SPI.transfer(MSB);      
        SPI.transfer(LSB);
        SS_High();
    }
};

ESP_MAX7219 esp(SS);

 
void send(const uint8_t msb, const uint8_t lsb)
{
    esp.writeData(msb, lsb);
    delay(10);
    // Serial.print("Slave: ");
    // Serial.println(esp.readData());
    // Serial.println();
}

void setup()
{
    Serial.begin(115200);
    SPI.begin();
    esp.begin();
    delay(1000);
    send(0x0C, 0x01); // Normal Operation
}

void loop()
{
    delay(1000);
    send(0x0C, 0x01); // Normal Operation
    Serial.println("Shutdown off");
    delay(1000);
    send(0x09, 0xFF);
    Serial.println("BCD Mode all 8 digits");
    delay(1000);
    send(0x0A, 0x03);
    Serial.println("Quarter brightness");
    delay(1000);
    send(0x0B, 0x07);
    Serial.println("8 Digit Scan");
    delay(1000);
    send(0x0F, 0x01);
    Serial.println("Test mode on");
    delay(1000);
    send(0x0F, 0x00);
    Serial.println("Test mode off");
    delay(1000);
    send(0x01, 0x08);
    Serial.println("Digit 8");
    send(0x02, 0x07);
    Serial.println("Digit 7");
    send(0x03, 0x06);
    Serial.println("Digit 6");
    send(0x04, 0x05);
    Serial.println("Digit 5");
    send(0x05, 0x04);
    Serial.println("Digit 4");
    send(0x06, 0x03);
    Serial.println("Digit 3");
    send(0x07, 0x02);
    Serial.println("Digit 2");
    send(0x08, 0x01);
    Serial.println("Digit 1");

    /* send(0x0C, 0x00);
    delay(3000);
    Serial.println("Shutdown on"); */
 
    send(0x0C, 0x01);
    Serial.println("Shutdown off");
    delay(3000);
}

There are plenty of other projects out there using the MAX7219, but I could not find one that uses the ESP-12E (or ESP8266). The MAX7219 uses SPI which seems to be gradually being replaced by I2C. This has been tested and worked for me, but turned out to be harder than I thought, but that is another blog post.

Happy Trails.

Sunday, 8 April 2018

The rtlizer: low-cost spectrum analyzer using RTL2832U SDR

The Equalizer: 1985 TV Series
"Got a problem? Odds against you? Call the Equalizer. 212 555 4200." - The Equalizer

A spectrum analyzer is a serious piece of kit for an electronics engineer. You can easily spend over RM10,000 on one, although you can get generic ones online for RM4000. The RTL2832U DVB-T USB dongle SDR can be used to make a basic spectrum analyzer for RM39.

It is the great equalizer (after the 1985 TV Series), leveling the playing field for students, hobbyists and Third World engineers.

OZ9AEC has made an everyman's spectrum analyzer using a Raspberry Pi and the RTL2832U dongle. Happily, his design runs without modification on my Slackware 14.2 laptop.

rtlizer required rtl-sdr, which I had previously installed. Here we just need to tell rtlizer where to find it:

$export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/usr/local/lib/pkgconfig

And add '/usr/local/lib' to the file /etc/ld.so.conf. If you had just installed rtl-sdr it helps to also do:

$ldconfig

I downloaded rtlizer, and built it thus:

$unzip rtlizer-master.zip
$rtlizer/rtlizer-master/src$make

If necessary, unload (or even better, blacklist) the dongle's kernel drier:
$modprobe -vr dvb_usb_rtl28xxu
rmmod dvb_usb_rtl28xxu
rmmod dvb_usb_v2
rmmod rc_core

And it runs, right out of the box (with a complaint or two):

$./rtlizer 1024x720

(rtlizer:22436): Gtk-WARNING **: gtk_window_parse_geometry() called on a window w
ith no visible children; the window should be set up before gtk_window_parse_geom
etry() is called.
window size: 1024x720 pixels
SCALE: 7.20 / Y0: 216 / TXTMARG: 21
Found Fitipower FC0012 tuner

rtlizer responds to the the arrow keys. And wneh I centered rtlyzer on 330MHz, my autogate remote's frequency, I got the spike in the center, with the upper and lower sidebands when the button was pressed:

rtlizer centered on 330MHz
There you have it: an RM39 spectrum analyzer, courtesy of OZ9AEC. Happy Trails.