Wednesday 30 November 2016

I2C with the ESP8266 and in General

The ESP8266 has no hardware support for I2C, however the Espressif SDK (version 2.0.0 non-os) exposes low level functions which implement the protocol robustly enough on a pair of GPIO pins through bit-banging. As a result of being done in software, the implementation does tie up a bit of processor time, and in addition, the functions block while they write or read, etc. Note also that this SDK version reportedly does not support clock-stretching nor being the slave device (neither of which I have tried).

As a demonstration, and to learn how to use this I2C functionality, I got the ESP8266 to talk to a HMC5883L compass module which I had successfully used with an Arduino previously.

Code example of talking to an I2C compass module here.


The SDK functions are as follows:


To initialise (this only needs to be done once, usually on boot):

  • i2c_master_gpio_init(), and then
  • i2c_master_init()


For the actual I2C communications you have these functions with which to indirectly manipulate the GPIOs and craft a datastream:

  • i2c_master_start()
  • i2c_master_writeByte(your_8_bits_of_data)
  • i2c_master_readByte(), simply clocks out 8 pulses on SCL and reads SDA on each rising edge then returns the read data as an 8 bit integer. Handling the state of the slave so that it is ready to have data clocked out of it is not handled by this function, however the datasheet of whatever slave device you are using will usually give details on its I2C  r/w addresses, how to configure internal registers and pointers - and thus how to ready the device to send data.
  • i2c_master_stop()


  • i2c_master_checkAck(), will return true if the slave ACKs, false otherwise. For almost all I2C devices, this will need to be called after a byte is written so that the ESP8266 produces a 9th clock pulse in which the slave has the opportunity to ACK or NACK. i2c_master_writeByte does not take care of this crucial part of writing a byte to a slave.


The following two functions are intended to be called only when reading data from the slave. Either an ACK or NACK is sent to the slave after receiving a packet; this is to signal whether more packets are going to be read by the master or that the master is done reading from the slave:

  • i2c_master_send_ack()
  • i2c_master_send_nack()

These are all quite straightforward functions that do as they say on the tin, however the functions relating to acknowledging packets required some investigation and probing on the oscilloscope to figure out.

Some problems that I ran into:


I initially had issues because the ESP8266 was not producing the 9th clock pulse which the HMC5883L expected when being written to, causing the two chips to go out of sync, hence the warning about needing to call i2c_master_checkAck.

Also, I had a nasty bug which was caused by ACKing the last byte that I intended to read from the HMC5883L instead of NACKing it. This would cause it to anticipate more clock pulses coming and believe that the ESP8266 was going to try to read from it, as such, it retained control of SDA, stopping the ESP8266 from controlling SDA, which is necessary for issuing a stop condition. As the ESP8266 tried to pull SDA high to signal a stop, the HMC5883L would sink all the current coming out of that GPIO pin, effectively being a short, however this didn't seem to stop or crash the ESP8266, and serial remained fine despite the serial indicator LED lighting up solid. The only noticeable effect other than the serial LED going solid was the numerical readings from the HMC5883L turning to gibberish. It took a long time to find the cause of this bug because it simply wouldn't appear unless the compass was rotated to certain orientations, otherwise all would work fine. Eventually putting the oscilloscope on revealed that the compass was hogging SDA; before that, I had suspected corruption in either the ESP8266 or the HMC5883L, or some code struggling with certain values being read, or even EMI, so I completely re-flashed the ESP8266, added pullup resistors on SDA and SCL and combed over the code - to no avail.

A third problem that I had, specific to the HMC5883L, was with the datasheet, which said that to read from the compass, the number of bytes to be read has to be sent in a second packet after the read address. This second packet specifying the number of bytes to be read was always NACKed by the compass and turned out to be unnecessary, and further, stopped data from being read.


Decoding an I2C transmission on an oscilloscope:



Above: three bytes being successfully written to the HMC5883L. SDA is in yellow on top, SCL on the bottom in blue. It can be seen that the slave is ACKing on the 9th clock pulses by pulling SDA low; notice that when it is the HMC5883L pulling SDA low, it goes slightly closer to 0V than when the ESP8266 is pulling it low, this is related related to the differing amount of current each can sink, possibly due to differing gate types/sizes.

Anyway, to read that I2C waveform above, keep foremost in mind that a high clock state indicates that SDA is stable and thus ready to be read, and that a low clock state indicates that SDA may now be changed, and that it is not stable because the signal may still be propagating or settling. At the start of the transmission, both SDA and SCL are high, then SDA is pulled low while SCL is high; this violates the cardinal rule of only changing SDA when SCL is low to transfer data, but it is done in I2C to indicate the beginning of a transmission. The stop condition is a mirror of this and is also notable for violating the rule - SDA is allowed to go high while SCL is already high.

So from beginning to end of the I2C transmission above:


  • Start condition

  • First byte: 00111100 (transmitted by the ESP8266 over eight clock pulses)
  • ACK from HMC5883L (one clock pulse)
  • SDA floats high as it is released before the next byte and after the ACK. Its state does not actually matter in this region (SCL is low)

  • Second byte: 00000001
  • ACK
  • SDA floats high

  • Third byte: 00100000
  • ACK
  • SDA floats high then is pulled low again to prepare for the stop condition



  • Stop condition


This particular captured transmission writes to the HMC5883L to configure it. The first byte is the compass' write address, which it acknowledges to indicate that it recognises that it is being talked to. The second byte is the address of the register inside the compass which we want to write to in order to configure it (the HM5883L moves its internal register pointer to point to this address). The third byte is the value we want to set this register to. This syntax for writing a register is specific to the HMC5883L, but I imagine that it is similar for other devices; once again, the datasheet is your friend - the purpose of this section was more to demonstrate how to decipher I2C.

An I2C read is similar:

  • Start condition
  • Master sends 8 bit read address
  • Slave acknowledges

  • Slave sends 8 bits (but it is the master that controls the clock)
  • Master acknowledges
  • Repeat previous two steps until last packet...

  • Slave sends 8 bits
  • Master does not acknowledge (NACK)
  • Stop condition


Tuesday 19 April 2016

Combining the ESP8266 WiFi module with cheap 433MHz sensors

The ESP8266 is an incredibly cheap and powerful WiFi chip which is geared towards "internet of things" applications, it comes from an obscure Chinese company, but they've released its SDK as open source. With its high availability on eBay, it was hard to resist, so I bought one to play with. At the moment, recently inspired by cnlohr's work, I've got it serving webpages and detecting when "dumb" wireless PIRs and door sensors around my house are triggered.

A long story (skip it if you're here only to see stuff working):

For a long time after purchase, it sat unused like all useful electronics junk off the 'Bay. The thing was frustrating: I didn't have a 3.3v regulator, and I didn't have a USB to serial adapter to talk to the thing. Nevertheless, an Arduino board has both those things, so I powered it off the Arduino's 3v3 pin, and connected it to the tx and rx pins, using a voltage divider to make sure the ESP8266 didn't get fried. The aim was to get the Arduino to talk to it and send "AT commands" to it - the default firmware on the ESP8266 makes it act as a modem which can be controlled over serial/UART. This was a long shot. The Arduino's 3.3v rail is far undersized for an ESP8266, and three devices, one of them at a different voltage, on a single serial line, is asking for undefined behavior.

On testing, it didn't work; the ESP8266 never joined my WiFi network as it was supposed to. The Arduino was printing AT commands fine though (I could only sniff the Arduino's output because that is where the FTDI's rx is wired to). The setup not working wasn't surprising because it was a terrible way of doing things and there were too many unknowns.

Trying to make do without having to order then wait weeks for the proper parts off eBay, I forgot about talking to the Arduino, and tried to communicate directly with the ESP8266 through a serial monitor on the computer. To do this, the ESP8266's rx needs to be wired to the Arduino rx pin and tx to tx. The idea was to have the ESP8266 talk to the FTDI adapter and have the Arduino's ATmega328 remain silent. From memory, I think this worked just well enough to flash the chip, but commands and such often failed and caused resets because the Arduino's 3.3v regulator couldn't supply enough current. That sort of explains the first setup not working either.

This is the point where the ESP8266 went into storage for a long time.


Renewed interest:

The ESP8266 has become much more powerful and flexible with the release of its SDK, so I gave it another shot. I setup the SDK on Ubuntu in a virtual machine, I would highly recommend this; it works very well with USB pass through, and Linux is a joy to develop on (in my opinion). Also, I made a programming/breakout board for the module, otherwise it's a bit of a PITA as it can't be plugged directly into a breadboard.


The breakout board simply passes through all the pins to a header, but CH_PD (chip power down) and RST (reset) are pulled high through 4.7k resistors; this is a pre-requisite for normal operation (I'll go into the ESP8266's strange boot modes a bit later on).


The jumper on the breakout board, when inserted, pulls GPIO 0 to ground. This boots the ESP8266 into flash programming mode when the chip is reset by pulling RST low or by powering the chip off and on. To change the jumper, the ESP8266 has to be removed and then reinserted, this became a pain, so I started temporarily sticking a wire where the jumper would go and then briefly tapping a lead connected to RST to GND. The TX LED on the ESP8266 module would flash very briefly when RST was grounded, then stop completely - this behaviour indicates (not with certainty) that the chip is booted in flashing mode and is waiting for a program to be sent over serial to it. GPIO 0 can be un-grounded at this point. 

If you were to be listening to the serial port at this point, you'd see a bunch of garbage, unless your baud rate was set to 74880. This is the baud rate which the ESP8266's bootloader prints a bunch of information at on boot. One important piece of information is boot mode. Boot mode (1,6) or (1,7) indicates the module is in flashing mode. Boot mode (3,6) or (3,7) indicates it will load and run the program stored in flash. Listening to the ESP8266's serial output is the most reliable way to verify if it has booted into UART flashing mode properly, rather than trying to judge the board's TX LED.

It is important to leave GPIO 2 floating or pull it high (it has an internal pull up resistor anyway) whenever you reset or boot the ESP8266, otherwise it goes haywire, possibly trying to boot into SD card mode - but of course there is no SD card. This advice stands whether you are trying to boot into flashing mode or run-from-flash mode. GPIO 0 and 2 act as perfectly normal IO ports after booting, but the fact that they are so important at reset and when power is applied make them hard to use in any permanent application if you want to attach sensors. For example, with this project, I had to detach the 433MHz radio receiver from GPIO 2 every time the ESP8266's bootloader had control (on resets or power applied) then reconnect the receiver once booted.

This is the radio receiver I had lying around and used. It comes with a matching 433MHz transmitter, but I didn't need it.  The receiver has two data pins which are connected together; I'm not sure of the point of doing this. Anyway, the module drives the data pin high when it detects any carrier being present in the vicinity of 433MHz, and low when there isn't.

An antenna has to be soldered onto the board, or the reception range is merely a few centimetres. For 433MHz, a quarter wave monopole should be about 17 cm long. Use solid core wire for an antenna.
The 433MHz receiver module is regenerative one, rather than being a more expensive heterodyning one. From what I can tell, it has two stages of RF tuning and amplification: the first being being wide band and the second being more selective. Then the signal is fed into an op-amp, which I guess provides the massive gain which makes the output seem digital: high when a carrier is present, and low when there isn't.


This is an example of a 433MHz door/window sensor which the receiver can listen to. The door sensor simply chirps an ID repeatedly for a few hundred milliseconds when the magnet is taken away. The short transmission occurring only on triggering is pretty much a requirement for saving battery and not hogging the shared frequency (there is no collision avoidance or re-transmission). The downside is that the base station has to keep track of the state of all sensors and not miss any transmissions. Also, the battery in a sensor can die without the user knowing. This system would also be easily jammed or otherwise hacked, so these alarms are just toys, but that tends to be the nature of wireless - wireless on the cheap, anyway. These observations came from listening in with an RTL-SDR dongle, which I'll go into more detail on later.

The sensor's PCB is mostly un-populated. The 433MHz oscillator on the top right and the sheer lack of components (especially RF ones) hinted that data modulation wouldn't be much more sophisticated than switching a 433MHz carrier on and off like a Morse code transmitter. The name for this is on-off keying (OOK), which is a subset of amplitude-shift keying (ASK). The datasheet for the HS1527 (the IC on the sensor board), would've confirmed this guessing, had I had it at the time.

So here's an annotated screenshot from SDR Sharp, tuned in to the 433MHz area. I had just triggered a door sensor and a PIR sensor, which can be seen on the waterfall chart. Just quickly, the whole RTL-SDR project is amazing - it's made radio affordable to get into and incredibly flexible. Definitely check it out if you're interested even slightly in radio or what $15 of technology can do.

Signal 1. I figured was unrelated, or at least irrelevant, because it occurred every ~20 seconds and was comparatively weak. The weak signal suggested it came from farther away than the other two signals. Additionally, if it came from a door or PIR sensor, then I would have seen more than one instance of this signal, as there are about 8 sensors around my house. I wondered if it was the base station, but what use is it having the base station transmit when none of the remote sensors have receivers in them? Anyway, I was intending to bypass the existing base station.

Signal 2. and 3. appeared when I triggered a PIR and then a door sensor by walking along a corridor, stopping, opening a door, then walking back. So the burst which appeared twice must surely be from the PIR, having detected movement twice, leaving the other signal to be the door sensor.

Additionally, it was apparent that all the transmissions came from transmitters which must have cost the lesser part of a few dollars in RF-related components. It was something between the massive bandwidth the signals occupied, the ever-so-slight frequency drifting and how far off the labelled oscillator frequency of 433.92MHz they were. But the spread in frequency just made it easier to tell sensors apart from a glance in SDR Sharp, so it's not all bad.

The next step from here was to record the signals as audio and take a closer look in Audacity. I set SDR Sharp to AM demodulation and set a decent squelch, so noise wouldn't appear between transmissions in recordings. I made audio recordings on the lowest sampling quality setting. On-off keying is a subset of amplitude modulation (but with only two signal levels), so this was most appropriate.

Above is one of the signals, shown in Audacity, zoomed in on a single packet from a much larger transmission. A typical transmission is made up of the same packet repeated over and over again. This is done so that a receiver's AGC and noise suppression have time to respond (if present) and also to provide some noise immunity. Much like the output of the hardware receiver the ESP8266 was connected to, we see a high when the transmitter's oscillator has power and a low when it doesn't. In short, the waveform shows the state of the output pin of the HS1527 encoder inside a sensor over a period of time.

The datasheet for the HS1527 indicates that in its on-off encoding scheme, a short pulse is a 0 and a long pulse is a 1, additionally, the unique identifier each chip spits out is 20 bits long. Below is the output of the 433MHz receiver module; it shows a different sensor from the one in the Audacity screenshot transmitting, hence some of the differences:
This output on the oscilloscope was promising because it looked exactly like what the RTL-SDR snifffed, showing that the 433MHz receiver was receiving the sensors well. The software decoder I wrote for the ESP8266 works by interrupting on a rising edge, then on a falling edge, and so forth, to measure the duration of pulses and gaps between them. Measuring the gaps, in addition to the pulses, turned out to be useful for detecting the end of a packet or a whole transmission and for rejecting non-conforming signals.

The packet on the oscilloscope screen, despite its similarities, is a different length to the one recorded in SDR Sharp. This is because, in the 10ms radio silence between packets, the RTL-SDR's AGC re-adjusted, raising the noise floor, which triggered the squelch (seen in the recording where the slight noise disappears completely). This would then have had to all be undone when transmission resumed, hence why the first two bits are missing in the Audacity screenshot. Set a fixed gain and turn squelch off if you want to record in SDR Sharp. I didn't even notice at the time.

But back to analysing the packet captured on the oscilloscope, which is actually complete... The datasheet for the HS1527 says that: a short pulse is a zero; a long pulse is a one; a short zero pulse with a slightly longer gap afterwards is a pre-amble bit. This works out well: the pulse at the start of the packet is a pre-amble; then there are 20 bits, which is the sensor ID; then there are the last 4 bits, which supposedly indicate the state of four digital inputs on the chip.

When I wrote the ESP8266 OOK decoder, I didn't know what was inside the sensors, let alone have the datasheet for the HS1527 chip. I mistakenly treated the whole packet, including pre-amble and digital input state, as the sensor ID. Luckily the pre-amble is constant and the digital inputs never change for these simple sensors (they aren't connected to anything), so I got away with it.

Above is a packet from signal 1. received through the 433MHz receiver module. Even though it wasn't relevant to this project, I had a look at it anyway. At first glance it looks like it might use the same on-off keying scheme as the sensors in my alarm system, but it is actually encoded using Manchester encoding from what I can tell. If you try interpreting this signal using the same coding scheme as the previous signal, you might notice that the length of time each apparent bit takes up is varying, i.e. pulses aren't padded predictably with gaps. This is odd and tends to indicate that the signal had been misinterpreted because data streams usually have a constant data rate.

These two captures show the typical noise which appeared on the 433MHz module's output when there was no transmission present. Though this noise is easily rejected in software because it doesn't conform to what an expected OOK signal looks like, all the edges unfortunately create a lot of interrupts for the ESP8266 to deal with. It did not cause problems, from what I could tell, but if it did, the number of edges could be reduced with a small capacitor between the data line and ground. The capacitor would need to be connected to the output via a resistor to increase charge/discharge times, and then the ESP8266's GPIO would have to be connected to the junction of the resistor and capacitor.

More noise - in more detail.

Above: the ESP8266 all connected up. The board at the top is a Zigbee CC2530 development board. I discovered it lying around and detached the Zigbee module to reduce power usage. It was perfect for the ESP8266 because it has a CP2102 USB-to-UART/serial converter on it, a beefy 3.3V rail and a 5V rail fed from USB power or the 3xAA battery pack on the bottom of it. Importantly, all the buttons, LEDs, UART pins and power rails come out to header pins so they were easy to connect over to my ESP8266 programming board. Also, it's hard to see in the photo, but I added a decoupling capacitor between the ESP8266's VCC and GND, anecdotally it seemed to improve it's accuracy when flashing and stability in general. I did this because a few things made me suspicious that my programs were getting corrupted when I flashed them.

The grey wire and green wire left dangling grounded RST. A reset button on the programming board would have been much better. Also, another annoyance was having to disconnect the 433MHz receiver every time the ESP8266 was reset or turned on (I've mentioned this quite a few times now), hence the connection via an orange jumper and blue jumper, which made for easier disconnection.

The circuit above didn't work well though: the 433MHz receiver module's reception range was atrociously short because I was powering it off 3.3v so that I wouldn't have to level-shift its output for the ESP8266 and then have two power rails if I made a dedicated board for this project in future. The 433MHz receiver is designed to be powered off 5V, but I tested it briefly with 3.3V on the oscilloscope and it seemed fine. Granted, it was being tested with a nearby sensor. Later, I switched to powering it off 5V and used a voltage divider on the output; this improved reception dramatically, as expected. 

Results/final product:

On startup, or when there are no triggered sensors:

When sensors have been triggered:

The source code for this project is here and could be useful for finding examples of how to use the ESP8266's interrupts and create a simple HTTP web server on it. The ESP8266's SDK is slightly thin on coherent documentation, but looking through other people's code and the SDK's header files is enough information to figure most things out.

Other than that, be mindful of clearing dynamic memory as soon as possible, and be careful where your pointers point before passing them to functions or otherwise using them. If you try writing or reading from a memory address which is obviously invalid e.g. outside dynamic memory, the ESP8266 tends to catch it and print some debugging information then reset itself, like this: 

Fatal exception (29):
epc1=0x400127f0, epc2=0x00000000, epc3=0x00000000, excvaddr=0x00000064, depc=0x00000000

Exception 29 indicates that the program tried to write to a prohibited memory address. This is similar to exception 28, which results from trying to read a prohibited address. In this particular exception, the program instruction at 0x400127f0 has tried to write to 0x00000064... epc1 and excvaddr are the two most important details.

The rest of my run time debugging consisted of os_printf statements and commenting out lines.

An important consideration when working with the ESP8266 is that it has a whole bunch of internal registers which aren't changed when you flash a program to it. For example, WiFi credentials, station mode and whether to automatically reconnect to WiFi. These are modified at run time by making calls in your program to functions such as wifi_station_set_auto_connect(1);. If you are not explicit in setting or disabling settings such as this in your program, expect undefined and inconsistent behavior. It also appears that sometimes these persistent internal registers get corrupted or misconfigured, which apparently results in the alarming but not-always-fatal error MEMCHECK FAIL!!! being printed. I found that I stopped getting this message after upgrading the version of the SDK I was using.

It felt like there were endless traps and general weirdness with the ESP8266, but it is possible to do useful stuff using it and its SDK. 

Friday 5 February 2016

LED Night Lamp

A bit more of an artistic project I had a play round with to make use of a 10W LED and driver combo I got off eBay. The files are here: http://www.thingiverse.com/thing:1318807

The bottom piece was designed in OpenSCAD, the top piece - the "box" - was designed in Blender because it was originally meant to have complex patterns on the inside which would only be visible when lit.

Finished product:

The 10W LED is still surprisingly bright when covered, the colour output of the LED is white but the plastic box unfortunately yellows the light a lot.


Make sure you fit the hidden captive M3 nuts on the back side of those screw holes. This can be done by poking a long M3 screw through the hole, threading a nut on and then pulling the nut down into the nut trap. If the nut trap is undersized, heat the nut up with a soldering iron then quickly pull it into the nut trap.
Small 12v switch mode PSU on the left with a ~0.8A current limiter integrated which is important for powering LEDs and not having them burn up due to their non-ohmic behavior. The quality of the PSU isn't bad either, there's filtering and proper separation between high and low voltage, I didn't inspect the transformer's wingdings but I hope it's of the same quality inside as the rest of the board. The LED "chip" is on the right.

The makeshift bending process for the dual aluminium heat sink/outer casing. Had to keep that protective wrap on till the end so it didn't scratch! Those edges where the wrapping is curling up did get scratched though - turns out toothpaste is a scarily effective (and still pretty coarse) abrasive if you don't have anything else to polish with.

Oops... something went wrong, but this wasn't totally unexpected given the tooling. More important than the total height of the base or the slightly mis-matched bend radii was getting the sides level so I cut down the left side.
Here it is drilled and re-sized through much chiseling, and sanding. The amount I had to take off was a bit small for a hacksaw, too large to sand and don't even think about grinding aluminium!

Make the aluminium outer first and see how it comes out then modify the printed base to suit the errors - that's why it's parametric. In this case the radius has been made larger on one side and the overall height reduced. Some gaps are visible, and the aluminium isn't flat in the middle so viewing distance, angle and favourable lighting are late additions to the BOM.

All wired up, the PSU is hot glued to the base because it has no mounting holes anyway. Since the device runs on 240V mains and has exposed metal, a three pronged plug and lead with earth are crucial - even more crucial is to connect the ground lead to the metal casing: note the bottom right screw pillar which was made slightly shorter to allow room for a connection. I'm not an electrician or nuanced in electrical codes - so wiring is your own responsibility and most LED drivers from China aren't certified either. I don't leave this light running for long periods of time unattended.