STM32 IIC通信简介+PCF8563时钟芯片示例

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开始信号

//产生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;  
}

IIC通信发送一个字节的信号、接收一个字节的信号

//产生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;
}



IIC接口的IC很多,常见常用的AT24C02,DS1302,PCF8563等都是IIC接口的。

下面以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的某一寄存器写入某一数据。通信过程也是很明显的,先发出一个开始信号,表示开始传输的是数据了,发送一条读命令,告诉接收方(PCF8563)我要写入数据

然后等待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  //星期报警

<pre name="code" class="cpp">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);

}


 
 
其中最后一个函数就是往各个寄存器写入相应的值,这里不再贴代码了。






发布了54 篇原创文章 · 获赞 62 · 访问量 10万+

猜你喜欢

转载自blog.csdn.net/u013053268/article/details/52399301