#include "main.h" /****************************************************************** Lin总线帧格式:帧头+应答 帧头:同步间隔段(至少13个显性电平)+同步间隔段间隔符(至少1位隐形电平)+同步段(0x55)+字节间间隔+PID(ID+校验位) 注:PID=ID(6位)+校验(2位) ID 取值范围为: 0x00~0x3f ID的取值分类: 信号携带帧 : 0x00~0x3b 诊断帧(主机请求): 0x3c 诊断帧(从机应答): 0x3d 保留帧 : 0x3e,0x3f P0 = ID0⊕ID1⊕ID2⊕ID4 异或运算 P1 = ┐(ID3⊕ID4⊕ID5⊕ID1) 异或后取非 应答:应答间隔+数据段+校验和段 注:数据段 低字节的低位先发 标准型校验和: 只校验数据段 增强型校验和: 校验数据段以及PID 诊断帧只能用标准型校验和 ******************************************************************/ /****************************************************************** lin中断接收函数功能: 1、回环效果:即主机发送帧头或者主机发送帧头+应答,主机的中断服务程序都会接收数据。 可以检测出:主机串口Tx、Rx、Lin脚,三个引脚上的信号是相同的(除了电平不同)。 2、当串口检测到连续至少11位显性电平即进入中断开始接收。 3、中断服务函数接收数据时按进程推进 ①接收同步段是否OK? ②接收ID校验后解析是数据执行还是反馈 若是执行: 若是反馈: ③分步接收数据 ③准备数据在帧头结束后发送数据 ④匹配校验数据是否正确 ⑤解析数据并执行 ******************************************************************/ #define UART_TX_PIN PIN3 #define UART_TX_LOW() GPIO_ClrBit(GPIOD, UART_TX_PIN) #define UART_TX_HIGH() GPIO_SetBit(GPIOD, UART_TX_PIN) #define UART_RX_PIN PIN4 #define UART_RX_Value() GPIO_GetBit(GPIOD, UART_RX_PIN) #define MAX_LIN_BUF 16 __IO uint8_t lin_active = 0; __IO uint8_t lin_index = 0; uint8_t lin_buf[MAX_LIN_BUF]; //void uart1_putchar(uint8_t ch){while(UART_IsTXFIFOFull(UART1)){;}UART1->DATA=ch;} void uart1_putchar(uint8_t ch) { UART_WriteByte(UART1, ch); while(UART_IsTXBusy(UART1)) {;} } void uart1_putbuf(uint8_t *buf, uint32_t len) { while(len--) { uart1_putchar(*buf++); } } void uart1_puts(char *str) { while(*str) { uart1_putchar((int)*str++); } } uint8_t uart1_getchar(void) { int i = 0; while(!(UART1->CTRL & 0x8)) { DelayUs(1); //超过2MS没有数据就退出 。 if(i++ > 2000) { return 0xff; } } return (UART1->DATA & 0xff); } void UART1Init(void) { UART_InitStructure UART_initStruct; GPIO_Init(GPIOD, PIN3, 0, 1, 0, 0); //GPIOD.3 配置为输入引脚,开启上拉 GPIO_Init(GPIOD, PIN4, 0, 1, 0, 0); //GPIOD.4 配置为输入引脚,开启上拉 PORT_Init(PORTD, UART_RX_PIN, PORTD_PIN4_UART1_RX, 1); //GPIOD.4 配置为UART1输入引脚 PORT_Init(PORTD, UART_TX_PIN, PORTD_PIN3_UART1_TX, 0); //GPIOD.3 配置为UART1输出引脚 GPIO_Init(GPIOC, PIN4, 1, 0, 0, 0); //GPIOC.4 配置为输出引脚,推挽输出 GPIO_SetBit(GPIOC, PIN4); UART_initStruct.Baudrate = LIN_BPS; UART_initStruct.DataBits = UART_DATA_8BIT; //数据位位数,可取值UART_DATA_8BIT、UART_DATA_9BIT UART_initStruct.Parity = UART_PARITY_NONE; //奇偶校验位,可取值UART_PARITY_NONE、UART_PARITY_ODD、UART_PARITY_EVEN、UART_PARITY_ONE、UART_PARITY_ZERO UART_initStruct.StopBits = UART_STOP_1BIT; //停止位位数,可取值UART_STOP_1BIT、UART_STOP_2BIT UART_initStruct.TXThreshold = 0; //取值0--7 UART_initStruct.TXThresholdIEn = 0; //当TX FIFO中数据个数 <= TXThreshold时触发中断 UART_initStruct.RXThreshold = 7; //取值0--7 UART_initStruct.RXThresholdIEn = 0; //当RX FIFO中数据个数 >= RXThreshold时触发中断 UART_initStruct.TimeoutTime = 4; //超时时长 = TimeoutTime/(Baudrate/10) 秒 = 5/1920 = 2080 uS UART_initStruct.TimeoutIEn = 1; //超时中断,超过 TimeoutTime/(Baudrate/10) 秒没有在RX线上接收到数据时触发中断 UART_Init(UART1, &UART_initStruct); NVIC_SetPriority(UART1_IRQn, 1); UART_Open(UART1); } // 主机帧头部分 // 起先是同步间隔段,因为作为主机要连续发送至少13位显性电平,这里用的是STM32自带的库函数,直接调用就行。 void Lin_SendBreak(void) { GPIO_Init(GPIOD, UART_TX_PIN, 1, 0, 0, 0); //GPIOD.3 配置为输出引脚,推挽输出 UART_TX_LOW(); //显性电平 DelayUs((1000000 * 13) / LIN_BPS); //至少13位显性电平 UART_TX_HIGH(); //隐形电平 DelayUs((1000000 * 1 ) / LIN_BPS); //至少01位隐形电平 PORT_Init(PORTD, UART_TX_PIN, PORTD_PIN3_UART1_TX, 0); //GPIOD.3 配置为UART1输出引脚 } // 接着就是同步段,发送0x55 void Lin_SendSyncSegment(void) { uart1_putchar(0x55); } //然后就是发送PID(protect ID),这里的前六位为ID,后两位为校验位,函数功能为:输入ID,返回PID。 uint8_t Lin_CheckPID(uint8_t id) { uint8_t P0 = (((id) ^ (id >> 1) ^ (id >> 2) ^ (id >> 4)) & 0x01) << 6 ; uint8_t P1 = ((~((id >> 1) ^ (id >> 3) ^ (id >> 4) ^ (id >> 5))) & 0x01) << 7 ; return (id | P0 | P1) ; } // 该函数体就是单片机作为主机发送的帧头,可以指定ID发送帧头,接收从机返回的数据;也可以发送帧头+数据,让从机接收。 void Lin_Hearder(uint8_t id) { Lin_SendBreak(); Lin_SendSyncSegment(); uart1_putchar(Lin_CheckPID(id)); } // 此段函数功能:输入ID+数据,返回校验和段,里面有调用返回PID函数。诊断帧只能用标准校验这里还没有验证过,因为校验还没有测试。 // 是经典校验还是增强校验,另:诊断帧只能经典校验 uint8_t Lin_Checksum(uint8_t id, uint8_t data[]) { uint16_t sum ; sum = data[0]; if(id == 0x3c) // 如果是诊断帧,用经典校验 { for(int t = 1; t < 8; t++) { sum += data[t]; if(sum & 0xff00) { sum &= 0x00ff; sum += 1; } } sum = ~sum; return (uint8_t)sum ; } for(int t = 1; t < 8; 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 Lin_Response(uint8_t id, uint8_t data[]) { uart1_putbuf(data, 8); uart1_putchar(Lin_Checksum(id, data)); } void Lin_MainPutData(uint8_t id, uint8_t *data) { Lin_Hearder(id); Lin_Response(id, data); DelayUs((1000000 * 10) / LIN_BPS); //至少01位隐形电平 } #if 0 uint8_t Lin_MainGetData(uint8_t id, uint8_t *data) { uint8_t buf[12]; while((UART1->CTRL & 0x8)) { uint8_t tmp = UART1->DATA; //清接收缓冲 } Lin_Hearder(id); //发送命令头 //0x55 0xb1 0x12 0x34 0x56 0x78 0x90 0xab 0xcd 0xef 0x3f for(int i = 0; i < 11; i++) { buf[i] = uart1_getchar(); //接收11字节数据 } // for(int i=0;i<11;i++){printf("0x%02x ",buf[i]);} memcpy(data, buf + 2, 8); //取出数据 uint8_t ReceiveCheckSum = buf[10]; //接收校验和 uint8_t SumCheck = Lin_Checksum(id, data); //计算校验和 // printf("ReceiveCheckSum & SumCheck: 0x%02x=0x%02x \n\r",ReceiveCheckSum,SumCheck); if(ReceiveCheckSum == SumCheck) { return 1; //校验正确 } return 0; } #else uint8_t Lin_MainGetData(uint8_t id, uint8_t *data) { while((UART1->CTRL & 0x8)) { uint8_t tmp = UART1->DATA; //清接收缓冲 } Lin_Hearder(id); //发送命令头 lin_active = 0; lin_index = 0; UART1->CTRL |= 0x00000010; //开中断 while(!lin_active) {;} // for(int i=0;iCTRL &= 0xffffffef; //关中断 uint8_t ReceiveCheckSum = lin_buf[10]; //接收校验和 uint8_t SumCheck = Lin_Checksum(id, data); //计算校验和 // printf("ReceiveCheckSum & SumCheck: 0x%02x=0x%02x \n\r",ReceiveCheckSum,SumCheck); if(ReceiveCheckSum == SumCheck) { return 1; //校验正确 } return 0; } #endif void UART1_Handler(void) { uint32_t tmp; if(UART_INTStat(UART1, UART_IT_RX_THR)) //阈值中断 { //uart0_putchar('*');//测试阈值中断 for(uint16_t i = 0; i < 6; i++) //读取6个字节,留1个给超时中断再读,否则缓冲空了产生不了超时中断 { if(UART_ReadByte(UART1, &tmp) == 0) { if(lin_index >= MAX_LIN_BUF) { lin_index = 0; //防止缓冲溢出重新把接收指针指向缓冲开始 } lin_buf[lin_index++] = tmp; //读取字符 } else { lin_index = 0; //字符出错丢弃后重新把接收指针指向缓冲开始 } } } if(UART_INTStat(UART1, UART_IT_RX_TOUT))//超时中断 { UART_INTClr(UART1, UART_IT_RX_TOUT); //uart0_putchar('#');//测试超时中断 while(UART_IsRXFIFOEmpty(UART1) == 0) { if(UART_ReadByte(UART1, &tmp) == 0) { if(lin_index >= MAX_LIN_BUF) { lin_index = 0; //防止缓冲溢出重新把接收指针指向缓冲开始 } lin_buf[lin_index++] = tmp; //读取字符 } else { lin_index = 0; //字符出错丢弃后重新把接收指针指向缓冲开始 } } lin_index = 0; lin_active = 1; //取走接收数据长度并重新把接收指针指向缓冲开始,表明数据有效 } } __IO uint16_t LinkeyPressDown0 = 0x0; __IO uint16_t LinkeyLastMKey0 = 0x0; __IO uint16_t LinkeyRelease0 = 0x0; uint64_t Get_LIN_DI_data_M2(void) { uint8_t data[10]; uint16_t read_keys = 0, down_keys = 0, up_keys = 0; int r0 = Lin_MainGetData(0x30, data); if(r0) { //当前读取的键值,当按键按下时,相应的位为1 read_keys = data[2]; read_keys <<= 8; read_keys += data[1]; read_keys = (~read_keys) & 0xffff; //1 读键值 //处理按键 LinkeyPressDown0 = read_keys & (read_keys ^ LinkeyLastMKey0); //2 得到按下触发值 LinkeyRelease0 = (read_keys ^ LinkeyPressDown0 ^ LinkeyLastMKey0); //3 得到释放触发值 LinkeyLastMKey0 = read_keys; //4 得到所有未释放的键值 down_keys = LinkeyPressDown0; up_keys = LinkeyRelease0; } uint64_t ret = up_keys & 0xffff; ret <<= 16; ret += (~read_keys) & 0xffff; ret <<= 16; ret += read_keys & 0xffff; ret <<= 16; ret += down_keys & 0xffff; return ret; } __IO uint16_t LinkeyPressDown1 = 0x0; __IO uint16_t LinkeyLastMKey1 = 0x0; __IO uint16_t LinkeyRelease1 = 0x0; uint64_t Get_LIN_DI_data_M3(void) { uint8_t data[10]; uint16_t read_keys = 0, down_keys = 0, up_keys = 0; int r0 = Lin_MainGetData(0x32, data); if(r0) { //当前读取的键值,当按键按下时,相应的位为1 read_keys = data[2]; read_keys <<= 8; read_keys += data[1]; read_keys = (~read_keys) & 0xffff; //1 读键值 //处理按键 LinkeyPressDown1 = read_keys & (read_keys ^ LinkeyLastMKey1); //2 得到按下触发值 LinkeyRelease1 = (read_keys ^ LinkeyPressDown1 ^ LinkeyLastMKey1); //3 得到释放触发值 LinkeyLastMKey1 = read_keys; //4 得到所有未释放的键值 down_keys = LinkeyPressDown1; up_keys = LinkeyRelease1; } uint64_t ret = up_keys & 0xffff; ret <<= 16; ret += (~read_keys) & 0xffff; ret <<= 16; ret += read_keys & 0xffff; ret <<= 16; ret += down_keys & 0xffff; return ret; } void Put_LIN_DO_data(uint8_t id, uint16_t dout) { uint8_t data[10]; data[0] = 0x15; data[1] = (dout >> 0) & 0xff; data[2] = (dout >> 8) & 0xff; data[3] = 0xff; data[4] = 0xff; data[5] = 0xff; data[6] = 0xff; data[7] = 0xff; Lin_MainPutData(id, data); } /* */