Practical Arduino Cool Projects for Open Source Hardware- P40 pps

10 230 0
Practical Arduino Cool Projects for Open Source Hardware- P40 pps

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

Thông tin tài liệu

CHAPTER 15  VEHICLE TELEMETRY PLATFORM delay(5); } Then, just when you think it’s all done, the previous sequence is executed one final time but with the value B0010. lcd_pushNibble(B00100000); lcd_commandWriteSet(); delay(1); After that, the LCD will be initialized and it’s possible to write commands to it much less laboriously. The function lcd_commandWrite() can now be used to send subsequent commands, so first it’s set to 4- bit mode with a 5 × 8 font, as follows: lcd_commandWrite(B00101000); Then display control is turned on with a hidden cursor and no blink, as follows: lcd_commandWrite(B00001100); Entry mode is set for automatic position increment with no display shift, as follows: lcd_commandWrite(B00000110); The LCD is now ready to go but the function still needs to define those custom characters. The controller can store up to eight user-defined characters, so the OBDuinoMega sketch uses them for the following: • Character 0: not used • Characters 1 and 2: L/100 • Characters 3and 4: km/h • Character 5: ° (degree) • Characters 6 and 7: mi/g Because the program needs to define a total of only seven custom characters, it doesn’t use the first character position in memory, so it defines NB_CHAR and then calls lcd_commandWrite() to set the character-generator memory address to 0x08 (B1001000) to skip the first eight rows. #define NB_CHAR 7 lcd_commandWrite(B01001000); The characters are defined using a simple bitmap, which means that if the data is formatted in the correct way and you squint your eyes just right, you can pretend you’re in The Matrix and actually see the characters in the binary data just by looking at the array. To make it really easy for you to see, we’ve made the “high” bits bold. static prog_uchar chars[] PROGMEM ={ B10000,B00000,B10000,B00010,B00111,B11111,B00010, B10000,B00000,B10100,B00100,B00101,B10101,B00100, B11001,B00000,B11000,B01000,B00111,B10101,B01000, B00010,B00000,B10100,B10000,B00000,B00000,B10000, B00100,B00000,B00000,B00100,B00000,B00100,B00111, B01001,B11011,B11111,B00100,B00000,B00000,B00100, B00001,B11011,B10101,B00111,B00000,B00100,B00101, B00001,B11011,B10101,B00101,B00000,B00100,B00111, }; If you look at the first character on the left, you’ll make out the “L” in the top left corner, then the forward slash “/,” and then the “1” in the bottom right corner. You can then see the pair of “0” characters (actually just rectangles because they’re so tiny) in the bottom half of the second character. As you can see, it’s relatively simple to define your own characters. If you want to create your own, you can start with a 5 × 8 grid or a piece of graph paper and fill in the squares (pixels) you want to make 369 CHAPTER 15  VEHICLE TELEMETRY PLATFORM active on the LCD. Then just create an array using the format shown with 1 for every active pixel and 0 for every inactive pixel. Now that the raw character data has been defined in an array, it just needs to be clocked into the LCD controller’s character-generator RAM. The following nested loops read through each character position in turn (the outer loop), and then through chunks of eight bytes in the inner loop. for(byte x=0;x<NB_CHAR;x++) for(byte y=0;y<8;y++) lcd_dataWrite(pgm_read_byte(&chars[y*NB_CHAR+x])); The initialization sequence is now done, so it sends a “clear screen” command (define elsewhere in the sketch) and then sets the RAM address to 0. lcd_cls(); lcd_commandWrite(B10000000); } The sketch then defines a series of other helper functions that are largely self-explanatory, most of which are called by the initialization routine we just saw. GPS.pde Most of the hard work of communicating with the GPS module is taken care of by the TinyGPS library, so the GPS.pde file doesn’t need to do a whole lot. The entire file is wrapped in an “#ifdef ENABLE_GPS” block so that, unless GPS is included in the build options, none of the file will be compiled at all. The initGps() function is trivially simple, just sending acknowledgment to a connected host that the serial connection to the GPS module is being set up and then setting it to the appropriate baud rate. void initGps() { hostPrint(" * Initialising GPS "); GPS.begin(GPS_BAUD_RATE); hostPrintLn("[OK]"); } One of the most important parts of this file is the processGpsBuffer() function. It’s fairly trivial code, but it performs the vital role of pulling data from the serial buffer connected to the GPS and feeding it into the GPS object instantiated from the TinyGPS library. It’s important that this function be called frequently so that the serial buffer doesn’t overflow and drop characters being sent by the GPS, so calls to processGpsBuffer() are interspersed throughout the main program loop and in other places that could take a while to complete. bool processGpsBuffer() { while (GPS.available()) { if (gps.encode(GPS.read())) return true; } return false; } The GPS.pde file also contains a monster function called gpsdump() that mostly comes from the example code included with TinyGPS. In normal operation, this function isn’t called at all, but while working on the GPS code it can be useful to place a call to this function somewhere in the main loop so that you can see all the possible data you can get from it. It uses the GPS library to extract every possible parameter from the datastream being returned by the GPS module and prints it to the host via the serial port. 370 CHAPTER 15  VEHICLE TELEMETRY PLATFORM VDIP.pde One of the most interesting parts of the Vehicle Telemetry Platform project is the USB mass storage made possible by the Vinculum chip on VDIP1 and VDIP2 modules. This entire file is wrapped in an “#ifdef ENABLE_VDIP” check so that it’s skipped for builds without VDIP enabled. It includes the initialization function for the VDIP module, called during startup of the datalogger. Most of the function is totally boring, just twiddling the I/O lines used to control the VDIP status LEDs, providing the RTS (ready to send) handshaking, and applying hardware reset to the module under software control of the Arduino. The part to pay attention to, though, is where it asserts the pin connected to the VDIP reset line for 100ms to ensure it comes up in a clean state, then opens a serial connection to it at the configured baud rate set in the main file. Next, it sends the string “IPA,” which is a command string to tell the VDIP to enter ASCII communications mode. The Vinculum chip has two modes: ASCII and binary. The binary mode is more terse and efficient, but for ease of debugging we’re running it in ASCII mode so we can read messages sent to and received from the module and understand what’s happening. There is an equivalent command string of “IPH,” which switches the Vinculum into binary mode, and there are also binary equivalents of both the IPA and IPH commands. The module is smart enough to be able to recognize both the ASCII and binary forms of both commands in both modes, so we can issue the ASCII command form of IPA and it will always switch the module to ASCII mode irrespective of whether it was in ASCII or binary mode to begin with. void initVdip() { hostPrint(" * Initialising VDIP "); pinMode(VDIP_STATUS_LED, OUTPUT); digitalWrite(VDIP_STATUS_LED, HIGH); pinMode(VDIP_WRITE_LED, OUTPUT); digitalWrite(VDIP_WRITE_LED, LOW); pinMode(VDIP_RTS_PIN, INPUT); pinMode(VDIP_RESET, OUTPUT); digitalWrite(VDIP_RESET, LOW); digitalWrite(VDIP_STATUS_LED, HIGH); digitalWrite(VDIP_WRITE_LED, HIGH); delay( 100 ); digitalWrite(VDIP_RESET, HIGH); delay( 100 ); VDIP.begin(VDIP_BAUD_RATE); // Port for connection to Vinculum module VDIP.print("IPA\r"); // Sets the VDIP to ASCII mode VDIP.print(13, BYTE); digitalWrite(VDIP_WRITE_LED, LOW); hostPrintLn("[OK]"); } The processVdipBuffer() function simply pulls any characters stored in the serial buffer from the VDIP module and passes them on to the host. By periodically calling this function in the main loop, it’s possible for you to see the responses that the module sends to commands. void processVdipBuffer() { byte incomingByte; while( VDIP.available() > 0 ) { incomingByte = VDIP.read(); 371 CHAPTER 15  VEHICLE TELEMETRY PLATFORM if( incomingByte == 13 ) { HOST.println(); } HOST.print( incomingByte, BYTE ); } } The final function in the VDIP.pde file is modeButton(), an interrupt service routine that is attached to interrupt 1 on digital I/O pin 3 on the Mega using a falling-edge trigger. That pin is held high by the CPU’s internal pull-up resistor, and is connected to 0V via the “log on/off” button on the front panel. Pressing the button causes the level to fall and the ISR is invoked, which then checks the time since the last time the interrupt fired to provide a debounce mechanism, and then toggles the state of the logging LED. That LED is used in the main loop as a flag and checked on each pass through to determine whether to execute the logging portion of the code, so simply toggling the state of the LED is sufficient to indirectly activate or deactivate logging on the next pass while allowing the current logging cycle to complete. void modeButton() { if((millis() - logButtonTimestamp) > 300) // debounce { logButtonTimestamp = millis(); digitalWrite(LOG_LED, !digitalRead(LOG_LED)); } } Host.pde Rather than just reporting events to a connected host such as a laptop computer, the OBDuinoMega sketch has a simple command handler that allows you to control it from the host as well. The first function in Host.pde is processHostCommands(), called regularly by the main loop to check the incoming serial buffer for the host connection and act on any commands that have been received. Prior to checking for commands via the serial port, however, it checks for state changes in the logging status LED. As we just saw in VDIP.pde, one of the front panel buttons invokes an ISR that toggles the state of the logging LED. That LED is checked at this point to see if there is a discrepancy between the state of the logActive flag and the state of the LED. If logging is on but the LED has been deactivated, the VDIP status LED is set to inactive, the VDIP module is sent a command telling it to close the currently open OBDUINO.CSV file, and a message is sent to the host saying logging has stopped. Conversely, a mismatch in the other direction causes the VDIP status LED to be set to active, the VDIP is instructed to open the logfile, and the host is informed that logging has started. void processHostCommands() { // Check for state change from the front panel button if(logActive && !digitalRead(LOG_LED)) { logActive = 0; digitalWrite(VDIP_STATUS_LED, LOW); VDIP.print("CLF OBDUINO.CSV\r"); HOST.println("Stop logging"); } else if( !logActive && digitalRead(LOG_LED)) { 372 CHAPTER 15  VEHICLE TELEMETRY PLATFORM logActive = 1; digitalWrite(VDIP_STATUS_LED, HIGH); VDIP.print("OPW OBDUINO.CSV\r"); HOST.println("Start logging"); } Next, it checks the serial port buffer for commands from the host. Commands are currently limited to single characters, using numbers to control common tasks. This makes it really easy to control the Vehicle Telemetry Platform using the numeric keypad on a connected host. if( HOST.available() > 0) { char readChar = HOST.read(); The command “1” tells the sketch to open the CSV logfile on a connected USB memory stick and start logging to it. The status LED for the VDIP module is also switched from green to red to indicate that a file is open, showing that it’s not safe to remove the memory stick. If the file doesn’t currently exist, the Vinculum chip will create an empty file and open it. if(readChar == '1') { HOST.println("Start logging"); logActive = 1; digitalWrite(VDIP_STATUS_LED, HIGH); digitalWrite(LOG_LED, HIGH); VDIP.print("OPW OBDUINO.CSV\r"); HOST.print("> "); Likewise, command “2” deactivates logging by setting the appropriate states on the indicator LEDs and sending a “close file” command to the VDIP. This version also includes some test code to indicate whether the VDIP has failed to assert its active-low Ready To Send pin, meaning that the Vinculum’s internal buffer is full and it can’t accept more commands right now. In this version, the sketch just sits and spins until the Vinculum indicates that it’s ready to receive more data, which could potentially lead to the sketch blocking at this point. Ultimately, the VDIP code will need to be extended with more robust buffer checks and communications timeouts to prevent it from blocking the main loop. } else if( readChar == '2') { HOST.println("Stop logging"); while(digitalRead(VDIP_RTS_PIN) == HIGH) { HOST.println("VDIP BUFFER FULL"); } logActive = 0; digitalWrite(VDIP_STATUS_LED, LOW); digitalWrite(LOG_LED, LOW); VDIP.print("CLF OBDUINO.CSV\r"); HOST.print("> "); Command “3” appears to be quite simple but it has a bit of a trap for the unwary. It sends a command to the VDIP telling it to read out the contents of the logfile. Immedately after receiving this command, the VDIP will start sending the contents of the file to the Arduino’s serial connection as fast as it can. In the VDIP.pde file discussed previously, we saw a function called processVdipBuffer() that is called once per main loop, but because of all the other time-consuming things that happen in the sketch, it’s quite likely that by the time it starts processing the buffer, the VDIP will have already overflowed it. The result is that for very small logfiles of only a few lines, this command works just fine, but once the logfile grows a little bigger, this command fails to complete properly. 373 CHAPTER 15  VEHICLE TELEMETRY PLATFORM As an ugly workaround to this problem, the processVdipBuffer() function is called immediately after requesting a file read. Just be warned that if you execute this command with a big logfile, you’ll have to sit there and wait for the entire file to be printed before the sketch can proceed! } else if (readChar == '3'){ HOST.println("Reading file"); VDIP.print("RD OBDUINO.CSV\r"); processVdipBuffer(); HOST.print("> "); File deletion, command “4,” is quite simple. Because this command could be issued at any time, even when a file is open and being written to, it first performs the same actions as if a request had been made to stop logging (which will fail harmlessly if there was no log open), and then sends a “delete file” command to the VDIP. } else if (readChar == '4'){ logActive = 0; digitalWrite(VDIP_STATUS_LED, LOW); digitalWrite(LOG_LED, LOW); VDIP.print("CLF OBDUINO.CSV"); HOST.println("Deleting file"); VDIP.print("DLF OBDUINO.CSV\r"); HOST.print("> "); Command “5,” directory listing, is a convenience function that can be handy during testing just to make sure that a file is actually being created, without having to continually remove the memory stick and put it in another computer. } else if (readChar == '5'){ HOST.println("Directory listing"); VDIP.print("DIR\r"); HOST.print("> "); Command “6,” reset, can be extremely handy when messing around with commands to the VDIP module. When sending data to the module, it’s necessary to know in advance exactly how many bytes will be sent, including any terminating characters. If you make a miscalculation, the module can end up in a state where it sits waiting for more characters to arrive and never finishes. After having that happen once too many times while experimenting with data formats and then having to power-cycle the whole Vehicle Telemetry Platform, we connected the reset pin on the VDIP to a digital pin on the Arduino so we could reset it under software control and have everything continue on. The reset command performs almost the same actions as the initVdip() function, but assumes that the digital I/O lines have already been set to their correct modes and jumps straight in to asserting the hardware reset and then forcing ASCII mode using the IPA command. } else if (readChar == '6'){ HOST.print(" * Initializing flash storage "); pinMode(VDIP_RESET, OUTPUT); digitalWrite(VDIP_RESET, LOW); delay( 100 ); digitalWrite(VDIP_RESET, HIGH); delay( 100 ); VDIP.print("IPA"); VDIP.print(13, BYTE); HOST.println("[OK]"); HOST.print("> "); Finally, if a character is received that the host command processor doesn’t recognize, it displays a help message to explain what commands are available. } else { 374 CHAPTER 15  VEHICLE TELEMETRY PLATFORM HOST.print("Unrecognized command '"); HOST.print(readChar); HOST.println("'"); HOST.println("1 - Start logging"); HOST.println("2 - Stop logging"); HOST.println("3 - Display logfile"); HOST.println("4 - Delete logfile"); HOST.println("5 - Directory listing"); HOST.println("6 - Reset VDIP module"); HOST.print("> "); } } } The final two functions at the end of this file are really just wrappers for Serial.print() and Serial.println(), which might sound like a waste of time but it helps to simplify code elsewhere in the sketch. Because the OBDuinoMega sketch is designed to support being built without support for a serial connection to the host, these functions allow us to place calls that send messages to the host throughout the sketch without worrying about whether a host serial connection even exists. The functions themselves wrap their internal functionality inside “#ifdef MEGA” checks so that if the sketch is not built specifically for a Mega target, they will simply accept whatever is passed to them and immediately exit without doing anything. However, if the sketch was built for a Mega target, these functions invoke print() and println() to the appropriate serial port. void hostPrint( char* message ) { #ifdef MEGA HOST.print(message); #endif } void hostPrintLn( char* message ) { #ifdef MEGA HOST.println(message); #endif } PowerFail.pde The setup() function attaches the powerFail() interrupt service routine to a falling-edge on interrupt 0, which is on digital pin 2 and, therefore, connected to the voltage divider on the input of the power supply. If the voltage being provided to the power supply falls, this ISR is invoked. It then turns off the logging LED as a flag to the main loop that it needs to close the logfile on the USB memory stick on the next pass through, and also turns off the LCD backlight to save power. Ultimately, this function should probably be extended to shut down the GPS module as well to save even more power and make the power-supply capacitor last a few milliseconds longer, but this is not as easy as it might sound. Because the ISR could be called at any time, it could be invoked while the GPS is being read, resulting in the main loop blocking on a read that will never complete after the ISR turns off the GPS and exits. Using an ISR can have unexpected side effects and you always need to consider the result of it being executed at different points within the main program loop. 375 CHAPTER 15  VEHICLE TELEMETRY PLATFORM void powerFail() { digitalWrite(LOG_LED, LOW); analogWrite(BrightnessPin, brightness[0]); } Using the OBDuinoMega Sketch Menu Buttons The three buttons perform different roles depending on whether the system is displaying real-time data or is in menu mode. Real-time mode uses the following buttons: • Left: Rotate to next virtual screen. • Middle: Enter menu mode. • Right: Cycle through LCD brightness settings. • Left + Middle: Tank reset. Use this after filling up. • Middle + Right: Trip and outing reset. • Left + Right: Display PID information for current screen. Menu mode uses the following buttons: • Left: Decrease. • Middle: Select. • Right: Increase. Options you can set in the menu include the following: • LCD Contrast (0–100 in steps of 10). In our prototype, we used a variable resistor rather than controlling the display contrast from the sketch, but the menus allow for it in case you connect up contrast to a PWM output. • Use Metric units (NO/YES). NO gives miles and gallons; YES gives kilometers and liters. • Use Comma format (NO/YES). NO uses a period as the decimal place; YES uses a comma. • Fuel/hour speed (0–255). Below this speed, the display can show L/100KM or MPG; above it, the display switches to L/h or GPH. • Tank size (xx.y). Size of your tank in liters or gallons. • Fuel price (xx.y). Price of fuel per liter or gallon. 376 CHAPTER 15  VEHICLE TELEMETRY PLATFORM • Fuel Adjust (0–255%). Fuel consumption calibration factor. This can be tweaked after you’ve gone through a few tanks of fuel and manually checked how much is put in each time. • Speed Adjust (0–255%). Speedometer calibration factor. If your car speed sensor isn’t accurate, you can compensate using this value. • Engine Displacement (0.0L–10.0L). Only used with a MAP sensor. Newer cars use a MAF (mass air flow) sensor, but some cars use a MAP (manifold absolute pressure) sensor, in which case the MAF output has to be simulated using the MAF value and the engine displacement. Most cars shouldn’t need this value to be set. • Outing stopover (0–2550 minutes). Increments in periods of 10 minutes. If the car is turned off for more than this period of time, the outing will be automatically reset. Any stop shorter than this will be considered part of the same outing. For example, if you stop briefly at a shop and start the car again it will still be considered part of the same outing. Setting the value to 0 minutes will cause the outing to be reset every time the car is restarted. • Trip stopover (1–255 hours). Like the outing stopover value, but for longer periods such as a trip. This allows you to have a long journey with multiple stops, such as a road trip with hotel stays, all treated as a single trip, even if it consists of multiple “outings.” • Configure PIDs (NO/YES). Select YES to set the PIDs you want to display on each of the three virtual screens. You will then be asked to select the PID for each position. Selecting the current value with the middle button leaves it as is, while the left and right buttons decrement and increment the selection. Other than the regular OBD-II PIDs, there are also a number of additional nonstandard PIDs provided by the system itself. These are given in Table 15-12. Table 15-12. Additional non-standard J(“fake”) PIDs provided by the OBDuinoMega sketch PID Label Description 0xE9 OutWaste Fuel wasted idling for this outing 0xEA TrpWaste Fuel wasted idling for this trip 0xEB TnkWaste Fuel wasted iding for this tank 0xEC Out Cost Cost of fuel used for this outing 0xED Trp Cost Cost of fuel used for this trip 0xEE Tnk Cost Cost of fuel used for this tank 0xEF Out Time Time the car has been running 0xF0 No Disp No display, blank corner 377 CHAPTER 15  VEHICLE TELEMETRY PLATFORM 0xF1 InstCons Instant fuel consumption rate 0xF2 Tnk Cons Average fuel consumption for the tank 0xF3 Tnk Fuel Fuel used for the current tank 0xF4 Tnk Dist Distance done on the current tank 0xF5 Dist2MT Remaining distance possible on the current tank 0xF6 Trp Cons Average fuel consumption for the trip 0xF7 Trp Fuel Fuel used for the current trip 0xF8 Trp Dist Distance of the current trip 0xF9 Batt Vlt Car battery voltage 0xFA Out Cons Average fuel consumption for the outing 0xFB Out Fuel Fuel used for the current outing 0xFC Out Dist Distance of the current outing 0xFD Can Stat CAN status including TX/RX errors 0xFE PID_SEC Number of PIDs retrieved per second 0xFF Eco Vis Visual display of economy (free memory in debug mode) Running Logging In normal operation, the green “safe to remove” LED will be illuminated, meaning that the VDIP1 is not trying to access the memory stick and it can be insert or removed. To start logging, insert the memory stick and wait a few seconds to give the VDIP1 time to recognize it. If you have a computer connected to the USB port on the system and run the serial monitor in the Arduino IDE, you’ll see a message reported back when the VDIP1 probes the memory stick, so if things don’t seem to be working try running it with a computer connected so you can see if it generates any errors. Press the “logging on/off” button briefly and you’ll see the green “safe to remove” LED extinguish and the red “file open” LED illuminate. You’ll also see a flicker about once per second on the yellow “log activity” LED as it writes another line to the CSV file. Pressing the button again will turn logging off. When the green “safe to remove” LED comes back on, you can take out the memory stick, insert it into a computer, and process the logfile. The logfile 378 . 369 CHAPTER 15  VEHICLE TELEMETRY PLATFORM active on the LCD. Then just create an array using the format shown with 1 for every active pixel and 0 for every inactive pixel. Now that the. OutWaste Fuel wasted idling for this outing 0xEA TrpWaste Fuel wasted idling for this trip 0xEB TnkWaste Fuel wasted iding for this tank 0xEC Out Cost Cost of fuel used for this outing 0xED Trp. incoming serial buffer for the host connection and act on any commands that have been received. Prior to checking for commands via the serial port, however, it checks for state changes in the

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

Từ khóa liên quan

Mục lục

  • Prelim

  • Contents at a Glance

  • Contents

  • About the Author

  • About the Technical Reviewers

  • Acknowledgments

  • Introduction

  • Introduction

    • Fundamentals

    • Sharing Your Work

    • Practical Electronics for Software Developers

      • Current, Voltage, and Power

      • Mains Is Nasty

      • Reading Schematics

      • Resistance and Resistors

      • Ohm’s Law and Current Limiting

      • Choosing Wire

      • Diodes

      • Power Supplies

      • USB Power

      • Batteries

      • Wall Warts/Plugpacks

Tài liệu cùng người dùng

  • Đang cập nhật ...

Tài liệu liên quan