学习笔记|ADC反推电源电压|扫描按键(长按循环触发)|课设级实战练习|STC32G单片机视频开发教程(冲哥)|第十八集:ADC实战

news2024/11/18 22:29:23

文章目录

  • 1.ADC反推电源电压
    • 测出Vref引脚电压的意义?
    • 手册示例代码分析
    • 复写手册代码
    • Tips:乘除法与移位关系
    • 为什么4096后面还有L
  • 2.ADC扫描按键(长按循环触发)
    • 长按触发的实现
  • 3.实战小练
    • 1.初始状态显示 00 - 00 - 00,分别作为时,分,秒
    • 2.正常运行状态位:每隔一秒钟,秒+1,一分钟,分+1,以此类推,1:参数设置,此时时间不需要数字自动跳动
    • 3.时间到达00 - 00 - 30的时候,蜂鸣响3秒钟表示闹钟
    • 4.长按按钮A进入设置,数码管第一位闪烁,
    • 5.按下0-9将数值显示到数码管上,并且闪烁后移一位
    • 6.按下B停止闪烁,秒钟+1,长按连加;
    • 7.设置结束按下D结束,时钟正常走时;
  • 总结
  • 课后练习:

1.ADC反推电源电压

19.5.4 利用ADC第15通道(内部1.19V参考信号源)测量外部电压或电池电压
注意:这里的1.19V不是ADC 的基准电压ADC-Vref+,而是ADC15通道的固定输入信号源,1.19V
STC32G系列ADC的第15通道用于测量内部参考信号源,由于内部参考信号源很稳定,约为1.19V,且不会随芯片的工作电压的改变而变化,所以可以通过测量内部1.19V参考信号源,然后通过ADC 的值便可反推出外部电压或外部电池电压。
在这里插入图片描述

上面的方法是使用ADC的第15通道反推外部电池电压的。在ADC测量范围内,ADC的外部测量电压与ADC的测量值是成正比例的,所以也可以使用ADC的第15通道反推外部通道输入电压,假设当前已获取了内部参考信号源电压为BGV,内部参考信号源的ADC测量值为resg,外部通道输入电压的ADC测量值为resx,则外部通道输入电压Vx=BGV / resbg *resx;
在这里插入图片描述

测出Vref引脚电压的意义?

公式得知,我们需要知道Vref的电压才能换算出引脚上的输入电压!但是我们做小仪表的时候很多都是直接电池供电,锂电池电压3.7-4.3V(电压不定),为了省钱还会抠掉基准电压电路和稳压电路。所以首先换算出Vref的引脚电压至关重要。
我们就需要先反推出这一个Vref的引脚电压,那么才能换算出这个通道上的一个电压。就可以实现我们最低成本的一个电压检测。而且可以省掉一个基准电压基准电压电路和稳压电路。那我们只要测出一次这个Vref的一个引脚电压,这边我们这几分钟之内再去测
P1.0的电压他肯定是准的。所以反推电源电压非常重要。

手册示例代码分析

主程序main.c中:
变量BGV是int类型,接收VREFH_ADDR<<8)+VREFL_ADDR的计算结果。
在这里插入图片描述

从定义中可以,VREFH_ADDR为CHIPID7,打开搜索工具搜索:
#define CHIPID7 ((unsigned char volatile far)0x7efde7)
在这里插入图片描述
在这里插入图片描述

指向了内部1.19V参考信号源(高字节)。这两个地址分别存放了高、低字节的地址。我们直接读出来保存到BGV变量里。

复写手册代码

复制上节课(\教学视频配套附件-20230731\Example\13.ADC采集)代码目录为14.ADC应用,打开工程,在查询方式的代码部分进行修改。
比如我们要返回0-5V的电压,建议都是整数返回,也就是我们这边如果是0伏对应0,如果是5伏我们可以对应5000,精度为0.001V.
12位是4096,至少要13位,那么这边就直接一个16位的无符号u16,故新建函数u16 ADC_VrefCal(void),入口参数无。
我们直接在程序里面采集电压。
首先在查询代码前部定义int BGV;并复制宏定义,写完后可以在手册中搜索一下名称,看有没有问题

#define VREFH_ADDR CHIPID7
#define VREFL_ADDR CHIPID8
bit busy;
int BGV;

然后,按手册里的程序开始写。
首先第一步,先把这个数值给他读出来
BGV=(VREFH_ADDR<<8)+VREFL_ADDR; //读取内部1.19V的电压
利用内部ADC通道15,读取8次,取平均值(res >>= 3;//除以8)。
然后就可以计算出VCC:vcc=(int)(4096L* BGV/ res);
完整的函数ADC_VrefCal为:

	u16 ADC_VrefCal(void)
	{
		u8 i;
		int res;
		int VCC;

		BGV=(VREFH_ADDR<<8)+VREFL_ADDR;         //读取内部1.19V的电压

		ADC_Init();								//初始化ADC

//
//		ADC_Read();								//前2次读取建议废弃
//		ADC_Read();

		res = 0;

		for (i-0; i<8; i++)						//循环采集8次
		{
			res += ADC_Read(15);				//通道选择内部#15,
												//由于15号通道是内部直连的,不需要去初始化IO口位高阻输入
		}
		res >>= 3;								//除以8

		VCC = (int)(4096L* BGV / res);

		return VCC;
	}

声明:在adc.h文件中添加文件定义,adc.h可以在adc.c的引用处点击右键,open document打开文件。增加:u16 ADC_VrefCal(void)
调用:在主函数之前调用即可。主函数体中已包含初始化,可以先注释掉。ADC_VrefCal(); //adc初始化+电源电压读取
这里的Vref引脚接在2.5V基准电压源上,可以思考一下这里最终读出来的数值是多少?
刚刚通电以后,电源可能会存在不稳定的情况,需要延时500-1000ms, delay_ms(500); //等待电源稳定
定义变量:u16 VREF_VAL;
VREF_VAL= ADC_VrefCal(); //adc初始化+基准电源电压读取
printf(“当前电源电压数\xfd值:%dmv\r\n”,VREF_VAL); //串口打印ADC数值(10ms打印1次)
编译后,提示错误:

*** ERROR L127: UNRESOLVED EXTERNAL SYMBOL
    SYMBOL:  ADC_VrefCal
    MODULE:  .\Objects\Demo.obj (Demo)
*** ERROR L128: REFERENCE MADE TO UNRESOLVED EXTERNAL
    SYMBOL:  ADC_VrefCal
    MODULE:  .\Objects\Demo.obj (Demo)
    ADDRESS: FF15ABH
Program Size: data=9.4 edata+hdata=485 xdata=192 const=161 code=7893

提示ADC_VrefCal没有找到,先去adc.h中查一下,需要改成查询: #define ADC_Func ADC_CHECK //最终选择
重新编译,成功。打印结果可以看到,当前的电源电压值比较高,实际上我们上节课外用表也量过,只有2.5V
但是这里有2.613V。
怎么解决?可以按手册建议,将前几次读取值废弃,这里废弃4次,重新编译,输出数据2.503V,就跟我们的基准电压2.5V,非常近似。

Tips:乘除法与移位关系

原文链接:C语言中乘除法与移位关系
用移位实现乘除法运算
a=a8;
b=b/8;
可以改为:
a=a<<3;
b=b>>3;
说明:
除2 = 右移1位; 乘2 = 左移1位
除4 = 右移2位; 乘4 = 左移2位
除8 = 右移3位; 乘8 = 左移3位
通常如果需要乘以或除以2的n次方,都可以用移位的方法代替,大部分的C编译器,用移位的方法得到代码比调用乘除法子程序生成的代码效率高。
实际上,只要是乘以或除以一个整数才可以用移位的方法得到结果。
如:a=a
9
分析a9可以拆分成a(8+1)即a8+a1, 因此可以改为:
a=(a<<3)+a
a=a7
分析a
7可以拆分成a*(8-1)即a8-a1, 因此可以改为: a=(a<<3)-a
总结:a=an; n分解成(2^m + s),则a=an可以改为a=(a<<m)+as;
a
s再同理分解替换。
例:a=a10 => a=a(8+2) => a=a8 + a2 => a=(a<<3)+(a<<1)

为什么4096后面还有L

原文链接:C语言中,数字后面带个U,L,F的含义
U表示该常数用无符号整型方式存储,相当于 unsigned int
L表示该常数用长整型方式存储,相当于 long
F表示该常数用浮点方式存储,相当于 float三、自动类型转换
这些后缀跟是在字面量(literal,代码中的数值、字符、字符串)后面

2.ADC扫描按键(长按循环触发)

用ADC实现一个扫描按键。
上节课讲过图纸上,手册上有16个按键。而且我们只需要一个ADC口就可以实现
19.5.5 ADC作按键扫描应用线路图
读ADC键的方法:每隔10ms 左右读一次ADC值,并且保存最后3次的读数,其变化比较小时再判断键。判断键有效时,允许一定的偏差,比如士16个字的偏差。
手册上接的是P1.0,1个P1.0的口上可以接16个按键,并且每1个按键按下它的adc数值都是不一样的。那我们就可以用这个adc的数值去检测当前按键按下的是哪一个。
比如说我现在数值可能是64,比如说我返回一个67,按我们手册的19.5.5,最大值是1023,但目前芯片已经到了12位,见原理图:
在这里插入图片描述
可以看到我们这个ADC值接到了P10口,每个按键按下,都有对应的数值出来。比如说我现在检测到3075,如果偏差不大,可确定,但如果在2个数值中间,则判定不合格。
两个按键之间数值差256,可以折中,正负64都可以默认是中间数值的键,超过64卡在之间的数据,舍弃,就可以判断是哪个按键按下了。
基于这个,写一个今天的示例代码。接着刚刚的程序写。
在这里插入图片描述
获取电源电压很有必要,获取电源电压后,在ADC换算里面,上节课还用的是2.5V的一个基本电压源,可以把它读回来这个数据保存下来。然后计算的时候把这个25伏替换成它。
实际接的时候为了省钱,可能就不接这个高精度电源(基准电压源)了,直接就可以读取这个Vref引脚电压然后给它带进去,这就是一个ADC的一个换算。
按键读取代码,中断,查询都可以,可以放在公共区域,增加函数ADC_KeyRead,并在头文件中定义:

//========================================================================
// 函数名称:ADC_KeyRead
// 函数功能:返回当前的一个键值
// 入口参数: @
// 函数返回:返回哪一个按钮被按下1-16,0(没有按下)
// 当前版本: VER1.0
// 修改日期: 2023
// 当前作者:
// 其他备注:
//========================================================================

#define ADC_OFFSET 64		//ADC误差允许

u8 ADC_KeyRead(u16 adc )
{
	u8 i;
	u16 adc_cmp;			//直接定义一个adc的比较256*1 256*2
	//判断按钮有没有按下,第1个按钮按下时的数值是256,如果大于256+64或者小于256-64,数值无用。
	if( adc < (256 - ADC_OFFSET ))
	{
		return 0;			//表示没有按键按下
	}
	adc_cmp = 256;			//第1个按键比较的adc数值
	for(i=1;i<=16;i++)		//循环判断
	{
		//首先判断adc在不在范围之内
		if( adc >= (adc_cmp - ADC_OFFSET) && adc <= (adc_cmp + ADC_OFFSET))
		{
			return i;
		}
		else
			adc_cmp += 256;	//当前的adc比较值+256
	}
	return 0;				//如果加完1个循环,数值还是不正确,return 0
}

先把它这个通道0 P1.0的这个adc的数值给它读回来,然后放入ADC_KeyRead中去判断。
在demo.c的主函数中关闭printf,增加数码管显示部分:

SEG0 = 	ADC_KeyRead(ADC_Read(0));					//用到的是通道0

编译下载,能够正常显示到9,但是有16个按键,怎么实现?课后可以想一下,怎么样显示2位?

长按触发的实现

要实现:刚刚按下触发1次,长按3s后每0.1s接着触发1次,连续跳转按键,实现一个引脚就可以控制16个adc按键。
很实用的功能。
功能描述:按下以后变量+1,加到几表示这个按键在消抖,直到它累积到几,因为是持续检测到这个按键为按下,然后按下为几就是已经触发了。
松开以后变量清0。
首先,我们要有一个上次的一个状态,先定义个变量。我们只有初始的时候才能检测一次。static u8 key_last = 0; //按键上一次的状态。
还需要加一个计数,按键按下多长时间,可以计算一下static u16 key_downtime ; //按键按下时间,检测到每按下1次这个变量,就可以+1。
如果这个按键松开了, key_downtime = 0; //这里没有按键按下,变量清0 key_last = 0; //按键上一次的状态也要清0
因为按键松开了以后,下一次进来检测到两个变量的状态都是0。对下一次来说。上一次是松开的。
检测到几号按钮后,希望跳出for循环。用到一个关键词叫做break。如果条件成立,会直接break返回退出这个大括号
退出时,i值就是当前的一个按键,判断:

		if(key_last == i)			//本次按键序号和上一次一样
		{
			key_downtime ++;		//按下时间加1
			if( key_downtime==3 )	//按下10ms*3触发1次,起到滤波作用
			{
				return i;
			}
			else if ( key_downtime == 300 ) //按下10ms*300(3s)触发1次
			{
				key_downtime = 290;
				return i;
			}
			else
				return 0;
		}
		else
		{
			key_downtime = i;			//本次按下的按键和之前不一样,保存本次的按键
			key_last = 0;				//按键上一次的状态也要清0
		}

编译下载,运行结果:按下是1 ,长按3s之后开始计数,如果想换成第2个,或者第3个按键,调整:if(ADC_KeyRead(ADC_Read(0))==1)。
根据触发代码,目前是3s触发,以后每隔100ms(0.1s)触发一次,也可以改成0.5s,或者0.3s,根据项目需要去修改。
有了这套框架,修改还是很方便的。

3.实战小练

做一个简易时钟,功能如下:

1.初始状态显示 00 - 00 - 00,分别作为时,分,秒

首先我们数码管显示这个初始值,在seg_lcd.c中单独写初始化程序void Clock_InitShow( u8 hour, u8 minute, u8 second)。
先确定函数名,再计入函数写入SEG0,SEG1,…SEG7,小时显示10位:SEG0 = hour/10 ;小时显示个位:SEG1 = hour%10 ;
类似的写出second和minute,SEG_Tab扩容+1,增加0xdf(横杠)。SEG2 = 22;
初始化默认是不显示:u8 Show_Tab[8] = {20,20,20,20,20,20,20,20};。加函数头,完善相关内容。在头文件中增加函数声明。

//========================================================================
// 函数名称:Clock_InitShow
// 函数功能:初始化时钟显示
// 入口参数: @hour:小时,@minute:分钟,@second:秒
// 函数返回:
// 当前版本: VER1.0
// 修改日期: 2023
// 当前作者:
// 其他备注:
//========================================================================
void Clock_InitShow( u8 hour, u8 minute, u8 second)
{
	SEG0 = hour/10 ;
	SEG1 = hour%10 ;
	SEG2 = 22;
	SEG3 = minute/10 ;
	SEG4 = minute%10 ;
	SEG5 = 22;
	SEG6 = second/10 ;
	SEG7 = second%10 ;

}

在demo.c中调用,10ms刷新一次,while主循环中调用,上电时显示:零零杠零零杠零零:

//定义时间相关变量
u8 HOUR = 0;
u8 MINUTE = 0;
u8 SECOND = 0;

//while主循环中调用,上电时显示:零零杠零零杠零零
Clock_InitShow(HOUR,MINUTE,SECOND);					//刷新数码管的数值

新定义变量RUN_STATE,用于时钟状态设置:bit RUN_STATE = 0; //0:正常运行,1:参数设置

2.正常运行状态位:每隔一秒钟,秒+1,一分钟,分+1,以此类推,1:参数设置,此时时间不需要数字自动跳动

每隔一秒钟,秒+1,一分钟,分+1,以此类推,因为每10ms刷新,故需要执行100次(1000ms=1s)再运行。
定义计数变量count,u16 count = 0;每执行1s,刷新一次。

			Clock_InitShow( HOUR,MINUTE,SECOND );					//刷新数码管的数值

			if( RUN_STATE == 0 )									//系统正常运行
			{
				count++;
				if(count == 100)
				{
						count =0;
						SECOND++;
						if(SECOND>60)
						{
							SECOND = 0;
							MINUTE++;
							if( MINUTE > 60 )
							{
								MINUTE = 0;
								HOUR++;
								if( HOUR > 24 )
								{
									HOUR = 0;
								}
							}

						}
				}

			}

3.时间到达00 - 00 - 30的时候,蜂鸣响3秒钟表示闹钟

由于要用到蜂鸣器,需要在初始化部分打开蜂鸣:

			Clock_InitShow( HOUR,MINUTE,SECOND );					//刷新数码管的数值
			BEEP_RUN();

在每s运行的程序体中,加入判断,满足条件后执行蜂鸣:

						if( (HOUR == 0) && (SECOND == 0)  && (SECOND == 30))	//每S执行1次,时间到达00 - 00 - 30的时候,蜂鸣响3秒钟表示闹钟
						{
							BEEP_ON(300);	//蜂鸣时间300*10ms=3S
						}

编译,提示错误:

Demo.c(21): error C25: syntax error near 'unsigned'
Demo.c(21): error C25: syntax error near ')'

在提示错误的变量出,执行跳转,发现头文件stc32g.h中已有定义:#define HOUR (*(unsigned char volatile far *)0x7efe73)
变量重复,可将变量变成:s_HOUR,表示用户变量。
再次编译,提示u16 Count[8]与u16 count = 0;重复定义,修改为u16 counts = 0;并调整程序内使用的对应变量,重新编译正常。
初始化,可以看到中间的横杠有问题,需要修改对应代码,等待时间到30s时会蜂鸣3s。
修改横杠的显示代码,打开之前的码表,0点亮,1熄灭,经修改,应该为0xbf。

u8 SEG_Tab[23] = { 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90, 0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,0xff,0xbf,0xbf};	//0-9段码共10个,0-9带小数点共10个,全部不显示共1个

4.长按按钮A进入设置,数码管第一位闪烁,

看一下板子上的按钮A,是第11个按键,即返回值是11,需要先判断一下,先写一个swith,判断当前是第几个按钮按下:

				switch (ADC_KeyRead(ADC_Read(0)))
                {
                	case 1:
                		break;
                	case 11:
                		break;
                	default:
                		break;
                }

case 11情况下的长按、短按需要区分,那我们在这个函数里面编写,adc.c中的ADC_KeyRead中,返回值是这个按键的键值(0-16),占用的是一个最低位,
可以添加一个最高位,return i+0x80; //17的二进制是0001 0001,其实占到了最高位,即从后往前数第5位,最高位其实不会占用,可以将最高位赋值为1,代表长按。
执行一键设置时,运行状态停止,RUN_STATE状态变为进入设置。
case 11 + 0x80: RUN_STATE = 1; break; //按钮A长按
数码管第1位闪烁,看一下数码管刷新函数,要实现闪烁的效果,需要先显示数值然后熄灭(参考之前的LED闪烁的原理),然后显示再熄灭,那么这边就是05秒亮05秒灭
而且我们改变SEG_LED_Show就可以实现。如果需要显示0.5s,关闭0.5s。
增加计数500ms变量和闪动标志位:

bit blink = 0;	//0:点亮,1:熄灭,放在函数外,因为还需要让它不闪烁
void SEG_LED_Show( void )
{
	static u8 num = 0 ;
	static u16 count = 0;

	count++;
	if(count >= 500)
	{
		blink = !blink;
		count =0;			//清空
	}
	if( num <= 7 )		//8位数码管的刷新
	{
		LED_POW = 2;						//关闭LED的电源
		SEG_COM = COM_Tab[num];				//选择相应的数码管的位
		if(blink == 0)
			SEG_SEG = SEG_Tab[Show_Tab[num]];	//需要显示的数字的内码
		else
			SEG_SEG = 0xff;						//关闭
	}

增加闪烁的控制代码,增加定义:u8 blink_bit = 0xff; //8位变量(0-7闪烁),分别对应1-8号数码管闪烁,最大值是0xff,大于8全都不闪烁:

		if(blink_bit == num)
		{
			if(blink == 0)
				SEG_SEG = SEG_Tab[Show_Tab[num]];	//需要显示的数字的内码
			else
				SEG_SEG = 0xff;						//关闭
		}
		else
				SEG_SEG = SEG_Tab[Show_Tab[num]];	//需要显示的数字的内码

编译并修改错误,运行。长按a发现时间不走,但是数码管也不动,也不闪烁,排查问题。
首先,blink_bit未清零,修改为:u8 blink_bit = 0; //8位变量(0-7闪烁),分别对应1-8号数码管闪烁,最大值是0xff,大于8全都不闪烁
把blink和blink_bit设置为全局变量,加入seg_led.h,用了extern定义的就不能给他赋值:

extern bit blink ;	//0:点亮,1:熄灭,放在函数外,因为还需要让它不闪烁
extern u8  blink_bit;	//8位变量(0-7闪烁),分别对应1-8号数码管闪烁,最大值是0xff,大于8全都不闪烁

赋值需要在seg_led.c中进行:

bit blink = 0;	//0点亮 1熄灭
u8  blink_bit = 0xff;	//0-7分别对应1-8个的数码管闪烁,大于8全都不闪烁

可在刚刚进入设置时清0:,长按a以后第一设置的就是0位:
case 11 + 0x80: RUN_STATE = 1;blink_bit = 0; break; //按钮A长按
编译,看效果,长按0,时间停止走,第一位0开始闪动,达到预设的要求。

5.按下0-9将数值显示到数码管上,并且闪烁后移一位

新建功能函数,首先要键入数值,如果是2就是1(板子上写着1的按键)按下。SEG_Tab[blink_bit]是直接保存,我们可以再给它定义一个变量(main.c中)u8 keynum;,保存按键的状态,然后去判断之歌按键。
然后我们把这个按键的数据给他写进去,切换位的时候需要注意一下, 显示杠的地方还是保持。

				keynum = ADC_KeyRead(ADC_Read(0));
				switch (keynum)
                {

                	case 11 + 0x80:	RUN_STATE = 1;blink_bit = 0;	break;			//按钮A长按
                	case 1:
					case 2:
					case 3:
					case 4:
					case 5:
					case 6:
					case 7:
					case 8:
					case 9:
					case 10:					//如果按钮是0-9(从1到10)按下,这边应该把显示的值写到数码管里去,并且闪烁的位++
						Show_Tab[blink_bit] = (keynum - 1);		//因为这个变量是直接保存
						blink_bit++;	//闪烁的位转换到下一个
						if((blink_bit == 2)||(blink_bit == 5))
							blink_bit++;
						if(blink_bit == 8)
						{
							RUN_STATE = 0;
							blink_bit = 0;
						}
						break;
					default:
                		break;
                }

编译运行,数字开始闪,但按动按钮,仅往后移,发现我们这个按键输的数值不显示。
不管输入什么都显示0,排查原因。时间在走,位也还在显示,问题出在blink_bit的赋值上,blink_bit数值本来就大于8,可以改成0xff或不赋值
显示0的原因是:Show_Tab[blink_bit]修改了变量,修改while循环再次运行到Clock_InitShow( s_HOUR,MINUTE,SECOND ),刷新数码管的数值没有更新,还是初始值,故均显示0,设置完后,需要重新写入s_HOUR,MINUTE,SECOND:

						if(blink_bit == 8)
						{
							RUN_STATE = 0;
							//blink_bit = 0xff;
						}
                        s_HOUR = Show_Tab[0]*10 + Show_Tab[1];
						MINUTE = Show_Tab[3]*10 + Show_Tab[4];
						SECOND = Show_Tab[6]*10 + Show_Tab[7];

增加显示时间代码后,重新编译,功能正常。

6.按下B停止闪烁,秒钟+1,长按连加;

增加按键条件:case 12。刚刚按下时,秒钟+1:case 12: SECOND++; 但当秒+的时候,是不是有一个需要这个数值判断,在头部增加函数void TIME_SET_Second(void); //每执行1次加1s;
将增加秒的代码剪切过来,放入TIME_SET_Second(void)函数,达到执行1次,秒就++的效果,

void TIME_SET_Second(void)	//每执行1次加1s
{
	SECOND++;
	if(SECOND>60)
	{
		SECOND = 0;
		MINUTE++;
		if( MINUTE > 60 )
		{
			MINUTE = 0;
			s_HOUR++;
			if( s_HOUR > 24 )
			{
				s_HOUR = 0;
			}
		}

	}
}

替换原增加秒的代码段,并添加至条件判断区段:

                	case 11 + 0x80:
							RUN_STATE = 1;
							blink_bit = 0;
							break;		//按钮A长按
					case 12 :
							TIME_SET_Second();
							break;
					case 12 + 0x80:
							blink = 0;
							TIME_SET_Second();
							break; 		//按钮B长按	blink=0一直点亮

编译下载,出现个小问题,暂停设置时间,长按b时,数码管还在闪动。
分析SEG_LED_Show,其中的static u16 count = 0;还是有变化的,需要处理一下,如果需要关闭闪烁,count也要清0。
将count改为全局变量Time_count,放在函数体外:u16 T_count = 0; 修改后,T_count的定义也需要加在头文件中,并增加extern关键字:extern u16 T_count ;
SEG_LED_Show( void ) 中的Time_count修改:

	Time_count++;
	if( Time_count >= 500)
	{
		blink = !blink;
		Time_count = 0;			//清空
	}

在switch选择语句,修改case段加入运行状态的判断,case 12 无实际意义,可删除:

switch (keynum)
                {
                	case 11 + 0x80:
							RUN_STATE = 1;
							blink_bit = 0;
							break;		//按钮A长按
					case 12 + 0x80:
						if(RUN_STATE == 1)
							{
								blink = 0;
								Time_count = 0;
								TIME_SET_Second();
							}
						break; 		//按钮B长按	blink=0一直点亮
                	case 1:
					case 2:
					case 3:
					case 4:
					case 5:
					case 6:
					case 7:
					case 8:
					case 9:
					case 10:					//如果按钮是0-9(从1到10)按下,这边应该把显示的值写到数码管里去,并且闪烁的位++
							Show_Tab[blink_bit] = (keynum - 1);		//因为这个变量是直接保存
							blink_bit++;	//闪烁的位转换到下一个
							if((blink_bit == 2)||(blink_bit == 5))
								blink_bit++;
							if(blink_bit == 8)
							{
								RUN_STATE = 0;
								//blink_bit = 0xff;
							}
							s_HOUR = Show_Tab[0]*10 + Show_Tab[1];
							MINUTE = Show_Tab[3]*10 + Show_Tab[4];
							SECOND = Show_Tab[6]*10 + Show_Tab[7];
							break;
                }

编译下载,正常走秒,长按a,进入设置模式,第一个数码管闪烁,再长按b(短按没有效果),停止闪烁,秒开始加,松开重新进入设置模式,

7.设置结束按下D结束,时钟正常走时;

增加case判断,RUN_STATE = 0; //变量清0,进入正常走时模式,blink_bit = 0xff; //关闭闪烁,大于7均可,这里直接赋最大值:

                	case 14 :	//按钮D短按
						if(RUN_STATE == 1)
							{
								blink_bit = 0xff;	//关闭闪烁,大于7均可,这里直接赋最大值
								RUN_STATE = 0;	//变量清0,正常走时模式
							}
						break;

本节基本上就是单片机课设的难度了,一步一步解决问题即可。

总结

1学会反推电源电压,用低成本方案实现功能。
利用电源反推功能后,就不需要再用一个精准的电源电压(实验板上自带精准的2.5V基准电压源),如果接入电池
3.7V至4.3V波动,就很难判断。但是有了这个反推ADC电源电压的功能,就能帮助我们省下很多事情。
2.学会ADC的采集和实战应用
一个IO口控制16个按键的应用十分广泛,八脚的单机片去做,八脚的单片机只有6个lO口(毕竟1个电源1个地必不可少)
6个IO口最多控制2*4的矩阵按键,也就是8个按钮,如果使用ADC功能,一个IO口就可以控制16个,非常实用。包括长按触发循环,课后可以尝试找
一下生活中的其他应用,像触摸台灯,长按以后调解光的亮度,长按变亮或者长按变暗,都是长按触发的一个好的应用例子。

课后练习:

1.给今天的电子钟增加闹钟的时间设置功能。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

S5PV210裸机(三):串口

本文主要探讨210串口相关知识。 同步通信和异步通信 发送和接收方同时钟工作叫同步,否则叫异步 同步通信频率固定,双方通信频率不固定,,接收方收到起始信号开始接收信息 电平信号和差分信号 电平信号和差分信号是描述通信线路传输方式 电平信…

长剖与贪心+树上反悔贪心:1004T4

长剖的本质是一种贪心。&#xff08;启发式合并本质也是类似哈夫曼树的过程&#xff09; 在此题中&#xff0c;首先肯定变直径&#xff0c;然后选端点为根。然后选叶子。而每个叶子为了不重复计算&#xff0c;可以只计算其长剖后所在链的贡献。&#xff08;本题精髓&#xff0…

Postman使用实例

Postman使用实例 实体类Emp package com.example.springboot_postman.pojo;import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor;import javax.persistence.*; import j…

【C语言】善于利用指针(一)

&#x1f497;个人主页&#x1f497; ⭐个人专栏——C语言初步学习⭐ &#x1f4ab;点击关注&#x1f929;一起学习C语言&#x1f4af;&#x1f4ab; 目录 导读&#xff1a; 1. 什么是指针 1.1 概念 1.2 图解 1.3 示例 2. 指针和指针类型 2.1 指针的定义 2.2 指针的解引…

Kubernetes Deployment的运行

Deployment提供了一种更加简单的更新Replication Controller和Pod的机制&#xff0c;更好地解决了Pod的编排问题。本节将详细介绍如何通过Deployment实现Pod的管理。 15.1.1 什么是Deployment Deployment的中文意思为部署、调集&#xff0c;它是在Kubernetes的1.2版本中新增…

【kubernetes】基于prometheus的监控

目录 1 监控解决方案2 prometheus2.1 容器监控2.2 节点监控2.3 资源对象监控2.4 metrics--server 3 prometheus-operator vs kube-prometheus vs helm3.1 prometheus-operator3.2 kube-prometheus3.3 helm 参考文档 1 监控解决方案 从实现方案来说&#xff0c;监控分为3个部分…

计算机网络基础(二):物理层、数据链路层及网络层

一、物理层 1.物理层 物理层面的通信标准可以概括划分为与网络基础设施有关的标准和与被传输物理信号有关的标准两类。 网络基础设施的标准&#xff1a;鉴于物理层面的消息互通也是物理层应该兑现的服务&#xff0c;因此物理层的标准还会包括针脚的用途、线缆的材料与设计等…

vertx的学习总结7之用kotlin 与vertx搞一个简单的http

这里我就简单的聊几句&#xff0c;如何用vertx web来搞一个web项目的 1、首先先引入几个依赖&#xff0c;这里我就用maven了&#xff0c;这个是kotlinvertx web <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apac…

华为云云耀云服务器L实例评测|基于canal缓存自动更新流程 SpringBoot项目应用案例和源码

前言 最近华为云云耀云服务器L实例上新&#xff0c;也搞了一台来玩&#xff0c;期间遇到各种问题&#xff0c;在解决问题的过程中学到不少和运维相关的知识。 在之前的博客中&#xff0c;介绍过canal的安装和配置&#xff0c;参考博客 拉取创建canal镜像配置相关参数 & …

【数据库——MySQL】(14)过程式对象程序设计——游标、触发器

目录 1. 游标1.1 声明游标1.2 打开游标1.3 读取游标1.4 关闭游标1.5 游标示例 2. 触发器2.1 创建触发器2.2 修改触发器2.3 删除触发器2.4 触发器类型2.5 触发器示例 参考书籍 1. 游标 游标一般和存储过程一起配合使用。 1.1 声明游标 要使用游标&#xff0c;需要用到 DECLAR…

<C++> String

目录 一、标准库中的string类 1. string类 2. string类的常用接口说明 2.1 string类对象的常见构造 2.2 string类对象的容量操作 2.3 string类对象的访问及遍历操作 2.4 string类对象的修改操作 2.5 string类非成员函数 总结 前言 C语言中&#xff0c;字符串是以 \0 结尾的一些…

用JMeter对HTTP接口进行压测(一)压测脚本的书写、调试思路

文章目录 安装JMeter和Groovy为什么选择Groovy&#xff1f; 压测需求以及思路准备JMeter脚本以及脚本正确性验证使用Test Script Recorder来获取整条业务线上涉及的接口为什么使用Test Script Recorder&#xff1f; 配置Test Script Recorder对接口进行动态化处理处理全局变量以…

2. 资源管理

2. 资源管理 文章目录 2. 资源管理2.1 资源管理介绍2.2 YAML语言介绍2.3 资源管理方式2.2.1 命令式对象管理2.2.2 命令式对象配置2.2.3 声明式对象配置 2.4. 模拟使用普通用户来操作 2.1 资源管理介绍 在kubernetes中&#xff0c;所有的内容都抽象为资源&#xff0c;用户需要通…

二十九、【进阶】MySQL索引的概述和索引查询

1、索引概述 2、 普通查询和索引查询 &#xff08;1&#xff09;基础演示 无索引查询&#xff1a;在查询信息时&#xff0c;比如查询年龄age45的员工&#xff0c;系统会遍历字段为age的列&#xff0c;在找到age45的员工后&#xff0c;依旧会向下扫描&#xff0c;直到表末&…

如何使用 Dijkstra 算法找到从源到所有顶点的最短路径--附C++/Java源码

给定一个图和图中的源顶点,找到从源到给定图中所有顶点的最短路径。 例子: 输入: src = 0,图形如下图所示。 输出: 0 4 12 19 21 11 9 8 14解释:从 0 到 1 的距离 = 4。 从 0 到 2 的最小距离 = 12。0->1->2 从 0 到 3 的最小距离 = 19。0 ->1-

Python基础语法(3)

目录 一、函数 1.1 函数是什么 1.2 函数语法格式 1.3 函数参数 1.4 函数返回值 a. 一个函数中可以有多个 return 语句 b. 执行到 return 语句&#xff0c;函数就会立即执行结束&#xff0c;回到调用位置 c. 一个函数是可以一次返回多个返回值的。使用 , 来分割多个返回值…

Jmeter基础篇

1.性能测试指标 【虚拟用户数】&#xff1a;线程用户 【并发数】&#xff1a;指在某一时间&#xff0c;一定数量的虚拟用户同时对系统的某个功能进行交互&#xff0c;一般通过集合点实现 【事务】:事务代表一个完整的功能&#xff0c;一个接口可以是事务&#xff0c;多个接口…

MyBatisPlus(十一)包含查询:in

说明 包含查询&#xff0c;对应SQL语句中的 in 语句&#xff0c;查询参数包含在入参列表之内的数据。 in Testvoid inNonEmptyList() {// 非空列表&#xff0c;作为参数List<Integer> ages Stream.of(18, 20, 22).collect(Collectors.toList());in(ages);}Testvoid in…

【C语言】转圈报数问题(三种方法--指针,数组)

题目&#xff1a;有n个人围成一圈&#xff0c;顺序排号。从第一个人开始报数&#xff08;从1到3报数&#xff09;&#xff0c;凡报到3的人退出圈子&#xff0c;问最后留下的是原来第几号的那位。 方法一&#xff1a; #include <stdio.h> #define N 10int main() {int …

systemverilog function的一点小case

关于function的应用无论是在systemverilog还是verilog中都有很广泛的应用&#xff0c;但是一直有一个模糊的概念困扰着我&#xff0c;今天刚好有时间来搞清楚并记录下来。 关于fucntion的返回值的问题&#xff1a; function integer clog2( input logic[255:0] value);for(cl…