基于STM32的简易计算器proteus仿真设计(仿真+程序+设计报告+讲解视频)
仿真图proteus 8.9
程序编译器:keil 5
编程语言:C语言
设计编号:C0089
讲解视频:https://www.bilibili.com/video/BV1vn4y1Q7E2?t=30.3
1.主要功能
功能说明:
本次嵌入式课程设计综合实验的内容为基于STM32单片机的简易计算器仿真设计系统。完成LCD1602液晶显示、矩阵按键扫描、LCD1602显示等多项任务。
1、键盘模块电路,采用 4*4 矩阵式键盘作为输入电路;
2、LCD1602 液晶显示模块;
3、以 STM32单片机作为控制核心。
二、软件程序主要由三部分组成: 主程序、按键扫描程序和 LCD1602 显示程序。
三、 性能指标
(1) 用STM32单片机设计一个简易计算器, 并用 1602 液晶显示相应的数据。
(2) 可以进行简单的整数加减乘除运算,具有清零功能。
(3) 最大可以 9999*9999。
(4) 可以通过 proteus 仿真。
主要硬件设备:STM32F103R6单片机 矩阵按键 LCD1602
2. 仿真
打开仿真工程,双击proteus中的单片机,选择hex文件路径,然后开始仿真。
然后开始仿真。
加法验证:
减法验证:
除法验证:
乘法验证:
3. 程序
程序是用keil5 mdk版本打开的,如果打开有问题,核实下keil的版本。程序是标准库版本编写的,有注释可以结合讲解视频理解。
#include "stm32f10x.h"
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#define uchar unsigned char // 以后unsigned char就可以用uchar代替
#define uint unsigned int // 以后unsigned int 就可以用uint 代替
//=============================================================
//--- 1604 LCD驱动程序段 ---
#define LCD_RS(x) (x)?(GPIOC->ODR |= (1 << 8)):(GPIOC->ODR &=~(1 << 8))
#define LCD_EN(x) (x)?(GPIOC->ODR |= (1 << 9)):(GPIOC->ODR &=~(1 << 9))
#define LCD_PORT(x) GPIOC->ODR = (GPIOC->ODR & 0xFF00) | x
#define COM 0
#define DAT 1
/*********************************************************/
// 毫秒级的延时函数,time是要延时的毫秒数
/*********************************************************/
void DelayMs(uint time)
{
uint i,j; // 定义两个无符号整型变量i和j用于循环计数
for(i=0;i<time;i++) // 外层循环,根据time值进行迭代
for(j=0;j<200;j++); // 内层循环,每次迭代执行200次空操作,共同实现大约time毫秒的延迟
}
// 向LCD写入数据或命令的函数,rs为0时写命令,rs为1时写数据,dat是要写入的具体数据
void LCD_Write(char rs,char dat)
{
// for(int i=0;i<600;i++); // 这里原有一个延时循环被注释掉了,现在使用下面的DelayMs函数替代
DelayMs(1); // 引入一个1毫秒的延迟,可能是为了确保LCD有足够的时间处理上一条指令
if(0 == rs) LCD_RS(0); // 如果rs为0,则设置LCD的数据/命令选择线为命令模式
else LCD_RS(1); // 如果rs为1,则设置为数据模式
LCD_EN(1); // 使能LCD写入信号
LCD_PORT(dat); // 通过LCD_PORT函数向LCD发送dat数据
LCD_EN(0); // 写入完成后,关闭LCD使能信号
}
// 在指定位置写入单个字符到LCD,x决定列位置(0-3),y决定行位置。Data是要写入的字符
void LCD_Write_Char(char x,char y,char Data)
{
if(0 == x) LCD_Write(COM,0x80 + y); // 设置LCD地址指针到第一列
else if(1 == x) LCD_Write(COM,0xC0 + y); // 第二列
else if(2 == x) LCD_Write(COM,0x90 + y); // 第三列(假设存在)
else LCD_Write(COM,0xD0 + y); // 第四列(假设存在)
LCD_Write(DAT,Data); // 在设定的位置写入字符数据
}
// 在指定位置写入字符串到LCD,x决定列,y决定行,*s是字符串的指针
void LCD_Write_String(char x,char y,char *s)
{
if(0 == x) LCD_Write(COM,0x80 + y); // 设置LCD地址指针到第一列
else if(1 == x) LCD_Write(COM,0xC0 + y); // 第二列
else if(2 == x) LCD_Write(COM,0x90 + y); // 第三列(假设存在)
else LCD_Write(COM,0xD0 + y); // 第四列(假设存在)
while(*s) LCD_Write(DAT,*s++); // 循环写入字符串中的每个字符直到结束
}
// 清除LCD显示屏内容的函数
void LCD_Clear(void)
{
LCD_Write(COM,0x01); // 发送特定命令(0x01)到LCD以清屏
// for(int i=0;i<60000;i++); // 原有的延时循环被注释,使用下面的DelayMs代替
DelayMs(2); // 等待2毫秒让LCD完成清屏操作
}
void LCD_Init(void)
{
LCD_Write(COM,0x38); //--- 显示模式设置 ---
LCD_Write(COM,0x08); //--- 显示关闭 ---
LCD_Write(COM,0x06); //--- 显示光标移动设置 ---
LCD_Write(COM,0x0C); //--- 显示开及光标设置 ---
LCD_Clear();
}
void MyGPIO_Init(void)
{
GPIO_InitTypeDef MyGPIO;//定义GPIO结构体初始化变量
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC,ENABLE);//打开GPIOC外设时钟
MyGPIO.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 |
GPIO_Pin_4 | GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7 |
GPIO_Pin_8 | GPIO_Pin_9;
MyGPIO.GPIO_Speed = GPIO_Speed_50MHz;//设置响应速度
MyGPIO.GPIO_Mode = GPIO_Mode_Out_PP;//设置PC0~PC9为通用推挽输出
GPIO_Init(GPIOC,&MyGPIO);//调用GPIO初始化函数完成PC0~PC9引脚配置
}
unsigned char KEYTAB[] =
{
0xD7, //0
0xEE,0xDE,0xBE, //1,2,3
0xED,0xDD,0xBD, //4,5,6
0xEB,0xDB,0xBB, //7,8,9
0x7E,0x7D,0x7B,0x77, //A,b,C,d
0xE7,0xB7, //E,F
};
#define ROWOUT_COLIN() {
GPIOB->CRL = 0x44443333;
GPIOB->ODR = 0xF0;
}
#define ROWIN_COLOUT() {
GPIOB->CRL = 0x33334444;
GPIOB->ODR = 0x0F;
}
#define KEY_PORT() GPIOB->IDR
#define KEY_ROW (KEY_PORT() & 0xF0)
#define KEY_COL (KEY_PORT() & 0x0F)
int KeydlyCnt ;
int KeyBoard4X4_Scan(void)
{
int i,Key = 0xFF;
//读取列线引脚电平状态是否有键按下
if((0xF0 != KEY_ROW) && (999999 != KeydlyCnt ) && (++KeydlyCnt > 100))
{
if(0xF0 != KEY_ROW)//判断是否真得有键按下
{
KeydlyCnt = 999999;
Key = KEY_ROW;//获取列线的状态数值 KEY:1110 0000 0XE0
ROWIN_COLOUT();//配置PB0~7为行线输入列线输出 KEY:0000 1110 0X0E
Key |= KEY_COL; //获取行线状态并与列线状态数值合并 Key 1110 1110 0xee
for(i=0;i<sizeof(KEYTAB);i++)//查KEYTAB表是否存储该按键编码
{
if(KEYTAB[i] == Key)break;
}
//将编码值转换为数字代码存储到Key变量中
if(i >= sizeof(KEYTAB))i = 0xFF;else Key = i;//key =1
if(Key < sizeof(KEYTAB))
{
if(Key < 10)Key += '0';
else if(10 == Key)Key = '+';
else if(11 == Key)Key = '-';
else if(12 == Key)Key = '*';
else if(13 == Key)Key = '/';
else if(14 == Key)Key = 'C';
else if(15 == Key)Key = '=';
}
ROWOUT_COLIN();//配置PB0~7为行线输出列线输入引脚
}
}
else if(0xF0 == KEY_ROW)
{
if(999999 == KeydlyCnt )KeydlyCnt = 0;
}
return Key;// key =1
}
//--- LCD模块上显示数字函数 ---
char LCD_DisplayNum(int x, int y, int val)
{
int i; // 循环计数器
int m, nflag; // m为辅助计数器,nflag标记数值是否为负
char buff[10 + 1]; // 用于存储数字字符的缓冲区,最大支持10位数字加上结束符'�'
nflag = 0; // 初始化标志位,表示数值非负
if (val < 0) nflag = 1; // 如果输入的数值为负,则设置nflag为1
val = abs(val); // 将输入的数值转换为其绝对值以便处理
// 初始化缓冲区,填充空格字符并设置结束符
for (i = 0; i < sizeof(buff); i++) buff[i] = ' ';
buff[sizeof(buff) - 1] = '�';
i = sizeof(buff) - 2; // 设置缓冲区的起始写入位置(从后向前)
// 将数值转换为字符形式存入缓冲区
while (val)
{
buff[i--] = val % 10 + '0'; // 取当前数值的最后一位并转换为字符
val /= 10; // 数值除以10进入下一位
if (0 == i) break; // 如果已到达缓冲区的最前端,则停止
}
// 如果原始数值是负数,在适当位置插入负号
if (nflag) buff[i--] = '-';
// 移动缓冲区中的字符,去除前导空格
for (m = 0; m <= i; m++)
{
for (nflag = 1; nflag < sizeof(buff); nflag++)
buff[nflag - 1] = buff[nflag]; // 缓冲区内的字符向前移动
}
// 将处理后的数字字符串显示在LCD上指定位置
LCD_Write_String(x, y, buff);
// 返回实际显示的数字字符长度(不包括前导空格)
return strlen(buff);
}
int ch,act,i,m;
long num1,num2,result;
char str1[12],str2[2];
int main(void)
{
MyGPIO_Init(); // 初始化自定义的GPIO设置
LCD_Init(); // 初始化LCD显示屏
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 打开GPIOB端口的外设时钟
ROWOUT_COLIN(); // 配置GPIOB的0到7引脚为行输出和列输入,用于键盘扫描
LCD_Write_Char(1, 1, '0'); // 在LCD第一行显示该数字键
while(1) // 无限循环,等待并处理按键事件
{
ch = KeyBoard4X4_Scan(); // 扫描4x4键盘并获取按键值
if(0xFF != ch) // 如果获取到了有效的按键(0xFF表示未按下键)
{
if((ch == '+') || (ch == '-') || (ch == '*') || (ch == '/')) // 检查是否为运算符
{
LCD_Write_Char(0, i++, ch); // 在LCD的第一行显示获取到的运算符
act = ch; // 记录当前运算符
num1 = atof(str1); // 将之前输入的数字字符串str1转换为浮点数并赋值给num1
memset(str1, 0, 12); // 清空str1准备接收新的输入
result += num1; // 累加结果
}
else if(ch == 'C') // 如果是清除键
{
i = act = 0; // 重置索引和当前运算符
num1 = num2 = result = 0; // 重置所有数值
memset(str1, 0, 12); // 清空输入字符串
LCD_Write(COM, 0x01); // 发送清屏指令给LCD
}
else if(ch == '=') // 如果是等于号
{
LCD_Write_Char(1, 0, ch); // 在LCD第二行显示等于号
num2 = atof(str1); // 将输入的数字字符串转换为浮点数num2
// 根据之前记录的运算符执行相应的计算
switch(act)
{
case '+': result += num2; break;
case '-': result -= num2; break;
case '*': result *= num2; break;
case '/': result /= num2; break;
}
memset(str1, 0, 12); // 清空输入字符串
memset(str2, 0, 2); // 清空辅助字符串
// 处理并显示计算结果
i = 1;
if(result < 1) LCD_Write_Char(1, i++, '0'); // 如果结果小于1,先显示0
else
{
m = (int)result; // 取结果的整数部分
i += LCD_DisplayNum(1, i, m); // 显示整数部分,并更新显示位置
result -= m; // 从结果中减去已显示的整数部分,保留小数部分(如果有的话)
}
}
else // 对于其他数字键
{
LCD_Write_Char(0, i++, ch); // 在LCD第一行显示该数字键
sprintf(str2, "%c", ch); // 将按键字符格式化到str2
strcat(str1, str2); // 将str2附加到str1,累积输入的数字字符串
}
}
}
}
4. 设计报告
8586字设计报告,内容包括硬件设计、软件设计、调试、结论等。
0、常见使用问题及解决方法–必读!!!!
1、程序代码
2、Proteus仿真
3、功能要求
4、开题报告
5、设计报告
6、讲解视频
Altium Designer 安装破解
KEIL+proteus 单片机仿真设计教程
KEIL安装破解
Proteus元器件查找
Proteus安装
Proteus简易使用教程
单片机学习资料
相关数据手册
答辩技巧
设计报告常用描述
鼠标双击打开查找嘉盛单片机51 STM32单片机课程毕业设计.url
资料下载链接:
https://pan.baidu.com/s/1eiEeurqSY2hBktaMKal3GQ?pwd=6vam