案例源码解析
0x00流水灯代码解析
#include<STC15F2K60S2.H>
#define uchar unsigned char
#define uint unsigned int
sbit led_sel=P2^3;//P2^3----E3 对引脚的声明,大小为一位! 用"^"来声明引脚是C51特定的,指明IO口的引脚位置
uchar led; //一定要注意,因为P0是8个引脚,所以就8位,所以要定义成char! 不要定义成int,很容易因为移位等操作出错
void Init(){
P0M1=0x00;
P0M0=0xff; //P0口的控制寄存器
P2M1=0x00;
P2M0=0x08;
led_sel=1; //看电路图,P23有非门,置1才使二极管阴极为0
}
//STC-ISP“软件延时计算器”生成
void delay_ms(uint n)
{
while(n){
uchar i, j;
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
n--;
}
}
void Delay200ms() //@11.0592MHz
{
unsigned char i, j, k;
//_nop_();
//_nop_();
i = 9;
j = 104;
k = 139;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
void main()
{
Init();
led=0x01;
while(1) //让程序一直跑,死循环不退出
{
P0=led; //对端口赋值,设置引脚电平
//delay_ms(200);
Delay200ms(); //延时200ms
if(led==0x80) //也可以用_crol_函数去循环左移,就不用判断0x80的情况了 汇编为rol指令 rotate left
led=0x01;
else
led=led<<1; //每次就亮一盏灯。所以用移位!
}
}
对应的C51汇编代码:
$include(STC15F2K60S2.H)
ORG 0000H //伪指令
LJMP START
;---------------------------------------主程序----------------------------
ORG 0100H ;此指令用在原程序或数据块的开始,指明此语句后面目标程序或数据块存放的起始地址
//初始化,设置推挽输出
START: MOV P0M1, #00000000B
MOV P0M0, #11111111B
MOV P2M1, #00000000B
MOV P2M0, #00001000B
MOV P2, #00001000B
MAINLOOP:
MOV P0, #00000001B
CALL DELAY
MOV P0, #00000010B
CALL DELAY
MOV P0, #00000100B
CALL DELAY
MOV P0, #00001000B
CALL DELAY
MOV P0, #00010000B
CALL DELAY
MOV P0, #00100000B
CALL DELAY
MOV P0, #01000000B
CALL DELAY
MOV P0, #10000000B
CALL DELAY
JMP MAINLOOP
DELAY: MOV R0, #0fFH ;R0-R7是寄存器
L1: MOV R1, #04FH
L2: MOV R2, #20H
L3: NOP
DJNZ R2, L3 ;DJNZ RN,REL 是一条件转移指令,先将工作寄存器Rn中的数减“1”,判断结果是否为“0”,不为“0”程序就跳转到行标为REL的地方执行,否则,为“0”就不转移,继续执行下一条指令。
DJNZ R1, L2
DJNZ R0, L1
RET
END
IO口P0在头文件中被定义成sfr特殊功能寄存器,对应0x80地址。 因为导入了头文件,所以在汇编也能直接用P0
单片机程序也有堆栈!!
0x01八位数码管动态扫描
工作原理
P0口的8位输出分别控制1个LED数码管的7段和一个小数点;而P2.3经反相器U4C控制74HC138的使能信号E3,结合P2.0、P2.1、P2.2这3个位选控制信号确定8个LED数码管中的哪个被点亮;电阻R15~R22为限流电阻。当段选为高、使能信号有效时,对应的LED管将会发光。通过以一定频率扫描位选信号,修改段选信号进行数码管点亮一段时间,从而给人视觉上几个数码管几乎同时显示的效果。
电路图与数码管引脚定义
中间是数码管,下面是个译码器,用于位选。
P0.0对应A,P0.7对应小数点
流程图
相关寄存器设置
P0(8位)和P2.3需要设置成推挽输出,以驱动电路正常发光。涉及寄存器及配置值如下:
P2M1=0x00;
P2M0=0xff;
P0M1=0x00;
P0M0=0xff;
源码
#include <STC15F2K60S2.h>
#define uint unsigned int
#define uchar unsigned char
uint i=0;
uchar duanxuan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f}; //显示0-8
uchar weixuan[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}; //数码管0-7,P2.3是数码管的位选,但是有反相器,所以P2.3置0,数码管才能工作
void Delay(int n)
{
while(n--);
}
void main()
{
P2M0=0xff; //设置推挽输出
P2M1=0x00;
P0M0=0xff;
P0M1=0x00;
while(1)
{
for(i=0;i<8;i++)
{
P0=0;
P2=weixuan[i]; //选择数码管位数
P0=duanxuan[i+1]; //显示对应数值
Delay(600);
}
}
}
P2.3引脚=0时,数码管工作;=1时,LED灯工作
0x02八位数码管动态扫描+流水灯
#include <STC15F2K60S2.h>
#define uint unsigned int
#define uchar unsigned char
uchar arrSeg7Select[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f}; //显示0-8
uchar arrDigitSelect[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; //数码管0-7
sbit sbtLedSel = P2 ^ 3; //ÊýÂë¹ÜÓëLEDµÆÇл»Òý½Å
/*---------±äÁ¿¶¨Òå---------*/
uchar uiLed = 0x01; //LED灯值寄存
uint uiLedCnt = 0; //LED累计计数器
uchar i = 0; //数码管扫描循环显示
/*---------³õʼ»¯º¯Êý---------*/
void Init()
{
P0M1 = 0x00;
P0M0 = 0xff;
P2M1 = 0x00;
P2M0 = 0x08;
sbtLedSel = 0; //先选择数码管亮
}
//延时n毫秒
void delay_ms( uint n )
{
while( n )
{
uchar i, j;
i = 11;
j = 190;
do
{
while ( --j );
}
while ( --i );
n--;
}
}
void main()
{
Init();
while( 1 )
{
sbtLedSel = 0;
for( i = 0; i < 8; i++ )
{
P0 = 0;
P2 = arrDigitSelect[i]; //选择数码管位数
P0 = arrSeg7Select[i + 1]; //显示对应数值
delay_ms( 1 );
}
uiLedCnt++;
sbtLedSel = 1;
P0 = uiLed; //LED灯显示
delay_ms( 1 ); //延时1ms
if( uiLedCnt == 50 ) //同一个灯亮50次,再换下一个灯
{
if( uiLed == 0x80 ) //rotate
uiLed = 0x01;
else
uiLed = uiLed << 1; //流水灯左移
uiLedCnt = 0;
}
}
}
- 在数码管和流水灯切换时,因为共用P0来设置要亮的东西,所以在切换开关后的那一瞬间,有可能会使上一个器件亮下一个器件的P0! 解决办法是:在切换开关之前,设置P0=0,此时两个器件都是不亮的状态,切换开关之后,再设置下一个器件的P0,那么就避免了两器件误亮对方数值的情况。 这其实就是初始化的道理。
0x03 三按键测试
若KEY1被按下,则LED灯L0发光,否则,LO不发光。
若KEY2被按下,则LED灯L1发光,否则,L1不发光。
若KEY3被按下,则LED灯L2发光,否则,L2不发光。
按键电路示意图
(三个按键分别是K1、K2、K3)
当按键被按下的时候,电路导通接地,I/O口为低电平;当按键未被下时,电路断开,I/O口保持高电平的。
源码
#include"STC15F2K60S2.H"
//引脚
sbit KEY1 = P3^2;
sbit KEY2 = P3^3;
sbit KEY3 = P1^7;
sbit led_sel = P2^3;
void main()
{
//推挽输出
P0M0 = 0XFF;
P0M1 = 0X00;
P2M0 = 0X08;
P2M1 = 0X00;
led_sel = 1; //让LED发光
P0 = 0; //初始化P0,让灯全灭
while(1)
{
if(KEY1 == 0) //key1被按下
P0 |= 0x01; //L0发光
else
P0 &= ~0x01; //L0熄灭
if(KEY2 == 0)
P0 |= 0x02;
else
P0 &= ~0x02;
if(KEY3 == 0)
P0 |= 0x04;
else
P0 &= ~0x04;
}
}
只要key被按下不松口开,引脚就一直是低电平,LED灯就一直亮。 while会一直扫描三个按键的情况,每次扫描,按键在按时,对应灯都会亮一下,而while循环很快,所以造成灯一直在亮的现象。
0x04 蜂鸣器测试
蜂鸣器初始状态是没有发声;按下按键1,则蜂鸣器开始发声。再次按下按键1,蜂鸣器停止发声。
蜂鸣器分为有源蜂鸣器和无源蜂鸣器,这里的源特指振荡源;有源蜂鸣器直接加电就可以响起,无源蜂鸣器需要我们给提供振荡源。理想的振荡源为一定频率的方波。
本实验板采用的是无源蜂鸣器,相比与有源蜂鸣器,无源蜂鸣器的优点在于价格便宜,可以通过控制其振动频率来改变发出的声音,因此,无源蜂鸣器可以用于音乐的播放。而有源蜂鸣器的优点在于使用简单,不需要编写“乐谱”。本实验板使用的无源蜂鸣器是电磁式蜂鸣器,电磁式蜂鸣器由振荡器、电磁线圈、磁铁、振动膜片及外壳等组成。接通电源后,接收到的音频信号电流通过电磁线圈,使电磁线圈产生磁场。振动膜片在电磁线圈和磁铁的相互作用下,周期性地振动发声。
无源蜂鸣器只需改变Beep端口的电平,产生一个周期性的方波即可使蜂鸣器发生声音,不同的频率发出的声音不同。其中,ULN2003是一个功放,用于放大电流。电阻R14和电容C21是用来保护电路的。若人为将Beep端口的电平一直置为高电平,在没有保护电路的情况下,容易烧毁电路,但即使有保护电路也应该注意不要将Beep端口长时间置于高电平,这对器件也是有一定损害的。
可通过改变驱动口电平翻转的时间来获得不同声调的声音, 还可通过改变高低电平在一个周期内的比例来获取不同音量的声音.
相关电路
图1 无源蜂鸣器电路原图
按键控制电路
图2 按键控制电路
源码
#include "STC15F2K60S2.H"
#define uchar unsigned char
#define uint unsigned int
sbit beep = P3^4; //蜂鸣器
sbit key1 = P3^2; //按键1
bit flag; //蜂鸣器开关的标志
void init()
{
//ÉèÖÃÍÆÍìģʽ
P3M1=0x00;
P3M0=0x10; //设置P3^4为推挽模式
TMOD=0x00; //设置定时器0,工作模式0,16位自动重装定时器
TH0=0xff; //定时器初值
TL0=0x03;
EA=1; //打开总中断
ET0=1; //定时器0的中断允许位
TR0=1;
flag=0; //标志位置1
P0=0x00; //关闭P0端口
beep=0; //蜂鸣器引脚置0,保护蜂鸣器 因为板子复位时,所有引脚为高电平
}
void delay(uint xms)
{
uchar i;
for(; xms>0; xms--)
for(i=114; i>0; i--)
{
;
}
}
void main()
{
init();
while(1)
{
if(key1==0) //按下
{
delay(10); //延时消抖
if(key1==0)
{
while(!key1); //key1松开才停止循环,去翻转蜂鸣器开关标志
flag=~flag; //开关标志位翻转,用于中断函数内去翻转开关,产生方波
}
}
}
}
/********************
定时器中断
********************/
void timer0() interrupt 1 //用定时器来定时产生方波
{
if(flag)
{
beep=~beep; //产生方波使得蜂鸣器发声
}
else
{
beep=0; //flag=0时,说明已经再次按下了key1,那么蜂鸣器就该停止发声
}
}
0x05 实时时钟
通过DS1302芯片、晶振、电池和数码管实现实时时钟的数码管显示。
程序运行效果说明:将程序下载至芯片,数码管会出现实时的时钟,断开USB端口,不给实验板外部供电,时钟依然走秒。
DS1302芯片介绍
DS1302是 DALLAS 公司推出的涓流充电时钟芯片内含有一个实时时钟/日历和 31 字节静态 RAM,外接32.768kHz晶振,为芯片提供计时脉冲,在电路板的纽扣电池(位于电路板左下方圆柱体)的持续供电下,实现DS1302的独立时间走动。我们可以直接对DS1302的寄存器进行读写,然后把时分秒等数据显示在实验板的数码管上面。
实时时钟的核心是晶振,晶振频率为32768 Hz 。它为分频计数器提供精确的与低功耗的实基信号。它可以用于产生秒、分、时、日等信息。为了确保时钟长期的准确性,晶振必须正常工作,不能够收到干扰。
那实时时钟的晶振频率为什么是32768Hz? 实时时钟时间是以振荡频率来计算的。故它不是一个时间器而是一个计数器。而一般的计数器都是16位的。又因为时间的准确性很重要,故震荡次数越低,时间的准确性越低。所以必定是个高次数。32768 Hz = 2^15 即分频15次后为1Hz,周期 = 1s;经过工程师的经验总结32768 Hz,时钟最准确。
与单片机相连的参考电路如上所示。可见,DS1302作为外部设备,与CPU通过IO口传输数据。很明显与单片机相连只需要三条线。
CE复位引脚(RTC_/RST引脚):输入信号,在读写数据期间,必须为高,因此该引脚有两个功能,一是CE开始控制字访问移位寄存器的控制逻辑,二是提供结束单字节或多字节数据传输的方法。
I/O引脚:三线接口时的双向数据线。 只能通过该IO引脚来往ds1302芯片内读写数据,每次1位
SCLK:串行时钟,输入,控制数据的输入与输出。
DS1302对应的时序:单个字节读:在前8个SCLK时钟周期内,上升沿写入控制字,在后8个SCLK时钟周期内,下降沿读取数据字;均从最低位开始。
单个字节写:在前8 个 SCLK 时钟周期,上升沿写入控制字,在后 8 个 SCLK 时钟周期,上升沿写入数据字;均从最低位开始。
DS1302引脚
sbit RTC_sclk=P1^5; //时钟线引脚
sbitRTC_rst=P1^6; // CE线引脚
sbit RTC_io=P5^4; //实时时钟的数据线引脚
DS1302相关寄存器
DS1302内部一共有12个寄存器,其中七个寄存器与时钟,日历有关,就是上表中的前七个,寄存器中的数据是二—-十进制的BCD码。
其中:
①命令字:
位7:必须是逻辑1,如果它为0,则不能把数据写入到DS1302中。
位6:如果为0,则表示存取日历时钟数据,为1表示存取RAM数据;此程序中没有涉及RAM存取数据,所以位6置为0.
位5~位1(A4~A0) :指示操作单元的地址; 这里的地址是ds1302芯片内部的ram,共31字节,用于存储时间信息,控制信息等 与单片机的ram无关 所以源码中的define的0x80,0x81等,就是对应功能的控制字!而不是单片机的ram地址!! 可以算一下,0x80满足控制字的格式,指向ds1302的0x00ram地址
位0 :为0写操作,为1读操作。
命令字始终从LSB(位0)开始输入(最低有效位)。
讲一下为什么命令字的D7一定是1,。大家都知道单片机端口结构吧,端口置1后,灌电流的通道关闭,引脚上若进入数据,数据会保持不变的,也就是说数据会不会变化,不管是写还是读,都是正确的。若端口置0,则灌电流通道打开,引脚上进入的数据若是1,则通过该通道流走,变成0,举个例子,置0后,DS1302输出数据到该引脚,则该引脚读到的数字全是0。
②秒寄存器:
位7:时间暂停位,为1时钟振荡器停止工作,为0时,时钟振荡器启动;
初始化时,要启动时钟振荡器,在禁止写保护的情况下通过如下语句实现:
temp=ReadDS1302(DS1302_SECOND_READ)&0x7f;
WriteDS1302(0x80,temp);//晶振开始工作
③小时寄存器:
位7:12或24小时工作模式选择位,为1时12小时工作模式,此时位5为AM/PM位,低电平对应 AM,高电平对应PM;在 24 小时模式下,位5为第二个10小时位表示(20~23 时);
④写保护:
位7: WP 是写保护位,工作时除WP 外的其他位都置为0, 写操作之前WP必须为0,当WP 为1时不能进行写操作。
WriteDS1302(0x8E,0x00); //禁止写保护位
WriteDS1302(0x8E,0x80); //写保护位置1 是往ds1302芯片的ram对应位置写1!!
●控制寄存器的位7是写保护位。 ●前七个位(位0到6)被强制为0,并且在读取时始终为0。 ●在对时钟或RAM进行任何写操作之前,位7必须为0。 ●为高电平时,写保护位可防止对其他任何寄存器的写操作。 ●初始上电状态未定义。 因此,在尝试写入设备之前,应先清除WP位。
疑惑
命令字写在哪?? 写在控制寄存器还是ds1302ram?? 和wp所在寄存器重叠?
wp应该是防止对其他寄存器的写入,0是能写,1是不能写 写命令字受写保护的影响吗?
如果不受影响,那么说得通,wp就是防止对时钟即ram数据进行写入。
查阅ds1302数据手册:
可见,命令字8位会传到command and control logic模块去进行译码等,并没有进行写入操作,所以wp是指对ram和时钟寄存器等的写入,不包含命令字!!
源码
/**********************
进行实时时钟模块测试的例程
采用定时器0,方式1通过数码管对时间进行显示
***********************/
/**********************
基于STC15F2K60S2系列单片机C语言编程实现
使用如下头文件,不用另外再包含"REG51.H"
***********************/
#include"STC15F2K60S2.H" //头文件
#include"intrins.H" //头文件
#define uchar unsigned char //宏定义
#define uint unsigned int
/***********时分秒写寄存器**************/
#define DS1302_SECOND_WRITE 0x80 //不是sfr,是ds1302对应功能的控制字
#define DS1302_MINUTE_WRITE 0x82
#define DS1302_HOUR_WRITE 0x84
#define DS1302_WEEK_WRITE 0x8A
#define DS1302_DAY_WRITE 0x86
#define DS1302_MONTH_WRITE 0x88
#define DS1302_YEAR_WRITE 0x8C
/***********时分秒读寄存器**************/
#define DS1302_SECOND_READ 0x81
#define DS1302_MINUTE_READ 0x83
#define DS1302_HOUR_READ 0x85
#define DS1302_WEEK_READ 0x8B
#define DS1302_DAY_READ 0x87
#define DS1302_MONTH_READ 0x89
#define DS1302_YEAR_READ 0x8D
/**********************
引脚别名定义
***********************/
/********DS1302*******/
sbit Rtc_sclk = P1^5; //时钟线引脚,控制数据的输入与输出
sbit Rtc_rst = P1^6; //CE线引脚,读、写数据时必须置为高电平
sbit Rtc_io = P5^4; //实时时钟的数据线引脚
/********数码管显示******/
sbit Led_sel = P2^3; //流水灯和数码管选通引脚
sbit Sel0 = P2^0; //Sel0、Sel1、Sel2三位二进制进行数码管位选0-7
sbit Sel1 = P2^1;
sbit Sel2 = P2^2;
/**********************
变量定义
***********************/
uchar duanxuan[]={ //数码管显示所要用的段码
0x3F, //"0"
0x06, //"1"
0x5B, //"2"
0x4F, //"3"
0x66, //"4"
0x6D, //"5"
0x7D, //"6"
0x07, //"7"
0x7F, //"8"
0x6F, //"9"
0x77, //"A"
0x7C, //"B"
0x39, //"C"
0x5E, //"D"
0x79, //"E"
0x71, //"F"
0x76, //"H"
0x38, //"L"
0x37, //"n"
0x3E, //"u"
0x73, //"P"
0x5C, //"o"
0x40, //"-"
0x00, //熄灭
0x00 //自定义
};
uchar weixuan[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}; //数码管位选数组
uchar flag; //所选择点亮的数码管0-7标志位
uchar temp; //要写入到DS1302的数据
/**********************
定义时间结构体
***********************/
typedef struct __SYSTEMTIME__
{
uchar Second;
uchar Minute;
uchar Hour;
uchar Week;
uchar Day;
uchar Month;
uchar Year;
}SYSTEMTIME; //定义的时间类型
SYSTEMTIME t;
/*******************************
* 函数名:Delayms
* 描述 :毫秒延时程序
* 输入 :延时i毫秒
* 输出 :无
*******************************/
void Delayms(char i)
{
while(i--)
{
int n=500;
while (n--)
{
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
_nop_();
}
}
}
/**********************
函数名称:Ds1302_write
功能描述:Ds1302写入一字节
入口参数:uchar 要写入的数据
***********************/
//单个字节写:在前 8 个 SCLK 时钟周期,上升沿写入控制字,在后 8 个 SCLK 时钟周期,上升沿写入数据字;均从最低位开始。 在这里由代码生成sclk的波形,来控制数据写入
void Ds1302_write(uchar temp) //temp:要写入的数据
{
uchar i;
for(i=0;i<8;i++) //循环8次写入一个字节
{
Rtc_sclk=0; //时钟脉冲拉低
Rtc_io=(bit)(temp&0x01); //取最低位数据
temp>>=1; //右移一位
Rtc_sclk=1; //上升沿输入
}
}
/**********************
函数名称:Ds1302_read
功能描述:Ds1302读取一字节
入口参数:无
出口参数:返回uchar 读出的数据
***********************/
uchar Ds1302_read()
{
uchar i, dat;
for(i=0;i<8;i++)
{
Rtc_sclk=0; //时钟脉冲拉低 下降沿时读取
dat>>=1; //要返回的数据右移一位
if(Rtc_io==1) //数据线为高,证明该位数据为1
dat|=0x80; //要传输数据的当前值置为1,若不是,则为0
Rtc_sclk=1; //拉高时钟线
}
return dat; //返回读取出的数据
}
/**********************
函数名称:WriteDS1302
功能描述:向Addr对应的寄存器中写入数据Data
入口参数:uchar Addr 寄存器地址, uchar Data 要写入寄存器的数据
***********************/
void WriteDS1302(uchar Addr, uchar Data) //Addr: DS1302地址,Data: 要写的数据
{
Rtc_rst = 0; //初始CE线置0
Rtc_sclk = 0; //初始时钟线置0
Rtc_rst = 1; //初始CE线置为1,传输开始
Ds1302_write(Addr); // 地址,传输命令字 即控制字,写到控制字寄存器
Ds1302_write(Data); // 写1Byte数据
Rtc_rst = 0; //读取结束,CE置0,结束数据的传输
Rtc_sclk = 1; //时钟线拉高
}
/**********************
函数名称:ReadDS1302
功能描述:读出Addr对应的寄存器中的数据
入口参数:uchar cmd 寄存器地址
***********************/
uchar ReadDS1302(uchar cmd)
{
uchar Data;
Rtc_rst = 0; //初始CE线置为0
Rtc_sclk = 0; //初始时钟线置为0 就算之前sclk为高电平,产生下降沿,io口读数据也没事 因为读数据只是ds1302把数据传到io口,我们并没有进行接收,所以没事
Rtc_rst = 1; //初始CE置为1,传输开始
Ds1302_write(cmd); // 地址,传输命令字
Data =Ds1302_read(); // 读1Byte数据
Rtc_rst = 0; //读取结束,CE置为0,结束数据的传输
Rtc_sclk = 1; //时钟线拉高 write/read完sclk都是=1
return Data; //返回得到的数据
}
/**********************
函数名称:DS1302_GetTime_BCD
功能描述:读出DS1302中时、分、秒、年、月、日寄存器中对应的数据
***********************/
SYSTEMTIME DS1302_GetTime()
{
SYSTEMTIME Time;
uchar ReadValue;
ReadValue = ReadDS1302(DS1302_SECOND_READ); //1字节
//将BCD码转换成十进制
Time.Second=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取秒寄存器中的数据
ReadValue=ReadDS1302(DS1302_MINUTE_READ);
Time.Minute = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取分寄存器中的数据
ReadValue = ReadDS1302(DS1302_HOUR_READ);
Time.Hour = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取时寄存器中的数据
ReadValue = ReadDS1302(DS1302_DAY_READ);
Time.Day = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取日寄存器中的数据
ReadValue = ReadDS1302(DS1302_WEEK_READ);
Time.Week = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取周寄存器中的数据
ReadValue = ReadDS1302(DS1302_MONTH_READ);
Time.Month = ((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取月寄存器中的数据
ReadValue = ReadDS1302(DS1302_YEAR_READ);
Time.Year=((ReadValue&0x70)>>4)*10 + (ReadValue&0x0F); //读取年寄存器中的数据
return Time;
}
/**********************
函数名称:Init
功能描述:完成各部分功能模块的初始化
入口参数:无
出口参数:无
备注:
***********************/
void Init()
{
P2M0=0xff; //设置推挽模式
P2M1=0x00;
P0M0=0xff;
P0M1=0x00;
Led_sel=0; //选通数码管
TMOD=0x01; //定时器0,方式1
EA=1; //打开总的中断
ET0=1; //开启定时器中断
TH0=(65535-1000)/256; //设置定时初值
TL0=(65535-1000)%256;
TR0=1; //启动定时器
}
void Initial_DS1302(void)
{
WriteDS1302(0x8E,0x00); //禁止写保护位,因为初始化要进行写操作
temp=ReadDS1302(DS1302_SECOND_READ)&0x7f ; //第7位置0,启动晶振
WriteDS1302(0x80,temp); //晶振开始工作
WriteDS1302(0x8E,0x80); //写保护位置1,现在开始不能写了
}
/********************************定时器中断处理程序************************************************/
void timer0() interrupt 1 //把数码管的显示提到中断里面来了
{
TH0=(65535-1000)/256; //重新填充初值 定时1ms
TL0=(65535-1000)%256;
/********显示程序*******/
flag++; //初值为0
if(flag==8)
flag=0;
P0=0x00; //先初始化,什么都不亮
P2=weixuan[flag];
switch(flag)
{
case 0:P0=duanxuan[t.Hour/10];break;
case 1:P0=duanxuan[t.Hour%10];break;
case 3:P0=duanxuan[t.Minute/10];break;
case 4:P0=duanxuan[t.Minute%10];break;
case 6:P0=duanxuan[t.Second/10];break;
case 7:P0=duanxuan[t.Second%10];break;
default:P0=duanxuan[22];break; //duanxuan[22]所对应的字符为“-”
}
}
void main()
{
Init();
if (ReadDS1302(DS1302_SECOND_READ)&0x80) //判断掉电之后时钟是否还在运行
Initial_DS1302(); //若没有运行,则进行DS1302初始化
while(1)
{
Delayms(300);
t=DS1302_GetTime(); //取时间各数据放到结构体t中
}
}
main函数的while循环每300ms去读取ds1302内的时间数据,然后通过定时器,每1ms发生中断去让数码管显示t中的时间数据。
在该程序中,只在Initial_DS1302函数中用到了writeDS1302,去启动时钟。
0x06导航按键测试
利用STC15F2K60S2芯片的ADC口对来自导航按键不同方向的电压值进行采集,并将采集后的转换结果用数码管显示。程序主要是对ADC进行操作,并将寄存器相应位取出分别用8位二极管和数码管显示。第一位数码管显示8位转换结果中高三位值,最后两位数码管显示后低位值。数码管下方的发光二极管与数码管对应显示。
程序运行效果说明:根据用户对导航按键的操作情况,相应产生的ADC转换结果,在数码管最高位(命名Seg0)显示转换结果高三位,点亮对应的发光二极管L7—L5。数码管后两位(命名Seg6-Seg7)显示转换结果低五位,点亮对应的发光二极管L4—L0。具体显示情况如表1所示。注:实际数值如果有一点误差,是由于电阻的工艺使得电阻会有一定的误差,关系不大,我们在做导航按键的判断时,都只是取高三位的值,也就是数码管Seg0的值。
导航按键电路及工作原理说明
导航按键在上图的标注为MINI_KEY5,首先可以看到左边有多个不同阻值的电阻,然后是分别是1、2、3、4、5、6共6个接口。导航键的开关接到不同接口上由于总阻值不一样,所以加在这两点的电压肯定不同。从而可以根据这个原理,与A/D转换器配合,可以判断哪个方位被按下,获取按下后A/D转换的结果。
图片右上角KEY3是按键的电压输出,是模拟信号
这张图标注了STC板子上每一个接口的名称,当你想用这个某个端口的时候就在这张图上面找对应的名字就好。
发现前面还有一些字母,如ADC7,说明这个端口可以作为A/D转换的输入。
AD数据采集电路及采集步骤说明
ADC数据采集的步骤:
l 将ADC0~7的模拟量送到比较器中,用DAC(数/模转换器)转换的模拟量与输入的模拟量通过比较器进行比较。
l 转换结束后,将比较结果放入转换结果寄存器(ADC_RES和ADC_RESL)。
l 同时,需要将ADC_FLAG软件清零。
l 注意硬件会自动将ADC_START清零,如果需要进行下一次转换,则需要将ADC_START置位。
特别说明:
(1)数码管所显示的ADC转换结果并不是电压值,而是电压进行转换后所得的一个值。如果需要实际的电压值可以参照STC15F2K60S2数据手册的760页上面的公式进行计算得出。
(2)ADC转换结果是一个10位数据,若ADRJ=0,则ADC_RES存放高八位,ADC_RESL存放低两位。若ADRJ=1,则ADC_RESL存放高八位,ADC_RES存放低两位。本案例采用的是ADRJ=0,而且只取了高八位结果。
如图是AD转换需要用到的寄存器:
首先配置好我们AD采集信息的输入口,那么要写入信息,就要用到与此相关的寄存器,也就是P1ASF。它有八个口可以作为输入。而我们是采集导航键的,刚刚说了导航键的输出对应的是P1.7口。因为P1ASF可设置为:P1ASF = 0x80(1000 0000)。如图:(B7-B0哪位为1就表示选用哪个端口作为数据输入源)
信息源有了,接下来是打开我们的AD转换器。
跟打开定时器类似,也要用到控制寄存器,也就是ADC_CONTR啦。先来看看它的结构:
所以这个ADC_CONTR可以设置为:ADC_CONTR = 0x87 (1000 0111) ADC_FLAG需要特别注意,一定要软件清0,否则就只能转换一次。ADC_STATR此处不需要设置,你可以手动开启。ADC_CONTR也是可为寻址的,因此可以单独设置某一位的值。
那么转换后的结果放在哪呢?寄存器呗!AD转换有用来存储结果的寄存器,称为结果寄存器。如图:
最后是中断相关的寄存器
和定时器一样,因为AD转换器在CPU外部,所以AD转换完成会发生中断告知CPU转换完成!
查数据手册,可以知道中断表。 学校的板子是51的增强版,有3个定时器。
AD中断号为5
源码
/**************************
说明:导航按键的例程
修改:消除数码管显示中的残影
**************************/
/**************************
使用头文件如下:
**************************/
#include "STC15F2K60S2.H"
#include <intrins.h> //_cror_();
#define uint unsigned int
#define ulint unsigned long
#define uchar unsigned char
#define ADC_FLAG 0x10
/**************************
引脚别名定义如下:
**************************/
sbit SEL0 = P2^0;
sbit SEL1 = P2^1;
sbit SEL2 = P2^2;
sbit LED_SEL = P2^3;
/**************************
定义变量如下:
**************************/
uint global = 0;
uint date_h = 0;
ulint sum = 0;
uint x = 0; //全局变量存储AD转换值
uint y = 0;
uchar seg_flag;
//0123456789-
char duanxuan[] = {0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
//选择哪一位数码管
uchar weixuan[] = {0x00,0x06,0x07};
//初始化系统
void InitSYS(){
//P0口推挽输出,用于给数码管和发光二极管增加电流
P0M1 = 0x00;
P0M0 = 0xff;
//P2.3(即LED_SEL),P2.2,P2.1为推挽输出
P2M1 = 0x00;
P2M0 = 0x0E;
seg_flag = 0;
TMOD = 0x00; //定时器0,工作方式1;
EA = 1; //打开总中断;
TH0 = (65535-1000)/256; //定时器初值 1ms
TL0 = (65535-1000)%256;
ET0 = 1; //打开定时中断;
TR0 = 1; //启动定时器;
}
//初始化ADC
void InitADC()
{
P1ASF = 0x80; //P1.7作为A/D使用
ADC_RES = 0; //清零ADC结果寄存器
ADC_CONTR = 0X8F; //打开ADC电源 540个时钟周期转换一次
CLK_DIV = 0X00; //ADRJ = 0 ADC_RES存放高八位结果
EADC = 1; //允许ADC中断
PADC = 1; //ADC中断优先级为1
}
//显示高三位和低五位
void HighThree()
{
x = date_h&0xE0; //将八位转换结果的低五位清零
x = _cror_(x,5); //将高三位移到低三位
y = date_h&0x1F; //对原值取低5位,将八位转换结果的高三位清零
}
void timer0() interrupt 1 {
seg_flag++; //每1ms扫描一次,各位轮流显示
if(seg_flag==4)
seg_flag = 0;
P0 = 0x00; //每次首先初始化,什么都不亮
switch(seg_flag) {
case 0:
LED_SEL = 0; //选择数码管显示
P2 = weixuan[seg_flag];
P0 = duanxuan[x]; //显示ADC转换结果高三位的十进制值
break;
case 1:
LED_SEL = 0; //选择数码管显示
P2 = weixuan[seg_flag];
P0 = duanxuan[y/10]; //显示ADC转换结果低五位的十进制值的高位
break;
case 2:
LED_SEL = 0; //选择数码管显示
P2 = weixuan[seg_flag];
P0 = duanxuan[y%10]; //显示ADC转换结果低五位的十进制值的低位
break;
case 3:
LED_SEL = 1; //选择发光二极管显示
P0=date_h; //LED灯直接显示整个8位
break;
}
}
//AD中断
void adc_isr() interrupt 5 //只要ADC_CONTR的ADC_POWER=1(即打开AD电源),且每次AD中断中设置ADC_START=1,AD转换是一直在转换的,就算不按导航按键,数码管也会显示当前电压值。所以几乎是实时刷新当前电压值,刷新频率由控制寄存器的speed设置。 若想让AD停止,把Power位=0即可。
{
IE=0x00; //关闭所有中断,防止数据没有采集完就进入新的中断(包括定时器的中断! 反正AD中断函数不长,耽误不了多少数码管显示时间)
ADC_CONTR&=~0X10; //将ADC_FLAG位清零
global++;
if(global>1000) //将采集1000次后的数据求出平均值
{
date_h=(sum+500)/1000;//四舍五入
HighThree();
global=0;
sum=0;
}
sum+=ADC_RES; //每次中断,累加
ADC_CONTR|=0X08; //将ADC_START进行软件置位,开启下一次转换
IE=0xa2; //再次开启中断
}
void main()
{
InitSYS();
InitADC();
while(1) //直接让定时器和AD跑,不断扫描
;
}
x,y等的类型应该改成uchar,不然int直接赋给P0 sfr,大小不符,虽然也能跑
0x07 非易失性存储
本程序是对24C02存储页面的0x00地址写入可变化的数据,然后读取数据,并显示在数码管上。
位数码管默认显示0。按下key3,要写入数据的地址加1。按下key2要写入的数据加1。按下key1,向存储器写入数据并读取数据,并显示在数码管上。数码管左边2位(第一、第二位)是写入的地址,数码管中间两位(第四、第五位)是写入的数据,数码管右边两位(第七、第八位)是显示从非易失存储器读取的数据。
工作原理
非易失性存储器(nonvolatile memory)是掉电后数据能够保存的存储器,它不用定期地刷新存储器内容。这包括所有形式的只读存储器(ROM),像是可编程只读存储器(PROM)、可擦可编程只读存储器(EPROM)、电可擦除只读存储器(EEPROM)和闪存。在许多常见的应用中,微处理器要求非易失存储器来存放其可执行代码、变量和其他暂态数据(例如采集到的温度、光照等数据)。
24C02工作电路及其工作原理
24C02模块电路
本实验采用24C02芯片, 24C02通过IIC_SCL和IIC_SDA与单片机相连,单片机以IIC总线的方式对24C02进行读写。24C02是一个2K位(即256字节)串行E2PROM,内部含有256个8位字节。
(1)管脚配置
(2)管脚描述
A0A1A2是选择24c02芯片的序号
(3)寻址方式
寻址信号由一个字节构成,高7位为地址位,最低位为方向位,用以表明主机与从器件的数据传送方向。方向位为0,表明主机接下来对从器件进行写操作;方向位位1,表明主机接下来对从器件进行读操作。
A0,A1和A2对应器件的管脚1,2和3;
a8,a9和a10对应存储阵列地址字地址;
(4)读/写时序
写一个字节时序
读一个字节时序
如图,写一个字节时序,第一个DEV SEL是器件选择信号(即上面寻址方式描述的),器件选择的范围为(000~111),总共可以选择8个24C02芯片器件。但是本实验只用到了1个24C02芯片,所以对应的器件管脚地址A2A1A0为000。第二个信号BYTEADDR是地址信号,表示要对哪一个地址进行操作,第三个DATA IN则是写入的数据。而读操作则是多了一步,DEV SEL和BYTE ADDR后,还有一个DEV SEL,但此信号的最后一位为高,表示是读操作,随后从机会把相应地址的数据发送给主机。
24CXX支持I2C总线传输协议
I2C总线介绍
I2C(Inter-Integrated Circuit)总线是由PHILIPS公司开发的两线式串行总线,用于连接微控制器及其外围设备。是微电子通信控制领域广泛采用的一种总线标准。它是同步通信的一种特殊形式,具有接口线少,控制方式简单,器件封装形式小,通信速率较高等优点。I2C总线硬件结构图如下:
SCL是时钟线,SDA是数据线
I2C总线信号包括有,启始信号,停止信号和应答信号,在程序用分别用函数void start()、void stop()、void respons()表示。24C02的存储空间为2K,每一次写和读操作都只能操作已选定的对应24C02芯片的地址数据。要切换操作的芯片,需要重新发送寻址信号,在void write_add(uchar addr,uchar date)函数中,第一个寻址信号writebyte(0xa0),已经固定了本程序只能在第0个芯片进行操作(注:0xa0化为二进制为1010000,其中,前4位1010是固定不能改变的,最后一位0代表写操作,1代表读操作,而中间三位则是代表不同芯片地址的编号),若要改变需要操作的芯片,则只需改变中间三位即可。
(1)I2C位传输
数据传输:SCL为高电平时,SDA线若保持稳定,那么SDA上是在传输数据bit;若SDA发生跳变,则用来表示一个会话的开始或结束
数据改变:SCL为低电平时,SDA线才能改变传输的bit 因为SCL为高电平时,SDA跳变表示开始和结束信号
(2)I2C开始和结束信号
开始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传送数据。
结束信号:SCL为高电平时,SDA由低电平向高电平跳变,结束传送数据。
(3)I2C应答信号
主设备每发送完8bit数据后等待从设备的ACK。
即在第9个clock,从IC发ACK,SDA会被拉低。 此时SCL为高电平 注意:ACK是IC发给Master!! 所以不会误判为开始信号!! 下一个字节的传输会在SCL为低电平的时候进行SDA转换!!
若没有ACK,SDA会被置高,这会引起Master发生RESTART或STOP流程。
程序流程图
源码
#include <STC15F2K60S2.h>
#define uint unsigned int
#define uchar unsigned char
#define FOSC 11059200L //晶振频率
#define T_ms 0.1 //定时时间为0.1ms
#define NMAX_KEY 10
/*位声明*/
sbit Key1=P3^2; //按下key1,向存储器写入数据并读取该地址的数据,显示在数码管上。
sbit Key2=P3^3; //按下key2,要写入的数据加1
sbit Key3=P1^7; //按下key3,要写入的地址加1
sbit led=P2^3; //LED灯与数码管切换引脚
sbit SDA=P4^0; //I2C总线的数据线
sbit SCL=P5^5; //I2C总线的时钟线
/*变量定义*/
uchar duan[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71}; //数码管段选,显示0-f
uchar wei[]={0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07}; //数码管位选
uchar flag1; //数码管循环扫描变量
uchar write_addr; //写入地址
uchar write_date; //写入数据
uchar read_date; //读出数据
/*按键计数消抖变量*/
uchar G_count; //定时器0中断计数值
uint Key1_count; //KEY1键在1ms内达到低电平的次数
uint Key2_count; //KEY2键在1ms内达到低电平的次数
uint Key3_count; //KEY3键在1ms内达到低电平的次数
uint Key_count; //由100递减到0
bit flg_1ms; //表示1ms时间到
bit Key1_C;/*key1当前的状态*/
bit Key1_P;/*key1前一个状态*/
bit Key2_C;/*key2当前的状态*/
bit Key2_P;/*key2前一个状态*/
bit Key3_C;/*key3当前的状态*/
bit Key3_P;/*key3前一个状态*/
void SMG1(uchar date1,uchar date2,uchar date3) //数码管显示函数
{
flag1++;
if(flag1==8)
{
flag1=0;
}
P0=0x00;
P2=wei[flag1];
switch(flag1)
{
case 0:P0=duan[date1/16];break;
case 1:P0=duan[date1%16];break;
case 2:P0=0x40;break;
case 3:P0=duan[date2/16];break;
case 4:P0=duan[date2%16];break;
case 5:P0=0x40;break;
case 6:P0=duan[date3/16];break;
default:P0=duan[date3%16];break;
}
}
void KEY_init() //按键消抖模块初始化
{
G_count=0;
flg_1ms=0;
Key1_C=1; /*key1当前的状态*/
Key1_P=1; /*key1前一个状态*/
Key2_C=1; /*key2当前的状态*/
Key2_P=1; /*key2前一个状态*/
Key3_C=1; /*key3当前的状态*/
Key3_P=1; /*key3前一个状态*/
Key1_count=0x80+NMAX_KEY/3*2;
Key2_count=0x80+NMAX_KEY/3*2;
Key3_count=0x80+NMAX_KEY/3*2;
Key_count=NMAX_KEY;
}
void delay() //延时4us
{
;;
}
void IIC_init() //I2C总线初始化
{
SCL=1;
delay(); //让SCL稳定
SDA=1; //为开始信号做准备, 因为开始信号是SDA高到低
delay();
}
void start() //主机启动信号
{
SDA=1;
delay(); //让电平稳定
SCL=1;
delay();
SDA=0; //重点是在start函数内造成的SDA高到低! 之前的SDA是高是低都无关,因为这里是开始信号,这里声明开始传输
delay();
}
void stop() //停止信号
{
SDA=0;
delay();
SCL=1; //先让SDA稳定,再把SCL置1
delay();
SDA=1;
delay();
}
void respons() //从机应答信号
{
uchar i=0;
SCL=1;
delay();
while(SDA==1&&(i<255)) //表示若在一段时间内没有收到从器件的应答则
i++; //主器件默认从期间已经收到数据而不再等待应答信号。 这里没有考虑从机未接收到字节的情况
SCL=0;
delay();
}
void writebyte(uchar date) //对24C16写一个字节数据
{
uchar i,temp;
temp=date;
for(i=0;i<8;i++)
{
temp=temp<<1;
SCL=0;
delay();
SDA=CY; //进位标志,即左移出来的位
delay();
SCL=1;
delay();
}
SCL=0;
delay();
SDA=1;
delay();
}
uchar readbyte() //从24C16读一个字节数据
{
uchar i,k;
SCL=0;
delay();
SDA=1;
delay();
for(i=0;i<8;i++)
{
SCL=1; //接受过程中SCL=1, 不然SCL=0时IC不工作
delay();
k=(k<<1)|SDA; //SDA就1位 这里直接接收IC从SDA线传过来的数据
delay();
SCL=0;
delay();
}
delay();
return k;
}
void write_add(uchar addr,uchar date) //对24C16的地址addr,写入一个数据date
{
start(); //发生开始信号
writebyte(0xa0); // 10100000 最后一位=0,表示本次是写操作 A3A2A1=0
respons(); //接收ACK
writebyte(addr);
respons();
writebyte(date);
respons();
stop();
}
uchar read_add(uchar addr) //从24C16的addr地址,读一个字节数据
{
uchar date; //读字节为了和写字节工作模式统一,所以都要传输3个字节过去,之后SDA才会把数据传输给CPU
start();
writebyte(0xa0); //读和写的第一个DEV SEL最低位都是0,表示把地址写进去
respons();
writebyte(addr);
respons();
start(); //需要再一个start信号,表示要输入DEV SEL
writebyte(0xa1);
respons();
date=readbyte();
stop();
return date;
}
void IO_init() //IO口初始化,变量初始化
{
P2M1=0x00; //设置P0口和P2^3推挽输出
P2M0=0x08;
P0M1=0x00;
P0M0=0xff; //按键无需推挽
led=0; //关LED显示,开启数码管
P0=0x00; //什么都不显示
write_addr=0x00; //写入地址初始化
write_date=0x00; //写入数据初始化
}
void Timer0_Init() //计时器0初始化
{
TMOD=0x00; //计时器0工作方式0,16位自动重装计数
AUXR=0x80; //1T模式,T0x12=1,
EA=1; //开总中断
ET0=1; //开定时器0中断
TH0=(65536-T_ms*FOSC/1000)/256;//给定时器赋初值
TL0=(65536-T_ms*FOSC/1000);
TR0=1; //启动定时器
}
void Timer_T0() interrupt 1 //定时器0中断函数
{
G_count++;
if(G_count==10)
{
flg_1ms=1; //1ms时间到,fla_1ms=1 此方法也可用于数码管单位闪烁
G_count=0x00;
}
SMG1(write_addr,write_date,read_date); //在定时器中短中调用数码管显示函数
}
int main() //主函数
{
IO_init(); //IO口初始化
Timer0_Init(); //定时器0初始化
KEY_init(); //按键消抖模块初始化
IIC_init(); //IIC总线初始化
while(1)
{
while(flg_1ms) //1ms时间到 判断按键状态 计数消抖法
{
flg_1ms=0;
read_date=read_add(write_addr); //读出地址为write_addr的数据
if(Key1==0)
Key1_count--;
if(Key2==0)
Key2_count--;
if(Key3==0) //按键是按下状态
Key3_count--;
Key_count--; //总的次数直接减1,10ms减完 就是用作计数,每1ms进行一次扫描
if(Key_count==0)//10次完了 10ms
{
if(Key1_count<0x80) //即“按下”次数超出抖动最大范围,那么说明确实按下了
{
Key1_C=0;
if(Key1_P==1) //下降沿(按键做动作) 排除一直按下没松开的情况
{
Key1_P=0;
write_add(write_addr,write_date);//向地址write_addr写入数据
}
}
if(Key1_count>=0x80)
{
Key1_C=1;
if(Key1_P==0) //因为每10ms就会来判断一下,所以在按键松开后,会在这里把上一个key状态置1
Key1_P=1; //上升沿(假设不做动作那就继续)
}
if(Key2_count<0x80)
{
Key2_C=0;
if(Key2_P==1) //下降沿(按键做动作)
{
Key2_P=0;
write_date++; //按键数据加1
if(write_date==0xff) //假如输入的数据大于0xff,则变为0x00。
write_date=0x00;
}
}
if(Key2_count>=0x80)
{
Key2_C=1;
if(Key2_P==0)
Key2_P=1; //上升沿(假设不做动作那就继续)
}
if(Key3_count<0x80)
{
Key3_C=0;
if(Key3_P==1) //下降沿(按键做动作)
{
Key3_P=0;
write_addr++; //按键写入地址+1
if(write_addr==0xff)
write_addr=0;
}
}
if(Key3_count>=0x80)
{
Key3_C=1;
if(Key3_P==0)
Key3_P=1; //上升沿(假设不做动作那就继续)
}
Key1_count=0x80+NMAX_KEY/3*2; //新一轮的判断 key_cnt=0时才重置次数
Key2_count=0x80+NMAX_KEY/3*2;
Key3_count=0x80+NMAX_KEY/3*2;
Key_count=NMAX_KEY;
}
}//read_date=read_add(read_addr); 读数据函数放在此处数码管会无法及时显示
}
}
用了计数消抖法,代码要多看一会才理解。
注意点
- 若想数码管显示二位以上的数,要把整个数拆成各个位,用/和%处理。
-
另外,把IO口寄存器赋值后,只要不改变,那么IO口就一直是那个值!! 也就是说,就算正在执行delay函数,数码管依然显示进入delay之前的数值,不会熄灭!!
-
动态扫描就是每一位持续亮一段时间,再换成下一位,一轮之后再亮这一位,那么就是一位亮一段时间再停一段时间,再亮一段时间,让人觉得是一直亮的。 一般来讲,数码管显示都要通过delay来维持一段时间,保证亮度
-
重复性工作要写函数!
- 初始化时,要把所有变量都初始化,避免错误