1. Trang chủ
  2. » Công Nghệ Thông Tin

Practical Arduino Cool Projects for Open Source Hardware- P29 pps

10 144 0

Đang tải... (xem toàn văn)

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 10
Dung lượng 145,08 KB

Nội dung

CHAPTER 13  WEATHER STATION RECEIVER Because there is a high probability that data sent by the transmitter will be corrupt or only partially received, it’s important to have some way to check the validity of the message. The La Crosse transmitter includes a checksum in the message so the receiver can make sure it arrived correctly. The checksum calculation function doesn’t take any arguments because when invoked it operates on global variables that have already been set, and all it needs to return is a pass/fail response value. Internally, the function uses the variable dataPos to indicate the position within the data array that it will examine, and it accumulates the result of examining each byte into the checksum variable byte PacketAndChecksum_OK_WS2355(void) { byte dataPos; byte checksum; It skips across the first four bytes, though, because they’re the timestamp and aren’t included in the checksum calculation, which only applies to the first 48 bites (12 nibbles) of the message payload. checksum = 0; for( dataPos = 4; dataPos < 10; dataPos++ ) { checksum += (bICP_WSR_PacketData[bICP_WSR_PacketOutputPointer][dataPos] >> 4); checksum += (bICP_WSR_PacketData[bICP_WSR_PacketOutputPointer][dataPos] & 0x0F); } The accumulated value then has a bitwise AND operator applied against the value 0x0F. checksum &= 0x0F; The result is then compared to the checksum value sent in the transmission and the function immediately exits with a false value if it fails the test. if( checksum != (bICP_WSR_PacketData[bICP_WSR_PacketOutputPointer][10] >> 4) ) { return( false ); } The next thing that is checked is the first non-timestamp byte of the message payload, which must always be 0x09, or binary 00001001. This appears to be a general identifier for this type of weather station and might be different for other models, but for our purposes we can simply do a direct comparison and ignore messages that don’t have this byte set correctly. if( bICP_WSR_PacketData[bICP_WSR_PacketOutputPointer][4] != 0x09 ) { return( false ); } If those checks all passed then the message is probably okay, so the function returns true and bails out. return( true ); } The Init_RF_Interpreters function is called once during setup() to make sure everything is ready to go, and makes extensive use of macros that were defined in the header file. This approach can dramatically improve the readability of your code at a high, abstract level because you can read a line such as WSR_RESET() and know that it resets the weather station receiver state machine. On the other hand it can make the details of your code more opaque, because seeing a call to WSR_RESET() doesn’t help you understand how that macro works. Use of macros to simplify your code is a balancing act between conciseness and opacity. In this project, WSR_RESET() simply resets the state machine value and the packet bit position pointer. 259 CHAPTER 13  WEATHER STATION RECEIVER void Init_RF_Interpreters(void) { WSR_RESET(); The next cryptic-looking steps are critical to the entire operation of the sketch. First, it sets the Input Capture Pin into a floating input by setting the appropriate bit in DDRB (Data Direction Register, port B) and then writing to the same bit in PORTB to ensure the pull-up resistor is also disabled. DDRB &= ~(1<<DDB0); PORTB &= ~(1<<PORTB0); The green and red test LEDs are then set up. DDRD |= B11000000; GREEN_TESTLED_OFF(); RED_TESTLED_ON(); Next, the input capture is set up with the necessary settings for noise cancelling and prescaling, and another macro is called to set it to trigger on a rising edge. TCCR1A = B00000000; TCCR1B = ( _BV(ICNC1) | _BV(CS11) | _BV(CS10) ); SET_INPUT_CAPTURE_RISING_EDGE(); Then Timer1’s mask is set to enable Input Capture Interrupt Enable (ICIE1) and Timer Overflow Interrupt Enable (TOIE1). TIMSK1 = ( _BV(ICIE1) | _BV(TOIE1) ); } Because the weather station receiver is likely to be left running indefinitely, we need to add a little workaround for a design problem in the core Arduino system. In most projects it’s enough to use the millis() function to return the number of milliseconds since the Arduino started up, but unfortunately the value is stored internally in an unsigned long integer meaning that it can only store a maximum value of up to 2^32. In milliseconds that works out to about 49 days and 17 hours, after which any timing in the program can go drastically wrong. To get around this problem we define an interrupt service routine (ISR) to be executed whenever the ATMega’s internal Timer1 overflows. Timer1 increments on every CPU cycle so it overflows very fast— about every 262.144ms for a CPU running at 16Mhz. The last line shown previously set the Timer Overflow Interrupt Enable, which then causes this next function to be automatically executed every time an overflow occurs. Then on every call to this ISR we increment an unsigned long integer, giving us a counter that steps up in increments of 262.144ms. It can still only count to 2^32, like millis(), but because it’s updating only 1/262 as fast it will last 262 times as long (i.e., about 35 years) before it will overflow. Even when it does overflow, though, it won’t be a problem for this particular program because the value is only ever used to calculate a delta against the previous timestamp. As long as the delta calculation is performed using the same 32-bit unsigned data types, the delta will still read correctly even when an overflow has happened between one timestamp and the next. ISR( TIMER1_OVF_vect ) { ulICP_Timestamp_262_144mS++; } The next function is also an ISR and it was also enabled by the Init_RF_Interpreters function shown previously. Rather than being invoked on timer overflow, this one is invoked when the conditions set for the ICP cause it to be tripped. Using this approach it doesn’t matter when the receiver sends data through to the Arduino because it will be processed on demand by the ISR. Elsewhere in the book we go out of our way to stress that ISRs should be kept as short and fast as possible: just get in, update a global variable, and get right out again without wasting CPU cycles. This particular ISR breaks that rule by putting most of the processing required by the program inside the ISR 260 CHAPTER 13  WEATHER STATION RECEIVER or in other functions that the ISR invokes (something else you almost never see!), but in this case it’s justified because the whole program is written around analyzing events that trigger this particular interrupt. First, the ISR grabs the current capture time and stores it in a variable, then turns on the green LED so that we can see it flicker for visual feedback as a datastream arrives. ISR( TIMER1_CAPT_vect ) { uiICP_CapturedTime = ICR1; GREEN_TESTLED_ON(); Then it uses macros to grab the current capture polarity and reverse it to catch all the subsequent high and low periods arriving so they can be analyzed by the RF interpreter to follow. First, though, it makes a record of whether the period that was just captured was high or low. if( INPUT_CAPTURE_IS_RISING_EDGE() ) { SET_INPUT_CAPTURE_FALLING_EDGE(); bICP_CapturedPeriodWasHigh = false; } else { SET_INPUT_CAPTURE_RISING_EDGE(); bICP_CapturedPeriodWasHigh = true; } Then, it calculates the period that was just measured. uiICP_CapturedPeriod = (uiICP_CapturedTime - uiICP_PreviousCapturedTime); At this point the program knows both the polarity of the pulse (high or low) and its duration, so it calls the RF interpreter function to process them. There’s no need to pass arguments to the function because the values are stored in global variables. RF_Interpreter_WS2355(); After the interpreter finishes the capture data from this set is stored so that it can be used again next time around to perform the period calculation. uiICP_PreviousCapturedTime = uiICP_CapturedTime; uiICP_PreviousCapturedPeriod = uiICP_CapturedPeriod; bICP_PreviousCapturedPeriodWasHigh = bICP_CapturedPeriodWasHigh; Finally the green test LED is turned off. This whole function will have been executed within the space of a few milliseconds, so it will just look like the LED is flickering as a bitstream is received. GREEN_TESTLED_OFF(); } After the event timing has been stored by the previous function the RF interpreter examines each event to find out what type of bit the incoming period is. La Crosse weather stations transmit 52 bits per message packet, and they represent a logic 0 by sending a long high period followed by a long low period, and a logic 1 by sending a short high period followed by a long low period. The important thing to remember about the next section is that RF_Interpreter_WS2355() is called a single time on each rising or falling edge transition of the ICP interrupt service routine. As long as 52 consecutive valid bit periods come in, it will convert and load all of them into the incoming bICP_WSR_PacketData[] array, timestamp it, and set it as a good received packet for the main loop to deal with. void RF_Interpreter_WS2355() { volatile byte b; 261 CHAPTER 13  WEATHER STATION RECEIVER byte bValidBit = false; If the captured period is outside the expected range it’s probably noise, so the rest of the processing is only performed if the range is acceptable. if( (uiICP_CapturedPeriod >= WSR_PERIOD_FILTER_MIN) && (uiICP_CapturedPeriod <= WSR_PERIOD_FILTER_MAX) ) { It then checks if this is a valid 0 (long high) or 1 (short high) bit, or an invalid period in between. if( bICP_CapturedPeriodWasHigh ) { if( (uiICP_CapturedPeriod >= WSR_SHORT_PERIOD_MIN) && (uiICP_CapturedPeriod <= WSR_SHORT_PERIOD_MAX) ) { bValidBit = WSR_BIT_ONE; } else if( (uiICP_CapturedPeriod >= WSR_LONG_PERIOD_MIN) && (uiICP_CapturedPeriod <= WSR_LONG_PERIOD_MAX) ) { bValidBit = WSR_BIT_ZERO; } else { If the code got to this point, it must be an invalid period in the dead zone between short and long bit period lengths, so the program assumes it’s just seeing noise and calls the reset macro to set everything back to a default state and start waiting for the next bit transition to arrive. WSR_RESET(); } } The program then enters a little state machine to load and prepare the incoming bits into a potentially complete packet, performing different actions depending on the current state. if( bValidBit != false ) { switch( bICP_WSR_State ) { case WSR_STATE_IDLE: { if( bValidBit == WSR_BIT_ZERO ) { A good La Crosse bitstream packet always starts with a 0 bit, so if the sketch receives a 0 while the state machine is “idle” and still waiting for a good packet start, it loads the bit into the packet data buffer, increments the input bit pointer, and moves on to the next state to continue loading the rest of the potentially good packet. bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][bICP_WSR_PacketInputBitPointer >> 3] &= ~(0x01 << (bICP_WSR_PacketInputBitPointer&0x07)); bICP_WSR_PacketInputBitPointer++; bICP_WSR_State = WSR_STATE_LOADING_BITSTREAM; } else { WSR_RESET(); } break; } case WSR_STATE_LOADING_BITSTREAM: { 262 CHAPTER 13  WEATHER STATION RECEIVER At this point a potentially valid packet bitstream is on its way in, so the program keeps loading it up. if( bValidBit == WSR_BIT_ZERO ) { bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][bICP_WSR_PacketInputBitPointer >> 3] &= ~(0x80 >> (bICP_WSR_PacketInputBitPointer&0x07)); } else { bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][bICP_WSR_PacketInputBitPointer >> 3] |= (0x80 >> (bICP_WSR_PacketInputBitPointer&0x07)); } At a fixed location of the incoming bitstream a further check is made to see if the first five bits are the expected 00001, and a test for an occasionally missed first 0 bit is made and corrected for. The sketch checks the location of the incoming bitstream to see if it is valid, and throws it away if not. if( bICP_WSR_PacketInputBitPointer == (WSR_TIMESTAMP_BIT_OFFSET + 4) ) { b = bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][4]; b &= B11111000; An acceptable start to the packet is 00001, but sometimes the sketch will see 00010 if the receiver module missed the first 0. First, it checks for a “missed bit” condition by looking for a packet start of 00010, and if it’s a match, the sketch realigns the byte and continues up one position past the inserted missing bit. if( b == B00010000 ) { bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][4/*bICP_WSR_PacketInputBitPointer >> 3*/] = B00001000; bICP_WSR_PacketInputBitPointer++; The only other acceptable alternative is a clean start to the packet of 00001, so if the header didn’t match either case it must be invalid and the sketch resets the state machine for another try. } else if( b != B00001000 ) { WSR_RESET(); } } As a final check the sketch tests whether the last packet bit (52 bits in total) has arrived. If it has a complete set of 52 sequential bits, it marks this packet as done and moves the major packet input pointer along. if( bICP_WSR_PacketInputBitPointer == (WSR_TIMESTAMP_BIT_OFFSET + (WSR_RFPACKETBITSIZE-1)) ) { Since it received a full valid packet, the sketch timestamps it for the main loop. bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][0] = byte(ulICP_Timestamp_262_144mS >> 24); bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][1] = byte(ulICP_Timestamp_262_144mS >> 16); bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][2] = byte(ulICP_Timestamp_262_144mS >> 8); 263 CHAPTER 13  WEATHER STATION RECEIVER bICP_WSR_PacketData[bICP_WSR_PacketInputPointer][3] = byte(ulICP_Timestamp_262_144mS); It then sets the pointer and packet count. The received packet count will overflow and wrap, but that doesn’t really matter. It’s only set for display purposes so you can see it increment while debugging. bICP_WSR_PacketInputPointer = ((bICP_WSR_PacketInputPointer+1)&(WSR_PACKETARRAYSIZE-1)); uiICP_WSR_ReceivedPacketCount++; WSR_RESET(); } The pointer is then incremented to the next new bit location. bICP_WSR_PacketInputBitPointer++; break; } } } } else { Way back at the start of the function it checked whether the period was out of bounds. If it failed that match, the state machine is reset to throw away any out of range periods and clean up to try again on the next message. WSR_RESET(); } } With the sketch loaded on your Arduino, open the serial monitor and make sure it’s set to 38400bps. Apply power to your weather station receiver and you should see a series of readings reported back via the serial port (see Figure 13-13). Figure 13-13. Weather Station Receiver values reported via the serial console The weather station seems to transmit some values several times, and different parameters come through at different times rather than all at once in a batch. It’s up to you or the software you write to parse the data to keep track of the last reading for each parameter. 264 CHAPTER 13  WEATHER STATION RECEIVER Twitter Weather Updates There are many different things you could do with the data received by the Arduino, but just as a simple example we’ll send the temperature values as Twitter updates. This can be done by fitting an Ethernet shield or WiShield to the Arduino and having the Arduino program connect directly to the Twitter API via your network. If you want to investigate this option you’ll find lots of handy information at www.arduino.cc/playground/Code/TwitterLibrary. If you want to use a WiShield to connect via your WiFi network, have a look at the instructions in the Water Tank Depth Sensor project in Chapter 12 for information on using the WiShield. The WiShield comes with a Twitter client example that makes it fairly easy to submit Twitter updates automatically. However, rather than use an Ethernet shield or WiShield for this project, we’re going to demonstrate how to link your Arduino to a program running on your computer via the USB connection. There are many situations where you may want to use a program running on a host computer to control or process data coming from an Arduino, so even though this is a trivial example you may find it useful as the basis for a much more complex project. The first step is to make the data sent by the Arduino to your serial port available to the program that will run on your computer. Most scripting languages have very poor support for serial communications, so rather than trying to open the serial port directly, we’ll cheat by using a program that runs as a daemon and opens the port for us then makes it available as a network socket. Scripting languages are champions at dealing with network sockets, so this approach can dramatically simplify things. The serial-to-network gateway you need to use will depend on the operating system you are running. The Arduino site has a handy guide to available network proxies at www.arduino.cc/playground/Interfacing/SerialNet.For Mac OS and Windows the most popular seems to be “serproxy,” which comes with step-by-step instructions for configuration. Download the appropriate archive for your operating system using the link provided on the page mentioned in the previous paragraph, extract it, and have a look at the README file for more details. On Linux there is a serial-to-network gateway called “ser2net,” which is available prepackaged on most major distributions. To configure it, open the ser2net config file, usually located at /etc/ser2net.conf, and look right at the end. You need to make sure it has a line similar to the following: 5331:telnet:600:/dev/ttyUSB0:38400 8DATABITS NONE 1STOPBIT banner That line will open TCP port 5331 using a telnet text interface and expose the device /dev/ttyUSB0 (change to suit your requirements) at 38400bps. Restart ser2net to force it to reload its configuration. With the appropriate gateway installed and configured, you should be able to connect to the output of your Arduino by closing the serial monitor window in your IDE and opening a telnet connection to port 5331 on localhost. Now you need a script that will make that connection for you and send the values to Twitter. We used PHP because it’s commonly available for pretty much any popular operating system and has a C- derived syntax that should look fairly familiar if you’re used to Arduino. PHP scripts don’t have to be executed in a web server: they can be executed directly on the command line just like scripts written in other programming languages. Enter the following code (included as twitter-weather.php in the download for this project) into a text file, configure it with your Twitter username and password, make sure it is marked as executable using a command (such as “chmod +x twitterupdate.php“ on Linux), and then run it. The script will open a socket connection and listen for updates from your Arduino, and temperature and humidity values will be posted to your Twitter account automatically. If you’ve got this far with Arduino, it should be fairly self-explanatory even if it’s not a language you’ve ever used before! The only catches to watch out for are that you need to have the CLI (command-line interface) version of PHP installed, and you also need to have CURL installed. Other than that, it should be all smooth sailing. 265 CHAPTER 13  WEATHER STATION RECEIVER #!/usr/bin/php <?php // Set Twitter credentials $twitter_username = "abc123"; // Replace with your Twitter username $twitter_password = "abc123"; // Replace with your Twitter password // Configure the serial-to-socket proxy connection to the Arduino $arduino_host = "localhost"; $arduino_port = "5331"; $update_interval = 600; // Minimum number of seconds between updates (10 mins) $last_update_temp = 0; $last_update_relh = 0; // Connect to the Arduino $fp = fsockopen( "tcp://".$arduino_host, $arduino_port, $errno, $errstr, 30 ); stream_set_timeout( $fp, 3000 ); // Watch for changes while (!feof($fp)) { $rawdata = fgets($fp, 200); //echo "raw: $rawdata\n"; // Uncomment to watch the raw data stream $sample = explode( "=", $rawdata ); $dataParam = trim( $sample[0] ); $dataValue = trim( $sample[1] ); if( strlen( $dataValue ) > 0 ) { switch( $dataParam ) { case "TEMPERATURE": if( date("U") - $last_update_temp > $update_interval ) { $message = "The temperature outside is {$dataValue}C"; $command = "curl -u $twitter_username:$twitter_password -d status=\"$message\" http://twitter.com/statuses/update.xml"; shell_exec( $command ); $last_update_temp = date("U"); } break; case "HUMIDITY": if( date("U") - $last_update_relh > $update_interval ) { $message = "The relative humidity outside is {$dataValue}%"; $command = "curl -u $twitter_username:$twitter_password -d status=\"$message\" http://twitter.com/statuses/update.xml"; shell_exec( $command ); $last_update_relh = date("U"); } break; } } } fclose($fp); ?> To see the result of this script in action, you can check the weather conditions outside the author’s office by following @ivttemp on Twitter at twitter.com/ivttemp. 266 CHAPTER 13  WEATHER STATION RECEIVER 267 Variations Private Online Weather Station Just outside Melbourne, Australia, a group of hang glider pilots is currently investigating installing one of these systems near a favorite but remote launch location that unfortunately has no local weather service. By installing their own weather station nearby and feeding the data to a web service such as Pachube (www.pachube.com), Watch My Thing (www.watchmything.com), or even just to Twitter, they can easily access historical and real-time weather data at the launch location before they leave home, potentially saving them a three-hour drive only to discover that conditions aren’t suitable and then driving another three hours home again. Setting up the project as a cooperative venture and splitting the cost between a number of pilots means the cost to each individual is very low. Just one wasted trip would cost more in fuel than contributing to a share in their own personal weather station. . workaround for a design problem in the core Arduino system. In most projects it’s enough to use the millis() function to return the number of milliseconds since the Arduino started up, but unfortunately. = 0; // Connect to the Arduino $fp = fsockopen( "tcp://". $arduino_ host, $arduino_ port, $errno, $errstr, 30 ); stream_set_timeout( $fp, 3000 ); // Watch for changes while (!feof($fp)). very poor support for serial communications, so rather than trying to open the serial port directly, we’ll cheat by using a program that runs as a daemon and opens the port for us then makes

Ngày đăng: 03/07/2014, 20:20

TỪ KHÓA LIÊN QUAN