出租车计价器课程设计
项目概述
本项目旨在设计并制作一个基于单片机的出租车计价器,该计价器能够实时模拟出租车的计费过程,包括起步价、里程费、等待费,并能通过按键进行模拟启动、暂停/继续、复位等操作,最终将总费用、行驶里程和等待时间等信息显示在LCD1602液晶屏上。
通过本课程设计,学生可以综合运用单片机原理、C语言编程、传感器技术、人机接口设计等知识,完成一个从理论到实践的完整电子产品开发过程。

设计目标与要求
-
基本功能:
- 显示功能: 使用LCD1602液晶屏实时显示总费用、行驶里程(单位:公里)、等待时间(单位:分钟)。
- 计费模式:
- 起步价: 启动后,自动显示并开始计算起步价(前3公里内收费10元)。
- 里程费: 超过起步里程后,按每公里单价收费(2元/公里)。
- 等待费: 在车辆停止或低速行驶时,开始按时间计费(每分钟0.5元)。
- 控制功能:
- 启动键: 模拟乘客上车,计价器开始计费。
- 暂停/继续键: 模拟等红灯或临时停靠,暂停里程计费,开始等待计时;再次按下,继续行驶计费。
- 复位键: 模拟乘客下车,计价器清零所有数据,恢复初始状态。
-
技术指标:
- 计费精度: 费用计算精确到0.1元。
- 里程精度: 里程计算精确到0.01公里。
- 响应时间: 按键响应迅速,显示数据更新及时。
-
扩展功能 (可选):
- 夜间模式: 在特定时间段(如晚上11点后至早上5点前)自动切换为夜间计价标准(起步价和单价上浮20%)。
- 发票打印功能: 模拟打印发票,显示本次行程的详细信息。
- 语音播报: 使用语音模块播报“欢迎乘坐”、“请系好安全带”等提示音。
系统总体方案设计
系统采用“单片机最小系统 + 传感器模块 + 输入模块 + 显示模块”的结构。

工作流程:
- 用户通过按键模块向单片机发送指令(启动、暂停、复位)。
- 单片机根据指令控制计费状态。
- 单片机通过传感器模块获取里程信息(如霍尔传感器脉冲)。
- 单片机内部根据预设的计价算法(起步价、里程费、等待费)进行实时计算。
- 单片机将计算结果(总费用、里程、时间)发送到LCD1602显示屏上进行实时更新。
系统框图:
+---------------------+
| 按键模块 | -----> | |
| (启动/暂停/复位) | | |
+---------------------+ | |
| 单片机 |
+---------------------+ | (主控制器) |
| 传感器模块 | -----> | (如STC89C52) |
| (霍尔传感器/编码器) | | |
+---------------------+ | |
| |
+---------------------+ | |
| LCD1602 显示模块 | <----- | |
| (显示费用/里程/时间)| | |
+---------------------+ +----------------+
硬件选型与电路设计
-
主控制器:
- 型号: STC89C52RC 或 STM32F103C8T6
- 理由: STC89C52是经典的51单片机,资料丰富,成本低,非常适合教学,STM32性能更强,资源更多,适合有更高要求的项目,本设计以STC89C52为例。
-
显示模块:
- 型号: LCD1602 (1602字符型液晶显示器)
- 理由: 可以显示2行,每行16个字符,足以显示总费用、里程、等待时间等信息,接口简单,驱动成熟。
-
输入模块:
- 型号: 独立按键 x 3
- 理由: 用于实现启动、暂停/继续、复位功能,电路简单,只需连接到单片机的I/O口,并做必要的上拉或下拉。
-
里程检测模块:
- 方案A (推荐): 霍尔传感器 + 小磁铁
- 原理: 在车轮的转动部件上粘贴一小块磁铁,将霍尔传感器固定在附近,车轮每转一圈,磁铁经过一次,霍尔传感器就输出一个脉冲信号,单片机对脉冲进行计数,即可换算出里程。
- 优点: 非接触式,寿命长,安装方便。
- 方案B: 光电对管/红外对管
- 原理: 在车轮上贴一个反光片,利用红外发射和接收管检测反光片的经过。
- 缺点: 易受灰尘和光线影响,可靠性不如霍尔传感器。
- 方案C: 旋转编码器
- 原理: 可以输出正交脉冲,不仅能检测转动,还能检测方向。
- 优点: 精度高,信息量大。
- 缺点: 成本相对较高,电路稍复杂。
- 方案A (推荐): 霍尔传感器 + 小磁铁
-
电路连接:
- LCD1602:
RS, RW, E, D4-D7 -> 连接到单片机的P0或P2口(需要上拉电阻)。
- 按键:
三个按键分别连接到P3.0, P3.1, P3.2,并使用10KΩ电阻上拉到VCC。
- 霍尔传感器:
- VCC -> 5V
- GND -> GND
- OUT -> 连接到外部中断0(INT0, P3.2)或普通I/O口(如P3.3),使用中断可以更精确地捕获脉冲。
- LCD1602:
软件设计与流程图
软件是项目的核心,主要包括初始化、按键处理、计费逻辑、数据显示四大模块。
-
主程序流程图:
graph TD A[开始] --> B{系统初始化}; B --> C{等待按键事件}; C -- 启动键 --> D[启动计费]; C -- 暂停/继续键 --> E[切换计费状态]; C -- 复位键 --> F[系统复位]; D --> G[读取传感器数据]; G --> H[根据状态计算费用]; H --> I[更新LCD显示]; I --> C; E --> G; F --> C; -
计费逻辑详解 (伪代码):
// 定义常量 float START_PRICE = 10.0; // 起步价 float START_MILEAGE = 3.0; // 起步里程 float PRICE_PER_KM = 2.0; // 每公里单价 float PRICE_PER_MINUTE = 0.5; // 每分钟单价
// 定义变量 float total_price = 0.0; float mileage = 0.0; int wait_time = 0; bool is_running = false; // 是否在行驶 bool is_waiting = false; // 是否在等待 unsigned long last_pulse_time = 0; int pulse_count = 0; const float MILEAGE_PER_PULSE = 0.01; // 每个脉冲代表的里程数
// 中断服务函数 (霍尔传感器中断) void HALL_ISR() { if (is_running && !is_waiting) { mileage += MILEAGE_PER_PULSE; pulse_count++; } }
// 主循环中的计费逻辑 void calculate_fare() { if (is_running) { // 1. 计算里程费 if (mileage > START_MILEAGE) { total_price = START_PRICE + (mileage - START_MILEAGE) * PRICE_PER_KM; } else { total_price = START_PRICE; }
// 2. 计算等待费
if (is_waiting) {
// 等待费计算,可以在定时器中断里增加wait_time
total_price += wait_time * PRICE_PER_MINUTE;
}
}
3. **软件模块划分:**
* **`main.c`**: 主函数,负责调用其他模块,实现主循环。
* **`lcd1602.c / lcd1602.h`**: LCD1602驱动函数库,封装初始化、写字符、字符串等函数。
* **`key.c / key.h`**: 按键扫描和消抖函数。
* **`timer.c / timer.h`**: 定时器初始化和中断服务函数,用于产生系统时钟(如1秒定时)来计算等待时间。
* **`interrupt.c / interrupt.h`**: 外部中断(霍尔传感器)服务函数。
* **`fare.c / fare.h`**: 计费逻辑核心函数。
---
#### **六、 核心代码框架示例 (基于Keil C51)**
**`main.c`**
```c
#include <reg52.h>
#include "lcd1602.h"
#include "key.h"
#include "timer.h"
#include "fare.h"
sbit KEY_START = P3^0; // 启动按键
sbit KEY_PAUSE = P3^1; // 暂停/继续按键
sbit KEY_RESET = P3^2; // 复位按键
void main() {
// 初始化模块
Lcd_Init();
Timer0_Init(); // 初始化定时器,用于计时
Init_Interrupt(); // 初始化外部中断0
Lcd_ShowString(0, 0, "Total Price: 0.0");
Lcd_ShowString(1, 0, "Mileage: 0.00km");
Lcd_ShowString(0, 9, "Time: 0min");
while (1) {
// 按键检测
if (Key_Scan(KEY_START) == 0) {
Start_Fare(); // 启动计费
}
if (Key_Scan(KEY_PAUSE) == 0) {
Toggle_Fare_State(); // 切换状态
}
if (Key_Scan(KEY_RESET) == 0) {
Reset_Fare(); // 复位
}
// 计费逻辑
Calculate_Fare();
// 更新显示
Update_Display();
}
}
timer0.h (用于计时)
#ifndef __TIMER0_H__ #define __TIMER0_H__ void Timer0_Init(); #endif
timer0.c
#include "timer0.h"
unsigned int seconds = 0;
void Timer0_Init() {
TMOD &= 0xF0; // 清除T0设置
TMOD |= 0x01; // 设置T0为16位定时器模式
TH0 = 0xFC; // 定时1ms
TL0 = 0x18;
ET0 = 1; // 使能T0中断
EA = 1; // 开启总中断
TR0 = 1; // 启动T0
}
// 定时器0中断服务函数
void Timer0_ISR() interrupt 1 {
TH0 = 0xFC;
TL0 = 0x18;
seconds++;
}
fare.h
#ifndef __FARE_H__ #define __FARE_H__ void Start_Fare(); void Toggle_Fare_State(); void Reset_Fare(); void Calculate_Fare(); void Update_Display(); #endif
fare.c (核心逻辑)
#include "fare.h"
#include "lcd1602.h"
// 全局变量
float total_price = 0.0;
float mileage = 0.0;
unsigned int wait_time = 0;
bit is_running = 0; // 0: 停止, 1: 行驶
bit is_waiting = 0; // 0: 行驶中, 1: 等待中
// 计费参数
#define START_PRICE 10.0
#define START_MILEAGE 3.0
#define PRICE_PER_KM 2.0
#define PRICE_PER_MINUTE 0.5
void Start_Fare() {
if (!is_running) {
is_running = 1;
is_waiting = 0;
// 可以在这里启动一个里程计数定时器
}
}
void Toggle_Fare_State() {
if (is_running) {
is_waiting = !is_waiting;
}
}
void Reset_Fare() {
is_running = 0;
is_waiting = 0;
total_price = 0.0;
mileage = 0.0;
wait_time = 0;
}
// 在外部中断或定时器中断中调用此函数来增加里程
void Add_Mileage_Pulse() {
if (is_running && !is_waiting) {
mileage += 0.01; // 假设每脉冲0.01公里
}
}
// 在定时器中断中调用此函数来增加等待时间
void Add_Second() {
seconds++;
if (is_running && is_waiting) {
wait_time = seconds / 60; // 将秒转换为分钟
}
}
void Calculate_Fare() {
if (is_running) {
// 计算里程费
if (mileage > START_MILEAGE) {
total_price = START_PRICE + (mileage - START_MILEAGE) * PRICE_PER_KM;
} else {
total_price = START_PRICE;
}
// 计算等待费
if (is_waiting) {
total_price += (seconds / 60) * PRICE_PER_MINUTE;
}
}
}
void Update_Display() {
char buf[16];
// 显示总费用
sprintf(buf, "Total Price: %.1f", total_price);
Lcd_ShowString(0, 0, buf);
// 显示里程
sprintf(buf, "Mileage: %.2fkm", mileage);
Lcd_ShowString(1, 0, buf);
// 显示等待时间
sprintf(buf, "Time: %dmin", wait_time);
Lcd_ShowString(0, 9, buf);
}
项目扩展与深化
-
RTC (实时时钟) 模块:
- 加入DS1302或DS3231实时时钟芯片,可以获取当前时间,从而实现白天/夜间模式的自动切换,DS3232精度更高,自带温度补偿。
-
EEPROM 存储模块:
- 利用单片机自带的EEPROM或外挂AT24C02芯片,存储上一次的计费总额,防止掉电后数据丢失,出租车需要打印发票,这个功能很有必要。
-
串口通信模块 (MAX232):
将计费数据通过串口发送到PC,使用上位机软件进行更复杂的数据管理和图表绘制。
-
语音模块 (SYN6288):
添加语音播报功能,在启动、暂停、复位时播放相应的语音提示,提升用户体验。
-
OLED 显示屏:
将LCD1602替换为OLED显示屏,显示效果更佳,更省电,并且可以显示图形。
总结与展望
本课程设计提供了一个完整的出租车计价器实现方案,从硬件选型、电路连接到软件编程的各个环节都进行了详细的阐述,学生可以在此基础上,根据自己的兴趣和能力,选择性地实现扩展功能,从而深化对单片机应用的理解。
这个项目不仅锻炼了动手能力,更重要的是培养了系统思维和模块化编程的思想,是电子工程和自动化专业学生非常宝贵的实践经历。