流程:

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文件里面,效果如下:

按下回车键后,我们会看到终端和存储文件里面在实时记录传感器的数据:

上位机的数据存储已经完成,你可以对这些数据做卡尔曼滤波,可以将滤波后的数据作为神经网络的输入来训练模型,等等。

最后的效果请看我的主页的 视频 作品

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐