用STM32F103C8T6和OLED屏,我手搓了一台迷你自动售货机(附完整代码)
2026/6/11 10:23:51 网站建设 项目流程

从零打造STM32迷你售货机:硬件拆解与状态机实战

去年夏天在电子市场闲逛时,偶然发现角落里积灰的OLED屏和矩阵按键,突然萌生了做个桌面级售货机的念头。这个看似简单的项目,实际上融合了嵌入式开发中最经典的三大难题:外设驱动整合、状态机设计以及用户交互逻辑。下面就将这六周从零搭建的过程,包括那些深夜调试的血泪教训,完整呈现给各位硬件爱好者。

1. 硬件架构设计与核心器件选型

1.1 主控与显示模块的黄金组合

STM32F103C8T6这颗蓝色小芯片堪称嵌入式界的"瑞士军刀",Cortex-M3内核搭配72MHz主频,对于需要驱动多个外设的售货机项目再合适不过。我特别看重它丰富的GPIO资源(37个I/O口)和3个USART接口,这为后续扩展留下了充足空间。

显示模块选用0.96寸OLED(SSD1306驱动),相比LCD有三大优势:

  • 功耗表现:全亮状态下仅20mA,待机时低于1mA
  • 可视角度:170度无死角显示
  • 响应速度:刷新率可达100Hz

实际接线时发现个有趣现象:I²C接口的OLED只需要4根线(VCC、GND、SCL、SDA),比SPI接口节省3根线。这对于GPIO紧张的迷你项目至关重要。

1.2 输入输出设备配置方案

矩阵按键采用4x4布局,通过74HC165移位寄存器扩展输入。这种设计将16个按键压缩到3个GPIO口,接线示意图如下:

列扫描线 → PC0-PC3 行读取线 → 74HC165 → SPI1

继电器模块选用HK19F-DC5V,关键参数:

  • 触点容量:10A/250VAC
  • 动作时间:<10ms
  • 线圈功耗:0.36W

特别提醒:继电器的反电动势可能干扰MCU,务必在线圈两端并联1N4148续流二极管。

2. 状态机设计与系统逻辑实现

2.1 五状态工作模型

售货机的核心是状态流转,我将业务流程抽象为五个状态:

typedef enum { STATE_IDLE, // 待机状态 STATE_SELECT, // 商品选择 STATE_QUANTITY, // 数量设定 STATE_PAYMENT, // 支付处理 STATE_DELIVERY // 出货状态 } VendingState;

状态转换触发条件如下表所示:

当前状态触发事件下一状态执行动作
IDLE按键1/5SELECT显示商品列表
SELECT按键9/13QUANTITY更新数量显示
QUANTITY按键12PAYMENT计算总价
PAYMENT投币完成DELIVERY驱动继电器

2.2 按键消抖的硬件方案

传统软件消抖需要20-50ms延时,这在状态机中会阻塞流程。我的解决方案是:

  1. 在74HC165输入端并联0.1μF电容
  2. 配置STM32的硬件消抖滤波器(如下寄存器配置)
GPIO_InitTypeDef GPIO_InitStruct; GPIO_InitStruct.Pin = GPIO_PIN_0; GPIO_InitStruct.Mode = GPIO_MODE_INPUT; GPIO_InitStruct.Pull = GPIO_PULLUP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Alternate = GPIO_AF0_EVENTOUT; HAL_GPIO_Init(GPIOC, &GPIO_InitStruct); // 启用输入滤波器 GPIO->PUPDR |= GPIO_PUPDR_PUPD0_1; // 下拉 GPIO->PUPDR |= GPIO_PUPDR_PUPD1_1;

实测可将按键抖动从毫秒级降低到微秒级,状态转换更加可靠。

3. OLED界面优化技巧

3.1 分层渲染策略

为避免频繁刷新导致的闪烁,采用三级显示缓存:

  1. 背景层:静态元素(如边框、标题)
  2. 数据层:动态数值(价格、数量)
  3. 交互层:光标、按钮高亮

刷新时仅更新必要区域,通过以下指令局部刷新:

void OLED_PartialRefresh(uint8_t x0, uint8_t y0, uint8_t x1, uint8_t y1) { SSD1306_SetColumnAddress(x0, x1); SSD1306_SetPageAddress(y0/8, y1/8); HAL_I2C_Mem_Write(&hi2c1, SSD1306_ADDR, 0x40, I2C_MEMADD_SIZE_8BIT, &buffer[y0/8][x0], x1-x0+1, 100); }

3.2 字体压缩技术

标准16pt字体占16x16像素,通过自定义字模可压缩到12x16。以数字"8"为例:

const uint8_t Font12x16_8[] = { 0x1E, 0x3F, 0x33, 0x33, 0x3F, 0x1E, 0x1E, 0x3F, 0x33, 0x33, 0x3F, 0x1E };

相比标准字库节省25%显示空间,这在小型OLED上尤为宝贵。

4. 出货机制与异常处理

4.1 继电器驱动电路优化

最初直接使用GPIO驱动继电器,发现两个问题:

  1. 线圈吸合时导致电源电压跌落
  2. MCU复位时可能误动作

改进方案:

  1. 增加MOSFET驱动电路(IRLZ44N)
  2. 配置硬件看门狗(IWDG)

电路原理图:

STM32 GPIO → 1kΩ电阻 → IRLZ44N栅极 │ └─ 10kΩ下拉电阻 继电器线圈接在MOSFET漏极与12V电源之间

4.2 故障检测机制

通过ADC监测关键点电压,建立三级保护:

  1. 电源监测:检测3.3V和5V轨电压

    hadc1.Instance = ADC1; hadc1.Init.ContinuousConvMode = ENABLE; hadc1.Init.DMAContinuousRequests = ENABLE; hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START; HAL_ADC_Start(&hadc1);
  2. 温度监测:DS18B20检测继电器温度

  3. 出货反馈:光电传感器验证商品掉落

当检测到异常时,系统会自动:

  • 切断继电器电源
  • OLED显示错误代码
  • 蜂鸣器发出特定报警音

5. 功耗优化实战记录

5.1 运行模式划分

通过实测发现不同模块的功耗差异惊人:

模块工作电流休眠电流
STM32全速36mA2.1mA
OLED显示20mA0.05mA
继电器保持72mA0mA

据此设计三种工作模式:

  1. 活跃模式:所有外设供电(<150mA)
  2. 待机模式:关闭继电器和OLED背光(<25mA)
  3. 休眠模式:仅维持RTC(<3mA)

5.2 动态时钟调整

根据负载动态调整系统时钟,关键代码:

void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; // 外部8MHz晶振 RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; // 根据不同模式调整PLL倍频 if(power_mode == POWER_HIGH) { RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; // 72MHz } else { RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL4; // 32MHz } HAL_RCC_OscConfig(&RCC_OscInitStruct); }

实测可降低30%以上的动态功耗,这对电池供电版本尤为重要。

6. 项目进阶与扩展思路

6.1 无线功能集成

通过ESP-01S模块添加WiFi连接,实现两个实用功能:

  1. 远程库存管理:上传销售数据到云平台
  2. 固件OTA更新:无需拆机即可升级程序

接线示意图:

ESP8266 STM32 TX → PA3(RX) RX → PA2(TX) EN → 3.3V GND → GND

6.2 机械结构改良建议

经过三个原型迭代,总结出机械设计的黄金法则:

  • 出货滑道倾斜角度≥30度
  • 商品隔间宽度比商品大2-3mm
  • 光电传感器安装位置距离出货口5-8cm

推荐使用3D打印的模块化结构,便于调整参数。PLA材料厚度建议≥2mm,关键受力部位可增加到3mm。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询