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.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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.")
# 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:
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
*/
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();
}
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;
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).
*/
// 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
*/
// 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;
// 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;
// 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;
// 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.
*/