文章目录
- 前言
- 测试用的硬件连接
- Vivado 配置
- Vitis MicroBlaze CANFD 代码
- 测试代码
- 测试截图
- Github Link
前言
官网: CAN with Flexible Data Rate (CAN FD) (xilinx.com)
特征:
- 支持8Mb/s的CANFD
- 多达 3 个数据位发送器延迟补偿(TDC, transmitter delay compensation)
- 32-deep TX FIFO, 2 * 64-deep RX FIFO, 32 ID Filter
- 接收支持 顺序(Sequential)模式 和 邮箱(Mailbox)模式
- Bus-Off 恢复模式: 自动恢复 或 用户干预的自动恢复
参考:
- How to configure CAN/CANFD clocks and identify the CAN nodes (xilinx.com)
- canfd 默认文档和示例路径:
C:\Xilinx\Vitis\2023.2\data\embeddedsw\XilinxProcessorIPLib\drivers\canfd_v2_8
本文仅作学习交流使用.
测试用的硬件连接
硬件:
- FPGA: xc7a35tfgg484-2
- 时钟: 50MHz, 有源, 单端, 3V3, 引脚Y18
- 复位: 低电平复位, 3V3, 引脚F20
- UART: TX G16, RX G15
- CANFD: TX F16, RX E17
Vivado 配置
如图:
时钟:
- clk_out1, 160MHz, can_clk_x2, 也是其它组件的时钟
- clk_out2, 80MHz, can_clk, 是CAN和CANFD配置的主时钟
- 低电平复位
调试串口:
- 9600 or 115200 bps
时钟的配置截图
CANFD的配置, 收发FIFO Depth都是32, 接收使用顺序模式, 不用FIFO1
可适当减少RX FIFO-0 Depth以减少RAM消耗. 含MicroBlaze 64K, 一路CANFD, 没有使用任何外部RAM的消耗如下(xc7a35tfgg484-2):
Vitis MicroBlaze CANFD 代码
初始化:
- 查找并初始化已有CANFD配置
- 进入配置模式, 配置位速率, 采样点, TDC, 滤波器, 关闭BRS
- 设置收发错误事件回调, 使能中断
- 进入正常模式
- 注意在C文件中编译, 不然Cpp中 XSetupInterruptSystem 会报错
error: invalid conversion from 'void (*)(void*)' to 'void*' [-fpermissive]
参考代码如下:
int bsp_canfd_init(XCanFd *InstancePtr, uint32_t BaseAddress, uint32_t BaudRate,
float SamplePoint, uint32_t FastBaudRate,
float FastSamplePoint) {
XCanFd_Config *ConfigPtr = XCanFd_LookupConfig(BaseAddress);
if (ConfigPtr == NULL) {
bsp_canfd_debug_printf("Error: XCanFd_LookupConfig returned NULL\n");
return -1;
} else {
bsp_canfd_debug_printf("XCanFd_Config:\n");
bsp_canfd_debug_printf(" BaseAddress: 0x%08X\n", ConfigPtr->BaseAddress);
bsp_canfd_debug_printf(" Rx_Mode: %s\n",
ConfigPtr->Rx_Mode ? "Mailbox" : "Sequential");
bsp_canfd_debug_printf(" NumofRxMbBuf: %d\n", ConfigPtr->NumofRxMbBuf);
bsp_canfd_debug_printf(" NumofTxBuf: %d\n", ConfigPtr->NumofTxBuf);
bsp_canfd_debug_printf(" IntrId: %d\n", ConfigPtr->IntrId);
bsp_canfd_debug_printf(" IntrParent: 0x%08X\n", ConfigPtr->IntrParent);
}
int Status =
XCanFd_CfgInitialize(InstancePtr, ConfigPtr, ConfigPtr->BaseAddress);
if (Status != XST_SUCCESS) {
bsp_canfd_debug_printf("Error: XCanFd_CfgInitialize returned %d\n", Status);
return -2;
} else {
bsp_canfd_debug_printf("XCanFd_CfgInitialize: Success\n");
}
// config mode
XCanFd_EnterMode(InstancePtr, XCANFD_MODE_CONFIG);
while (XCanFd_GetMode(InstancePtr) != XCANFD_MODE_CONFIG)
;
bsp_canfd_debug_printf("XCanFd_EnterMode: XCANFD_MODE_CONFIG\n");
// 80MHz / (3 + 1) = 20MHz, 20MHz / (1 + (30 + 1) + (7 + 1)) = 500KHz
// XCanFd_SetBitTiming(InstancePtr, 8, 7, 30);
XCanFd_SetBaudRatePrescaler(InstancePtr, 0x3);
u16 total_tq = 20000000 / BaudRate;
u16 tseg1 = (u16)(SamplePoint * total_tq) - 2;
u8 tseg2 = (u8)(total_tq - tseg1 - 3);
u8 sjw = tseg2 + 1;
XCanFd_SetBitTiming(InstancePtr, sjw, tseg2, tseg1);
// 80MHz, 80 / (1 + (30 + 1) + (7 + 1)) = 2MHz
// XCanFd_SetFBitTiming(InstancePtr, 8, 7, 30);
XCanFd_SetFBaudRatePrescaler(InstancePtr, 0x0);
u16 ftotal_tq = 80000000 / FastBaudRate;
u8 ftseg1 = (u8)(FastSamplePoint * ftotal_tq) - 2;
u8 ftseg2 = (u8)(ftotal_tq - ftseg1 - 3);
u8 fsjw = ftseg2 + 1;
XCanFd_SetFBitTiming(InstancePtr, fsjw, ftseg2, ftseg1);
// TDC, 0~31
// XCanFd_Set_Tranceiver_Delay_Compensation(InstancePtr, 0x3);
XCanFd_SetBitRateSwitch_DisableNominal(InstancePtr);
bsp_canfd_debug_printf("XCanFd: %d@0.%d, %d@0.%d\n", BaudRate,
(int)(SamplePoint * 1000), FastBaudRate,
(int)(FastSamplePoint * 1000));
if (XCANFD_GET_RX_MODE(InstancePtr) == 0) {
bsp_canfd_debug_printf(
"RX_MODE Sequential Filter: XCANFD_AFR_UAF_ALL_MASK\n");
XCanFd_AcceptFilterDisable(InstancePtr, XCANFD_AFR_UAF_ALL_MASK);
XCanFd_AcceptFilterEnable(InstancePtr, XCANFD_AFR_UAF_ALL_MASK);
} else {
bsp_canfd_debug_printf("RX_MODE Mailbox Filter: Need to be implemented\n");
}
XCanFd_SetHandler(InstancePtr, XCANFD_HANDLER_SEND, (void *)SendHandler,
(void *)InstancePtr);
XCanFd_SetHandler(InstancePtr, XCANFD_HANDLER_RECV, (void *)RecvHandler,
(void *)InstancePtr);
XCanFd_SetHandler(InstancePtr, XCANFD_HANDLER_ERROR, (void *)ErrorHandler,
(void *)InstancePtr);
XCanFd_SetHandler(InstancePtr, XCANFD_HANDLER_EVENT, (void *)EventHandler,
(void *)InstancePtr);
Status =
XSetupInterruptSystem(InstancePtr, &XCanFd_IntrHandler, ConfigPtr->IntrId,
ConfigPtr->IntrParent, XINTERRUPT_DEFAULT_PRIORITY);
if (Status != XST_SUCCESS) {
bsp_canfd_debug_printf("Error: XSetupInterruptSystem returned %d\n",
Status);
return 1;
} else {
bsp_canfd_debug_printf("XSetupInterruptSystem: Success\n");
}
XCanFd_InterruptEnable(InstancePtr, XCANFD_IXR_ALL);
XCanFd_EnterMode(InstancePtr, XCANFD_MODE_NORMAL);
while (XCanFd_GetMode(InstancePtr) != XCANFD_MODE_NORMAL)
;
bsp_canfd_debug_printf("XCanFd_EnterMode: XCANFD_MODE_NORMAL\n");
return 0;
}
发送套用Linux SocketCAN
接口, 毕竟这个用的人多, 易于理解. 因为发送FIFO深度为32, 所以, 正常情况下连发32帧无需插入延时, 也无需自己处理发送队列, 这已经够大部分情况使用了.
int bsp_canfd_send(XCanFd *InstancePtr, struct canfd_frame *frame) {
bool is_extended = frame->can_id & CAN_EFF_FLAG ? true : false;
bool is_remote = frame->can_id & CAN_RTR_FLAG ? true : false;
bool is_fd = frame->flags & CANFD_FDF ? true : false;
bool is_brs = frame->flags & CANFD_BRS ? true : false;
bool is_esi = frame->flags & CANFD_ESI ? true : false;
u32 TxFrame[CANFD_MTU];
TxFrame[0] = XCanFd_CreateIdValue(
CAN_SFF_MASK & (is_extended ? ((frame->can_id & CAN_EFF_MASK) >> 18)
: frame->can_id),
is_extended ? 1 : (u32)is_remote, (u32)is_extended,
(u32)is_extended ? (frame->can_id & 0x3FFFF) : 0,
is_extended ? (u32)is_remote : 0);
if ((!is_fd) && (!is_brs)) {
TxFrame[1] = XCanFd_CreateDlcValue(frame->len);
} else {
if (is_brs) {
TxFrame[1] =
XCanFd_Create_CanFD_Dlc_BrsValue(XCanFd_GetLen2Dlc(frame->len));
} else {
TxFrame[1] = XCanFd_Create_CanFD_DlcValue(XCanFd_GetLen2Dlc(frame->len));
}
}
u8 *FramePtr = (u8 *)(&TxFrame[2]);
for (int i = 0; i < frame->len; i++) {
FramePtr[i] = frame->data[i];
}
u32 TxBufferNumber;
int status = XCanFd_Send(InstancePtr, TxFrame, &TxBufferNumber);
if (status == XST_FIFO_NO_ROOM) {
bsp_canfd_debug_printf("Error: XCanFd_Send returned XST_FIFO_NO_ROOM\n");
return -1;
}
if (status != XST_SUCCESS) {
bsp_canfd_debug_printf("Error: XCanFd_Send returned %d\n", status);
return -2;
}
return 0;
}
接收的示例, 类似 candump:
- 标准帧是
%03X
打印, 扩展帧是%08X
打印. - 远程帧打
R
, 数据帧打D
- CANFD打
F
且长度为[%02d]
, Classic CAN打-
且数据长度为[%d]
- 开启了BRS打
B
, 否则打-
- ESI 暂不关心
- 当然这里打印接收只是用于测试, 实际不要在中断中阻塞打印
static void RecvHandler(void *CallBackRef) {
XCanFd *CanPtr = (XCanFd *)CallBackRef;
int Status;
u32 RxFrame[CANFD_MTU];
/* Check for the design 1 - MailBox 0 - Sequential */
if (XCANFD_GET_RX_MODE(CanPtr) == 1) {
Status = XCanFd_Recv_Mailbox(CanPtr, RxFrame);
} else {
Status = XCanFd_Recv_Sequential(CanPtr, RxFrame);
}
u32 id1 = (RxFrame[0] >> (u32)XCANFD_IDR_ID1_SHIFT) & (u32)0x7FF;
u32 is_extended = (RxFrame[0] >> (u32)XCANFD_IDR_IDE_SHIFT) & (u32)0x1;
u32 id2 = (RxFrame[0] >> (u32)XCANFD_IDR_ID2_SHIFT) & (u32)0x3FFFF;
u32 is_remote = is_extended
? (RxFrame[0] & 0x01)
: ((RxFrame[0] >> (u32)XCANFD_IDR_SRR_SHIFT) & (u32)0x1);
/* Get the Dlc inthe form of bytes */
u32 len = XCanFd_GetDlc2len(RxFrame[1] & XCANFD_DLCR_DLC_MASK, EDL_CANFD);
if (Status != XST_SUCCESS) {
bsp_canfd_debug_printf("Error: XCanFd_Recv returned %d\n", Status);
return;
}
u32 is_brs = RxFrame[1] & XCANFD_DLCR_BRS_MASK ? 1 : 0;
u32 is_fdf = RxFrame[1] & XCANFD_DLCR_EDL_MASK ? 1 : 0;
// bsp_canfd_debug_printf("%08X ", RxFrame[0]);
u8 *FramePtr = (u8 *)(&RxFrame[2]);
if (is_extended) {
bsp_canfd_debug_printf("%08X ", id1 << 18 | id2);
} else {
bsp_canfd_debug_printf("%03X ", id1);
}
if (is_remote) {
bsp_canfd_debug_printf("R [%d]", len);
} else {
bsp_canfd_debug_printf("D ");
if (is_fdf) {
bsp_canfd_debug_printf("F ");
} else {
bsp_canfd_debug_printf("- ");
}
if (is_brs) {
bsp_canfd_debug_printf("B ");
} else {
bsp_canfd_debug_printf("- ");
}
if ((!is_fdf) && (!is_brs)) {
bsp_canfd_debug_printf("[%d] ", len);
} else {
bsp_canfd_debug_printf("[%02d] ", len);
}
for (int i = 0; i < len; i++) {
bsp_canfd_debug_printf("%02X ", FramePtr[i]);
}
}
bsp_canfd_debug_printf("\n");
}
测试代码
如下:
- 位速率 500K@80%, 4M@80%, 如果出现收发不正常, 可能需要微调初始化中的TDC, 检查CAN收发器以及终端电阻
- 对数据帧远程帧, CAN或CANFD或CANFD BRS都做了测试
- 连续发送32帧
int main() {
xil_printf("============================================\n");
XCanFd CanFd0;
int Status =
bsp_canfd_init(&CanFd0, XPAR_CANFD_0_BASEADDR, 500000, 0.8, 4000000, 0.8);
if (Status != 0) {
xil_printf("Error: bsp_canfd_init returned %d\n", Status);
return -1;
}
for (int i = 0; i < 4; i++) {
struct canfd_frame frame = {
.can_id = 0x123,
.len = 8,
.flags = 0,
.__res0 = 0,
.__res1 = 0,
.data = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 0x10, 0x11, 0x12, 0x13,
0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D,
0x1E, 0x1F, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27,
0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 0x30, 0x31,
0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B,
0x3C, 0x3D, 0x3E, 0x3F},
};
// std can
frame.can_id = 0x123;
frame.len = 8;
frame.flags = 0;
int Status = bsp_canfd_send(&CanFd0, &frame);
// std can remote
frame.can_id = 0x124 | CAN_RTR_FLAG;
frame.len = 8;
frame.flags = 0;
Status |= bsp_canfd_send(&CanFd0, &frame);
// std can fd
frame.can_id = 0x125;
frame.len = 64;
frame.flags = CANFD_FDF;
Status |= bsp_canfd_send(&CanFd0, &frame);
// std can fd brs
frame.can_id = 0x126;
frame.len = 64;
frame.flags = CANFD_FDF | CANFD_BRS;
Status |= bsp_canfd_send(&CanFd0, &frame);
// ext can
frame.can_id = 0x12345678 | CAN_EFF_FLAG;
frame.len = 8;
frame.flags = 0;
Status |= bsp_canfd_send(&CanFd0, &frame);
// ext can remote
frame.can_id = 0x12345679 | CAN_EFF_FLAG | CAN_RTR_FLAG;
frame.len = 8;
frame.flags = 0;
Status |= bsp_canfd_send(&CanFd0, &frame);
// ext can fd
frame.can_id = 0x1234567A | CAN_EFF_FLAG;
frame.len = 64;
frame.flags = CANFD_FDF;
Status |= bsp_canfd_send(&CanFd0, &frame);
// ext can fd brs
frame.can_id = 0x1234567B | CAN_EFF_FLAG;
frame.len = 64;
frame.flags = CANFD_FDF | CANFD_BRS;
Status |= bsp_canfd_send(&CanFd0, &frame);
if (Status != 0) {
xil_printf("Error: bsp_canfd_send %d returned %d\n", i, Status);
return -1;
}
}
while (1) {
}
return 0;
}
测试截图
设置:
MCU连续发出的32帧报文:
逻辑分析仪上可以看出来两帧之间卡着标准的3 ITM来的
出现BRS时的情况, 左边500K, 右边4M
CAN分析仪向FPGA发送测试:
Github Link
domain_controller_orin_x2_tc397/axi_canfd_microblaze at main · weifengdq/domain_controller_orin_x2_tc397 (github.com)