news 2026/8/31 15:57:24

基于STM32的计算器设计与实现:从按键扫描到OLED显示的完整工程解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
基于STM32的计算器设计与实现:从按键扫描到OLED显示的完整工程解析

简介:本资源是一套基于STM32F103ZET6单片机实现的嵌入式计算器工程源码,面向嵌入式初学者与课程设计实践者,解决基础人机交互与算术运算逻辑在裸机环境下的落地问题。项目适配正点原子战舰开发板及2.5寸LCD显示屏,完整实现数字输入、四则运算、结果实时显示等核心功能,配套效果视频直观展示运行流程。压缩包共177个文件,含32个C源文件(如jisuanqi.c、paint.c)、25个头文件(.h)、33个编译中间文件(.o/.d)及Keil工程配置文件(.uvprojx、.uvoptx)、可执行镜像(.hex)和调试配置(.dbgconf),总大小4.27MB,结构清晰,便于理解STM32固件库开发全流程。已有4857人学习下载,提供可直接编译烧录的完整工程,涵盖硬件驱动、GUI绘制、按键扫描与表达式解析等关键模块,是掌握STM32外设协同与小型应用系统开发的实用参考。 最近整理了一份基于STM32设计的计算器工程源码,支持加、减、乘、除四则基本运算,配套矩阵按键输入和OLED显示,压缩包里有完整的Keil工程和烧录说明。这次就顺着这个项目,把从硬件选型、按键扫描、运算逻辑到调试踩坑的整个过程拆开讲讲。无论你是刚学完STM32基础想找个练手项目,还是想搞懂计算器这种实时交互系统怎么设计,这篇都可以直接照着做。

很多人会觉得计算器太简单,无非是读取按键、算个结果、显示出来。但真正实现起来,里面藏着不少坑:按键抖动怎么消,连续运算怎么写,除数为零怎么处理,负数显示要不要支持,浮点结果怎么格式化。这些细节恰恰是嵌入式开发的典型问题,做完这个项目,对中断、定时器、状态机、外设驱动这些概念的理解都会上个台阶。

1. 内容整体设计与思路拆解

1.1 为什么选STM32实现计算器

做计算器,用51单片机也能做,用树莓派也能做,但STM32是嵌入式领域最主流的MCU,选它有几个实在的理由。

首先是外设资源够用。计算器需要按键输入和显示输出,STM32的GPIO、定时器、I2C/SPI接口都绰绰有余。如果用带触摸屏的方案,甚至可以用FSMC接口驱动TFT,为后续扩展留足余量。其次是开发环境成熟,Keil MDK、STM32CubeIDE、标准外设库、HAL库、LL库,随便选,资料多,遇到问题随便搜都有答案。第三是性能冗余,STM32F103系列主频72MHz,做计算器连1%的性能都用不到,但这正好意味着你能在上面跑更复杂的算法,比如中缀表达式解析、带优先级运算、甚至科学计算器功能,而不是被性能卡住。

这个项目用的主控是STM32F103C8T6,也就是俗称的“蓝丸”核心板上的那颗芯片。72MHz主频、64KB Flash、20KB RAM,对于这套计算器逻辑绰绰有余。你完全可以用别的型号,比如STM32F407或者G030,只要引脚配置对应改一下就行。核心逻辑是平台无关的,这也是这个项目源码值得参考的原因。

1.2 基本运算功能需求梳理

所谓“基本运算”,我们定义为四则运算:加(+)、减(-)、乘()、除(/)。在这个基础上,还需要配套的操作符优先级判断,也就是乘除优先于加减。例如输入"2+34",结果应该是14,而不是20。

具体功能需求列表如下:

  • 支持整数和浮点数输入,小数点在显示区可以切换
  • 支持正负数显示,负数用"-"前缀标识
  • 支持连续运算,例如"12+34*5-"这种边输入边显示的过程
  • 支持等号后继续运算,按下等号出结果后,再按数字键会以当前结果为起点继续输入
  • 支持清除(C)、退格(Backspace)等基本编辑操作
  • 除数为0时显示"Error",并允许用户清除后继续操作
  • 结果显示位数要截断到合理范围,不能出现"1.9999999"这种浮点误差

这些需求看似简单,但每一条都对应一个实现细节。比如连续运算,你既要保存当前结果,又要保留待运算的运算符,还要处理好“等号后继续输入”的状态切换。这就是典型的有限状态机应用场景。

1.3 系统架构与状态机设计

从软件架构上,我把整个系统分成三层:输入层、处理层、显示层。

输入层负责按键扫描、去抖、键值映射,最终产出一个“按键事件”。处理层是一个核心状态机,维护当前输入值、操作数栈、运算符,以及各种状态标志。显示层将处理层的结果格式化成字符串,刷到屏幕上。

核心状态机的状态划分如下:

状态含义触发条件动作
INPUT_A输入第一个操作数开机或清除后接收数字键、小数点
OP_WAIT等待选择运算符操作数输入完成接收+ - * /,必要时计算
INPUT_B输入第二个操作数已选运算符接收数字键、小数点
CALC_DONE运算完成按下等号显示结果,支持继续运算
ERROR错误状态除数为0或数据溢出显示Error,仅清除有效

举个例子:按下"12+"时,状态从INPUT_A切到OP_WAIT,同时把12暂存。按下"34"时,状态切到INPUT_B,显示区变成34。再按""时,处理层会先完成12+34的加法,得到46,然后被当作新运算符,状态回到OP_WAIT,显示区显示46。这种即时计算方式叫“逐步计算法”,适合基本四则运算,不需要复杂的表达式转后缀。

如果你想实现带括号和优先级更高的计算器,那就要用中缀转后缀表达式(逆波兰式),再配合双栈计算。但这次是基本运算,逐步计算法代码量小、状态清晰,非常适合教学和二次开发。

2. 硬件电路设计与按键方案

2.1 主控最小系统与引脚分配

硬件上我直接用了市面上常见的STM32F103C8T6最小系统板,板载晶振、复位电路、USB转串口,省去自己画底板的麻烦。但如果你要自己做PCB,最小系统必须包含:8MHz主晶振及两个20pF负载电容、32.768kHz低速晶振(若使用RTC)、上电复位电路(10kΩ上拉 + 100nF电容)、VTref/VDDA/VSSA等去耦电容、BOOT0和BOOT1配置。

引脚分配上,按键矩阵用PA0-PA3做行,PB0-PB3做列;OLED显示屏用I2C1,PA5做SCL、PA6做SDA。串口使用PA9、PA10,方便打印调试信息。每个按键的GPIO都配置为上拉输入,列线配置为推挽输出,扫描时逐列拉低。

有一件事必须注意:STM32的PB3、PB4引脚默认复用为JTAG调试口(JTDO和NJTRST),如果你们把矩阵键盘配到这两个脚上,会发现在线调试正常,但脱离调试器后按键不工作。解决方法是禁用JTAG,仅保留SWD——调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE)。这个坑我在第一个版本踩过,花了一个晚上才定位到。

2.2 矩阵键盘 vs 独立按键

为什么选择矩阵键盘而不是独立按键?原因很简单:节省GPIO。一个4x4矩阵键盘用8个GPIO就能支持16个按键,而独立按键要16个GPIO。STM32F103C8T6虽然有37个GPIO,但还要留给出显示和串口,独立按键资源紧张。矩阵键盘的原理是行列分时导通:每列输出低电平,其余列输出高电平,然后读取所有行引脚电平,若某行读到低,说明该行与当前列交叉的按键被按下。

实际按键布局我用了16个键,分别是:

按键功能按键功能
0-9数字+
.小数点-
=计算*
C清除/
Back退格+/-负号切换

如果你不需要负号切换,可以把4x4改成3x4矩阵,节省2个IO给其他用途。拓展一下,想要更多按键(比如三角函数、括号、pi),可以换成7x11或者用I2C扩展芯片PCF8574,一块芯片就能扩8个IO。

2.3 显示方案怎么选

显示方案常见的有三种:数码管、OLED、LCD1602/2004。数码管显示数字美观但无法显示字符"Error";LCD1602可以显示两行16个字符,价格便宜,但模块体积大、接线多(除I2C转接板以外);OLED(0.96英寸,SSD1306驱动)体积小、支持I2C接口、显示自由,很适合计算器这种信息量不大的场景。

我最终用的0.96寸OLED,分辨率128x64。一屏能显示8行16个英文字符,或者4行三号大字。为了显示效果,我把结果区放在底部一行,用大字体显示;把表达式历史或操作提示放在上部。OLED的控制核心是SSD1306控制器,通过I2C发送命令和数据。需要熟悉的关键命令有:显示开关(0xAE/0xAF)、页地址设置(0xB0-B7)、列地址设置(0x00-0x0F低四位 + 0x10-0x1F高四位)、对比度设置(0x81)。如果使用HAL库,可以简化成两个函数:OLED_WriteCommand和OLED_WriteData,然后用DMA或阻塞方式发送。

有一点值得提醒:很多OLED模块的I2C地址是0x3C,但也有0x3D的。驱动不了时先检查地址,这个比排查代码快得多。还有一种方式是使用4线SPI,速度快一些,但对于刷新一帧只有几十字节的场景,I2C完全够用。

2.4 电路连接与电源注意事项

连接关系非常简单,但有几个电源和信号完整性的坑。

按键矩阵的行线分别接PA0-PA3,列线接PB0-PB3。所有按键的一端接行线,另一端接列线,通过行列交叉点连通。这里要注意的是,行线必须配置为上拉输入模式,因为扫描列时,未被选中的列输出高电平,被选中的列输出低电平,行线通过按键与列线相连。若按键按下,行线被拉低,单片机读到低电平。如果没有内部上拉,悬空状态下电平不确定,按键误触发分分钟教你做人。

OLED的SDA和SCL必须接上拉电阻。很多模块本身带了上拉,如果没带,在I2C总线上加两个4.7kΩ到3.3V。电源方面,STM32和OLED都是3.3V供电,按键不需要额外电源。注意不要让5V进入I2C引脚,否则时间长了会损坏MCU。

静电问题也要重视。在干燥环境中,手指触摸按键容易产生静电,虽然有内部保护二极管,但反复放电也可能导致复位或数据错误。可以在每个按键靠近MCU的一端加一个100pF电容到地,起到简单的ESD滤波作用。实际测试中,加与不加在普通环境下区别不明显,但在工业环境就会体现差异。

3. 软件核心模块实现

3.1 按键扫描与消抖处理

按键扫描的基本思路是每10ms执行一次,每次驱动一列输出低电平,然后读取4行电平,翻到下一条列线继续。一个完整周期扫描4列需要40ms,对应25Hz的扫描率,人手的按键持续时间通常超过100ms,不会丢键。

消抖处理有两种做法:硬件消抖(RC滤波)和软件消抖。我采用软件消抖,其实也是滤波思想:连续两次扫描得到的键值一致,才认为按键有效。具体状态如下:

uint8_t KeyScan(void) { uint8_t key = 0; Key_CtrlColumn(LOW, COL0); // 拉低第0列 Key_DelayUs(10); // 等待电平稳定 if (Key_ReadRow() & ROW0_MASK) key = '1'; // 读取行 if (Key_ReadRow() & ROW1_MASK) key = '4'; //... Key_SetAllColumns(HIGH); // 恢复所有列 // 依次扫描其他列 return key; }

但更实用的消抖方式是使用一个“状态机+定时器”的结构。我定义了一个key_debounce_driver,每个按键有释放态、按下确认态、长按态。对于计算器这种短促输入,只需要“按下确认”和“释放”两种事件。每次扫描到按下时,记录时间戳,如果同一个键持续被扫描到超过20ms,就认为是一次有效按键,然后把键值放入一个环形缓冲区。这个缓冲区的存在能有效防止按键期间CPU正在做耗时运算(比如浮点格式化)导致丢键。

3.2 核心状态机与运算逻辑

整个运算逻辑可以写成一个大函数ProcessKey(uint8_t key),内部根据当前状态决定处理方式。这是我最想分享的部分,因为很多初学者会把运算过程写成“按一个键处理一锤子”,结果按等号时不知道该怎么把结果带出来。

我把关键状态机用结构体表示:

typedef struct { char display[16]; // 显示缓冲区 double accumulator; // 累加器,存第一个操作数或中间结果 double current_input; // 当前输入的操作数 char op; // 当前运算符 uint8_t state; // 状态机状态 uint8_t decimal_place; // 小数位计数 } Calc_t; Calc_t calc;

加法减法很容易,但乘除的过程必须注意“运算符优先级”在逐步计算中的正确处理。比如输入"2+34"时,按照逐步计算法,按下+先不做加法,按下3后,再按时,系统会判断当前运算符是+,而新运算符优先级更高,于是不能立刻计算2+3,必须先算34?不对,这种思路在逐步计算法里反而复杂化了。

更简单的做法是:不处理优先级,直接从左到右计算。这样"2+3*4"会得到20。想要得到14,就必须采用“待定运算符”的机制:当新运算符优先级大于当前运算符时,不计算,仅保存当前运算符和新运算符,等后续低优先级运算符进来时再统一计算。但这样会引入更复杂的控制逻辑。

这里我选择一种折中方案:使用“双变量 + 单运算符”的传统简单计算器逻辑。它不处理同级运算的优先级问题,但可以支持连续四则运算。例如:

  • 输入 "12+34",当前是INPUT_B,显示34。
  • 再按 "",因为当前运算符是+,先把12+34算出来得到46,显示46,并保存运算符
  • 再输入 "5",显示5。
  • 按 "=",计算46*5=230,显示230。

这种逻辑适合学习,也基本符合普通用户的使用预期。如果你希望严格按数学优先级,那需要像前面说的一样实现表达式解析。源码包我注释里也加了宏定义,可以通过开关切换“严格优先级模式”,但严格优先级模式会引入中缀转后缀逻辑,代码量多出200多行。建议先从简单模式跑通,再逐步加深。

等号处理有一个容易忽略的点:按等号时如果当前没有第二个操作数(例如用户按了"12+"后直接按=),应该将第二个操作数视为与第一个相同,执行"12+12=24",还是忽略?我采用的是忽略,直接不响应等号。这更符合直觉。

3.3 浮点显示与精度控制

基本运算中,除法会产生小数。例如10/3=3.3333333333333335。直接格式化到串口会显示一长串,非常难看。因此显示模块需要把double型结果按最多8位有效数字显示,并去掉尾部多余的0。

实现方式:

snprintf(buffer, sizeof(buffer), "%g", value);

如果value是整数,%g会输出整数格式;如果是小数,输出科学计数法(当数值过大或过小时)。为了避免科学计数法,可以用自定义格式化函数,遍历小数点后的位数,当超过6位时截断。

对于整数结果,也要防止输出"12.000000"。我的做法是:先检查value与round(value)的差值是否小于1e-6,如果是,直接按整数显示,否则按浮点显示。

这里还要注意float和double的选择。STM32F103做乘法时使用浮点运算是通过软件模拟的,性能不高,但只要你不做高频率计算,10毫秒内完成一次乘除法没压力。如果你改用STM32F4系列,硬件FPU会让浮点运算快十几倍。不过计算器场景,F1足够。

3.4 OLED显示驱动与刷新策略

显示刷新要避免“闪屏”。如果你的OLED驱动是整屏全刷,每秒刷新几十次,会出现闪烁。正确做法是用“分区刷新”或“帧缓冲”。我采用128字节的RAM缓存,先在内存里画好一帧,然后调用一次OLED_Clear,再整屏写入。OLED本身有显存,不需要外置帧缓冲,但为了简化绘制,我维护了一个屏幕缓冲区并映射到GPIO。

实际驱动SSD1306的I2C写一个像素的方法是设置页地址(0~7)和列地址(0~127)。我封装的OLED_DrawChar和OLED_DrawString函数,会从字模数组中取模数据,填充到缓冲区。用到的大字模是16x32点阵,适合显示大号数字。

由于计算器需要频繁更新数字,我采用“局部刷新”策略:只在结果区域的矩形范围内刷新。比如结果区是屏幕的第5行到第7行,页地址0x05~0x07,列地址0x20~0x5F。每次刷新只写这3页数据,速度显著提升,肉眼看不到闪烁。

代码实现中需要注意I2C通信的超时处理。如果总线挂死,read/ write函数会一直卡住。用HAL库时,将I2C通信的超时设置为10ms,超过就重启I2C外设。

4. 实操过程与核心环节实现

4.1 源码目录结构与工程配置

拿到工程源码后,先看清目录结构,大致如下:

STM32_Calculator/ |-- Core/ | |-- Inc/ // 头文件 | |-- Src/ // 主函数、中断 |-- Drivers/ | |-- BSP/ // OLED、MatrixKey、Systick | |-- CMSIS/ |-- MDK-ARM/ // Keil工程文件 |-- Doc/ // 说明文档

工程基于标准外设库(Standard Peripheral Library)3.5版本,如果你不习惯,可以改到HAL库。源码里所有的驱动都放在BSP层,硬件相关的实现与上层逻辑隔离。这样换硬件平台时,只需要改BSP,不需要动核心计算逻辑。

在Keil MDK中打开工程前,先确认自己安装了对应芯片的Device Family Pack,比如Keil.STM32F1xx_DFP。工程选项里,Target标签页的晶振频率要填8.0MHz,后面代码里延时和波特率都依赖这个配置。如果是用内部时钟(HSI),需要修改SystemInit中的宏,这点容易忽略。

还有一点,如果使用V6编译器,标准外设库的很多代码会报warning甚至error。建议选择AC5编译器,兼容性最好。如果你坚持用AC6,需要关闭“非标准语法”等编译选项,工程里我已经配置好了,但其他项目不一定。

4.2 编译环境与烧录流程

Windows下推荐直接装Keil MDK5,安装包比较庞大,但一次性配置好。安装完成后,需要安装STM32F1系列支持包。打开Pack Installer,搜索F1,点击Install即可。

打开工程后,先编译。如果一切正常,会在MDK-ARM目录下生成Calculator.hex。如果没有生成,检查Output标签页是否勾选了Create HEX File。

烧录有两种方式:ST-Link和USB转串口ISP。ST-Link需要接线:SWDIO接PA13,SWCLK接PA14,GND接GND,3.3V接电源。然后打开ST-Link Utility,连接后点击Programming按钮,选择刚编译的hex文件,地址默认0x08000000。烧录成功后,拔掉ST-Link,按一下复位按键,计算器就开始运行。

如果你用ST-Link仿真,还可以在线查看calc结构体的值,跟踪状态机的变化,这是调试逻辑问题的强大利器。我强烈建议在开发过程中使用仿真,而不是盲目烧录看显示结果。

4.3 核心代码详解:按键事件与计算流程

下面截取一段核心的ProcessKey处理代码,方便大家理解整体流程:

void ProcessKey(uint8_t key) { if (key >= '0' && key <= '9') { if (calc.state == CALC_DONE) { // 等号后按数字,重新开始新运算 ResetCalc(); calc.state = INPUT_A; } // 追加数字到当前输入 AppendDigit(key - '0'); UpdateDisplay(); } else if (key == '+' || key == '-' || key == '*' || key == '/') { if (calc.state == ERROR) return; if (calc.state == OP_WAIT) { // 当前已有一个运算符,替换之 calc.op = key; } else { // 先执行上一次运算 CalcResult(); calc.op = key; calc.state = OP_WAIT; } // 清空当前输入,准备第二个操作数 calc.current_input = 0; calc.decimal_place = 0; UpdateDisplay(); } // 其他按键处理类似 }

AppendDigit函数负责把数字追加到current_input上,同时处理小数位:

void AppendDigit(uint8_t digit) { if (calc.decimal_place == 0) { calc.current_input = calc.current_input * 10 + digit; } else { double factor = pow(10, calc.decimal_place); calc.current_input += digit / factor; calc.decimal_place++; } }

CalcResult函数根据运算符执行运算:

void CalcResult(void) { switch(calc.op) { case '+': calc.accumulator += calc.current_input; break; case '-': calc.accumulator -= calc.current_input; break; case '*': calc.accumulator *= calc.current_input; break; case '/': if (calc.current_input == 0) { calc.state = ERROR; SetDisplayString("Error"); return; } calc.accumulator /= calc.current_input; break; default: break; } // 结果保存到累加器 calc.current_input = calc.accumulator; FormatResult(calc.accumulator, calc.display); }

这里有个关键点:计算完成后把累积器值赋给current_input,这样“按等号后再按数字键”就能从结果继续输入。如果不这样做,新输入会把结果清掉。

4.4 仿真与验证用例

编译烧录后,建议按下面的测试用例逐一验证:

操作序列期望显示说明
12 + 34 =46简单加法
12 + 34 * 5 =230或(严格优先级170)取决于模式
100 / 7 =14.2857浮点除法截断
1 / 0 =Error除数为0
3 . 14 =3.14小数输入
5 + =5等号无操作
12 + 34 C0清除
8 - 2 = 6 + 3 =9等号后连续运算

注意,严格优先级模式需要打开源码中的USE_PRECEDENCE宏,并重新编译。两种模式在源码包中都预置了测试用例。

仿真调试时,建议在ProcessKey函数设置断点,观察每一次按键后calc结构体的状态变化。逐个比对状态字段是否按预期跳转。如果发现state在某个按键后跳到了意外值,通常是某个分支缺少else,或者未在分支末尾更新state。这种问题用仿真很容易定位。

5. 常见问题与排查技巧实录

5.1 按键失灵或误触发的处理

按键失灵最常见的原因是引脚配置错误。检查GPIO初始化代码,确保行线是上拉输入,列线是推挽输出。其次要检查扫描函数中列电平的翻转顺序,例如扫描完第0列后,是否把第0列恢复高电平,再拉低第1列。否则两个列同时为低,按键会发生串键。

误触发多半是电平不稳定,或者扫描间隔太短。把扫描周期改为10ms,并在扫描到按键后延时20ms再确认一次,左右两边的抖动都能滤掉。如果在按键扫描期间发生串口中断,中断里如果执行了延时函数,也会导致扫描周期不固定,可以在进入扫描前关中断,扫描完再开。

5.2 显示乱码、花屏的排查

OLED显示乱码通常有两种原因:I2C地址错误或SCL/SDA接反。先检查代码中的OLED_ADDR是否等于模块实际地址。然后用逻辑分析仪抓I2C波形,看设备地址后是否有ACK。如果没有ACK,说明模块地址不对或外部上拉缺失。

还有一种情况是字模取模方式不匹配。我用的字模是纵向取模,高位在前,如果你换了一个字模软件,取模方式可能变成了横向取模,就会显示乱码。解决方法是统一使用Standard模式,纵向取模,8位为一行,宽度倍数为16。计算器数字使用的字模我放在了oled_font.c中,可以直接复用。

5.3 运算结果不对的常见原因

排除硬件问题后,运算结果错误一般出在状态解析上。例如,输入"10+20="得到30,但输入"10+="却得到20,那是因为执行运算时把current_input当成了0,但等号处理逻辑里没有初始化current_input。正确的思路是按下等号时,如果当前没有第二操作数,应判断为无效操作,或者复用当前显示结果。

另外,计算中途更改运算符也要注意。比如输入"10+",又按下"",用户期望是"10",而不是"10+*"。我在处理中把新运算符直接赋给calc.op,同时保持accumulator不变,这样显示区不会出现多余的加号。如果此时显示逻辑仍然把旧运算符打印出来,用户会困惑。建议显示区只显示当前部分,或者显示"表达式提示行"。

浮点数精度也是常见坑。比如"0.1+0.2"可能得到0.30000000000000004。我在FormatResult函数里对结果做了近似处理:如果误差小于1e-6,调整到合法小数位。具体做法是将结果加上0.0000005后取小数点后6位,再去掉尾部0。这个补丁能消除绝大部分浮点误差问题。

5.4 扩展建议:升级为科学计算器

运行起来之后,可以往这几个方向扩展:

  • 增加括号和复杂表达式解析:使用中缀表达式转后缀表达式,实现带优先级的完整计算
  • 增加32位浮点或64位浮点模式切换,甚至实现大数运算(用数组模拟十进制数)
  • 增加历史记录功能:用EEPROM或Flash存储最近20条运算记录
  • 增加键盘背光或声音反馈:用无源蜂鸣器播放按键音,效果会好很多
  • 更换更大屏幕,比如1.3寸OLED或2.4寸TFT,显示更多信息
  • 增加低功耗模式:在无操作一段时间后进入STOP模式,按键唤醒

源码中有一个扩展接口:Calc_ExecuteWithExpression(char* expr),预留了字符串表达式运算入口。如果后续接上位机或蓝牙,可以通过串口直接把表达式发过来计算,而不需要按键逐个输入。这个接口目前没有完全实现,但给你留好了位置。

6. 源码使用与后续学习建议

如果你下载了这个工程源码,建议不要只停留在烧录成功。把源代码完整读一遍,特别是BSP驱动的写法,然后尝试自己重写一遍Calc逻辑。重写时可以先不接OLED和矩阵键盘,用串口调试助手输入按键字符,观察计算器逻辑是否正确。这个“无硬件调试法”能省下很多焊接和接线的时间。

我实际测试时,用串口发送字符 '1','2','+','3','4','=' 就能得到计算结果。如果你也想用串口调试,只需要在串口中断回调里把接收到的字符代换成ProcessKey即可。

最后说一个个人经验:做计算器这类交互系统,工程上的难点不是计算本身,而是输入输出时序的配合。按键扫描、显示刷新、运算逻辑三者的调度关系,远比算法本身有趣。你把这三个模块捋顺之后,再做菜单系统、波形显示、甚至简单游戏,都会顺手很多。

这套源码里的BSP驱动是通用的,矩阵键盘扫描和OLED驱动copy到其他项目也能直接用。用STM32做项目,最重要的就是攒一套自己的底层驱动库,以后换项目就是拼积木。计算器这个项目正好帮你把这套积木的首批组件打磨出来。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/31 15:57:08

鸿蒙 AGC:配置公钥指纹

公钥指纹是应用签名证书的摘要信息&#xff0c;用于校验应用的真实性。如应用当前集成的华为相关服务&#xff08;如华为账号服务&#xff09;依赖公钥指纹&#xff0c;需要在应用调试或上架前将公钥指纹配置到AppGallery Connect。 一、公钥指纹 公钥指纹是应用签名证书&…

作者头像 李华
网站建设 2026/8/31 15:56:07

2026AI论文工具排行榜[特殊字符]真实实测排名|避坑不踩雷

无广纯实测&#xff5c;2026双审适配&#xff5c;本硕通用&#xff5c;硬核横向对比✅ 毕业季全网五花八门的AI论文工具太多了&#xff01;免费的容易翻车、付费的性价比低、通用AI学术性差、专业工具功能单一。 为了帮大家精准避雷、按需选工具&#xff0c;我实测全网10主流…

作者头像 李华
网站建设 2026/8/31 15:54:29

美团秋招真题解析:滑动窗口、动态规划与搜索题陷阱

写这篇东西前先说说背景。我刷题这么多年&#xff0c;围观过不少校招笔试&#xff0c;也陪人复盘过美团秋招的真实考题。2019年美团秋招的编程题&#xff0c;放在今天看&#xff0c;风格依然典型&#xff1a;不考偏题怪题&#xff0c;不堆砌冷门数据结构&#xff0c;但特别考验…

作者头像 李华
网站建设 2026/8/31 15:53:54

基于STM32的超声波风速风向测量仪:从硬件到算法的完整工程复盘

简介&#xff1a;这是一套面向嵌入式初学者与课程设计者的STM32超声波风速风向测量仪完整开发资料&#xff0c;解决气象参数低成本、非接触式实时采集的技术实现问题&#xff0c;适用于电子设计竞赛、毕业设计及物联网传感节点开发场景。资源包共405个文件&#xff0c;涵盖114个…

作者头像 李华
网站建设 2026/8/31 15:53:27

Hypermesh二次开发入门:从录制宏到流程自动化

打开 Hypermesh 的界面&#xff0c;如果你只是一个普通使用者&#xff0c;你可能会觉得它是一个建模和网格划分工具&#xff1b;但如果你被同一个重复操作折磨过几次&#xff0c;比如一连十几个模型&#xff0c;每个都要反复设置材料、检查网格质量、调整单元法向&#xff0c;你…

作者头像 李华
网站建设 2026/8/31 15:50:02

HTTP——服务端、客户端格式,浏览器解析

bit::Shadow✧(≖ ◡ ≖✿ 目录 浏览器作为客户端&#xff0c;我的代码作为服务端。 抓取浏览器的HTTP格式的请求 请求行格式 请求行 请求头 空行 正文 请求方&#xff08;Request&#xff09; POST举例Login.html 见资源绑定&#xff08;测试&#xff09; 服务端应…

作者头像 李华