从零打造百元级STM32智能手表环境监测与游戏开发实战在创客圈里智能穿戴设备一直是热门DIY项目。但市面上大多数教程要么成本高昂要么功能单一。本文将带你用STM32F103C8T6这款性价比极高的MCU配合常见传感器打造一款成本控制在120元左右的实用型智能手表。不同于简单demo我们将实现环境数据监测温湿度气压、体感交互抬手亮屏和可扩展的游戏功能并提供完整的工程源码。1. 硬件选型与成本控制策略1.1 核心器件清单下表是经过多次迭代验证的最优BOM方案部件型号单价元采购渠道MCU最小系统板STM32F103C8T615-20主流电商平台环境传感器BME28012-18电子元器件商城运动传感器MPU60508-12同上OLED显示屏0.96寸SSD130618-25同上锂电池管理模块TP40565-8同上其他电阻/电容-≈10本地电子市场提示BME280建议选择I2C接口版本避免与MPU6050的地址冲突问题。实际采购时可批量购买进一步降低成本。1.2 硬件连接要点核心电路连接遵循以下原则电源部分TP4056模块为3.7V锂电池提供充放电管理输出端需接100μF电容稳压传感器接口BME280的SCL/SDA接PB6/PB7I2C1MPU6050的SCL/SDA接PB10/PB11I2C2显示模块SSD1306使用SPI接口CS接PA4DC接PA3// 硬件初始化代码片段 void Hardware_Init() { GPIO_InitTypeDef GPIO_InitStruct; // I2C1初始化BME280 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_OD; GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); // SPI初始化OLED RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin GPIO_Pin_3 | GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; GPIO_Init(GPIOA, GPIO_InitStruct); }2. BME280传感器驱动开发2.1 寄存器配置技巧BME280需要正确设置工作模式和补偿参数。关键配置步骤如下读取芯片ID0xD0确认设备正常加载出厂校准参数0x88~0xA1, 0xE1~0xE7设置控制寄存器温度/压力oversampling设为×2湿度oversampling设为×1模式选择forced mode// BME280初始化示例 uint8_t BME280_Init() { uint8_t id I2C_ReadByte(BME280_ADDR, 0xD0); if(id ! 0x60) return 1; // 设备检测失败 // 读取补偿参数 dig_T1 I2C_ReadU16(BME280_ADDR, 0x88); dig_T2 (int16_t)I2C_ReadU16(BME280_ADDR, 0x8A); // ...其他参数读取 // 配置测量模式 uint8_t ctrl_hum 0x01; // 湿度x1 uint8_t ctrl_meas (0x02 5) | (0x02 2) | 0x01; // 温度x2,压力x2,强制模式 I2C_WriteByte(BME280_ADDR, 0xF2, ctrl_hum); I2C_WriteByte(BME280_ADDR, 0xF4, ctrl_meas); return 0; }2.2 数据补偿算法优化原始传感器数据需要经过补偿计算才能得到准确值。针对STM32F103的M3内核我们采用定点数运算优化浮点计算int32_t compensate_T(int32_t adc_T) { int32_t var1, var2, T; var1 ((((adc_T3) - ((int32_t)dig_T11))) * ((int32_t)dig_T2)) 11; var2 (((((adc_T4) - ((int32_t)dig_T1)) * ((adc_T4) - ((int32_t)dig_T1))) 12) * ((int32_t)dig_T3)) 14; t_fine var1 var2; T (t_fine * 5 128) 8; return T; }注意BME280每次读取数据后需要重新触发测量建议设置1Hz的定时采样频率。3. 运动感知与低功耗设计3.1 MPU6050 DMP库移植使用DMPDigital Motion Processor可以大幅降低MCU的运算负担从官方MotionDriver包中提取以下关键文件inv_mpu.cinv_mpu_dmp_motion_driver.ceMPL_outputs.c修改I2C底层驱动接口// 在inv_mpu.c中替换以下函数 int i2c_write(unsigned char slave_addr, unsigned char reg_addr, unsigned char length, unsigned char const *data) { return I2C_WriteBytes(slave_addr, reg_addr, data, length); } int i2c_read(unsigned char slave_addr, unsigned char reg_addr, unsigned char length, unsigned char *data) { return I2C_ReadBytes(slave_addr, reg_addr, data, length); }初始化流程mpu_init(); mpu_set_sensors(INV_XYZ_GYRO | INV_XYZ_ACCEL); mpu_configure_fifo(INV_XYZ_GYRO | INV_XYZ_ACCEL); dmp_load_motion_driver_firmware(); dmp_set_orientation(inv_orientation_matrix_to_scalar(gyro_orientation)); dmp_enable_feature(DMP_FEATURE_TAP | DMP_FEATURE_SEND_RAW_ACCEL); dmp_set_fifo_rate(10); // 10Hz采样率 mpu_set_dmp_state(1); // 启用DMP3.2 低功耗优化方案通过以下策略实现72小时续航电源模式管理无操作30秒后进入STOP模式MPU6050配置为运动中断唤醒OLED完全断电而非仅关闭显示代码实现void Enter_LowPower_Mode() { // 关闭外设时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_ALL, DISABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ALL, DISABLE); // 配置唤醒源 EXTI_InitTypeDef EXTI_InitStruct; EXTI_InitStruct.EXTI_Line EXTI_Line0; // PA0连接MPU6050 INT EXTI_InitStruct.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStruct); // 进入STOP模式 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); // 唤醒后恢复时钟 SystemInit(); }4. 功能集成与界面设计4.1 多任务调度实现采用时间片轮转方式管理各功能模块typedef struct { uint32_t interval; uint32_t last_run; void (*task)(void); } Task_t; Task_t tasks[] { {100, 0, Sensor_Update}, // 10Hz传感器更新 {200, 0, Battery_Update}, // 5Hz电量检测 {33, 0, UI_Refresh}, // 30fps界面刷新 {1000, 0, RTC_Sync} // 1Hz时间同步 }; void Scheduler_Run() { uint32_t now millis(); for(int i0; isizeof(tasks)/sizeof(Task_t); i) { if(now - tasks[i].last_run tasks[i].interval) { tasks[i].task(); tasks[i].last_run now; } } }4.2 表盘UI设计要点使用SSD1306的framebuffer实现双缓冲避免闪烁创建128x64的显示缓冲区uint8_t oledBuffer[1024]; // 128x64/8实现基础绘图APIvoid Draw_Pixel(uint8_t x, uint8_t y, uint8_t color) { if(x 128 || y 64) return; uint16_t idx x (y/8)*128; if(color) oledBuffer[idx] | (1 (y%8)); else oledBuffer[idx] ~(1 (y%8)); } void Draw_Char(uint8_t x, uint8_t y, char ch) { uint8_t i,j; for(i0; i5; i) { uint8_t line font_5x7[ch*5 i]; for(j0; j7; j) { if(line (1j)) Draw_Pixel(xi, yj, 1); } } }环境数据可视化方案void Draw_Environment() { // 温度计图标 for(int i0; i20; i) Draw_Pixel(5, 60-i, 1); // 动态柱状图 int temp_height map(temperature, 10, 40, 0, 20); for(int i0; itemp_height; i) for(int j0; j3; j) Draw_Pixel(3j, 60-i, 1); // 湿度百分比显示 char str[16]; sprintf(str, H:%d%%, humidity); Draw_String(90, 10, str); }5. 游戏功能开发与系统整合5.1 音乐播放器实现利用PWM产生不同频率方波实现8bit音乐void Play_Tone(uint32_t freq, uint32_t duration) { if(freq 0) { TIM_SetCompare1(TIM3, 0); // 静音 delay_ms(duration); return; } uint32_t arr SystemCoreClock / freq / 2 - 1; TIM3-ARR arr; TIM_SetCompare1(TIM3, arr/2); delay_ms(duration); } void Play_Melody(const uint32_t *notes) { while(1) { uint32_t note *notes; if(note TONE_REPEAT) notes melody_start; if(note TONE_END) break; uint32_t freq note 16; uint32_t duration note 0xFFFF; Play_Tone(freq, duration); } }5.2 经典游戏移植以贪吃蛇为例的关键实现typedef struct { uint8_t x; uint8_t y; } Point; Point snake[64]; uint8_t length 3; uint8_t dir 0; // 0上,1右,2下,3左 void Snake_Update() { // 移动蛇身 for(int ilength-1; i0; i--) { snake[i] snake[i-1]; } // 根据方向移动头部 switch(dir) { case 0: snake[0].y--; break; case 1: snake[0].x; break; case 2: snake[0].y; break; case 3: snake[0].x--; break; } // 边界检测 if(snake[0].x 128) snake[0].x 0; if(snake[0].y 64) snake[0].y 0; // 绘制 for(int i0; ilength; i) { Draw_Rect(snake[i].x, snake[i].y, 4, 4, 1); } }5.3 完整工程架构最终项目采用模块化设计/Project ├── /Drivers │ ├── bme280.c # 环境传感器驱动 │ ├── mpu6050.c # 运动处理驱动 │ └── ssd1306.c # 显示驱动 ├── /Games │ ├── snake.c # 贪吃蛇游戏 │ └── music_player.c # 音乐播放器 ├── /UI │ ├── watchface.c # 表盘界面 │ └── menu.c # 系统菜单 ├── /Utilities │ ├── scheduler.c # 任务调度 │ └── low_power.c # 低功耗管理 └── main.c # 主程序入口在开发过程中最耗时的部分是DMP库的移植和低功耗调试。实际测试发现MPU6050的中断信号需要添加10kΩ上拉电阻才能稳定工作这个细节在数据手册中并未明确说明。