【嵌入式】如何利用MCU的GPIO的进行I2C总线主接口设计
0赞在进行基于MCU的电路设计时,经常需要和外围提供I2C接口的芯片通信,虽然市场上有专用I2C总线接口芯片,但是地址可选范围小、性能指标固定、功能单一、使用不方便。根据I2C总线的电气特性及其通讯协议,利用MCU的GPIO可以很方便地实现I2C总线的通讯接口,且具有高速、易调试、可以灵活地实现地在线配置等优点,同时大大地减少了系统的开发周期。
I2C总线系统由两根总线即SCL(串行时钟)线和SDA(串行数据)线构成。I2C总线主从器件之间传送的一次数据称为一帧,由启动信号、地址码、若干数据字节、应答位以及停止信号等组成。通讯启动时,主器件发送一个启动信号(当SCL线上是高电平时,SDA线上产生一个下跳沿)、从器件的地址(唯一的7位地址码)和1位读写方向标志位;通讯停止时,主器件发送一个停止信号(当SCL线上是高电平时,SDA线上产生一个上跳沿)。在数据传送过程中。当SCL线上是高电平时,必须保证SDA线上的数据稳定,传完一个字节的数据,必须由从器件送回一个应答信号。总线的传输速率为100kbit/s(标准)-400kbit/s(快速)。
具体的协议细节可以参考I2C标准协议规范进行查看。以下的连接为I2C标准协议规范,感兴趣的可以下载。
http://pan.baidu.com/share/link?shareid=482125&uk=3355540905
本文利用新唐的M051单片机来模拟I2C总线,可以实时更新I2C器件的地址,并根据不同的器件地址宽度或者数据宽度进行更新程序顶层应用,很方便的实现想要的功能。本模拟程序已成功应用于各个项目中。
1.先定义宏变量,方便使用。
#define Set_SDA DrvGPIO_SetBit(E_PORT2, 6) //设置p2.6为SDA管脚 并置1
#define Clr_SDA DrvGPIO_ClrBit(E_PORT2, 6) //SDA置0
#define Set_SCL DrvGPIO_SetBit(E_PORT2, 5) //设置p2.6为SCL管脚 SCL置1
#define Clr_SCL DrvGPIO_ClrBit(E_PORT2, 5)// SCL置0
#define SET_SDA_OUTPUT DrvGPIO_Open(E_PORT2, 6,E_IO_OUTPUT) //SDA管脚设置为输出状态
#define SET_SDA_INPUT DrvGPIO_Open(E_PORT2, 6,E_IO_INPUT) //SDA管脚设置为输入状态
#define GET_SDA_DATA DrvGPIO_GetBit(E_PORT2, 6) //读取SDA管脚数据状态
2.START状态的 实现
void I2C_Start(void)
{
/* SDA -------_________
SCL ------------____ */
SET_SDA_OUTPUT;
Set_SDA;
Set_SCL;
delay(DELAY_TIME);
Clr_SDA;
delay(DELAY_TIME);
Clr_SCL;
delay(DELAY_TIME);
}
3.发送单个字节
void I2C_Send_Byte(unsigned char data)
{
/* MSB
SDA ______-------------__________
SCL _________-------_____________ */
unsigned char i = 8;
SET_SDA_OUTPUT;
Clr_SDA;
Clr_SCL;
delay(DELAY_TIME);
while( i-- )
{
if (data&0x80)
Set_SDA;
else
Clr_SDA;
delay(DELAY_TIME);
Set_SCL;
delay(DELAY_TIME);
Clr_SCL;
delay(DELAY_TIME);
Clr_SDA;
delay(DELAY_TIME);
data = data << 1;
}
}
void Check_Ack(void)
{
/* SDA ____zzzzzzzzzzzzz
SCL ____------_______ */
unsigned char a=1;
SET_SDA_INPUT;
a = 1;
Set_SCL; // SCL = 1
delay(DELAY_TIME);
a=GET_SDA_DATA;
delay(DELAY_TIME);
if(a==1)
uart_printf("not receive ack\r\n");
else
uart_printf("receive ack\r\n");
while(a)
{
a=GET_SDA_DATA;
delay(DELAY_TIME);
}
Clr_SCL;
SET_SDA_OUTPUT;
Clr_SDA;
delay(DELAY_TIME);
}
5.停止位实现
void I2C_Stop(void)
{
/* SDA _________--------
SCL ____------------- */
SET_SDA_OUTPUT;
delay(DELAY_TIME);
Clr_SDA;
Clr_SCL;
delay(DELAY_TIME);
Set_SCL;
delay(DELAY_TIME);
Set_SDA;
delay(DELAY_TIME);
}
6.接收一个字节
unsigned char I2C_Receive_Byte(void)
{
unsigned char rn = 0, data=0,rr=0x80;
SET_SDA_INPUT;
Clr_SCL;
delay(DELAY_TIME);
for(rn=0;rn<8;rn++)
{
Set_SCL;
delay(DELAY_TIME);
if( GET_SDA_DATA)
data|=rr;
else
data&=~rr;
delay(DELAY_TIME);
rr>>=1;
Clr_SCL;
delay(DELAY_TIME);
}
delay(DELAY_TIME);
return data;
}
7.N_ack实现
void I2C_Nack(void)
{
/* SDA ___--------_____
SCL ____------_______ */
SET_SDA_OUTPUT;
Set_SDA;
delay(DELAY_TIME);
Set_SCL;
delay(DELAY_TIME);
Clr_SCL;
delay(DELAY_TIME);
Clr_SDA;
delay(DELAY_TIME);
}
8.延时函数
void delay(unsigned int value)
{
unsigned int i;
for(i=0;i<value;i++)
;
}
1 读写 24C64系列EEPROM
unsigned char write_e2prom(unsigned int addr, unsigned char wdata)
{
unsigned char H_ADD,L_ADD;
H_ADD=addr<<8;
L_ADD=addr&0xff;
I2C_Start();
I2C_Send_Byte(0xa0);//24C64地址
Check_Ack();
I2C_Send_Byte(H_ADD); //地址高字节
Check_Ack();
I2C_Send_Byte(L_ADD);//地址
Check_Ack();
I2C_Send_Byte(wdata);
Check_Ack();
I2C_Stop();
}
unsigned char read_e2prom(unsigned int rdaddr)
{
unsigned char rdata;
unsigned char H_ADD,L_ADD;
H_ADD=addr<<8;
L_ADD=addr&0xff; //16位地址宽度
I2C_Start();
I2C_Send_Byte(0xa0);
Check_Ack();
I2C_Send_Byte(H_ADD);
Check_Ack();
I2C_Send_Byte(L_ADD);
Check_Ack();
I2C_Start();
I2C_Send_Byte(0xa1);
Check_Ack();
rdata = I2C_Receive_Byte();
I2C_Nack();
I2C_Stop();
return(rdata);
}
2.读写ADV7611
void write_adv7611register(unsigned char device_addr,unsigned char register_addr,unsigned char udata)
{
I2C_Start();
I2C_Send_Byte(device_addr);
Check_Ack();
I2C_Send_Byte(register_addr);
Check_Ack();
I2C_Send_Byte(udata);
Check_Ack();
I2C_Stop();
}
unsigned char read_adv7611register(unsigned char wr_device_addr,unsigned char register_addr)
{
unsigned char rdata=0;
I2C_Start();
I2C_Send_Byte(wr_device_addr); //8位地址宽度
Check_Ack();
I2C_Send_Byte(register_addr);
Check_Ack();
I2C_Start();
I2C_Send_Byte(wr_device_addr|0x01);
Check_Ack();
rdata = I2C_Receive_Byte();
I2C_Nack();
I2C_Stop();
return(rdata);
}
根据不同的应用范围可以根据实际情况调整delay的参数值改变I2C的速率。而对于不同的器件的地址和数据宽度,可以自由扩展,顶层应用,在实际项目使用中方便,并且易于调试。