IIC总线是嵌入式设备最常用的接口之一,包括51单片机在内的MCU一般都可以进行IIC通信。
IIC通信有3种类型的信号:开始信号,结束信号,和应答信号。
开始信号:SCL为高电平,SDA由高电平向低电平跳变,表示可以开始传输信号,进行通信了。
结束信号:SCL为高电平,SDA由低电平向高电平跳变,表示传输信号的时间已经过了。
应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。
CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号。这个很容易理解,就好像人的交流(通信),要建立起通信,肯定需要开始条件,好像需要约定两个人都上线了才能通信,这就是开始信号。结束信号也一样,处理器不可能一直处于与其他IC通信的状态的。而应答信号,发送方把自己要发送的数据发送出去了,但不知道对方有没有收到,所以有些情况,需要等待接收方返回应答信号,告诉发送方我已经收到了,你可以继续发送下一条数据。
值得注意的是,虽然大部分MCU都带有IIC总线接口,但实际应用中,使用的一般都是引脚模拟的IIC。
以下分别介绍IIC配置的软件实现。
引脚配置(初始化)
//初始化IIC
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB, ENABLE );
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP ; //推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOB, &GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_6|GPIO_Pin_7); //PB6,PB7 输出高
}
#define SDA_IN() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=0X80000000;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=0X30000000;}
//产生IIC起始信号
void IIC_Start(void)
{
SDA_OUT(); //sda线输出
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0;//START:when CLK is high,DATA change form high to low
delay_us(4);
IIC_SCL=0;//钳住I2C总线,准备发送或接收数据
}
//产生IIC停止信号
void IIC_Stop(void)
{
SDA_OUT();//sda线输出
IIC_SCL=0;
IIC_SDA=0;//STOP:when CLK is high DATA change form low to high
delay_us(4);
IIC_SCL=1;
IIC_SDA=1;//发送I2C总线结束信号
delay_us(4);
}
//等待应答信号到来
//返回值:1,接收应答失败
// 0,接收应答成功
u8 IIC_Wait_Ack(void)
{
u8 ucErrTime=0;
SDA_IN(); //SDA设置为输入
IIC_SDA=1;delay_us(1);
IIC_SCL=1;delay_us(1);
while(READ_SDA)
{
ucErrTime++;
if(ucErrTime>250)
{
IIC_Stop();
return 1;
}
}
IIC_SCL=0;//时钟输出0
return 0;
}
//产生ACK应答
void IIC_Ack(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=0;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//不产生ACK应答
void IIC_NAck(void)
{
IIC_SCL=0;
SDA_OUT();
IIC_SDA=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC_Send_Byte(u8 txd)
{
u8 t;
SDA_OUT();
IIC_SCL=0;//拉低时钟开始数据传输
for(t=0;t<8;t++)
{
if((txd&0x80)>>7)
IIC_SDA=1;
else
IIC_SDA=0;
txd<<=1;
delay_us(2);
IIC_SCL=1;
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC_Read_Byte(unsigned char ack)
{
unsigned char i,receive=0;
SDA_IN();//SDA设置为输入
for(i=0;i<8;i++ )
{
IIC_SCL=0;
delay_us(2);
IIC_SCL=1;
receive<<=1;
if(READ_SDA)receive++;
delay_us(1);
}
if (!ack)
IIC_NAck();//发送nACK
else
IIC_Ack(); //发送ACK
return receive;
}
下面以PCF8563为例介绍如何使用MCU的IIC与其他器件进行通信。
PCF8563是一款时钟芯片,具体的介绍请查看手册。时钟芯片大部分都是采用BCD编码的。先介绍一下BCD码和十进制码之间的转换。
可以认为,BCD码就是十进制码变成十六进制。如:59(十进制)对应的BCD码是0x59 。 10 对应0x10 。 1 对应0x01 。
互相转换的代码:
unsigned char RTC_BinToBcd2(unsigned char BINValue)
{
unsigned char bcdhigh = 0;
while (BINValue >= 10)
{
bcdhigh++;
BINValue -= 10;
}
return ((unsigned char)(bcdhigh << 4) | BINValue);
}
unsigned char RTC_Bcd2ToBin(unsigned char BCDValue)
{
unsigned char tmp = 0;
tmp = ((unsigned char)(BCDValue & (unsigned char)0xF0) >> (unsigned char)0x04) * 10;
return (tmp + (BCDValue & (unsigned char)0x0F));
}
根据手册,查得IIC器件的地址
#define PCF8563_Write (unsigned char)0xa2 //写命令
#define PCF8563_Read (unsigned char)0xa3 //读命令
然后等待PCF8563回应说我知道了,你可以写入数据了。然后就再告诉对方写入哪里,等他的回应,最后告诉他要写入什么,同样也等待回应。写完了就可以结束这次通信了
void PCF8563_Write_Byte(unsigned char REG_ADD, unsigned char dat)
{
IIC_Start();
IIC_Send_Byte(PCF8563_Write);//发送写命令并检查应答位
while(IIC_Wait_Ack());
IIC_Send_Byte(REG_ADD);
IIC_Wait_Ack();
IIC_Send_Byte(dat); //发送数据
IIC_Wait_Ack();
IIC_Stop();
}
unsigned char PCF8563_Read_Byte(unsigned char REG_ADD)
{
u8 ReData,t=0;
IIC_Start( );
IIC_Send_Byte(PCF8563_Write); //发送写命令并检查应答位
while(IIC_Wait_Ack( ));
IIC_Send_Byte(REG_ADD); //确定要操作的寄存器
IIC_Wait_Ack();
IIC_Start(); //重启总线
IIC_Send_Byte(PCF8563_Read); //发送读取命令
IIC_Wait_Ack();
ReData = IIC_Read_Byte(0); //读取数据,加发送非应答
IIC_Stop();
return ReData;
}
/******************************************************************************
参数寄存器地址宏定义
******************************************************************************/
#define PCF8563_Address_Control_Status_1 (unsigned char)0x00 //控制/状态寄存器1
#define PCF8563_Address_Control_Status_2 (unsigned char)0x01 //控制/状态寄存器2
#define PCF8563_Address_CLKOUT (unsigned char)0x0d //CLKOUT频率寄存器
#define PCF8563_Address_Timer (unsigned char)0x0e //定时器控制寄存器
#define PCF8563_Address_Timer_VAL (unsigned char)0x0f //定时器倒计数寄存器
#define PCF8563_Address_Years (unsigned char)0x08 //年
#define PCF8563_Address_Months (unsigned char)0x07 //月
#define PCF8563_Address_Days (unsigned char)0x05 //日
#define PCF8563_Address_WeekDays (unsigned char)0x06 //星期
#define PCF8563_Address_Hours (unsigned char)0x04 //小时
#define PCF8563_Address_Minutes (unsigned char)0x03 //分钟
#define PCF8563_Address_Seconds (unsigned char)0x02 //秒
#define PCF8563_Alarm_Minutes (unsigned char)0x09 //分钟报警
#define PCF8563_Alarm_Hours (unsigned char)0x0a //小时报警
#define PCF8563_Alarm_Days (unsigned char)0x0b //日报警
#define PCF8563_Alarm_WeekDays (unsigned char)0x0c //星期报警
void pcf_reg_init(void)
{
_PCF8563_Register_Typedef PCF8563_Register_Structrue;
PCF8563_Register_Structrue.Control_Status_1=0x00;
PCF8563_Register_Structrue.Control_Status_2=0x02;
//默认时间设置
PCF8563_Register_Structrue.Years=0x16;
PCF8563_Register_Structrue.Months_Century=0x08;
PCF8563_Register_Structrue.WeekDays =0x01;
PCF8563_Register_Structrue.Days=0x08;
PCF8563_Register_Structrue.Hours=0x04;
PCF8563_Register_Structrue.Minutes=0x03;
PCF8563_Register_Structrue.Seconds=0x55|(0<<7);
//默认闹钟设置
PCF8563_Register_Structrue.Hour_Alarm=0x03|(0<<7);
PCF8563_Register_Structrue.Minute_Alarm=0x05|(0<<7);
PCF8563_Register_Structrue.WeekDays_Alarm=0x01|(1<<7);
PCF8563_Register_Structrue.Day_Alarm=0x08|(1<<7);
//定时器默认设置
PCF8563_Register_Structrue.CLKOUT_Frequency=0x03;
PCF8563_Register_Structrue.Timer_Countdown_Value=0x00;
PCF8563_Register_Structrue.Timer_Control=0x03;
PCF8563_SetRegister(PCF_Format_BCD,PCF_Century_20xx,&PCF8563_Register_Structrue);
}