ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

基于STM32与LCD的智能宠物喂食器设计与仿真实现

基于STM32与LCD的智能宠物喂食器设计与仿真实现 1. 从一个喂食器项目说起为什么我选了STM32加LCD这条路线做智能宠物喂食器这个想法最早来自我自己养猫的一段经历。出差两天回来猫粮盆空得能照出影子猫蹲在旁边用一种很复杂的眼神看着我。当时我就想这东西本质上不就是个定时出粮的机械结构加一块能看状态的屏吗市面上成品不少但要么贵得离谱要么功能花哨到根本用不上。作为一个常年跟单片机打交道的人我决定自己搞一个顺便把整个设计过程整理出来。这个项目的核心其实就三件事定时、出粮、显示。定时靠STM32的定时器出粮靠舵机或者步进电机带动螺旋杆显示则交给LCD屏。听起来简单但真做起来光是把这三块协调好就够折腾一阵子。尤其是LCD显示这一块涉及驱动芯片选型、字库处理、刷新逻辑坑一个接一个。而仿真设计这个环节是我强烈建议每个做嵌入式项目的人都认真对待的——它能在你焊坏第一块板子之前就把大部分逻辑问题暴露出来。这篇文章面向的是有一定单片机基础、想做一个完整嵌入式项目的朋友。不管你是学生做课程设计还是像我一样想给自己宠物做个实用玩意儿这套思路都能直接参考。我会从硬件选型讲到仿真验证再到LCD显示的具体实现把每个决策背后的理由说清楚。关键词里的STM32、LCD、仿真设计就是贯穿全文的三条主线。先说结论STM32F103系列是这类项目最稳妥的主控选择LCD用SPI接口的TFT屏比并口屏省引脚且仿真友好仿真平台用Proteus配合Keil或者STM32CubeIDE就能跑通大部分逻辑。下面我一步步拆开讲。2. 主控与显示方案的选型逻辑为什么不是Arduino为什么不是并口屏2.1 STM32F103C8T6凭什么成为默认答案很多人做小项目第一反应是Arduino上手快、库多、社区大。但宠物喂食器这个场景有几个硬需求需要精确的定时中断、需要驱动电机、需要刷新LCD、最好还能留出串口做调试或联网扩展。Arduino Uno那颗ATmega328P在定时器资源和引脚数量上都比较紧张尤其是你要同时跑LCD刷新和电机控制的时候中断冲突会让人很头疼。STM32F103C8T6就不一样了。它基于Cortex-M3内核主频72MHz有4个通用定时器加2个高级定时器SPI、I2C、USART接口齐全GPIO数量也够用。关键是它的定时器支持硬件PWM输出驱动舵机或者步进电机的时候不需要软件模拟CPU负担小很多。价格上这颗芯片在国内市场已经非常成熟开发板遍地都是资料也全。我实际选型的时候对比过几款主控型号主频定时器数量SPI接口价格区间适合程度STM32F103C8T672MHz7个2个10-15元非常适合ATmega328P16MHz3个1个15-20元勉强够用STM32F40184MHz8个3个20-30元性能过剩ESP32240MHz4个4个15-25元偏重联网F103C8T6在这个项目里属于刚刚好的选择不多不少。F401性能更强但价格上去了对于喂食器这种不需要复杂运算的场景有点浪费。ESP32适合需要WiFi控制的版本但如果你只是做本地定时喂食它的蓝牙和WiFi模块反而增加了功耗和复杂度。2.2 LCD屏的接口选择SPI为什么比并口更适合仿真LCD显示这块市面上常见的有几种方案1602字符屏、12864点阵屏、TFT彩屏。1602只能显示字符做喂食器的话显示下次喂食时间当前粮量这些信息勉强够但界面很单调。12864能显示中文和简单图形算是个折中。TFT彩屏效果最好但驱动复杂度也最高。我最终选了1.8寸或2.4寸的SPI接口TFT屏驱动芯片是ILI9341或者ST7789。原因有几个第一SPI接口只需要4根线SCK、MOSI、CS、DC加上复位和背光控制也就6根比并口屏的16根数据线加控制线省太多了。引脚省下来可以留给电机驱动和传感器。第二SPI接口在仿真软件里更容易模拟。Proteus里面并口屏的时序模拟经常出问题SPI相对简单时序清晰仿真通过率更高。第三ILI9341这颗驱动芯片的资料极其丰富初始化序列网上到处都是Arduino和STM32的驱动库都很成熟。你甚至可以直接移植现成的驱动代码改改引脚定义就能用。这里有个细节要注意ILI9341的SPI模式有4线制和3线制之分4线制多一根DC线用来区分命令和数据3线制则通过特殊时序区分。我强烈建议用4线制因为3线制的时序在仿真里很难调对而且实际接线也容易出错。2.3 仿真设计在这个项目里的真实价值很多人觉得仿真就是走个过场真正的东西还得上实物。这话对了一半。仿真确实不能完全替代实物调试但它在宠物喂食器这个项目里有几个不可替代的作用第一验证定时逻辑。喂食器的核心是每隔X小时出粮一次这个逻辑用仿真跑一遍你可以把时间压缩几秒钟就能看到几小时后的行为不用真的等。第二验证LCD显示内容。屏幕上要显示什么、怎么排版、中文字库怎么调用这些在仿真里都能看到效果。如果直接上实物每次改显示内容都要重新烧录效率低很多。第三验证状态机流转。喂食器有待机喂食中缺粮报警几个状态状态之间的切换逻辑在仿真里可以用断点和变量监视看得清清楚楚。我用的是Proteus 8.9配合Keil MDK。Proteus里有STM32F103的模型也有ILI9341的LCD模型虽然模型不是100%准确但跑逻辑足够了。仿真文件我会在后面的章节里说怎么搭。3. 仿真环境搭建从零把Proteus和Keil联调跑通3.1 软件安装与版本匹配的坑仿真环境搭建这块版本匹配是第一个坑。Proteus 8.9之前的版本对STM32F103的支持不完整有些外设模型跑不起来。Keil MDK建议用5.30以上版本因为需要安装STM32F1的Device Family Pack。安装顺序我建议这样先装Keil MDK安装的时候勾选STM32F1系列的支持包。再装Proteus安装时确保选中了STM32相关的库文件。最后装Proteus的Keil联调插件如果有的话或者直接用Proteus的Edit Component功能加载编译好的hex文件。这里有个常见问题Proteus里的STM32模型默认晶振频率是8MHz但你的代码可能配置的是72MHz主频。如果两边不一致仿真里的定时器时间会完全不对。解决办法是在Proteus的STM32属性里把晶振频率改成和代码里HSE一致的值通常是8MHz然后代码里通过PLL倍频到72MHz。3.2 Proteus原理图的关键连接在Proteus里画原理图核心元件就几个STM32F103C8、ILI9341的LCD模型、几个按键、一个舵机模型或者用LED代替表示出粮动作。LCD和STM32的连接我一般这样分配引脚LCD引脚STM32引脚功能说明SCKPA5SPI1时钟MOSIPA7SPI1数据输出CSPA4片选低有效DCPA3命令/数据切换RSTPA2复位低有效BLPA1背光控制VCC3.3V电源GNDGND地这个引脚分配不是随便定的。PA5和PA7是STM32F103的SPI1默认复用引脚用硬件SPI比软件模拟SPI快得多刷新LCD的时候差别很明显。PA4做片选是因为它紧挨着SPI引脚布线方便。DC、RST、BL用普通GPIO就行不占用特殊功能。注意Proteus里的ILI9341模型可能没有背光控制引脚或者背光默认常亮。如果你的模型没有BL引脚代码里对应的GPIO操作可以保留仿真里不生效但实物上能用。3.3 Keil工程配置与hex文件生成Keil工程的新建流程我就不一步步截图了说几个关键配置第一时钟配置。在SystemInit或者自己写的时钟初始化函数里确保HSE使能PLL倍频到72MHz。如果你用STM32CubeMX生成代码这部分会自动处理好。第二SPI配置。SPI1配置为全双工主模式波特率预分频器设成2或者4这样SPI时钟在18MHz到36MHz之间ILI9341最高支持到10MHz左右但实际上很多模块能跑到更高。仿真里不用太纠结这个实物上如果花屏就降低波特率。第三GPIO配置。控制引脚配置为推挽输出速度设成50MHz。SPI引脚配置为复用推挽输出。第四生成hex文件。在Keil的Output选项卡里勾选Create HEX File编译后会在Objects文件夹里生成hex文件。然后在Proteus里双击STM32模型在Program File里加载这个hex。联调的时候有个技巧先在Proteus里点运行再在Keil里点调试这样Keil的调试器可以连上Proteus里的虚拟芯片实现单步调试。不过这个功能需要Proteus和Keil的版本配合好有时候会连不上那就只能用串口打印的方式看变量了。4. LCD显示驱动的核心实现从点亮到显示中文4.1 ILI9341初始化序列的逐条解读ILI9341的初始化序列是一长串命令网上能找到很多版本但很多人是直接复制粘贴不知道每条命令在干什么。我挑几条关键的说说// 软件复位 LCD_WriteCmd(0x01); HAL_Delay(120); // 电源控制A LCD_WriteCmd(0xCB); LCD_WriteData(0x39); LCD_WriteData(0x2C); LCD_WriteData(0x00); LCD_WriteData(0x34); LCD_WriteData(0x02); // 像素格式设置16位色 LCD_WriteCmd(0x3A); LCD_WriteData(0x55); // 内存访问控制设置扫描方向 LCD_WriteCmd(0x36); LCD_WriteData(0x48);0xCB这条命令是电源控制A里面的参数决定了LCD内部电荷泵的工作方式。这些值不是随便写的是厂家根据面板特性推荐的。如果你换了不同批次的屏有时候需要微调这些参数才能让显示效果正常。0x3A设置像素格式0x55表示16位色RGB565。这个模式下每个像素占2个字节比18位色省内存颜色也够用。0x36是内存访问控制决定了画面的扫描方向。0x48这个值表示从左到右、从上到下扫描同时交换了RGB的顺序。如果你发现显示的颜色不对比如红色显示成蓝色就是这里的RGB顺序需要调整。4.2 中文字库的两种方案取模软件和外部FlashLCD显示中文是很多人卡住的地方。英文字母用ASCII码一个字节对应一个字符简单直接。但中文有几千个常用字不可能全部塞进STM32的内部Flash。方案一用取模软件生成字模数组。比如用PCtoLCD2002设置好字体大小16x16或者24x24、取模方式逐行式或逐列式生成一个C数组。你只需要把项目里用到的那些汉字取出来比如喂食时间缺粮正常这些几十个字而已占不了多少空间。方案二外挂SPI Flash存字库。把完整的GB2312字库烧到W25Q64这样的Flash里需要显示的时候根据区位码去读取。这个方案适合需要显示任意中文的场景但增加了硬件成本和代码复杂度。对于宠物喂食器我强烈建议用方案一。你数数屏幕上要显示的字喂食器、下次、时间、当前、状态、正常、缺粮、设置、小时、分钟……加起来不超过50个字。用取模软件生成一个数组放在code区简单可靠。取模的时候有个细节字模的取模方式要和你的显示函数匹配。如果你取模用的是逐行式、高位在前那显示函数里就要按这个顺序去解析字节。不匹配的话显示出来就是乱码或者镜像。4.3 显示刷新策略全屏刷新还是局部刷新喂食器的LCD界面不需要每秒刷新几十次。实际上大部分时间屏幕内容是静止的只有倒计时在变。如果每次倒计时变化都全屏刷新SPI总线占用率高屏幕还会闪。我的做法是局部刷新。把屏幕分成几个区域顶部显示宠物喂食器标题中间显示下次喂食时间底部显示当前状态。倒计时数字变化的时候只刷新那个数字所在的矩形区域。实现局部刷新的关键是设置显示窗口。ILI9341有0x2A和0x2B两条命令用来设置列地址和行地址范围设置好之后连续写入的数据就会自动填充这个窗口。比如你要刷新一个从(100, 80)到(180, 96)的矩形区域先设置列地址100到180行地址80到96然后写入16x16个像素的数据。void LCD_SetWindow(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2) { LCD_WriteCmd(0x2A); LCD_WriteData(x1 8); LCD_WriteData(x1 0xFF); LCD_WriteData(x2 8); LCD_WriteData(x2 0xFF); LCD_WriteCmd(0x2B); LCD_WriteData(y1 8); LCD_WriteData(y1 0xFF); LCD_WriteData(y2 8); LCD_WriteData(y2 0xFF); LCD_WriteCmd(0x2C); // 开始写显存 }这个函数是LCD驱动的核心所有显示操作都建立在它之上。画点、画线、显示字符、显示图片本质上都是先设置窗口再写数据。提示设置窗口之后写入的数据数量必须和窗口大小匹配。如果你设置了16x16的窗口但只写了100个像素剩下的像素会保留之前的内容显示出来就是半截字。所以显示函数里要确保数据数量正确。5. 喂食逻辑与显示状态的联动设计5.1 定时器中断驱动的时间基准喂食器的定时功能靠STM32的定时器实现。我用TIM2做一个1ms的基准中断在中断里维护一个毫秒计数器然后软件层面累加出秒、分、小时。volatile uint32_t g_msTick 0; void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); g_msTick; } }这个g_msTick就是整个系统的时间基准。喂食间隔、倒计时显示、按键消抖都基于它来计算。为什么不用RTCSTM32F103有内置RTC但RTC需要外部32.768kHz晶振才能准而且RTC的秒中断在仿真里不太好观察。用定时器做软件RTC仿真的时候可以加速几秒钟就能模拟几小时调试效率高很多。5.2 状态机设计待机、喂食、报警三个状态的流转喂食器的逻辑用状态机来描述最清晰。我定义了三个状态STATE_IDLE待机状态LCD显示下次喂食倒计时电机不动。STATE_FEEDING喂食状态电机转动出粮LCD显示喂食中。STATE_ALARM报警状态粮仓空了或者电机堵转LCD显示缺粮并闪烁。状态之间的切换条件当前状态触发条件下一状态动作IDLE倒计时归零FEEDING启动电机FEEDING出粮时间到IDLE停止电机重置倒计时FEEDING电机电流异常ALARM停止电机报警ALARM用户按键复位IDLE清除报警重置倒计时这个状态机在主循环里跑每次循环检查当前状态和触发条件。LCD显示的内容根据状态变化这样屏幕上的信息和实际动作始终一致。5.3 LCD显示内容与状态同步的代码结构显示和逻辑的同步我的做法是状态变化时更新显示而不是每轮循环都刷新。具体来说定义一个全局变量g_displayDirty当状态变化或者倒计时数字变化时把g_displayDirty置1。主循环里检查这个标志如果为1就刷新对应的显示区域然后清零。if (g_displayDirty) { switch (g_currentState) { case STATE_IDLE: LCD_ShowString(10, 50, 下次喂食:); LCD_ShowNum(90, 50, g_nextFeedHour, 2); LCD_ShowString(110, 50, :); LCD_ShowNum(120, 50, g_nextFeedMin, 2); break; case STATE_FEEDING: LCD_ShowString(10, 50, 喂食中...); break; case STATE_ALARM: LCD_ShowString(10, 50, 缺粮!请添加); break; } g_displayDirty 0; }这种做法的好处是SPI总线不会一直被占用CPU有时间处理其他任务。而且屏幕不会因为频繁刷新而闪烁。6. 仿真调试中遇到的典型问题与排查思路6.1 LCD白屏或花屏的仿真排查仿真里LCD白屏是最常见的问题。排查顺序我一般是这样第一步检查SPI引脚连接。Proteus里很容易把MOSI和MISO接反或者CS、DC接错引脚。对照原理图一根根查。第二步检查初始化序列。在LCD_WriteCmd和LCD_WriteData函数里加断点看初始化的时候有没有正常写入。如果卡在某个HAL_Delay里出不来可能是时钟配置有问题。第三步检查时序。Proteus里的SPI模型对时序比较敏感。如果你的SPI波特率设得太高仿真里可能来不及响应。把预分频器调大降低SPI速度试试。第四步检查背光。有些LCD模型背光默认是关的需要在属性里设置或者代码里拉高BL引脚。花屏通常是数据错位或者窗口设置不对。重点检查0x2A和0x2B命令的参数以及像素数据的字节顺序。ILI9341默认是高位在前如果你的数据是低位在前颜色就会乱。6.2 定时器仿真时间不准的修正Proteus仿真STM32定时器的时候时间基准和真实硬件有偏差。这是因为仿真软件不是实时系统指令执行时间受电脑性能影响。我的经验是仿真里不要依赖精确的定时而是用事件驱动的方式验证逻辑。比如你要验证2小时后喂食不用真的等2小时可以在代码里加一个调试变量把2小时改成2秒仿真里看逻辑对不对。逻辑对了再把时间改回去。如果一定要在仿真里看定时器计数可以在Keil的Watch窗口里观察g_msTick的值看它增长的速度是否大致合理。完全精确是做不到的但数量级对就行。6.3 中文字模显示乱码的定位方法中文乱码通常有三个原因原因一取模方式和显示函数不匹配。比如取模用的是逐列式显示函数按逐行式解析。解决办法是统一取模设置和显示代码。原因二字模数组的索引计算错误。一个16x16的汉字占32个字节第n个字的起始偏移是n*32。如果偏移算错了显示的就是别的字。原因三LCD的扫描方向设置和字模方向不一致。比如字模是从左到右、从上到下但LCD设置成了从右到左显示出来就是镜像的。调整0x36命令的参数可以解决。排查的时候先显示一个简单的汉字比如中如果这个字正常说明驱动没问题问题在字模数据。如果中也是乱的那就是驱动或者初始化的问题。7. 从仿真到实物几个必须注意的过渡细节仿真跑通之后下一步就是做实物。从仿真到实物有几个地方必须调整第一电源。仿真里不用考虑电源实物上STM32需要3.3VLCD背光可能需要5V或者3.3V舵机通常需要5V到6V。如果用一个电源供电要注意电流够不够以及电平匹配。第二SPI波特率。仿真里可以跑很高的SPI速度实物上ILI9341的SPI时钟一般不超过10MHz有些模块能到15MHz但稳定性下降。建议先用低速跑通再逐步提高。第三按键消抖。仿真里的按键是理想开关实物上有机械抖动。需要在代码里加消抖逻辑一般是检测到按键按下后延时20ms再确认。第四电机干扰。舵机或者步进电机工作时会产生电磁干扰可能导致LCD花屏或者STM32复位。电机电源和逻辑电源要分开电机两端加续流二极管必要时加光耦隔离。我在实物上踩过的一个坑是LCD的CS引脚和电机的PWM引脚共用了同一个GPIO。仿真里因为电机模型简单没发现问题实物上电机一转LCD就花屏。后来把CS换到另一个引脚就好了。所以引脚分配的时候高速信号和电机控制信号尽量分开。8. 一些让项目更实用的扩展思路这个喂食器做完基础版本之后还有不少可以扩展的方向。比如加一个超声波测距模块检测粮仓余量用HC-SR04装在粮仓顶部往下测根据距离判断还剩多少粮。这个在仿真里也能做Proteus有超声波传感器的模型。再比如加DS3231时钟模块这样断电之后时间不丢不用每次上电重新设置。DS3231走I2C接口STM32的I2C驱动很成熟接上就能用。如果想让喂食器联网可以加一个ESP-01S WiFi模块通过串口和STM32通信实现手机远程查看状态和手动喂食。不过这就涉及到网络配置复杂度会上去不少建议先把本地功能做稳定再考虑。还有一个很实用的改进加一个蜂鸣器做声音提示。喂食的时候响一声缺粮的时候间歇响这样即使不在屏幕旁边也能知道状态。蜂鸣器用PWM驱动还能发出不同频率的声音区分不同事件。这些扩展不需要一次性全做先把核心的定时喂食和LCD显示做扎实再一个个往上加。每加一个功能都在仿真里验证一遍确认没问题再上实物。这个节奏看起来慢但实际上是最快的路径因为返工最少。我个人在实际操作中的体会是嵌入式项目最怕的就是贪多。一开始想把所有功能都塞进去结果每个都做不透调试的时候问题纠缠在一起根本分不清是哪个模块的毛病。先把一个功能做稳定再加下一个这样每次出问题都只可能在新加的部分排查范围小很多。
返回列表