CAN使用指南
基本概述
- 最大可使用CAN controller数量:10。
- CAN最高传输速率:8M。(受限于transceiver的波特率限制,目前实验室只测试验证到5M波特率。)
- 一个controller的Ram内划分的Block个数:
- CAN0~CAN3:4 Block (可变payload);
- CAN4~CAN9:4 Block (可变payload)+ 4 Block(固定payload)。
- 一个controller支持的最大Mailbox个数为128。
- 一个controller支持一路RxFIFO,FIFO深度为:
- CAN0~CAN3:8 * 64 bytes;
- CAN4~CAN9:32 * 64 bytes。
- 不支持 TTController,即不支持TTCAN(一种基于CAN总线的高层协议)。
- CAN支持多核使用,可将不同的CAN控制器绑定在不同的核心上,但不支持多个核心同时使用同一个CAN控制器。
软件架构
S100芯片的Can控制器位于MCU域,负责Can数据收发。由于感知等应用位于Acore,因此部分Can数据需要通过IPC核间通信机制转发到Acore。架构保证传输可靠性,转发机制实现数据正确性检测、丢包检测和传输超时检测等机制。此外,还需要规避MCU侧高频转发小数据块导致CPU占用率过高,造成MCU实时性降低等性能问题。
S100 Can转发方案的核心流程如下:
- 首先通过MCU侧CAN2IPC模块将CAN通道映射到对应IPC通道,然后通过Acore侧CANHAL模块将IPC通道反映射为虚拟Can设备通道。最后用户通过CANHAL提供的API接口获取虚拟Can设备中的数据。其中,CAN2IPC模块为MCU侧服务,CANHAL模块为Acore侧提供给应用程序的动态库。
- CAN2IPC模块周期性采集MCU侧CAN数据,按照指定传输协议进行打包,然后通过IPC核间通信转发到Acore。Ipc instance 0中的channel0~channel7默认分配给Can转发使用。
- CANHAL模块获取来自MCU侧的IPC数据,按照指定的传输协议解析数据,并支持业务软件通过API获取原始Can帧。
数据流如上图所示:
- 外设数据通过CAN的PHY和控制器器件被MCU域CAN驱动接收后,CAN驱动将数据上报并缓存在hobot CANIF模块。
- CAN2IPC Service周期性从CANIF模块取出CAN帧,按照可靠传输协议进行打包,然后通过IPC核间通信机制转发给Acore。
- CANHAL模块获取来自MCU侧的IPC数据,按照指定的传输协议解析数据,Acore 应用程序通过CANHAL Lib库提供的API获取Can帧。
方案特性说明:
- 支持数据透传正确性校验。
- 支持数据透传丢包检测。
- 支持传输超时检测。MCU侧CAN2IPC转发数据时将数据包打上MCU侧的时间戳,Acore CANHAL接收到数据后会读取Acore的时间戳,如果传输超时会报警。注意,需要提前启动时间同步完成MCU RTC时间和Acore 网卡phc0的时间同步。
- 支持多个CAN通道并行传输。MCU侧多个CAN控制器的数据可同时被转发给Acore,Acore应用程序通过CANHAL从不同通道号读出CAN数据。
- 由于CANHAL底层通过ipc核间通信进行传输,而ipc目前不支持多个进程或者线程读写同一个通道,因此CANHAL也不支持该特性。
波特率配置
CAN的标称位时(Nominal bit timing)可以分为四个段:
- 同步段(sync_seg):用于节点间的时钟同步,所有节点在此段内检测信号边沿。其长度固定为1个时间单位(TQ)
- 传播段(prop_seg):补偿信号在物理线路上的传播延迟。其长度可调整,用于确保信号在物理介质上的传输时间
- 相位缓冲段1(phase_seg1):用于调整相位误差,确保采样点的准确性,可以扩展重同步
- 相位缓冲段2(phase_seg2):同样用于调整相位误差,但可以缩短 这些段的总和决定了CAN的总位时间,通过调整这 些段的长度,可以配置不同的波特率。 此外还有以下几个重要概念:
- 同步跳转宽度(SJW,synchronization jump width):CAN 总线同步机制中允许调整相位缓冲段的最大时间量,在 硬同步 和 重同步 过程中补偿节点间的时钟偏差,确保采样点对齐。
- 延迟补偿偏移量(Transceiver Delay Compensation Offset):仅 CAN FD 支持,用于解决数据段高速传输时的物理层时序偏移用于补偿 CAN FD 模式下 收发器环路延迟 和 信号传播时间 的固定修正值
- 采样点:CAN控制器在位时间内对总线电平进行采样的精确时刻,用于判定位的逻辑值(显性0或隐性1)
取值范围和公式计算
- 采样点计算:(sync_seg + prop_seg + phase_seg1)/(sync_seg + prop_seg + phase_seg1 + phase_seg2)×100%
- 同步段固定一个tq
- prop_seg + phase_seg1>phase_seg2
- SJW ≤ min(Phase_Seg1, Phase_Seg2)
- 当配置5M及以上波特率时,需配置补偿参数,补偿参数计算公式如下:
- TDC offset = (PropSeg + Seg1 + 1) * Fd Prescaler
配置仲裁段1M数据段5M实例说明
1. 基础参数确认
- CAN时钟频率(CAN_CLK): 40 MHz
- 目标波特率(Bit Rate): 5 Mbps
- 预分频值(Prescaler): 1(不分频)
- 单Bit时间内的TQ总数: TQ = CAN_CLK/(Bit Rate×Prescaler)=40MHz/5Mbps×1=8TQ
- 时间量子:Tq time = 1 / (40M / prescaler) = 1/40M = 25ns
2. 时间量子(TQ)分配
Sync_Seg(固定段): 1 TQ(同步段不可修改)
剩余TQ分配: Prop_Seg+Phase_Seg1+Phase_Seg2=8−1=7TQ
此处取Prop_Seg=1,Phase_Seg1=4,Phase_Seg2=2,则采样点= (Sync_Seg+Prop_Seg+Phase_Seg1)/Total TQ x 100% = (1+4+1)/8 x 100%= 75%
3. 延迟补偿偏移量(Transceiver Delay Compensation Offset)
Offset=(Prop_Seg+Phase_Seg1+1)×Prescaler = (1+4+1)×1=6TQ
SJW(Synchronization Jump Width)设置:
SJW必须满足: SJW≤min(Phase_Seg1,Phase_Seg2)=min(4,2)=2
因此,配置 SJW = 2 TQ。
4. 终配置参数
根据上述的方式同理可以计算到1M情况下的参数,由于部分寄存器获取到的值会自动加一,所以实际写入的值要减一,具体可看下表
- 5M 75%数据段配置
参数名 | 值(TQ或时间) | 需写入寄存器的值 |
---|---|---|
Sync_Seg | 1 TQ | 无需写入,固定为1 |
Prop_Seg | 1 TQ | 1 |
Phase_Seg1 | 4 TQ | 3 |
Phase_Seg2 | 2 TQ | 1 |
Prescaler | 1 | 0 |
SJW | 2 TQ | 1 |
延迟补偿偏移量 | 6 TQ | 6 |
- 1M 80%仲裁段配置
参数名 | 值(TQ或时间) | 需写入寄存器的值 |
---|---|---|
Sync_Seg | 1 TQ | 无需写入,固定为1 |
Prop_Seg | 7 TQ | 6 |
Phase_Seg1 | 8 TQ | 7 |
Phase_Seg2 | 4 TQ | 3 |
Prescaler | 2 | 1 |
SJW | 2 TQ | 1 |
5. 8M的配置相对于5M较为特殊,使用60%的采样
参数名 | 值(TQ或时间) | 需写入寄存器的值 |
---|---|---|
Sync_Seg | 1 TQ | 无需写入,固定为1 |
Prop_Seg | 1 TQ | 1 |
Phase_Seg1 | 1 TQ | 0 |
Phase_Seg2 | 2 TQ | 1 |
Prescaler | 1 | 0 |
SJW | 1 TQ | 0 |
延迟补偿偏移量 | 3 TQ | 3 |
6. 将结果更新到 配置文件中
配置文件路径:
${mcu_sdk}/Config/McalCdd/gen_s100_sip_B_mcu1/Can/src/Can_PBcfg.c
配置文件中存在两个波特率相关的重要结构体,下面以CAN5为例分别说明:
- Can_aControllerConfig:用于配置 CAN 控制器。每个控制器都有一个对应的配置项
static const Can_ControllerConfigType Can_aControllerConfig[CAN_CONTROLLER_CONFIG_COUNT]=
{
...
{
/* Controller ID configured */
(uint8)5U,
/* Hw controller Offset */
(uint8)5U,
/* Base Address */
FLEXCAN_5_BASE,
/* Activation or not */
(boolean)TRUE,
/* Bus Off uses polling or not */
(boolean)TRUE,
/* Global mask of Legacy FIFO (not used) */
(uint32)0xFFFFFFFFU,
/* Acceptance Mode of Legacy FIFO (not used)*/
CAN_LEGACY_FIFO_FORMAT_A,
/* Legacy FIFO Warning Notification */
NULL_PTR,
/* Legacy FIFO Overflow Notification */
NULL_PTR,
/* Enhanced FIFO Overflow Notification */
NULL_PTR,
/* Error interrupt enable or not */
(boolean)TRUE,
/* Can Error Notification */
Can_ErrorNotif,
/* CanFd Error Notification */
CanFd_ErrorNotif,
/* Default Baudrate ID, 4--1M+5M 5--1M+8M */
(uint16)4U,
/* Baudrate config Count*/
(uint16)6U,
/* Pointer to baudrate config Structure */
Can_aBaudrateConfig_Ctrl5,
/* Pointer to LLD structure to IP config */
&Flexcan_aCtrlConfigPB[5U],
/* HwObject count */
(uint8)9U,
/* Point to group of HwObject that referenced to the current Controller */
Can_apHwObject_Ctrl5
},
...
}
- Can_aBaudrateConfig_Ctrl5:用于定义特定控制器的波特率配置,这是一个大数组,上述步骤中生成的参数均写到这个数组中
static const Can_BaudrateConfigType Can_aBaudrateConfig_Ctrl5[6U]=
{
{
/*Enhance CBT support */
(boolean)TRUE,
/* Tx Bit Rate Switch or not */
(boolean)TRUE,
/* CanFd support */
(boolean)TRUE,
/* Nominal bit rate */ //仲裁段配置
{
(uint8)6U, // 传播段(prop_seg)
(uint8)7U, // 相位缓冲段1(phase_seg1)
(uint8)3U, // 相位缓冲段2(phase_seg2)
(uint16)3U, // 预分频值(Prescaler)
(uint8)1U //同步跳转宽度(SJW)
},
/* Data bit rate */ //数据段配置
{
(uint8)3U,
(uint8)3U,
(uint8)1U,
(uint16)3U,
(uint8)1U
},
/* Tx Arbitration Start delay */
(uint8)12U, // 延迟补偿偏移量(Transceiver Delay Compensation Offset)
/* Tranceiver Delay Disable */
(boolean)FALSE,
(uint8)0U
},
...
RDK S100默认配置了6组参数,用户可以通过修改Can_aControllerConfig中的u16DefaultBaudrateID成员值来选择波特率,下表为索引对应的波特率参数:
u16DefaultBaudrateID | 仲裁段频率 | 数据段频率 |
---|---|---|
0 | 500K | 1M |
1 | 500K | 2M |
2 | 1M | 2M |
3 | 1M | 5M(短距离:小于50m) |
4 | 1M | 5M(长距离:大于50m) |
5 | 1M | 8M |
应用sample
使用指南
MCU侧CAN2IPC源码目录:mcu/Service/HouseKeeping/can_ipc/src/hb_CAN2IPC.c
- 源码中hb_CAN2IPC_MainFunction函数被OS周期性调用,其内部通过调用hb_CAN2IPC_Proc 函数将指定的CAN控制器数据通过IPC转发到Acore。
- hb_CAN2IPC_Proc 函数中三个传入参数分别为:CAN控制器、ipc instance、ipc 指定instance下的虚拟chennel。
S100 默认将Can5~Can9 转发给ACORE,示例如下
void hb_CAN2IPC_MainFunction(void) {
hb_CAN2IPC_Proc(CANTRANS_INS0CH5_CONTROLLER, IpcConf_IpcInstance_IpcInstance_0, IpcConf_IpcInstance_0_IpcChannel_4);
//CAN4 for chassis,map to ipc channel 4 and 5,for Acore vechileio and pnc
hb_CAN2IPC_Proc(CANTRANS_INS0CH6_CONTROLLER, IpcConf_IpcInstance_IpcInstance_0, IpcConf_IpcInstance_0_IpcChannel_6);
//CAN6 for radar, map to ipc channel 6
hb_CAN2IPC_Proc(CANTRANS_INS0CH7_CONTROLLER, IpcConf_IpcInstance_IpcInstance_0, IpcConf_IpcInstance_0_IpcChannel_7);
//CAN7 for radar, map to ipc channel 7
hb_CAN2IPC_Proc(CANTRANS_INS0CH8_CONTROLLER, IpcConf_IpcInstance_IpcInstance_0, IpcConf_IpcInstance_0_IpcChannel_2);
//CAN8 for radar, map to ipc channel 2
hb_CAN2IPC_Proc(CANTRANS_INS0CH9_CONTROLLER, IpcConf_IpcInstance_IpcInstance_0, IpcConf_IpcInstance_0_IpcChannel_3);
//CAN9 for radar, map to ipc channel 3
}
Acore canhal使用可参考sample源码目录:source/hobot-io-samples/debian/app/Can,可以在S100的/app/Can目录下直接make编译使用。
以多路透传为例,目录结构如下:
$ tree /app/Can/can_multi_ch
.
├── Makefile
├── config
│ ├── channels.json
│ ├── ipcf_channel.json
│ └── nodes.json
├── main.c
└── readme.md
json文件配置主要包括3个json配置文件:node.json、ipcf_channel.json、channels.json。目前为了支持多进程,各个进程都会去当前路径下的config目录下寻找这3个配置文件。
node.json负责创建虚拟Can设备节点给CANHAL API访问。关键配置选项包括:
- channel_id字段指定该虚拟Can设备从ipc配置文件ipcf_channel.json中哪一个节点获取数据。
- target字段表示该虚拟Can设备节点的名称,CANHAL API通过该名称访问指定的节点。
- enable字段表示该节点是否使能。
{
"nodes" : [
{
"id" : 0,
"enable" : true,
"mode_comment" : "value_table: R, W, RW",
"mode" : "RW",
"target" : "can6_ins0ch6",
"clk_source" : "/dev/hrtc0",
"io_channel" : {
"device_type_comment" : "value_table: can, eth, ipcf, spi",
"device_type" : "ipcf",
"channel_id" : 0
},
"raw_protocol" : "built_1.0"
},
.....
{
"id" : 4,
"enable" : true,
"mode_comment" : "value_table: R, W, RW",
"mode" : "RW",
"target" : "can5_ins0ch4",
"clk_source" : "/dev/hrtc0",
"io_channel" : {
"device_type_comment" : "value_table: can, eth, ipcf, spi",
"device_type" : "ipcf",
"channel_id" : 4
},
"raw_protocol" : "built_1.0"
}
]
}
ipcf_channel.json将node.json中用到的ipc节点映射到具体的instance和channel。
{
"enable" : true,
"libipcf_path" : "/usr/hobot/lib/libhbipcfhal.so.1",
"channels" : [
{
"id" : 0,
"channel" : {
"name" : "can6_ins0ch6",
"instance": 0,
"channel": 6,
"fifo_size": 64000,
"fifo_type": 0,
"pkg_size_max": 4096,
"dev_path":"/dev/ipcdrv",
"dev_name":"ipcdrv",
"recv_timeout" : 4000
}
},
......
{
"id" : 4,
"channel" : {
"name" : "can5_ins0ch4",
"instance": 0,
"channel": 4,
"fifo_size": 64000,
"fifo_type": 0,
"pkg_size_max": 4096,
"dev_path":"/dev/ipcdrv",
"dev_name":"ipcdrv",
"recv_timeout" : 4000
}
}
]
}
channels.json指定ipc配置文件,用 户一般不需要更改。
{
"io_channels" : {
"ipcf" : "./config/ipcf_channel.json"
}
}
Acore无法直接操作CAN外设,需要通过借助Ipc模块来中转数据,与外设通道的映射关系可以查阅 MCU IPC使用指南 中的IPC 使用情况章节。
Acore应用程序通过CANHAL获取MCU侧Can帧的流程伪代码如下:
void send_frame_data(void *arg)
{
for (int count = 1000; count > 0; count--) {
canSendMsgFrame(test_params->target, &frame[0], &pack);
}
}
void *recv_frame_data(void *arg)
{
while (!exit_flag) {
canRecvMsgFrame(target, frame, &pack); // non blocking
}
}
int main(int argc, char *argv[])
{
ret = canInit();
pthread_create(&send_thread, NULL, send_frame_data, &tx_params);
pthread_create(&rx_threads[i], NULL, recv_frame_data, &rx_params[i])
pthread_join(send_thread, NULL);
pthread_join(rx_threads[i], NULL);
canDeInit();
}
- 首先执行canInit()完成初始化,然后创建发送线程和接收线程
- 发送线程调用canSendMsgFrame()发送数据包,接收线程调用canRecvMsgFrame()接收数据包,其中target参数为json文件中配置好的通道。
- pack信息包含这一包数据的信息,包括can帧数量、mcu侧的时间戳以及acore侧的monotic时间戳等信息。
- canhal会从这一包ipc数据中解析出can帧,用户通过frame指针读取出所有can帧。
- 最后执行canDeInit()释放资源。
ACORE侧实例说明
简单的can收发sample
目录介绍
// /app/Can/can_send
.
├── Makefile // 主编译脚本
├── canhal_send.c // 发送一帧标准帧数据
└── config // 配置文件目录
├── channels.json // 通道映射配置文件
├── ipcf_channel.json // 通道映射配置文件
└── nodes.json // 通道映射配置文件
// /app/Can/can_get
.
├── Makefile // 主编译脚本
├── canhal_get.c // while 1循环,接收数据
└── config // 配置文件目录
├── channels.json // 通道映射配置文件
├── ipcf_channel.json // 通道映射配置文件
└── nodes.json // 通道映射配置文件