STM32F103C8T6实战用CubeMXHAL库驱动SSD1306 OLED全攻略当我在实验室第一次看到那块蓝色的小板子时完全没想到这个售价不到20元的STM32F103C8T6最小系统板配合几块钱的SSD1306 OLED屏能做出这么多有趣的嵌入式项目。作为嵌入式开发的经典入门组合这套方案不仅成本低廉更能让初学者快速掌握外设驱动、图形显示等核心技能。本文将带你从零开始在Keil MDK环境下用CubeMX配置I2C接口并针对C8T6有限的64KB Flash/20KB RAM资源对u8g2图形库进行深度优化移植。1. 硬件准备与环境搭建1.1 硬件选型要点我手头的这套设备包括STM32F103C8T6最小系统板蓝色PCB带USB转串口0.96寸SSD1306 OLED屏I2C接口分辨率128x644.7kΩ上拉电阻x2用于I2C总线杜邦线若干注意市面上SSD1306模块有SPI和I2C两种接口版本购买时务必确认是I2C型号通常只有4个引脚VCC/GND/SCL/SDA1.2 开发环境配置推荐使用这套工具链组合STM32CubeMX 6.6.1图形化配置工具Keil MDK 5.37带STM32F1支持包ST-Link V2下载器也可用串口ISP下载安装时特别注意CubeMX中要安装F1系列的HAL库版本1.8.4Keil需要安装Keil.STM32F1xx_DFP设备支持包建议使用J-Link或ST-Link进行调试串口下载速度较慢# 检查ST-Link连接状态 $ st-info --probe Found 1 stlink programmers serial: 303030303030303030303031 openocd: \x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x312. CubeMX工程配置详解2.1 时钟树配置技巧在CubeMX中新建工程时选择STM32F103C8型号后首要任务是正确配置时钟。虽然C8T6最高支持72MHz主频但考虑到I2C通信稳定性我建议采用以下配置时钟源配置值说明HSE8MHz外部晶振频率PLLMULx98MHz x9 72MHzAPB1 Prescaler/236MHzI2C时钟上限APB2 Prescaler/172MHz// 生成的时钟初始化代码片段 void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct {0}; RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL RCC_PLL_MUL9; HAL_RCC_OscConfig(RCC_OscInitStruct); }2.2 I2C外设配置在Connectivity选项卡中启用I2C1选择标准模式100kHz。引脚会自动分配为PB6(SCL)和PB7(SDA)。关键配置参数Timing Register: 0x2000090E自动计算值No Stretch Mode: EnabledAddress Mode: 7-bitOwn Address: 0x00主模式不需要地址提示如果使用软件模拟I2C只需配置两个GPIO为输出模式即可但本文推荐硬件I2C方案3. u8g2库的深度优化移植3.1 库文件精简策略原始u8g2库完整编译需要约50KB Flash远超C8T6的64KB容量。通过以下步骤可大幅缩减体积从GitHub下载最新源码后只保留这些关键文件/csrc ├── u8g2.h ├── u8x8.h ├── u8g2_d_setup.c ├── u8g2_d_memory.c ├── u8g2_buffer.c ├── u8x8_d_ssd1306_128x64_noname.c └── u8x8_cad.c在u8g2_d_setup.c中注释掉所有非SSD1306的初始化函数修改u8g2_d_memory.c仅保留uint8_t *u8g2_m_16_4_1(uint8_t *page_cnt) { static uint8_t buf[128]; *page_cnt 1; return buf; }经过优化后库文件体积降至约12KB完美适配C8T6的存储限制。3.2 硬件适配层实现需要为u8g2提供三个关键回调函数// 精简版GPIO和延时回调 uint8_t u8g2_stm32_gpio_delay(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { switch(msg) { case U8X8_MSG_DELAY_MILLI: HAL_Delay(arg_int); break; case U8X8_MSG_GPIO_I2C_CLOCK: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET); break; case U8X8_MSG_GPIO_I2C_DATA: HAL_GPIO_WritePin(GPIOB, GPIO_PIN_7, arg_int ? GPIO_PIN_SET : GPIO_PIN_RESET); break; default: return 0; } return 1; } // I2C字节传输回调 uint8_t u8g2_byte_hal_i2c(u8x8_t *u8x8, uint8_t msg, uint8_t arg_int, void *arg_ptr) { static uint8_t buffer[32]; // I2C缓冲区 switch(msg) { case U8X8_MSG_BYTE_INIT: HAL_I2C_Init(hi2c1); break; case U8X8_MSG_BYTE_SEND: if(HAL_I2C_Master_Transmit(hi2c1, 0x78, arg_ptr, arg_int, 100) ! HAL_OK) Error_Handler(); break; } return 1; }4. 完整示例与性能优化4.1 显示驱动实现在main.c中添加以下测试代码u8g2_t u8g2; void OLED_Init(void) { u8g2_Setup_ssd1306_i2c_128x64_noname_f(u8g2, U8G2_R0, u8g2_byte_hal_i2c, u8g2_stm32_gpio_delay); u8g2_InitDisplay(u8g2); u8g2_SetPowerSave(u8g2, 0); } void OLED_ShowDemo(void) { char temp_str[20]; u8g2_ClearBuffer(u8g2); // 绘制边框 u8g2_DrawFrame(u8g2, 0, 0, 128, 64); // 显示不同字体 u8g2_SetFont(u8g2, u8g2_font_6x10_tf); u8g2_DrawStr(u8g2, 5, 15, STM32F103C8T6); u8g2_SetFont(u8g2, u8g2_font_10x20_tf); sprintf(temp_str, CPU:%dMHz, SystemCoreClock/1000000); u8g2_DrawStr(u8g2, 5, 40, temp_str); u8g2_SendBuffer(u8g2); }4.2 内存优化技巧当需要显示动态内容时可采用这些优化策略局部刷新只更新变化的部分区域void UpdateTemperature(float temp) { char str[10]; sprintf(str, %.1fC, temp); u8g2_SetDrawColor(u8g2, 0); // 清除背景 u8g2_DrawBox(u8g2, 80, 25, 40, 15); u8g2_SetDrawColor(u8g2, 1); u8g2_DrawStr(u8g2, 80, 40, str); u8g2_UpdateDisplayArea(u8g2, 80, 25, 40, 15); }使用无缓冲模式修改构造函数为u8g2_Setup_ssd1306_i2c_128x64_noname_1可节省1KB RAM字体裁剪通过fonttool工具只提取需要的字符经过实测完整项目编译后的资源占用情况资源类型使用量总量利用率Flash42KB64KB65%RAM5KB20KB25%