ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于STM32F103C8T6的矩阵键盘OLED计算器设计

基于STM32F103C8T6的矩阵键盘OLED计算器设计 简介基于STM32F103C8T6的矩阵键盘计算器项目适合学习STM32外设开发与嵌入式人机交互的初学者和课程设计者。项目完整实现矩阵键盘扫描、OLED显示与基本算术运算涉及GPIO、中断、定时器、I2C/SPI等典型外设可作为毕业设计或电子竞赛的参考方案。压缩包共192个文件以C源码、头文件、Object文件为主同时包含uvprojx工程配置、axf/hex烧录文件及map、lst等编译辅助文件总大小7.36MB工程结构完整可直接用Keil打开编译。已有637人学习下载。通过这份资源可获得可直接运行的工程源码、键盘识别算法、OLED显示驱动以及计算器主流程框架其中d、o、crf等中间文件便于对比编译过程map文件可辅助分析内存占用适合对照代码逐模块学习快速上手STM32嵌入式项目开发。 去年整理工作台的时候翻出一块吃灰的stm32f103c8t6最小系统板正好手边还有一个4x4矩阵键盘和一块0.96寸OLED就花了半天时间把计算器这个小项目做了出来。别看计算器听起来简单从矩阵键盘扫描、按键消抖、数值状态切换到OLED显示整套流程几乎把嵌入式开发最常用的外设操作都过了一遍。这篇文章就把完整的硬件接线、软件思路和代码都整理出来适合刚学完GPIO中断、想动手做点完整小项目的朋友参考。做这个项目最大的好处是模块少、逻辑清晰不涉及复杂通信协议踩坑点也集中。你不需要一上来就看寄存器手册直接用HAL库就能把整个流程跑通。只要有一个stm32f103c8t6板子、一个矩阵键盘模块、一块OLED屏幕就够了成本很低。下面我从设计思路开始讲再逐步落到接线和代码。1. 项目概述与整体设计思路1.1 为什么选择stm32f103c8t6做计算器stm32f103c8t6是STM32F1系列里的经典型号64KB Flash、20KB RAM主频最高72MHz。做计算器这种项目资源完全是溢出的但正因为这款芯片资料多、玩的人多遇到问题随便一搜就有解决方案特别适合作为入门项目。更重要的是这芯片的GPIO资源足够富余。计算器需要12到16个按键如果用独立按键方案每个按键都要单独接一个IO加上OLED和必要的调试接口引脚会非常紧张。而stm32f103c8t6有37个GPIO看似够用但实际布局时还要考虑SWD调试口、晶振引脚、复位引脚真正方便用的普通IO并没有想象中那么多。用矩阵键盘方案后16个按键只需要8个GPIO省出来的引脚可以接OLED、接串口、甚至接个蜂鸣器后续想扩展也方便。我选的这块板子是市面最常见的蓝色最小系统板板载8MHz晶振和AM1117稳压芯片USB口供电ST-Link四根线就能下载调试。整板也就十来块钱用起来不心疼。1.2 矩阵键盘方案代替独立按键的优势矩阵键盘的核心思想是用行列交叉的方式压缩IO数量。4x4矩阵键盘有16个按键但只需要8个引脚4行4列。每个按键本质上是把对应行和对应列短接MCU通过扫描行列状态来判断哪个按键被按下。相比独立按键矩阵键盘的缺点是要做扫描代码会稍微复杂一点但换来的是IO数量减半和更整齐的布局。对这个项目来说矩阵键盘还有一个额外好处扫描过程本身就是一种很典型的外设读取练习做完这个项目你对GPIO输入输出、上拉下拉、延时消抖、状态机这些基础概念都会有一个更直观的理解。按键布局我建议做成下面这种行\列第1列第2列第3列第4列第1行123第2行456-第3行789*第4行C0/这样16个按键刚好覆盖数字0到9、四则运算符、等号和清零不需要额外的复位按钮。如果你需要小数点可以考虑把“”挪到别的位置或者干脆用5x5键盘但作为入门项目先实现整数四则运算已经能覆盖核心知识点了。2. 硬件设计与接线配置2.1 硬件清单和接线表这次项目用到的硬件非常少我列一下stm32f103c8t6最小系统板一块4x4矩阵键盘模块一个带排针那种最方便0.96寸OLED屏幕一块SSD1306驱动芯片I2C接口ST-Link下载器一个杜邦线若干面包板可选具体的引脚分配我做了这样一个表格全程避开了PA13、PA14这两个SWD调试口避免出现“代码下不进去”的尴尬设备功能接STM32引脚矩阵键盘行0/行1/行2/行3PA0/PA1/PA2/PA3矩阵键盘列0/列1/列2/列3PA4/PA5/PA6/PA7OLEDSDAPB6OLEDSCLPB7OLEDVCC/GND3.3V/GND矩阵键盘模块本身不需要供电它只是排针接口直接和MCU的GPIO相连。OLED屏幕需要3.3V供电注意不要接到5V否则时间长了有可能损坏模块。2.2 矩阵键盘扫描原理和GPIO配置矩阵键盘扫描原理可以用一句话概括让每一行单独输出低电平同时把每一列设置为上拉输入如果某一行上的按键被按下对应列就会被拉低MCU读到低电平就判断出按键位置。我之前见过有人把行和列都设置为推挽输出然后靠读输出寄存器来判断这种做法虽然也能工作但容易受到外部干扰而且代码可读性差。更规范的做法是行引脚推挽输出默认输出高电平扫描某一行时把该行拉低列引脚上拉输入按键没按下时读到高电平按下时读到低电平GPIO配置代码用HAL库写很简洁GPIO_InitTypeDef GPIO_InitStruct {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); // 行PA0-PA3推挽输出 GPIO_InitStruct.Pin GPIO_PIN_0 | GPIO_PIN_1 | GPIO_PIN_2 | GPIO_PIN_3; GPIO_InitStruct.Mode GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull GPIO_NOPULL; GPIO_InitStruct.Speed GPIO_SPEED_FREQ_LOW; HAL_GPIO_Init(GPIOA, GPIO_InitStruct); // 列PA4-PA7上拉输入 GPIO_InitStruct.Pin GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_6 | GPIO_PIN_7; GPIO_InitStruct.Mode GPIO_MODE_INPUT; GPIO_InitStruct.Pull GPIO_PULLUP; HAL_GPIO_Init(GPIOA, GPIO_InitStruct);注意列引脚一定要配置成GPIO_PULLUP如果不带上拉按键在悬空状态下读到的是不确定电平会出现乱跳的情况。内部上拉电阻大约30到50kΩ推挽输出低电平时的驱动能力足够把列拉低这个组合在F103上实测非常稳定。3. 软件设计与核心代码实现3.1 矩阵键盘扫描函数和键值映射矩阵键盘扫描最常见的方式是逐行扫描。先把第0行拉低然后依次读取4个列引脚。如果某一列读到低电平说明第0行和这一列交叉处的按键被按下。扫描完第0行后把该行恢复高电平再把第1行拉低重复这个过程。代码写出来是这样#define KEY_ROW_PORT GPIOA #define KEY_COL_PORT GPIOA const char keyMap[4][4] { {1, 2, 3, }, {4, 5, 6, -}, {7, 8, 9, *}, {C, 0, , /} }; uint16_t rowPins[4] {GPIO_PIN_0, GPIO_PIN_1, GPIO_PIN_2, GPIO_PIN_3}; uint16_t colPins[4] {GPIO_PIN_4, GPIO_PIN_5, GPIO_PIN_6, GPIO_PIN_7}; char scanMatrixKey(void) { for (int row 0; row 4; row) { // 把当前行拉低 HAL_GPIO_WritePin(KEY_ROW_PORT, rowPins[row], GPIO_PIN_RESET); HAL_Delay(5); for (int col 0; col 4; col) { if (HAL_GPIO_ReadPin(KEY_COL_PORT, colPins[col]) GPIO_PIN_RESET) { // 检测到按键延时消抖 HAL_Delay(20); // 等待按键释放避免一次按下触发多次 while (HAL_GPIO_ReadPin(KEY_COL_PORT, colPins[col]) GPIO_PIN_RESET); HAL_GPIO_WritePin(KEY_ROW_PORT, rowPins[row], GPIO_PIN_SET); return keyMap[row][col]; } } // 恢复当前行为高电平再扫描下一行 HAL_GPIO_WritePin(KEY_ROW_PORT, rowPins[row], GPIO_PIN_SET); } return 0; }这里有几个细节值得说明。第一每次扫描完一行必须把该行恢复成高电平再去拉低下一行。如果前一行还处于低电平就扫描下一行两行同时为低按键有可能会被错误触发。第二按键释放检测我用的是死等方式在简单计算器这种单任务场景下没有任何问题代码简单直观。如果以后要做RTOS或者多任务建议改成状态机方式检测到按下后先记录状态等主循环再次扫描时再确认释放。3.2 计算器逻辑用状态机管理输入流程计算器的核心逻辑其实不复杂但很容易写乱。我建议用一个简单的状态机来管理当前输入阶段输入第一个操作数输入运算符输入第二个操作数按下等号计算结果对应代码可以拆成几个部分先用缓冲区存当前输入的数字typedef enum { STATE_INPUT_FIRST, STATE_INPUT_SECOND } CalcState; char inputBuf[16] {0}; uint8_t inputLen 0; double firstOperand 0; char operatorChar 0; CalcState state STATE_INPUT_FIRST;数字键的处理很直接往缓冲区里追加字符同时更新显示if (key 0 key 9) { if (inputLen 12) { inputBuf[inputLen] key; inputBuf[inputLen] \0; } OLED_ShowString(0, 0, inputBuf); }按下运算符时把当前缓冲区里的数字保存成第一个操作数然后清空缓冲区准备输入第二个操作数if (key || key - || key * || key /) { firstOperand atof(inputBuf); operatorChar key; inputLen 0; inputBuf[0] \0; state STATE_INPUT_SECOND; }等号按键则负责结算if (key ) { double secondOperand atof(inputBuf); double result 0; switch (operatorChar) { case : result firstOperand secondOperand; break; case -: result firstOperand - secondOperand; break; case *: result firstOperand * secondOperand; break; case /: if (secondOperand ! 0) result firstOperand / secondOperand; else { OLED_ShowString(0, 0, Error); inputLen 0; inputBuf[0] \0; return; } break; } sprintf(inputBuf, %g, result); OLED_ShowString(0, 0, inputBuf); inputLen strlen(inputBuf); state STATE_INPUT_FIRST; }这里用atof和sprintf来处理数字转换会自动处理多位整数和负数的情况省去自己写字符串转数字的麻烦。使用Keil MDK时建议在工程选项里开启MicroLib否则sprintf这种库函数可能会导致代码体积偏大甚至出现硬错误。%g格式符会自动去掉小数点后面多余的0显示结果比较干净。3.3 OLED显示和主循环OLED我选的是SSD1306驱动、I2C接口的0.96寸屏逻辑简单网上驱动代码也很多。接线就是PB6接SDA、PB7接SCL供电3.3V。初始化完成之后主要用两个函数清屏和显示字符串。主循环非常直接int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); OLED_Init(); OLED_Clear(); while (1) { char key scanMatrixKey(); if (key ! 0) { calc_handleKey(key); } HAL_Delay(10); } }OLED驱动需要注意两个点。第一I2C地址通常是0x3C也有少量模块是0x3D确认一下驱动代码里的地址和你的模块一致。第二有些驱动代码用硬件I2C有些用软件模拟I2C。我个人更推荐用软件模拟I2C因为可以随意选择引脚而且只要把SCL和SDA配置成推挽输出时序正确就能稳定工作不用去查硬件I2C的各种标志位。4. 常见问题与排查技巧实录4.1 按键无反应、重复触发和串键按键问题是矩阵键盘里最容易出问题的环节。我整理了一张排查表基本上覆盖了常见情况现象可能原因解决办法所有按键都没反应行或列GPIO配置错误或者接线没共地检查GPIO模式行必须是输出列必须是上拉输入某些按键没反应对应行或列杜邦线接触不良重新插拔用万用表量通断按下一次触发很多次消抖不充分或者没有等待释放增加20ms延时增加释放检测按键串键按一个键出现另一个键扫描完一行没恢复高电平或行列引脚之间有错误导通确保每次扫描完当前行后恢复高电平检查模块排针有没有虚焊刚上电不按按键也乱跳列引脚没有配置上拉确认列引脚配置为GPIO_PULLUP另外要提醒一个很隐蔽的坑STM32F103C8T6上的PA11和PA12是USB引脚PA13和PA14是SWD调试引脚PA15、PB3、PB4默认是JTAG引脚。如果你把这些引脚当作普通GPIO使用默认状态下是异常的程序运行起来行为会很奇怪。我这次选PA0到PA7就是特意避开了这些特殊引脚。4.2 结果显示异常、除零和边界情况用atof把字符串转成数字后计算逻辑本身很简单但你一定会遇到几个边界问题。第一个是除零。如果不做判断计算结果会变成inf显示出来就是一串乱码所以在除法分支里必须判断第二个操作数是否为0。第二种情况是连续按运算符比如先按“1”再按“”又按“-”。我的处理方式是直接把运算符覆盖掉这种交互虽然不够完善但不会崩溃对入门项目足够了。还有一个常见问题是按等号时缓冲区是空的atof()会返回0导致计算结果错误。解决方式也很简单在等号分支里先判断inputLen 0如果是就直接返回不执行计算。如果你发现sprintf出来的结果在OLED上显示不完整大概率是缓冲区长度不够。我用的是char inputBuf[16]显示两位数乘法结果绰绰有余但如果后续扩展成科学计数法建议把缓冲区加到32。4.3 程序下载失败的常见坑很多人做到一半会遇到下载不了程序原因五花八门。最常见的是PA13、PA14被复用成普通GPIO后SWD调试接口失效。尽量避免把这两个引脚用在你的设计里。还有一种情况是复位电路和BOOT0跳线帽没设置对下载时要把BOOT0拉低否则芯片会进入系统存储器模式ST-Link连不上。我实测ST-Link连接到SWDIO、SWCLK、GND、3.3V四个引脚就可以下载。如果出现连接失败先检查BOOT0是不是低电平再检查ST-Link驱动是否正常这两个原因占了九成。5. 实测效果与后续扩展5.1 实际运行效果和个人心得做完之后我把整个流程完整跑了一遍。按顺序输入“1234”OLED上正常显示“46”除零时显示“Error”按C键清零后重新输入整个过程没有按键乱跳也没有显示闪烁。我最大的感受是这种小项目的难度不在于某一块单独的功能而在于把几个功能串起来时对整体流程的理解。矩阵键盘扫描、状态机管理、字符串转换、OLED驱动每个单独拿出来都不难但组合在一起就非常考验代码组织能力。建议第一次做的朋友先把矩阵键盘单独调通用串口把按键键值打印出来确认16个按键都能稳定识别再开始写计算逻辑。如果一上来就全部写完再去调试出了问题根本不知道是扫描问题还是计算问题。5.2 可以继续扩展的方向虽然项目已经可以正常使用了但其实还有很多可以改进的地方。最简单的是加上小数点和负数支持只需要在数字键处理里增加对.和-的判定。想继续深入的话可以做完整的表达式解析比如支持“123*5”一次性计算这种就需要用到中缀表达式转后缀表达式、用栈保存运算符和操作数价值比现在的两操作数版本高不少。硬件上也可以继续扩展。OLED可以换成LCD1602显示区域更大还能显示历史记录。矩阵键盘可以换成带背光的版本外观更完整。如果嫌杜邦线不稳定可以画一块PCB把所有模块集中到一个板子上做成一个真正能拿在手里的计算器。但这些都属于锦上添花核心的矩阵键盘扫描和计算逻辑是不变的先把这套基础跑通后面怎么改都不慌。本文还有配套的精品资源点击获取
返回列表