基于STM32设计的出租车计费系统

一、项目介绍

在城市交通中,出租车是一种常见的交通工具。为了方便乘客和司机之间的交易,出租车计费系统被广泛应用于出租车行业。系统能够自动计算乘客的费用,提供准确、方便的计费服务,并且能够记录乘客的行驶数据,方便后续查询和管理。

传统的出租车计费方式是基于人工计算,司机根据里程和时间进行估算并告知乘客费用。然而,这种计费方式容易存在误差和争议,并且对司机和乘客都不够方便和透明。因此,出租车行业迫切需要一种更加准确、高效和可靠的计费系统。

基于此背景,本项目设计和开发一种基于STM32微控制器的出租车计费系统,以替代传统的人工计费方式。该系统将利用STM32微控制器的强大处理能力和丰富的外设接口,集成各种功能模块,实现自动计算乘客费用、显示计费信息等功能。

通过该出租车计费系统,乘客只需在上车时按下对应按钮,系统将自动开始计费,并在显示屏上实时显示行驶时间、里程和费用等信息。乘客还可以通过按键输入特殊情况,如堵车或夜间行驶,以便系统进行相应的额外计费。当乘客下车时,系统将自动停止计费,并显示最终费用。同时,系统还将记录乘客的行驶数据以备查询和管理。

二、系统设计思路

2.1 系统架构

出租车计费系统的主要组成部分包括:STM32微控制器、LCD显示屏、按键、计时电路、收费器和外部存储器。整个系统的架构如下:

  • STM32微控制器:采用STM32F103RCT6作为系统的控制核心,负责接收并处理来自各个模块的输入信号,并控制液晶显示屏上的信息显示和收费器的操作。
  • LCD显示屏:采用1.44寸LCD显示屏,用于显示当前的计费信息,包括行驶时间、里程和费用等。
  • 按键:用于输入乘客上车和下车的时间以及其他特殊情况,如堵车、夜间行驶等。
  • 计时电路:用于准确地测量行驶时间。
  • 收费器:负责根据计费规则和实时数据计算乘客的费用。
  • 外部存储器:用于存储行驶数据和计费规则。

2.2 系统功能

出租车计费系统具有以下主要功能:

  • 实时计算行驶时间和里程。
  • 根据计费规则自动计算乘客费用。
  • 在LCD显示屏上显示当前的计费信息。
  • 支持特殊情况的额外计费,如堵车、夜间行驶等。
  • 存储行驶数据和计费规则以备查询和更新。

三、代码设计

3.1 LCD显示屏代码

cpp 复制代码
#include "stm32f10x.h"
​
// 定义LCD引脚连接
#define LCD_RS_PIN  GPIO_Pin_0
#define LCD_RS_PORT GPIOA
#define LCD_RW_PIN  GPIO_Pin_1
#define LCD_RW_PORT GPIOA
#define LCD_E_PIN   GPIO_Pin_2
#define LCD_E_PORT  GPIOA
#define LCD_D4_PIN  GPIO_Pin_3
#define LCD_D4_PORT GPIOA
#define LCD_D5_PIN  GPIO_Pin_4
#define LCD_D5_PORT GPIOA
#define LCD_D6_PIN  GPIO_Pin_5
#define LCD_D6_PORT GPIOA
#define LCD_D7_PIN  GPIO_Pin_6
#define LCD_D7_PORT GPIOA
​
// 定义命令和数据的宏
#define LCD_COMMAND 0
#define LCD_DATA    1
​
// 延时函数,用于产生适当的延时
void Delay(uint32_t nCount) {
    for (; nCount != 0; --nCount) {
    }
}
​
// 发送命令或数据到LCD函数
void LCD_Send(uint8_t byte, uint8_t mode) {
    GPIO_WriteBit(LCD_RS_PORT, LCD_RS_PIN, (mode == LCD_DATA) ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_RW_PORT, LCD_RW_PIN, Bit_RESET);
    
    GPIO_WriteBit(LCD_D4_PORT, LCD_D4_PIN, (byte >> 4) & 0x01 ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_D5_PORT, LCD_D5_PIN, (byte >> 5) & 0x01 ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_D6_PORT, LCD_D6_PIN, (byte >> 6) & 0x01 ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_D7_PORT, LCD_D7_PIN, (byte >> 7) & 0x01 ? Bit_SET : Bit_RESET);
    
    GPIO_WriteBit(LCD_E_PORT, LCD_E_PIN, Bit_SET);
    Delay(1000);
    GPIO_WriteBit(LCD_E_PORT, LCD_E_PIN, Bit_RESET);
    
    GPIO_WriteBit(LCD_D4_PORT, LCD_D4_PIN, (byte >> 0) & 0x01 ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_D5_PORT, LCD_D5_PIN, (byte >> 1) & 0x01 ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_D6_PORT, LCD_D6_PIN, (byte >> 2) & 0x01 ? Bit_SET : Bit_RESET);
    GPIO_WriteBit(LCD_D7_PORT, LCD_D7_PIN, (byte >> 3) & 0x01 ? Bit_SET : Bit_RESET);
    
    GPIO_WriteBit(LCD_E_PORT, LCD_E_PIN, Bit_SET);
    Delay(1000);
    GPIO_WriteBit(LCD_E_PORT, LCD_E_PIN, Bit_RESET);
    
    Delay(1000);
}
​
// 初始化LCD函数
void LCD_Init(void) {
    Delay(45000);
    LCD_Send(0x30, LCD_COMMAND);
    Delay(4500);
    LCD_Send(0x30, LCD_COMMAND);
    Delay(150);
    LCD_Send(0x30, LCD_COMMAND);
    Delay(150);
    
    LCD_Send(0x20, LCD_COMMAND);
    Delay(150);
    
    LCD_Send(0x28, LCD_COMMAND);
    Delay(150);
​
    LCD_Send(0x08, LCD_COMMAND);
    Delay(150);
    
    LCD_Send(0x01, LCD_COMMAND);
    Delay(150);
    
    LCD_Send(0x06, LCD_COMMAND);
    Delay(150);
    
    LCD_Send(0x0C, LCD_COMMAND);
    Delay(150);
}
​
// 在指定位置显示数字函数
void LCD_DisplayNumber(uint8_t number, uint8_t x, uint8_t y) {
    uint8_t data = 0x30 + number;  // 转换数字为对应的ASCII码
    
    if (x >= 0 && x < 16 && y >= 0 && y < 2) {
        uint8_t addr = 0x80 + (y * 0x40) + x;
        
        LCD_Send(addr, LCD_COMMAND);
        LCD_Send(data, LCD_DATA);
    }
}
​
int main(void) {
    // 初始化GPIO和LCD
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = LCD_RS_PIN | LCD_RW_PIN | LCD_E_PIN | LCD_D4_PIN | LCD_D5_PIN | LCD_D6_PIN | LCD_D7_PIN;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    LCD_Init();
    
    while (1) {
        // 在第一行第一列显示数字1
        LCD_DisplayNumber(1, 0, 0);
    }
}
​

3.2 计时代码

通过定时器2实现了收费计时功能,并在串口上打印出计时的实时时间。通过按下'S'键启动计时器,按下'Q'键停止计时器。每隔500毫秒,在串口上打印出实时时间。

cpp 复制代码
#include "stm32f10x.h"
#include <stdio.h>
​
// 定义计时状态
typedef enum {
    TIMER_STOPPED,
    TIMER_RUNNING
} TimerState;
​
TimerState timerState = TIMER_STOPPED; // 计时器初始状态为停止
uint32_t startTime = 0; // 开始计时的时间
​
// 初始化定时器2
void Timer2_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
​
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_TimeBaseStructure.TIM_Prescaler = 7200 - 1; // 设置预分频值,产生1ms的时间基准
    TIM_TimeBaseStructure.TIM_Period = 1000 - 1;  // 设置计数器的重载值,每1秒中断一次
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
​
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    TIM_Cmd(TIM2, ENABLE);
​
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
}
​
// 开始计时
void StartTimer(void) {
    if (timerState == TIMER_STOPPED) {
        startTime = TIM_GetCounter(TIM2); // 记录开始计时的时间
        timerState = TIMER_RUNNING;
    }
}
​
// 停止计时
void StopTimer(void) {
    if (timerState == TIMER_RUNNING) {
        timerState = TIMER_STOPPED;
    }
}
​
// 获取实时时间,返回单位为毫秒
uint32_t GetElapsedTime(void) {
    if (timerState == TIMER_RUNNING) {
        return TIM_GetCounter(TIM2) - startTime;
    } else {
        return 0;
    }
}
​
// 初始化串口1
void USART1_Init(void) {
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1 | RCC_APB2Periph_GPIOA, ENABLE);
​
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; // TX引脚
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
​
    USART_InitTypeDef USART_InitStructure;
    USART_InitStructure.USART_BaudRate = 115200;
    USART_InitStructure.USART_WordLength = USART_WordLength_8b;
    USART_InitStructure.USART_StopBits = USART_StopBits_1;
    USART_InitStructure.USART_Parity = USART_Parity_No;
    USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
    USART_InitStructure.USART_Mode = USART_Mode_Tx;
    USART_Init(USART1, &USART_InitStructure);
​
    USART_Cmd(USART1, ENABLE);
}
​
// 重定向printf函数到串口输出
int fputc(int ch, FILE *f) {
    if (ch == '\n') {
        USART_SendData(USART1, '\r');
        while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
    }
​
    USART_SendData(USART1, ch);
    while (USART_GetFlagStatus(USART1, USART_FLAG_TXE) == RESET);
​
    return ch;
}
​
int main(void) {
    // 初始化定时器和串口
    Timer2_Init();
    USART1_Init();
​
    printf("Press 'S' to start the timer.\r\n");
    printf("Press 'Q' to stop the timer.\r\n");
​
    while (1) {
        if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) != RESET) {
            uint8_t rxData = (uint8_t)USART_ReceiveData(USART1);
            
            if (rxData == 'S' || rxData == 's') {
                StartTimer();
                printf("Timer started.\r\n");
            } else if (rxData == 'Q' || rxData == 'q') {
                StopTimer();
                printf("Timer stopped.\r\n");
            }
        }
​
        // 每隔500毫秒打印实时时间
        if (GetElapsedTime() >= 500) {
            printf("Elapsed time: %lu ms\r\n", GetElapsedTime());
            startTime = TIM_GetCounter(TIM2); // 更新开始计时的时间
        }
    }
}
​
// 定时器2中断处理函数
void TIM2_IRQHandler(void) {
    if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) {
        TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
    }
}
​
相关推荐
用户685453759776915 分钟前
同步成本换并行度:多线程、协程、分片、MapReduce 怎么选才不踩坑
后端
javaTodo23 分钟前
Claude Code 记忆机制详解:从 CLAUDE.md 到 Auto Memory,六层体系全拆解
后端
LSTM9744 分钟前
使用 C# 和 Spire.PDF 从 HTML 模板生成 PDF 的实用指南
后端
JaguarJack1 小时前
为什么 PHP 闭包要加 static?
后端·php·服务端
BingoGo1 小时前
为什么 PHP 闭包要加 static?
后端
是糖糖啊1 小时前
OpenClaw 从零到一实战指南(飞书接入)
前端·人工智能·后端
百度Geek说1 小时前
基于Spark的配置化离线反作弊系统
后端
Java编程爱好者2 小时前
虚拟线程深度解析:轻量并发编程的未来趋势
后端
苏三说技术2 小时前
Spring AI 和 LangChain4j ,哪个更好?
后端