【STC8H】全网最详细的IIC协议

七、IIC协议

(一)原理

1.IIC总线

 IIC(Inter-Integrated Circuit)是 IIC Bus 简称,中文叫集成电路总线。它是一种串行通信总线,使用多主从(多个主机可以连接多个从机)架构。

 IIC使用两根双向信号线进行通信:

一根时钟线SCL,用于通信双方时钟的同步;

一根数据线SDA,用于收发数据。

IIC总线上所有器件的SDA、SCL引脚输出驱动都为 开漏(OD) 结构,并且IIC为同步的半双工通信方式(第五章的第一节有提到过半双工通信模式哦!)

主机(Master): 初始化总线的数据传输并产生允许传输的时钟信号的器件。

从机(slave):任何被寻址的器件。每个器件都有一个唯一的地址(无论是微控制器、LCD驱动器、存储器或键盘接口...),而且都可以作为一个发送器或接收器(由器件的功能决定)。

IIC总线上可以挂很多设备:可以是一个主机和多个从机,还可以是多个主机和多个从机。

img

(一个主机多个从机)

img

(多个主机多个从机)

(二)IIC协议内容

1.数据的有效性

SDA 线上的数据必须在时钟的高电平周期保持稳定。数据线的高或低电平状态只有在SCL 线的时钟信号是低电平时才能改变。

img

2.起始与终止信号

当SCL为高期间:

SDA : 由高到低------->起始信号

SDA:由低到高------->终止信号

起始信号和终止信号都是由主机发送的。上文提到过IIC为同步的半双工通信方式,所以起始信号产生之后,总线就处于被占用的状态;终止信号产生之后,总线就处于空闲状态

3.发送一个字节(主机发送)

SCL低电平期间,从机将数据位依次放到SDA线上(高位先行),然后拉高SCL,从机将在SCL高电平期间读取数据位,所以SCL高电平期间SDA不允许有数据变化(前面的“数据的有效性”中有提过原因),依次循环上述过程8次即可发送一个字节

图中START:起始信号

MSB:数据位的最高位

LSB:数据位的最低位(图中未标识出)

4.从地址和R/W位

从机地址上文说过就是从机设备的地址号,主机通过从机的地址号能找到对应的从机设备传输数据。

img

(有些从机地址可以从从机的芯片手册获得)

IIC数据传输遵循下图所示的格式。每次通信开始时,主设备发送一个地址帧来指定与之通信的从设备。这个地址是7位长,后面跟着第八位,第八位是一个数据方向位(R/W)

“0”表示传输(WRITE)

“1”表示数据请求(READ))。

数据传输总是由Master生成的STOP条件(P)终止。然而,如果master仍然希望在总线上通信,它可以生成一个重复的START条件并在没有首先生成STOP条件的情况下寻址另一个从设备。在这样的传输中,各种读/写格式的组合是可能的。

5.接收一个字节(主机接收)

SCL低电平期间,从机将数据位依次放到SDA线上然后拉高SCL,主机将在SCL高电平期间读取数据位(高位在前),所以SCL高电平期间SDA不允许有数据变化,依次循环上述过程8次即可接收一个字节(主机在接收之前,需要释放SDA)

6.发送/接收应答信号

发送应答: 在接收完一个字节之后,主机在下一个时钟发送一位数据,数据0表示应答,数据1表示非应答

接收应答: 在发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,需要释放SDA)。

应答表示 接收器已经成功地接受了该字节

非应答表示 接收器接收该字节没有成功。

(三) IIC数据帧

1.主机发送---从机接收

(1)主机对从机发送一个起始信号

(2)发送七位的从机地址

(3)发送一位的读/写位

(4)等待直到接收到从机设备的ACK信号

(5)主机发送8位数据,每发送8位数据之后就得等待从机设备的ACK应答信号

(6)发完所有数据并且接收到应答信号后,发送结束信号。

2.主机接收---从机发送

主机对向从机读取数据时,方式同发送数据有所不同,要多一次通信过程。

 主机需要先向从机发送一次信号,告诉从机”我要读取数据“,然后重开一次通信,等待从机主动返回数据。

请添加图片描述

3.代码

(1)起始信号
void vI2C_Start_Signal(I2C_HandleTypedef * hIICx)
{
​
    I2C_SDA_1        (hIICx);      //SDA 1
    I2C_SCL_1        (hIICx);      //SCL 1
    Delay_us         (I2C_Delay);  //延时
    Delay_us         (I2C_Delay);  //延时
​
    
    I2C_SDA_0        (hIICx);      //SDA 0
    Delay_us         (I2C_Delay);  //延时
    
    I2C_SCL_0        (hIICx);      //SCL 0
    Delay_us         (I2C_Delay);  //延时
​
}
​
(2)结束信号
void vI2C_End_Signal(I2C_HandleTypedef * hIICx)
{
    I2C_SCL_0        (hIICx);      //SCL 0
    I2C_SDA_0        (hIICx);      //SDA 0                      
    Delay_us         (I2C_Delay);  //延时            
     
    I2C_SCL_1        (hIICx);      //SCL 1                    
    Delay_us         (I2C_Delay);  //延时
    
    I2C_SDA_1        (hIICx);      //SDA 1              
    Delay_us         (I2C_Delay);  //延时
}
​
(3)发送一字节数据
int nI2C_SendByte(I2C_HandleTypedef * hIICx,uint8_t uSendByte) //数据从高位到低位//
{
  uint8_t i=8;
    uint8_t byte = uSendByte;
    int nAck;
    
    while(i--)
    {    
            //准备这次发送的数据
            if(byte&0x80)     I2C_SDA_1   (hIICx);  
            else              I2C_SDA_0   (hIICx); 
        
            //给一个时钟脉冲 告诉对方来拿数据
          Delay_us         (I2C_Delay);  //延时   
            I2C_SCL_1                     (hIICx);
            Delay_us         (I2C_Delay);  //延时 
            Delay_us         (I2C_Delay);  //延时         
            I2C_SCL_0                     (hIICx);
            Delay_us         (I2C_Delay);  //延时 
        
            //准备下一位即将发送的数据
            byte<<=1                           ;
    }          
    
    I2C_SDA_1        (hIICx);      //SDA 1  
    nAck =   nI2C_WaitAck(hIICx);
    return nAck;
}  
​
(4)读取一字节数据
void vI2C_ReadByte(I2C_HandleTypedef * hIICx,uint16_t nBytes, uint8_t *dataP)  //数据从高位到低位D7 D6 D5 D4 D3 D2 D1 D0(R/W位)
{ 
    uint8_t   i;    
    uint16_t  j;
    uint16_t  uEndByteNum  = 0x0000;    
    uint8_t   uReceiveByte = 0x00;
    
    uEndByteNum = nBytes-1;
    
    
    for(j=0;j<nBytes;j++)
    {   
            uReceiveByte = 0;
        
            for(i=0;i<8;i++)
          {
                    Delay_us         (I2C_Delay);  //延时 
                    I2C_SCL_1        (hIICx);
                    Delay_us         (I2C_Delay);  //延时 
                    uReceiveByte = uReceiveByte<<1;
                    if(I2C_SDA_R(hIICx) != 0)
                    {
                            uReceiveByte = uReceiveByte+1;  
                    }
                    Delay_us         (I2C_Delay);  //延时
                    I2C_SCL_0        (hIICx);
                    Delay_us         (I2C_Delay);  //延时
​
            }  
            
            if(j == uEndByteNum)
            {
                    vI2C_NAck        (hIICx);
            }
            else
            {                  
                    vI2C_Ack         (hIICx); 
            }
                    
            *(dataP+j) = uReceiveByte; 
    }
       
} 
(5)应答信号
void vI2C_Ack(I2C_HandleTypedef * hIICx)
{   
    I2C_SDA_0        (hIICx);      //SDA 0
    Delay_us         (I2C_Delay);  //延时
    
    I2C_SCL_1        (hIICx);      //SCL 1
    Delay_us         (I2C_Delay);  //延时
    Delay_us         (I2C_Delay);  //延时 
    I2C_SCL_0        (hIICx);      //SCL 0
    
    Delay_us         (I2C_Delay);  //延时
    I2C_SDA_1        (hIICx);      //SDA 1
}   
​
(6)非应答信号
void vI2C_NAck(I2C_HandleTypedef * hIICx)
{       
    I2C_SDA_1        (hIICx);      //SDA 1
    Delay_us         (I2C_Delay);  //延时
    
    I2C_SCL_1        (hIICx);      //SCL 1
    Delay_us         (I2C_Delay);  //延时
    Delay_us         (I2C_Delay);  //延时 
    I2C_SCL_0        (hIICx);      //SCL 0
    
    Delay_us         (I2C_Delay);  //延时
    I2C_SDA_1        (hIICx);      //SDA 1  
}   
(7)等待应答信号
int nI2C_WaitAck(I2C_HandleTypedef * hIICx)      //返回为:=1有ACK,=0无ACK
{
    int nAck;
    
    Delay_us         (I2C_Delay);  //延时
    I2C_SCL_1        (hIICx);      //SCL 1
    Delay_us         (I2C_Delay);  //延时 
​
    if(I2C_SDA_R(hIICx) == 0)
    {
            nAck = 0;
    }
    else
    {
            nAck = 1;
    }
    Delay_us         (I2C_Delay);  //延时 
    I2C_SCL_0        (hIICx);      //SCL 0      
    Delay_us         (I2C_Delay);  //延时 
    I2C_SDA_0        (hIICx);      //SDA 0  
    
    return nAck;
}
(8)检查设备地址
int nI2C_Check_Device_Address(I2C_HandleTypedef * hIICx)
{
    int nAck;
    //===============================================================
    //                          发送器件地址(+写信号 0)
    //===============================================================   
    vI2C_Start_Signal(hIICx);                            //1.  I2C_Start                 ;  起始信号        
    nAck = nI2C_SendByte(hIICx ,hIICx->uDevice_Addr)!=0; //2.  I2C_Send Device Address(W);  发送(设备地址)告诉总线即将操作的设备      
    if(nAck != 0){vI2C_End_Signal(hIICx);return -1; }    //3.  I2C_WaitAck               ;  等待响应
    
  vI2C_End_Signal(hIICx);
    
    return I2C_OK;
}
​

(四)TIM1650数码管

TM1650是一款国产4位共阴数码管驱动芯片,它还带有矩阵按键扫码功能。它的基本参数如下:

  • 工作电压:3~5V

  • 数码管驱动模式:8段x4位共阴数码管

  • 矩阵按键驱动模式:7x4矩阵按键,不支持组合键

  • 通信接口:类IIC,使用了IIC相同的时序,但没有完全遵守IIC的协议,不带从机地址。

1.引脚

SCL:串行通信时钟线

SDA:串行通信数据线

VCC:电源线

GND:接地线

2.通讯时序

TM1650采用2线串行传输协议通讯:

(1)开始信号

保持CLK为“1”电平,DAT从“1”跳“0”,认为是开始信号

结束信号

保持CLK为“1”电平,DAT从“0”跳“1”,认为是结束信号

(2)写“1”

保持DAT为“1”电平,CLK从“0”跳到“1”,再从“1”跳到“0”

写“0”:

保持DAT为“0”电平,CLK从“0”跳到“1”,再从“1”跳到“0”

(3)ACK信号

如果本次通讯正常,芯片在串行通讯的第8个时钟下降沿后,TM1650主动把DAT拉低。直到检测到CLK来 了上升沿,DAT释放为输入状态(对芯片而言)

(4) 一个字节(8位)数据传输格式

数据发送时高位先进。

当CLK是高电平时,DAT上的信号必须保持不变;只有CLK上的时钟信号为低电平时, DAT上的信号才能改变。(之前说的数据有效性)

数据输入的开始条件:

CLK为高电平时,DAT由高变低;

结束条件:

CLK为高时,DAT 由低电平变为高电平。

5、 写显示操作

ADDRESS:显示地址(68H、6AH、6CH、6EH);

DATA:显示数据。

6.完整时序

起始信号>>地址码(1字节)>>ACK应答信号>>数码管段数据(1字节)>>ACK应答信号>>结束信号

command1:系统命令48H;

command2:系统参数设置;(设置功能 | 显示控制 | 开关显示位)

img

ADDRESS:显示地址(68H、6AH、6CH、6EH);(ps:四个数码管的地址)

DATA:显示数据。

3.Demo代码

(1)7/8段显示方式
static uint8_t s_7number[11] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x40};   // 7段显示方式0~9,-
​
static uint8_t s_8number[11] = {0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x40};   // 8段显示方式0~9,-
(2)设备初始化
void vSen_TM1650_Initialize(I2C_HandleTypedef * iicHandle , uint8_t uLight_Degree ,uint8_t uSeg_Num)
{
    //===============================================================
    //             TM1650初始化:Delay 100 MS
    //===============================================================
    Delay_ms(100);
    //===============================================================
    //             TM1650初始化:Command1(0x48) + Command2
    //===============================================================
    vSen_TM1650_Write_Data( iicHandle , TM1650_COMMAND_1 , uLight_Degree | uSeg_Num | TM1650_DISPLAY_ON);
}
(3)发送数据
void vSen_TM1650_Write_Data(I2C_HandleTypedef * iicHandle,uint8_t uAddr,uint8_t uData)
{
  vI2C_Start_Signal(iicHandle);         //IIC_Start                 ; 起始信号                                                                      
​
    nI2C_SendByte(iicHandle, uAddr);      //IIC_Send DataBuffer           ; 发送地址   
    nI2C_SendByte(iicHandle, uData);            //IIC_Send DataBuffer         ; 发送数据   
​
  vI2C_End_Signal(iicHandle);             //IIC_End                     ; 结束信号  
}
(4)数码管单个显示
void vSen_TM1650_Set_Single_Display(I2C_HandleTypedef * iicHandle, uint8_t uDigitron_Mem_Addr , uint8_t uValue  , uint8_t uDp_Enable , uint8_t Zero_NC_Enable)
{
    uint8_t Display_data = 0x00;
    
    if(Zero_NC_Enable && (uValue == 0)){Display_data = 0x00;                            }//不显示0
    else                                                       {Display_data = s_7number[uValue];   }//显示0
    
    vSen_TM1650_Write_Data( iicHandle , uDigitron_Mem_Addr , Display_data | uDp_Enable);    
}
(5)数码管全部显示
void vSen_TM1650_Set_Display(I2C_HandleTypedef * iicHandle, uint8_t *uVar , uint8_t uDp_Pos)
{
        //===============================================================
        //                         小数点位置
        //===============================================================       
        uint8_t uDp_set[4] = {TM1650_DP_DISENABLE , TM1650_DP_DISENABLE ,TM1650_DP_DISENABLE ,TM1650_DP_DISENABLE};
        if(uDp_Pos != 0){uDp_set[uDp_Pos-1] = TM1650_DP_ENABLE;}
        
        //===============================================================
        //                              
        //===============================================================   
        vSen_TM1650_Set_Single_Display( iicHandle , TM1650_DIGITRON_1_MEM_ADDR , uVar[0]      , uDp_set[0]  , 0);
        vSen_TM1650_Set_Single_Display( iicHandle , TM1650_DIGITRON_2_MEM_ADDR , uVar[1]    , uDp_set[1]  , 0); 
        vSen_TM1650_Set_Single_Display( iicHandle , TM1650_DIGITRON_3_MEM_ADDR , uVar[2]    , uDp_set[2]  , 0); 
        vSen_TM1650_Set_Single_Display( iicHandle , TM1650_DIGITRON_4_MEM_ADDR , uVar[3]    , uDp_set[3]  , 0); 
​
}   
(6)浮点数显示
void vSen_TM1650_Set_Num_Display(I2C_HandleTypedef * iicHandle, float fValue )
{
    uint8_t uDp_Pos = 0;//是否有小数点 0无 1有 对应第一个数码管  类推······
    uint8_t uNum_Buf[4] = {0,0,0,0};//4位数码管数值
    uint16_t uTemp  = 0;    
    
    if(fValue >= 0)
    {
        if     (fValue>1000)          { uTemp = (uint16_t)(fValue);       uDp_Pos = 0; }//千位数 显示零位小数 
        else if(fValue>100)           { uTemp = (uint16_t)(fValue*10.f);    uDp_Pos = 3; }//百位数 显示一位小数      
        else if(fValue>10 )           { uTemp = (uint16_t)(fValue*100.f);   uDp_Pos = 2; }//十位数 显示二位小数          
        else if(fValue<10 && fValue>0){ uTemp = (uint16_t)(fValue*1000.f);uDp_Pos = 1; }//小数   显示三位小数 & 个位数 显示三位小数
​
        uNum_Buf[0] = (uTemp/1000);                                                       
        uNum_Buf[1] = (uTemp%1000/100); 
        uNum_Buf[2] = (uTemp%100/10);   
        uNum_Buf[3] = (uTemp%10);       
    }
    else
    {
        fValue = -fValue;//数值取反
        uNum_Buf[0] =10;  //第一个数码管显示负数
        
        if     (fValue>1000)          { uTemp = (uint16_t)(fValue);       uDp_Pos = 0; }//千位数 显示零位小数 
        else if(fValue>100)           { uTemp = (uint16_t)(fValue);         uDp_Pos = 0; }//百位数 显示一位小数  
        else if(fValue>10 )           { uTemp = (uint16_t)(fValue*10.f);    uDp_Pos = 3; }//十位数 显示二位小数          
        else if(fValue<10 && fValue>0){ uTemp = (uint16_t)(fValue*100.f); uDp_Pos = 2; }//小数   显示三位小数 & 个位数 显示三位小数
                                            
        uNum_Buf[1] = (uTemp%1000/100); 
        uNum_Buf[2] = (uTemp%100/10);   
        uNum_Buf[3] = (uTemp%10);  
    }
​
    vSen_TM1650_Set_Display( iicHandle , uNum_Buf , uDp_Pos);
    
}

4.实验现象

VCC------>5V

GND------>GND

SCL------->P34

SDA------->P33

//显示浮点数0.123

void main()
{
​
    I2C_HandleTypedef hIIC_TM1650;//定义TM1650的iic操作句柄
    
​
    GPIO_Init();            //STC15W单片机引脚初始化函数
    
    vI2C_Handle_Init(&hIIC_TM1650 , GPIO_Port_3 , GPIO_Pin_4 , GPIO_Port_3 , GPIO_Pin_3, NULL);       // P34- SCL  P33- SDA
    vSen_TM1650_Initialize(&hIIC_TM1650 , TM1650_BACKGROUND_LIGHT_02_8 , TM1650_DISPLAY_MODE_8_SEG);    
    
    
    while(1)
    {
    
        vSen_TM1650_Set_Num_Display(&hIIC_TM1650 , 0.123f);
        Delay_ms(1000); 
​
    }
​
}

5.逻辑分析仪观察波形

可以通过解码器分析从起始信号到结束信号的整个波形(起到检查作用)

屏幕右上角有解码器设置

设置这两条线

就可以看到波形了

  • 26
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
为了通过IIC总线读取LMSCSZ100,需要按照以下步骤进行操作: 1. 首先,需要连接LMSCSZ100到STC8H1的IIC总线上,并设置好IIC的工作模式和时钟频率。 2. 接着,需要初始化IIC的相关寄存器,设置好传输数据的格式、数据长度等参数。 3. 然后,可以通过IIC向LMSCSZ100发送读取命令,并设置好读取的起始地址和数据长度。 4. 在接收到LMSCSZ100返回的数据后,需要进行处理,可以将数据存储到内部的缓冲区中,或者直接进行后续的操作。 以下是一个简单的STC8H1用IIC读取LMSCSZ100的例程,供参考: ``` #include <reg51.h> #define IIC_FREQ 100000 // IIC总线时钟频率 #define BUF_LEN 16 // 数据缓冲区长度 sbit SDA = P1^7; // SDA引脚 sbit SCL = P1^6; // SCL引脚 unsigned char data_buf[BUF_LEN]; // 数据缓冲区 void delay_us(unsigned int us) { while(us--); } void iic_start() { SDA = 1; delay_us(2); SCL = 1; delay_us(2); SDA = 0; delay_us(2); SCL = 0; delay_us(2); } void iic_stop() { SDA = 0; delay_us(2); SCL = 1; delay_us(2); SDA = 1; delay_us(2); } unsigned char iic_send(unsigned char dat) { unsigned char i; for(i = 0; i < 8; i++) { SDA = (dat & 0x80) ? 1 : 0; delay_us(2); SCL = 1; delay_us(2); SCL = 0; delay_us(2); dat <<= 1; } SDA = 1; delay_us(2); SCL = 1; delay_us(2); if(SDA) return 1; // 发送失败 else return 0; // 发送成功 } unsigned char iic_recv(unsigned char ack) { unsigned char i, dat = 0; SDA = 1; delay_us(2); for(i = 0; i < 8; i++) { SCL = 1; delay_us(2); dat = (dat << 1) | SDA; SCL = 0; delay_us(2); } if(ack) SDA = 1; else SDA = 0; delay_us(2); SCL = 1; delay_us(2); SCL = 0; delay_us(2); SDA = 1; delay_us(2); return dat; } void iic_init() { SDA = 1; SCL = 1; } void iic_read(unsigned char addr, unsigned char len) { unsigned char i; iic_start(); iic_send(0xA0); // 发送写命令 iic_recv(1); iic_send(addr); // 发送读取的起始地址 iic_recv(1); iic_start(); iic_send(0xA1); // 发送读命令 iic_recv(1); for(i = 0; i < len; i++) { data_buf[i] = iic_recv(i == len - 1 ? 1 : 0); // 保存读取到的数据 } iic_stop(); } void main() { iic_init(); // 初始化IIC iic_read(0x00, BUF_LEN); // 读取LMSCSZ100的数据 // 对读取到的数据进行处理,如存储到内部的EEPROM中等 while(1); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值