Saturday, January 28, 2012

Interfacing Sensirion temperature / humidity sensors to the XBee using 'bit-banging' technique

This article documents an technique to interfacing digital sensors such as the Sensirion SHT21 to an XBee Series 2 radio module running ZigBee end device firmware without using any additional hardware components. The same technique should apply to similar digital sensors.

The XBee Series 2 radio modules from Digi are ideal for developing prototype and low production run wireless sensors. The modules are pre-certified in US, Europe and other regions eliminating the requirement to under go RF certification. In addition to a UART port for communications the XBee module also provides  several IO lines which can be configured in digital input, digital output, ADC and PWM modes.

Digi provide several protocol options including DigiMesh (their own proprietary protocol) and ZigBee. For interoperability with other vendors equipment and security I use ZigBee. Each protocol family has several firmware options which can be loaded on the module: usually Coordinator, Router and End Device.  For battery powered applications the End Device firmware must be used to achieve reasonable battery life. In this mode the module spends most of its time asleep using less than 1µA current. Periodically it will wake (briefly consuming 15 - 40mA), query its parent router to see if there are any waiting packets. If there are none it will go back to sleep. In this mode a set of AAA batteries can last a year or more.

Analog sensors can be coupled to XBee IO lines in ADC mode (10 bits resolution ranging from 0 - 1.2V) and read remotely using Digi's remote sampling API. However analog sensors will probably require signal conditioning circuitry (op amps, filters etc) to make the best use of the ADC voltage range. This conditioning circuitry is likely to require individual calibration to achieve good results.
The headache of analog design can be conveniently side-stepped by using a sensor with a digital IO interface. Sensirion produce a range of temperature / humidity sensors with an impressive resolution (14 bit temperature, 12 bit humidity) and accuracy (less than ±0.3°C for some versions). These sensors talk I2C protocol (or a variant of I2C). But there is a catch: the XBee firmware from Digi does not directly support I2C.

A common solution is to use a low cost MCU as a bridge between the sensor and the XBee's UART. The MCU waits for a command from the network (via the XBee's UART), queries the sensor and relays the result back to the UART for transmission on the ZigBee network. By monitoring the XBee's SLEEP pin the MCU can also spend most of its time in a low power sleep, waking only when the XBee wakes.
There is an alternative... a common solution in a situation where there is no direct hardware support for a serial protocol: "bit banging". Each of the XBee's IO lines can be set high, low or in high impedance (input) state by remote control: everything needed to realize the I2C and SPI protocols. But there is a catch: unlike a MCU bit banging its own IO lines which can happen at clock speeds exceeding 100kHz, remotely bit banging XBee IO lines is a very slow process. Fortunately the Sensirion sensor datasheets do not place any lower bound on the protocol clock speed. So a clocking speed of just 1Hz will work just as well as 100kHz... it's just going to take a while to complete a query.
This is what the test setup looks like. A SHT75 sensor is at the bottom of the photo. A XBee board (the Grove XBee Carrier board from SeeedStudio) with DIO1 and DIO2 connected to the SHT75 clock and data pins respectively. The 3.3V power supply from the board powers the sensor.




Implemenation details:

The details of how to communicate with an XBee module are beyond the scope of this article. A good introduction to the topic is the book Building Wireless Sensor Networks from O’Reilly.

It’s important to note that the I2C protocol requires pull-up resistors on the clock and data lines. Fortunately the XBee has configurable 30k internal pull-up resistors for digital inputs which are configured with the ATPR command. They are enabled by default.

A sensor query begins by first issuing a remote ATIR command to the XBee to initiate frequent IO sampling. I found 100ms sampling period gave good results. ATIR must be followed by ATAC (commit) for the change to take effect. The sampling will keep the (normally sleeping) XBee awake. Also each time a sample packet is transmitted the acknowledgement will let the XBee know if there are incoming packets waiting for it. So it helps keep packet latency relatively low. Samples packets comprise the high/low state of any digital input pins and the ADC value of any pins configured as ADC.

Before proceeding to the next step wait until the first sample arrives. This may take several seconds depending on where the XBee is in its sleep cycle. When it starts to transmit samples you know it is awake and will stay awake.

Now issue ATD commands to set the clock and data lines into the necessary state. For example, ATD13 sets DIO1 into input (or high impedance) state, ATD14 sets DIO1 into a output low (0V) and ATD15 sets DIO1 into output high (3.3V). Any ATD command will need to be followed by an ATAC before the change will take effect.

To read a data line during the read phase of an I2C conversation, wait for a IO sample to arrive after the low-to-high transition of the clock.

Finally when the I2C conversation is complete set ATIR=0 to stop automatic sampling, followed by ATAC. On reception the XBee should go back to sleep.

I found that a short delay (about 100ms) between each AT command was required for acceptable results.

This is an oscilloscope trace of a temperature query of a SHT75 sensor. The SHT7x and SHT1x range use a protocol similar to, but not compatible with I2C. Note the time base is 3.7 seconds per division! The clock is in green, the data in yellow. The blue trace is connected to a third pin which was used for debugging (to help separate out parts of the conversation). Here it is set high while writing out the command (0x03, read temperature) .


The SHT21 and SHT25 are more recent temperature / humidity sensors from Sensirion. These sensors use standard I2C protocol. This trace is a I2C read temperature query to a SHT21 sensor:



Some results:

This is a chart of a few hours of data from a SHT21 and SHT75 connected to an XBee using this technique. For comparison a third wireless sensor, a Digi XS-Z16-CB2R sensor is included. All three sensors were enclosed in an insulated polystyrene box to ensure that all sensors were reading the same temperature and humidity. I've also included the supply voltage (a handy feature of the XBee modules).


One slightly disappointing result is that there are quite a few failed queries (compare the number of  CB2R samples in blue to those in red and green). It seems that with my current implementation of this bit banging technique the success rate is about 66%. I believe this can be significantly improved with some changes to the implementation.

Conclusion:

Bit banging serial IO protocols such as I2C, SPI with the XBee IO lines under remote control is feasible. A battery powered sensor unit can be constructed with nothing more than a XBee, a digital sensor (and some means to physically link the sensor to the XBee), a battery holder and suitable housing.  No other components are required.

However there are some down sides: it takes a long time to make a measurement (10 - 30 seconds). During this period the XBee is awake (consuming 15 - 30mA). This is not a problem if  AA or AAA cells are used and the measurements are infrequent (eg once per hour). As currently implemented, reliability is far from perfect (2 out of 3 queries succeed) however I believe this can be improved with some tweaks to the implementation. Also over 100 ZigBee packets are required to complete one measurement: this could be a problem on a congested network.

Code:

Unfortunately the test setup is too complex to package up a simple self contained ZIP file to implement this technique. However here is the source code of the main Java class file which implements the necessary XBee AT commands.

/**
 * Implement temperature and humidity queries to a SHT71 and SHT75 sensor by bit banging
 * XBee IO lines. 
 * 
 * @author Joe Desbonnet, jdesbonnet@gmail.com
 *
 */
public class SHT7x {

 public static final int CMD_TEMPERATURE_READ = 0x03;
 public static final int CMD_HUMIDITY_READ = 0x05;
 
 private XBeeSeries2 xbee;
 
 private int clockPin;
 private int dataPin;
 
 // delay=100, sampleRate=250 does not work
 // delay=150, sampleRate=250 does not work (reliably)
 // delay=200, sampleRate=250 works
 // delay=180, sampleRate=50 works
 // delay=180, sampleRate=100 works

 // delay between sending each packet to the NIC for transmission
 private int delay = 180;
 
 // ms between each sample
 private int sampleRate = 100;

 /**
  * 
  * @param xbee XBee proxy object
  * @param clockPin XBee pin used to implement SCK (0 = DIO0, 1 = DIO1 etc)
  * @param dataPin XBee pin used to implement SDA (0 = DIO0 .. etc)
  */
 public SHT7x(XBeeSeries2 xbee, int clockPin, int dataPin) {
  this.xbee = xbee;
  this.clockPin = clockPin;
  this.dataPin = dataPin;
 }

 /**
  * Implement short delay. Usually used to space ZigBee packets apart.
  */
 private void delay() {
  try {
   Thread.sleep(delay);
  } catch (InterruptedException e) {
   // TODO Auto-generated catch block
   e.printStackTrace();
  }
 }

 private void clockHigh() throws IOException {
  byte[] param = new byte[1];
  param[0] = XBeeSeries2.HIGH;
  xbee.atCommand("D" + clockPin, param);
  xbee.atCommand("AC");
  delay();
 }

 private void clockLow() throws IOException {
  byte[] param = new byte[1];
  param[0] = XBeeSeries2.LOW;
  xbee.atCommand("D" + clockPin, param);
  xbee.atCommand("AC");
  delay();
 }

 private void dataHigh() throws IOException {
  byte[] param = new byte[1];
  // Data high is achieved by high impedance state (HIGH_Z) ie digital
  // input mode
  param[0] = XBeeSeries2.HIGH_Z;
  xbee.atCommand("D" + dataPin, param);
  xbee.atCommand("AC");
  delay();
 }

 private void dataLow() throws IOException {
  byte[] param = new byte[1];
  param[0] = XBeeSeries2.LOW;
  xbee.atCommand("D" + dataPin, param);
  xbee.atCommand("AC");
  delay();
 }

 /**
  * Reset communications with sensor. Required if the previous query did not complete.
  * 
  * @param xbee
  */
 public void resetComms() throws IOException {

  int i;

  dataHigh();

  // Pulse clock 9+ times while data high
  for (i = 0; i < 10; i++) {
   clockHigh();
   clockLow();
  }
 }

 /**
  * Start sequence.
  * 
  * @throws IOException
  * 
  */
 private void startSequence() throws IOException {
  clockHigh();
  dataLow();
  clockLow();
  clockHigh();
  dataHigh();
  clockLow();
 }
 
 /**
  * A command is 8 bits (MSB first) followed reading an ack bit from the device. 
  * In this implementation I ignore the result of the ack bit (but there
  * still must be a 9th clock pulse). Bits are written by setting the
  * data pin to either 0V (logic 0) or high impedance (logic 1). The data is
  * read by the sensor during a low to high transition of the clock signal.
  * 
  * @param command
  * @throws IOException
  */
 private void sendCommand (int command) throws IOException {
  int i;
  boolean lastBit = true;
  boolean currentBit = false;

  // MSB first
  for (i = 0; i < 8; i++) {
   currentBit = ((command & 0x80) != 0);

   // Only change data pin if there is a change. This reduces the number
   // of ZigBee packets transmitted.
   if (currentBit != lastBit) {
    if (currentBit) {
     dataHigh();
    } else {
     dataLow();
    }
    lastBit = currentBit;
   }

   // Pulse clock
   clockHigh();
   clockLow();
   command <<= 1;
  }

  // If data currenly low bring to high_z/input mode
  if (currentBit == false) {
   dataHigh();
  }

  // I don't bother to read the ACK bit, but the clock
  // must still be pulsed for it. 
  clockHigh();
  // don't bother sampling data pin -- will assume all ok
  clockLow();

 }

 /**
  * Reference datasheet §4.3.
  * @return Temperature in °C
  * @throws IOException
  */
 public float readTemperature() throws IOException {
  int v = makeReading(CMD_TEMPERATURE_READ);
  return -39.7f + 0.01f * (float) v;  
 }
 
 /**
  * Reference datasheet §4.1.
  * 
  * @return Humidity as RH%
  * @throws IOException
  */
 public float readHumidity() throws IOException {
  int v = makeReading(CMD_HUMIDITY_READ);
  return (float)(-2.0468 + 0.0367*(double)v - 1.5955e-6*(double)v*(double)v);
 }
 
 /**
  * Reset comms, send start sequence, command and read 16 bits of data.
  * 
  * @param what One of SHT7x.CMD_TEMPERATURE_READ or SHT7x.CMD_HUMIDITY_READ
  * @return
  * @throws IOException
  */
 private int makeReading (int what) throws IOException {

  int i;

  //
  // Configure XBee to send frequent IO samples. This has two important functions.
  // First it keeps the XBee end device awake. Also an end device transmitting a 
  // packet (which must go via its parent) has the side effect of polling the 
  // parent for any incoming packets. So frequent transmission also means low
  // latency in receiving packets.
  
  byte[] params2 = new byte[2];
  params2[0] = (byte) (sampleRate >> 8);
  params2[1] = (byte) (sampleRate & 0xff);
  xbee.atCommand("IR", params2);
  xbee.atCommand("AC");
  
  // Now wait for the first sample to arrive before proceeding. At this point we'll
  // know the end device awake.
  long t0 = System.currentTimeMillis();
  while (xbee.getLastIOSampleTime() < t0) {
   delay();
  }
  
  // XBee End Device should now be awake and responsive

  // The last query may not have completed leaving the communications in an undefined
  // state. Reset communications to a known state.
  resetComms();
  delay();
  delay();

  // Start sequence ref §3.2.
  startSequence();
  delay();
  delay();

  // Send 8 bit command (and read ack bit)
  sendCommand(what);

  // Wait for measurement to complete. We could poll the DATA line. It will be pulled
  // low by the sensor when the reading is complete. However the overhead of doing
  // this makes it not worth the effort.
  try {
   Thread.sleep(200);
  } catch (InterruptedException e1) {
   // ignore
  }

  //
  // Read 16 bits
  //

  int v = 0, sample;
  for (i = 0; i < 16; i++) {

   v <<= 1;

   clockHigh();

   // wait for IO sample
   t0 = System.currentTimeMillis();
   while (xbee.getLastIOSampleTime() < t0) {
    try {
     Thread.sleep(100);
    } catch (InterruptedException e) {
     // ignore
    }
   }
   // sample = xbee.getIOSample();
   sample = xbee.getLastIOSample();
   if ((sample & 0x04) != 0) {
    v |= 1;
   }
   clockLow();

   // write ack bit
   if (i == 7 || i == 15) {
    // write ack bit (0)
    dataLow();
    clockHigh();
    clockLow();
    dataHigh();
   }
  }


  //
  // Return end device to normal sleep pattern by disabling sampling (ie by
  // setting sample period to 0).
  //
  params2 = new byte[2];
  params2[0] = 0;
  params2[1] = 0;
  xbee.atCommand("IR", params2);
  xbee.atCommand("AC");

  return v;

 }

}

Thursday, December 15, 2011

LED killed the fairy light

I noticed something while visiting my local hardware store today: they had no incandescent fairy lights this year. None at all. All LED. It's sad to see such an old technology die.

The "fairy light" is 129 years old this year. Its etymology comes from the Gilbert and Sullivan operetta Iolanthe which opened on 25 November 1882. The story is about fairies and someone had the bright idea of festooning some of the principle fairies with miniature light bulbs powered by a small battery. Apparently it caused quite a sensation and the term "fairy light" came into common use ever since.

Installing Cadsoft Eagle 6 on Ubuntu 10.4

Update (19 Dec 2014): Just reviewing old blog posts. This article is now way out of date. Please ignore.

Update (5 Mar 2012): I've just been informed that libpng-1.4.8 has been replaced by libpng-1.4.9 and removed from ftp.simplesystems.org. I'll update the script in the next few days. In the mean time, please edit the script to change the version of libpng.

The recently released Eagle 6 from Cadsoft has a dependency on libpng 1.4, libjpeg 8, libcrypt 1.0, libssl 1.0. My Ubuntu 10.4 distribution has older versions of these libraries which Eagle 6 does not like.

There are a few posts on how to fix this, eg this one. However it relies on some helpful person's pre-built binaries. While I'm sure it's perfectly good, I'm just a little too paranoid to copy and execute code from an unknown source. So I set about downloading and building these libraries from source. I've written up the procedure as a bash script in case someone as paranoid as myself would like to do the same.

This script will build these libraries from source, and copy them to a directory specifically for use with Eagle.

This is the procedure:
  1. First create an empty directory. Copy this script into it. Next open the script with an editor.
  2. Set EAGLE_DEPS_DIR to a location where you want the libraries to be copied to after building (eg /home/joe/libeagle in my case)
  3. I recommend that you verify the download locations of the library sources before you run this script. This is security best practice: it's generally not a good idea to download and execute code from an untrusted source. Once you are happy with the download URLs, then set I_HAVE_VERIFIED_DOWNLOAD_LOCATIONS=true
  4. Save and run the script. If it's your first time running it, it will download the sources. Subsequent runs wont waste bandwidth if they are already there. Building the libraries will take a few minutes.
  5. Verify the contents of the directory defined in EAGLE_DEPS_DIR. It should have the following files:
    • libcrypto.so.1.0.0
    • libjpeg.so.8.3.0
    • libpng14.so.14.8.0
    • libssl.so.1.0.0
    and these symbolic links:
    • libjpeg.so.8 -> /home/joe/libeagle/libjpeg.so.8.3.0
    • libpng14.so.14 -> /home/joe/libeagle/libpng14.so.14.8.0
  6. Before running the Eagle installer or the Eagle software set environment variable LD_LIBRARY_PATH to include this directory (eg export LD_LIBRARY_PATH=/home/joe/libeagle )
Enjoy!

Update (30 Jan 2012): I received some improvements to the script from Leandro (wisleca@gmail.com).  Revision 2 is here.

Tuesday, December 13, 2011

Time tagging data streams using a short bash script

I've often had to write Arduino sketches and small PIC programs to read data from sensors for transmission to a PC over the serial link. This is mostly for testing and evaluating sensors in the lab – so I don't want to waste any more time than I have to writing software.  

A common requirement is to have each sensor record time tagged. However implementing a real time clock on a Arduino, PIC etc can be a lot of hassle. It makes more sense to tag the record on the PC as it comes in.

Here is a short bash shell script that runs on a Linux PC which reads data from an input stream (usually the serial port) line by line and prefixes each line with the unix epoch time (seconds since 1 Jan 1970):

#!/bin/bash
while read line ; do
    echo `date +%s` $line
done


So, for example, right now I have an Arduino sketch polling a SHT75 temperature humidity sensor. The Arduino is writing a record (about 1 every second) with the current temperature and humidity. The data looks like this:

19.70 49.22
19.70 49.38
19.68 49.38
19.66 49.22


This is coming in on /dev/ttyUSB2 in this case. The above script is in file timetag.sh. On the PC I do:

cat /dev/ttyUSB2 | bash timetag.sh > sensor.log

sensor.log looks like this:

1323808719 19.70 49.22
1323808720 19.70 49.38
1323808721 19.68 49.38
1323808723 19.66 49.22


The data can now be plotted with the insanely useful gnuplot utility like this:


$ gnuplot

set title "Temperature and Relative Humidity from SHT75 sensor"
set grid
set xlabel "Time"
set ylabel "Temperature (C) / Relative Humidity (%)"
set xdata time          # X axis is time
set timefmt "%s"     # Input file time format is unix epoc time
set format x "%R"   # Display time in 24 hour notation on the X axis
plot 'sensor.log' using 1:2 title 'T', 'sensor.log' using 1:3 title 'RH'

If you don't like the unix time notation, you can use alternative notations. See the manual page for the unix date command to get alternative time formats. You'll need to match the date format with the gnuplot 'set timefmt' command (help timefmt inside gnuplot will display available formats). For plots spanning more than one day you'll need to adjust time labeling with 'set format x' command (help set format time_specifier will provide information on this).

Sunday, December 11, 2011

Quick and dandy BusPirate breadboard connector

I have found that the probe kit that I had ordered from SeeedStudio with my BusPirate last year wasn't as useful as I had hoped. The quality of the the probe connectors wasn't great and having 10 probes in a bunch when all you need is 4 can be cumbersome. I can't complain – the probe kit was only $5.

I needed to check out a I2C temperature/humidity sensor with the BusPirate on a breadboard. So hacked this connector comprising a short length of 10 wire ribbon cable, a 5x2 IDE IDC connector to mate with the BusPirate IO header at one end and an IC socket at the other end.

The IC socket can then be plugged into the breadboard. I don't expect this connector to last forever, but it's certainly neater and easier to use than the probe kit.



Friday, November 25, 2011

A water drop microscope with a smart phone camera

I took this photo of a HTC Desire AMOLED display a while ago with a USB microscope. It has insufficient resolution to see the individual sub pixels clearly. But by adding a drop of water onto the display glass, the subpixels come into clear view.

Which got me thinking: what if I put a drop of water onto the camera window: would the camera become a microcope?

And indeed it does. The quality isn't great, but you can certainly see things that are not visible with the naked eye.

The trick is to ensure that the drop is small enough so that the entire surface is curved. A large drop will have a mostly flat surface and will not act as a lens. Objects will come into focus at a very short distance from the water drop lens – about 5mm.


Using the front facing camera on some of the newer camera models (eg the Galaxy Nexus) has the benefit that you can easily illuminate the subject while viewing the display. The normal camera can be trickier to get good photos because the camera blocks the light needed for a good exposure. The flash doesn't work very well because the subject is so close to the lens. If the subject matter is semi-transparent (eg a leaf) you can try back lighting it. When finished dry and clean the camera window with a soft cloth.


Here are some photos I took (some with the Galaxy Nexus, some with Galaxy Tab).

A microscopic photo of a HTC Magic LCD display through the water drop.
Holly leaf back lit with a desk lamp.

The pins of a TQFP IC package. The width of the pin is 0.22mm.

A holly leaf back lit by a desk lamp.
Finger tip.
Leaf, back lit by a desk lamp. Cropped and some sharpening applied.
PCB vias.

If you have any suggestions on how to improve the quality of the images or would like to share some of your microscope images, please leave a comment at the end of this post. More photos from this online album.

Updates:

27 Nov 2011: It occurred to me that oil has a higher refractive index than water (1.47 for vegetable oil vs 1.33 for water) which might result in better magnification. I'll try it at some point, but it will certainly be messier to clean up.

Monday, November 14, 2011

Roaming bandwidth in Spain with the Samsung Galaxy Tab

There seems no end in sight to the roaming ripoff in Europe. While the situation is improving slowly it's still ridiculous.

At the moment I'm on holiday in the Canary Islands (part of Spain). Before travelling I checked with my operator (Vodafone IE) re data roaming packages. The best they could do is €12 per day for 50MB after which €1 is charged per additional MB (down from €5/MB a few years ago!). I need about 100MB per day so that's €12 for the first 50MB and €50 for the next 50MB: a total of €62 per day, or €620 for a 10 day trip! Added to this is the complication that between myself and the wife we have 6 WiFi devices (2 x laptops, 2 x smartphones, a Kindle and an Android tablet). Our hotel WiFi is locked to a single device, is expensive, slow (128kbps?!), and is only accessible in certain areas (not our rooms).

I found a nice solution. I purchased a pay-as-you-go SIM from a local Vodafone shop (a €9 once off charge and you'll need photo ID to make that purchase). I then applied a data top-up to that SIM: €15 for 1GB lasting one week or €20 for 1GB lasting up to a month. I inserted the SIM into my Samsung Galaxy Tab 10.1v and started the portable hotspot mode (under Settings -> Wireless & networking -> Tethering and portable hotspot -> Portable WiFi Hotspot). Bingo: a portable WiFi hotspot good for up to 5 devices.

The Galaxy Tab's battery life is sufficient that I can carry the tablet in my day bag and have enough power to keep all our devices continuously connected no matter where we were. The Galaxy Tab has security disabled by default: I'd strongly recommend enabling security to prevent others nearby gaining unauthorized access to precious bandwidth (the mobile network here is good... it won't take long to download a full 1GB).

There are still a few problems (which I'm sure there are good solutions out there): Android 3.0 (Honeycomb) installed on the Galaxy Tab 10.1v (aka GT-P7100) seems to have no ability to report current or historical bandwidth use. I believe this has been addressed in Android 4.0 which I eagerly look forward to. Also I haven't figured out how to get remaining data credit on my SIM. And finally Windows 7: in the first few minutes after connecting it seems to go through megabytes per minute: presumably software updaters phoning home, Facebook, GMail brower windows fetching updates etc. It seems to settle down to a  reasonable rate after a minute when about 5MB is consumed.

Monday, October 31, 2011

Weather radar animation

Here is a short weather radar animation I made of a 24 hour period last week (24 October 2011) when the east coast of Ireland (Dublin city in particular) was deluged with an exceptionally heavy rain storm.


I made this video clip starting with a directory of individual radar images in PNG format and then used the mencoder tool (part of mplayer) to generate a MP4 video clip from the frames.

mencoder mf://*.png -mf fps=5:type=png -ovc lavc -lavcopts vcodec=mpeg4 -o video.mp4

Note that I've set the frames per second to 5 (fps=5). The usual 24 frames per second is too fast.

Images from Met Éireann (the Irish Meteorology service). 

Friday, October 21, 2011

Horologium Romanum


Despite the Roman Empire’s  sanitation, medicine, education, wine, public order, irrigation, roads, fresh-water system, and public health: they never had a decent digital clock. Well here it is!

The following video shows the clock in action. The clock is speeded up by over x100 so you can see a full 12 hours in just a minute or two.


The project uses only components that were available in the 1970s, 74 series chips, LEDs and resistors and a 50Hz or 60Hz clock signal.

There is plenty of support in the 74 series logic chips for driving 7 segment displays and Nixie tubes. But it seems nobody ever thought about developing a roman numeral driver chip! (I wonder why :)

I’ve broken this project into several segments and will describe each in turn:

  • Derive a pulse per second from clock source (divide by 50 or 60)
  • Count 60 seconds to obtain minutes
  • Count minutes and display in roman numberals (0 to 59)
  • Count hours and display in roman numerals (1 to 12)

For the display I opted to make the roman numeral letters using LEDs out on a breadboard: crude but adequate to illustrate the logic in action. If there was no limit to my time or budget I would have liked to have crafted something more elaborate.  Neon tubes would have been fantastic but way beyond my budget.

Decoding roman numerals

Before describing each segment, a little background on roman numerals. Here are numbers 1 - 60 laid out in a table.



On close inspection it can be seen it’s rather systematic. The important observations are this: we are only dealing with digits 0 - 59 and and the 0 - 9 of the 10s digit can be substituted for (blank),”X”,”XX”,”XXX”,”XL” and “L” respectfully, and the 0 - 9 of the units digit can be substituted for (blank),”I”,”II”,”III”,”IV”,”V”,”VI”,”VII”,”VIII”,”IX” respectfully.

For the units display I'm going to use 7 display elements which I'll call I3, X0, V0, I2, I1, I0. For the 10s display I will use 4 display elements: X3, X2, X1 and L0.

An important aid in decoding roman numberals is to be able to count in ‘decades’. For this reason I chose the 74390 decade/bi-quintary counter. So lets look at the truth table for the numbers 0 - 9 with the 74390 in bi-quintinary mode. Shown here is the Qn pins on the counter and the desired state of the display.

Q0Q3Q2Q1I3X0V0I2I1I0
00000000000
10001000011
20010000011
30011000111
40100101000
51000001000
61001001001
71010001011
81011001111
91100110000

By visual inspection the following rules for driving the display can be deduced:

I0 = Q1 OR Q2
I1 =  Q2
I2 = Q1 AND Q2
V0 = Q0 XOR Q3
X0 = Q0 AND Q3
I3 = Q3

And now the 10s display. The task is simplified because we're only interested in counting up to 59.

Q0Q3Q2Q1X3X2X1L0
000000000
100010010
200100110
300111110
401000011
510000001

L0 = Q3 OR Q0
X1 = Q1 OR Q2 OR Q3
X2 = Q2
X3 = Q1 AND Q2

Dividing 50Hz to get one pulse per second

For this project I'm using a timing signal derived from the mains frequency (although for prototyping I've been using an Arduino which allows me to experiment with various frequencies). In Europe and many other parts of the world this is 50Hz.  To get one pulse per second this signal will need to be divided by 50. This is easy with the 74390: either divide by 5 and then by 10, or vice versa. All else being equal I think it's a good idea to reduce the working frequency as soon as possible, therefore I opted to divide by 10 first, and then by 5.



Decade counter 1 (on the left) divides the incoming 50Hz signal by 10 bring the frequency down to 5Hz, and then decade counter 2 (on right) then divides it by 5 bring it to 1Hz.

You say tomato, I say tomato...

In the US the mains frequency is 60Hz which complicates things a little. Thankfully it's not difficult to fix. Connecting 2Q0 AND 2Q1 will cause decade counter 2 (on right) to reset at 6, thereby dividing the incoming frequency by 60.

Dividing seconds by 60 to get one pulse per minute

This is identical to the US mains 60Hz problem described in the previous section. I use a 74390 and tie the MR (reset) of decade counter 2 to 2Q0 AND 2Q1. The Q0 pin forms the input to the minutes counter.


The Minutes Counter and Display Decoder

This purpose of this segment is to count minutes from 0 to 59 and then roll over to 0. It also decodes the counter output pins (1Q0 to 3 and 2Q0 to 3) to drive a roman numeral display. Decade counter 1 (on left) counts minutes and decade counter 2 (on right) counts the 10s of minutes. The roll over at 60 is accomplished by feeding 2Q1 AND 2Q0 (ie 6 on the 10s counter) to the counter reset.

The Hours Counter and Display Decoder

The hours count from 1 - 12 which is a problem: the 74390 has no preset capability: it's always all 0s on reset. So how to get the clock to roll over to value 1?  This took a bit of thinking and I (eventually) came up with a solution that involved two D flip-flops (7474) in series.

The first D flip flop delays the falling edge of 2Q1 by one clock cycle and the second flip flop delays it by a further cycle. By ANDing  Q bar of the first flip flop with Q of the second flip flop I get a pulse which is delayed by one clock cycle. I then OR that to the input of the hours counter and it creates an second short pulse whenever the clock rolls over.. causing the clock to start at a count of 1.





Photos





Conclusion

This is really just a proof-of-concept which was prototyped using a breadboard. However the electronics checks out and could form the basis a more permanent project.  I had some ideas on ways of (safely) tapping into the mains 50/60Hz signal to get a high precision clocks signal. Modern power utility networks control the frequency to a very high degree of accuracy (if averaged over 24 hours) and is as good as typical quartz crystal solution. Unfortunately I've run out of time. I might follow this up in a separate post later if there is interest.

Also I ran out of time on the clock setting function, which is a pity, because it's important :) This was going to be implemented by feeding the pulse-per-minute output from the seconds counter together with the output of a microswitch to a XOR gate and then forwarding the output of the XOR gate to the minutes counter.  This would allow the user to manually advance the minutes with each key button press (there may be need to debounce the switch). Ditto for the hours counter.   An alternative approach is use a microswitch to feed the 50Hz clock signal to the seconds stage, advancing the clock at high speed while the switch is depressed.

The bill of material:
  • 4 x 74390
  • 4 x 7408 (AND gates)
  • 2 x 7486 (XOR gates)
  • 3 x 7432 (OR gates)
  • 1 x 7484 (D flipflops )
  • LEDs (lots, about 70 + limiting resistors)
  • 50Hz (or 60Hz) signal generator (using Arduino for prototype)
  • 5V power supply (using Arduino 5V power supply)



The Arduino sketch to generate the 50Hz signal is:

void setup() {               
  pinMode(13, OUTPUT);    
}
void loop() {
  digitalWrite(13, HIGH);   // set the LED on
  delay(10);
  digitalWrite(13, LOW);    // set the LED off
  delay(10);
}






Wednesday, September 28, 2011

Controlling XBee IO lines with ZigBee commands

Summary: this post explains to send remote AT commands to an XBee module using ZigBee commands. The end point, profile ID, cluster ID and command and response format is documented. This is useful if you need to control a XBee's IO lines from a non-XBee device.
The Digi XBee Series 2 module. 
The Digi XBee module is a popular RF module featuring a UART and several IO lines that can be configured as input/output or in some cases ADC or PWM. The "Series 2" version of these modules can be flashed with ZigBee compatible firmware. The modules then participate in a ZigBee mesh network.

The modules are configured and controlled by  AT commands (like the modems of old) which are issued through the module's UART by a computer or microcontroller. There are two varieties of the firmware: AT mode and API mode. The latter provides access to more advanced features of the module.

One feature of the API mode is the ability to send AT commands to remote XBee modules. This can be used to control relays, read the state of switches, read temperature etc. This in theory allows many small control applications to be accomplished with nothing more than a XBee module on it's own -- no need for an attached microcontroller.

There is a problem however. All the examples and documentation I've seen to date (I may very well have missed something, but not for the lack of trying) only cover controlling a XBee module through the XBee API. So if your interface to the ZigBee network is not a XBee (or equivalent Digi product) you're out of luck. In my case I have a Texas Instruments CC2530 based USB dongle and I require that software running on a computer control the state of a remote XBee's IO lines.

So, reverse engineering time! I wrote scripts to control the XBee digital IO lines from another XBee, and simultaneously ran a 802.15.4 packet sniffer. This is what I learned:

For remote AT commands the sending XBee issues a ZigBee command to the remote XBee on endpoint 230 (0xE6), with profile 0xC105 (Digi private profile), cluster 0x0021. The command is formatted as follows:

0x00, 0x32, 0x00, frame-id, sender-ieee-addr, 0x00, 0x00, atcmd0, atcmd1, [param]

frame-id: one byte API frame ID used in many XBee API calls
sender-ieee-addr: the 64 bit IEEE address (8 bytes, the most significant byte first)
atcmd0: the ASCII code of first character of the AT command (eg 0x4d or 'M' if command is ATMY)
atcmd1: the ASCII code of the second char of the AT command (eg ...)
params: zero, one or more optional parameter bytes

For example: to send ATMY (get 16 bit network address) the command will be:
0x00, 0x32, 0x00, 0x0f, 0x00, 0x13, 0xa2, 0x00, 0x40, 0x3c, 0x15, 0x5c, 0x00, 0x00, 0x4d, 0x59

The bytes with values 0x00 and 0x32 may have some significance, but I have no idea what it might be. I'm not sure if the sender IEEE address is important. It seems to work the same no mater what address I use.

Responses are sent on cluster 0x00a1. In response to the ATMY command I get:
0x0f, 0x4d, 0x59, 0x00, 0xd1, 0xed

So that seems to be:
frame-id, atcmd0, atcmd1, 0x00, atresponse...

The XBee digital IO lines are configured with the ATDn commands, where 'n' is the number of the IO line. The lines can be configured as input, output low, output high, analog and PWM (not all lines are capable of all the functions). The two functions that are of interest to me are output high (parameter value 5) and output low (parameter value 4).

Note: there is potential for some confusion encoding AT commands. Take for example the command ATD04. What this means is AT command D0 with parameter 4.  The digit 0 in the command part is encoded as the ASCII code ie 0x30, but the digit in the parameter part (4) is the byte value 0x04. So the command and parameters is encoded as 0x44, 0x30, 0x04.

Other clusters in endpoint 230 have other functions, which I'll document in another post.

Update (2 Oct 2011): Small edit. I omitted the frame-id in the format of the AT command response.