Using Lin Transceivers to communicate between master and slave
This is the 9th tutorial in the series on the UART peripheral of STM32 Microcontrollers. In this series we will cover different ways of transmitting and receiving data over the UART protocol. We will also see different UART modes available in the STM32 microcontrollers and how to use them.
This tutorial is the PART2 in the small series covering the Lin protocol. In today’s tutorial we will see how to connect a lin transceiver with the MCU. We will also use another MCU as the slave which will be connected to another lin transceiver. The data transfer will take place between the transceivers and it will be read by the slave MCU.
I have already covered the basics of the lin protocol in the previous tutorial, so I am skipping that part today. Here we will just focus on connecting the transceiver to the MCU and also the slave related programming.
MCP2004 The Lin Transceiver
I am going to use the microchip’s MCP2004 as the Lin transceiver. There is no specific reason to choose this IC, it was easily available so I am using it. You can also use MCP2003 as it is mostly similar to the MCP2004. Below is the pinout of the MCP2004.
- RXD (RECEIVE DATA OUTPUT) is an Open-Drain (OD) output. This pin must be connected to the RX pin of the UART.
- CS (CHIP SELECT) is used to enable or disable the transmitter mode of the transceiver. To enable the transmitter, this pin must be set to HIGH and to disable the transmitter, the pin must be LOW. We will connect this pin to the GPIO of the MCU.
- FAULT/TXE pin is bidirectional and allows disabling of the transmitter, as well as Fault reporting related to disabling the transmitter. We will leave this pin disconnected.
- TXD (TRANSMIT DATA INPUT) pin has an internal pull-up. The LIN pin is low (dominant) when TXD is low and high (recessive) when TXD is high. This pin must be connected to the TX pin of the UART.
- VSS is the supply ground pin. We will connect it to the ground of the 12v supply.
- LBUS is the bidirectional LIN Bus pin (LBUS) and is controlled by the TXD input. This pin will be connected to the LBUS of the another transceiver.
- VBB is the Battery Positive Supply Voltage pin. We will connect it to the +12V supply.
- VREN (VOLTAGE REGULATOR ENABLE OUTPUT) is the External Voltage Regulator Enable pin. We will leave this pin disconnected.
The Master
CubeMX Configuration
Below is the image showing the configuration of the UART in the Lin mode.
The USART1 is configured in the Lin Mode. The Lin protocol supports the transfer up to the baud rate of 200 Kbps, but here I am using 9600 bps. The data size is set to 8 bits with no parity and 1 stop bit.
We also need to set a GPIO pin as the output. This will be used as the CS pin for the transceiver.
Here I am setting the pin PC7 as the output pin.
The Code
I have already explained the master code in the previous tutorial. Although I have made some changes in the data we are going to send and it is explained below.
Below is the code in the main function.
int indx = 0;
int main ()
{
...
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_7, 1); // Pull the cs pin HIGH to enable transmitter
while (1)
{
TxData[0] = 0x55; // sync field
TxData[1] = pid_Calc(0x34);
for (int i=0; i<8; i++)
{
TxData[i+2] = indx++;
if (indx>255) indx = 0;
}
TxData[10] = checksum_Calc(TxData[1], TxData+2, 8); //lin 2.1 includes PID, for line v1 use PID =0
HAL_LIN_SendBreak(&huart1);
HAL_UART_Transmit(&huart1, TxData, 11, 1000);
HAL_Delay(1000);
}
}
Since the master will be transmitting the data continuously, we would want to keep the transmitter mode enabled. This is why the CS pin is set to HIGH.
We will send the TxData buffer via the UART, so we need to prepare it first.
- First store the sync bytes (0x55) to the buffer.
- The next element will contain the PID. Here I am using the ID 0x34, which will be then converted to the PID.
- Then copy the data bytes to the buffer. I am storing 8 data bytes with the values starting from 0 to 7. The data bytes are just the values of the indx variable, which will keep incrementing.
- The last element of the buffer will contain the checksum.
- I am using the Lin version 2.1, so the PID must be included in the checksum.
- For the lin version 1.x, the PID is not needed and hence you can just pass the value 0 for the PID.
After preparing the TxData buffer, we will send it via the UART. The function HAL_LIN_SendBreak is used to send the break field. After sending the break field, we will send the TxData buffer.
The SLAVE
I am going to use the STM32F103C8T6 as the slave MCU. Below is the configuration of the slave MCU.
CubeMX Configuration
Below is the image showing the cubeMX configuration of the slave MCU.
I am using the USART3 on the STM32F103C8T6. The USART is configured in the Lin Mode with the baud rate of 9600 bps, 8 data bits with 1 stop bit and no parity. This is basically the same configuration as we did in the master MCU.
I have also enabled the Global interrupt for the USART3 as I am going to receive the data in the interrupt mode.
Also make sure to enable the pull up for the RX pin in the GPIO setting. This is necessary or else the data will not be received. If your MCu does not support the internal pullup, connect an external pull up on this pin.
We also need to set a GPIO pin as the output. This will be used as the CS pin for the transceiver.
Here I am using the pin PB1 as the CS pin for the Transceiver IC.
The Code
We will start with receiving the data via the UART in the interrupt mode.
HAL_UARTEx_ReceiveToIdle_IT(&huart3, RxData, 20);
Here I am using the function receive to idle in the interrupt mode. So when all the 20 bytes has been received, or an idle line is detected before that, an interrupt will trigger and the RX Event Callback will be called.
We will process the received data inside this callback. Below is the image showing the data received by the slave in a single Lin frame.
The received bytes contains the following:
- 1 byte of the break field (0x00).
- 1 byte of the Sync field (0x55).
- 1 byte of the protected ID.
- 1 – 8 bytes of actual data.
- 1 byte of the checksum.
Basically other than the actual data bytes, we have 4 extra bytes containing other information. Since the master can send any number of actual data bytes between 1 to 8 bytes, we must have some means in the slave device to figure out how many actual data bytes were sent by the master.
void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size)
{
numDataBytes = Size - 4;
The variable numDataBytes will be used to track the actual number of data bytes received. The Size parameter of the callback contains the total bytes received and we have 4 additional bytes other than the data bytes. To extract the actual number of data bytes, we can simply use numDataBytes = Size – 4.
uint8_t checksum = checksum_Calc(RxData[2], RxData+3, numDataBytes);
if (checksum != RxData[Size-1])
{
isDataValid = 0;
// call error handler
}
else isDataValid = 1;
We can check the validity of the received data by calculating the checksum. The actual data starts from the offset of 3, and the total number of data bytes has already been calculated.
We will verify this checksum with the received checksum. If it is same, that means the received data is valid and we can process it. Here we will set the variable isDataValid to 1 so that we can later process the data in the while loop.
ID = RxData[2]&0x3F;
We can also extract the actual ID from the protected ID. We know that the protected ID is made up of 6 bits of the actual ID and the 2 parity bits (P0 & P1). So to extract the actual ID from the PID, we can simply extract the first 6 bits from it.
HAL_UARTEx_ReceiveToIdle_IT(&huart3, RxData, 20);
}
Finally we will call the receive to idle function again, so that we have the continuous reception of data.
We will process the data in the while loop.
while (1)
{
if (isDataValid == 1)
{
for (int i=0; i<numDataBytes; i++)
{
Data[i] = RxData[i+3];
}
isDataValid = 0;
}
}
Here we will check if the isDataValid variable is set to 1. If it is, then we will copy the received data from the RX buffer to the Data buffer. We can process this data buffer in any way we want.
Also set the isDataValid variable to 0, so that this loop does not run again.
Connection
Below is the image showing the connection between the MCUs and their respective transceivers.
The UART TX pins are connected to the transceiver TX pins and the UART RX pins are connected to the transceiver RX pins. The pin 2 of the transceiver is the CS pin and it is connected to the respective GPIO on the MCU.
The Lin bus of the transceivers are connected together as it will transmit and receive the signal.
The transceivers are powered using 12V from the battery.
Result
Below the images shows the data received by the slave MCU and the actual data extracted from the received data.
The slave received a total of 12 bytes of data. The received data contains the following:
- The break field 0x00.
- The sync field 0x55.
- The PID 0xB4.
- 8 Data bytes.
- The checksum byte.
The Data buffer contains the 8 data bytes extracted from the RX buffer.