一、I2C介绍
I2C:Inter-Integrated Circuit bus,双方向的2-wire bus:SDA-serial data;SCL-serial clock.一般用于两个设备间的通信,即master和slave,slave既可以做receiver也可以做transmitter。I2C总线协议规定,任何将数据传送到总线的作为发送器,任何从总线接收数据的器件作为接收器。
数据传送由主器件控制:SCL以及起始终止条件均由master产生。
在这里介绍的主要是两种时钟芯片:DS1302和AT24C02:
二、读写字节的实现
DS1302:
RST是复位或者片选信号,因为它起着这两个功能。
时序分析:
单字节写
以上为DS1302一个字节写入的时序图,第一个是地址字节,第二个是数据字节,RST必须拉高,否则数据的输入是无效的,即RST信号控制数据信号输入的开始和结束,地址字节和数据字节的读取时上升沿有效,而且是从LSB开始读入。
单字节读:
Vivado部分:
用一个AXI GPIO的IP来组合三个信号。
代码部分:
void WriteClock(u32 address, u32 value)
{
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_OFF);//0x00
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_ON); // SLCK = off 0x01
// write command byte
u32 bitval = 0x0;
int i;
for(i = 0; i<8; i++)
{
bitval = (address >> i) & BITMASK; // sending bit for bit 0x01
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_ON);//0x03
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_ON); // SLCK = off0x01
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
}
// write value
bitval = 0x0;
for(i = 0; i<8;i++)
{
bitval = (value >> i) & BITMASK; // sending bit for bit
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_ON);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_ON); // SLCK = off
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
}
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_OFF);
}
/*
* This function reads the DS1302 clock value on the address (hex)
* provided in the parameter.
* Note: addresses can be used from the header file (seconds, minutes, hours, etc.)
* One does not need to care about setting the read bit. The function adds it, if the
* programmer forgot.
*/
u32 ReadClock(u32 address)
{
address = address | 0x01; // add read bit if not already added
u32 retval = 0x00;
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_OFF);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_ON); // SLCK = off
// write command byte
u32 bitval = 0x0;
int i;
for(i = 0; i<8; i++)
{
bitval = (address >> i) & BITMASK; // sending bit for bit
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_ON);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_ON); // SLCK = off
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_1, bitval);
}
// read value
bitval = 0x0;
XGpio_SetDataDirection(&Gpio_DS1302, CHANNEL_1, 0xff);
for(i = 0; i<8;i++)
{
//reading bit for bit
bitval = XGpio_DiscreteRead(&Gpio_DS1302, CHANNEL_1);
// setting the bit value in the return value (hex)
retval ^= (-bitval ^ retval) & (1 << i);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_ON);
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_ON); // SLCK = off
}
XGpio_DiscreteWrite(&Gpio_DS1302, CHANNEL_2, DS1302_RST_SCLK_OFF);
XGpio_SetDataDirection(&Gpio_DS1302, CHANNEL_1, 0x0);
return retval;
}
读字节和写字节有一些不同之处,第一个字节也是先写地址,然后再读数据字节,RST全程拉高,写字节的时候SCLK上升沿有效,读字节时下降沿有效。
在写一个字节的时候I/O一直保持输出状态,相反的是在读字节的时候I/O先为输出状态,然后为输入状态,且必须改变时钟信号的顺序。
AT24C02:
起始和终止条件:
void startCondition()
{
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,1);
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,1);
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,0);
delay1();
delay1();
}
void stopCondition()
{
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,0);
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,1);
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,1);
delay1();
delay1();
}
在数据传送8位后,等待或者发送一个应答信号:
char response()
{
char receive_ack;
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);//初始SCL
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,1);//将SDA拉高
XGpio_SetDataDirection(&Gpio, SDA_CHANNEL,1);//SDA放弃对总线的控制权
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,1);//SCL拉高
delay1();
receive_ack=XGpio_DiscreteRead(&Gpio,SDA_CHANNEL);//获取应答信号
delay1();
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);
delay1();
XGpio_SetDataDirection(&Gpio, SDA_CHANNEL,0);//SDA重新获取控制权
delay1();
return receive_ack;
}
此时要在SDA线上置1,放弃对总线的控制权,从而接受应答信号,接收信号完后,又将其方向设为0,重新获取控制权
单字节写和页写实现代码:
void write_add(char address,char value)
{
int isAck;
startCondition();
write_byte(0xa0);//器件地址
isAck=response();
if(isAck==0)//确认则继续写入数据
{
write_byte(address);//写地址
isAck=response();
}
if(isAck==0)
{
write_byte(value);//写入的数据
isAck=response();
}
stopCondition();
}
void write_byte(char value)
{
char bitval=0x0;
int i;
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);
delay1();
delay1();
for(i=0;i<8;i++)
{
bitval=value>>(7-i);
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);//SCL拉低准备写数据
delay1();
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,bitval);//将数据送入SDA
delay1();
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,1);//SCL拉高数据写完毕
delay1();
delay1();
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);//SCL拉低准备写数据
}
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);
delay1();
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,1);
delay1();
}
int write_Page(char page,char *value)
{
int isAck,i;
startCondition();
write_byte(0xa0);
isAck=response();
if(isAck==0)//确认则继续写入数据
{
write_byte(16*page);//写地址
isAck=response();
}
for(i=0;i<16;i++)
{
if (isAck == 0)
{
write_byte(value);
isAck=response();
}
else
break;
}
stopCondition();
return XST_SUCCESS;
}
在写操作时,第一步写入器件地址,第二步写入写地址,第三步才是要写入的数据。
读操作实现代码:
char read_byte()
{
char value=0x0;
char temple=0x0;
int i,j;
XGpio_DiscreteWrite(&Gpio,SCL_CHANNEL,0);
delay1();
XGpio_DiscreteWrite(&Gpio,SDA_CHANNEL,1);
delay1();
for(j=0;j<8;j++)
{
XGpio_DiscreteWrite(&Gpio, SCL_CHANNEL, 1);
delay1();
delay1();
temple=XGpio_DiscreteRead(&Gpio, SDA_CHANNEL);
value=(value<<1)|(temple&0x01);
XGpio_DiscreteWrite(&Gpio, SCL_CHANNEL, 0);
delay1();
delay1();
}
return value;
}
char read_add(char address)
{
int isAck;
char data;
startCondition();
write_byte(0xa0);
isAck=response();
if(isAck==0)
{
write_byte(address);
isAck=response();
startCondition();
}
if(isAck==0)
{
write_byte(0xa1);
isAck=response();
}
if(isAck==0)
{
data=read_byte();
}
stopCondition();
return data;
}
读操作和写操作有很大的区别就是,首先要写入进行读操作的从器件地址,然后写入要读的地址。这之后需要重新开始一次起始条件,再开始进行读字节。
三、HardwareDebug查看软件时序
Open Synthesized Design打开综合后的设计,然后打开debug的schematic或者Netlist设置要查看的引脚,即mark debug。
Setup Debug:
Export Hardware,启动SDK,Open Hardware Manager:
选择open target的auto connect连接到我们的板子上,然后program device打开查看时序的界面,根据之前的设计,设置触发的起始条件:
最初的状态是Idle,当点击运行时状态变为 Waiting for Trigger,当SDK部分运行或调试启动时,状态变为Full,触发开始记录时序:
最终的软件运行时序为: