深入解析:零知IDE——基于STM32F407VET6和MCP2515实现CAN通信与数据采集

   ✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!

✔访问零知实验室,获取更多实战项目和教程资源吧!

www.lingzhilab.com

目录

一、硬件系统部分

1.1 硬件清单

1.2 接线方案

1.3 具体接线图

1.4 连接实物图

二、代码解析部分

2.1 发送端代码

2.2 接收端代码

三、项目演示

3.1 操作流程

3.2 演示视频

四、MCP2515工作原理详解

4.1 CAN总线结构

4.2 SPI接口数据交换

五、常见问题解答

Q1: 为什么CAN通信经常失败?

Q2: MPU6050数据读取异常怎么办?

Q3: 数据发送间隔如何优化?


(1)项目概述

        本项目基于STM32F407VET6主控芯片和MCP2515 CAN通信模块,实现了一个完整的多数据传感器CAN模块通信的监控系统。通过分层架构设计和模块化编程,实现了稳定可靠的数据采集、传输和显示功能

(2)项目难点及解决方案

       问题描述1:CAN标准数据帧最多8字节,但传感器数据量较大

解决方案:采用数据分包传输策略,将IMU数据拆分为两个CAN消息

一、硬件系统部分

1.1 硬件清单

(1)发送端组件

组件型号数量用途
主控板STM32F407VET61数据处理与控制
CAN模块MCP2515+TJA10501CAN通信
加速度传感器MPU60501运动状态检测
温湿度传感器DHT111环境监测
按键tactile switch1模式切换

(2)接收端组件

组件型号数量用途
主控板STM32F407VET61数据显示与控制
CAN模块MCP2515+TJA10501CAN通信
显示屏ST7789 240×2401状态显示
按键tactile switch2界面切换

1.2 接线方案

(1)发送端引脚

零知增强板引脚功能连接器件备注
53SPI_CSMCP2515_CSCAN片选
52SPI_SCKMCP2515_SCKSPI时钟
50SPI_MISOMCP2515_MISOSPI输入
51SPI_MOSIMCP2515_MOSISPI输出
21/SCLI2C_SCLMPU6050_SCLI2C时钟
20/SDAI2C_SDAMPU6050_SDAI2C数据
7DATADHT11_DATA温湿度数据
2INTMODE_BUTTON模式切换
LED_BUILTINLEDSELF_TEST_LED状态指示

(2)接收端引脚

零知增强板引脚功能连接器件备注
53SPI_CSMCP2515_CSCAN片选
52SPI_SCKMCP2515_SCKSPI时钟
50SPI_MISOMCP2515_MISOSPI输入
51SPI_MOSIMCP2515_MOSISPI输出
6DCST7789_DC数据/命令
7RSTST7789_RST复位
10CSST7789_CS片选
9SCLKST7789_SCLK时钟
8MOSIST7789_MOSI数据输入
12INTSELF_TEST_BUTTON自检按钮
13INTDISPLAY_MODE_BUTTON显示切换

        接收端部分的显示屏使用软件SPI串口、MCP2515 CAN通信模块使用硬件SPI串口

1.3 具体接线图

(1)发送端接线图

        MCP2515 CAN通信模块接到5V电源,接收端和发送端的CAN_H和CAN_L总线互连

(2)接收端接线图

1.4 连接实物图

二、代码解析部分

2.1 发送端代码

(1)核心数据结构

// 精心设计的8字节数据结构,充分利用CAN帧容量
typedef struct {
int16_t accelX;    // X轴加速度,范围-32768~32767,对应±2g/±4g/±8g/±16g量程
int16_t accelY;    // Y轴加速度,MPU6050原始数据,需除以16384(±2g)得到g值
int16_t accelZ;    // Z轴加速度
int16_t gyroX;     // X轴角速度,范围-32768~32767,对应±250/±500/±1000/±2000°/s
} IMUDataPart1;      // 精确8字节,避免内存对齐问题
typedef struct {
int16_t gyroY;     // Y轴角速度,实际值=原始数据/131(±250°/s)
int16_t gyroZ;     // Z轴角速度
int16_t temperature; // 温度值(放大100倍存储),解决float传输问题
uint16_t reserved; // 保留位,用于数据对齐和未来扩展
} IMUDataPart2;      // 精确8字节,CAN帧完美承载

        CAN标准数据帧最多8字节,通过将16字节的完整IMU数据拆分为两个逻辑部分

(2)数据采集算法

IMUDataPart1 readIMUDataPart1() {
IMUDataPart1 data;
int16_t ax, ay, az, gx, gy, gz;
if (mpu.testConnection()) {
// MPU6050原始数据读取,6轴运动数据一次性获取
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
// 数据直接传递,实际应用中可以添加校准偏移量
data.accelX = ax;
data.accelY = ay;
data.accelZ = az;
data.gyroX = gx;
} else {
// 传感器故障处理,清零数据并设置错误标志
memset(&data, 0, sizeof(data));
}
return data;
}
IMUDataPart2 readIMUDataPart2() {
IMUDataPart2 data;
int16_t ax, ay, az, gx, gy, gz;
int16_t temp;
if (mpu.testConnection()) {
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
temp = mpu.getTemperature(); // 读取芯片内部温度传感器
data.gyroY = gy;
data.gyroZ = gz;
// 温度转换公式:TEMP_degC = (TEMP_OUT / 340) + 36.53
data.temperature = (temp / 340.0 + 36.53) * 100; // 放大100倍保持精度
data.reserved = 0; // 预留位,可用于校验或扩展
} else {
memset(&data, 0, sizeof(data));
}
return data;
}

getMotion6()一次性读取所有运动数据,减少I2C通信次数

(3)CAN通信核心实现

void sendCANMessages(IMUDataPart1 imu1, IMUDataPart2 imu2, EnvData env, SystemData sys) {
// IMU数据第一部分发送
memcpy(imuMsg1.data, &imu1, sizeof(imu1));
if (mcp2515.sendMessage(&imuMsg1) != MCP2515::ERROR_OK) {
Serial.println("Failed to send IMU data part 1");
// 可添加重试机制
delay(1);
if (mcp2515.sendMessage(&imuMsg1) != MCP2515::ERROR_OK) {
systemStatus = STATUS_ERROR;
}
}
// IMU数据第二部分发送(立即发送,保证数据完整性)
memcpy(imuMsg2.data, &imu2, sizeof(imu2));
if (mcp2515.sendMessage(&imuMsg2) != MCP2515::ERROR_OK) {
Serial.println("Failed to send IMU data part 2");
systemStatus = STATUS_ERROR;
}
// 环境数据发送(优先级较低)
memcpy(envMsg.data, &env, sizeof(env));
mcp2515.sendMessage(&envMsg); // 不检查错误,避免阻塞
// 系统状态发送(最低优先级)
memcpy(sysMsg.data, &sys, sizeof(sys));
mcp2515.sendMessage(&sysMsg);
}

        关键数据(IMU)采用错误检测和重试,相关数据包连续发送,减少中间间隔

(4)发送端完整代码

#include
#include
#include
#include
#include
// CAN相关定义
struct can_frame imuMsg1;  // 改为两个IMU消息
struct can_frame imuMsg2;
struct can_frame envMsg;
struct can_frame sysMsg;
MCP2515 mcp2515(53);
// 传感器对象
MPU6050 mpu;
#define DHT_PIN 7
#define DHT_TYPE DHT11
DHT dht(DHT_PIN, DHT_TYPE);
// 引脚定义
const int MODE_BUTTON = 2;
const int SELF_TEST_LED = LED_BUILTIN;
// 数据结构
typedef struct {
int16_t accelX;    // 2字节
int16_t accelY;    // 2字节
int16_t accelZ;    // 2字节
int16_t gyroX;     // 2字节
} IMUDataPart1;      // 总共8字节
typedef struct {
int16_t gyroY;     // 2字节
int16_t gyroZ;     // 2字节
int16_t temperature; // 2字节(将float转换为int16_t,放大100倍)
uint16_t reserved; // 2字节(保留)
} IMUDataPart2;      // 总共8字节
typedef struct {
int16_t temperature; // 2字节(放大100倍)
int16_t humidity;    // 2字节(放大100倍)
uint32_t readTime;   // 4字节
} EnvData;            // 总共8字节
typedef struct {
uint8_t systemStatus;    // 1字节
uint8_t operationMode;   // 1字节
uint8_t errorCode;       // 1字节
uint8_t batteryLevel;    // 1字节
uint32_t uptime;         // 4字节
} SystemData;              // 总共8字节
// 系统状态定义
#define STATUS_SELF_TEST  0
#define STATUS_NORMAL     1
#define STATUS_ERROR      2
// 操作模式定义
#define MODE_NORMAL       0
#define MODE_HIGH_PRECISE 1
#define MODE_LOW_POWER    2
// 错误代码定义
#define ERROR_NONE        0
#define ERROR_MPU6050     1
#define ERROR_DHT11       2
#define ERROR_CAN         3
// 全局变量
volatile bool modeChanged = false;
uint8_t currentMode = MODE_NORMAL;
uint8_t systemStatus = STATUS_SELF_TEST;
unsigned long lastSendTime = 0;
unsigned long startTime = 0;
bool selfTestPassed = false;
void setup() {
Serial.begin(115200);
Serial.println("Starting Vehicle Monitoring System - Transmitter");
// 初始化引脚
pinMode(MODE_BUTTON, INPUT_PULLUP);
pinMode(SELF_TEST_LED, OUTPUT);
attachInterrupt(digitalPinToInterrupt(MODE_BUTTON), modeButtonISR, FALLING);
// 初始化传感器
Wire.begin();
// 系统自检
selfTest();
// 初始化CAN消息结构
initCANMessages();
// 初始化MCP2515
if (!initCAN()) {
systemStatus = STATUS_ERROR;
Serial.println("CAN initialization failed!");
return;
}
startTime = millis();
systemStatus = STATUS_NORMAL;
Serial.println("System initialized successfully");
}
void loop() {
// 主循环代码保持不变,只修改数据读取和发送部分
unsigned long currentTime = millis();
if (systemStatus == STATUS_SELF_TEST) {
runSelfTest();
return;
}
if (systemStatus == STATUS_ERROR) {
handleErrorState();
return;
}
if (modeChanged) {
changeOperationMode();
modeChanged = false;
}
uint32_t sendInterval = getSendInterval();
if (currentTime - lastSendTime >= sendInterval) {
// 读取传感器数据
IMUDataPart1 imuData1 = readIMUDataPart1();
IMUDataPart2 imuData2 = readIMUDataPart2();
EnvData envData = readEnvData();
SystemData sysData = readSystemData();
// 数据有效性检查
if (!checkDataValidity(imuData1, imuData2, envData)) {
systemStatus = STATUS_ERROR;
return;
}
// 打包并发送CAN消息
sendCANMessages(imuData1, imuData2, envData, sysData);
// 串口调试输出
if (currentTime % 2000 = 1000) {
// 读取所有传感器数据
IMUDataPart1 imuData1 = readIMUDataPart1();
IMUDataPart2 imuData2 = readIMUDataPart2();
EnvData envData = readEnvData();
Serial.println("=== Self-Test Results ===");
Serial.print("MPU6050: ");
Serial.println(mpu.testConnection() ? "OK" : "FAIL");
Serial.print("DHT11 Temp: ");
if (!isnan(envData.temperature)) {
Serial.print(envData.temperature);
Serial.println(" °C");
} else {
Serial.println("FAIL");
}
Serial.print("System Uptime: ");
Serial.print(millis() / 1000);
Serial.println("s");
Serial.println("========================");
lastTestTime = currentTime;
}
// 闪烁LED指示自检模式
digitalWrite(SELF_TEST_LED, (currentTime / 500) % 2);
}
// 初始化CAN
bool initCAN() {
if (mcp2515.reset() != MCP2515::ERROR_OK) {
return false;
}
if (mcp2515.setBitrate(CAN_125KBPS, MCP_8MHZ) != MCP2515::ERROR_OK) {
return false;
}
if (mcp2515.setNormalMode() != MCP2515::ERROR_OK) {
return false;
}
return true;
}
// 初始化CAN消息
void initCANMessages() {
// IMU数据第一部分 (ID: 0x101)
imuMsg1.can_id = 0x101;
imuMsg1.can_dlc = 8;
// IMU数据第二部分 (ID: 0x102)
imuMsg2.can_id = 0x102;
imuMsg2.can_dlc = 8;
// 环境数据消息 (ID: 0x103)
envMsg.can_id = 0x103;
envMsg.can_dlc = 8;
// 系统状态消息 (ID: 0x104)
sysMsg.can_id = 0x104;
sysMsg.can_dlc = 8;
}
// 读取IMU数据
IMUDataPart1 readIMUDataPart1() {
IMUDataPart1 data;
int16_t ax, ay, az, gx, gy, gz;
if (mpu.testConnection()) {
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
data.accelX = ax;
data.accelY = ay;
data.accelZ = az;
data.gyroX = gx;
} else {
memset(&data, 0, sizeof(data));
}
return data;
}
IMUDataPart2 readIMUDataPart2() {
IMUDataPart2 data;
int16_t ax, ay, az, gx, gy, gz;
int16_t temp;
if (mpu.testConnection()) {
mpu.getMotion6(&ax, &ay, &az, &gx, &gy, &gz);
temp = mpu.getTemperature(); // 正确读取温度值
data.gyroY = gy;
data.gyroZ = gz;
data.temperature = temp / 340 + 36.53 * 100; // 转换为摄氏度并放大100倍
data.reserved = 0;
} else {
memset(&data, 0, sizeof(data));
}
return data;
}
// 读取环境数据
EnvData readEnvData() {
EnvData data;
float temp = dht.readTemperature();
float hum = dht.readHumidity();
// 检查数据有效性并转换
if (!isnan(temp) && !isnan(hum)) {
data.temperature = (int16_t)(temp * 100); // 放大100倍保存为整数
data.humidity = (int16_t)(hum * 100);     // 放大100倍保存为整数
} else {
data.temperature = -9999; // 错误值
data.humidity = -9999;    // 错误值
}
data.readTime = millis();
return data;
}
// 读取系统数据
SystemData readSystemData() {
SystemData data;
data.systemStatus = systemStatus;
data.operationMode = currentMode;
data.errorCode = ERROR_NONE;
data.batteryLevel = analogRead(A0) / 13;
data.uptime = millis() - startTime;
return data;
}
// 检查数据有效性
bool checkDataValidity(IMUDataPart1 imu1, IMUDataPart2 imu2, EnvData env) {
// 检查MPU6050数据
if (abs(imu1.accelX) > 30000 || abs(imu1.accelY) > 30000 || abs(imu1.accelZ) > 30000) {
systemStatus = STATUS_ERROR;
return false;
}
// 检查DHT11数据
if (env.temperature  8000 ||
env.humidity  10000) {
systemStatus = STATUS_ERROR;
return false;
}
return true;
}
// 发送CAN消息
void sendCANMessages(IMUDataPart1 imu1, IMUDataPart2 imu2, EnvData env, SystemData sys) {
// 打包IMU数据第一部分
memcpy(imuMsg1.data, &imu1, sizeof(imu1));
if (mcp2515.sendMessage(&imuMsg1) != MCP2515::ERROR_OK) {
Serial.println("Failed to send IMU data part 1");
} else {
Serial.println("IMU data part 1 sent successfully");
}
// 打包IMU数据第二部分
memcpy(imuMsg2.data, &imu2, sizeof(imu2));
if (mcp2515.sendMessage(&imuMsg2) != MCP2515::ERROR_OK) {
Serial.println("Failed to send IMU data part 2");
} else {
Serial.println("IMU data part 2 sent successfully");
}
// 打包环境数据
memcpy(envMsg.data, &env, sizeof(env));
if (mcp2515.sendMessage(&envMsg) != MCP2515::ERROR_OK) {
Serial.println("Failed to send environment data");
} else {
Serial.println("Environment data sent successfully");
}
// 打包系统数据
memcpy(sysMsg.data, &sys, sizeof(sys));
if (mcp2515.sendMessage(&sysMsg) != MCP2515::ERROR_OK) {
Serial.println("Failed to send system data");
} else {
Serial.println("System data sent successfully");
}
}
// 获取发送间隔(根据模式)
uint32_t getSendInterval() {
switch(currentMode) {
case MODE_HIGH_PRECISE: return 50;   // 20Hz
case MODE_NORMAL:       return 100;  // 10Hz
case MODE_LOW_POWER:    return 500;  // 2Hz
default:                return 100;
}
}
// 模式切换中断服务函数
void modeButtonISR() {
static unsigned long lastInterruptTime = 0;
unsigned long interruptTime = millis();
if (interruptTime - lastInterruptTime > 300) {
modeChanged = true;
}
lastInterruptTime = interruptTime;
}
// 切换操作模式
void changeOperationMode() {
currentMode = (currentMode + 1) % 3;
Serial.print("Mode changed to: ");
switch(currentMode) {
case MODE_NORMAL:
Serial.println("Normal Mode");
break;
case MODE_HIGH_PRECISE:
Serial.println("High Precision Mode");
break;
case MODE_LOW_POWER:
Serial.println("Low Power Mode");
break;
}
}
// 错误状态处理
void handleErrorState() {
digitalWrite(SELF_TEST_LED, (millis() / 200) % 2); // 快速闪烁
// 尝试恢复
if (millis() % 5000 < 100) {
Serial.println("Attempting system recovery...");
selfTest();
if (mpu.testConnection() && !isnan(dht.readTemperature())) {
systemStatus = STATUS_NORMAL;
Serial.println("System recovered successfully");
}
}
}
// 更新状态LED
void updateStatusLED() {
switch(systemStatus) {
case STATUS_NORMAL:
// 根据模式设置不同的闪烁频率
uint32_t blinkInterval;
switch(currentMode) {
case MODE_NORMAL: blinkInterval = 1000; break;
case MODE_HIGH_PRECISE: blinkInterval = 300; break;
case MODE_LOW_POWER: blinkInterval = 2000; break;
default: blinkInterval = 1000;
}
digitalWrite(SELF_TEST_LED, (millis() % blinkInterval) < (blinkInterval / 2));
break;
case STATUS_ERROR:
digitalWrite(SELF_TEST_LED, (millis() / 200) % 2); // 快速闪烁
break;
}
}
// 调试信息输出
void printDebugInfo(IMUDataPart1 imu1, IMUDataPart2 imu2, EnvData env, SystemData sys) {
Serial.println("=== Sensor Data ===");
Serial.print("IMU - Accel: ");
Serial.print(imu1.accelX); Serial.print(", ");
Serial.print(imu1.accelY); Serial.print(", ");
Serial.print(imu1.accelZ);
Serial.print(" | Gyro: ");
Serial.print(imu1.gyroX); Serial.print(", ");
Serial.print(imu2.gyroY); Serial.print(", ");
Serial.print(imu2.gyroZ);
Serial.print(" | Temp: "); Serial.print(imu2.temperature / 100.0); Serial.println("°C");
Serial.print("Env - Temp: ");
Serial.print(env.temperature / 100.0);
Serial.print("°C, Humidity: "); Serial.print(env.humidity / 100.0); Serial.println("%");
Serial.print("System - Mode: "); Serial.print(sys.operationMode);
Serial.print(", Battery: "); Serial.print(sys.batteryLevel);
Serial.print("%, Uptime: "); Serial.print(sys.uptime / 1000); Serial.println("s");
Serial.println("===================");
}

2.2 接收端代码

(1)显示系统架构设计

// 显示模式状态机
void updateDisplay() {
if (!displayNeedsRefresh) return;
// 根据当前模式调用对应的显示函数
switch(displayMode) {
case DISPLAY_MODE_OVERVIEW:
displayOverview();    // 综合信息界面
break;
case DISPLAY_MODE_IMU:
displayIMUData();     // 详细IMU数据
break;
case DISPLAY_MODE_ENV:
displayEnvData();     // 环境数据界面
break;
case DISPLAY_MODE_SYSTEM:
displaySystemInfo();  // 系统状态信息
break;
case DISPLAY_MODE_SELF_TEST:
displaySelfTestScreen(); // 自检界面
break;
}
displayNeedsRefresh = false; // 清除刷新标志
}

(2)数据接收与处理

void processCANMessages() {
if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) {
lastDataTime = millis(); // 更新最后接收时间
// 基于CAN ID的数据分类处理
switch(canMsg.can_id) {
case 0x101: // IMU数据第一部分
memcpy(¤tIMU1, canMsg.data, sizeof(currentIMU1));
break;
case 0x102: // IMU数据第二部分
memcpy(¤tIMU2, canMsg.data, sizeof(currentIMU2));
// 可添加数据完整性校验
break;
case 0x103: // 环境数据
memcpy(¤tEnv, canMsg.data, sizeof(currentEnv));
break;
case 0x104: // 系统状态
memcpy(¤tSystem, canMsg.data, sizeof(currentSystem));
// 更新系统状态指示
updateSystemStatus();
break;
}
// 设置显示刷新标志,避免频繁刷新
displayNeedsRefresh = true;
}
}

        处理发送端不同CAN ID数据,memcpy内存拷贝到目标结构体,确保:消息数据长度(CAN 标准帧最大 8 字节)= 目标结构体大小

(3)接收端完整代码

#include
#include
#include
#include
// CAN相关定义
struct can_frame canMsg;
MCP2515 mcp2515(53);
// ST7789显示屏定义
#define TFT_CS   10
#define TFT_SCK  9
#define TFT_MOSI 8
#define TFT_RST  7
#define TFT_DC   6
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_MOSI, TFT_SCK, TFT_RST);
// 引脚定义
const int SELF_TEST_BUTTON = 12;
const int DISPLAY_MODE_BUTTON = 13;
// 数据结构(与发送端匹配)
typedef struct {
int16_t accelX;
int16_t accelY;
int16_t accelZ;
int16_t gyroX;
} IMUDataPart1;
typedef struct {
int16_t gyroY;
int16_t gyroZ;
int16_t temperature;
uint16_t reserved;
} IMUDataPart2;
typedef struct {
int16_t temperature;
int16_t humidity;
uint32_t readTime;
} EnvData;
typedef struct {
uint8_t systemStatus;
uint8_t operationMode;
uint8_t errorCode;
uint8_t batteryLevel;
uint32_t uptime;
} SystemData;
// 显示模式定义
#define DISPLAY_MODE_OVERVIEW   0
#define DISPLAY_MODE_IMU        1
#define DISPLAY_MODE_ENV        2
#define DISPLAY_MODE_SYSTEM     3
#define DISPLAY_MODE_SELF_TEST  4
// 全局变量
IMUDataPart1 currentIMU1;
IMUDataPart2 currentIMU2;
EnvData currentEnv;
SystemData currentSystem;
// 状态变量
uint8_t displayMode = DISPLAY_MODE_OVERVIEW;
bool systemWarning = false;
unsigned long lastDataTime = 0;
bool selfTestMode = false;
unsigned long startTime = 0;
bool displayNeedsRefresh = true;
// 颜色定义
#define ST77XX_DARKBLUE 0x000F
#define ST77XX_DARKGREEN 0x03E0
#define ST77XX_DARKRED 0x7800
void setup() {
Serial.begin(115200);
Serial.println("Starting Vehicle Monitoring System - Receiver");
// 初始化引脚
pinMode(SELF_TEST_BUTTON, INPUT_PULLUP);
pinMode(DISPLAY_MODE_BUTTON, INPUT_PULLUP);
// 初始化显示屏
tft.init(240, 240);
tft.setRotation(1);
tft.fillScreen(ST77XX_BLACK);
tft.setTextWrap(false);
// 显示启动界面
showStartupScreen();
// 初始化CAN
if (!initCAN()) {
showErrorScreen("CAN Init Failed");
while(1);
}
startTime = millis();
Serial.println("Receiver initialized successfully");
}
void loop() {
// 检查按钮输入
checkButtons();
// 自检模式
if (selfTestMode) {
runSelfTestMode();
return;
}
// 处理CAN消息
processCANMessages();
// 检查数据超时
checkDataTimeout();
// 更新显示(局部刷新)
updateDisplay();
delay(50);
}
// 初始化CAN
bool initCAN() {
if (mcp2515.reset() != MCP2515::ERROR_OK) {
return false;
}
if (mcp2515.setBitrate(CAN_125KBPS, MCP_8MHZ) != MCP2515::ERROR_OK) {
return false;
}
if (mcp2515.setNormalMode() != MCP2515::ERROR_OK) {
return false;
}
return true;
}
// 检查按钮输入
void checkButtons() {
static unsigned long lastButtonTime = 0;
unsigned long currentTime = millis();
if (currentTime - lastButtonTime  5000) { // 5秒无数据
systemWarning = true;
// 显示超时警告
tft.fillRect(0, 200, 240, 40, ST77XX_RED);
tft.setTextSize(2);
tft.setTextColor(ST77XX_WHITE);
tft.setCursor(20, 210);
tft.println("NO DATA RECEIVED");
} else {
systemWarning = false;
}
}
// 运行自检模式
void runSelfTestMode() {
static unsigned long lastTestTime = 0;
unsigned long currentTime = millis();
if (displayNeedsRefresh) {
displaySelfTestScreen();
displayNeedsRefresh = false;
}
if (currentTime - lastTestTime >= 1000) {
updateSelfTestScreen();
lastTestTime = currentTime;
}
}
// 显示自检屏幕
void displaySelfTestScreen() {
tft.fillScreen(ST77XX_BLACK);
displayHeader("SELF-TEST");
tft.setTextSize(2);
tft.setTextColor(ST77XX_YELLOW);
tft.setCursor(50, 50);
tft.println("SYSTEM TEST");
tft.setCursor(50, 80);
tft.println("IN PROGRESS...");
}
// 更新自检屏幕
void updateSelfTestScreen() {
tft.fillRect(0, 120, 240, 120, ST77XX_BLACK);
tft.setTextSize(2);
tft.setCursor(10, 120);
// CAN通信状态
tft.setTextColor(lastDataTime > 0 ? ST77XX_GREEN : ST77XX_RED);
tft.print("CAN: ");
tft.println(lastDataTime > 0 ? "OK" : "FAIL");
// 数据时效性
tft.setCursor(10, 150);
uint32_t timeSinceLastData = millis() - lastDataTime;
tft.setTextColor(timeSinceLastData  70) return ST77XX_GREEN;
if (level > 30) return ST77XX_YELLOW;
return ST77XX_RED;
}
// 串口调试输出
void printReceivedData() {
Serial.println("=== Received Data ===");
Serial.print("IMU - Accel: ");
Serial.print(currentIMU1.accelX); Serial.print(", ");
Serial.print(currentIMU1.accelY); Serial.print(", ");
Serial.print(currentIMU1.accelZ);
Serial.print(" | Temp: "); Serial.println(currentIMU2.temperature);
Serial.print("Env - Temp: ");
Serial.print(currentEnv.temperature);
Serial.print("C, Humidity: "); Serial.println(currentEnv.humidity);
Serial.print("System - Status: "); Serial.print(currentSystem.systemStatus);
Serial.print(", Mode: "); Serial.print(currentSystem.operationMode);
Serial.print(", Battery: "); Serial.println(currentSystem.batteryLevel);
Serial.println("=====================");
}

系统流程图

三、项目演示

3.1 操作流程

(1)硬件连接和烧录

        按照接线表完成所有硬件连接,分别烧录发送端和接收端程序

(2)系统启动错误界面

        上电后观察启动界面和自检过程,当前显示为错误界面:"CAN Init Failed"

(3)观察DHT11温湿度变化

        切换到displayEnvData()温湿度数据显示界面,没有数据传输时屏幕底部显示"NO DATA RECEIVED"

3.2 演示视频

MCP2515实现CAN通信与数据采集

发送端获取MPU6050和DHT11传感器的数据,通过MCP2515 CAN通信模块传输到接收端零知增强板的显示屏上

四、MCP2515工作原理详解

      SPI总线的CAN控制器芯片MCP2515,通过SPI通信的CAN扩展芯片最高可实现1Mbps的遵循CAN 2.0B的协议通信

4.1 CAN总线结构

(1)闭环结构总线网络

        将MCP2515 模块上的120Ω电阻排针短接,CAN总线两端各连接一个120欧的电阻,两根信号线形成回路。采用CAN总线网络的闭环结构

        CAN总线由两根信号线 CANH和CANL,没有时钟同步信号。所以CAN是一种异步通信方式,与UART的异步通信方式类似

(2)额定总线电平

        信号线的电压差CANH-CANL表示CAN总线的电平,与传输的逻辑信号1或0对应。对应于逻辑1的称为隐性(Recessive)电平,对应于逻辑0成为显性(Dominant)电平

隐性电平在电压差0附近,显性电平主要在电压差2V附近

(3)CAN位时序和波特率

        通过位时序的控制,CAN总线可以进行位同步,以吸收节点时钟差异产生的波特率误差,保证接收数据的准确性

标称位事件(Nominal Bit Time,NBT)指的是传输一个位数据的时间,用于确定CNA总线的波特率

4.2 SPI接口数据交换

        MCP2515支持最高10MHz的SPI通信,可直接与微控制器上的SPI外设连接,并支持模式0和模式3,遵从SPI协议,可通过CS引脚片选的下拉开启通信;同时应注意,在传输另一个指令前应将片选置高后再拉低

SPI发送操作指令

        在MCP2515中,SPI发送的首个字节即为操作指令,上图为操作指令集

1)读指令

        将CS引脚拉低后,向MCP2515依次发送读指令和 8 位地址码, MCP2515 会将指定地址寄存器中的数据通过 SO引脚移出。每一数据字节移出后,器件内部的地址指针将自动加一以指向下一个地址。因此,通过持续提供时钟脉冲,可以对下一个连续地址寄存器进行读操作。通过该方法可以顺序读取任意个连续地址寄存器中的数据。通过拉高CS引脚电平可以结束读操作。

2)读接收缓冲区指令

        读取接收信息的常用指令,与读指令相比,省略了一个字节的地址位,同时会在CS引脚拉高后自动清零接收标志位(CANINTF.RXnIF),不用手动执行清零指令,强烈建议在读取接收缓冲区数据时使用本指令。

3)写指令

        将CS引脚拉低后,向MCP2515依次发送写指令、地址码和至少一个字节的数据(如果是多个数据,其地址是连续的)。

4)装载发送缓冲区指令

        和读取接收缓冲区类似,该指令也省略了一个字节的地址位,可以更快速的写入发送帧的标志ID、拓展ID、DLC和数据帧,同样强烈建议使用该指令装载发送帧。

5)请求发送指令

使用RTS命令可以启动一个或多个发送缓冲器的报文发送,该命令的bit3-bit0显示了哪些发送缓冲器被使能发送,该命令会将缓冲器对应的TxBnCTRL.TXREQ 位置1;如果发送的RTS命令中nnn =000,将忽略该命令。

6)位操作指令

该指令允许对寄存器的指定位进行置“1”或清零操作,在片选拉低后,以此发送位操作指令、寄存器地址、掩码和数据码,其中掩码位为“1”的允许进行更改,“0”则不允许更改;对于允许更改的位,数据码即为寄存器将被修改的目标码。

并非所有寄存器均支持位操作指令,只有标灰色的寄存器可进行位操作

7)读状态指令

        读状态指令允许单条指令访问常用的报文接收和发送状态位

8)RX状态指令

RX 状态指令用于快速确定与报文和报文类型(标准帧、扩展帧或远程帧)相匹配的过滤器。命令字节发送后,控制器会返回包含状态信息的 8 位数据

五、常见问题解答

Q1: 为什么CAN通信经常失败?

A: 常见原因包括:

        波特率设置不匹配、终端电阻未正确连接(120Ω)、SPI时序配置错误、电源噪声干扰

Q2: MPU6050数据读取异常怎么办?

A: 检查步骤:

        确认I2C地址是否正确(通常0x68或0x69)、检查电源电压是否稳定、验证I2C上拉电阻是否连接、检查传感器初始化序列

Q3: 数据发送间隔如何优化?

A: 根据应用需求调整:

uint32_t getSendInterval() {
switch(currentMode) {
case MODE_HIGH_PRECISE: return 50;   // 20Hz,实时控制
case MODE_NORMAL:       return 100;  // 10Hz,平衡模式
case MODE_LOW_POWER:    return 500;  // 2Hz,节能模式
}
}

项目资源整合

        MCP2515库文件:autowp/mcp2515

posted @ 2025-09-25 14:07  wzzkaifa  阅读(11)  评论(0)    收藏  举报