MCP2515调试心得

1. MCP2515 芯片解析

1.1 外部时钟源

  • 使用晶振
    外部晶振由 OSC1 和 OSC2 引脚输入
    在这里插入图片描述
    支持 8M 和 16M 两种时钟,详细参考下图:
    在这里插入图片描述
  • 使用外部时钟源
    在这里插入图片描述
    需要注意 OSC1 前面添加了一个反相器,猜测原因是为了提高外部时钟的驱动力。

在某次项目,使用 STM32 的 MCO 直接输出 8M 的 HSE 连接进 MCP2515 的 OSC1 出现无法驱动的情况,可见很需要加一个反相器

  • 第三种,方式不清楚没有使用过。
    在这里插入图片描述

1.2 可采用连续传输提高效率

网上大部分的 MCP2515 芯片驱动是发送一个字节,先发送该字节要写入的寄存器地址,这种方式在操作连续地址的多个寄存器时的效率很低。

发送数据时,使用 TX0 为例:

参阅 MCP2515 手册可知与 TX0 相关的寄存器地址是连续递增的,如下:

#define MCP2515_TXB0CTRL            0x30
#define MCP2515_TXB0SIDH            0x31
#define MCP2515_TXB0SIDL            0x32
#define MCP2515_TXB0EID8            0x33
#define MCP2515_TXB0EID0            0x34
#define MCP2515_TXB0DLC             0x35
#define MCP2515_TXB0D0              0x36
#define MCP2515_TXB0D1              0x37
#define MCP2515_TXB0D2              0x38
#define MCP2515_TXB0D3              0x39
#define MCP2515_TXB0D4              0x3A
#define MCP2515_TXB0D5              0x3B
#define MCP2515_TXB0D6              0x3C
#define MCP2515_TXB0D7              0x3D

其实 TXB0SIDH 到 MCP2515_TXB0D7 是可以通过只设置一次地址,连续传输将数据写入到 MCP2515 里。
具体操作的编码可以这样实现:

uint32_t _hdl_mcp2515_send(uint32_t mcp2515, uint32_t recv_id, uint8_t id_type, uint8_t *sendBuff, uint32_t count)
{
	uint32_t returnValue = 0;
    _id_reg idReg;
    _ctrl_status_t ctrlStatus;
    uint8_t instruction_load_buffer;
    uint8_t instruction_request_to_send;

    idReg.tempSIDH = 0;
    idReg.tempSIDL = 0;
    idReg.tempEID8 = 0;
    idReg.tempEID0 = 0;
  
    ctrlStatus.ctrl_status = _hdl_mcp2515_read_status_instruction(mcp2515);

    /* 目前 Transmission 查找并传送未 Pending 的缓冲器。 */
    if (ctrlStatus.TXB1REQ != 1)
    {
        instruction_load_buffer = MCP2515_LOAD_TXB1SIDH;
        instruction_request_to_send = MCP2515_RTS_TX1;
        returnValue = 1;
    }
    else if (ctrlStatus.TXB2REQ != 1)
    {
        instruction_load_buffer = MCP2515_LOAD_TXB2SIDH;
        instruction_request_to_send = MCP2515_RTS_TX2;
        returnValue = 1;
    } 
    else if (ctrlStatus.TXB0REQ != 1)
    {
        instruction_load_buffer = MCP2515_LOAD_TXB0SIDH;
        instruction_request_to_send = MCP2515_RTS_TX0;
        returnValue = 1;
    }

    if (returnValue)
    {
        /* 转换成符合 ID 类型 */
        _hdl_mcp2515_convert_can_id_to_register(recv_id, id_type, &idReg);
        /* Loading 要传送到 Tx Buffer */
        _hdl_mcp2515_load_tx_buffer(mcp2515, instruction_load_buffer, &idReg, sendBuff, count);
        /* 请求传送Tx Buffer的数据 */
        _hdl_mcp2515_request_to_send(mcp2515, instruction_request_to_send);
    }

    return returnValue;
}

  • _hdl_mcp2515_convert_can_id_to_register :将 can 的 id 转成对应的寄存器 id;
  • _hdl_mcp2515_load_tx_buffer :把数据填充到对应的长度寄存器 DLC 和 D0 ~ D7 寄存器;
  • _hdl_mcp2515_request_to_send: 发送命令将 TXnBuffer 中的数据发生出去;
    在这里插入图片描述
static void _hdl_mcp2515_convert_can_id_to_register(uint32_t tempPassedInID, uint8_t canIdType, _id_reg_t passedIdReg) 
{
    uint8_t wipSIDL = 0;

    if (canIdType == dEXTENDED_CAN_MSG_ID_2_0B) 
    {
        // EID0
        passedIdReg->tempEID0 = 0xFF & tempPassedInID;
        tempPassedInID = tempPassedInID >> 8;

        // EID8
        passedIdReg->tempEID8 = 0xFF & tempPassedInID;
        tempPassedInID = tempPassedInID >> 8;

        // SIDL
        wipSIDL = 0x03 & tempPassedInID;
        tempPassedInID = tempPassedInID >> 2;
        wipSIDL = ((0x07 & tempPassedInID) << 5) | wipSIDL;
        wipSIDL = wipSIDL | 0x08;
        passedIdReg->tempSIDL = 0xEB & wipSIDL;

        // SIDH
        tempPassedInID = tempPassedInID >> 3;
        passedIdReg->tempSIDH = 0xFF & tempPassedInID;
    }
    else
    {
        passedIdReg->tempEID8 = 0;
        passedIdReg->tempEID0 = 0;
        tempPassedInID = tempPassedInID << 5;
        passedIdReg->tempSIDL = 0xFF & tempPassedInID;
        tempPassedInID = tempPassedInID >> 8;
        passedIdReg->tempSIDH = 0xFF & tempPassedInID;
    }
}

void _hdl_mcp2515_load_tx_buffer(uint32_t mcp2515, uint8_t instruction, _id_reg_t id_reg, uint8_t *data, uint8_t length)
{
    uint8_t buff[length + sizeof(_id_reg) + 1]; // 不推荐这样写,后面会改这部分实现
    uint8_t idx = 0;

    buff[idx++] = instruction;

    memcpy(buff + idx, id_reg, sizeof(_id_reg));
    idx += sizeof(_id_reg);
    buff[idx++] = length;
    memcpy(buff + idx, data, length);
    hdl_mcp2515_transmit_hook(mcp2515, buff, sizeof(buff));
}

void _hdl_mcp2515_request_to_send(uint32_t mcp2515, uint8_t instruction)
{
    hdl_mcp2515_transmit_hook(mcp2515, &instruction, 1);
}

相关的寄存器定义如下:

typedef struct {
    uint8_t tempSIDH;
    uint8_t tempSIDL;
    uint8_t tempEID8;
    uint8_t tempEID0;
} _id_reg;

typedef union {
    struct {
        uint8_t RX0IF      : 1;
        uint8_t RX1IF      : 1;
        uint8_t TXB0REQ    : 1;
        uint8_t TX0IF      : 1;
        uint8_t TXB1REQ    : 1;
        uint8_t TX1IF      : 1;
        uint8_t TXB2REQ    : 1;
        uint8_t TX2IF      : 1;
    };
    uint8_t ctrl_status;  
} _ctrl_status_t;

1.3 关于 MASK 和 Filter 的注意事项

1.3.1 Filter 的注意事项

Filter 的设置有四个寄存器,如下:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
转成 C 的结构体:

typedef struct
{
    struct
    {
        uint8_t RXFnSIDH;
        struct
        {
            uint8_t EID16 : 1;
            uint8_t EID17 : 1;
            uint8_t reserve_1 : 1;
            uint8_t EXIDE : 1;
            uint8_t reserve_2 : 1;
            uint8_t SID0 : 1;
            uint8_t SID1 : 1;
            uint8_t SID2 : 1;
        } RXFnSIDL;
    } _sid_reg;
    struct 
    {
        uint8_t RXFxEID8;
        uint8_t RXFxEID0;
    } _eid_reg;
} _mcp2515_filter_reg;

typedef _mcp2515_filter_reg* _mcp2515_filter_reg_t;
  • 标准 ID 和扩展 ID 是根据 EXIDE 位来控制,这想必没问题。
  • 但是注意上方红色方框的话,如果采用的扩展 ID,那么 RXFxEID8 : RXFxEID0 对应的是 ID 号的低 16 位,剩余的位,按低到高依次填充进 EID16、EID17、SID0、SID1、SID2、RXFnSIDH 寄存器里面。

对应的代码实现:

void _hdl_mcp2515_add_filter(uint32_t mcp2515, hdl_mcp2515_filter_t filter, uint32_t id, HDL_MCP2515_BOOL isExt)
{
    _mcp2515_filter_reg filter_reg = {0};
    uint8_t filter_addr;


    switch (filter)
    {
        case HDL_MCP2515_RX_BUFF1_FILTER_1: filter_addr = MCP2515_RXF0SIDH; break;
        case HDL_MCP2515_RX_BUFF1_FILTER_2: filter_addr = MCP2515_RXF1SIDH; break;
        case HDL_MCP2515_RX_BUFF2_FILTER_1: filter_addr = MCP2515_RXF2SIDH; break;
        case HDL_MCP2515_RX_BUFF2_FILTER_2: filter_addr = MCP2515_RXF3SIDH; break;
        case HDL_MCP2515_RX_BUFF2_FILTER_3: filter_addr = MCP2515_RXF4SIDH; break;
        case HDL_MCP2515_RX_BUFF2_FILTER_4: filter_addr = MCP2515_RXF5SIDH; break;
        default: 
            break;
    }

    if (isExt == HDL_MCP2515_TRUE)
    {
        filter_reg._sid_reg.RXFnSIDL.EXIDE = 0x01;

        filter_reg._eid_reg.RXFxEID0 = id & 0xFF;
        filter_reg._eid_reg.RXFxEID8 = (id >> 8) & 0xFF;
        filter_reg._sid_reg.RXFnSIDL.EID16 = (id >> 16) & 0x01;
        filter_reg._sid_reg.RXFnSIDL.EID17 = (id >> 17) & 0x01;

        filter_reg._sid_reg.RXFnSIDL.SID0 = (id >> 18) & 0x01;
        filter_reg._sid_reg.RXFnSIDL.SID1 = (id >> 19) & 0x01;
        filter_reg._sid_reg.RXFnSIDL.SID2 = (id >> 20) & 0x01;

        filter_reg._sid_reg.RXFnSIDH = (id >> 21) & 0xFF;

        _hdl_mcp2515_write_sequence(mcp2515, filter_addr, (uint8_t*)&filter_reg, sizeof(filter_reg));
    }
    else
    {
        filter_reg._sid_reg.RXFnSIDL.EXIDE = 0x00;
        
        filter_reg._sid_reg.RXFnSIDL.SID0 = id & 0x01;
        filter_reg._sid_reg.RXFnSIDL.SID1 = (id >> 1) & 0x01;
        filter_reg._sid_reg.RXFnSIDL.SID2 = (id >> 2) & 0x01;
		filter_reg._sid_reg.RXFnSIDH = (id >> 3) & 0xFF;

        _hdl_mcp2515_write_sequence(mcp2515, filter_addr, (uint8_t*)&filter_reg, sizeof(filter_reg._sid_reg));
    }
}

Filter 寄存器的设置也可以采用连续传输的方式实现。

1.3.2 MASK 设置的一些问题

  • MASK 寄存器的设置与 Filter 非常相似,除了没有 EXIDE 位之外;
  • 设置 MASK 后,不推荐标准 ID 和 扩展 ID 混用,在 ID 过滤上的计算很复杂,具体说来:
    – 设置 MASK 为扩展 ID 位时,标准 ID 过滤采用 RXMnSIDH、RXMnSIDL 里面的位;
    – 设置 MASK 为标准 ID 位时,RXMnEID8 和 RXMnEID0 会默认为 0,此时扩展 ID 也是参考 RXMnSIDH、RXMnSIDL 的位;

RXBn 接收标准 ID 或扩展 ID 还是同时接收,可以配置 RXM<1:0> 来设置:
在这里插入图片描述

2. STM32 硬件 SPI 问题

采用 STM32 硬件 SPI 时注意使用软件去控制 NSS 的输出,硬件控制操作 MCP2515 芯片会有问题。本人使用的是 STM32F407VG 和 STM32F429 去操作时都会出现这个问题。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
MCP2515是一种常见的CAN总线控制器芯片,它主要用于控制和管理CAN总线通信。Verilog是一种硬件描述语言,用于设计和描述数字电路。所以,MCP2515 Verilog是指使用Verilog语言来设计和描述MCP2515芯片的功能和工作原理。 对于MCP2515的Verilog设计,首先需要了解MCP2515的内部结构和功能。MCP2515具有CAN控制器和SPI接口,可以与微处理器进行通信,并通过CAN总线与其他CAN设备进行通信。因此,在Verilog设计中,我们需要实现MCP2515的功能模块,包括SPI接口模块、CAN控制器模块以及相关的寄存器和状态机等。 首先,我们可以设计SPI接口模块,用于与微处理器进行通信。这个模块可以实现SPI读写功能,并按照MCP2515的通信协议与MCP2515芯片进行数据传输。 其次,我们可以设计CAN控制器模块,用于控制和管理CAN总线通信。这个模块可以实现CAN消息的发送和接收功能,并根据MCP2515的寄存器设置来控制CAN总线的速率、滤波和屏蔽等功能。 除了上述的功能模块,还需要实现一些其他的辅助模块,例如时钟控制模块、状态机模块等,用于支持MCP2515的正常工作。 综上所述,MCP2515 Verilog设计主要涉及到SPI接口模块、CAN控制器模块以及相关的辅助模块的设计和实现。这些模块相互配合,共同完成MCP2515的功能和工作原理,从而实现与CAN总线的通信和数据处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值