✔零知IDE 是一个真正属于国人自己的开源软件平台,在开发效率上超越了Arduino平台并且更加容易上手,大大降低了开发难度。零知开源在软件方面提供了完整的学习教程和丰富示例代码,让不懂程序的工程师也能非常轻而易举的搭建电路来创作产品,测试产品。快来动手试试吧!
✔访问零知实验室,获取更多实战项目和教程资源吧!
目录
[1.1 硬件清单](#1.1 硬件清单)
[1.2 接线方案](#1.2 接线方案)
[1.3 具体接线图](#1.3 具体接线图)
[1.4 连接实物图](#1.4 连接实物图)
[2.1 发送端代码](#2.1 发送端代码)
[2.2 接收端代码](#2.2 接收端代码)
[3.1 操作流程](#3.1 操作流程)
[3.2 演示视频](#3.2 演示视频)
[4.1 CAN总线结构](#4.1 CAN总线结构)
[4.2 SPI接口数据交换](#4.2 SPI接口数据交换)
[Q1: 为什么CAN通信经常失败?](#Q1: 为什么CAN通信经常失败?)
[Q2: MPU6050数据读取异常怎么办?](#Q2: MPU6050数据读取异常怎么办?)
[Q3: 数据发送间隔如何优化?](#Q3: 数据发送间隔如何优化?)
(1)项目概述
本项目基于STM32F407VET6主控芯片和MCP2515 CAN通信模块,实现了一个完整的多数据传感器CAN模块通信的监控系统。通过分层架构设计和模块化编程,实现了稳定可靠的数据采集、传输和显示功能
(2)项目难点及解决方案
问题描述1:CAN标准数据帧最多8字节,但传感器数据量较大
解决方案:采用数据分包传输策略,将IMU数据拆分为两个CAN消息
一、硬件系统部分
1.1 硬件清单
(1)发送端组件
组件 | 型号 | 数量 | 用途 |
---|---|---|---|
主控板 | STM32F407VET6 | 1 | 数据处理与控制 |
CAN模块 | MCP2515+TJA1050 | 1 | CAN通信 |
加速度传感器 | MPU6050 | 1 | 运动状态检测 |
温湿度传感器 | DHT11 | 1 | 环境监测 |
按键 | tactile switch | 1 | 模式切换 |
(2)接收端组件
组件 | 型号 | 数量 | 用途 |
---|---|---|---|
主控板 | STM32F407VET6 | 1 | 数据显示与控制 |
CAN模块 | MCP2515+TJA1050 | 1 | CAN通信 |
显示屏 | ST7789 240×240 | 1 | 状态显示 |
按键 | tactile switch | 2 | 界面切换 |
1.2 接线方案
(1)发送端引脚
零知增强板引脚 | 功能 | 连接器件 | 备注 |
---|---|---|---|
53 | SPI_CS | MCP2515_CS | CAN片选 |
52 | SPI_SCK | MCP2515_SCK | SPI时钟 |
50 | SPI_MISO | MCP2515_MISO | SPI输入 |
51 | SPI_MOSI | MCP2515_MOSI | SPI输出 |
21/SCL | I2C_SCL | MPU6050_SCL | I2C时钟 |
20/SDA | I2C_SDA | MPU6050_SDA | I2C数据 |
7 | DATA | DHT11_DATA | 温湿度数据 |
2 | INT | MODE_BUTTON | 模式切换 |
LED_BUILTIN | LED | SELF_TEST_LED | 状态指示 |
(2) 接收端引脚
零知增强板引脚 | 功能 | 连接器件 | 备注 |
---|---|---|---|
53 | SPI_CS | MCP2515_CS | CAN片选 |
52 | SPI_SCK | MCP2515_SCK | SPI时钟 |
50 | SPI_MISO | MCP2515_MISO | SPI输入 |
51 | SPI_MOSI | MCP2515_MOSI | SPI输出 |
6 | DC | ST7789_DC | 数据/命令 |
7 | RST | ST7789_RST | 复位 |
10 | CS | ST7789_CS | 片选 |
9 | SCLK | ST7789_SCLK | 时钟 |
8 | MOSI | ST7789_MOSI | 数据输入 |
12 | INT | SELF_TEST_BUTTON | 自检按钮 |
13 | INT | DISPLAY_MODE_BUTTON | 显示切换 |
接收端部分的显示屏使用软件SPI串口、MCP2515 CAN通信模块使用硬件SPI串口
1.3 具体接线图
(1)发送端接线图

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

1.4 连接实物图

二、代码解析部分
2.1 发送端代码
(1)核心数据结构
cpp
// 精心设计的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)数据采集算法
cpp
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通信核心实现
cpp
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)发送端完整代码
cpp
#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();
// 初始化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 < 100) {
printDebugInfo(imuData1, imuData2, envData, sysData);
}
lastSendTime = currentTime;
}
updateStatusLED();
}
// 系统自检
void selfTest() {
Serial.println("=== System Self-Test ===");
digitalWrite(SELF_TEST_LED, HIGH);
// 测试MPU6050
Serial.print("Testing MPU6050... ");
mpu.initialize();
if (mpu.testConnection()) {
Serial.println("OK");
} else {
Serial.println("FAILED");
systemStatus = STATUS_ERROR;
return;
}
// 测试DHT11
Serial.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; // 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)显示系统架构设计
cpp
// 显示模式状态机
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)数据接收与处理
cpp
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)接收端完整代码
cpp
#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 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 < 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(¤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));
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: 根据应用需求调整:
cpp
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