1 基于单片机的智能收银机模拟系统设计
点击链接下载prrotues仿真设计资料:https://download.csdn.net/download/m0_51061483/92081484
1.1 系统概述与设计意义
收银机是零售场景中最常见的交易终端之一,超市、菜市场、便利店、文具店乃至小型摊位,都需要一个简单可靠的"输入单价---输入数量---计算金额---累加总价---结算显示"的工作流程。真实商用收银机通常包含扫描枪、热敏打印机、联网与后台管理等复杂功能,但其核心交易逻辑仍然是:对每一笔商品按"单价×数量"得到金额,再把金额累计到总金额中,最后按"结算/总计"显示应收金额。
本设计以单片机为核心,模拟实现一台"简化版智能收银机"。系统通过矩阵键盘输入数字与运算符号(×、十、=、复位),使用多位数码管进行动态显示。它严格遵循题目描述的操作习惯:上电初始显示最右一位为0;输入单价时数字逐位左移;按"×"后输入数量,单价显示熄灭并显示数量;按"十"计算该商品金额并累加到总金额中,同时显示最新累计总金额;按过"十"后再输入下一笔单价时清除总金额显示并进入新单价输入;按"="显示最终累计总金额并结束本顾客交易;按复位键返回初始状态准备下一位顾客。
该系统综合涉及单片机的典型应用能力:
- 矩阵键盘扫描与消抖:实现数字与功能键输入。
- 多位数码管动态扫描显示:实现逐位移位显示、熄灭控制、显示切换。
- 定点数或整数运算:实现单价×数量、金额累加、总金额显示。
- 状态机控制逻辑:实现不同输入阶段之间的切换规则。
- 异常与边界处理:例如输入位数溢出、金额超范围、重复按键等。
2 系统功能设计
2.1 功能需求逐条解析
依据题目描述,收银机模拟系统应实现以下功能与行为规则。
2.1.1 上电初始显示
-
系统通电后进入初始状态:
- 最右侧数码管显示"0"
- 其它数码管熄灭(不显示任何数字)
-
表示系统已准备接收商品单价输入。
此处"其它数码管不亮"意味着初始显示不是"000000",而是只有一位"0",更符合真实设备的待机显示习惯。
2.1.2 输入商品单价(逐位左移显示)
-
用户开始键入商品单价(仅输入数字)。
-
当输入第一个数字时:最右位显示由"0"变为该数字。
-
之后每输入一个新数字:
- 旧数字整体向左移动一位
- 最右位显示新输入的数字
-
单价输入过程中数码管始终显示"当前输入的单价"。
这一规则本质上是一个"输入移位寄存器":每次输入把显示数组左移并把新数塞到末尾,同时单价数值也应同步更新(或由显示数组反算得到)。
2.1.3 按"×"后输入数量(切换显示为数量)
-
用户输入完单价后按"×"键,表示接下来输入数量。
-
按"×"后:单价显示暂时保持不变,系统进入"等待数量输入"状态。
-
当用户开始输入数量的第一个数字时:
- 数码管立即熄灭单价显示
- 转为逐位显示商品数量(同样按逐位左移规则输入)
-
输入数量过程中显示的是"当前输入数量"。
该流程强调"按×后单价先不变,但数量一旦开始输入,单价立刻消失",这是一种典型收银机的阶段输入体验:避免用户误以为单价被覆盖,但又保证输入数量时显示明确。
2.1.4 按"十"键计算金额并累加总金额
-
当数量输入完成后按"十"键(可理解为"+"或"累计"键):
- 计算最近一笔商品金额 = 单价 × 数量
- 将该金额累加到此前累计总金额中
- 数码管显示最新累计总金额
-
若用户连续多次输入商品并按"十",系统应持续累加。
此处"十"键的意义相当于"将本笔商品加入账单并显示累计金额"。
2.1.5 按过"十"后输入下一笔商品单价
-
当显示的是"累计总金额"状态时,用户开始输入下一笔商品单价:
- 刚才显示的总金额消失
- 系统进入"单价输入状态"
- 数码管转为逐位显示新输入的单价
-
之后流程重复:单价→×→数量→十→累计...
这要求系统必须识别"总金额显示状态"与"单价输入状态"的切换触发条件:当处于"显示累计总金额"且检测到数字键输入时,应清屏并开始单价移位输入。
2.1.6 按"="键结算并停止接收下一笔
-
所有操作完成后按"="键:
- 显示最终累计总金额
- 当前顾客累计结束
- 系统不再等待下一笔单价输入(进入结算锁定状态)
-
结算后只允许复位操作,避免误输入继续累加。
2.1.7 复位键返回初始状态
-
一位顾客交易结束后按复位键:
- 清除单价、数量、总金额缓存
- 回到初始显示:最右位"0",其余熄灭
- 准备接待下一位顾客
2.2 数据表示与金额单位设计(工程化说明)
在收银机中,单价通常包含小数(元/角/分)。但题目未明确是否包含小数点输入。为了使系统实现更稳定且符合单片机资源特点,常见做法是使用"定点整数"表示金额,例如:
- 以"分"为最小单位:1元=100分
- 若不实现小数点输入,则输入"1234"可解释为12.34元或1234元取决于设计约定。
在本设计说明中,可采用更常见的课程简化方案:
- 单价、数量、金额均为整数(无小数点),直接显示整数金额。
这样可以把重心放在"移位输入、运算与累加、状态切换"上。若需要扩展小数点,可在后续版本加入"."键与固定小数位显示策略。
2.3 系统状态机规划
为了严格实现题目流程,系统建议采用状态机管理:
ST_INIT:初始待机显示0ST_PRICE_INPUT:单价输入中(逐位左移显示)ST_WAIT_QTY:已按×,等待数量输入第一位(单价仍显示)ST_QTY_INPUT:数量输入中(单价熄灭,显示数量)ST_SHOW_TOTAL:已按十,显示累计总金额,等待下一笔单价或结算ST_DONE:已按=结算锁定,只允许复位
状态切换触发:
- INIT/SHOW_TOTAL → PRICE_INPUT:数字键输入
- PRICE_INPUT → WAIT_QTY:按×
- WAIT_QTY → QTY_INPUT:数量第一位数字输入(触发显示切换)
- QTY_INPUT → SHOW_TOTAL:按十
- SHOW_TOTAL → DONE:按=
- DONE/任意状态 → INIT:复位键
3 系统电路设计
3.1 硬件总体结构
本收银机模拟系统以单片机为核心,主要硬件模块如下:
- 单片机最小系统模块
- 矩阵键盘输入模块(数字键、×、十、=、复位)
- 多位数码管显示模块(动态扫描)
- 电源与稳压模块
- 可选扩展模块(蜂鸣器按键提示、指示灯状态提示、EEPROM掉电保存等)
题目未要求其他外设,因此重点在键盘与数码管的可靠驱动。
3.2 单片机最小系统模块
3.2.1 模块功能
单片机承担:
- 键盘扫描与消抖
- 数码管动态刷新显示
- 单价、数量输入移位处理
- 单价×数量计算与总金额累加
- 状态机切换与结算锁定控制
3.2.2 设计要点
-
时钟电路:采用晶振(如11.0592MHz/12MHz)确保定时扫描稳定。
-
复位电路:上电复位保证进入初始显示;另设复位按键用于顾客切换。
-
去耦电容:单片机供电端0.1uF+10uF,避免数码管刷新电流波动造成系统不稳。
-
IO资源规划:
- 键盘矩阵需要若干行列IO(如4×4或4×5)
- 数码管段选a~g+dp需要8路(可用锁存/移位寄存器扩展)
- 数码管位选需要N路(N位数码管)
若IO紧张可使用74HC595扩展段选或位选,但题目不要求扩展芯片,仍可用直连方式说明。
3.3 矩阵键盘输入模块
3.3.1 模块功能
键盘用于输入:
- 数字0~9
- 运算/控制键:×、十、=
- 复位键(可独立按键,不一定在矩阵内)
3.3.2 矩阵键盘方案与优势
矩阵键盘通过行列复用节省IO,例如4×4可实现16个按键,足够容纳:
- 10个数字键
- ×、十、=、清除/复位等功能键
扫描方法:
- 逐行输出低电平,其余行高电平
- 读取列输入,判断是否有按键按下
- 组合行列得到按键编码
3.3.3 消抖与长按处理
按键机械抖动会产生多次触发,需要:
- 软件延时消抖(10~20ms)
- 采用"按下沿触发"只记录一次按键事件
- 可选:长按自动重复输入(对收银机不一定需要,反而容易误输入)
3.4 数码管显示模块
3.4.1 模块功能
数码管用于显示:
- 单价输入值(逐位左移显示)
- 数量输入值(逐位左移显示)
- 累计总金额(按十后显示)
- 初始状态:最右位"0",其余熄灭
- 结算状态:显示最终总金额并锁定
3.4.2 位数选择与显示范围
为了体现"逐位左移输入"和"总金额显示",建议使用至少6位或8位数码管:
- 6位可显示到999999,适合课程设计的金额范围
- 8位更接近真实收银机,但IO占用更大
显示数据组织:
- 使用显示缓冲数组
disp[NUM_DIG],每个元素存一个数字0~9或空白。 - 动态扫描刷新时按位输出段码与位选。
3.4.3 动态扫描设计要点
- 刷新频率足够高(整屏刷新≥100Hz)避免闪烁。
- 切换位时先关闭所有位选,再更新段码,再开启目标位,减少重影。
- 对"熄灭"可以用特殊码表示(例如0xFF代表空白)。
3.5 电源与稳压模块
3.5.1 模块功能
提供稳定5V电源给单片机与数码管。数码管电流较大,必须保证供电能力足够,否则会出现:
- 显示变暗
- 单片机复位或键盘误判
3.5.2 设计要点
- 电源输入端加滤波电容(如470uF)平滑电压波动。
- 数码管段选与位选驱动电流应通过限流电阻控制,保护LED段。
- 若显示较多位数,建议用驱动管或驱动芯片分担电流,避免直接由IO承载大电流。
4 程序设计
4.1 软件总体架构
软件采用"定时刷新 + 事件驱动 + 状态机"的结构:
-
定时器中断:用于数码管动态扫描刷新(高频、稳定)
-
主循环:
- 键盘扫描与消抖
- 解析按键事件(数字键/功能键)
- 按状态机更新单价、数量、总金额与显示缓冲
-
数据运算:使用整数乘法与加法完成金额计算与累加
-
结算锁定:按"="后禁止继续输入数字/×/十,直到复位
4.2 显示缓冲与输入移位设计
4.2.1 显示缓冲定义
假设使用8位数码管:
disp_buf[8]保存每一位的显示内容- 约定:
0~9为数字,0xFF为空白
4.2.2 逐位左移输入算法
当输入新数字d时:
- 从左到右整体左移:
disp[i]=disp[i+1] - 最右位赋值为d:
disp[last]=d
初始状态下只有最右位为0,其余为空白。输入第一位数字时需要替换最右位0为该数字,符合题目规则。
4.3 核心变量与状态定义
4.3.1 状态枚举
c
typedef enum
{
ST_INIT = 0,
ST_PRICE_INPUT,
ST_WAIT_QTY,
ST_QTY_INPUT,
ST_SHOW_TOTAL,
ST_DONE
} SYS_STATE;
4.3.2 数据变量设计
unsigned long price;当前商品单价unsigned long qty;当前商品数量unsigned long total;累计总金额SYS_STATE state;当前状态unsigned char disp_buf[8];数码管显示缓冲bit price_valid; qty_valid;是否已输入有效单价/数量
为了避免溢出,需要限制输入位数与最大值:例如单价最多6位、数量最多4位、总金额最多8位等。课程设计可在说明中提出"超范围则忽略或报错",但不必实现复杂报错显示。
4.4 系统初始化模块
4.4.1 初始化目标
- 配置IO口与定时器
- 清空数据与状态
- 设置初始显示:最右位0,其余空白
4.4.2 初始化代码示例
c
#define DIGITS 8
#define BLANK 0xFF
unsigned char disp_buf[DIGITS];
SYS_STATE state;
unsigned long price, qty, total;
void Display_InitToZero(void)
{
unsigned char i;
for(i=0;i<DIGITS-1;i++) disp_buf[i] = BLANK;
disp_buf[DIGITS-1] = 0;
}
void System_Reset(void)
{
price = 0;
qty = 0;
total = 0;
state = ST_INIT;
Display_InitToZero();
}
4.5 键盘扫描与按键事件模块
4.5.1 模块功能
- 扫描矩阵键盘获取按键值
- 消抖并输出"单次按下事件"
- 将按键映射为:数字0~9、KEY_MUL(×)、KEY_ADD(十)、KEY_EQ(=)、KEY_RST(复位)
4.5.2 按键事件定义示例
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.5.3 扫描框架示例(简化)
c
KEY_CODE Key_Scan(void)
{
// 这里给出框架:实际需按矩阵行列实现
// 返回KEY_NONE表示无按键
return KEY_NONE;
}
4.6 单价输入处理模块
4.6.1 单价数值更新策略
当输入新数字d时,单价数值可按十进制构建:
price = price * 10 + d
同时,为了与"逐位左移显示"一致,需要处理"位数超过显示范围"时的截断策略:- 若超出最大位数,可丢弃最高位或停止接收新数字。
在模拟系统中,为了符合"显示左移",可采用"保留最后N位"的策略,即: price = (price % 10^(N-1))*10 + d
这样数值与显示缓冲一致。
4.6.2 单价输入代码示例
c
unsigned long LimitAppend(unsigned long val, unsigned char d, unsigned long max_pow10)
{
// max_pow10 = 10^(N-1),用于保留N位
val = (val % max_pow10) * 10 + d;
return val;
}
void ShiftInDigit(unsigned char d)
{
unsigned char i;
for(i=0;i<DIGITS-1;i++) disp_buf[i] = disp_buf[i+1];
disp_buf[DIGITS-1] = d;
}
void Price_InputDigit(unsigned char d)
{
// 假设单价最多8位显示
price = LimitAppend(price, d, 10000000UL);
ShiftInDigit(d);
}
4.7 数量输入处理模块
4.7.1 数量输入显示切换规则
题目要求:
- 按×后,单价显示保持不变,等待数量输入
- 数量第一位输入时,单价显示立即熄灭,转为显示数量
实现方式:
-
在
ST_WAIT_QTY状态下收到数字键:- 清空显示缓冲为"初始数量显示"
- 将该数字作为数量第一位写入显示
- 进入
ST_QTY_INPUT
4.7.2 数量输入代码示例
c
void ClearDisplayToBlank(void)
{
unsigned char i;
for(i=0;i<DIGITS;i++) disp_buf[i] = BLANK;
}
void Qty_InputFirstDigit(unsigned char d)
{
qty = 0;
ClearDisplayToBlank();
qty = LimitAppend(qty, d, 10000000UL);
ShiftInDigit(d);
state = ST_QTY_INPUT;
}
void Qty_InputDigit(unsigned char d)
{
qty = LimitAppend(qty, d, 10000000UL);
ShiftInDigit(d);
}
4.8 运算与累计模块(按"十"键)
4.8.1 运算规则
按"十"键时:
-
计算金额
amount = price * qty -
累加到
total += amount -
显示
total -
清空当前商品输入缓存,为下一笔商品做准备:
price = 0; qty = 0;- 状态进入
ST_SHOW_TOTAL
4.8.2 显示总金额的方法
总金额显示需要把整数转换为数码管缓冲:
- 从右向左取余数得到每一位
- 高位不足的部分设为空白
- 若总金额为0则显示最右为0,其余空白
4.8.3 代码示例
c
void Display_Number(unsigned long val)
{
unsigned char i;
for(i=0;i<DIGITS;i++) disp_buf[i] = BLANK;
if(val == 0)
{
disp_buf[DIGITS-1] = 0;
return;
}
for(i=0; i<DIGITS; i++)
{
unsigned char d = (unsigned char)(val % 10);
disp_buf[DIGITS-1 - i] = d;
val /= 10;
if(val == 0) break;
}
}
void Add_CalcAndAccumulate(void)
{
unsigned long amount = price * qty;
total += amount;
Display_Number(total);
price = 0;
qty = 0;
state = ST_SHOW_TOTAL;
}
4.9 结算模块(按"="键)
4.9.1 结算规则
按"="键后:
- 显示最终累计总金额(如果当前已在显示总金额状态则保持)
- 进入结算锁定状态
ST_DONE - 锁定后不再接受数字、×、十等操作(仅允许复位)
4.9.2 代码示例
c
void Equal_Finish(void)
{
Display_Number(total);
state = ST_DONE;
}
4.10 复位模块(下一位顾客)
4.10.1 复位规则
无论处于任何状态,只要按下复位键:
- 清空所有数据
- 初始显示恢复为最右0其余空白
- 状态回到
ST_INIT
4.10.2 代码示例
c
void Handle_Reset(void)
{
System_Reset();
}
4.11 按键事件处理总流程(状态机核心)
4.11.1 事件解析原则
-
KEY_RST随时有效 -
ST_DONE状态下除复位外全部忽略 -
数字键在不同状态下含义不同:
- INIT/PRICE_INPUT/SHOW_TOTAL:输入单价
- WAIT_QTY/QTY_INPUT:输入数量
-
×只在单价输入完成后有效 -
十只在数量输入完成后有效(或数量>0)
4.11.2 核心处理示例代码
c
unsigned char IsDigit(KEY_CODE k)
{
return (k >= KEY_0 && k <= KEY_9);
}
unsigned char KeyToDigit(KEY_CODE k)
{
return (unsigned char)(k - KEY_0);
}
void KeyEvent_Handle(KEY_CODE k)
{
if(k == KEY_RST)
{
Handle_Reset();
return;
}
if(state == ST_DONE)
{
// 结算锁定,忽略除复位外所有键
return;
}
if(IsDigit(k))
{
unsigned char d = KeyToDigit(k);
if(state == ST_INIT || state == ST_PRICE_INPUT || state == ST_SHOW_TOTAL)
{
// 若从SHOW_TOTAL进入新单价输入,需要清屏
if(state == ST_SHOW_TOTAL)
{
Display_InitToZero(); // 先回到右0状态也可,或直接清空为空白
// 按题意:总金额消失后逐位显示单价
ClearDisplayToBlank();
}
// 第一个数字输入时:右边"0"变为该数字
if(state == ST_INIT)
{
ClearDisplayToBlank();
price = 0;
}
state = ST_PRICE_INPUT;
Price_InputDigit(d);
}
else if(state == ST_WAIT_QTY)
{
Qty_InputFirstDigit(d);
}
else if(state == ST_QTY_INPUT)
{
Qty_InputDigit(d);
}
return;
}
if(k == KEY_MUL)
{
if(state == ST_PRICE_INPUT)
{
state = ST_WAIT_QTY; // 等待数量第一位输入,单价显示保持
}
return;
}
if(k == KEY_ADD)
{
if(state == ST_QTY_INPUT)
{
Add_CalcAndAccumulate();
}
return;
}
if(k == KEY_EQ)
{
// 无论当前处于哪个输入阶段,都以当前累计total为准结算
// 若希望必须在SHOW_TOTAL才能结算,可在此加条件
Equal_Finish();
return;
}
}
该逻辑体现题目关键细节:
- INIT时最右0,第一位数字输入时替换0并开始移位输入
- ×后进入WAIT_QTY,数量第一位输入时清除单价显示并显示数量
- 十键计算并显示累计总金额
- 在显示总金额状态输入下一笔单价时总金额消失并开始单价移位显示
- =进入DONE锁定,不再等待下一笔单价
4.12 数码管动态扫描模块(显示驱动)
4.12.1 段码表与显示输出
数码管段码取决于共阳/共阴,设计中可抽象为:
SegTable[10]:0~9段码BLANK显示为空白(段码全灭)
4.12.2 扫描中断示例
c
unsigned char SegTable[10] = {
/* 0~9 段码表(示意),实际需按硬件填写 */
};
unsigned char cur_pos = 0;
void Seg_Refresh_ISR(void) interrupt 1
{
// 关闭所有位
DIG_ALL_OFF();
// 输出段码
if(disp_buf[cur_pos] == BLANK)
SEG_PORT = 0x00; // 全灭
else
SEG_PORT = SegTable[disp_buf[cur_pos]];
// 打开当前位
DIG_ON(cur_pos);
cur_pos++;
if(cur_pos >= DIGITS) cur_pos = 0;
}
刷新频率建议:
- 每1ms切换一位,8位则8ms刷新一轮,约125Hz整屏刷新,视觉稳定。
5 边界处理与使用体验优化(丰富说明)
5.1 输入位数溢出处理
逐位左移输入意味着显示只保留最后N位。若用户输入超过位数:
- 显示自动丢弃最左位,保留最新输入(符合题目"左移"直觉)
- 数值构建也应同步"保留最后N位",避免显示与数值不一致
- 对实际收银机而言溢出应报警或限制输入,本模拟系统可只实现截断,简化逻辑
5.2 数量为0的处理
若用户按×后不输入数量直接按十:
- 可忽略十键
- 或将数量视为0,金额为0并不改变总金额
为了避免误操作,建议只有在qty>0或ST_QTY_INPUT状态下才允许十键生效。
5.3 结算锁定的合理性
按=后锁定是题目明确要求,意义在于:
- 防止结算后继续误输入导致金额变化
- 模拟"本顾客交易结束"的流程
此时允许复位键进入下一顾客,符合真实收银流程。
5.4 人机反馈增强(可选)
尽管题目未要求,设计说明中可提出可扩展的反馈:
- 蜂鸣器:每次按键"滴"提示,十键与=键可用不同音调或时长提示
- 错误提示:溢出或非法操作时闪烁显示或短鸣
- 记忆功能:掉电保存总金额(不符合单顾客场景,但可用于日营业额统计扩展)
6 总结
基于单片机的智能收银机模拟系统围绕"单价输入---×输入数量---十计算并累计---=结算显示---复位准备下一顾客"的典型收银流程,采用矩阵键盘作为输入终端,多位数码管作为显示终端,通过逐位左移显示实现符合真实设备的输入体验,并用状态机严格控制各阶段的显示切换与按键规则。系统通电后以"最右位0、其余熄灭"的方式进入待机;输入单价时数字逐位左移并更新单价值;按×进入数量输入流程,数量第一位输入时立即熄灭单价显示并显示数量;按十计算单价×数量并累加到总金额,数码管显示最新累计总金额;在总金额显示后再次输入数字时自动进入下一笔单价输入;按=显示最终累计并锁定,直到复位返回初始状态,从而完整模拟一位顾客的结算过程。
在电路设计方面,系统由单片机最小系统、矩阵键盘输入模块与多位数码管动态显示模块构成,配合稳定电源与必要的限流、去耦措施,保证键盘扫描准确与显示无闪烁。在程序设计方面,系统采用定时器中断刷新数码管、主循环扫描键盘并通过状态机解析按键事件,将显示移位、数值构建、乘法计算与金额累加有机结合,逻辑清晰、可扩展性强。该模拟系统不仅适合作为单片机课程设计与仿真实验项目,也可作为进一步扩展条码输入、打印输出、库存管理等更复杂收银系统的基础控制框架。