1,在ICCAVR上编译通过。
2,今天用M8进行试验,试验成功了TIMER0的定时中断,结果自己乱点锁死了ATMEGA8,只能等待新的芯片回来。
3,为什么说这个代码靠谱,这里没有推换的1输出,符合I2C的物理规范。这样才能真正实现多I2C主器件,虽然这里只有一个主器件。
4,这里面讲ACK得检测放在每次的字节写里面非常合适。
5,Try_Write_I2C是我添加的函数,用来实现自动等待器件就绪,尝试次数n应该设置得大一些。
6,我看这里延迟应该不大,所以我觉得这里可以做一下延迟时间的调试,一般来说SCL时钟不要高于100KHZ。这里需要调试一下。
7,这里DELAY调用了一个函数,可以很方便的修改延迟时间。
8,这里用到也页读写,其实就是连续读写,这不错。
9,这个代码很好,可以修改,移植到几乎所有双向IO的单片机器系统里。
//文件名:I2C.h
//描述:AVR模拟I2C读写24C02的相关函数
#include<iom16v.h>
#include<macros.h>
#include"I2C.h"
//外部上拉电阻,PORTC.0--SCL,PORTC.1--SDA,模拟I2C协议
//当DDRC.0和DDRC.1置为输出时,拉低SDA;置为输入时,外部上拉拉高SDA
#define SCL_0 DDRC|=BIT(0)
#define SCL_1 DDRC&=~BIT(0)
#define SDA_0 DDRC|=BIT(1)
#define SDA_1 DDRC&=~BIT(1)
#define SDA_in (PINC&0X02) //判断SDA的电平
#define Page_size 8 //一页大小,8字节
//函数名;I2C_inti
//输入:无
//输出:无
//描述:初始化SDA和SCL
void I2C_inti(void)
{
PORTC&=~(BIT(0)|BIT(1));
SCL_1;
SDA_1;
}
//函数名;Delay
//输入:无
//输出:无
//描述:延时1us
void Delay(void)
{
NOP();
NOP();
NOP();
NOP();
}
//函数名;delay_ms
//输入:无
//输出:无
//描述:延时1ms
void delay_ms(uint ms)
{
uint i,j;
for(i=0;i<ms;i++)
for(j=0;j<564;j++);
}
//函数名;I2C_Start
//输入:无
//输出:无
//描述:I2C起始条件
uchar I2C_Start(void)
{
SDA_1;
SCL_1;
Delay();
SDA_0;
Delay();
SCL_0;
Delay();
return 1;
}
//函数名;I2C_Stop
//输入:无
//输出:无
//描述:I2C结束条件
void I2C_Stop(void)
{
SDA_0;
SCL_1;
Delay();
SDA_1;
Delay();
}
//函数名;Write_I2C
//输入:待写的一字节数据data
//输出:有无应答,有应答--1;无应答--0
//描述:发送一字节,返回有无应答
uchar Write_I2C(uchar data)
{
uchar ack,i;
for(i=0;i<8;i++)
{
if(data&0X80)
{
SDA_1;
}
else
{
SDA_0;
}
SCL_1;
Delay();
SCL_0;
data<<=1;
Delay();
}
Delay();
SDA_1;
Delay();
SCL_1;
Delay();
if(SDA_in)
{
ack=0;
}
else
{
ack=1;
}
SCL_0;
Delay();
return ack;
}
//函数名;Try_Write_I2C
//输入:待写的一字节数据data,如果无应答,尝试发送n次。
//输出:最终有无应答,有应答--1;无应答--0
//描述:尝试n发送一个字节,最终发送成功就返回1,n次全失败就返回0
//备注:主要用在检测器件是否就绪并等待。李伟添加 2014-4-28
uchar Try_Write_I2C(uchar data,int times)
{
uchar r ;
while(times--){
r = Write_I2C(data);
if (r==1)return 1;
}
return 0;
}
//函数名;Read_I2C
//输入:是否应答ack,0--不应答,1--应答
//输出:接受的一字节数据
//描述:接收一字节数据
uchar Read_I2C(uchar ack)
{
uchar i,temp;
temp=0;
SDA_1;
for(i=0;i<8;i++)
{
Delay();
SCL_0;
Delay();
SCL_1;
Delay();
temp<<=1;
if(SDA_in)
{
temp++;
}
}
SCL_0;
Delay();
if(!ack)
{
SDA_1;
}
else
{
SDA_0;
}
Delay();
SCL_1;
Delay();
SCL_0;
Delay();
return temp;
}
//函数名;Write_24c02
//输入:存储地址add,待写的一字节数据data
//输出:无
//描述:向24c02指定地址写一字节的数据
void Write_24c02(uchar add,uchar data)
{
I2C_Start();
Write_I2C(0XAE);
Write_I2C(add);
Write_I2C(data);
I2C_Stop();
delay_ms(10);
}
//函数名;Read_24c02
//输入:存储地址add
//输出:读出的一字节数据
//描述:向24c02指定地址读一字节的数据
uchar Read_24c02(uchar add)
{
uchar data;
I2C_Start();
Write_I2C(0XAE);
Write_I2C(add);
I2C_Start();
Write_I2C(0XAF);
data=Read_I2C(0);
I2C_Stop();
return data;
}
//函数名;Write_24c02_page
//输入:存储地址add,待写缓冲区的首地址arr
//输出:无
//描述:主机写24c02指定地址开始n个字节的数据(n<8),在一页内
void Write_24c02_page(uchar add,uchar n,uchar* arr)
{
uchar i;
I2C_Start();
Write_I2C(0XAE);
Write_I2C(add);
for(i=0;i<n;i++)
{
Write_I2C(*arr++);
}
I2C_Stop();
delay_ms(10);
}
//函数名;Write_m_24c02
//输入:存储地址add,待写缓冲区的首地址arr,待写字节数n
//输出:无
//描述:主机写24c02指定地址开始n个字节的数据
void Write_m_24c02(uchar add,uchar n,uchar* arr)
{
uchar n_left;
n_left=(uchar)Page_size-(uchar)(add&0X07); //本页剩余空间
if(n<=n_left)
{
Write_24c02_page(add,n,arr);
}
else
{
Write_24c02_page(add,n_left,arr); //写完本页
add+=n_left;
n-=n_left;
arr+=n_left;
while(n>=Page_size) // 连续写一整页
{
delay_ms(150);
Write_24c02_page(add,Page_size,arr);
add+=Page_size;
n-=Page_size;
arr+=Page_size;
}
if(n!=0)
{
Write_24c02_page(add,n,arr);
}
}
}
//函数名;Read_m_24c02
//输入:存储地址add,存储缓冲区的首地址arr,待读字节数n
//输出:无
//描述:主机读24c02指定地址开始n个字节的数据
void Read_m_24c02(uchar add,uchar n,uchar* arr)
{
uchar i;
I2C_Start();
Write_I2C(0XAE);
Write_I2C(add);
I2C_Start();
Write_I2C(0XAF);
for(i=0;i<n-1;i++)
{
*arr++=Read_I2C(1);
}
*arr=Read_I2C(0);
I2C_Stop();
}
--------------------------------------------------------------------------以下函数是修改过的并且试验通过的了------------------------------------------------------------------------------------------
//文件名:I2C.h
//描述:AVR模拟I2C读写24C32的相关函数
#include<iom8v.h>
#include<macros.h>
//外部上拉电阻,PORTD.2--SCL,PORTD.3--SDA,模拟I2C协议
//当DDRD.2和DDRD.3置为输出时,拉低SDA;置为输入时,外部上拉拉高SDA
#define SCL_0 DDRD|=BIT(2)
#define SCL_1 DDRD&=~BIT(2)
#define SDA_0 DDRD|=BIT(3)
#define SDA_1 DDRD&=~BIT(3)
#define SDA_in (PIND&0X08)
#define Page_size 8
#define uchar unsigned char
#define uint unsigned int
//函数名;I2C_inti
//输入:无
//输出:无
//描述:初始化SDA和SCL
void I2C_init(void)
{
PORTD&=~(BIT(2)|BIT(3));
SCL_1;
SDA_1;
}
//函数名;Delay
//输入:无
//输出:无
//描述:延时1us
void Delay(void)
{
NOP();
NOP();
NOP();
NOP();
}
//函数名;I2C_Start
//输入:无
//输出:无
//描述:I2C起始条件
static uchar I2C_Start(void)
{
SDA_1;
SCL_1;
Delay();
SDA_0;
Delay();
SCL_0;
Delay();
return 1;
}
//函数名;I2C_Stop
//输入:无
//输出:无
//描述:I2C结束条件
static void I2C_Stop(void)
{
SDA_0;
SCL_1;
Delay();
SDA_1;
Delay();
}
//函数名;Write_I2C
//输入:待写的一字节数据data
//输出:有无应答,有应答--1;无应答--0
//描述:发送一字节,返回有无应答
static uchar Write_I2C(uchar data)
{
uchar ack,i;
for(i=0;i<8;i++)
{
if(data&0X80)
{
SDA_1;
}
else
{
SDA_0;
}
SCL_1;
Delay();
SCL_0;
data<<=1;
Delay();
}
Delay();
SDA_1;
Delay();
SCL_1;
Delay();
if(SDA_in)
{
ack=0;
}
else
{
ack=1;
}
SCL_0;
Delay();
return ack;
}
//函数名;Read_I2C
//输入:是否应答ack,0--不应答,1--应答
//输出:接受的一字节数据
//描述:接收一字节数据
static uchar Read_I2C(uchar ack)
{
uchar i,temp;
temp=0;
SDA_1;
for(i=0;i<8;i++)
{
Delay();
SCL_0;
Delay();
SCL_1;
Delay();
temp<<=1;
if(SDA_in)
{
temp|=1;
}
}
SCL_0;
Delay();
if(!ack)
{
SDA_1;
}
else
{
SDA_0;
}
Delay();
SCL_1;
Delay();
SCL_0;
Delay();
return temp;
}
//函数名;Write_24c32
//输入:存储地址add,待写的一字节数据data
//输出:无
//描述:向24c32指定地址写一字节的数据
uchar Write_24c32(uchar dev_addr ,uint addr,uchar data)
{
uchar i ;
uchar r, addr_low ,addr_hig ;
addr_low = addr &0xff ;
addr = addr<<8 ;
addr_hig = addr&0xff;
dev_addr = dev_addr<<1;
i=10;
do {
I2C_Start();
r=Write_I2C(dev_addr);
i--;
if(i==0)return 0;
}while(r==0) ;
Write_I2C(addr_hig);
Write_I2C(addr_low);
Write_I2C(data);
I2C_Stop();
return 1 ;
}
//函数名;Read_24c02
//输入:存储地址add
//输出:读出的一字节数据
//描述:向24c02指定地址读一字节的数据
uchar Read_24c32(uchar dev_addr , uint byte_addr,uchar *dat)
{
uchar i ;
uchar r, addr_low ,addr_hig ;
addr_low = byte_addr &0xff ;
byte_addr = byte_addr<<8 ;
addr_hig = byte_addr&0xff;
dev_addr = dev_addr<<1 ;
i=100;
do {
I2C_Start();
r=Write_I2C(dev_addr);
i--;
if(i==0)return 0;
}while(r==0) ;
Write_I2C(addr_hig);
Write_I2C(addr_low);
I2C_Start();
dev_addr++;
Write_I2C(dev_addr);
*dat=Read_I2C(0);
I2C_Stop();
return 1;
}
main(){
char hex[] ={0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
unsigned int c ,i;
I2C_init();
while(1)
{
Write_24c32(0x50 ,c,(1+c&0xff)); /// 写c+1 到C这个位置
i=Read_24c32(0x50,c,hex); c=hex[0]; //读出C这个位置的数据并且保存在C这个变量里面
}
}
1,红色部分的主代码实现了单字节的写入和读出。
2,Write_24c32和Read_24c32函数实现的时候已经加入了对器件是否忙碌的等待,写函数之紧接的是读函数,没有使用延迟函数,下图是逻辑分析仪抓取并分析的片段,我们看到连续对器件询问了三次都没有得到应答,最后在第四次得到了应答,经历时间是1.6毫秒。这1.6秒的等待如何在代码里面实现呢?大多数的代码都是直接DELAY上级个毫秒,而我们这里使用了查询,实现代码如下 :
i=10;
do {
I2C_Start();
r=Write_I2C(dev_addr);
i--;
if(i==0)return 0;
}while(r==0) ;
我们观测带查询了3次就得到了器件的应答ACK,所以我们这里设置最大查询册数得10.
,
3,EEPROM的写入要经过大约1.6毫秒的时间,这点我们已经通过上面的截图测得。也就是说一次写EEPROM的操作之后,1.6毫秒内器件不再响应对他的询问。
4,我们看一下一次读操作之后,是否需要延迟后面的操作呢?先猜测一下,我觉得不需要的。看下图:
我们看到,在红点之前,是对EEPROM进行了读操作,之后对器件的询问,我们发现器件立即响应了。大家用编程器可能有个体验,读比烧写快很多,也就是这个原因。
5,如果要修改成24C02这样的单字节地址的EEPROM,只需要屏蔽掉蓝色的代码。
6,这个代码可以轻易实现页读写,就是在写数据的时候连续写多个之后再发停止位,在读数据的时候连读多个,最后一个字节才发NACK,其他的都发ACK。在大多情况下随机读写已经足够用了,因此不在这里实现页读写。
我的话:
1,I2C EEPROM 读写函数已经很完善了。
2,下步要整理一下思路,精简一下,做一个使用逻辑分析仪调试I2C的专题,可能不提到器件等待这部分。
3,下步要做的是,使用逻辑分析仪进行精密IO延迟的测定。
4,UART协议的实现。
5,TIMER的调试。