[51 MCU Experimental Notes] LED Chapter (4) Basic control of LED dot matrix


foreword

The hardware touched in this chapter is still closely related to LED , which is a display module in which multiple LEDs are packaged in a matrix . With it, we can create flowing subtitles , custom animation and other effects.

The package source files involved in this section can be found in "Module Function Package Summary" .

The complete project files of this section have been uploaded to GitHub , the warehouse address , welcome to download and communicate!


hardware introduction

The basic unit of LED dot matrix is ​​composed of LEDs , and the common dot matrix screen size is 8x8 or 16x16 . The characteristic is that each row of LEDs has a common anode and each column of LEDs has a common cathode (different modules will have differences).

Figure 1 8x8 dot matrix screen
Figure 2 Schematic diagram of LED dot matrix

Indeed, similar to the digital tube , we cannot directly drive the LED dot matrix through the IO port, for two reasons :

  • MCU pins are weak pull-ups , which can accept large currents at low levels , but are difficult to drive at high levels .
  • The dot-matrix screen consumes a lot of IO port resources , and a small-sized 8x8 dot-matrix will consume 16 IO pins , which is obviously unreasonable.

Therefore, we generally use driver chips to drive LED dot matrix screens .


driver chip

In the control of digital tubes , in order to save IO resources , a dynamic scanning method is adopted to realize simultaneous display of multiple digital tubes . The control idea of ​​LED dot matrix is ​​also similar to it . But the difference is that we use the 74HC245 driver chip and 74HC138 decoder solution in the digital tube (of course, it can also be applied to the LED dot matrix ), while the LED dot matrix uses the 74HC595 chip , which can realize only Use 3 IO ports to control 8 pins , and can also be cascaded .

It needs to be clear that there are more than one control schemes for any component , and you can choose the appropriate chip by consulting the chip manual . Just from the perspective of learning, don't limit your cognition too much.


74HC595 chip (serial to parallel)

insert image description here
The IO expansion chip can convert 8-bit serial input into three-state parallel output . Built-in shift register and storage register , controlled by their own clocks .

  • SERFor the serial input port .
  • SRCLKIt is the clock pin of the shift register . When receiving the rising edge , the serial data is sequentially stored in the shift register from the high bit to the low bit (a rising edge shifts one bit, similar to the push process ).
  • RCLKThe pin is the clock pin of the storage register . When receiving the rising edge , all the data in the shift register will be stored in the storage register at one time.
  • OEis the enable pin .
  • SRCLRis the reset pin .
  • QH'For serial output , used for 74HC595 cascading (can realize 3 IOs to control multiple LED dot matrix! )

timing analysis


MAX7219 chip

MAX7219 is also an LED dot matrix screen control chip. It has 8x8 data registers inside , which can automatically scan and display a static graphic . This is obviously better than 74HC595 , which only has 8-bit data registers , and it needs to be completed by dynamic scanning to display a static graphic .

Now the relatively large LED dot matrix on the market basically uses the MAX7219 chip , which also only uses 3 IO ports , but can automatically complete the scan .


Principle analysis

The LED dot matrix can display static images and scrolling images , and the implementation details are slightly different. The summary is as follows:

  • Regular rectangle : No dynamic scanning is required , just which row or column you want to light up can be directly assigned to the corresponding pin . For example, turn on a certain LED in the LED dot matrix .
  • Custom graphics : Since custom graphics are often irregular ( non-rectangular ), dynamic scanning must be used . The design of the pattern can be calculated by yourself , or with the help of a modeling tool . For example, light up a heart shape .
  • Custom animation : The above are all static pictures , and the LED dot matrix can also achieve relatively simple animation effects . The picture of each frame needs to be saved in an array in advance , and each frame is displayed by dynamic scanning , and after a period of time , it jumps to the next frame to form a coherent animation. For example, scrolling captions , spinning pinwheels , spirals , and more.

For custom animation, since the on-chip RAM is only 128B , and the on-chip ROM has 8KB , the animation data can be stored in the ROM through codekeywords . In addition, some animations with mathematical laws can calculate the data of the next frame through algorithms , instead of storing all animation frames in an array to occupy space .


Software Implementation

Love pictures

Achieved effect: Static display of a love heart
insert image description here

#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
		}
	}
}

The key thing in the code is LED_controlthe content in the function. As long as you understand the working principle of the 74HC595 chip , it is not difficult to understand the logic of the code. have to be aware of is,Using dynamic scanning will inevitably have the problem of ghosting, remember to disappear .


spinning windmill

This rotating windmill is relatively abstract, hahahahaha, mainly because the number of dots is too small.
insert image description here
In order to improve portability and reusability , I extracted the 74HC595 driver display and delay codes.
delay.h

#ifndef _DELAY_H_
#define _DELAY_H_

#include <REGX52.H>


#define false 0
#define true 1

typedef unsigned char u8;
typedef unsigned int u16;

void delay_10us(u16);
void delay_ms(u16);

#endif

delay.c

#include "delay.h"
/** 
 **  @brief    通用函数
 **  @author   QIU
 **  @data     2023.08.23
 **/

/*-------------------------------------------------------------------*/

/**
 **  @brief   延时函数(10us)
 **  @param   t:0~65535,循环一次约10us
 **  @retval  无
 **/
void delay_10us(u16 t){
    
    
	while(t--);
}


/**
 **  @brief   延时函数(ms)
 **  @param   t:0~65535,单位ms
 **  @retval  无
 **/
void delay_ms(u16 t){
    
    
	while(t--){
    
    
		delay_10us(100);
	}
}

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(){
    
    
	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;
			}
		}
	}
}

Among them, WindMill_Animation_Array[]the array is generated by the modulo software .

Make sure that the animation consists of several frames, and each frame is displayed by dynamic scanning . The fluency of the animation depends on the duration of each frame , which can be SPEEDtested by adjusting.


rolling date

insert image description here

#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 > 59){
    
    
				j = 0;
			}
		}
	}
}

Note that the above header files are included

The implementation principle is similar to that of the rotating windmill , except that the rotating windmill moves frame by frame ( one frame requires 8 bytes ), and the scrolling subtitle moves byte by byte .


Spiral animation

PS : All codes are written in this one C file .
insert image description here

#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(); //重新开始
		}
	}
}

For this animation, I did not calculate each frame in advance, but calculated the next light to be lit each time , while retaining the current light .


LED dot matrix function package

LED_Matrix.h

#ifndef _LED_MATRIX_
#define _LED_MATRIX_

#include "delay.h"

#define LED_Matrix_PORT P0

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

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

#endif

LED_Matrix.c

#include "LED_Matrix.h"
/** 
 **  @brief    LED点阵封装(74HC595芯片驱动代码)
 **  @author   QIU
 **  @data     2023.08.23
 **/

/*-------------------------------------------------------------------*/


/**
 **  @brief   74HC595芯片(串转并)驱动代码
 **  @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);
}


/**
 **  @brief   清屏函数
 **  @param   无
 **  @retval  无
 **/
void LED_Init(){
    
    
	LED_Matrix_PORT = 0xff;
}


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

Essentially, it is the encapsulation of the driving logic of the 74HC595 chip .


Summarize

Relatively speaking, the LED dot matrix displays a lot of things! Use your imagination , you can also innovate a lot of interesting patterns and animations.

Guess you like

Origin blog.csdn.net/m0_46500149/article/details/128576286