完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
前言 本篇博客主要介绍自己开发一个计算器小项目的过程以及自己在开发过程中遇到的一些问题以及解决的方法。 基于STM32的计算器开发有如下需求: 1.操作界面在电脑端,用C++编写,只负责按键和显示; 2.计算部分在STM32端,用c语言编写,实现计算功能。 概括一下就是在电脑上编写一个上位机程序,主要负责发送数据,计算功能通过32实现。使用到的软件有keil5,QT,需要的基本知识有QT界面开发,C++编程基础,串口通讯等。 提示:此计算器可能存在一些bug和缺点,仅供大家学习参考。如果有新的想法以及问题,欢迎大家前来交流。 一、开发步骤 分为STM32和QT两部分完成,首先我们完成STM32的部分,在STM32上我们需要编写如下功能: 1.数据的接收与计算结果的发送 2.对接收数据的处理 QT上需要实现如下功能: 1.发送数据(相当于我们平时用的串口小程序) 2.接收计算结果并显示 明白了基本需求,我们接下来就开始一步一步的实现这些功能吧!!! 二、项目实现 1.STM32实现串口通信 STM32的串口通讯实现很简单,都是一个套路。网络上也有很多相关的资料,这里就不具体赘述,只简单的叙述一下串口初始化的步骤以及数据收发的操作。 首先我们需要初始化串口,基本步骤如下: 1.串口时钟使能,GPIO 时钟使能 2.复位串口 3.GPIO 端口模式的设置 4.串口参数初始化 5.中断设置初始化 6.使能串口 代码如下: //这里使用USART1作为我们的串口 void uart_init(u32 bound) { GPIO_InitTypeDef GPIO_InitStructure; USART_InitTypeDef USART_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; //串口时钟使能,GPIO 时钟使能 RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1| RCC_APB2Periph_GPIOA, ENABLE); //使能 USART1,GPIOA 时钟 //串口复位 USART_DeInit(USART1); //复位串口1 //GPIO 端口模式设置 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //ISART1_TX PA.9 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.9 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10; //USART1_RX PA.10 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入 GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化 GPIOA.10 //④串口参数初始化 USART_InitStructure.USART_BaudRate = bound; //波特率设置 USART_InitStructure.USART_WordLength = USART_WordLength_8b; //字长为 8 位 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_Rx | USART_Mode_Tx;//收发模式 USART_Init(USART1, &USART_InitStructure); //初始化串口 初始化好串口后,需要设置数据接收的结束标志位,这里我们设置输入的结束为“/n/r”,所有从上位机发来的数据必须以“/n/r”结尾,每接收一次数据单片机都会响应一次中断,所以我们的接收数据程序在中断程序中实现。 其中USART_RX_STA用于计算接收数据的位数,其中0-13位为有效数据位,14,15用于存储我们的结束标志位。 void USART1_IRQHandler(void) //串口1中断服务程序 { u8 Res; #if SYSTEM_SUPPORT_OS //如果SYSTEM_SUPPORT_OS为真,则需要支持OS. OSIntEnter(); #endif if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //接收中断(接收到的数据必须是0x0d 0x0a结尾) { Res =USART_ReceiveData(USART1); //读取接收到的数据 if((USART_RX_STA&0x8000)==0)//接收未完成 { if(USART_RX_STA&0x4000)//接收到了0x0d { if(Res!=0x0a)USART_RX_STA=0;//接收错误,重新开始 else USART_RX_STA|=0x8000; //接收完成了 } else //还没收到0X0D { if(Res==0x0d)USART_RX_STA|=0x4000; else { USART_RX_BUF[USART_RX_STA&0X3FFF]=Res ; USART_RX_STA++; if(USART_RX_STA>(USART_REC_LEN-1))USART_RX_STA=0;//接收数据错误,重新开始接收 } } } } 输出数据直接使用printf函数即可。 2.STM32的计算逻辑 首先我们从串口读到的数据都是数字或者计算符号的16进制ASCII代码,我们首先需要对ASCII进行转换才能得到我们所输入的数据。具体转换代码如下,change_to_dec1(u16 num_len)表示对整个数据(从下标0开始对数据进行转化),change_to_dec2(u16 num_len,u16 len)可以对数据中的某一段进行转化,num_len表示需要转换数据的长度,len表示截止位置到下标0的长度。 代码如下: //num_len表示需要转换数据的长度 u16 change_to_dec1(u16 num_len) { u16 change_result = 0; u16 t; for(t=0;t change_result += (USART_RX_BUF[t]-48)*pow(10,num_len-1-t); } //printf("rnrn"); //printf("前面的加数是%d",change_result); return change_result; } u16 change_to_dec2(u16 num_len,u16 len) { u16 change_result = 0,p = 0; u16 t; for(t=len-num_len;t change_result += (USART_RX_BUF[t]-48)*pow(10,num_len-1-p); p++; } //printf("rnrn"); //printf("后面的加数是%d",change_result); return change_result; } 能够对数据进行转换后,需要判断我们输入数据中符号(加减乘除包括小数点)的位置以及数据的位置,所以我对读取到的数据进行了遍历,先获取小数点的位置(小数点的ASCII码是0x2E) for(t=0;t if(USART_RX_BUF[t]==0x2E) //0x2E表示. { location[m]=t; m++; //printf("xiaoshu weizhi%d",t); } } 在记录完小数位置后,需要统计小数点的个数(num_2E),flag表示是否有小数计算,有小数则将标志位置1,代码如下: for(t=0;t<2;t++) { if(location[t]!=0) { //printf("小数点的位置是%u",location[t]); flag=1; num_2E++; } } 接下来,根据小数点数的个数的不同,分情况0,1,2三种情况考虑。将传入数据分为整数部分和小数部分进行转换,下面以计算加法为例。 首先进入一个判断是否存在小数计算,若存在小数计算,还要细分小数个数的位数(1,2),若小数点个数为1,则还要判断小数点是在计算符号前还是计算符号后,废话不多说,直接来看代码。 if(USART_RX_BUF[t]==0x2B & t!=0) //0x2B表示加号 { way = 1; //+ //存在小数计算 if(flag == 1) { //分为整数和小数部分计算,首先确定有几个小数 if(num_2E==1) { for(i=0;i<2;i++) { if(location!=0) { loc1 = location; } } if(loc1 if(USART_RX_BUF[0]==0x2D) { int_num_front_len = loc1-1; num_front_len = t-loc1-1; int_num_behind_len = len-t-1; int_num_front=change_to_dec2(int_num_front_len,loc1); num_front = change_to_dec2(num_front_len,t); int_num_behind = change_to_dec2(int_num_behind_len,len); minus_flag = 1; } else{ } break; } else if(loc1>t) { if(USART_RX_BUF[0]==0x2D) { int_num_front_len = t-1; int_num_behind_len = loc1-t-1; num_behind_len = len-loc1-1; int_num_front = change_to_dec2(int_num_front_len,t); int_num_behind = change_to_dec2(loc1-t-1,loc1); num_behind = change_to_dec2(num_behind_len,len); minus_flag = 1; } else{ int_num_front_len = t; int_num_behind_len = loc1-t-1; num_behind_len = len-loc1-1; int_num_front = change_to_dec1(int_num_front_len); int_num_behind = change_to_dec2(loc1-t-1,loc1); num_behind = change_to_dec2(num_behind_len,len); } break; } } else if(num_2E==2) { if(USART_RX_BUF[0]==0x2D) { loc1 = location[0]; loc2 = location[1]; int_num_front_len =loc1-1; num_front_len = t-loc1-1; int_num_behind_len = loc2-t-1; num_behind_len = len-loc2-1; int_num_front = change_to_dec2(int_num_front_len,loc1); num_front = change_to_dec2(num_front_len,t); int_num_behind = change_to_dec2(int_num_behind_len,loc2); num_behind = change_to_dec2(num_behind_len,len); minus_flag = 1; } else{ loc1 = location[0]; loc2 = location[1]; int_num_front_len =loc1; num_front_len = t-loc1-1; int_num_behind_len = loc2-t-1; num_behind_len = len-loc2-1; int_num_front = change_to_dec2(int_num_front_len,loc1); num_front = change_to_dec2(num_front_len,t); int_num_behind = change_to_dec2(int_num_behind_len,loc2); num_behind = change_to_dec2(num_behind_len,len); } } } //都为整数 else{ if(USART_RX_BUF[0]==0x2D) { int_num_front_len = t-1; int_num_behind_len = len-t-1; int_num_front = change_to_dec2(int_num_front_len,t); int_num_behind = change_to_dec2(int_num_behind_len,len); minus_flag = 1; } else{ int_num_front_len = t; int_num_behind_len = len-t-1; int_num_front = change_to_dec1(int_num_front_len); int_num_behind = change_to_dec2(int_num_behind_len,len); } break; } } 以上这段代码做的事情就是提取符号两边的整数和小数部分,便于接下来的计算。好了,在提取完数据后,我们来看看计算的部分,看看下面的代码吧。 这段代码中我们通过一个switch函数来判断是哪种计算方式(1 加法,2 减法,3 乘法,4 除法),minus_flag是一个用来判断第一个数据是否为负数的标志位,通过整数加上小数部分除以以10为底数小数部分长度为指数的小数即可还原出我们的原始数据,然后接下来就是简单粗暴的计算啦,其他计算方式也是差不多的处理方式,所以在这里就不重复写啦。 switch(way) { case 1: if(flag == 1) { if(minus_flag == 1) { _result = -(int_num_front + num_front/pow(10,num_front_len)+ (int_num_behind+num_behind/pow(10,num_behind_len)); } else{ _result = (int_num_front + num_front/pow(10,num_front_len)+ (int_num_behind+num_behind/pow(10,num_behind_len)); } printf("%f",_result); } else{ if(minus_flag==1) { result = -int_num_front + int_num_behind; } else{ result = int_num_front + int_num_behind; } printf("%d",result); } flag=0,num_2E=0,m=0; num_front_len=0,num_behind_len=0,int_num_front_len=0,int_num_behind_len=0; num_front=0,num_behind=0,int_num_front=0,int_num_behind=0; location[0]=0,location[1]=0,loc1=0,loc2=0; minus_flag=0; break; } 以上就是STM32程序中的主要内容。 3.Qt界面设计 Qt中的设计主要分为两部分,一部分是界面的实现,另外一部分是串口通信的设计部分。 3.1 界面的实现 界面的实现非常简单,直接进入.ui文件里,拖出我们想要的界面,然后再在各个按钮上添加自己需要的槽函数即可。 接下来我会对按钮的槽函数进行解释。分别有以下几类功能的按钮:数字按钮(用于输入数据),小数点按钮(为数据增加小数点),计算符号按钮(用于判断是哪种计算方式),清屏按钮,后退按钮(用于修改自己的输入)。 1.数字按钮 数据按钮相当于我们的键盘,要实现如下的逻辑:首先需要开机后,按下按钮才有反应,当显示屏没有数值时,按下数据按钮会在显示屏上增添按钮对应的数字,要注意如果0是第一个字符,再次按下0后,不可以在显示屏上再增加一个0。当我们计算完计算结果后,按下数字按钮会直接清屏,然后显示我们按下的按钮,具体代码如下,其中1-10代表数字1-9,0。 void MyComputer::on_pushButton1_1_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("1"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"1"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("1"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_2_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("2"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"2"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("2"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_3_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("3"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"3"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("3"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_4_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("4"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"4"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("4"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_5_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("5"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"5"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("5"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_6_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("6"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"6"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("6"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_7_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("7"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"7"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("7"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_8_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("8"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"8"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("8"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_9_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { ui->lineEdit->setText("9"); } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"9"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("9"); result = ""; } ui->lineEdit->setReadOnly(true); } } void MyComputer::on_pushButton1_10_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); if(ui->lineEdit->displayText()=="0") { } else{ if(ui->lineEdit->displayText().length()<8){ ui->lineEdit->setText(ui->lineEdit->displayText()+"0"); } } if(result!="") { ui->lineEdit->clear(); ui->lineEdit->setText("0"); result = ""; } ui->lineEdit->setReadOnly(true); } } 2.小数点按钮 必须得再开机后才能使用,当计算出结果后,此按钮应该处于冻结状态,无法使用。 void MyComputer::on_pushButton1_11_clicked() { if(ui->pushButton1_19->text() == tr("关")&result==""){ ui->lineEdit->setReadOnly(false); QString component; int component_len; component = ui->lineEdit->displayText(); component_len = component.length(); // qDebug()< if(component_len != 0) { if(component[component.length()-1]=="."|component[component.length()-1]=="+"| component[component.length()-1]=="-"|component[component.length()-1]=="*"|component[component.length()-1]=="/") {} else{ ui->lineEdit->setText(ui->lineEdit->displayText()+"."); } } ui->lineEdit->setReadOnly(true); } } 3.计算符号按钮 同样必须开机了以后才可以使用,输入的符号不可以和前一位符号相同,同时只有在计算结果为空的时候才可以输入符号。其中比较special的一点就是如果输入加(减)号后,输入减(加)号可以将前面的符号覆盖。 //+ void MyComputer::on_pushButton1_17_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); QString component; int component_len; component = ui->lineEdit->displayText(); component_len = component.length(); // qDebug()< if(component_len != 0) { if(component[component.length()-1]=="+") {} else{ ui->lineEdit->setText(ui->lineEdit->displayText()+"+"); } } if(component[component.length()-1]=="+") { component.replace(component_len-1,1,"-"); ui->lineEdit->setText(component); } if(result!="") { ui->lineEdit->setText(""); ui->lineEdit->setText(result+"+"); result = ""; } ui->lineEdit->setReadOnly(true); } } //- void MyComputer::on_pushButton1_16_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); QString component; int component_len; component = ui->lineEdit->displayText(); component_len = component.length(); // qDebug()< if(component[component.length()-1]=="-") {} else{ ui->lineEdit->setText(ui->lineEdit->displayText()+"-"); } if(component[component.length()-1]=="+") { component.replace(component_len-1,1,"-"); ui->lineEdit->setText(component); } if(result!="") { ui->lineEdit->setText(""); ui->lineEdit->setText(result+"-"); result = ""; } ui->lineEdit->setReadOnly(true); } } //* void MyComputer::on_pushButton1_14_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); QString component; int component_len; component = ui->lineEdit->displayText(); component_len = component.length(); // qDebug()< if(component_len != 0) { if(component[component.length()-1]=="*") {} else{ ui->lineEdit->setText(ui->lineEdit->displayText()+"*"); } } if(result!="") { ui->lineEdit->setText(""); ui->lineEdit->setText(result+"*"); result = ""; } ui->lineEdit->setReadOnly(true); } } // / void MyComputer::on_pushButton1_13_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); QString component; int component_len; component = ui->lineEdit->displayText(); component_len = component.length(); // qDebug()< if(component_len != 0) { if(component[component.length()-1]=="/") {} else{ ui->lineEdit->setText(ui->lineEdit->displayText()+"/"); } } if(result!="") { ui->lineEdit->setText(""); ui->lineEdit->setText(result+"/"); result = ""; } ui->lineEdit->setReadOnly(true); } } 4.清屏(AC)按钮 开机后才可以使用,按下后,能够清楚显示器上的所有内容。 void MyComputer::on_pushButton1_12_clicked() { if(ui->pushButton1_19->text() == tr("关")){ ui->lineEdit->setReadOnly(false); // qDebug()< ui->lineEdit->setText(""); result = ""; ui->lineEdit->setReadOnly(true); } } 5.后退(Backoff)按钮 开机后才能使用,仅在运算表达式输入时才可以使用,按下后,可以清除本次输入的字符。 void MyComputer::on_pushButton1_15_clicked() { if(ui->pushButton1_19->text() == tr("关")&result==""){ ui->lineEdit->setReadOnly(false); QString component,component_cpy; int component_len; component = ui->lineEdit->displayText(); component_len = component.length(); // qDebug()< if(component_len != 0) { // component[component_len-1]=' |