51 点亮LED
文章目录
LED
中文名:发光二极管
外文名:Light Emitting Diode
简称:LED
用途:照明、广告灯、指引灯、屏幕
特性:电流从正极(长引脚)进,负极(短引脚)出
STC89C52RC的LED模块(图源:普中科技参考手册)
LED两侧链接又电阻,起限流作用(限流电阻,防止LED因电流过大而烧毁)
由二极管的特性可知,控制LED即为控制单片机引脚输出高低电平(5V/0V),当输出为低电平时,LED处有电流通过,亮起
MCU核心引脚出处对应的电阻(图源:普中科技参考手册)
单片机控制设备的方式
MCU集成有CPU,代码在CPU中执行
MCU中还有寄存器(一种存储器),寄存器以8个为一组,每一位寄存器后面的导线上,有一个驱动器(用于增大电流),导线最终连接到IO口(1为高电平,0为低电平)
即CPU通过控制寄存器来控制硬件
点亮第一颗LED
添加单片机信息
由于Keil 5中没有包含STC生产的单片机的信息,为了方便操作,我们需要通过 stc-isp 软件带有的功能向 Keil 5 中添加相应的信息
添加过后在 Keil5 中即能看到STC生产的单片机的对应型号
程序写入
P2是单片机控制LED的IO口,我们需要控制LED的亮灭即控制P2口输出的0和1
在写程序的时候直接输入原始二进制数据,程序会把它识别成10进制,即我们需要把二进制数据转换成16进制形式(和二进制的转换较为方便)写入
需要添加头文件使程序识别P2(可以在第一行右键直接添加)
#include<STC89C5xRC.H>
//添加单片机对应的头文件使程序识别相应接口
void main()
{
P2 = 0xFE;
// 1111 1110
//16进制为0xfe
//10进制为245
}
//用常规的C程序写法好像也可以(这个项目可以,其它没试过)
#include<STC89C5xRC.H>
int main()
{
P2 = 0xfe;
return 0;
}
我们可以使用计算器的程序员模式进行快速的进制转换
只要单片机开始工作后,程序不会自动停止执行,而是在主函数执行完毕后再次执行,因此我们可以采用死循环的方式让单片机反复执行一个循环使程序停下来
#include<STC89C5xRC.H>
void main()
{
P2 = 0xFE;
while(1)
{
}
}
LED闪烁
让LED闪烁即写一个控制LED不断开关的程序
#include<STC89C5xRC.H>
void main()
{
while(1)
{
P2 = 0xFE;
P2 = 0xFF;
}
}
此时,我们看到的现象是LED常量,但亮度不如我们第一次点亮LED,此时的LED实际是在快速闪烁,为此,我们需要添加一个延时使闪烁的现象更加明显
我们可以通过stc-isp中“软件延时计算器”的功能来生成我们所需要的延时对应的函数
#include <STC89C5xRC.H>
#include <INTRINS.H>
void Delay500ms(void) //@12.000MHz
{
unsigned char data i, j, k;
_nop_();
//需要添加头文件<INTRINS.H>
i = 4;
j = 205;
k = 187;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
while(1)
{
P2 = 0xFE;
Delay500ms();
P2 = 0xFF;
Delay500ms();
}
}
此时LED会以1s为周期闪烁
通过自定义延时实现流水灯
#include <STC89C5xRC.H>
void Delay1ms(unsigned int xms) //@12.000MHz
{
unsigned char data i, j;
while(xms)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
void main()
{ while(1)
{
P2 = 0xFE;
Delay1ms(100);
P2 = 0xFD;
Delay1ms(100);
P2 = 0xFB;
Delay1ms(100);
P2 = 0xF7;
Delay1ms(100);
P2 = 0xEF;
Delay1ms(100);
P2 = 0xDF;
Delay1ms(100);
P2 = 0xBF;
Delay1ms(100);
P2 = 0x7F;
Delay1ms(100);
}
}
利用C语言操作单片机的基础知识
数据类型
类型 | 是否有符号 | 关键字 | 位数 | 可表示范围 |
---|---|---|---|---|
整形 | 有 | (signed) short | 16 | -32768~32767 |
(signed) int | 16 | -32768~32767 | ||
(signed) long | 32 | -2147483648~2147483647 | ||
无 | unsigned short int | 16 | 0~65535 | |
unsigned int | 16 | 0~65535 | ||
unsigned long int | 32 | 0~4294967295 | ||
浮点型(实型) | 有 | float | 32 | 3.4e-38~3.4e38 |
double | 64 | 1.7e-308~1.7e308 | ||
字符型 | 有 | char | 8 | -128~127 |
无 | unsigned char | 8 | 0~255 |
运算符
类别 | 运算符 | 意义 | 类别 | 运算符 | 意义 |
---|---|---|---|---|---|
算数 | + | 加 | 逻辑 | && | 逻辑与 |
- | 减 | || | 逻辑或 | ||
* | 乘 | ! | 逻辑非 | ||
/ | 除 | 位运算 | << | 按位左移 | |
% | 取余 | >> | 按位右移 | ||
= | 赋值 | & | 按位与 | ||
判断 | > | 大于 | | | 按位或 | |
>= | 大于等于 | ^ | 按位异或 | ||
< | 小于 | ~ | 按位取反 | ||
<= | 小于等于 | ||||
== | 等于 | ||||
!= | 不等于 |
关于位运算操作符的详细信息,见作者的另一篇博文
基本语句
判断语句
if(逻辑表达式1)
{
语句体1;
}
else if(逻辑表达式2)
{
语句体2;
}
else
{
语句体3;
}
//含义:
//如果逻辑表达式1成立,则执行第一个大括号中的语句
//如果逻辑表达式2成立,则执行第二个大括号中的语句
//注意:else if 可以有多个
//如果逻辑表达式1、2均不成立,则执行第三个大括号中的语句
循环语句
//while循环:
while(逻辑表达式)
{
循环体;
}
//含义:
//若逻辑表达式成立,则反复执行循环体,每执行一次对逻辑表达式进行一次判断
//for循环:
for(初始化;判断(逻辑表达式);调整)
{
循环体;
}
//含义:对变量进行初始化并判断,若判断成立则进入循环,并在每次循环后进行调整,在循环结束后进行下一次判断
//其相对于while更简洁的原因就是在while语句中,初始化、判断、调整三个部分是分开的
//do...while语句
do
{
循环体;
}
while (逻辑表达式);
//含义:先执行一次循环体,并在结束后判断是否进行下一次循环
选择语句
//switch语句:
switch(整形表达式):
{
//语句项;
case a:
语句;
break;
case b:
语句;
break;
default:
语句;
}
//含义:
//当switch后的整形表达式输出整形时,执行对应case中的语句
//break跳出switch语句(只能跳出自己所在的):若无break,则会从进入的地方一直向下执行(break的实际效果时把语句列表划分为不同的分支部分)
//利用default输出case中没有的选择:(每个switch语句只能出现一个default)
//注意:
//switch语句必须用整形(包括字符(ASCII))
//default可以放在switch语句中的任意位置,包括case前面
//case没有顺序要求
//switch允许嵌套使用
独立按键控制LED
轻触按键:相当于是一种电子开关,按下时开关接通,松开时开关断开,实现原理是通过轻触按键内部的金属弹片受力弹动来实现接通和断开
左右两边的两个触点分别链接,按下时金属片被压扁,将左右两组触点相连,此时相当于四个触点相连
STC89C52RC上的独立按键(图源:沃兹济派德)
STC89C52RC上独立按键模块的连接方式(图源:普中科技参考手册)
单片机接点的时候,所有IO都是高电平,当按键按下时,就相当于接地,变成低电平(即我们可以通过读取相应IO口的电平来识别按键是否被按下。
由于头文件STC89C5xRC.H并未对每个接口8个位数进行单独定义,我们可以在头文件中自行对其定义
//声明独立的位数
sbit P2_0 = 0XA0;
sbit P2_1 = 0XA1;
sbit P2_2 = 0XA2;
sbit P2_3 = 0XA3;
sbit P2_4 = 0XA4;
sbit P2_5 = 0XA5;
sbit P2_6 = 0XA6;
sbit P2_7 = 0XA7;
后来我发现,STC89C5xRC.H对单个位数是有定义的,但是没加下划线,所以在调用的时候可以把下划线去了
实现按下独立按键亮起
#include <STC89C5xRC.H>
void main()
{
//P2 = 0xfe;
P2_0 = 1;
while(1)
{
if(P31 == 0)
//判断按键状态
{
P2_0 = 0;
}
else
{
P2_0 = 1;
}
}
}
独立按键控制LED状态
当我们按下按键时,由于按键的机械结构(金属片),会产生抖动
按键的抖动:对于机械开关,当机械触点断开、闭合时,由于机械触点的弹性作用,一个开关在闭合时不会马上稳定地接通,在断开时也不会一下子断开,所以在开关闭合及断开的瞬间会伴随一连串的抖动
按键的消抖:
- 硬件消抖:通过添加装置
- 软件消抖:通过代码延时
我们一般使用软件消抖
#include<STC89C5xRC.H>
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char data i, j;
while(xms > 0)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
//生成一个1ms的延时并自行改造成适应任何延时要求的函数
void main()
{
while(1)
{
if(P31 == 0)
{
Delay(20);
// 按键消抖
while(P31 == 0);
//如果不松手,就会一直在这个循环里面
//这里如果不写循环,那么会在按键按下时切换,现象是在按下时LED快速闪烁
Delay(20);
//松手检测
P2_0 = ~P2_0;
}
}
}
独立按键控制LED显示二进制
#include <STC89C5xRC.H>
//这里我将延时1ms的函数放入了头文件中便于使用,只要传入一个数字便可得到对应ms的延时
void main()
{
unsigned char LED_num = 0;
//较旧的C语言标准规定,变量要在函数的开头定义,若改变位置,会显示变量未定义
//较新的标准(如C99)支持在任意位置定义变量
while(1)
{
if(P31 == 0)
{
Delay(20);
//这里把Delay作为一个函数放进STC89C5xRC.H里面了,方便后续直接调用
while (P31 == 0);
Delay(20);
//P2默认位1111 1111,即全为高电平
P2 = ~LED_num;
LED_num++;
}
}
}
//放入头文件的函数
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char data i, j;
while(xms > 0)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
xms--;
}
}
独立按键控制LED移位
#include <STC89C5xRC.H>
void main()
{
unsigned char LED_num = 0;
P2 = ~0x01;
// 亮起第一个灯
while(1)
{
//按下K1时,亮灯右移
if(P31 == 0)
{
Delay(20);
while(P31 == 0);
Delay(20);
if (LED_num == 7)
{
LED_num = 0;
}
else
{
LED_num++;
}
P2 = ~(0x01 << LED_num);
}
//当按下K2时,亮灯左移
if(P30 == 0)
{
Delay(20);
while(P30 == 0);
Delay(20);
if(LED_num == 0)
{
LED_num = 7;
}
else
{
LED_num--;
}
P2 = ~(0x01 << LED_num);
}
}
}