嵌入式计算器模块实现

嵌入式计算器模块规划

计算器混合算法解析
上面我们的算法理论已经完善, 我们只用给一个混合运算式, 计算器就可以帮助我们计算出结果.

但是存在一个痛点, 每次计算算式,都要重新编译程序, 所以我们想到了, 利用单片机, 读取用户输入的按键, 组成算式, 输入给机器, 这样我们就可以利用上面的算法, 再次计算出结果了.

大致流程如下:

image-20240624100125749

所以, 我们第一步做的,就是stm32利用矩阵按键识别键值

第二步,就是单片机把读取到的键值, 组合成算式

第三步,就是把算式传给算法,计算出结果

第四步, 就是把结果清零

拓展:第五步,可以把结果转换成语音播报

第一步: 矩阵按键,读取键值

(1)读取原理

首先我们要了解矩阵按键, 是干什么的, 顾名思义,矩阵按键,就是几行几列,依次排开的,按钮, 我们如果知道行,知道列,就可以锁定那个按键,所以我们只需要让单片机知道,我们按下的是哪一行,哪一列,那么单片机通过提前布局好的按键键值,就可以知道我们按下的是哪个按键了.

image-20240624101158298

(2)按键布局

如下图, 即为我们矩阵按键的键盘布局:

第一行 ( ) / *

第二行 1 2 3 +

第三行 4 5 6 -

第四行 7 8 9 Esc

第五行 ← 0 → =

image-20240624102450597

(3)按键原理图

矩阵按键原理图:

image-20240624103255572

(4)单片机io口识别原理

我们锁定行列, 此矩阵按键有五行,四列, 我们就先识别按下哪一列吧,然后后面再锁定行,这样就可以得到坐标了.

单说, 按下按键, 怎么io口怎么识别出按下按键呢?

举一个例子, 单片机io口,相当于装满水的水杯, 单片机会检测io口水杯, 是满的,还是空的. 用户按下按键,就相当于, 把水倒掉,那么我们就可以让单片机检测到按键.

image-20240624122114389

所以, 单片机检测io口,端口被称作输入口,顾名思义就是读取io口状态, 并把io口状态,反馈给单片机,那么水倒哪里了呢? 我们按下按键, 水就倒到了地下. 所以我们按键另一端按键接地是持续输出低电平的输出口.

<1>识别列

我们把上述的步骤, 复制四份, 我们就可以判断按下是哪一列了

image-20240624122801579

此时,四个列检测io口, 链接四个按键,按键另一端连接的是地, 这个地可以用io口输出低电平代替,方便后续矩阵键盘拓展.

image-20240624131512384

但是我们有四列, 五行, 所以, 每列都有,五个按钮, 我们紧接着, 再把他们排列起来.

image-20240624131141800

<2>识别行

此时, 我们通过判断 PA1,PA2,PA3,PA4那个io口变化了,就可以识别到是哪列按键按下了,

但是我们是矩阵按键,还要进行行的识别.

其实,识别方法同理,我们只需要把, io口类型进行反转,把列控制端口全反转成输出低电平, 现在检测io口状态的变成每行的端口,当按下第一行按钮时候,对应的端口就会变成低电平, 单片机检测到,就可以识别到行 如下图所示

image-20240624132339632

进行拓展后,就变成了矩阵按键,

image-20240624132534304

<3> 识别原理总结

我们识别原理就是,先赋予列检测io口,高电平,设置其端口类型为输入口,读取按键状态,按键另一端,是输出口,持续输出低电平.

当我们按下按键的时候, 输入口io口的高电平会通过按键,送到低电平,此时单片机检测到低电平, 就判定是哪一列的按键按下了.

此时列已经锁定了, 下面开始锁定行,此时按下仍然处于按下状态.

我们此时设置控制行的io口,为输入口,检测按键状态, 控制列的io口,设置为输出低电平,那么此时按键是按下状态, 对应的行io口,电平就会从高电平->低电平, 那么单片机就会检测到io口状态变化, 我们就可以锁定对应的行.

行和列已经锁定,那么我们通过计算就可以得到我们按下的是矩阵按键的哪一个按键了.

<4>代码实现

我们使用线性反转法的核心思想,就是锁定行和列的坐标, 通过反转io口类型,检测io口状态,根据按键按下的状态,进而锁定坐标.

为了更快的进入列的选择,我们把四列io口,全部设置成中断形式,对应的行io口,全部设置为推挽输出低电平, 四列io口的中断触发方式,设置成下降沿触发.

①锁定列

这样我们就可以很快的进行, 锁定列了;

配置 PA1 ,PA2, PA3, PA4 为下降沿触发中断

1.中断初始化
void Exti_key_config(void)
{
	//定义官方文档结构体
	EXTI_InitTypeDef   EXTI_InitStructure;
	GPIO_InitTypeDef   GPIO_InitStructure;
	NVIC_InitTypeDef   NVIC_InitStructure;

	//初始化
	//使能时钟 PA  AFIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);	
	
	//io口初始化, 设置//使能内部上拉
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;	
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
    /* Connect EXTI1 line to PA1 pin */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource1);
    /* Connect EXTI2 line to PA2 pin */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource2);
    /* Connect EXTI3 line to PA3 pin */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource3);
    /* Connect EXTI4 line to PA4 pin */
    GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource4);
	
	
	//中断端口的设置(外部中断线 1,2,3和4,  端口模式, 什么触发: 下降沿 , 开启等)
	/* Configure EXTI0 line */
	EXTI_InitStructure.EXTI_Line = EXTI_Line1 | EXTI_Line2 | EXTI_Line3 | EXTI_Line4;
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;  
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;
	EXTI_Init(&EXTI_InitStructure);

	//下面配置中断优先级
    /* Configure EXTI1 interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&NVIC_InitStructure);
    
    /* Configure EXTI2 interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_Init(&NVIC_InitStructure);
    
    /* Configure EXTI3 interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI3_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_Init(&NVIC_InitStructure);
    
    /* Configure EXTI4 interrupt */
    NVIC_InitStructure.NVIC_IRQChannel = EXTI4_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x0;
    NVIC_Init(&NVIC_InitStructure);
}	
2.触发中断

char key_number; // 1–4 (代表一到四列)

_Bool key_button_down; //代表按键按下

void EXTI1_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line1) != RESET)
    {
		delay(0x20000);	//中断服务函数当中 , 是不能用大延时的,快进		快出
        /* Your code goes here */
        key_number = 1;
		key_button_down = 1;
        //中断服务函数当中 , 是不能用大延时的,快进		快出
		delay(0x20000);	
        EXTI_ClearITPendingBit(EXTI_Line1);
    }
}

void EXTI2_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line2) != RESET)
    {
        //中断服务函数当中 是不能用大延时的,快进		快出
		delay(0x20000);	
        /* Your code goes here */
        key_number = 2;
		key_button_down = 1;
        //中断服务函数当中 , 是不能用大延时的,快进快出
		delay(0x20000);	
        EXTI_ClearITPendingBit(EXTI_Line2);
    }
}

void EXTI3_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line3) != RESET)
    {
        //中断服务函数当中 , 是不能用大延时的,快进快出
		delay(0x20000);	
        /* Your code goes here */
        key_number = 3;
		key_button_down = 1;
        //中断服务函数当中 , 是不能用大延时的,快进快出
		delay(0x20000);	
        EXTI_ClearITPendingBit(EXTI_Line3);
    }
}

void EXTI4_IRQHandler(void)
{
    if (EXTI_GetITStatus(EXTI_Line4) != RESET)
    {
        //中断服务函数当中 , 是不能用大延时的,快进快出
		delay(0x20000);	
        /* Your code goes here */
        key_number = 4;
		key_button_down = 1;
        //中断服务函数当中 , 是不能用大延时的,快进快出
		delay(0x20000);	
        EXTI_ClearITPendingBit(EXTI_Line4);
    }
}

3.锁定列数值
extern char key_number;
extern char chose_column;//选中的列
void find_column(void)
{
	if(key_number == 1)
	{
		chose_column = 1;
	}
	else
	if(key_number == 2)
	{
		chose_column = 2;
	}		
	else
	if(key_number == 3)
	{
		chose_column = 3;
	}
	else
	if(key_number == 4)
	{
		chose_column = 4;
	}	
	else
	{

	}		
}
②锁定行

此时列已经锁定, 我们把列io口,全变成输出低电平, 控制行的io口,变成输入口,高电平, 此时单片机检测io的变化,就可以检测到哪行按下了.

1.端口类型转换
/* 切换为推挽输出模式 GPIO_Mode_Out_PP*/
//下面进行判断是哪个行(对调模式)
//GPIOA, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4
void exchange_Mode(void)
{
	GPIO_InitTypeDef   GPIO_InitStructure;
	//初始化
	//使能时钟 PA  AFIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);	
	
	//io口初始化, 设置//使能内部上拉
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3 | GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;	
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	
	
	/* 切换为下拉输入输入模式  GPIO_Mode_IPD */
	//GPIOA, GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7
	//GPIOB, GPIO_Pin_0 | GPIO_Pin_10	
	//io口初始化, 设置//使能内部上拉
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;	
	GPIO_Init(GPIOA, &GPIO_InitStructure);	
	
	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_10;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;	
	GPIO_Init(GPIOB, &GPIO_InitStructure);		
	
}	

2.锁定行

电平反转, 类型反转,当检测到行io口电平变成低电平,就可以锁定行了

extern char chose_line;//选中的行
void find_line(void)	//行
{
	GPIO_SetBits(GPIOA, GPIO_Pin_1);
	GPIO_SetBits(GPIOA, GPIO_Pin_2);
	GPIO_SetBits(GPIOA, GPIO_Pin_3);
	GPIO_SetBits(GPIOA, GPIO_Pin_4);
	
	GPIO_ResetBits(GPIOA, GPIO_Pin_5);
	GPIO_ResetBits(GPIOA, GPIO_Pin_6);
	GPIO_ResetBits(GPIOA, GPIO_Pin_7);
	GPIO_ResetBits(GPIOB, GPIO_Pin_0);
	GPIO_ResetBits(GPIOB, GPIO_Pin_10);
	delay(0x20000);
	if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == SET)
	{
		delay(0x20000);
		while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_5) == SET);
		chose_line = 1;
	}
	else
	if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == SET)
	{
		delay(0x20000);
		while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) == SET);
		chose_line = 2;
	}
	else
	if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == SET)
	{
		delay(0x20000);
		while(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_7) == SET);
		chose_line = 3;
	}
	else
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == SET)
	{
		delay(0x20000);
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0) == SET);
		chose_line = 4;
	}
	else
	if(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == SET)
	{
		delay(0x20000);
		while(GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_10) == SET);
		chose_line = 5;
	}	
}
③ 计算键值

//根据所在行列 ,计算出特定符号

char compute_sign(char lines,char columns)
{
	//算出特定需要
	char counter;
	char sign;
	counter = (lines-1)*4 + columns;
/* 
	
行  1	2	3	4 (列)
1   (	)	/	*
2	1	2	3	+
3	4	5	6	-
4	7	8	9	Esc
5	<-	0	->	=
	
*/	
	switch(counter)
	{
		case 1:	
			sign = '(';
			break;
		case 2:
			sign = ')';
			break;
		case 3:
			sign = '/';
			break;
		case 4:
			sign = '*';
			break;
		case 5:
			sign = '1';
			break;
		case 6:
			sign = '2';
			break;
		case 7:
			sign = '3';
			break;
		case 8:
			sign = '+';
			break;
		case 9:
			sign = '4';
			break;
		case 10:
			sign = '5';
			break;
		case 11:
			sign = '6';
			break;
		case 12:
			sign = '-';
			break;		
		case 13:
			sign = '7';
			break;		
		case 14:
			sign = '8';
			break;		
		case 15:
			sign = '9';
			break;		
		case 16:
			sign = 'x';
			break;		
		case 17:
			sign = '<';
			break;		
		case 18:
			sign = '0';
			break;		
		case 19:
			sign = '>';
			break;		
		case 20:
			sign = '=';
			break;		
		default:
			sign = 0;
			break;		
	}
	return sign;
}

④ main函数调用展示键值
<1>软件初始化
void Software_Init(void)
{
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//中断控制器分组设置
	OLED_Init();
	Delay_Init();
//	uart_init();//波特率115200
	uart1_init(115200);
	//线性反转第一波(等待按键按下)
	button_key_config();
	Exti_key_config();
	init_collect_data();//按键收集中缀式初始化
}
<2>按键展示在OLED上
//按键展示
void show_keyboard(void)
{
	int counter;
	//展示选中的数字
	OLED_ShowChar(80, 0, show_sign, OLED_8X16);
    
	if(collect_space.counter == collect_space.insert_locate && (show_sign != '<') && (show_sign != '>'))
	{
		OLED_ClearArea(0, 40, 128, 16);
		//for循环输出字符
		for(counter = 0; counter < collect_space.counter ;counter++)
		{
			OLED_ShowChar(5+(counter)*14, 40, collect_space.data_symbol[counter], OLED_8X16);
		}		
	}
	else
	if(collect_space.counter != collect_space.insert_locate)
	{
		OLED_ClearArea(0, 40, 128, 16);
		//for循环输出字符
		for(counter = 0; counter < collect_space.counter ;counter++)
		{
			OLED_ShowChar(5+(counter)*14, 40, 			collect_space.data_symbol[counter], OLED_8X16);
		}

	}
	
	OLED_Update();	
}
<3>循环检测按键按下
	while(1)
	{
		//按键按下
		if(key_button_down == 1)
		{
			//识别按键
			scan_keyboard();	//注意,此时按键应该抬起
			//拾取按键到中缀式
			collect_Key_information(show_sign);
			//按键展示
			show_keyboard();

			//擦屁股, 恢复读取模式 线性反转第一波(等待按键按下)
			button_key_config();
			Exti_key_config();	
			
			key_button_down = 0;
			
		}

	}

第二步: 单片机键值, 组合成算式字符串

(1)收集框架

我们收集的算式, 包括,加减乘除,‘0’-‘9’,还有左右括号,至于等于号,就不需要了,因为按完算式后,按下等于号, 相当于计算结果,等计算出结果后, 我们再按下等于,相当于清零,所以我们要做一下区分.

image-20240624170617115

(2)中缀式数据结构

所以, 我们定义一个中缀算式的数据结构

#define data_MaxSize   100
//中缀式处理结构体
struct CollectSpace
{
	char data_symbol[data_MaxSize];	//存储中缀式符号的数组
	
	int counter;					//最后一个字符的数组位置
	
	int insert_locate;					//当前需要插入的位置
	
	_Bool Start_Mode;				//是否开始计算
	
    _Bool clear;					//是否清零		
};	

(3)读取开始运算标志,计算结果

如果第一次按下等于号 ,就开始计算

if(collect_space.Start_Mode == 1)	//判断中缀式处理结构,是否进入计算模式
{
	//中缀式结构数组 --> 后缀式
Conversion_expression(collect_space.data_symbol,Suffix_expression);//转换后缀式
	//计算数值
	Calculate_result = Calculate_value(Suffix_expression);
	OLED_Clear();
	//结果
	OLED_Printf(0,0, OLED_8X16,"%.2f", Calculate_result);
	//总算式
	OLED_ShowString(0,20, collect_space.data_symbol,OLED_8X16);
	OLED_Update();
	collect_space.Start_Mode = 0;	//停止计算
}

(4)收集按键信息

①分按键类型,进入不同模式

收集的信息, 分为数字,运算符和等于号,

我们分别根据类型, 进入不同的模式进行处理:

image-20240624180928671

判断对应类型,进入不同处理模式

void collect_Key_information(char collect_key)
{
	
	//获得按键
	//查询功能
	if(collect_key >= '0' && collect_key <= '9')
	{
		char_input.character_Mode = 1;	//数字模式
	}
	else
	if(
	   collect_key == '+' || 
	   collect_key == '-' || 
	   collect_key == '*' || 
	   collect_key == '/' || 
	   collect_key == '(' || 
	   collect_key == ')'	)		
	{
		char_input.character_Mode = 2;	//运算符模式
	}
	else
	if(collect_key == '<'||collect_key == '>')
	{
		char_input.character_Mode = 3;	//移动编辑模式
	}
	else
	if(collect_key == 'x')
	{
		char_input.character_Mode = 4;	//编辑删除模式
	}
	else
	if(collect_key == '=')
	{
		char_input.character_Mode = 5;	//运算结果模式
	}
	deal_mode(collect_key);
}

② 根据不同模式,进入不同的处理
<1>数字模式

数字可以插入第一个位置和 第字符个数+1的位置

处理方法: 先腾出要插入的位置,然后把对应位置插入

if(char_input.character_Mode == 1)
{
	//合法性判断(可以插入第一个位置, 和 第(字符个数 + 1)的位置)直接使用物理序号
	if(collect_space.insert_locate >= 0 && collect_space.insert_locate <= collect_space.counter)
	{
		for(j = collect_space.counter; j > collect_space.insert_locate; j--)
		{
			collect_space.data_symbol[j] = collect_space.data_symbol[j-1];
		}
		collect_space.data_symbol[collect_space.insert_locate] = deal_key;
		//合法后 , 字符数量加1
		collect_space.counter++;
		//默认下次光标插入位置++
		collect_space.insert_locate++;
	}
}
<2>运算符模式

(可以插入第一个位置, 和 第(字符个数 + 1)的位置)直接使用物理序号

if(char_input.character_Mode == 2)	//加入合法性判断
{
	//合法性判断(可以插入第一个位置, 和 第(字符个数 + 1)的位置)直接使用物理序号
	if(collect_space.insert_locate >= 0 && collect_space.insert_locate <= collect_space.counter)
	{
		for(j = collect_space.counter; j > collect_space.insert_locate; j--)
		{
			collect_space.data_symbol[j] = collect_space.data_symbol[j-1];
		}
		collect_space.data_symbol[collect_space.insert_locate] = deal_key;
		//合法后 , 字符数量加1
		collect_space.counter++;
		//默认下次光标插入位置++
		collect_space.insert_locate++;
	}
	
}
<3>移动编辑模式

这里, 我们只是切换了光标序号,所以我们下次插入字符的时候,就不能直接覆盖了,而是腾出位置,然后插入了

if(char_input.character_Mode == 3)	//移动编辑插入字符模式
{
	//光标跟踪
	if(deal_key == '<')
	{
		if(collect_space.insert_locate > 0)
		{
			collect_space.insert_locate--;		
		}
	}
	else if(deal_key == '>')
	{
		if(collect_space.insert_locate < collect_space.counter)
		{
			collect_space.insert_locate++;
		}
	}
}
<4>删除字符模式

我们删除字符前, 要做删除位置合法性判断(删除位置 = 插入位置的前一个字符)

//合法性判断(可以删除的位置 0 ~ 数组的collect_space.counter-1)

if(char_input.character_Mode == 4)	//删除字符模式
{
	//删除位置合法性判断(删除位置 = 插入位置的前一个字符)
	deletesapce.now_delete = collect_space.insert_locate-1;
	//合法性判断(可以删除的位置 0 ~ 数组的collect_space.counter-1)
	if(deletesapce.now_delete >= 0 && deletesapce.now_delete < collect_space.counter)
	{
		//删除光标处的前一个字符 === //把光标后的字符移动到此为止(覆盖)
		//临界 data[collect_space.counter-2 ] = data[collect_space.counter-1 ];
		//==> j = collect_space.counter-2 =得出范围=> j < collect_space.counter-1
		for(j = deletesapce.now_delete; j < collect_space.counter-1; j++)
		{
			collect_space.data_symbol[j] = collect_space.data_symbol[j+1];
		}
		//数组个数减去1
		collect_space.counter--;
		//光标位序减去1
		collect_space.insert_locate--;

	}
}
<5>运算模式

当我们第一次按下按键,我们就进入运算模式, 并且加一个自锁变量,下次等于就是清零变量,collect_space.clear ^= 1;

if(char_input.character_Mode == 5)	//运算模式
{
    //开启计算结果,并将计算数值装入中缀式
	if(collect_space.clear == 0)
	{
		collect_space.data_symbol[collect_space.counter] = '\0';
		collect_space.Start_Mode = 1;	
		
		//中缀式结构 进入下次计算的待计算模式(擦屁股)//分情况
		collect_space.counter = 0;
		
		collect_space.insert_locate = 0;//初始的时候, 是数组, 0
		
		collect_space.clear ^= 1;
	}
	else
	if(collect_space.clear == 1)	//将计算清零(归零):擦屁股
	{
		//清空屏幕
		OLED_Clear();
		OLED_Update();
		collect_space.clear ^= 1;
	}

}

第三步,计算结果

我们通过观看博客原理,即可

https://blog.csdn.net/qq_57484399/article/details/138288148

第四步,屏幕显示

我们可以直接调用OLED显示函数,显示结果

但是如果我们需要使用语音,读出结果的话,就要对每一位进行分个进行读取,然后结合个十百千万以及汉语的语言习惯了,我会单独出一个博客,讲解,如果把自然数小数进行语音播报.

第五步, 清零

再次按下等于号,即可清零

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/740391.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker编译nanopc-t4源码流程介绍

官方文档 Android系统编译 vnc加环境变量配置 https://github.com/friendlyarm/docker-cross-compiler-novnc 下载 git clone https://github.com/friendlyarm/docker-ubuntu-lxde-novnc cd docker-ubuntu-lxde-novnc docker build --no-cache -t docker-ubuntu-lxde-novnc …

板凳--------第20章-信号:基本概念1

tlpi_hdr.h头文件使用及设置 liao__ran 于 2020-09-29 15:12:01 发布 阅读量1.6k 收藏 5 点赞数 1 分类专栏&#xff1a; linux系统编程手册 版权 linux系统编程手册 专栏收录该内容 7 篇文章 1 订阅 订阅专栏 使用的头文件&#xff0c;主要如下&#xff1a; ename.c.inc erro…

python实训day4

1、查看数据库的版本 2、查看当前用户 3、查看当前数据库 4、计算表达式的结果; 任何一个数据库,无论大小,都首先是一个超级计算器 5、查看当前MySQL环境中所有的数据库; 系统数据库(只能看)和自定义数据库(任何操作) 6、先建数据库 gaoming 7、如果表已经存在,则创建不能成功 …

【经典算法OJ题讲解】

1.移除元素 经典算法OJ题1&#xff1a; 移除元素 . - 力扣&#xff08;LeetCode&#xff09;. - 备战技术面试&#xff1f;力扣提供海量技术面试资源&#xff0c;帮助你高效提升编程技能,轻松拿下世界 IT 名企 Dream Offer。https://leetcode.cn/problems/remove-element/desc…

【文字+视频教程】在手机上用文生软件平台CodeFlying开发一个整蛊版《Flappy Bird》

前言&#xff1a; 在之前的文章中我们介绍了国内首家文生软件平台码上飞CodeFlying&#xff0c;并且教给了大家如何用它来开发复杂的项目信息管理系统以及恶搞拼图小游戏等。今天就继续给大家带来一起用码上飞开发整蛊版《Flappy Bird》小游戏的教程。 老规矩&#xff0c;咱还…

node.js环境安装以及Vue-CLI脚手架搭建项目教程

目录 ▐ vue-cli 搭建项目的优点 ▐ 安装node.js环境 ▐ 搭建vue脚手架项目 ▐ 项目结构解读 ▐ 常用命令 ▐ 创建组件 ▐ 组件路由 ▐ vue-cli 搭建项目的优点 传统的前端项目架构由多个html文件&#xff0c;且每个html文件都是相互独立的&#xff0c;导入外部组件时需…

【计算机毕业设计】基于Springboot的网页时装购物系统【源码+lw+部署文档】

包含论文源码的压缩包较大&#xff0c;请私信或者加我的绿色小软件获取 免责声明&#xff1a;资料部分来源于合法的互联网渠道收集和整理&#xff0c;部分自己学习积累成果&#xff0c;供大家学习参考与交流。收取的费用仅用于收集和整理资料耗费时间的酬劳。 本人尊重原创作者…

solidworks安装教程 - 解决安装后服务不能自动启动问题

Solidworks安装教程&#xff0c;有些同学的电脑过于复杂&#xff0c;产生了正常的服务不能启动。 前面的有个重要的操作操作界面有&#xff0c;大家应该是执行了&#xff1a; 那么我们有变通的方法可以让这个服务启动&#xff1a; 1. cmd用管理员启动 2. 测试下如下命令是否…

Charles配置与API数据抓取

2024软件测试面试刷题&#xff0c;这个小程序&#xff08;永久刷题&#xff09;&#xff0c;靠它快速找到工作了&#xff01;&#xff08;刷题APP的天花板&#xff09;-CSDN博客跳槽涨薪的朋友们有福了&#xff0c;今天给大家推荐一个软件测试面试的刷题小程序。https://blog.c…

Vue 的 axios二次封装

&#xff08;以下的接口地址链接换成自己的写&#xff01;&#xff01;&#xff01;&#xff09; 首先在项目中src的目录下创建一个api的文件夹&#xff0c;在api的文件下在穿件两个文件用于二次封装 别忘了先安装axios&#xff1a;&#xff08;在根目录下安装axios&#xff0…

【消息队列】Kafka学习笔记

概述 定义 传统定义: 一个分布式的, 基于发布订阅模式的消息队列, 主要应用于大数据实时处理领域新定义: 开源的分布式事件流平台, 被用于数据管道/流分析/数据集成 消息队列的使用场景 传统消息队列的主要应用场景包括: 削峰: 解耦: 异步: 两种模式 点对点模式 发布/订…

计算机网络 DHCP以及防护

一、理论知识 1.DHCP&#xff1a;用于在网络中自动分配IP地址及其他网络参数&#xff08;如DNS、默认网关&#xff09;给客户端设备。 2.VLAN&#xff1a;逻辑上的局域网分段&#xff0c;用于隔离和管理不同的网络流量。 3.DHCP地址池&#xff1a;为每个VLAN配置不同的DHCP地…

高考志愿填报秘籍:工具篇

选择适合自己的大学和专业&#xff0c;对广大考生来说至关重要。从某种程度上来说&#xff0c;决定了考生未来所从事的行业和发展前景。为了帮助广大考生更加科学、合理地填报志愿&#xff0c;选择适合自己的大学和专业&#xff0c;本公众号将推出如何用AI填报高考志愿专栏文章…

国际数字影像产业园:打造生态智慧写字楼新纪元

国际数字影像产业园凭借其独特的生态办公环境、智慧化服务体系、多元化功能空间和创新活力&#xff0c;成功打造了生态智慧写字楼的新纪元&#xff0c;为成都乃至全球的数字文创产业注入了新的活力和动力。 1、生态办公环境的构建&#xff1a; 公园城市理念的融入&#xff1a;…

骨传导运动耳机的怎么买到好用的?超全的选购攻略附带好物推荐!

近年来&#xff0c;骨传导耳机作为一个新型并且收到大量关注的一个设备&#xff0c;很多人在购买时会在想骨传导耳机的哪个牌子好&#xff0c;主要是市面上涌现了很多型号和品牌&#xff0c;让很多人不怎么怎么现在&#xff0c;那么我这几年作为一个用了那么多骨传导耳机的数码…

车辆检测之图像识别

1. 导入资源包 import torch.nn as nn import tkinter as tk from tkinter import filedialog, messagebox from PIL import Image, ImageTk,ImageDraw,ImageFont import torch from torchvision import transforms, models from efficientnet_pytorch import EfficientNet im…

[职场] 怎么写个人简历模板 #其他#知识分享

怎么写个人简历模板 怎么写个人简历模板1 姓名&#xff1a;xxx 性别&#xff1a;x 年龄&#xff1a;x岁 婚姻状况&#xff1a;x 最高学历&#xff1a;xx 政治面貌&#xff1a;xx 现居城市&#xff1a;xx 籍贯&#xff1a;xx 联系电话&#xff1a;xxxxxx 电子邮箱&#xff1a;xx…

安装Django Web框架

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 Django是基于Python的重量级开源Web框架。Django拥有高度定制的ORM和大量的API&#xff0c;简单灵活的视图编写&#xff0c;优雅的URL&#xff0c;适…

软件工程体系概念

软件工程 软件工程是应用计算机科学、数学及 管理科学等原理开发软件的工程。它借鉴 传统工程的原则、方法&#xff0c;以提高质量&#xff0c;降 低成本为目的。 一、软件生命周期 二、软件开发模型 1.传统模型 瀑布模型、V模型、W模型、X 模型、H 模型 (1)瀑布模型 瀑布…

Crypto++ 入门

一、简介 Crypto&#xff08;也称为CryptoPP、libcrypto或cryptlib&#xff09;是一个免费的开源C库&#xff0c;提供了多种加密方案。它由Wei Dai开发和维护&#xff0c;广泛应用于需要强大加密安全的各种应用程序中。该库提供了广泛的加密算法和协议的实现&#xff0c;包括&…