基于HAL库UART实现LIN通信

首先要了解LIN的协议,LIN是采用问询的方式进行通信的,一主多从,从机根据帧ID进行回应。

问询的帧头也是规定的。帧头包括同步间隔段、同步段以及PID(Protected Identifier,受保护ID)段,应答包括数据段和校验和段,其中值“0”为显性电平(Dominant),值“1”为隐性电平(Recessive)

这些都跟后续写代码有着密切联系,所以要先了解(文章最后附上源码,需要自己整理一下)。

同步间隔段:同步间隔段由同步间隔和同步间隔段间隔符构成,同步间隔是至少持续13位的显性电平,同步间隔段间隔符是至少持续1位的隐性电平。

这个代码实现就比较简单,HAL库有封装好的程序,直接调用就行。

//同步间隔段
void Lin_SendBreak(UART_HandleTypeDef *huart)
{
	if(HAL_LIN_SendBreak(huart)!=HAL_OK)
	{
		printf("Lin_SendBreak Failed\r\n");
	}
}

同步段:数据传输都是先发送LSB,最后发送MSB,这里确保所有从机节点都和主机节点波特率相同发送和接收数据,同步段固定一个字节,值固定为0x55。

代码实现就比较简单了,直接发送应该0X55就行。

unsigned char SyncSegmentData[1] = {0x55};
//同步段
void Lin_SendSyncSegment(UART_HandleTypeDef *huart)
{
    if(HAL_UART_Transmit(huart,SyncSegmentData,1, 1000)!=HAL_OK)
    {
    	printf("Lin1_SendSyncSegment Failed\r\n");
    }
}

 受保护ID段(PID):受保护ID由6位帧ID和两位奇偶校验组成,帧ID的范围在0x00~0x3F之间,共64个,奇偶校验位的校验公式如下:

校验公式如下,其中“⊕”代表“异或”运算, “¬”代表“取非”运算。

P0 = ID0⊕ID1⊕ID2⊕ID4

P1 = ¬(ID1⊕ID3⊕ID4⊕ID5)

代码实现需要对其进行运算。

//PID(protect ID),这里的前六位为ID,后两位为校验位
//函数功能为:输入ID,返回PID。
uint8_t Lin_CheckPID(uint8_t id)
{
	uint8_t returnpid ;
	uint8_t P0 ;
	uint8_t P1 ;

	P0 = (((id)^(id>>1)^(id>>2)^(id>>4))&0x01)<<6 ;
	P1 = ((~((id>>1)^(id>>3)^(id>>4)^(id>>5)))&0x01)<<7 ;

	returnpid = id|P0|P1 ;

	return returnpid ;
}

 数据段:节点发送的数据位于数据段,最高8个字节,先发送编号最低的DATA1,依次增加发送。

代码就是调用HAL库封装好的串口发送函数。

//LIN数据发送
void Lin_SentData(UART_HandleTypeDef *huart,uint8_t data[],uint8_t length)
{
	HAL_UART_Transmit(huart,data,length,1000);
}

 校验和段:就是对帧中所传输的内容进行校验。

这里由两种校验方式分为标准型校验和(Classic Checksum)及增强型校验和(Enhanced Checksum) 。

 

经典型校验算法涵盖的范围只有LIN的数据段,即一帧LIN信号的数据段包含几个字节的数据就计算几个字节,计算的时候是累加计算,因为校验字节只有一个字节,所以checksum的数值一定是小于255(0xFF),用一个公式来表示如下:

byte 0 + ... + byte n + checksum = 0xFF 的整数倍

即 checksum = 0xFF - 数据之和的低八位。

//输入ID+数据,返回校验和段(标准型)
uint8_t Lin_CheckSum_S(uint8_t id , uint8_t data[],uint8_t length)
{
	uint8_t t ;
	uint16_t sum ;

	sum = data[0];

	for(t=1;t<length;t++)
	{
		sum += data[t];
		if(sum&0xff00)
		{
			sum&=0x00ff;
			sum+=1;
		}
	}
	sum = ~sum;
	return (uint8_t)sum ;
}

增强型校验相比于经典型校验最大的区别就是计算的范围变化了,增强型校验需要计算的范围是除了数据段还要加上ID的数值,变化一下计算公式如下:

ID + byte 0 + ... + byte n + checksum = 0xFF 的整数倍

除此之外,在计算上和经典型校验完全一样。


//输入ID+数据,返回校验和段(增强型)
uint8_t Lin_CheckSum_E(uint8_t id , uint8_t data[],uint8_t length)
{
	uint8_t t ;
	uint16_t sum ;

	sum = data[0];
	if(id == 0x3c)
	{

		for(t=1;t<length;t++)
		{
			sum += data[t];
			if(sum&0xff00)
			{
				sum&=0x00ff;
				sum+=1;
			}
		}
		sum = ~sum;

		return (uint8_t)sum;
	}

	for(t=1;t<length;t++)
	{
		sum += data[t];
		if(sum&0xff00)
		{
			sum&=0x00ff;
			sum+=1;
		}
	}
	sum+=Lin_CheckPID(id);
	if(sum&0xff00)
	{
		sum&=0x00ff;
		sum+=1;
	}
	sum = ~sum;
	return (uint8_t)sum ;
}

至此,整个发送过程结束。

因为有帧头的原因,所以接收需要在接收中断做一些处理。

void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	uint8_t ReceiveData;

	if (Run_Mode == LIN_MODE)
	{
		if (huart == &huart3)
		{
			HAL_UART_Receive_IT(&huart3, LIN1RxData, 1);

			// printf("%02x ",LIN1RxData[0]);
			ReceiveData = LIN1RxData[0];

			//帧头判断
			if (DataProcess1 == 0) //同步段间隔段
			{
				if (ReceiveData != 0x00)
					return;
				if (ReceiveData == 0x00)
				{
					DataProcess1 = 1;
					return;
				}
			}
			if (DataProcess1 == 1)//同步段
			{
				if (ReceiveData != 0x55)
					return;
				if (ReceiveData == 0x55)
				{
					DataProcess1 = 2;
					return;
				}
			}

			//帧ID处理
			if (DataProcess1 == 2)    
			{
				ReceivePID1 = ReceiveData;
				ReceiveID1 = ReceivePID1 & 0x3f;

				LIN1Buffer.id = ReceiveID1;

				lin_time_flag1 = 1;

				DataProcess1 = 3;
				DtRProcess1 = 0;

				return;
			}

			//数据段接收
			if (DataProcess1 == 3)
			{
				LIN1Buffer.linDMA_rxBuf[DtRProcess1++] = ReceiveData;
			}
		}
}

程序按照过程式接收,相应的数据正确才往下一步走。

if (lin_time_flag1)
{
	lin_time_flag1 = 0;

	DtRProcess1 = 0;
	DataProcess1 = 0;

}
if (lin1_s_sendFlag == 1)
{
	if(LIN1Buffer.id == 0x01)
	{
		Lin1_SentData(LIN1TxData, 8);
		Lin1_SendCheckSum(0x01, LIN1TxData, 8);
	}

	lin1_s_sendFlag = 0;
}

从机主函数进行标志位清零和数据发送。

最后附上LIN.C和LIN.H;

LIN.C

/*
 * lin.c
 *
 *  Created on: Apr 19, 2023
 *      Author: Administrator
 */

#include "lin.h"
#include "usart.h"
#include "stdio.h"


unsigned char SyncSegmentData[1] = {0x55};
unsigned char CheckPIDData[1] = {0x00};
unsigned char CheckSumData[1] = {0x00};

unsigned char LIN1useFlag = 0,LIN2useFlag = 0;

//同步间隔段
void Lin_SendBreak(UART_HandleTypeDef *huart)
{
	if(HAL_LIN_SendBreak(huart)!=HAL_OK)
	{
		printf("Lin_SendBreak Failed\r\n");
	}
}



//同步段
void Lin_SendSyncSegment(UART_HandleTypeDef *huart)
{
    if(HAL_UART_Transmit(huart,SyncSegmentData,1, 1000)!=HAL_OK)
    {
    	printf("Lin1_SendSyncSegment Failed\r\n");
    }
}



//PID(protect ID),这里的前六位为ID,后两位为校验位
//函数功能为:输入ID,返回PID。
uint8_t Lin_CheckPID(uint8_t id)
{
	uint8_t returnpid ;
	uint8_t P0 ;
	uint8_t P1 ;

	P0 = (((id)^(id>>1)^(id>>2)^(id>>4))&0x01)<<6 ;
	P1 = ((~((id>>1)^(id>>3)^(id>>4)^(id>>5)))&0x01)<<7 ;

	returnpid = id|P0|P1 ;

	return returnpid ;
}

//主机帧头
void Lin_SendHead(UART_HandleTypeDef *huart,uint8_t id)
{
	Lin_SendBreak(huart);
	Lin_SendSyncSegment(huart);
	CheckPIDData[0] = Lin_CheckPID(id);
	if(HAL_UART_Transmit(huart,CheckPIDData,1, 1000)!=HAL_OK)
	{
		printf("Lin1_SendHead Failed\r\n");
	}
}



//输入ID+数据,返回校验和段(标准型)
uint8_t Lin_CheckSum_S(uint8_t id , uint8_t data[],uint8_t length)
{
	uint8_t t ;
	uint16_t sum ;

	sum = data[0];

	for(t=1;t<length;t++)
	{
		sum += data[t];
		if(sum&0xff00)
		{
			sum&=0x00ff;
			sum+=1;
		}
	}
	sum = ~sum;
	return (uint8_t)sum ;
}

//输入ID+数据,返回校验和段(增强型)
uint8_t Lin_CheckSum_E(uint8_t id , uint8_t data[],uint8_t length)
{
	uint8_t t ;
	uint16_t sum ;

	sum = data[0];
	if(id == 0x3c)
	{

		for(t=1;t<length;t++)
		{
			sum += data[t];
			if(sum&0xff00)
			{
				sum&=0x00ff;
				sum+=1;
			}
		}
		sum = ~sum;

		return (uint8_t)sum;
	}

	for(t=1;t<length;t++)
	{
		sum += data[t];
		if(sum&0xff00)
		{
			sum&=0x00ff;
			sum+=1;
		}
	}
	sum+=Lin_CheckPID(id);
	if(sum&0xff00)
	{
		sum&=0x00ff;
		sum+=1;
	}
	sum = ~sum;
	return (uint8_t)sum ;
}

//LIN数据发送
void Lin_SentData(UART_HandleTypeDef *huart,uint8_t data[],uint8_t length)
{
	HAL_UART_Transmit(huart,data,length,1000);
}



//LIN数据校验和
void Lin_SendCheckSum(UART_HandleTypeDef *huart,uint8_t id ,uint8_t data[],uint8_t length)
{
	CheckSumData[0] = Lin_CheckSum_S(id,data,length);
	HAL_UART_Transmit(huart,CheckSumData,1, 1000);
}

void linAskCmd(UART_HandleTypeDef *huart,uint8_t id)
{
	Lin_SendHead(huart,id);

}

LIN.H 

/*
 * lin.h
 *
 *  Created on: Apr 19, 2023
 *      Author: Administrator
 */

#ifndef INC_LIN_H_
#define INC_LIN_H_

#include "stm32g4xx_hal.h"
#include "main.h"

#define	LIN_DATA_LEN  1200//每一帧最多传的字节数,包括CRC,必须为2的整数倍

extern unsigned char SyncSegmentData[1];
extern unsigned char CheckPIDData[1];
extern unsigned char CheckSumData[1];


typedef struct
{
	uint8_t receive_flag:1;//空闲接收完成标志
	uint8_t id;//接收id
	uint8_t rx_len;//接收长度
	uint8_t linDMA_rxBuf[LIN_DATA_LEN];//DMA接收缓存
}LINRXBuffer;

void Lin_SendHead(UART_HandleTypeDef *huart,uint8_t id);

void Lin_SentData(UART_HandleTypeDef *huart,uint8_t data[],uint8_t length);

void Lin_SendCheckSum(UART_HandleTypeDef *huart,uint8_t id ,uint8_t data[],uint8_t length);

 uint8_t Lin_CheckSum_S(uint8_t id , uint8_t data[],uint8_t length);
 uint8_t Lin_CheckSum_E(uint8_t id , uint8_t data[],uint8_t length);


 void linAskCmd(UART_HandleTypeDef *huart,uint8_t id);

#endif /* INC_LIN_H_ */

最后再附上几点建议(自己踩的坑):

  • LIN的波特率最高20k,波特率不要选到115200了,不然全是错误帧,发不出去,推荐9600.
  • 开了看门狗的一定要在中断喂狗,不然很容易被其他中断打断,导致不停重启。
  • 主函数不要加延迟函数,最好利用定时器定时。不然很容易出现主机问了三四帧,从机才回了一帧,因为从机是通过标志位回复主机的,如果有延迟函数的话,主函数就不会一直去判断标志位,就会导致少回了。
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值