Serial Communication and Variable Storage
Hey everyone;
Simply put, I have an ELM322 OBDII (On-Board Diagnostic) device that I am trying to get to communicate with the Prop.
The protocol between the two is simple since the chip accepts regular ASCII Symbols with an "AT" prefix when giving direction to the ELM322; or hex characters, with no prefix, to give direction to the OBDII system. Both type of commands are terminated with a carriage return, "13".
I am looking to interface the two chips to display polled results to an LCD.
I am quite the beginner with the Prop, and programming general, and as such, arrived at a standstill. I am now trying to see if someone could lend a hand.
My issue is that I have been able to, with the Prop, reset the ELM322 ("ATZ" command), turn character echoing off ("ATE0" command), and display the Chip's version number, ("ATI").
I am working on getting the ELM chip to transmit back the status of the Fuel System from the OBDII, ("0103" command). I have come as far as getting the Prop to display the result on a Serial LCD. Since I do not have the ability to work on the program whilst in the vehicle, I end up getting a "NO DATA" reply.
My issue is, I would like to store this reply into a variable so that I can manipulate it, i.e., some of the replies will require formula to display data that makes sense and instead of a "NO DATA" reply, I could put N/A with a CASE loop.
I need the variable to hold enough data, the longest result would be "NO DATA". I am currently using the Extended Full Duplex Serial Object to communicate and find that it is near impossible, due to my lack of understanding, to store the replies into a variable.
I am looking to do this with nine different parameters, or PIDs as they are called, looped as fast as the ELM322 can manage to transmit them, since the ELM is based on a much slower PIC12C5XX Micro, it is rated at a nominal data rate of 9600 Baud.
Any help given is greatly appreciated.
Let me know if I need to post the code...
TDII
Simply put, I have an ELM322 OBDII (On-Board Diagnostic) device that I am trying to get to communicate with the Prop.
The protocol between the two is simple since the chip accepts regular ASCII Symbols with an "AT" prefix when giving direction to the ELM322; or hex characters, with no prefix, to give direction to the OBDII system. Both type of commands are terminated with a carriage return, "13".
I am looking to interface the two chips to display polled results to an LCD.
I am quite the beginner with the Prop, and programming general, and as such, arrived at a standstill. I am now trying to see if someone could lend a hand.
My issue is that I have been able to, with the Prop, reset the ELM322 ("ATZ" command), turn character echoing off ("ATE0" command), and display the Chip's version number, ("ATI").
I am working on getting the ELM chip to transmit back the status of the Fuel System from the OBDII, ("0103" command). I have come as far as getting the Prop to display the result on a Serial LCD. Since I do not have the ability to work on the program whilst in the vehicle, I end up getting a "NO DATA" reply.
My issue is, I would like to store this reply into a variable so that I can manipulate it, i.e., some of the replies will require formula to display data that makes sense and instead of a "NO DATA" reply, I could put N/A with a CASE loop.
I need the variable to hold enough data, the longest result would be "NO DATA". I am currently using the Extended Full Duplex Serial Object to communicate and find that it is near impossible, due to my lack of understanding, to store the replies into a variable.
I am looking to do this with nine different parameters, or PIDs as they are called, looped as fast as the ELM322 can manage to transmit them, since the ELM is based on a much slower PIC12C5XX Micro, it is rated at a nominal data rate of 9600 Baud.
Any help given is greatly appreciated.
Let me know if I need to post the code...
TDII

Comments
If you just want to store the information, you can use an array of bytes for each string you want to store and terminate each string with a zero byte. You'll have to store the text one character at a time since the only receive routines in the FullDuplexSerial driver are single character routines. There are built-in routines in Spin for copying a block of bytes (BYTEMOVE), comparing two strings for equality (STRCOMP), and determining the length of a string (STRSIZE).
So I have to store the strings that I know will come up, then compare them to the strings that are sent from the ELM chip?
Or store them as they are received one byte at a time?
It looks as though you are saying that I have a few avenues...
TDII
This is from the DAT block were I have created two byte arrays named Error and CAP_FS_Str. The Error array shows the string that will be produced if there is no data response from the ELM chip.
Below is the Public block setup for the CAPture Fuel System Method. It sends out a string, 0103, with carriage return, that will instruct the ELM chip to reply with the status of the Fuel System. The Prop will store the reply into the CAP_FS_Str array. A case loop compares the two arrays to see if they match, if they do, it displays "N/A".
pub CAP_FS 'Fuel System Status serial.str(string("0103",13)) 'Query ELM322 to reply with Fuel System Status lcd.gotoxy(0,0) 'Go home on LCD serial.rxstr(@cap_fs_str) 'Receive String from ELM and store into array CASE @cap_fs_str 'Begin CASE loop that will determine whether array has useful data @error : lcd.str(string("N/A")) 'If CAP_FS_STR array equals ERROR array, display N/A on LCD OTHER : lcd.str(@cap_fs_str) 'OTHERwise, display contents of CAP_FS_STR array timing.pause1s(1) lcd.cls lcd.gotoxy(0,0) lcd.str(@cap_fs_str) lcd.gotoxy(0,1) lcd.str(@error) timing.pause1s(1)I added a snippet at the end to clear the screen and display the contents of both arrays. The problem I see is that the display shows:
When I erase the contents of the @ERROR array in the DAT block, I find I get:
So, I thought maybe I am mentioning the error array somewhere else in the program and it is "mirroring" the received data that is filled into the other array, but I am not.
What am I missing?
Any help is appreciated.
TDII
if strcomp(@error,@cap_fs_str) lcd.str(string("N/A")) else lcd.str(@cap_fs_str)I tried that before, but thought I needed to add a Zero to the end of the string in CAP_FS_STR.
It works now!
Thanks again.
TDII
I have successfully, with Mike's help, been able to get the Prop chip and the ELM chip to exchange information with each other for several different OBDII parameters, or PIDs.
I have found that, due to my knowledge of proper coding, I make my code as bulky as possible then try to trim it after it works. In my opinion, it is not the way I would like to do it, I believe one should be able to draw up "lean" code at the onset of the project.
Below is a snippet of code that compares the contents of a pair of Arrays that are given by a previous method.
if strcomp(@error, VARIABLE) ''Compare the ERROR array and the array given with VARIABLE LCD.str(string("N/A")) ''If the arrays match, then display "N/A" else if VARIABLE == @EL_DatStr2 ELcalcThis leads to the following piece:
pub ELcalc (LCDx, LCDy)| Index LCD.gotoxy(LCDx, LCDy) Idx := 0 ''Initalizes the Idx counter repeat until Idx > 2 ''Repeats the loop twice EL_TempVar[noparse][[/noparse]Idx] := BYTE[noparse][[/noparse]@EL_DatStr2][noparse][[/noparse]Idx+4] ''Reads the byte value of @EL_DatStr2 at the Idx+4 location ''and stores it in the Idx byte location of EL_TempVar case EL_TempVar[noparse][[/noparse]Idx] ''Due to the byte received from the ELM chip is actually given as ''an ASCII symbol, a conversion must take place. eg."FF" is not ''the hex FF but the letters FF, thus in Dec, they equal "7070" 48..57 : EL_CalcVar[noparse][[/noparse]Idx] := EL_TempVar[noparse][[/noparse]Idx] - 48 ''If the Idx of TempVar = 48..57 the subtract 48 65..70 : EL_CalcVar[noparse][[/noparse]Idx] := EL_TempVar[noparse][[/noparse]Idx] - 55 ''If the Idx of TempVar = 65..70 the subtract 55 Idx++ ''Increment Idx by 1 EL_ResultVar := ((((EL_CalcVar[noparse][[/noparse]0]*16)+EL_CalcVar)*100)/255) ''Calculate and display LCD.dec(EL_ResultVar)As stated, there are several different parameters that need to be gathered, compared to the error array and calculations made. I wanted to sum all of these methods into one method using the following code, starting at the StrComp IF Loop:
if variable == @EL_DatStr2 Calc (EL_TempVar, EL_CalcVar, EL_ResultVar, VARIABLE, 1, 3, 2, 1)This would call this method:
PUB CALC (TempVar, CalcVar, ResultVar, AAdd, LCDx, LCDy, Bytes, CNum) | Idx LCD.gotoxy(LCDx, LCDy) ''Sets up cursor to proper location on LCD Idx := 0 ''Initalizes the Idx counter repeat BYTES ''Repeats the loop for the number that Bytes is set as TempVar[noparse][[/noparse]Idx] := BYTE[noparse][[/noparse]AAdd][noparse][[/noparse]Idx + 4] ''Reads the byte value of AAdd at the Idx+4 location and stores ''it in the Idx byte location of TempVar case TempVar[noparse][[/noparse]Idx] ''Due to the byte received from the ELM chip is actually given as ''an ASCII symbol, a conversion must take place. eg."FF" is not ''the hex FF but the letters FF, thus in Dec, they equal "7070" 48..57 : CalcVar[noparse][[/noparse]Idx] := TempVar[noparse][[/noparse]Idx] - 48 ''If the Idx of TempVar = 48..57 the subtract 48 65..70 : CalcVar[noparse][[/noparse]Idx] := TempVar[noparse][[/noparse]Idx] - 55 ''If the Idx of TempVar = 65..70 the subtract 55 Idx++ ''Increment Idx by 1 case CNum ''For each parameter different calculations are made 1 : ResultVar := ((((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) * 100) / 255) ''and displayed. lcd.dec(ResultVar) lcd.str(string("%")) 2 : ResultVar := (((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) - 40) lcd.dec(ResultVar) 3 : ResultVar := ((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) lcd.decf((((ResultVar - 128) * 7812) / 10000), 4) lcd.str(string("%")) 4 : ResultVar := ((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) lcd.dec((((ResultVar * 1450377) - 147000000) / 10000000)) lcd.str(string(".")) lcd.dec((FM.FABS((ResultVar) - 147)) // 10) 5 : ResultVar := (((((((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) * 256))) + ((CalcVar * 16) + CalcVar)) / 4) lcd.dec(ResultVar) 6 : lcd.decf(((((CalcVar[noparse][[/noparse]0] * 16) + CalcVar)-128)/2),3)Finally, I added subbed in a dummy array value of "FF" to test the code. On the LCD, I get 445% instead of 100%.
I added some code to display the variables after the calculation loop:
My problem is that for some reason, even though the first byte and second bytes of TempVar are valued at FF, (dec 7070), CalcVar is equated to decimal 15 but CalcVar[noparse][[/noparse]0] stays at F, (dec 70)??
I have tried to take out certain aspects of the code, eg., separating the gathering loop from the conversion loop, etc. but to no avail.
You can see that having several methods to calculate, etc. would be a waste of code, thus, you would understand my determination with getting this to work.
Any help, is greatly appreciated.
TDII
Post Edited (tdeyle) : 7/6/2007 7:32:33 PM GMT
2) You've presented a lot of code fragments and it's not clear to me what you're asking. Start with the format of the responses from the OBDII. What are the different formats that you need to process? Can you list what the different responses are. Are all the numbers hex values? Are some of them decimal? Is there a delimiter like a comma or space used? Are the responses fixed format (like 4 hex digits)?
The responses of the ELM chip are in a "pseudo hex" format, they look like Hex characters but are in fact ASCII symbols.
For example if you transmit:
to the ELM chip, you will receive the status of the Engine Load from the ELM:
In this example, I will use:
The first four bytes are replies from the ELM giving:
"4" - Indicates an ELM Response, "1" - OBDII Mode, "04" - PID, "FF" - PID Data
I only process the last two bytes of data in this example, but there are others that have 4 bytes, (ex. RPM).
Since these are ASCII Characters, not Hex, I need to convert them to the proper values. The case loop in the CALC method, converts the values received from the ELM.
In this example, "FF" or "7070" would be converted into "1515".
This is where the problem lies since, using this method, the result after the conversion is "7015". This value gets carried further into the method and is output as 445% instead of 100%.
The only delimiter that is present is the Carriage Return (13), on the Tx string to the ELM chip. I have removed the default "," declaration from Extended_FDSerial to ensure this.
I have posted the code, less fragmented, to clarify the process.
The first method performed after the LCD setup is:
pub Gather ''This method transmits pseudo-hex commands and receives data from the ELM322 serial.rxflush ''Flush the serial receive buffer repeat ''Repeat serial.str(string("0103",13)) ''Transmit 0103 to the ELM322 ELM_RX (@FS_DatStr2) ''Capture "Fuel System Status" Data serial.str(string("0104",13)) ''Transmit 0104 to the ELM322 ELM_RX (@EL_DatStr2) ''Capture "Engine Load" Data serial.str(string("0105",13)) ''Transmit 0105 to the ELM322 ELM_RX (@CT_DatStr2) ''Capture "Coolant Temperature" Data serial.str(string("0106",13)) ''Transmit 0108 to the ELM322 ELM_RX (@STFT_DatStr2) ''Capture "Short Term Fuel Trim" Data serial.str(string("0107",13)) ''Transmit 0107 to the ELM322 ELM_RX (@LTFT_DatStr2) ''Capture "Long Term Fuel Trim" Data serial.str(string("010B",13)) ''Transmit 010B to the ELM322 ELM_RX (@MAP_DatStr2) ''Capture "MAP" Data serial.str(string("010C",13)) ''Transmit 010C to the ELM322 ELM_RX (@RPM_DatStr2) ''Capture "Engine RPM" Data serial.str(string("010E",13)) ''Transmit 010E to the ELM322 ELM_RX (@TIM_DatStr2) ''Capture "Ignition Timing" Data serial.str(string("010F",13)) ''Transmit 010F to the ELM322 ELM_RX (@IAT_DatStr2) ''Capture "Intake Air Temperature" Data serial.str(string("0111",13)) ''Transmit 0111 to the ELM322 ELM_RX (@TPS_DatStr2) ''Capture "Throttle Position Sensor" DataWhich calls the ELM_Rx method:
pub ELM_Rx (variable) ''Method used to collect and compare the data received from the ELM322 serial.rxstr(variable) ''Receive the data from the ELM322 using the variable given if strcomp(@error, VARIABLE) ''Compare the ERROR array and the array given with VARIABLE LCD.str(string("N/A")) ''If the arrays match, then display "N/A" else if variable == @EL_DatStr2 Calc (EL_TempVar, EL_CalcVar, EL_ResultVar, VARIABLE, 1, 3, 2, 1) elseif variable == @EL_DatStr2 Calc (EL_TempVar, EL_CalcVar, EL_ResultVar, VARIABLE, 1, 3, 2, 1) elseif variable == @CT_DatStr2 Calc (CT_TempVar, CT_CalcVar, CT_ResultVar, @CT_DatStr2, 17, 3, 2, 2) elseif variable == @STFT_DatStr2 Calc (STFT_TempVar, STFT_CalcVar, STFT_ResultVar, @STFT_DatStr2, 1, 1, 2, 3) elseif variable == @LTFT_DatStr2 Calc (LTFT_TempVar, LTFT_CalcVar, LTFT_ResultVar, @LTFT_DatStr2, 10, 1, 2, 3) elseif variable == @MAP_DatStr2 Calc (MAP_TempVar, MAP_CalcVar, MAP_ResultVar, @MAP_DatStr2, 7, 3, 2, 4) elseif variable == @RPM_DatStr2 Calc (RPM_TempVar, RPM_CalcVar, RPM_ResultVar, @RPM_DatStr2, 1, 2, 4, 5) elseif variable == @TIM_DatStr2 Calc (TIM_TempVar, TIM_CalcVar, TIM_ResultVar, @TIM_DatStr2, 13, 2, 2, 6) elseif variable == @IAT_DatStr2 Calc (IAT_TempVar, IAT_CalcVar, IAT_ResultVar, @IAT_DatStr2, 13, 3, 2, 2) elseif variable == @TPS_DatStr2 Calc (TPS_TempVar, TPS_CalcVar, TPS_ResultVar, @TPS_DatStr2, 7, 2, 2, 1) serial.rxflushThis calls the Calc method with various parameters, shown below:
PUB CALC (TempVar, CalcVar, ResultVar, AAdd, LCDx, LCDy, Bytes, CNum) | Idx LCD.gotoxy(LCDx, LCDy) ''Sets up cursor to proper location on LCD Idx := 0 ''Initalizes the Idx counter repeat BYTES ''Repeats the loop for the number that Bytes is set as TempVar[noparse][[/noparse]Idx] := BYTE[noparse][[/noparse]AAdd][noparse][[/noparse]Idx + 4] ''Reads the byte value of AAdd at the Idx+4 location and stores ''it in the Idx byte location of TempVar case TempVar[noparse][[/noparse]Idx] ''Due to the byte received from the ELM chip is actually given as ''an ASCII symbol, a conversion must take place. eg."FF" is not ''the hex FF but the letters FF, thus in Dec, they equal "7070" 48..57 : CalcVar[noparse][[/noparse]Idx] := TempVar[noparse][[/noparse]Idx] - 48 ''If the Idx of TempVar = 48..57 then subtract 48 from TempVar[noparse][[/noparse]Idx] and store as CalcVar[noparse][[/noparse]Idx] 65..70 : CalcVar[noparse][[/noparse]Idx] := TempVar[noparse][[/noparse]Idx] - 55 ''If the Idx of TempVar = 65..70 then subtract 55 from TempVar[noparse][[/noparse]Idx] and store as CalcVar[noparse][[/noparse]Idx] Idx++ ''Increment Idx by 1After this, I placed some debug code to show me what the values of some of the variables are:
The method continues and assigns a calculation, based on the value of CNum, that it needs to perform on the variables.
case CNum ''For each parameter different calculations are made 1 : ResultVar := ((((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) * 100) / 255) ''and displayed. lcd.dec(ResultVar) lcd.str(string("%")) 2 : ResultVar := (((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) - 40) lcd.dec(ResultVar) 3 : ResultVar := ((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) lcd.decf((((ResultVar - 128) * 7812) / 10000), 4) lcd.str(string("%")) 4 : ResultVar := ((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) lcd.dec((((ResultVar * 1450377) - 147000000) / 10000000)) lcd.str(string(".")) lcd.dec((FM.FABS((ResultVar) - 147)) // 10) 5 : ResultVar := (((((((CalcVar[noparse][[/noparse]0] * 16) + CalcVar) * 256))) + ((CalcVar * 16) + CalcVar)) / 4) lcd.dec(ResultVar) 6 : lcd.decf(((((CalcVar[noparse][[/noparse]0] * 16) + CalcVar)-128)/2),3)TDII
pri HexToBin(strAddr, strLen) | char repeat while --strLen > 0 char := byte[noparse][[/noparse]strAddr++] if (char => "0") and (char =< "9") result := (result << 4) | (char - "0") elseif (char => "A") and (char =< "F") result := (result << 4) | (char - "A" + $A) else abort ' Invalid character (like early end of line)and you'd do
if HexToBin(variable,1) <> 4 abort ' Not ELM response if HexToBin(variable+1,1) <> 1 abort ' Not expected OBDII mode if HexToBin(variable+2,2) <> 4 abort ' Not expected PID value PidData := HexToBin(variable+4,2) if byte[noparse][[/noparse]variable+6] <> 0 abort ' Missing end of linePost Edited (Mike Green) : 7/6/2007 10:45:23 PM GMT
When I express it as a byte variable, I do not get a reading, I believe it is aborting on the first "if" statement.
TDII
TDII
upper or lower case letters)
pri HexToBin(strAddr, strLen) | digit repeat while --strLen > 0 if (digit := lookdown(byte[noparse][[/noparse]strAddr++] : "0".."9", "A".."F", "a".."f") - 1) > 16 digit -= 6 if digit < 0 abort 'Invalid character result := (result << 4) | digitTo be quite honest, I'm not sure how this and Mike's code would compare for speed and size,
but my feelings are this would be a bit quicker because·there are·less spin functions, but
larger because of the lookup (lookdown) table.
My C background would also tend to cause me to write the repeat as:
repeat while strlen-- > 0
·
There was initially no action on the LCD screen so I coded a program that would display the raw strings straight from the ELM chip with no formatting.
serial.rxflush ''Flush Receiving Buffer serial.str(string("0104",13)) ''Send 0104 string with CR to ELM serial.rxstr(@EL_DatStr) ''Result is received and stored if strcomp(@EL_DatStr,@error) ''Compare the result to see timing.pause1ms(100) else lcd.gotoxy(0,0) lcd.str(@EL_DatStr) lcd.str(string(".")) timing.pause1ms(100)The results I get on the LCD are:
I tweaked the lcd.str(@EL_DatStr), adding [noparse][[/noparse]1, 2 and 3] to the Array to see what each one contained. This is what I got, shown as [noparse][[/noparse]1, 2 an 3], repsectively:
Seeing this, I rewrote lcd.str(@EL_DatStr) to read lcd.str(@EL_DatStr[noparse][[/noparse] 3]) and grouped everything in a Repeat loop.
repeat serial.rxflush ''Flush Receiving Buffer serial.str(string("0104",13)) ''Send 0104 string with CR to ELM serial.rxstr(@EL_DatStr) ''Result is received and stored if strcomp(@EL_DatStr,@error) ''Compare the result to see timing.pause1ms(100) else lcd.gotoxy(0,0) lcd.str(@EL_DatStr[noparse][[/noparse] 3]) lcd.str(string(".")) timing.pause1ms(100)After this change, I get looping data, (43 ., 42 ., 45 ., etc.) as well as some other strings: (DATA .). Which, if you reveal the whole string, reads: "NO DATA ." Now, that is why I have the STRCOMP, to compare the incoming strings with the @Error array, which reads, "NO DATA ",0 but it seems that it is not working.
I increased the pause from 100ms to 1000ms, hoping that it would allow the chips to communicate better. However, it didn't work, so I am led to believe it is due to the ELM chip communicating with the OBDII network.
My questions, is there something wrong with the STRCOMP section?
Is there any other way to differentiate between real data and NO DATA?
I am guessing that the reason why the HexToBin method did not work was due to the fact that the string will never equal to four, thus aborting the routine after the first question.
TDII