MPU6050从数据采集到上位机通信,AI应用和算法研究前景
在新时代,我们一定要紧跟时代,AI的功能越来越多,越来越强。合理利用AI能极大的提高我们的工程速度,在造轮子的的事情上不要浪费时间。我们可以在此基础上,借助AI,在有了数据的情况下,开始我们的算法研究。在工程应用上,我们一般在有linux系统的开发板上运行代码,我们可以编写好windows格式的代码,将文件拷贝到板子上,修改一下文件路径、配置好环境、连接好硬件。来开始我们的产品开发旅程。开发出一个
流程:
1、在CUBEMX里面创建工程
点击中间的ACCESS TO MCU SELECTOR
点击左上角收藏的五角星,再点击右下角收藏的芯片stm32f103c8t6

点击右上角的start project开始工程

按照下面的配置操作:






一定要注意选择CMake(用vscode或qoder来打开),选择MDK-ARM就是用keil打开。最后点击右上角的GENERATE CODE来生成代码。配置好vscode,安装好qoder,在安装的过程中选择参考vscode,后面就能在qoder里面来打开vscode的工程。
在qoder里面打开文件夹
ctrl+l开启右侧的AI(也可以在vscode里面安装比如千问、trae等AI插件,然后直接在vscode里面操作,就不用下载qoder)。在qoder里面打开工程:

会生成基本的配置,接下来需要连接好mpu6050和单片机,连接好单片机和TTL转USB模块,连接好红蓝两个小灯(注意引脚,单片机输出两个高电平,所以灯的长的引脚接单片机引脚,短的接地)。
main.c文件里面,让AI采集传感器的数据,然后通过串口发送到电脑,串口软件及对应版本是sscom5.13.1,要选择好端口号、波特率(和在cubemx的要一样)
main.c如下:(采集数据并串口发送到串口助手,采样频率看这里:uint16_t sampleInterval = 100; // 采样间隔100ms(0.1秒))。写好main.c文件后,在keil里面打开这个工程文件,你每次在qoder里面修改好代码后,你回到keil都会提示代码已经更新,你只需要点击ok就行。接着点击编译,编译通过就行,编译不通过,就将报错复制并粘贴到qoder的右侧,来让AI修改代码,修改完成后,回到keil并点击ok,编译。编译通过后就用STLINK将代码烧录到单片机的flash。按一下单片机最小系统上面的启动按钮,程序就开始运行了,接着,打开上面的串口助手,会以1/100ms的频率输出数据到串口助手。
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2026 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
// MPU6050地址定义
#define MPU6050_ADDR 0xD0 // 7位地址0x68左移1位
#define MPU6050_PWR_MGMT_1 0x6B // 电源管理寄存器
#define MPU6050_ACCEL_XOUT_H 0x3B // 加速度数据起始寄存器
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart2;
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_I2C1_Init(void);
static void MX_USART2_UART_Init(void);
/* USER CODE BEGIN PFP */
void MPU6050_Init(void);
void MPU6050_ReadData(uint8_t *buffer);
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
/**
* @brief 初始化MPU6050传感器
* @retval None
*/
void MPU6050_Init(void)
{
uint8_t check;
uint8_t data;
char msg[80];
// 尝试主地址0xD0 (7位地址0x68)
HAL_StatusTypeDef ret = HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, 0x75, 1, &check, 1, 100);
if (ret != HAL_OK)
{
// 主地址失败,尝试备用地址0xD2 (7位地址0x69)
const char *tryMsg = "[INFO] 0xD0 failed, trying 0xD2...\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)tryMsg, strlen(tryMsg), 100);
ret = HAL_I2C_Mem_Read(&hi2c1, 0xD2, 0x75, 1, &check, 1, 100);
if (ret == HAL_OK && check == 0x68)
{
// 备用地址成功
sprintf(msg, "[OK] MPU6050 Found at 0xD2! ID=0x%02X\r\n", check);
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
// TODO: 需要将后续所有MPU6050_ADDR改为0xD2
return;
}
// 两个地址都失败,开始I2C总线扫描
const char *errMsg = "[ERROR] I2C Communication Failed!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)errMsg, strlen(errMsg), 100);
const char *scanMsg = "[INFO] Scanning I2C bus (0x01-0x7F)...\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)scanMsg, strlen(scanMsg), 100);
int foundCount = 0;
for (uint8_t addr = 1; addr < 128; addr++)
{
if (HAL_I2C_IsDeviceReady(&hi2c1, addr << 1, 1, 10) == HAL_OK)
{
sprintf(msg, " -> Device found at 0x%02X (7-bit: 0x%02X)\r\n", addr << 1, addr);
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
foundCount++;
}
}
if (foundCount == 0)
{
const char *noDevice = "\r\n[ERROR] No I2C devices found!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)noDevice, strlen(noDevice), 100);
const char *checkList = "Please check:\r\n"
" 1. SCL/SDA pins (PB6/PB7)\r\n"
" 2. Pull-up resistors (4.7k)\r\n"
" 3. MPU6050 power supply\r\n"
" 4. Wire connections\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)checkList, strlen(checkList), 100);
}
else
{
sprintf(msg, "\r\n[INFO] Total %d device(s) found\r\n", foundCount);
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
}
return;
}
if (check == 0x68) // MPU6050在线
{
// 发送成功消息
sprintf(msg, "[OK] MPU6050 Detected! ID=0x%02X at 0xD0\r\n", check);
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
// 唤醒MPU6050(写0x00到电源管理寄存器)
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, MPU6050_PWR_MGMT_1, 1, &data, 1, HAL_MAX_DELAY);
// 配置加速度量程为±2g(可选)
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, 0x1C, 1, &data, 1, HAL_MAX_DELAY);
// 配置陀螺仪量程为±250°/s(可选)
data = 0x00;
HAL_I2C_Mem_Write(&hi2c1, MPU6050_ADDR, 0x1B, 1, &data, 1, HAL_MAX_DELAY);
const char *okMsg = "[OK] MPU6050 Initialized!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)okMsg, strlen(okMsg), 100);
}
else
{
// 检测到设备但ID不对
sprintf(msg, "[ERROR] Wrong Device ID=0x%02X (Expected 0x68)\r\n", check);
HAL_UART_Transmit(&huart2, (uint8_t*)msg, strlen(msg), 100);
}
}
/**
* @brief 读取MPU6050所有数据(加速度+温度+陀螺仪)
* @param buffer: 存储14字节数据的缓冲区
* [0-1]: AccX (加速度X轴)
* [2-3]: AccY (加速度Y轴)
* [4-5]: AccZ (加速度Z轴)
* [6-7]: Temp (温度)
* [8-9]: GyroX (陀螺仪X轴)
* [10-11]:GyroY (陀螺仪Y轴)
* [12-13]:GyroZ (陀螺仪Z轴)
* @retval None
*/
void MPU6050_ReadData(uint8_t *buffer)
{
// 从0x3B寄存器开始连续读14字节
HAL_StatusTypeDef ret = HAL_I2C_Mem_Read(&hi2c1, MPU6050_ADDR, MPU6050_ACCEL_XOUT_H, 1, buffer, 14, HAL_MAX_DELAY);
if (ret != HAL_OK)
{
// 读取失败,填充错误标记
const char *errMsg = "[ERROR] Failed to read MPU6050 data!\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)errMsg, strlen(errMsg), 100);
memset(buffer, 0xFF, 14); // 填充0xFF表示错误
}
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART2_UART_Init();
/* USER CODE BEGIN 2 */
uint8_t receivedata[2];
uint8_t mpu6050Data[14]; // 存储MPU6050的14字节数据
uint32_t lastSampleTime = 0; // 上次采样时间
uint16_t sampleInterval = 100; // 采样间隔100ms(1秒)
uint8_t ledState = 0; // LED状态(0=红灯亮,1=蓝灯亮)
// 测试LED:上电后红灯闪3次,证明程序在运行
for (int i = 0; i < 3; i++)
{
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET); // 亮
HAL_Delay(200);
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET); // 灭
HAL_Delay(200);
}
// 初始化MPU6050
const char *initStartMsg = "\r\n=== System Initializing ===\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)initStartMsg, strlen(initStartMsg), 100);
MPU6050_Init();
HAL_Delay(100); // 等待传感器稳定
// 发送初始化完成提示
const char *initMsg = "\r\nSystem Ready!\r\n"
"Auto sampling every 1 second...\r\n"
"LED will toggle RED/BLUE on each data\r\n"
"Commands:\r\n"
" R1 - Manual read\r\n"
" S0 - Stop auto sampling\r\n"
" S1 - Start auto sampling\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)initMsg, strlen(initMsg), 100);
uint8_t autoSampling = 1; // 自动采样标志(1=开启,0=关闭)
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
// 自动采样模式:每隔sampleInterval毫秒采集一次数据
if (autoSampling)
{
uint32_t currentTime = HAL_GetTick();
if (currentTime - lastSampleTime >= sampleInterval)
{
lastSampleTime = currentTime;
// 读取MPU6050数据
MPU6050_ReadData(mpu6050Data);
// 发送时间戳
char timeBuf[40];
sprintf(timeBuf, "\r\n[%lu ms] ", currentTime);
HAL_UART_Transmit(&huart2, (uint8_t*)timeBuf, strlen(timeBuf), 100);
// 发送原始数据(十六进制)
char hexBuf[4];
for (int i = 0; i < 14; i++)
{
sprintf(hexBuf, "%02X ", mpu6050Data[i]);
HAL_UART_Transmit(&huart2, (uint8_t*)hexBuf, 3, 100);
}
// 解析并发送可读数据
int16_t accX = (mpu6050Data[0] << 8) | mpu6050Data[1];
int16_t accY = (mpu6050Data[2] << 8) | mpu6050Data[3];
int16_t accZ = (mpu6050Data[4] << 8) | mpu6050Data[5];
int16_t temp = (mpu6050Data[6] << 8) | mpu6050Data[7];
int16_t gyroX = (mpu6050Data[8] << 8) | mpu6050Data[9];
int16_t gyroY = (mpu6050Data[10] << 8) | mpu6050Data[11];
int16_t gyroZ = (mpu6050Data[12] << 8) | mpu6050Data[13];
char dataBuf[100];
sprintf(dataBuf, "| Acc:%d,%d,%d Gyro:%d,%d,%d T:%d\r\n",
accX, accY, accZ, gyroX, gyroY, gyroZ, temp);
HAL_UART_Transmit(&huart2, (uint8_t*)dataBuf, strlen(dataBuf), 100);
// LED红蓝交替闪烁(每次收到数据切换一次)
if (ledState == 0) {
// 红灯亮,蓝灯灭
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_SET);
ledState = 1;
} else {
// 蓝灯亮,红灯灭
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin, GPIO_PIN_RESET);
ledState = 0;
}
HAL_Delay(100); // 保持100ms
}
}
// 检查是否有串口指令(非阻塞接收)
if (HAL_UART_Receive(&huart2, receivedata, 2, 10) == HAL_OK)
{
// 判断指令类型
if ((receivedata[0] == 'R' || receivedata[0] == 'r') && receivedata[1] == '1')
{
// 【手动读取模式】
const char *readMsg = "\r\n[MANUAL] ";
HAL_UART_Transmit(&huart2, (uint8_t*)readMsg, strlen(readMsg), 100);
MPU6050_ReadData(mpu6050Data);
// 发送十六进制数据
char hexBuf[4];
for (int i = 0; i < 14; i++)
{
sprintf(hexBuf, "%02X ", mpu6050Data[i]);
HAL_UART_Transmit(&huart2, (uint8_t*)hexBuf, 3, 100);
}
HAL_UART_Transmit(&huart2, (uint8_t*)"\r\n", 2, 100);
// LED闪烁
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_RESET);
HAL_Delay(100);
HAL_GPIO_WritePin(LED_RED_GPIO_Port, LED_RED_Pin, GPIO_PIN_SET);
}
else if ((receivedata[0] == 'S' || receivedata[0] == 's') && receivedata[1] == '0')
{
// 【停止自动采样】
autoSampling = 0;
const char *stopMsg = "\r\n[INFO] Auto sampling STOPPED\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)stopMsg, strlen(stopMsg), 100);
}
else if ((receivedata[0] == 'S' || receivedata[0] == 's') && receivedata[1] == '1')
{
// 【启动自动采样】
autoSampling = 1;
lastSampleTime = HAL_GetTick(); // 重置时间
const char *startMsg = "\r\n[INFO] Auto sampling STARTED\r\n";
HAL_UART_Transmit(&huart2, (uint8_t*)startMsg, strlen(startMsg), 100);
}
else
{
// 【回环模式】
HAL_UART_Transmit(&huart2, receivedata, 2, 100);
}
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief I2C1 Initialization Function
* @param None
* @retval None
*/
static void MX_I2C1_Init(void)
{
/* USER CODE BEGIN I2C1_Init 0 */
/* USER CODE END I2C1_Init 0 */
/* USER CODE BEGIN I2C1_Init 1 */
/* USER CODE END I2C1_Init 1 */
hi2c1.Instance = I2C1;
hi2c1.Init.ClockSpeed = 100000;
hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
hi2c1.Init.OwnAddress1 = 0;
hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
hi2c1.Init.OwnAddress2 = 0;
hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
if (HAL_I2C_Init(&hi2c1) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN I2C1_Init 2 */
/* USER CODE END I2C1_Init 2 */
}
/**
* @brief USART2 Initialization Function
* @param None
* @retval None
*/
static void MX_USART2_UART_Init(void)
{
/* USER CODE BEGIN USART2_Init 0 */
/* USER CODE END USART2_Init 0 */
/* USER CODE BEGIN USART2_Init 1 */
/* USER CODE END USART2_Init 1 */
huart2.Instance = USART2;
huart2.Init.BaudRate = 115200;
huart2.Init.WordLength = UART_WORDLENGTH_8B;
huart2.Init.StopBits = UART_STOPBITS_1;
huart2.Init.Parity = UART_PARITY_NONE;
huart2.Init.Mode = UART_MODE_TX_RX;
huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart2.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart2) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN USART2_Init 2 */
/* USER CODE END USART2_Init 2 */
}
/**
* @brief GPIO Initialization Function
* @param None
* @retval None
*/
static void MX_GPIO_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/* USER CODE BEGIN MX_GPIO_Init_1 */
/* USER CODE END MX_GPIO_Init_1 */
/* GPIO Ports Clock Enable */
__HAL_RCC_GPIOD_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*Configure GPIO pin Output Level */
HAL_GPIO_WritePin(GPIOA, LED_RED_Pin|LED_BLUE_Pin, GPIO_PIN_SET);
/*Configure GPIO pins : LED_RED_Pin LED_BLUE_Pin */
GPIO_InitStruct.Pin = LED_RED_Pin|LED_BLUE_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN MX_GPIO_Init_2 */
/* USER CODE END MX_GPIO_Init_2 */
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */
上面的单片机作为下位机,工作已经全部完成了
我们需要对采集来的数据做一个应用,所以数据不能只在串口助手输出,需要输出到一个文档里面,我这里将数据存储到了一个csv文件,当然你也可以将数据存储到txt等其他文件里面。
存储数据到mpu6050_data.csv文件里面需要编写python程序serial_logger.py,当然这个程序也可以用c/c++,serial_logger.py代码如下:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
STM32 MPU6050 串口数据记录器 (CSV格式)
自动保存串口数据到CSV文件,支持实时显示和Excel分析
"""
import serial
import time
from datetime import datetime
import os
import re
import csv
# ==================== 配置参数 ====================
SERIAL_PORT = 'COM3' # 修改为你的串口号(Windows: COM3, Linux: /dev/ttyUSB0)
BAUD_RATE = 115200
SAVE_DIR = 'serial_logs' # 保存目录
FILE_PREFIX = 'mpu6050_data' # 文件名前缀
# ==================== 创建保存目录 ====================
if not os.path.exists(SAVE_DIR):
os.makedirs(SAVE_DIR)
print(f"[INFO] 创建目录: {SAVE_DIR}")
# ==================== 生成文件名 ====================
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
filename = os.path.join(SAVE_DIR, f"{FILE_PREFIX}.csv")
print("=" * 60)
print(" STM32 MPU6050 串口数据记录器")
print("=" * 60)
print(f"串口: {SERIAL_PORT}")
print(f"波特率: {BAUD_RATE}")
print(f"保存文件: {filename}")
print("=" * 60)
print("按 Ctrl+C 停止记录\n")
# ==================== 打开串口和文件 ====================
line_count = 0 # 初始化计数器
try:
ser = serial.Serial(SERIAL_PORT, BAUD_RATE, timeout=1)
print(f"[OK] 串口已打开: {SERIAL_PORT}")
with open(filename, 'w', encoding='utf-8-sig', newline='') as f:
# 创建CSV写入器
csv_writer = csv.writer(f)
# 写入CSV表头
csv_writer.writerow([
'PC时间', 'MCU时间(ms)',
'AccX', 'AccY', 'AccZ',
'GyroX', 'GyroY', 'GyroZ',
'Temp',
'原始数据'
])
f.flush()
print(f"\n[OK] CSV表头已写入")
print(f"表头: PC时间, MCU时间, AccX, AccY, AccZ, GyroX, GyroY, GyroZ, Temp, 原始数据\n")
line_count = 0
# 主循环:读取并保存数据
while True:
if ser.in_waiting > 0:
try:
# 读取一行数据
line = ser.readline().decode('utf-8', errors='ignore').strip()
if line and '[' in line and 'ms]' in line:
# 添加PC时间戳
pc_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
# 解析数据:提取MCU时间戳和传感器数值
# 格式: [7377 ms] DF 00 ... | Acc:-8448,-68,14848 Gyro:-310,-414,-4 T:-4832
# 提取MCU时间
mcu_time_match = re.search(r'\[(\d+) ms\]', line)
mcu_time = mcu_time_match.group(1) if mcu_time_match else ''
# 提取原始十六进制数据
hex_match = re.search(r'\] ([0-9A-F ]+) \|', line)
hex_data = hex_match.group(1) if hex_match else ''
# 提取加速度数据 Acc:x,y,z
acc_match = re.search(r'Acc:(-?\d+),(-?\d+),(-?\d+)', line)
if acc_match:
acc_x = acc_match.group(1)
acc_y = acc_match.group(2)
acc_z = acc_match.group(3)
else:
acc_x = acc_y = acc_z = ''
# 提取陀螺仪数据 Gyro:x,y,z
gyro_match = re.search(r'Gyro:(-?\d+),(-?\d+),(-?\d+)', line)
if gyro_match:
gyro_x = gyro_match.group(1)
gyro_y = gyro_match.group(2)
gyro_z = gyro_match.group(3)
else:
gyro_x = gyro_y = gyro_z = ''
# 提取温度数据 T:value
temp_match = re.search(r'T:(-?\d+)', line)
temp = temp_match.group(1) if temp_match else ''
# 写入CSV行
csv_writer.writerow([
pc_time, mcu_time,
acc_x, acc_y, acc_z,
gyro_x, gyro_y, gyro_z,
temp,
hex_data
])
f.flush() # 立即刷新到文件
# 终端显示(美化输出)
print(f"[{line_count+1:04d}] {pc_time} | MCU:{mcu_time:>6}ms | "
f"Acc:({acc_x:>6},{acc_y:>6},{acc_z:>6}) | "
f"Gyro:({gyro_x:>4},{gyro_y:>4},{gyro_z:>4}) | T:{temp:>5}")
line_count += 1
# 每50行提示一次
if line_count % 50 == 0:
print(f"\n[INFO] ✅ 已记录 {line_count} 行数据到CSV\n")
elif line: # 其他信息行(如初始化信息)
print(f"[INFO] {line}")
except UnicodeDecodeError:
print("[WARNING] 解码错误,跳过此行")
continue
except Exception as e:
print(f"[WARNING] 数据解析错误: {e}")
continue
time.sleep(0.01) # 短暂休眠,降低CPU占用
except serial.SerialException as e:
print(f"[ERROR] 串口错误: {e}")
print(f"[TIP] 请检查:")
print(f" 1. 串口号是否正确(当前: {SERIAL_PORT})")
print(f" 2. 设备是否已连接")
print(f" 3. 串口是否被其他程序占用")
except KeyboardInterrupt:
print("\n\n[INFO] 用户中断,正在保存...")
finally:
try:
if ser.is_open:
ser.close()
print(f"[OK] 串口已关闭")
except:
pass
print(f"[OK] 数据已保存到: {filename}")
print(f"[INFO] 共记录 {line_count} 行数据")
print("\n程序结束")
在运行python代码之前,我们需要安装一个环境,来放我们用到的库。首先我们需要在电脑(或者开发板)上(带windows或者ubuntu系统)安装好anaconda,anaconda自带python编译器,就不用安装python编译器了。在终端用
conda create -n mpu6050 python=3.10
来创建一个名为mpu6050的环境,这个环境里面安装好了我们写的3.10版本的python,注意python=3.10的等号前后不能有空格。
安装好环境后,在终端用
conda activate mpu6050
来进入我们创建的环境,目前我们的环境里面只有版本为3.10的python,我们的python文件里面代码显示需要安装下面这些库

以第一个为例:在终端进入环境后,在终端输入
pip install pyserial
来安装好库。安装好每个库以后,由于下位机一直在运行,我们需要启动上位机程序,来记录数据到文件里面。在终端进入环境后输入
python serial_logger.py
来执行上位机的数据存储程序。数据会被存储到serial_logs\mpu6050_data.csv文件里面,效果如下:
按下回车键后,我们会看到终端和存储文件里面在实时记录传感器的数据:

上位机的数据存储已经完成,你可以对这些数据做卡尔曼滤波,可以将滤波后的数据作为神经网络的输入来训练模型,等等。
最后的效果请看我的主页的 视频 作品
更多推荐



所有评论(0)