-
DHT22 temperature/humidity sensor
12/04/2015 at 12:32 • 0 commentsSince I'm still a bit stuck on the preamble part of the RF link (I need more precise timers to be able to sync to the incoming serial stream), I turned my attention to setting up a DHT22 sensor on my Photon instead.
Communication with the DHT22 is via a one-wire protocol, though a special one: data bits are encoded in the length of HIGH state (~26us for 0, ~70us for 1) and are each preceded by a ~50us LOW state. To get the DHT22 to measure and send out its data, one needs to pull the data line low, then high. Adafruit have written a nice Arduino library for the DHT22, which I'm basing my code on.
Essentially I'm reusing the SoftRx idea of capturing via the ICU and creating binary data from the time-list, plus some data line wiggling to initiate the sensor:
################################################################################ # DHT22 on viperized Photon (V2) # # Created: 2015-12-04 # # This software provides data readout from a DHT22 temperature+humidity sensor on any digital pin of an MCU running VIPER python. # This code was developed and tested on a viperized Photon board (Particle Photon). # It follows closely the Arduino code by Adafruit (https://github.com/adafruit/DHT-sensor-library). # # Copyright (c) 2015 A.C. Betz. All right reserved. Developed using the VIPER IDE. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ############################################################################################################################# ############ ######### # DO THESE THINGS IN THE MAIN PROGRAM ######### ############ # import the streams module for USB serial port. import streams # open the default serial port streams.serial() #import ICU library import icu import timers ############ ######### # end of things to do in main program ######### ############ ######### # functions ######### def getDHT22data(_receivepin,_receivepinShort): #expects input like (D3.ICU,D3) [TODO: do some nifty string manipulation to get "D3" from D3.ICU] ### don't execute this more than once every 2s! timer1 = timers.timer() timer1.start() foo = 0 DHT22_temp = 0 DHT22_hum = 0 BinListDHT22 = [] timeListDHT22 = [] #Go into high impedence state to let pull-up raise data line level andstart the reading process. pinMode(_receivepinShort,OUTPUT) digitalWrite(_receivepinShort, HIGH) timer1.reset() while timer1.get()<10: foo+=1 #First set data line low for 10 milliseconds. digitalWrite(_receivepinShort, LOW) timer1.reset() while timer1.get()<10: foo+=1 # maybe change this while to one_shot? tmpICU = icu.capture(_receivepin,LOW,86,10000,time_unit=MICROS)#call to ICU seems to take some time, thus call *before* initiation is finished # End the start signal by setting data line high for [40 microseconds]. digitalWrite(_receivepinShort, HIGH) pinMode(_receivepinShort,INPUT_PULLUP) # remove all even entries, they're just "start bits", discard 1st two entries for i in range(3,len(tmpICU),1): if i%2!=0: #these are the odd entries timeListDHT22.append(tmpICU[i]) # convert to list of binaries for i in range(len(timeListDHT22)): if timeListDHT22[i] < 35: # shouldn't be longer than 28us, but allow some wiggle room here BinListDHT22.append(0) else: BinListDHT22.append(1) # extract hum, temp parts (16bits each) tmp_hum = BinListDHT22[0:16] #1st 16 bits are humidity, 2nd 16 bits are temperature tmp_temp = BinListDHT22[16:32] tmp_tempSign = 1 if tmp_temp[0] == 1: tmp_tempSign = -1 # neg temperatures are encoded most significant bit = 1 tmp_temp[0] = 0 tmp_temp = tmp_temp[::-1] #invert the list for conversion to decimal tmp_hum = tmp_hum[::-1] for i in range(16): DHT22_temp += tmp_temp[i]*(2**i) DHT22_hum += tmp_hum[i]*(2**i) DHT22_temp = DHT22_temp/10 DHT22_hum = DHT22_hum/10 digitalWrite(_receivepinShort, HIGH) timer1.clear() return (DHT22_hum, DHT22_temp) ############################################################################################ ############################################################################################ #test the code sleep(1000) print("starting") sleep(500) global DHT22_temp global DHT22_hum timer2 = timers.timer() timer2.start() while True: if timer2.get()>2500: DHT22_hum, DHT22_temp = getDHT22data(D3.ICU,D3) print(DHT22_temp,DHT22_hum) timer2.reset()
It works quite well, although the temperature readings seem a bit high (by 2-3 degrees).
update 12/12/2015
I'm happy to see that Davide over at viperize.it has adapted my DHT22 code to also work with the DHT11.
-
Manchester encoding via ICU
11/25/2015 at 17:14 • 0 commentsReceiving Manchester encoded data on the Photon's Input Capture Unit is now finally up and running. Well, the data bit at least (see below). I am still struggling to sync the receiver to the incoming serial clock, though. But that's a story for another day...
Here's the code that let's me capture Manchester encoded data (sent from a Nano following closely the work done by mchr3k) on the Photon
################################################################################ # ManchesterRx_v6 # # Created: 2015-11-20 # # This software creates a serial code receiver on any digital pin of an MCU running VIPER python. # Serial data needs to be Manchester encoded. # The Manchester en/decoding works along the lines of the Atmel Application Note "Manchester Coding Basics" # This code was developed and tested on a viperized Photon board (Particle Photon). # # Copyright (c) 2015 A.C. Betz. All right reserved. Developed using the VIPER IDE. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ############################################################################################################################# ############ ######### # DO THESE THINGS IN THE MAIN PROGRAM ######### ############ # import the streams module for USB serial port. import streams # open the default serial port streams.serial() #import ICU library import icu ############ ######### # end of things to do in main program ######### ############ ############################################################################################################################# ############ ######### # functions ######### ######### # convert RxBuffer to decimal ######### def SoftRx_Bin2Dec(_BinList): global tmpDec tmpDec = 0 for i in range(8): tmpDec += _BinList[i]*(2**i) return tmpDec _BinList[0:8] = [] ######### # round of values ######### def round(_x): if _x - int(_x) >0.5: # int() always rounds towards zero & _x always >0 return _x+1 else: return _x ######### # receive Manchester encoded data on the software serial port using InputCaptureUnit ######### # # this code is waiting for 8 bits encoded in Manchester code flanked by a start and stop signal (each a [HIGH,LOW] tuple, i.e. a Manchester 0). # Manchester code represents logic 1/0 by rising/falling edge transition in the bit middle: # HIGH/1 __ LOW/0 __ # __| |__ # # ######### # receive an array of data bytes (without termination zeros in the stream!) def receiveManByteArray(_receivepin,_baudrate,_numberOfBytes): # expects inputs of the form (D1.ICU, 600, 10) duration1bitMICROS = int(round((1/_baudrate)*(10**6))) # 1s = 10^6 microseconds, rounded to next integer global RxBuffer RxBufferMAN = [] BinListMAN = [] tmpICU = icu.capture(_receivepin,LOW,16*_numberOfBytes,int(round((8*_numberOfBytes+0.75)*duration1bitMICROS)),time_unit=MICROS) # start capturing on falling edge, i.e. in the middle of the start "0" timeValues = [int(round(2*x/duration1bitMICROS)) for x in tmpICU] if len(tmpICU) > 0: BinListMAN = [] # initiate list of binary values created from the microseconds list coming from the ICU, these are "half bits" though! for i in range(len(tmpICU)): if i%2==0: for j in range(timeValues[i]): BinListMAN.append(0) else: for j in range(timeValues[i]): BinListMAN.append(1) BinListMAN[0:1] = [] #remove first "bit" for i in range(8*_numberOfBytes+1): if BinListMAN[2*i:2*i+2] == [1,0]: RxBufferMAN.append(0) else: RxBufferMAN.append(1) print(RxBufferMAN) for i in range(_numberOfBytes): RxBuffer.append(SoftRx_Bin2Dec(RxBufferMAN)) RxBufferMAN[0:8] = [] RxBufferMAN = [] return RxBuffer ############################################################################################################################# # ############ # ######### # # this section is only to test the SoftRx_ICU code # ######### # ############ _baudrate = 500 _receivepin = D4.ICU global RxBuffer RxBuffer = [] sleep(3000) print("starting") while True: receiveManByteArray(D4.ICU,500,5) print(RxBuffer) RxBuffer = []
-
Manchester Encoding
11/10/2015 at 11:22 • 0 commentsOne of the goals of this project is to have sensor nodes transmitting data wireless to the Photon.
Reading serial data from a digital pin is of course a good start for this, but it's not going to work very well with the cheap 433MHz modules that I have available at the moment: The 433MHz modules communicate in so called On-Off-Keying, i.e. 1 and 0 are represented by RF power on and off. In the absence of actual data transmission the receiver will then ramp up its RF gain till it sees a "signal" and we end up with noise "data". This might even happen for say a transmission of the form 0b10000000.
A way to combat this is to use Manchester encoding: logic bits 0/1 are encoded as rising/ falling edges in the middle of the transmitted bit.
There are good libraries for sending out Manchester encoded data on Arduino (or any other MCU IDE, I guess) and on the VIPER side of things we are also nearly there, as decoding Manchester data is all about the time between rising and falling edges.
So the next step is to adapt the SoftRx_ICU routine for Manchester data and then get a first wireless transmission!
-
Software Serial Receive via the Input Capture Unit
11/06/2015 at 16:58 • 0 commentsSince the Photon is now running Python, which can do multi-threading, it's not necessarily the best idea to "interrupt & delay". That could grind everything to a halt. Not what we want...
So instead I've been working on reading the incoming serial from the Photon's Input Capture Unit. Most of the Photon's digital pins are enabled for that and since it's a hardware (!) feature it means the rest of the code can continue to run (Provided the different parts are in different threads).
So here's the code:
################################################################################ # SoftRx_ICU # # Created: 2015-11-06 16:31:10 # # This software creates a serial code receiver on any digital pin of an MCU running VIPER python. # It was developed and tested on a viperized Photon board (Particle Photon). # # Copyright (c) 2015 A.C. Betz. All right reserved. Developed using the VIPER IDE. # # This library is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2.1 of the License, or (at your option) any later version. # # This library is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA # ############################################################################################################################# ############ ######### # DO THESE THINGS IN THE MAIN PROGRAM ######### ############ # import the streams module for USB serial port. import streams # open the default serial port streams.serial() #import necessary libraries import timers import pwm #needed to enable hwtimers to run (?) import hwtimers import icu ############ ######### # end of things to do in main program ######### ############ ############################################################################################################################# ############ ######### # functions ######### ######### # convert RxBuffer to decimal ######### def SoftRx_Bin2Dec(_BinList): global tmpDec tmpDec = 0 for i in range(8): tmpDec += _BinList[i]*(2**i) return tmpDec _BinList[0:8] = [] ######### # round off values ######### def round(_x): if _x - int(_x) >0.5: # int() always rounds towards zero & _x always >0 return _x+1 else: return _x ######### # receive data on the software serial port using InputCaptureUnit ######### # # this code is waiting for an 8bit number flanked by a 2bit start and stop signal (each a [1,0] tuple) # so it's looking for this kind of signal: # __ __ # |__--|--|--|--|--|--|--|--| |__ # # start| 8 bit binary |stop # # as far as I'm aware the arduino SoftSerial library only sends a LOW start bit and a HIGH stop bit. # The start bit would be OK for this code (it's looking for a falling edge to start capture), but it doesn't cope well with just a HIGH stop bit. ######### def receive1byteICU(_receivepin,_baudrate): # _receivepin must be DigPin.ICU global RxBuffer duration1bitMICROS = int(round((1/_baudrate)*(10**6))) # 1s = 10^6 microseconds, rounded to next integer #wait for a falling edge to start capturing tmpICU = icu.capture(_receivepin,LOW,8,int(10*(round(1/_baudrate))*(10**6)),time_unit=MICROS)#returns a list of microseconds #create binary list from tmpICU timeListICU = [int(round(x/duration1bitMICROS)) for x in tmpICU] #list of times in units of bit duration BinListICU = [] # initiate list of binary values created from the microseconds list coming from the ICU for i in range(len(tmpICU)): if i%2==0: for j in range(timeListICU[i]): BinListICU.append(0) else: for j in range(timeListICU[i]): BinListICU.append(1) BinListICU[0:1] = [] #remove start bit if len(BinListICU) < 8: #fill up all 8 bits for i in range(8-len(BinListICU)): BinListICU.append(0) RxBuffer.append(SoftRx_Bin2Dec(BinListICU)) # add received decimal to buffer return RxBuffer # hand the buffer back to the thread/main program ######### # start up serial port using InputCaptureUnit ######### def SoftRx_startICU(_rxpin,_baudrate): global RxBuffer # any thread/program that wants to modify (!) the buffer needs to declare it global RxBuffer = [] while True: receive1byteICU(_rxpin,_baudrate) # now we're receiving decimal serial data ############################################################################################################################# ############ ######### # this section is only to test the SoftRx_ICU code ######### ############ def threadFct2(): while True: #testing sleep(10) # give the buffer time to fill if len(RxBuffer)>9: print(RxBuffer) RxBuffer = [] sleep(1000) print("starting") sleep(500) thread(SoftRx_startICU,D4.ICU,400) #start listening on the SoftSerial port thread(threadFct2)
And a screenshot of the SoftRx_ICU in action: -
Arduino-like SoftwareSerial
10/28/2015 at 17:02 • 0 commentsIn the Arduino IDE there's a very nice little library called NewSoftSerial, which allows to establish a serial link on any digital pin of your Arduino board. Very neat stuff indeed and often used for 433MHz RF links.
Since this didn't exist on VIPER (yet), I've ported the "receive serial" part, using interrupt routines, to it (see below).
Now, I'm sure that there's better ways of coding this, but for the moment I'm quite happy with it.
################################################################################ # SoftRx # # Created: 2015-10-27 08:46:15.793526 # ################################################################################ #DO THESE THINGS IN THE MAIN PROGRAM # import the streams module for USB serial port. import streams # open the default serial port streams.serial() #import necessary libraries import timers import pwm #needed to enable hwtimers to run (?) import hwtimers global RxBuffer global RxByteDec ## end of things to do in main program ######### #set up the SoftRx routine ######### def SoftRx_setup(_rxpin, _baudrate, _timeUnit): # set _timeUnit=1 for microseconds, default is milliseconds global last_Rx_time global Rx_time global Rxpin global duration1bitMICROS global duration1bitMILLIS global RxBuffer global TimeUnit TimeUnit = _timeUnit Rxpin = _rxpin duration1bit = 1/_baudrate #duration of HIGH/LOW for 1 transmitted bit in seconds # duration1bitMILLIS = int(duration1bit*(10**3)) duration1bitMICROS = int(duration1bit*(10**6)) # 1s = 10^6 microseconds, rounded to next integer RxBuffer = [] last_Rx_time = 0 #used for interrupt settling Rx_time = timers.now() #used for interrupt settling, time in milliseconds ## debugging # print("debugging") # print(duration1bitMILLIS) # print(3/2*duration1bitMILLIS) # print(int(3/2*duration1bitMILLIS)) ######### # receive data on the software serial port ######### def receive8bits(): global last_Rx_time global Rx_time global Rxpin global duration1bitMICROS global duration1bitMILLIS global RxBuffer Rx_time = timers.now() time_test = Rx_time - last_Rx_time #time difference between last 2 interrupts, in milliseconds if time_test > int(10*duration1bitMILLIS): #interrupt fires at each HIGH->LOW, hence need to ignore any interrupt during reception of 1 byte = 1 start bit + 8 bits+1 stopbit if TimeUnit==1: hwtimers.sleep_micros(int(duration1bitMICROS/2)) # read at bit centres else: sleep(int(duration1bitMILLIS/2)) #only works for baudrates <1000 #sleep(1) for i in range(9): #now read 9 bits (start+8) at their respective centres tmp = digitalRead(Rxpin) if len(RxBuffer)>255: #don't store more than 32 bytes (=256 bits) in buffer RxBuffer[0] = [] #remove oldest bit RxBuffer.append(tmp) #print(tmp) #use for debugging if TimeUnit==1: hwtimers.sleep_micros(duration1bitMICROS) # start bit: LOW for duration=1/baudrate, then read at bit centres else: sleep(duration1bitMILLIS) #only works for baudrates <1000 #sleep(2) last_Rx_time = Rx_time RxBuffer[0:1] = [] #remove start bit return RxBuffer else: return ######### # start up the serial port ######### def SoftRx_start(): onPinFall(Rxpin,receive8bits) #start listening on the SoftSerial port ######### # convert RxBuffer to decimal ######### def SoftRx_Bin2Dec(_BinList): tmpDec = 0 for i in range(8): tmpDec += _BinList[i]*(2**i) return tmpDec ##the following is just to test the above... SoftRx_setup(D0,500,1) # set up all the necessary bits SoftRx_start() #start listening on the SoftSerial port while True: sleep(1000) if len(RxBuffer)>0: RxData = RxBuffer[0:8] RxBuffer[0:8] = [] RxByteDec = SoftRx_Bin2Dec(RxData) print(RxData) print(RxByteDec) print("---")
-
Viperizing the Photon
10/28/2015 at 16:40 • 0 commentsOK, what the hell is he talking about, I hear you say.
A few months ago I bought a new wifi enabled MCU (then) called Spark Photon (https://docs.particle.io/datasheets/photon-datasheet/). It comes with its own Arduino inspired IDE and some cloud API. Which it seemed to me everything runs through, all my code, sensor readings, the lot. I'm not a fan of putting stuff on the cloud and only flashing over-the-air, so I looked for alternatives in terms of firmware. Enter VIPER (www.viperize.it), a Python based firmware for MCUs.
After connecting the Photon to the Particle cloud once (and with great difficulty, I must say), it was time to follow the VIPER guys; instructions and "viperize" the Photon. With the right DFU drivers and VIPER installed it went ahead just fine. Even the wifi connection was established near out-of-the box :)