Tải bản đầy đủ (.pdf) (10 trang)

Practical Arduino Cool Projects for Open Source Hardware- P40 pps

Bạn đang xem bản rút gọn của tài liệu. Xem và tải ngay bản đầy đủ của tài liệu tại đây (153.88 KB, 10 trang )

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

×