<div style="text-align: left;">/*******************************************************************************
复制代码* 文件名: 数码管显示上电计数值
* 描 述: 上电计数
* 功 能:数码管的使用
* 作 者:大核桃 597627977
* 版本号:1.0.1(2018.09.21)
*******************************************************************************/
#include "stc15w.h"//头文件
#include "intrins.h"
/*******************************************************************************
* 文件名: 重定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2018.09.21)
*******************************************************************************/
typedef unsigned char uint8;
typedef unsigned int uint16;
typedef unsigned long uint32;
/*******************************************************************************
* 文件名:共阳数码管真值表
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2018.09.21)
*******************************************************************************/
code uint8 LedChar[] = {
0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,
0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e
};
uint16 counter; //记忆上电次数,最大65535
/*******************************************************************************
* 文件名:单独位定义
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2018.09.21)
*******************************************************************************/
sbit LED0 = P1^0;//第1组LED
sbit LED1 = P1^1;//第2组LED
sbit LED2 = P1^2;//第3组LED
sbit LED3 = P1^3;//第4组LED
sbit LED4 = P1^4;//第5组LED
sbit LED5 = P3^2;//第6组LED
sbit LED6 = P0^0;//第7组LED
sbit LED7 = P0^1;//第8组LED
sbit LEDS1 = P3^3;//数码管1
sbit LEDS2 = P3^4;//数码管2
sbit LEDS3 = P3^6;//数码管3
sbit LEDS4 = P3^7;//数码管4
/*******************************************************************************
* 文件名:全局变量定义区域
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
#define CMD_IDLE 0 //空闲模式
#define CMD_READ 1 //IAP字节读命令
#define CMD_PROGRAM 2 //IAP字节编程命令
#define CMD_ERASE 3 //IAP扇区擦除命令
#define ENABLE_IAP 0x82 //if SYSCLK<20MHz
/*******************************************************************************
* 文件名:函数前置声明
* 描 述:
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Mcu_Port_Init();
void LedScan();
void Delay500ms(); //24MHZ
void Time0_Init();//定时器0
void IapIdle();
uint8 IapReadByte(uint16 addr);
void IapProgramByte(uint16 addr, uint8 dat);
void IapEraseSector(uint16 addr);
#define Delay() {_nop_();_nop_();_nop_();_nop_();}
/*******************************************************************************
* 文件名
* 描 述: 主函数
* 功 能:入口
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void main(void)
{
counter = IapReadByte(0x0000);//读取数据
counter++;//写
Mcu_Port_Init();//IO上电初始化
Time0_Init();
IapEraseSector(0x0000);//擦除数据
IapProgramByte(0x0000, counter);//写入数据
while(1);
}
/*******************************************************************************
* 文件名:void LedScan()
* 描 述: LED刷新
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void LedScan()
{
static uint8 i = 0;
P2 = 0Xff;
switch(i)
{
case 0: LEDS4 = 0;LEDS1 = 1;P2 = LedChar[counter / 1000 % 10];i++;break;
case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[counter / 100 % 10];i++;break;
case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[counter / 10 % 10];i++;break;
case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[counter % 10];i = 0;break;
default:break;
}
}
/*******************************************************************************
* 文件名:void Time0_Init()
* 描 述: 定时器0初始化
* 功 能:10毫秒@11.0592MHz
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Time0_Init(void)
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TMOD |= 0X01; //确保不干扰其他配置
TH0 = 0xDC; //设置定时初值
TL0 = 0x00; //设置定时初值
ET0 = 1;
TR0 = 1; //定时器0开始计时
EA = 1;
}
/*******************************************************************************
* 文件名:
* 描 述: 中断函数
* 功 能:10毫秒@11.0592MHz
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void ET0_IRQHandler() interrupt 1
{
TH0 = 0xDC; //设置定时初值
TL0 = 0x00; //设置定时初值
LedScan();
}
/*******************************************************************************
* 文件名:void Mcu_Port_Init()
* 描 述: io初始化
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Mcu_Port_Init()
{
//将P0口低二位配置为推挽输出
//234567位配置位高阻输入
P0M1 = 0xFC;//1111 1100
P0M0 = 0X03;//0000 0011
//P0 = 0X01;//第6个
//P0 = 0X02;//第7个
//高3位配置高阻输入,用作模拟口
//其他配置推挽输出,驱动LED
P1M1 = 0xE0;//1110 0000
P1M0 = 0X1F;//0001 1111
//P2口配置准双向口
P2M1 = 0X00;
P2M0 = 0X00;
P2 = 0Xff; //上电为1111 1111
// //P54,P55口为推挽输出
P5M1 = 0X00;
P5M0 = 0X00;
P5 = 0xFF;
//P37,P36,3.2,P3.3 P3.4口为推挽输出
P3M1 = 0X00;
P3M0 = 0XFC;
P3 = 0X23; //0010 0111//第5个LED端口
LED0 = 0;//第1组LED,如果使能请置为1
LED1 = 0;
LED2 = 0;
LED3 = 0;
LED4 = 0;
LED5 = 0;
LED6 = 0;
LED7 = 0;
}
/*******************************************************************************
* 文件名:void Delay500ms() //@24.000MHz
* 描 述:Y5内核延时
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void Delay500ms() //@24.000MHz
{
unsigned char i, j, k;
_nop_();
_nop_();
i = 46;
j = 153;
k = 245;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}
/*******************************************************************************
* 文件名:void IapIdle()
* 描 述:关闭IAP
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void IapIdle()
{
IAP_CONTR = 0; //关闭IAP功能
IAP_CMD = 0; //清除命令寄存器
IAP_TRIG = 0; //清除触发寄存器
IAP_ADDRH = 0x80; //将地址设置到非IAP区域
IAP_ADDRL = 0;
}
/*******************************************************************************
* 文件名:uint8 IapReadByte(uint16 addr)
* 描 述:从ISP/IAP/EEPROM区域读取一字节
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
uint8 IapReadByte(uint16 addr)
{
uint8 dat; //数据缓冲区
IAP_CONTR = ENABLE_IAP; //使能IAP
IAP_CMD = CMD_READ; //设置IAP命令
IAP_ADDRL = addr; //设置IAP低地址
IAP_ADDRH = addr >> 8; //设置IAP高地址
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)
_nop_(); //等待ISP/IAP/EEPROM操作完成
dat = IAP_DATA; //读ISP/IAP/EEPROM数据
IapIdle(); //关闭IAP功能
return dat; //返回
}
/*******************************************************************************
* 文件名:void IapProgramByte(uint16 addr, uint8 dat)
* 描 述: 写一字节数据到ISP/IAP/EEPROM区域
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void IapProgramByte(uint16 addr, uint8 dat)
{
IAP_CONTR = ENABLE_IAP; //使能IAP
IAP_CMD = CMD_PROGRAM; //设置IAP命令
IAP_ADDRL = addr; //设置IAP低地址
IAP_ADDRH = addr >> 8; //设置IAP高地址
IAP_DATA = dat; //写ISP/IAP/EEPROM数据
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)
_nop_(); //等待ISP/IAP/EEPROM操作完成
IapIdle();
}
/*******************************************************************************
* 文件名:void IapEraseSector(uint16 addr)
* 描 述: 扇区擦除
* 功 能:
* 作 者:大核桃
* 版本号:1.0.1(2017.05.23)
*******************************************************************************/
void IapEraseSector(uint16 addr)
{
IAP_CONTR = ENABLE_IAP; //使能IAP
IAP_CMD = CMD_ERASE; //设置IAP命令
IAP_ADDRL = addr; //设置IAP低地址
IAP_ADDRH = addr >> 8; //设置IAP高地址
IAP_TRIG = 0x5a; //写触发命令(0x5a)
IAP_TRIG = 0xa5; //写触发命令(0xa5)
_nop_(); //等待ISP/IAP/EEPROM操作完成
IapIdle();
}
程序上电后的执行效果图片如下:可以看到程序记录上电12次,稍后我们详细的解析下这个程序。
关于数码管的一些问题
一个8段的数码管其实就是8个小灯啊,我们知道LED是有方向的,只有加正向偏置电压才会点亮,正极的一端是阳极,负极的一端是阴极,如果我们把所有的阳极连到一个公共点,通过给其阴极一个低电位的方法能够点亮的,叫做共阳极数码管,那么共阴极数码管就是倒过来了,高电平点亮,所有的阴极连在一起,限流电阻是友情提供的,实际是没有的,如下图所示:
有人可能觉得,那这8个小灯是如何排列的啊?怎么看呢?客官,您别急,我来画一下,您就明白了。如下图所示,共阳极数码管示意图:
有了这张图,我们来看一下程序,就好办了,想一想,如果我要在数码管上显示一个数字0怎么弄呢?如果是共阳极数码管。我应该让ABCDEF都是0才可以,也即是说,点亮该段即可实现,那么结合我们前面所讲解的数字电路知识,最高位我们不管,默认1即可 就是说要显示一个0,那么八段从低到高依次是,a = 0,b = 0,c = 0,d = 0,e = 0,f = 0,g = 1,dot = 1;也就是二进制的1100_0000,16进制是0XC0,如果我们想要0-9这10个数字,那么是不是可以用同样的方式,算出来,好了,真值表就是这么来的,至于共阳极,取反一下就是了。我们新建一个无符号字符型数组,将我们算好的数据放进数组里面。
/******************************************************************************** 文件名:共阳数码管真值表 * 描 述: * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2018.09.21) *******************************************************************************/ code uint8 LedChar[] = { 0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8, 0x80,0x90,0x88,0x83,0xc6,0xa1,0x86,0x8e };
复制代码前面为什么要加一个CODE关键字呢?51单片机有好多关键字,默认都是蓝色标识,注意,这个表示这个关键字在单片机系统中已经有名字了,不能随便命名,CODE关键字的意思是将该部分代码放在FLASH里面,而不是放在RAM里面,节省了程序运行空间,放在FLASH里面的变量是不能在程序运行时改变的。
关于数码管的扫描刷新
我们了解一个常识,就是人的眼睛是不能够分辨刷新速度小于10MS的物体的,就算变化了,你也看不出来的,最好的例子,就是,拿手机拍电视录像,一条条的,就是因为手机拍摄的速度太快,而电视画面刷新的太慢造成的,而这样的现象,我们是看不见的。
用数码管来显示数字,基本上都是动态扫描刷新,所谓动态扫描,也就是先在1数码管赋值,然后切换到2数码管,切换到3,来回切换,我们只要把刷新速度控制在10MS之内,那么人的眼睛也看不出来的,我们这个代码就是这样进行处理的,如下所示;
我们用到了SWITCH语句,SWITCH是一条多选一语句,以CASE为分支,break语句作为结束。我们来看下开发的原理图,4个数码管分别是NLED0,NLED1,NLED2,NLED3,这个段码和位码是如何选择的呢?用万用表的二极管档位,我们知道二极管是单向导电的,我们又知道正向偏置是可以点亮小灯的,不断的变换万用表的表笔,将亮的段位和引脚记下来,按照提供的数码管引脚图就可以分出段码和位码来。
如果我们要显示一个1,打开对应的IO,那么我们只要对P2赋值P2 = LedChar[1]就好了;可是在实际应用中,我们需要显示的更加复杂,因此,只能这样动态进行赋值了,新建一个counter变量,然后将最低位的数码管显示个位,第二个数码管显示10位,第三个数码管显示百位,第四个数码管显示千位,依次这样,相除取余数即可实现。
关于内部EEPROM
这个代码,是从STC的客户端上复制下来的,稍微整理了一下,不需要深入学习,你只要知道有多少个扇区,每个扇区的起始地址,就可以了,必要时候,回来翻阅数据手册就可以搞定,使用的时候,一定要注意,同一扇区的数据会全部被擦除掉,如果不想全部擦除,一定要写到不同的扇区,我们实现的功能是,先上电读取一次0X0000地址的数据,然后我们counter++,然后我们擦除0X0000地址的数据,在重新向0X0000地址写入一个新的数据就OK,注意,写入之前先擦除,不然写不进去的
我们了解一个常识,就是人的眼睛是不能够分辨刷新速度小于10MS的物体的,就算变化了,你也看不出来的,最好的例子,就是,拿手机拍电视录像,一条条的,就是因为手机拍摄的速度太快,而电视画面刷新的太慢造成的,而这样的现象,我们是看不见的。
用数码管来显示数字,基本上都是动态扫描刷新,所谓动态扫描,也就是先在1数码管赋值,然后切换到2数码管,切换到3,来回切换,我们只要把刷新速度控制在10MS之内,那么人的眼睛也看不出来的,我们这个代码就是这样进行处理的,如下所示;
/******************************************************************************** 文件名:void LedScan() * 描 述: LED刷新 * 功 能: * 作 者:大核桃 * 版本号:1.0.1(2017.05.23) *******************************************************************************/ void LedScan() { static uint8 i = 0; P2 = 0Xff; switch(i) { case 0: LEDS4 = 0;LEDS1 = 1;P2 = LedChar[counter / 1000 % 10];i++;break; case 1: LEDS1 = 0;LEDS2 = 1;P2 = LedChar[counter / 100 % 10];i++;break; case 2: LEDS2 = 0;LEDS3 = 1;P2 = LedChar[counter / 10 % 10];i++;break; case 3: LEDS3 = 0;LEDS4 = 1;P2 = LedChar[counter % 10];i = 0;break; default:break; } }
复制代码我们用到了SWITCH语句,SWITCH是一条多选一语句,以CASE为分支,break语句作为结束。我们来看下开发的原理图,4个数码管分别是NLED0,NLED1,NLED2,NLED3,这个段码和位码是如何选择的呢?用万用表的二极管档位,我们知道二极管是单向导电的,我们又知道正向偏置是可以点亮小灯的,不断的变换万用表的表笔,将亮的段位和引脚记下来,按照提供的数码管引脚图就可以分出段码和位码来。
如果我们要显示一个1,打开对应的IO,那么我们只要对P2赋值P2 = LedChar[1]就好了;可是在实际应用中,我们需要显示的更加复杂,因此,只能这样动态进行赋值了,新建一个counter变量,然后将最低位的数码管显示个位,第二个数码管显示10位,第三个数码管显示百位,第四个数码管显示千位,依次这样,相除取余数即可实现。
关于内部EEPROM
这个代码,是从STC的客户端上复制下来的,稍微整理了一下,不需要深入学习,你只要知道有多少个扇区,每个扇区的起始地址,就可以了,必要时候,回来翻阅数据手册就可以搞定,使用的时候,一定要注意,同一扇区的数据会全部被擦除掉,如果不想全部擦除,一定要写到不同的扇区,我们实现的功能是,先上电读取一次0X0000地址的数据,然后我们counter++,然后我们擦除0X0000地址的数据,在重新向0X0000地址写入一个新的数据就OK,注意,写入之前先擦除,不然写不进去的