OBDII Interface Project

 

Counter

When I can ever find enough time away from schoolwork, I try to work on an OBDII compliant portable ScanTool. However, with my current course load and lab projects progress on this project has been very slow. I first became interested in designing my own handheld ScanTool when my Dad purchased a PC-to-OBDII interface. The interface is simple, just a PIC16F84-20I/P programmed to send OBDII frames to the PC via RS-232. The interface works great, however, it is somewhat impractical because in order to use it you must drag a laptop all the way out to the car. That got pretty tedious; especially when you get everything out to the car and realize the laptop battery is dead! Thus the idea of my own self-contained ScanTool was born. The idea is to have the OBDII interface, DTC lookup tables, and an information read-out all on one tool.

 

          Hardware Interfaces Designer’s Notes Receive Routine CRC Routine Home VPWM Library

***UPDATE 3/23/2008***

It has been quite a while since I've made an update to this page. Mainly because of the fact that I haven't worked on this project in several years. Then today as I was browsing through old code for the T6963C Graphic LCD I came across a goofy mistake on my part. When I packaged and uploaded the library files, I zipped the wrong ones. The ones that got uploaded contained an error. I have uploaded the correct copy of the library so the T6963C drivers should be corrected.

 

The receive routine has now been completed and is no longer theory only. Click the "Receive Routine" link above to be taken to the appropriate section.  Also, for some reason I forgot to include the CRC calculation routine in the previous upload.  This has since been added to the library.  It will not be discussed thoroughly (just information on how to use it) and credit must be given to the original author, B. Roadman www.obddiagnostics.com.

 

Be sure to check out the forum for the latest updates and design notes. The link is provided to the left. The forum contains the latest announcements, fixes, and is a place to post your comments and questions.

 

The “Brain”

 

The core of this project is a Microchip PIC16F877(A) microprocessor. The PIC interfaces with the OBD bus, retrieves any returned DTC information from the external EEPROM chips, and displays the information on a 64X128 Graphic LCD. Currently, the firmware will only have support for the J1850 VPW protocol. However, in future versions I do intend to extend the supported protocols to include the J1850 PWM and ISO-9141 but expansion will be dependant upon the receipt of protocol specifications.

 

Before I delve into the nitty-gritty of how the PIC handles each of the tasks above, a word should be said about the J1850 VPW protocol. J1850 VPW is an “asynchronous, master-less, peer-to-peer protocol that supports equal network access for every node (1).” The J1850 bus supports CSMA/CR (Carrier Sense, Multiple Access, Collision Resolution) arbitration for ultimate bus control. Interpretation? Basically any number of nodes can begin transmitting at any given time, each fighting for control of the bus while doing so, and each node has equal priority on the network. If each node has equal priority, how is the “winner” of the bus determined you ask? Well, bus control is determined through a process called arbitration. Due to the hardware architecture this process allows “active” symbols to win over “passive” symbols. Therefore, the node that has the most “active” symbols in the beginning of their message frame will ultimately control the bus. More on “active” versus “passive” symbols to follow in a moment. There are two key features that make this type of network possible:

 

  1. The bus is weakly pulled to ground.
  2. Asynchronous communication – each transmitting node sees its own message “echoed” back as it is transmitting.

 

The first feature is important in that when idle, the bus will settle to the ground state. This is important for the Carrier Sense (CS) portion of the protocol. Before beginning transmission, each node must first “listen” or poll the bus for a certain amount of time to determine if the bus is available, i.e. in the ground state. The second feature is important for arbitration purposes. If a node is transmitting a “passive” symbol but “sees” an active symbol echoed back then that node has lost arbitration and should cease transmitting and continue functioning as a receiver. This process continues for each remaining node until only one node is left. The dominance of active symbols over passive symbols ensures message integrity. So what is an active or passive symbol? Well, in the J1850 VPW protocol a “1” is not necessarily defined as a high potential and a “0” is not necessarily defined as a low potential like you might expect. Instead, each character is represented by the amount of time the bus stays in a particular state. This leads to two possible representations for each bit, one active and one passive. In particular, a “0” bit is represented by either a high potential (active) lasting for 128mS or a low potential (passive) lasting for 64mS. Likewise a “1” bit is represented by either a high potential (active) lasting for 64mS or a low potential (passive) lasting for 128mS. The bus may transition between high and low but it is the amount of time the bus spends in each state that determines the character. This is particularly useful in “noisy” environments, such as your automobile. These timings are shown below in figure 5.

 

 

This type of encoding scheme leads to the dominance of a “0” over a “1”. An example of the arbitration process is shown below in figure 2 demonstrating the dominance of an active symbol over a passive one. (I didn’t assign the figure numbers; both came from different parts of the same text.)

 

 

In the above timing diagram three nodes (A, B, & C) all begin talking at the same time. The bottom trace shows what potential is actually being represented on the bus. In this example, all three messages start out the same, a SOF (Start of Frame) symbol (discussed in a moment), and the following data bits- 00110. At this point node C wishes to continue transmission with a 1 bit but both nodes A & B wish to continue with a 0 bit. Therefore, node C attempts to let the bus settle back to the low potential state but nodes A & B keep driving the bus to a high potential. Node C detects this contention and drops out of the transmission. Nodes A & B continue transmitting for two more bits until node A wants to transmit a 1 but node B wants to transmit a 0. Node A detects this contention and drops out of the transmission. Node B continues to transmit until its last bit has been sent. Looking at the bottom trace, the J1850 Bus reflects that the message sent by node B has been left intact. Following the completion of node B’s transmission, nodes A & C can begin re-transmitting their messages, again arbitrating for control of the bus.

 

So what’s that SOF thingy at the beginning of each message? Recall that the idle state for the bus is a low potential. The SOF or Start of Frame symbol is a character used to denote the beginning of a message frame. Basically it’s a node’s way of waving its hand and saying, “Hey, I wanna talk now.” It is defined as a high potential on the bus lasting for a nominal period of 200mS.

 

Now you might be asking, “Why did I go into such a lengthy discussion about the protocol here when this section is supposed to be about the PIC?” and the answer is it will help you understand the logic in the code that I have written. So now the moment you’ve been waiting for, a functional description of the code. (Drum roll anyone?)

 

Basically, there is nothing too special about the program itself. In order to ensure accurate symbol timings, the Capture/Compare/PWM module on the PIC is used to toggle an output pin to drive the bus. In order to do this we must first configure both the Timer1 module and the CCPXCON to work together to create our bit symbols. First we need to determine exactly how long a message frame will be in seconds and configure timer1 to be able to count this long without overflowing. If timer1 overflows then the amount of work we have to do as programmers has just increased greatly. A complete J1850 message frame looks like this: SOF + 1 or 3 header bytes + 10 or 8 data bytes + 1 CRC byte + EOF. The J1850 defines the maximum number of bytes that can be transmitted in a single frame is 12 and that includes the CRC byte. Therefore, from a standpoint of timing the longest message actually looks like this: SOF + 12 data bytes + EOF. Since SOF and EOF have predefined timings the only variable we have in terms of time is the 12 data bytes.

 

Looking at the defined timings for each bit we determine that the “longest” byte that can be sent in terms of time would be 0b_1010_1010 or 0xAA. The time division per symbol in this case would be 128mS. So for an 8-bit byte the total time for this byte would be 8 * 128mS = 1024mS or 1.024mS. If we have a message composed of 12 of these bytes (remember this is a worst case scenario, it can never actually be used as a valid message frame) the total time for this message would be 12 * 1.024mS = 12.288mS. Adding to this the time allotted for the SOF and EOF symbols we arrive at a total frame time of 12.288mS + 500mS = 12.788mS. So as long as timer1 does not overflow within 12.788mS we will be fine. Using a 20MHz crystal for the PIC translates to a 200nS instruction cycle. Since timer1 is a 16-bit wide timer, at this frequency, technically we could get away without using a prescaler for timer1. (2^16 * 200nS = 0.0131072 S or 13.1072mS > 12.788mS) However, in order to compensate for clock mismatch on the network (remember the protocol is asynchronous meaning there is no “master” clock so just because our clock says that time’s up doesn’t mean that all the other node’s clocks say it is) and just to make the calculations simpler in the program I opted to use a prescaler value of 1:4. This gives us an overflow time of 2^16 * 200nS * 4 = 0.0524288 S or 52.4288mS. This not only has ample room for clock mismatch but also gives us a break in that both our 128mS and 64mS bit times can be loaded by a single 8-bit byte value. We now know how we need to set up the timer1 module so lets go ahead and do that.

f877_t1con = 0x00 -- 1:4 prescaler for timer1

t1ckps1 = on

tmr1if = false -- clear interrupt flag

f877_tmr1h = 0x00

f877_tmr1l = 0x00

 

f877_t1con = 0x00 -- 1:4 prescaler for timer1

t1ckps1 = on

tmr1if = false -- clear interrupt flag

f877_tmr1h = 0x00

f877_tmr1l = 0x00

 

f877_t1con = 0x00 -- 1:4 prescaler for timer1

t1ckps1 = on

tmr1if = false -- clear interrupt flag

f877_tmr1h = 0x00

f877_tmr1l = 0x00

 

 
 

 

 

 

 

 

 

 


So now we have timer1 setup and initialized with our 1:4 prescaler and the timer’s high and low registers have been cleared. It’s time to move on to the compare registers.

 

I’m assuming that if you are reading this site then you already have at least a basic understanding of PIC’s and of the integrated modules. If you already know about the compare module and its features then you can skip down to the microchip. For those who are not familiar with PIC’s or do not really understand what the compare module does then read on. To start, the PIC16F877 has all sorts of handy little “features” built into the hardware inside the chip. For example, the F877 has a built-in 10-bit wide A/D converter, built in USART and MSSP for serial communications, built-in Capture/Compare/PWM modules, and many many more features. These built in hardware features make our lives as programmers much simpler since all of the “hard stuff” associated with coding these into our programs has been taken care of by the chip. They also reduce the need for many external chips that would be required if these features were not integrated. We will be primarily focusing on the Capture/Compare/PWM module with this project. The CCP module is a set of two registers 8-bits wide + 1 control register that can be setup to “raise a flag” when either a value from timer1 has been captured, or when timer1 has reached a certain value. It can be used to control the time base for a Pulse Width Modulated output. The PIC16F877 has two CCP modules onboard referred to as CCP1 and CCP2 (how original huh?). From the datasheet:

Each Capture/Compare/PWM (CCP) module contains a 16-bit register which can operate as a:

• 16-bit Capture register

• 16-bit Compare register

• PWM Master/Slave Duty Cycle register

So what does the CCP capture or compare? Well timer1 of course! The control register is where the CCP module is configured to perform its required task. When it is configured as a 16-bit Compare register a user-determined value is loaded into the 2 x 8-bit CCP registers. Timer1 is then allowed to run freely, from either an internal or external clock. When the value in timer1 matches the loaded value in the CCP registers one of four things can happen:

  1. Compare mode, set output on match (CCPxIF bit is set)
  2. Compare mode, clear output on match (CCPxIF bit is set)
  3. Compare mode, generate software interrupt on match (CCPxIF bit is set, CCPx pin is unaffected)
  4. Compare mode, trigger special event (CCPxIF bit is set, CCPx pin is unaffected); CCP1 resets TMR1; CCP2 resets TMR1 and starts an A/D conversion (if A/D module is enabled)

 

What’s all this mumbo-jumbo you ask? Remember earlier when I said that I use the compare module to toggle an output pin to generate my bit symbols that is what situations 1 & 2 describe above. The CCP module can be configured to control an output pin on the PIC. In situation 1, when a match between timer1 and the CCP module occurs, the CCP module immediately sets or drives the output pin high. Situation 2 describes the opposite, when a match occurs the CCP module drives the output pin low. What might not be so implicit in this is that when the CCP module is configured to drive the output pin low it first places the output pin in a high potential state while it waits! It wouldn’t make any sense to drive a pin low that was already low now would it? Both situations 1 & 2 above do not affect the value that is loaded in the timer. Situation 3 is useful in that it does not affect the state of the output pin; it only raises its hand and says, “Hey look, timer1 has matched me!” Situation 4 is similar to situation 3 in that it doesn’t affect the state of the output pin, but it does do some other things. Both CCP1 and CCP2 will reset timer1, i.e. start it over at 0, but CCP2 also does something a little extra; it starts an A/D conversion if the chip is so configured. All 4 situations raise the same flag I mentioned in situation 3.

 

When the CCPX module is configured to capture, the CCPX module will “take a picture” of the current value stored in timer1. When this happens depends upon how the CCPX module is configured. In capture mode, the CCPX module can be configured in one of 4 ways:

  1. Capture mode, every falling edge
  2. Capture mode, every rising edge
  3. Capture mode, every 4th rising edge
  4. Capture mode, every 16th rising edge

 

Now you say, “every rising/falling edge of what?” Well, the CCPX module watches an input pin very closely. When it is configured to capture on the rising edge of this input, the module will capture the timer1 value when the input pin makes a transition from low to high. When it is configured to capture on the falling edge it does so when the input pin transitions from high to low. See… rising edge – low to high, falling edge – high to low. This stuff is easy. The last two modes don’t really warrant an explanation since you already understand what rising and falling edges mean J.

 

The PWM portion of the module is not used in this project so it will not be discussed. (I’m already long-winded enough don’t you think?) If you want to know more about PWM then grab yourself a copy of the datasheet and start reading.

 

 

In this project we will be using the CCP2 register for data transmission and CCP1 & either timer2 or CCP2 for data reception. Since I haven’t really finished coding the data reception part I haven’t decided on which combination will be used. Data reception will be discussed from a software theory point of view only later in this document. Right now though we will focus on data transmission. The reason I decided to use the timer1/CCP2 combo for data transmission is because of the CCP’s ability to directly manipulate an output pin. This will provide us with very consistent and precise bit symbol timings with little software overhead. Recall we already have timer1 configured for our appropriate 1:4 prescaler and its registers have been reset to 0x00. That leaves us with configuring the CCP2 register. So how do we do that? First we need to determine what we want the CCP2 module to do. Looking at the J1850 specs, all data is transferred using and alternating active/passive scheme. Look back up at figure 2 to refresh you memory if you need to. This means that there is only one transition between bits. Sounds like a perfect job for the CCP2 module doesn’t it? I mean, use the CCP2 module to toggle our output pin at predefined intervals, you’d think that is what it was designed to do J . And you thought this was going to be hard. What makes this not so simple, however, is that each bit has different time intervals associated with each state. Therefore we need to update the compare registers dynamically, or “on-the-fly” while we are transmitting. Now I could be mean and simply post the entire 225 line send procedure and say have fun, but I will walk through each portion of the routine so that everyone can understand what exactly is going on. The entire library is available for download at the end of this document but why would you skip ahead and miss out on all the explanations?

 

We are faced with a problem; we need to somehow dynamically control the period between our output pin changes. We have been given somewhat of a break though. It seams that the mechanical engineers over at the SAE group got smart and hired an electrical engineer to work out this little multiplexing problem. The EE simply said, “if you’ve got to have two different time periods for each symbol, at least make the periods consistent.” As such our break is that we only have two time periods we need to worry about, 64mS and 128mS. The SOF and EOF symbols are a different matter but we’ll worry about those in a minute.

 

So how do we use this “gift” to our advantage? Well, we know that timer1 will be running at a fixed frequency. (For our purposes its frequency is fixed anyway, frequency drift and phase changes don’t exist for us J) We also know that we only have two nominal time periods we have to worry about, 64mS and 128mS. So we should be able to calculate exactly how many timer1 “ticks” occur between each of these time periods right? This is a fairly straightforward calculation. For the 64mS period we have: 64mS/(200nS*4) = 80D or 0x50. Or if you prefer to use frequency instead of instruction cycle times: (5MHz/4) * 64mS = 80D or 0x50. By common sense, the number of ticks for 128mS should be exactly double that of 64mS but we’ll work through the calculations anyway just to be sure. 128mS/(200nS * 4) = 160D or 0xA0. No surprise there. Lets go ahead and define those as constants to be used later. Place these definitions at the beginning of your program. The per_sof and per_eof constants deal with the SOF and EOF symbol periods respectively. We’ll talk about those later. The constants comp_drive_low, comp_drive_high, and comp_only are the required values that need to be loaded into the CCP2 control register to get it to do what we want it to do.

const per_short = 0x50 -- number of cycles for 64uS for tmr1

const per_long = 0xA0 -- number of cycles for 128uS for tmr1

const per_sof = 0xFA -- number of cycles for 200uS for tmr1

const per_eof = 0xAF -- #cycles 280uS, must use 1:8 prescaler for EOF detection

const comp_drive_low = 0x09 -- for ccp2con to drive CCP2 pin low on compare

const comp_drive_high = 0x08 -- for ccp2con to drive CCP2 pin high on compare

const comp_only = 0x0A -- for ccp2con to compare only

 

 
 

 

 

 

 

 

 

 

 


Ok so now we know how many timer ticks are between each respective interval, but how do we use that info? I should probably just show you first and then try to explain it. Here is the beginning part of the vpw_send procedure:

 

function vpw_send (byte in data, bit in send_sof) return bit is

var bit error_flag = false

var volatile bit bit_out at data : 7 -- select bit to be transmitted

var bit dom_pass = false -- keep track of active or passive symbol

var byte timer_low_byte, timer_high_byte, prtc_buf

var volatile byte next_bit = 0x00 -- sets next pulse width

 

for 8 loop -- shift data out one bit at a time; MSB first

 

if bit_out then -- if bit to be sent is a One

if dom_pass then -- send "Active" One

next_bit = per_short

else -- send "Passive" One

next_bit = per_long

end if

else -- Bit to be sent is a Zero

if dom_pass then -- send active zero

next_bit = per_long

else -- send "Passive" zero

next_bit = per_short

end if

end if

 

-- start of frame takes care of period adjustment for first bit. If current

-- iteration is not sending a SOF, must adjust new period by adding pulse width

-- to current ccp2H:ccp2L registers.

 

if send_sof then -- need to send a start of frame first?

 

--SNIP SOME CODE TO BE REVEILED IN A MOMENT—

 

else -- just send the remaining bits

assembler -- add next period to compare reference. 8-bit + 16-bit

bank MOVF next_bit,W

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

end assembler

end if

 

while ! pir2_ccp2if loop -- wait until end of period, when loop is

-- exited bus transition should be done.

 

--SNIP SOME MORE CODE TO BE REVEILED IN A MOMENT—

end loop

 

pir2_ccp2if = false -- reset ccp2 interrupt flag

 

if dom_pass then -- switch transition directions

<SNIP>

f877_ccp2con = comp_drive_high -- if previously sending an active signal

dom_pass = false -- next symbol will be passive. Ergo need

else -- to drive bus high on next compare.

f877_ccp2con = comp_drive_low -- Same logic here just opposite results

dom_pass = on

end if

 

data = data << 1 -- shift in next bit to be sent

end loop

return true

end function

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


And this is the condensed version. J Breaking the above code snippet down into sections let’s start with the beginning.

function vpw_send (byte in data, bit in send_sof) return bit is

var bit error_flag = false

var volatile bit bit_out at data : 7 -- select bit to be transmitted

var bit dom_pass = false -- keep track of active or passive symbol

var byte timer_low_byte, timer_high_byte, prtc_buf

var volatile byte next_bit = 0x00 -- sets next pulse width

 
 

 

 

 

 

 

 

 


Now I am going to assume that you are familiar with the JAL language so I am going to skip the syntax speech and get right to telling you what the variables are. The two parameters that are passed to this function are data and send_sof. Data holds the byte that is to be transmitted and send_sof is a flag that tells the function that the data byte needs to be preceded by a SOF symbol. The first defined variable is the bit variable error_flag. This is pretty self-explanatory; if this flag is ever set then an error has occurred; such as loosing arbitration. The second variable that is declared is the bit variable bit_out. It has been defined to be the MSB of the byte parameter data. This bit variable holds the current bit that is to be transmitted. Since this bit cannot be trusted to hold the last known value, it gets a volatile modifier. The next variable that is declared is the variable dom_pass. This is a flag that tells the function if the current symbol should be active or passive. A “1” = active, “0” = passive. Since the first bit of every byte being transmitted is always a passive symbol, this bit initializes to the passive state. The next three bytes that are defined deal with the arbitration issue and will be discussed later. The last variable that is defined is the variable next_bit. This is a byte variable that holds the number of timer ticks the output pin should remain in its current state. It should also be mentioned that this function returns a bit value. If the transmission was successful then the function returns true. If the transmission failed for whatever reason, then the function returns false.

 

Ok that was easy enough, on to the next part.

for 8 loop -- shift data out one bit at a time; MSB first

 

if bit_out then -- if bit to be sent is a One

if dom_pass then -- send "Active" One

next_bit = per_short

else -- send "Passive" One

next_bit = per_long

end if

else -- Bit to be sent is a Zero

if dom_pass then -- send active zero

next_bit = per_long

else -- send "Passive" zero

next_bit = per_short

end if

end if

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


The first line sets up a loop that will repeat a total of 8 times. Makes sense since we are transmitting 8-bits each time. The next construct determines what the bit to be sent is, a 1 or a 0, and whether it is an active symbol or a passive symbol. I’ll leave the logic decryption to you. Once the type and state of the current bit symbol have been determined, then the next_bit register is loaded with the appropriate period length. So far so good, what’s next?

-- start of frame takes care of period adjustment for first bit. If current

-- iteration is not sending a SOF, must adjust new period by adding pulse width

-- to current ccp2H:ccp2L registers.

 

if send_sof then -- need to send a start of frame first?

 

--SNIP SOME CODE TO BE REVEILED IN A MOMENT—

 

else -- just send the remaining bits

assembler -- add next period to compare reference. 8-bit + 16-bit

bank MOVF next_bit,W

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

end assembler

end if

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


This is where things get a little more conceptual. I need to back up some and divulge some more of the theory behind this procedure before the next bit of code can be explained exactly. This procedure is limited by the fact that it can only send one byte of data at a time. Since JAL does not have support for passing “arrays” as parameters to procedures and I am too lazy to set it up to pass both the starting address pointer and array width parameters, I worked around this one-byte limitation. Since J1850 messages always contain more than one byte of information, there needs to be a method for calling this procedure repeatedly without disturbing the timing between bytes. If you think about it, if the timer were stopped every time at the end of the procedure there would be no way to account for the lost time between the procedure return, procedure recall, re-initialization of the parameters, and finally updating the output pin accordingly. For a particular message frame, the result would be bit symbols that would have worse and worse pulse periods between bytes each time this procedure was called. So how do we work around this one byte limitation without causing a time skew between bytes? The answer is to have 1 timer that runs constantly for the duration of the message frame. This way we have a common time base for each byte that is being transmitted. Ok so what does this mean? Well, when this procedure is called for the first time, it starts timer1 counting, once all 8-bits of the current data byte have been transmitted, the procedure returns, leaving timer1 running in the process. The procedure is then called again and the next byte is then transmitted out using the previous “match point” from the last byte transmitted as a reference. The result is a complete message frame that is devoid of time skew.

 

“Wait, I’m still confused! Where is timer1 started? How does this relate to using the number of timer ‘ticks’ between transitions? What did you mean by ‘match point’?” These are all very good questions that need to be answered before moving on. First of all timer1 is started in the portion of code that has been snipped. Don’t worry, I’ll tell you the general idea’s behind what is going on in there in a moment, but first, we need to talk more about the timer “ticks.” Lets take a step back for a moment and ask ourselves a simple physics problem, “If I start out at point A and it takes X seconds to go to point B if I am traveling at Y m/s, how long do I need to travel to get to point B if I travel at Y m/s?” This is a simple enough problem, you just need to travel for X seconds, that is assuming your direction is constant and is towards point B and Y is much less than the speed of light J. The same principle applies to our number of timer ticks, only in our case our “velocity”, Y, is our clock rate, our position is one of “ticks”, point B is the number of “ticks” later that we expect a transition to occur, and point A is our last known “match point.” So what is a “match point?” It is a term that I am using to define a point in “ticks” when our timer1 module “matches” or compares with the value we have stored in the CCP2 module. Still a little fuzzy? I’ll explain further. For this case lets say that we take point A to be defined at the point timer1 = 0, i.e. point A’s “position” is at 0 ticks. Now lets say that we want to travel away from point A for 64mS to point B and then take a rest break. How far away from point A is point B in terms of ticks if our “velocity” is 1,250,000 ticks/s? Well we’ve actually already done this calculation remember? We determined that for our given “velocity” or clock rate, 64mS is only 0x50 ticks away. That means for our given clock rate, if we start out at point A and count off 0x50 ticks then we will be at point B, i.e. point B’s position is at timer1 = 0x50. Now lets say that we want to leave point B and travel for another 64mS to point C. How far away are we now from point A? Well since this is only one-dimensional motion and 64mS equates to 0x50 ticks, we are now 0x50 ticks away from point B. Point B is 0x50 ticks away from point A so we are now 0x50 + 0x50 = 0xA0 ticks away from point A. This is the principle behind the timer registers. Each tick translates to a specific amount of time. So if we wanted to time 128mS we would simply count off 0xA0 ticks. If you are already familiar with timers and how to use them I am sorry for the over-simplified breakdown. If you aren’t as familiar, then I hope you learned something.

 

The point of that whole analogy was to prove that ticks correspond to time and that the same number of ticks will always represent the same amount of time provided that the clock and prescaler values remain constant. So if we want to measure 64mS from any arbitrary point P all we need to do is add the appropriate number of ticks to the point and wait. This is the principle that I applied above. From the comments in my code:

           -- start of frame takes care of period adjustment for first bit. If current

-- iteration is not sending a SOF, must adjust new period by adding pulse width

-- to current ccp2H:ccp2L registers.

 

If you follow the code example above, when send_sof is false, i.e. current iteration is not sending a SOF symbol, the next transition time for the CCP2 output pin is simply the previous transition time + appropriate number of ticks for current symbol. Ok everybody on the same page now?

 

What about when send_sof is true? In the portion of code that is missing, basically three main things occur.

1.     The bus is “listened” to in order to determine if it is idle.

2.     Once the bus is idle, a SOF character is generated and timer1 is started.

3.     The period register is loaded with the appropriate value for the first bit to be transmitted.

 

The actual code and in-depth descriptions for each of these will be given later. Until then, “On to the next part!”

while ! pir2_ccp2if loop -- wait until end of period, when loop is

-- exited bus transition should be done.

 

--SNIP SOME MORE CODE TO BE REVEILED IN A MOMENT—

end loop

 

 
 

 

 

 

 

 


This is a not-so-idle idle loop to stall the PIC while it is waiting for the CCP2 module to raise its hand and wave. Inside this loop is where the first part of the all-important arbitration takes place and will be described in full detail a little later. That was pretty painless so what about the last little bit?

 

It’s actually pretty painless as well, even the part that has been snipped, for it simply toggles the dominate/passive indicator bit and reconfigures the CCP2 module to drive the pin to the next state in the event of a timer1 match. The portion that has been cut is the second half of arbitration detection and will be covered later. The CCP2 module’s “waving hand” is then cleared to prepare for the next compare and the next bit to be transmitted is then shifted into place. The for 8 loop end is then reached so it starts back again at the top. If the for 8 loop finishes without error, then the function returns a true value indicating that the process has completed successfully.

pir2_ccp2if = false -- reset ccp2 interrupt flag

if dom_pass then -- switch transition directions

<SNIP>

f877_ccp2con = comp_drive_high -- if previously sending an active signal

dom_pass = false -- next symbol will be passive. Ergo need

else -- to drive bus high on next compare.

f877_ccp2con = comp_drive_low -- Same logic here just opposite results

dom_pass = on

end if

 

data = data << 1 -- shift in next bit to be sent

end loop

return true

end function

 

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


Pheww, that was a lot of work for something so simple. Well now its time to get to the stuff that I left out. (evil grin spreads across my face J) No, it’s not really that hard, or complicated, its just…well you’ll see.

 

Let me begin by first reminding you that before beginning any transmission the first thing the PIC must do is make sure that the bus is idle. The J1850 specs require a nominal period of 300mS of separation between message frames. This means that there must be 300mS between the last falling edge of the last message and the first rising edge of the next message’s SOF. So how do we determine if the bus is idle, and once it is idle how do we know how long it’s been idle? First we begin by setting up our timer module to count out 300mS so lets go ahead and perform that calculation. 300mS / (200nS * 4) = 375D or 0x177. Uh oh, this value is greater than 255 or 0xFF, the largest number that can be represented by 8-bits. So what do we do? Well actually, since timer1 is a 16-bit wide timer we don’t have to do anything. But rather than have to worry about performing 16-bit reads and writes on an active counter, I opted to change the prescaler value for the idle checking routine. I know that there are very good arguments against doing this but I like simplicity. Plus on the PIC’s I’ve tested, we get an added bonus doing it this way. Whether or not this bonus will be applicable to you depends on your particular PIC and oscillator. So keep in mind that you might have to fiddle with the timings to get the correct results. I will explain the bonus in a moment. In the meantime, lets continue with setting up timer1 for EOF/IFS detection. If we read the J1850 specs carefully, we will notice that the 300mS time period is a nominal value. In reality the IFS (inter-frame separation) is satisfied when at least 280mS has passed. Quoted directly from the J1850 specs for VPW IFS timings:

               Inter-Frame Separation (IFS) - Inter-Frame Separation is used to allow proper synchronization of various nodes during back-to-back frame operation.

 

            A transmitter that desires bus access must wait for either of two conditions before transmitting a SOF (see Figure 19):

 

              a.  IFS minimum has expired (Tv6).

 

              b.  EOF minimum and another rising edge has been detected (Tv4).

 

J1850 defines Tv6 to be no less than 280mS. So this is the value that we actually set our timer to measure. This is where our bonus comes into the picture. When the timer times out at 280mS, there is a latency period due to the time it takes to reconfigure and reset the timer and return from the procedure call. And by some stroke of luck, the amount of time this takes places the first bus transition right at 300mS. So we detect the minimum amount of time between frames passing, and our SOF symbol begins right around the nominal 300mS mark. We got lucky and everything fell into spec on that one. Also, only checking for the minimum IFS time simplifies the idle-checking routine greatly as you will see. So how do we load the CCP2 module to match at 280mS? Well we still need to reconfigure timer1 for a larger prescaler since we really want to stay away from working with 16-bit numbers if at all possible. The only available option we have left is a 1:8 prescaler. By selecting this new prescaler we determine the value to load into the CCP2 compare register to be 280mS / (200nS * 8) = 175D or 0xAF. So now that we now know how many ‘ticks’ we have to wait for the IFS minimum to pass, how to we tell if the bus is idle? Basically we just poll the bus and time how long it stays in the low potential. Here let me show you. The following code is the idle checking routine with explanations to follow.

procedure vpw_idle_chk is

tmr1on = false

vpwout = false -- make sure only transmitting a low signal

pir2_ccp2if = false

t1ckps1 = on

t1ckps0 = on -- set 1:8 prescaler for timer1

 

f877_tmr1l = 0x00 -- reset timer one high and low bytes

f877_tmr1h = 0x00

 

f877_ccpr2l = per_eof -- set period for compare to be ~280uS

f877_ccpr2h = 0x00

 

f877_ccp2con = 0x0A -- set CCP2 module to only flag when timer times out

 

while vpwin loop end loop -- loop until bus transitions low

 

tmr1on = on -- start timer

 

while ! pir2_ccp2if loop -- wait while bus is low

if vpwin then -- if activity detected on bus restart timer

f877_tmr1l = 0x00 -- no need to reset high byte

end if

end loop

 

pir2_ccp2if = false -- clear interrupt flag

 

tmr1on = false -- stop timer 1

 

f877_tmr1l = 0x00 -- reset timer 1

f877_tmr1h = 0x00

 

t1ckps0 = false -- 1:4 prescaler for timer1

end procedure

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


The first 7 lines are simply initializing the routine. They make sure that the timer is not already running, reset the prescaler value for timer1 to 1:8, and reset the timer. They also clear the interrupt flag for the CCP module just in case it somehow was left set. The next three lines set up the CCP2 module to compare only. NO output pin is affected when a match occurs in this routine. The first while loop initially polls the bus to determine if it is active already. If the bus is active then the PIC just sits and does nothing until the bus goes low again. If the bus is already low, or we have waited for it to become low, then the next line is executed and the timer is started. Then in the same manner as before, we simply wait until the appropriate number of ticks has passed. While we are waiting, we are constantly polling the bus to see if it becomes active. If it does, then we know that a node must still be transmitting and our timer starts over. This is repeated until the existing message has finished and the minimum separation time has passed. And since we are only waiting for the minimum separation time between messages, any active symbol detected must be, by default, part of an existing message frame. That is how we get around dealing with condition “B” directly. As long as we are still in that loop then the EOF minimum time has not passed, therefore we don’t need to distinguish SOF’s from regular bits here. The last few lines stop and reset the timer. We now have a method for determining exactly when the bus becomes available for transmission.

 

So what’s next?

 

After the bus becomes available, we need to generate our SOF symbol. This is one of the sections of code I removed from the vpw_send function presented above and it will now be presented.

-- start of frame takes care of period adjustment for first bit. If current

-- iteration is not sending a SOF, must adjust new period by adding pulse width

-- to current ccp2H:ccp2L registers.

 

if send_sof then -- need to send a start of frame first?

--STITCH CODE BACK IN

-- send start of frame plus first bit

vpw_idle_chk -- verify bus is idle before beginning transmission

 

f877_ccp2con = comp_drive_low -- bus will be high for SOF. Drive bus low

-- when pulse width is achieved. should also

-- drive output pin high beginning transmission

 

tmr1on = on -- begin timing bus position

 

f877_ccpr2l = per_sof -- set period for compare to be ~200uS

f877_ccpr2h = 0x00

 

pir2_ccp2if = false

 

 

error_flag = false -- assume success unless failure occurs

 

while ! pir2_ccp2if loop end loop -- wait until timer1 times out

 

-- If the bus is still active then that means another node is still

-- transmitting. The only other allowed active symbol that lasts for

-- this duration is a BREAK symbol. So we poll the bus for the longest

-- allowed SOF time. If this is passed then we will cease our attempt

-- to transmit.

while vpwin loop -- allows for nodes with slower clocks to finish their

assembler -- SOF's.

local shorter

movf f877_tmr1h,w -- The new starting point for the compare register

movwf f877_ccpr2h -- is updated while the other nodes finish.

movf f877_tmr1l,w

movwf f877_ccpr2l

movf f877_tmr1h,w -- ensures proper loading of CCP2 with the current

movwf f877_ccpr2h -- timer value

bcf status_Z

movlw 0x01

subwf f877_ccpr2h,w -- magnitude comparison of the current high byte

BTFSS status_Z -- if the current timer value is greater than

GOTO shorter -- 0x128 then the maximum allowed transmission

movlw 0x28 -- length for a SOF has been reached. If it isnt

bsf status_C -- then we need to poll the bus again and repeat.

subwf f877_ccpr2l,w

BTFSS status_C

GOTO shorter

bsf error_flag

shorter:

end assembler

if error_flag then

vpwout = false

tmr1on = false

f877_tmr1l = 0x00

f877_tmr1h = 0x00

f877_ccp2con = 0x00

return false

end if

end loop

 

-- SOF is always followed by data bytes. Prep for first bit to be sent

assembler -- add next period to compare reference, 8-bit + 16-bit

bank MOVF next_bit,W

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

end assembler

 

asm clrf f877_ccp2con -- first bit to be transmitted is always low

f877_ccp2con = comp_drive_high -- set ccp2 to drive output high on match

 

pir2_ccp2if = false -- reset interrupt flag

send_sof = false -- no more start of frame symbols to transmit

-- END STITCHED CODE

 

else -- just send the remaining bits

assembler -- add next period to compare reference. 8-bit + 16-bit

bank MOVF next_bit,W

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

end assembler

end if

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


This is the complete “if send_sof then” statement. It shows how the SOF symbol is generated. Recall that the bit send_sof is one of the parameters that are passed to the function. This is how the function distinguishes the first data byte from the rest. Once we have determined that this is the first byte of the message frame, we need to begin the process for transmitting. You’ll notice that the very first thing that the function now does is wait for the bus to become available. The technical stuff has already been presented for the idle checking procedure so I won’t elaborate any further. Once the bus becomes available then a SOF symbol is generated in the exact same manner I described before for the rest of the data bits. What might not be apparent here is where the bus is placed in the high potential state marking the beginning of the SOF. Remember, in order for the CCPX module to drive the output pin low, it must first be in the high state. When the CCP2 module is configured to drive the bus low, it auto-magically drives the output pin high for us. Next, the CCP2 module is loaded with the number of ticks it must count out for the SOF period. The while loop simply places the PIC in a holding pattern until the SOF is finished. Since our SOF will be present for the exact J1850 spec, arbitration here is not needed. If another node is generating a SOF at the same time and its period happens to be a little shorter that’s ok, our PIC will continue transmitting and the other node will simply chalk it up to clock mismatch, as our PIC does if it is the “shorter” SOF. That’s where the while vpwin loop comes in. If our SOF symbol has completed but the bus is still active then that means there are only three options to describe the situation.

1)     Clock mismatch – another node generating its SOF has yet to complete

2)     A “Break” symbol is being transmitted

3)     A bus fault has occurred, i.e. the bus has been shorted to the “+” terminal

 

What the PIC then does is it updates the CCP2 module with the current timer value so that the module has a current reference point for the end of the SOF. However, if the amount of time the bus stays active exceeds the maximum allowed reception time for a SOF symbol then the transmission is deemed unsuccessful and is aborted.

 

Once our SOF symbol has successfully transmitted then the first data bit’s period information is added to the CCP2’s compare value and the CCP2 module is configured appropriately. This is exactly the same process that is used for the remaining bits that I described earlier. Since this section of code follows the section where the bit’s period information is determined, that information is already known so no additional processing is required. The SOF flag is then cleared and the function then proceeds normally.

 

This now brings us to that pesky little thing called arbitration. To refresh your memory, when two or more nodes begin transmitting at the same time, they are constantly competing for control of the network. The fact that active symbols dominate over passive ones determines which node controls the bus. Therefore, if a node is transmitting a passive symbol and “sees” an active one on the bus then the node transmitting the passive symbol simply stops transmitting. While this is a very simple and straightforward concept, there are several factors that need to be considered when implementing this feature. First off, the effects of clock mismatch between nodes must be taken into account for the process to work correctly. Remember, 128mS on our clock might only be 121mS on another and vise-versa. The second factor that must be accounted for is exactly how our code, PIC, and the forward progression of Time all work together. This interaction can be somewhat messy and difficult to see at first glance. In fact, it was while I was debugging this section of code that I realized I was actually being affected by the limitations of the JAL compiler for the first time L. That is why a large portion of this section of code is written in raw assembler.

 

Lets break each factor down and crank out a solution for each, starting with the code/PIC/Time factor. The thing to realize is that JAL is a high-level language and simple statements within the code can actually translate into several more intricate statements at the assembler level. Also, remember that computers/microprocessors are dumb; they cannot infer information from what you give them they just do exactly what they are told. This being said, we will take a look at a While X Do Y loop where X is a bit and Y is your action. Going by the English of this statement, it is apparent that we want to continue to do Y as long as X is true and as soon as X is not true we want to quit doing Y. Now lets look at what the PIC tells the compiler to do.

Loop:

          BTFSC X

          GOTO Y

          GOTO Z

Y:

-- Do the stuff in the loop

          GOTO Loop

Z:

          -- Done with the loop continue with your program

 

Assuming you don’t know anything about assembly language the above code does the following. The Loop:, Y:, & Z: terms create labels or program markers. These are used to set sections of your code apart from the rest. The first actual instruction our PIC encounters is the BTFSC X. BTFSC actually stands for Bit Test F Skip if Clear and then X contains the register location and bit number to be tested. In English this means that the PIC will look in register location F and determine if our bit X is a ‘1’ or a ‘0’. If X is set (a ‘1’) then the PIC just moves on to the next instruction: GOTO Y. If, however, X is clear (a ‘0’) then the PIC skips the instruction GOTO Y and moves on to the GOTO Z. Inside the Y: label, the loop code is executed and then branches back up to the label Loop. The compiler generates the Loop, Y, and Z labels for us and the code we write inside the loop goes into the Y label. The point is the status of our loop control is only tested once at the beginning of each pass. If you’ve ever programmed computers before this should not be new information to you. For counting controls or flags that are set within Y this is not a real problem. However, if X is dependant upon external factors, such as the passage of time J, then yes this does become a problem because if X becomes false after we have tested it, then we will execute Y when really we don’t need to. Like I said, if you’ve done any sort of programming before then this is not news to you. I only mention it because I spent a good 20 min. trying to track down a problem with my code and it was because of this. So what have we learned? If your control depends upon influences outside of the loop always place a second control check before executing the loop.

 

Another example of where code/PIC/time factors need to be considered is when dealing with pins that we do not control directly. These can be pins configured as either inputs or outputs. In our case, we are comparing the state of two pins, one input and one output. We have indirect control of our output pin and absolutely no control of our input pin. This is a bad combination all around. Getting back to our goal of detecting if we’ve lost arbitration we need to determine if our output does not match our input. Although as simple and as tempting as it might be to use a statement like this:

          if vpwin != vpwout then

          -- do the arbitration stuff here

          end if

 

This does not work and I’ll tell you why. Because we have no direct control of either pin, their state is able to change without warning. Also, due to the way this statement compiles, there is ample opportunity for a pin to change mid comparison and cause a false evaluation. When it examined there are 4 possible paths that can be followed:

Path 1) vpwin = vpwout = on -- path leads to if statement being skipped

Path 2) vpwin = vpwout = off -- path leads to if statement being skipped

Path 3) vpwin = on & vpwout = off -- path leads to if statement being executed

Path 4) vpwin = off & vpwout = on -- path leads to if statement being executed

 

At the assembly level, the logic behind choosing which path to take is a complex arrangement of bit testing and GOTO statements. Why this type of arrangement does not work is because if a pin were to change while we were still testing the IF statement then it could evaluate true or false incorrectly. Therefore in order to safely and correctly compare our input and output, we need to “take a picture” of the port and then work with that “picture.”

 

To illustrate how this is done, here is a snippet of this part of the arbitration detection.

 

while ! pir2_ccp2if loop -- wait until end of period, when loop is

-- exited bus transition should be done.

prtc_buf = port_c -- Check for arbitration. If output does not

prtc_buf = prtc_buf & 0x06 -- match input then arbitration may be lost.

if ( (prtc_buf == 0x04) & (! pir2_ccp2if)) then

<SNIP>

 
 

 

 

 

 

 

 

 


The while ! pir2_ccp2if loop is the same loop shown before, remember this is a loop where the PIC waits until the correct number of timer ticks have passed. The only difference here is it now contains the first part of the bus contention detection: input/output mismatch. The variable prtc_buf was declared earlier and is a buffer for port_c, our input/output port. The first line following the while statement takes a “picture” of the entire port. The next line masks out all the other pins on the port and leaves our input and output pin “images” unaffected, this way the buffer contains the state of only these two pins. Next, the “image” is examined to determine if there is a mismatch condition. The ! pir2_ccp2if constraint is there to ensure that, if there is a discrepancy between the two pins, we only act on it if our pulse period has not finished yet. This is very important because the J1850 bus has a capacitance and resistance associated with it that is somewhat dependant upon the number of nodes currently connected to it. This capacitance will cause a time delay between when our PIC switches and the time this change is actually “appears” on the bus. This delay will be on the order of approx. 5*t where t = R*C is the time constant for the network. Although t by itself is a pretty small number, 5.2mS according to the specs, our PIC is still fast enough to detect the delay between our switching time and the time the network reacts which will cause false arbitration. Its that whole external influences thing with loops remember? Therefore we put in the condition that only checks for I/O mismatch PRIOR to our PIC switching the output. If the if statement returns true then we know we have a mismatch condition between our output and the actual OBDII bus. So we’re done right? Nope, there’s one more detail that we need to address first.

 

We’ve pretty much wrapped up I/O mismatch detection so now we need to move on to dealing with clock mismatch. If it were a perfect world, there would be no hunger, no MTV, and everybody’s clocks would keep exactly the same time. Alas, it is not a perfect world; I’m hungry, my roommate watches MTV reality shows constantly, and my VCR clock is always 2 minutes faster than the clock on the wall. The same is true for the clock on our PIC. In fact, I can almost guarantee that our clock will be different than the other node’s clocks. That’s where the concept of a timing “window” comes from. A timing window gives a tolerance to the amount of time a pulse can actually last. A window is usually defined by a nominal value and then either a percent difference or a max. and min. value. In our case our nominal values are 64mS and 128mS. The actual timing windows, as defined by the J1850 specs, are shown below in table 1.

 

TABLE 1 - VPW Pulse Width Times (μsec)

 

Symbol

Tx,min

Tx,nom

Tx, max

Rx,min

Rx,max

Tv1: Short Pulse

49

64

79

34

96

Tv2: Long Pulse

112

128

145

95

162

Tv3: SOF / EOD time

182

200

218

164

237

Tv4: EOF time

261

280

N/A

241

N/A

 

As we can see from the table our 64mS and 128mS transmit pulse widths are actually defined by the windows 49mS to 79mS and 112mS to 145mS respectively. This means that any pulse we transmit, as long as it falls within its respective window, will be considered valid and accepted by the other nodes. This is good for us as it gives us a little wiggle room in our pulse width timing. But what does this mean in terms of arbitration? It means that simply polling for an I/O mismatch is not enough. What if another node that is talking has a slightly faster clock and it finishes its pulse at 58mS (ref. our clock) for a 64mS nominal pulse? According to the specs, 58mS is a valid pulse width and therefore must be accepted. Uh-oh, what do we do now?

 

For active symbols, other nodes that have faster clocks (i.e. shorter pulses) are not a big problem. That is because if we are transmitting an active symbol and another node attempts to let the bus settle back to ground, our node will continue to drive the bus high. This will leave the state of the bus unchanged and will not generate a mismatch condition. So as long as the other nodes don’t think that our 64mS pulse lasted longer than 96mS we will be okay. So active “short” pulses are not really a big deal, but what about passive short pulses? For example lets say there are two nodes and both are transmitting a passive “1” bit but one of the nodes drives the bus high after only 117mS. If we were only using an I/O mismatch scheme and not comparing relative state times we would detect this as the other node transmitting a “0” and drop out. So how do we get around this “false arbitration” detection? Well we’ve already determined the method, state time comparison, but how do we implement it? That is a little tricky, but it is easy to do, and in this design was actually implemented in two parts, one part for each case. Each case? When dealing with window timings, there are two possible cases, a pulse width that is less than the nominal and a pulse width that is greater than the nominal.

 

When dealing with pulses that are less than the nominal, we’ve already determined that we don’t have to worry about active pulses. We do, however, need to worry about short passive pulses as demonstrated above. Looking back at our I/O mismatch detection what have we got?

<recopied here for memory sake>

 

while ! pir2_ccp2if loop -- wait until end of period, when loop is

-- exited bus transition should be done.

prtc_buf = port_c -- Check for arbitration. If output does not

prtc_buf = prtc_buf & 0x06 -- match input then arbitration may be lost.

if ( (prtc_buf == 0x04) & (! pir2_ccp2if)) then

<SNIP>

 
 

 

 

 

 

 

 

 


If we examine the flow of the code we will remain inside this loop until our timer has run out. Inside the loop, we are constantly polling for an I/O mismatch. But not just any I/O mismatch, we are looking particularly for the mismatch condition that occurs when we are transmitting a passive symbol and the bus is taken high before we have finished, i.e. prtc_buf = 0x04 and pir2_ccp2if = false. We only care about the case prtc_buf = 0x04 because the case prtc_buf = 0x02 would indicate that we are transmitting an active symbol and receiving a passive one. That can only happen if there is a bus fault, which is checked for a little later. With our if statement we have now determined that the bus has transitioned from low to high before we thought it should. The next thing we need to do is determine if this is due to the fact that we have indeed lost arbitration or if our clock is just a little slower than the rest. We could do this in several ways, using nested If’s to create logic flow based upon our current period timing but I went with a somewhat more elegant approach. The theory is if the transition occurred within the defined “window” of the pulse, then the transition marked a valid pulse. Since we told the CCP2 module to let the timer count for exactly the nominal width of the pulse we can use the value in the CCP2 register as a reference. Then looking at the minimum pulse width’s required for both the 64 and 128mS windows we can make a generalization, any pulse received that is within 16mS of the nominal is a valid pulse. Looking at table 1 this is an ok generalization. 64mS - 49mS = 15mS and 128mS - 112mS = 16mS. The 64mS window is actually a little tighter than this 16mS delta but what’s 1mS between friends? We now know that any pulse transitioning early by less than 16mS is valid. The way we use this information is to compare the difference between the actual transition time and our expected transition time to this 16mS “window” edge. If the difference falls within this window then the pulse is okay, if it doesn’t then that means we’ve lost arbitration, or a fault has occurred. In either case we need to stop talking anyway.

 

In order to accomplish this comparison we first need to determine the difference between the expected transition time and the current timer value. Performing this operation on a running timer is not advised so the timer is stopped to do this. I know I made a lot of hoopla about keeping the timer running the whole time but in this case we’re ok. The subtraction section is shown below.

 

if ((prtc_buf == 0x04) & (! pir2_ccp2if)) then

assembler

bcf tmr1on -- Comparison between the bus transition time

-- and the time remaining before bus expected

-- transition is used to compensate for clock

-- mismatch. Therefore false "lost arbitration"

-- is avoided. This portion only checks for nodes

-- that transition early. Nodes that transition

-- later are dealt with after this portion.

bsf status_C

bcf status_Z

 

local EXITE, EXIT

MOVF f877_tmr1l,W -- determine delta between expected transition

SUBWF f877_ccpr2l,w -- and actual transition time.

movwf timer_low_byte -- timer_X_byte = ccpr2X - f877_tmr1X

MOVF f877_tmr1h,W

BTFSS status_C

INCF f877_tmr1h,W

SUBWF f877_ccpr2h,w

movwf timer_high_byte

<SNIP>

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


The variables timer_high_byte and timer_low_byte are defined above and are used to hold the difference between the compare and timer registers. More descriptive variable names could have and probably should have been used but I had already declared these variables for a previous version of the code and didn’t feel like changing the names. (I have a problem with reusing old variables and then not changing the names to reflect their current purpose.) The subtraction routine is a pretty standard 16-bit subtraction operation so I won’t describe what’s going on in detail.

 

After the difference between the timer and compare registers has been determined, we now do a magnitude comparison between this delta and our 16mS lower boundary limit. But first we need to convert 16mS into the appropriate number of “ticks.” 16mS * 5MHz/4 = 20D or 0x14. The magnitude comparison is also fairly straightforward. By subtracting our window limit from the difference obtained between the timer and the CCP2 module we can determine if the transition occurred within the limits. The complete equation looks like this: (CCP2 – timer1) – 0x14 = D obtained – 0x14 = ticks remaining until window is reached. If the result is a negative number then that means that the transition occurred within our specified window, i.e. CCP2 – timer1 < 0x14. If the result is 0 then that means the transition occurred right on the boundary and must still be considered valid. The complete check is performed below.

 

if ((prtc_buf == 0x04) & (! pir2_ccp2if)) then

assembler

bcf tmr1on -- Comparison between the bus transition time

-- and the time remaining before bus expected

-- transition is used to compensate for clock

-- mismatch. Therefore false "lost arbitration"

-- is avoided. This portion only checks for nodes

-- that transition early. Nodes that transition

-- later are dealt with after this portion.

bsf status_C

bcf status_Z

 

local EXITE, EXIT

MOVF f877_tmr1l,W -- determine delta between expected transition

SUBWF f877_ccpr2l,w -- and actual transition time.

movwf timer_low_byte -- timer_X_byte = ccpr2X - f877_tmr1X

MOVF f877_tmr1h,W

BTFSS status_C

INCF f877_tmr1h,W

SUBWF f877_ccpr2h,w

movwf timer_high_byte

btfss status, status_z -- the difference between high bytes must be zero

goto EXITE

movlw 0x14 -- to be within tolerance the difference must be less

bsf status_C -- than 20 instructions. Setting the Carry/Borrow bit

subwf timer_low_byte,w -- makes it available to borrow.

BTFSS status_C -- If the low byte is >= 20 then a borrow

goto EXIT -- will not have occurred and status_c will still be set.

BTFSC status_Z -- If the result is 0 then transition occurred right on

goto EXIT -- the boundary and is still valid.

EXITE: -- check somewhere has not passed.

bsf error_flag

EXIT:

end assembler

<SNIP>

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


In this code, a flag is set if the transition received is invalid. The last remaining piece of this section of code is presented below. Basically if the transition is invalid, the function resets and returns an unsuccessful flag. If however, the transition is valid, the new reference point is set for the CCP2 module and the arbitration routine exits. Finally, the bus is checked for a fault condition and exits if it exists. Here is the remaining portion of this code.

end assembler

if (error_flag) then -- bus dominance lost.

vpwout = false -- "Shut up and try again later"

tmr1on = false

f877_tmr1l = 0x00

f877_tmr1h = 0x00

f877_ccp2con = 0x00

return false

else -- early transitioning node transitioned within specs. Change

f877_tmr1l = f877_ccpr2l -- output and move on.

f877_tmr1h = f877_ccpr2h

tmr1on = on

end if

end if

if ((prtc_buf == 0x02) & (! pir2_ccp2if)) then -- bus fault has been detected. Try again later.

vpwout = false

tmr1on = false

f877_tmr1l = 0x00

f877_tmr1h = 0x00

f877_ccp2con = 0x00

return false

end if

end loop

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


That finishes up the discussion on arbitration and thus wraps up the entire vpw_send routine. Wait, nope, we’re not done yet. You only wish this was the end J. I’ve still got to talk about the other half of the arbitration detection, the case where the bus transition occurs after the nominal period.

 

Just like with the case when the measured pulse is shorter than the nominal, the case where the pulse is longer than the nominal only has significance in one situation. This situation happens to be the exact opposite to that of the short pulse. For passive symbols, nodes with slower clocks (i.e. longer pulses) are not a big deal. This is due to the fact that if our node transitions early then the bus will be driven to an active state and the other nodes will adjust, so long as our transition doesn’t occur too early. It’s the case where our node transitions from active to passive before the other nodes that this becomes a problem. For example, lets say there are two nodes that want to transmit an active “1.” The first node finishes at exactly the nominal time and tries to let the bus settle back to ground. However, the second node doesn’t finish with its pulse until 75mS. Had we only been using a simple I/O mismatch routine we would detect this as lost arbitration and quit transmitting. But since we are alert engineers we have foreseen this problem and have devised a solution, and since we are good engineers this solution is simple, as are all good solutions J.

 

For this situation, we need to be aware of the repercussions of a node transitioning “late.” If we were to place the PIC into an idle loop while it waited for the other node to catch up, we would be in trouble because the PIC would count the time the PIC spent waiting on the other node as time spent sending the passive symbol. To illustrate I have “free-moused” a diagram to demonstrate what can happen if we don’t update our starting position accordingly.

 

 

The top trace represents the signal levels actually present on the J1850 bus. The bottom trace represents the state of our transmitting node and the middle node is some other node with a little bit slower clock. As you can see, our node switches high to low at the nominal time but the other node doesn’t transition till some time later. This is reflected on the J1850 trace as the other node’s active symbol “over-writes” our passive one. Since we are using more than a simple I/O mismatch scheme, we detect that the other node transitioned within the correct window and continue transmitting. This diagram, however, represents what will happen if we do not synchronize our pulse to the falling edge of the other node. We would proceed to count off time from when we made our transition and not from when the bus actually transitioned. Therefore there is a space, no longer than 32mS, that we count as time spent “low” that the bus was not actually low. In the case of a passive “0”, where the low period is only 64mS nominal, we have just cut the pulse period in half. So instead of the bus being low for the full 64mS like we thought it was, it was actually only low for 32mS. A 32mS short pulse is outside of the defined window for the pulse so there is no guarantee that it will be accepted. On the other hand, if our second bit was a passive 1 the amount of time the bus actually spent low would be 128mS - 32mS = 96mS. 96mS is right on the boundary between a long and a short pulse and can be interpreted as either a 1 or a 0. Therefore, it is up to the receiving node to make a decision as to what the symbol actually is. The receiving node has a 50-50 chance of guessing correctly which it will get wrong 90% of the time. This error would be detected by the CRC byte, assuming that the CRC byte is received correctly as well, and would result in our having to retransmit our message. But who wants to spend all of their time repeating themselves? But who wants to spend all of their time repeating themselves? (I’m a dork I know but I couldn’t resist. J)

 

So now we know that we not only need to detect how long the other node over-drives the bus, but that we also need to synchronize to this other node’s symbol edge. But wait, we also need to distinguish between arbitration loss and a node with a different clock. Boy this keeps getting more involved by the minute huh? So what do we do? First we need to remember that this effect is only relevant when we have just finished sending and active symbol. Looking back at the short pulse arbitration loop we just finished talking about we realize that if we have passed that loop and have not returned an error thus far, then we are in a predefined state. That is, the CCP2 register has either just driven our output pin low if we were sending an active symbol, or it has just driven the output pin high if we were sending a passive symbol and the nominal period for that symbol has passed. A brief glance at the remaining code for the send routine we see that there is a check done after this loop that configures the CCP2 module for the next bit and updates the next symbol state. Since this piece of code already makes the distinction between active and passive symbols it looks like a good place to do our synchronization/arbitration detection. Lets go ahead and set this first part up.

 

if dom_pass then -- switch transition directions

-- must allow for nodes that are transmitting their active signals

-- for just a little bit longer than we are. For the short pulse, the

-- maximum allowed overshoot in time is 32uS and for the long pulse the

-- maximum allowed overshoot in time is 34uS. We will only be checking

-- for the 32uS case.

if vpwin then -- bus is still active

assembler

 
 

 

 

 

 

 

 

 

 


The first IF just makes a distinction between active/passive symbols, we want only the case for active. The next IF statement determines whether or not we even need to resynchronize or arbitrate. Pretty straightforward stuff so far. So what’s all this stuff about 32mS, where does it come from? Well this is where stuff starts to get a little bit grey for me. The J1850 specs are not all that clear as to whether, when dealing with arbitration issues, we need to synchronize to the maximum allowed transmit time or to the maximum allowed receive time. In other words, which window do we look at when dealing with arbitration, the transmit window or the receive window? Currently, I have the routine configured to test for the maximum edge of the receive window although as I am writing this I am becoming less sure that this is the correct thing to do. (It’s funny how explaining stuff makes you understand things better yourself. They say that teaching is the best way to learn.) So if you are reading this and you know which window edge needs to be synchronized to please let me know and I’ll update accordingly. You can send me an email at this address to tell me one-way or the other: sstandfast@yahoo.com.

 

Since there are two possibilities to explore, I will present details for each variation, as they are identical with the exception of how many timer ticks we count. If we are wanting to synchronize to the longest edge for the receive window then the maximum amount of over-shoot allowed to be a valid symbol is 96mS - 64mS = 32mS for the 64mS pulse and 162mS - 128mS = 34mS for the 128mS case. Splitting the difference gives us an average over-shoot of 33mS. Converting 33mS into timer ticks gives us 33mS * 5MHz/4 = 41D or 0x29 ticks. For the case where we are synchronizing to the maximum transmit window the calculations are as follows: 79mS - 64mS = 15mS for the 64mS pulse and 145mS - 128mS = 17mS for the 128mS pulse. Splitting the difference once again gives us an average over-shoot of 16mS. This is the same as for our under-shoot scenario earlier. We calculated the number of timer ticks for 16mS to be 20D or 0x14. In the following example, I will use the value for the maximum transmit window (0x14). To switch to the receive window simply substitute the value 0x29 for 0x14. Everything else remains the same.

 

With our maximum over-shoot value in hand, we now proceed to integrate this into our code. Picking back up where we left off above we have established logic that determines whether or not we need to adjust our timing window. If we have determined that we do need to adjust our window it is because either there is another node that is a little slower than we are or a node is transmitting a symbol that takes precedence over our symbol. We need to be able to make a distinction between the two. In order to do this, we set up the CCP2 register to “time-out” only after the window edge has been reached. While we are waiting for the PIC to time out we are constantly polling the input pin. There are four possible situations that can occur while we are doing this, they are:

1)     input pin = timeout flag = off -- this case means that the pin has switched before our timeout period.

2)     input pin = timeout flag = on -- this case means that the input pin has not switched before the timeout period.

3)     input pin = on & timeout flag = off -- this case means that the timeout period has not expired but the bus is still active.

4)     input pin = off & timeout flag = on -- this case means that the pin switched right at the timeout period.

 

Cases 1 & 4 depict the two cases that mean the bus transitioned within the specified window and we must now synchronize to the current timer value. Case 2 represents the situation where the input is still high and our window edge has since passed. This means that we have either lost arbitration or a break symbol is being transmitted and we need to stop transmitting. Case 3 represents the general loop-back situation. The bus is still high but our window edge has not yet been reached so we simply wait some more to find out for sure what is going on. This loop is accomplished below.

 

if vpwin then -- bus is still active

assembler

local loop1, done, exita

movlw comp_only

movwf f877_ccp2con

MOVlW 0x14 -- THIS VALUE CAN BE CHANGED TO 0x29 FOR RECEIVE WINDOW EDGE!!!

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

loop1:

BTFSS vpwin -- loops until either the input pin changes

goto done -- or our "added" time expires.

BTFSS pir2_ccp2if

goto loop1

bcf tmr1on -- if the program gets here then that means the input

bcf vpwout -- is still set and our timer period has expired. If

clrf f877_tmr1l -- we were sending a short pulse then it means arbitration

clrf f877_tmr1h -- has been lost. If it is a long pulse then it could

clrf f877_ccp2con -- mean a break symbol is being transmitted. Either way

bsf error_flag -- we need to stop transmitting and exit.

bcf pir2_ccp2if

goto exita

done:

clrf f877_ccp2con -- updates the compare registers with new

movf f877_tmr1h,w -- starting values

movwf f877_ccpr2h

movf f877_tmr1l,w

movwf f877_ccpr2l

movf f877_tmr1h,w

movwf f877_ccpr2h

bcf pir2_ccp2if

exita:

end assembler

if error_flag then

return false

end if

end if

 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


The first section of this code simply adds the window edge to the CCP2 register so that it now has a reference to time against. The CCP2 register is then configured to “compare only” which will leave our output pin alone. Next we enter into the loop. The first line checks the state of the input pin. If it has changed, then the program jumps to the done label. Once there, the compare register is then reloaded with the current timer1 value. Which just so happens to coincide with the time the bus makes its transition ± a small error. The arbitration portion is then allowed to exit. Back at the top of the loop, if the state of the input pin has not changed the code moves on and checks the timeout flag. If the timeout period has not expired then the program simply loops back to the top and repeats. Alternatively, if the timeout period has expired then that means we have lost bus dominance and need to stop transmitting.

 

All in all that pretty much does it for the entire sending routine. According to the “word count” feature, this portion is only 27 pages long so if you made it all the way through without falling asleep then I commend you. For completeness the send routine is posted in its entirety below but is also available for download at the end. If you have any questions or comments please feel free to send them to me via email at: sstandfast@yahoo.com. Following the complete transmit routine is a brief description of how the receive routine works and how to use it.  After the receive routine is a few comments about the T6963 Graphic LCD and 24A512 EEPROM interface libraries I wrote. Links to these libraries are provided. Finally the document is concluded with a few programmer/design notes and observations that I have made along the way.

 

-- Function vpw_send(byte in data) shifts out "data" one bit at a time

-- MSB first. Arbritration is taken care of during transmission. If the

-- transmission is successful then the function returns true else it returns

-- false. Call procedure repeatedly to send multiple bytes. just be sure to

-- only send one start of frame symbol per message frame.

 

-- Example Usage: *success is a var of type bit

-- Send Single byte of data: success = vpw_send(data,true)

-- tmr1on = false

-- Send Multiple bytes in the same frame: success = vpw_send(data1,true)

-- if success then

-- success = vpw_send(data2,false)

-- end if

-- tmr1on = false

-- Send "Array" of data:

-- var bit send_sof = true

-- var byte count = 0x00

-- while ((success) & (count < arrayX_put_index)) loop

-- temp = arrayX

-- success = vpw_send(temp,send_sof)

-- send_sof = false

-- count = count + 1

-- end loop

-- tmr1on = false

 

function vpw_send (byte in data, bit in send_sof) return bit is

var bit error_flag = false

var volatile bit bit_out at data : 7 -- select bit to be transmitted

var bit dom_pass = false -- keep track of active or passive symbol

var byte timer_low_byte, timer_high_byte, prtc_buf

var volatile byte next_bit = 0x00 -- sets next pulse width

 

for 8 loop -- shift data out one bit at a time; MSB first

 

if bit_out then -- if bit to be sent is a One

if dom_pass then -- send "Active" One

next_bit = per_short

else -- send "Passive" One

next_bit = per_long

end if

else -- Bit to be sent is a Zero

if dom_pass then -- send active zero

next_bit = per_long

else -- send "Passive" zero

next_bit = per_short

end if

end if

-- start of frame takes care of period adjustment for first bit. If current

-- itteration is not sending a SOF, must adjust new period by adding pulse width

-- to current ccp2H:ccp2L registers.

 

if send_sof then -- need to send a start of frame first?

-- send start of frame plus first bit

vpw_idle_chk -- verify bus is idle before beginning transmission

 

f877_ccp2con = comp_drive_low -- bus will be high for SOF. Drive bus low

-- when pulse width is achieved. should also

-- drive output pin high beginning transmission

 

tmr1on = on -- begin timing bus position

 

f877_ccpr2l = per_sof -- set period for compare to be ~200uS

f877_ccpr2h = 0x00

 

pir2_ccp2if = false

 

 

error_flag = false -- assume success unless failure occurs

 

while ! pir2_ccp2if loop end loop -- wait until timer2 times out. Once

-- this loop is exited our SOF symbol

-- will have finished and our output

-- pin will be low.

-- If the bus is still active then that means another node is still

-- transmitting. The only other allowed active symbol that lasts for

-- this duration is a BREAK symbol. So we poll the bus for the shortest

-- allowed BREAK time. If this is passed then we will cease our attempt

-- to transmit.

while vpwin loop -- allows for nodes with slower clocks to finish their

assembler -- SOF's.

local shorter

movf f877_tmr1h,w -- The new starting point for the compare register

movwf f877_ccpr2h -- is updated while the other nodes finish.

movf f877_tmr1l,w

movwf f877_ccpr2l

movf f877_tmr1h,w -- ensures proper loading of CCP2 with the current

movwf f877_ccpr2h -- timer value

bcf status_Z

movlw 0x01

subwf f877_ccpr2h,w -- magnitude comparison of the current high byte

BTFSS status_Z -- if the current timer value is greater than

GOTO shorter -- 0x128 then the maximum allowed transmission

movlw 0x28 -- length for a SOF has been reached. If it isnt

bsf status_C

subwf f877_ccpr2l,w -- then we need to poll the bus again and repeat.

BTFSS status_C

GOTO shorter

bsf error_flag

shorter:

end assembler

if error_flag then

vpwout = false

tmr1on = false

f877_tmr1l = 0x00

f877_tmr1h = 0x00

f877_ccp2con = 0x00

return false

end if

end loop

-- SOF is always followed by data bytes. Prep for first bit to be sent

assembler -- add next period to compare reference, 8-bit + 16-bit

bank MOVF next_bit,W

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

end assembler

asm clrf f877_ccp2con -- first bit to be transmitted is always low

f877_ccp2con = comp_drive_high -- set ccp2 to drive output high on match

 

pir2_ccp2if = false -- reset interrupt flag

send_sof = false -- no more start of frame symbols to transmit

else -- just send the remaining bits

assembler -- add next period to compare reference. 8-bit + 16-bit

bank MOVF next_bit,W

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

end assembler

end if

 

while ! pir2_ccp2if loop -- wait until end of period, when loop is

-- exited bus transition should be done.

prtc_buf = port_c -- Check for arbitration. If output does not

prtc_buf = prtc_buf & 0x06 -- match input then arbitration may be lost.

if ((prtc_buf == 0x04) & (! pir2_ccp2if)) then

assembler

bcf tmr1on -- Comparison between the bus transition time

-- and the time remaining before bus expected

-- transition is used to compensate for clock

-- mismatch. Therefore false "lost arbritration"

-- is avoided. This portion only checks for nodes

-- that transition early. Nodes that transition

-- later are dealt with after this portion.

bsf status_C

bcf status_Z

 

local EXITE, EXIT

MOVF f877_tmr1l,W -- determine delta between expected transition

SUBWF f877_ccpr2l,w -- and actual transition time.

movwf timer_low_byte -- timer_X_byte = ccpr2X - f877_tmr1X

MOVF f877_tmr1h,W

BTFSS status_C

INCF f877_tmr1h,W

SUBWF f877_ccpr2h,w

movwf timer_high_byte

btfss status, status_z -- the difference between high bytes must be zero

goto EXITE

movlw 0x14 -- to be within tolerance the difference must be less

bsf status_C -- than 20 instructions. Setting the Carry/Borrow bit

subwf timer_low_byte,w -- makes it available to borrow.

BTFSS status_C -- If the low byte is >= 20 then a borrow

goto EXIT -- will not have occured and status_c will still be set.

BTFSC status_Z -- If the result is 0 then transition occured right on

goto EXIT -- the boundary and is still valid.

EXITE: -- check somewhere has not passed.

bsf error_flag

EXIT:

end assembler

if (error_flag) then -- bus dominance lost.

vpwout = false -- "Shut up and try again later"

tmr1on = false

f877_tmr1l = 0x00

f877_tmr1h = 0x00

f877_ccp2con = 0x00

return false

else -- early transitioning node transitioned within specs. Change

f877_tmr1l = f877_ccpr2l -- output and move on.

f877_tmr1h = f877_ccpr2h

tmr1on = on

end if

end if

if ((prtc_buf == 0x02) & (! pir2_ccp2if)) then -- bus fault has been detected. Try again later.

vpwout = false

tmr1on = false

f877_tmr1l = 0x00

f877_tmr1h = 0x00

f877_ccp2con = 0x00

return false

end if

end loop

 

pir2_ccp2if = false -- reset ccp2 interrupt flag

 

if dom_pass then -- switch transition directions

-- first must allow for nodes that are transmitting their active signals

-- for just a little bit longer than we are. For the short pulse, the

-- maximum allowed overshoot in time is 32uS and for the long pulse the

-- maximum allowed overshoot in time is 34uS. Splitting the difference

-- we will check for 33uS. However, I believe this to be incorrect and used 16uS.

if vpwin then -- bus is still active

assembler

local loop1, done, exita

movlw comp_only

movwf f877_ccp2con

MOVlW 0x14

bank ADDWF f877_ccpr2l,f

BTFSC status_C

bank INCF f877_ccpr2h,f

loop1:

BTFSS vpwin -- loops until either the input pin changes

goto done -- or our "added" time expires.

BTFSS pir2_ccp2if

goto loop1

bcf tmr1on -- if the program gets here then that means the input

bcf vpwout -- is still set and our timer period has expired. If

clrf f877_tmr1l -- we were sending a short pulse then it means arbitration

clrf f877_tmr1h -- has been lost. If it is a long pulse then it could

clrf f877_ccp2con -- mean a break symbol is being transmitted. Either way

bsf error_flag -- we need to stop transmitting and exit.

bcf pir2_ccp2if

goto exita

done:

clrf f877_ccp2con -- updates the compare registers with new

movf f877_tmr1h,w -- starting values

movwf f877_ccpr2h

movf f877_tmr1l,w

movwf f877_ccpr2l

movf f877_tmr1h,w

movwf f877_ccpr2h

bcf pir2_ccp2if

exita:

end assembler

if error_flag then

return false

end if

end if

f877_ccp2con = comp_drive_high -- if previously sending an active signal

dom_pass = false -- next symbol will be passive. Ergo need

else -- to drive bus high on next compare.

f877_ccp2con = comp_drive_low -- Same logic here just opposite results

dom_pass = on -- No need to check for devices with "longer"

end if -- periods here because we have driven the

-- bus high already.

 

 

data = data << 1 -- shift in next bit to be sent

end loop

return true

end function

 

 

Receive Routine ***UPDATED 4/20/06

 

Apparently I was closer to finishing the receive routine than I first assessed.  Basically, the routine is very similar to how I described it before, the pulse width is measured and appropriate symbols are attached to each received pulse.  But just to reiterate, let me go ahead and outline the basic principles behind what is going on inside the receive routine. First and foremost, the routine measures how long the bus is in each respective state. The pulse width measurement is done following the guidelines outlined in the Microchip document DS41214A “PICmicro CCP and ECCP Tips ‘n Tricks” with the exception that the subtraction is performed between any two pulse edges, not just the active pulse case as demonstrated in the document. I have uploaded a copy of the paper, which can be found here, but I’m sure it can still be found on Microchip’s website as well.  Once the width of the pulse is determined, the bit symbol is then decoded based upon which edge of the pulse triggered the interrupt. Wait! Interrupt? Yes, the receive routine is still interrupt driven to lessen the amount of software overhead dedicated to decoding the symbols.  Plus it ensures that a pulse edge will not be missed.  That’s not to say, however, that you can’t switch it to all inline if you really wanted to.  I decided to leave it as half interrupt half inline because it made my job just a little bit easier; compressing code is not one of my strong suites.  But if you wanted to switch it to inline, the shortest pulse width that has to be measured is 34mS.  That translates to something like 170 PIC instructions @ 20MHz. I am almost certain that you can fit the pulse width measurement, symbol decoding, and message buffering in less than 170 instructions but that just depends on how good your code is. 

 

Message buffering is done through the use of “arrays” kept in bank1.  In its current form, the routine buffers one entire frame per call.  The received frame is placed in array4.  Space has been set aside to allow multiple frames to be buffered in arrays 2 & 3.  Just “unload” array4 into one of these other arrays to free up the receive buffer again.  Eventually, I will probably go back and modify the routine to accept a memory address pointer as a parameter to store frames without having to “unload” the message buffer.  The modified routine would accept an “array_starting_address” as a parameter and would fill the next 12 bytes with the received frame.  But that’s probably something that will be included in VPWM v1.2.

 

One inherent issue with just simply timing pulses is we have no way of determining where one message ends until the next message starts. (i.e. there will be one really long pulse separating messages.) This is due to the fact that there is no stipulation on the number of bytes that must be contained within a given message frame. Only the maximum number of bytes is limited. This means that we cannot just count the number of pulses and figure out if the end of the message has been reached. To solve this problem we need another timer or a compare register set to timeout when the bus has been inactive for a given amount of time.  I considered two possibilities for implementing this EOF detection, either using timer2 or the other CCP module.  I decided upon using timer2 because it has a period control register.  This makes the timeout feature sort of “set and forget.”  Timer2 is reset after each pulse so that we time from the last known pulse edge.  Since all that is involved in resetting the timer is clearing its register this greatly reduced software overhead.  We don’t have to constantly do 16-bit addition like we would have to with the other CCP module.

 

That in a nutshell describes the theory behind the receive routine.  I will describe a little bit about the details behind using it but it will be nothing like the 33-page dissertation about the send routine above so you can breathe easy.  First thing I should probably mention is that IFR handling is NOT included yet.  This is because GM doesn’t use In-Frame Response so I have no use for it yet.  Again this is a feature that will probably be included in VPWM v1.2.  Secondly, this routine is a function that returns a bit value.  This bit value is a pseudo-error flag.  Pseudo-error flag?  What the heck does that mean?  Well, it means that the procedure went for longer than 239mS but less than 280mS between received pulses.  J1850 defines the minimum allowed pulse width received for a valid BREAK symbol to be 239mS.  What the procedure does if it encounters a pulse that is longer than 239mS but less than 280mS (remember if the routine goes for longer than 280mS without receiving a pulse it times out and exits) is it resets and starts again from the beginning.  The function then returns true indicating that one or more frames has been ignored prior to the frame that is stored in the buffer.  BUT there is a catch.  If your VPW network uses In-Frame Responses, the normalization bit occurs after 239mS and before 280mS and it WILL trigger this response of ignoring the first frame.  However, since the normalization bit is either and active 1 or 0 and not a SOF, the procedure will not recognize the In-Frame Response as a valid message and will ignore it.  Thus, this will cause two messages to be ignored if In-Frame Responses are used.  So just remember, if the function returns true then it is not really an error, it just means that one or more message frames have been ignored.

 

It should also be noted that the function makes no attempt to detect or report errors of any other type.  There is no error flag indicating a bus timeout has occurred or an incomplete frame was received.  So if the routine is called and nothing is received, i.e. the bus just remains idle and no other nodes talk on the network, the PIC will wait forever until it receives something and it will not set a flag informing the user that this has happened.  A third timer (ahem timer0) should be added inside the user’s code to act as a watchdog to prevent this from happening.  This was left out to provide the user with greater flexibility on how they want to implement this into their own project.

 

I think that is about all I want to say about the receive routine right now.  I will make a few changes/improvements here in the next few days to resolve some of those issues I talked about above but other than those, the routine works great.  It is presented below in its entirety.

 

-- Some Variables

-- Byte Variables RESERVED FOR INTERRUPT ROUTINE

 var byte tmr1_low_old = 0x00, tmr1_high_old = 0x00 -- stores old tmr1 values

 var byte tmr1_low_new = 0x00, tmr1_high_new = 0x00 -- stores new tmr1 values

 var byte delta_low = 0x00, delta_high = 0x00 -- stores the difference between

                                                                  -- tmr1_old & tmr1_new

 var bit pulse_rec = off

 

-- procedure vpw_receive configures the ccp1 module to capture timer 1 and timer2

 -- to time inactivity.  First ccp1 is configured to interrupt on the rising

 -- edge.  On the first rising edge, timer 1 is started and the ccp1 module is

 -- reset for falling edge int. On the following edge interrupts, the two different

 -- captured timer 1 values are subtracted from each other to determine the

 -- pulse witdh.  Then based upon which direction of the interrupting edge, the

 -- symbol is decoded. Once a SOF character has been decoded timer2 is enabled to start

 -- counting out 280uS to determine if IFS has been satisfied. Timer2 is reset

 -- on each valid pulse received.  Therefore, timer2 clocks 280uS from the last bit received.

 --  Once a bit is received, it is placed in a bit buffer that is shifted left

 --  after each bit.  When 8 bits have been received, that byte is placed into

 --  the first available frame buffer array.

 

 -- ***NOTE*** In-Frame Response has NOT been implimented yet!! If you are using

 -- this library for a VPW system that requires IFR then you will need to add it.

 -- I will get around to adding it sometime in the near future but right now I do

 -- not need it (GM doesn't use IFR).

 -- -----------------------------------------------------------------------------

 

 function vpw_receive return bit is

 

   var byte delta_low_old = 0x00, delta_high_old = 0x00 -- buffers delta bytes

   var volatile byte rec_buff = 0x00  -- buffers the current received byte before

                                                    -- loading into the buffer array

   var byte bit_count = 0x00 -- counts number of bits received

   var volatile bit bit_rec = false -- holds current received bit

   var volatile bit data_rec = false -- determines whether data has been received

   var volatile bit bit_buff at rec_buff : 0 -- stores current received bit

   var volatile bit long_short = false -- flag indicating long or short pulse

   var volatile bit brk_rec = false -- flag indicating a BRK symbol has been received

   var volatile byte ccpconfig = 0x00 -- stores current ccp1 configuration

   var volatile bit sof_rec = false

  

   f877_tmr1l = 0x00 -- reset timer 1

   f877_tmr1h = 0x00

   tmr1_low_old = 0x00 -- reset tmr1 storage variables

   tmr1_high_old = 0x00

   array4_put_index = 0x00 -- reset processing buffer index to 0

   pulse_rec = false

   f877_ccp2con = 0x00

  

   intcon_gie = on -- enable interrupts

   intcon_peie = on

  

   pir1_tmr2if = false

  

   vpw_idle_chk -- wait until beginning of next frame before starting to receive

 

   asm clrf f877_ccp1con

   f877_ccp1con = rising

 

   while ! pir1_tmr2if loop

     while ! pulse_rec loop end loop

     pulse_rec = false

     delta_low_old = delta_low  -- buffer the delta values incase interrupt occurs

     delta_high_old = delta_high -- before we finish working with this pulse

     ccpconfig = f877_ccp1con    -- buffer ccp1 configuration incase interrupt

                                             -- occurs before we finish working with this pulse

 

      if ((delta_high_old == 0x00) ) then

        if delta_low_old >= 0xCC then

          sof_rec = on

          tmr2on = on

          bit_count = 0x00

          f877_tmr2 = 0x00

          array4_put_index = 0x00

        elsif ((delta_low_old <= 0xCB) & (delta_low_old >= 0x78)) then

          long_short = on

          data_rec = on

          f877_tmr2 = 0x00

        elsif ((delta_low_old <= 0x77) & (delta_low_old >= 0x2A)) then

          long_short = off

          data_rec = on

          f877_tmr2 = 0x00

        else

          data_rec = false

        end if

      elsif ((delta_high_old == 0x01) ) then

        if ((delta_low_old >= 0x2B) & (! pir1_tmr2if)) then

          brk_rec = on

          sof_rec = false

          data_rec = false

          long_short = false

          tmr1on = false

          tmr2on = false

          f877_tmr1l = 0x00

          f877_tmr1h = 0x00

          f877_tmr2 = 0x00

          array4_put_index = 0x00

          tmr1_low_old = 0x00

          tmr1_high_old = 0x00

          delta_low_old = 0x00

          delta_high_old = 0x00

          asm clrf f877_ccp1con

          f877_ccp1con = rising

        elsif delta_low_old < 0x2B then

          sof_rec = on

          tmr2on = on

          pir1_tmr2if = false

          bit_count = 0x00

          f877_tmr2 = 0x00

          array4_put_index = 0x00

        end if

      end if

      if data_rec & sof_rec then

        if ccpconfig == rising then -- if the next interrupt edge will be rising

          if long_short then        -- then that means the previous pulse was active.

            bit_rec = off           -- that means a long pulse = 0 and a short pulse = 1

          else

            bit_rec = on

          end if

        else                -- next interrupt edge will be falling, indicating the

          if long_short then -- previous pulse was passive.  long_pulse = 1 & short_pulse = 0

            bit_rec = on

          else

            bit_rec = off

          end if

        end if

        rec_buff = rec_buff << 1

        bit_buff = bit_rec

        data_rec = false

        bit_count = bit_count + 1

      end if

      if bit_count == 0x08 then

        array4 = rec_buff

        bit_count = 0x00

      end if

   end loop

   tmr1on = false

   tmr2on = false

   f877_ccp1con = 0x00

   f877_tmr1l = 0x00

   f877_tmr1h = 0x00

   f877_tmr2 = 0x00

   pir1_tmr2if = false

   pir1_ccp1if = false

 

   intcon_gie = false

   intcon_peie = false

   return brk_rec

 end function

 

 

 -- Table for determining pulse widths from delta values:

 --            |long pulse| short pulse|    SOF     |   break   |

 --            | max | min | max  | min | max | min | max | min |

 -- delta_high |0x00 | 0x00| 0x00 | 0x00| 0x01| 0x00| 0x01| 0x01|

 -- delta_low  |0xCC | 0x78| 0x78 | 0x2A| 0x2B| 0xCC| 0x2B| 0x2B|

 --

 -- Logic Flow:  If delta_high > 0 then either SOF or BREAK

 --              if delta_low - 0x2B > 0 & delta_high > 0 then it is a break

 --              if delta_low - 0x2B <= 0 & delta_high > 0 then it is a SOF

 --      cases for delta_high == 0

 --      IF delta_low - 0xCC > 0 then it is a SOF

 --      If delta_low - 0x78 >= 0 & delta_low - 0xCC < 0 then it is a long pulse

 --      if delta_low - 0x2A >= 0 & delta_low - 0x78 < 0 then it is a short pulse

 --      if delta_low - 0x2A < 0 then it is too short

 

 procedure interrupt_handler is  -- edge has been detected on ccp1

 pragma interrupt

      tmr1_low_new = f877_ccpr1l

      tmr1_high_new = f877_ccpr1h

--     tmr1_new - tmr1_old = delta

      assembler -- 16-bit subtraction of old values from the new

      btfss tmr1on

        bsf tmr1on

      bank   MOVF   tmr1_low_old,W

      bank   SUBWF  tmr1_low_new,W

      bank   MOVWF  delta_low

      bank   MOVF   tmr1_high_old,W

             BTFSS  status_C

             INCFSZ tmr1_high_old,W

      bank   SUBWF  tmr1_high_new,W

      bank   MOVWF  delta_high

      end assembler

      tmr1_low_old = tmr1_low_new  -- replace old values with new ones

      tmr1_high_old = tmr1_high_new

 

    if ccp1m0 then  -- rising edge interrupt has been detected on ccp1

      assembler

      clrf f877_ccp1con  -- configure for falling edge

      movlw falling

      movwf f877_ccp1con

      end assembler

    else

--    falling edge detected

      assembler

        clrf f877_ccp1con

        movlw rising

        movwf f877_ccp1con

      end assembler

    end if

    pir1_ccp1if = false

    pulse_rec = on

 end procedure

 

Back to top

 

CRC Routine:

 

Appended to the end of every J1850 message frame is a CRC byte that is used to detect transmission errors.  I must admit that I myself did not write the original code for calculating the CRC byte.  The original C-code was written by B. Roadman, www.obddiagnostics.com, and I merely ported it to JAL to use in this project.  The CRC division polynomial is X8 + X4 + X3 + X2 + 1 and the remainder of this division is what is appended to the message frame.  Since I myself did not write this procedure and I do not have a strong grasp on CRC calculation I probably will not be able to answer technical questions about how this procedure calculates its value.  I CAN however, answer questions regarding its implementation and usage.

 

Let me continue by explaining the in’s and out’s of using this procedure.  I will then follow up with an example and finally the actual CRC routine.  Typical of all CRC calculations, the remainder byte that is “tacked-on” to the end of the frame is the result of dividing the entire message frame by the division polynomial.  In our case, this translates to calling the procedure for each byte that we want to transmit, starting with the first header byte.  The previous CRC remainder is passed to the procedure for each successive byte being transmitted.  Once the final byte being transmitted has been processed the resulting remainder is then complimented or XOR’d with 0xFF before being attached to the end of the message.  However, on its first usage, the remainder must be initialized to the value 0xFF.  Below, an example of using this routine and then sending the complete message is provided.  It is assumed that the message being sent is stored in array0.

 

var byte crc_remainder = 0xFF  -- create and initialize the crc remainder

var byte temp = 0x00, count = 0x00  -- some temporary and loop index variables

var bit success = on, sof = on  -- procedure flags

 

for array0_put_index loop  -- calculate the crc remainder for the current message

  temp = array0

  crc_calc(temp, crc_remainder)

end loop

 

asm comf crc_remainder,f  -- compliment the resultant remainder before appending to the end of the message

 

array0 = crc_remainder  -- append crc to message end

 

array0_get_index = 0x00  -- reset the “get” index for the array

 

while ((success) & (count < array0_put_index)) loop  -- send current message

  temp = array0  -- obtain data byte

  success = vpw_send(temp, sof)  -- send data byte – if first iteration a SOF will be sent

  sof = false  -- no more sof’s will be sent

  count = count + 1  -- increment index

end loop

 

tmr1on = false  -- turn off timer since message has been sent

  <

 

This example shows how the CRC should be implemented in your program if you are sending a message.  The example also show’s how a typical message should be sent using the vpw_send procedure.  The complete CRC routine source is shown here:

 

 

 procedure crc_calc(byte in data, byte in out crc ) is -- calculates the CRC byte

 var byte bit_point = 0x80  -- that is appended to the end of the OBD message Frame

 var byte poly              -- NOTE* Procedure is to be called continuously for each

 for 8 loop                 -- byte that is transmitted.  However, once final byte is

   if (bit_point & data) != 0 then -- passed through the CRC routine the final result in crc_reg

     if (crc & 0x80) != 0 then  -- must be complimented before appending to the message.

       poly = 0x01          -- For all messages, parameter CRC should be initialized to

     else                   -- 0xFF for the first iteration.

       poly = 0x1C

     end if

     crc = ((crc << 1) | 1) ^ poly -- original C Code for CRC posted on http://obddiagnostics.com by B. Roadman

   else                            -- adapted to JAL by Shawn Standfast

     poly = 0x00

     if (crc & 0x80) != 0 then

       poly = 0x1D

     end if

     crc = (crc << 1) ^ poly

   end if

   bit_point = bit_point >> 1

 end loop

end procedure

 

 

Hardware: Drivers and Includes

 

There are two other main pieces of hardware that comprise the rest of the ScanTool. They are the T6963 based graphic LCD and the 24AA512 EEPROM bank. I happened to come across a great deal on EBay for the LCD; 2qty. DataVision 12864-12 64x128 graphic LCD’s for $10.00 + $5.00 S/H so that is why I opted to use the “more expensive” graphic LCD instead of the cheaper 16x2 HD44780 route. But with the graphic LCD I now have the flexibility to display much more information simultaneously. This will be handy for performing diagnostics with the engine running. Plus the LCD is just about the right size to still make the device comfortable to hold. However there is a downside to using the graphic LCD and that comes in the form of pin count. As it is right now, this interface requires 14 pins to work. This can be reduced to 13 if the user doesn’t want to be able to change the font size.

 

Going with the graphic LCD meant that I now had to write a driver to interface with the display. This was actually a lot easier than I first anticipated because our PIC is too slow to make timing an issue. The interface library can be found here: T6963.zip and it comes in three parts: t6963.jal, t6963i.jal, and t6963p.jal. T6963.jal is just a “wrapper” file that includes the other two libraries and is the one you include explicitly in your program. T6963i.jal is the instruction library and is where all the fun happens. It has procedures for reading and writing data/commands explicitly (user is responsible for correct transfer sequence), “built-in” ability to write text or graphics to user specified places on the screen (see documentation for more information about the text/graphics write), and a number of predefined command sequences (text/graphics home move, cursor placement, address pointer placement). I believe the procedures to be fairly well commented/documented but if you have any questions feel free to send them to me.

 

The last major piece of hardware that will be used in this project is the EEPROM memory bank. The need for non-volatile memory for storing the DTC tables should be obvious and I figured that EEPROM’s would be the best way to go for this. I decided upon the Microchip 24AA512 512Kbit EEPROM because it is the largest/fastest EEPROM chip they had. This project calls for three of these devices in order to store all the P, B, C, & U codes, their descriptions, and a look-up table to map the codes.

 

Once again, a driver had to be written for the PIC so that it can interface with the EEPROM’s. The EEPROM’s are interfaced via I2C. Not wanting to use the software I2C library that comes with the JAL compiler, I wrote a library that utilizes the 16F877’s onboard I2C controller. Both libraries are available for download here: 24AA512.zip. Also included in that download is a modified version of the standard jpic.jal library. This version may need to be included to ensure that your program compiles.

 

Also available for download is the bank_arrays library written by Stef Mientki. This is a wonderful library he wrote that sets up “arrays” in the various PIC memory banks and is utilized extensively in my program.

 

Back to top

Programmer’s Notes and Observations:

 

In the past, you may have been aware that I had trouble getting the firmware to function properly. Everything works now but it took a little bit to get it that way. Specifically, I was having trouble with getting the SOF symbol to appear on the bus. Using the program that I wrote to test/debug the receive routine I was able to determine that there was in fact a pause or a “blank spot” that matched the width of the SOF exactly. The only problem was the bus was remaining low for this duration instead of being driven high like it should have been. After several re-writes of the code and various other tests I was about to give up and try something else when I happened to notice something on my scope. What I noticed was a teeny tiny itty bitty little blip where the bus was in fact driven high but it immediately fell back to the low potential. This spike lasted for less than 1mS before it was gone which made it very difficult to see and detect, especially when the scope was setup to observe signals with periods on the order of milliseconds.

 

Now believe me when I tell you that before I saw the blip, I had already tried running the program on a different (another 16F877) chip to rule out the possibility of the chip being “bad.” But when I saw the spike on the scope, I promptly reached for another chip to verify that this was still a software problem I was looking for and not hardware. It just so happened that the chip I grabbed was a 16F877A and not another 16F877. And guess what, it worked! Right there on the scope was the elusive SOF pulsing proud for all to see. A little befuddled by my discovery, I reprogrammed both of my older 16F877’s with the same program and sure enough no SOF from either chip.

 

The moral of the story is that this program works on all the 16F877A’s I’ve tested but not on my two 16F877’s. This could be a fluke and I have two bad F877’s or there could be a silicon flaw that was corrected in the migration to the A version. As I don’t have any other non-A F877’s to test this theory with I cannot back this claim up. So if you have both A and non-A versions of the 16F877 try programming both and let me know what you find.

 

As a control, I will provide the hex file that I used when testing the send routine. It is a very simple program that just sends the same three-byte message – 0x00 0xFF 0x08 - over and over. The hex file is available here: test.zip. The 0x00 byte should be preceded by a SOF symbol.

 

The entire VPWM.jal library is provided.  Read above about the usage/guidelines for the receive routine before attempting to use in your own program.

 

Back to top

 

Disclaimer- As usual with designs that are posted freely, I cannot be held responsible or liable in any way for any damages incurred from these or any future designs use or misuse. All information is provided "As-Is" with no warranty and is not intended for use in military or medical devices. Commercial use is strictly forbidden without expressed written permission from the original creator(s). All bugs (if any) are FREE! ;)