STC12C 发表于 2025-9-22 14:37:22

强大的AI8051U没有CAN?NO,只要有SPI就有CAN.

强大的AI8051U,看哪哪都好,全兼容唯一就是没有CAN做项目很受限。

这都不是事。
大家来看过来,MCP2515完美解决。
看看怎么用吧:
MCP2515 编程核心是配置寄存器实现 CAN 通信模式,再通过数据寄存器收发数据,关键在于理解寄存器功能和初始化流程。以下内容提炼数据手册中编程相关的核心信息,按 “基础认知→编程步骤→关键寄存器” 结构整理,可直接用于开发参考。

[*]一、MCP2515 编程基础认知在开始编程前,需明确两个核心前提,避免后续配置出错:

[*]通信接口:MCP2515 通过SPI 接口与 MCU(如 STM32、Arduino)通信,编程时需先确保 MCU 的 SPI 外设正常工作(配置 SPI 时钟、极性、相位,需与 MCP2515 一致)。
[*]工作模式:MCP2515 有 3 种核心工作模式,编程时需通过 “模式配置寄存器(CANCTRL)” 设置,不同模式对应不同功能:

[*]配置模式(Configuration Mode):仅用于修改寄存器(如波特率、滤波规则),此时不参与 CAN 通信。
[*]正常模式(Normal Mode):正常收发 CAN 数据,支持标准帧(11 位 ID)和扩展帧(29 位 ID)。
[*]监听模式(Listen-Only Mode):仅接收 CAN 数据,不发送,也不参与总线仲裁,常用于调试或被动接收场景。

二、MCP2515 核心编程步骤(以 “正常收发” 为例)所有操作需遵循 “先进入配置模式→修改寄存器→切换到目标模式” 的逻辑,具体步骤如下:
1. 初始化 SPI 通信(MCU 侧)这是与 MCP2515 通信的前提,以 STM32 为例,关键配置项:


[*]SPI 模式:模式 0(CPOL=0,CPHA=0),MCP2515 默认支持此模式。
[*]时钟频率:MCP2515 最大支持 10MHz SPI 时钟,实际需根据 MCU 性能和通信稳定性调整(推荐 1-5MHz)。
[*]数据位:8 位数据帧,MSB(高位)先发送。
2. 进入配置模式(MCP2515 侧)MCP2515 上电默认进入 “休眠模式”,需主动切换到配置模式才能修改寄存器:


[*]向 MCP2515 发送 “写寄存器命令(0x02)”。
[*]发送目标寄存器地址:CANCTRL 寄存器(地址 0x0F)。
[*]发送配置值:0x80(将 CANCTRL 的最高位(REQOP2)设为 1,REQOP1/REQOP0 设为 0,即请求进入配置模式)。
[*]延时 1-2ms 后,读取 “CANSTAT 寄存器(地址 0x0E)”,确认 bit7-bit5 为100,即已成功进入配置模式。
3. 配置 CAN 波特率(核心!)CAN 波特率需与总线中其他设备一致(如 250kbps、500kbps、1Mbps),通过 “波特率配置寄存器(CNF1、CNF2、CNF3)” 设置,核心是计算 “位时序”(每个 CAN 位分为同步段、传播段、相位缓冲段 1/2)。

以500kbps 波特率(假设 MCP2515 外部晶振为 16MHz) 为例,配置值参考:


[*]CNF1 寄存器(地址 0x2A):0x00(SJW=1TQ,BRP=0,即波特率预分频系数 = 1)。
[*]CNF2 寄存器(地址 0x29):0x90(BTLMODE=1,SAM=1,PHSEG1=4TQ)。
[*]CNF3 寄存器(地址 0x28):0x02(PHSEG2=3TQ)。
[*]位时序总长度:1(同步段)+ 1(传播段)+ 4(PHSEG1)+ 3(PHSEG2)= 9TQ,波特率 = 16MHz/(2BRP总 TQ)=16MHz/(219)≈888kbps?实际需根据晶振和需求调整,可参考数据手册 “波特率计算表”。
4. 配置滤波与掩码(可选,按需设置)MCP2515 有 2 个接收缓冲器(RXB0、RXB1),可通过 “滤波寄存器(RXFnSID、RXFnEID)” 和 “掩码寄存器(RXMnSID、RXMnEID)” 设置接收规则,只接收符合 ID 规则的数据(减少无效数据中断)。


[*]若不需要滤波:直接将掩码寄存器设为0x00(接收所有 ID 的帧)。
[*]若需要接收指定标准帧(如 ID=0x123):

[*]配置 RXF0SID 寄存器(地址 0x00):0x12(ID 的高 8 位)。
[*]配置 RXF0SIDH 寄存器(地址 0x01):0x60(ID 的低 3 位,结合 bit6-bit5 的 IDE 位(0 = 标准帧))。
[*]配置 RXM0SID 寄存器(地址 0x02):0xFF(掩码高 8 位,全 1 表示严格匹配)。
[*]配置 RXM0SIDH 寄存器(地址 0x03):0xE0(掩码低 3 位,全 1 表示严格匹配)。

5. 配置中断(可选,推荐)若需通过中断通知 MCU “数据接收完成” 或 “数据发送完成”,需配置 “中断使能寄存器(CANINTE)”:


[*]接收中断:将 CANINTE 的 RX0IE 位(bit0)设为 1,当 RXB0 接收到数据时,MCP2515 的 INT 引脚会拉低,触发 MCU 中断。
[*]发送中断:将 CANINTE 的 TX0IE 位(bit3)设为 1,当 TXB0 数据发送完成时,触发中断。
6. 切换到正常模式完成所有配置后,需切换到正常模式才能收发数据:


[*]向 CANCTRL 寄存器(0x0F)发送配置值0x00(REQOP2-REQOP0=000,请求进入正常模式)。
[*]读取 CANSTAT 寄存器,确认 bit7-bit5 为000,即已进入正常模式。
7. 数据发送流程
[*]检查 “发送缓冲器状态寄存器(TXB0CTRL)” 的 TXREQ 位(bit3),若为 0,表示 TXB0 空闲,可写入数据。
[*]向 MCP2515 发送 “写 TXB0 数据命令(0x31)”(或先发送写寄存器命令,再写 TXB0D0-TXB0D7 地址)。
[*]依次发送 CAN 数据(最多 8 字节,如 0x01、0x02、0x03)。
[*]向 TXB0CTRL 寄存器(地址 0x30)写入0x08(将 TXREQ 位设为 1,请求发送数据)。
[*](若开启发送中断)等待中断触发,或轮询 TXB0CTRL 的 TXREQ 位,变为 0 表示发送完成。
8. 数据接收流程
[*](若开启接收中断)当 INT 引脚拉低时,进入中断服务函数;或轮询 “接收缓冲器状态寄存器(RXB0CTRL)” 的 RX0IF 位(bit0),为 1 表示有数据。
[*]向 MCP2515 发送 “读 RXB0 数据命令(0x90)”(或先发送读寄存器命令,再读 RXB0D0-TXB0D7 地址)。
[*]依次读取数据(最多 8 字节),存储到 MCU 缓存中。
[*]读取 “接收状态寄存器(RXB0SIDH/RXB0SIDL)”,确认接收帧的 ID(标准帧 / 扩展帧、具体 ID 值)。
[*]清除接收中断标志:向 “中断标志寄存器(CANINTF)” 的 RX0IF 位(bit0)写入 0。
三、核心编程寄存器汇总(必看)以下是编程中高频使用的寄存器,地址和功能需熟记,避免混淆:


寄存器名称地址核心功能关键位 / 配置值示例
CANCTRL(模式控制)0x0F设置 MCP2515 工作模式配置模式:0x80;正常模式:0x00
CANSTAT(状态查询)0x0E读取当前工作模式配置模式:bit7-bit5=100;正常模式:000
CNF1(波特率 1)0x2A配置同步跳转宽度(SJW)和波特率预分频(BRP)BRP=0(分频 1):0x00
CNF2(波特率 2)0x29配置采样点(SAM)和相位缓冲段 1(PHSEG1)采样 1 次 + PHSEG1=4TQ:0x90
CNF3(波特率 3)0x28配置相位缓冲段 2(PHSEG2)PHSEG2=3TQ:0x02
CANINTE(中断使能)0x2B使能接收 / 发送中断使能 RX0 中断:0x01;使能 TX0 中断:0x08
CANINTF(中断标志)0x2C读取 / 清除中断标志RX0 中断标志:bit0;TX0 中断标志:bit3
TXB0CTRL(发送控制)0x30控制 TXB0 发送请求请求发送:0x08;发送完成:TXREQ=0
RXB0CTRL(接收控制)0x60查看 RXB0 接收状态有数据:RX0IF=1


四、编程常见问题与避坑
[*]SPI 通信失败:检查 SPI 模式是否为 “模式 0”,时钟频率是否超过 10MHz,MCP2515 的 CS 引脚(片选)是否在通信时拉低(仅通信时拉低,结束后拉高)。
[*]无法进入配置模式:确认 CANCTRL 寄存器写入值是否为0x80,若多次失败,可能是 SPI 接线错误(如 SCK、MOSI、MISO 接反)。
[*]发送数据失败:先检查 TXB0 是否空闲(TXREQ=0),再确认 CAN 总线是否有其他设备(总线需接 120Ω 终端电阻,否则信号衰减)。
[*]接收不到数据:检查滤波和掩码配置是否正确(若不需要滤波,掩码需设为全 0),CAN 波特率是否与发送端一致,INT 引脚中断是否使能。

STC12C 发表于 2025-9-22 14:38:17

MCP2515.C


#include "mcp2515.h"

// 延时函数(约10us@11.0592MHz)
void Delay10us(unsigned int t)
{
    while(t--)
    {
      _nop_();_nop_();_nop_();_nop_();
      _nop_();_nop_();_nop_();_nop_();
      _nop_();_nop_();_nop_();_nop_();
    }
}

// SPI发送一个字节
unsigned char SPI_SendByte(unsigned char dat)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
      MCP2515_SCK = 0;               // 时钟线拉低
      if(dat & 0x80) MCP2515_MOSI = 1;// 发送高位
      else MCP2515_MOSI = 0;
      dat <<= 1;                     // 移位
      MCP2515_SCK = 1;               // 时钟线拉高,从机采样
    }
    return 0;
}

// SPI接收一个字节
unsigned char SPI_ReceiveByte(void)
{
    unsigned char i, dat=0;
    for(i=0;i<8;i++)
    {
      MCP2515_SCK = 0;               // 时钟线拉低
      dat <<= 1;                     // 移位
      MCP2515_SCK = 1;               // 时钟线拉高
      if(MCP2515_MISO) dat |= 0x01;// 接收数据
    }
    return dat;
}

// 写MCP2515寄存器
void MCP2515_WriteReg(unsigned char addr, unsigned char dat)
{
    MCP2515_CS = 0;                   // 片选拉低
    SPI_SendByte(CMD_WRITE);          // 发送写命令
    SPI_SendByte(addr);               // 发送寄存器地址
    SPI_SendByte(dat);                // 发送数据
    MCP2515_CS = 1;                   // 片选拉高
    Delay10us(1);
}

// 读MCP2515寄存器
unsigned char MCP2515_ReadReg(unsigned char addr)
{
    unsigned char dat;
    MCP2515_CS = 0;                   // 片选拉低
    SPI_SendByte(CMD_READ);         // 发送读命令
    SPI_SendByte(addr);               // 发送寄存器地址
    dat = SPI_ReceiveByte();          // 接收数据
    MCP2515_CS = 1;                   // 片选拉高
    Delay10us(1);
    return dat;
}

// 软件复位MCP2515
void MCP2515_Reset(void)
{
    MCP2515_CS = 0;
    SPI_SendByte(CMD_RESET);
    MCP2515_CS = 1;
    Delay10us(100);// 等待复位完成
}

// 设置MCP2515工作模式
unsigned char MCP2515_SetMode(unsigned char mode)
{
    unsigned char i;
    MCP2515_WriteReg(REG_CANCTRL, mode);// 写入模式配置
    Delay10us(10);                        // 等待模式切换
   
    // 检查模式是否设置成功
    for(i=0;i<10;i++)
    {
      if((MCP2515_ReadReg(REG_CANSTAT) & 0xE0) == (mode & 0xE0))
      {
            return 0;// 成功
      }
      Delay10us(10);
    }
    return 1;// 失败
}

// 初始化MCP2515(500kbps,16MHz晶振)
void MCP2515_Init(void)
{
    // 初始化SPI引脚
    MCP2515_CS = 1;
    MCP2515_SCK = 1;
    MCP2515_MOSI = 0;
   
    MCP2515_Reset();// 软件复位
   
    // 进入配置模式
    while(MCP2515_SetMode(MODE_CONFIG));
   
    // 配置波特率(500kbps @16MHz)
    MCP2515_WriteReg(REG_CNF1, 0x01);// CNF1: BRP=1 (分频2)
    MCP2515_WriteReg(REG_CNF2, 0xB6);// CNF2: 采样点=87.5%
    MCP2515_WriteReg(REG_CNF3, 0x05);// CNF3: PHSEG2=6TQ
   
    // 关闭滤波,接收所有标准帧
    MCP2515_WriteReg(0x02, 0x00);// RXM0SIDH
    MCP2515_WriteReg(0x03, 0x00);// RXM0SIDL
   
    // 使能接收中断
    MCP2515_WriteReg(REG_CANINTE, 0x01);// 使能RX0中断
   
    // 进入正常工作模式
    MCP2515_SetMode(MODE_NORMAL);
   
    // 初始化外部中断0(接收中断)
    IT0 = 1;// 下降沿触发
    EX0 = 1;// 使能外部中断0
    EA = 1;   // 使能总中断
}

// 发送数据(标准帧,ID=0x123)
unsigned char MCP2515_Send(unsigned char *data, unsigned char len)
{
    unsigned char i;
   
    // 检查发送缓冲区是否空闲
    if(MCP2515_ReadReg(REG_TXB0CTRL) & 0x08)
    {
      return 1;// 发送缓冲区忙
    }
   
    // 限制数据长度(最大8字节)
    if(len > 8) len = 8;
   
    // 写入数据到TXB0缓冲区
    MCP2515_CS = 0;
    SPI_SendByte(CMD_WRITE);
    SPI_SendByte(0x31);// TXB0D0地址
   
    // 写入数据
    for(i=0;i<len;i++)
    {
      SPI_SendByte(data);
    }
    MCP2515_CS = 1;
   
    // 写入标准帧ID(0x123)
    MCP2515_WriteReg(0x32, 0x12);// TXB0SIDH
    MCP2515_WriteReg(0x33, 0x60);// TXB0SIDL (ID低3位+IDE=0)
    MCP2515_WriteReg(0x35, len);   // 数据长度
   
    // 请求发送
    MCP2515_CS = 0;
    SPI_SendByte(CMD_RTS_TX0);
    MCP2515_CS = 1;
   
    return 0;// 发送成功
}

// 检查是否有接收数据
unsigned char MCP2515_CheckReceive(void)
{
    return (MCP2515_ReadReg(REG_CANINTF) & 0x01);// 检查RX0中断标志
}

// 接收数据
unsigned char MCP2515_Receive(unsigned char *data, unsigned char *len)
{
    unsigned char i;
   
    // 检查是否有数据
    if(!(MCP2515_ReadReg(REG_CANINTF) & 0x01))
    {
      return 1;// 无数据
    }
   
    // 读取数据长度
    MCP2515_CS = 0;
    SPI_SendByte(CMD_READ_RX0);
    SPI_ReceiveByte();// 跳过SIDH
    SPI_ReceiveByte();// 跳过SIDL
    SPI_ReceiveByte();// 跳过EID8
    SPI_ReceiveByte();// 跳过EID0
    *len = SPI_ReceiveByte() & 0x0F;// 读取数据长度
   
    // 限制最大长度
    if(*len > 8) *len = 8;
   
    // 读取数据
    for(i=0;i<*len;i++)
    {
      data = SPI_ReceiveByte();
    }
    MCP2515_CS = 1;
   
    // 清除中断标志
    MCP2515_WriteReg(REG_CANINTF, 0x00);
   
    return 0;// 接收成功
}

// 外部中断0服务函数(接收中断)
void EX0_ISR(void) interrupt 0
{
    // 可在这里添加接收提示代码
    // 例如:设置接收标志位
}

STC12C 发表于 2025-9-22 14:38:50

MCP2515.H

#ifndef __MCP2515_H__
#define __MCP2515_H__

#include <reg52.h>
#include <intrins.h>

// 引脚定义(可根据实际接线修改)
sbit MCP2515_CS= P3^4;// 片选引脚
sbit MCP2515_INT = P3^2;// 中断引脚(外部中断0)
sbit MCP2515_SCK = P3^6;// SPI时钟
sbit MCP2515_MOSI= P3^5;// 主机发送,从机接收
sbit MCP2515_MISO= P3^7;// 主机接收,从机发送

// MCP2515命令
#define CMD_WRITE       0x02// 写寄存器
#define CMD_READ      0x03// 读寄存器
#define CMD_RTS_TX0   0x20// 请求发送TXB0
#define CMD_READ_RX0    0x90// 读RXB0缓冲区
#define CMD_RESET       0xC0// 软件复位

// 寄存器地址
#define REG_CANCTRL   0x0F// 模式控制寄存器
#define REG_CANSTAT   0x0E// 状态寄存器
#define REG_CNF1      0x2A// 波特率配置1
#define REG_CNF2      0x29// 波特率配置2
#define REG_CNF3      0x28// 波特率配置3
#define REG_CANINTE   0x2B// 中断使能寄存器
#define REG_CANINTF   0x2C// 中断标志寄存器
#define REG_TXB0CTRL    0x30// TXB0控制寄存器
#define REG_RXB0CTRL    0x60// RXB0控制寄存器

// 工作模式
#define MODE_CONFIG   0x80// 配置模式
#define MODE_NORMAL   0x00// 正常模式
#define MODE_LISTEN   0x20// 监听模式

// 函数声明
void MCP2515_Init(void);                   // 初始化MCP2515
unsigned char MCP2515_SetMode(unsigned char mode);// 设置工作模式
unsigned char MCP2515_Send(unsigned char *data, unsigned char len);// 发送数据
unsigned char MCP2515_Receive(unsigned char *data, unsigned char *len);// 接收数据
unsigned char MCP2515_CheckReceive(void);// 检查是否有接收数据

#endif

STC12C 发表于 2025-9-22 14:39:23

MAIN.C

#include <reg52.h>
#include "mcp2515.h"

unsigned char send_buf = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
unsigned char recv_buf;
unsigned char recv_len;

// 毫秒级延时函数
void DelayMs(unsigned int ms)
{
    unsigned int i, j;
    for(i=0;i<ms;i++)
      for(j=0;j<112;j++);
}

void main(void)
{
    MCP2515_Init();// 初始化MCP2515
   
    while(1)
    {
      // 发送数据
      MCP2515_Send(send_buf, 8);
      DelayMs(1000);// 每秒发送一次
      
      // 检查接收
      if(MCP2515_CheckReceive())
      {
            MCP2515_Receive(recv_buf, &recv_len);
            // 接收到的数据在recv_buf中,长度为recv_len
            // 可在这里添加数据处理代码
      }
    }
}

神农鼎 发表于 2025-9-22 15:48:04

要CAN, 用 STC32G12K128, STC32G12K64, STC32G8K64
要CAN-FD, 用最新 STC32G144K246

晓飛飛 发表于 2025-9-22 22:41:55

MCP2515比带CAN的单片机贵多了,不经济
页: [1]
查看完整版本: 强大的AI8051U没有CAN?NO,只要有SPI就有CAN.