51单片机之蜂鸣器、中断系统、点阵LED

蜂鸣器

蜂鸣器是一种能够发出声音的电子元器件,常用于报警、提示和音频信号输出等场景。

  1. 有些蜂鸣器元件内部自带震荡驱动电路,这种蜂鸣器叫做有源蜂鸣器(Active Buzzer,自激式蜂鸣器);而有些则不带震荡驱动电路,这种蜂鸣器叫做无源蜂鸣器(Passive Buzzer,它激式蜂鸣器)。

要求:为按键矩阵增加按键提示音,要求按键按下后,蜂鸣器响0.1s。

  1. 无源蜂鸣器:
  2.  从外部输入一定频率的方波,方波的频率就是蜂鸣器发声的频率,这里使用500Hz的方波                                                                                                                                        
  3. 代码实现
// Int_DigitalTube.h 
#ifndef __INT_DIGITALTUBE_H__
#define __INT_DIGITALTUBE_H__
#define SMG_EN P36
#define LED_EN P34

#include "Com_Util.h"

void Int_DigitalTube_Init();
void Int_DigitalTube_DisplayNum(s32 num);
void Int_DigitalTube_Refresh();

#endif /* __INT_DIGITALTUBE_H__ */

// Int_DigitalTube.c
#include "Int_DigitalTube.h"
#include <STC89C5xRC.H>
static u8 s_codes[11] = {
    0x3F, // 0
    0x06, // 1
    0x5B, // 2
    0x4F, // 3
    0x66, // 4
    0x6D, // 5
    0x7D, // 6
    0x07, // 7
    0x7F, // 8
    0x6F, // 9
    0x40  // -
};

// 存放显示的数的缓存
static u8 s_buffer[8];

//  打开数码管的输入使能
void Int_DigitalTube_Init()
{
    SMG_EN = 0;
    LED_EN = 0;
}
static void Int_DigitalTube_DisplaySingle(u8 position, u8 num_code)
{
    // 关闭当前数码管 否则数码管将有重影
    P0 = 0x00;
    // 位选
    // 先将要显示的数左移 3 位
    position <<= 3;
    // 按位与 将P1 的 p13、P14、P15 位置的数变为0
    P1 &= 0xC7;
    // 按位或 |  将 P1 的 p13、P14、P15 位置和 position 相应位置数交换
    P1 |= position;
    // 段选 需要放置的数字
    P0 = num_code;
}

// 将显示的数放在缓存里
void Int_DigitalTube_DisplayNum(s32 num)
{
    u8 i;
    // 将显存清零
    for (i = 0; i < 8; i++) {
        s_buffer[i] = 0x00;
    }

    if (num < 0) { // 这个功能不需要考虑
        num = -num; // 取绝对值
        i   = 7;    // 从最右侧开始显示数字
        // 将每个位上的数放入显存,从右向左填充
        while (num > 0) {
            s_buffer[i] = s_codes[num % 10];
            num /= 10;
            i--;
        }
        // 如果是负数,将负号放在数字之前的一位
        if (i < 7) {
            s_buffer[i] = s_codes[10]; // s_codes[10] 对应负号 '-'
        }
    } else if (num > 0) {
        i = 7; // 正数从最右侧开始显示
        while (num > 0) {
            s_buffer[i] = s_codes[num % 10];
            num /= 10;
            i--;
        }
    } else {
        // 如果 num == 0 ,将0 放在最右边
        s_buffer[7] = s_codes[0];
    }
}

// 循环多次刷新数码管
void Int_DigitalTube_Refresh()
{
    u8 i;
    for (i = 0; i < 8; i++) {
        Int_DigitalTube_DisplaySingle(i, s_buffer[i]);
        Com_Util_Delay1ms(1); // 延时
    }
}


// Int_KeyMatrix.h
#ifndef __INT_KEYMATRIX_H__
#define __INT_KEYMATRIX_H__

#include "Com_Util.h"
#include <STC89C5xRC.H>

u8 Int_KeyMatrix_CheckSW();

#endif /* __INT_KEYMATRIX_H__ */

// Int_KeyMatrix.c
#include "Int_KeyMatrix.h"

u8 Int_KeyMatrix_CheckSW() {
    u8 i, j;
    u8 lines[4] = {0xFE, 0xFD, 0xFB, 0xF7};
    u8 columns[4] = {0x10, 0x20, 0x40, 0x80};
    for (i = 0; i < 4; i++) {
        P2 = lines[i];
        for ( j = 0; j < 4; j++) {
            if ((P2 & columns[j]) == 0x00) {
                Com_Util_Delay1ms(10);
                if ((P2 & columns[j]) == 0x00) {
                    while ((P2 & columns[j]) == 0x00);
                    return 5 + j + 4 * i;
                }
            }
        }
    }
    return 0;
}

// Int_Buzzer.h
#ifndef __INT_BUZZER_H__
#define __INT_BUZZER_H__
#include <STC89C5xRC.H>
#define BUZZ P46

/**
 * @brief 蜂鸣器响0.1s
 *
 */
void Int_Buzzer_Buzz();

#endif /* __INT_BUZZER_H__ */

// Int_Buzzer.c

#include "Int_Buzzer.h"

void Int_Buzzer_Buzz() {
    u8 counter = 100;
    while(counter) {
    BUZZ = ~BUZZ;
    Com_Util_Delay1ms(1);
    counter--;
}

// main.c
#include "Int_DigitalTube.h"
#include "Int_KeyMatrix.h"
#include "Int_Buzzer.h"
void main()
{
    u8 key;
    Int_DigitalTube_Init();
    while(1) {
        key = Int_KeyMatrix_CheckSW();
        if (Key) {
            Int_DigitalTube_DisplayNum(key);
            Int_Buzzer_Buzz();
        }
        Int_DigitalTube_Refresh();
    }
}

中断系统

  1. 中断系统是单片机用于处理外部紧急事件的一种机制。
  2. 当CPU正在处理某项任务时,外部发生了某个紧急事件,此时CPU会暂停当前的工作,转而去处理这个紧急事件,处理完之后,再回到原来被中断的位置,继续处理原来的工作。
  3.   中断系统使单片机能够实时响应外部事件,提高了系统的灵活性和响应能力。                                                           
  4. 中断源:中断源是指能够引发中断的事件。
  5. 中断标志位:中断标志位用于标识某个中断是否发生,每个中断源都有一个与之对应的中断标志位。当某个中断发生时,相应的中断标位就会置为1,当CPU检测到标志位时,就会处理相应的中断。当中断处理完毕后,中断标志位需要复位(置0),以便接收下一次中断,有些中断源的标志位,会在CPU处理完中断后,自动复位,而有些则需要开发者手动复位。
  6. 中断服务程序:中断服务程序指处理中断的逻辑,当某个中断标志位置1时,CPU会自动执行相应的中断服务程序。
  7. 中断优先级:中断优先级是指在多个中断同时发生时,单片机响应中断的先后顺序,并且高优先级的中断可以打断低优先级的中断。
  8. 中断源:STC89C52RC共有8个中断源,8个中断源可分为3类,3个类别分别是外部中断、定时器中断、串口中断,下面简要介绍每种类型。
  9. 外部中断是指由单片机外部的紧急事件触发的中断,通过向单片机的特定引脚发送特定的信号触发。STC89C52RC共提供了4个外部中断引脚,分别是INT0,INT1,INT2,INT3。
  10. 触发方式:51单片机的外部中断支持两种触发方式,分别是低电平触发下降沿触发
  11. 定时器中断:定时器中断是指由单片机内部的定时器触发的中断。
  12. STC89C52RC共有三个定时器,分别是Timer0、Timer1、Timer2,每个定时器都有一个相对应的中断。

  13. 串口中断:串口中断是由单片机串口触发的中断。串口是单片机用于收发数据的重要接口之一,当单片机通过串口接收到数据或者发送完数据后都会触发相应的中断。STC89C52RC的串口引脚为TxD和RxD,其中TxD用于发送数据,RxD用于接收数据。

  14. 中断服务程序:中断服务程序是指用于处理中断的一段代码,当中断发生时,CPU就会暂停当前程序的执行,转而执行对应的中断服务程序,处理完中断后再恢复到原来的程序。                                                                                                                                          

  15. 中断优先级:STC89C52RC共有四个中断优先级,每个中断源都可以单独设置优先级。若多个中断同时发生,优先级高的会被优先处理;若两个中断的优先级相同,则根据其中断号决定处理顺序,中断号越小越优先。

  16. 高优先级的中断还可以打断低优先级的中断,也就是说当CPU正在处理一个中断时,又发生了另外一个优先级比它还高的中断,此时CPU会暂停原来中断的服务程序,转而去处理这个高优先级的中断,处理完之后,再回到原来低优先级的中断服务程序。这个机制叫做中断嵌套,STC89C52RC支持两级中断嵌套。

外部中断

  1.  通过外部中断的方式检测按钮,必须将按钮接入4个外部中断引脚之一,如下图所示,可以看到SW3可用于触发外部中断0。                                                                                       

启用外部中断

  1. 默认情况,CPU会屏蔽所有中断请求,也就是说CPU不会响应任何中断请求,要使用中断,必须先进行启用。
  2. 每个中断源是否被启用,是由单片机内部的两个寄存器控制的,这两个寄存器分别是IE(Interrupt Enable,中断允许)寄存器和XICON(Auxiliary Interrupt Control,辅助中断控制)寄存器。
  3. 两个寄存器各有8位,每位的作用可参考下图(STC89C52系列中断系统结构图)。                
  4. STC89C52系列的中断系统具有两级控制,首先是EA总控制位,其次是每个中断源各自的控制位。启用某个中断,需要先将总控制位EA置位1,再将该中断自身的控制为置为1。

配置外部中断触发方式

  1. STC89C52系列的外部中断支持两种触发方式,分别是低电平触发和下降沿触发
  2. 4个外部中断各需要1个控制位用于设置触发方式,这4个控制位分布于如下两个寄存器中,分别是XICON(Auxiliary Interrupt Control,辅助中断控制)寄存器中、TCON(Timer 0 and 1 Control寄存器。

配置中断优先级(可选)

  1. STC89C52系列的中断系统支持四个优先级,所以每个中断源的优先级都需要通过2个控制位进行配置,8个中断源共需要16个控制位,这16个控制位分布在如下3个寄存器中,分别是IPH(Interrupt Priority High,中断优先级高位)寄存器,IP(Interrupt Priority Low,中断优先级低位)寄存器,XICON(Auxiliary Interrupt Control,辅助中断控制)寄存器。

  2. 三个寄存器的具体结构如下。                                                                                                 

定义中断服务程序

  1. 外部中断0到外部中断3的中断号分别是:0、2、6、7,以外部中断0为例,其中断服务程序应定义为

    void INT0_Handler() interrupt {  }

代码实现

#include <STC89C5xRC.H> //包含STC89C52的头文件
#define LED0 P00
void Init_INT0() {
    // 1.启用中断
    EA = 1;
    EX0 = 1;
    // 2.触发方式 - 下降沿触发
    IT0 = 1;
}

void main() {
    Init_INT0();
    while(1);
}

void INT0 Func() interrupt 0 {
    // 延时
    Com_Util_Delay1ms(10);
    // 触发中断,打开LED0
    LED0 = ~LED0;
}

定时器中断

    需求:使用定时器中断的方式实现LED闪烁,具体要求是使用定时器0令LED1每1秒钟闪烁一次

定时器:定时器的基本工作原理,是使用一个n位的脉冲计数器,对时钟信号的脉冲进行计数,每个脉冲加1,当脉冲计数器达到最大值(2n)时,也就是溢出时,触发定时器中断。

根据定时器的工作原理,可以看出,定时的时间会受到以下几个因素的影响。

  1. 脉冲计数器的位数
  2. 时钟信号的频率
  3. 脉冲计数的初始值

启用定时器中断

  1. STC89C52系列共有3个定时器,每个定时器都有其对应的中断。定时器0的中断允许控制位位于IE寄存器(中断允许)。

选择定时器工作模式

  1. STC89C52系列的定时器都有定时和计数两种工作方式。定时方式用于产生精确的时间延迟,计数方式用于统计外部脉冲信号的个数。

  2. 定时方式下的脉冲信号为系统时钟信号,而计数方式下,脉冲信号来自单片机外部引脚。

  3. 每个定时器都有一个控制位,用于设置计数/定时方式。定时器0的控制位是TMOD(Timer Mode,定时器模式)寄存器中的C/T(Counter/Timer)位。                                                    需使用定时器方式,应将C/T控制位设置为0,注意 TMOD寄存器不可位寻址。

  4. 每个定时器还提供了多种工作模式

    1. 模式0——13位定时/计数器
      1. 该模式下的脉冲计数器共有13位,最大计数为8192。如下图所示,TL0和TH0为两个8位寄存器,用于存储脉冲计数器数值,该模式下TL0只用到了低5位。

    2. 模式1——16位定时/计数器
      1. 该模式的脉冲计数器共有16位,最大计数为65536。
    3. 模式2——8位自动重装载
      1. 前两种模式,一次定时完毕后,如需再次定时,需要开发者重新为脉冲计数器设定初始值。而该模式可以在脉冲计数器溢出时,自动重新设置初始值,很适合用于执行周期性任务。

      2. 该模式下,只用TL0寄存器用于存储脉冲计算器数值,TH0则用于存储脉冲计数器的初始值,每次TL0溢出之后, 都会自动将TH0的值重新装入TL0。                      

    4. 模式3——双8位定时/记数器

      1. 该模式下,TL0和TH0分别用作一个8位脉冲计数器,如果需要使用两个8位定时器可使用该模式。                                                                                                    

    5. 这四种工作模式需要两个控制位进行设置,两个控制位位于TMOD(Timer Mode,定时器模式)寄存器。                                                                              

    6. M1

      M0

      工作模式

      0

      0

      工作模式013位)

      0

      1

      工作模式1(16位)

      1

      0

      工作模式28位自动重装载)

      1

      1

      工作模式3(双8位)

设置脉冲计数器初始值

  1. 由于51单片机定时器是在脉冲计数器溢出时触发中断,因此定时的长短需要通过脉冲计数器的初始值控制。
明确每个记数脉冲的时间
  1. 传递给脉冲计数器的信号是系统时钟信号经过分频后得到,且分频可选两种,分别是12分频和6分频,默认是12分频。
  2. 当前系统的时钟频率为11.0592MHz,也就是11059200Hz,所以计数脉冲的频率是11059200/12 Hz,因此一个计数脉冲的时间是12/11059200 s,大约是1.08us。

计算所需脉冲个数
  1. 明确每个计数脉冲的时长之后,在根据期望定时时长便能计算出所需脉冲个数。假如现在需要定时1ms,那么1ms需要的脉冲个数应为0.001/(12/11059200)。
计算脉冲计数器初始值

假如现在需要定时1ms,那么1ms需要的脉冲个数应为0.001/(12/11059200),因此定时器的初始值应为65536-0.001/(12/11059200),大约等于64614。

计算完毕后,需要将该值赋予TL0(低8位)和TH0(高8位),如下。

TL0 = 64614;

TH0 = 64614 >> 8;

启动定时器

当GATE=0时,外部引脚(INT0,P3.2)无效,此时只能由内部寄存器TR0控制,当TR0=1时,脉冲计数器开始计数,TR0=0时,停止计数。

当GATE=1时,外部引脚(INT0,P3.2)生效,此时只有当内部寄存器TR0和外部引脚INT0都为1时,脉冲计数器才开始计数,否则停止计数。

定时器0的GATE控制位位于TMODE寄存器:

定时器0的TR0控制位,位于TCON寄存器。

定义中断服务程序

定时器0的中断号为1,因此中断服务函数应定义为。

void Timer0_Hander() interrupt 1    {}

代码实现

启用定时器0中断

// 中断总开关

EA = 1;

// 定时器0中断开关

ET0 = 1;

选择定时器0工作模式

首先需要明确定时/计数的工作方式,其次还需选择脉冲计数器的工作模式。此处选择计时+模式1(16位),具体配置如下。                                                                                                       

另外由于TMOD寄存器不可位寻址,所以可在设置工作模式的同时,将 GATE位也一并设置好,当前案例不需要外部引脚控制定时器,因此将GATE设置为0即可。

所以TMOD寄存器低四位的值应为0001,而高四位的值应保持原值,所以可对TMOD寄存器做出如下配置。

// GATE=0C/T=0M1=0M0=1

TMOD &= 0xF0;

TMOD |= 0x01;

​​​​​​​设置脉冲计数器的初始值

当前需求是令LED1每秒钟闪烁一次,具体来说就是每隔0.5s改变一下LED1的状态,显然这是一个周期性任务。对于该任务,我们可以先考虑为定时器定时0.5s,然后在定时器中断触发后,再次定时0.5s,这样就能实现周期性任务了。

但是需要注意,0.5s所需的脉冲个数为0.5/(12/11059200)= 460800个,显然已经超出了16位置脉冲计数器的最大值(65536),也就是说定时器不支持0.5s的长时间定时。

针对这种情况,就需要令脉冲计数器溢出多次来达到期望的定时时长,具体来讲就是设定一个较短的定时,比如1ms,中断之后,再次定时1ms,直到达到期望的定时时长之后,再去执行具体的任务。

综上所述,对于当前需求,我们就可以将定时时长设置为1ms,每次中断之后,就再定时1ms,除此之外,我们还需要对中断的次数进行统计,每中断500次,就改变一下LED1的状态,这样就能够实现0.5s的周期性任务了。

所以脉冲计数器的初始值应该设置为65536-0.001/(12/11059200)= 64614,具体如下。

TL0 = 64614;

TH0 = 64614 >> 8;

​​​​​​​启动定时器

由于GATE已经设置为0,因此只需将TR0设置为1,即可启动定时器。                                             

// 启动定时器

TR0 = 1;

​​​​​​​定义中断服务程序

按照前文的描述,中断服务程序需要完成如下任务。

  1. 重新装载脉冲计数器
  2. 统计脉冲次数,每500次改变一次LED1的状态

具体代码如下。

void Timer0_Hander() interrupt 1

{

    //定义静态局部变量

    static unsigned int count = 0;

    //重新状态脉冲计数器

    TL0 = 64614;

    TH0 = 64614 >> 8;

    //统计中断次数

    if (count++ >= 500) {

        LED1  = ~LED1;

        count = 0;

    }

}

// Dri_Timer0.h
#ifndef __DRI_TIMER0_H__
#define __DRI_TIMER0_H__
#include <STDIO.H>
typedef void (*Timer0_Callback) (void); // 函数指针起别名 Timer0_Callback
void Dri_Timer0_Init();  // 初始化定时器 
// 注册入口,注册完成的函数,会以1000Hz的频率被调用 成功返回 1; 失败返回 0
bit Dri_Timer0_RedigisterCallback(Timer0_Callback callback);
// 取消注册回调函数,取消注册的函数不会再被周期调用
// 反注册的结果,成功 1; 失败 0
bit Dri_Timer0_DeredigisterCallback(Timer0_Callback callback);

#endif /* __DRI_TIMER0_H__ */

// Dri_Timer0.c
#include "Dri_Timer0.h"
#include <STC89C5xRC.H>
#define MAX_CALLBACK_COUNT 4  // 注册函数的个数
#include "Com_Util.h"
// 注册函数指针数组
static Timer0_Callback s_callbacks[MAX_CALLBACK_COUNT];
void Dri_Timer0_Init() {
    u8 i;
    // 1。启用定时器
    EA = 1;
    ET0 = 1;
    //  2.工作模式
    TMOD &= 0xF0;
    TMOD |= 0x01;
    // 3.初始值
    TL0 = 64614;
    TH0 = 64614 >> 8;
    // 4.启动
    TR0 = 1;
    // 5.初始化函数指针数组
    for(i = 0; i < MAX_CALLBACK_COUNT; i++) {
        s_callbacks[i] = NULL;    
    }
}
bit Dri_Timer0_RedigisterCallback(Timer0_Callback callback) {
    // 1.确保不会重复注册
    u8 i;
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] == callback) {
            rerurn 1;    
        }
    }
    // 2.确定数组有空位
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] == NULL) {
            s_callbacks[i] = callback;
            rerurn 1;    
        }
    }
    return 0;
}
bit Dri_Timer0_DeredigisterCallback(Timer0_Callback callback) {
    u8 i;
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] == callback) {
            s_callbacks[i] = NULL;
            rerurn 1;    
        }
    }
    return 0;
}
void Dri_Timer0_Handler() interrupt 1 {
    u8 i;
    // 1.重装载
    TL0 = 64614;
    TH0 = 64614 >> 8;
    // 调用所有的回调函数
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] != NULL) {
            s_callbacks[i](); // 调用注册的函数
        }
    }
}
//  main.c
#include <STC89C5xRC.H>
#include "Dri_Timer0.h"
#include "Com_Util.h"
// 预处理程序的执行功能
void LED_Blink() {
    static u16 count = 0;
    count++;
    if (count >= 500) {
        P00 = ~P00;
        count = 0;
    }
}

void main() {
    // 在定时器回中注册回调函数ToogleLED
    // 这样LED_Blink这个函数会每1ms被调用一次
    Dri_Timer0_Init();
    Dri_Timer0_RegisterCallback(LED_Blink);
    while (1) {
        
    }
}

点阵LED

点阵工作原理

点阵LED静态显示

使用点阵LED显示一排由左上到右下的斜线

  1. 硬件原理图:点阵LED的显示需要逐行或者逐列扫描,此处选择逐行扫描。
  2. 每行要显示的内容由点阵LED阴极的8个引脚控制,将8个引脚接入MCU的8个GPIO引脚即可。   ​​​​​​​
  3. 点阵LED的行扫描并未使用74HC238芯片,而是使用了74HC595。该芯片的用法

实现思路:

  1. 根据每行要显示的内容,计算出P0的值;
  2. 将每行要显示的内容放到一个buffer数组中;
  3. 扫描每行的同时,依次将每行要显示的内容赋值给P0;

代码实现

// Dri_Timer0.h
#ifndef __DRI_TIMER0_H__
#define __DRI_TIMER0_H__

#include <STDIO.H>
typedef void (*Timer0_Callback)(void);

void Dri_Timer0_init();
bit Dri_Timer0_RegisterCallback(Timer0_Callback callback);
bit Dri_Timer0_DeregisterCallback(Timer0_Callback callback);

#endif /* __DRI_TIMER0_H__ */
// Dri_Timer0.c
#include "Dri_Timer0.h"
#include <STC89C5xRC.H>
#define MAX_CALLBACK_COUNT 4
#include "Com_Util.h"
static Timer0_Callback s_callbacks[MAX_CALLBACK_COUNT];
void Dri_Timer0_init() {
    u8 i;
    // 1.启用定时器
    EA = 1;
    ET0 = 1; 
    // 2.工作模式
    TMOD &= 0xF0;
    TMOD |= 0x01;
    // 3.初始化
    TL0 = 64614;
    TH0 = 64614 >> 8;
    // 4.启动
    TR0 = 1;
    // 5.初始化函数指针数组
    for(i = 0; i < MAX_CALLBACK_COUNT; i++) {
        s_callbacks[i] = NULL;
    }
}

bit Dri_Timer0_RegisterCallback(Timer0_Callback callback) {
    u8 i;
    // 不会重复注册
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] == callback) {
            return 1;
        }   
    }
    // 有坑位
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] == NULL) {
            s_callbacks[i] = callback;
            return 1;
        }
    }
    return 0;
}

bit Dri_Timer0_DeregisterCallback(Timer0_Callback callback) {
    // 取消注册
    u8 i;
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] == callback) {
            s_callbacks[i] = NULL;
            return 1;
        }
    }
    return 0;

}

void Dri_Timer0_Handler() interrupt 1
{
    u8 i;
    // 1. 重装载
    TL0 = 64614;
    TH0 = 64614 >> 8;
    for (i = 0; i < MAX_CALLBACK_COUNT; i++) {
        if (s_callbacks[i] != NULL) {
            s_callbacks[i]();
        }
    }
}

//  Int_LEDMatrix.h
#ifndef __INT_LEDMATRIX_H__
#define __INT_LEDMATRIX_H__
#include "Com_Util.h"
#include <STC89C5xRC.H>

void Int_LEDMatrix_Init();
void Int_LEDMatrix_SetPic(u8 pic[]);
void Int_LEDMatrix_Refresh();
void Int_LEDMatrix_RefreshByTimer0();

#endif /* __INT_LEDMATRIX_H__ */


//  Int_LEDMatrix.c
#include "Int_LEDMatrix.h"
#include "Dri_Timer0.h"
#define SER P10
#define RCK P11
#define SCK P12
#define LED_MATRIX_EN P35
#define LED_EN P34

void Int_LEDMatrix_RefreshByTimer0();
static u8 s_buffer[8];
void IInt_LEDMatrix_Init() {
    u8 i;
    LED_MATRIX_EN = 0;
    LED_EN = 0;
    for (i = 0; i < 8; i++) {
        s_buffer[i] = 0;
    }
    Dri_Timer0_RegisterCallback(Int_LEDMatrix_RefreshByTimer0);
}
void Int_LEDMatrix_SetPic(u8 pic[]) {
    u8 i;
    for (i = 0; i < 8; i++) {
        s_buffer[i] = pic[i];
    }
}

void Int_LEDMatrix_Refresh() {
    u8 i;
    for (i = 0; i < 8; i++) {
        // 将P0 每次显示后重置避免出现下次显示
        P0 = 0xFF;
        if (i == 0) {
            SER =1;
        } else {
            SER = 0;
        }
        // 给SCK RCK 一个上升沿
        SCK = 0;
        SCK = 1;
        RCK = 0;
        RCK = 1;
        P0 = ~s_buffer[i];
        // 延时,避免出现重影
        Com_Util_Delay1ms(1);
        
    }
}

// 通过定时器来延时
void Int_LEDMatrix_RefreshByTimer0() {
    static u8 i = 0;
    P0 = 0xFF;
    if (i == 0) {
        SER = 1;
    } else {
        SER = 0;
    }
    SCK = 0;
    SCK = 1;
    RCK = 0;
    RCK = 1;
    P0  = ~s_buffer[i];
    i++; 
    if (i == 8) {
        i = 0;
    }
}

// main.c
#include "Int_LEDMatrix.h"
#include "Dri_Timer0.h"
void main() {
    u8 pic[8] = {
        0x1,
        0x2,
        0x4,
        0x8,
        0x10,
        0x20,
        0x40,
        0x80
    };
    // 初始化定时器
    Dri_Timer0_init();
    // 初始化LED的输出使能,同时注册定时函数
    Int_LEDMatrix_Init();
    Int_LEDMatrix_SetPic(pic);
    while (1) {
    
    }
}

LED点阵滚动显示

实现代码:

// Int_LEDMatrix.h
#ifndef __INT_LEDMATRIX_H__
#define __INT_LEDMATRIX_H__
#include "Com_Util.h"

void Int_LEDMatrix_Init();
void Int_LEDMatrix_SetPic(u8 pic[]);
void Int_LEDMatrix_Shift(u8 pic_row);
void Int_LEDMatrix_RefreshByTimer0();

#endif /* __INT_LEDMATRIX_H__ */


// Int_LEDMatrix.c
#include <STC89C5xRC.H>
#define LED_MATRIX_EN P35
#define LED_EN        P34

#define SER           P10
#define SCK           P12
#define RCK           P11

static u8 s_buffer[8];
void Int_LEDMatrix_RefreshByTimer0();
void Int_LEDMatrix_Init() {
    u8 i;
    LED_MATRIX_EN = 0;
    LED_EN = 0;
    for (i = 0; i < 8; i++) {
        s_buffer[i] = 0;
    }
    Dri_Timer0_RegisterCallback(Int_LEDMatrix_RefreshByTimer0);
}

void Int_LEDMatrix_SetPic(u8 pic[]) {
    u8 i;
    for (i = 0; i < 8; i++) {
        s_buffer[i] = pic[i];
    }
}

void Int_LEDMatrix_Shift(u8 pic_row) {
    u8 i = 7;
    // 将数组中的每个元素后移一位空出第一位
    for (i = 7; i >= 1; i--) {
        s_buffer[i] = s_buffer[i-1];
    }
    s_buffer[0] = pic_row;
}

void Int_LEDMatrix_RefreshByTimer0() {
    static u8 i = 0;
    P0 = 0xFF;
    if (i == 0) {
        SER = 1;
    } else {
        SER = 0;
    }
    SCK = 0;
    SCK = 1;
    RCK = 0;
    RCK = 1;
    P0  = ~s_buffer[i];
    i++;
    if (i == 8) {
        i = 0;
    }
}


// main.c
#include "Int_LEDMatrix.h"
#include "Dri_Timer0.h"

u8 picture[11] = {
    0xFF,
    0x2A,
    0x14,
    0xA8,
    0x78,
    0xA8,
    0xFC,
    0x2A,
    0x69,
    0xA8,
    0x00
};
void main() {
    u8 i;
    Dri_Timer0_init();
    Int_LEDMatrix_Init();
    while (1) {
        for (i = 0; i < 11; i++) {
            Int_LEDMatrix_Shift(picture[i]);
            Com_Util_Delay1ms(200);
        }
        
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值