打印
[应用相关]

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

[复制链接]
1309|19
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
晓伍|  楼主 | 2019-7-5 08:37 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
IIC总线是嵌入式设备最常用的接口之一,包括51单片机在内的MCU一般都可以进行IIC通信。

IIC通信有3种类型的信号:开始信号,结束信号,和应答信号。

开始信号:SCL为高电平,SDA由高电平向低电平跳变,表示可以开始传输信号,进行通信了。

结束信号:SCL为高电平,SDA由低电平向高电平跳变,表示传输信号的时间已经过了。

应答信号:接收数据的IC在接收到8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已收到数据。

CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号。这个很容易理解,就好像人的交流(通信),要建立起通信,肯定需要开始条件,好像需要约定两个人都上线了才能通信,这就是开始信号。结束信号也一样,处理器不可能一直处于与其他IC通信的状态的。而应答信号,发送方把自己要发送的数据发送出去了,但不知道对方有没有收到,所以有些情况,需要等待接收方返回应答信号,告诉发送方我已经收到了,你可以继续发送下一条数据。



值得注意的是,虽然大部分MCU都带有IIC总线接口,但实际应用中,使用的一般都是引脚模拟的IIC。



以下分别介绍IIC配置的软件实现。


使用特权

评论回复
沙发
晓伍|  楼主 | 2019-7-5 08:37 | 只看该作者
引脚配置(初始化)

//初始化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 输出高
}

使用特权

评论回复
板凳
晓伍|  楼主 | 2019-7-5 08:37 | 只看该作者
#define SDA_IN()  {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=0X80000000;}
#define SDA_OUT() {GPIOB->CRL&=0X0FFFFFFF;GPIOB->CRL|=0X30000000;}

使用特权

评论回复
地板
晓伍|  楼主 | 2019-7-5 08:38 | 只看该作者
引脚模拟的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总线,准备发送或接收数据
}

使用特权

评论回复
5
晓伍|  楼主 | 2019-7-5 08:38 | 只看该作者
停止信号
//产生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);                                                                  
}

使用特权

评论回复
6
晓伍|  楼主 | 2019-7-5 08:38 | 只看该作者
应答信号
//等待应答信号到来
//返回值: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;  
}

使用特权

评论回复
7
晓伍|  楼主 | 2019-7-5 08:39 | 只看该作者
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;
}

使用特权

评论回复
8
晓伍|  楼主 | 2019-7-5 08:39 | 只看该作者
//不产生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;
}

使用特权

评论回复
9
晓伍|  楼主 | 2019-7-5 08:39 | 只看该作者
//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);
    }         
}

使用特权

评论回复
10
晓伍|  楼主 | 2019-7-5 08:40 | 只看该作者
//读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;
}

使用特权

评论回复
11
晓伍|  楼主 | 2019-7-5 08:40 | 只看该作者
IIC接口的IC很多,常见常用的AT24C02,DS1302,PCF8563等都是IIC接口的。
下面以PCF8563为例介绍如何使用MCU的IIC与其他器件进行通信。

PCF8563是一款时钟芯片,具体的介绍请查看手册。时钟芯片大部分都是采用BCD编码的。先介绍一下BCD码和十进制码之间的转换。

可以认为,BCD码就是十进制码变成十六进制。如:59(十进制)对应的BCD码是0x59 。 10 对应0x10 。 1 对应0x01 。

使用特权

评论回复
12
晓伍|  楼主 | 2019-7-5 08:40 | 只看该作者
互相转换的代码:

unsigned char RTC_BinToBcd2(unsigned char BINValue)
{
        unsigned char bcdhigh = 0;
       
        while (BINValue >= 10)
        {
                bcdhigh++;
                BINValue -= 10;
        }
       
        return ((unsigned char)(bcdhigh << 4) | BINValue);
}

使用特权

评论回复
13
晓伍|  楼主 | 2019-7-5 08:40 | 只看该作者
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));
}

使用特权

评论回复
14
晓伍|  楼主 | 2019-7-5 08:41 | 只看该作者
根据手册,查得IIC器件的地址

#define PCF8563_Write                            (unsigned char)0xa2  //写命令
#define PCF8563_Read                             (unsigned char)0xa3  //读命令


向PCF8563的某一寄存器写入某一数据。通信过程也是很明显的,先发出一个开始信号,表示开始传输的是数据了,发送一条读命令,告诉接收方(PCF8563)我要写入数据
然后等待PCF8563回应说我知道了,你可以写入数据了。然后就再告诉对方写入哪里,等他的回应,最后告诉他要写入什么,同样也等待回应。写完了就可以结束这次通信了

使用特权

评论回复
15
晓伍|  楼主 | 2019-7-5 08:41 | 只看该作者
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();
}

使用特权

评论回复
16
晓伍|  楼主 | 2019-7-5 08:41 | 只看该作者
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;
}

使用特权

评论回复
17
晓伍|  楼主 | 2019-7-5 08:42 | 只看该作者
/******************************************************************************
                             参数寄存器地址宏定义
******************************************************************************/

#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  //星期报警

使用特权

评论回复
18
晓伍|  楼主 | 2019-7-5 08:42 | 只看该作者
<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);

}

使用特权

评论回复
19
晓伍|  楼主 | 2019-7-5 08:42 | 只看该作者
其中最后一个函数就是往各个寄存器写入相应的值,这里不再贴代码了。

使用特权

评论回复
20
goodluck09876| | 2019-7-5 11:19 | 只看该作者
这个是不是需要用结构体的方式来搞?

有没有完整的?

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

60

主题

3893

帖子

1

粉丝