STM32智能控制台:I2C与SPI总线实战指南
1. 项目概述:打造一个基于STM32的智能控制台
这个项目要构建一个功能完整的智能控制台系统,核心在于通过STM32同时驾驭I2C和SPI两大通信总线。这不是简单的协议学习实验,而是一个真实可用的嵌入式设备开发案例。
智能控制台需要实现三个核心功能:
- 通过OLED屏幕显示系统状态和交互界面(I2C总线)
- 使用EEPROM芯片保存用户设置(I2C总线)
- 利用Flash芯片记录系统事件日志(SPI总线)
我选择STM32F103作为主控芯片,它不仅具备丰富的外设接口,性价比也极高,非常适合作为嵌入式学习的平台。这个项目特别适合已经掌握STM32基础开发,想要进阶学习总线通信和系统设计的开发者。
2. 硬件架构设计
2.1 核心器件选型
主控芯片选用STM32F103C8T6,也就是我们常说的"蓝莓派",它具备:
- 72MHz Cortex-M3内核
- 64KB Flash + 20KB RAM
- 丰富的外设接口(2xSPI, 2xI2C, 3xUSART等)
显示模块使用0.96寸OLED(SSD1306驱动),这是目前最流行的嵌入式显示方案,具有:
- 128x64分辨率
- 超低功耗
- I2C接口(仅需2根线)
存储部分采用双芯片方案:
- AT24C02 EEPROM(I2C接口)用于保存设置
- W25Q64 Flash(SPI接口)用于记录日志
2.2 引脚分配策略
在已有工程基础上新增I2C和SPI外设时,引脚选择需要特别注意避免冲突。我的分配方案如下:
| 引脚 | 功能 | 备注 |
|---|---|---|
| PB6 | I2C1_SCL | OLED和EEPROM共用 |
| PB7 | I2C1_SDA | OLED和EEPROM共用 |
| PA5 | SPI1_SCK | Flash时钟线 |
| PA6 | SPI1_MISO | Flash数据输入 |
| PA7 | SPI1_MOSI | Flash数据输出 |
| PA4 | FLASH_CS | 片选信号(GPIO模拟) |
特别注意:PB0-PB1已经被PWM占用(控制RGB LED),所以Flash的片选不能使用这些引脚。
3. I2C总线深度解析
3.1 I2C协议工作原理
I2C是Philips发明的两线制串行总线,包含:
- SCL(时钟线)
- SDA(数据线)
关键特性:
- 多主多从架构
- 7位设备地址(可扩展为10位)
- 标准模式100kHz,快速模式400kHz
- 半双工通信
典型传输时序:
START → 地址+R/W → ACK → 数据 → ACK → ... → STOP3.2 EEPROM操作实战
AT24C02是常见的I2C接口EEPROM,操作时有两个关键注意事项:
- 页写入限制:AT24C02内部按8字节分页,跨页写入需要特殊处理
bool App_EEPROM_Write(uint8_t addr, const uint8_t *data, uint8_t len) { while (len > 0) { uint8_t page_remain = 8 - (addr % 8); // 计算当前页剩余空间 uint8_t chunk = (len < page_remain) ? len : page_remain; HAL_I2C_Mem_Write(&hi2c1, 0xA0, addr, I2C_MEMADD_SIZE_8BIT, (uint8_t*)data, chunk, HAL_MAX_DELAY); data += chunk; addr += chunk; len -= chunk; HAL_Delay(5); // 必须等待写入完成 } return true; }- 写入周期等待:每次写入后需要延时5ms
HAL_I2C_Mem_Write(&hi2c1, 0xA0, 0x00, I2C_MEMADD_SIZE_8BIT, buf, 8, 100); HAL_Delay(5); // 等待内部写入完成3.3 OLED驱动实现
SSD1306 OLED驱动要点:
- 初始化序列必须严格按datasheet顺序:
void App_OLED_Init(void) { oled_cmd(0xAE); // Display OFF oled_cmd(0x20); // Set Memory Mode // ...更多初始化命令 oled_cmd(0xAF); // Display ON }- 采用双缓冲机制减少I2C通信:
static uint8_t vram[8][128]; // 本地显存 void App_OLED_Refresh(void) { for(int page=0; page<8; page++) { uint8_t buf[129] = {0x40}; // 控制字节 memcpy(buf+1, vram[page], 128); HAL_I2C_Master_Transmit(&hi2c1, 0x3C<<1, buf, 129, HAL_MAX_DELAY); } }4. SPI总线深度解析
4.1 SPI协议特点
SPI是Motorola发明的四线制同步串行接口,包含:
- SCK(时钟)
- MOSI(主出从入)
- MISO(主入从出)
- CS(片选)
与I2C相比,SPI的优势:
- 全双工通信
- 更高速度(可达几十MHz)
- 更简单的协议
4.2 Flash芯片操作
W25Q64是8MB SPI Flash,操作时需要注意:
- 写操作前必须先发送写使能命令:
void flash_write_enable(void) { uint8_t cmd = 0x06; cs_low(); HAL_SPI_Transmit(&hspi1, &cmd, 1, 100); cs_high(); }- 页编程限制(最大256字节):
bool App_Flash_WritePage(uint32_t addr, const uint8_t *buf, uint16_t len) { if(len > 256) return false; flash_write_enable(); uint8_t cmd[4] = {0x02, addr>>16, addr>>8, addr}; cs_low(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); HAL_SPI_Transmit(&hspi1, (uint8_t*)buf, len, 100); cs_high(); flash_wait_busy(); // 等待写入完成 return true; }- 扇区擦除(4KB为单位):
bool App_Flash_EraseSector(uint32_t addr) { flash_write_enable(); uint8_t cmd[4] = {0x20, addr>>16, addr>>8, addr}; cs_low(); HAL_SPI_Transmit(&hspi1, cmd, 4, 100); cs_high(); flash_wait_busy(); // 等待擦除完成(约400ms) return true; }5. 系统软件架构
5.1 模块化设计
整个工程采用模块化设计,主要分为以下几层:
硬件抽象层:
- app_i2c.c/h - I2C总线操作
- app_spi.c/h - SPI总线操作
设备驱动层:
- app_oled.c/h - OLED显示驱动
- app_eeprom.c/h - EEPROM存储
- app_flash.c/h - Flash存储
应用层:
- app_menu.c/h - 用户界面
- app_settings.c/h - 设置管理
- app_log.c/h - 日志系统
5.2 关键数据结构
- 设置存储结构(EEPROM):
typedef struct { uint8_t magic; // 标识符0xA5 uint8_t default_channel;// 默认LED通道 uint8_t led_brightness; // LED亮度 uint8_t breath_step; // 呼吸灯步进 } App_Settings;- 日志条目结构(Flash):
typedef struct { uint32_t timestamp; // 时间戳 uint8_t event_type; // 事件类型 uint8_t param; // 参数 uint8_t reserved; // 保留 uint8_t checksum; // 校验和 } App_LogEntry;6. 系统集成与调试
6.1 初始化流程
正确的初始化顺序对系统稳定运行至关重要:
- 底层外设初始化:
MX_GPIO_Init(); MX_I2C1_Init(); MX_SPI1_Init();- 设备驱动初始化:
App_OLED_Init(); App_Flash_Init();- 应用模块初始化:
App_Settings_Load(&settings); App_Log_Init(); App_Menu_Init();6.2 主循环设计
主循环需要平衡各个功能的执行时间:
while(1) { // 1. 输入处理 App_Key_Tick(); App_Usart_Process(); // 2. 业务逻辑 App_Breath_Tick(); App_DS18B20_Process(); // 3. 用户界面 App_Menu_Tick(); // 4. 低优先级任务 if(tick % 100 == 0) { App_Log_Process(); } HAL_Delay(1); }7. 常见问题与解决方案
7.1 I2C通信失败
现象:OLED不显示或EEPROM读写异常 排查步骤:
- 检查硬件连接(SCL/SDA是否接反)
- 用逻辑分析仪抓取波形
- 确认从设备地址正确(OLED通常0x3C,EEPROM通常0x50)
- 检查上拉电阻(通常4.7KΩ)
7.2 SPI通信异常
现象:Flash无法识别或数据错误 排查步骤:
- 确认CPOL/CPHA设置匹配(Mode 0或Mode 3)
- 检查CS信号是否正常
- 降低时钟速度测试(从1MHz开始)
- 读取JEDEC ID验证通信:
uint32_t id = App_Flash_JEDEC_ID(); printf("JEDEC ID: 0x%06lX\n", id); // W25Q64应为0xEF40177.3 显示刷新慢
优化建议:
- 减少全屏刷新次数,只刷新变化区域
- 使用DMA传输显示数据
- 提高I2C时钟速度(最高400kHz)
- 采用局部刷新策略:
void App_OLED_PartialRefresh(uint8_t page, uint8_t col, uint8_t len) { oled_cmd(0xB0 + page); // 设置页地址 oled_cmd(col & 0x0F); // 列低4位 oled_cmd(0x10 | (col >> 4)); // 列高4位 uint8_t buf[129] = {0x40}; memcpy(buf+1, &vram[page][col], len); HAL_I2C_Master_Transmit(&hi2c1, 0x3C<<1, buf, len+1, 100); }8. 项目进阶方向
这个基础框架可以扩展更多实用功能:
- 增加无线通信(如ESP8266 WiFi模块)
- 实现OTA固件升级
- 添加更多传感器(如湿度、气压)
- 开发PC端配置工具
- 设计更精美的用户界面
我在实际项目中发现,良好的架构设计可以大幅降低后续扩展的难度。比如采用"发布-订阅"模式处理系统事件,或者使用RTOS来管理多个任务。
