1、前言
开发CAN的时候,一直使用Canable的开源硬件,尤其是刷了PCAN的固件后,性价比真是太高了。后来需要开发CANFD的产品,就买了Canable2.0的开源软硬件,刷了slcanfd固件 (非常可惜没有PCAN FD固件的开源),体验使用下来实在是差强人意,就萌生了用Canable2.0的硬件自己开发下USB2CANFD的调试工具。
2、硬件资源
STM32G431CBT6 + TJA1050
- 一路全速USB: 一次最多传输64字节,这就导致在传输DLC=64的CANFD的数据时就需要分包处理才行 (PA12/PA11)
- 一路CANFD控制器:PB8/PB9
- 2个LED灯:PA0/PA15
3、软件工具
开发环境:STM32CubeIDE 1.14.1 HAL库版本: FW_G4_V1.5.2
使用到组件:
- USB : 使用USB CDC 虚拟串口功能
- FDCAN1
- FreeRTOS
4、固件实现的功能目标
- 1、支持上位机打开/关闭USB2CANFD功能
- 2、支持设置CAN/CANFD模式,支持设置常用波特率配置,
- 3、支持接收/发送CAN/CANFD帧,标准帧/拓展帧,支持开启BRS
- 4、支持配置1组标准帧的硬件ID过滤范围、1组拓展帧的硬件ID过滤范围
- 5、支持接收/发送指示灯LED的闪烁显示
5、软件设计思路
接收到CAN数据转换成协议数据后,专门设计了1个通过USB虚拟串口发送数据给上位机的Task, 主要是控制USB向上位机发送协议数据的频率,最多1ms发送一次协议数据包,这样做主要是为了降低上位机处理串口数据的难度,避免因为上位机因为处理接收数据异常,导致显示CAN的报文的丢帧。
6、USB2CANFD 自定义传输控制协议
6.1 设备打开/关闭-串口数据协议
6.2 设备传输CAN(FD)报文串口协议
其中CAN类型的规定如下:
7、USB虚拟串口CDC配置
STM32中USB中间件配置成虚拟串口的使用还是比较简单的,前提先配置好USB的时钟源为48MHZ
使能USB外设,配置PIN脚,中断
然后中间件这里选择CDC的类即可
完成以上配置,main函数中调用初始化后,我们的设备就能在电脑上被正确的枚举成串口。
MX_USB_Device_Init();
我们只要关心以下2个API的使用即可,在usbd_cdc_if.c文件中
USB数据接收中断回调函数CDC_Receive_FS中,我们自己在这里面实现协议数据的接收处理逻辑
static int8_t CDC_Receive_FS(uint8_t* Buf, uint32_t *Len)
{
/* USER CODE BEGIN 6 */
// 这里写自己的数据接收处理逻辑
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &Buf[0]);
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
return (USBD_OK);
/* USER CODE END 6 */
}
USB发送接口函数:需要发送数据时,直接调用即可
uint8_t CDC_Transmit_FS(uint8_t* Buf, uint16_t Len)
8、FDCAN1配置
同样是配置好时钟源,然后引脚、中断配置,至于CANFD其他参数如波特率/采样率的配置,随意配置即可,因为我们后面会重新写接口来通过上位机来控制这些参数的配置
这里附上CANFD外设初始化代码配置,上位机发送打开CAN的功能指令后,下位机调用CanFd_if_Start的接口函数接口,这里面主要是配置了CAN的工作模式,波特率,采样率,然后依次配置硬件过滤、注册FIFO0/FIFO1的中断回调函数、BUSOFF的中断回调函数。
// --------------------------------------------------------------------------------------------------------------------
/// \brief CanFd_if_Start
// --------------------------------------------------------------------------------------------------------------------
void CanFd_if_Start(CANFD_PARA_SET_U CanFd_Para)
{
//设置波特率
const uint8_t CanBaud = CanFd_Para.PARA.CanBaud;
const uint8_t CanMode = CanFd_Para.PARA.CanMode;
const uint8_t CanDataBaud = CanFd_Para.PARA.CanDataBaud;
const uint32_t StdFileterId1 = LDR32_BIG(&CanFd_Para.PARA.StdFileterId1[0]);
const uint32_t StdFileterId2 = LDR32_BIG(&CanFd_Para.PARA.StdFileterId2[0]);
const uint32_t ExtFileterId1 = LDR32_BIG(&CanFd_Para.PARA.ExtFileterId1[0]);
const uint32_t ExtFileterId2 = LDR32_BIG(&CanFd_Para.PARA.ExtFileterId2[0]);
uint32_t NominalPrescaler = 0;
uint32_t DataPrescaler = 0;
HAL_FDCAN_DeInit(&hfdcan1);
// 81.25%
switch (CanBaud)
{
case 0: //125k
NominalPrescaler = 48;
break;
case 1: //250K
NominalPrescaler = 24;
break;
case 2: //500K
NominalPrescaler = 12;
break;
case 3: //1M
NominalPrescaler = 6;
break;
default:
NominalPrescaler = 12;
break;
}
// 75%
switch (CanDataBaud)
{
case 0: // 1M
DataPrescaler = 12;
break;
case 1: // 2M
DataPrescaler = 6;
break;
case 2: // 4M
DataPrescaler = 3;
break;
default:
break;
}
switch (CanMode)
{
case 0: //classic can
hfdcan1.Init.FrameFormat = FDCAN_FRAME_CLASSIC;
break;
case 1: //canfd
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_NO_BRS;
break;
case 2: //canfd brs
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS;
break;
default:
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS;
break;
}
hfdcan1.Instance = FDCAN1;
hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1;
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL;
hfdcan1.Init.AutoRetransmission = ENABLE;
hfdcan1.Init.TransmitPause = DISABLE;
hfdcan1.Init.ProtocolException = DISABLE;
// 波特率和采样率计算
// FDCAN 时钟频率 FDCLK 96MHZ
// baud = FDCLK / Prescaler / (1 + TimeSeg1 + TimeSeg2) eg. 96 / 12 / (1+12+3) = 0.5
// sample = ( 1 + TimeSeg1) / (1 + TimeSeg1 + TimeSeg2) eg. (1+12) / (1+12+3) = 81.25%
// SyncJumpWidth:
// 81.25%
hfdcan1.Init.NominalPrescaler = NominalPrescaler; // 96 / 12 = 8
hfdcan1.Init.NominalSyncJumpWidth = 3;
hfdcan1.Init.NominalTimeSeg1 = 12;
hfdcan1.Init.NominalTimeSeg2 = 3; // 8 / (1 + 12 + 3) = 0.5
// 75%
hfdcan1.Init.DataPrescaler = DataPrescaler; // 96 / 6 = 16
hfdcan1.Init.DataSyncJumpWidth = 2;
hfdcan1.Init.DataTimeSeg1 = 5;
hfdcan1.Init.DataTimeSeg2 = 2; // 16 / (1 + 5 + 2) = 2
hfdcan1.Init.StdFiltersNbr = 28;
hfdcan1.Init.ExtFiltersNbr = 8;
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION;
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
CanFd_if_Canfd1Config(StdFileterId1,StdFileterId2,ExtFileterId1,ExtFileterId2);
}
// --------------------------------------------------------------------------------------------------------------------
/// \brief CanFd_if_Canfd1Config
// --------------------------------------------------------------------------------------------------------------------
void CanFd_if_Canfd1Config(uint32_t std_filterid1, uint32_t std_filterid2, uint32_t ext_filterid1, uint32_t ext_filterid2)
{
sFilterConfig1.IdType = FDCAN_STANDARD_ID;
sFilterConfig1.FilterIndex = 0;
sFilterConfig1.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig1.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig1.FilterID1 = std_filterid1;
sFilterConfig1.FilterID2 = std_filterid2;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig1) != HAL_OK)
{
Error_Handler();
}
sFilterConfig1.IdType = FDCAN_EXTENDED_ID;
sFilterConfig1.FilterIndex = 0;
sFilterConfig1.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig1.FilterConfig = FDCAN_FILTER_TO_RXFIFO1;
sFilterConfig1.FilterID1 = ext_filterid1;
sFilterConfig1.FilterID2 = ext_filterid2;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig1) != HAL_OK)
{
Error_Handler();
}
/* Configure global filter on both FDCAN instances:
Filter all remote frames with STD and EXT ID
Reject non matching frames with STD ID and EXT ID */
if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
Error_Handler();
}
/* Activate Rx FIFO 0 new message notification on both FDCAN instances */
if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
{
Error_Handler();
}
if (HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan1, FDCAN_RX_FIFO0, FDCAN_RX_FIFO_OVERWRITE) != HAL_OK)
{
Error_Handler();
}
/* Activate Rx FIFO 1 new message notification on both FDCAN instances */
if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO1_NEW_MESSAGE, 0) != HAL_OK)
{
Error_Handler();
}
if (HAL_FDCAN_ConfigRxFifoOverwrite(&hfdcan1, FDCAN_RX_FIFO1, FDCAN_RX_FIFO_OVERWRITE) != HAL_OK)
{
Error_Handler();
}
/* Activate busoff notification on both FDCAN instances */
if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_BUS_OFF, 0) != HAL_OK)
{
Error_Handler();
}
/* Configure and enable Tx Delay Compensation, required for BRS mode.
TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler
TdcFilter default recommended value: 0 */
// HAL_FDCAN_ConfigTxDelayCompensation(&hfdcan1, hfdcan1.Init.DataPrescaler * hfdcan1.Init.DataTimeSeg1, 0);
// HAL_FDCAN_EnableTxDelayCompensation(&hfdcan1);
HAL_FDCAN_Start(&hfdcan1);
}
9、FREERTOS的配置
打开FreeRtos的中间件,选择API的版本为V2
创建任务,队列,给每个任务配置堆栈大小,修改Task的优先级
创建Event, 因为我们会在USB/CAN的中断回调中设置Event事件, 我们要注意配置FreeRTOS的系统中断优先级不能低于USB/CAN的系统中断优先级(值越小系统中断优先级越高),否则在USB/CAN中断中调用事件置位的API会导致FreeRTOS挂了。
配置完这些,main.c里初始化时是自动生成创建这些Task,Event,Queue的代码。
/* creation of Can2UsbCmdQueue */
Can2UsbCmdQueueHandle = osMessageQueueNew (35, sizeof(CAN2USB_CMD_T), &Can2UsbCmdQueue_attributes);
/* creation of CanIfQueue */
CanIfQueueHandle = osMessageQueueNew (30, sizeof(S_CanIf_RxMsg_t), &CanIfQueue_attributes);
/* creation of Usb2CanCmdQueue */
Usb2CanCmdQueueHandle = osMessageQueueNew (70, sizeof(USB2CANFD_CMD_U), &Usb2CanCmdQueue_attributes);
/* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */
/* USER CODE END RTOS_QUEUES */
/* Create the thread(s) */
/* creation of defaultTask */
defaultTaskHandle = osThreadNew(StartDefaultTask, NULL, &defaultTask_attributes);
/* creation of ReciveMsgTask */
ReciveMsgTaskHandle = osThreadNew(StartReciveMsgTask, NULL, &ReciveMsgTask_attributes);
/* creation of SendMsgTask */
SendMsgTaskHandle = osThreadNew(StartSendMsgTask, NULL, &SendMsgTask_attributes);
/* creation of LedFlashTask */
LedFlashTaskHandle = osThreadNew(StartLedFlashTask, NULL, &LedFlashTask_attributes);
/* USER CODE BEGIN RTOS_THREADS */
/* add threads, ... */
/* USER CODE END RTOS_THREADS */
/* Create the event(s) */
/* creation of LedEvent */
LedEventHandle = osEventFlagsNew(&LedEvent_attributes);
/* creation of ReceiveEvent */
ReceiveEventHandle = osEventFlagsNew(&ReceiveEvent_attributes);
/* creation of SendEvent */
SendEventHandle = osEventFlagsNew(&SendEvent_attributes);
我们自己写下Task的实体就行,例如事件等待型的Task
// --------------------------------------------------------------------------------------------------------------------
/// brief: StartReciveMsgTask
/// param:
/// note:
// --------------------------------------------------------------------------------------------------------------------
void StartReciveMsgTask(void *argument)
{
uint32_t event;
for(;;)
{
event = osEventFlagsWait(ReceiveEventHandle,
RECE_EVENT_ALL,
osFlagsWaitAny,
osWaitForever);
if((event & RECE_INI_EVENT) == RECE_INI_EVENT)
{
}
if((event & RECE_USB_EVENT) == RECE_USB_EVENT)
{
ReciveMsgTaskUsbHandle();
}
if((event & RECE_CAN_EVENT) == RECE_CAN_EVENT)
{
ReciveMsgTaskCanHandle();
SendMsgTaskSetCanEvent();
LedFlashTaskSetRxLedEvent();
vTaskDelay(1);
}
//osEventFlagsClear(ReceiveEventHandle,RECE_EVENT_ALL);
}
}
10、逻辑功能代码编写
剩下的就是逻辑功能代码编写了,源码比较多,不好贴,我上传了源代码,感兴趣的可以下载学习参考。
https://download.csdn.net/download/yinzimu/89497901
11、功能验证
使用python开发上位机验证了USB2CANFD盒子的功能,使用 CANFD 500K/2M 配置,进行512K大小的固件进行FOTA测试,36服务每次传输4K数据,整个刷机过程还是非常稳定的。