1. 项目概述与设计意义
在零售行业中,收银机是最基础也是最核心的交易终端设备。从大型超市到街边小店,几乎每个商业场所都需要一个可靠的收银系统来完成商品交易。虽然现代商用收银机集成了条码扫描、小票打印、库存管理等多种复杂功能,但其核心交易逻辑始终不变:输入商品单价和数量,计算金额并累加,最后完成结算。
这个基于单片机的智能收银机模拟系统,旨在用最简单的硬件实现一个完整的收银流程。它采用51系列单片机作为控制核心,配合矩阵键盘和多位数码管,完美模拟了真实收银机的核心功能。这个项目特别适合作为电子类专业学生的课程设计或毕业设计选题,因为它涵盖了单片机开发的多个关键技术点:
- 矩阵键盘扫描与消抖处理
- 数码管动态显示控制
- 状态机程序设计
- 数值运算与数据处理
- 人机交互逻辑设计
提示:这个项目虽然硬件简单,但软件设计上需要考虑很多细节问题,比如输入数字的逐位左移显示、不同状态下的按键响应逻辑等。这些细节处理能力正是区分优秀工程师的关键。
2. 系统功能设计
2.1 核心功能需求解析
2.1.1 上电初始状态
系统通电后进入待机状态,此时:
- 最右侧数码管显示"0"
- 其他数码管全部熄灭
- 系统准备接收第一个商品的单价输入
这种显示方式比全显示"000000"更符合真实收银机的待机习惯,也更能体现专业设计的细节考量。
2.1.2 单价输入流程
当用户开始输入商品单价时:
- 第一个数字输入会替换最右位的"0"
- 后续每个新数字输入都会使已有数字左移一位
- 新输入的数字始终显示在最右位
这实际上实现了一个移位寄存器的功能,既符合用户输入习惯,又能直观显示当前输入的数值。
2.1.3 数量输入流程
单价输入完成后按"×"键:
- 单价显示保持不动
- 系统进入等待数量输入状态
- 当输入第一个数量数字时:
- 单价显示立即熄灭
- 开始显示数量值(同样采用逐位左移方式)
这种设计既避免了用户混淆单价和数量,又能清晰指示当前输入的是哪个参数。
2.1.4 金额计算与累加
数量输入完成后按"十"键:
- 计算当前商品金额:单价 × 数量
- 将该金额累加到总金额中
- 数码管显示最新的累计总金额
- 清空当前商品的单价和数量缓存,准备下一笔输入
2.1.5 结算流程
所有商品输入完成后按"="键:
- 显示最终应收总金额
- 进入结算锁定状态,不再接收新的商品输入
- 只有按复位键后才能开始下一位顾客的交易
2.2 数据表示设计
在真实的收银系统中,金额通常需要处理小数(元角分)。但在这个教学用模拟系统中,我们做了以下简化设计:
- 所有金额以"分"为单位存储和计算
- 显示时直接显示整数(如1234表示12.34元)
- 单价和数量都限制为整数输入
- 总金额使用32位无符号长整型存储,最大可表示42949672.95元
这种设计既保证了计算精度,又简化了程序设计,特别适合教学演示。在实际应用中,可以进一步扩展支持小数点输入和显示功能。
2.3 系统状态机设计
为了清晰管理复杂的交互流程,系统采用状态机设计,定义了6个主要状态:
- ST_INIT:初始状态,最右位显示0
- ST_PRICE_INPUT:单价输入状态
- ST_WAIT_QTY:等待数量输入状态(已按×键)
- ST_QTY_INPUT:数量输入状态
- ST_SHOW_TOTAL:显示累计总金额状态
- ST_DONE:结算锁定状态
状态转换规则如下:
- 初始状态下输入数字 → 单价输入状态
- 单价输入状态下按× → 等待数量状态
- 等待数量状态下输入数字 → 数量输入状态
- 数量输入状态下按十 → 显示总金额状态
- 显示总金额状态下输入数字 → 单价输入状态(开始新商品)
- 任何状态下按= → 结算锁定状态
- 任何状态下按复位 → 返回初始状态
3. 硬件系统设计
3.1 总体硬件架构
系统硬件主要由以下模块组成:
- 单片机最小系统:包括STC89C52芯片、晶振电路、复位电路等
- 矩阵键盘模块:4×4矩阵键盘,包含数字键和功能键
- 数码管显示模块:8位共阳数码管,采用动态扫描驱动
- 电源模块:5V稳压电源,为系统提供稳定工作电压
3.2 单片机最小系统
3.2.1 核心功能
单片机承担以下关键任务:
- 定时扫描矩阵键盘,检测按键输入
- 管理数码管动态显示刷新
- 处理数值计算和状态转换
- 实现各种边界条件和异常处理
3.2.2 关键电路设计
- 时钟电路:采用11.0592MHz晶振,这个频率特别适合产生标准的串口波特率
- 复位电路:包含上电复位和手动复位两种方式
- 去耦电容:在单片机电源引脚附近放置0.1μF和10μF电容,确保电源稳定
- IO分配:
- P1口:矩阵键盘行扫描输出
- P2口:数码管段选控制
- P3口:数码管位选控制和功能按键输入
3.3 矩阵键盘设计
3.3.1 按键布局设计
采用4×4矩阵键盘布局,包含:
- 数字键0-9
- 功能键:×(乘法)、十(累加)、=(等号)
- 复位键(独立按键,不在矩阵中)
3.3.2 扫描原理
矩阵键盘扫描采用"行扫描、列检测"的方法:
- 依次将每一行置低电平,其他行置高电平
- 读取列线状态,检测是否有按键按下
- 通过行列组合确定具体按键
3.3.3 消抖处理
机械按键在按下和释放时会产生抖动,可能导致多次误触发。系统采用软件消抖方法:
- 检测到按键按下后,延时20ms再确认按键状态
- 只有确认按键仍然按下,才视为有效按键
- 采用边沿触发方式,确保每个按键动作只处理一次
3.4 数码管显示模块
3.4.1 显示方案选择
系统采用8位共阳数码管,主要考虑:
- 8位足够显示大多数交易金额(最大99999999)
- 共阳数码管驱动简单,可直接用单片机IO口驱动
- 动态扫描方式节省IO资源
3.4.2 动态扫描原理
动态扫描通过分时复用方式驱动多位数码管:
- 每次只点亮一位数码管
- 快速轮流点亮各位(刷新频率>100Hz)
- 利用人眼视觉暂留效应,形成稳定显示效果
3.4.3 驱动电路设计
数码管驱动需要注意:
- 段选限流电阻:220Ω电阻串联在每个段选线上
- 位选驱动:使用PNP三极管增强驱动能力
- 消隐处理:在切换位选时先关闭所有显示,避免重影
4. 软件系统设计
4.1 软件架构概述
系统软件采用"前后台"架构:
- 后台:定时器中断服务程序,负责数码管动态刷新
- 前台:主循环处理键盘扫描和业务逻辑
- 数据层:维护单价、数量、总金额等核心数据
4.2 核心数据结构
4.2.1 状态枚举定义
c复制typedef enum {
ST_INIT = 0, // 初始状态
ST_PRICE_INPUT, // 单价输入状态
ST_WAIT_QTY, // 等待数量输入
ST_QTY_INPUT, // 数量输入状态
ST_SHOW_TOTAL, // 显示总金额
ST_DONE // 结算锁定状态
} SYS_STATE;
4.2.2 全局变量定义
c复制unsigned long price; // 当前商品单价
unsigned long qty; // 当前商品数量
unsigned long total; // 累计总金额
SYS_STATE state; // 当前系统状态
unsigned char disp_buf[8]; // 数码管显示缓冲区
4.3 键盘扫描模块
4.3.1 按键编码定义
c复制typedef enum {
KEY_NONE = 0,
KEY_0, KEY_1, KEY_2, KEY_3, KEY_4,
KEY_5, KEY_6, KEY_7, KEY_8, KEY_9,
KEY_MUL, // ×键
KEY_ADD, // 十键
KEY_EQ, // =键
KEY_RST // 复位键
} KEY_CODE;
4.3.2 键盘扫描实现
键盘扫描函数需要完成以下工作:
- 逐行扫描键盘矩阵
- 检测列线状态,确定按键位置
- 将物理按键映射为逻辑按键编码
- 实现消抖处理,返回稳定的按键事件
4.4 显示驱动模块
4.4.1 数码管段码表
c复制// 共阳数码管段码表(0-9)
const unsigned char SegTable[10] = {
0xC0, // 0
0xF9, // 1
0xA4, // 2
0xB0, // 3
0x99, // 4
0x92, // 5
0x82, // 6
0xF8, // 7
0x80, // 8
0x90 // 9
};
4.4.2 动态扫描中断服务程序
c复制void Timer0_ISR() interrupt 1 {
static unsigned char pos = 0;
// 关闭所有位选
DIG_ALL_OFF();
// 输出当前位的段码
if(disp_buf[pos] == 0xFF) {
SEG_PORT = 0xFF; // 全灭
} else {
SEG_PORT = SegTable[disp_buf[pos]];
}
// 打开当前位选
DIG_ON(pos);
// 更新位选位置
pos = (pos + 1) % 8;
// 重装定时器初值
TH0 = 0xFC;
TL0 = 0x66;
}
4.5 业务逻辑实现
4.5.1 单价输入处理
c复制void HandlePriceInput(unsigned char digit) {
// 数值计算:保留最后8位
price = (price % 10000000) * 10 + digit;
// 显示处理:左移显示
for(int i=0; i<7; i++) {
disp_buf[i] = disp_buf[i+1];
}
disp_buf[7] = digit;
// 更新状态
state = ST_PRICE_INPUT;
}
4.5.2 数量输入处理
c复制void HandleQtyInput(unsigned char digit) {
if(state == ST_WAIT_QTY) {
// 第一次输入数量,清空显示
memset(disp_buf, 0xFF, 8);
qty = 0;
state = ST_QTY_INPUT;
}
// 数值计算
qty = (qty % 10000000) * 10 + digit;
// 显示处理
for(int i=0; i<7; i++) {
disp_buf[i] = disp_buf[i+1];
}
disp_buf[7] = digit;
}
4.5.3 金额计算与累加
c复制void CalculateAndAccumulate() {
unsigned long amount = price * qty;
total += amount;
// 显示总金额
DisplayNumber(total);
// 清空当前商品数据
price = 0;
qty = 0;
// 更新状态
state = ST_SHOW_TOTAL;
}
4.6 主程序流程
c复制void main() {
System_Init(); // 系统初始化
while(1) {
KEY_CODE key = Key_Scan(); // 扫描键盘
if(key != KEY_NONE) {
KeyEvent_Handle(key); // 处理按键事件
}
}
}
5. 系统优化与扩展
5.1 输入容错处理
在实际使用中,需要考虑各种异常情况:
- 输入溢出处理:当输入位数超过8位时,自动丢弃最早输入的数字
- 无效操作处理:如在单价输入状态按"十"键,系统应忽略无效操作
- 零数量处理:如果数量为零,累加时应跳过计算
5.2 人机交互增强
虽然基础功能已经完整,但还可以增加以下增强功能:
- 声音反馈:每次按键时发出短促"滴"声,提高操作确认感
- 错误提示:对于无效操作,可以用特定声音或显示方式提示
- 亮度调节:根据环境光线自动或手动调节数码管亮度
5.3 功能扩展方向
这个基础系统可以进一步扩展为更完整的收银系统:
- 商品数据库:增加EEPROM存储常用商品信息
- 条码扫描:增加条码扫描接口,支持商品自动识别
- 小票打印:增加热敏打印机接口,实现小票打印功能
- 数据统计:增加日销售统计、商品销售排行等功能
6. 开发心得与注意事项
在实际开发过程中,我总结了以下几点重要经验:
- 状态机设计是关键:清晰的状
