找回密码
 立即注册
查看: 184|回复: 5

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

[复制链接]
  • 打卡等级:常住居民III
  • 打卡总天数:167
  • 最近打卡:2025-10-15 15:04:29

2

主题

9

回帖

776

积分

高级会员

积分
776
发表于 2025-9-22 14:37:22 | 显示全部楼层 |阅读模式
  强大的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。
    三、核心编程寄存器汇总(必看)以下是编程中高频使用的寄存器,地址和功能需熟记,避免混淆:

    [td]
    寄存器名称
    地址
    核心功能
    关键位 / 配置值示例
    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 引脚中断是否使能。
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:167
  • 最近打卡:2025-10-15 15:04:29

2

主题

9

回帖

776

积分

高级会员

积分
776
发表于 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
{
    // 可在这里添加接收提示代码
    // 例如:设置接收标志位
}
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:167
  • 最近打卡:2025-10-15 15:04:29

2

主题

9

回帖

776

积分

高级会员

积分
776
发表于 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
回复

使用道具 举报 送花

  • 打卡等级:常住居民III
  • 打卡总天数:167
  • 最近打卡:2025-10-15 15:04:29

2

主题

9

回帖

776

积分

高级会员

积分
776
发表于 2025-9-22 14:39:23 | 显示全部楼层
MAIN.C

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

unsigned char send_buf[8] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
unsigned char recv_buf[8];
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
            // 可在这里添加数据处理代码
        }
    }
}
回复

使用道具 举报 送花

  • 打卡等级:以坛为家I
  • 打卡总天数:208
  • 最近打卡:2025-10-15 11:03:15

787

主题

1万

回帖

1万

积分

管理员

积分
19770
发表于 2025-9-22 15:48:04 | 显示全部楼层
要CAN, 用 STC32G12K128, STC32G12K64, STC32G8K64
要CAN-FD, 用最新 STC32G144K246
回复

使用道具 举报 送花

  • 打卡等级:以坛为家II
  • 打卡总天数:563
  • 最近打卡:2025-10-15 12:15:11
已绑定手机

41

主题

2444

回帖

2637

积分

荣誉版主

积分
2637
发表于 2025-9-22 22:41:55 | 显示全部楼层
MCP2515比带CAN的单片机贵多了,不经济
睁开眼睛做场梦~~~
回复

使用道具 举报 送花

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|手机版|深圳国芯人工智能有限公司 ( 粤ICP备2022108929号-2 )

GMT+8, 2025-10-16 06:10 , Processed in 0.115098 second(s), 72 queries .

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表