I said I’d leave the hard parts to last – well, even with these done, we’re not done yet, but it’s time to tackle the rain fall and wind speed sensors. These are slightly different from the others in that they are not external “high level” devices to read, but are digital inputs directly to the Pi so we need to write the code on the Pi to make them work. Both these inputs can trigger at any time, so we could poll for them, but if the Pi is busy then we might miss the pulse, so we will use a software interrupt mechanism to read them.
In a traditional microcontroller, e.g. PIC or ATmega, an interrupt input would cause the CPU to stop what it’s doing then execute your code with a bare minimal of delay then carry on what it was doing before.
The Raspberry Pi runs a multi-user, multi-tasking operating system, and if you want to do this on the Pi, then you need to write your own code which runs inside the Linux kernel. This is considered hard. So there is another way. The Linux kernel allows us to mimic this at the user-level by implementing a virtual file system, then posting a file-changed update in response to a GPIO pin changing state. This is no-where near as fast as code running inside the kernel, however for most cases it’s usable. This is what we’ll use here.
wiringPi provides a nice easy way to wrap this up for you. You simply specify the pin number, the trigger condition (a pin going from high to low, low to high or both) and a function to call when this event happens. When the trigger happens, Linux detect it, puts your main program to sleep, executes the named function, then wakes up your main program again.
(If you are lucky to be running this on a multi-core system, then your main program may not be put to sleep, but carry on running on one core with the called function running in parallel on another core)
This seems easy, and it is, but there are a few pitfalls. One is variable handling. The C compiler is very clever and it can keep track of variables – and if you had a loop, reading a variable, with nothing else in that loop changing it – well, it might be treated as a constant variable, after all, it never changes, so … So your program may produce the wrong result if this variable is changed in some sort of interrupt code that the compiler doesn’t know about. To get round this we need to use the C keyword volatile This tells the compiler that it might change elsewhere, so produces the appropriate code to handle it.
There are other issues to do with accessing variables in concurrent or parallel running threads/programs, but that’s left for an exercise for another time…
Back to the rain gauge… It turned out to be relatively simple to write the code in the end. The function that gets called when the pin interrupt is triggered is only a few lines long:
* * rainDrop: * "ISR" routine to count each tip of the rain gauge sea saw. * The debouce strategy is to set a timer and if we get called again before * the time has expired, then we reset the timer - so rather than just wait * a fixed time, we need to see /at least/ the time period without any * triggers before we count the next one. ********************************************************************************* */ void rainDrop (void) { static struct timeval lastTime = { 0, 0 } ; struct timeval now, interval ; // See if we're being called inside our debounce time gettimeofday (&now, NULL) ; timersub (&now, &lastTime, &interval) ; if (interval.tv_usec < (DEBOUNCE_TIME * 1000)) // Inside? lastTime = now ; // Reset for another period else { pthread_mutex_lock (&rainDropLock) ; ++rainDrops ; pthread_mutex_unlock (&rainDropLock) ; } }
The rainDrops variable here is a global declared with the volatile keyword. It’s used elsewhere in the file, but most of the test is handling debounce. Mechanical switches bounce and reed relays are no exception. Bounces can cause false readings, so we need to cater for them.
The rain fall and wind speed functions need a separate initialiser function: Here is the rain fall one…
void startRain (void) { rainDrops = 0 ; pullUpDnControl (RAIN, PUD_UP) ; wiringPiISR (RAIN, INT_EDGE_FALLING, rainDrop) ; }
And at this point I realised the “pins.h” file had been good so-far, but I felt it could be better, so I renamed it to setup.h and created a separate setup.h file with all the sensor initialisations in it. This meant editing all the files to change pins.h to setup.h, changing the Makefile and of-course testing it.
The setup.c file contains one function to setup all the hardware which right now looks like:
void setupHardware (void) { wiringPiSetup () ; mcp3422Setup (ADC_PIN_BASE, 0x69, 0,0) ; // To-Do: Check error codes... bmp180Setup (BMP180_PIN_BASE) ; ds18b20Setup (GROUND_T_PIN_BASE, "0000053af458") ; htu21dSetup (HTU21D_PIN_BASE) ; startRain () ; startWindSpeed () ; }
Note that the DS18B20 setup has the sensor serial number hard-coded into it. That’s telling me that maybe its better off somewhere else. Not sure where right now, but I’ll probably think of something.
At this point, I’ve tested all the hardware. The rain and wind direction sensors appear to be working – they’re just returning simple counts right now – will convert that into real numbers shortly, but it’s almost all there now.
I’ve been using this site for some thoughts about presentation:
http://www.dartcom.co.uk/weather
These folks are not that far from me (about 15 miles away) although I’ve never popped in to say hello… They also have a nice webcam on Dartmoor: http://www.dartcom.co.uk/webcam which is nice to look at some days…
Anyway, one thing on that site is a dew-point gauge – this is not a real sensor but it’s a calculated value based on the relative humidity and temperature – so I implemented a little file with the calculation in it, in the same way other sensor files are implemented in the system. I did the same for a wind chill feature too. These values can be computed at any time from the existing data, but I think it’s just as easy to do it here.
At this point, I’m making plans to move it outside… I’ve identified a location near the house. It’s not going to be in an optimal location whatsoever – the wind speed and direction will be wildly wild but it’ll do for a demonstration. I’ve installed a a Wi-Fi dongle and re-adjusted the network settings (editing /etc/network/interfaces – your setup may vary) to give me a static IP address on my Wi-Fi LAN) and tested them. It’s outside, but in-range of one of my access points, so that’ll be fine and all I need to do is run power to it. The prototype I have has a separate UBEC (Universal Battery Eliminator Circuit – popular in radio control setups to provide a 5v supply off the main power battery which may be 18v or higher) so I’ll wire that in and test it. Dave supplied me with a 24v power supply – I’ll have to extend the lead on it, but that’s just a 10 minute job with the soldering iron and some heat shrink sleeving.