蜂鸣器

两种蜂鸣器的介绍

有源蜂鸣器一般是输入一个电流或电压即可直接驱动工作,而无源蜂鸣器则需要输入脉冲信号才可以进行工作。在51单片机开发板上的即为无源蜂鸣器。

蜂鸣器相关电路图

 

可以看出,信号是通过P15传递到ULN2003D芯片后进而传递到芯片的OUT5(即BEEP端口)再传递到蜂鸣器中的,其中ULN2003D芯片起着电流放大的作用。

控制代码

首先我们先获得控制蜂鸣器的引脚,从电路图可以看出是P15,所以:

sbit BEEP= P1^5;

因为这是无源蜂鸣器,所以我们需要给它提供脉冲信号输入才能使它工作。而当BEEP为0时有电流,BEEP为1时无电流,所以我们需要循环改变BEEP的值,主函数代码如下所示:

int main() {
    while (1)
    {
        BEEP = ~BEEP;
        deley(10);
    }
}

如果我们希望改变蜂鸣器的音调,只需要改变脉冲信号的频率即可,也就是while循环中deley()的参数。

我们也可以不断改变deley()中填入的参数来使蜂鸣器发出奇怪的声音

int main() {
    u16 time = 10; 
    u8 cnts = 50;
    u8 i;
    for(time=10;time<200;time++) {
        for(i=0;i<cnts;i++) {
            BEEP = ~BEEP;
            deley(time);
        }
    }
}

独立按键

独立按键电路图

可以看到,这4个独立按键都是一端和单片机的引脚(P3[0..3])相连,而另一端直接接地的。

这些按键的效果是,当按键没有按下时,它们对应的端口的输出是高电平,而当按键按下之后,这些端口的输出则变为低电平了。

因此我们可以使用轮询的方式查看这些端口的电平情况来检测按钮是否被按下,如果按下,则我们可以进行计数等控制其他元件的操作。

按键控制一个LED的点亮和熄灭

我们希望当点击按键时,第一个LED点亮,而在此单击时则熄灭。

按照之前的思路,我们很容易就能写出对应的控制代码:

sbit OneLED = P2^0; // 使用OneLED来控制对应的引脚的输出
sbit k1 = P3^1;

void keypros() {
    if (k1 == 0) {
        deley(1000); // 消抖
        if (k1 == 0) {
            OneLED = ~OneLED;
        }
        while (!k1);
    }
}

int main() {
    while (1) {
        keypros();           
    }
}

重要的是keypros()函数中的内容,当我们点击第一个按钮时,k1的值会变为0,因此我们进行轮询的时候就会进入到keypros()函数的第一个if中。然后进行deley(1000);是为了进行消抖处理。然后再次查看k1是否为0,若不为0则说明此次按动是非法的(即可能是抖动产生的),而如果为0则进入切换LED状态的代码。然后使用while (!k1);来等待抬手,若没有这句话,则此次函数调用结束后我们的手还没有抬起来又再次进入函数了,会导致灯光不断亮灭变换,和我们希望的不符,所以使用这个循环语句进行等待抬手再次轮询。

运行效果:
在这里插入图片描述

设置控制框架

可以看出,我们的按键控制其他硬件的框架已经搭好了,以后我们需要控制其他元件时就只需要将keypros()函数中的OneLED = ~OneLED;这个部分切换为其他的控制代码即可。但是直接切换又会显得非常的麻烦,因此我们使用C语言的一个好用的东西函数指针来完成这项功能。

其实回调函数这种东西是非常常见的,特别是在JavaScript这个语言中被大量地使用,在JavaScript中函数是一等公民,可以随便作为参数进行传递,而在C语言可以通过函数指针将一个函数作为另一个函数的参数进行传递,例如:

typedef void (*CallBack)();

int getRunTime(CallBack function) {
    clock_t startTime = clock();
    function();
    clock_t endTime = clock();
    return endTime - startTime;
}

这里定义了一个回调函数指针类型CallBack,其指向的函数无参数无返回值。然后我们可以将一个函数指针作为参数传递到getRunTime()函数中,这样运行结束后我们即可获得传递进去的函数的运行时间是多少了,例如:

void countFunc() {
    for (int i = 0; i < 10000000; ++i) {}
}

int main() {
    int runTime = getRunTime(countFunc); <--将函数指针传递进去,相当于回调函数
    cout << "runTime: " << runTime << "ms" << endl;
}

参考博客:https://www.jianshu.com/p/2862d05f64e8

所以我们使keypros()函数接收一个函数指针,然后将OneLED = ~OneLED;替换为传入的函数:

typedef void (*CallBack)(void);

void keypros(CallBack function) {
    if (k1 == 0) {
        deley(1000);
        if (k1 == 0) {
            function();
        }
        while (!k1);
    }
}

这样,我们写一个切换LED状态的函数:

void switchLED() { 
	OneLED = ~OneLED; 
}

然后我们在主函数中将它传递到keypros()函数中:

int main() {
    while (1) {
        keypros(switchLED);
    }
}

即我们希望在单击按钮后发生什么事件,就将这个事件的函数传递到keypros()中即可,这样按钮控制事件的框架就搭好了。

按钮点击计数器

既然框架已经搭好,我们继续编写事件函数即可。

此时事件为计数器+1并显示出来。

u8 code smgduan[16] = {
	0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07,
	0x7f, 0x6f, 0x77, 0x7c, 0x39, 0x5e, 0x79, 0x71
};

u8 addOneDigitCnt = 1;
void addOneDigit() {
    P0 = smgduan[addOneDigitCnt];
    addOneDigitCnt++;
    if (addOneDigitCnt == 16) {
        addOneDigitCnt = 0;
    }
}

int main() {
    P0 = smgduan[0];
    while (1) {
        keypros(addOneDigit);
    }
}

运行效果:
在这里插入图片描述

按钮点击流水灯

我们点击按钮是LED灯向前传递一格。

代码:

void LEDMove() {
    LED <<= 1;
    if (LED != 0xfe) {
        LED += 1;
    }
}

int main() {
    LED = 0xfe;
    while (1) {
        keypros(LEDMove);
    }
}

运行效果:
在这里插入图片描述