注:此单片机型号为 STC15F2K60S2.
如图,发光二极管L1—L8 共阳接法,VCC为电源正极,高电平。
已知发光二极管正向导通反向截至,要使得二极管发光,就要让Q1—Q8为低电平。可控管脚为P00—P07,所以要输入P00—P07为低电平。为了让P0能够影响到Q1—Q8,就要使得锁存器 M74HC573M1R 导通,即要让管脚LE为高电平。又管脚LE与Y4C连接,所以Y4C也为高电平。
又Y4C连接了一个或非门,或非门的输入端为Y4和WR。因为WR已用跳线帽与GND连接在了一起,GND为接地,所以WR为0(低电平)。
根据或非门逻辑表格:
Y4 WR Y4C
0 0 1
0 1 0
1 0 0
1 1 0
所以,当Y4C为0时,Y4为1;当Y4C为1时,Y4为0.
所以,要使得Y4C为高电平,Y4就要为低电平,即Y4为0.
又Y4是由 74HC138 位译码器的输入端A、B、C来控制,以 C、B、A 为逻辑顺序,CBA组成二进制数,来选中Y0—Y7,被选中者即为低电平。
所以,要使得 Y4 为低电平,CBA 要为 100.
另外,由于 P0 口是复用的,意味着也要控制别的器件,所以如果一直让 LE 为高,那么就在控制别的器件的时候就会影响到 LED,所以在锁存器导通后,应该截止保存住它的状态。
整理逻辑:
点亮LED:
Q1—Q8为低电平 —> P00-P07为低电平 -> 导通锁存器,LE为高电平 ->
Y4C为高电平 -> 或非门,Y4为低电平 -> 译码器,CBA组合为100 ->
锁存状态,断开锁存器,LE为低电平,Y4C为低电平 ->
或非门,Y4为高电平 -> 译码器,CBA组合为000.(不为100即可)
同理,熄灭LED:
Q1—Q8为高电平 —> P00-P07为高电平 -> 导通锁存器,LE为高电平 ->
Y4C为高电平 -> 或非门,Y4为低电平 -> 译码器,CBA组合为100 ->
锁存状态,断开锁存器,LE为低电平,Y4C为低电平 ->
或非门,Y4为高电平 -> 译码器,CBA组合为000.(不为100即可)
来源:蓝桥杯官方资料。
#include "reg52.h"
// #include "absacc.h"
// 关闭外设
void Cls_Peripheral(void)
{ // IO模式(J13-2和J13-3相连)
P0 = 0xFF;
P2 = P2 & 0x1F | 0x80; // P27~P25清零,再定位Y4C
P2 &= 0x1F; // P27~P25清零
P0 = 0;
P2 = P2 & 0x1F | 0xA0; // P27~P25清零,再定位Y5C
P2 &= 0x1F; // P27~P25清零
//XBYTE[0x8000] = 0xFF; // MM模式(J13-2和J13-1相连)
//XBYTE[0xA000] = 0;
}
// LED显示
void Led_Disp(unsigned char ucLed)
{ // IO模式(J13-2和J13-3相连)
P0 = ~ucLed; //unsigned char
P2 = P2 & 0x1F | 0x80; // P27~P25清零,再定位Y4C
P2 &= 0x1F; // P27~P25清零
//XBYTE[0x8000] = ~ucLed; // MM模式(J13-2和J13-1相连)
}
// 延时函数(最小约1ms@12MHz)
void Delay(unsigned int num)
{
unsigned int i;
while(num--)
for(i=0; i<628; i++);
}
// 主函数
void main(void)
{
unsigned char i, j;
Cls_Peripheral();
while(1)
{ // 4个亮度等级
for(i=0; i<4; i++)
for(j=0; j<100; j++)
{
Led_Disp(0xff);
Delay(i+1);
Led_Disp(0);
Delay(4-i);
}
}
}
第一段:关闭外设 。
因为C51单片机默认P口为高电平,所以LED和蜂鸣器等等在程序运行前就会工作。具体原理看这个@我的头绝不是面团捏的 —— 蓝桥杯单片机比赛系列 1 初探关闭外设。
代码就直接放这了:
// 关闭外设
void Cls_Peripheral(void)
{ // IO模式(J13-2和J13-3相连)
P0 = 0xFF;
P2 = P2 & 0x1F | 0x80; // P27~P25清零,再定位Y4C
P2 &= 0x1F; // P27~P25清零
P0 = 0;
P2 = P2 & 0x1F | 0xA0; // P27~P25清零,再定位Y5C
P2 &= 0x1F; // P27~P25清零
//XBYTE[0x8000] = 0xFF; // MM模式(J13-2和J13-1相连)
//XBYTE[0xA000] = 0;
}
(注:这个IO模式还是不懂诶,懂了我再填上,先放个参考博客——liu_endong——51 单片机 ——IO 口工作模式及配置)
第二段:LED 显示
// LED显示
void Led_Disp(unsigned char ucLed)
{ // IO模式(J13-2和J13-3相连)
P0 = ~ucLed; //unsigned char
P2 = P2 & 0x1F | 0x80; // P27~P25清零,再定位Y4C
P2 &= 0x1F; // P27~P25清零
//XBYTE[0x8000] = ~ucLed; // MM模式(J13-2和J13-1相连)
}
先设置 P0 口,选择要点亮的灯的编码 ucled ,再选中 Y4 导通锁存器,在 LED 点亮后再锁存。
第三段:延时函数
// 延时函数(最小约1ms@12MHz)
void Delay(unsigned int num)
{
unsigned int i;
while(num--)
for(i=0; i<628; i++);
}
这个就不用说了吧,应该都懂。
第四段:主函数
void main(void)
{
unsigned char i, j; //设置无符号char型参数 i,j.
Cls_Peripheral(); //关闭外设
while(1) //循环
{ // 4个亮度等级
for(i=0; i<4; i++)
for(j=0; j<100; j++)
{
Led_Disp(0xff);//1111 1111,全灭
Delay(i+1); //设置延迟时间
Led_Disp(0); //0000 0000 全亮
Delay(4-i); //
}
}
}
呼吸灯其实就是以不同的亮度点亮led灯,关键就在于如何以不同的亮度来点亮led灯,也就是改变led灯的亮度。简单的方法就是在肉眼观察不到的时间范围内,改变 led 点亮时间的占比,从而形成不同亮度的视觉效果。
// 4个亮度等级
for(i=0; i<4; i++)
for(j=0; j<100; j++)
{
Led_Disp(0xff);//1111 1111,全灭
Delay(i+1); //设置延迟时间
Led_Disp(0); //0000 0000 全亮
Delay(4-i); //
}
两个循环,第一个循环和Delay函数一起用来设置亮与灭的时间,从而设置灯的亮度等级。第二个循环则设置每一个亮度等级的运行时间。
第二个循环内的内容,是将亮与灭的时间分为四份,按照不同搭配组合调节灯的亮度。
led.c
/****************************************************************************
* Copyright (C), 2022,Moqim
* 文件名: led.c
* 内容简述:LED初始化、LED控制函数
*
* 文件历史:
* 版本号 日期 作者 说明
* 01a 2022-04-24 Moqim 创建该文件
*
* All rights reserved
*/
#include "led.h"
/****************************************************************************
* 函数名: LED_Init()
* 功 能: 关闭全部的LED灯
* 输 入: 无
* 输 出: 无
*/
void LED_Init(void)
{
P0 = 0Xff; //拉高所有PO端口
P2 = P2 & 0x1f | 0x80;// P27~P25清零,再定位Y4C 关闭led
P2 &= 0x1f; // P27~P25清零,锁存
}
/****************************************************************************
* 函数名: LED_Ctrl()
* 功 能: 关闭全部的LED灯
* 输 入: 无
* 输 出: 无
*/
void LED_Ctrl(unsigned char ucLed)//uc 表示 "unsigned char" 类型
{
P0 = ~ucLed; //ucled取反 0000 0001 -> 1111 1110
P2 = P2 & 0x1f | 0x80;// P27~P25清零,再定位Y4C
P2 &= 0x1f; // P27~P25清零,锁存
}
led.h
//为了防止头文件被重复引用,应当用 ifndef/define/endif结构产生预处理块
#ifndef __LED_H
#define __LED_H
#include "stc15f2k60s2.h"
void LED_Init(void);
void LED_Ctrl(unsigned char ucLed);
#endif /*__LED_H*/
main.c
/****************************************************************************
* Copyright (C), 2022,Moqim
* 文件名: main.c
* 内容简述:实现十个等级的呼吸灯与向右移动的LED流水灯
*
* 文件历史:
* 版本号 日期 作者 说明
* 01a 2022-04-24 Moqim 创建该文件
*
* All rights reserved
*/
//头文件调用区
#include "stc15f2k60s2.h"
#include "led.h"
//变量定义区
//函数声明区
void LED_NotifyLight(void);
void LED_Runningwater(void);
void Delay_ms(unsigned int num);
//Main Body
int main(void)
{
LED_Init();
while (1)
{
LED_NotifyLight();
LED_Runningwater();
}
}
/****************************************************************************
* 函数名: LED_NotifyLight()
* 功 能: 实现10个等级的呼吸灯
* 输 入: 无
* 输 出: 无
* 说 明:像油画调色一样,不同比例的亮与暗的搭配,可以实现LED的不同亮度
*/
void LED_NotifyLight(void)
{
int i, j;
for(i=0; i<10; i++)//划分等级,将LED的亮度划分为十份
for(j=0; j<100; j++)//为保证肉眼能够观察,将每个时段的时间扩大一百倍
{
//熄灭时间 + 点亮时间 = 10(按比例理解也是一样的)
LED_Ctrl(0xff);
Delay_ms(i+1);//设定LED熄灭的时间
LED_Ctrl(0);
Delay_ms(10-i);//设定LED点亮的时间
}
}
/****************************************************************************
* 函数名: LED_Runningwater()
* 功 能: 实现向右移动的LED流水灯
* 输 入: 无
* 输 出: 无
* 说 明:注意蓝桥杯板子上LED灯的排序,是低位在前,~0x01在第一个,~0x02在第二个,~0x04在......
*/
void LED_Runningwater(void)
{
int i = 0;//变量定义放在外面,或者紧挨着函数开头的括号
for (i = 0;i<8;i++)//右移8次 (包括移动0位)
{
LED_Ctrl(0x01 << i);//取反后为:1111 1110 -> 1111 1101 ->1111 1011 ->......
Delay_ms(500);
}
}
/****************************************************************************
* 函数名: Delay_ms()
* 功 能: 延时
* 输 入: unsigned int num:延时num毫秒
* 输 出: 无
*/
void Delay_ms(unsigned int num)//软件延时不精确,凑合用吧
{
int i,j;
for(i = 0;i<num;i++)
{
for(j = 0;j<625;j++);
}
}
int i;
while(1){
for(i=0;i<8;i++){
P0 = ~(1<<i);//从右往左
//从左往右 P0 = ~(0x80>>i);
P2 = P2 & 0x1F | 0x80;
P2& = 0x1F;
}
}
流水灯连续左移右移可能会导致有一个灯闪烁两次,为了解决这个问题,可以使得左移 1-7,右移也 1-7,直接交叉点亮。