【51单片机实验笔记】LED篇(四) LED点阵的基本控制


前言

本章接触的硬件依然与LED息息相关,它是多个LED矩阵形式封装的一个显示模块。有了它,我们就可以制作出流动字幕自定义动画等效果了。

本节涉及到的封装源文件可在《模块功能封装汇总》中找到。

本节完整工程文件已上传GitHub仓库地址,欢迎下载交流!


硬件介绍

LED点阵的基本单元由LED组成,常见点阵屏大小为8x816x16LED点阵根据行的连接方式进行区分。共阳极点阵的特点是每行LED共阳极每列LED共阴极共阴极点阵的特点是每行LED共阴极每列LED共阳极

图1 8x8点阵屏
图2 共阳极LED点阵原理图

诚然,与数码管类似,我们不可以通过IO口直接驱动LED点阵,原因有两点:

  • 单片机引脚为弱上拉灌电流较大,但拉电流很小,难以直接驱动
  • 点阵屏极其消耗IO口资源,小规格的8x8点阵就要消耗16个IO引脚,这显然是不合理的。

因此,我们一般采用驱动芯片来实现LED点阵屏的驱动。


驱动芯片

数码管控制中,为节省IO资源,采用了动态扫描的方式实现多个数码管的同时显示。LED点阵的控制思想也与之类似。但稍有不同的是,在数码管中我们采用的是74HC245驱动芯片74HC138译码器的解决方案(当然也可以应用于LED点阵),而LED点阵采用了74HC595芯片,它可以实现仅使用3个IO口就控制8个引脚,还可以级联

需要明确一点,任何元器件的控制方案都不止一个,可以通过查阅芯片手册来选择合适的芯片。仅从学习角度而言,不要太局限自己的认知。


1. 74HC595芯片(串转并)

图1 开发板原理图
图2 74HC595引脚图

IO扩展芯片,可以实现将8位串行输入转为三态并行输出。内置移位寄存器存储寄存器,由各自的时钟控制。

  • SER串行输入8bit数据)。
  • SRCLK(SCK)移位寄存器时钟引脚。当接收上升沿时,将串行数据由高位至低位依次存进移位寄存器8位)中(一个上升沿移一位,类似于压栈过程)。最大工作频率可达50MHz
  • RCLK(RCK)存储寄存器时钟引脚。当接收上升沿时,将移位寄存器中的所有数据一次性全部存进存储寄存器中。
  • OE(G)使能引脚低电平使能,高电平并行输出为高阻态
  • SRCLR(SCLR)复位引脚低电平复位(清除移位寄存器)。
  • QH'串行输出。用于74HC595级联(可以实现3个IO控制多个LED点阵!)。最大传输延时20ns

注:时钟最小脉宽16ns


2. MAX7219芯片

MAX7219也是LED点阵屏控制芯片,它内部有8x8数据寄存器,可以自动扫描显示一张静态图形。这显然优于74HC595,它只有8位的数据寄存器,显示一张静态图形,需要靠动态扫描完成。

现在市面上比较大型的LED点阵基本采用MAX7219芯片缺点就是!),它同样也只用3个IO口,但可以自动完成扫描


i. 引脚说明

图1 MAX7219引脚图
图2 74HC595引脚图
  • DIN串行输入16bit数据)。
  • LOAD(CS)片选引脚。该引脚状态不影响串行数据加载至移位寄存器中(MAX7219)。拉高时将最后16bit数据锁定。
  • CLK时钟引脚。当接收上升沿时,串行数据高位至低位加载至移位寄存器中。当接收下升沿时,数据从DOUT输出最大频率10MHz
  • DOUT串行输出。用于级联,在DIN接收数据16.5个时钟周期后有效。最大传输延时20ns
  • ISET亮度调节。通过一个可调电阻连接到V+,可实现段电流调节。
  • DIG0~7位选引脚。用于连接共阴极接受灌电流。当关闭时,上拉V+高电平
  • SEGA~G,SEGDP段选引脚。根据待显示段码提供驱动电流。当该段关闭时,下拉GND

ii. 时序分析

在这里插入图片描述
数据格式
在这里插入图片描述


iii. 寄存器说明

14 个可寻址的数字寄存器(digit and control registers)和控制寄存器。数据寄存器由一个在片上的8×8 的双向SRAM 来实现,它们可以直接寻址,所以只要在电压大于2V 的情况下每个数据都可以独立的修改或保存。
共有8个。

控制寄存器包括译码控制寄存器、亮度控制寄存器、扫描界限寄存器、关断模式寄存器、测试控制寄存器。

数字寄存器由片上8x8双端口SRAM实现。它们是直接寻址的,因此只要V+通常超过2V,单个数字就可以更新和保留数据。控制寄存器包括解码模式、显示强度、扫描限制(扫描位数)、关机和显示测试(所有led灯亮)。

当MAX7219处于关机模式时,扫描振荡器停止工作,将所有段电流源拉到地,并将所有数字驱动器拉到V+,从而使显示消失。MAX7221是相同的,除了驱动器是高阻抗。数字寄存器和控制寄存器中的数据保持不变。关机可用于节省电力,也可作为报警,通过先后进入和离开关机模式来闪烁显示屏。对于在关机模式下的最小电源电流,逻辑输入应在地或V+ (cmos逻辑电平)。

通常,MAX7219/ MAX7221离开关机模式的时间不超过250µs。显示驱动程序可以在关机模式下编程,并且关机模式可以被显示测试功能覆盖。

在初始上电时,所有控制寄存器复位,显示为空白,MAX7219/MAX7221进入关机模式。在显示器使用之前对显示驱动程序进行编程。否则,它将初始设置为扫描一位,它将不解码数据寄存器中的数据,并且强度寄存器将设置为其最小值。

  • 无操作寄存器 No-Op Register),地址0X00
    • 当某个显示器无需
  • 译码模式寄存器Decode-Mode Register),地址0X09
    • 对于数码管,采用BCD译码可以免去软件译码,轻松实现0-9-符号的显示。对于LED点阵使用非译码模式
    • 对应数据位置1开启BCD译码置0关闭。
  • 亮度寄存器Intensity Register),地址0X0A
    • 16级亮度调节(0x00~0x0F
  • 扫描限制寄存器Scan Limit Register),地址0X0B
    • 扫描列数,一般为8列,即0x07
  • 关断寄存器Shutdown Register),地址0X0C
    • 数据0x01表示显示0x00表示关闭
  • 显示器测试寄存器Display-Test Register),地址0X0F
    • 正常模式0x00
    • 测试模式0x01,会点亮所有LED,不受寄存器控制。

原理分析

LED点阵可以显示静态画面滚动画面,实现的细节稍有不同,总结如下:

  • 规则矩形无需动态扫描,想要点亮哪排哪列直接给对应的引脚赋电平即可。例如,点亮LED点阵中的某个LED
  • 自定义图形:由于自定义图形往往不规则非矩形),必须采用动态扫描的方式。图案的设计可以自己手算,或者借助取模工具。例如,点亮一个爱心图形
  • 自定义动画:以上都是静态画面LED点阵也可以实现比较简陋动画效果每一帧的画面需要提前保存在数组中每一帧都采用动态扫描显示,一段时间后跳至下一帧,连贯成动画。例如,滚动字幕旋转风车螺旋线等等。

对于自定义动画,由于片内RAM只有128B,而片内ROM8KB,可以通过code关键字将动画数据存放在ROM中。另外,有些具备数学规律的动画可以通过算法计算下一帧的数据,而不用将所有的动画帧都存放在数组中占用空间


软件实现

1. 爱心图片

实现的效果:静态显示一个爱心
在这里插入图片描述

#include <REGX52.H>
#define LED_PORT P0

typedef unsigned char u8;
typedef unsigned int u16;

sbit SER = P3^4; //串行输入
sbit ST = P3^5; //存储寄存器时钟引脚
sbit SH = P3^6; //移位存储器时钟引脚

u8 code LED_portX_Array[] = {0x7e,0xbd,0xdb,0xe7}; // 巧用对称性
u8 code LED_portY_Array[] = {0x38,0x7c,0x7e,0x3f};

void delay(u16 t){
	while(t--);
}

void LED_control(u8 dat){
	u8 i;
	//将一个字节拆分成串行输入
	for(i=0;i<8;i++){
		SER = dat >> 7; //先将最高位送入SER中
		dat <<= 1; //左移1位(去掉最高位)更新数据
		SH = 0; //给移位寄存器时序脉冲
		delay(1);
		SH = 1; //检测到上升沿时将SER数据读入移位寄存器中
		delay(1);
	}
	ST = 0; //当一个字节传输完毕,此时移位寄存器已满。给存储寄存器时序脉冲
	delay(1);
	ST = 1;//检测到上升沿时将移位寄存器中的8位数据全部读入存储寄存器中。通过并行输出引脚可以直接检测到
	delay(1);
}

void main(){
	u8 i; //必须先定义,放在第一个
	P0 = 0xff; //初始全熄灭
	while(1){
		for(i=0;i<4;i++){
			LED_control(0x00); //消影
			LED_control(LED_portY_Array[i]);
			P0 = LED_portX_Array[i];
			delay(100); //1ms
		}
	}
}

代码中比较关键的就是LED_control函数中的内容,只要理解74HC595芯片的工作原理,就不难理解代码的逻辑。需要注意的是,采用了动态扫描就必然会有重影的问题,要记得消影


2. 旋转大风车

这个旋转风车比较抽象,哈哈哈哈哈,主要是点阵数太少。
在这里插入图片描述
为了提高移植性复用性,我将74HC595驱动显示延时的代码抽取出来。

LED_Matrix.h

#ifndef _LED_MATRIX_
#define _LED_MATRIX_

#include "delay.h"

void LED_Init();
void LED_Animation_Show(u8 ,u8);

#endif

LED_Matrix.c

#include "LED_Matrix.h"


#define LED_PORT P0


sbit SER = P3^4; //串行输入
sbit ST = P3^5; //存储寄存器时钟引脚
sbit SH = P3^6; //移位存储器时钟引脚

/**
  *  @brief 串转并驱动代码
  *  @param dat:8位串行数据
  *  @retval 返回值:无
  */
void LED_control(u8 dat){
	u8 i;
	//将一个字节拆分成串行输入
	for(i=0;i<8;i++){
		SER = dat >> 7; //先将最高位送入SER中
		dat <<= 1; //左移1位(去掉最高位)更新数据
		SH = 0; //给移位寄存器时序脉冲
		delay_10us(1);
		SH = 1; //检测到上升沿时将SER数据读入移位寄存器中
		delay_10us(1);
	}
	ST = 0; //当一个字节传输完毕,此时移位寄存器已满。给存储寄存器时序脉冲
	delay_10us(1);
	ST = 1;//检测到上升沿时将移位寄存器中的8位数据全部读入存储寄存器中。通过并行输出引脚可以直接检测到
	delay_10us(1);
}


void LED_Init(){
	LED_PORT = 0xff;
}

/**
  *  @brief 显示对应静态画面(8*8)
  *  @param datX:阴极,datY:阳极
  *  @retval
  */
void LED_Animation_Show(u8 datX, u8 datY){
	LED_control(datY); //阳极码
	LED_PORT = ~(0x80>>datX);
	delay_10us(100);
	LED_Init(); //消影
}

main.c

#include "LED_Matrix.h"

#define SPEED 8 //动画速度

u8 code WindMill_Animation_Array[] = {
	0x40,0x63,0x36,0x1C,0x38,0x6C,0xC6,0x02,
	0x0C,0x18,0x90,0xDE,0x7B,0x09,0x18,0x30,
	0x02,0xC6,0x6C,0x38,0x1C,0x36,0x63,0x40,
};

void main(){
	// i为当前点亮的LED列号;
	// t为计数器,用于记录进入下一帧画面的时间;
	// step为当前帧数(8的倍数)
	u8 i, t=0, step=0; 
	while(1){
		for(i=0;i<8;i++){
			LED_Animation_Show(i, WindMill_Animation_Array[i+step]);
		}
		t++;
		if(t > SPEED){
			t = 0;
			step += 8; // 播放下一帧
			if(step > 16){
				step = 0;
			}
		}
	}
}

其中,WindMill_Animation_Array[]数组由取模软件生成。

确定好动画由几帧组成,对于每一帧,都是采用动态扫描显示。而动画的流畅度取决于每一帧停留的时间,可以通过调节SPEED来测试。


3. 滚动日期

在这里插入图片描述

#include "LED_Matrix.h"

#define SPEED 20

// 2022年12月30日
u8 code Animation_Array[] = {
	0x61,0x83,0x85,0x89,0x71,0x00,0x7E,0x81,
	0x81,0x7E,0x00,0x61,0x83,0x85,0x89,0x71,
	0x00,0x61,0x83,0x85,0x89,0x71,0x00,0x44,
	0xDC,0x54,0x7F,0x54,0x44,0x00,0x40,0xFF,
	0x00,0x61,0x83,0x85,0x89,0x71,0x00,0x01,
	0xFE,0xA8,0x82,0xFF,0x00,0x42,0x91,0x99,
	0x66,0x00,0x7E,0x81,0x81,0x7E,0x00,0xFF,
	0x91,0x91,0xFF,0x00,0x00,0x00,0x00,0x00,
	0x00,0x00,0x00};


void main(){
	u8 i, count=0, j=0;
	LED_Init();
	while(1){
		for(i=0;i<8;i++){
			LED_Animation_Show(i, Animation_Array[i+j]);
		}
		count++;
		if(count > SPEED){
			count = 0;
			j++;
			if(j > sizeof(Animation_Array)/sizeof(Animation_Array[0]) - 8){
				j = 0;
			}
		}
	}
}

实现原理和旋转风车类似,只不过旋转风车一帧一帧移动的(一帧需要8个字节),滚动字幕一个字节一个字节的移动。


4. 螺旋线动画

PS:所有代码都写在了这一个C文件里
在这里插入图片描述

#include <REGX52.H>
#include <INTRINS.H>
#define LED_PORT P0
#define SPEED 30

typedef unsigned char u8;
typedef unsigned int u16;

sbit SER = P3^4; //串行数据输入引脚
sbit ST_CP = P3^5; //存储寄存器时钟引脚
sbit SH_CP = P3^6; //移位寄存器时钟引脚

//全局变量
u8 x,y,num_step,count,dir,posY[8] = {0};

//延时函数
void delay(u16 t){
	while(t--);
}

//生成对应位置的二进制代码
u8 produce_B_Code(u8 pos){	
	return 0x01<<pos;
}

//串行数据生成对应并行数据
void Ser2Para(u8 dat){
	u8 i;
	for(i=0;i<8;i++){
		SER = dat>>7;
		dat <<= 1;
		SH_CP = 0;
		_nop_(); // 延迟一个机器周期
		SH_CP = 1; //获得一个上升沿
		_nop_(); 
	}
	ST_CP = 0;
	_nop_();
	ST_CP = 1;
	_nop_();
}

void clear_LED(){
	LED_PORT = 0xff; //清屏
	Ser2Para(0x00); 
}

//显示每帧画面
void display(u8 XDATA,u8 YDATA){
	LED_PORT = ~produce_B_Code(XDATA); //按列由右往左扫描
	Ser2Para(YDATA);
	delay(100);
	clear_LED(); //消影
}

//更新方向与数组
void update_posY(u8 dir){
	//判断方向
	switch(dir%4){
		case 0: //向下
		{
			y = y - 1; //更新当前点坐标
			posY[x] += produce_B_Code(y); //更新需要点亮的Y坐标码
			break;
		}
		case 1: //向右
		{
			x = x - 1;
			posY[x] += produce_B_Code(y); //更新需要点亮的Y坐标码
			break;
		}
			
		case 2: //向上
		{
			y = y + 1; //更新当前点坐标
			posY[x] += produce_B_Code(y); //更新需要点亮的Y坐标码
			break;
		}
		case 3: //向左(往高位走)
		{
			x = x + 1; //更新当前点坐标
			posY[x] += produce_B_Code(y); //更新需要点亮的Y坐标码
			break;
		}
	}
}

//重启
void reset(){
	u8 k;
	x = 4,y = 4,num_step = 1,count=0,dir=0;
	for(k=0;k<8;k++){
		if(k == x){
			posY[x] = produce_B_Code(y);//记录初始化的值
		}else{
			posY[k] = 0;
		}
	}
}


/* 
*	count: 记录每个动作(上下,左右)需要执行几次
*	x,y:记录当前点的坐标
*	posY[]:记录当前需要点亮的灯的十六进制码
*/
void main(){
	u8 i,j=0;
	reset(); //初始化
	while(1){
		for(i=0;i<8;i++){
			//逆时针
			display(i,posY[i]); //把数组传过去
		}
		count++;
		//下一帧
		if(count > SPEED){
			count = 0;
			//更新一下posY数组,显示下一个点
			update_posY(dir);
			//记录每更改一次方向需要几帧
			j++;
			//每两组换向多走一步
			num_step = dir/2 + 1;
			//更新方向
			if(j>=num_step){
				dir++;
				j=0; //复位
			}
		}
		//结束条件
		if(x==0 && y==8){
			delay(10000); //保持画面
			clear_LED(); //清屏
			delay(50000);
			reset(); //重新开始
		}
	}
}

这个动画我没有提前将每一帧都计算出来,而是每次计算出下一步要点亮的灯,同时保留当前的灯


总结

LED点阵相对来说显示的东西还是很丰富的吧!发挥你的想象力,你也可以创新很多有趣的图案和动画的。

  • 6
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
51单片机俄罗斯方块程序LED点阵是一种基于51单片机控制LED点阵显示器,用于实现俄罗斯方块游戏的显示和操作。 这个程序的实现主要包括以下几个方面: 1. 硬件连接:将51单片机的IO通过适当的电路连接到LED点阵的行列控制引脚,以实现对点阵控制。 2. 初始化:在程序开始时,需要对LED点阵进行初始化。这包括设置行列引脚的输入输出模式、设置刷新频率等。 3. 俄罗斯方块的显示:通过在点阵上分别亮起不同的LED点来显示俄罗斯方块的各个方块。可以通过定义一个数组,保存不同形状的方块在点阵上的显示方式,根据方块的位置和形状,通过控制点阵的引脚亮灭来实现。 4. 俄罗斯方块的控制:通过按键输入控制俄罗斯方块的移动、旋转等操作。可以通过读取按键的状态,判断用户输入,然后调用相应的函数来实现方块的移动、旋转等操作。 5. 游戏的逻辑控制:通过不断刷新LED点阵的显示,更新方块的位置和形状,并检测和处理碰撞等情况,实现俄罗斯方块游戏的进行。可以通过定时断来控制刷新频率,从而实现游戏的流畅进行。 总之,51单片机俄罗斯方块程序LED点阵是一项基于51单片机LED点阵的工程项目,通过提供适当的硬件连接和软件实现来实现俄罗斯方块游戏在LED点阵上的显示和操作。这样的程序既可以用于娱乐,也可以作为学习和研究的项目。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

悬铃木下的青春

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值