Car TPMS Tinkering
Taking apart his car’s Tire Pressure Management System (TPMS) led Scott to gain hands-on knowledge of LIN bus. LIN bus is a communication scheme used in automobiles when CAN bus is too large an option. In this article, Scott explains what LIN bus is, and how understanding it helped him discover what was wrong with his car’s TPMS.
Years ago, when the government decided that the general population did not have the necessary skills to properly use a conventional tire gauge, it was decided that all automobiles would be required to support a Tire Pressure Management System (TPMS). The intention was to inform the driver when the tire pressure was too low, which would cause the vehicle to suffer decreased gas mileage. In their wisdom, it appears they require it for electric vehicles as well.
My Tesla Roadster is one of the early generation cars from Tesla. It was built before they began assembly-line manufacturing. And the TPMS that was installed has given me headaches since the car was new. By the time the car was out of warranty and I realized I would have to pay for replacement of these parts, I decided it was time to investigate the deficiencies. Tesla had long since stopped using this model in favor of a better designed system.
Like most TPMS systems today, the system starts with a pressure and temperature sensor mounted on the wheel valve stem. This transmits packets of data to a receiver located under the vehicle. In an attempt to determine how it worked, I did what any good engineer would do: I took it apart.
The overall system consists of a central controller and two antenna modules (Figure 1). The central controller is connected to the rest of the vehicle through a CAN (Controller Area Network) bus. The actual antenna module (Figure 2) contains an RF receiver and small microcontroller (MCU) that will process the data and store it for forwarding to a central controller. Both receivers share a single signal bus.
I opened one of the receiver antenna units to try to determine what it was and how it worked. The entire board was coated with a substance like tar (Figure 3), probably to help arrest vibrations from affecting the surface-mounted components. Using acetone, alcohol, toothbrushes and patience, I was able to clean the board to the point that I could identify the components. There were three lines labeled at the connector: 12V, Ground, and LIN (Figure 4).
WHAT IS LIN?
LIN stands for Local Interconnect Network. The network is designed to provide master/slave communication and is used extensively in automobiles, where the CAN bus is too large and robust . The LIN bus is a single line with open collector outputs. You can think of it as similar to I2C without a clock. The voltage level is typically an automotive 12V system, so a level converter is required to reduce it to the 5V or 3.3V of an MCU. An additional requirement is that the level converter must support bidirectional communication. The Microchip Technology MCP2021 LIN transceiver  fits the requirements, and also provides a built-in 5V regulator to run the MCU. Microchip offers this device for both 5V and 3.3V systems.
The LIN protocol is a simple poll/response communication that supports 6 bits of addressing, and data packets up to 8 bytes, followed by a simple checksum. One advantage built into the specification is the lack of a clock line. Instead, the specification defines a timing header that the slave can measure and use to adjust for drift in the baud rate. The timing header diagram is shown in Figure 5. This allows for the use of lower cost MCUs with less accurate clocks to compensate for any drift in MCU speed. However, the sync signal component of the header complicates the use of conventional UARTs in most MCUs, because it must be at least 13 bits in length.
A single master will send out the timing signal followed by a byte referred to as an address or identification (ID) byte. The master and receiver have to agree on the addressing scheme. This gives them a mutual understanding of which ID is used when data are being sent from the master, and which ID is used when data are being returned to the master. Like most specifications that share a bus, the line is open collector, and the master or slave device will pull the line to ground to indicate a “1” or leave it high to indicate a “0.” Once the master controller sends out the timing header and ID, it either sends out its own data, or listens for a slave reply with its data.
The LIN standard defines 6 bits used for an identification byte. Slaves can respond to more than one ID. Therefore, the ID can be thought of as either an address or a command code. Included in the ID are two additional parity bits, making the ID a full 8 bits. The 6 bits for the ID allow for 64 unique addresses or commands. The newer specification attempts to break the ID bytes into classes for data lengths, message types, configuration and future enhancements. However, the TPMS used by my car was designed long ago, so it doesn’t follow any of the current recommendations for IDs.
The message format used by this system is an ID byte followed by 8 bytes of data, the direction of which is dependent on the ID number being sent. These 9 bytes total are followed by a checksum byte, which is simply a value needed to make the message add up to 0xff. I purchased Microchip’s LIN Analyzer, and began sniffing the bus to see what the chatter was. By unplugging antennas individually, I was able to determine the ID numbers to which each antenna responded.
Table 1 shows the original message IDs I determined. It is clear there is redundancy, since both the front and rear antennas are programmed with the wheel addresses for all four wheels. So, any single antenna would provide information about all four wheels. The ID column shows the actual ID number first and the ID with parity bits in parentheses. Each ID byte is 6 bits and two parity bits. The low-order bits are transmitted first, and the parity bits are bit numbers 6 and 7. In an attempt to make the ID byte more secure, the first parity bit is generated by making an even parity from bits 0, 1, 2 and 3, whereas the second parity is generated by making an odd parity from bits 1, 3, 4 and 5. In computer terms that is:
P0 = b0 ^ b1 ^ b2 ^ b4
P1 = ~(b1 ^ b3 ^ b4 ^ b5)
The master polls both antennas with ID numbers 23, 33, 24 and 34 every 2 seconds. This does not mean the wheel transmitters are broadcasting that frequently. The antennas are simply storing the last message they heard and forwarding it when asked. If the master isn’t given any valid data in a reasonable period of time, it will assume the transmitters have failed, and will notify the car of the failure through its vehicle management system.
Approximately every 2 minutes, it will also poll IDs 14, 15, 16 and 17, which are the requests for the wheel antenna addresses stored in the front and rear antennas. These are 4 bytes each, so one message can contain two wheel addresses. Close examination shows that the 4-byte addresses of the wheel transmitters are sequentially fairly close, as you might expect. Examples of the contents of each message are show in Table 2. The tire pressure is a value that, when divided by 2.755, yields the actual pressure in PSI. In this example x52 is 82/2.755 = 29.76psi. The temperature is simply a measurement in Fahrenheit: x45 = 69°F.
WHAT WENT WRONG
When I examined the system after another failure, I could see that the ID 14 was replying with 44 50 45 6C 45 6C 44 71—which is incorrect information. This ID is supposed to be the front wheel addresses, which the front antenna is listening for. But the 8 bytes looked nothing like the wheel addresses that were programmed originally. Since the antenna did not have the correct address, it obviously never heard from the wheels. Naturally, I reprogrammed it myself, using the LIN analyzer tool from Microchip. Within a few months it indicated another failure. Next time it was a different ID number, but the same fault: the wheel address had been changed. Now it was time to take a closer look.
I examined the IDs for the wheel addresses. I could see that ID 14 (the front wheel addresses) were still correct, with values of 08 06 66 2E 08 06 66 EC. However, this time ID 15 (the rear wheel addresses) had the values 70 53 70 52 27 00 A9 00—nothing like what the addresses were originally. In fact, I noticed they seemed partially similar to tire pressure and temperature values. A value of x70 is decimal 112, which works out to be 40psi, and x53 is 83°F, the expected values of my rear tires in early spring. Apparently, the unit was corrupting the stored wheel addresses with data that were tire readings. How could this happen? A unit would have to confuse an ID value of A3 as a D3. Turns out, it’s not really that hard.
Looking closely at the bit patterns, I could see that it would be easy for a timing error to misinterpret an ID bit into a parity bit (bit 5 to bit 6), changing an ID from A3 to D3 (1010 0011 -> 1100 0011). The parity bits in the ID would not detect it, and the checksum doesn’t include the ID. This seemed like an easy error to occur in a noisy environment, and one unlikely to be detected. So, while antenna A thought it needed to send tire data, antenna B heard something else, and thought it was receiving new addresses.
Perhaps the fault is due to noise or timing errors. But whatever the cause, it’s clear they should have chosen different values for the IDs to set the wheel addresses. The values being used can succumb to errors too easily.
FRAMING IT UP
So, now that I knew what the problem was, I wondered if I could emulate the messages and communicate with the controller. It seemed simple enough. Because the 13-bit sync signal is not compatible with conventional UARTs, I would have to write my own serial input and output. I selected Microchip’s PIC16F628 MCU, because it was small and inexpensive. The first thing was to compute the expected timing of the 13-bit sync signal, because you don’t want a baud rate drift to cause the system to confuse a data byte of all ones with a sync signal. The LIN bus uses a baud rate of 9600 baud.
The break signal must be at least 13 bits in length. Therefore, at an estimated baud rate of 9600, or 104µs per bit, the break signal must be approximately 1,350µs long. And with a clock drift of 15% either way, it means we must find a sync signal between 1,147µs and 1,552µs. However, an 8-bit data byte of all ones, including the start bit, is about 936µs, and if the MCU clock drifted upward by 15% it could appear to be as high as 1,076µs to the MCU. That gives a difference between a slower sync signal and a higher data byte of 1,147 – 1,076 or about 71µs.
The MCU has an internal clock of 4MHz, and each instruction, along with timer counts, takes four clock cycles to execute. That makes a convenient clocking of 1MHz, and the times computed above can be used directly in 16-bit timer values. The sync duration will require a timer value between x610 and x47b.
Initially I wanted the LIN signal to trigger an interrupt, but I rapidly learned that I would have to add up the number of additional clock cycles as the ISR branched through its routines. Instead, I used a brute force test of watching the IO line until it changed, and examining the high byte of the timer. If the high byte was x03 or below, it was too short; and if it was x07 or above, it was too long. This could give me an overlap with a larger period of a data byte, but as a prototype should work fine. The interbyte space is 1 bit, so that allows the MCU about 100 instructions, if further resolution is desired.
Once the sync signal is detected, it becomes easier, because the header byte is a square wave made from a 0x55 value. So, it is simply a process of waiting for the rising edge after the start bit, starting the timer, and counting eight transitions. Once completed, the timer value can be divided by 8 to determine the real bit time.
This process is shown in the flow chart in Figure 6. The simulator has antenna addresses and tire data hard-coded in the program. It will transmit back in response to any of the IDs that the controller will send. It will also receive a full 8-byte message to set a new address; it will discard the message, though, because the unit is not intended to have its addresses reprogrammed. The entire assembly code is less than 500 lines. Listing 1 contains a snippet of the first 50+ lines. The full code listing is available on Circuit Cellar’s article code and files webpage.
LISTING 1 – The entire assembly code is less than 500 lines. Shown here is a snippet of the first 50+ lines. The full code listing is available on Circuit Cellar’s article code and files webpage.
LIST p=16F628a ; , F=INHX8M
CONFIG _CP_OFF & _WDT_ON & _INTOSC_OSC_CLKOUT & _MCLRE_ON & _ BODEN_OFF & _LVP_OFF
#define LINSLAVERAM 0x20 ; startaddress
DATABANK udata LINSLAVERAM
edgecount RES 1 ; Used to count the edges of the sync byte
bitlength RES 1 ; Number fo machine cycles that is a bit
bitcount RES 1 ; number of bits received
bits RES 1 ; Bit counter as they are received,
bytecount RES 1 ; number of bytes in the message recevied
comflags RES 1 ; linbus communication bits
bufferptr RES 1
combuffer RES 1
checksum RES 1 ; checksum over xmit / rcsv block
frontaddr RES 8 ; data transmission buffer
rearaddr RES 8
tiredata1 RES 8
tiredata2 RES 8
rxbuffer RES 8 ; data receive block buffer
; Define the PORTB pins used for communication
#define INPUT 0
#define OUTPUT 1
STARTUP org 0x00
movlw 0x09 ; RB0 in, all others out
movwf TRISB ; Bank1 Set PORTB
clrf TRISA ; Bank1 Set PORTA to all output
banksel PORTA ; Select Bank 0
call SeedFrontAddr ; Prelim set TX buffer to a GOB
Go to Circuit Cellar’s article code and files webpage for the full assembly code listing.
BUILDING THE SIMULATOR
The simulator circuit board is a small module designed to fit into a single project case. The schematic is shown in Figure 7. The signal lines have 22V transient suppressor diodes to protect the inputs, and the 12V power line has a protection diode to prevent accidental reverse polarity.
Once the board was assembled and mounted, I took a cue from the original and coated the parts with epoxy, to ensure they stayed put on the board. The assembled unit is shown in Figure 8. Using the same connector part, it was as simple as plugging-in the unit directly as a replacement to both antenna units, and fooling the system into thinking the tire health was fine.
The LIN communications bus is common in vehicle systems where the messages are not required to be as robust and fault-tolerant as with a CAN bus. As demonstrated here, it is possible to have noise or timing result in an altered ID byte, which would go undetected by the slaves, and could even potentially reprogram the system with incorrect information. In this case, no resources allowed me to rewrite the code in the central controller unit, so there was no way to enhance the communication to be more robust. The LIN communication If this system were being designed from scratch, the choice of ID codes should be changed, to better protect against single-bit errors. Another improvement might be to alter the behavior to include the ID byte in the checksum, because the LIN specification omits it.
 https://microchip.wdfiles.com/local–files/lin%3Aspecification/LIN-Spec_2.2_Rev_A.PDF https://www.microchip.com/wwwproducts/en/MCP2021
Microchip Technology | www.microchip.com
PUBLISHED IN CIRCUIT CELLAR MAGAZINE • MARCH 2021 #368 – Get a PDF of the issueSponsor this Article
Scott Weber is a software engineer for a scanner manufacturer in the US. He earned his BSEE late in life from the University of Texas in Arlington. Scott has been working with embedded controllers for both work and fun during the past 20 years, and has been developing PC software for more than 35 years. He lives in Texas with his beautiful wife and her equally beautiful garden.