Det här är en artikel som jag skrev till Vetenskap och Folkbildnings tidskrift Folkvett, nr 1-2 2015.
Strålskyddsstiftelsen (SSS) är en insamlingsstiftelse med ett namn som är förrädiskt likt (Statens) Strålskyddsinstitut, numera Strålsäkerhetsmyndigheten. SSS syfte är att informera om och skydda mot skadlig elektromagnetisk strålning. De är flitiga med att sprida varningar om att trådlös teknik är skadlig på diverse sätt. Jag tänkte här gå igenom vad jag kommit fram till om påståendena i två av artiklarna på SSS hemsida.
Hjärntumörer i Danmark
Den första artikeln är publicerad i juni 2014 och har rubriken ”Hjärntumörer ökar i Danmark. 55% fler på tio år” [1]. SSS menar också att ett markant trendbrott i tumörfallen skedde i Danmark 2003 som är utgångsåret för påståendet om 55% ökning på tio år (till 2012). (Vi kan bortse från att det bara handlar om nio år från 2003 till 2012.)
Frågan är om påståendet håller för en närmare granskning.
Om det vore så att mobilanvändningen, som rimligen ökat och blivit väldigt vanlig från och med någon gång på 90-talet, var orsaken till ökningen av cancerfall så borde man se samma ökning i länder med liknande mobilanvändning som Danmark, t.ex. Sverige. I artikeln medger dock SSS att utvecklingen i Sverige är en helt annan, men de berättar inte hur statistiken för Sverige egentligen ser ut.
Jag har letat upp statistiken från Danmark som SSS bygger sina påståenden på samt motsvarande statistik från Sverige [2]. Det verkar som om det är samma statistisk som SSS använt, för jag fick fram de siffror om Danmark som nämndes i artikeln. När jag skriver detta har tyvärr statistiken om tumörfall försvunnit från ssi.dk där jag hittade de danska siffrorna, men ungefär motsvarande siffror går att få fram genom att kombinera statistik från [3] och [4].
För Danmark finns data för 1978 till 2012 och jag har därför valt att visa den tidsperioden i Figur 1 där det framgår hur många nya fall av tumörer i hjärnan eller centrala nervsystemet per 100 000 invånare som rapporterats för varje år.
Notera att den danska siffran för år 2003 som SSS använde som utgångsår är lägre (18) än omgivande år (21 respektive 24), vilket ger en mer uppseendeväckande ökning, nämligen 55%, jämfört med 17% om man hade valt 2004 som utgångsår. Det är nog ingen slump att SSS valde att utgå just från 2003.
Om det nu vore mobilanvändning som orsakade ökningen för den här sortens tumörer så är det lite märkligt att ökningen ser ut att ha varit ganska stadig sedan åtminstone början av 80-talet då mobilanvändning var mycket ovanlig och då det definitivt inte fanns några mobiler där man höll antennen nära huvudet. Normalt tar det ju dessutom ganska många år från att man exponeras för en riskfaktor tills en tumör skulle kunna utvecklas. Så uppenbarligen finns det andra faktorer än mobiltelefoner som kan orsaka en ökning i tumörstatistiken.
Jag vet inte vad som ligger bakom den här ökningen, men det finns inget som pekar ut just mobilanvändning. Kanske är det en åldrande befolkning i kombination med effektivare diagnosmetoder för tumörer eller bättre rapporteringssystem som är orsaken?
Mobilanvändningen i Sverige och Danmark har rimligen utvecklats på ungefär samma sätt, så om ökningen av tumörer i Danmark orsakats av ökat mobilanvändande så borde utvecklingen av antalet hjärntumörer i Sverige likna den i Danmark. Dock har kurvan med den svenska statistiken från Socialstyrelsen ett annat utseende än den danska. Den är i stort sett helt platt över hela tidsintervallet. Om man jämför 2003 med 2012 så som SSS behagade göra för Danmark så ser man till och med att antalet fall per 100 000 invånare har minskat med 7%!
Även om man hittar en ökande trend i en sådan här tidsserie och vill använda den som ett argument för att mobiltelefoner är farliga så måste man troliggöra att det är just mobiltelefonerna och inte något annat som är orsaken till ökningen. Vad framför då SSS som belägg för detta? I själva artikeln nämner de en f.d. Teliaanställd vid namn Egon Reiver (som också råkar vara ordförande för Elöverkänsligas Förening i Jönköpings län) som använt mobiltelefon sedan 80-talet och visserligen inte själv fått en tumör, men som säger sig ha tre kollegor som fått det. Säkerligen tragiska fall, men det säger såklart inget alls om vad som orsakade dem. Bättre argument har SSS i form av en sammanställning [5] med referenser till studier som de menar visar på sambandet. Det skulle ta alldeles för mycket plats att gå igenom alla studierna de hänvisar till, men vi kan ta en närmare titt på de två första punkterna på listan.
Den första studien är en fransk studie från 2014 [6]. SSS påstår att den visar att risken för gliom (en sorts hjärntumör eller tumör på centrala nervsystemet; elakartade hjärntumörer är oftast gliom) ökar fyra gånger om man använder mobiltelefon 30 minuter om dagen under en längre tidsperiod samt att även risken för meningiom (en sorts tumör som oftast är godartad och som utgår från hjärnhinnorna) ökar för dem som använder mobiltelefoner mest. Vidare påstås det att studien funnit ett dos-responssamband, alltså att mer mobilanvändning leder till större risk.
Dessa påståenden om studien är till stor del missvisande. Studien har också kritiserats för brister i metodologin som kan snedvrida slutsatserna. I sammanfattningen skriver artikelförfattarna: ”No association with brain tumours was observed when comparing regular mobile phone users with non-users”, alltså att de inte kunnat påvisa att det skulle vara farligare med normal mobilanvändning jämfört med att inte använda mobil alls. Däremot fann de ett statistiskt signifikant samband mellan den mest intensiva användningen av mobiltelefoner och risken för att få en tumör.
En brist i studien är att mobiltelefonanvändningen skattats i efterhand genom att personer har fått svara på formulär om hur mycket de använt mobiltelefon genom åren. Minnet är som bekant inte så pålitligt och kanske kan en person som fått en hjärntumör vara benägen att leta efter en orsak och undermedvetet överdriva hur mycket hen använt mobiltelefon. En något längre diskussion om studien med kommentarer från ett par olika experter återfinns i [7].
Nästa punkt på SSS lista refererar till tre olika studier, alla med Lennart Hardell som huvud- eller medförfattare [8], [9], [10]. De två första kommer fram till slutsatsen att det finns ett samband mellan mobilanvändning (och i vissa fall användning av trådlösa telefoner) och tumörer i huvudet på den sida där man oftast hållit telefonen, medan slutsatsen i den sista studien (som inte har Hardell som huvudförfattare) är: ”No conclusive evidence of an association between use of mobile and cordless phones and meningioma was found.” Kanske har de två första studierna rätt, men de går emot forskningsläget i stort enligt American Cancer Society [11]. Här är ett citat (i översättning):
[M]ånga studier som publicerats av samma forskargrupp i Sverige har rapporterat ökad risk för tumörer på samma sida av huvudet som telefonen hållits, särskilt efter minst tio års användning. Det är svårt att veta vad man ska göra med dessa fynd eftersom de flesta studier av andra forskare inte nått samma resultat, och det finns ingen övergripande ökning av hjärntumörer i Sverige under de år som rapporterna avser.
Hardells resultat avviker alltså från resultaten från majoriteten av övrig forskning på området, vilket gör att man kan tvivla på deras tillförlitlighet. Något som också talar emot Hardells slutsatser är att cancerstatistiken för Sverige alltså inte visar någon total ökning av förekomsten av tumörer i huvudet.
Sammanfattningsvis har Strålskyddsstiftelsen i [1] plockat ut väl valda delar av ett statistiskt underlag för att måla upp en överdriven bild av en ökning av antalet hjärntumörer. Vidare påstår de, med stöd av anekdoter och mer eller mindre selektivt utvalda studier, att orsaken är mobiltelefonanvändning. Studierna de valt visar inte alltid det som SSS vill påskina och i de fall de gör detta, tycks de gå emot forskningsläget i stort.
Hjärntumörer av okänd typ
Den andra artikeln är publicerad i oktober 2014 och har rubriken ”Sharp increase in patients treated for brain tumors with unclear diagnosis in Sweden” [12]. Här försöker SSS bemöta kritiken att deras eviga slutsats om att mobiltelefoner orsakar tumörer motsägs av att statistiken i cancerregistret för Sverige inte visar någon ökning av antalet tumörer i hjärnan under de senaste decennierna. Argumenten de framför är främst följande två:
Enligt en annan statistikkälla än cancerregistret så har antalet patienter i den specifika kategorin ”behandlas för en okänd typ av tumör i hjärnan eller centrala nervsystemet” ökat med 30% från 2008 till 2012. Enligt dödsorsaksregistret har antalet dödsfall som orsakas av en hjärntumör av okänd typ ökat ännu kraftigare med 157% från 2008 till 2013.
Statistiken från cancerregistret är inte tillförlitlig eftersom det är känt att det finns en underrapportering in till registret.
Det första argumentet bygger på statistik som SSS säger sig ha fått från Socialstyrelsen, men som jag inte lyckats hitta. Låt oss dock anta att de siffror som SSS anger i sin artikel är korrekta. Figur 2 och 3 illustrerar siffrorna.
Återigen har SSS valt ett utgångsår (2008) som ger så dramatiska siffror som möjligt. Startåret 2008 har nämligen det lägsta antalet diagnoser av hjärntumörer av okänd typ (D43) sedan 2003 och antalet dödsfall i diagnos D43 är lägst sedan åtminstone 2002. Ser man på summan av diagnoserna av elakartade tumörer (C71) och tumörer av okänd typ så är trenden en måttlig ökning på 7% under perioden 2008 till 2012. Detta är en mer relevant siffra än de 30% som SSS anger. Siffrorna i statistiken är absoluta och inte relaterade till befolkningsstorleken som växt drygt 3% under tidsperioden, så ökningen är i själva verket ytterligare något mindre. Kanske finns det även fler typer av diagnoser som borde räknas med i summeringen, t.ex. godartade tumörer. Det skulle kunna påverka förändringen ytterligare.
När det gäller dödsorsaker så är ökningen mycket riktigt dramatisk (157%) när det gäller kategorin tumörer i hjärnan och centrala nervsystemet av okänd typ under perioden 2008 till 2013. Men denna ökning kompenseras nästan helt av en motsvarande minskning av antalet dödsfall i diagnostiserat elakartade tumörer. Detta noterar även SSS, men de drar inte slutsatsen att det fäller deras argument. Ökningen i dödsfall orsakade av summan av elakartade tumörer och tumörer av okänd typ från 2008 till 2013 är 1,4%. Alltså en något mindre uppseendeväckande siffra än 157%. Kompenserar man för befolkningsökningen handlar det sannolikt om en minskning. Det kanske helt enkelt är så att något har gjort att läkare blivit mindre säkra på sina diagnoser och att det därför blir fler diagnoser i kategorin ”okänd typ” och färre i kategorin ”elakartad”?
SSS hävdar att ovanstående statistik inte är förenlig med att cancerregistret visar en ganska konstant förekomst av diagnoser för tumörer i hjärnan och centrala nervsystemet de senaste åren. Men om man tar hänsyn till alla diagnoser som grupperas ihop i cancerregistret och kompenserar för befolkningsökningen så är det inte uppenbart att det skulle finnas någon diskrepans. En svårighet i jämförelsen är att cancerregistret delar upp statistiken i andra kategorier än de som SSS redovisar i artikeln. Hur som helst så är den relevanta ökningen – om den ens finns – inte alls så dramatisk som SSS vill påskina när de anger ökningar på 30% respektive 157% på några få år.
Vidare påpekar SSS att det finns tecken som tyder på att det finns en väsentlig underrapportering av hjärntumörer till cancerregistret. Främst hänvisar de till en artikel i Läkartidningen 2009 [13]. Såvitt jag kan bedöma är det legitim kritik mot tillförlitligheten hos cancerregistret som framförs där, men i sig styrker kritiken inte SSS påstående om att tumörfallen ökar. För att vara till stöd i SSS argumentation måste man ju visa att underrapporteringen blir värre och värre. Om den vore lika illa idag som för tio år sedan är ju trenderna i statistiken ändå korrekta. Och om rapporteringen skulle råka vara bättre idag än innan artikeln i Läkartidningen publicerades så har vi ju till och med en nedåtgående trend i verkligheten. Så att statistiken är otillförlitlig talar varken för eller emot SSS ståndpunkt. Jag är i alla fall enig med SSS i att det är allvarligt om statistiken är kraftigt felaktig.
Sammantaget så är det uppenbart att SSS utgår från sin övertygelse om att mobiltelefoner och annan trådlös teknik är skadlig när de väljer sina argument och vilka artiklar de refererar till. De använder statistik på ett gravt vilseledande sätt för att överdriva risker och skapa opinion och de tar uppenbarligen inte till sig av allt som talar emot deras slutsats. Om man läser deras artiklar okritiskt och utan relevant bakgrundskunskap är det nog lätt att bli övertygad om att de har rätt, men det troliga är att man då blir vilseledd.
2013 fick Strålskyddsstiftelsen föreningen Vetenskap och Folkbildnings förvillarpris. Det var välförtjänt.
Strålskyddsstiftelsen, ”Risk för hjärntumör och tumör i huvudområdet av mobiltelefon och trådlös telefon”, <http://www.donotlink.com/dbos>.
Coureau G, Bouvier G, Lebailly P, et al., ”Mobile phone use and brain tumours in the CERENAT case-control study”, Occupational and Environmental Medicine 7, 2014, 514–522.
Hardell L., et al., ”Case-Control Study of the Association Between Malignant Brain Tumours Diagnosed Between 2007 and 2009 and Mobile and Cordless Phone Use”, International Journal of Oncology 43(6), 2013, 1833–1845.
Hardell L., Carlberg M., ”Mobile phones, cordless phones and the risk for brain tumours”, International Journal of Oncology 35(1), 2009, 5–17.
Carlberg M. et al. ”Meningioma Patients Diagnosed 2007–2009 and the Association with Use of Mobile and Cordless Phones: a Case–control Study”, Environmental Health 12, 2013, 60.
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.
*/