1. 前言:
为了使STM32F407VGT6单片机能够与汽车进行OBD通信实验,要求对STM32F407开发板进行验证实验,以下是实验过程。
2. 分析原理图
需要利用STM32F407VGT6的CAN接口和串口,CAN接口用于通过OBD获取车速,USART串口用于以数据帧格式上报给主控芯片。
CAN接口是STM32F407VGT6的PA12(T)和PA11(R);
USART3串口是PD9(PXD3)和PD8(TXD3);
3. CubeMX配置STM32F407VGT6功能
3.1 CAN1配置
配置CAN相关参数,波特率设定为500K,时钟频率为42M,
波特率计算公式:42M/21/(1+2+1)=500K。
3.2 USART3配置
4. CubeMX配置STM32F407IGT6功能
4.1 基础配置:
配置CAN相关参数,波特率设定为500K,时钟频率为42M,
波特率计算公式:42M/21/(1+2+1)=500K.
另外开启串口1用于后续调试。
5. 工程配置
建立一个Bsp文件夹用于存放Bsp_CAN的程序。
5.1 配置过滤器
我们可以看到CAN有四个中断。
我们希望接收ID为0X7E8的数据帧。如果想接收车速和转速就可u配置FIF0和FID0过滤器就可以如下设计:
HAL_StatusTypeDef CAN_SetFilters()
{CAN_FilterTypeDef canFilter;
//1. 设置FIFO 0 的筛选器uint32_t StdId = 0x7E8;canFilter.FilterBank = 0; //筛选器组编号canFilter.FilterMode = CAN_FILTERMODE_IDMASK; //ID掩码模式canFilter.FilterScale = CAN_FILTERSCALE_32BIT; //16位长度//只接收stdID为奇数的帧canFilter.FilterIdHigh = StdId<<5; //CAN_FxR1 寄存器的高16位canFilter.FilterIdLow = 0x0000; //CAN_FxR1 寄存器的低16位canFilter.FilterMaskIdHigh = StdId<<5; //CAN_FxR2寄存器的高16位canFilter.FilterMaskIdLow = 0x0000; //CAN_FxR2寄存器的低16位canFilter.FilterFIFOAssignment = CAN_RX_FIFO0; //应用于FIFO 0canFilter.FilterActivation = ENABLE; //使用筛选器canFilter.SlaveStartFilterBank = 14; //从CAN控制器筛选器起始的BankHAL_StatusTypeDef result=HAL_CAN_ConfigFilter(&hcan1, &canFilter);
//2. 设置FIFO 1的筛选器canFilter.FilterBank = 1; //筛选器组编号// 接收所有帧canFilter.FilterIdHigh = StdId<<5; //CAN_FxR1 寄存器的高16位canFilter.FilterIdLow = 0x0000; //CAN_FxR1 寄存器的低16位canFilter.FilterMaskIdHigh = StdId<<5; //CAN_FxR2寄存器的高16位,所有位任意canFilter.FilterMaskIdLow = 0x0000; //CAN_FxR2寄存器的低16位,所有位任意canFilter.FilterFIFOAssignment = CAN_RX_FIFO1; //应用于FIFO 1result=HAL_CAN_ConfigFilter(&hcan1, &canFilter);return result;
}
这样就可以过滤掉不是这个数据帧ID的数据。
5.2 配置发送数据帧
网络资料上很多配饰发送数据帧是基于老版本库进行开发的。以前的can的标准外设库中用于初始化can的句柄中(也就是那个结构体)会要求用户定义接收以及发送的结构体。以下是旧版本的标准外设库(现在当你用cubeMX创建工程的时候你会发现,在标准外设库中多了一个叫legacy的文件夹,这个文件夹中的can的外设库就是旧版的)中关于初始化句柄的内容:
typedef struct
{
CAN_TypeDef *Instance;
CAN_InitTypeDef Init;
CanTxMsgTypeDef* pTxMsg;
CanRxMsgTypeDef* pRxMsg;
__IO HAL_CAN_StateTypeDef State;
HAL_LockTypeDef Lock;
__IO uint32_t ErrorCode;
}CAN_HandleTypeDef;
看到其中的pTxMsg、pRxMsg就是让我们定义接收已经发送的结构体,然后在最新的库中没有了这两个,现在最新的库函数使用时,can启动时要用到2个函数:
1.HAL_CAN_Start(&hcan) //开启can
2.HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING); //开启中断
发送和接收函数是使用:
1.Can发送函数
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef*hcan,CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox);
2.Can接收函数
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef*hcan,uint32_tRxFifo,CAN_RxHeaderTypeDef *pHeader, uint8_t aData[]);
然而在官方那里并没有收发数据的数据帧组数,这需要自己来定义一个,在最新的HAL库中,我们需要申明一个数组来存放我们要发送的数据。这个函数就可以根据我们的实际需要,填写ID值,以及后面的发送数据。
void CAN_SendMsg(uint16_t msgID, uint8_t frameType, uint8_t txdata0, uint8_t txdata1, uint8_t txdata2)
{CAN_TxHeaderTypeDef TxHeader;TxHeader.StdId = msgID; //stdIDTxHeader.RTR = frameType; //数据帧,CAN_RTR_DATATxHeader.IDE = CAN_ID_STD; //标准格式TxHeader.DLC =8; //数据长度TxHeader.TransmitGlobalTime = DISABLE;uint8_t TxData[8]; //最多8个字节TxData[7] = 0x00;TxData[6] = 0x00;TxData[5] = 0x00;TxData[4] = 0x00; TxData[3] = 0x00;TxData[2] = txdata2;TxData[1] = txdata1;TxData[0] = txdata0;while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) < 1) {} //等待有可用的发送邮箱uint32_t TxMailbox; //临时变量, 用于返回使用的邮箱编号/* 发送到邮箱,由CAN模块负责发送到CAN总线 */if(HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) != HAL_OK){printf("数据发送成功");}
}
例如在这个程序里,我们将其放到主循环中,发送查询车速的指令。
CAN_SendMsg(0x7DF, CAN_RTR_DATA, 0x02, 0x01, 0x0d);//车速查询
5.2 处理接收回调函数
OBD调试会自动上报很多数据,因此我们需要配置过滤器只保留我们想要的数据。OBD调试首先需要我们发送查询指令,汽车才会响应我们的查询数据,这个数据是混杂在CAN总线众多数据流之中的,首先开启CAN中断回调函数。
//FIFO0 接收到新消息事件中断回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{CAN_ReadMsg(CAN_RX_FIFO0);
}
//FIFO1 接收到新消息事件中断回调函数
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)
{CAN_ReadMsg(CAN_RX_FIFO1);
}
在接收数据函数中我们需要对数据帧的第2位进行判断是否为0x0d,这个代码就是车速代码,如果确定为车速代码,那么利用串口将数据帧发送出去。程序上这样设计,只有接收到车速信息就发送,确保消息的实时性。
5.3 串口数据帧发送函数
//读取和显示FIFO0的消息
//参数FIFO_num是FIFO编号,CAN_RX_FIFO0或CAN_RX_FIFO1
void CAN_ReadMsg(uint32_t FIFO_num)
{CAN_RxHeaderTypeDef RxHeader;uint8_t RxData[8]; //接收数据缓存区,最多8个字节if (FIFO_num==CAN_RX_FIFO0){if(HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData) != HAL_OK){return;}}//显示接收到的消息for (uint8_t i=0; i<8; i++){RxData[i]=RxData[i];}if(RxData[2]==0x0d){串口发送函数}
}
那么设个数据帧的协议可以根据自己要求设置。
5.3.1 发送函数调用数组
串口发送函数组要调用接收数组,因此形参应该定义一个数组指针直指向这个数组。
然后定义这个数据帧格式:
#define FRAME_LENTH 8 // 指令长度
#define FRAME_START 0x05 // 协议帧开始1
#define FRAME_SECOND 0x88 // 协议帧开始2
#define FRAME_CHECK_BEGIN 0 // 校验码开始的位置
#define FRAME_CHECKSUM 7 // 校验码的位置
#define FILL_VALUE 0xAA // 填充值
uint8_t sendBuffer[FRAME_LENTH];//应答帧
我们设计,没有用到的数据帧用0xAA进行填充。首先尽力一个循环,先把我们设定的数组进行全部填充,然后再在这个基础上对我们需要设定的位进行修改。
uint8_t i = 0;for(i = 0; i < FRAME_LENTH; i++){sendBuffer[i] = FILL_VALUE;}
void usart_sendframe(uint8_t *rx_data)
{uint8_t i,j = 0;for(i = 0; i < FRAME_LENTH; i++){sendBuffer[i] = FILL_VALUE;}sendBuffer[0] = FRAME_START;sendBuffer[1] = FRAME_SECOND;sendBuffer[2] = rx_data[2];sendBuffer[3] = rx_data[3];sendBuffer[FRAME_CHECKSUM] = 0;for(j=0;j<FRAME_CHECKSUM;j++){sendBuffer[FRAME_CHECKSUM] += sendBuffer[j];}sendBuffer[FRAME_CHECKSUM] = sendBuffer[FRAME_CHECKSUM];HAL_UART_Transmit(&huart3,(uint8_t *)&sendBuffer,FRAME_LENTH,0xffff); // 发送数据帧
}
RTR :
CAN_RTR_DATA 数据帧
CAN_RTR_REMOTE 远程帧
IDE :
CAN_ID_STD 标准帧 0-0x7FF -->11bit
CAN_ID_EXT 扩展帧 0-0x1FFFFFFF -->29bit
DLC :
数据长度 0-8
FIFO0对应CAN1_RX0_IRQHandler,FIFO1对应CAN1_RX1_IRQHandler
5.4 主函数初始化
首先初始化过滤器,然后开启CAN,并且开启中断。
CAN_SetFilters();HAL_CAN_Start(&hcan1);__HAL_CAN_ENABLE_IT(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
然后就在循环里面已知查询OBD车速。
while (1){/* USER CODE END WHILE */CAN_SendMsg(0x7DF, CAN_RTR_DATA, 0x02, 0x01, 0x0d);//车速查询HAL_Delay(100);/* USER CODE BEGIN 3 */}