当前位置: 代码迷 >> 综合 >> 【STM32CubeMX】STM32F407配置CAN1通信调制OBD
  详细解决方案

【STM32CubeMX】STM32F407配置CAN1通信调制OBD

热度:72   发布时间:2023-12-16 20:16:11.0

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 */}

6. 结果显示

  相关解决方案