文章目录
前言
之前的博客里介绍了我开发一个基于STM32的计算器的全部过程,包括下位机的开发以及上位机开发,但是这个计算器仍然存在许多的缺点。
1.无法进行混合运算,只可以进行两个数的基本四则运算
2.没有开方,CE等功能
3.计算逻辑考虑复杂,可以有更简单的实现方式
4.由于发送数据方式的问题,最多只能计算一个长度为8的表达式(包括计算符号)
这篇文章主要介绍我们计算器2.0的开发过程以及对一些关键算法逻辑的解释,计算器2.0能够实现复杂的多项式运算,判断运算优先级,并且增加了开方运算,百分号等功能。
1、下位机(STM32)实现计算逻辑
下位机用来完成我们的计算,他的计算逻辑包括如下几个部分:数据存储,运算优先级判断以及数据计算,下面分成这三块来介绍我的算法。
1.1 数据存储
在计算器1.0中,上位机发送数据的方式是一次性发送所有数据,这样的发送方式会存在一个问题就是当我们输入的计算表达式过长时,无法完全发送,最多只能发送8位数据。所以在计算器2.0版本中,采用一位一位发送数据的方式,这样我们计算表达式的位数就不会受到限制了。
根据这种发送数据的方式,我们使用了另外一种存储数据的方式,将数据和计算字符按顺序分别存放在两个数组中。首先设置标志位(temp_flag)用于判断是否有数据输入,而不是结束标志(0x0d 0x0a),当收到数据发送完成的结束标志位后,我们会对之前存储的数据进行运算,这个后面会说。
对上位机发送过来的每个数据首先需要进行判断是数值还是符号,判断函数如下:
//先判断输入是否为计算符号,若是计算符号,记录计算方式以及符号的位置
//存储在一个结构体save_ComputeInfo中
int find_ComputeWay(u8 sign)
{
if(sign == '+')
{
return 1;
}
if(sign == '-')
{
return 2;
}
if(sign == '*')
{
return 3;
}
if(sign == '/')
{
return 4;
}
return 0;
}
way = find_ComputeWay(USART_RX_BUF[len-1]);
if(way){
if(char_idx<100){
save_ComputeInfo[char_idx].compute_way=way;
save_ComputeInfo[char_idx].compute_location=len-1;
char_idx++;
}
else{
char_idx=0;}
}
如果不是计算符号,我们就开始我们的数值存储过程,这里我们设置了一个符号标志位dot_flag用于判断输入的数值是否为小数,在没有小数点输入前,data[i]=data[i]*10+USART_RX_BUF,但是一旦我们输入了小数点,标志位就会被置一,data[i]的计算公式就变成了data[i]=data[i]+USART_RX_BUF/ten,小数后面每增加一位,所除的数ten就会乘十。每个数值输入的结束是由计算符标志的,当一个计算符输入后,用于计算数值个数的num_idx就会自加1,下次输入的数值就会被存储到数组的下一个位置中。
同时,为了能够实现开方运算,设置了一个开方标志位sqrt_flag,当输入代表开方运算(这里我将其设置为s)的符号后,该标志位就会被置一,之后当一个数值输入完成如果这个标志位为1,就会对这个数值进行开方运算。
if(48<=USART_RX_BUF[len-1]&USART_RX_BUF[len-1]<=57&dot_flag==0)
{
data[num_idx]=data[num_idx]*10+(USART_RX_BUF[len-1]-48);
}
else if(USART_RX_BUF[len-1]==37)
{
data[num_idx]/=100;
dot_flag=0;
ten=10;
}
else if(USART_RX_BUF[len-1]==115)
{
sqrt_flag=1;
}
else if(USART_RX_BUF[len-1]==43|USART_RX_BUF[len-1]==45|USART_RX_BUF[len-1]==42|USART_RX_BUF[len-1]==47){
if(sqrt_flag==1)
{
data[num_idx]=sqrt(data[num_idx]);
}
num_idx++;
dot_flag=0;
sqrt_flag=0;
ten=10;
}
else if(dot_flag==1){
data[num_idx]=data[num_idx]+(USART_RX_BUF[len-1]-48)/ten;
ten=ten*10;
}
else if(USART_RX_BUF[len-1]==46)
{
dot_flag=1;
}
}
temp_flag=0;
要注意:在对输入判断结束后,需要对temp_flag标志位清0,以便于用于下次输入的判断。
1.2 运算优先级判断
运算的优先级顺序,同样使用一个数组进行存储,首先先按照顺序初始化一个存储计算优先级顺数的数组,之后遍历之前存储计算符号的种类以及位置的结构体并将其与按照优先级数组索引出的计算符号进行比较,如果是除号或者乘号则将他们的计算顺数与加减符号的计算位置进行交换。
void ChangeComputeSeq(struct character save_ComputeInfo[],u8 priority[],u8 char_idx,u8 priority_idx)
{
int i;
u8 temp;
for(i=0;i<priority_idx;i++)
{
if(save_ComputeInfo[priority[i]].compute_way==1|save_ComputeInfo[priority[i]].compute_way==2)
{
temp = priority[priority_idx];
priority[priority_idx] = priority[i];
priority[i] = temp;
}
}
}
for(i=0;i<char_idx;i++)
{
priority[priority_idx] = i;
if(save_ComputeInfo[i].compute_way==3|save_ComputeInfo[i].compute_way==4)
{
ChangeComputeSeq(save_ComputeInfo,priority,char_idx,priority_idx);
}
priority_idx++;
}
1.3 数据计算
数据计算前需要判断是否有负数存在,判断的方法是,如果在乘号或者除号后的相邻位置是否有减号,如果有,则将对应位置相邻两个数据相减,并将之后的数据向前移动一位,存放计算符号的数组也要向前移动一位,将负号覆盖掉,因为我们已经在存放数值的数组中存储了我们输入的负数了。
好了,在处理好我们的数值后,我们便可以开始计算了,我们需要根据之前存储运算顺序的数组计算,并且需要设置一个计算标志位为了之后能够能够使用上一次计算的结果,数据计算代码如下:
if(USART_RX_STA&0x8000)
{
//判断是否有负号,是否需要进行开方运算
for(i=0;i<char_idx-1;i++)
{
if((save_ComputeInfo[i].compute_way==3)&(save_ComputeInfo[i+1].compute_way==2)&(save_ComputeInfo[i+1].compute_location-save_ComputeInfo[i].compute_location==1))
{
Sforward(save_ComputeInfo,i,char_idx);
forward(data,i,num_idx);
char_idx--;
num_idx--;
}
if((save_ComputeInfo[i].compute_way==4)&(save_ComputeInfo[i+1].compute_way==2)&(save_ComputeInfo[i+1].compute_location-save_ComputeInfo[i].compute_location==1))
{
Sforward(save_ComputeInfo,i,char_idx);
forward(data,i,num_idx);
char_idx--;
num_idx--;
}
}
if(sqrt_flag==1)
{
data[num_idx]=sqrt(data[num_idx]);
}
for(i=0;i<char_idx;i++)
{
priority[priority_idx] = i;
if(save_ComputeInfo[i].compute_way==3|save_ComputeInfo[i].compute_way==4)
{
ChangeComputeSeq(save_ComputeInfo,priority,char_idx,priority_idx);
}
priority_idx++;
}
for(i=0;i<priority_idx-1;i++)
{
if(compute_flag[priority[i]]==0&compute_flag[priority[i]+1]==0)
{
compute_flag[priority[i]]=1;
compute_flag[priority[i]+1]=1;
result=temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,data[priority[i]],data[priority[i]+1]);
}
else if(compute_flag[priority[i]]==1&compute_flag[priority[i]+1]==0)
{
compute_flag[priority[i]+1]=1;
result=temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,temp[priority[i]],data[priority[i]+1]);
}
else if(compute_flag[priority[i]]==0&compute_flag[priority[i]+1]==1)
{
compute_flag[priority[i]]=1;
result=temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,data[priority[i]],temp[priority[i]+1]);
}
else
{
result = temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,temp[priority[i]],temp[priority[i]+1]);
}
}
//最后一步运算
i=priority_idx-1;
if(compute_flag[priority[i]]==0&compute_flag[priority[i]+1]==0)
{
compute_flag[priority[i]]=1;
compute_flag[priority[i]+1]=1;
result=temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,data[priority[i]],data[priority[i]+1]);
}
else if(compute_flag[priority[i]]==1&compute_flag[priority[i]+1]==0)
{
compute_flag[priority[i]+1]=1;
result=temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,result,data[priority[i]+1]);
}
else if(compute_flag[priority[i]]==0&compute_flag[priority[i]+1]==1)
{
compute_flag[priority[i]]=1;
result=temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,data[priority[i]],result);
}
else
{
if(priority[priority_idx-1]<priority[priority_idx-2]){
result = temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,temp[priority[i]],result);
}
else if(priority[priority_idx-1]>priority[priority_idx-2])
{
result = temp[priority[i]] = temp[priority[i]+1] = compute(save_ComputeInfo[priority[i]].compute_way,result,temp[priority[i]+1]);
}
}
printf("%f",result);
ClearData(data,max-1);
ClearData(temp,max-1);
for(i=0;i<max-1;i++)
{
compute_flag[i]=0;
}
USART_RX_STA=0;
char_idx=0;
num_idx=0;
priority_idx=0;
ten=10;
dot_flag = 0;
sqrt_flag=0;
切记:最后要将使用过的标志位和下表索引复位!
2、上位机开发
和计算器1.0相比,新增了如下功能:能够进行开方运算,可以清除本次输入,以及%。除此以外和计算器1.0不同的地方还是数据发送的方式。
2.1 数据发送
其中特别要注意的地方是没发送一个数据要设置一个延时,不然单片机没法响应每次中断,具体代码如下:
void sleep(unsigned int msec)
{
QTime reachTime=QTime::currentTime().addMSecs(msec);
while (QTime::currentTime()<reachTime) {
QCoreApplication::processEvents(QEventLoop::AllEvents,100);
}
}
//发送数据
for(int i=0;i<sendData.length();i++)
{
str = QString(sendData[i]);
sendData_2 = str.toLatin1();
sleep(10);
qDebug() << "serial.write()"<<serial->write(sendData_2);
qDebug() << "sendData_2 = " << sendData_2<<endl;
}
str=QString(0x0d);
sendData_2 = str.toLatin1();
serial->write(sendData_2);
str=QString(0x0a);
sendData_2 = str.toLatin1();
serial->write(sendData_2);
2.2 新增按钮功能
2.2.1 开方运算
void Widget::on_char_sqrt_clicked()
{
if(ui->char_open->text() == tr("关")){
ui->lineEdit->setReadOnly(false);
QString component;
int component_len;
component = ui->lineEdit->displayText();
component_len = component.length();
// qDebug()<<component.length();
//首先判断显示器上显示的位数是否为0
if(component_len != 0)
{
if(component[component_len-1]=="+"|component[component_len-1]=="-"|\
component[component_len-1]=="*"|component[component_len-1]=="/")
{
ui->lineEdit->setText(ui->lineEdit->displayText()+"s");}
else{
}
}
else
{
ui->lineEdit->setText(ui->lineEdit->displayText()+"s");
}
if(result!="")
{
ui->lineEdit->setText("");
ui->lineEdit->setText("s");
result = "";
}
ui->lineEdit->setReadOnly(true);
}
}
2.2.2 CE
void Widget::on_char_CE_clicked()
{
if(ui->char_open->text() == tr("关")&result==""){
ui->lineEdit->setReadOnly(false);
// qDebug()<<ui->lineEdit->displayText();
QString component = ui->lineEdit->displayText();
int i;
int component_length = component.length();
for(i=component_length-1;i>-1;i--)
{
if(component[i]=="+"|component[i]=="-"|component[i]=="*"|component[i]=="/")
{
component.replace(i+1,component_length-i-1,"");
ui->lineEdit->setText(component);
break;
}
}
qDebug()<<"i:"<<i<<endl;
if(i==-1)
{
ui->lineEdit->clear();
}
ui->lineEdit->setReadOnly(true);
}
}
2.2.3 %
void Widget::on_char_37_clicked()
{
if(ui->char_open->text() == tr("关")){
ui->lineEdit->setReadOnly(false);
QString component;
int component_len;
component = ui->lineEdit->displayText();
component_len = component.length();
// qDebug()<<component.length();
//首先判断显示器上显示的位数是否为0
if(component_len != 0)
{
if(component[component_len-1]=="+"|component[component_len-1]=="-"|\
component[component_len-1]=="*"|component[component_len-1]=="/")
{
}
else{
ui->lineEdit->setText(ui->lineEdit->displayText()+"%");
}
}
if(result!="")
{
ui->lineEdit->setText("");
ui->lineEdit->setText(result+"%");
result = "";
}
ui->lineEdit->setReadOnly(true);
}
}
总结
以上就是这篇博客的全部内容了,主要是在之前开发的计算器1.0的基础上的改进,修正了许多的不足之处,如果大家对文章内容有问题,欢迎大家在评论区讨论,文章最后会贴上我的源代码以及最后打包好的exe文件。
这里是我的源代码供大家参考:
Qt源代码:
链接:https://pan.baidu.com/s/1QiKZuucHFUXEfSOf2SgEiA
提取码:0d1m
STM32源代码:
链接:https://pan.baidu.com/s/1DQ58EFI7OwMwG541JXrGrA
提取码:n0l6
生成的exe文件:
链接:https://pan.baidu.com/s/1t8v4vxk19KLQONb8BFzGmw
提取码:paoe