写在前面:在前面的学习过程中,我们学习了串口通信的USART(通用同步异步收发器),本节我们将继续学习一种串行通信协议——IIC通信协议。之前我使用51单片机也分享过相关的IIC通信的知识,其实本质的知识是相通的,本节我们继续探讨STM32关于IIC的基础知识。

一、IIC总线协议介绍

1.1基础介绍

常见的通信协议有:串口、SPI、IIC、CAN以及USB

IIC:集成电路总线,是一种同步、串行、半双工通信总线;

同步:含有时钟线SCL;串行:数据的传输是一位一位传送的;半双工:单方向发、单方向收;

什么是总线?什么又是协议呢?

        这其实是一种说法,总线就是从硬件层面看,是传输数据的通道,协议是从软件层面看,是传输数据的规则。总线就好比是一条道路,而协议是通过这条道路的交规。

IIC总线的特点:

1、总线由数据线 SDA 和时钟线 SCL 构成的串行总线,数据线用来传输数据,时钟线用来
同步数据收发。并且都接有上拉电阻,保证总线在空闲状态为高电平。

2、总线支持多设备连接,允许多主机并存,每个设备都有属于自己的设备地址,所以我们只需要知道器件的地址,根据时序就可以实现微控制器与器件之间的通信。

3、连接到总线上的数目受总线的最大电容400pf限制,(寄生电容)每个设备都有电容;

4、数据的传输速率:

标准模式:100kbit/s;快速模式:400kbit/s;高速模式:3.4Mbit/s;

IIC协议:

三个信号:起始信号、停止信号与应答信号;

两个注意:数据的有效性;数据的传输顺序;

一个状态:空闲状态;

         I2C两设备进行通信的流程为:首先主机控制总线,通过总线时序找到想要通信的从设备(每个从设备都有固定地址),被选中的从机准备发送/接收收据,未被选中的从机打开上述开关,断绝同总线的联系。主机与从机传递数据,如果传递1直接打开开关通过上拉电阻实现总线高电平,如果传递0关闭开关,使总线达到低电平主从机设备传递信息严格遵循相应的时序。

1.2 IIC协议

下图为:IIC协议的时序图:

其中主要为 1、起始信号;2、终止信号;3、应答信号;4、数据有效性;5、

数据传输;6、空闲状态;

1、起始信号与终止信号

 

起始条件:SCL高电平期间,SDA从高电平切换到低电平;

结束条件:SCL高电平期间,SDA从低电平切换到高电平;

2、应答信号

应答条件:在上拉电阻的影响下,SDA默认为高电平,而从机拉低SDA表示收到信号即(ACK),若没有收到则不用拉低(NACK);发送完一个字节之后,主机在下一个时钟接收一位数据,判断从机是否应答,数据0表示应答,数据1表示非应答(主机在接收之前,应该释放SDA);

3、数据收发

        数据在低电平期间准备好,高电平期间数据发送(高电平期间保持有效),数据先发送高8、位,数据以8位(1bit)为一个单位进行发送。数据在SCL高电平期间,保持稳定,主句发送完数据释放数据线,不影响从机的应答;

1.3相关驱动代码:

起始信号
void iic_start(void)

{

        IIC_SDA(1);

        IIC_SCL(1);

        iic_delay();

        IIC_SDA(0);

        iic_delay();

        IIC_SCL(0);

        iic_delay()

}

 停止信号

void iic_stop(void)

{

        IIC_SDA(0);

        iic_delay();

        IIC_SCL(1);

        iic_delay();

        IIC_SDA(1);

        iic_delay();

}

发送应答信号

void iic_ack(void)

{

        IIC_SCL(0);

        iic_delay();

        IIC_SDA(0);

        iic_delay();

        IIC_SCL(1);

        iic_delay();

}

发送非应答信号

void iic_nack(void)

{

          IIC_SCL(0);

          iic_delay();

          IIC_SDA(0);

          iic_delay();

          IIC_SCL(1);

          iic_delay();

}

检测应答信号

uint8_t iic_wait_ack(void)

{

        IIC_SDA(1);

        iic_delay();

        IIC_SCL(1);

        iic_delay();

        if(IIC_READ_SDA)
                {

                 iic_stop();

                return 1;

                }

          IIC_SCL(1);  

          iic_delay();  

   

      reutn 0;  

}

发送一个字节:

void iic_send_byte(uint8_t data)
{
    uint8_t i;
    for (i=0;i<8;i++)
    {
        IIC_SDA((data&0x80)>>7); /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
        iic_delay();
        data<<=1;
    }
    IIC_SDA(1);      /* 发送完成,释放数据线 */
}

接收一个字节

uint8_t icc_recive_byte(uint8_t ack)
{
      uint8_t i,recive;
    for(i=0;i<8;i++)
    {
        recive<<=1;  /* 高位先输出,所以先收到的数据位要左移 */
        IIC_SCL(1);
        iic_delay();
        if(IIC_READ_SDA)
        {
        recive++; 
        }  
        IIC_SCL(0);
        iic_delay();      
    }
    if(ack==1)
    {
        iic_ack();/* 发送 ACK */
    }
   else
    {
        iic_nack();/* 发送 nACK */
    }

return recive;

}

二、AT2402介绍

2.1基础介绍

        24C02 是一个 2K bit 的串行 EEPROM 存储器,内部含有 256 个字节。在 24C02 里面还有
一个 8 字节的页写缓冲器。该设备的通信方式 IIC,通过其 SCL 和 SDA 与其他设备通信。

存储介质:E2PROM;

通讯接口:I2C通信接口;

容量:256字节;

1A0地址输入
2A1地址输入
3A2地址输入
4VSS电源地
5SDA串行地址和数据输入输出
6SCL串行时钟输入
7WP写保护
8VCC电源正极

AT24C02 器件地址为 7 位,高 4 位固定为 1010,低 3 位由 A0/A1/A2 信号线的电平决定。 因为传输地址或数据是以字节为单位传送的,当传送地址时, 器件地址占 7 位,还有最后一位(最低位 R/W)用来选择读写方向,它与地址 无关。其格式如下:

1010A2A1A0R/W

2.2 AT24C02读写时序

写操作:起始信号——地址和方向(设备地址+写)——应答——内存地址——数据内容——应答——停止;

读操作:起始信号——地址和方向(设备地址+写)——应答——内存地址——应答——起始信号——地址和方向(设备地址+读)——应答——数据——应答······——停止;

2.3 AT24C02驱动与步骤

IIC配置:

1、使能SCL和SDA引脚的时钟;

2、GPIO配置模式:SDA:复用开漏;SCL:复用推挽;

3、编写基本信息:起始,停止,应答,接收应答;

4、编写读写函数;

AT24C02驱动步骤:

1、初始化IIC接口;

2、编写写入/读取一个字节数据函数;

3、编写连续读写函数;

三、程序设计 

         每按下 KEY1,MCU 通过 IIC 总线向 24C02 写入数据,通过按下 KEY0 来控制 24C02 读
取数据。同时在 LCD 上面显示相关信息。LED0 闪烁用于提示程序正在运行。

源码文件:

链接:https://pan.baidu.com/s/1JtCEJ4oTfy6Da-Rffr93qg 
提取码:1022

myiic.c

#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"
 
 
 /**
 * @brief       初始化IIC
 * @param       无
 * @retval      无
 */
void iic_init()//
{
      __HAL_RCC_GPIOB_CLK_ENABLE();/* 使能 IIC 的 SCL 和 SDA 对应的 GPIO 时钟 */
      GPIO_InitTypeDef gpio_init_struct;
    
    gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;
    gpio_init_struct.Pin=GPIO_PIN_6;
    gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;
    gpio_init_struct.Pull=GPIO_PULLUP; 
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);
    /* 设置对应 GPIO 工作模式(SCL 推挽输出 SDA 开漏输出) */
    gpio_init_struct.Mode=GPIO_MODE_OUTPUT_OD;
    gpio_init_struct.Pin=GPIO_PIN_7;
    gpio_init_struct.Speed=GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &gpio_init_struct);

}

 /**
 * @brief       IIC延时函数,用于IIC读写的速度,IIC在进行数据传递时,时钟信号为高电平期间,数据线必须保持稳定
                延时的作用就是保持在高电平期间的数据稳定。
 * @param       无
 * @retval      无
 */
static void iic_delay(void)
{
        delay_us(2);
  /* 2us 的延时, 读写速度在 250Khz 以内 */
}

/**
 * @brief       IIC起始信号
 * @param       无
 * @retval      无
 */
void icc_start()
{
 
    IIC_SCL(1);
    IIC_SDA(1);
    iic_delay();
    IIC_SDA(0);/* 在时钟为高电平期间,数据线由高电平变为低电平,表示起始信号 */
    iic_delay();
    IIC_SCL(0);  /* 钳住I2C总线,并没有把数据线释放,准备发送或接收数据,因为紧接着还是主机进行操作 */
    iic_delay();
}

/**
 * @brief       IIC停止信号
 * @param       无
 * @retval      无
 */
void icc_stop()
{  
    IIC_SDA(0);
    iic_delay();
    IIC_SCL(1);
    iic_delay();
    IIC_SDA(1);/* 在时钟为高电平期间,数据线由低电平变高低电平,表示停止信号 */
    iic_delay();
}

/**
 * @brief       IIC发送一个字节
 * @param       data: 要发送的数据
 * @retval      无
 */
void iic_send_byte(uint8_t data)
{
    uint8_t i;
    for (i=0;i<8;i++)
    {
        IIC_SDA((data&0x80)>>7); /* 高位先发送 */
        iic_delay();
        IIC_SCL(1);
        iic_delay();
        IIC_SCL(0);
        iic_delay();
        data<<=1;
    }
    IIC_SDA(1);      /* 发送完成,释放数据线 */
}

/**
 * @brief       IIC读取一个字节
 * @param       ack: ck=1时,发送ack; ack=0时,发送nack
 * @retval      接收到的数据
 */
uint8_t icc_recive_byte(uint8_t ack)
{
      uint8_t i,recive;
    for(i=0;i<8;i++)
    {
        recive<<=1;  /* 高位先输出,所以先收到的数据位要左移 */
        IIC_SCL(1);
        iic_delay();
        if(IIC_READ_SDA)
        {
        recive++; 
        }  
        IIC_SCL(0);
        iic_delay();      
    }
    if(ack==1)
    {
        iic_ack();/* 发送 ACK */
    }
   else
    {
        iic_nack();/* 发送 nACK */
    }

return recive;

}
/**
 * @brief       等待应答信号
 * @param       无
 * @retval      返回0,接收应答成功
                返回1,接收应答失败
 */
uint8_t icc_wait_ack(void)
{
    uint8_t wattime=0;
    uint8_t rack=0;
    IIC_SDA(1);    /* 主机释放数据线,由对应的从机进行拉低 */ 
    iic_delay();
    IIC_SCL(1);     /* 高电平期间,从机发送应答信号 */
    iic_delay();
    while(IIC_READ_SDA) /* 主等待应答 */
    {
        wattime++;
        if(wattime>250)
        {
            icc_stop();
            rack=1;         /* 超时,应答失败 */
            break; 
        }
    }
        IIC_SCL(0);         /*时钟结束,应答成功 */
        iic_delay();
    return rack;
}

/**
 * @brief       产生应答信号
 * @param       无
 * @retval      无
 */
void iic_ack(void)
{
    IIC_SDA(0);     /* 产生应答信号 */
    iic_delay();
    IIC_SCL(1);     /* 高电平期间,发送应答信号 */
    iic_delay();
    IIC_SCL(0);
    iic_delay();
    IIC_SDA(1);     /* 释放数据线 */
    iic_delay();

}

/**
 * @brief       产生不应答信号
 * @param       无
 * @retval      无
 */
void iic_nack(void)
{
    IIC_SDA(1);          /* 产生不应答信号 */
    iic_delay();
    IIC_SCL(1);
    iic_delay();        /* 高电平期间,发送不应答信号 */
    IIC_SCL(0);
    iic_delay();
}

AT24Cxx.c

#include "./BSP/AT24CXX/AT24Cxx.h"
#include "./BSP/IIC/myiic.h"
#include "./SYSTEM/delay/delay.h"
/**
 * @brief       初始化IIC接口
 * @param       无
 * @retval      无
 */
void at24cxx_init(void)
{
    iic_init();
}
/**
 * @brief       在AT24CXX指定地址写入一个数据
 * @param       arr: 写入数据的目的地址
 * @param       data: 要写入的数据
 * @retval      无
 */
void at24cxx_write_onebyte(uint16_t arr,uint8_t data)    
{
     icc_start();  /* 发送起始信号 */
    if(EE_type>AT24C16)     /* 24C16以上的型号, 分2个字节发送地址 */
    {
        iic_send_byte(0XA0);
        icc_wait_ack();
        iic_send_byte(arr>>8);
    }
    else
    {
        iic_send_byte(0XA0 + ((arr >> 8) << 1));  /* 24C16以下信号发送器件 0XA0 + 高位a8/a9/a10地址,写数据 */ 
    }
    
    icc_wait_ack();/* 每次发送完一个字节,都要等待ACK */
    iic_send_byte(arr%256);/* 内存地址低位 */
    icc_wait_ack();
    
    iic_send_byte(data); /* 发送1字节 */
    icc_wait_ack(); /* 要等待ACK */
    icc_stop();     /* 产生一个停止条件 */
    delay_ms(10);   /* 注意: EEPROM 写入比较慢,必须等到10ms后再写下一个字节 */
}

/**
* @brief
在 AT24CXX 指定地址读出一个数据
* @param
readaddr: 开始读数的地址
* @retval
      读到的数据
*/
uint8_t at24cxx_read_onebyte(uint16_t arr)
{
    uint8_t temp;
    icc_start();
    if(EE_type>AT24C16)
    {
        iic_send_byte(0XA0);
        icc_wait_ack();
        iic_send_byte(arr>>8);
    }
    else
    {
        iic_send_byte(0XA0 + ((arr >> 8) << 1));   
    }
    icc_wait_ack();
    iic_send_byte(arr%256);
    icc_wait_ack();
    
    icc_start();
    iic_send_byte(0XA1);
    icc_wait_ack();
    temp=icc_recive_byte(0);
    icc_stop();
    return temp;
}
/**
* @brief
检查 AT24CXX 是否正常
*   @note
检测原理: 在器件的末地址写如 0X55, 然后再读取, 如果读取值为 0X55
*
则表示检测正常. 否则,则表示检测失败.
* @param无
* @retval检测结果
*0: 检测成功
*1: 检测失败
*/
uint8_t at24cxx_chack(void)
{
    uint8_t temp;
    uint16_t add  = EE_type;
    
    temp =at24cxx_read_onebyte(add);
    if(temp==0x55)
    {
        return 0;
    }
    else
    {
    at24cxx_write_onebyte(add,0x55);
      temp =at24cxx_read_onebyte(add);
    if(temp==0x55)  return 0;
    }
    return 1;
}
    void at24cxx_read(uint16_t add,uint8_t *pbuf,uint16_t datalen)
    {
        while(datalen--)
        {
            *pbuf++=at24cxx_read_onebyte(add++);
        }    
    }
    
    void at24cxx_write(uint16_t add,uint8_t *pbuf,uint16_t datalen)
    {
        while(datalen--)
        {
            at24cxx_write_onebyte(add,*pbuf);
            add++;
            pbuf++;
        } 
    
    }



main.c

#include "./SYSTEM/sys/sys.h"
#include "./SYSTEM/usart/usart.h"
#include "./SYSTEM/delay/delay.h"
#include "./BSP/LED/led.h"
#include "./BSP/KEY/key.h"
#include "./BSP/LCD/lcd.h"
#include "./BSP/AT24CXX/AT24Cxx.h"
const uint8_t g_text_buf[] = {"STM32 IIC TEST"};
#define TEXT_SIZE       sizeof(g_text_buf)  /* TEXT字符串长度 */
int main(void)
{
    uint8_t key;
    uint8_t datatemp[TEXT_SIZE];


    HAL_Init();                              /* 初始化HAL库 */
    sys_stm32_clock_init(RCC_PLL_MUL9);      /* 设置时钟, 72Mhz */
    usart_init(115200);                         /* 串口初始化为115200 */
    delay_init(72);                          /* 延时初始化 */
    LED_init(); 
    lcd_init();    /* LED初始化 */
    key_init();
    at24cxx_init();
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "IIC TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);
    lcd_show_string(30, 110, 200, 16, 16, "KEY1:Write  KEY0:Read", RED);    /* 显示提示信息 */
   
    while (at24cxx_chack()) /* 检测不到24c02 */
    {
        lcd_show_string(30, 130, 200, 16, 16, "24C02 Check Failed!", RED);
        delay_ms(500);
        lcd_show_string(30, 130, 200, 16, 16, "Please Check!      ", RED);
        delay_ms(500);  
    }
    lcd_show_string(30, 130, 200, 16, 16, "24C02 Ready!", RED);
    while(1)
    { 
        key=key_scan();
        if (key == 1)   /* KEY1按下,写入24C02 */
        {
            lcd_fill(0, 150, 239, 319, WHITE);  /* 清除半屏 */
            lcd_show_string(30, 150, 200, 16, 16, "Start Write 24C02....", BLUE);
            at24cxx_write(0, (uint8_t *)g_text_buf, TEXT_SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "24C02 Write Finished!", BLUE);   /* 提示传送完成 */
        }

        if (key == 2)   /* KEY0按下,读取字符串并显示 */
        {
            lcd_show_string(30, 150, 200, 16, 16, "Start Read 24C02.... ", BLUE);
            at24cxx_read(0, datatemp, TEXT_SIZE);
            lcd_show_string(30, 150, 200, 16, 16, "The Data Readed Is:  ", BLUE);   /* 提示传送完成 */
            lcd_show_string(30, 170, 200, 16, 16, (char *)datatemp, BLUE);          /* 显示读到的字符串 */
        }
     
    }
}

key.c

#include "./BSP/KEY/key.h"
#include "./SYSTEM/delay/delay.h"
void key_init(void)
{
        GPIO_InitTypeDef gpio_init_struct;
        __HAL_RCC_GPIOE_CLK_ENABLE(); 
        __HAL_RCC_GPIOA_CLK_ENABLE();
    
        gpio_init_struct.Mode=GPIO_MODE_INPUT;
        gpio_init_struct.Pull=GPIO_PULLUP;
        gpio_init_struct.Pin=GPIO_PIN_4;
        HAL_GPIO_Init(GPIOE,&gpio_init_struct);
        
        gpio_init_struct.Mode=GPIO_MODE_INPUT;
        gpio_init_struct.Pull=GPIO_PULLUP;
        gpio_init_struct.Pin=GPIO_PIN_3;
        HAL_GPIO_Init(GPIOE,&gpio_init_struct);
    
        gpio_init_struct.Mode=GPIO_MODE_INPUT;
        gpio_init_struct.Pull=GPIO_PULLDOWN;
        gpio_init_struct.Pin=GPIO_PIN_0;
        HAL_GPIO_Init(GPIOA,&gpio_init_struct);

}

uint8_t  key_scan(void)
{
     uint8_t keyval = 0;
    if (KEY0 == 0|| KEY1==0 || WK_UP==1)
    {
        delay_ms(10); 
            if(KEY0==0)
            {
                while(KEY0==0);                
                 keyval=1;
            }
            
            if(KEY1==0)
            {
                while(KEY1==0);                
                 keyval=2;
            }
             if(WK_UP==1)
            {
                while(WK_UP==0);                
                 keyval=3;
            }
    }
    return keyval;

}


led.c

#include "./BSP/LED/led.h"

void LED_init()
{
     __HAL_RCC_GPIOB_CLK_ENABLE();
    GPIO_InitTypeDef gpiob_init_struct;
    gpiob_init_struct.Mode=GPIO_MODE_OUTPUT_PP;
    gpiob_init_struct.Pin=GPIO_PIN_5;
    gpiob_init_struct.Speed=GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &gpiob_init_struct);
    
      __HAL_RCC_GPIOE_CLK_ENABLE();
    GPIO_InitTypeDef gpioe_init_struct;
    gpioe_init_struct.Mode=GPIO_MODE_OUTPUT_PP;
    gpioe_init_struct.Pin=GPIO_PIN_5;
    gpioe_init_struct.Speed=GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOE, &gpioe_init_struct);
    
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_5, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOE, GPIO_PIN_5, GPIO_PIN_SET);

}

lcd.c

此驱动代码太长,就不在展示,在百度网盘中的文件里面都有;

实验现象:

iic实验

总结:本节我们再次学习了STM32的IIC通信协议的基本知识,结合AT24C02的EEPROM进行了数据的读取实验,大家需要对IIC的时序十分熟悉,对代码的操作也需要亲自动手。有问题欢迎在评论区探讨。

创作不易,还请大家多多点赞支持!!!

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐