桌面宠物这个品类玩的人不算少但大多数是纯软件方案——装个Python脚本让Sprite在屏幕角落跑来跑去。其实用实体硬件做一只“有生命感”的小东西完全是另一套逻辑而且更符合“智能”这两个字的本意。这篇文章想聊的就是用STM32单片机我实际用的是STM32F103C8T6后面简称F103从零搭一只智能桌面宠物它不是一个会发热的摆件而是一个能感知环境、能和你交互、有表情有动作的小家伙。你可以摸它、戳它、看它打瞌睡它也能告诉你办公室PM2.5爆表了、湿度不对了、或者你太久没起来活动了。这个项目特别适合两类人一类是学过单片机基础、正憋着做一个综合项目的学生另一类是桌面DIY爱好者想把手边的传感器模块和屏幕组合成真正有完成度的作品。它把定时器、中断、串口、IIC、SPI、PWM这几个单片机课上的“硬骨头”全串起来了但又不会难到让你想砸键盘。下面就把我从选型、画板、写驱动到调参踩坑的完整过程拆开讲每一步都给可以直接抄的配置和代码。1. 整体设计与方案选型1.1 为什么是STM32F103C8T6选主控这件事我纠结过一段时间。51单片机门坎最低网上资料也多但说实话真要塞下“显示触摸多传感器舵机控制”这么一坨需求51的资源就开始捉襟见肘了。F103这颗芯片是STM32家族里最经典的一颗72MHz主频、20KB RAM、64KB Flash外设该有的全都有3个定时器、2个SPI、2个IIC、3个USART、10个ADC通道还自带USB从机控制器。更重要的是它的价格和资料量某宝上蓝板子几块钱一片教程多到看不完踩了坑也容易搜到答案。有人会问既然预算和体积都不敏感为什么不用ESP32或者更潮的K210ESP32确实有WiFi和蓝牙但它的强项在联网裸机外设的实时性反而不如F103直接。K210带AI加速能做视觉识别但那个开发环境对新手不太友好光是烧录和IDE配置就能劝退一大半人。做桌面宠物这类“物理交互密集型”项目F103是最稳的选择外设驱动成熟、中断响应确定、调试工具普遍。而且以后想升级CubeMX生成的代码也能方便地迁移到F407或H7系列。1.2 桌面宠物的功能拆解与技术路线先把需求一条条列出来再决定每一条用哪块硬件、消耗哪些外设资源。我这个版本的功能清单是这样的表情与动画显示用0.96寸TFT屏幕显示宠物眼睛、嘴巴、腮红支持眨眼、开心、委屈、睡觉等状态触摸交互屏幕是电阻触摸屏摸头会开心摸肚子会委屈长按可以切换功能模式环境感知板载DHT11测温湿度光敏电阻感知环境亮度让宠物会根据环境“打哈欠”或“眯眼”物理动作两个SG90舵机驱动宠物耳朵开心时摇头晃脑睡醒时伸懒腰声音反馈蜂鸣器播放简单音效做对交互时“叮”一声调试接口串口输出状态日志方便看运行情况对应的硬件选型和技术路线可以整理成一张表功能需求硬件选型STM32外设核心要点表情显示0.96寸TFTST7735SPI1帧缓冲局部刷新触摸输入电阻触摸屏XPT2046SPI2ADC坐标线性映射温湿度采集DHT11GPIO定时器单总线时序亮度感知光敏电阻分压电路ADC1_IN0连续采样取均值耳朵动作SG90舵机x2TIM2_CH1/CH250Hz PWM输出音效反馈无源蜂鸣器TIM3_CH1不同频率的方波状态调试USB转TTLUSART1日志输出标粗提一下屏幕是这项目的核心交互面所以我把它放在SPI1上时钟能跑到36MHz以上刷新才够流畅。舵机用的是TIM2的两路通道因为TIM2是挂在APB1总线上的配置成50Hz不占CPU。DHT11这种慢速传感器就放在普通GPIO口上用定时器计数来读时序。1.3 显示与交互选型细节屏幕这步我前后试过三种方案0.96寸OLEDSSD1306、1.8寸TFTST7735、2.0寸IPSILI9341。OLED对比度高、功耗低但只有128x64单色做表情颗粒感太重ILI9341效果最好但2寸模块体积大驱动时RAM开销也大F103的20KB RAM跑全屏帧缓冲很勉强。最后选了1.8寸TFT160x128分辨率RGB565色深做宠物的眼睛眉毛腮红刚好够细腻模块还自带电阻触摸层和TF卡槽性价比很高。这里直接给出结论型的建议做桌面宠物表情交互1.8寸ST7735是F103方案里甜点级的选择。刷新率实测局部刷新能稳定在30fps左右帧缓存用160x128x2字节算下来约40KB直接放不下RAM所以要用分块刷新的思路后面第3节会详细讲。触摸层用的是XPT2046它本质是一个ADC芯片通过SPI接口把触摸点的电压转换成12位数字。这里有一个很多人第一次做会踩的坑触摸坐标不是屏幕坐标必须做线性映射和校准否则你点屏幕右上角宠物会在左下角做出反应。这个映射公式我放在第3.3节里讲这里先记住一句话——“触摸坐标是物理层的东西显示坐标是逻辑层的东西两者之间必须做一个坐标系转换”。2. 硬件搭建与电路设计2.1 系统组成与引脚分配硬件整体分三块主控板、传感器板、动作机构。主控就是最小系统板F103C8T6蓝色标准板传感器板和动作机构我用的洞洞板手工焊接没有去画PCB打样因为功能迭代太频繁画板反而拖速度。如果你打算做成品送人或参赛建议画一版PCB走线和供电都能更可靠。引脚分配表如下可以直接照抄外设信号引脚STM32引脚说明ST7735 TFTSCL / SDAPB13 / PB15SPI1_SCK / SPI1_MOSIDC / CS / RSTPB11 / PB12 / PB10命令数据选择/片选/复位XPT2046T_CLK / T_DIN / T_DOUTPB13 / PB15 / PB14触摸SPI复用SPI1T_CS / T_IRQPB9 / PB8触摸片选/触摸中断DHT11DATAPA1单总线光敏电阻AOUTPA0ADC1_IN0SG90舵机1SIGPA8TIM1_CH1SG90舵机2SIGPA9TIM1_CH2无源蜂鸣器I/OPA10TIM1_CH3PWM输出按键KEY1 / KEY2PB0 / PB1上拉输入USART1调试TX / RXPA9 / PA10注意和蜂鸣器复用问题注意我在实际分配时发现PA9和PA10同时给了舵机/蜂鸣器和USART1直接冲突了。后来调整成蜂鸣器改成用TIM4_CH1PB6USART1的PA9/PA10只留给调试串口。这个坑非常典型——凡事提前查引脚复用表CubeMX里分配引脚时它会自动变红提示冲突所以建议直接使用CubeMX做初始分配不要手画引脚表。2.2 电源与供电设计F103工作电压是3.3V舵机SG90推荐电压是4.8V至6V所以供电方案必须分开设计。我用的方案是USB 5V进板子后分两路——一路经AMS1117-3.3给主控和传感器供电另一路直接给舵机供电。这里有个容易踩的坑舵机启动瞬间电流能达到500mA甚至更高如果和主控共用同一路LDO瞬间压降会把单片机拉复位。所以我用了一个小技巧——舵机电源先过一个470uF电解电容和一个100nF陶瓷电容做缓冲再接一个1N5819肖特基二极管做隔离确保舵机堵转时不会拖垮逻辑电源。DHT11的光敏电阻分压电路也很简单光敏电阻和一个10K电阻串联中间点接PA0。注意光敏电阻的光谱响应和人眼感知不完全一致所以阈值需要在实测环境里标定我最后取的是“连续采样50次取中位数”的方式避免光线瞬时波动触发误动作。2.3 传感器和执行器的接线难点DHT11的单总线协议接线简单但时序苛刻。数据线要接一个4.7K上拉电阻到3.3V否则信号边沿不够陡读出来的数据会周期性错乱。更隐蔽的问题是DHT11的时序要用“微秒级”延时来实现而F103的延时函数写法直接决定成败。用HAL_Delay这类毫秒级库函数读DHT11是读不出来的必须用定时器做微秒延时或者用循环空转校准。我最终用的是TIM2做1us基准计数__HAL_TIM_SET_COUNTER(htim2, 0)来读当前计数值比for循环空转稳定得多。SG90舵机接线时有一个新手容易忽略的点舵机的信号线电平逻辑是3.3V兼容的所以可以直接接主控GPIO不需要电平转换。但电源线一定要接对极性SG90的红线是正极、棕线是负极、橙线是信号。接反了轻则舵机不动重则烧毁板子。我有一次就是两根线插反板子上的LDO当场冒烟教训相当深刻。3. 软件架构与核心实现3.1 程序框架状态机加分层驱动裸机开发最忌讳的事情就是把所有逻辑都堆在main函数的while(1)里然后一边刷屏一边读传感器还一边处理触摸最后每件事都慢半拍。我采用的分工方式是中断负责“采集事件”主循环负责“处理逻辑”定时器负责“固定频率调度”。有点像现实中的前台打电话登记、后台慢慢处理事务——前台中断只负责记录“有电话来了”后台主循环根据自己的处理节奏去接电话。具体到代码结构上我拆成了四层驱动层HAL封装底层外设操作比如LCD_DrawPixel、Touch_ReadXY、DHT11_Read不涉及业务逻辑服务层Service把底层数据加工成业务事件比如触摸坐标映射成“摸头/摸肚子”手势温湿度数据转换成“舒适/干燥/闷热”状态状态机层FSM管理宠物的情绪状态每个状态对应一套显示和动作输出调度层Scheduler用定时器产生1ms和10ms的节拍负责周期任务调度比如每50ms刷新一次表情、每20ms扫描一次触摸有人问我“单片机C语言没有堆栈吗为什么有中断嵌套就会出问题”这里顺便说清楚——F103的C语言有完整的调用栈并且中断也有自己的栈空间问题的关键不是“没有堆栈”而是裸机开发时默认给主栈分配的空间有限CubeMX默认是0x400也就是1KB。如果你的中断函数里调用了大量局部变量、或者printf重定向到串口栈很容易爆掉。解决办法是调大启动文件里的Stack_Size或者把大缓冲区改成全局变量。我习惯把栈配到0x1000并且中断服务函数里只置标志位不处理复杂逻辑。3.2 表情动画与屏幕刷新策略TFT屏幕只有160x128像素画眼睛嘴巴并不难难点在于怎么让动画看起来流畅自然。我使用了帧动画的思路把宠物的各个表情做成二维数组每个字节对应一个像素的颜色索引存放在Flash里用const修饰需要切换表情时直接从这个数组搬数据到显存。因为1.8寸TFT全屏缓冲区要40KBRAM放不下所以我把屏幕做了一个“窗口分块”的处理。ST7735支持设置显示窗口SetAddressWindow我只刷新屏幕中间的一小块区域比如眼睛区域36x36像素动画帧只需要更新这一块数据这样RAM只要分配2KB左右的临时缓冲就可以了。实测刷新眼睛动画一帧大约2ms整个眨眼动画60帧跑下来也就120ms看起来就很自然。伪代码如下// 按窗口刷新屏上某区域的图像 void LCD_ShowImagePart(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *data) { LCD_SetWindow(x, y, x w - 1, y h - 1); for (uint16_t i 0; i w * h; i) { LCD_WriteData(data[i]); // SPI写像素 } }实际调试的时候会发现SPI刷屏频率不是越高越好。ST7735的数据手册建议SPI频率不超过15MHz我实测给它上到36MHz结果花屏和雪花点明显增多。最终的稳定值在9MHz左右加上局部刷新策略动画体验反而比全屏暴力刷新更流畅。3.3 触摸坐标映射与事件判定这是触摸屏项目里最容易让新手抓狂的一个环节——逻辑上你点的位置和屏幕显示的内容对不上。XPT2046返回的是一组12位的ADC原始值它的大小跟触摸点在屏幕上的物理位置是线性相关的但和屏幕像素坐标是两套坐标系。为了把两者对应起来需要做“两点校准法”。校准思路是先在屏幕上显示两个已知像素坐标的点比如左上角点Ax1, y1和右下角点Bx2, y2让用户分别触摸这两个点记录下对应的触摸ADC值tx1, ty1和tx2, ty2。之后任意触摸点tx, ty对应的屏幕坐标用下面两个公式计算X_pixel (x2 - x1) * (tx - tx1) / (tx2 - tx1) x1 Y_pixel (y2 - y1) * (ty - ty1) / (ty2 - ty1) y1这个公式的原理就是线性插值——把触摸ADC的数值范围映射到屏幕像素范围。如果触摸屏的X轴和Y轴方向是反的只要调换映射关系即可。实际使用中你会发现环境对触摸校准有影响换一个环境温度、或者屏幕贴膜磨损后校准参数都要重新测。所以我做了一个功能——长按宠物的尾巴屏幕右下角3秒进入校准模式校准完把参数存到Flash里下次开机直接读。校准之后还要做“手势判定”。我把屏幕划分成几个热区头部区域、肚子区域、尾巴区域。模触摸事件发生后先根据映射坐标判断落在哪个热区再结合按下、抬起的时间差生成几个业务事件EVENT_TOUCH_HEAD、EVENT_TOUCH_BELLY、EVENT_LONG_PRESS_TAIL。这样主循环的逻辑就非常简单只需要消费事件不用关心触摸屏底层怎么工作。3.4 定时器与PWM的硬核细节舵机和蜂鸣器都需要PWMF103的定时器资源很多但是要想不互相干扰就得合理分配。我用TIM1产生舵机的50Hz信号TIM4产生蜂鸣器的音频信号。为什么舵机一定要用50Hz因为SG90舵机的控制信号周期是20ms脉宽在0.5ms到2.5ms之间对应0到180度。计算PWM参数的思路PWM频率 定时器时钟频率 / ((预分频器 1) * (自动重载值 1)) 假设APB2定时器时钟 72MHz目标是50Hz 如果预分频设72则计数器频率 72MHz / 72 1MHz 自动重载值 1MHz / 50Hz - 1 19999 所以 CCR 占空比 * 20000 / 10000 0度对应0.5ms脉宽CCR 500 180度对应2.5ms脉宽CCR 2500这个计算看着简单但实际操作中50Hz接示波器看信号会有一点毛刺。毛刺来源是把TIM1的时钟挂在APB2上APB2是72MHz而TIM2挂在APB1上是36MHz两者的分频系数配置稍有不同。我最终的配置是舵机用TIM1_CH1预分频72ARR 19999蜂鸣器用TIM4_CH1预分频72ARR 800-1200不同音高。超声波测距如果后期扩展也用定时器来做输入捕获。F103的定时器输入捕获模式可以直接测回响引脚的高电平脉宽不需要额外占用CPU。计算公式距离(cm) (高电平时间us * 340m/s) / 2 / 10比如捕获到回响高电平持续580us距离就是580 * 340 / 10000 / 2 ≈ 9.86cm。这个值用来判断“宠物感觉到手靠近了”可以做躲闪动画体验很新鲜。3.5 串口调试与日志体系裸机开发最大的痛苦是“盲跑”出了bug不知道程序在哪一步崩了。我做的第一件事就是把USART1重定向到printf然后在每个模块的入口和出口打印日志。比如开机时打印[SYS] Boot OK触摸事件打印[TOUCH] head pressed at (42, 38)舵机动作打印[SERVO] move to 90 deg。这样拿着USB转TTL小板插上电脑就能看到程序的完整动态。有一个细节值得记住——HAL库里的HAL_UART_Transmit是阻塞的在1ms定时器中断里调用会导致中断时间过长影响其他任务。我的处理方式是在调试阶段用阻塞打印在产品化阶段改用DMA环形缓冲区的方案把日志数据放在队列里慢慢发。另外printf默认是浮点全支持非常吃Flash空间如果像我只打印整数和字符串可以开启--no_div_optimization或者用printf精简版把浮点格式化去掉节约下来的Flash可以多塞几帧动画。4. 实操从零跑通一个完整交互流程4.1 开发环境让芯片先亮起来STM32开发环境到现在已经比以前简单很多了。我用的是STM32CubeMX生成初始化代码 Keil MDK编译调试这套流程也是目前最主流的搭配。CubeMX里选择芯片型号STM32F103C8T6配置时钟树外部8MHz晶振锁相环倍频到72MHz、配置调试接口SWD、按引脚分配表勾选好外设生成代码后直接用Keil打开。如果你之前只玩过51第一次用Keil5会遇到一个典型的困惑为什么装了Keil5还是找不到STM32芯片原因是Keil5不像旧版那样内置所有厂商的芯片支持包需要单独下载STM32F1系列的Device Pack。在Pack Installer里搜STM32F1就能找到装上重启Keil就有了。如果你同时开发C51和STM32注意选择完工程再打开文件因为两者的编译器和调试器配置是分开的。下载调试我用的是ST-LINK V2接线方式SWDIO接PA13、SWCLK接PA14、GND接GND3.3V供电。ST-LINK V2在Keil里的配置选ST-Link Debugger然后在Settings里把Flash Download勾上Reset and Run这样下载完程序自动复位运行省得每次手动按复位键。新手最容易在这步卡住的“USB设备无法识别”问题多半是ST-LINK固件版本太老用官方STM32 ST-LINK Utility升级一下就好。升级完如果还显示No ST-LINK detected检查一下是不是ST-LINK和开发板之间的杜邦线太长——超过20cm在高速SWD下就可能不稳定。4.2 最小闭环OLED显示加按键换表情跑通平台后第一个要做的闭环功能不是全套交互而是“按键切换表情”。这个闭环可以验证GPIO读取、屏幕显示和中断处理三个基础环节。我把KEY1接到PB0上按下时产生下降沿中断在中断回调里打开一个标志位主循环检测到标志位后调用Pet_SetState(PET_ANGRY)宠物表情切换成生气。这个功能虽然简单但实际证明了整条软件链路是通的。如果你这个功能都跑不出来后面加再多传感器也没用因为你不知道是哪一层出了问题。我的习惯是每次只做一小步用现象验证一个断言。比如这步的断言就是“按下按键屏幕立刻变表情串口打印出对应的状态日志”。一切都符合预期再做下一步。这个阶段的代码可以很粗暴直接在中断回调里切换表情状态先追求跑起来而不是优雅。但有一点要注意中断函数里不要直接调LCD刷屏因为LCD驱动是SPI传输耗时几毫秒会让中断时间过长。正确做法是中断里只置标志位主循环刷屏。4.3 加入传感器让宠物有“感觉”第二步接入DHT11和光敏电阻。DHT11读出温度和湿度后我把它们和宠物状态绑定温度超过28度时宠物会吐舌头显示喘气动画湿度低于30%时宠物会萎靡不振。为了让体验不生硬状态切换要做“迟滞”——比如温度到28度进“热”状态但温度降到26度才恢复这样就不会在临界点反复跳变。光敏电阻这步更有趣因为它是抗干扰环境的映射。晚上关灯后房间变暗光敏ADC值升高宠物自动切换到睡眠动画眼睛变成半眯状态。白天光线充足就正常开心。这个逻辑非常能体现“智能感”你会突然觉得桌上这个小东西是“活”的。这部分我加了一个保护逻辑连续采样的窗口时间必须超过10ms因为光敏电阻的响应速度较慢快速闪烁的光源会让ADC值抖动。我在代码里用了HAL_ADC_Start_DMA采集再在DMA传输完成中断里处理平均值效率很高。4.4 加入动作舵机让宠物“活”起来最后一步给宠物装上耳朵。两个SG90分别驱动左右耳做开心时双耳高频摆动、委屈时单耳下垂、睡觉时双耳平放。舵机的运动速度要控制不能像开关一样一下跳到位而是用增量式的方法每次控制周期增加一小步。以0.9度为单位累加10ms调度一次耳朵摆到目标角度大约需要200ms视觉上就有“动画过渡”的感觉了。舵机摆动的动画逻辑不能放在主循环的while循环里死等否则屏幕刷新会被卡住。正确的设计是设置好目标脉宽值让TIM1在中断里不断更新CCR寄存器主循环继续刷表情。这样耳朵摆动和表情变化是并行的体验流畅很多。5. 常见问题与排查实录5.1 编译下载类问题Keil5编译报错找不到CMSIS头文件。大部分情况是Device Pack没装好或者工程选项里的C/C页面的Include Paths没有添加标准库路径。最快解法是回到CubeMX重新生成一次代码它会自动把必要的头文件路径加进工程。ST-LINK连接失败报Error: Flash Download failed - Cortex-M3。排查顺序是先确认板子供电很多最小系统板需要外部供电才给芯片上电再确认SWDIO和SWCLK没有接反最后考虑降低SWD时钟频率——在Keil的Debug设置里把SWD Clock从4MHz降到1MHz往往就能解决。程序烧进去了但没反应。第一件事不是查硬件而是确认SystemInit是否执行以及RCC_GetClocksFreq返回的主频是否正确。我遇到过一次外置晶振虚焊程序卡在HSE起振等待while循环死等。解决方法是换内部RC振荡器HSI先跑确认系统能启动再排查晶振硬件。5.2 显示与触控异常屏幕白屏或花屏。花屏多半是SPI速率过高干扰降频就行。白屏要先查RST引脚——很多ST7735模块的复位引脚需要主控控制如果悬空可能处于错误状态初始化时先拉低20ms再拉高。另外检查VCC电源电压模块标注3.3V还是5V接错供电很容易白屏。触摸漂移。排除硬件故障后最常见的原因就是校准参数丢失或环境变化。我遇到过在空调房里触摸还算准拿到窗边晒太阳就开始飘的情况后来在校准流程上加了一层“触摸坐标滤波”把连续10次采样排序取中位数漂移明显改善。漂移校准这块每过一段时间重新执行一次两点校准比写什么滤波算法都管用。点击按钮没反应。这不一定是触摸坏了可能是触摸中断引脚配置问题。XPT2046的T_IRQ引脚在无触摸时是高电平有触摸时拉低所以应配置为上拉输入触发下降沿。如果你配置成上升沿每次抬手反而会触发一次假事件。这类细节用逻辑分析仪一抓就看到没有逻辑分析仪就串口打印原始ADC值观察事件触发时刻数据是否跳动异常。5.3 供电与稳定性问题舵机一抖单片机就复位。这是教科书级的供电问题。SG90堵转电流很大电压瞬间跌落会让F103检测到掉电复位。我的处理方案已经提过舵机电源和逻辑电源之间用肖特基二极管隔离加470uF大电容储能。如果你换成MG996R这类大舵机建议干脆用独立的5V开关电源模块供电别省这个钱。系统周期性死机。这种问题最耗时。先用串口打桩定位到具体模块然后重点检查中断里是否做了耗时的操作。我曾经在TIM1中断回调里加了printf打印舵机角度结果定时器中断执行时间超过PWM周期造成定时器事件丢失舵机转半圈就卡住。去掉中断里的打印改到主循环里周期性打印故障立刻消失。DHT11读数偶尔跳变。首先是上拉电阻问题其次是读取时序被中断打断。DHT11的单总线时序是微秒级的如果在读取过程中来了一个串口中断或者定时器中断可能会让采样的电平边沿错乱。解决思路是在读取DHT11的10us延时过程中关闭所有中断__disable_irq()读完再打开。这个优先级控制是裸机开发里很经典的操作值得养成习惯。5.4 避坑清单速查现象根因解决方案ST-LINK识别不到线太长/固件太老缩短SWD线、用ST-LINK Utility升级固件烧录后无反应外部晶振起振失败改用HSI启动排查硬件屏幕花屏SPI速率过高降SPI到9MHz以下触摸偏移校准参数丢失重新两点校准并存Flash系统复位舵机拉低电源舵机独立供电储能电容DHT11数据跳变中断打断时序读传感器时关中断PWM舵机抖动占空比突变增量式控制定时中断平滑输出这些都是我实际踩过或帮人排查过的坑。嵌入式调试七成时间都花在这些“小问题”上把这些清单存下来能省不少事。6. 扩展方向与个人体会6.1 从桌面宠物到智能外设这个项目的天花板其实很高。F103的资源完成后想升级并不需要换主控——直接用CubeMX把代码迁移到STM32F411或GD32F450刷屏、触控、传感器逻辑基本不用改只需把外设型号改一下。更大的提升空间在“连接性”上WiFi模块ESP8266/ESP-01S通过串口AT指令把温湿度数据上报到局域网手机App就能远程看到宠物状态蓝牙BLE模块JDY-31手机靠近宠物时宠物会识别到主人并做出欢迎动画LoRa模块如果做多只桌面宠物组成“宠物社群”LoRa的远距离低功耗特性很适合每只宠物一个角色ID互相接近时会打招呼语音识别LD3320或离线语音模块喊一声“过来”宠物会摇耳朵回应动态光效加一条WS2812灯带做宠物轮廓光切换情绪时同步变色氛围感直接拉满我还在这个项目里预留了编码器接口以后如果改成“桌面小车”形态直接接上霍尔编码器就能测速做PID闭环F103的32位定时器资源完全够用。可以说桌面宠物只是这个平台的一个“演示程序”。6.2 我做完这个项目最深的体会做一个硬件项目学到的往往不是某个具体外设怎么用而是整个工程的“组织方式”。刚开始我写在while(1)里一把梭结果加第三个功能的时候代码已经没法看了。后来强制自己用层和状态机重写才发现裸机开发也能做到“模块干净、逻辑清晰”每一个外设驱动是一个文件每一个业务状态是一个枚举改起来非常舒服。另一点体会是“调试意识”。很多新手拿到板子先写功能不写日志不打点出了问题就只能靠猜。我的习惯是每个外设驱动写完后先单独跑一个测试函数打印每个寄存器的值确认OK再集成。这样即使后面哪里出了问题也能快速定位到是哪一层。最后分享一个小技巧桌面宠物的外壳我用CAD画了STL模型然后打印内部留了两个M3螺纹孔用来固定舵机支架前脸开窗口露出屏幕。如果你的3D打印机不在手边用瓦楞纸板或亚克力板也能快速做一版原型。对于“如何让桌面宠物看起来可爱”这件事硬件工程师的自尊心可能会说这是设计师的事但实际经验是——把眼睛画大一点、眨眼速度调慢一点效果立竿见影。宠物嘛萌就完事了。
