为何树可以长那么高,为什么狗可以单身这么久
之前一直接触的是CAN通讯、串口之类的,突然有一个LIN通讯的CASE让我有点措手不及,在习惯性的在CSDN上寻找答案时发现这一块好像并不是很多,所以在结束了这个CASE之后想着写点关于做LIN通讯时的所见所感。
1.关于LIN总线
LIN的英文全称是Local Interconnect Network,它是基于UART/SCI的低成本串行通讯协议,主要用与车上传感器和执行器之间,例如车窗、后视镜、大灯、门锁等,我的那个CASE是用在换挡器手球和手柄之间的LIN通讯。
LIN的传输不同于CAN,用的是单线传输,单主机多从机(最多16个从机),总线电平12V,最大速率20Kbps。
总线任务:调度线上帧传输次序;监测数据处理错误;接收唤醒总线命令(这个我没研究)。
从机任务:发送应答;接收应答;既不接受也不应答(注:从机不能直接向总线发送数据,在收到主节点的帧头后通过ID判断具体操作)。
LIN的特点:最大的特点应该就是比CAN便宜吧
2.方向
这次案例是换挡器手柄的LIN通讯检测,我用的是MC9S12XS128,接下来写的主要是思路和代码,硬件部分暂不叙述。
由于我要检测的是一个LIN从机部分的应答,所以我需要做一个LIN的主机,这个主机的功能就是让我要检测的从机发送相应的应答。
从机发送应答的前提是主机提供了帧头以及帧头带有相应的ID,那么接下来我的目标就明确了:做一个循环发送从机对应的帧头就OK了。理论上这样从机就可以发送出我需要检测的应答部分,当然这边还需要一个接收应答的功能,综上可以分为两步:
1.循环发送帧头
2.接收从机的应答
3.具现
LIN的报文分为两部分:帧头和应答。帧头部分由同步间隔段、同步段、ID组成;应答部分由数据段、校验和组成。
我需要做的主节点发送帧头那发送的内容如下图
我一开始不理解后面的那段平的怎么发或者说怎么用代码表达,其实不用表达这部分,就只发前面的帧头部分就可以了。
所有的代码如下
#include <hidef.h> /* common defines and macros */
#include "derivative.h" /* derivative-specific definitions */
#define LEDCPU PORTK_PK4
#define LEDCPU_dir DDRK_DDRK4
#define BUS_CLOCK 32000000 //总线频率
#define OSC_CLOCK 16000000 //晶振频率
#define BAUD 19200 //串口波特率
#define BIT(A,B) ((A>>B)&0x01) // A 为变量
#define ID 0x20 // ID场值为0x30
#define EN PTM_PTM6 //LIN使能
#define EN_dir DDRM_DDRM6
//unsigned int x=0;
Bool a;
struct message
{
unsigned char identifier;
unsigned char data[8];
};
struct message msg_send;
struct message msg_get;
// 定义LIN状态
enum lin_state { IDLE, _BREAK, SYNCH, PROTECTED_IDENTIFIER, DATA0, DATA1,
DATA2, DATA3, DATA4, DATA5, DATA6, DATA7, CHECKSUM };
// 定义帧结构体
struct frame {
unsigned char protected_id;
unsigned char data[8];
unsigned char check;
enum lin_state state;
unsigned char error;
};
struct frame frame_receive;
/*************************************************************/
/* 初始化锁相环 ,frame_receive */
/*************************************************************/
void INIT_PLL(void)
{
CLKSEL &= 0x7f; //set OSCCLK as sysclk
PLLCTL &= 0x8F; //Disable PLL circuit
CRGINT &= 0xDF;
#if(BUS_CLOCK == 40000000)
SYNR = 0x44;
#elif(BUS_CLOCK == 32000000)
SYNR = 0x43;
#elif(BUS_CLOCK == 24000000)
SYNR = 0x42;
#endif
REFDV = 0x81; //PLLCLK=2×OSCCLK×(SYNDIV+1)/(REFDIV+1)=64MHz ,fbus=32M
PLLCTL =PLLCTL|0x70; //Enable PLL circuit
asm NOP;
asm NOP;
while(!(CRGFLG&0x08)); //PLLCLK is Locked already
CLKSEL |= 0x80; //set PLLCLK as sysclk
}
/************************************************************/
/* 初始化TIM模块 */
/************************************************************/
void initialize_TIM(void)
{
TSCR1_TFFCA = 1; // 定时器标志位快速清除
TSCR1_TEN = 1; // 定时器使能位. 1=允许定时器正常工作; 0=使主定时器不起作用(包括计数器)
TIOS = 0xff; //指定所有通道为输出比较方式
TCTL1 = 0x00; // 后四个通道设置为定时器与输出引脚断开
TCTL2 = 0x00; // 前四个通道设置为定时器与输出引脚断开
TIE = 0x00; // 禁止所有通道定时中断
TSCR2 = 0x07; // 预分频系数pr2-pr0:111,,时钟周期为4us,
TFLG1 = 0xff; // 清除各IC/OC中断标志位
TFLG2 = 0xff; // 清除自由定时器中断标志位
}
/*************************************************************/
/* 初始化LIN */
/*************************************************************/
void INIT_LIN(void)
{
unsigned char i;
SCI0BD = BUS_CLOCK/16/BAUD; //设置SCI0波特率为9600
SCI0CR1 = 0x00; //设置SCI0为正常模式,八位数据位,无奇偶校验
SCI0CR2 = 0x2c; //允许接收和发送数据,允许接收中断功能
SCI0SR2_BRK13 = 1;
frame_receive.protected_id=0;
frame_receive.state=IDLE;
frame_receive.error=0;
frame_receive.check=0;
for (i=0;i<8;i++)
frame_receive.data[i]=0;
EN_dir=1;
EN=1;
}
/*************************************************************/
/* 计算奇偶校验位 */
/*************************************************************/
unsigned char LINCalcParity(unsigned char id)
{
unsigned char parity, p0,p1;
parity=id;
p0=(BIT(parity,0)^BIT(parity,1)^BIT(parity,2)^BIT(parity,4))<<6; //偶校验位
p1=(!(BIT(parity,1)^BIT(parity,3)^BIT(parity,4)^BIT(parity,5)))<<7; //奇校验位
parity|=(p0|p1);
return parity;
}
/*************************************************************/
/* 计算和校验位 */
/*************************************************************/
unsigned char LINCalcChecksum(unsigned char *data)
{
unsigned int sum = 0;
unsigned char i;
for(i = 0; i < 8; i++)
{
sum += data[i];
if(sum&0xFF00)
sum = (sum&0x00FF) + 1;
}
sum ^= 0x00FF;
return (unsigned char)sum;
}
/*************************************************************/
/* 发送检测函数 */
/*************************************************************/
Bool LINCheckSend(enum lin_state status, unsigned char val)
{
//等待串口数据接收到为止
while(frame_receive.state < status)
if(frame_receive.error)
return(FALSE);
switch(status)
{
case _BREAK:
case SYNCH:
break;
case PROTECTED_IDENTIFIER:
if(frame_receive.protected_id != val)
return(FALSE);
break;
case DATA0:
case DATA1:
case DATA2:
case DATA3:
case DATA4:
case DATA5:
case DATA6:
case DATA7:
if(frame_receive.data[status-DATA0] != val)
return(FALSE);
break;
case CHECKSUM:
if(frame_receive.check != val)
return(FALSE);
break;
}
return(TRUE);
}
/*************************************************************/
/* LIN发送字节函数 */
/*************************************************************/
Bool LINSendChar(unsigned char ch)
{
while(!SCI0SR1_TDRE); //等待发送数据寄存器(缓冲器)为空
SCI0DRL = ch;
return(TRUE);
}
/*************************************************************/
/* LIN发送间隔场 */
/*************************************************************/
Bool LINSendbreak(void)
{
while(!SCI0SR1_TDRE); //等待发送数据寄存器(缓冲器)为空
SCI0CR2_SBK = 1; //队列待发送的中止字符
SCI0CR2_SBK = 0; //返回正常发送操作
return(TRUE);
}
/***********发送帧头 ****************/
Bool LINZhuSendMsg(void)
{
unsigned char parity_id;
frame_receive.error = 0;
if(!LINSendbreak())// 发送间隔场
return(FALSE);
if(!LINCheckSend(_BREAK,0x00))// 检查间隔场发送
return(FALSE);
if(!LINSendChar(0x55))// 发送同步场
return(FALSE);
if(!LINCheckSend(SYNCH,0x55))// 检查同步场发送
return(FALSE);
parity_id=LINCalcParity(msg_send.identifier); // 计算奇偶校验
if(!LINSendChar(parity_id)) // 发送标识符场
return(FALSE);
if(!LINCheckSend(PROTECTED_IDENTIFIER, parity_id))// 检查标识符场发送
return(FALSE);
return(TRUE);
}
/*************************************************************/
/* LIN接收字节函数 */
/*************************************************************/
Bool LINGetChar(void)
{
unsigned volatile char ch;
// LIN接收通道状态
switch(frame_receive.state)
{
case IDLE: /*1.总线空闲时,检测间隔场*/
if(!(SCI0SR1&0x22))
return(FALSE);
if(SCI0DRL)
return(FALSE);
break;
case _BREAK: /*2.检测到间隔场,检测同步场*/
if(!(SCI0SR1&0x20))
return(FALSE);
if(SCI0DRL != 0x55)
return(FALSE);
break;
case SYNCH: /*3.检测到同步场,接收PID*/
if(!(SCI0SR1&0x20))
return(FALSE);
ch = SCI0DRL;
frame_receive.protected_id = ch;
break;
case PROTECTED_IDENTIFIER:/*4.接收到PID,接收数据0*/
case DATA0: /*5.接收数据1-7*/
case DATA1: //5
case DATA2: //6
case DATA3: //7
case DATA4: //8
case DATA5: //9
case DATA6: //10
if(!(SCI0SR1&0x20))
return(FALSE);
ch = SCI0DRL;
frame_receive.data[frame_receive.state-PROTECTED_IDENTIFIER] = ch;
break;
case DATA7: /*6.接收到数据7,接收CheckSum*/
if(!(SCI0SR1&0x20))
return(FALSE);
ch = SCI0DRL;
frame_receive.check = ch;
break;
case CHECKSUM: /*7.CheckSum接收完成*/
return(FALSE);
}
frame_receive.state+=1;
return(TRUE);
}
/***************************************************
函数名称:UC LinSlave_Msg_Handle(void)
函数功能: 从机接收数据后处理
形式参数:
返回值: TRUE -- 成功
FALSE -- 失败
*****************************************************/
Bool LINSlave_Msg_Handle(void)
{
if(frame_receive.state == CHECKSUM)
{
//判断数据接收是否正确
if((frame_receive.protected_id == LINCalcParity(ID))&&(frame_receive.check == LINCalcChecksum(frame_receive.data)))
{
//LEDCPU=~LEDCPU;
return TRUE;
}
frame_receive.state = IDLE;
return TRUE;
}
return FALSE;
}
/*************************************************************/
/* 延时函数 */
/*************************************************************/
void delay1ms(unsigned int n)
{
unsigned int i;
for(i=0;i<n;i++)
{
TFLG1_C0F = 1; //清除标志位
TC0 = TCNT + 250; //设置输出比较时间为1ms
while(TFLG1_C0F == 0); //等待,直到发生输出比较事件
}
}
/*************************************************************/
/* LIN检查发送的数据 */
/*************************************************************/
#pragma CODE_SEG __NEAR_SEG NON_BANKED
interrupt void LINreceive(void)
{
if(!LINGetChar())
{
frame_receive.error = 1;
frame_receive.state = IDLE;
}
}
#pragma CODE_SEG DEFAULT
/*************************************************************/
/* 主函数 */
/*************************************************************/
void main(void)
{
DisableInterrupts;
INIT_PLL();
initialize_TIM();
INIT_LIN();
LEDCPU_dir=1;
LEDCPU=0;
EnableInterrupts;
msg_send.identifier = ID; // 标识符
for(;;)
{
a = LINZhuSendMsg();
delay1ms(10);
if(a)
{
delay1ms(10);
if(frame_receive.state == CHECKSUM)
{
//判断数据接收是否正确
if((frame_receive.protected_id == LINCalcParity(ID))&&(frame_receive.check == LINCalcChecksum(frame_receive.data)))
{
LEDCPU=~LEDCPU;
}
frame_receive.state = IDLE;
}
}
delay1ms(500);
}
}
简单讲解一下代码部分:
主函数里面的逻辑是发送帧头,检测是否发送成功,发送成功后接收报文。接收部分用的是中断接收。
enum lin_state { IDLE, _BREAK, SYNCH, PROTECTED_IDENTIFIER, DATA0, DATA1,
DATA2, DATA3, DATA4, DATA5, DATA6, DATA7, CHECKSUM };
这里面定义的就是上面图中描述的LIN完整报文的组成部分。
完整的代码里主节点发送的只是帧头;下面是主节点发送完整的报文的代码,相比之下多了数据和校验部分。
/*************************************************************/
/* LIN发送信息函数 */
/*************************************************************/
Bool LINSendMsg(void)
{
unsigned char check_sum, parity_id, i;
frame_send.error = 0;
// 发送间隔场
if(!LINSendbreak())
return(FALSE);
// 检查间隔场发送
if(!LINCheckSend(_BREAK,0x00))
return(FALSE);
// 发送同步场
if(!LINSendChar(0x55))
return(FALSE);
// 检查同步场发送
if(!LINCheckSend(SYNCH,0x55))
return(FALSE);
// 计算奇偶校验
parity_id=LINCalcParity(msg_send.identifier);
// 发送标识符场
if(!LINSendChar(parity_id))
return(FALSE);
// 检查标识符场发送
if(!LINCheckSend(PROTECTED_IDENTIFIER, parity_id))
return(FALSE);
for(i=0; i < 8; i++)
{
// 发送数据场
if(!LINSendChar(msg_send.data[i]))
return(FALSE);
// 检查数据场发送
if(!LINCheckSend(DATA0+i, msg_send.data[i]))
return(FALSE);
}
check_sum = LINCalcChecksum(msg_send.data);
// 发送校验和场
if(!LINSendChar(check_sum))
return(FALSE);
// 检查校验和场发送
if(!LINCheckSend(CHECKSUM, check_sum))
return(FALSE);
frame_send.state = IDLE;
return(TRUE);
}
最后在废话一句:我用的是SCI0不是SCI1。