Category Archives: Electronics

Macro Photography Using a Macro Coupler

Sometimes I want to take closeup photos of printed circuit boards to e.g. document broken or incorrectly assembled components. Given the small size of many components (like 0402 or even 0201), a high degree of magnification is often required. I have a 150 mm Sigma macro lens that can do 1:1 magnification from subject to detector, but this is not always good enough, so I was looking for another solution, preferably going up to a magnification of about 6:1 so that a 4 mm subject would fill up the view of my Nikon D300. Also, I did not want to spend too much on new equipment as this is something I do not do very often.

It turns out that there are a number of ways to make existing lenses more suited for macro photography, namely:

  • extension tubes (reduces minimum focus distance)
  • macro bellows (essentially long and adjustable extension tubes)
  • close-up lenses to put on the front of existing non-macro lenses
  • reversing rings to mount lenses backwards
  • macro couplers to mount one lens backwards in front of another

I did some quick calculations (using information from this page) and figured out that extension tubes or bellows would not give me much of additional magnification. They need to be very long to have much of an effect on long lenses and with shorter lenses the focus distance for large magnification becomes very small.

According to a formula on https://en.wikipedia.org/wiki/Close-up_filter, a close-up lens (or close-up filter as it is also called) needs to have a power of 20 diopters to give a 6:1 magnification on my 70-300 mm lens and 33 diopters on my 150 mm macro lens. The problem is that such strong close-up lenses seem to be rare and if they exist they are probably not very sharp.

To get magnification from mounting a single lens in reverse, the focal length needs to be short. From a 50 mm lens, the expected magnification is probably only about 1:1, so this did also not seem like a very good option.

The solution I opted for was instead to use a macro coupler to mount my 50 mm f/1.4 lens backwards in front of my 70-300 mm zoom. A 50 mm lens has a power measured in diopters of 1/(0.050 m) = 20 diopters, so it will act as a close-up lens that powerful, giving a magnification at the 300 mm setting of about (300/50):1 or 6:1.

Since the 50 mm lens has a 58 mm thread and the zoom has a 67 mm thread, I needed a step-up ring from 58 to 67 mm and a 67-67 mm macro coupler ring. I found inexpensive ones at a local Internet shop, http://kaffebrus.com/step-up-ringar-121.html and http://kaffebrus.com/adapterring-koppling-122.html. Total cost was 147 kr or about $18.

This is what it looked like when I used the rings to mount the short lens in backwards in front of the zoom lens on the camera:

50 mm lens in front of 70-300 mm lens.
50 mm lens in front of 70-300 mm lens.
50 mm lens in front of 70-300 mm lens.
50 mm lens in front of 70-300 mm lens.

DSC_6707_sm

One thing that immediately becomes apparent when looking into the viewfinder is how dark it is. This is due to the fact that the 50 mm lens goes to minimum aperture when it is not connected to a camera, so it lets in very little light. There is no aperture ring on this lens, but there is a small lever in the mount that one can manually pull to increase the aperture and I found that it is possible to put a piece of tape on the lever to fix it in a desired position. Small aperture is good to get maximum depth of field, but it can be hard to see the subject unless the lighting is very bright, so taping the lever to maximum aperture while composing the scene and then removing the tape before taking the shot might be a good idea.

Aperture lever
Aperture lever

The aperture of the zoom lens seems to not be very critical, but it should be open enough to not cause vignetting. Also, zooming out far away from 300 mm causes vignetting, so the setup is mostly useful at or close to 300 mm.

It is of course necessary to use a tripod and in order to get as sharp photos as possible, one needs to take every reasonable step to reduce vibrations, like using a remote shutter release cord and the mirror-up mode so that the mirror does not cause camera shake.

A future improvement would be to build a focusing rail and apply focus stacking to get a greater depth of field. Building a stepper motor controlled focusing rail could be a fun project.

Below are some test photos I have taken with the setup.

Millimeter lines on the scale of a caliper. The field of view is about 4 mm wide.
Millimeter lines on the scale of a caliper. The field of view is about 4 mm wide.
Detail from a 100 kr bill.
Detail from a 100 kr bill.
An 0603 inductor and an 0402 capacitor.
An 0603 inductor and an 0402 capacitor.
An integrated circuit I made around 1995 as a project at the university. The technology is 0.8 µm CMOS.
An integrated circuit I made around 1995 as a project at the university. The technology is 0.8 µm CMOS.
Pins of a TQFP package, of which one is broken and another is damaged.
Pins of a TQFP package (0.5 mm pitch), of which one is broken and another is damaged.
Detail from a flower.
Detail from a flower.
Two 0603 resistors.
Two 0603 resistors.
A SOT23 component.
An SOT23 component.

 

Sportident Station Reports Unrealistic Voltage

Today I discovered that one of the SI BSF8 stations (serial number 111515) on which I updated the firmware to 6.23 a few weeks ago was behaving strangely and that the voltage reported by SI Config was 1.64 V. With such a low voltage, the processor can hardly run and the beeper will probably not beep, at least not as loudly as it did.

This all sounds like a known bug that sometimes occurs after updating to 6.23. The Sportident release notes for firmware 6.23 says:

Very low battery voltage indicated
After booting to firmware 623, it can happen in rare cases that the device indicates a very low battery voltage. Config+ will show “(invalid)” for the battery voltage. This is a measurement error of the station. As a workaround, you should use the “Factory reset” command in Config+. This will reset the device and should fix the voltage value.

I opened the unit up and found that the sleeve of the battery was cracked, but that there were no visible signs of corrosion or other problems, unlike in a previous station. The idle battery voltage was 3.46 V.

Sportident station 111515 with a cracked battery sleeve.
Sportident station 111515 with a cracked battery sleeve.

I followed the recommendation in the release notes and used SI Config+ to do a factory reset of the station and after that the reported battery voltage was a much more realistic 3.17 V. A few minutes or so later the voltage was up to 3.25 V (a case of “voltage delay” in lithium thionyl chloride batteries).

I hope the station stays in the sane state and that the Sportident developers soon figures out and solves this bug.

FRDM-based ZigBee Humidity Sensor Node Prototype

I have previously written about how to interface an XBee ZigBee module to a FRDM-KL25Z board and also how to talk to an XBee ZigBee module from a Python program on a PC. In this article I build on these foundations and show how to make a somewhat more complex system where a Python program running on a PC sends commands to – and receives replies from – a sensor node. The sensor node has an SHT21 humidity (and temperature) sensor which is connected via I2C to the FRDM board.

Here I use the API mode of the XBee module on the sensor node, instead of AT mode that I used in the previous blog post. There are a few XBee libraries for e.g. Arduino and mbed available. It might be possible to use one of those, but I looked into some of them briefly and it seemed like more work to understand and adapt one of those than to write the code necessary to take care of the somewhat limited communication with the XBee module required for this system. On the Python side, I continue to use the XBee library I used before.

Preparing the XBee Modules

The XBee ZigBee modules need to be properly configured before they can be used in this system. The one connected to the PC shall be programmed with the ZigBee API-mode Coordinator firmware and the one used in the sensor node shall have the API-mode Router firmware. A 64-bit PAN ID must be selected and both modules should be configured with this.

Humidity Sensor SHT21 and I2C

The combined humidity and temperature sensor I selected for this project is called SHT21 and is made by Sensirion. This is a reasonably priced nice little sensor (3×3 mm2) with I2C interface and a tolerance on the relative humidity of at most 3% (2% typical) between 20% and 80% RH while the temperature is accurate to 0.4 °C (0.3 °C typical) between 5 and 60 °C.

A minor problem is that it comes in a 6-pad DFN-package (dual flat, no leads) with a pitch of 1 mm. I connected to the chip by soldering very thin wires (a single strand from a relatively thin multi-stranded wire) to the four pads that are used. I then soldered these wires to a small piece of prototype board and added a decoupling capacitor and I2C pull-up resistors (I happened to use 2.2 kΩ and 4.7 kΩ because I had them easily available in suitable form factors). The top side of the package needs to be away from the board to expose the opening of the sensor to the environment. This could potentially lead to short circuits between the board and the bare thin wires, but I put a piece of insulating tape under the sensor to prevent this. See photo below.

SHT21 sensor on a prototype board with auxiliary components.
SHT21 sensor (central black square) on a prototype board with a few auxiliary components.

I ended up using the pins PTC11 (J1 pin 15, SDA1) and PTC10 (J1 pin 13, SCL1) as the I2C bus on the FRDM-KL25Z. I first tried A4 (J10 pin 10, SDA1) and A5 (J10 pin 12, SCL1), but it turned out that there is a square wave on A5 that obviously ruins the I2C communication. Maybe this could be turned off, but I moved to other pins instead of investigating what caused the square ware.

The SHT21 has a bit of a quirky requirement on the I2C communication, namely that a delay of 20 µs is recommended between the ACK bit after some command and before issuing the stop condition. This turned out to be straightforward to implement using the mbed I2C library routines, see the file sht21.cpp.

I did not implement a test of the checksum that the SHT21 provides. That can be added later and the easiest is probably to use code from the SHT21 sample code application note for this.

A photo of the complete sensor node prototype is shown below.

ZigBee sensor node based on a FRDM board, an XBee module and a SHT21 humidity sensor.
ZigBee sensor node prototype based on a FRDM board, an XBee module and an SHT21 humidity sensor.

Communication Protocol

To enable the PC to control the sensor node, some form of protocol needs to be devised on top of ZigBee that allows the PC to send requests to the sensor and also allows the sensor to respond.

To send data between the two XBee modules, the “Transmit Request” API command is used. This allows messages containing about 80 bytes of payload data to be sent. At the receiving end the messages show up as “RX Packet” frames.

I defined the request data packet to just contain two payload bytes, namely an ASCII acronym for the command to be executed. Currently I have two commands defined:

  • “ME” – perform the measurement and return the data
  • “ID” – return information about the sensor node

The payload of the replies to these commands are defined as follows:

  • #<command>#<command specific data>

For the ME command, the <command specific data> format is:

  • 4 bytes – the characters “HTR ” (which stands for Humidity, Temperature and Received Signal Strength Indicator (RSSI)
  • 4 bytes – a float with the humidity in %RH
  • 4 bytes – a float with the temperature in °C
  • 1 byte – the RSSI in negative dBm
  • 1 byte – error counter

So a reply to the ME command might look like:

#ME#HTR <a float with the value 47.18><a float with the value 24.11><a byte with the value 57><a byte with the value 0>

This would mean that the relative humidity is 47.18%, the temperature is 24.11 °C, the RSSI is -57 dBm and that the error counter is at zero.

For the ID command, the <command specific data> format is:

  • 4 bytes – ASCII characters  containing the node type code
  • 4 bytes – ASCII characters  containing the node revision
  • ? bytes – An ASCII string with a free text description

So this reply is purely in ASCII characters and a reply might look like this:

#ID#HT  0001Humidity and temperature sensor

This protocol can easily be expanded to more commands and different kinds of replies from different kinds of sensors.

Sample Terminal Output

Examples of text printed in the terminal windows (Tera Term for the sensor node and IPython for the Python program) are shown below.

Text printed from the Python program.
Text printed from the Python program.
Text printed from the sensor node to the USB serial port.
Text printed from the sensor node to the USB serial port.

Python Code

The Python code running on the PC starts out by selecting a COM port to use, sets up communication with the XBee module and sends an ID command to the sensor node before it enters into the main loop. The main loop regularly sends ME commands to the sensor. It also prints dots to show that it is running and checks if the q key has been hit, in which case it exits the program.

There is also a function that is called in the background by the xbee library whenever a frame has been received from the XBee. This function takes the proper action based on what kind of frame was received. The information in replies to the ID and ME command are printed out. Also, if there is anything anomalous in the status replies to transmissions, a warning detailing the issue is printed.

The 64-bit address of the sensor node is hard coded into the program (this might be OK for a quick demo or a one-off system, but should be solved in a more flexible manner in the future). The 16-bit address of the sensor node is not known at the beginning, but it is revealed in the first frame it sends back to the PC and so the Python program can store that address and use it in subsequent transmissions to decrease the burden on the ZigBee network.

The Python code can be found below.

Expand the Python code xbee_rx.py
[python] #! /usr/bin/python

"""
xbee_rx.py

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

This code is public domain and comes with absolutely no warranty. Enjoy!

Regularly send measurement requests to a remote ZigBee sensor node and display
the data that comes back.
"""

from xbee import ZigBee
import serial
import serial.tools.list_ports
import sys
import time
import msvcrt
import struct

# Remote node address
long_addr = "\x00\x13\xA2\x00\x40\xB4\x51\x40" # Hard coded for now
short_addr = ‘\xff\xfe’ # Use 0xfffe as 16-bit address until it becomes known

# Callback function that runs when frames are received
def receive_data(data):
    global short_addr
    if data[‘id’] == ‘rx_explicit’:
        if short_addr == ‘\xff\xfe’ and data[‘source_addr_long’] == long_addr:
            short_addr = data[‘source_addr’]             print ‘Discovered remote 16-bit address: 0x{:02x}{:02x}’.format(ord(short_addr[0]),
                                                                            ord(short_addr[1]))
        # Transmission from a node
        if data[‘rf_data’][:4] == ‘#ID#’:
            # Frame with node ID
            print ‘\nNode identifiaction frame’
            print ‘Node type: "’ + data[‘rf_data’][4:8] + ‘"’
            print ‘Node revision: "’ + data[‘rf_data’][8:12] + ‘"’
            print ‘Node description: "’ + data[‘rf_data’][12:] + ‘"’
        elif data[‘rf_data’][:4] == ‘#ME#’:
            # Reply to measure command
            print ‘Measurement data frame’
            if data[‘rf_data’][4:8] == "HTR ":
                humidity = struct.unpack(‘f’, data[‘rf_data’][8:12])[0]                 temperature = struct.unpack(‘f’, data[‘rf_data’][12:16])[0]                 rssi = ord(data[‘rf_data’][16])
                err_cnt = ord(data[‘rf_data’][17])
                print "Humidity: {0:.2f}%, Temperature: {1:.2f} C, RSSI: -{2:d} dBm".format(humidity,
                                                                                            temperature,
                                                                                            rssi)
            else:
                print ‘Measurement from unknown node type "{}"’.format(data[‘rf_data’][4:8])
                print data
        else:
            print ‘Unknown RX frame type "{}"’.format(data[‘rf_data’][:4])
            print data
    elif data[‘id’] == ‘tx_status’:
        # Status from a transmission
        print ‘\nTX status frame, fid={:d}’.format(ord(data[‘frame_id’]))
        if ord(data[‘retries’]) != 0:
            print ‘TX Warning: {:d} retries were done’.format(ord(data[‘retries’]))
        if ord(data[‘deliver_status’]) != 0:
            print ‘TX Warning: delivery failed 0x{:x}’.format(ord(data[‘deliver_status’]))
        if ord(data[‘discover_status’]) != 0:
            print ‘TX Warning: discovery overhead 0x{:x}’.format(ord(data[‘discover_status’]))
    else:
        print "\nUnknown frame type"
        print data

# Look for a COM port that might have an XBee connected
portfound = False
ports = list(serial.tools.list_ports.comports())
for p in ports:
    # The SparkFun XBee Explorer USB board uses an FTDI chip as USB interface
    print p
    if "FTDIBUS" in p[2]:
        print "Found possible XBee on " + p[0]         if not portfound:
            portname = p[0]             portfound = True
            print "Using " + portname + " as XBee COM port."
        else:
            print "Ignoring this port, using the first one that was found."

if portfound:
    ser = serial.Serial(portname, 9600)
else:
    sys.exit("No serial port seems to have an XBee connected.")

xbee = ZigBee(ser, callback = receive_data)
cnt = 0
cnt_me = 100
id = 2

# Request information about remote node
xbee.send("tx", data="ID", dest_addr_long=long_addr, dest_addr=short_addr)
print "Press q to quit"

# Main loop
while True:
    try:
        if msvcrt.kbhit():
            # Ctrl-C exits the whole shell in IPython on Windows,
            # so make a press on the q-key exit the main loop.
            if msvcrt.getch() == ‘q’:
                print "Exiting loop"
                break
        time.sleep(0.1)
        cnt = cnt+1
        cnt_me = cnt_me+1
        if cnt >= 10:
            # Show that the program is still running
            sys.stdout.write(‘.’)
            sys.stdout.flush()
            cnt = 0
        if cnt_me >= 100:
            # Send request for measurement data to remote node
            xbee.send("tx", data="ME", dest_addr_long=long_addr, dest_addr=short_addr, frame_id=chr(id))
            id = id+1
            if id > 255:
                id = 2
            cnt_me = 0

    except KeyboardInterrupt:
        print "Ctrl-C was pressed"
        break
        
xbee.halt()
ser.close()
[/python]

Mbed Code

The program running on the FRDM-KL25Z in the sensor node is divided into several source code files. It works like this:

An interrupt routine receives the bytes coming from the XBee serial port and when it finds a valid frame, it stores it in an available frame buffer and marks that frame buffer as containing a frame to be processed.

The main program:

  • sets everything up and then enters the main loop that:
  • regularly performs the measurements
  • looks at the frame buffers to see if there is any that contains an unprocessed frame
  • when it finds a new frame, it processes it (sends the reply to the command, handles the reply to an AT command or sends an ATDB command to get the RSSI value)
  • prints status information to the PC serial port (for debug purposes)

Currently the error counter functionality is not implemented.

The program is divided into several source code files. These files together with the Python program can all be downloaded as a zip archive:

XBee sensor source code, 2015-07-31

The individual files are described further below.

main.cpp

This file contains the main part of the program that

  • sets up communication with the XBee module
  • sets up the SHT21 sensor
  • a main loop as described above
  • code to update the variables with the most recent measurement results
  • code to parse AT command replies from the XBee module (used to figure out RSSI), handle_at_response()
  • code to send out the measurement data, send_humidity_temperature()
  • code to reply to the ID command, send_id()
  • code to parse the incoming RX frames, handle_rx()
Expand main.cpp
[cpp] /*
main.cpp

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

This code is public domain and comes with absolutely no warranty. Enjoy!

Sensor node based on an XBee ZigBee module, a FRDM-KL25Z development board
and an SHT21 humidity/temperature sensor.

This file contains the main program that:
– sets everything up
– regularly performs the measurements
– processes ZigBee frames that the interrupt routine has stored in frame buffers
– responds to commands from other ZigBee nodes
– prints status information to the PC serial port
*/

#include "mbed.h"
#include "serial_buffer.h"
#include "sht21.h"
#include "zigbee.h"
#include "xbee_api.h"

Serial pc(USBTX, USBRX);
Serial xbee(PTE0, PTE1);
DigitalOut redled(LED1);
DigitalOut greenled(LED2);
DigitalOut blueled(LED3);

uint8_t rssi; // Most recent RSSI value
uint8_t rssi_updated; // Whether the RSSI value was updated since last time it was read
float humidity = -1.0;
float temperature = -1000.0;
uint8_t id = 1; // ID used for TX packets

// Update the temperature and humidity variables
void update_measurements() {
    humidity = sht21_read_humidity();
    temperature = sht21_read_temperature();    
}

// Process a frame that was a response to an AT command
void handle_at_response(const uint8_t *buffer) {
    uint32_t length;
    uint8_t frame_type;
    uint8_t status;
    uint32_t at;
    
    length = zb_get_buffer_length(buffer);
    frame_type = buffer[ZB_OFFS_FRAME_TYPE];
    if (frame_type != ZB_FRAME_TYPE_AT_RESPONSE) {
        // Not the expected frame type for this handler!
        pc.printf("Incorrect frame type 0x%02x for at-response handler\n", (uint32_t)frame_type);
        return;
    }
    status = buffer[ZB_OFFS_AT_RESP_STATUS];
    if (status != 0) {
        // Status is not OK
        pc.printf("Status is not OK (0x%02x) in at-response\n", (uint32_t)status);
        return;
    }
        
    at = zb_get_at(buffer);
    if (at == ZB_ATDB) {
        // Response to ATDB command
        if (length != 6) {
            // Unexpected length of ATDB response
            pc.printf("Unexpected length (%u) of at-response\n", length);
            return;
        }
        rssi = buffer[ZB_OFFS_AT_RESP_DATA0];
        rssi_updated = 1;
        pc.printf("RSSI = -%u dBm\n", (uint32_t)rssi);
    } else {
        pc.printf("No handler for response to AT%c%c\n",
            buffer[ZB_OFFS_AT_RESP_AT0], buffer[ZB_OFFS_AT_RESP_AT1]);
    }
}

// Send a TX packet with humidity, temperature and RSSI information
void send_humidity_temperature(const uint8_t *dest64, const uint8_t *dest16) {
    humidity_data_t data;
    
    // Make sure measurements are OK
    if(humidity < 0) {
        update_measurements();
    }
    
    data.cmd_reply[0] = ‘#’; // Shows what command we are responding to
    data.cmd_reply[1] = ‘M’;
    data.cmd_reply[2] = ‘E’;
    data.cmd_reply[3] = ‘#’;
    data.sensor_data_type[0] = ‘H’; // Humidity
    data.sensor_data_type[1] = ‘T’; // Temperature
    data.sensor_data_type[2] = ‘R’; // RSSI
    data.sensor_data_type[3] = ‘ ‘;
    data.humidity = humidity;
    data.temperature = temperature;
    data.rssi = rssi;
    data.err_cnt = 0;
    
    id++; // Increase frame ID
    if(id < 2) {
        id = 2; // Never let it be less than 2
    }
    // Send the data
    zb_send_tx_req(id, dest64, dest16, (uint8_t *)&data, sizeof(data));
}

// Send a TX packet identifying this sensor node type
void send_id(const uint8_t *dest64, const uint8_t *dest16) {
    // Data format:
    // 4 bytes – The command this is a response to, surrounded by # signs
    // 4 bytes – The node type code
    // 4 bytes – The node revision
    // ? bytes – A free text description
    char reply[] = "#ID#" "HT  " "0001" "Humidity and temperature sensor";
    
    id++; // Increase frame ID
    if(id < 2) {
        id = 2; // Never let it be less than 2
    }
    // Send the data
    // Do not include the trailing nul in the string
    zb_send_tx_req(id, dest64, dest16, (uint8_t *)&reply, sizeof(reply)-1);
}

// Process a frame containing an RX packet
void handle_rx(const uint8_t *buffer) {
    uint8_t frame_type;
    uint32_t cmd;
    uint32_t length, data_len;
    
    pc.printf("RX handler\n");
    length = zb_get_buffer_length(buffer);
    frame_type = buffer[ZB_OFFS_FRAME_TYPE];
    if (frame_type != ZB_FRAME_TYPE_RX_RECEIVED) {
        // Not the expected frame type for this handler!
        pc.printf("Incorrect frame type 0x%02x for RX handler\n", (uint32_t)frame_type);
        return;
    }

    // Ignore receive options field
    data_len = length – ZB_OFFS_RX_DATA_0 + 3;
    if (data_len < 2) {
        pc.printf("Too little data in RX packet\n");
        return;
    }
    cmd = zb_get16(buffer, ZB_OFFS_RX_DATA_0);
    if (cmd == ZB_CMD_ME) {
        // ME Measure command
        // Send a response packet with the measurement data.
        blueled = 0;
        pc.printf("Sending reply to ME\n");
        send_humidity_temperature(buffer+ZB_OFFS_RX_SRC64_0, buffer+ZB_OFFS_RX_SRC16_0);
        blueled = 1;
    } else if (cmd == ZB_CMD_ID) {
        // ID, identify command
        // Send a response
        blueled = 0;
        pc.printf("Sending reply to ID\n");
        send_id(buffer+ZB_OFFS_RX_SRC64_0, buffer+ZB_OFFS_RX_SRC16_0);
        blueled = 1;
    } else {
        redled = 0;
        pc.printf("No handler for RX command %c%c\n",
            buffer[ZB_OFFS_RX_DATA_0], buffer[ZB_OFFS_RX_DATA_0+1]);
        redled = 1;
    }
}

int main() {
    uint8_t frame_type;
    Timer rssi_timer, humidity_timer, data_timer;
    
    redled = 1;    // led off
    blueled = 1;   // led off
    greenled = 0;  // led on
    wait(1);
    pc.printf("\n\nZigBee sensor node test program\n");
    xbee.baud(9600);
    xbee.attach(&rx_interrupt, Serial::RxIrq);

    greenled = 1; // led off

    pc.printf("Setting up I2C\n");
    while (!sht21_setup()) {
        pc.printf("SHT21 setup failed\n");
        redled = 0;
        wait(1);
    }
    redled = 1;
    pc.printf("SHT21 setup successful\n");
    
    humidity_timer.start();
    
    // Main loop, look for frames coming from the XBee module
    while (1) {
        // Check if there is any complete frame in any buffer
        for (int ii=0; ii<n_rx_buffers; ii++) {             if (buffer_used[ii] == 2) {                 // Found a complete frame, process it                 frame_type = rx_buffer[ii][ZB_OFFS_FRAME_TYPE];                 switch (frame_type) {                     case ZB_FRAME_TYPE_AT_RESPONSE:                         // Response from AT command                         handle_at_response(rx_buffer[ii]);                         break;                     case ZB_FRAME_TYPE_TX_RESPONSE:                         // Response from TX command                         pc.printf("Response from TX\n");                         zb_send_simple_at("DB", 1); // Update RSSI                         break;                     case ZB_FRAME_TYPE_RX_RECEIVED:                         // Incoming RX frame                         handle_rx(rx_buffer[ii]);                         break;                 }                 // The frame has been processed, mark it as empty                 buffer_used[ii] = 0;             }         }         // Timer controlled activities         if(humidity_timer.read() > 10.0) {
            // Read humidity and temperature
            greenled = 0;
            update_measurements();
            pc.printf("Relative humidity = %.2f%% ", humidity);
            pc.printf(", Temperature = %.2f C\n", temperature);
            humidity_timer.reset();
            greenled = 1;
        }
    }
}
[/cpp]

xbee_api.h

This header file declares some global variables from main.cpp and typedefs a struct used for the data format in replies to the ME command.

Expand xbee_api.h
[cpp] /*
xbee_api.h

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

This code is public domain and comes with absolutely no warranty. Enjoy!

Declarations for the main part of the program.
*/

#include "mbed.h"

extern Serial pc;
extern Serial xbee;
extern DigitalOut redled;
extern DigitalOut greenled;
extern DigitalOut blueled;

typedef struct humidity_data_t {
    char cmd_reply[4];
    char sensor_data_type[4];
    float humidity;
    float temperature;
    uint8_t rssi;
    uint8_t err_cnt;
} humidity_data_t;
[/cpp]

serial_buffer.cpp

This file contains the interrupt routine rx_interrupt() which is called when data arrives from the XBee serial port. The data is parsed and if it is found to be a valid ZigBee frame, it is stored in a free frame buffer. The buffer is then marked as containing a valid frame to be processed (by the main loop).

Expand serial_buffer.cpp
[cpp] /*
serial_buffer.cpp

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

This code is public domain and comes with absolutely no warranty. Enjoy!

Frame buffers for data from the XBee module.

rx_interrupt() is called when data arrives from the XBee serial port. The data is parsed
and if it is found to be a valid ZigBee frame, it is stored in a free frame buffer.
The buffer is then marked as containing a valid frame to be processed (by the main loop).
*/

#include "mbed.h"
#include "serial_buffer.h"
#include "xbee_api.h"
#include "zigbee.h"

// Frame buffers for XBee RX data – used by interrupt routines

uint8_t rx_buffer[n_rx_buffers][buffer_size+1]; // The received frames are stored here
// buffer_used[]:
// 0 – not used
// 1 – being filled up
// 2 – filled with a frame, waiting to be processed by main loop
// (variables with static storage are initilized to all zeros)
int buffer_used[n_rx_buffers];
volatile int rx_ptr=0;

// Interrupt routine to read frame data from the serial port into frame buffers.
void rx_interrupt() {
    // State:
    // 0 = waiting for start delimiter of new frame,
    // 1 = inside frame, storing it in a buffer
    // 2 = inside frame, dropping it
    static int state = 0;
    static int current_buffer;
    static uint32_t length;
    uint8_t cur_byte;
    static uint8_t sum; // For checksum calculation
    
    while (xbee.readable()) {
        cur_byte = ((int)xbee.getc())&0xff;
        if (state == 0) {
            // Waiting for a start delimiter
            if (cur_byte != ZB_START_DELIM) {
                // Not a start delimiter. This is a bit unexpected.
                // Just eat it and wait for the next byte.
            } else {
                // Got a start delimiter.
                // Find a buffer to store it in.
                current_buffer = -1;
                for (int ii=0; ii < n_rx_buffers; ii++) {
                    if (!buffer_used[ii]) {
                        // Found an empty buffer
                        current_buffer = ii;
                        state = 1;
                        buffer_used[ii] = 1;
                        rx_ptr = 0;
                        rx_buffer[current_buffer][rx_ptr++] = cur_byte;
                        break;
                    }
                }
                if (current_buffer < 0) {                     // There was no free buffer!                     // Ignore this packet and wait for a new one.                     pc.printf("Error: Discarding frame, no free buffers.\n");                     state = 2;                 }             }         } else if (state == 1) {             // Copying a frame into a buffer             if (rx_ptr == 1) {                 // MSB of length                 rx_buffer[current_buffer][rx_ptr++] = cur_byte;             } else if (rx_ptr == 2) {                 // LSB of length                 rx_buffer[current_buffer][rx_ptr++] = cur_byte;                 length = zb_get_buffer_length(rx_buffer[current_buffer]);                 sum = 0;                 if (length + 4 > buffer_size) {
                    // Ooops! The frame is bigger than the buffer, this is very unexpected.
                    // We need to discard this frame.
                    pc.printf("The frame is bigger than the buffer (%u), discarding it.\n", length);
                    buffer_used[current_buffer] = 0;
                    state = 2;
                }
            } else if (length > 0) {
                // This is a byte inside the frame
                sum += cur_byte;
                rx_buffer[current_buffer][rx_ptr++] = cur_byte;
                length–;
            } else {
                // The checksum byte
                sum += cur_byte;
                rx_buffer[current_buffer][rx_ptr++] = cur_byte;
                state = 0;
                if (sum != 0xff) {
                    // Checksum error, discard frame
                    pc.printf("Frame checksum error\n");
                    buffer_used[current_buffer] = 0;
                } else {
                    // Checksum OK, mark buffer as ready to be processed
                    buffer_used[current_buffer] = 2;
                    state = 0;
                }
            }
        } else {
            // Discarding incoming frame
            if (rx_ptr==1) {
                // MSB of length
                length = ((uint32_t)cur_byte)<<8;             } else if (rx_ptr==2) {                 // LSB of length                 length += cur_byte;                 if (length > 102) {
                    // Length is unrealistic, we do not want to wait for
                    // so many bytes before starting to look for a new frame.
                    // Start looking for start of a new frame immediately.
                    length = 0;
                }
            } else if (length > 0) {
                // A byte inside the frame
                length–;
            } else {
                // Finally the checksum, change state
                state = 0;
            }
        }
    }
}
[/cpp]

serial_buffer.h

Header file for serial_buffer.cpp

Expand serial_buffer.h
[cpp] /*
serial_buffer.h

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

This code is public domain and comes with absolutely no warranty. Enjoy!

Frame buffers for data from the XBee module.

rx_interrupt() is called when data arrives from the XBee serial port. The data is parsed
and if it is found to be a valid ZigBee frame, it is stored in a free frame buffer.
The buffer is then marked as containing a valid frame to be processed (by the main loop).
*/

void rx_interrupt();

// ZigBee frame buffers
const int buffer_size = 256;
const int n_rx_buffers = 4;
// The received frames are stored here
extern uint8_t rx_buffer[n_rx_buffers][buffer_size+1];
// buffer_used[]:
// 0 – not used
// 1 – being filled up
// 2 – filled with a frame, waiting to be processed by main loop
extern int buffer_used[n_rx_buffers];
[/cpp]

sht21.cpp

Functions to talk to an SHT21 humidity and temperature sensor via I2C.

Expand sht21.cpp
[cpp] /*
sht21.cpp

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

This code is public domain and comes with absolutely no warranty. Enjoy!

Functions to talk to an SHT21 humidity and temperature sensor via I2C.
Some of the code is adapted from https://developer.mbed.org/handbook/I2C
*/

#include "mbed.h"
#include "xbee_api.h"

I2C i2c(PTC11, PTC10);
 
static const int sht21_addr = 0x80;
static const uint8_t i2c_cmd_trig_t = 0xf3;
static const uint8_t i2c_cmd_trig_h = 0xf5;
static const uint8_t i2c_cmd_write_reg = 0xe6;
static const uint8_t i2c_cmd_read_reg = 0xe7;
static const uint8_t i2c_cmd_reset = 0xfe;

static uint8_t sht21_default_reg = 0;

// Initialize the SHT21 sensor. Returns true on success.
int sht21_setup() {
    char data[2];
    
    i2c.frequency(200000); // SHT21 supports up to 400 kHz
    // Soft reset
    data[0] = i2c_cmd_reset;
    if(i2c.write(sht21_addr, data, 1)) {
        pc.printf("I2C Error: Did not receive ACK on soft reset.\n");
        return 0;
    }
    wait_ms(16); // Soft reset takes less than 15 ms
    
    // Read user register to figure out the default bits
    data[0] = i2c_cmd_read_reg;
    if(i2c.write(sht21_addr, data, 1)) {
        pc.printf("I2C Error: Did not receive ACK on read reg 1.\n");
        return 0;
    }
    if(i2c.read(sht21_addr, data, 1)) {
        pc.printf("I2C Error: Did not receive ACK on read reg 2.\n");
        return 0;
    }
    // Mask out the default bit pattern for reserved bits 3, 4, 5
    sht21_default_reg = data[0] & 0x34;
    
    // Set it to maximum resolution
    data[0] = i2c_cmd_write_reg;
    data[1] = sht21_default_reg | 0x02; // max resolution, disable heater, disable OTP reload
    if(i2c.write(sht21_addr, data, 1)) {
        pc.printf("I2C Error: Did not receive ACK on write reg.\n");
        return 0;
    }
    return 1;
}    

    
// Read the humidity from the SHT21.
// The humidity is returned as a float (%RH).
// Returns a large negative value if an error occured.
float sht21_read_humidity() {
    char data[3];
    int32_t humidity;
    
    // Read user register to figure out default bits
    data[0] = i2c_cmd_trig_h;
    if(i2c.write(sht21_addr, data, 1, true)) {
        pc.printf("I2C Error: Did not receive ACK on humidity trigger.\n");
        wait_us(20);
        i2c.stop();
        return -(1<<24);
    }
    // Need to send the stop condition a little later
    wait_us(20);
    i2c.stop();
    wait_ms(30); // Wait until we are sure the conversion is done (29 ms maximum)
    if(i2c.read(sht21_addr, data, 3)) {
        pc.printf("I2C Error: Did not receive ACK on read humidity.\n");
        return -(1<<24);
    }
    // Ignore checksum testing
    humidity = ((((int32_t)data[0])&0xff)<<8) + (data[1]&0xfc); // Combine bytes and clear status bits
    return 125.0*humidity/65536.0 – 6.0;
}

// Read the temperature from the SHT21.
// The temperature is returned as a float (degrees C).
// Returns a large negative value if an error occured.
float sht21_read_temperature() {
    char data[3];
    int32_t temperature;
    
    // Read user register to figure out default bits
    data[0] = i2c_cmd_trig_t;
    if(i2c.write(sht21_addr, data, 1, true)) {
        pc.printf("I2C Error: Did not receive ACK on temperature trigger.\n");
        wait_us(20);
        i2c.stop();
        return -(1<<24);
    }
    // Need to send the stop condition a little later
    wait_us(20);
    i2c.stop();
    wait_ms(90); // Wait until we are sure the conversion is done (85 ms maximum)
    if(i2c.read(sht21_addr, data, 3)) {
        pc.printf("I2C Error: Did not receive ACK on read temperature.\n");
        return -(1<<24);
    }
    // Ignore checksum testing
    temperature = ((((int32_t)data[0])&0xff)<<8) + (data[1]&0xfc); // Combine bytes and clear status bits
    return 175.72*temperature/65536.0 – 46.85;
}
[/cpp]

sht21.h

Header file for sht21.cpp

Expand sht21.h
[cpp] /*
sht21.h

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

Header file for routines for SHT21 I2C humidity and temperature sensor.
*/

int sht21_setup();
float sht21_read_humidity();
float sht21_read_temperature();
[/cpp]

zigbee.cpp

Functions to deal with the XBee ZigBee module. This includes functions to create and send various kinds of API frames as well as to get information out of received frames.

Expand zigbee.cpp
[cpp] /*
zigbee.cpp

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

Functions to deal with the XBee ZigBee module.
*/

#include "xbee_api.h"
#include "zigbee.h"

// Calculate and set the checksum of the ZB buffer pointed to by buffer with the length len.
// len is the number of bytes in the buffer, including start delimiter and length.
void zb_set_checksum(uint8_t *buffer, int len) {
    uint8_t sum = 0;
    
    for(int ii = ZB_OFFS_LEN_LSB+1; ii < len; ii++) {         sum += buffer[ii];     }     buffer[len] = 0xff – sum; }      // Set the length field of the ZigBee buffer. // len is the number of bytes in the buffer, including start delimiter and length, // but excluding the checksum void zb_set_len(uint8_t *buffer, uint32_t len) {     uint32_t len_val;          len_val = len – 3;     buffer[ZB_OFFS_LEN_MSB] = len_val >> 8;
    buffer[ZB_OFFS_LEN_LSB] = len_val & 0xff;
}

// Send a buffer containing a ZigBee frame to the XBee module
void zb_send_buffer(const uint8_t *buffer) {
    uint32_t len;

    len = zb_get_buffer_length(buffer) + 4;
    pc.printf("Sending frame: ");    
    for(int ii=0; ii < len; ii++) {
        pc.printf("%02x ", (uint32_t)buffer[ii]);
        xbee.putc(buffer[ii]);
    }
    pc.printf("\n");    
}

// Send an AT command without parameter
void zb_send_simple_at(const char *at, char frame_id) {
    uint8_t buffer[8];
    uint32_t bufp = 0;
    
    buffer[0] = ZB_START_DELIM;
    buffer[ZB_OFFS_FRAME_TYPE] = ZB_FRAME_TYPE_AT;
    buffer[ZB_OFFS_FRAME_ID] = frame_id;
    buffer[ZB_OFFS_AT_CMD_AT0] = at[0];
    buffer[ZB_OFFS_AT_CMD_AT1] = at[1];
    bufp = ZB_OFFS_AT_CMD_AT1+1;
    zb_set_checksum(buffer, bufp);
    zb_set_len(buffer, bufp);
    
    zb_send_buffer(buffer);           
}

// Send an AT command with a single byte as parameter
void zb_send_at_byte_param(const char *at, uint8_t param) {
    uint8_t buffer[9];
    uint32_t bufp = 0;
    
    buffer[0] = ZB_START_DELIM;
    buffer[ZB_OFFS_FRAME_TYPE] = ZB_FRAME_TYPE_AT;
    buffer[ZB_OFFS_FRAME_ID] = 0;
    buffer[ZB_OFFS_AT_CMD_AT0] = at[0];
    buffer[ZB_OFFS_AT_CMD_AT1] = at[1];
    bufp = ZB_OFFS_AT_CMD_AT1+1;
    buffer[bufp++] = param;
    zb_set_checksum(buffer, bufp);
    zb_set_len(buffer, bufp);
    
    zb_send_buffer(buffer);           
}

// Send a transmit request frame
void zb_send_tx_req(uint8_t id, const uint8_t *dest64, const uint8_t *dest16, const uint8_t *data, int data_len) {
    uint8_t buffer[18+82]; // How much payload can there be?
    int bufp = 0;
    
    buffer[0] = ZB_START_DELIM;
    buffer[ZB_OFFS_FRAME_TYPE] = ZB_FRAME_TYPE_TX_REQ;
    buffer[ZB_OFFS_FRAME_ID] = id;
    for(int ii=0; ii<8; ii++) {
        buffer[ZB_OFFS_TX_DEST64_0+ii] = dest64[ii];
    }
    buffer[ZB_OFFS_TX_DEST16_0] = dest16[0];
    buffer[ZB_OFFS_TX_DEST16_1] = dest16[1];
    buffer[ZB_OFFS_TX_RADIUS] = 0;
    buffer[ZB_OFFS_TX_OPTIONS] = 0;
    bufp = ZB_OFFS_TX_DATA_0;
    for(int ii=0; ii<data_len; ii++) {
        buffer[bufp++] = data[ii];
    }
    zb_set_checksum(buffer, bufp);
    zb_set_len(buffer, bufp);
    zb_send_buffer(buffer);           
}

// Return two bytes (MSB and LSB) from a uint8_t array as an unsigned int
uint32_t zb_get16(const uint8_t *buffer, int start) {
    return (buffer[start]<<8) + buffer[start+1];
}

// Return the length field from a frame
int zb_get_buffer_length(const uint8_t *buffer) {
    return zb_get16(buffer, ZB_OFFS_LEN_MSB);
}

// Return the AT command field from an AT command response frame
uint32_t zb_get_at(const uint8_t *buffer) {
    return zb_get16(buffer, ZB_OFFS_AT_RESP_AT0);
}
[/cpp]

zigbee.h

Header file with declarations and constants for ZigBee communication.

Expand zigbee.h
[cpp] /*
zigbee.h

Rev 0.1, 2015-07-31
Per Magnusson, Axotron, axotron.se/blog

Header file with declarations and constants for ZigBee communication.
*/

#define ZB_BUFSIZE 150
// ZigBee defines
// General frame offsets
#define ZB_START_DELIM 0x7e
#define ZB_OFFS_LEN_MSB 1
#define ZB_OFFS_LEN_LSB 2
#define ZB_OFFS_FRAME_TYPE 3
#define ZB_OFFS_FRAME_ID 4
// AT command frame offsets
#define ZB_OFFS_AT_CMD_AT0 5
#define ZB_OFFS_AT_CMD_AT1 6
#define ZB_OFFS_AT_CMD_DATA0 7
// AT response frame offsets
#define ZB_OFFS_AT_RESP_AT0 5
#define ZB_OFFS_AT_RESP_AT1 6
#define ZB_OFFS_AT_RESP_STATUS 7
#define ZB_OFFS_AT_RESP_DATA0 8

// TX request frame offsets
#define ZB_OFFS_TX_DEST64_0 5
#define ZB_OFFS_TX_DEST64_1 6
#define ZB_OFFS_TX_DEST64_2 7
#define ZB_OFFS_TX_DEST64_3 8
#define ZB_OFFS_TX_DEST64_4 9
#define ZB_OFFS_TX_DEST64_5 10
#define ZB_OFFS_TX_DEST64_6 11
#define ZB_OFFS_TX_DEST64_7 12
#define ZB_OFFS_TX_DEST16_0 13
#define ZB_OFFS_TX_DEST16_1 14
#define ZB_OFFS_TX_RADIUS 15
#define ZB_OFFS_TX_OPTIONS 16
#define ZB_OFFS_TX_DATA_0 17

// RX packet frame offsets
#define ZB_OFFS_RX_SRC64_0 4
#define ZB_OFFS_RX_SRC64_1 5
#define ZB_OFFS_RX_SRC64_2 6
#define ZB_OFFS_RX_SRC64_3 7
#define ZB_OFFS_RX_SRC64_4 8
#define ZB_OFFS_RX_SRC64_5 9
#define ZB_OFFS_RX_SRC64_6 10
#define ZB_OFFS_RX_SRC64_7 11
#define ZB_OFFS_RX_SRC16_0 12
#define ZB_OFFS_RX_SRC16_1 13
#define ZB_OFFS_RX_OPTIONS 14
#define ZB_OFFS_RX_DATA_0 15

// Frame types
#define ZB_FRAME_TYPE_AT 0x08
#define ZB_FRAME_TYPE_TX_REQ 0x10
#define ZB_FRAME_TYPE_AT_RESPONSE 0x88
#define ZB_FRAME_TYPE_TX_RESPONSE 0x8b
#define ZB_FRAME_TYPE_RX_RECEIVED 0x90

// AT commands as numbers
#define ZB_ATDB ((((uint32_t)’D’)&lt;&lt;8) + ‘B’)

// Commands to sensor nodes
#define ZB_CMD_ME ((((uint32_t)’M’)&lt;&lt;8) + ‘E’)
#define ZB_CMD_ID ((((uint32_t)’I’)&lt;&lt;8) + ‘D’)

void zb_send_simple_at(const char *at, char frame_id=0);
void zb_send_at_byte_param(const char *at, uint8_t param);
void zb_send_tx_req(uint8_t id, const uint8_t *dest64, const uint8_t *dest16, const uint8_t *data, int data_len);
uint32_t zb_get16(const uint8_t *buffer, int start);
int zb_get_buffer_length(const uint8_t *buffer);
uint32_t zb_get_at(const uint8_t *buffer);
[/cpp]