用STM32F103C8T6和0.96寸OLED,DIY一个AIDA64性能监视器(附完整代码)
基于STM32F103C8T6与0.96寸OLED的PC性能监视器实战指南在创客圈里将硬件与软件结合打造个性化工具一直是个热门话题。今天我们要做的就是利用手头常见的STM32F103C8T6开发板和0.96寸OLED屏幕打造一个实时显示电脑性能数据的桌面监视器。这个项目不仅成本低廉总成本不到50元而且能让你深入了解串口通信、状态机编程和嵌入式显示技术的实际应用。1. 硬件准备与环境搭建1.1 所需材料清单制作这个性能监视器你需要准备以下硬件STM32F103C8T6最小系统板蓝色药丸板这款基于ARM Cortex-M3内核的微控制器性价比极高72MHz主频完全够用0.96寸OLED显示屏I2C接口推荐使用SSD1306驱动的版本分辨率128×64USB转TTL串口模块用于STM32与PC之间的通信杜邦线若干建议使用母对母的线材方便连接Micro USB数据线为开发板供电提示购买OLED时注意区分I2C和SPI接口版本本教程基于I2C接口1.2 开发环境配置在开始编码前需要搭建好开发环境安装Keil MDK到ARM官网下载并安装最新版Keil uVision安装STM32支持包在Keil的Pack Installer中搜索并安装STM32F1xx_DFP安装串口驱动根据你的USB转TTL模块型号安装对应驱动常见的有CH340、CP2102等准备OLED驱动库下载SSD1306的I2C驱动代码# 推荐使用ST-Link工具烧录程序 openocd -f interface/stlink-v2.cfg -f target/stm32f1x.cfg2. AIDA64数据输出配置2.1 软件设置步骤AIDA64作为一款专业的系统检测工具提供了丰富的数据输出接口。我们需要配置它通过串口发送性能数据打开AIDA64进入文件→设置→LCD在LCD类型中选择Pertelian即使没有这个设备设置LCD端口为你连接STM32的COM口可在设备管理器中查看屏幕大小选择16×4对应我们的0.96寸OLED2.2 性能监控项添加在AIDA64的LCD设置界面右下角点击新建添加需要监控的项目CPU使用率Label设为CPU内存使用率Label设为MemCPU温度Label设为CPUTempGPU温度Label设为GPUTemp注意每个监控项的Unit栏需要添加符号作为数据结束标志这是后续数据解析的关键配置完成后界面应类似这样项目名称LabelUnitCPU使用率CPU%内存使用率Mem%CPU温度CPUTemp℃GPU温度GPUTemp℃3. 串口通信协议解析3.1 数据包格式分析通过串口监控软件观察AIDA64发出的数据包可以发现其遵循特定格式每行数据以0xFE开头第二个字节表示行号0x80第一行0xC0第二行0x94第三行0xD4第四行数据内容为ASCII字符以0x40即符号结束典型数据包示例FE 80 35 30 25 40 → CPU50% FE C0 38 32 25 40 → Mem82%3.2 状态机实现原理为了可靠地解析这种不定长数据包我们采用状态机设计模式。状态转换逻辑如下初始状态等待0xFE起始字节接收行号根据第二个字节确定数据存储位置接收数据持续存储直到遇到0x40结束符完成处理添加字符串结束符\0重置状态enum RxState { STATE_WAIT_START, STATE_WAIT_LINE_NUM, STATE_RECEIVE_CPU, STATE_RECEIVE_MEM, STATE_RECEIVE_CPUTEMP, STATE_RECEIVE_GPUTEMP, STATE_FINISH_CPU, STATE_FINISH_MEM, STATE_FINISH_CPUTEMP, STATE_FINISH_GPUTEMP };4. STM32固件开发4.1 串口初始化代码STM32的USART配置需要设置正确的波特率、数据位和中断void Serial_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; // 启用时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE); // 配置TX(PA9)为推挽输出 GPIO_InitStructure.GPIO_Pin GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); // 配置RX(PA10)为上拉输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_10; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure); // USART配置 USART_InitStructure.USART_BaudRate 9600; USART_InitStructure.USART_WordLength USART_WordLength_8b; USART_InitStructure.USART_StopBits USART_StopBits_1; USART_InitStructure.USART_Parity USART_Parity_No; USART_InitStructure.USART_Mode USART_Mode_Rx; USART_InitStructure.USART_HardwareFlowControl USART_HardwareFlowControl_None; USART_Init(USART1, USART_InitStructure); // 启用接收中断 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // NVIC配置 NVIC_InitStructure.NVIC_IRQChannel USART1_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority 1; NVIC_InitStructure.NVIC_IRQChannelSubPriority 1; NVIC_InitStructure.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStructure); USART_Cmd(USART1, ENABLE); }4.2 中断服务程序优化原始代码中的状态机可以进一步优化减少重复代码void USART1_IRQHandler(void) { static uint8_t state STATE_WAIT_START; static uint8_t index 0; static char* current_buffer NULL; if(USART_GetITStatus(USART1, USART_IT_RXNE) SET) { uint8_t data USART_ReceiveData(USART1); switch(state) { case STATE_WAIT_START: if(data 0xFE) { state STATE_WAIT_LINE_NUM; } break; case STATE_WAIT_LINE_NUM: switch(data) { case 0x80: current_buffer Serial_RxPacket_CPU; state STATE_RECEIVE_CPU; break; case 0xC0: current_buffer Serial_RxPacket_Mem; state STATE_RECEIVE_MEM; break; case 0x94: current_buffer Serial_RxPacket_CPUTemp; state STATE_RECEIVE_CPUTEMP; break; case 0xD4: current_buffer Serial_RxPacket_GPUTemp; state STATE_RECEIVE_GPUTEMP; break; default: state STATE_WAIT_START; } index 0; break; case STATE_RECEIVE_CPU: case STATE_RECEIVE_MEM: case STATE_RECEIVE_CPUTEMP: case STATE_RECEIVE_GPUTEMP: if(data 0x40) { // 结束符 current_buffer[index] \0; RxFlag 1; state STATE_WAIT_START; } else if(index 19) { // 防止缓冲区溢出 current_buffer[index] data; } break; } USART_ClearITPendingBit(USART1, USART_IT_RXNE); } }4.3 OLED显示实现使用SSD1306驱动OLED显示接收到的数据void OLED_UpdateDisplay(void) { OLED_Clear(); // 第一行CPU使用率 OLED_ShowString(0, 0, CPU:); OLED_ShowString(40, 0, Serial_RxPacket_CPU); OLED_ShowString(80, 0, %); // 第二行内存使用率 OLED_ShowString(0, 2, Mem:); OLED_ShowString(40, 2, Serial_RxPacket_Mem); OLED_ShowString(80, 2, %); // 第三行CPU温度 OLED_ShowString(0, 4, CPU Temp:); OLED_ShowString(72, 4, Serial_RxPacket_CPUTemp); OLED_ShowChar(112, 4, 0xB0); // 度符号 OLED_ShowChar(120, 4, C); // 第四行GPU温度 OLED_ShowString(0, 6, GPU Temp:); OLED_ShowString(72, 6, Serial_RxPacket_GPUTemp); OLED_ShowChar(112, 6, 0xB0); // 度符号 OLED_ShowChar(120, 6, C); }5. 项目优化与扩展5.1 性能优化技巧双缓冲机制创建两个显示缓冲区一个用于当前显示一个用于准备下一帧数据数据校验添加简单的校验和验证数据完整性掉电保存使用STM32的Flash或EEPROM保存用户偏好的显示布局5.2 功能扩展思路添加更多监控项如硬盘使用率、网络速度等实现可视化图表利用OLED的像素级控制绘制简单的折线图无线传输版本替换串口为蓝牙或WiFi模块多屏支持通过I2C地址切换支持多个OLED屏幕// 示例折线图绘制函数 void DrawSimpleGraph(uint8_t x, uint8_t y, uint8_t width, uint8_t height, int16_t *values, uint8_t count) { int16_t min values[0], max values[0]; // 找出最大值和最小值 for(uint8_t i 1; i count; i) { if(values[i] min) min values[i]; if(values[i] max) max values[i]; } // 绘制坐标轴 OLED_DrawLine(x, y, x, y height); OLED_DrawLine(x, y height, x width, y height); // 计算缩放比例 float scale (max min) ? 1 : (float)height / (max - min); // 绘制折线 for(uint8_t i 0; i count - 1; i) { uint8_t x1 x (i * width) / (count - 1); uint8_t y1 y height - (uint8_t)((values[i] - min) * scale); uint8_t x2 x ((i 1) * width) / (count - 1); uint8_t y2 y height - (uint8_t)((values[i 1] - min) * scale); OLED_DrawLine(x1, y1, x2, y2); } }5.3 常见问题排查OLED不显示检查I2C地址是否正确通常0x78或0x7A确认SCL和SDA线连接正确检查电源是否正常3.3V串口数据乱码确认STM32和AIDA64的波特率设置一致检查USB转TTL模块的TX/RX是否交叉连接确保没有其他程序占用了COM口数据显示不更新检查RxFlag是否被正确置位确认AIDA64的LCD设置中数据刷新率不是太低在串口中断中添加调试输出检查数据接收情况