零知IDE——基于STM32F407VET6和MCP2515实现CAN通信与数据采集

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

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

www.lingzhilab.com

目录

一、硬件系统部分

[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 演示视频)

四、MCP2515工作原理详解

[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(&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)接收端完整代码

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(&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: 根据应用需求调整:

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

相关推荐
big\hero4 小时前
基于STM32设计的智能手环(ESP8266+华为云IOT)
stm32·物联网·华为云
天將明°4 小时前
错误追踪技术指南:让Bug无处可逃的追踪网
c语言·单片机·嵌入式硬件
JiaWen技术圈4 小时前
关于【机器人小脑】的快速入门介绍
单片机·嵌入式硬件·机器人·硬件架构
明飞19876 小时前
android-USB-STM32
stm32
GilgameshJSS7 小时前
STM32H743-ARM例程2-UART命令控制LED
arm开发·stm32·单片机·嵌入式硬件
糖糖单片机设计12 小时前
硬件开发_基于STM32单片机的汽车急控系统
stm32·单片机·嵌入式硬件·物联网·汽车·51单片机
仰望星空的凡人12 小时前
一文了解瑞萨MCU常用的芯片封装类型
单片机·嵌入式硬件·瑞萨·封装方式
小莞尔13 小时前
【51单片机】【protues仿真】基于51单片机恒温箱系统
c语言·开发语言·单片机·嵌入式硬件·51单片机
阿华学长单片机设计14 小时前
【开源】基于STM32的智能车尾灯
stm32·单片机·嵌入式硬件