STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)

         关于LIN,这里就不多介绍,但写代码前还是得知道我们应该发什么,接收什么,这一部分可以看看我对LIN总线的介绍博文,博文中还加入了我的一点理解。

        知道了我们应该发什么接收什么,那么下面内容就讲解我们如何进行收发,如何编写代码。

        主要代码分为3个文件,usart文件是对GPIO端口和串口Lin模式的初始化,lin_buffer文件是对串口环形缓冲区的编写,lin_driver文件是Lin从机模式驱动代码。通过这三个文件的代码便可实现STM32F103的LIN从机通讯。


目录

一.  全部完整 .H 文件代码

1. usart.h 文件代码 

2. lin_buffer.h 文件代码 

3.lin_driver.h 文件代码 

二.  全部完整 .C 文件代码

1. usart.c 文件代码 

2. lin_buffer.c 文件代码 

3.lin_driver.c 文件代码  

 三.  usart.c 初始化代码讲解

1. 串口的LIN模式初始化

2. 串口的LIN模式代码编写

四.  lin_buffer.c 环形缓冲区代码讲解

1. 环形缓冲区简介

2. 为什么用环形缓冲区

3. 环形缓冲区代码编写

(1)写缓冲区

(2)读缓冲区

五.  lin_driver.c 从机驱动代码讲解

1.  LIN同步间隔段发送函数   

2. LIN发送多字节函数   

3. 校验和获取函数   

4. PID获取函数   

5. LIN从机中断函数

6. LIN从机接收处理函数

六. main.c主函数代码

七. 注意


还是先给出全部完整代码,再对代码进行讲解。

一.  全部完整 .H 文件代码

1. usart.h 文件代码 

#ifndef __USART_H
#define __USART_H
 
#include "main.h" 
 
/* USART1 引脚定义 */
#define   USART1_TX_PIN   				 GPIO_Pin_9  		
#define   USART1_RX_PIN  		   		 GPIO_Pin_10
 
 
typedef struct __FILE FILE;
uint32_t USART1_Init(uint32_t baud);
 
 
#endif

 2. lin_buffer.h 文件代码 

#ifndef __LINBUFFER_H
#define __LINBUFFER_H
#include "stm32f10x.h"                 
#include "lin_driver.h"
 
#define  LIN_RINGBUF_LEN  64             //缓冲区长度
 
typedef struct{
	uint16_t Head;						//队列头
	uint16_t Tail;						//队列尾
	uint16_t Length; 					//保存的数据长度
	uint16_t BufOutcnt;                	//溢出计数
	LIN_MSG  LINBuffer[LIN_RINGBUF_LEN]; 	//缓冲区	
}LIN_BUFFER;
 
 
uint16_t LIN_RingBUF_ReadLen(LIN_BUFFER* pLINBuff);
void LIN_RingBUF_Init(LIN_BUFFER* pLINBuff);
void LIN_RingBUF_Write(LIN_BUFFER* pLINBuff);
void LIN_RingBUF_ClearRxMsg(LIN_BUFFER* pLINBuf);
void LIN_Rx_data(uint8_t PID, uint8_t* pData,uint8_t DataLen);
LIN_MSG* LIN_RingBUF_GetMsg(LIN_BUFFER* pLINBuff);
int LIN_RingBUF_Read(LIN_BUFFER* pLINBuff, LIN_MSG** pLINMsg);
 
#endif  
 

3.lin_driver.h 文件代码 

#ifndef __LINDRIVER_H
#define __LINDRIVER_H
#include "stm32f10x.h"          
 
#define  LIN_MASTER_IRQHandler 		 	 USART1_IRQHandler   //串口1中断定义
#define  LIN_BUFFER_SIZE             64
 
 
//LIN接收状态
typedef enum{ 
	BREAK_GET = 0,
	SYNCH_GET,
	PID_GET,
	MSG_GET,
	CHECKSUM_GET, 
}LIN_RxState; 
 
 
//LIN错误代码
typedef enum{ 
	LIN_OK = 0, 
	FIFO_VOID,        //无数据
	SYNC_ERR,         //同步段错误
	PID_ERR,          //PID错误	
	NO_RESPONES,      //无响应	
	CHECK_ERR,        //数据长度错误	
	FORMAT_ERR        //校验和错误
}LIN_ERROR_Code;
 
 
//LIN消息结构
typedef struct{
  uint8_t Sync;         //同步段:固定值0x55
	uint8_t FrameID;      //帧ID
  uint8_t PID;          //PID
  uint8_t DataLen;      //LIN数据段有效字节数
  uint8_t Data[8];      //数据段(LIN规定数据长最多8字节)
  uint8_t Checksum;     //校验和
}LIN_MSG;
 
 
void LIN_MasterRxMsg(uint8_t Data);
uint8_t LIN_GetPID(uint8_t FrameID);
void LIN_SendBreak(USART_TypeDef* USARTx);
void LIN_TimOutCmd(TIM_TypeDef* TIMx, FunctionalState NewState);
void LIN_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint8_t DataLen);
uint8_t LIN_GetChecksum(uint8_t PID, uint8_t* pData,uint8_t DataLen,uint8_t flag);
 
 
#endif

        .h文件没什么要讲的,自己看看就好,主要是结构体的定义和接收状态的枚举什么的。接下来是主要+重点的.c文件。

二.  全部完整 .C 文件代码

1. usart.c 文件代码 

#include "main.h"
 
 
/**
  * @brief  串口1初始化
  * @param  baud:波特率
  * @retval None
  */
uint32_t USART1_Init(uint32_t baud)   
{
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1,ENABLE);  //开启串口1与GPIOA时钟
 
	GPIO_InitTypeDef GPIO_InitStruct;	
	GPIO_InitStruct.GPIO_Pin = USART1_TX_PIN;   	   //串口发送引脚TX  GPIO_Pin_9
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; 	   //复用推挽输出
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;     
	GPIO_Init(GPIOA,&GPIO_InitStruct);
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPU;          //上拉输入
	GPIO_InitStruct.GPIO_Pin = USART1_RX_PIN;		   //串口接收引脚RX GPIO_Pin_10
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;	   //可没有	
	GPIO_Init(GPIOA,&GPIO_InitStruct);	
	
	
	USART_InitTypeDef USART_InitStruct;
	
	USART_InitStruct.USART_BaudRate = baud;   								 	 //串口波特率
	USART_InitStruct.USART_Mode = USART_Mode_Tx | USART_Mode_Rx;				 //收发模式
	USART_InitStruct.USART_Parity = USART_Parity_No;    						 //不使用奇偶校验
	USART_InitStruct.USART_StopBits = USART_StopBits_1;                          //停止位:1位
	USART_InitStruct.USART_WordLength = USART_WordLength_8b;                     //8位数据长度
	USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; //不使用硬件控制流		
	USART_Init(USART1,&USART_InitStruct); 									     //初始化串口配置
	USART_LINBreakDetectLengthConfig(USART1,USART_LINBreakDetectLength_11b);	 //设置USART1_LIN断开符检测长度:11位
	
 
	USART_Cmd(USART1,ENABLE);	  //使能串口时钟
	USART_LINCmd(USART1,ENABLE);  //使能串口LIN模式
	
	NVIC_InitTypeDef  NVIC_InitStruct;        		//定义NVIC结构体	
	NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;
	NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;//配置抢占优先级为最高优先级
	NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;//配置响应优先级为最高优先级	
	NVIC_Init(&NVIC_InitStruct);
 
	USART_ITConfig(USART1,USART_IT_RXNE,ENABLE);		//使能串口接收中断
	USART_ITConfig(USART1, USART_IT_LBD,ENABLE);        //打开LIN断开符检测中断	
	//USART_ITConfig(USART1, USART_IT_IDLE,ENABLE);       //开启空闲中断
	//LIN_BUF_Init(&LINRxBuffer,(LIN_MSG*)LINRxDataBuf,LIN_BUFFER_SIZE);
	return baud; //返回波特率
}
 

2. lin_buffer.c 文件代码 

#include "Lin_buffer.h"
#include 
 
 
//缓冲区初始化
void LIN_RingBUF_Init(LIN_BUFFER* pLINBuff)
{
	pLINBuff->Head = 0;
	pLINBuff->Tail = 0;
	pLINBuff->Length = 0;
	pLINBuff->BufOutcnt = 0;
}
 
 
//获取LIN消息
LIN_MSG* LIN_RingBUF_GetMsg(LIN_BUFFER* pLINBuff)
{
	return  &pLINBuff->LINBuffer[pLINBuff->Tail];    //取出缓冲区内的数据
}
 
 
//写缓冲区
void LIN_RingBUF_Write(LIN_BUFFER* pLINBuff)
{
	pLINBuff->Tail = (pLINBuff->Tail + 1) % LIN_RINGBUF_LEN; //防止越界非法访问
	if (pLINBuff->Length < LIN_RINGBUF_LEN) {                //判断缓冲区是否已满
		pLINBuff->Length++;
	}else{
		pLINBuff->BufOutcnt++;
		if (pLINBuff->BufOutcnt == LIN_RINGBUF_LEN) {
			pLINBuff->BufOutcnt = 0;			
		}
		pLINBuff->Head = pLINBuff->BufOutcnt;
	}
}
 
//清空当前缓冲区
void LIN_RingBUF_ClearRxMsg(LIN_BUFFER* pLINBuf)
{
	LIN_MSG* pMsg = LIN_RingBUF_GetMsg(pLINBuf);
	pMsg->Sync = 0;
	pMsg->PID = 0;
	pMsg->DataLen = 0;
	pMsg->Checksum = 0;
}
 
 
//获取缓冲区消息长度
uint16_t LIN_RingBUF_ReadLen(LIN_BUFFER* pLINBuff)
{
	return pLINBuff->Length <= 0 ? 0 : pLINBuff->Length;
}
 
 
//读缓冲区
int LIN_RingBUF_Read(LIN_BUFFER* pLINBuff, LIN_MSG** pLINMsg)
{
	 __set_PRIMASK(1);
	if (pLINBuff->Length <= 0) {  //判断缓冲区是否为空	
		pLINBuff->BufOutcnt = 0;
		 __set_PRIMASK(0);
		return 1;
	}else {
		*pLINMsg = &pLINBuff->LINBuffer[pLINBuff->Head];   	      //先进先出FIFO,从缓冲区头出
		pLINBuff->Head = (pLINBuff->Head + 1) % LIN_RINGBUF_LEN;  //防止越界非法访问
		pLINBuff->Length--;
		 __set_PRIMASK(0);
		return 0;
	}
}
 
 
 

3.lin_driver.c 文件代码  

#include "main.h"  
#include "stdio.h"
 
 
LIN_RxState       LIN_RxStateGet = BREAK_GET; 
LIN_ERROR_Code    ErrCode = FIFO_VOID;
u8                data[8] = {0x11,0x01,0x34,0x34,0x56,0x34,0x78,0x67};
 
 
//LIN接收缓冲区
LIN_BUFFER  LIN_RxDataBuff = {
	.Head = 0,
	.Tail = 0,
	.Length = 0,
	.BufOutcnt = 0	
};
 
 
/**
  * @brief  LIN从机中断函数
  * @param  None
  * @retval None
	*/
void LIN_MASTER_IRQHandler(void)
{
	uint8_t ReceiveData = 0;
	
	//LIN断开帧中断
	if ((USART_GetITStatus(USART1,USART_IT_LBD) == SET)){		//LIN断路检测中断  1 -- 有同步间隔段	
		LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);              //清空当前缓冲区
		USART1->SR;     //读取USART1->SR会导致接收缓冲区中的数据被移除或清空
    USART1->DR;	
  	LIN_RxStateGet = SYNCH_GET;
		USART_ClearITPendingBit(USART1,USART_IT_LBD);         //清除LIN断路检测中断
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
		return;		
	}
	
	if (USART_GetITStatus(USART1,USART_IT_RXNE) == SET){    			  //LIN接收中断			
		ReceiveData = USART_ReceiveData(USART1);	                  	//返回USARTx外设最近接收到的数据。
		if (USART_GetFlagStatus(USART1,USART_FLAG_FE) == RESET){ 		  //帧错误标志  0 -- 没有检测到帧错误	
			if ((ReceiveData==0x55)&&(LIN_RxStateGet==BREAK_GET)){ 		  //处理无同步间隔信号的LIN数据                                    //初始化定时器值 类似喂狗
	    	LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);            	 		//清空当前缓冲区
				USART1->SR;     
				USART1->DR;
				LIN_RxStateGet = SYNCH_GET;
				return;   
			}
				LIN_MasterRxMsg(ReceiveData);   //消息处理
		}
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);	
	}
}
 
 
/**
  * @brief  LIN从机接收处理函数
	* @param  Data: 串口消息
  * @retval None
  */
void LIN_MasterRxMsg(uint8_t Data)
{
	LIN_MSG* pLINMsg = LIN_RingBUF_GetMsg(&LIN_RxDataBuff);
	
	switch (LIN_RxStateGet){
		case BREAK_GET:      //同步间隔段
		{
		}
		break;
		
		case SYNCH_GET:      //同步段
		{
			if(Data != 0x55){  //判断是不是同步段
				ErrCode = SYNC_ERR;
				LIN_RxStateGet = BREAK_GET;	
			}else{
				pLINMsg->Sync = Data;
				LIN_RxStateGet = PID_GET;	
			}	
		}
		break;	
		
		case PID_GET:        //PID段
		{
			pLINMsg->FrameID = Data&0x3F;
			pLINMsg->PID = Data;
			uint8_t PID = LIN_GetPID(pLINMsg->FrameID);  //根据ID获取奇偶校验位 得到校验的PID
			
			if(PID == pLINMsg->PID){                     //判断PID是否正确  后续根据LDF定
				//根据判断是执行还是反馈 改变标志位
				if(pLINMsg->FrameID == 0x31){              // 1 -- 执行  即继续接收数据
					LIN_RxStateGet = MSG_GET;	
				}else if(pLINMsg->FrameID == 0x32){        // 2 -- 反馈  即向LIN总线发送消息
					LIN_Rx_data(pLINMsg->PID,data,sizeof(data)/sizeof(data[0]));   //反馈消息
					LIN_RxStateGet = BREAK_GET;
				}else{                                     // 3 -- 其他  即不执行也不反馈                       
					LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff); //清空当前缓冲区
					LIN_RxStateGet = BREAK_GET;
				}
			}else{   //PID校验不正确
				ErrCode = PID_ERR;
				LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);   //清空当前缓冲区
				LIN_RxStateGet = BREAK_GET;				
			}												
		}
		break;	
		
		case MSG_GET:       //数据段
		{
			pLINMsg->Data[pLINMsg->DataLen++] = Data;
			pLINMsg->Checksum = Data;
			LIN_RxStateGet = (pLINMsg->DataLen>=8)?CHECKSUM_GET:MSG_GET;	
		}
		break;	
		
		case CHECKSUM_GET:  //校验和段
		{
			pLINMsg->Checksum = Data;
			uint8_t Checksum = LIN_GetChecksum(pLINMsg->PID,pLINMsg->Data,pLINMsg->DataLen,1);     //获取校验和段
			if((Checksum+pLINMsg->Checksum) == 0xFF){            //判断校验和是否正确
				LIN_RingBUF_Write(&LIN_RxDataBuff);
			}else{
				ErrCode = FORMAT_ERR;
				LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);           //清空当前缓冲区
			} 
			LIN_RxStateGet = BREAK_GET;		
		}
		break;
		
		default:       			//其他
			LIN_RxStateGet = BREAK_GET;	
		break;
	}
}
 
 
/**
  * @brief  反馈消息给总线	
  * @param  PID:校验ID,pData:数据指针,Length:数据长度
  * @retval 无
  */
void LIN_Rx_data(uint8_t PID, uint8_t* pData,uint8_t DataLen)
{
	uint8_t Linbuffer[DataLen+1]; 	                             //定义发送数组(数据+校验和)
	uint8_t Checksum = LIN_GetChecksum(PID,pData,DataLen,0);     //获取校验和段
	for (uint8_t i = 0; i < DataLen; i++)                        //存DataLen个字节数据段
	{     
		Linbuffer[i] = *(pData + i);		
	}
	Linbuffer[DataLen] = Checksum;                   //校验和
	LIN_SendBytes(USART1, Linbuffer ,DataLen+1);     //发送从机数据
}
 
 
/**
  * @brief  LIN同步间隔段发送		
  * @param  USARTx:串口号
  * @retval 无
  */
void LIN_SendBreak(USART_TypeDef* USARTx)
{
	USART_SendBreak(USARTx);
}
 
 
/**
  * @brief  LIN发送字节	
  * @param  USARTx:串口号,pData:数据指针,Length:数据长度
  * @retval 无
  */
void LIN_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint8_t DataLen)
{
	for (uint8_t i = 0; i < DataLen; i++){
		USART_SendData(USARTx,*pData++);
		while (USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);	//传输数据寄存器空标志
	}
	while (USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);		//传输完成标志
}
 
 
/**
  * @brief  LIN协议规定校验和长度为1个字节,获取校验和	
	* @param  PID:校验ID,pData:数据指针,DataLen:数据长度,flag :发送0 接收1 
  * @retval 累加校验和
  */
uint8_t LIN_GetChecksum(uint8_t PID, uint8_t* pData,uint8_t DataLen,uint8_t flag) 			 
{  
	  uint16_t CheckSum = 0;  
    //FrameID为3C 3D的PID为3C 7D
	  if((PID!=0x3C)&&(PID!=0x7D)){    //诊断帧只能使用标准校验和,标准校验和不包含PID 只校验数据段              	  
			CheckSum = PID;     		
	  }
	  for(uint8_t i = 0; i < DataLen; i++){
			CheckSum += pData[i];		  
			if (CheckSum > 0xFF){
				CheckSum -= 0xFF;  
			}
	  }
		
		if(flag == 0){
			return (~CheckSum) & 0xFF;  //发送方需要取反
		}else{
			return CheckSum & 0xFF; 		//接收方不需要
		}
}
 
 
/**
  * @brief LIN_PID校验函数		
  * @param ID(FrameID):帧ID(0 ~ 63) 
  * P0(bit6) =   ID0 ^ ID1 ^ ID2 ^ ID4   <==>  (偶校验:操作数中1的个数为偶数,校验位为0,1的个数为奇数校验位为1)
  * P1(bit7) = ~(ID1 ^ ID3 ^ ID4 ^ ID5)  <==>  (奇校验:操作数中1的个数为奇数,校验位为0,1的个数为偶数校验位为1)
  * @retval 返回PID
  */
uint8_t LIN_GetPID(uint8_t ID)  
{
	uint8_t PID = 0,P0 = 0,P1 = 0;	
	P0 = (((ID>>0)^(ID>>1)^(ID>>2)^(ID>>4))&0x01)<<6; //偶校验位           			
	P1 = ((~((ID>>1)^(ID>>3)^(ID>>4)^(ID>>5)))&0x01)<<7; //奇校验位	
	PID = (ID|P0|P1);	
	return PID;   
}

 三.  usart.c 初始化代码讲解

1. 串口的LIN模式初始化

LIN模式是通过设置USART_CR2寄存器的LINEN位选择。在LIN模式下,下列位必须保持为0:

        ● USART_CR2寄存器的CLKEN位

        ● USART_CR3寄存器的STOP[1:0], SCEN, HDSEL和IREN

 --  上面是STM32中文资料里LIN模式的初始化寄存器版本

 --  LINEN位在USART_CR2寄存器里

 --  STOP[1:0]其实是在USART_CR2寄存器里的,中文资料有误

2. 串口的LIN模式代码编写

 先看寄存器内容:

USART_CR2:(LINEN位置  CLKEN位置0   STOP位置00

STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)_第1张图片

STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)_第2张图片

STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)_第3张图片

USART_CR3:(SCEN位置0   HDSEL位置0   IREN位置0

STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)_第4张图片

         其实寄存器版本代码要编写的只有LINEN位置1,因为CR2 CR3寄存器的复位值是0x0000,也就是其中位的值默认是0,如果想要开启对应中断,再置1对应中断位即可。

         库函数版本代码也挺简单,在常规的串口初始化中加入:

//设置USART1_LIN断开符检测长度:11位USART_LINBreakDetectLengthConfig(USART1,USART_LINBreakDetectLength_11b);  

//使能串口LIN模式
USART_LINCmd(USART1,ENABLE); 

开启对应中断:

 USART_ITConfig(USART1, USART_IT_LBD,ENABLE);        //打开LIN断开符检测中断  

四.  lin_buffer.c 环形缓冲区代码讲解

1. 环形缓冲区简介

        环形缓冲区就是一个带“头指针”和“尾指针”的数组。“头指针”指向环形缓冲区中可读的数据,“尾指针”指向环形缓冲区中可写的缓冲空间。通过移动“头指针”和“尾指针”就可以实现缓冲区的数据读取和写入。在通常情况下,应用程序读取环形缓冲区的数据仅仅会影响“头指针”,而串口接收数据仅仅会影响“尾指针”。当串口接收到新的数组,则将数组写入环形缓冲区中,同时将“尾指针”加1,以保存下一个数据;应用程序在读取数据时,“头指针”加1,以读取下一个数据。当“尾指针”超过数组大小,则“尾指针”重新指向数组的首元素,从而形成“环形缓冲区”!,有效数据区域在“头指针”和“尾指针”之间。

2. 为什么用环形缓冲区

        最简单的串口数据处理机制是数据接收并原样回发的机制是:成功接收到一个数,触发进入中断,在中断函数中将数据读取出来,然后立即。这一种数据处理机制是“非缓冲中断方式”,虽然这种数据处理方式不消耗时间,但是这种数据处理方式严重的缺点是:数据无缓冲区,如果先前接收的的数据如果尚未发送完成(处理完成),然后串口又接收到新的数据,新接收的数据就会把尚未处理的数据覆盖,从而导致“数据丢包”。
        对于“数据丢包”,最简单的办法就是使用一个数组来接收数据:每接收一个数据,数组下标偏移。虽然这样的做法能起到一定的“缓冲效果”,但是数组的空间得不到很好的利用,已处理的数据仍然会占据原有的数据空间,直到该数组“满载”(数组的每一个元素都保存了有效的数据),将整个数组的数据处理完成后,重新清零数组,才能开启新一轮的数据接收。

        “环形缓冲区”数据接收处理机制的好处在于:利用了队列的特点,一头进,一头出,互不影响,在数据进去(往里存)的时候,另一边也可以把数据读出来,而读出来的数据,留下的空位,又可以增加多的存储空间,从而避免一边接收数据且一边处理数据会在数据量密集的时候而导致的丢掉数据或者数据产生冲突的问题。

环形缓冲区介绍原文链接:https://blog.csdn.net/weixin_41226265/article/details/106685103

3. 环形缓冲区代码编写

        环形缓冲区最主要的写和读。

(1)写缓冲区

        1. 将“尾指针”加1,加上防止越界非法访问处理。

        2. 判断缓冲区是否已满,即判断缓冲区长度。

        3. 缓冲区未满则长度+1,完成。 --  尾指针已加1。

        4. 缓冲区已满则溢出计数+1,把“头指针”指向溢出计数值

//写缓冲区
void LIN_RingBUF_Write(LIN_BUFFER* pLINBuff)
{
	pLINBuff->Tail = (pLINBuff->Tail + 1) % LIN_RINGBUF_LEN; //防止越界非法访问
	if (pLINBuff->Length < LIN_RINGBUF_LEN) {                //判断缓冲区是否已满
		pLINBuff->Length++;
	}else{
		pLINBuff->BufOutcnt++;
		if (pLINBuff->BufOutcnt == LIN_RINGBUF_LEN) {
			pLINBuff->BufOutcnt = 0;			
		}
		pLINBuff->Head = pLINBuff->BufOutcnt;
	}
}

      防止越界非法访问处理是为了避免出现“尾指针”加1后,缓冲区长度超过LIN_RINGBUF_LEN。

      把“头指针”指向溢出计数值是为了处理当读速度小于写速度的情况,移动“头指针”相当舍弃部分未读的数据,腾出空间接收新数据。

       没有将数组写入环形缓冲区的部分是因为通过获取LIN消息函数,在串口接收处理函数中已经写入到缓冲区中。

//获取LIN消息
LIN_MSG* LIN_RingBUF_GetMsg(LIN_BUFFER* pLINBuff)
{
	return  &pLINBuff->LINBuffer[pLINBuff->Tail];    //取出缓冲区内LIN的数据
}

(2)读缓冲区

        1. 判断缓冲区是否为空,即判断缓冲区长度。

        3. 缓冲区为空则溢出计数也清0,函数退出。

        4. 缓冲区不为空根据先进先出FIFO,从缓冲区头读出数据

        5. 将“头指针”加1,加上防止越界非法访问处理。

        6.缓冲区长度减1。

      ( __set_PRIMASK(1) 起关闭全局中断作用    参数0为开启)

//读缓冲区
int LIN_RingBUF_Read(LIN_BUFFER* pLINBuff, LIN_MSG** pLINMsg)
{
	 __set_PRIMASK(1);
	if (pLINBuff->Length <= 0) {  //判断缓冲区是否为空	
		pLINBuff->BufOutcnt = 0;
		 __set_PRIMASK(0);
		return 1;
	}else {
		*pLINMsg = &pLINBuff->LINBuffer[pLINBuff->Head];   	      //先进先出FIFO,从缓冲区头出
		pLINBuff->Head = (pLINBuff->Head + 1) % LIN_RINGBUF_LEN;  //防止越界非法访问
		pLINBuff->Length--;
		 __set_PRIMASK(0);
		return 0;
	}
}

五.  lin_driver.c 从机驱动代码讲解

1.  LIN同步间隔段发送函数   

        直接调用库函数即可。

/**
  * @brief  LIN同步间隔段发送		
  * @param  USARTx:串口号
  * @retval 无
  */
void LIN_SendBreak(USART_TypeDef* USARTx)
{
	USART_SendBreak(USARTx);
}

2. LIN发送多字节函数   

        调用串口发送数据库函数,同时判断标志位以确保发送正确。

/**
  * @brief  LIN发送多字节	
  * @param  USARTx:串口号,pData:数据指针,Length:数据长度
  * @retval 无
  */
void LIN_SendBytes(USART_TypeDef* USARTx,uint8_t* pData,uint8_t DataLen)
{
	for (uint8_t i = 0; i < DataLen; i++){
		USART_SendData(USARTx,*pData++);
		while (USART_GetFlagStatus(USARTx,USART_FLAG_TXE) == RESET);	//传输数据寄存器空标志
	}
	while (USART_GetFlagStatus(USARTx,USART_FLAG_TC) == RESET);		//传输完成标志
}

3. 校验和获取函数   

        判断PID是否是诊断帧,不是诊断帧则采用增强型校验和,加上PID进行校验。

        判断是接收方还是发送方,发送方校验和结果需要取反接收方不需要取反。

/**
  * @brief  LIN协议规定校验和长度为1个字节,获取校验和	
	* @param  PID:校验ID,pData:数据指针,DataLen:数据长度,flag :发送0 接收1 
  * @retval 累加校验和
  */
uint8_t LIN_GetChecksum(uint8_t PID, uint8_t* pData,uint8_t DataLen,uint8_t flag) 			 
{  
	  uint16_t CheckSum = 0;  
    //FrameID为3C 3D的PID为3C 7D
	  if((PID!=0x3C)&&(PID!=0x7D)){    //诊断帧只能使用标准校验和,标准校验和不包含PID 只校验数据段              	  
			CheckSum = PID;     		
	  }
	  for(uint8_t i = 0; i < DataLen; i++){
			CheckSum += pData[i];		  
			if (CheckSum > 0xFF){
				CheckSum -= 0xFF;  
			}
	  }
		
		if(flag == 0){
			return (~CheckSum) & 0xFF;  //发送方需要取反
		}else{
			return CheckSum & 0xFF; 		//接收方不需要
		}
}

4. PID获取函数   

        根据Frame ID计算出PID。

/**
  * @brief LIN_PID校验函数		
  * @param ID(FrameID):帧ID(0 ~ 63) 
  * P0(bit6) =   ID0 ^ ID1 ^ ID2 ^ ID4   <==>  (偶校验:操作数中1的个数为偶数,校验位为0,1的个数为奇数校验位为1)
  * P1(bit7) = ~(ID1 ^ ID3 ^ ID4 ^ ID5)  <==>  (奇校验:操作数中1的个数为奇数,校验位为0,1的个数为偶数校验位为1)
  * @retval 返回PID
  */
uint8_t LIN_GetPID(uint8_t ID)  
{
	uint8_t PID = 0,P0 = 0,P1 = 0;	
	P0 = (((ID>>0)^(ID>>1)^(ID>>2)^(ID>>4))&0x01)<<6; //偶校验位           			
	P1 = ((~((ID>>1)^(ID>>3)^(ID>>4)^(ID>>5)))&0x01)<<7; //奇校验位	
	PID = (ID|P0|P1);	
	return PID;   
}

5. LIN从机中断函数

      从机中断函数就是串口中断函数,前面我们开启了串口接收中断LIN断开符检测中断

      (即进入串口中断函数的途径有俩,串口接收到数据和检测到LIN断开符)

(1) 当断开长度达到一定值时,LBD位置1,同时产生中断,看下图256

(2) 在串口中断函数中,通过USART_GetITStatus(USART1,USART_IT_LBD)函数判断LBD位。

(3)如果检测到LIN断路,就清空当前缓冲区(清理旧数据),并清除标志位。

STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)_第5张图片

/**
  * @brief  LIN从机中断函数
  * @param  None
  * @retval None
	*/
void LIN_MASTER_IRQHandler(void)
{
	uint8_t ReceiveData = 0;
	
	//LIN断开帧中断
	if ((USART_GetITStatus(USART1,USART_IT_LBD) == SET)){		//LIN断路检测中断  1 -- 有同步间隔段	
		LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);              //清空当前缓冲区
		USART1->SR;     //读取USART1->SR会导致接收缓冲区中的数据被移除或清空
    USART1->DR;	
  	LIN_RxStateGet = SYNCH_GET;
		USART_ClearITPendingBit(USART1,USART_IT_LBD);         //清除LIN断路检测中断
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);
		return;		
	}
	
	if (USART_GetITStatus(USART1,USART_IT_RXNE) == SET){    			  //LIN接收中断			
		ReceiveData = USART_ReceiveData(USART1);	                  	//返回USARTx外设最近接收到的数据。
		if (USART_GetFlagStatus(USART1,USART_FLAG_FE) == RESET){ 		  //帧错误标志  0 -- 没有检测到帧错误	
			if ((ReceiveData==0x55)&&(LIN_RxStateGet==BREAK_GET)){ 		  //处理无同步间隔信号的LIN数据                                    //初始化定时器值 类似喂狗
	    	LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);            	 		//清空当前缓冲区
				USART1->SR;     
				USART1->DR;
				LIN_RxStateGet = SYNCH_GET;
				return;   
			}
				LIN_MasterRxMsg(ReceiveData);   //消息处理
		}
		USART_ClearITPendingBit(USART1,USART_IT_RXNE);	
	}
}

再看下图257,当LBD位置1时,RXNE和FE位也置1了。

(1)即检测到断开段时,读数据寄存器非空位RXNE = 1(收到数据),检测到帧错误位FE = 1。

(2)断开段也算数据,不过由于断开段是超过10位的0,不符合串口的帧标准,所以出现了帧错误

(3)判断串口接收的是断开段还是正常数据时,根据:不产生帧错误的接收(RXNE)中断,那一定不是断开段,而是正常数据。

STM32F103 -- LIN从机通讯 -- 程序代码详细讲解(2万字长文)_第6张图片

6. LIN从机接收处理函数

(1)各部分都要进行校验,以确保传输的帧正确。

(2)从机处理有三种情况,根据接收到的PID而定:

          1 -- 执行  即继续接收数据  

          2 -- 反馈  即向LIN总线发送消息

          3 -- 其他  即不执行也不反馈  

(3)通过该函数将缓冲区内的LIN消息结构体地址赋值给变量:

LIN_MSG* pLINMsg = LIN_RingBUF_GetMsg(&LIN_RxDataBuff);

  当校验全都成功时,再通过该函数写入缓冲区:

LIN_RingBUF_Write(&LIN_RxDataBuff);
/**
  * @brief  LIN从机接收处理函数
	* @param  Data: 串口消息
  * @retval None
  */
void LIN_MasterRxMsg(uint8_t Data)
{
	LIN_MSG* pLINMsg = LIN_RingBUF_GetMsg(&LIN_RxDataBuff);
	
	switch (LIN_RxStateGet){
		case BREAK_GET:      //同步间隔段
		{
		}
		break;
		
		case SYNCH_GET:      //同步段
		{
			if(Data != 0x55){  //判断是不是同步段
				ErrCode = SYNC_ERR;
				LIN_RxStateGet = BREAK_GET;	
			}else{
				pLINMsg->Sync = Data;
				LIN_RxStateGet = PID_GET;	
			}	
		}
		break;	
		
		case PID_GET:        //PID段
		{
			pLINMsg->FrameID = Data&0x3F;
			pLINMsg->PID = Data;
			uint8_t PID = LIN_GetPID(pLINMsg->FrameID);  //根据ID获取奇偶校验位 得到校验的PID
			
			if(PID == pLINMsg->PID){                     //判断PID是否正确  后续根据LDF定
				//根据判断是执行还是反馈 改变标志位
				if(pLINMsg->FrameID == 0x31){              // 1 -- 执行  即继续接收数据
					LIN_RxStateGet = MSG_GET;	
				}else if(pLINMsg->FrameID == 0x32){        // 2 -- 反馈  即向LIN总线发送消息
					LIN_Rx_data(pLINMsg->PID,data,sizeof(data)/sizeof(data[0]));   //反馈消息
					LIN_RxStateGet = BREAK_GET;
				}else{                                     // 3 -- 其他  即不执行也不反馈                       
					LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff); //清空当前缓冲区
					LIN_RxStateGet = BREAK_GET;
				}
			}else{   //PID校验不正确
				ErrCode = PID_ERR;
				LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);   //清空当前缓冲区
				LIN_RxStateGet = BREAK_GET;				
			}												
		}
		break;	
		
		case MSG_GET:       //数据段
		{
			pLINMsg->Data[pLINMsg->DataLen++] = Data;
			pLINMsg->Checksum = Data;
			LIN_RxStateGet = (pLINMsg->DataLen>=8)?CHECKSUM_GET:MSG_GET;	
		}
		break;	
		
		case CHECKSUM_GET:  //校验和段
		{
			pLINMsg->Checksum = Data;
			uint8_t Checksum = LIN_GetChecksum(pLINMsg->PID,pLINMsg->Data,pLINMsg->DataLen,1);     //获取校验和段
			if((Checksum+pLINMsg->Checksum) == 0xFF){            //判断校验和是否正确
				LIN_RingBUF_Write(&LIN_RxDataBuff);
			}else{
				ErrCode = FORMAT_ERR;
				LIN_RingBUF_ClearRxMsg(&LIN_RxDataBuff);           //清空当前缓冲区
			} 
			LIN_RxStateGet = BREAK_GET;		
		}
		break;
		
		default:       			//其他
			LIN_RxStateGet = BREAK_GET;	
		break;
	}
}

六. main.c主函数代码

#include "main.h"

LIN_MSG*  pWiperMsg;    //LIN数据 
extern    LIN_BUFFER  LIN_RxDataBuff;   //缓冲区


int main(void)	
{
	//设置中断优先级分组为2 
	SystemInit();
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	delay_init();
	LED_Init();							//LED初始化	
	USART1_Init(19200);   //串口1初始化:波特率19200
	while (1)
	{	   
		LIN_RingBUF_Read(&LIN_RxDataBuff,&pWiperMsg);  //不断取出缓冲区数据
		LIN_LED_Drive(pWiperMsg);                      //对数据不断解析
	}
} 	


//从机接收驱动LED
void LIN_LED_Drive(LIN_MSG* pLINMsg)
{	
	if (pLINMsg->FrameID == 0x31){
		switch (pLINMsg->Data[0]){		
			case 0x01:
					LEDR_M;	    					
			break;
			case 0x02:	
					LEDR_L;	    					
			break;				
		}		
	}
}

七. 注意

        为了传输的进一步完善,我们还应该在接收的过程中加入超时判断,当超过一定时间没有接收到数据,退出接收数据,进入下一个帧的断开段检测,这样可以防止一些干扰。

STM8S003 -- LIN从机通讯 -- 程序代码讲解,下面是链接!!!

                http://t.csdn.cn/H8S4t

你可能感兴趣的:(STM32,LIN,stm32,汽车,信息与通信,信号处理,嵌入式硬件,单片机)