单片机初步学习

2019-04-15 16:50发布

单片机

电平: 单片机为TTL电平:
高 +5V 低 0V 计算机串口
高 -12V 低 +12V (计算机与单片机之间通讯要电平转换芯片 max232) B:表示二进制 (0001B) H:表示十六进制(1H) ^:“异或”运算:相同就为 0 ,不同就为 1

80C51 系列介绍

以8051为基核开发出的CMOS工艺单片机产品。 STC89C52RC
STC: 公司名。 89:系列。 C:CMOS。 52:5为系列,2为内部存储空间大小(2*4K = 8K) 40C-PDIP
40:运行的速度(40兆Hz) C:商业级(还有工业级,主要区别为温度使用范围) PDIP:封装型号 0721CV4336(0721生产,后面也为型号)
总线:计算机各部件之间传送信息的公共通道。
内部总线:CPU内部。 外部:CPU与外部连接。 RAM:存放可以读写的数据。
ROM:存放程序。

C-51

数据类型扩充定义:

sfr:特殊功能寄存器声明
sfr16:sfr的16位数据声明
sbit:特殊功能位声明
bit:位变量声明 sbit OV = PSW^2 (PSW寄存器的第二位,以OV来表示)

C-51的包含的头文件:

通常有:reg51.h reg52.h math.h ctype.h stdio.h stdlib.h absacc.h
常用有:reg51.h reg52.h(定义特殊功能寄存器和位寄存器) math.h(定义常用数学运算)
>>:位右移:向右移一位,最高位补0
&:按位与: 5(0101)& 2(0010) = 0000
~:按位取反
最小系统能够运行起来的必要条件:
1.电源 2.晶振 3.复位电路 对单片机任意IO口的随意操作
1.输出控制电平高低 2.输出检测电平高低 (自己动手需要的硬件材料)
DIP40脚座一个
杜邦线,单头带帽,40根
单排针 两排
小电路板一块
DS12C887 一片
焊锡、烙铁等 OE上面有一杠,代表:低电平有效
D:输入端
vcc和vdd都接电源正vss接电源负,提供电源
DIOLA使其直通 51单片机所有I/O口上电后默认全是高电平。 位从0开始。(与数组下标一样) #include sbit D1 = P1^0; void main() { D1 = 0; //输出0表示连通点亮(从高电平变成低电平) } //位操作让灯亮 二进制转换成十六进制:4个一组
比如:1111 1101 : fd :f 代表 1111 d 代表 1101 (倒着念) 1:灭 0:亮 1 P1.0 灭 D1
0 P1.1 亮 D2
1 P1.2 灭 D3
1 P1.3 灭 D4
1 P1.4 灭 D5
1 P1.5 灭 D6
1 P1.6 灭 D7
1 P1.7 灭 D8 倒着,4个一组,1111:f 1101:d 二进制:1111 1101 十六进制:0xfd #include void main() { P1 = 0xfd; } //控制总线控制灯亮,直接用十六进制操作 控制延迟时间: #include sbit D1=P1^0; sbit D2=P1^1; sbit D3=P1^2; sbit D4=P1^3; sbit D5=P1^4; sbit D6=P1^5; sbit D7=P1^6; sbit D8=P1^7; unsigned int a; unsigned int b; void main() { a=50000; D1=0; while(a--); a=50000; D1=1; D2=0; while(a--); } a=50000; while(a–); (大约0.5秒,与芯片频率有关) //直接调用函数进行流水灯 #include #include #define uint int char temp; void delay() { uint x; x=50000; while(x--); } void main() { temp = 0xfe; //为什么用char型?(十六进制数,用char或int都可以) P1 = temp; // 也可以直接用P1 while(1) { temp = _crol_(temp,1); //将temp循环左移1位, temp = 0xfd (16进制数,二进制8位,让0去不断循环) delay(); P1=temp; } } 蜂鸣器: sbit beet=P2^3; beet = 0; 数码管:高电平数据直通,低电平数据锁存,锁存端一个下降沿把输入端数据保存在输出端,之后断开。 #include #include sbit dula=P2^6; sbit wela=P2^7; int a; char temp; void main() { while(1) { dula = 1; wela = 1; P0 = 0xfe; wela = 0; P0 = 0x06; P1 = 0xfe; a=100000; while(a--); wela = 1; P0 = 0xfd; wela = 0; P0 = 0x5b; P1 = _crol_(P1,1); a=100000; while(a--); wela = 1; P0 = 0xfb; wela = 0; P0 = 0x4f; P1 = _crol_(P1,1); a=100000; while(a--); wela = 1; P0 = 0xf7; wela = 0; P0 = 0x66; P1 = _crol_(P1,1); a=100000; while(a--); wela = 1; P0 = 0xef; wela = 0; P0 = 0x6d; P1 = _crol_(P1,1); a=100000; while(a--); wela = 1; P0 = 0xdf; wela = 0; P0 = 0x7d; P1 = _crol_(P1,1); a=100000; while(a--); dula = 0; } } //流水加数码管循环(每次位锁存开启1,关闭0,段锁存可以一头一尾) 锁存器是一个芯片,要开启才能实现锁存器的功能。
位锁存:sbit wei=P2^7;
段锁存:sbit duan=P2^6; 数码管:P0; 定义编码表:char code table[]={} : code表示将其存储在程序存储器,不用code会放在随机存储器中,而单片机的随机存储器是有限的,声明数组注意加code。 // 电平中断方式 EA=1; //打开总中断 EX0=1; //打开外部中断0 //IT0=0 电平触发方式,不用写,因为单片机所有默认都为0 void exter0() interrupt 0 // interrupt 表示中断总程序, 0为外部中断0 { P1=0xfe; } //要用镊子夹P3去辅助中断,并且中断后无法退出要收回镊子复原 EA=1; //总中断打开 EX0=1; //外部中断0打开 // IT0=1 直接对寄存器操作,为边沿触发 TCON=0x01 // 对寄存器的某一位进行操作 // 两者效果相同 //边沿触发方式,只要给一个边沿触发方式,会自动执行中断,之后中断停止返回主程序

定时器

定时器和中断在一起的。 定时器:实现定时功能,比较方便的办法是利用单片机内部的定时/计数器。也可以采用下面三种方法。 实现定时功能:1.软件定时。(不占用硬件资源,占用CPU时间,降低CPU利用率)
2.采用时基电路定时:外接必要的元器件,定时值不可编程(即不可由软件控制和修改)
3.采用可编程芯片定时:可以用软件来确定和修改。在单片机的定时/计数器不够用时,可以考虑进行扩展。 定时/计数器实质是加1计数器(16位),由高8位和低8位两个寄存器组成。
TMOD是定时/计数器的工作方式寄存器,确定工作方式和功能。
TCON是控制寄存器,控制T0、T1的启动和停止及设置溢出标志。 TMOD : 低四位用于T0,高四位用于T1。 7 6 5 4 3 2 1 0 GATE C/〒 M1 M0 GATE C/〒 M1 M0 GATE:门控位。GATE=0时,只要用软件使TCON中的TR0或TR1为1,就可以启动定时/计数器工作。 GATE=1时,要用软件使TR0或TR1为1,同时外部中断引脚也为高电平时,才能启动。 C/〒 : 定时/计数模式选择位。C/〒=0为定时模式; C/〒=1为计数模式。 M1M0:工作方式设置位。定时/计数器有四种工作方式,由M1M0进行设置。 00:方式0:13位定时/计数器
01:方式1:16位定时/计数器
10:方式2:8位自动重装定时/计数器
11:方式3:T0分成两个独立的8位定时/计数器;T1此方式停止计数 TCON:低4位控制外部中断。高4位控制定时/计数器的启动和中断申请。 7 6 5 4 TF1 TR1 TF0 TR0 TF1:T1溢出中断请求标志位。硬件自动设置。CPU可随时查询TF1的状态。TF1可用作查询测试的标志。 TR1:T1运行控制位。TR1置1时,T1开始工作;TR1置0时,T1停止工作。由软件置1或0。所以,用软件可控制定时/计数器的启动与停止。 TF0:T0溢出中断请求标志位。与TF1类同。 TR0:T0运行控制位。与TR1类同。 计数个数与计数初值的关系为:X=2的十六次方-N 计数器不装值的话默认为0,每来一个机器周期要加1位,依次往前,低八位和高八位全装满之后,再来一个机器周期,就全部清零。 方式1:TL0作为低8位,TH0作为高8位。 初始化程序应完成如下工作:
对TMOD赋值,以确定T0和T1的工作方式。
计算初值,并将其写入TH0、TL0或TH1、TL1。
中断方式时,则对IE赋值,开放中断。
使TR0或TR1置位,启动定时/计数器定时或计数。 #include #define uchar unsigned char #define uint unsigned int sbit wela=P2^7; sbit dula=P2^6; char code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d, 0x7d,0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71,0x00}; uint num=0; uchar tt=0; /*void delay() { int i=50000; while(i--); }*/ void exter0() interrupt 1 // 1表示计时器中断方式开启 { TH0=(65536-50000)/256; //每次进入要清空,所以初始化 TL0=(65536-50000)%256; tt++; } void main() { TMOD=0x01; //分别对应 GATA C/〒 M1 M0,打开计时器功能 TH0=(65536-50000)/256; //高8位存数 因实现50毫秒 故减去50000 TL0=(65536-50000)%256; //剩余数存在低8位 EA=1; ET0=1; //打开定时器0中断 TR0=1; //启动定时器0 wela=1; P0=0xea; wela=0; dula=1; P0=0x3f; dula=0; while(1) { if(tt==20) { tt=0; num++; if(num==16) num=0; dula=1; P0=table[num]; dula=0; } } } //中断为50毫秒,执行20次,定时1毫秒后变数 #include #define uchar unsigned char #define uint unsigned int sbit wei=P2^7; sbit duan=P2^6; uchar code table[]={0x3f,0x06,0x5b,0x4f,0x66, 0x6d,0x7d,0x07,0x7f,0x6f,0x77, 0x7c,0x39,0x5e,0x79,0x71}; uchar num,temp; void delay(uint z) { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } void main() { duan=1; P0=0; duan=0; wei=1; P0=0xc0; wei=0; while(1) { P3=0xfe; //打开P3状态 temp=P3; //借值 temp=temp&0xf0; //与 1111 0000 按位与,如果前面出现0(有键按下),那么与后的结果不等于 0xf0 while(temp!=0xf0) { delay(5); //消波 temp=P3; temp=temp&0xf0; while(temp!=0xf0) { temp=P3; switch(temp) { case 0xee: num=1; break; case 0xde: num=2; break; case 0xbe: num=3; break; case 0x7e: num=4; break; } while(temp!=0xf0) { temp=P3; temp=temp&0xf0; } //放下后变值 duan=1; P0=table[num-1]; duan=0; } } } }

D/A转换器

#include sbit csda=P3^2; //控制D12小灯,片选 sbit wr=P3^6; void main() { csda=0; //高电平打开 wr=0; P0=0xff; //全 1 是最亮 全 0 是最暗 while(1); }

A/D转换器

A/D转换器与发光二极管的锁存器相连 #include #define uint unsigned int #define uchar unsigned char sbit wr=P3^6; sbit rd=P3^7; void delay(uint z) { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } void init () { P0=0x7f; } void start() //启动A/D转换 { wr=1; wr=0; wr=1; } void main() { init(); while(1) { start(); delay(100); //缓时越高,闪的越慢 rd=0; //低才能出现数据 相当于A/D芯片的OE(锁存缓存器) delay(10); rd=1; delay(10); } } //二极管闪,可用A/D调

串口

计算机通信:并行通信与串行通信
并行:将数据字节的各位用多条数据线同时传送(控制简单,传输速度快,但不适合长距离传送)
串行:将数据字节分成一位一位的形式在一条线上传送(8位顺次传送,凑成一个字节,适合长距离传送,但传送控制复杂)

异步通信:

发送和接收设备各有一个时钟(晶振),每一个机器周期发一个数,要求发送和接收设备的时钟尽可能一致。 异步通信是以字符(构成的帧)为单位进行传输,字符与字符之间的间隙(时间间隔)是任意的,但每个字符中的各位是以固定的时间传送的,字符之间不一定有“位间隔”的整数倍的关系,但同一字符内的各位之间的距离均为“位间隔”的整数倍。(字符间隔任意,字符内位间隔固定一致) 异步通信 ,先发送一个起始位(要发送数据了,低电平开始),然后是数据位(先发最低位),后跟校验位(检查数据有没有传错,若传错,重新发)(校验位可能没有),最后停止位(高电平结束) 异步通信传输效率不高
单片机的串口属于异步通信。

同步通信:(用的非常少,了解)

接收和发送一个时钟控制,使双方完全同步,位之间的距离均为“位间隔”的整数倍,字符间无间隙。

串行通信的传输方向:

单工:数据传输仅能沿一个方向。
半双工:数据传输可以沿两个方向,但需要分时进行。
全双工:数据可以同时进行双向传输。

信号的调制与解调:

利用调制器把数字信号转换成模拟信号,然后送到通信线路上去,再由解调器把从通信线路上收到的模拟信号转换成数字信号。由于通信是双向的,调制器和解调器合并在一个装置中,这就是调制解调器MODEM。

串行通信的错误校验:

奇偶校验:

在发送数据时,数据位尾随的1位为奇偶校验位(1或0)。
奇校验时,数据中“1”的个数与校验位“1”的个数之和应为奇数;
偶校验时,数据中“1”的个数与校验位“1”的个数之和应为偶数。
接受字符时,对“1”的个数进行校验,若发现不一致,则说明传输数据过程中出现了差错。(奇校验或偶校验发送数据时自行决定)

代码和校验:

发送方将所求数据块求和(或各字节异或),产生一个字节的校验字符(校验和)附加到数据块末尾。
接收方接收数据同时对数据块(除校验字节外)求和(或各字节异或),将所得的结果与发送方的“校验和”进行比较,相符则无差错,否则即认为传送过程中出现了差错。

循环冗余校验:

通过某种数学运算实现有效信息与校验位之间的循环校验。
常用于对磁盘信息的传输、存储区的完整性校验等。
这种校验方法纠错能力强,广泛应用于同步通信中。

传输速率:

比特率每秒钟传输二进制代码的位数,单位是:位/秒(bps)。
如每秒钟传输240个字符,而每个字符格式包含10位(1个起始位、1个停止位、8个数据位),这时的比特率为: 10位 × 240个/秒 = 2400 bps

传输距离与传输速率的关系:

串行接口或终端直接传送串行信息位流的最大距离与传输速率及输线的电气特性有关。
当传输线使用每0.3m(约1英尺)有50PF电容的非平衡屏蔽双绞线时,传输距离随传输速率的增加而减小。
当比特率超过1000bps时,最大传输距离迅速下降,如9600bps时最大距离下降到只有76m(约250英尺)。

80C51串行口结构:

SBUF:串行口寄存器。
TXD:P3^1。
RXD:P3^0。
(TXD和RXD进行串口传送) 每发送/接收到一个字节,申请一个中断,控制SBUF把数据取走。 有两个物理上独立的接收、发送缓冲器SBUF,他们占用同一地址99H;(一个负责发,一个负责收)
接收器是双缓冲结构;
发送缓冲器,因为发送时CPU是主动的,不会产生重叠错误。

80C51串行口的控制寄存器:

SCON是一个特殊功能寄存器,用以设定串行口的工作方式、接收/发送控制以及设置状态标志。 7 6 5 4 3 2 1 0
SM0 SM1 SM2 REN TB8 RB8 TI RI SM0和SM1为工作方式选择位,可选择四种工作方式。(用的最多的是方式1)(方式1为01) SM2:多机通信控制位,主要用于方式2和方式3。
当SM2=1时,可利用收到的RB8来控制是否激活RI(RB8=0时不激活RI,收到的信息丢弃;RB8=1时收到的数据寄存带SBUF,并激活RI,在中断服务中将数据从SBUF中读走)。
当SM2=0时,不论收到的RB8为0或1,均可以使收到的数据进入SBUF并激活RI(即此时RI不受RB8控制)。
通过控制SM2,可实现多机通信。
(方式0或方式1,SM2直接置0) REN:允许串行接收位。由软件置REN=1,则启动串行口接收数据。若软件置REN=0,则禁止接收。 TB8:方式2或方式3中,是发送数据的第9位,可用软件规定其作用。可作奇偶校验位,或在多机通信中作为地址帧/数据帧的标志位。
方式0或方式1中,该位未用。 RB8:在方式2或方式3中,是接收到数据的第9位,作为奇偶校验位或地址帧/数据帧的标志位。
在方式1中,若SM2=0,则RB8是接收到的停止位。 TI:发送中断标志位。
在方式0时,当串行发送第8位数据结束, 或在其他方式,串行发送停止位的开始时,由内部硬件使TI置1,向CPU发中断申请。
在中断服务程序中,必须用软件将其清0,取消此中断申请。 RI:接收中断标志位。
在方式0时,当串行发送第8位数据结束, 或在其他方式,串行接收停止位的中间时,由内部硬件使RI置1,向CPU发中断申请。
也必须在中断服务程序中,用软件将其清0,取消此中断申请。 PCON中只有一位SMOD与串行口工作有关。
SMOD(PCON.7):波特率倍增位。在串行口方式1、方式2、方式3时,波特率与SMOD有关,当SMOD=1时,波特率提高一倍。复位时,SMOD=0。

工作方式:

方式1:

10位数据的异步通信口。 TXD为数据发送引脚,RXD为数据接收引脚。
1位起始位,8位数据位,1位停止位。 写入SBUF: SBUF=(要发送的数据,比如 0x01);

波特率计算:

在串行通信中,收发双方对发送或接收数据的速率要有约定。通过软件可对单片机串行口编程为四种工作方式。
其中方式0和方式2的波特率是固定的。
方式1和方式3的波特率是可变的,由定时器T1的溢出率来决定。 串行口的四种工作方式对应三种波特率。
由于输入的移位时钟的来源不同,所以,各种方式的波特率计算公式也不相同。 方式0的波特率 = fosc/12
方式2的波特率 = (2(SMOD)/64)· fosc (2(SMOD)表示2的SMOD次方)
方式1的波特率 =(2(SMOD)/32)·(T1溢出率)
方式3的波特率 =(2(SMOD)/32)·(T1溢出率)
(fosc为晶振频率) 当T1作为波特率发生器时,最典型的用法是使T1工作在自动再装入的8位定时器方式(即方式2,且TCON的TR1=1,以启动定时器)。这时溢出率取决于TH1中的计数值。(不断把TH1中的数拿到TL1中)
* T1 溢出率 = fosc /{12×[256 -(TH1)]} *
在单片机的应用中,常用的晶振频率为:12MHz和11.0592MHz。所以,选用的波特率也相对固定。常用的串行口波特率以及各参数的关系如表所示。

初始化:

串行口工作之前,应初始化,设置产生波特率的定时器1、串行口控制和中断控制。
确定T1的工作方式(编程TMOD寄存器);
计算T1的初值,装载TH1、TL1;
启动T1(编程TCON中的TR1位);
确定串行口控制(编程SCON寄存器);
串行口在中断方式工作时,要进行中断设置(编程IE、IP寄存器)。 //查询法(看是否收到数据) #include void main() { TMOD=0x20; //设置定时器1为工作方式 TH1=0xfd; //常数定时器 TL1=0xfd; TR1=1; //打开定时器1 REN=1; //打开接收口 SM0=0; SM1=1; //设置串口的工作方式 while(1) { if(RI==1) { RI=0; //硬件自动置1,手动置0 P1=SBUF; //SBUF是你收到的数据 } } } //电脑发送数据由单片机串口接收数据并执行 //中断法 #include void ding() interrupt 4 //interrupt 4 表示串口中断 { RI=0; P1=SBUF; } void main() { TMOD=0x20; //设置定时器1为工作方式 TH1=0xfd; //常数定时器 TL1=0xfd; TR1=1; //打开定时器1 REN=1; //打开接收口 SM0=0; SM1=1; //设置串口的工作方式 EA=1; ES=1; //串口中断允许位,打开中断 while(1); } //接收数据的同时发送数据 #include unsigned char flag, a; void ser() interrupt 4 { RI=0; P1=SBUF; a=SBUF; flag=1; } void main() { TMOD=0x20; //设置定时器1为工作方式 TH1=0xfd; //常数定时器 TL1=0xfd; TR1=1; //打开定时器1 REN=1; //打开接收口 SM0=0; SM1=1; //设置串口的工作方式 EA=1; ES=1; //串口中断允许位,打开中断 while(1) { if(flag==1) //如果收到了数据 { ES=0; //关闭串口中断,防止TI置为1时进入中断 flag=0; SBUF=a; // SBUF在左边是发送寄存器,在右边是接收寄存器 while(TI==0); //发送完后TI会置1,会再次进入中断 TI=0; ES=1; //退出if后仍能发送数据进入中断 } } }

液晶显示:

#include #define uchar unsigned char #define uint unsigned int sbit lcden=P3^4; sbit lcdrs=P3^5; sbit duan=P2^6; sbit wei=P2^7; uchar table[]="Hello world!"; uchar table1[]="Yeah!"; uchar num; void delay(uint z) { uint x,y; for(x=z;x>0;x--) for(y=110;y>0;y--); } void write_com(uchar com) //命令函数,读指令 { lcdrs=0; //按照操作时序使RS=L P0=com; delay(5); lcden=1; //高电平,持续一段时间 delay(5); lcden=0; } void write_data(uchar date) //命令函数,读数据 { lcdrs=1; //按照操作时序使RS=H P0=date; delay(5); lcden=1; //高电平,持续一段时间 delay(5); lcden=0; } void init() //初始化函数 { duan=0; wei=0; //数码管关掉防止数码管和液晶屏一起显示 lcden=0; write_com(0x38); //写指令 write_com(0x0f); write_com(0x06); write_com(0x01); //清屏 write_com(0x80); //数据指针初始化 80+0 指向第一个 } void main() { init(); for(num=0;num<12;num++) { write_data(table[num]); delay(20); } write_com(0x80+0x40); //重设数据指针,40为第二行开始 for(num=0;num<5;num++) { write_data(table1[num]); delay(300); } while(1); }

IIC串行总线:

IIC总线的数据传送:

时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。 SCL线为高电平期间,SDA线由高电平向低电平的变化表示起始信号;
SCL线为高电平期间,SDA线由低电平向高电平的变化表示终止信号。 起始和终止信号都是由主机(一般为单片机)发出的。
在起始信号产生后,总线就处于被占用的状态;
在终止信号产生后,总线就处于空闲状态。 接收器件收到一个完整数据字节,有可能完成其他动作,这时可以把SCL拉低,等待它准备好接受下一个,再拉高。 每个字节必须8位长度,先传最高位(MSB),每一个被传送的字节后面都必须跟随一位应答位(即一帧共有9位)

数据帧格式:

IIC总线上传送的数据信号是广义的,既包括地址信号,又包括真正的数据信号。 在起始信号后必须传送一个从机的地址(7位)。
第8位是数据的传送方向位(R/T)。(下一次)
用“0”表示主机发送数据(T),“1”表示主机接收数据(R)
每次数据传送总是由主机产生的终止信号结束。
但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。

从机的地址:

从机的地址由固定部分和可编程部分组成。
在一个系统中可能希望接入多个相同的从机。
从机地址中可编程部分决定了可接入总线该类器件的最大数目。 如一个7位寻址位有4位是固定位,3位是可编程位。
这时仅能寻址8个同样的器件,即可以有8个同样的器件接入到该IIC总线系统中。

总线数据传送的模拟:

主机可以采用不带IIC总线接口的单片机(如80C51、AT89C2051等单片机)
利用软件实现IIC总线的数据传送,即软件与硬件结合的信号模拟。 为了保证数据传送的可靠性。
标准的IIC总线的数据传送有严格的时序要求。 起始信号: void T2CStart(void) { SDA=1; SomeNop(); //延时(>4.7微秒) SCL=1; SomeNop(); SDA=0; SomeNop(); } 终止信号: void l2cStop(void) { SDA=0; SomeNop(); SCL=1; SomeNop(); SDA=1; SomeNop(); }

移位操作:

左移时最低位补0,最高位移入PSW的CY位。
右移时最高位保持原数,最低位移除。 #include #define uchar unsigned char #define uint unsigned int sbit sda=P2^0; sbit scl=P2^1; void delay() { ; ;} void delay1(uchar x) { uchar a,b; for(a=x;a>0;a--) for(b=100;b>0;b--); } void start() //起始信号,SCL高电平期间SDA从高到低 { sda=1; delay(); scl=1; delay(); sda=0; delay(); } void stop() //终止信号,SDA从低到高 { sda=0; delay(); scl=1; delay(); sda=1; delay(); } void respons() //应答信号 { uchar i; scl=1; delay(); while(sda==1 && i<250) i++; //SDA有应答或者时间超出 scl=0; //第九个时钟结束 delay(); } void init() { sda=1; //把线全部释放置为1 scl=1; } void write_byte(uchar date) //写入一个字节 { uchar i,temp; temp=date; scl=0; delay(); for(i=0;i<8;i++) { temp=temp<<1; //最高位移入CY当中 sda=CY; delay(); scl=1; //使数据稳定 delay(); scl=0; delay(); } sda=1; //数据总线不用的话就释放,不释放无法收到低电平信号 delay(); } uchar read_byte() { uchar i, j, k; scl=0; delay(); sda=1; delay(); for(i=0;i<8;i++) { scl=1; delay(); j=sda; //SDA为读回来的一位 k=(k<<1)|j; //左移最低位补0,j不是1就是0 scl=0; delay(); } return k; } void write_add(uchar address, uchar date) //指定地址写一个数据 { init(); start(); write_byte(0xa0); //器件地址 respons(); write_byte(address); //器件内部存储器地址 respons(); write_byte(date); //传送数据 respons(); stop(); } uchar read_add(uchar address) //指定地址读取 { uchar date; start(); write_byte(0xa0); respons(); write_byte(address); respons(); start(); write_byte(0xa1); respons(); date=read_byte(); stop(); return date; } void main() { write_add(23,0xaa); delay1(100); //不能太快,停下再开始要延迟 P1=read_add(23); while(1); }