一、任务要求
1、开机默认或按下静态按键显示16*16点阵图像“凹凸电子”(如下图所示)
2、按下动态按键后由下至上循环显示指定汉字“闲鱼凹凸电子”(如下图所示)
二、系统原理
以stc89c52为主控芯片,为驱动16*16LED点阵屏,横向1~16引脚通过俩块74HC595芯片级联驱动,而纵向A~P引脚则由两块74HC138操控电平,依赖定时器中断来控制滚动汉字的速度,采用外部中断完成多种模式的切换。
三、整体方案
1、单片机最小系统
社区内有关单片机最小系统科普不少,各位大佬讲的又是通俗易懂,那我就略掉啦,欸嘿(*/ω\*)
哦,对啦~除开单片机的“电源电路”“复位电路”“晶振电路”这三位不可或缺的大神外,同样值得注意的知识点还有单片机的EA引脚,她表示“存取外部程序代码”。当EA = 1时,执行片内程序存储器ROM中的程序,而EA = 0时,执行片外ROM中的程序。虽然该引脚内部置有TLL,在悬空(啥都不接)情况理论调试为高电平,但CMOS本身较为容易受外接干扰等因素,故建议尽量按照原有案例或说明进行连接。
由于proteus自身的特性(缺陷),该软件中的单片机其实不接最小系统压根不影响工作······,然而该接最好还是解一下,就当养成好习惯啦。
2、16*16点阵电路
如上图,初次了解LED点阵屏但了解多位数码管工作原理肯定能很快理解。她们本质上相同,都是由数个二极管整齐排布(数码管摆了个“8.”,而LED点阵则摆的更像矩阵),从横向将各个二极管的正极(负极)相连并引出用来作"行",从纵向将各个二极管的负极(正极)相连并引出用来作"列",控制方式也与多位数码管高度相似,各“行”相当于“公共端”依次赋值位选,各“列”相当于“A-dp”用于发送段码。
3、74HC595(串转并)电路
如果我们只想使用一块74HC595来解决单片机IO口的扩展问题,直接上网搜素该芯片的中文资料手册,我们立即就可以知道想要在程序上实现功能,只需每当spi_shcp上升沿到来时,spi_ds引脚当前电平值在移位寄存器中左移一位,在下一个上升沿到来时移位寄存器中所有位都会向左移一位,这样连续进行8次,就可以把数组中每一个数(8位的数)送到移位寄存器;然后当spi_stcp上升沿到来时,移位寄存器的值将会被锁存到锁存器里,并从Q1-7引脚输出。
然而毕竟是16*16的LED点阵,所以想要实现两块74HC595的级联,我们需要看到串行输出(Q7'),考虑到当spi_shcp上升沿到来的同时Q7’也会串行输出移位寄存器中高位的值,所以只需要将第一块595的Q7'接向第二块595的DS端就没有问题啦。
#ifndef __74HC595_H_
#define __74HC595_H_
/**********************************
包含头文件
**********************************/
#include<reg51.h>
#include "intrins.h"
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
/**********************************
PIN口定义
**********************************/
sbit SER = P1^4;
sbit RCLK = P1^3;
sbit SRCLK = P1^2;
/**********************************
函数声明
**********************************/
/*向74HC595发送一个字节的数据*/
void Hc595SendByte_8(uchar dat);
/*向74HC595发送两个字节的数据*/
void Hc595SendByte_16(uint dat);
#endif
#include"74HC595.H"
/*******************************************************************************
* 函 数 名 : Hc595SendByte_8(uchsr dat)
* 函数功能 : 向74HC595发送一个字节的数据
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Hc595SendByte_8(uchar dat)
{
uint a;
SRCLK = 1;
RCLK = 1;
for(a=0;a<8;a++) //发送8位数
{
SER = dat >> 7; //从最高位开始发送
dat <<= 1;
SRCLK = 0; //发送时序
_nop_();
_nop_();
SRCLK = 1;
}
RCLK = 0;
_nop_();
_nop_();
RCLK = 1;
}
/*******************************************************************************
* 函 数 名 : Hc595SendByte_16(uchsr dat)
* 函数功能 : 向74HC595发送两个字节的数据
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Hc595SendByte_16(uint dat)
{
uint a;
SRCLK = 1;
RCLK = 1;
for(a=0;a<16;a++) //发送16位数
{
SER = dat &0x8000; //从最高位开始发送
dat <<= 1;
SRCLK = 0; //发送时序
_nop_();
_nop_();
SRCLK = 1;
}
RCLK = 0;
_nop_();
_nop_();
RCLK = 1;
}
可以看到单个595(void Hc595SendByte_8(uchar dat);)与两个595级联(void Hc595SendByte_16(uint dat);)后的驱动代码并没有太大区别,仅仅只是输入的16进制数(dat)的字节数不同罢了。
值得注意的是"uint"与”uchar“的区别,两者占用空间有所不同,uchar的内存占用空间为1个字节,8比特。uint的内存占用空间为2个字节,16比特。(我开发的时候就在这个东西上面吃过亏)
4、74HC138译码器电路
正如前面所说,点阵的工作原理与数码管相似,那么一定存在所谓的公共端,所以这里我们选择”行共阴“作为所谓的公共端,为了驱动点阵的公共端们,必须一个一个轮流赋值低电平,所以为了实现节约IO口的设计,锵锵,还有比74HC138更适合这项工作的嘛。
74HC138的程序设计完全依照真值表(真值表可是数电芯片的灵魂啊,混蛋)
#ifndef __74HC138_H_
#define __74HC138_H_
/**********************************
包含头文件
**********************************/
#include<reg51.h>
//---重定义关键词---//
#ifndef uchar
#define uchar unsigned char
#endif
#ifndef uint
#define uint unsigned int
#endif
/**********************************
PIN口定义
**********************************/
#define HC138_DATAPINS P2
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
/**********************************
函数声明
**********************************/
/*改变74HC138数据输出*/
void ZdyHc138_8(uint i);
/*改变两个74HC138数据输出*/
void ZdyHc138_16(uint i);
#endif
#include"74HC138.H"
/*******************************************************************************
* 函 数 名 : ZdyHc138_8(uint i)
* 函数功能 : 改变74HC138数据输出
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void ZdyHc138_8(uint i)
{
switch(i)
{
case(0):
LSA = 1;LSB = 1;LSC = 1; break; //显示第0位
case(1):
LSA = 0;LSB = 1;LSC = 1; break; //显示第1位
case(2):
LSA = 1;LSB = 0;LSC = 1; break; //显示第2位
case(3):
LSA = 0;LSB = 0;LSC = 1; break; //显示第3位
case(4):
LSA = 1;LSB = 1;LSC = 0; break; //显示第4位
case(5):
LSA = 0;LSB = 1;LSC = 0; break; //显示第5位
case(6):
LSA = 1;LSB = 0;LSC = 0; break; //显示第6位
case(7):
LSA = 0;LSB = 0;LSC = 0; break; //显示第7位
}
}
/*******************************************************************************
* 函 数 名 : ZdyHc138_16_16(uint i)
* 函数功能 : 改变两个74HC138数据输出
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void ZdyHc138_16(uint i)
{
switch(i)
{
case(0):
HC138_DATAPINS = 0xFD; break; //显示第0位
case(1):
HC138_DATAPINS = 0xDD; break; //显示第1位
case(2):
HC138_DATAPINS = 0xBD; break; //显示第2位
case(3):
HC138_DATAPINS = 0x9D; break; //显示第3位
case(4):
HC138_DATAPINS = 0x7D; break; //显示第4位
case(5):
HC138_DATAPINS = 0x5D; break; //显示第5位
case(6):
HC138_DATAPINS = 0x3D; break; //显示第6位
case(7):
HC138_DATAPINS = 0x1D; break; //显示第7位
case(8):
HC138_DATAPINS = 0xFE; break; //显示第8位
case(9):
HC138_DATAPINS = 0xFA; break; //显示第9位
case(10):
HC138_DATAPINS = 0xF6; break; //显示第10位
case(11):
HC138_DATAPINS = 0xF2; break; //显示第11位
case(12):
HC138_DATAPINS = 0xEE; break; //显示第12位
case(13):
HC138_DATAPINS = 0xEA; break; //显示第13位
case(14):
HC138_DATAPINS = 0xE6; break; //显示第14位
case(15):
HC138_DATAPINS = 0xE2; break; //显示第15位
}
}
这个就比较简单了, 只是两块芯片一起工作时要考虑到,行共阴们有且只能有一个低电平,所以需要改变E2与E3的电平来使两块芯片分别使能,只有一块138时就不需要考虑这么多直接接地即可。
5、按键电路
#ifndef __EXTER_H_
#define __EXTER_H_
/**********************************
包含头文件
**********************************/
#include<reg51.h>
/**********************************
函数声明
**********************************/
/*设置外部中断0*/
void Int0Init();
/*设置外部中断1*/
void Int1Init();
#endif
#include"EXTER.H"
/*******************************************************************************
* 函 数 名 : Int0Init()
* 函数功能 : 设置外部中断0
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Int0Init() //设置INT0
{
IT0 = 1; //跳变沿出发方式(下降沿)
EX0 = 1; //打开INT0的中断允许。
EA = 1; //打开总中断
}
/*******************************************************************************
* 函 数 名 : Int1Init()
* 函数功能 : 设置外部中断1
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Int1Init() //设置INT1
{
IT1 = 1; //跳变沿出发方式(下降沿)
EX1 = 1; //打开INT1的中断允许。
EA = 1; //打开总中断
}
/*******************************************************************************
* 函 数 名 : Int0() interrupt 0
* 函数功能 : 外部中断 0 的中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Int0() interrupt 0
{
Delay_Us(1000); // 延时消抖
if(key_exter_0 == 0)
{
zt = 0;
}
}
/*******************************************************************************
* 函 数 名 : Int1() interrupt 2
* 函数功能 : 外部中断 1 的中断函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void Int1() interrupt 2
{
Delay_Us(1000); // 延时消抖
if(key_exter_1 == 0)
{
zt = 1;
}
}
按键电路实在没啥好讲的,调用了两个外部中断······,其实代码的周期不算长 ,滚动汉字用的延时还是定时器中断,普通的按键完全够用,主要是中断是真好用呀。
四、仿真绘制
五、软件实现
/*******************************************************************************
* 文件名 :main.c
* 描述 :基于51的LED点阵。
* 实验平台 :Proteus8.6
* 作者 :凹凸电子
* 日期 :2023.10
* 备注 :无
接线说明:
实验操作:
*******************************************************************************/
// 8051单片机头文件
#include <reg51.h>
// 标准输入输出头文件
#include <stdio.h>
#include"DELAY.H"
#include"74HC138.H"
#include"74HC595.H"
#include"51_LEDDIANZHEN_ZK.H"
#include"TIMER.H"
#include"EXTER.H"
// 定义unsigned char类型的uchar
#define uchar unsigned char
// 定义unsigned int类型的uint
#define uint unsigned int
// PIN口定义
sbit key_exter_0 = P3^2;
sbit key_exter_1 = P3^3;
uint j = 0,zt = 0;
// 函数定义
void hz_jt_8_8();
void hz_zy_16_16();
/*******************************************************************************
* 函 数 名 : main
* 函数功能 : 主函数
* 输 入 : 无
* 输 出 : 无
*******************************************************************************/
void main()
{
Timer0Init();
Int0Init();
Int1Init();
while (1)
{
while (zt == 0)
hz_jt_8_8();
while (zt == 1)
hz_zy_16_16();
}
}
void hz_jt_8_8()
{
uint i;
for(i = 0;i < 16;i ++)
{
ZdyHc138_16(i);
Hc595SendByte_16(hz_aoyudianzi_8[i]); //发送段选数据
Delay_Us(100); //间隔一段时间扫描
Hc595SendByte_16(0x0000); //消隐
}
}
void hz_zy_16_16()
{
uint i;
for(i = 0;i < 16;i ++)
{
ZdyHc138_16(15 - i);
Hc595SendByte_16(hz_xianyuaoyudianzi_16[i + j]); //发送段选数据
Delay_Us(100); //间隔一段时间扫描
Hc595SendByte_16(0x0000); //消隐
}
}
void Timer0() interrupt 1
{
static uint i;
TH0 = 0XFC; // 给定时器赋初值,定时1ms
TL0 = 0X18;
i++;
if (i >= 500 )
{
i = 0;
j ++;
}
if (j >= 112 || zt == 0)
{
j = 0;
}
}
六、作者留言
如你们所见,我本来是干闲鱼上接单的,叫凹凸电子,就这一套,我卖9块8。说实话,我也是一位学生,一位大学生,接单的时候遇到了不少同学的课设,学生单不好做哇,学生对价格敏感,加上好老师不会布置仅仅需要复制的作业,而且由于存在展示和答辩,学生不可以一点都不懂,所以兼职客服与工程师(毕竟整个店就我一人)的我往往很疲惫。
在干完一学生单的时候,我突然回想起自己刚开始接触嵌入式的时候,我是怎么一点一点学到现在的呢?又想起自己遇上的一些客户,他们并没有下单的念头,仅仅只是向我请教问题罢了,而我也并未拒绝。
我曾经也因为缺少一些开源资料而苦恼过,所以有感而发,敲下了这么一篇文章,请诸位学子一同共勉,文章有写得不好之处,果然还是欠功夫,还请大家不要笑话。
七、开源链接
上面的代码还差个定时器中断初始化及延时函数等,所以给个github链接,所有的文件都在里面,Proteus用的是8.6,而且还会不断更新的。
GitHub - mynameisliuyu/51_project
再送大家一本物理教材上几句我很喜欢的话