鱼离水则身枯,心离书则神索。
- 前言
- 闪灯
- 呼吸灯
- 流水灯
- 二进制数显示灯
- 蜂鸣器节拍
- 流水音乐会
- 总结
前言
上回书咱们简单了解了一点有关特殊功能寄存器sfr、通用输入输出GPIO、位操作运算符sbit和一个不靠单片机上的晶振(拿来定时的)的依托于单片机CPU空操作的ms级延时函数,这节课咱们来复盘一下并更深入的实操一下
闪灯
既然咱们已经能让LED亮起来了,那一闪一闪亮晶晶对咱们来说也就只是加一个延时函数的事情:
#include<regx52.h>
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
while(1) {
P2_0 = 0; // 点亮LED
delay_ms(100);
P2_0 = 1; // 熄灭LED
delay_ms(100);
}
}
呼吸灯
咱们可是会利用变量的,那当然可以让它闪烁的频率像一个垂垂老矣的迟暮耄耋老者愈发慢下来
#include<regx52.h>
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
unsigned int i = 100;
while(1) {
P2_0 = 0; // LED亮
delay_ms(i);
P2_0 = 1; // LED灭
delay_ms(i);
if(++i >= 1000) i = 100; // 呼吸范围控制
}
}
流水灯
一个LED太孤单了,让咱们把一个GPIO口的八个LED伙伴都运行起来呢?
#include<regx52.h>
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
unsigned int i;
while(1) {
for(i=0; i<8; ++i) {
P2 = ~(1 << i); // 流水灯位移
delay_ms(100);
}
}
}
咱知道,这里肯定会有朋友直接懵逼了,因为咱们之前点亮LED使用的是位操作法,即使用sbit对寄存器地址的某一单独位定义后进行操作;但是咱们这么想,这里一个地址对应八个二进制数也就是八个LED,要定义代码冗余且不说效率也低啊,万一以后老板让我同时控制音乐会上的几万个LED光代码就几万行也太难写了,于是咱们引入总线操作法,也就是一次性操作整个P2端口(8个引脚)的方法:咱上一篇说到过一个GPIO对应的特殊功能寄存器sfr绑定的地址有八个二进制数,诶,那咱们直接写入一个表示八位二进制数转换成十六进制的数不就好了吗?
但是这里的核心代码 P2 = ~(1 << i); // 流水灯位移还是看不懂咋办?别急,这里涉及到位运算取反操作的一些性质,咱这样列个表会清晰很多:
i值 | 1 << i(二进制) | 取反操作(~) | 最终P2输出(十六进制) | 点亮效果 |
---|---|---|---|---|
0 | 00000001 | 11111110 | 0xFE | 第1个LED亮 |
1 | 00000010 | 11111101 | 0xFD | 第2个LED亮 |
2 | 00000100 | 11111011 | 0xFB | 第3个LED亮 |
3 | 00001000 | 11110111 | 0xF7 | 第4个LED亮 |
4 | 00010000 | 11101111 | 0xEF | 第5个LED亮 |
5 | 00100000 | 11011111 | 0xDF | 第6个LED亮 |
6 | 01000000 | 10111111 | 0xBF | 第7个LED亮 |
7 | 10000000 | 01111111 | 0x7F | 第8个LED亮 |
等效的位操作法代码:
#include<regx52.h>
// 定义所有LED引脚
sbit LED0 = P2^0;
sbit LED1 = P2^1;
sbit LED2 = P2^2;
sbit LED3 = P2^3;
sbit LED4 = P2^4;
sbit LED5 = P2^5;
sbit LED6 = P2^6;
sbit LED7 = P2^7;
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
unsigned char i;
while(1) {
// 正向流水灯
for(i=0; i<8; ++i) {
// 关闭所有LED
LED0 = 1; // 高电平熄灭
LED1 = 1;
LED2 = 1;
LED3 = 1;
LED4 = 1;
LED5 = 1;
LED6 = 1;
LED7 = 1;
// 点亮当前LED
switch(i) {
case 0: LED0 = 0; break; // 低电平点亮
case 1: LED1 = 0; break;
case 2: LED2 = 0; break;
case 3: LED3 = 0; break;
case 4: LED4 = 0; break;
case 5: LED5 = 0; break;
case 6: LED6 = 0; break;
case 7: LED7 = 0; break;
}
delay_ms(100);
}
}
}
二进制数显示灯
诶,恭喜你,已经把成都理工大学单片机课程中的GPIO实验做得七七八八了,接下来你会问:“主播主播你的设计确实简单,但是还是太无聊了,有没有什么既简单又有意思的实验?”,有的兄弟,有的,来看看这一款八位二进制数显示灯,先看代码:
#include<regx52.h>
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
unsigned char counter = 0;
while(1) {
P2 = ~counter; // 二进制显示
delay_ms(100);
counter++;
}
}
counter从0(0000,0000)到255(1111,1111)循环,可以直观感受二进制进位(看到LED的波浪式闪烁)
蜂鸣器节拍
蜂鸣器一般分为有源蜂鸣器和无源蜂鸣器,按咱现在的学习和知识储备直白地来说,前者音调较为固定(可用PWM调制,后面会讲,但是效果不好),无源蜂鸣器则音域较广(PWM调制效果好),而这里咱们之所以衔接蜂鸣器而不是按键,是因为蜂鸣器与LED一样都是显示器件,它与LED一样都是依赖简单的GPIO地O也就是OUTPUT输出功能,咱们先把这一类显示器件的使用归类到一起,让观者可以在共性的模板中更好的学习
(这里笔者的板子对应的蜂鸣器是P1的第5位)
#include<regx52.h>
sbit beep = P1^5;
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
while(1) {
beep = !beep; // 0响1停,P口引脚最开始默认输出1
delay_ms(10); // 控制节拍速度
}
}
流水音乐会
好了,咱们已经熟悉了简单显示器件的使用,咱们来做个综合实验
#include<regx52.h>
sbit beep = P1^5;
void delay_ms(unsigned int n) {
unsigned int i;
while(n--) {
for(i=0; i<280; ++i);
}
}
int main() {
unsigned int i;
while(1) {
for(i=0; i<8; ++i) {
P2 = ~(1 << i); // 流水灯
beep = !beep;
delay_ms(10);
beep = !beep;
delay_ms(100);
}
}
}
总结
简单显示器件的使用都是大同小异,已经难不倒咱们了,于是接下来咱们要去学习一点比较困难的显示器件,它们可能需要的操作往往不局限在代码内,可能是翻阅数据手册,可能是计算进制数……