2014-04-10 15:14:57 huang20083200056 阅读数 4632
  • LED点阵-第1季第8部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第8个课程,讲解了16*16LED点阵的驱动方式和文字显示。本课程的学习目标是理解点阵式LED屏幕的驱动方式、74HC595的时序和编程等。

    2289 人正在学习 去看看 朱有鹏

51单片机开发系列四

LED点阵扫描显示

象棋小子    1048272975

LED点阵屏发光亮度强,指示效果好,可以制作运动的发光图文,更容易吸引人的注意力,信息量大,随时更新,有着非常好的广告和告示效果。笔者此处就LED点阵屏动态扫描显示作一个简单的介绍。

1. LED点阵屏显示原理概述

图1-1为一种8x8的LED点阵单色行共阳模块的内部等效电路图,对于红光LED其工作正向电压约为1.8v,其持续工作的正向电流一般10ma左右,峰值电流可以更大。如下图,当某一行线为高电平而某一列线为低时,其行列交叉的点就被点亮,当某一行线为低电平时,无论列线如何,对应的这一行的点全部为暗。LED点阵屏显示就是通过一定的频率进行逐行扫描,数据端不断输入数据显示,只要扫描频率足够高,由于人眼的视觉残留效应,就可以看到完整的文字或图案信息。通常有4、8、16线扫描方式,扫描行数越少,点阵的显示亮度越好,但相应硬件数据寄存器需求也越多。


图1-1 点阵内部原理图

2. 硬件设计

微控制器的IO口均不能流过过大的电流,LED点亮时有约10ms的电流,因此LED点阵引脚不要直接接单片机IO口,应先经过一个缓冲器74HC573。单片机IO口只需很小的电流控制74HC573即可间接的控制LED点阵某一行(或某一列),而74HC573输出也能负载约10ms的电流。设置LED每点驱动电流为ID =15ma,这个电流点亮度好,并且有一定的裕度,即使电源输出电压偏高也不会烧毁LED,限流电阻值

R = (VCC- VCE – VOL – VLED) / ID

VCC为5v供电,VCE为三极管C、E间饱和电压,估为0.2v, VOL为74hc573输出低电平时电压,不同灌电流,此值不一样,估为0.2v,具体查看规格书,VLED为红光驱动电压,估为1.7v,根据上式可算出限流电阻为R = 200R。

LED点阵屏需接收逐个扫描信号,扫描到相应列(或行),对应的列(或行)数据有效,即显示这一列(或行)的信息。一般产生扫描信号是需要采用专门的译码器,如三线八线译码器74HC138,这样可硬件保证任意时刻只有一列(或一行)正在扫描,并且可减少微控制器的IO口占用。市面上的51开发板对于LED点阵屏的设计基本都没有采用译码器,直接用单片机IO产生扫描信号,为兼容软件,笔者此处也不加译码器,软件保证IO口产生相应的扫描信号。

当某一列(或一行)LED点均点亮时,电流约15max8=90ma流过这一列(或一行)公共端,微控制器IO口无法直接驱动这个电流,需加三极管驱动,由于51单片机低电平灌电流较大,因此适合采用PNP三极管作为驱动。三极管基极电流设为2ma即可让三极管饱和,最大驱动电流远大于90ma。基极偏置电阻阻值

Rb =(VCC - VEB – VOL) / IB

VCC为5v供电,VEB为三极管E、B间的导通电压0.7v,VOL为单片机IO口输出低电平时电压,可根据规格书估为0.2v,故Rb = 2k即可。


图2-1 8X8共阴LED点阵原理图

3. 驱动实现

LED点阵数据口接P0口,扫描选择线接P2口的0~7位。对于动态扫描,都是有一个扫描频率的,LED屏扫描频率下限为50HZ,低于一定的扫描频率,显示会闪烁。频率过高,则亮度较差且占用cpu资源。一般整个屏扫描一遍时间为约10ms较合适(即扫描频率100HZ),我们采用的是8线扫描方式,每一行点亮时间为1.5ms,扫描一遍为12ms。为保证这个刷新频率,通常是通过定时器来周期性进行点阵屏刷新。

显示屏显示往往会涉及到画点、画线、画图等复杂的运算,改变屏幕的信息,只需处理显存中的数据,因此对于显示屏,是需要开辟出一块内存空间作为显存使用的。8X8点阵每个点可用1 bit表示,一行1字节,显存8字节即可。由于点阵屏像素点太少,没有必要实现画线、画图等复杂操作,笔者此处仅对点阵屏画点、文字上下左右移动进行代码实现。

点阵屏动态显示功能模块文件Matrix.c内容如下:

#include"reg52.h"

#include"Matrix.h"

 

// 每个LED点需1位保存,8X8点阵需8字节显存

static unsigned char FrameBuffer[8];

 

// 外部模块通过该函数获得显存内存位置进行处理

unsigned char *MatrixGetBuffer()

{

    return FrameBuffer;

}

 

// 点阵刷新,保证以一定周期调用刷新

void MatrixScan()

{

    static unsigned char Select =0; // 记录扫描的选择线

    // 列数据输出到点阵数据端口

    MatrixOutputData(FrameBuffer[Select]);

    // 扫描信号输出到点阵扫描选择端口

    MatrixOutputSelect(Select);

    Select++; // 进入到下一行扫描

    if (Select >= 8) {

        Select= 0; // 所有行已扫描,回到第一行再次开始扫描

    }

}

 

// LED点阵屏打点函数,对(x, y)位置进行亮,灭,状态取反

voidMatrixSetPoint(unsigned char x, unsigned char y, unsigned char Operation)

{

    if (x>7 || y>7) { // 位置保证在点阵屏区域内

        return;

    }

    switch (Operation) {

    case SET: // (x, y)位置置位,灯灭

        FrameBuffer[x] |= 1<< y;

        break;

    case CLEAR: // (x, y)位置清零,灯亮

        FrameBuffer[x] &= ~(1<< y);

        break;

    case NEGATE: // (x, y)位置取反,灯状态改变

        FrameBuffer[x] ^= 1<< y;

        break;

    default:

        break;

    }

}

 

// LED点阵屏清屏,显存对应1的位置,灯灭,0相应的灯才点亮

voidMatrixClearScreen()

{

    unsigned char i;

    for (i=0; i<8; i++) {

        FrameBuffer[i] = 0xff;

    }

}

 

// 点阵平移,上下左右四个方向平移1,平移空缺位置用数据Filling填充

void MatrixMove(unsignedchar Direction, unsigned char Filling)

{

    unsigned char i;

    switch (Direction) { // 判断平衡的方向

    case MOVE_UP: // 向上平移1,每列数据第7位移到第6位,如此类推

        for (i=0; i<8; i++) {

            FrameBuffer[i] =(FrameBuffer[i]>>1) | ((Filling<<(7-i))&0x80);

        }

        break;

    case MOVE_DOWN: // 向下平移1,每列数据第0位移到第1位,如此类推

        for (i=0; i<8; i++) {

            FrameBuffer[i]= (FrameBuffer[i]<<1) | ((Filling>>i)&0x01);

        }

        break;

    case MOVE_LEFT: // 向左平移1,右一列的数据移到当前列中,如此类推

        for (i=0; i<7; i++) {

            FrameBuffer[i] = FrameBuffer[i+1];

        }

        FrameBuffer[i] = Filling;

        break;

    case MOVE_RIGHT: // 向右平移1,左一列的数据移到当前列中,如此类推

        for (i=7; i>=1; i--) {

            FrameBuffer[i] = FrameBuffer[i-1];

        }

        FrameBuffer[i] = Filling;

        break;

    default:

        break;

    }

}

 

我们在点阵屏模块头文件Matrix.h中实现模块的宏定义及接口访问宏实现,使之方便移植及修改接口配置。模块头文件同时也引出模块的接口函数,如MatrixScan()为点阵屏刷新函数,需周期性调用刷新点阵屏显示。点阵屏动态显示功能模块文件Matrix.h内容如下:

#ifndef__Matrix_H__

#define__Matrix_H__

 

#ifdef__cplusplus

extern"C" {

#endif

 

#define SET         0x1 //置1操作

#define CLEAR           0x2 // 清0操作

#define NEGATE      0x3 //取反操作

 

#defineMOVE_UP     0x1 // 向上平移1

#defineMOVE_DOWN   0x2 // 向下平移1

#defineMOVE_LEFT   0x3 // 向左平移1

#defineMOVE_RIGHT 0x4 // 向右平移1

 

// 列数据输出到P0口

#defineMatrixOutputData(Dat) {P0 = (Dat);}

// P2口输出对应列的扫描选择线,低有效

#defineMatrixOutputSelect(Select) {P2 = ~(1<<(Select));}

 

 

voidMatrixClearScreen(void);

voidMatrixMove(unsigned char Direction, unsigned char Filling);

unsigned char*MatrixGetBuffer(void);

voidMatrixScan(void);

voidMatrixSetPoint(unsigned char x, unsigned char y, unsigned char Operation);

 

#ifdef__cplusplus

}

#endif

 

#endif/*__Matrix_H__*/

 

外部应用通过引入点阵屏的模块头文件Matrix.h来实现调用点阵屏驱动函数,简单测试调用(心形在点阵屏内随机平移)实现如下:

#include"reg52.h"

#include"Matrix.h"

 

// 心形坐标数据

static unsigned charcode HeartShape[][2] = {

{3, 3}, {4, 2}, {5,3}, {5, 4}, {4, 5},

{3, 6}, {2, 5}, {1,4}, {1, 3}, {2, 2},

};

 

// 以定时器时间为计时标准,记录时间间隔

static volatileunsigned int SystemTick = 0;

 

// 定时器1.5ms中断处理进行数码管刷新

void T0_Interrupt()interrupt 1

{

    TH0 = (65536-1500) / 256;

    TL0 = (65536-1500) % 256;

    SystemTick++; // 记录时间间隔

    MatrixScan(); // 刷新数码管

}

 

void T0_Init()

{

    TMOD = 0x01; // 定时器0工作方式1

    // 1.5ms计时中断(12M)

    TH0 = (65536-1500) / 256;

    TL0 = (65536-1500) % 256;

    ET0 = 1; // 定时器T0中断允许

    EA = 1; // 总中断允许

}

 

 

void main()

{

    unsigned char *pBuffer;

    unsigned char State = 0;

    unsigned char Point;

    unsigned char Direction;

    unsigned char DataAnd;

    unsigned char i;

    // 定时器初始化

    T0_Init();

    // 获得点阵显存,以作数据处理

    pBuffer = MatrixGetBuffer();

    // 点阵屏清屏

    MatrixClearScreen();

    // 开启定时器进行计时以及点阵扫描

    TR0 = 1;

 

    Point = 0;

    while(1) {

       switch (State) {

       case 0:    //状态0为逐点打出心形

           if (SystemTick > 334) { // 500ms打心形的一个点

              SystemTick = 0;

           MatrixSetPoint(HeartShape[Point][0],HeartShape[Point][1], CLEAR);

              Point++;

              if (Point >sizeof(HeartShape)/sizeof(HeartShape[0])) {

                  State = 1; // 心形打完,进入状态1,是否到边界判断

                  Direction = TL0& 0x3; // 随机得出心形的移动方向

              }                

           }

           break;

 

       case 1:    // 状态1为心形是否移动到点阵屏边界的判断

           switch (Direction) { // 移动方向判断是否到相应方向的边界

           case 0:    // 左边界判断

              // 第一列的点有一个亮,则认为图形到了左边界

              if (pBuffer[0] !=0xff) {

                  Direction = TL0& 0x3; // 重新选择移动方向

              } else {

                  State = 2; // 未到左边界,进入状态2进行左平移

              }

              break;

           case 1:    // 右边界判断

              // 第八列的点有一个亮,则认为图形到了右边界

              if (pBuffer[7] !=0xff) {

                  Direction = TL0& 0x3; // 重新选择移动方向

              } else {

                  State = 2; // 未到右边界,进入状态2进行右平移

              }

              break;

           case 2:    // 上边界判断

              // 所有列的第一行点有一个亮,则认为图形到了上边界

              DataAnd = 0xff;

              for (i=0; i<8; i++) {

                  DataAnd &= pBuffer[i];

              }

              if (DataAnd & 0x1) {

                  State = 2; // 未到上边界,进入状态2进行上平移                           

              } else {

                  Direction = TL0& 0x3; // 重新选择移动方向

              }

              break;

           case 3:    // 下边界判断

              // 所有列的第八行点有一个亮,则认为图形到了下边界

              DataAnd = 0xff;

              for (i=0; i<8; i++) {

                  DataAnd &= pBuffer[i];

              }

              if (DataAnd & 0x80) {

                  State = 2; // 未到下边界,进入状态2进行下平移                           

              } else {

                  Direction = TL0& 0x3; // 重新选择移动方向

              }

              break;

           default:

              break;

           }

           break;

 

       case 2:    // 状态2为对点阵屏平移

           if (SystemTick < 667){  // 1s平移1次

              continue;

           }

           SystemTick = 0;

           switch (Direction) {

           case 0:    // 左平移,平移后的空缺位置灭

              MatrixMove(MOVE_LEFT, 0xff);

              break;

           case 1: // 右平移,平移后的空缺位置灭

              MatrixMove(MOVE_RIGHT,0xff);

              break;

           case 2:    // 上平移,平移后的空缺位置灭

              MatrixMove(MOVE_UP, 0xff);

              break;

           case 3:    // 下平移,平移后的空缺位置灭

              MatrixMove(MOVE_DOWN, 0xff);

              break;

           default:

              break;

           }

           State = 1; // 平移后再进入状态1进行边界检测

           break;

 

       default:

           break;

       }  

    }

}

附录:

此章节的Keil工程,包含源码,Preteus仿真,包含仿真电路,可直接查看效果,Matrix.rar。

http://pan.baidu.com/s/1dDchpYh

 

 

2019-04-02 21:21:44 sinat_37787331 阅读数 395
  • LED点阵-第1季第8部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第8个课程,讲解了16*16LED点阵的驱动方式和文字显示。本课程的学习目标是理解点阵式LED屏幕的驱动方式、74HC595的时序和编程等。

    2289 人正在学习 去看看 朱有鹏

1.硬件

德菲来 51开发板,双色点阵

 

2.基本流程

蛇身用二维数组存储,上下左右通过dx,dy组合,有4个使能位(上行时不能向下),HC595锁存发数据。

功能:暂停、加减速、上下左右(keyscan()里扫描改变状态)、变色、食物长时间随机消失

使用了两个定时器,T0:发送点码,2ms。        T1:keyscan(),time检测食物存活时间。65ms

单片机

 

3.代码

#include<reg52.h>    //包含头文件,一般情况不需要改动,头文件包含特殊功能寄存器的定义
#include <intrins.h> 
	
/**硬件端口定义**/
sbit LATCH= P1^0; 	   //锁存
sbit SRCLK= P1^1;      //时钟
sbit SER  = P1^2;      //数据
sbit LATCH_B= P2^2;    //锁存,公共端
sbit SRCLK_B= P2^1;	   //时钟
sbit SER_B= P2^0;      //数据                                      
sbit LED=P1^4;		   //结束提示灯
sbit key1=P3^0;	   //上
sbit key2=P3^1;	   //下
sbit key3=P3^2;	   //左
sbit key4=P3^3;      //右
sbit key5=P3^4;	   //暂停
sbit key6=P1^5;
/**全局变量定义**/
unsigned char x[30],y[30];     //蛇身坐标
unsigned char speed=10;        //控制速度变量	 
unsigned char dx=0,dy=-1;	   //控制转向变量,初始化为向下运动。上:-1 0 ,下:1 0 ,左 0 1 ,右 0 -1
bit stop_start,inverse,time;		   //开始/暂停标志位,颜色显示标志位
bit up=1,down=1,left=0,right=1;//上下左右使能控制位,如避免向上运动时启动向下操作
unsigned char tab[8];		   //显示缓冲数组
unsigned char  segout[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //8列扫描
int lim=0;

/**us延时函数,晶振12MHZ,大致延时 2*t us**/
void DelayUs2x(unsigned char t)
{   
    while(--t);
}

/**ms延时函数,晶振12MHZ,大致延时 t ms**/
void DelayMs(int t)	 //大致延时1mS
{    
    while(t--)
    {     
    	 DelayUs2x(245);
		 DelayUs2x(245);
    }
}

/**发送字节,写入数据原理,SRCLK 输入时钟信号,为输入数据提供时间基准,跟随时钟信号输入对应的
数据信号。这部分仅仅发送数据,没有锁存输出部分。锁存部分在所有数据传输完毕后执行**/
void SendByte(unsigned char dat)
{    
  	unsigned char i;        
    for(i=0;i<8;i++)
    {
	     SRCLK=0;
	     SER=dat&0x80; //取首位
	     dat<<=1;
	     SRCLK=1;
    }        
}

/**发送双字节程序,595级联,n个595,就需要发送n字节后锁存。同时控制 2 种颜色就必须同时写入 2 个字节**/
void Send2Byte(unsigned char dat1,unsigned char dat2)
{    
    SendByte(dat1);
    SendByte(dat2);      
}

/**595锁存程序,595级联发送数据后,锁存有效,这个锁存仅仅针对 2 种颜色控制的 HC595 有效,**/
void Out595(void)
{
    LATCH=0;
    _nop_();	  //增加上升沿时间
    LATCH=1;
}

/**发送位码字节程序使用另外一片单独595,公共端控制用 HC595 数据发送与锁存,由于只使用 1 个 HC595,没有级联,
可以在一个函数中直接输入数据并锁存**/
void SendSeg(unsigned char dat)
{    
	unsigned char i;         
    for(i=0;i<8;i++)  //发送字节
    {
         SRCLK_B=0;
         SER_B=dat&0x80;
         dat<<=1;
         SRCLK_B=1;
    }
    LATCH_B=0;    //锁存
    _nop_();
    LATCH_B=1;         
}							  

/**按键扫描函数**/
void key_scan()
{
	 switch(P3)	  //上下左右停加减,P3.0-P3.4,P3.6-3.7
	 {
	 	case 0xfe: if(up)						    	//up
				   {
					   dx=-1;dy=0;						//执行向上功能
					   down=0;left=1;right=1;		   	//向下功能失效,其他功能可用
				   }			 		     
					break;
		case 0xfd: if(down)								//down
				   {dx=1;dy=0;up=0;left=1;right=1;}     // 			 
					break;
		case 0xfb: if(left)								//left
				   {dx=0;dy=1;down=1;up=1;right=0;}     //			
					break;
		case 0xf7: if(right)							//right
				   {dx=0;dy=-1;down=1;left=0;up=1;}     //		 
		 			break;
		case 0xef:				             //暂停/开始键
					DelayMs(10);			 //延时去抖
					if(P3==0xef)			 //再次确认按键是否按下
					stop_start=~stop_start;	 //暂停/开始标志位取反(按一下暂停再按一下开始)
					while(P3==0xef);		 //等待按键释放
					break;
		case 0xbf: 
					DelayMs(10);			 //延时去抖
					if(P3==0xbf)			 //再次确认按键是否按下
					speed+=2; 		 		 //速度增大,实际减速
					while(P3==0xbf);		 //等待按键释放
					break;
		case 0x7f: 	DelayMs(10);			 //延时去抖
					if(P3==0x7f)			 //再次确认按键是否按下		
					speed-=2; 			 	 //速度减小,实际速度增加
					while(P3==0x7f);		 //等待按键释放
					break;
		default: break;
	 }
}								  															

/**清除显示缓冲区,即清屏**/
void clr_ram(void)
{
    unsigned char i;
    for(i = 0; i < 8; i++) 
        tab[i] = 0x00;	   //逐行清除数组内容
}																 

/** 画点函数,擦点或者绘点
点阵左上角坐标为(0, 0)    左下角坐标为(7, 7)
横坐标为x:0~7        纵坐标为y:0~7
k = 1 --绘点      k = 0 --擦点 **/   
void point1(unsigned char x, unsigned char y, bit k)
{
    if(k) tab[y] |= 0x01 << x;		 //保留原始点,绘制新点,先定位x的行,在这一列与或
    else  tab[y] &= ~(0x01 << x);	 //保留其它点,只擦其中一个点
}


/**定时器0初始化*/
void T0_init(void)
{
    TMOD|= 0x01;
    TH0  = 0xf8;        //方式1,计数值为(65536-63542=2000)=2ms
    TL0  = 0x36;
    //IE  |= 0x82;	   //中断开放寄存器,ETO=1
	EA=1;ET0=1;
    TR0  = 1;
}

/**定时器1初始化*/
void T1_init(void)                    
{									  
    TMOD|= 0x01;
    TH1  = 0x00;        //65ms
    TL1  = 0x00;
   // IE  |= 0x88;		//IE1,ET1=1
	EA=1;ET1=1;
    TR1  = 1;
}

/**主程序**/
void main()
{
	unsigned char i=0,foodx, foody;	//食物坐标
	unsigned char num=3;            //蛇长度初始3
	bit food,over;                  //食物和结束标志位
  	//IT0  = 1;  						//外部中断0(即P3^2脚)选择边沿触发,下降沿有效
	//EX0  = 1;  						//打开定时器中断0
	T0_init();						//定时器0初始化
	T1_init();						//定时器1初始化
	stop_start=0;                   //开始/暂停标志位置,0为开始	   	
	while(1)
	{
		x[0] += dx;   y[0] += dy;	//根据dxdy不同的值来使蛇头移动
        x[0] &= 0x07; y[0] &= 0x07; //作用穿墙,x或y加到8时变为0,-1(ff)->7
	    if(time)
		{	time=0;
			point1(foodx, foody, 0);	 //10秒未吃到,食物更新
			food = 0;
		}

		if(!food )                   //放置食物,0被吃
        {
  again:    foodx = TL0&0x07;   	//随机取食物坐标,0~7,但不会超过7             
            foody = TH0&0x07; 
            for(i = 0; i < num; i++)
            {
                if(foodx==x[i]&&foody==y[i])  //若食物与蛇身重叠,                
				goto again;					  //则重放食物。
            }
		//inverse=0;						  //颜色标志位置0,显示红色
		//inverse=1;
            point1(foodx, foody, 1);          //显示食物
            food = 1;                         //置食物标志位
        }


		if(x[0] == foodx && y[0] == foody)    //吃到食物    
        {
            num++;                            //蛇长增加1节
            food = 0;                         //清食物标志位
			inverse=~inverse;                 //变色
        }
		
		for(;stop_start;);					  //按下暂停键程序在此进入死循环,1代表暂停

								      //颜色标志位置1,显示绿色
		for(i = 0; i < num; i++)              //显示蛇身 
        	point1(x[i], y[i], 1);

        point1(x[i], y[i], 0);                //清蛇尾
		for(i = 1; i < num; i++)              //判断是否自撞
        {
            if((x[0]==x[i])&&(y[0]==y[i]))
            over = 1;                         //置结束标志位
        }
		for(i=0;i<speed;i++) 
		{	 
		      DelayMs(15); 	  //蛇运动速度,暂停显示蛇身
        }
        for(i = 0; i < num; i++)              //蛇移动蛇身
        {
            x[num-i] = x[num-i-1]; 	          //除蛇头外均移动一格
            y[num-i] = y[num-i-1];
        }
		if(over)                      //判断是否结束
        {
			for(i=0;i<10;i++)		  //LED闪5次
			{						  
				LED=~LED;			 
				DelayMs(100);
			}
			clr_ram();                //清除屏幕
			num = 3;                  //重新设定蛇长
            point1(foodx, foody, 1);  //重新放置食物
            x[0] = 0; y[0] = 0;       //起点位置
            dx=0;dy=-1; up=1,down=1,left=0,right=1;               //向右运动
            over = 0; 				  //清除结束标志
		}
	}
}

/**定时器1中断服务**/
void T1_intservice(void) interrupt 3
{
  
	TH1  = 0x00;
    TL1  = 0x00;
	key_scan();	
    lim++;
	if(lim >= 200)	 //10秒
	{
	  
	  lim  = 0;
	  time = 1;
	}
											 
 }

/**定时器0中断服务**/
void T0_intservice(void) interrupt 1
{
    static unsigned char n;		//定义静态变量
    TR1 = 0;					//关闭定时器1
    TH0  = 0xf8;                       
    TL0  = 0x36;    		    //重装初值,2ms 
	SendSeg(segout[n]);		    //发送列码(相当于数码管中的位码)
	if(inverse)	
    	Send2Byte(0xff,~tab[n]);//发送点码(相当于数码管中的段码),显示绿色,交换两个量可改变颜色
    else
		Send2Byte(~tab[n],0xff);//发送点码(相当于数码管中的段码),显示红色,交换两个量可改变颜色
	Out595();					//595锁存程序
	DelayMs(1);	
	Send2Byte(0xff,0xff);       //防止重影
	Out595();	
	n++; if(n == 8) n = 0;      //循环扫描
    TR1 = 1;					//打开定时器1
}																	

																 

 

2019-01-24 19:02:48 cax1165 阅读数 1836
  • LED点阵-第1季第8部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第8个课程,讲解了16*16LED点阵的驱动方式和文字显示。本课程的学习目标是理解点阵式LED屏幕的驱动方式、74HC595的时序和编程等。

    2289 人正在学习 去看看 朱有鹏

开发板led点阵模块电路图如下:

 

 

点阵内部led连接方式如下图:

led点阵点亮图案仍然使用动态扫描的方式,方法同“51单片机实验5:动态数码管”https://blog.csdn.net/cax1165/article/details/86557551

D0口到D7口依次控制点阵的第8到1行(注意顺序),P0^0到P0^7口依次控制点阵的第8到1列(注意顺序)。

显然,根据点阵内部led连接电路,74HC595控制高电平输出,P0口控制低电平输出。

以移位寄存器74HC595控制段选,P0口控制位选!

注意:写程序时需要考虑74HC595移位寄存器的工作特点。

程序可参考“51单片机实验8:led点阵(1):点亮一个点 ”https://blog.csdn.net/cax1165/article/details/86607762

#include<reg52.h>
#include<intrins.h>
#define uc unsigned char
sbit srclk=P3^6;
sbit rclk=P3^5;
sbit ser=P3^4;
uc ledduan[]={0x00,0x00,0x3e,0x41,0x41,0x41,0x3e,0x00};
uc ledwei[]={0x7f,0xbf,0xdf,0xef,0xf7,0xfb,0xfd,0xfe};
void delay(uc x)
{
	uc i,j;
	for(i=1;i<=x;i++)
		for(j=1;j<=110;j++);
}
void c74hc595(uc dat)
{
	uc i;
    srclk=rclk=0;
	for(i=1;i<=8;i++)
	{
		ser=dat>>7;
		dat<<=1;
		srclk=1;
		_nop_();
		srclk=0;
	}
	rclk=1;
	_nop_();
	rclk=0;
}
void main()
{
	uc i;
	while(1)
	{
		//P0=0x7f;
        for(i=1;i<=8;i++)
		{
			P0=ledwei[i-1];
			c74hc595(ledduan[i-1]);
			delay(1);
			c74hc595(0x00);
		}
	}
}

 

2018-08-31 21:13:51 weixin_42911200 阅读数 1095
  • LED点阵-第1季第8部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第8个课程,讲解了16*16LED点阵的驱动方式和文字显示。本课程的学习目标是理解点阵式LED屏幕的驱动方式、74HC595的时序和编程等。

    2289 人正在学习 去看看 朱有鹏
  • 查看原理图

Led点阵原理图

D0-D7口接在74HC595芯片上,芯片的输入端口为P34、P35、P36端口;

控制每一列的为P00-P07端口

  • LED点阵工作原理

根据点阵原理图,P00-P07接低电平,D0-D7接高电平时led点亮,由于单片机控制端口有限,于是只能每次点亮一列,逐列扫描,利用人眼的视觉暂留形成同时点亮的现象。将要点亮的LED灯标记为1,每一列根据D0-D7的高低电平,转换成2进制数,再转换成16进制数,于是得到每一个汉字所对应的数组,通过74HC595移位输出(这里可以通过IC资料查看74HC595工作原理编程)。

  • 程序代码

 

 

LED点阵
2018-11-22 13:50:31 weixin_43616727 阅读数 300
  • LED点阵-第1季第8部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第1季第8个课程,讲解了16*16LED点阵的驱动方式和文字显示。本课程的学习目标是理解点阵式LED屏幕的驱动方式、74HC595的时序和编程等。

    2289 人正在学习 去看看 朱有鹏

*觉得有用有帮助的评论下,谢谢~

LED点阵

点阵原理

DianZYuanL

  • 我的开发板是 16*16 红绿 双色的LED点阵,但我只用到了红色的,所以我们暂且只看黑色LED,其实只要让LED导通,我们就可以点亮任意一个LED小灯

    如要点亮 左上角第一个LED ,只要让 221230 就可以让它导通,就亮了

  • 如果要让整个点阵按自己的想法显示,比如显示数字汉字啥的,可以用类似于数码管的 动态扫描 想法,我的理解是给 一个 段选 码表,给 一个 位选 码表,让 逐行扫描,配合列看看该行有没有要显示的LED,不好理解等下看程序

点亮一个

  • 下面我们先来点亮第一个LED看看效果
程序
#include <STC89C5xRC.H>

#define LedH P0		//用P0口控制行
#define LedL P1      //用P1口控制列

void main()
{
	while(1)
	{
		LedH=0x01;    //只让第一个LED导通
		LedL=0xfe;
	}
}

效果

oneled

显示图形

  • 显示图形可以自己去计算每个点,但是效率太低,这里我们可以用到 取字模软件

xingtu

  • 就像这样,它会直接把你点亮的地方的段码给你,这样我们就可以直接用了
  • 还有这个软件的一个参数设置,根据不同的编程,参数设置也是不同的

cans

程序

#include <STC89C5xRC.H>

#define LedH P0
#define LedL P1

unsigned char code wei[8]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
unsigned char code duan[8]=
{0x1C,0x3E,0x7E,0xFC,0xFC,0x7E,0x3E,0x1C};

void main()
{
	unsigned char i;
	while(1)
	{
		for(i=0;i<8;i++)
		{
			LedL=0xff;			//消影
			LedH=duan[i];		
			LedL=~wei[i];			
		}
	}
}

效果

xing

  • 不知道为什么,我用IO来传输数据时,它显示的有点暗,而用 95芯片 传输时,则不会这样,不知道是不是IO口的驱动能力不足导致的,所以下面我就用 595芯片 来驱动LED点阵

HC595芯片

  • 在显示上,为了更节省IO口,更简便,我们可以用 595芯片 ,也可用 138芯片 之类的,而我则用了两个 595芯片,一个用来控制 给它 段选 ,一个来控制 给它 位选 ,这样就节省了两个IO口,同时为了防止产生上面显示太暗的这个弊端

用法

  • 下面我直接给出 595芯片驱动程序吧,要说原理我也不太清楚,要用的直接在这个上面改就行了
#include "STC89C5xRC.H"			 
#include "intrins.h"
#define uchar unsigned char //对数据类型进行声明定义
sbit SRCLK = P3^6;        //定义IO口,看开发板的原理图
sbit RCLK1 = P3^5;        
sbit SER = P3^4;
void HC595(uchar dat1)			//595芯片驱动程序,,这是传一个,要穿两个可以加一个dat2
{
		uchar a;
		SRCLK = 1;
		RCLK1 = 1;
		for(a=0;a<8;a++)       //发送8位数
		{
			SER=dat1>>7;       //从最高位开始发送
			dat1<<=1;
			SRCLK=0;				//发送时序
			_nop_();
			_nop_();
			SRCLK=1;
		}
		RCLK1=0;				
		_nop_();
		_nop_();
		RCLK1=1;
}	

二维数组

  • 在最后的程序里,为了方便,我还用了 二维数组

    数据类型 数组名[数组长度1][数组长度2];

  • 数组元素的数量可以小于数组元素个数,没有赋值的会自动给0,当数组元素的数量等于数组个数的时候,如下

unsigned char a[2][3] =
{
   {1,2,3}, {4,5,6}
};

或者是

unsigned char a[2][3] = 
{1,2,3,4,5,6};
  • 当数组元素的数量小于数组个数的时候,如下所示:
unsigned char a[2][3] = 
{
   {1,2}, {3,4}
}; 

等价于

unsigned char a[2][3] =
{1,2,0,3,4,0};

反过来

unsigned char a[2][3] = 
{1,2,3,4}; 

等价于

unsigned char a[2][3] = 
{
   {1,2,3}, {4,0,0}
};

最终程序

#include <STC89C5xRC.H>
#include "intrins.h"
#define uchar unsigned char


uchar code imagewei[8]=
{
	0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80
};

uchar code imageduan[11][8]=
{
	{0x00,0x00,0x44,0x92,0x92,0x92,0x6C,0x00}, //3
	{0x00,0x00,0xE4,0xA2,0xA2,0xA2,0x9C,0x00}, //2
	{0x00,0x00,0x88,0x84,0xFE,0x80,0x80,0x00}, //1
	{0x00,0xC3,0xC3,0xFF,0xFF,0xC3,0xC3,0x00}, //i
	{0x1C,0x3E,0x7E,0xFC,0xFC,0x7E,0x3E,0x1C}, //心
	{0x00,0x7F,0xFF,0xC0,0xC0,0xFF,0x7F,0x00}, //u

	{0x00,0x18,0x3C,0x78,0x78,0x3C,0x18,0x00},  //小心
	{0x1C,0x3E,0x7E,0xFC,0xFC,0x7E,0x3E,0x1C},  //中心
	{0x3E,0x7F,0xFF,0xFE,0xFE,0xFF,0x7F,0x3E},	//大		
//	{0x1C,0x3E,0x7E,0xFC,0xFC,0x7E,0x3E,0x1C},  //中心
	{0x00,0x18,0x3C,0x78,0x78,0x3C,0x18,0x00},  //缩小	
	{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},  //全灭
	
};

sbit SRCLK = P3^6;
sbit RCLK1 = P3^5;
sbit SER = P3^4;

void HC595(uchar dat1,uchar dat2)			//595芯片驱动程序,直接拿上面的来用,修改一下
{
	uchar a;
	SRCLK = 1;
	RCLK1 = 1;
	for(a=0;a<8;a++)
	{
		SER=dat1>>7;
		dat1<<=1;
		SRCLK=0;				
		_nop_();
		_nop_();
		SRCLK=1;
	}
	for(a=0;a<8;a++)		//传输的第二个数据
	{
		SER=dat2>>7;
		dat2<<=1;
		SRCLK=0;				
		_nop_();
		_nop_();
		SRCLK=1;
	}
	RCLK1=0;				
	_nop_();
	_nop_();
	RCLK1=1;
}	

void main()   
{
	int k,x,y;
	uchar j;
	uchar i;
	y=0;
	while(1)
	{
		if(j<6)
			{x=200;}			//让前面321倒计时切换慢点
		else
			{x=60;}			//让后面心跳快点
		for(k=0;k<x;k++)							//段码之间切换的时间
		{
			for(i=0;i<8;i++)					//扫描
			{
				HC595(~imagewei[i],imageduan[j][i]);	
			}		
		}
		j++;
		if(j==11)		//单独循环7次后面的心跳
		{	
			j=7;
			y++;
		}
		if(y==7)			//单独循环完后重新开始
		{
			j=0;
			y=0;
		}
	}
}



最终效果展示

xingtiao

啦~最终效果就完成啦

是不是看起来还可以,工科直男也有浪漫的好吧,我们不需要什么玫瑰花,不需要什么礼物,我们玩的高科技,玩的是技术,不开玩笑了,收工~

  • 我本来想的在加一个定时器中断蜂鸣器来模拟心跳的声音的,但是这个不知道怎么上传视频,就懒得弄了

双色点阵

阅读数 889

没有更多推荐了,返回首页