基于 STM32 芯片的 MCP2515 芯片调试心得
- 1. MCP2515 芯片解析
- 1.1 外部时钟源
- 1.2 可采用连续传输提高效率
- 发送数据时,使用 TX0 为例:
- 1.3 关于 MASK 和 Filter 的注意事项
- 1.3.1 Filter 的注意事项
- 1.3.2 MASK 设置的一些问题
- 2. STM32 硬件 SPI 问题
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 去操作时都会出现这个问题。