#include "Main.h" #include "stm32f10x_conf.h" #include "DataType.h" #include "NVIC.h" #include "SysTick.h" #include "RCC.h" #include "Timer.h" #include "UART1.h" #include "LED.h" #include "KeyOne.h" #include "ProcKeyOne.h" #include <string.h> #include <stdio.h> #include <stdlib.h> #include <math.h>
时间: 2025-07-28 18:00:32 浏览: 27
<think>根据提供的头文件,我们需要实现一个基于STM32的简易计算器程序。程序将使用UART1进行输入输出,通过按键(KeyOne)触发计算,并在LED上显示状态。我们将实现一个能够处理基本四则运算(包括括号)的计算器。
主要功能:
1. 通过UART接收用户输入的表达式(例如:3.14*2+5)
2. 按键KEY1按下时,计算表达式并返回结果
3. 使用LED指示计算状态(例如:计算中闪烁,错误常亮等)
4. 表达式求值支持加减乘除和括号,支持浮点数
我们使用双栈法(操作数栈和运算符栈)来实现表达式求值。
步骤:
1. 初始化系统时钟、外设(UART、LED、按键)
2. 设置按键中断(或轮询)来触发计算
3. 通过UART接收表达式字符串
4. 实现表达式求值函数
5. 将结果通过UART发送回用户
6. 使用LED指示状态
注意:由于嵌入式资源有限,表达式长度和数字精度需要限制。
我们将定义以下常量:
#define MAX_EXPR_LEN 64 // 表达式最大长度
#define STACK_SIZE 16 // 栈大小
错误处理:除零错误、括号不匹配、无效字符、栈溢出等。
由于提供的头文件较多,我们主要关注核心计算逻辑和主循环。假设已经实现了UART发送接收函数和按键处理函数。
下面我们将编写主程序,包括:
- 表达式求值函数
- 主循环(接收串口输入,等待按键触发计算)
由于问题中提到了KeyOne和ProcKeyOne,我们假设按键已经配置为外部中断,当按键按下时执行ProcKeyOne函数。
我们采用轮询方式处理串口输入,并将输入的字符存储到缓冲区。当按下KEY1时,计算缓冲区中的表达式。
代码结构如下:
1. 包含头文件
2. 定义全局变量(表达式缓冲区,结果等)
3. 初始化函数(初始化各外设)
4. 表达式求值函数
5. 按键处理函数(ProcKeyOne)
6. 主循环(接收串口字符并存储)
注意:由于嵌入式系统中通常避免使用动态内存和标准库函数(如malloc, atof等),我们将自己实现表达式解析。
表达式求值算法(双栈法)步骤:
初始化操作数栈和运算符栈
遍历表达式:
如果是空格,跳过
如果是数字或小数点,解析整个数字(包括小数部分)入操作数栈
如果是运算符:
如果当前运算符优先级小于等于栈顶运算符优先级,则弹出栈顶运算符和两个操作数进行计算,将结果入操作数栈,然后当前运算符入栈
否则直接入栈
如果是左括号,入栈
如果是右括号,弹出运算符和操作数直到遇到左括号,并将计算结果入栈
遍历结束后,依次弹出运算符栈的运算符进行计算
最后操作数栈顶元素即为结果
我们将实现一个简单的状态机来解析数字(包括小数)。
由于浮点数在嵌入式系统中处理较慢且占用资源,我们也可以考虑使用定点数,但根据要求,我们使用浮点数。
注意:浮点数精度问题,以及避免除零错误。
下面开始编写代码:</think># 基于STM32的简易计算器实现
下面是一个完整的嵌入式简易计算器实现,基于您提供的头文件结构。这个计算器支持基本四则运算、括号和小数运算,通过UART进行输入输出,使用按键触发计算功能。
```c
#include "Main.h"
#include "stm32f10x_conf.h"
#include "DataType.h"
#include "NVIC.h"
#include "SysTick.h"
#include "RCC.h"
#include "Timer.h"
#include "UART1.h"
#include "LED.h"
#include "KeyOne.h"
#include "ProcKeyOne.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
// 配置参数
#define MAX_EXPR_LEN 64 // 表达式最大长度
#define STACK_SIZE 16 // 栈大小
#define MAX_DECIMAL 6 // 最大小数位数
// 全局变量
char expression[MAX_EXPR_LEN] = {0}; // 存储表达式
float result = 0.0f; // 计算结果
u8 expr_valid = 0; // 表达式是否有效
u8 calculated = 0; // 是否已计算
// 错误代码
typedef enum {
CALC_OK = 0,
CALC_SYNTAX_ERROR,
CALC_DIV_BY_ZERO,
CALC_STACK_OVERFLOW,
CALC_PAREN_MISMATCH,
CALC_INVALID_CHAR
} CalcError;
// 运算符优先级
typedef enum {
PRIO_LOWEST = 0,
PRIO_ADD_SUB, // +, -
PRIO_MUL_DIV, // *, /
PRIO_HIGHEST // 函数和括号
} OperatorPriority;
// 初始化所有外设
void Init_Peripherals(void) {
// 系统时钟配置
RCC_Configuration();
// 中断配置
NVIC_Configuration();
// 系统定时器配置
SysTick_Configuration();
// 串口初始化
UART1_Init(115200);
// LED初始化
LED_Init();
// 按键初始化
KeyOne_Init();
}
// 清空表达式
void ClearExpression(void) {
memset(expression, 0, MAX_EXPR_LEN);
expr_valid = 0;
calculated = 0;
result = 0.0f;
UART1_SendString("\r\n表达式已清空\r\n> ");
}
// 添加字符到表达式
void AddToExpression(char ch) {
size_t len = strlen(expression);
if (len >= MAX_EXPR_LEN - 1) {
UART1_SendString("\r\n错误:表达式过长\r\n");
return;
}
// 只允许数字、运算符和小数点
if (isdigit(ch) || ch == '+' || ch == '-' || ch == '*' || ch == '/' ||
ch == '(' || ch == ')' || ch == '.' || ch == ' ') {
expression[len] = ch;
expr_valid = 1;
calculated = 0; // 新输入后需要重新计算
UART1_SendChar(ch); // 回显
}
else if (ch == '\r' || ch == '\n') {
UART1_SendString("\r\n> ");
}
}
// 获取运算符优先级
static int GetPriority(char op) {
switch (op) {
case '+':
case '-':
return PRIO_ADD_SUB;
case '*':
case '/':
return PRIO_MUL_DIV;
case '(':
case ')':
return PRIO_HIGHEST;
default:
return PRIO_LOWEST;
}
}
// 执行二元运算
static float Calculate(float a, float b, char op, CalcError *error) {
switch (op) {
case '+':
return a + b;
case '-':
return a - b;
case '*':
return a * b;
case '/':
if (fabs(b) < 1e-6f) {
*error = CALC_DIV_BY_ZERO;
return 0.0f;
}
return a / b;
default:
*error = CALC_SYNTAX_ERROR;
return 0.0f;
}
}
// 表达式求值
CalcError EvaluateExpression(const char* expr, float* result) {
float num_stack[STACK_SIZE];
char op_stack[STACK_SIZE];
int num_top = -1, op_top = -1;
float current_num = 0.0f;
float decimal_place = 0.0f;
char last_char = 0;
int negative = 0;
CalcError error = CALC_OK;
if (expr == NULL || result == NULL) {
return CALC_SYNTAX_ERROR;
}
while (*expr) {
// 跳过空格
if (*expr == ' ') {
expr++;
continue;
}
// 处理数字
if (isdigit(*expr) || *expr == '.') {
current_num = 0.0f;
decimal_place = 0.0f;
negative = 0;
// 处理负号
if (last_char == '-' && (op_top >= 0 && op_stack[op_top] == '-')) {
negative = 1;
op_top--; // 移除负号运算符
}
// 整数部分
while (isdigit(*expr)) {
current_num = current_num * 10 + (*expr - '0');
expr++;
}
// 小数部分
if (*expr == '.') {
expr++;
decimal_place = 0.1f;
while (isdigit(*expr)) {
current_num += (*expr - '0') * decimal_place;
decimal_place *= 0.1f;
expr++;
}
}
// 应用负号
if (negative) {
current_num = -current_num;
}
// 数字入栈
if (num_top < STACK_SIZE - 1) {
num_stack[++num_top] = current_num;
}
else {
return CALC_STACK_OVERFLOW;
}
last_char = '0'; // 标记最后处理的是数字
continue;
}
// 处理运算符
if (*expr == '+' || *expr == '-' || *expr == '*' || *expr == '/') {
// 处理负号特殊情况
if (*expr == '-' && (last_char == 0 || last_char == '(' ||
last_char == '+' || last_char == '-' || last_char == '*' || last_char == '/')) {
// 作为负号处理,推入特殊标记
if (op_top < STACK_SIZE - 1) {
op_stack[++op_top] = 'n'; // 'n' 表示负号
}
else {
return CALC_STACK_OVERFLOW;
}
last_char = *expr;
expr++;
continue;
}
// 处理高优先级运算符
while (op_top >= 0 && op_stack[op_top] != '(' &&
GetPriority(op_stack[op_top]) >= GetPriority(*expr)) {
if (num_top < 1) {
return CALC_SYNTAX_ERROR;
}
float b = num_stack[num_top--];
float a = num_stack[num_top--];
char op = op_stack[op_top--];
num_stack[++num_top] = Calculate(a, b, op, &error);
if (error != CALC_OK) {
return error;
}
}
if (op_top < STACK_SIZE - 1) {
op_stack[++op_top] = *expr;
}
else {
return CALC_STACK_OVERFLOW;
}
last_char = *expr;
expr++;
continue;
}
// 处理左括号
if (*expr == '(') {
if (op_top < STACK_SIZE - 1) {
op_stack[++op_top] = *expr;
}
else {
return CALC_STACK_OVERFLOW;
}
last_char = *expr;
expr++;
continue;
}
// 处理右括号
if (*expr == ')') {
while (op_top >= 0 && op_stack[op_top] != '(') {
if (num_top < 1) {
return CALC_SYNTAX_ERROR;
}
float b = num_stack[num_top--];
float a = num_stack[num_top--];
char op = op_stack[op_top--];
num_stack[++num_top] = Calculate(a, b, op, &error);
if (error != CALC_OK) {
return error;
}
}
if (op_top < 0 || op_stack[op_top] != '(') {
return CALC_PAREN_MISMATCH;
}
op_top--; // 弹出左括号
last_char = *expr;
expr++;
continue;
}
// 无效字符
return CALC_INVALID_CHAR;
}
// 处理剩余的负号
while (op_top >= 0 && op_stack[op_top] == 'n') {
if (num_top < 0) {
return CALC_SYNTAX_ERROR;
}
num_stack[num_top] = -num_stack[num_top];
op_top--;
}
// 处理剩余运算符
while (op_top >= 0) {
if (op_stack[op_top] == '(') {
return CALC_PAREN_MISMATCH;
}
if (num_top < 1) {
return CALC_SYNTAX_ERROR;
}
float b = num_stack[num_top--];
float a = num_stack[num_top--];
char op = op_stack[op_top--];
num_stack[++num_top] = Calculate(a, b, op, &error);
if (error != CALC_OK) {
return error;
}
}
if (num_top != 0) {
return CALC_SYNTAX_ERROR;
}
*result = num_stack[0];
return CALC_OK;
}
// 按键1处理函数 - 计算表达式
void ProcKeyOne(void) {
// 按键消抖
Delay_ms(20);
if (GPIO_ReadInputDataBit(KEYONE_GPIO_PORT, KEYONE_PIN) == Bit_RESET) {
LED_Toggle(); // 指示按键按下
if (!expr_valid) {
UART1_SendString("\r\n错误:没有有效表达式\r\n");
return;
}
CalcError error = EvaluateExpression(expression, &result);
if (error == CALC_OK) {
calculated = 1;
char buffer[32];
snprintf(buffer, sizeof(buffer), "\r\n结果: %.6f\r\n> ", result);
UART1_SendString(buffer);
}
else {
const char *error_msg = "";
switch (error) {
case CALC_SYNTAX_ERROR: error_msg = "语法错误"; break;
case CALC_DIV_BY_ZERO: error_msg = "除零错误"; break;
case CALC_STACK_OVERFLOW: error_msg = "栈溢出"; break;
case CALC_PAREN_MISMATCH: error_msg = "括号不匹配"; break;
case CALC_INVALID_CHAR: error_msg = "无效字符"; break;
default: error_msg = "未知错误";
}
char buffer[64];
snprintf(buffer, sizeof(buffer), "\r\n错误: %s\r\n> ", error_msg);
UART1_SendString(buffer);
}
// 等待按键释放
while (GPIO_ReadInputDataBit(KEYONE_GPIO_PORT, KEYONE_PIN) == Bit_RESET);
LED_Toggle(); // 恢复LED状态
}
}
// 主函数
int main(void) {
// 初始化外设
Init_Peripherals();
// 启动信息
UART1_SendString("\r\n简易计算器已启动\r\n");
UART1_SendString("使用说明:\r\n");
UART1_SendString("1. 通过串口输入表达式 (如: 3.14*2+5)\r\n");
UART1_SendString("2. KEY1 = 计算结果\r\n");
UART1_SendString("3. 清空表达式: 输入'c'并按回车\r\n");
UART1_SendString("> ");
// 主循环
while (1) {
// 处理串口输入
if (UART1_GetRxFlag()) {
char ch = UART1_GetChar();
// 清空命令
if (ch == 'c' || ch == 'C') {
ClearExpression();
}
else {
AddToExpression(ch);
}
}
// 处理按键
ProcKeyOne();
// 系统空闲处理
__WFI(); // 进入低功耗模式
}
}
// 系统滴答定时器中断处理函数
void SysTick_Handler(void) {
// 系统心跳处理
static u32 tick = 0;
if (tick++ >= 1000) {
tick = 0;
// 可添加周期任务
}
}
```
## 头文件实现
### Main.h
```c
#ifndef __MAIN_H
#define __MAIN_H
// 包含必要的标准库头文件
#include <stdint.h>
// 系统时钟频率定义
#define SYSCLK_FREQ_72MHz 72000000
// 函数声明
void Init_Peripherals(void);
#endif /* __MAIN_H */
```
### DataType.h
```c
#ifndef __DATATYPE_H
#define __DATATYPE_H
// 自定义数据类型定义
typedef uint8_t u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef int8_t s8;
typedef int16_t s16;
typedef int32_t s32;
#endif /* __DATATYPE_H */
```
### UART1.h
```c
#ifndef __UART1_H
#define __UART1_H
#include "DataType.h"
// 函数声明
void UART1_Init(u32 baudrate);
void UART1_SendChar(char ch);
void UART1_SendString(const char *str);
char UART1_GetChar(void);
u8 UART1_GetRxFlag(void);
#endif /* __UART1_H */
```
### KeyOne.h
```c
#ifndef __KEYONE_H
#define __KEYONE_H
#include "stm32f10x.h"
#include "DataType.h"
// 按键硬件定义
#define KEYONE_GPIO_PORT GPIOA
#define KEYONE_PIN GPIO_Pin_0
// 函数声明
void KeyOne_Init(void);
#endif /* __KEYONE_H */
```
### ProcKeyOne.h
```c
#ifndef __PROCKEYONE_H
#define __PROCKEYONE_H
// 函数声明
void ProcKeyOne(void);
#endif /* __PROCKEYONE_H */
```
## 代码功能说明
### 1. 系统架构
- **硬件抽象层**:通过各外设头文件封装硬件操作
- **核心计算模块**:实现表达式解析和计算
- **用户接口**:通过UART进行输入输出
- **按键控制**:使用KEY1触发计算
### 2. 核心功能
- **表达式解析**:支持加减乘除和小数运算
- **括号处理**:实现括号优先级
- **负数支持**:处理表达式开头的负号
- **错误检测**:除零错误、括号不匹配等
- **栈溢出保护**:防止表达式过长导致系统崩溃
### 3. 关键算法
- **双栈算法**:使用操作数栈和运算符栈实现表达式求值
- **浮点数解析**:通过`decimal_place`变量处理小数部分
- **运算符优先级**:正确处理`*/`高于`+-`的优先级
- **负号识别**:区分减号和负号
### 4. 资源管理
- **内存优化**:固定大小的表达式缓冲区和栈空间
- **低功耗设计**:主循环中使用`__WFI()`进入低功耗模式
- **实时响应**:通过SysTick定时器提供系统心跳
### 5. 用户交互
- **串口命令**:
- 输入表达式:`3.14 * (2 + 5)`
- 清空命令:`c`或`C`
- **按键功能**:
- KEY1:计算当前表达式
- **状态指示**:
- LED闪烁表示按键按下
- 串口反馈计算结果或错误信息
## 实现原理
### 表达式求值算法
使用经典的双栈算法(操作数栈和运算符栈):
1. **操作数栈**:存储解析出的数字
2. **运算符栈**:存储运算符和括号
算法流程:
1. 从左到右扫描表达式
2. 遇到数字时解析完整数值并入操作数栈
3. 遇到运算符时与栈顶运算符比较优先级:
- 当前优先级低:弹出栈顶运算符并计算
- 当前优先级高:运算符入栈
4. 遇到左括号直接入栈
5. 遇到右括号时弹出运算符直到左括号
6. 表达式扫描完后处理栈中剩余运算符
### 浮点数解析
使用`decimal_place`变量处理小数部分:
1. 遇到小数点前:解析整数部分
2. 遇到小数点:设置`decimal_place = 0.1`
3. 小数点后:每解析一位乘以当前`decimal_place`并累加,然后`decimal_place *= 0.1`
### 按键处理
采用消抖处理和状态指示:
1. 检测到按键按下
2. 延时20ms消抖
3. 再次确认按键状态
4. LED状态切换指示按键按下
5. 执行计算功能
6. 等待按键释放后恢复LED状态
## 使用示例
1. 通过串口输入表达式:`3.14 * (2 + 5)`
2. 按下KEY1计算
3. 串口输出结果:`结果: 21.980000`
4. 清空表达式:输入`c`并按回车
## 优化建议
1. 增加LCD显示支持
2. 添加更多数学函数(sin, cos, sqrt等)
3. 实现表达式历史记录
4. 添加更多按键功能(如退格键)
5. 使用DMA优化串口传输
阅读全文
相关推荐

















