独立按键电路图
首先我们来看一下按键的电路结构图
单片机独立按键按下时一般为低电平。当按键按下时,按键引脚会连接到地(GND),使得引脚上的电压降低,因此为低电平。
其真实结构图是下面这幅图所示
当开关不按下电路不导通,I/O端口为高电位。当按键按下时,电路导通,此时I/O端口和地线直接相连,I/O端口为低电位。
在这里我们为了演示按键按下的效果,我们来将按键和LED来结合演示。
通过上面的电路图,我们要注意一下K1独立按键接的是P31引脚,而K2接的是P30引脚(可能是设计者设计错了???)
按下按键让LED发亮,松开LED熄灭
扫描原理:通过反复读取按键输入引脚的信号,然后通过识别引脚处的高低电平来判断是否有按键触发。
#include<regx52.h>
void main(){
while(1){
if(P3_0==0){
P2_0=0;
}
else{
P2_0=1;
}
}
}
按键去抖动处理
是不是只要引脚处检测到低电平,就一定是按键按下了呢?
可以看出来干扰信号的低电平持续时间比较短,那我们如何做到去抖动呢?
去抖动原理:首次检测到按键输入引脚有低电平后,稍作延时,再次读取改引脚电平,如果还是低电平,则确认为按键触发信号,否则,判断为干扰信号,不予处理。
#include<reg52.h>
sbit led1=P2^0;
sbit button=P3^1;
void delay(unsigned int num){//这里注意一下
while(num--);
}
void check(){//实现让led闪烁三下
unsigned char i;
for(i=0;i<3;i++){
led1=0;
delay(40000);
led1=1;
delay(40000);
}
}
void control(){
while(1){
if(button==0){
delay(200);//去除抖动
if(button==0){
while(button==0);
led1=~led1;//取反用的很好
}
}
}
}
void main(){
check();
while(1){
control();
}
}
这里我们在control函数中实现的是:检测按键是否按下,然后延迟去除干扰信号,再次检验,如果按键仍然处于按下状态,持续检测,直到松开手按键弹起,跳出while循环,改变led电位,让led发光。然后下一次按下按键之后松开再次改变led状态,从而实现独立按键成为led开关。
(这里手不松开时LED灯的状态不改变)
这里while(button==0)很关键,并且用取反符号很方便,这里还要注意的是delay的参数应该使用unsigned int类型,不能用char类型,空间不够大。
按键按下小测试 * 2
//实现按下一次按键模拟8位二进制进位,还是注意给led低电平才让led亮起来,所以要取反一下
#include<regx52.h>
sbit button1=P3^1;
void delay(unsigned int num){
while(num--);
}
void control(){
unsigned char i=0;
while(1){
if(button1==0){
delay(200);
if(button1==0){
while(button1==0);
i++;
P2=~i;
}
}
}
}
void main(){
control();
}
//实现按下一次按键让led1-8分别亮起来,这里可以采用移位运算符来让程序更加简单易读,并且注意取反
#include<regx52.h>
sbit button=P3^1;
void delay(unsigned int num){
while(num--);
}
void control(){
unsigned char i=0;
unsigned char a=1;
while(1){
if(button==0){
delay(200);
if(button==0){
while(button==0);
if(i==8)
i=0;
P2=~(a<<i);
i++;
}
}
}
}
void main(){
while(1){
control();
}
}
用按键来实现互相约束,一个电源开关,一个灯光开关。
#include<regx52.h>
sbit LED6=P2^5;//第一盏灯
sbit LED8=P2^7;//第二盏灯
sbit sw_led=P2^0;//电源开关灯光
sbit led_switch=P3^1;//电源开关按钮
sbit led_change=P3^0;//灯光切换按钮
void delay(unsigned int num){
while(num--);
}
unsigned char flag=0;
void control(){
while(1){
//电源开关代码
if(led_switch==0){
delay(200);
if(led_switch==0){
while(led_switch==0);
sw_led=~sw_led;
}
}
//下面这5行代码实现了关闭电源总开关后,灯光开关重置,并且两盏全部熄灭
if(sw_led==1){
flag=0;
LED6=1;
LED8=1;
}
//灯光切换代码
if(led_change==0&&sw_led==0){
delay(200);
if(led_change==0){
while(led_change==0);
flag++;
switch(flag){
case 1:LED6=0;break;
case 2:LED6=0;LED8=0;break;
case 3:LED6=1;LED8=1;flag=0;break;
}
}
}
}
}
void main(){
while(1){
control();
}
}
我们的逻辑:先打开电源才能调节灯光,灯光调节时根据按钮按下的次数不同,对次数用3取模对应三种情况,分别是一盏灯亮,两盏灯亮,两盏灯灭。在电源开关关闭之后我们开关状态要进行重置,并且所有灯光关闭。
在这里,下面按键按下的代码总都是一样的,这个需要我们直接记忆
if(led_switch==0){
delay(200);
if(led_switch==0){
while(led_switch==0);
模块化编程
在模块化编程中,一个系统被划分为多个模块,每个模块都专注于完成特定的任务或提供特定的功能。这些模块之间通过明确定义的接口进行通信和交互,而不需要了解彼此的内部实现细节。这种分解系统的方式有助于降低耦合度,减少代码之间的依赖关系,从而使系统更易于维护和升级。
举几个例子:比如说Web前端开发中的html,css,js写在不同的文件中,然后文件与文件之间建立联系,这样不用把代码都挤在一起,便于阅读和修改。又比如说QT中写游戏,我们会选择单独建立每个角色的.h和.cpp文件,然后统一在mainwindow.cpp中添加对应头文件,以此来显示。
.h文件示例
让我们来简单看一下.h大致文件的写法。
#ifndef __LCD1602_H__
#define __LCD1602_H__
// 这里放置需要条件编译的代码
#endif
这个算是我们添加.h文件的基础框架了,这个代码的意思是:#ifndef
检查是否定义了 __LCD1602_H__
宏,如果没有定义,则定义该宏并执行其中的代码。然后 #endif
结束条件编译块,确保其中的代码只在满足条件时被编译。这种结构通常用于保护头文件避免重复包含,同时也可以用于根据条件选择性地包含或排除特定代码片段。
中间存放的东西是函数的声明,具体如下
需要注意的是函数的声明需要在后面加上一个分号,不需要大括号写函数体,但是要写形参。
.c文件示例
这个是Nixie.c文件,这个文件中我们可以看到主要写的是函数的实现,这里可以看到这个函数实现用到了其他文件中的函数delay(),这样的话就得包含其他文件的.h文件,并且这里使用到了P2_2这样的宏,就要包含头文件<REGX52.H>。还要注意的是包含系统的文件使用的是<>括号,包含自定义文件使用的是"",这一点和QT一样。
LCD1602调试工具
代码来自于江协科技。
调试中间结果可视化常见的方法有三种:数码管显示,液晶屏显示,串口调试。
这里代码的话大家到这个视频中去找[5-2] LCD1602调试工具_哔哩哔哩_bilibili
这里我们可以将老师给的代码用模块化编程的方式来添加到.c和.h文件,依次来方便我们调试。
需要注意的是:
我们在调用这里面的代码时,需要先进行初始化,即先调用LCD_Init();示例如下:
#include<regx52.h>
#include"LCD1602.h"
void main(){
LCD_Init();
LCD_ShowChar(1,2,'S');
LCD_ShowString(2,2,"hello world");
while(1){
}
}
可以利用这些函数来显示我们的中间运行结果
#include<regx52.h>
#include"LCD1602.h"
void delay( unsigned int t){
while(t--);
}
void main(){
unsigned int num=0;
LCD_Init();
while(1){
LCD_ShowNum(1,1,num,2);
delay(40000);
num++;
}
}
比如这里我们就可以利用在液晶屏上面显示数字来直观地感受到数字的加减。
特别注意
特别注意
特别注意
重要的事情说三遍
unsigned int num=0;对于这句定义局部变量的代码我们一定要放到第一句。
当然,写成全局变量也是可以的,但是假如我这里写成下面这样
LCD_Init();
unsigned int num=0;
我将局部变量的定义放到了第二行,这里就会出现报错
main.c(13): error C202: 'num': undefined identifier//表示我们没有定义i这个变量
正常我们写c或者c++肯定是可以这么写的,但是单片机中使用的c语言是比较老版本的c语言,
特别是针对 8051 系列单片机的编程规范,变量的定义通常会放在函数的开头部分,这样可以确保在程序执行时可以正确地分配内存空间。将变量定义放在函数的最前面可能是一个比较好的做法,特别是在嵌入式系统编程中。
说实话挺让人无语的,但是我们还是要遵守的,接下来详细介绍一点定义变量方面c语言和单片机中c语言的差别。
令人无语的单片机c语言
c语言旧的标准规定了:在一个模块里(函数、循环体等)先声明一切所需变量后才能进行相关操作的规定。声明定义的前面不能有任何其他执行语句。
void main(){
int a=1,b=2;
c=a+b;
int out;
out=c;
}
或者这样,都是不可以的
void main(){
int a=1,b=2;
c=a+b;
for(int i=0;i++;i<8){
c++;
}
}
尤其不能在for中定义,这一点会让熟悉c++的我们倍感难受。
欢迎大家指正!