当前位置: 首页 > news >正文

零知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.53data.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 <SPI.h>
#include <mcp2515.h>
#include <Wire.h>
#include <MPU6050.h>
#include <DHT.h>// 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();// 初始化MCP2515if (!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 < 100) {printDebugInfo(imuData1, imuData2, envData, sysData);}lastSendTime = currentTime;}updateStatusLED();
}// 系统自检
void selfTest() {Serial.println("=== System Self-Test ===");digitalWrite(SELF_TEST_LED, HIGH);// 测试MPU6050Serial.print("Testing MPU6050... ");mpu.initialize();if (mpu.testConnection()) {Serial.println("OK");} else {Serial.println("FAILED");systemStatus = STATUS_ERROR;return;}// 测试DHT11Serial.print("Testing DHT11... ");dht.begin();delay(100);float testTemp = dht.readTemperature();if (!isnan(testTemp)) {Serial.println("OK");} else {Serial.println("FAILED");systemStatus = STATUS_ERROR;return;}// 测试CAN(在initCAN中完成)Serial.println("Self-test completed successfully");digitalWrite(SELF_TEST_LED, LOW);delay(500);digitalWrite(SELF_TEST_LED, HIGH);delay(500);digitalWrite(SELF_TEST_LED, LOW);
}// 运行自检模式
void runSelfTest() {static unsigned long lastTestTime = 0;unsigned long currentTime = millis();if (currentTime - lastTestTime >= 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 < -4000 || env.temperature > 8000 || env.humidity < 0 || 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;   // 20Hzcase MODE_NORMAL:       return 100;  // 10Hzcase MODE_LOW_POWER:    return 500;  // 2Hzdefault:                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(&currentIMU1, canMsg.data, sizeof(currentIMU1));break;case 0x102: // IMU数据第二部分memcpy(&currentIMU2, canMsg.data, sizeof(currentIMU2));// 可添加数据完整性校验break;case 0x103: // 环境数据memcpy(&currentEnv, canMsg.data, sizeof(currentEnv));break;case 0x104: // 系统状态memcpy(&currentSystem, canMsg.data, sizeof(currentSystem));// 更新系统状态指示updateSystemStatus();break;}// 设置显示刷新标志,避免频繁刷新displayNeedsRefresh = true;}
}

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

(3)接收端完整代码

#include <SPI.h>
#include <mcp2515.h>
#include <Adafruit_GFX.h>
#include <Adafruit_ST7789.h>// 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 0x7800void 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();// 初始化CANif (!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 < 300) return;// 自检按钮if (digitalRead(SELF_TEST_BUTTON) == LOW) {selfTestMode = !selfTestMode;displayNeedsRefresh = true;lastButtonTime = currentTime;Serial.println(selfTestMode ? "Entering self-test mode" : "Exiting self-test mode");return;}// 显示模式切换按钮if (digitalRead(DISPLAY_MODE_BUTTON) == LOW) {displayMode = (displayMode + 1) % 4; // 循环0-3,自检模式单独处理displayNeedsRefresh = true;lastButtonTime = currentTime;Serial.print("Display mode changed to: ");Serial.println(displayMode);}
}// 处理CAN消息
void processCANMessages() {if (mcp2515.readMessage(&canMsg) == MCP2515::ERROR_OK) {lastDataTime = millis();switch(canMsg.can_id) {case 0x101: // IMU数据第一部分memcpy(&currentIMU1, canMsg.data, sizeof(currentIMU1));break;case 0x102: // IMU数据第二部分memcpy(&currentIMU2, canMsg.data, sizeof(currentIMU2));break;case 0x103: // 环境数据memcpy(&currentEnv, canMsg.data, sizeof(currentEnv));break;case 0x104: // 系统数据memcpy(&currentSystem, canMsg.data, sizeof(currentSystem));break;}displayNeedsRefresh = true;}
}// 更新显示(局部刷新)
void updateDisplay() {if (!displayNeedsRefresh) return;switch(displayMode) {case DISPLAY_MODE_OVERVIEW:displayOverview();break;case DISPLAY_MODE_IMU:displayIMUData();break;case DISPLAY_MODE_ENV:displayEnvData();break;case DISPLAY_MODE_SYSTEM:displaySystemInfo();break;}displayNeedsRefresh = false;
}// 显示概览界面
void displayOverview() {// 清屏(只清除内容区域,保留标题栏)tft.fillRect(0, 30, 240, 210, ST77XX_BLACK);// 显示标题displayHeader("OVERVIEW");// 显示IMU数据tft.setCursor(10, 50);tft.setTextColor(ST77XX_WHITE);tft.setTextSize(2);tft.print("Accel: ");tft.print(currentIMU1.accelX); tft.print(", ");tft.print(currentIMU1.accelY); tft.print(", ");tft.println(currentIMU1.accelZ);tft.setCursor(10, 80);tft.print("Gyro:  ");tft.print(currentIMU1.gyroX); tft.print(", ");tft.print(currentIMU2.gyroY); tft.print(", ");tft.println(currentIMU2.gyroZ);// 显示环境数据tft.setCursor(10, 120);tft.print("Temp:  ");tft.print(currentEnv.temperature / 100.0, 1);tft.println(" C");tft.setCursor(10, 150);tft.print("Humidity: ");tft.print(currentEnv.humidity / 100.0, 1);tft.println(" %");// 显示系统状态tft.setCursor(10, 190);tft.setTextColor(systemWarning ? ST77XX_RED : ST77XX_GREEN);tft.print("Status: ");tft.println(systemWarning ? "WARNING" : "NORMAL");
}// 显示IMU数据界面
void displayIMUData() {tft.fillRect(0, 30, 240, 210, ST77XX_BLACK);displayHeader("IMU DATA");tft.setTextSize(2);// 加速度数据tft.setCursor(10, 50);tft.setTextColor(ST77XX_CYAN);tft.println("Acceleration:");tft.setCursor(20, 80);tft.setTextColor(ST77XX_WHITE);tft.print("X: "); tft.println(currentIMU1.accelX);tft.setCursor(20, 110);tft.print("Y: "); tft.println(currentIMU1.accelY);tft.setCursor(20, 140);tft.print("Z: "); tft.println(currentIMU1.accelZ);// 陀螺仪数据tft.setCursor(10, 170);tft.setTextColor(ST77XX_CYAN);tft.println("Gyroscope:");tft.setCursor(20, 200);tft.setTextColor(ST77XX_WHITE);tft.print("X: "); tft.println(currentIMU1.gyroX);
}// 显示环境数据界面
void displayEnvData() {tft.fillRect(0, 30, 240, 210, ST77XX_BLACK);displayHeader("ENVIRONMENT");tft.setTextSize(3);// 温度显示tft.setCursor(50, 80);tft.setTextColor(ST77XX_RED);tft.print(currentEnv.temperature / 100, 1);tft.println(" C");// 湿度显示tft.setCursor(50, 140);tft.setTextColor(ST77XX_BLUE);tft.print(currentEnv.humidity / 100, 1);tft.println(" %");// 数据更新时间tft.setTextSize(1);tft.setCursor(10, 200);tft.setTextColor(ST77XX_WHITE);tft.print("Last update: ");tft.print((millis() - currentEnv.readTime) / 1000);tft.println("s ago");
}// 显示系统信息界面
void displaySystemInfo() {tft.fillRect(0, 30, 240, 210, ST77XX_BLACK);displayHeader("SYSTEM INFO");tft.setTextSize(2);// 系统状态tft.setCursor(10, 50);tft.setTextColor(getStatusColor(currentSystem.systemStatus));tft.print("Status: ");switch(currentSystem.systemStatus) {case 0: tft.println("SELF-TEST"); break;case 1: tft.println("NORMAL"); break;case 2: tft.println("ERROR"); break;default: tft.println("UNKNOWN"); break;}// 操作模式tft.setCursor(10, 80);tft.setTextColor(ST77XX_YELLOW);tft.print("Mode: ");switch(currentSystem.operationMode) {case 0: tft.println("NORMAL"); break;case 1: tft.println("HIGH PRECISE"); break;case 2: tft.println("LOW POWER"); break;default: tft.println("UNKNOWN"); break;}// 电池电量tft.setCursor(10, 110);tft.setTextColor(getBatteryColor(currentSystem.batteryLevel));tft.print("Battery: ");tft.print(currentSystem.batteryLevel);tft.println(" %");// 运行时间tft.setCursor(10, 140);tft.setTextColor(ST77XX_WHITE);tft.print("Uptime: ");tft.print(currentSystem.uptime / 1000);tft.println(" s");// 错误代码if (currentSystem.errorCode != 0) {tft.setCursor(10, 170);tft.setTextColor(ST77XX_RED);tft.print("Error: ");tft.println(currentSystem.errorCode);}
}// 显示标题栏
void displayHeader(const char* title) {// 标题栏背景tft.fillRect(0, 0, 240, 30, ST77XX_BLUE);// 标题文字tft.setTextSize(2);tft.setTextColor(ST77XX_WHITE);tft.setCursor(10, 8);tft.println(title);// 模式指示器tft.setCursor(180, 8);tft.print("M");tft.print(displayMode + 1);
}// 检查数据超时
void checkDataTimeout() {if (millis() - lastDataTime > 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 < 3000 ? ST77XX_GREEN : ST77XX_RED);tft.print("Data Age: ");tft.print(timeSinceLastData / 1000);tft.println("s");// 系统运行时间tft.setCursor(10, 180);tft.setTextColor(ST77XX_WHITE);tft.print("Uptime: ");tft.print((millis() - startTime) / 1000);tft.println("s");// 显示模式提示tft.setCursor(10, 210);tft.setTextColor(ST77XX_CYAN);tft.println("Press TEST to exit");
}// 显示启动界面
void showStartupScreen() {tft.fillScreen(ST77XX_BLACK);tft.setTextSize(3);tft.setTextColor(ST77XX_GREEN);tft.setCursor(30, 80);tft.println("VEHICLE");tft.setCursor(50, 120);tft.println("MONITOR");tft.setTextSize(2);tft.setTextColor(ST77XX_WHITE);tft.setCursor(40, 170);tft.println("System Ready");delay(2000);
}// 显示错误屏幕
void showErrorScreen(const char* error) {tft.fillScreen(ST77XX_RED);tft.setTextSize(2);tft.setTextColor(ST77XX_WHITE);tft.setCursor(20, 100);tft.println("SYSTEM ERROR");tft.setCursor(20, 130);tft.println(error);
}// 获取状态颜色
uint16_t getStatusColor(uint8_t status) {switch(status) {case 0: return ST77XX_YELLOW;  // 自检case 1: return ST77XX_GREEN;   // 正常case 2: return ST77XX_RED;     // 错误default: return ST77XX_WHITE;}
}// 获取电池颜色
uint16_t getBatteryColor(uint8_t level) {if (level > 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      

http://www.dtcms.com/a/403045.html

相关文章:

  • 若依框架-Spring Boot
  • 全新 CloudPilot AI:嵌入 Kubernetes 的 SRE Agent,降本与韧性双提升!
  • 自建网站推广的最新发展wordpress同步到报价号
  • 4、导线、端子及印制电路板元器件的插装、焊接及拆焊
  • 【Java八股文】13-中间件面试篇
  • (四)优雅重构:洞悉“搬移特性”的艺术与实践
  • 网站建设专用图形库商务网站建设方案
  • 快速入门HarmonyOS应用开发(三)
  • Easysearch 国产替代 Elasticsearch:8 大核心问题解读
  • 【机器学习】搭建对抗神经网络模型来实现 MNIST 手写数字生成
  • 做推广的网站那个好中国机房建设公司排名
  • odoo18应用、队列服务器分离(SSHFS)
  • 老年健康管理小工具抖音快手微信小程序看广告流量主开源
  • c#vb.net动态创建二维数组
  • php做网站完整视频动漫制作和动漫设计哪个好
  • 云原生微服务中间件选型
  • Python/JS/Go/Java同步学习(第二十四篇)四语言“元组概念“对照表: 雷影“老板“发飙要求员工下班留校培训风暴(附源码/截图/参数表/避坑指南)
  • vue3在 script 中定义组件
  • 【CSRF】防御
  • vue从template模板到真实渲染在页面上发生了什么
  • 从构建工具到状态管理:React项目全栈技术选型指南
  • 做彩票网站电话多少钱湛江网站网站建设
  • 云手机性能会受到哪些因素的影响?
  • app网站维护网站开发众包平台
  • [iOS] OC高级编程 - 引用计数 (1)
  • MyBatis-Plus实用指南:玩转自动化与高效CRUD
  • 揭开AI神秘面纱:大语言模型原理与Python极简开发
  • cmake详解
  • RabbitMQ-高可用机制
  • 云手机对网络游戏的重要性