这是详细版,链接:STM32F103 – LIN从机通讯 – 程序代码 – 详细讲解版(2万字长文)。精简版少了串口环形缓冲区以及错误判断等内容,足以应对一般的应用场景,代码也更通俗易懂。
但建议先看详细版,详细版主要代码分为3个文件,usart文件是对GPIO端口和串口Lin模式的初始化,lin_buffer文件是对串口环形缓冲区的编写,lin_driver文件是Lin从机模式驱动代码,详细版讲解了我们如何进行收发,如何编写代码。精简版不多讲解,只挑一些讲解,因为原理是一样的。
一.usart文件
usart文件没有更改,不多讲解。
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; //返回波特率
}
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
二. lin_driver 文件代码
lin_driver.c 文件代码
对lin_buffer文件进行了大改,不再使用缓冲区,减少了结构体的使用,加入了超时判断。
接收到的数据格式为:0–同步段 1–ID 2~9–数据段 10–校验和段。
Lin_Rx_Data[10] = 0xAA; //超时标志 校验和段如果是AA,即超时。
其他没什么了,只要你用心分析代码,就一定能看懂。
#include "main.h"
//发送数据
u8 Lin_Tx_Data[8] = {0x01,0x00,0x3C,0x00,0x00,0x00,0x00,0x00};
//接收数据 0--同步段 1--ID 2~9--数据段 10--校验和段
u8 Lin_Rx_Data[11] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
//接收数据 方便校验
u8 Checksum_Data[8] = {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
u8 LIN_RxStateGet = 0;
extern u8 Tim_time;
/**
* @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 -- 有同步间隔段
USART1->SR;
USART1->DR;
LIN_RxStateGet = 1;
Tim_time = 0; //进行超时判断
TIM_Cmd(TIM3,ENABLE);
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 == 0)){ //处理无同步间隔信号的LIN数据
USART1->SR;
USART1->DR;
return;
}
Tim_time = 0;
LIN_MasterRxMsg(ReceiveData); //消息处理
}
USART_ClearITPendingBit(USART1,USART_IT_RXNE);
}
}
/**
* @brief LIN从机接收处理函数
* @param Data: 串口消息
* @retval None
*/
u8 i = 0,j = 0,Checksum = 0;
uint8_t ID,PID;
void LIN_MasterRxMsg(uint8_t ReceiveData){
switch(LIN_RxStateGet){
case 1:
for(i = 0;i < 12 ;i++){
Lin_Rx_Data[i] = 0x00;
}
i = 0;
j = 0;
if(ReceiveData == 0x55){ //判断同步段
Lin_Rx_Data[i] = ReceiveData;
i = 1;
LIN_RxStateGet = 2;
}else{
LIN_RxStateGet = 0;
}
break;
case 2:
ID = ReceiveData&0x3F;
PID = LIN_GetPID(ID);
if(PID == ReceiveData){ //判断PID
if(ID == 0x3A){
LIN_Tx_data(PID,Lin_Tx_Data,8);
if(Lin_Tx_Data[0] == 0x02){
Lin_Tx_Data[0] = 0x01;
}else{
Lin_Tx_Data[0] = 0x02;
}
LIN_RxStateGet = 0;
}else if(ID == 0x39){
LIN_RxStateGet = 3;
}
Lin_Rx_Data[i] = ID;
i = 2;
}
break;
case 3:
Lin_Rx_Data[i] = ReceiveData;
Checksum_Data[j] = ReceiveData;
j++;
i++;
LIN_RxStateGet = (i>=10)?4:3; //判断接收数据长度
break;
case 4:
Lin_Rx_Data[i] = 0x00;
Checksum = 0x00;
Checksum = ~LIN_GetChecksum(PID,Checksum_Data,8);
Tim_time = 0;
if((Checksum + ReceiveData) == 0xFF){
Lin_Rx_Data[i] = 0xFF; //校验成功
}else{
Lin_Rx_Data[i] = 0x00;
}
Tim_time = 0;
TIM_Cmd(TIM3,DISABLE); //定时器失能
LIN_RxStateGet = 0;
break;
}
}
//发送数据,包括校验和
void LIN_Tx_data(uint8_t PID,uint8_t* pData,uint8_t DataLen){
uint8_t Linbuffer[9]; //定义发送数组(数据+校验和)
uint8_t Checksum = LIN_GetChecksum(PID,pData,DataLen); //获取校验和段
for (uint8_t i = 0; i < DataLen; i++) //存DataLen个字节数据段
{
Linbuffer[i] = *(pData + i);
}
Linbuffer[DataLen] = Checksum; //校验和
LIN_SendBytes(USART1,Linbuffer ,DataLen+1); //发送从机数据
}
//定时器3中断服务程序
void TIM3_IRQHandler(void) //TIM3中断
{
if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) //检查TIM3更新中断发生与否
{
TIM_ClearITPendingBit(TIM3, TIM_IT_Update); //清除TIMx更新中断标志
Tim_time++;
if(LIN_RxStateGet == 0){
Tim_time = 0;
TIM_Cmd(TIM3,DISABLE);
}
if(Tim_time >= 5){
LIN_RxStateGet = 0;
Lin_Rx_Data[10] = 0xAA; //超时标志
Tim_time = 0;
TIM_Cmd(TIM3,DISABLE);
}
}
}
/**
* @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:数据长度
* @retval 累加校验和
*/
uint8_t LIN_GetChecksum(uint8_t PID, uint8_t* pData,uint8_t DataLen)
{
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;
}
}
return ~(CheckSum & 0xFF); //发送方
}
/**
* @brief LIN_PID校验函数 STM8单片机可实现奇偶校验位自动检测
* @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;
}
lin_driver.h 文件代码
#ifndef __LINDRIVER_H
#define __LINDRIVER_H
#include "stm32f10x.h"
#define LIN_MASTER_IRQHandler USART1_IRQHandler //串口1中断定义
void LIN_Init(uint32_t bau);
void LIN_MasterRxMsg(uint8_t ReceiveData);
void LIN_Tx_data(uint8_t PID,uint8_t* pData,uint8_t DataLen);
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 LIN_GetPID(uint8_t ID);
#endif
三. tim定时文件代码
tim.c 文件代码
#include "main.h"
/**
* @brief 通用定时器3初始化配置
* @param 自动重装载值,该参数范围0x0000 到 0xFFFF
* @param 预分频器,该参数范围0x0000 到 0xFFFF
* @retval None
*/
void TIM3_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //使能定时器3时钟
TIM_InternalClockConfig(TIM3); //时基单元时钟选择内部时钟(定时器复位后默认为内部时钟)
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//每隔1ms产生一次更新中断
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up; //定时器定时模式:向上计数
TIM_TimeBaseInitStruct.TIM_Period = 10 - 1; //自动重装载值
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1; //预分频器
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0; //重复计数器(只存在于高级定时器中)
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStruct); //初始化定时器
TIM_ClearFlag(TIM3,TIM_FLAG_Update);
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //使能定时器更新中断
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM3_IRQn; //开启定时器中断源
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; //响应优先级
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; //使能定时器中断
NVIC_Init(&NVIC_InitStruct); //初始化定时器
// TIM_Cmd(TIM3,ENABLE); //使能定时器
}
tim.h 文件代码
#ifndef __TIM_H
#define __TIM_H
#include "main.h"
void TIM3_Init(void);
#endif
四. main文件代码
#include "main.h"
//接收数据 0--同步段 1--ID 2~9--数据段 10--校验和段 0xFF:校验成功 0x00:校验失败 0xAA:超时
extern u8 Lin_Rx_Data[11];
u8 Tim_time = 0;
int main(void)
{
//设置中断优先级分组为2
SystemInit();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init();
LED_Init(); //LED初始化
USART1_Init(19200); //串口1初始化:波特率19200 | 2400字节每秒
TIM3_Init(); //1ms中断一次
while (1)
{
if((Lin_Rx_Data[1] == 0x39) && (Lin_Rx_Data[10] == 0xFF)){
if(Lin_Rx_Data[2] == 0x01){
LED_L;
}else if(Lin_Rx_Data[2] == 0x02){
LED_M;
}
}
}
}