C51学习笔记
1、学习三极管的使用
三极管一共有 3 个极,从图 3-6 来看,横向左侧的引脚叫做基极(base),中间有一个箭头, 一头连接基极,另外一头连接的是发射极 e(emitter),那剩下的一个引脚就是集电极 c(collector)。箭头朝内 PNP,导通电压顺箭头过,电压导通, 电流控制。
只要箭头外侧比箭头内侧电压高0.7V,e和c级就会导通。也就是通过b级控制ec级,并且可以用低电压去控制高电压如下图使用NPN三极管:
当IO输出高电平,OUT输出0V,IO输出低电平,OUT输出12V。
同时ec间电流由eb间电流控制 Ieb*β=Iec . 特殊值:红色贴片LED压降约为2V,硅三极管导通时be间压降约为0.7V。
2、学习定时器
时钟周期T 是时序中最小的时间单位,T=1/时钟源频率。开发板上用的晶振11.0592M,那么T=1/11059200 秒。C51的机器周期一般为12个始终周期,12/11059200 秒。定时器内部有一个寄存器,每过一个机器周期寄存器的值就会加一。可以通过该寄存器来知晓经过的时间。
C51有以下重要寄存器
TCON:控制寄存器 TMOD:模式寄存器
TMOD可以用来确定寄存器的工作模式,通常使用定时器模式1和模式2.
模式1是 THn 和 TLn 组成了一个 16 位的定时器,计数范围是 0~65535,溢出后,只 要不对 THn 和 TLn 重新赋值,则从 0 开始计数。模式 2,是 8 位自动重装载模式,只有 TLn 做加 1 计数,计数范围 0~255,THn 的值并不发生变化,而是保持原值,TLn 溢出后,TFn 就直接置 1 了,并且 THn 原先的值直接赋给 TLn,然后 TLn 从新赋值的这个数字开始计数。 这个功能可以用来产生串口的通信波特率。
C/T = 0 的时候,一个机器周期 TL 就会加 1 一次, C/T =1 的时候,T0 引脚即 P3.4 引脚来一个脉冲,TL 就加 1 一次,这也就是计数器功能。
使用步骤:
第一步:设置特殊功能寄存器 TMOD,配置好工作模式。
第二步:设置计数寄存器 TH0 和 TL0 的初值。
第三步:设置 TCON,通过 TR0 置 1 来让定时器开始计数。
第四步:判断 TCON 寄存器的 TF0 位,监测定时器溢出情况。
用定时器定时时间:设一个机器周期T,计数次数x ,假设定时0.02S,
则x = 0.02/Tx*12/11059200=0.02,得到 x= 18432。那么初值 y = 65536 - 18432 = 47104, 16进制是0xB800,也就是 TH0 = 0xB8,TL0 = 0x00。
3、学习中断
以下是C51的一些中断寄存器
在触发中断时,会进入一个中断函数,需要使用interrupt X 来“绑定”中断函数。X为对应的中断部件的编号。如void InterruptTimer0() interrupt 1 绑定了该函数为定时器0中断函数。
抢占优先级
这些位中被赋1的位能够抢占未赋1的中断。
使用外部中断的例子:
//中断初始化
void Interrupt_eint()
{
EX0 = 1; //开启外部中断0
IT0 = 1; //中断触发0:下降沿触发 1:高电平触发
EA = 1; //开启总中断
PX0 = 1; //将外部中断的优先级提高
}
//外部0中断服务函数
void int0() interrupt 0
{
uchar j = 0;
for(j = 0;j<=3;++j)
{
DisplayNum(j);
delay_ms(1000);
}
}
4、有关单片机IO口
对于51单片机来说,复位后,单片机内部的各专用寄存器的状态:P0~P3口是高电平。
准双向IO口:C51单片机的P1~P3口上电默认为准双向IO口
这种具有上拉的准双向 IO 口,如果要正常读取外部信号的状态,必须首先得保证自己内部输出的是 1,如果内部输出 0,则无论外部信号是 1 还是 0,这个引脚读进来的都是 0。
开漏输出和准双向 IO 的唯一区别,就是开漏输出把虚线框内部的上拉电阻去掉了。标准
51 单片机的 P0 口默认就是开漏输出,如果要用让它输出高电平外部需要加上拉电阻。
上拉电阻的使用:
1、 OC 门要输出高电平,必须外部加上拉电阻才能正常使用,其实 OC 门就相当于单片机 IO 的开漏输出。
2、 加大普通 IO 口的驱动能力。STC89C52 内部是 20K 的上拉电阻,所以最大输出电流是 250uA,因此外部加个上拉电阻,可以增大高电平时电流的输出能力。
3、 在电平转换电路中,比如5V 转 12V 的电路中,上拉电阻其实起到的是限流电阻的作用。
4、 单片机中未使用的引脚,比如总线引脚,引脚悬空时,容易受到电磁干扰而处于紊乱状态,虽然不会对程序造成什么影响,但通常会增加单片机的功耗,加上一个对 VCC 的上拉电阻或者一个对 GND 的下拉电阻后,可以有效的抵抗电磁干扰。
设计:
1、 从降低功耗的方面考虑应当足够大,因为电阻越大,电流越小。
2、 从确保足够的引脚驱动能力考虑应当足够小,电阻小了,电流才能大。
5、单片机PWM使用
PWM 是 Pulse Width Modulation 的缩写,它的中文名字是脉冲宽度调制,一种说法是它利用微处理器的数字输出来对模拟电路进行控制的一种有效的技术,其实就是使用数字信号达到一个模拟信号的效果。
这是一个周期是 10ms,即频率是 100Hz 的波形,但是每个周期内,高低电平脉冲宽度各不相同,这就是 PWM 的本质。第一段占空比为40%,第二段60%,第三段80%。通过改变高电平所占一个周期的时间,可以达到类似改变电压的效果。
使用代码:
/* 配置并启动PWM,fr-频率,dc-占空比 */
void ConfigPWM(unsigned int fr, unsigned char dc)
{ unsigned int high, low; unsigned long tmp;
tmp = (11059200/12) / fr; //计算一个周期所需的计数值
high = (tmp*dc) / 100; //计算高电平所需的计数值
low = tmp - 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;
TMOD &= 0xF0; //清零T0的控制位
TMOD |= 0x01; //配置T0为模式1
TH0 = HighRH; //加载T0重载值
TL0 = HighRL;
ET0 = 1; //使能T0中断
TR0 = 1; //启动T0
PWMOUT = 1; //输出高电平
}
/* 关闭PWM */ void ClosePWM()
{
TR0 = 0; //停止定时器
ET0 = 0; //禁止中断
PWMOUT = 1; //输出高电平
}
/* T0中断服务函数,产生PWM输出 */
void InterruptTimer0() interrupt 1
{
if (PWMOUT == 1) //当前输出为高电平时,装载低电平值并输出低电平
{
TH0 = LowRH; TL0 = LowRL;
PWMOUT = 0;
}
else //当前输出为低电平时,装载高电平值并输出高电平
{
TH0 = HighRH; TL0 = HighRL;
PWMOUT = 1;
}
}
6、单片机UART使用
单片机 1 想给单片机 2 发送数据时,比如发送一个 0xE4 这个数据,用二进制形式表示就是 0b11100100,在 UART 通信过程中,是低位先发,高位后发的原则,那么就让 TXD 首先拉低电平,持续一段时间,发送一位 0,然后拉高,再持续一段时间,发送了一个1
这段“持续的时间”就是1/baud(波特率)。在通信时两个单片机波特率要一致。
在实际发送时发一个字节要发10位,因为要加上起始位和中止位。
51 单片机内部存在这样一个 UART 模块,控制它的寄存器为SCON:
其中模式1便是8位数据加一位起始位和一位停止位的模式。
要使用此模块,需要用定时器给此模块提供采样定时,也就是给它提供波特率,作为它的波特率发生器。对于 STC89C52 单片机来讲,这个波特率发生器只能由定时器 T1 或定时器 T2 产生,采用T1产生波特率时,需要使用T1的模式2(自动重装载模式),且必须关闭T1的中断。
定时器的重载值计算公式: TH1 = TL1 = 256 - 晶振值/12 /2/16 /波特率
其中“16”指的是在数据采集时,模块实际上会将一个位检测16次。和波特率有关的还有一个寄存器,是一个电源管理寄存器 PCON,他的最高位可以把波特率提高一倍,也就是如果写 PCON |= 0x80 以后,计算公式就成了: TH1 = TL1 = 256 - 晶振值/12 /16 /波特率
可以设需要计数的次数为X,那么X* (12/晶振值) = 一次采样的时间 = 1/(波特率*16).
使用代码
/* 串口配置函数,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
}
/* UART中断服务函数 */
void InterruptUART() interrupt 4
{ if (RI) //接收到字节
{
RI = 0; //手动清零接收中断标志位
SBUF = SBUF + 1; //接收的数据+1后发回,左边是发送SBUF,右边是接收SBUF
}
if (TI) //字节发送完毕
{
TI = 0; //手动清零发送中断标志位
}
}
7、基于1602液晶的时序图学习
读操作时序的 RS 引脚和 R/W 引脚,这两个引脚先进行变化,不管它原来是什么。读指令还是读数据,都是读操作,而且都有可能,所以 RS 引脚既有可能是置为高电平,也有可能是置为低电平,而 RS 和 R/W 变化了经过 Tsp1 这么长时间后,使能引脚 E 才能从低电平到高电平发生变化。
而使能引脚 E 拉高经过了 tD 这么长时间后,LCD1602 输出 DB 的数据就是有效数据了,我们就可以来读取 DB 的数据了。读完了之后,先把使能 E 拉低,经过一段时间tHD1、tHD2后 RS、R/W 和 DB 才可以继续变化。
tC:指的是使能引脚 E 从本次上升沿到下次上升沿的最短时间是 400ns,而我们单片机因为速度较慢,一个机器周期就是 1us 多,而一条 C 语言指令肯定是一个或者几个机器周期的,所以这个条件完全满足。
tPW:指的是使能引脚 E 高电平的持续时间最短是 150ns,同样由于我们的单片机比较慢,这个条件也完全满足。
tR, tF:指的是使能引脚 E 的上升沿时间和下降沿时间,不能超过 25ns,别看这个数很小,其实这个时间限值是很宽裕的,我们实际用示波器测了一下开发板的这个引脚上升沿和下降沿时间大概是 10ns 到 15ns 之间,完全满足。
tSP1:指的是 RS 和 R/W 引脚使能后至少保持 30ns,使能引脚 E 才可以变成高电平,这个条件同样也完全满足。
tHD1:指的是使能引脚 E 变成低电平后,至少保持 10ns 之后,RS 和 R/W 才能进行变化,这个条件也完全满足。
tD:指的是使能引脚 E 变成高电平后,最多 100ns 后,1602 就把数据送出来了,那么我们就可以正常去读取状态或者数据了。
tHD2:指的是读操作过程中,使能引脚 E 变成低电平后,至少保持 20ns,DB 数据总线才可以进行变化,这个条件也完全满足。
tSP2:指的是 DB 数据总线准备好后,至少保持 40ns,使能引脚 E 才可以从低到高进行使能变化,这个条件也完全满足。
tHD2:指的是写操作过程中,要引脚 E 变成低电平后,至少保持 10ns,DB 数据总线才可以变化,这个条件也完全满足。
驱动代码:
/* 向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;
}
8、单片机IIC通信学习
I2C 总线是由时钟总线 SCL 和数据总线 SDA 两条线构成,连接到总线上的所有器件的 SCL 都连到一起,所有 SDA 都连到一起。I2C 总线是开漏引脚并联的结构,因此要添加上拉电阻。对于开漏电路外部加上拉电阻,就组成了线“与”的关系。总线上线“与”的关系就是说,所有接入的器件保持高电平,这条线才是高电平,而任何一个器件输出一个低电平,那这条线就会保持低电平,因此可以做到任何一个器件都可以拉低电平,也就是任何一个器件都可以作为主机,
IIC位时序:先发送起始信号:在保证SCL和SDA都是高电平的情况下,SDA产生一个下降沿。IIC通信和UART通信相反,是先发送高位。SDA数据线在SCL处于低电平时由发送方操作,在SCL处于高电平时,接收方会来读取SDA。停止信号:在保证SCL和SDA都是低电平后,先给SCL一个上升沿,再给SDA一个上升沿。
IIC字节时序:要完成完整IIC通讯,主机先在总线上发送起始信号,然后要先发送一个从机地址,从机地址只有7位,发完7位之后需要紧接着发一位读写位,0表示要发数据,1表示要读数据。在从机接收完8位后,主机再将SCL拉低,并把SDA拉高,(可理解为发送第9位0),再接着将SCL拉高,然后检测SDA的状态,如果SDA还为高,那么从机没有响应,若SDA为低,则从机发来了响应。
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=存在且空闲或写入成功
}
在整个过程中,SCL均由单片机来操作。无论是起始、读、写操作函数,在调用这些函数之后SCL被提前拉低。
I2C 通信分为低速模式 100kbit/s、快速模式 400kbit/s 和高速模式 3.4Mbit/s。在使用100Kbit/s时需要SCL 的高低电平持续时间都不短于 5us。
单片机与EEPROM用IIC通信的例子:
单片机写EEPROM:
第一步,发送 I2C 的起始信号,接着跟上I2C 的器件地址,在读写方向上选择“写”操作。
第二步,发送数据的存储地址。24C02 一共 256 个字节的存储空间,地址从 0x00~0xFF,我们想把数据存储在哪个位置,此刻写的就是哪个地址。
第三步,发送要存储的数据第一个字节、第二个字节…在写数据的过程中, EEPROM 每个字节都会回应一个“应答位 0”,来告诉我们写 EEPROM 数据成功,如果没有回应答位,说明写入不成功。
在写数据的过程中,每成功写入一个字节,EEPROM 存储空间的地址就会自动加 1,当加到 0xFF 后,再写一个字节,地址会溢出又变成了 0x00。
/* 向EEPROM中写入一个字节,addr-字节地址 */
void E2WriteByte(unsigned char addr, unsigned char dat)
{
I2CStart();
I2CWrite(0x50<<1); //寻址器件,后续为写操作
I2CWrite(addr); //写入存储地址
I2CWrite(dat); //写入一个字节数据
I2CStop();
}
单片机读EEPROM:
第一步,发送 I2C 的起始信号,接着跟上首字节,也就是我们前边讲的 I2C 的器件地址,依然在读写方向上选择“写”操作。因为是要把前七个位的器件地址“写”进去。
第二步,发送要读取的数据的地址,注意是地址而非存在 EEPROM 中的数据,通知
EEPROM 我要哪个地址的信息。
第三步,重新发送 I2C 起始信号和器件地址,并且在方向位选择“读”操作。
这三步当中,每一个字节实际上都是在“写”,所以每一个字节 EEPROM 都会回应一个
“应答位 0”。
第四步,读取从器件发回的数据,读一个字节,如果还想继续读下一个字节,就发送一个“应答位 ACK(0)”,如果不想读了,告诉 EEPROM,我不想要数据了,别再发数据了,那就发送一个“非应答位 NAK(1)”。
和写操作规则一样,我们每读一个字节,地址会自动加 1,那如果我们想继续往下读,给 EEPROM 一个 ACK(0)低电平,那再继续给 SCL 完整的时序,EEPROM 会继续往外送数据。如果我们不想读了,要告诉 EEPROM 不要数据了,那我们直接给一个 NAK(1)高电平即可。
使用代码:
unsigned char E2ReadByte(unsigned char addr) /* 读取EEPROM中的一个字节,addr-字节地址 */
{ unsigned char dat;
I2CStart();
I2CWrite(0x50<<1); //寻址器件,后续为写操作
I2CWrite(addr); //写入存储地址
I2CStart(); //发送重复启动信号
I2CWrite((0x50<<1)|0x01); //寻址器件,后续为读操作 dat = I2CReadNAK(); //读取一个字节数据
I2CStop();
Return dat;
}
EEPROM的页读写:每给EEPROM写入一个字节,实际上一点时间给它将数据存入不易丢失的储存区,这段时间它不会响应主机的任何操作即使是启示信号。可以将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();
}
}
9、单片机SPI通信学习
典型的SPI通信线有四根:
SSEL:从设备片选使能信号。如果从设备是低电平使能的话,当拉低这个引脚后,从设备就会被选中,主机和这个被选中的从机进行通信。
SCLK:时钟信号,由主机产生,和 I2C 通信的 SCL 有点类似。
MOSI:主机给从机发送指令或者数据的通道。
MISO:主机读取从机的状态或者数据的通道。
CPOL: Clock Polarity,时钟的极性。如果 SCLK 在数据发送之前和之后的空闲状态是高电平,那么CPOL=1,如果空闲状态 SCLK 是低电平,那么就是 CPOL=0。
CPHA: Clock Phase,时钟的相位。 CPHA=1,表示数据的输出是在一个时钟周期的第一个沿上。CPHA=0,表示数据的采样是在一个时钟周期的第一个沿上。
10、AD模数转换与DA数模转换
AD主要用于测量外部管脚的电压值,DA主要用于给管脚输出一个定值的电压。
A/D的主要指标有:
1、ADC 的位数
一个 n 位的 ADC 表示这个 ADC 共有 2 的 n 次方个刻度。8 位的 ADC,输出的是从 0~255 一共 256 个数字量,也就是 2 的 8 次方个数据刻度。
- 基准源
基准源,也叫基准电压,是 ADC 的一个重要指标,要想把输入 ADC 的信号测量准确,那么基准源首先要准,如果基准电压给的是5V,那么能测的最大值也只有5V。
- 分辨率
分辨率是数字量变化一个最小刻度时,模拟信号的变化量,定义为满刻度量程与 2n-1 的比值。假定 5.10V 的电压系统,使用 8 位的 ADC 进行测量,那么相当于 0~255 一共 256 个刻度把 5.10V 平均分成了 255 份,那么分辨率就是 5.10/255 = 0.02V。
- INL(积分非线性度)和 DNL(差分非线性度)
一个基准为 5.10V 的 8 位 ADC,它的分辨率就是 0.02V,用它去测量一个电压信号,得到的结果是 100,就表示它测到的电压值是 100*0.02V=2V,假定它的 INL 是 1LSB,就表示这个电压信号真实的准确值是在 1.98V~2.02V 之间的,按理想情况对应得到的数字应该是 99~101,测量误差是一个最低有效位,即 1LSB。
DNL 表示的是 ADC 相邻两个刻度之间最大的差异,单位也是 LSB。一个基准为 5.10V 的 8 位 ADC,假定它的 DNL 是 0.5LSB,那么当它的转换结果从 100 增加到 101 时,理想情况下实际电压应该增加 0.02V,但 DNL 为 0.5LSB 的情况下实际电压的增加值是在 0.01~0.03V 之间。值得一提的是 DNL 并非一定小于 1LSB,很多时候它会等于或大于 1LSB。
5、转换速率
转换速率,是指 ADC 每秒能进行采样转换的最大次数,它与 ADC 完成一次从模拟到数字的转换所需要的时间互为倒数关系。ADC 的种类比较多,其中积分型的 ADC 转换时间是毫秒级的,属于低速 ADC;逐次逼近型 ADC 转换时间是微妙级的,属于中速 ADC;并行/串行的 ADC 的转换时间可达到纳秒级,属于高速 ADC。
PCF8591 AD/DA转换器
1、2、3、4 是 4 路模拟输入,引脚 5、6、7 是 I2C 总线的硬件地址,8 脚是数字地 GND,9 脚和 10 脚是 I2C 总线的 SDA 和 SCL。12 脚是时钟选择引脚,如果接高电平表示用外部时钟输入,接低电平则用内部时钟,我们这套电路用的是内部时钟,因此 12 脚直接接 GND,同时 11 脚悬空。13 脚是模拟地 AGND。
PCF8591 的软件编程:
PCF8591 的通信接口是 I2C,片机对 PCF8591 进行初始化,一共发送三个字节即可。
在读取电压值时,需要先读一个空字节,作为给它的SCL驱动脉冲
第一个字节,和 EEPROM 类似,是器件地址字节,其中 7 位代表地址,1 位代表读写方向。地址高 4 位固定是 0b1001,低三位是 A2,A1,A0。
第二个字节将被存储在控制寄存器,用于控制 PCF8591 的功能。其中第 3 位和第 7 位是固定的 0,第 6 位是 DA 使能位,这一位置 1 表示 DA 输出引脚使能,第4位和第5位可以实现把PCF8591的4路模拟输入配置成单端模式和差分模式。2 位是自动增量控制位,当使用多个通道时,读完了通道 0,下一次再读,会自动进入通道 1 进行读取。第 0 位和第 1 位是通道选择位,00、01、10、11 代表了从 0 到 3 的一共
4 个通道选择。
第三个字节 D/A 数据寄存器,表示 D/A 模拟输出的电压值。如果只使用 A/D 功能的话,就可以不发送第三个字节。
使用代码:
/* 读取当前的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;
}
/* 设置DAC输出值,val-设定值 */
void SetDACOut(unsigned char val)
{
I2CStart();
if (!I2CWrite(0x48<<1)) //寻址PCF8591,如未应答,则停止操作并返回
{
I2CStop();
return;
}
I2CWrite(0x40); //写入控制字节
I2CWrite(val); //写入DA值
I2CStop();
}