用STM32F103C8T6和OLED屏,我手搓了一台迷你自动售货机(附完整代码)
从零打造STM32迷你售货机硬件拆解与状态机实战去年夏天在电子市场闲逛时偶然发现角落里积灰的OLED屏和矩阵按键突然萌生了做个桌面级售货机的念头。这个看似简单的项目实际上融合了嵌入式开发中最经典的三大难题外设驱动整合、状态机设计以及用户交互逻辑。下面就将这六周从零搭建的过程包括那些深夜调试的血泪教训完整呈现给各位硬件爱好者。1. 硬件架构设计与核心器件选型1.1 主控与显示模块的黄金组合STM32F103C8T6这颗蓝色小芯片堪称嵌入式界的瑞士军刀Cortex-M3内核搭配72MHz主频对于需要驱动多个外设的售货机项目再合适不过。我特别看重它丰富的GPIO资源37个I/O口和3个USART接口这为后续扩展留下了充足空间。显示模块选用0.96寸OLEDSSD1306驱动相比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延时这在状态机中会阻塞流程。我的解决方案是在74HC165输入端并联0.1μF电容配置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 分层渲染策略为避免频繁刷新导致的闪烁采用三级显示缓存背景层静态元素如边框、标题数据层动态数值价格、数量交互层光标、按钮高亮刷新时仅更新必要区域通过以下指令局部刷新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-x01, 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驱动继电器发现两个问题线圈吸合时导致电源电压跌落MCU复位时可能误动作改进方案增加MOSFET驱动电路IRLZ44N配置硬件看门狗IWDG电路原理图STM32 GPIO → 1kΩ电阻 → IRLZ44N栅极 │ └─ 10kΩ下拉电阻 继电器线圈接在MOSFET漏极与12V电源之间4.2 故障检测机制通过ADC监测关键点电压建立三级保护电源监测检测3.3V和5V轨电压hadc1.Instance ADC1; hadc1.Init.ContinuousConvMode ENABLE; hadc1.Init.DMAContinuousRequests ENABLE; hadc1.Init.ExternalTrigConv ADC_SOFTWARE_START; HAL_ADC_Start(hadc1);温度监测DS18B20检测继电器温度出货反馈光电传感器验证商品掉落当检测到异常时系统会自动切断继电器电源OLED显示错误代码蜂鸣器发出特定报警音5. 功耗优化实战记录5.1 运行模式划分通过实测发现不同模块的功耗差异惊人模块工作电流休眠电流STM32全速36mA2.1mAOLED显示20mA0.05mA继电器保持72mA0mA据此设计三种工作模式活跃模式所有外设供电150mA待机模式关闭继电器和OLED背光25mA休眠模式仅维持RTC3mA5.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连接实现两个实用功能远程库存管理上传销售数据到云平台固件OTA更新无需拆机即可升级程序接线示意图ESP8266 STM32 TX → PA3(RX) RX → PA2(TX) EN → 3.3V GND → GND6.2 机械结构改良建议经过三个原型迭代总结出机械设计的黄金法则出货滑道倾斜角度≥30度商品隔间宽度比商品大2-3mm光电传感器安装位置距离出货口5-8cm推荐使用3D打印的模块化结构便于调整参数。PLA材料厚度建议≥2mm关键受力部位可增加到3mm。