ARTICLE DETAIL

资讯详情

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

STM32单片机计算器设计:LCD1602显示与矩阵键盘驱动全解析

STM32单片机计算器设计:LCD1602显示与矩阵键盘驱动全解析 基于STM32单片机计算器LCD1602显示项目从零拆解最近后台有不少读者在问基于STM32的计算器该怎么做正好手头有一个典型的课题基于STM32单片机计算器配LCD1602显示项目编号S014A。这个项目是很多学校的课程设计和毕业设计题目看起来简单但真要把它讲透涉及的东西其实不少GPIO操作、LCD1602时序驱动、矩阵键盘扫描、按键消抖、运算解析、甚至状态机设计。我这次把整个项目的实现思路、硬件接线、代码逻辑以及调试中容易踩的坑全部整理出来给准备做这个题目的朋友一份可以直接照着做的参考也聊聊怎么把这个“课设级”项目做出点自己的设计感。先给没接触过这块的朋友一句话说明这个项目要做的事情就是——用STM32单片机读取用户通过键盘输入的数字和运算符完成加减乘除运算再把输入表达式和结果实时显示在LCD1602液晶屏上。听起来不难但真正动手你会发现显示驱动、键盘扫描、运算状态管理任何一个环节没处理好做出来的东西就是“按着按着数字乱跳”“算到一半结果丢了”“屏幕只亮不显示字”这类经典问题。下面我按模块逐一拆。1. 整体设计思路与方案选型1.1 为什么核心选STM32而不是51很多计算器课题默认用51单片机因为学校教的就是51代码简单资料也多。但选STM32做这个题目我个人的判断是——这是一个“同样的题目更高的完成度”的选择。STM32F103系列最常用的是C8T6这个型号主频72MHz51单片机常见的是12MHz晶振、12个时钟周期执行一条指令两者计算能力差了不止一个数量级。计算器这种对实时性有要求、而且要做按键消抖和LCD时序驱动的应用STM32跑起来非常轻松定时器、外部中断、I2C、SPI这些资源也完全够用后续如果想扩展比如加上温度传感器、语音播报、蓝牙传数据STM32的余量也比51充裕得多。而且从学习角度讲STM32是ARM Cortex-M3内核学会了它的GPIO配置、时钟树、中断机制你以后接触任何ARM芯片都能很快上手。借助STM32CubeMX这个图形化配置工具初始化代码都是自动生成的我们只需要专心写自己的业务逻辑。另外要提醒一点选型时尽量选Flash在64KB以上的型号。计算器的功能虽然不复杂但如果你把LCD驱动、矩阵键盘、运算逻辑、显示刷新这些代码都写上再加上可能加的一些扩展功能比如历史记录、按键音代码量很容易超过32KB。C8T6是64KB Flash、20KB RAM做计算器绰绰有余。1.2 LCD1602显示方案的取舍显示部分课题指定用LCD1602这是工业界最经典的字符型液晶屏16列2行每行最多显示16个字符。它只能显示ASCII字符和少量日文假名、自定义字符想要显示完整的表达式比如123456一行16个字符是刚好够用的。LCD1602有两种接法8位并行和4位并行。8位接法一次传输8位数据占用DB0~DB7共8个引脚外加RS、RW、EN三个控制引脚一共11个IO。4位接法数据分两次传输高4位在前低4位在后只需要DB4~DB7共4个引脚加上3个控制引脚一共7个IO。我强烈建议用4位接法理由很实际STM32F103C8T6虽然有32个GPIO可用但一旦电路上接了矩阵键盘4x4需要8个IO、LCD7个IO、可能还有LED指示灯、蜂鸣器、串口下载电路IO资源就变得紧张了。4位接法省下的4个IO用途很大。而且4位模式在时序上只是多了一次数据写入对72MHz主频来说几乎没有延迟感。1.3 输入方案的两种思路对比键盘输入有独立按键和矩阵键盘两种方案。独立按键最简单每个按键单独占用一个IO口读取直接判断高低电平。但这只适合按键数量少的场景4个方向键、2~3个功能键计算器至少需要0~9十个数字键、加、减、乘、除四个运算符、等号、清除总共16个键。16个独立按键要占16个IO这在STM32上很浪费。所以主流方案是4x4矩阵键盘4根行线加4根列线一共8个IO口就能组合出16个按键。扫描原理是“行列反转法”或者“逐行扫描法”先把行线设为输出、列线设为输入依次拉低每一行检测哪一列变成低电平就能唯一确定按下的按键。这个原理本身不难但要注意按键的硬件消抖和软件消抖配合。综合来看我选的方案是STM32F103C8T6 4x4矩阵键盘 LCD16024位模式供电采用USB转TTL模块的5V输出板载AMS1117-3.3稳压到3.3V给MCU供电LCD1602直接用5V供电它的逻辑电平兼容3.3V输入这个后面细说。2. 硬件接线与关键电路解析2.1 最小系统与供电设计STM32F103C8T6的最小系统需要三样东西3.3V电源、8MHz外部晶振、复位电路。这个项目的常用做法是直接买一块“STM32最小系统板”来用板上已经集成了晶振、复位按键、LDO稳压、BOOT选择跳线、Type-C接口不用自己画PCB就能把精力聚焦在功能实现上。不过如果你是想自己画板子做实物需要注意这几个细节VDD和VDDA引脚都要接3.3V并且每个电源引脚旁边放一个100nF的去耦电容靠近引脚放置NRST引脚接一个10kΩ上拉电阻到3.3V再加一个100nF电容到地构成低电平复位的RC电路VDDA需要额外的磁珠或小电阻隔离减少电源噪声对ADC精度的影响。LCD1602的工作电压是4.7V~5.3V典型应用就是5V供电。这里有个新手经常疑惑的点STM32的GPIO是3.3V电平能直接驱动5V供电的LCD1602吗答案是可以的。LCD1602的逻辑输入高电平门槛大约是2VTTL电平标准STM32输出的3.3V高电平完全高于这个门槛。所以数据引脚直接连就行不需要电平转换。但这有个前提你的LCD1602模块是标准并口模块如果买的是带I2C转接板的那就是另一套玩法了。我这次以标准并口为例。2.2 LCD1602四线制接线对照表以最小系统板的PA口和PB口来分配引脚我常用的分配如下LCD1602引脚功能接STM32引脚VSS电源地GNDVDD电源正5VVO对比度调节接10k电位器中间脚RS寄存器选择1数据/0指令PB0RW读写选择1读/0写PB1直接接地只写不读E使能信号PB10DB4数据位4PB12DB5数据位5PB13DB6数据位6PB14DB7数据位7PB15LEDA背光正极串联220Ω电阻接5VLEDK背光负极GND有几个关键点RW引脚直接接地。LCD1602的读操作基本用不上我们只需要往里面写指令和数据。RW接地后GPIO只做输出引脚占用从11个降到10个而且在驱动代码里省了读忙标志BF的环节唯一需要做的就是“写完指令/数据后延时等待”。VO对比度引脚必须接电位器不能直接接地或接电源。LCD1602上电后不显示字、只亮背光八成是这个引脚的电平没调好。用一个10k电位器中间脚接VO两端分别接5V和GND上电后慢慢旋转电位器直到屏幕出现清晰的两行黑块或者字符。这一步是100%会遇到的先有这个心理预期。2.3 4x4矩阵键盘的接线与扫描思路矩阵键盘我分配在PA0~PA3做行线PA4~PA7做列线具体对应关系如下表按键行/列对应字符(0,0)PA0/PA41(0,1)PA0/PA52(0,2)PA0/PA63(0,3)PA0/PA7A清屏(1,0)PA1/PA44(1,1)PA1/PA55(1,2)PA1/PA66(1,3)PA1/PA7B退格(2,0)PA2/PA47(2,1)PA2/PA58(2,2)PA2/PA69(2,3)PA2/PA7C负号(3,0)PA3/PA40(3,1)PA3/PA5(3,2)PA3/PA6-(3,3)PA3/PA7× ÷ 矩阵键盘最经典的扫描办法是逐行扫描先把4根行线PA0~PA3设为推挽输出4根列线PA4~PA7设为上拉输入。然后依次让某一行的输出为低电平其他行为高电平再读列线如果哪一根列线读到低电平说明该行该列的按键被按下了。如果担心按键接触抖动导致误判软件上做两件事就够了检测到按下后延时10ms再读一次确认松开时也要消抖不然容易一次按键触发两三次操作。2.4 蜂鸣器与指示电路可选扩展如果想让作品看起来更完整可以加一个无源蜂鸣器到PB5通过TIM3的PWM输出驱动实现按键提示音。这个功能代码量很小但给评审老师的印象分会明显不一样。具体实现可以直接用TIM3输出频率可调的方波按键按下时调用一次Buzzer_Beep(2000, 50)表示“输出2kHz频率、持续50ms”人耳听到就是很清脆的“嘀”一声。3. LCD1602驱动与按键扫描代码解析3.1 初始化流程与双层结构LCD1602的驱动我习惯封装成一个独立的模块文件lcd1602.c对外只暴露LCD_Init()、LCD_Clear()、LCD_SetCursor(row, col)、LCD_WriteString(char* str)这几个接口。这样主程序里不用关心底层的时序细节代码结构非常清爽。LCD1602的初始化时序是网上资料最多也最容易抄错的地方其实跟厂商数据手册对照来写就不容易错。核心流程是上电后延时至少40ms让内部电路稳定连续三次写入指令 0x38这里有个细节在4位模式下这个0x38要分两次发送先发高4位0x3再发低4位0x8但网上很多代码这里写的是0x30然后依次写入 0x08显示关闭、0x01清屏、0x06光标右移、0x0C显示开、光标关、不闪烁。这里有一个很经典的坑如果用STM32CubeMX生成的代码GPIO初始化和LCD初始化都在main函数里执行LCD的初始化必须放在延时函数正常工作的前提下。启动时系统时钟已经是72MHzHAL_Delay依赖SysTick中断如果初始化阶段系统中断还没配好延时函数会失效。解决方法是LCD_Init() 放在MX_GPIO_Init()之后调用并且确保 HAL_Init() 已经被调用。另外如果用的是其他厂商的库或者自己写的延时函数第一次写LCD代码时建议把延时时间放大一倍等确认显示正常了再把延时降到数据手册的标准值这种方式排查问题会省心很多。3.2 写指令与写数据时序LCD1602的底层操作就是两条写命令和写数据。4位模式下每次都是高4位先送、低4位再送。核心代码如下void LCD_Write4Bit(uint8_t data) { GPIOB-ODR (GPIOB-ODR 0x0FFF) | ((data 0x0F) 12); LCD_E_HIGH; delay_us(1); LCD_E_LOW; delay_us(1); } void LCD_WriteByte(uint8_t data) { LCD_Write4Bit(data 4); // 高4位 LCD_Write4Bit(data 0x0F); // 低4位 } void LCD_WriteCmd(uint8_t cmd) { LCD_RS_LOW; LCD_WriteByte(cmd); delay_us(40); } void LCD_WriteData(uint8_t data) { LCD_RS_HIGH; LCD_WriteByte(data); delay_us(40); }代码里的GPIOB-ODR操作是寄存器层面的写法意思是在不改动其他引脚输出的情况下把PB12~PB15这四位设置成对应的数据。这里的0x0FFF是掩码保留低12位的状态把高4位清零再通过 12把数据放到PB12~PB15的位置上。不推荐在LCD驱动里用HAL_GPIO_WritePin一位一位地写速度慢不说一个字节要写8次代码也啰嗦。寄存器操作或者直接操作BSRR寄存器效率高得多。用BSRR的方式更安全可以一次性用ODR设置但要注意不要影响不相关的引脚。时序参数方面LCD1602数据手册要求E引脚的高电平脉冲宽度最小是230ns典型值450ns对于72MHz的STM32来说两条语句之间只有几纳秒的间隔所以必须插入至少1微秒的延时。delay_us(1)用DWT数据观察点与跟踪单元实现即可也可以直接用for循环空转几次。3.3 按键扫描与状态机消抖矩阵键盘扫描代码我推荐用“状态机”的方式写而不是简单地“检测到按下就返回键值”。状态机的思路是定义三个状态释放态、按下确认态、持续按住态。每次主循环里先读取当前IO状态再根据状态转移条件决定是否返回有效键值。简化版本可以这样做uint8_t Key_Scan(void) { uint8_t row, col, key 0xFF; // 行线置为输出低电平列线检测 for (row 0; row 4; row) { GPIOA-ODR ~(0x01 row) 0x0F; // 第row行拉低 delay_us(10); // 等待电平稳定 uint8_t col_val (GPIOA-IDR 4) 0x0F; if (col_val ! 0x0F) { for (col 0; col 4; col) { if ((col_val (1 col)) 0) { key row * 4 col; break; } } break; } } return key; }这个函数返回的是0~15的矩阵编号具体对应哪个字符有一个按键映射表KeyMap[16]把每个编号映射成0~9、、-、*、/、、C。这个分离的好处是换按键布局只需要改映射表不影响扫描逻辑。关于消抖我实际测试下来机械按键的抖动时间一般在5ms~10ms之间。所以消抖策略很简单第一次扫描到键值后延时10ms再扫描一次如果两次结果一致就确认按下否则认为是一次抖动。松键检测同样重要否则一按“5”可能出来“55”。实现上用一个变量记录“上一次有效键值”只有当上一次键值为0xFF且本次不为0xFF时才认为是一次“新按下”。3.4 主循环框架与实时性计算器的主循环不需要像工业控制那样追求微秒级响应但要有清晰的框架。我在这个项目里用的是“轮询状态标志”的方式while (1) { uint8_t key Key_Scan(); if (key ! 0xFF) { Buzzer_Beep(2000, 50); // 按键提示音 Calculator_Input(key); // 处理按键输入 } Calculator_UpdateDisplay(); // 刷新显示 delay_ms(5); }这里Calculator_Input()是计算器核心逻辑模块的入口它接收按键编号更新内部的表达式缓冲区、运算状态和结果。显示刷新单独抽出来是因为LCD1602写一次字符串大约要几百微秒如果每次按键都全屏重写不仅闪烁感明显而且会和按键扫描互相干扰。4. 计算器核心逻辑与算法设计4.1 表达式如何存储与解析计算器核心要解决的一个问题是用户任意输入数字、运算符、等号程序如何知道现在处于什么状态、下一步该干什么。我推荐用“状态机”管理计算流程定义几个阶段STATE_INPUT_NUM1正在输入第一个操作数STATE_INPUT_OP已经输入运算符等待第二个操作数STATE_INPUT_NUM2正在输入第二个操作数STATE_RESULT已经算出结果每次按键进来根据当前状态决定处理方式如果按的是数字键当前状态是STATE_INPUT_NUM1就把数字追加到第一个操作数缓冲区后面如果状态是STATE_RESULT说明用户要开始新计算先清空缓冲区再追加。如果按的是运算符记录运算符进入STATE_INPUT_OP状态如果按的是等号把待计算的表达式交给运算函数得出结果进入STATE_RESULT状态。操作数缓冲区用字符串数组存比如char num1[12], num2[12];通过atoi()或atof()转成数字做运算。这样做的优势是显示什么就是什么用户输入的原始内容都保存着后续想实现“历史记录”“表达式回显”都很容易。直接用变量接收数字换算会导致无法区分用户输入的是“00”还是“0”缓冲区都是字符串“0”。4.2 运算优先级怎么处理计算器要不要支持“先乘除后加减”这取决于课题要求。如果只需要做“顺序计算”A 加/减/乘/除 B 再 加/减/乘/除 C……那非常简单每次拿到等号就直接result num1 op num2。但如果要求完整表达式的运算优先级就复杂一些。基于课设的通常要求我采用的是中缀表达式转后缀表达式逆波兰表达式再计算的方案。具体做法是维护两个栈一个运算符栈、一个数字栈遍历表达式遇到数字就入数字栈遇到运算符如果运算符栈空或者当前运算符优先级高于栈顶就入栈否则先弹出栈顶运算符参与计算再入栈遍历结束后把运算符栈全部弹出参与计算。这样12*3正确结果是7而不是顺序计算的9。代码量大约50行但对课设来说能明显拉开“普通完成”和“优秀完成”的差距。如果你只想做顺序计算那在等号按键处理里直接算就行但要注意用户的输入以字符串形式存在如果用户输入的是23要转换成整数23转换函数要注意溢出问题——计算器能显示的最大位数由LCD1602的16列决定操作数位数通常不超过7位int32足够用。4.3 除数为零与小数问题计算器项目最容易在“除数为零”这个边界情况上翻车。不做保护的代码会直接进入硬件错误中断HardFault屏幕冻住按键全部失灵。正确的处理方式是在做除法前判断num2 0如果是LCD显示Error! Div 0清空状态机等待用户按C键复位。另一个容易忽略的是小数运算。普通课设如果只要求整数运算那简单整数除法在C语言里直接截断但要告诉用户这一点不然输入5/2显示成2可能让人觉得你算错了。更好的做法是引入浮点运算用sprintf把浮点数转成字符串显示显示时限制小数位数。我建议至少支持一位小数然后处理成整数避免浮点显示兼容性问题。比如内部用int32存储100倍的结果显示时手动加小数点这种方法避开了sprintf的浮点格式化开销也避免了浮点数精度误差实测非常稳定。4.4 显示刷新与字符长度控制LCD1602每行最多16个字符计算器的显示策略一般是第一行显示表达式第二行显示结果。但如果用户输入的表达式超过16个字符要自动左移还是停止输入我在项目里做的是滚动显示缓冲区最多存20个字符超过16个字符时LCD显示缓冲区末尾的16个字符这样用户总是能看到最新输入的数字。显示刷新有个细节如果每次都全清第一行再重写屏幕会闪肉眼感觉就是一直“抖”。解决方法是记录上一次显示的字符串比较新旧字符串内容没变的行不做写操作。这个对LCD1602这种只能整屏写的液晶来说输出体验的提升很明显。另外要处理的一个坑是LCD1602的清屏指令0x01执行耗时约1.6ms而lcd_clear()里如果清屏后立刻接着写字可能第一个字符写不进去。所以清屏后要加一个2ms左右的延时或者清屏时直接把“清屏写入”串在一个函数里中间不留异步空间。5. 常见问题与调试实录5.1 LCD1602只亮背光不显示字符这个问题在我的私信里出现频率最高原因八九不离十是VO对比度没调好。LCD1602在5V供电下VO引脚电压在0.4V~1.5V之间才显示清晰。如果你用了一个固定电阻分压而不是电位器一旦阻值不合适字就不出来。排查步骤确认VO接的是电位器中间脚旋转电位器看有没有变化如果旋转过程中屏幕出现一行黑块说明LCD本身工作正常问题在初始化代码如果怎么旋转都没有任何反应检查电源VDD和VSS有没有接反、检查E引脚有没有波形示波器或者万用表频率档看、检查RW是否接地。还有一个容易忽略的是STM32F103的PB3、PB4、PA15这几个引脚上电默认是JTAG调试口功能不是普通GPIO。如果LCD的数据线不小心接到了这几个口上GPIO初始化了也没用因为默认是复用功能必须先把__HAL_AFIO_REMAP_SWJ_NOJTAG()或禁用JTAG才能当普通IO用。这个坑我当年踩过排查了一整个下午。所以LCD引脚尽量选PB5以后或者PA不使用JTAG的引脚省心。5.2 按键扫描有“串键”现象按下键盘的“8”结果显示“5”或者跳两个数字这种现象的本质是扫描时序问题。一个是行线和列线的电平没有稳定就读取另一个是消抖不彻底。排查思路每次切换行线输出后加入至少10us的延时再读列线避免引脚翻转的瞬态过程干扰消抖延时从5ms提高到15ms检查硬件接线矩阵键盘的每根信号线最好都接一个10kΩ的上拉电阻列线内部上拉也可以但如果有长杜邦线连接外部上拉更可靠杜邦线不要飞得太长、互相缠绕矩阵键盘本身是低电平有效线间电容耦合可能导致误触发。我实测过用面包板搭矩阵键盘线长超过15cm就开始出现偶发串键缩短线长、用排线或者直接焊在PCB上问题基本消失。5.3 结果算错或者出现负数溢出如果你做的是整数运算999999*999999已经超过int32的21亿上限。显示出来的是一个莫名其妙的负数这其实是整数溢出不是程序bug但用户不会理解这一点。处理方法是在乘法前先判断操作数位数如果两个操作数加起来超过9位直接显示“Overflow”算是一种保护机制。如果用浮点运算还要注意sprintf(buf, %f, result)输出的字符串在LCD上显示不下。比如0.000001展开后很长。建议用sprintf(buf, %.2f, result)限制两位小数不做四舍五入的复杂需求时也可以用%.1f。5.4 Keil工程无法下载程序STM32计算器最常见的下载问题是用ST-Link下载时报 “No target connected” 或者 “Target not found”。处理顺序确认ST-Link的SWDIO、SWCLK、GND三根线连接正确注意有些ST-Link需要接3.3V供电有些不需要确认BOOT0跳线接到0低电平BOOT1不需要管进Keil的Options - Debug - Settings看是否能识别到目标芯片IDCODE如果不能多半是硬件接线如果之前往芯片里烧录过一个把SWD引脚PA13、PA14配置成普通GPIO的程序调试接口会被禁用用“先用串口ISP擦除整片Flash”的方式救回来。顺便说一个很常见的现象下载器连着板子的时候能正常识别一拔掉再单独给板子上电程序就跑飞。这个通常是电源问题——电脑USB口供电给ST-Link再转给板子电压只有4.7V左右板载LDO输出可能只有3.1V芯片不稳定。解决办法是单独给板子接5V电源或者换一个电压更准的USB口。5.5 计算器按键响应延迟明显如果主循环里每次扫描键盘后都要刷新LCD再加上延时可能导致按键按下去1秒才有反应。检查点LCD刷新函数里是不是调用了清屏指令0x01这个指令耗时长按键扫描函数里的for循环是不是在每次判断按键时都重新初始化GPIOGPIO初始化很耗时延时函数是不是用了HAL_Delay这种依赖系统滴答的阻塞延时主循环里建议用非阻塞的HAL_GetTick()做时间片轮询。我最后的优化做法是给按键扫描和LCD显示各分配一个5ms的时间片用HAL_GetTick()计数而不是阻塞整个系统反应速度提升非常明显从“明显卡顿”到“随按随应”。6. 工程组织与代码框架建议6.1 模块划分与头文件设计项目虽小工程结构还是要规范。我习惯把代码拆成4个模块main.c系统初始化、主循环调度lcd1602.c/hLCD底层驱动和应用显示接口key.c/h矩阵键盘扫描、消抖、键值映射calc.c/h运算状态机、表达式解析、结果格式化。这种分法的好处是每个模块都能独立测试。我先单独跑LCD显示字符串的例子在屏幕上显示任意字符串确认LCD没问题再单独跑按键扫描程序把按键值通过串口发到电脑上的串口助手确认键盘没问题最后才把两个模块接到一起写运算逻辑。每次出现bug问题范围就会缩小到一个小小的模块内而不是全部代码都要查。6.2 使用STM32CubeMX快速初始化现在做STM32开发基本离不开STM32CubeMX。这个项目的CubeMX配置非常快选择芯片STM32F103C8T6SYS - Debug 选择 Serial Wire这个必须选否则下载一次后SWD引脚被占用RCC - HSE 选择 Crystal/Ceramic Resonator时钟树里把HCLK设置到72MHz可以输入72回车软件自动算PLL参数GPIO直接默认引脚分配在代码里用寄存器操作生成工程时选择Makefile或者Keil Toolchain都可以。CubeMX生成的GPIO初始化里需要注意把不用的引脚设置成模拟输入这样功耗低也稳定。但这个项目无所谓默认就行。6.3 对课设/毕设的更高完成度扩展如果你做这个题目希望在答辩时拿个高分除了基础功能还可以加几个低成本高亮点的扩展连续运算按下等号后继续按运算符直接用上一次结果参与运算按键提示音加一个无源蜂鸣器按键和报错有不同的音调LCD背光控制用PWM控制LCD背光亮度按键时闪一下上电自检开机时LCD显示“System OK”并在1秒后进入计算界面掉电记忆使用STM32内部Flash保存上次关机时的结果开机恢复。这里要注意操作Flash时最好把中断关掉避免擦写过程中被打断。这些扩展每一项代码量都不大但每一件都能跟评审老师聊上两分钟对“工作量”的体现非常明显。7. 调试工具与常用技巧7.1 串口打印调试法很多STM32初学者忽略了串口调试功能。我强烈建议在开发阶段预留一个串口打印通道PA9TX、PA10RX接USB转TTL模块通过printf重定向到USART1。具体的重定向代码是int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }这样在代码里写printf(key%d, state%d\n, key, state)电脑上就能实时看到按键扫描结果和计算器状态机的变化。硬件调试器断点可以查问题但计算器这种轮询程序用串口日志弹性更好不需要暂停CPU就能看运行轨迹。7.2 用逻辑分析仪抓时序如果你手头有一个几十块钱的8通道逻辑分析仪强烈建议在调试LCD1602驱动时用一次。把逻辑分析仪的通道接到RS、E、DB4~DB7上按一次初始化按键就能非常直观地看到E引脚的高低电平和数据引脚上电平变化是否符合数据手册的时序要求。我实际抓过几次波形发现新手写的代码很容易出现E引脚高电平时间太短的问题代码逻辑看起来没问题但LCD就是没反应用逻辑分析仪一看E脉冲宽度只有几百纳秒低于规格书要求的230ns。加了几个空指令延时后问题立即消失。这种调试手段的效果是教科书上完全学不到的建议有兴趣的朋友尽早入手一个逻辑分析仪几十块钱能帮你省下几天排查时间。7.3 硬件复位的技巧调试时板子偶尔会进入一种“跑飞”状态现象是什么都不显示、按键无反应。这时按下板子上的复位按键程序从头开始运行现象可能消失也可能复现。如果复位后能恢复正常说明是程序运行中某个状态没有处理好如果复位后依然卡死就要检查硬件电源、晶振和初始化代码了。还有一个经验是给STM32供电的电源尽量不要直接从ST-Link的3.3V引脚取。ST-Link的3.3V输出能力比较弱驱动LCD1602背光背光典型电流约20mA~50mA加上STM32本身的工作电流会让电压跌落导致系统不稳定。用外部5V供电再接板载LDO最稳。8. 最终效果复盘与几点个人体会整个项目做完你手里的东西应该是一个能完成整数四则运算能显示表达式和结果的单片机计算器。如果不做扩展裸机代码量在300行左右硬件成本最小系统板加LCD加按键不超过30块钱但整个过程中涉及的知识点覆盖了STM32开发的大部分核心内容。我在实际调试这个项目的过程中最深刻的体会是计算器这种“看起来简单”的题目恰恰是把单片机基础串起来最好的练习。LCD驱动让你理解了时序和外设操作的本质矩阵键盘让你理解了IO方向切换和消抖的原理运算状态机让你第一次接触“程序状态”这个概念这不是背代码能学到的。最后分享一个实用小技巧如果你用的是LCD1602 4位模式找一张纸把每个引脚的接法画出来贴在工作台上不然每次换板子、换引脚都要重新理一遍线特别容易接错。我在做了三次这个项目以后已经把引脚分配固定成一套标准只要新项目用LCD1602就直接套用同一套分配。标准化接线和标准化驱动代码能省掉不少重复劳动。这个项目后续可以扩展的方向不少比如加上I2C转接板让LCD只占用两根线、用FreeRTOS把按键和显示拆成两个任务、加入DS1302时钟模块做成带时间显示的计算器甚至通过ESP8266把运算结果传到手机App上。但不管扩展成什么样底层的LCD驱动、按键扫描、运算状态机这三块内容是不会变的把它吃透了其他应用都是在这个基础上的迁移和组合。
返回列表