前言
蓝桥杯的这些通信协议的外设好像会难倒很多人,但是实际上是没有那么难的,基本上记住这些芯片的套路就能搞明白怎么去写。
硬件略解
这里就不略解硬件啦要,因为都是和上一章的DS18B20一样很简答的硬件连接方式。值得注意的一点是芯片的连接方式决定了器件的IIC地址,这一点在芯片手册是有提到的
A0 A1 A2都是负责这件事的。
软件详解
其实软件发生了很多奇奇怪怪的事情导致我调IIC,花费了较多的时间,其实很多时候都不是IIC的时序问题是我们操作器件的问题。
下面是IIC.c的文件,其实我的操作就是把他的软件延时改成了33个nop(我看到很多资料让我这么改,我朋友告诉我不改他也调通了,很迷),以及在最后加了一个IIC_Ack(bit ackbit)函数。
/*
程序说明: IIC总线驱动程序
软件环境: Keil uVision 4.10
硬件环境: CT107单片机综合实训平台 8051,12MHz
日 期: 2011-8-9
*/
#include "intrins.h"
#include "iic.h"
#include "common.h"
#define somenop \
{ \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
_nop_(); \
}
//总线引脚定义
sbit SDA = P2 ^ 1; /* 数据线 */
sbit SCL = P2 ^ 0; /* 时钟线 */
void IIC_Delay(unsigned char i)
{
do
{
_nop_();
} while (i--);
}
//总线启动条件
void IIC_Start(void)
{
SDA = 1;
SCL = 1;
somenop;
SDA = 0;
somenop;
SCL = 0;
}
//总线停止条件
void IIC_Stop(void)
{
SDA = 0;
SCL = 1;
somenop;
SDA = 1;
somenop;
}
//发送应答
void IIC_SendAck(bit ackbit)
{
SCL = 0;
SDA = ackbit; // 0:应答,1:非应答
somenop;
SCL = 1;
somenop;
SCL = 0;
SDA = 1;
somenop;
}
//等待应答
bit IIC_WaitAck(void)
{
SDA = 1;
somenop;
SCL = 1;
somenop;
if (SDA)
{
SCL = 0;
IIC_Stop();
return 0;
}
else
{
SCL = 0;
return 1;
}
}
//通过I2C总线发送数据
void IIC_SendByte(unsigned char byt)
{
unsigned char i;
for (i = 0; i < 8; i++)
{
SCL = 0;
somenop;
if (byt & 0x80)
SDA = 1;
else
SDA = 0;
somenop;
SCL = 1;
byt <<= 1;
somenop;
}
SCL = 0;
}
//从I2C总线上接收数据
unsigned char IIC_RecByte(void)
{
unsigned char i, da;
for (i = 0; i < 8; i++)
{
SCL = 1;
somenop;
da <<= 1;
if (SDA)
da |= 1;
SCL = 0;
somenop;
}
return da;
}
void IIC_Ack(bit ackbit)
{
if (ackbit)
{
SDA = 0;
}
else
{
SDA = 1;
}
somenop;
SCL = 1;
somenop;
SCL = 0;
SDA = 1;
somenop;
}
以及接下来的是PCF8591,也就是AD/DA芯片的使用,注意一点的是,这芯片一次只能使用AD和DA功能中选一个。比如说你想让DA一直输出个3v,还要读一下AD,那个3v就会消失。
#include "PCF8591.h"
//PCF8591 8位adc 基准5v
//转换公式为dat/255*5
uchar Read_AINX(uchar num)
{
uchar dat;
IIC_Start(); //IIC总线起始信号
IIC_SendByte(0x90); //PCF8591的写设备地址
IIC_WaitAck(); //等待从机应答
IIC_SendByte(num); //写入PCF8591的控制字节
IIC_WaitAck(); //等待从机应答
IIC_Stop(); //IIC总线停止信号
IIC_Start(); //IIC总线起始信号
IIC_SendByte(0x91); //PCF8591的读设备地址
IIC_WaitAck(); //等待从机应答
dat = IIC_RecByte(); //读取PCF8591通道3的数据
IIC_Ack(0); //产生非应答信号
IIC_Stop(); //IIC总线停止信号
return dat;
}
void SetDACOut(uchar val)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x40);
IIC_WaitAck();
IIC_SendByte(val);
IIC_WaitAck();
IIC_Stop();
}
EEPROM存储芯片用起来也很简单。需要注意的是EEPROM的写单个字节的速度较慢,每次写大概需要耗时5ms,所以说如果你写完立刻读是读不到的。
#include "eeprom.h"
//@参数:add是写的地址,从0x00-0xff可以写
//@参数:data是写的数据
bit write_24c02(unsigned char add, unsigned char date)
{
uchar ack;
EA = 0;
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_SendByte(date);
ack = IIC_WaitAck();
IIC_Stop();
EA = 1;
return ack;
}
unsigned char read_24c02(unsigned char add)
{
unsigned char temp;
EA = 0;
IIC_Start();
IIC_SendByte(0xa0); //发送器件地址
IIC_WaitAck();
IIC_SendByte(add); //发送要操作的地址
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0xa1); //发送读操作
IIC_WaitAck();
temp = IIC_RecByte(); //读一字节
IIC_Ack(0);
IIC_Stop();
EA = 1;
return temp;
}