全部例程

CAN-控制器局域网络接口

W55MH32

更新于 2026年8月27日

本篇总结

使用 CAN,收发数据帧并通过中断打印报文。

🔑 KEIL MDK、UART调试、CAN

硬件平台:本例程基于 W55MH32L-EVB 开发板运行。

采购推荐:点击前往 WIZnet 官方淘宝店铺 选购 W55MH32L-EVB

W55MH32 内置 1 个 CAN 控制器,支持 CAN 2.0A/B 协议标准,最高速率 1Mbps。本示例演示 CAN 控制器的初始化、过滤器配置,每秒发送数据帧,接收中断打印报文内容。

前置工具准备

开始本例程前,请确认以下工具已安装:

硬件连接

本例程使用 CAN1 的 TX(PB9) 和 RX(PB8) 引脚(通过 GPIO_Remap1_CAN1 重映射)。在环回模式下无需外部 CAN 收发器即可自测。如需连接外部 CAN 总线,需通过 CAN 收发器芯片(如 TJA1050)连接到 CANH/CANL 总线。USART1 用于输出调试信息。

引脚功能方向模式
PB9CAN1_TX开发板 → 收发器复用推挽输出 (AF_PP)
PB8CAN1_RX收发器 → 开发板上拉输入 (IPU)
PA9USART1_TX开发板 → PC复用推挽输出 (AF_PP)
PA10USART1_RXPC → 开发板浮空输入 (IN_FLOATING)

工程结构

工程位于 SDK 内 1.SDK\ModuleDemo\CAN\,核心文件如下:

CAN/
├── USER/
│   ├── main.c            ← CAN初始化调用 + 收发测试 + printf重定向
│   ├── w55mh32_conf.h    ← 外设头文件集中包含
│   ├── w55mh32_it.c/h    ← 中断服务函数
│   ├── system_w55mh32.c/h← 系统初始化(时钟配置)
│   └── startup_w55mh32.s ← 启动文件
└── SYSTEM/
    ├── can/
    │   └── can.c/h        ← CAN初始化 + 发送/接收函数
    └── delay/
        └── delay.c/h      ← SysTick 延时函数

步骤1:CAN 初始化与过滤器配置

函数:CAN_Mode_Init() — 配置 PB9(TX)/PB8(RX) 引脚(GPIO_Remap1_CAN1 重映射)、CAN 波特率为 500Kbps、过滤器接收所有标准帧。

uint8_t CAN_Mode_Init(uint8_t tsjw, uint8_t tbs2, uint8_t tbs1, uint16_t brp, uint8_t mode)
{
    GPIO_InitTypeDef      GPIO_InitStructure;
    CAN_InitTypeDef       CAN_InitStructure;
    CAN_FilterInitTypeDef CAN_FilterInitStructure;

    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1, ENABLE);

    /* PB9: CAN_TX → 复用推挽输出(重映射) */
    GPIO_PinRemapConfig(GPIO_Remap1_CAN1, ENABLE);
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_9;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* PB8: CAN_RX → 上拉输入(重映射) */
    GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    /* CAN 基本参数 */
    CAN_InitStructure.CAN_TTCM = DISABLE;
    CAN_InitStructure.CAN_ABOM = DISABLE;
    CAN_InitStructure.CAN_AWUM = DISABLE;
    CAN_InitStructure.CAN_NART = ENABLE;
    CAN_InitStructure.CAN_RFLM = DISABLE;
    CAN_InitStructure.CAN_TXFP = DISABLE;
    CAN_InitStructure.CAN_Mode = mode;
    CAN_InitStructure.CAN_SJW       = tsjw;
    CAN_InitStructure.CAN_BS1       = tbs1;
    CAN_InitStructure.CAN_BS2       = tbs2;
    CAN_InitStructure.CAN_Prescaler = brp;
    CAN_Init(CAN1, &CAN_InitStructure);

    /* 过滤器0:接收所有标准帧 */
    CAN_FilterInitStructure.CAN_FilterNumber         = 0;
    CAN_FilterInitStructure.CAN_FilterMode           = CAN_FilterMode_IdMask;
    CAN_FilterInitStructure.CAN_FilterScale          = CAN_FilterScale_32bit;
    CAN_FilterInitStructure.CAN_FilterIdHigh         = 0x0000;
    CAN_FilterInitStructure.CAN_FilterIdLow          = 0x0000;
    CAN_FilterInitStructure.CAN_FilterMaskIdHigh     = 0x0000;
    CAN_FilterInitStructure.CAN_FilterMaskIdLow      = 0x0000;
    CAN_FilterInitStructure.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0;
    CAN_FilterInitStructure.CAN_FilterActivation     = ENABLE;
    CAN_FilterInit(&CAN_FilterInitStructure);

    return 0;
}
  

波特率计算公式:CAN_BaudRate = Fpclk1 / ((1 + BS1 + BS2) × brp)。本例程调用 CAN_Mode_Init(CAN_SJW_1tq, CAN_BS2_8tq, CAN_BS1_9tq, 4, CAN_Mode_Normal),Fpclk1=36MHz,则波特率 = 36M / ((1+9+8) × 4) = 36M / 72 = 500Kbps。过滤器设置为 IdMask 模式且掩码全 0,表示接收所有 ID 的报文。引脚通过 GPIO_Remap1_CAN1 重映射到 PB9(TX)/PB8(RX)。

如果你想自定义

  • 改波特率 → 修改 CAN_PrescalerCAN_BS1CAN_BS2 参数
  • 改工作模式CAN_Mode_LoopBack(环回自测)、CAN_Mode_Silent(静默监听)、CAN_Mode_Normal(正常通信)
  • 过滤特定ID → 设置 CAN_FilterIdHigh/Low 和掩码,只接收指定 ID 的报文

步骤2:CAN 发送与接收中断

Can_Send_Msg() 构造标准数据帧(ID=0x12)并发送,Can_Receive_Msg() 轮询查询 FIFO0 中是否有报文并读取。

/* CAN 发送函数(ID=0x12,标准数据帧) */
uint8_t Can_Send_Msg(uint8_t *msg, uint8_t len)
{
    uint8_t  mbox;
    uint16_t i = 0;
    CanTxMsg TxMessage;
    TxMessage.StdId = 0x12;            // 标准标识符
    TxMessage.ExtId = 0x12;            // 扩展标识符
    TxMessage.IDE   = CAN_Id_Standard; // 标准帧
    TxMessage.RTR   = CAN_RTR_Data;    // 数据帧
    TxMessage.DLC   = len;             // 数据长度
    for (i = 0; i < len; i++)
        TxMessage.Data[i] = msg[i];
    mbox = CAN_Transmit(CAN1, &TxMessage);
    i    = 0;
    while ((CAN_TransmitStatus(CAN1, mbox) == CAN_TxStatus_Failed) && (i < 0XFFF)) i++;
    if (i >= 0XFFF) return 1;
    return 0;
}

/* CAN 接收查询函数 */
uint8_t Can_Receive_Msg(u8 *buf)
{
    uint32_t i;
    CanRxMsg RxMessage;
    if (CAN_MessagePending(CAN1, CAN_FIFO0) == 0) return 0; // 无数据
    CAN_Receive(CAN1, CAN_FIFO0, &RxMessage);               // 读取数据
    for (i = 0; i < 8; i++)
        buf[i] = RxMessage.Data[i];
    return RxMessage.DLC;
}
  

发送时构造 CanTxMsg 结构体设置 ID(0x12)、帧类型(标准数据帧)、数据长度(8字节)和数据内容,调用 CAN_Transmit() 发送并等待发送完成。接收端采用轮询方式,通过 CAN_MessagePending() 检查 FIFO0 是否有报文,有则调用 CAN_Receive() 读取。

步骤3:主函数逻辑

函数:main() — 初始化 CAN(500Kbps,正常模式)和串口,串口收到 's' 指令时发送 8 字节数据帧,同时轮询接收 CAN 报文。

int main(void)
{
    uint8_t           res, i, key;
    uint8_t           canbuf[8];
    RCC_ClocksTypeDef clocks;

    delay_init();
    UART_Configuration(115200);
    RCC_GetClocksFreq(&clocks);

    printf("\n");
    printf("SYSCLK: %3.1fMhz, HCLK: %3.1fMhz, PCLK1: %3.1fMhz, "
           "PCLK2: %3.1fMhz, ADCCLK: %3.1fMhz\n",
           (float)clocks.SYSCLK_Frequency / 1000000,
           (float)clocks.HCLK_Frequency / 1000000,
           (float)clocks.PCLK1_Frequency / 1000000,
           (float)clocks.PCLK2_Frequency / 1000000,
           (float)clocks.ADCCLK_Frequency / 1000000);
    printf("CAN Normal Test.\n");

    /* CAN 初始化:500Kbps,正常模式 */
    CAN_Mode_Init(CAN_SJW_1tq, CAN_BS2_8tq, CAN_BS1_9tq, 4, CAN_Mode_Normal);

    while (1)
    {
        /* 串口收到 's' 指令时发送数据 */
        if (GetCmd() == 's')
        {
            for (i = 0; i < DATA_LEN; i++)
            {
                canbuf[i] = 0x5A + i;
            }
            res = Can_Send_Msg(canbuf, 8); // 发送 8 字节
            if (res == 0)
                printf("Can Normal Send Data Success\n");
            else
                printf("Can Normal Send Data Fail\n");
        }

        /* 轮询查询是否收到数据 */
        key = Can_Receive_Msg(canbuf);
        if (key)
        {
            printf("Can Normal Recv Data Success\n");
            for (i = 0; i < key; i++)
            {
                printf("canbuf[%d] = 0x%x\n", i, canbuf[i]);
            }
        }
    }
}
  

主循环中,串口收到 's' 指令时发送 8 字节数据(0x5A~0x61),ID=0x12。同时持续轮询 Can_Receive_Msg() 检查是否收到 CAN 报文,收到则逐字节打印。

运行验证

打开 WIZ UartTool V1.0,波特率 115200 / 8N1。下载固件并按 RESET 后,串口先打印系统时钟信息和 "CAN Normal Test."。在串口工具中输入字符 's' 发送,开发板收到后通过 CAN 总线发送 8 字节数据帧(ID=0x12),并打印 "Can Normal Send Data Success"。若总线上有其他节点发来数据,则打印接收内容。如使用 CAN 分析仪软件,可看到总线上的数据帧。

例程在 WIZ UartTool V1.0 上的输出信息,输入“s”执行 CAN 发送数据
CAN 分析仪显示总线数据帧

常见问题

Q: CAN 通信无数据收发

确认 CAN 模式设置正确。环回模式(LoopBack)下无需外部收发器即可自测。正常模式(Normal)下必须连接 CAN 收发器芯片和终端电阻(120Ω)。检查波特率参数是否与总线一致。

Q: 接收不到数据

本例程采用轮询方式接收,确认 Can_Receive_Msg()CAN_MessagePending(CAN1, CAN_FIFO0) 返回值是否大于 0。若需使用中断方式接收,需使能 CAN_IT_FMP0 并在 USB_LP_CAN1_RX0_IRQHandler 中处理。

Q: 波特率不匹配导致通信失败

CAN 波特率 = APB1_CLK / (Prescaler × (1 + BS1 + BS2))。确保所有节点使用相同的波特率。W55MH32 的 APB1 时钟默认 36MHz,可通过 RCC_GetClocksFreq 确认。

总结 

本文基于 W55MH32 的 CAN 控制器实现了数据帧的发送与轮询接收:

  • CAN 初始化:配置 PB9(TX)/PB8(RX) 引脚(GPIO_Remap1_CAN1 重映射),波特率 500Kbps,正常模式
  • 过滤器配置:IdMask 模式,过滤器0,掩码全 0 接收所有 ID 的标准数据帧
  • 串口收到 's' 指令时发送 8 字节数据帧(ID=0x12,数据 0x5A~0x61),轮询接收并打印报文内容
  • 支持 CAN 2.0A/B 协议标准,适用于汽车电子和工业控制领域的高可靠性通信

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板

Andy

W5500 技术支持专家