51单片机学习

目录

单片机三大资源:

点亮一个小灯:

流水灯:

电路硬件知识:

三极管:

74HC245:

定时器和计数器

定时器存储寄存器:

定时器控制寄存器:TCON:

定时器模式寄存器:TMOD:

 使用定时器的方法:

蜂鸣器频率控制: 

数码管静态显示:

数码管动态显示:

中断:

中断使能寄存器:IE:

中断优先级寄存器:IP:

点阵LED:

按键与数码管:

永磁式步进电机:

实例

数字秒表

PWM实例

51单片机RAM划分

长短按键的应用

UART串口通信:

1602液晶

I2C与EEPROM

DS1302时钟

红外通信

DS18B20温度传感器

AD与DA

RS485通信与Modbus协议

多功能电子钟


单片机三大资源:

        FLASH <程序存储空间 ROM>

        RAM <内存>

        SFR <特殊功能寄存器>

STC89C52RC:8K FLASH、512字节RAM、32个IO口、3个定时器、1个UART、8个中断源。

单片机最小系统:电源电路、复位电路、晶振电路

点亮一个小灯:

#include<reg52.h>

sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

void main()
{
    /*使能74H138*/
    ENLED = 0;
    ADDR3 = 1;    
    /*编码值为110,对应原理图的LEDS6*/
    ADDR2 = 1;    
    ADDR1 = 1;
    ADDR0 = 0;

    LED = 0;    //控制小灯点亮,DB_0置为零,即DB0为0,LED2点亮
    while(1);
}

流水灯:

方式一:

while(1)
{
    P0 = 0xFE;
    for(i=0; i<30000; i++);
    P0 = 0xFD;
    for(i=0; i<30000; i++);
    P0 = 0xFB;
    for(i=0; i<30000; i++);
    P0 = 0xF7;
    for(i=0; i<30000; i++);
    P0 = 0xEF;
    for(i=0; i<30000; i++);
    P0 = 0xDF;
    for(i=0; i<30000; i++);
    P0 = 0xBF;
    for(i=0; i<30000; i++);
    P0 = 0x7F;
    for(i=0; i<30000; i++);  
}

方式二:

unsigned char cnt = 0;
while(1)
{
    P0 = ~(0x01 << cnt);
    for(i=0; i<30000; i++)
    cnt++;
    if(cnt >= 8)
    {
        cnt=0;
    }
}

电路硬件知识:

电池干扰:静电放电(ESD)、快速瞬间脉冲群(EFT)、浪涌(Surge)

去耦电容:铝电解电容、钽电容、陶瓷电容

低频滤波电容:耐压值、容值:对电压电流起稳定作用。

高频滤波:104电容:10*10^4pf :0.1uf:放在正负极之间。

三极管:

PNP:9012,NPN:9013:b 基级,e 发射极,c 集电极

三种状态:1、截至:e和b不导通

                  2、饱和:e和b导通 Ib>Iec/β        β为放大倍数

                  3、放大:e和b导通 Ib=Iec/β

                  导通电压:0.7v

控制端:集电极 c和基级 b之间

箭头朝内PNP,导通电压顺着箭头过,电压导通,电流控制

74HC245

        电流缓冲

定时器和计数器

时钟周期:时钟源分之一,单片机时序中的最小单位。

机器周期:单片机完成一个操作的最短时间。

定时器:机器周期是定时器的计数周期:存储寄存器的值经过一个机器周期自动加一。

定时器存储寄存器:

                               TH0 (0x8C),TL0 (0x8A); TH1 (0x8D),TL1 (0x8B)

                                TL0从0x00开始计数,计数到0xFF (255),清零,TH0进位。

定时器控制寄存器:TCON:

                                TF1 (7), TR1 (6),

                                TF0 (5), TR0 (4), TF0表示溢出标志位,TR0为运行控制位

                                IE1 (3), IT1 (2),

                                IE0 (1),  IT0 (0)

定时器模式寄存器:TMOD:

                                GATE(T1) (7),

                                C/T(T1) (6), counter or timer

                                M1(T1) (5), M0(T1) (4), 

                                GATE(T0) (3),

                                C/T(T0) (2),

                                M1(T0) (1),  M0(T0) (0),

                                        00 (0): TH0的8位和TL0的5位组成一个13位寄存器

                                        01 (1): TH0与TL0组成一个16位寄存器

                                        10 (2): 8位自动重装模式,定时器溢出之后TH0重装到TL0中

                                        11 (3): 禁用定时器1,定时器0变成2个8位定时器。

 使用定时器的方法:

        TMOD -> TH0,TL0 ->TCON -> 判断TF0

#include <reg52.h>

sbit LED = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

void main()
{
    unsigned char cnt = 0;

    ENLED = 0;
    ADDR3 = 1;
    ADDR2 = 1;
    ADDR1 = 1;
    ADDR0 = 0;
    
    TMOD = 0x01;    //计时模式1
    /* 寄存器从0xB800计数到0xFFFF,然后清零,经过了(0xFFFF-0xB800)个
机器周期,定时了[(0xFFFF-0xB800)*12]/11059200时间,即20ms,就是0.02s*/
    TH0 = 0xB8;
    TL0 = 0x00;   
    TR0 = 1;    //打开定时器    

    while(1)
    {
        if(TF0 == 1)
        {
            TF0 = 0;    //软件溢出置零
            /*定时器重载*/
            TH0 = 0xB8;
            TL0 = 0x00;
            cnt++;
            if(cnt>=50)    //定时0.02*50 s
            {
                cnt = 0;
                LED = ~LED;
            }
        }
    }
}

蜂鸣器频率控制: 

/* 蜂鸣器启动函数,frequ-工作频率 */
void OpenBuzz(unsigned int frequ)
{
 unsigned int reload; //计算所需的定时器重载值
 
 reload = 65536 - (11059200/12)/(frequ*2); //由给定频率计算定时器重载值
 T0RH = (unsigned char)(reload >> 8); //16 位重载值分解为高低两个字节
 T0RL = (unsigned char)reload;
 TH0 = 0xFF; //设定一个接近溢出的初值,以使定时器马上投入工作
 TL0 = 0xFE;
 ET0 = 1; //使能 T0 中断
 TR0 = 1; //启动 T0
}

数码管静态显示:

#include<reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[]={
    0xc0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86,0x8E
};    //数组存储数码管的真值表

void main()
{
    unsigned char cnt = 0;
    unsigned char sec = 0;

    ENLED = 0;
    ADDR3 = 1;
    ADDR2 = 0;
    ADDR1 = 0;
    ADDR0 = 0;

    TMOD = 0x01;
    THO = 0x8B;
    TL0 = 0x00;
    TR0 = 1;

    while(1)
    {
        if(TF0 == 1)
        {
            TF0 = 0;
            TH0 = 0xB8;
            TL0 = 0x00;
            cnt++;
            if(cnt >= 50)
            {
                cnt = 0;
                P0 = LedChar[sec];    //数码管每秒加一
                sec++;
                if(sec >= 16)    //显示到头清零
                {
                    sec=0;
                }
            }
        }
    }
}

数码管动态显示:

        1ms,100hz 为视觉暂停效应。

中断:

        定时器TF0自动清零。

中断使能寄存器:IE:

                                   EA (7), 中断总使能

                                    -- (6),

                                   ET2 (5), 定时器2中断使能,中断编号 5

                                   ES (4), 串口中断使能,中断编号 4

                                   ET1 (3), 定时器1中断使能,中断编号 3

                                   EX1 (2), 外部中断1使能,中断编号 2

                                   ET0 (1), 定时器0中断使能,中断编号 1

                                   EX0 (0), 外部中断0使能,中断函数编号 0

中断优先级寄存器:IP:

                                        -- (7), -- (6),

                                        PT2 (5), 定时器2中断优先级控制位

                                        PS (4), 串口中断控制

                                        PT1 (3), 定时器1中断优先控制位

                                        PX1 (2), 外部中断1中断优先控制位

                                        PT0 (1), 定时器0中断优先控制位

                                        PX0 (0), 外部中断0优先级控制位

 不采用中断,数码管有抖动:

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4

unsigned char code LedChar[]={
	0xc0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
	0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
};
unsigned char LedBuff[6]={
	0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
void main()
{
	unsigned char cnt=0;
	unsigned long sec=0;
	unsigned char i=0;
	
	ENLED = 0;
	ADDR3 = 1;
	TMOD = 0x01;
	TH0 = 0xFC;
	TL0 = 0x67;
	TR0 = 1;
	
	while(1)
	{
		if(TF0 == 1)
		{
			TF0 = 0;
			TH0 = 0xFC;
			TL0 = 0x67;		//(0xFFFF-0xFC67)*12/11059200
			cnt++;
			if(cnt >= 1000)
			{
				cnt = 0;
				sec++;
				/*对sec进行拆分*/
				LedBuff[0] = LedChar[sec%10];
				LedBuff[1] = LedChar[sec/10%10];
				LedBuff[2] = LedChar[sec/100%10];
				LedBuff[3] = LedChar[Sec/1000%10];
				LedBuff[4] = LedChar[Sec/10000%10];
				LedBuff[5] = LedChar[Sec/100000%10];
			}
			/*将拆分后的sec分别显示到每个数码管上*/
			P0=0xFF;	//关闭数码管后再显示,防止鬼影,消影
			switch(i)
			{
				case 0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break;
				case 1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break;
				case 2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break;
				case 3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break;
				case 4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break;
				case 5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break;
				default:break;
			}
		}
	}
}

 采用中断,数码管稳定:

#include <reg52.h>

sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned char code LedChar[]={
	0xC0,0xF9,0xA4,0xB0,0x99,0x92,0x82,0xF8,
	0x80,0x90,0x88,0x83,0xC6,0xA1,0x86,0x8E
};
unsigned char LedBuff[6]={
	0xFF,0xFF,0xFF,0xFF,0xFF,0xFF
};
unsigned char flags = 0; //1 秒定时标志

void main()
{

	unsigned long sec=0;
	
	
	ENLED = 0;
	ADDR3 = 1;
	TMOD = 0x01;
//	TH0 = 0xFC;		//定时1ms
//	TL0 = 0x67;
	TR0 = 1;
	ET0 = 1;
	EA = 1;
	while(1)
	{
		if(flags == 1)	//定时1s
		{
			flags = 0;
			sec++;
			/*对sec进行拆分*/
			LedBuff[0] = LedChar[sec%10];
			LedBuff[1] = LedChar[sec/10%10];
			LedBuff[2] = LedChar[sec/100%10];
			LedBuff[3] = LedChar[sec/1000%10];
			LedBuff[4] = LedChar[sec/10000%10];
			LedBuff[5] = LedChar[sec/100000%10];
		}
	}
}
void InterruptTime0() interrupt 1
{
    static unsigned int cnt = 0;	//若cnt为char类型,那么最大值为255,
                                //就不可能计数到1000
    static unsigned char i = 0;    //变量定义为局部变量,在局部使用,定义为静态,可以连续使用变量
	TH0 = 0xFC;
	TL0 = 0x67;
	cnt++;
	if(cnt >= 1000)		//定时1s
	{
		cnt = 0;
		flags = 1;
	}
	/*将拆分后的sec分别显示到每个数码管上*/
		P0=0xFF;	//关闭数码管后再显示,防止鬼影,消影
		switch(i)
		{
			case 0:ADDR2=0;ADDR1=0;ADDR0=0;i++;P0=LedBuff[0];break;
			case 1:ADDR2=0;ADDR1=0;ADDR0=1;i++;P0=LedBuff[1];break;
			case 2:ADDR2=0;ADDR1=1;ADDR0=0;i++;P0=LedBuff[2];break;
			case 3:ADDR2=0;ADDR1=1;ADDR0=1;i++;P0=LedBuff[3];break;
			case 4:ADDR2=1;ADDR1=0;ADDR0=0;i++;P0=LedBuff[4];break;
			case 5:ADDR2=1;ADDR1=0;ADDR0=1;i=0;P0=LedBuff[5];break;
			default:break;
		}
}

点阵LED:

        保存点阵的数组如果太大的话应当放在ROM中,加code修饰。

按键与数码管:

资源清单:

#include <reg52.h>
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1 = P2^4;
sbit KEY_IN_2 = P2^5;
sbit KEY_IN_3 = P2^6;
sbit KEY_IN_4  = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;

unsigned char code LedChar[]={		//数码管编码
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6]={		//显示数码管数值
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char code KeyCodeMap[4][4]={		//矩阵键盘编码
    { 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键
    { 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键
    { 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键
    { 0x30, 0x1B, 0x0D, 0x27 }  //数字键0、ESC键、  回车键、 向右键
};
unsigned char KeySta[4][4]={	//矩阵键盘当前数值	
    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
};

void KeyDriver();

void main()
{
	EA = 1;		//中断总开关
	/*选择数码管*/
	ENLED = 0;	
	ADDR3 = 1;
	TMOD = 0x01;	//定时模式1
	/*定时1ms*/
	TH0 = 0xFC;
	TL0 = 0x67;
	ET0 = 1;	//打开T0中断
	TR0 = 1;	//启动T0
	LedBuff[0] = LedChar[0];	//上电显示0
	
	while(1)
	{
		KeyDriver();
	}
}
void ShowNumber(unsigned long number)
{
	signed char i;
	unsigned char buf[6];	//存储每一位要显示的数字
	
	/*分离整数到每一位*/
	for(i=0;i<6;i++)
	{
		buf[i]=number%10;
		number=number/10;
	}
	/*高位为0不显示*/
	for(i=5;i>=1;i--)
	{
		if(buf[i] == 0)
			LedBuff[i]=0xFF;
		else
			break;
	}
	/*显示不为0低位*/
	for(;i>=0;i--)
	{
		LedBuff[i]=LedChar[buf[i]];
	}
}
void KeyAction(unsigned char keycode)
{
	static unsigned long result = 0;	//运算结果
	static unsigned long addend = 0;	//用于保存输入的加数
	
	if((keycode>=0x30)&&(keycode<=0x39))	//输入0-9的数字
	{
		addend = (addend*10)+(keycode-0x30);	//整体十进制左移,新数字进入个位,比如65,先显示5,再显示6
		ShowNumber(addend);		//显示运算结果
	}
	else if(keycode == 0x26)	//向上键作加法,执行加法或连加
	{
		result += addend;
		addend = 0;
		ShowNumber(result);
	}
	else if(keycode==0x0D)		//回车键,执行加法运算(实际效果与加法相同)
	{
		result += addend;
		addend = 0;
		ShowNumber(result);
	}
	else if(keycode==0x1B)	//Esc键,清零结果
	{
		addend = 0;
		result = 0;
		ShowNumber(addend);
	}
}
void KeyDriver()
{
	unsigned char i,j;
	static unsigned char backup[4][4] = {	//前一次的键盘值
	{1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
	};
	/*循环检测KeySta的值是否发生改变,若确定按下,更新数码管的显示*/
	for(i=0;i<4;i++)
	{
		for(j=0;j<4;j++)
		{
			if(backup[i][j] != KeySta[i][j])
			{
				if(backup[i][j] != 0)
				{
					KeyAction(KeyCodeMap[i][j]);
				}
				backup[i][j] = KeySta[i][j];
			}
		}
	}
}
void LedScan()
{
    static unsigned char i = 0;  //动态扫描的索引
    
    P0 = 0xFF;   //显示消隐
    switch (i)
    {
        case 0: ADDR2=0; ADDR1=0; ADDR0=0; i++; P0=LedBuff[0]; break;
        case 1: ADDR2=0; ADDR1=0; ADDR0=1; i++; P0=LedBuff[1]; break;
        case 2: ADDR2=0; ADDR1=1; ADDR0=0; i++; P0=LedBuff[2]; break;
        case 3: ADDR2=0; ADDR1=1; ADDR0=1; i++; P0=LedBuff[3]; break;
        case 4: ADDR2=1; ADDR1=0; ADDR0=0; i++; P0=LedBuff[4]; break;
        case 5: ADDR2=1; ADDR1=0; ADDR0=1; i=0; P0=LedBuff[5]; break;
        default: break;
    }
}
void KeyScan()
{
	unsigned char i;
	static unsigned char keyout = 0;
	static unsigned char keybuf[4][4]={		//记录按键实际状态
	    {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}
	};
	
	keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;	//左移,最左边(最高位)移出,低位补零,若keybuf低位受KEY_IN_1控制
	keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
	keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
	keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
	
	/*循环检测四个按键,如果连续有四个全0,则判断KeySta为0,
		如果有连续四个为1,则判断KeySta为1
	*/
	for(i=0;i<4;i++)
	{
		if((keybuf[keyout][i] & 0x0F) == 0x00)
		{
			KeySta[keyout][i] = 0;
		}
		if((keybuf[keyout][i] & 0x0F) == 0x0F)
		{
			KeySta[keyout][i] = 1;
		}
	}
	
	keyout++;
	keyout = keyout & 0x03;		//4(100)& 0x03(011)为0,即keyout加到4为0
	switch(keyout)	//由单片机内部结构控制,想要捕获输入电平,需要拉低输出引脚
	{
		case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
		case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
		case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
		case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
		default: break;
	}
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void InterruptTimer0() interrupt 1
{
    TH0 = 0xFC;  //重新加载初值
    TL0 = 0x67;
    LedScan();   //调用数码管显示扫描函数
    KeyScan();   //调用按键扫描函数
}

永磁式步进电机:

八拍模式: 转子转动一圈则需要 8*8=64

电机启动:每秒给出 550 个步进脉冲的情况下,可以正常启动,即需要大于1s/550=1.8ms刷新节拍。

启动函数:

/* 步进电机启动函数,angle-需转过的角度 */
void StartMotor(unsigned long angle)
{
 //在计算前关闭中断,完成后再打开,以避免中断打断计算过程而造成错误
 EA = 0;
 beats = (angle * 4076) / 360; //实测为 4076 拍转动一圈
 EA = 1;
}

 IO控制:

unsigned char code BeatCode[8] = { //步进电机节拍对应的 IO 控制代码
 0xE, 0xC, 0xD, 0x9, 0xB, 0x3, 0x7, 0x6
};

节拍控制代码:

 tmp = P1; //用 tmp 把 P1 口当前值暂存
 tmp = tmp & 0xF0; //用&操作清零低 4 位
 tmp = tmp | BeatCode[index]; //用|操作把节拍代码写到低 4 位
 P1 = tmp; //把低 4 位的节拍代码和高 4 位的原值送回 P1
 index++; //节拍输出索引递增
 index = index & 0x07; //用&操作实现到 8 归零
 delay(); //延时 2ms,即 2ms 执行一拍

实例

数字秒表

定时器定时时间调整:原因:中断需要时间,进中断后需要保护之前计算的值:中断压栈。

办法:一:使用debug观察,进行补偿。二:长时间运行累积误差调整。

字节操作修改位:0和1与0进行与运算,结果是0,与1进行与运算,保持原来的值

                                     与0进行或运算,结果保存原值,与1进行或运算,结果是1。

3-8译码器输入设计:

P1=(P1 & 0xF8) | i;        //只修改P1的低3位,即三八译码器的输入位
P0 = LedBuff[i];
if(i<5) 
    i++; 
else 
    i=0;

#include <reg52.h>
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY1 = P2^4;
sbit KEY2 = P2^5;
sbit KEY3 = P2^6;
sbit KEY4  = P2^7;


unsigned char code LedChar[]={		//数码管编码
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[6]={		//显示数码管数值
	0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char KeySta[4]={	//键盘当前数值	
    1, 1, 1, 1
};
bit StopwatchRunning = 0;  //秒表运行标志
bit StopWatchRefresh = 1;  //秒表计数刷新标志
unsigned char DecimalPart = 0;  //秒表的小数部分
unsigned int  IntegerPart = 0;  //秒表的整数部分
unsigned char T0RH = 0;  //T0重载值的高字节
unsigned char T0RL = 0;  //T0重载值的低字节

void ConfigTimer0(unsigned int ms);
void WatchDisplay();
void KeyDriver();

void main()
{
	EA = 1;		//中断总开关
	/*选择数码管*/
	ENLED = 0;	
	ADDR3 = 1;
	P2 = 0xFE;	//P2.0置0,选择第4行按键作为独立按键
	ConfigTimer0(2);
	
	while(1)
	{
		if(StopWatchRefresh)
		{
			StopWatchRefresh = 0;
			WatchDisplay();
		}
		KeyDriver();
	}
}
void ConfigTimer0(unsigned int ms)
{
	unsigned long begin;	//计数初值
	begin = 65536-((11059200/12)*ms)/1000;	
	//定时了[(0xFFFF-tmp)*12]/11059200/1000 = ms 时间
	begin = begin + 18;		//补偿中断响应延时造成的误差
	TMOD &= 0xF0;	//清零控制位
	TMOD |= 0x01;	//配置定时模式1
	T0RH = (unsigned char)(begin>>8);	//定时器高位移至低位后,强制类型转换,只剩第八位
	T0RL = (unsigned char) begin;	//强制类型转换,只剩低位
	TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
	ET0 = 1;	//开中断
	TR0 = 1;	//开定时器
}
void WatchDisplay()
{
	signed char i;
	unsigned char buf[4];
	
	/*显示小数部分*/
	LedBuff[0] = LedChar[DecimalPart%10];
	LedBuff[1] = LedChar[DecimalPart/10];
	
	/*拆解整数部分*/
	buf[0] = IntegerPart%10;
	buf[1] = (IntegerPart/10)%10;
	buf[2] = (IntegerPart/100)%10;
	buf[3] = (IntegerPart/1000)%10;
	
	/*高位为0不显示*/
	for(i=3;i>=1;i--)	
	{
		if(buf[i] == 0)
			LedBuff[i+2] = 0xFF;
		else
			break;
	}
	for(;i>=0;i--)		//点亮整数部分
	{	
		LedBuff[i+2] = LedChar[buf[i]];
	}
	LedBuff[2] &= 0x7F;		//点亮小数点
 }
/* 秒表启停函数 */
void StopwatchAction()
{
    if (StopwatchRunning)    //已启动则停止
        StopwatchRunning = 0;
    else                     //未启动则启动
        StopwatchRunning = 1;
}
/* 秒表复位函数 */
void StopwatchReset()
{
    StopwatchRunning = 0;  //停止秒表
    DecimalPart = 0;       //清零计数值
    IntegerPart = 0;
    StopWatchRefresh = 1;  //置刷新标志
}
/* 秒表计数函数,每隔10ms调用一次进行秒表计数累加 */
void StopwatchCount()
{
    if (StopwatchRunning)  //当处于运行状态时递增计数值
    {
        DecimalPart++;           //小数部分+1
        if (DecimalPart >= 100)  //小数部分计到100时进位到整数部分
        {
            DecimalPart = 0;
            IntegerPart++;       //整数部分+1
            if (IntegerPart >= 10000)  //整数部分计到10000时归零
            {
                IntegerPart = 0;
            }
        }
        StopWatchRefresh = 1;    //设置秒表计数刷新标志
    }
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
    unsigned char i;
    static unsigned char backup[4] = {1,1,1,1};

    for (i=0; i<4; i++)  //循环检测4个按键
    {
        if (backup[i] != KeySta[i])  //检测按键动作
        {
            if (backup[i] != 0)      //按键按下时执行动作
            {
                if (i == 1)          //Esc键复位秒表
                    StopwatchReset();
                else if (i == 2)     //回车键启停秒表
                    StopwatchAction();
            }
            backup[i] = KeySta[i];   //刷新前一次的备份值
        }
    }
}
/* 按键扫描函数,需在定时中断中调用 */
void KeyScan()
{
    unsigned char i;
    static unsigned char keybuf[4] = {  //按键扫描缓冲区
        0xFF, 0xFF, 0xFF, 0xFF
    };
    
    //按键值移入缓冲区
    keybuf[0] = (keybuf[0] << 1) | KEY1;
    keybuf[1] = (keybuf[1] << 1) | KEY2;
    keybuf[2] = (keybuf[2] << 1) | KEY3;
    keybuf[3] = (keybuf[3] << 1) | KEY4;
    //消抖后更新按键状态
    for (i=0; i<4; i++)
    {
        if (keybuf[i] == 0x00)
        {   //连续8次扫描值为0,即16ms内都是按下状态时,可认为按键已稳定的按下
            KeySta[i] = 0;
        }
        else if (keybuf[i] == 0xFF)
        {   //连续8次扫描值为1,即16ms内都是弹起状态时,可认为按键已稳定的弹起
            KeySta[i] = 1;
        }
    }
}
/* 数码管动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
    static unsigned char i = 0;  //动态扫描索引
    
    P0 = 0xFF;             //关闭所有段选位,显示消隐
    P1 = (P1 & 0xF8) | i;  //位选索引值赋值到P1口低3位
    P0 = LedBuff[i];       //缓冲区中索引位置的数据送到P0口
    if (i < 5)             //索引递增循环,遍历整个缓冲区
        i++;
    else
        i = 0;
}
/* T0中断服务函数,用于数码管显示扫描与按键扫描 */
void InterruptTimer0() interrupt 1
{
	static unsigned char tmr10ms = 0;
    TH0 = T0RH;  //重新加载重载值
    TL0 = T0RL;
    LedScan();   //调用数码管显示扫描函数
    KeyScan();   //调用按键扫描函数
	//定时10ms进行一次秒表计数
    tmr10ms++;
    if (tmr10ms >= 5)
    {
        tmr10ms = 0;
        StopwatchCount();  //调用秒表计数函数
    }
}

PWM实例

#include <reg52.h>

sbit PWMOUT = P0^0;
sbit ADDR0 = P1^0;
sbit ADDR1 = P1^1;
sbit ADDR2 = P1^2;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;

unsigned long PeriodCnt = 0;  //PWM周期计数值
unsigned char HighRH = 0;  //高电平重载值的高字节
unsigned char HighRL = 0;  //高电平重载值的低字节
unsigned char LowRH  = 0;  //低电平重载值的高字节
unsigned char LowRL  = 0;  //低电平重载值的低字节
unsigned char T1RH = 0;    //T1重载值的高字节
unsigned char T1RL = 0;    //T1重载值的低字节

void ConfigTimer1(unsigned int ms);
void ConfigPWM(unsigned int fr, unsigned char dc);

void main()
{
    EA = 1;     //开总中断
    ENLED = 0;  //使能独立LED
    ADDR3 = 1;
    ADDR2 = 1;
    ADDR1 = 1;
    ADDR0 = 0;
    
    ConfigPWM(100, 10);  //配置并启动PWM
    ConfigTimer1(50);    //用T1定时调整占空比
    while (1);
}
/* 配置并启动T1,ms-定时时间 */
void ConfigTimer1(unsigned int ms)
{
    unsigned long tmp;  //临时变量
    
    tmp = 11059200 / 12;      //定时器计数频率
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
    tmp = 65536 - tmp;        //计算定时器重载值
    tmp = tmp + 12;           //补偿中断响应延时造成的误差
    T1RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
    T1RL = (unsigned char)tmp;
    TMOD &= 0x0F;   //清零T1的控制位
    TMOD |= 0x10;   //配置T1为模式1
    TH1 = T1RH;     //加载T1重载值
    TL1 = T1RL;
    ET1 = 1;        //使能T1中断
    TR1 = 1;        //启动T1
}
void ConfigPWM(unsigned int fr, unsigned char dc)
{
    unsigned int high,low;

    PeriodCnt = (11059200/12)/fr;    //单片机晶振频率11059200除以一个机器周期,即一个周期的频率,除以pwm波的频率,即一个周期的计数值
    high = PeriodCnt*(dc/100);    //计数周期乘以占空比,等于高电平持续的计数值
    low = PeriodCnt-high;    //低电平计数值等于总计数值减去高电平计数值
    highsta = 65536-high+12;    //高电平起始计数值
    lowsta = 65536-low+12;    //低电平起始计数值
    HighRH = (unsigned char)(highsta>>8);    //高电平高8位
    HighRL = (unsigned char)highsta;    //高电平低8位
    LowRH = (unsigned char)(lowsta>>8);    //低电平高8位
    LowRL = (unsigned char)lowsta;    //低电平低8位
    TMOD &= 0xF0;    //清零控制位
    TMOD |= 0x01;    //配置计数模式1
    TH0 = HighRH;
    TL0 = HighRL;
    ET0 = 1;
    TR0 = 1;
    PWMOUT = 1;    //输出高电平
}
/* T0中断服务函数,产生PWM输出 */
void InterruptTimer0() interrupt 1
{
    if (PWMOUT == 1)  //当前输出为高电平时,装载低电平值并输出低电平
    {
        TH0 = LowRH;
        TL0 = LowRL;
        PWMOUT = 0;
    }
    else              //当前输出为低电平时,装载高电平值并输出高电平
    {
        TH0 = HighRH;
        TL0 = HighRL;
        PWMOUT = 1;
    }
}
/* 占空比调整函数,频率不变只调整占空比 */
void AdjustDutyCycle(unsigned char dc)
{
    unsigned int  high, low;
    
    high = (PeriodCnt*dc) / 100;    //计算高电平所需的计数值
    low  = PeriodCnt - high;        //计算低电平所需的计数值
    high = 65536 - high + 12;       //计算高电平的定时器重载值并补偿中断延时
    low  = 65536 - low  + 12;       //计算低电平的定时器重载值并补偿中断延时
    HighRH = (unsigned char)(high>>8); //高电平重载值拆分为高低字节
    HighRL = (unsigned char)high;
    LowRH  = (unsigned char)(low>>8);  //低电平重载值拆分为高低字节
    LowRL  = (unsigned char)low;
}
/* T1中断服务函数,定时动态调整占空比 */
void InterruptTimer1() interrupt 3
{
    static bit dir = 0;
    static unsigned char index = 0;
    unsigned char code table[13] = {  //占空比调整表
        5, 18, 30, 41, 51, 60, 68, 75, 81, 86, 90, 93, 95
    };

    TH1 = T1RH;  //重新加载T1重载值
    TL1 = T1RL;
    AdjustDutyCycle(table[index]); //调整PWM的占空比
    if (dir == 0)  //逐步增大占空比
    {
        index++;
        if (index >= 12)
        {
            dir = 1;
        }
    }
    else          //逐步减小占空比
    {
        index--;
        if (index == 0)
        {
            dir = 0;
        }
    }
}

51单片机RAM划分

长短按键的应用

#include <reg52.h>

sbit BUZZ  = P1^6;
sbit ADDR3 = P1^3;
sbit ENLED = P1^4;
sbit KEY_IN_1  = P2^4;
sbit KEY_IN_2  = P2^5;
sbit KEY_IN_3  = P2^6;
sbit KEY_IN_4  = P2^7;
sbit KEY_OUT_1 = P2^3;
sbit KEY_OUT_2 = P2^2;
sbit KEY_OUT_3 = P2^1;
sbit KEY_OUT_4 = P2^0;

unsigned char code LedChar[] = {  //数码管显示字符转换表
    0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8,
    0x80, 0x90, 0x88, 0x83, 0xC6, 0xA1, 0x86, 0x8E
};
unsigned char LedBuff[7] = {  //数码管+独立LED显示缓冲区
    0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
};
unsigned char code KeyCodeMap[4][4] = { //矩阵按键编号到标准键盘键码的映射表
    { 0x31, 0x32, 0x33, 0x26 }, //数字键1、数字键2、数字键3、向上键
    { 0x34, 0x35, 0x36, 0x25 }, //数字键4、数字键5、数字键6、向左键
    { 0x37, 0x38, 0x39, 0x28 }, //数字键7、数字键8、数字键9、向下键
    { 0x30, 0x1B, 0x0D, 0x27 }  //数字键0、ESC键、  回车键、 向右键
};
unsigned char KeySta[4][4] = {  //全部矩阵按键的当前状态
    {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
};
unsigned long pdata KeyDownTime[4][4] = {  //每个按键按下的持续时间,单位ms
    {0, 0, 0, 0},  {0, 0, 0, 0},  {0, 0, 0, 0},  {0, 0, 0, 0}
};
bit enBuzz = 0;     //蜂鸣器使能标志
bit flag1s = 0;     //1秒定时标志
bit flagStart = 0;  //倒计时启动标志
unsigned char T0RH = 0;  //T0重载值的高字节
unsigned char T0RL = 0;  //T0重载值的低字节
unsigned int  CountDown = 0;  //倒计时计数器

void ConfigTimer0(unsigned int ms);
void ShowNumber(unsigned long num);
void KeyDriver();

void main()
{
    EA = 1;       //使能总中断
    ENLED = 0;    //选择数码管和独立LED
    ADDR3 = 1;
    ConfigTimer0(1);  //配置T0定时1ms
    ShowNumber(0);    //上电显示0
	
    while (1)
    {
        KeyDriver();  //调用按键驱动函数
        if (flagStart && flag1s) //倒计时启动且1秒定时到达时,处理倒计时
        {
            flag1s = 0;
            if (CountDown > 0)   //倒计时未到0时,计数器递减
            {
                CountDown--;
                ShowNumber(CountDown); //刷新倒计时数显示
                if (CountDown == 0)    //减到0时,执行声光报警
                {
                    enBuzz = 1;        //启动蜂鸣器发声
                    LedBuff[6] = 0x00; //点亮独立LED
                }
            }
        }
    }
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  //临时变量
    
    tmp = 11059200 / 12;      //定时器计数频率
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
    tmp = 65536 - tmp;        //计算定时器重载值
    tmp = tmp + 28;           //补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0为模式1
    TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
}
/* 将一个无符号长整型的数字显示到数码管上,num-待显示数字 */
void ShowNumber(unsigned long num)
{
    signed char i;
    unsigned char buf[6];
    
    for (i=0; i<6; i++)   //把长整型数转换为6位十进制的数组
    {
        buf[i] = num % 10;
        num = num / 10;
    }
    for (i=5; i>=1; i--)  //从最高位起,遇到0转换为空格,遇到非0则退出循环
    {
        if (buf[i] == 0)
            LedBuff[i] = 0xFF;
        else
            break;
    }
    for ( ; i>=0; i--)    //剩余低位都如实转换为数码管显示字符
    {
        LedBuff[i] = LedChar[buf[i]];
    }
}
/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)  //按键动作函数,根据键码执行相应动作
{
	
	if((keycode>=0x30)&&(keycode<=0x39))	//输入0-9的数字
	{
		CountDown = (CountDown*10)+(keycode-0x30);	//整体十进制左移,新数字进入个位,比如65,先显示5,再显示6
		ShowNumber(CountDown);		//显示倒计时设定值
	}
    if (keycode == 0x26)       //向上键,倒计时设定值递增
    {
        if (CountDown < 9999)  //最大计时9999秒
        {
            CountDown++;
            ShowNumber(CountDown);
        }
    }
    else if (keycode == 0x28)  //向下键,倒计时设定值递减
    {
        if (CountDown > 1)     //最小计时1秒
        {
            CountDown--;
            ShowNumber(CountDown);
        }
    }
    else if (keycode == 0x0D)  //回车键,启动倒计时
    {
        flagStart = 1;         //启动倒计时
    }
    else if (keycode == 0x1B)  //Esc键,取消倒计时
    {
        enBuzz = 0;            //关闭蜂鸣器
        LedBuff[6] = 0xFF;     //关闭独立LED
        flagStart = 0;         //停止倒计时
        CountDown = 0;         //倒计时数归零
        ShowNumber(CountDown);
    }
}
/* 按键驱动函数,检测按键动作,调度相应动作函数,需在主循环中调用 */
void KeyDriver()
{
    unsigned char i, j;
    static unsigned char pdata backup[4][4] = {  //按键值备份,保存前一次的值
        {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1},  {1, 1, 1, 1}
    };
    static unsigned long pdata TimeThr[4][4] = {  //快速输入执行的时间阈值
        {1000, 1000, 1000, 1000},  {1000, 1000, 1000, 1000},
        {1000, 1000, 1000, 1000},  {1000, 1000, 1000, 1000}
    };
    
    for (i=0; i<4; i++)  //循环扫描4*4的矩阵按键
    {
        for (j=0; j<4; j++)
        {
            if (backup[i][j] != KeySta[i][j])     //检测按键动作
            {
                if (backup[i][j] != 0)            //按键按下时执行动作
                {
                    KeyAction(KeyCodeMap[i][j]);  //调用按键动作函数
                }
                backup[i][j] = KeySta[i][j];      //刷新前一次的备份值
            }
            if (KeyDownTime[i][j] > 0)            //检测执行快速输入
            {
                if (KeyDownTime[i][j] >= TimeThr[i][j])
                {                                 //达到阈值时执行一次动作
                    KeyAction(KeyCodeMap[i][j]);  //调用按键动作函数
                    TimeThr[i][j] += 200; //时间阈值增加200ms,以准备下次执行
                }
            }
            else   //按键弹起时复位阈值时间
            {
                TimeThr[i][j] = 1000;  //恢复1s的初始阈值时间
            }
        }
    }
}
/* 按键扫描函数,需在定时中断中调用 */
void KeyScan()
{
    unsigned char i;
    static unsigned char keyout = 0;   //矩阵按键扫描输出索引
    static unsigned char keybuf[4][4] = {  //矩阵按键扫描缓冲区
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF},
        {0xFF, 0xFF, 0xFF, 0xFF},  {0xFF, 0xFF, 0xFF, 0xFF}
    };

    //将一行的4个按键值移入缓冲区
    keybuf[keyout][0] = (keybuf[keyout][0] << 1) | KEY_IN_1;
    keybuf[keyout][1] = (keybuf[keyout][1] << 1) | KEY_IN_2;
    keybuf[keyout][2] = (keybuf[keyout][2] << 1) | KEY_IN_3;
    keybuf[keyout][3] = (keybuf[keyout][3] << 1) | KEY_IN_4;
    //消抖后更新按键状态
    for (i=0; i<4; i++)  //每行4个按键,所以循环4次
    {
        if ((keybuf[keyout][i] & 0x0F) == 0x00)
        {   //连续4次扫描值为0,即4*4ms内都是按下状态时,可认为按键已稳定的按下
            KeySta[keyout][i] = 0;
            KeyDownTime[keyout][i] += 4;  //按下的持续时间累加
        }
        else if ((keybuf[keyout][i] & 0x0F) == 0x0F)
        {   //连续4次扫描值为1,即4*4ms内都是弹起状态时,可认为按键已稳定的弹起
            KeySta[keyout][i] = 1;
            KeyDownTime[keyout][i] = 0;   //按下的持续时间清零
        }
    }
    //执行下一次的扫描输出
    keyout++;        //输出索引递增
    keyout &= 0x03;  //索引值加到4即归零
    switch (keyout)  //根据索引,释放当前输出引脚,拉低下次的输出引脚
    {
        case 0: KEY_OUT_4 = 1; KEY_OUT_1 = 0; break;
        case 1: KEY_OUT_1 = 1; KEY_OUT_2 = 0; break;
        case 2: KEY_OUT_2 = 1; KEY_OUT_3 = 0; break;
        case 3: KEY_OUT_3 = 1; KEY_OUT_4 = 0; break;
        default: break;
    }
}
/* LED动态扫描刷新函数,需在定时中断中调用 */
void LedScan()
{
    static unsigned char i = 0;  //动态扫描索引
    
    P0 = 0xFF;             //关闭所有段选位,显示消隐
    P1 = (P1 & 0xF8) | i;  //位选索引值赋值到P1口低3位
    P0 = LedBuff[i];       //缓冲区中索引位置的数据送到P0口
    if (i < 6)             //索引递增循环,遍历整个缓冲区
        i++;
    else
        i = 0;
}
/* T0中断服务函数,完成数码管、按键扫描与秒定时 */
void InterruptTimer0() interrupt 1
{
    static unsigned int tmr1s = 0;  //1秒定时器
    
    TH0 = T0RH;   //重新加载重载值
    TL0 = T0RL;
    if (enBuzz)   //蜂鸣器发声处理
        BUZZ = ~BUZZ;  //驱动蜂鸣器发声
    else
        BUZZ = 1;      //关闭蜂鸣器
    LedScan();   //LED扫描显示
    KeyScan();   //按键扫描
    if (flagStart)  //倒计时启动时处理1秒定时
    {
        tmr1s++;
        if (tmr1s >= 1000)
        {
            tmr1s = 0;
            flag1s = 1;
        }
    }
    else  //倒计时未启动时1秒定时器始终归零
    {
        tmr1s = 0;
    }
}

UART串口通信:

串行通信:一次只能发送一位,发送8次才能发送1个字节

通信端口:P3.0/RXD,P3.1/TXD

波特率baud:发送二进制数据的速率,收发双方的波特率应当一致。

一个完整的数据帧:1位起始位0,8位数据位,1位停止位1。

RS232:使用在工业领域,负电平代表1,正电平代表0

IO口模拟串口通信:

#include <reg52.h>

sbit PIN_RXD = P3^0;  //接收引脚定义
sbit PIN_TXD = P3^1;  //发送引脚定义

bit RxdOrTxd = 0;  //指示当前状态为接收还是发送
bit RxdEnd = 0;    //接收结束标志
bit TxdEnd = 0;    //发送结束标志
unsigned char RxdBuf = 0;  //接收缓冲器
unsigned char TxdBuf = 0;  //发送缓冲器

void ConfigUART(unsigned int baud);
void StartTXD(unsigned char dat);
void StartRXD();

void main()
{
    EA = 1;   //开总中断
    ConfigUART(9600);  //配置波特率为9600
    
    while (1)
    {
        while (PIN_RXD);    //等待接收引脚出现低电平,即起始位
        StartRXD();         //启动接收
        while (!RxdEnd);    //等待接收完成
        StartTXD(RxdBuf+1); //启动发送,接收到的数据+1后,发送回去
        while (!TxdEnd);    //等待发送完成
    }
}
/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x02;   //配置T0为模式2,8位自动重装载,TL0从0~256,后TH0自动赋值给TL0
    TH0 = 256 - (11059200/12)/baud;  //计算T0重载值,每1/baud进入中断
}
/* 启动串行接收 */
void StartRXD()
{
    TL0 = 256 - ((256-TH0)>>1);  //接收启动时的T0定时为半个波特率周期,中一个波特率中间接收数据
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
    RxdEnd = 0;     //清零接收结束标志
    RxdOrTxd = 0;   //设置当前状态为接收
}
/* 启动串行发送,dat-待发送字节数据 */
void StartTXD(unsigned char dat)
{
    TxdBuf = dat;   //待发送数据保存到发送缓冲器
    TL0 = TH0;      //T0计数初值为重载值
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
    PIN_TXD = 0;    //发送起始位
    TxdEnd = 0;     //清零发送结束标志
    RxdOrTxd = 1;   //设置当前状态为发送
}
/* T0中断服务函数,处理串行发送和接收 */
void InterruptTimer0() interrupt 1
{
    static unsigned char cnt = 0; //位接收或发送计数

    if (RxdOrTxd)  //串行发送处理
    {
        cnt++;
        if (cnt <= 8)  //低位在先依次发送8bit数据位
        {
            PIN_TXD = TxdBuf & 0x01;
            TxdBuf >>= 1;
        }
        else if (cnt == 9)  //发送停止位
        {
            PIN_TXD = 1;
        }
        else  //发送结束
        {
            cnt = 0;    //复位bit计数器
            TR0 = 0;    //关闭T0
            TxdEnd = 1; //置发送结束标志
        }
    }
    else  //串行接收处理
    {
        if (cnt == 0)     //处理起始位
        {
            if (!PIN_RXD) //起始位为0时,清零接收缓冲器,准备接收数据位
            {
                RxdBuf = 0;    //接收到0,作为起始位
                cnt++;
            }
            else          //起始位不为0时,中止接收
            {
                TR0 = 0;  //关闭T0
            }
        }
        else if (cnt <= 8)   //处理8位数据位
        {
            RxdBuf >>= 1;    //低位在先,所以将之前接收的位向右移
            /*接收脚为1时,缓冲器最高位置1,而为0时不处理即仍保持移位后的0*/
            if (PIN_RXD)     //接收到1后的处理
            {                
                RxdBuf |= 0x80;
            }
            cnt++;
        }
        else  //停止位处理
        {
            cnt = 0;         //复位bit计数器
            TR0 = 0;         //关闭T0
            if (PIN_RXD)     //停止位为1时,方能认为数据有效
            {
                RxdEnd = 1;  //置接收结束标志,作为停止位
            }
        }
    }
}

UART通信模块:

        串行控制寄存器:SCON(地址0x98)

                SM0<7>,SM1<6>:串口通信模式,常用模式1

                SM2<5>:多机通信控制位

                REN<4>:使能串行接收

                TB8<3>,RB8<2>:模式2

                TI<1>:发送中断标志位,当发送电路发送到停止位的中间位置时,TI

                RI<0>:接收中断标志位,当接收电路接收到停止位的中间位置时,RI

波特率发生器:

        计算公式:TH1=TL1=256-晶振值/12/2/16/波特率

                若PCON |= 0x80; (PCON电源管理寄存器)

                计算公式为:TH1=TL1=256-晶振值/12/16/波特率        //每位取16次,若16的中间位置中有两次为1,则认为接收到了1

/* 串口配置函数,baud-通信波特率 */
void ConfigUART(unsigned int baud)
{
    SCON  = 0x50;  //配置串口为模式1
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x20;  //配置T1为模式2
    TH1 = 256 - (11059200/12/32)/baud;  //计算T1重载值
    TL1 = TH1;     //初值等于重载值
    ET1 = 0;       //禁止T1中断
    ES  = 1;       //使能串口中断
    TR1 = 1;       //启动T1
}

/* 串口中断服务函数 */
void InterruptUART() interrupt 4
{
    if (RI)  //接收到新字节
    {
        RI = 0;  //清零接收中断标志位
        if (cntRxd < sizeof(bufRxd)) //接收缓冲区尚未用完时,
        {                            //保存接收字节,并递增计数器
            bufRxd[cntRxd++] = SBUF;
        }
    }
    if (TI)  //字节发送完毕
    {
        TI = 0;   //清零发送中断标志位
        flagTxd = 1;  //设置字节发送完成标志
    }
}

串口操作函数

/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len)
{
    while (len--)  //循环发送所有字节
    {
        flagTxd = 0;      //清零发送标志
        SBUF = *buf++;    //发送一个字节数据
        while (!flagTxd); //等待该字节发送完成
    }
}
/* 串口数据读取函数,buf-接收指针,len-指定的读取长度,返回值-实际读到的长度 */
unsigned char UartRead(unsigned char *buf, unsigned char len)
{
    unsigned char i;
    
    if (len > cntRxd)  //指定读取长度大于实际接收到的数据长度时,
    {                  //读取长度设置为实际接收到的数据长度
        len = cntRxd;
    }
    for (i=0; i<len; i++)  //拷贝接收到的数据到接收指针上
    {
        *buf++ = bufRxd[i];
    }
    cntRxd = 0;  //接收计数器清零
    
    return len;  //返回实际读取长度
}
/* 串口接收监控,由空闲时间判定帧结束,需在定时中断中调用,ms-定时间隔 */
void UartRxMonitor(unsigned char ms)
{
    static unsigned char cntbkp = 0;
    static unsigned char idletmr = 0;

    if (cntRxd > 0)  //接收计数器大于零时,监控总线空闲时间
    {
        if (cntbkp != cntRxd)  //接收计数器改变,即刚接收到数据时,清零空闲计时
        {
            cntbkp = cntRxd;
            idletmr = 0;
        }
        else                   //接收计数器未改变,即总线空闲时,累积空闲时间
        {
            if (idletmr < 30)  //空闲计时小于30ms时,持续累加
            {
                idletmr += ms;
                if (idletmr >= 30)  //空闲时间达到30ms时,即判定为一帧接收完毕
                {
                    flagFrame = 1;  //设置帧接收完成标志
                }
            }
        }
    }
    else
    {
        cntbkp = 0;
    }
}
/* 串口驱动函数,监测数据帧的接收,调度功能函数,需在主循环中调用 */
void UartDriver()
{
    unsigned char len;
    unsigned char pdata buf[40];

    if (flagFrame) //有命令到达时,读取处理该命令
    {
        flagFrame = 0;
        len = UartRead(buf, sizeof(buf));  //将接收到的命令读取到缓冲区中
        UartAction(buf, len);  //传递数据帧,调用动作执行函数
    }
}

命令控制:

/* 内存比较函数,比较两个指针所指向的内存数据是否相同,
   ptr1-待比较指针1,ptr2-待比较指针2,len-待比较长度
   返回值-两段内存数据完全相同时返回1,不同返回0 */
bit CmpMemory(unsigned char *ptr1, unsigned char *ptr2, unsigned char len)
{
    while (len--)
    {
        if (*ptr1++ != *ptr2++)  //遇到不相等数据时即刻返回0
        {
            return 0;
        }
    }
    return 1;  //比较完全部长度数据都相等则返回1
}
/* 串口动作函数,根据接收到的命令帧执行响应的动作
   buf-接收到的命令帧指针,len-命令帧长度 */
void UartAction(unsigned char *buf, unsigned char len)
{
    unsigned char i;
    unsigned char code cmd0[] = "buzz on";   //开蜂鸣器命令
    unsigned char code cmd1[] = "buzz off";  //关蜂鸣器命令
    unsigned char code cmd2[] = "showstr ";  //字符串显示命令
    unsigned char code cmdLen[] = {          //命令长度汇总表
        sizeof(cmd0)-1, sizeof(cmd1)-1, sizeof(cmd2)-1,
    };
    unsigned char code *cmdPtr[] = {         //命令指针汇总表
        &cmd0[0],  &cmd1[0],  &cmd2[0],
    };

    for (i=0; i<sizeof(cmdLen); i++)  //遍历命令列表,查找相同命令
    {
        if (len >= cmdLen[i])  //首先接收到的数据长度要不小于命令长度
        {
            if (CmpMemory(buf, cmdPtr[i], cmdLen[i]))  //比较相同时退出循环
            {
                break;
            }
        }
    }
    switch (i)  //循环退出时i的值即是当前命令的索引值
    {
        case 0:
            flagBuzzOn = 1; //开启蜂鸣器
            break;
        case 1:
            flagBuzzOn = 0; //关闭蜂鸣器
            break;
        case 2:
            buf[len] = '\0';  //为接收到的字符串添加结束符
            LcdShowStr(0, 0, buf+cmdLen[2]);  //显示命令后的字符串
            i = len - cmdLen[2];              //计算有效字符个数
            if (i < 16)  //有效字符少于16时,清除液晶上的后续字符位
            {
                LcdAreaClear(i, 0, 16-i);
            }
            break;
        default:   //未找到相符命令时,给上机发送“错误命令”的提示
            UartWrite("bad command.\r\n", sizeof("bad command.\r\n")-1);
            return;
    }
    buf[len++] = '\r';  //有效命令被执行后,在原命令帧之后添加
    buf[len++] = '\n';  //回车换行符后返回给上位机,表示已执行
    UartWrite(buf, len);
}

 主函数与中断:

void main()
{
    EA = 1;            //开总中断
    ConfigTimer0(1);   //配置T0定时1ms
    ConfigUART(9600);  //配置波特率为9600
    InitLcd1602();     //初始化液晶
    
    while (1)
    {
        UartDriver();  //调用串口驱动
    }
}
/* 配置并启动T0,ms-T0定时时间 */
void ConfigTimer0(unsigned int ms)
{
    unsigned long tmp;  //临时变量
    
    tmp = 11059200 / 12;      //定时器计数频率
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
    tmp = 65536 - tmp;        //计算定时器重载值
    tmp = tmp + 33;           //补偿中断响应延时造成的误差
    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
    T0RL = (unsigned char)tmp;
    TMOD &= 0xF0;   //清零T0的控制位
    TMOD |= 0x01;   //配置T0为模式1
    TH0 = T0RH;     //加载T0重载值
    TL0 = T0RL;
    ET0 = 1;        //使能T0中断
    TR0 = 1;        //启动T0
}
/* T0中断服务函数,执行串口接收监控和蜂鸣器驱动 */
void InterruptTimer0() interrupt 1
{
    TH0 = T0RH;  //重新加载重载值
    TL0 = T0RL;
    if (flagBuzzOn)  //执行蜂鸣器鸣叫或关闭
        BUZZ = ~BUZZ;
    else
        BUZZ = 1;
    UartRxMonitor(1);  //串口接收监控
}

1602液晶

基本操作时序:

        读状态:输入:RS=L,RW=H,E=H

                       输出:D0~D7=状态字

        写指令:输入:RS=L,RW=L,D0~D7=指令码,E=高脉冲

                       输出:无

        读数据:输入:RS=H,RW=H,E=H

                       输出:D0~D7=数据

        写数据:输入:RS=H,RW=L,D0~D7=数据,E=高脉冲

                       输出:无

读写检测:需要确保ST7为0

#include <reg52.h>

#define LCD1602_DB  P0
sbit LCD1602_RS = P1^0;
sbit LCD1602_RW = P1^1;
sbit LCD1602_E  = P1^5;

void InitLcd1602();
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);

void main()
{
    unsigned char str[] = "Kingst Studio";

    InitLcd1602();
    LcdShowStr(2, 0, str);
    LcdShowStr(0, 1, "Welcome to KST51");
    while (1);
}
/* 等待液晶准备好 */
void LcdWaitReady()
{
    unsigned char sta;
    
    LCD1602_DB = 0xFF;
    LCD1602_RS = 0;
    LCD1602_RW = 1;
    do {
        LCD1602_E = 1;
        sta = LCD1602_DB; //读取状态字
        LCD1602_E = 0;
    } while (sta & 0x80); //bit7等于1表示液晶正忙,重复检测直到其等于0为止
}
/* 向LCD1602液晶写入一字节命令,cmd-待写入命令值 */
void LcdWriteCmd(unsigned char cmd)
{
    LcdWaitReady();
    LCD1602_RS = 0;
    LCD1602_RW = 0;
    LCD1602_DB = cmd;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
/* 向LCD1602液晶写入一字节数据,dat-待写入数据值 */
void LcdWriteDat(unsigned char dat)
{
    LcdWaitReady();
    LCD1602_RS = 1;
    LCD1602_RW = 0;
    LCD1602_DB = dat;
    LCD1602_E  = 1;
    LCD1602_E  = 0;
}
/* 设置显示RAM起始地址,亦即光标位置,(x,y)-对应屏幕上的字符坐标 */
void LcdSetCursor(unsigned char x, unsigned char y)
{
    unsigned char addr;
    
    if (y == 0)  //由输入的屏幕坐标计算显示RAM的地址
        addr = 0x00 + x;  //第一行字符地址从0x00起始
    else
        addr = 0x40 + x;  //第二行字符地址从0x40起始
    LcdWriteCmd(addr | 0x80);  //设置RAM地址
}
/* 在液晶上显示字符串,(x,y)-对应屏幕上的起始坐标,str-字符串指针 */
void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str)
{
    LcdSetCursor(x, y);   //设置起始地址
    while (*str != '\0')  //连续写入字符串数据,直到检测到结束符
    {
        LcdWriteDat(*str++);  //先取str指向的数据,然后str自加1
    }
}
/* 初始化1602液晶 */
void InitLcd1602()
{
    LcdWriteCmd(0x38);  //16*2显示,5*7点阵,8位数据接口
    LcdWriteCmd(0x0C);  //显示器开,光标关闭
    LcdWriteCmd(0x06);  //文字不动,地址自动+1
    LcdWriteCmd(0x01);  //清屏
}

I2C与EEPROM

起始位:SDA由高到低后SCL由高到低

停止位:SCL由低到高后SDA由低到高

数据位:SCL为低时SDA变化,SCL为高时SDA可读 

 数据传输:起始位,先传地址1~7位ADDRESS,8位R/W读写位,ACK为高等待从机相应,

                  然后发送数据,ACK为高等待从机相应,发送数据,ACK为高等待从机相应,最后停止

/* 产生总线起始信号 */
void I2CStart()
{
    I2C_SDA = 1; //首先确保SDA、SCL都是高电平
    I2C_SCL = 1;
    I2CDelay();
    I2C_SDA = 0; //先拉低SDA
    I2CDelay();
    I2C_SCL = 0; //再拉低SCL
}
/* 产生总线停止信号 */
void I2CStop()
{
    I2C_SCL = 0; //首先确保SDA、SCL都是低电平
    I2C_SDA = 0;
    I2CDelay();
    I2C_SCL = 1; //先拉高SCL
    I2CDelay();
    I2C_SDA = 1; //再拉高SDA
    I2CDelay();
}
/* I2C总线写操作,dat-待写入字节,返回值-从机应答位的值 */
bit I2CWrite(unsigned char dat)
{
    bit ack;  //用于暂存应答位的值
    unsigned char mask;  //用于探测字节内某一位值的掩码变量

    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        if ((mask&dat) == 0)  //该位的值输出到SDA上
            I2C_SDA = 0;
        else
            I2C_SDA = 1;
        I2CDelay();
        I2C_SCL = 1;          //拉高SCL
        I2CDelay();
        I2C_SCL = 0;          //再拉低SCL,完成一个位周期
    }
    I2C_SDA = 1;   //8位数据发送完后,主机释放SDA,以检测从机应答
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    ack = I2C_SDA; //读取此时的SDA值,即为从机的应答值
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return (~ack); //应答值取反以符合通常的逻辑:
                   //0=不存在或忙或写入失败,1=存在且空闲或写入成功
}
/* I2C寻址函数,即检查地址为addr的器件是否存在,返回值-从器件应答值 */
bit I2CAddressing(unsigned char addr)
{
    bit ack;

    I2CStart();  //产生起始位,即启动一次总线操作
    ack = I2CWrite(addr<<1);  //器件地址需左移一位,因寻址命令的最低位
                              //为读写位,用于表示之后的操作是读或写
    I2CStop();   //不需进行后续读写,而直接停止本次总线操作
    
    return ack;
}

EEPROM 24c02

 字节写数据:

起始信号,首字节(EEPROM的地址和读写位组合,选择写),EEPROM内部存储地址,数据,停止。

 字节读数据:

起始信号,首字节(EEPROM的地址和读写位组合,选择读),EEPROM内部存储地址,数据,发送ACK0继续读,发送NACK停止读

/* I2C总线读操作,并发送非应答信号,返回值-读到的字节 */
unsigned char I2CReadNAK()
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 1;   //8位数据发送完后,拉高SDA,发送非应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成非应答位,并保持住总线

    return dat;
}
/* I2C总线读操作,并发送应答信号,返回值-读到的字节 */
unsigned char I2CReadACK()
{
    unsigned char mask;
    unsigned char dat;

    I2C_SDA = 1;  //首先确保主机释放SDA
    for (mask=0x80; mask!=0; mask>>=1) //从高位到低位依次进行
    {
        I2CDelay();
        I2C_SCL = 1;      //拉高SCL
        if(I2C_SDA == 0)  //读取SDA的值
            dat &= ~mask; //为0时,dat中对应位清零
        else
            dat |= mask;  //为1时,dat中对应位置1
        I2CDelay();
        I2C_SCL = 0;      //再拉低SCL,以使从机发送出下一位
    }
    I2C_SDA = 0;   //8位数据发送完后,拉低SDA,发送应答信号
    I2CDelay();
    I2C_SCL = 1;   //拉高SCL
    I2CDelay();
    I2C_SCL = 0;   //再拉低SCL完成应答位,并保持住总线

    return dat;
}

EEPROM读写:

/* E2读取函数,buf-数据接收指针,addr-E2中的起始地址,len-读取长度 */
void E2Read(unsigned char *buf, unsigned char addr, unsigned char len)
{
    do {                       //用寻址操作查询当前是否可进行读写操作
        I2CStart();
        if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
        {
            break;
        }
        I2CStop();
    } while(1);
    I2CWrite(addr);            //写入起始地址
    I2CStart();                //发送重复启动信号
    I2CWrite((0x50<<1)|0x01);  //寻址器件,后续为读操作
    while (len > 1)            //连续读取len-1个字节
    {
        *buf++ = I2CReadACK(); //最后字节之前为读取操作+应答
        len--;
    }
    *buf = I2CReadNAK();       //最后一个字节为读取操作+非应答
    I2CStop();
}
/* E2写入函数,buf-源数据指针,addr-E2中的起始地址,len-写入长度 */
void E2Write(unsigned char *buf, unsigned char addr, unsigned char len)
{
    while (len > 0)
    {
        //等待上次写入操作完成
        do {                       //用寻址操作查询当前是否可进行读写操作
            I2CStart();
            if (I2CWrite(0x50<<1)) //应答则跳出循环,非应答则进行下一次查询
            {
                break;
            }
            I2CStop();
        } while(1);
        //按页写模式连续写入字节
        I2CWrite(addr);           //写入起始地址
        while (len > 0)
        {
            I2CWrite(*buf++);     //写入一个字节数据
            len--;                //待写入长度计数递减
            addr++;               //E2地址递增
            if ((addr&0x07) == 0) //检查地址是否到达页边界,24C02每页8字节,
            {                     //所以检测低3位是否为零即可
                break;            //到达页边界时,跳出循环,结束本次写操作
            }
        }
        I2CStop();
    }
}

 内存数据转换为16进制字符串:

/* 将一段内存数据转换为十六进制格式的字符串,
   str-字符串指针,src-源数据地址,len-数据长度 */
void MemToStr(unsigned char *str, unsigned char *src, unsigned char len)
{
    unsigned char tmp;

    while (len--)
    {
        tmp = *src >> 4;           //先取高4位
        if (tmp <= 9)              //转换为0-9或A-F
            *str++ = tmp + '0';
        else
            *str++ = tmp - 10 + 'A';
        tmp = *src & 0x0F;         //再取低4位
        if (tmp <= 9)              //转换为0-9或A-F
            *str++ = tmp + '0';
        else
            *str++ = tmp - 10 + 'A';
        *str++ = ' ';              //转换完一个字节添加一个空格
        src++;
    }
	*str = '\0';                   //添加字符串结束符
}
/* 将一字符串整理成16字节的固定长度字符串,不足部分补空格
   out-整理后的字符串输出指针,in-待整理字符串指针 */
void TrimString16(unsigned char *out, unsigned char *in)
{
    unsigned char i = 0;
    
    while (*in != '\0')  //拷贝字符串直到输入字符串结束
    {
        *out++ = *in++;
        i++;
        if (i >= 16)    //当拷贝长度已达到16字节时,强制跳出循环
        {
            break;
        }
    }
    for ( ; i<16; i++)  //如不足16个字节则用空格补齐
    {
        *out++ = ' ';
    }
    *out = '\0';        //最后添加结束符
}

DS1302时钟

单字节读操作需要发送8位命令控制和8位数据

 读写字节到总线

/* 发送一个字节到DS1302通信总线上 */
void DS1302ByteWrite(unsigned char dat)
{
    unsigned char mask;
    
    for (mask=0x01; mask!=0; mask<<=1)  //低位在前,逐位移出
    {
        if ((mask&dat) != 0) //首先输出该位数据
            DS1302_IO = 1;
        else
            DS1302_IO = 0;
        DS1302_CK = 1;       //然后拉高时钟
        DS1302_CK = 0;       //再拉低时钟,完成一个位的操作
    }
    DS1302_IO = 1;           //最后确保释放IO引脚
}
/* 由DS1302通信总线上读取一个字节 */
unsigned char DS1302ByteRead()
{
    unsigned char mask;
    unsigned char dat = 0;
    
    for (mask=0x01; mask!=0; mask<<=1)  //低位在前,逐位读取
    {
        if (DS1302_IO != 0)  //首先读取此时的IO引脚,并设置dat中的对应位
        {
            dat |= mask;
        }
        DS1302_CK = 1;       //然后拉高时钟
        DS1302_CK = 0;       //再拉低时钟,完成一个位的操作
    }
    return dat;              //最后返回读到的字节数据
}

 DS1302的写和读

/* 用单次写操作向某一寄存器写入一个字节,reg-寄存器地址,dat-待写入字节 */
void DS1302SingleWrite(unsigned char reg, unsigned char dat)
{
    DS1302_CE = 1;                   //使能片选信号
    DS1302ByteWrite((reg<<1)|0x80);  //发送写寄存器指令
    DS1302ByteWrite(dat);            //写入字节数据
    DS1302_CE = 0;                   //除能片选信号
}
/* 用单次读操作从某一寄存器读取一个字节,reg-寄存器地址,返回值-读到的字节 */
unsigned char DS1302SingleRead(unsigned char reg)
{
    unsigned char dat;
    
    DS1302_CE = 1;                   //使能片选信号
    DS1302ByteWrite((reg<<1)|0x81);  //发送读寄存器指令
    dat = DS1302ByteRead();          //读取字节数据
    DS1302_CE = 0;                   //除能片选信号
    
    return dat;
}

 DS1302初始化:

/* DS1302初始化,如发生掉电则重新设置初始时间 */
void InitDS1302()
{
    unsigned char i;
    unsigned char code InitTime[] = {  //2013年10月8日 星期二 12:30:00
        0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13
    };
    
    DS1302_CE = 0;  //初始化DS1302通信引脚
    DS1302_CK = 0;
    i = DS1302SingleRead(0);  //读取秒寄存器
    if ((i & 0x80) != 0)      //由秒寄存器最高位CH的值判断DS1302是否已停止
    {
        DS1302SingleWrite(7, 0x00);  //撤销写保护以允许写入数据
        for (i=0; i<7; i++)          //设置DS1302为默认的初始时间
        {
            DS1302SingleWrite(i, InitTime[i]);
        }
   

突发模式写和读

当我们写指令到DS1302 的时候,只要我们将要写的 5 位地址全部写1,即读操作用0xBF, 写操作用 0xBE,这样的指令送给 DS1302 之后,它就会自动识别出来是 burst 模式,马上把所有的 8 个字节同时锁存到另外的 8 个字节的寄存器缓冲区内,这样时钟继续走,而我们读数据是从另外一个缓冲区内读取的。

/* 用突发模式连续写入8个寄存器数据,dat-待写入数据指针 */
void DS1302BurstWrite(unsigned char *dat)
{
    unsigned char i;
    
    DS1302_CE = 1;
    DS1302ByteWrite(0xBE);  //发送突发写寄存器指令
    for (i=0; i<8; i++)     //连续写入8字节数据
    {
        DS1302ByteWrite(dat[i]);
    }
    DS1302_CE = 0;
}
/* 用突发模式连续读取8个寄存器的数据,dat-读取数据的接收指针 */
void DS1302BurstRead(unsigned char *dat)
{
    unsigned char i;
    
    DS1302_CE = 1;
    DS1302ByteWrite(0xBF);  //发送突发读寄存器指令
    for (i=0; i<8; i++)     //连续读取8个字节
    {
        dat[i] = DS1302ByteRead();
    }
    DS1302_CE = 0;
}

突发模式DS1302初始化 

/* DS1302初始化,如发生掉电则重新设置初始时间 */
void InitDS1302()
{
    unsigned char dat;
    unsigned char code InitTime[] = {  //2013年10月8日 星期二 12:30:00
        0x00,0x30,0x12, 0x08, 0x10, 0x02, 0x13, 0x00
    };
    
    DS1302_CE = 0;  //初始化DS1302通信引脚
    DS1302_CK = 0;
    dat = DS1302SingleRead(0);  //读取秒寄存器
    if ((dat & 0x80) != 0)      //由秒寄存器最高位CH的值判断DS1302是否已停止
    {
        DS1302SingleWrite(7, 0x00);  //撤销写保护以允许写入数据
        DS1302BurstWrite(InitTime);  //设置DS1302为默认的初始时间
    }
}

 1602液晶显示:数字转换为字符

        if (flag200ms)  //每200ms读取依次时间
        {
            flag200ms = 0;
            for (i=0; i<7; i++)  //读取DS1302当前时间
            {
                time[i] = DS1302SingleRead(i);
            }
            if (psec != time[0]) //检测到时间有变化时刷新显示
            {
                str[0] = '2';  //添加年份的高2位:20
                str[1] = '0';
                str[2] = (time[6] >> 4) + '0';  //“年”高位数字转换为ASCII码
                str[3] = (time[6]&0x0F) + '0';  //“年”低位数字转换为ASCII码
                str[4] = '-';  //添加日期分隔符
                str[5] = (time[4] >> 4) + '0';  //“月”
                str[6] = (time[4]&0x0F) + '0';
                str[7] = '-';
                str[8] = (time[3] >> 4) + '0';  //“日”
                str[9] = (time[3]&0x0F) + '0';
                str[10] = '\0';
                LcdShowStr(0, 0, str);  //显示到液晶的第一行
                
                str[0] = (time[5]&0x0F) + '0';  //“星期”
                str[1] = '\0';
                LcdShowStr(11, 0, "week");
                LcdShowStr(15, 0, str);  //显示到液晶的第一行
                
                str[0] = (time[2] >> 4) + '0';  //“时”
                str[1] = (time[2]&0x0F) + '0';
                str[2] = ':';  //添加时间分隔符
                str[3] = (time[1] >> 4) + '0';  //“分”
                str[4] = (time[1]&0x0F) + '0';
                str[5] = ':';
                str[6] = (time[0] >> 4) + '0';  //“秒”
                str[7] = (time[0]&0x0F) + '0';
                str[8] = '\0';
                LcdShowStr(4, 1, str);  //显示到液晶的第二行
                
                psec = time[0];  //用当前值更新上次秒数
            }
        }

时钟结构体

struct sTime {  //日期时间结构体定义
    unsigned int  year;
    unsigned char mon;
    unsigned char day;
    unsigned char hour;
    unsigned char min;
    unsigned char sec;
    unsigned char week;
};

/* 获取实时时间,即读取DS1302当前时间并转换为时间结构体格式 */
void GetRealTime(struct sTime *time)
{
    unsigned char buf[8];
    
    DS1302BurstRead(buf);
    time->year = buf[6] + 0x2000;
    time->mon  = buf[4];
    time->day  = buf[3];
    time->hour = buf[2];
    time->min  = buf[1];
    time->sec  = buf[0];
    time->week = buf[5];
}
/* 设定实时时间,时间结构体格式的设定时间转换为数组并写入DS1302 */
void SetRealTime(struct sTime *time)
{
    unsigned char buf[8];
    
    buf[7] = 0;
    buf[6] = time->year;
    buf[5] = time->week;
    buf[4] = time->mon;
    buf[3] = time->day;
    buf[2] = time->hour;
    buf[1] = time->min;
    buf[0] = time->sec;
    DS1302BurstWrite(buf);
}

DS1302初始化

/* DS1302初始化,如发生掉电则重新设置初始时间 */
void InitDS1302()
{
    unsigned char dat;
    struct sTime code InitTime[] = {  //2013年10月8日 12:30:00 星期二
        0x2013,0x10,0x08, 0x12,0x30,0x00, 0x02
    };
    
    DS1302_CE = 0;  //初始化DS1302通信引脚
    DS1302_CK = 0;
    dat = DS1302SingleRead(0);  //读取秒寄存器
    if ((dat & 0x80) != 0)      //由秒寄存器最高位CH的值判断DS1302是否已停止
    {
        DS1302SingleWrite(7, 0x00);  //撤销写保护以允许写入数据
        SetRealTime(&InitTime);      //设置DS1302为默认的初始时间
    }
}

红外通信

#include <reg52.h>

sbit IR_INPUT = P3^3;  //红外接收引脚

bit irflag = 0;  //红外接收标志,收到一帧正确数据后置1
unsigned char ircode[4];  //红外代码接收缓冲区

/* 初始化红外接收功能 */
void InitInfrared()
{
    IR_INPUT = 1;  //确保红外接收引脚被释放
    TMOD &= 0x0F;  //清零T1的控制位
    TMOD |= 0x10;  //配置T1为模式1
    TR1 = 0;       //停止T1计数
    ET1 = 0;       //禁止T1中断
    IT1 = 1;       //设置INT1为负边沿触发
    EX1 = 1;       //使能INT1中断
}
/* 获取当前高电平的持续时间 */
unsigned int GetHighTime()
{
    TH1 = 0;  //清零T1计数初值
    TL1 = 0;
    TR1 = 1;  //启动T1计数
    while (IR_INPUT)  //红外输入引脚为1时循环检测等待,变为0时则结束本循环
    {
        if (TH1 >= 0x40)
        {            //当T1计数值大于0x4000,即高电平持续时间超过约18ms时,
            break;   //强制退出循环,是为了避免信号异常时,程序假死在这里。
        }
    }
    TR1 = 0;  //停止T1计数

    return (TH1*256 + TL1);  //T1计数值合成为16bit整型数,并返回该数
}
/* 获取当前低电平的持续时间 */
unsigned int GetLowTime()
{
    TH1 = 0;  //清零T1计数初值
    TL1 = 0;
    TR1 = 1;  //启动T1计数
    while (!IR_INPUT)  //红外输入引脚为0时循环检测等待,变为1时则结束本循环
    {
        if (TH1 >= 0x40)
        {            //当T1计数值大于0x4000,即低电平持续时间超过约18ms时,
            break;   //强制退出循环,是为了避免信号异常时,程序假死在这里。
        }
    }
    TR1 = 0;  //停止T1计数

    return (TH1*256 + TL1);  //T1计数值合成为16bit整型数,并返回该数
}
/* INT1中断服务函数,执行红外接收及解码 */
void EXINT1_ISR() interrupt 2
{
    unsigned char i, j;
    unsigned char byt;
    unsigned int time;
    
    //接收并判定引导码的9ms低电平
    time = GetLowTime();
    if ((time<7833) || (time>8755))  //时间判定范围为8.5~9.5ms,
    {                                //超过此范围则说明为误码,直接退出
        IE1 = 0;   //退出前清零INT1中断标志
        return;
    }
    //接收并判定引导码的4.5ms高电平
    time = GetHighTime();
    if ((time<3686) || (time>4608))  //时间判定范围为4.0~5.0ms,
    {                                //超过此范围则说明为误码,直接退出
        IE1 = 0;
        return;
    }
    //接收并判定后续的4字节数据
    for (i=0; i<4; i++)  //循环接收4个字节
    {
        for (j=0; j<8; j++)  //循环接收判定每字节的8个bit
        {
            //接收判定每bit的560us低电平
            time = GetLowTime();
            if ((time<313) || (time>718)) //时间判定范围为340~780us,
            {                             //超过此范围则说明为误码,直接退出
                IE1 = 0;
                return;
            }
            //接收每bit高电平时间,判定该bit的值
            time = GetHighTime();
            if ((time>313) && (time<718)) //时间判定范围为340~780us,
            {                             //在此范围内说明该bit值为0
                byt >>= 1;   //因低位在先,所以数据右移,高位为0
            }
            else if ((time>1345) && (time<1751)) //时间判定范围为1460~1900us,
            {                                    //在此范围内说明该bit值为1
                byt >>= 1;   //因低位在先,所以数据右移,
                byt |= 0x80; //高位置1
            }
            else  //不在上述范围内则说明为误码,直接退出
            {
                IE1 = 0;
                return;
            }
        }
        ircode[i] = byt;  //接收完一个字节后保存到缓冲区
    }
    irflag = 1;  //接收完毕后设置标志
    IE1 = 0;     //退出前清零INT1中断标志
}

DS18B20温度传感器

温度格式

 初始化

sbit IO_18B20 = P3^2;  //DS18B20通信引脚

/* 软件延时函数,延时时间(t*10)us */
void DelayX10us(unsigned char t)
{
    do {
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
        _nop_();
    } while (--t);
}
/* 复位总线,获取存在脉冲,以启动一次读写操作 */
bit Get18B20Ack()
{
    bit ack;
    
    EA = 0;   //禁止总中断
    IO_18B20 = 0;     //产生500us复位脉冲
    DelayX10us(50);
    IO_18B20 = 1;
    DelayX10us(6);    //延时60us
    ack = IO_18B20;   //读取存在脉冲
    while(!IO_18B20); //等待存在脉冲结束
    EA = 1;   //重新使能总中断
    
    return ack;
}

/* 向DS18B20写入一个字节,dat-待写入字节 */
void Write18B20(unsigned char dat)
{
    unsigned char mask;
    
    EA = 0;   //禁止总中断
    for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次移出8个bit
    {
        IO_18B20 = 0;         //产生2us低电平脉冲
        _nop_();
        _nop_();
        if ((mask&dat) == 0)  //输出该bit值
            IO_18B20 = 0;
        else
            IO_18B20 = 1;
        DelayX10us(6);        //延时60us
        IO_18B20 = 1;         //拉高通信引脚
    }
    EA = 1;   //重新使能总中断
}

 读

/* 从DS18B20读取一个字节,返回值-读到的字节 */
unsigned char Read18B20()
{
    unsigned char dat;
    unsigned char mask;
    
    EA = 0;   //禁止总中断
    for (mask=0x01; mask!=0; mask<<=1)  //低位在先,依次采集8个bit
    {
        IO_18B20 = 0;         //产生2us低电平脉冲
        _nop_();
        _nop_();
        IO_18B20 = 1;         //结束低电平脉冲,等待18B20输出数据
        _nop_();              //延时2us
        _nop_();
        if (!IO_18B20)        //读取通信引脚上的值
            dat &= ~mask;
        else
            dat |= mask;
        DelayX10us(6);        //再延时60us
    }
    EA = 1;   //重新使能总中断

    return dat;
}

 ROM 操作指令:

        Skip ROM(跳过 ROM):0xCC。//当总线上只有一个器件的时候,可以跳过 ROM,不进行 ROM 检测。

RAM 存储器操作指令:

        Read Scratchpad(读暂存寄存器):0xBE        //DS18B20 的温度数据是 2 个字节,我们读取数据的时候,先读取到的是低字节的低位,读完了第一个字节后,再读高字节的低位,直到两个字节全部读取完毕。

        Convert Temperature(启动温度转换):0x44         //从转换开始到获取温度,DS18B20 是需要时间的,而这个时间长短取决于 DS18B20 的精度。

/* 启动一次18B20温度转换,返回值-表示是否启动成功 */
bit Start18B20()
{
    bit ack;
    
    ack = Get18B20Ack();   //执行总线复位,并获取18B20应答
    if (ack == 0)          //如18B20正确应答,则启动一次转换
    {
        Write18B20(0xCC);  //跳过ROM操作
        Write18B20(0x44);  //启动一次温度转换
    }
    return ~ack;   //ack==0表示操作成功,所以返回值对其取反
}

 读取温度

/* 读取DS18B20转换的温度值,返回值-表示是否读取成功 */
bit Get18B20Temp(int *temp)
{
    bit ack;
    unsigned char LSB, MSB; //16bit温度值的低字节和高字节
    
    ack = Get18B20Ack();    //执行总线复位,并获取18B20应答
    if (ack == 0)           //如18B20正确应答,则读取温度值
    {
        Write18B20(0xCC);   //跳过ROM操作
        Write18B20(0xBE);   //发送读命令
        LSB = Read18B20();  //读温度值的低字节
        MSB = Read18B20();  //读温度值的高字节
        *temp = ((int)MSB << 8) + LSB;  //合成为16bit整型数
    }
    return ~ack;  //ack==0表示操作应答,所以返回值为其取反值
}

AD与DA

AD

        第一个字节

        和 EEPROM 类似,是器件地址字节,其中 7 位代表地址, 1 位代表读写方向。
         第二个字节

        第 3 位和第 7 位是固定的 0 

        4 位和第 5 位可以实现把 PCF8591 4 路模拟输入配置成单端模式和差分模式

        控制字节的第 2 位是自动增量控制位

        控制字节的第 0 位和第 1 位就是通道选择位了,00011011 代表了从 0 3 的一共4 个通道选择。

/* 读取当前的ADC转换值,chn-ADC通道号0~3 */
unsigned char GetADCValue(unsigned char chn)
{
    unsigned char val;
    
    I2CStart();
    if (!I2CWrite(0x48<<1))  //寻址PCF8591,设定为写,如未应答,则停止操作并返回0
    {
        I2CStop();
        return 0;
    }
    I2CWrite(0x40|chn);        //写入控制字节,选择转换通道
    I2CStart();
    I2CWrite((0x48<<1)|0x01);  //寻址PCF8591,指定后续为读操作    
    I2CReadACK();              //先空读一个字节,提供采样转换时间
    val = I2CReadNAK();        //读取刚刚转换完的值
    I2CStop();
    
    return val;
}
/* ADC转换值转为实际电压值的字符串形式,str-字符串指针,val-AD转换值 */
void ValueToString(unsigned char *str, unsigned char val)
{
    //电压值=转换结果*2.5V/255,式中的25隐含了一位十进制小数
    val = (val*25) / 255;
    str[0] = (val/10) + '0';  //整数位字符
    str[1] = '.';             //小数点
    str[2] = (val%10) + '0';  //小数位字符
    str[3] = 'V';             //电压单位
    str[4] = '\0';            //结束符
}

DA

        第三个字节

                发送给 PCF8591 的第三个字节 D/A 数据寄存器,表示 D/A 模拟输出的电压值。

                一个 8 位的 D/A,从 0~255,代表了 0~2.55V 的话,那么我们用单片机给第三个字节发送 100,D/A 引脚就会输出一个 1V 的电压,发送 200 就输出一个 2V 的电压。

/* 设置DAC输出值,val-设定值 */
void SetDACOut(unsigned char val)
{
    I2CStart();
    if (!I2CWrite(0x48<<1)) //寻址PCF8591,如未应答,则停止操作并返回
    {
        I2CStop();
        return;
    }
    I2CWrite(0x40);         //写入控制字节
    I2CWrite(val);          //写入DA值  
    I2CStop();
}
/* 按键动作函数,根据键码执行相应的操作,keycode-按键键码 */
void KeyAction(unsigned char keycode)
{
    static unsigned char volt = 0;  //输出电压值,隐含了一位十进制小数位
    
    if (keycode == 0x26)  //向上键,增加0.1V电压值
    {
        if (volt < 25)
        {
            volt++;
            SetDACOut(volt*255/25); //转换为AD输出值
        }
    }
    else if (keycode == 0x28)  //向下键,减小0.1V电压值
    {
        if (volt > 0)
        {
            volt--;
            SetDACOut(volt*255/25); //转换为AD输出值
        }
    }
}

RS485通信与Modbus协议

 RS485

sbit RS485_DIR = P1^7;  //RS485方向选择引脚

/* 串口数据写入,即串口发送函数,buf-待发送数据的指针,len-指定的发送长度 */
void UartWrite(unsigned char *buf, unsigned char len)
{
    RS485_DIR = 1;  //RS485设置为发送
    while (len--)   //循环发送所有字节
    {
        flagTxd = 0;      //清零发送标志
        SBUF = *buf++;    //发送一个字节数据
        while (!flagTxd); //等待该字节发送完成
    }
    DelayX10us(5);  //等待最后的停止位完成,延时时间由波特率决定
    RS485_DIR = 0;  //RS485设置为接收
}

Modbus

/* 串口动作函数,根据接收到的命令帧执行响应的动作
   buf-接收到的命令帧指针,len-命令帧长度 */
void UartAction(unsigned char *buf, unsigned char len)
{
    unsigned char i;
    unsigned char cnt;
    unsigned char str[4];
    unsigned int  crc;
    unsigned char crch, crcl;
    
    if (buf[0] != 0x01) //本例中的本机地址设定为0x01,
    {                   //如数据帧中的地址字节与本机地址不符,
        return;         //则直接退出,即丢弃本帧数据不做任何处理
    }
    //地址相符时,再对本帧数据进行校验
    crc = GetCRC16(buf, len-2);  //计算CRC校验值
    crch = crc >> 8;
    crcl = crc & 0xFF;
    if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
    {
        return;   //如CRC校验不符时直接退出
    }
    //地址和校验字均相符后,解析功能码,执行相关操作
    switch (buf[1])
    {
        case 0x03:  //读取一个或连续的寄存器
            if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持0x0000~0x0005
            {
                if (buf[3] <= 0x04)
                {
                    i = buf[3];      //提取寄存器地址
                    cnt = buf[5];    //提取待读取的寄存器数量
                    buf[2] = cnt*2;  //读取数据的字节数,为寄存器数*2
                    len = 3;         //帧前部已有地址、功能码、字节数共3个字节
                    while (cnt--)
                    {
                        buf[len++] = 0x00;          //寄存器高字节补0
                        buf[len++] = regGroup[i++]; //寄存器低字节
                    }
                }
                else  //地址0x05为蜂鸣器状态
                {
                    buf[2] = 2;  //读取数据的字节数
                    buf[3] = 0x00;
                    buf[4] = flagBuzzOn;
                    len = 5;
                }
                break;
            }
            else  //寄存器地址不被支持时,返回错误码
            {
                buf[1] = 0x83;  //功能码最高位置1
                buf[2] = 0x02;  //设置异常码为02-无效地址
                len = 3;
                break;
            }
            
        case 0x06:  //写入单个寄存器
            if ((buf[2]==0x00) && (buf[3]<=0x05)) //只支持0x0000~0x0005
            {
                if (buf[3] <= 0x04)
                {
                    i = buf[3];             //提取寄存器地址
                    regGroup[i] = buf[5];   //保存寄存器数据
                    cnt = regGroup[i] >> 4; //显示到液晶上
                    if (cnt >= 0xA)
                        str[0] = cnt - 0xA + 'A';
                    else
                        str[0] = cnt + '0';
                    cnt = regGroup[i] & 0x0F;
                    if (cnt >= 0xA)
                        str[1] = cnt - 0xA + 'A';
                    else
                        str[1] = cnt + '0';
                    str[2] = '\0';
                    LcdShowStr(i*3, 0, str);
                }
                else  //地址0x05为蜂鸣器状态
                {
                    flagBuzzOn = (bit)buf[5]; //寄存器值转为蜂鸣器的开关
                }
                len -= 2; //长度-2以重新计算CRC并返回原帧
                break;
            }
            else  //寄存器地址不被支持时,返回错误码
            {
                buf[1] = 0x86;  //功能码最高位置1
                buf[2] = 0x02;  //设置异常码为02-无效地址
                len = 3;
                break;
            }
            
        default:  //其它不支持的功能码
            buf[1] |= 0x80;  //功能码最高位置1
            buf[2] = 0x01;   //设置异常码为01-无效功能
            len = 3;
            break;
    }
    crc = GetCRC16(buf, len); //计算返回帧的CRC校验值
    buf[len++] = crc >> 8;    //CRC高字节
    buf[len++] = crc & 0xFF;  //CRC低字节
    UartWrite(buf, len);      //发送返回帧
}

多功能电子钟

条件编译

#ifndef __REG52_H__    //如果没有定义__REG52_H__,如果定义了__REG52——H__,则不往下执行
#define __REG52_H__    //定义__REG52_H__

外部变量声明

#ifndef __TIME_C    //如果没有定义__TIME_C,则可以声明外部变量,就可以防止在time.c里重复声明,还可以在别的文件里使用声明变量
//声明外部变量
#endif

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值