51单片机之IIC总线

IIC总线概念&通信协议

概念

  1. I2C(Inter-Integrated Circuit),通常简称为IIC,是一种用在集成电路(IC)之间的串行通信总线。
  2. I2C为同步串行通信,使用两根线路进行通信,分别是数据线(SDA)和时钟线(SCL)。
  3. SDA线用于数据传输,SCL线用于数据传输的同步。
  4. SCL的每个时钟周期,SDA传输一位数据。
  5. I2C规定,数据的接收方会在每个时钟周期的高电平期间读取数据,具体来讲就是在SCL处于高电平时,读取SDA上的数据。                                                                                         
  6. SDA必须在SCL低电平期准备好要发送的下一位数据,然后在SCL高电平期间保持稳定。

主从架构

I2C采用主从架构,一个主设备可连接多个从设备。主设备负责发起通信和控制总线,而从设备负责响应主设备的请求。

  1. I2C总线中的每个设备都有一个唯一的地址(用7位二进制数字表示),用于在总线上标识自己。
  2. SCL信号线上的时钟信号始终由主设备产生,而SDA信号线上的数据信号既可由主设备产生,也可由从设备产生。
  3. 当主设备向从设备发送数据时,SDA信号由主设备产生,从设备接收信号;当主设备从从设备读取数据时,SDA信号由从设备产生,主设备接收信号。

通信协议

  1. 空闲状态:I2C协议规定,当SDA和SCL均为高电平时,总线为空闲状态。
  2. 主设备和从设备间的每次通信,都需要以一个起始信号开始,以一个结束信号终止。
  3. 起始信号:当SCL处于高电平时,SDA由高变低。
  4. 结束信号:当SCL处于高电平时,SDA由低变高。
  5. 起始信号和结束信号,都只能由主设备产生。

  6. 确认信号:I2C协议规定,发送方每发送一个字节(8位)的数据,接收方都要向发送方回复一个1位的确认信号,0表示接收方已成功接收到该字节,发送方可继续发送下一字节,这个信号在I2C协议中称为ACK(Acknowledge);如果该信号为1,则表示接收方未能成功接收到该字节,或者不希望接收更多数据,该信号在I2C协议中称为NACK(Not Acknowledge)。

  7. 从机地址:一个I2C总线上可能有多个从设备,所以开始通信前,主设备需要先与目标设备取得联系,然后再进行数据传输。

  8. 读写标识:主机还需要向目标设备明确本次通信的操作是写数据还是读数据。

  9. 当主设备发送起始信号之后,会向所有设备发送一个字节的数据,这一个字节中,前7位为目标设备地址,第8位为读/写标识(1表示读,0表示写)。

通信流程:

​1.发送起始信号;

2.发送目标从设备地址和读写标识

3.接收从设备回复的确认信号

4.与从设备数据进行传输

5.发送结束信号

基础驱动编写

发送起始信号

  1. 确保处于空闲状态;
  2. 拉低SDA,发送起始信号;
  3. 拉低SCL,以便准备接下来要发送数据位;

实现代码:

// 发送起始信号
void Dri_IIC_Start() {
    SCL = 1;
    SDA = 1;
    SDA = 0;
    SCL = 0;
}

发送一个字节

  1. SCL低电平期间,准备下一位数据位;
  2. 拉高SCL,并在SCL高电平期间保持SDA稳定;
  3. 拉低SCL,以便准备下一数据位;

代码实现:

// 发送一个字节
void Dri_IIC_SendByte(u8 byte)
{
    u8 i;
    for (i = 0; i < 8; i++) {
        SDA = (byte & (0x80 >> i)) == 0 ? 0 : 1;
        SCL = 1;
        SCL = 0;
    }
}

接收确认信号

  1. 主设备释放SDA,以允许从设备驱动SDA;
  2. 从设备会在SCL低电平期间准备好确认信号;
  3. 拉高SCL,并在SCL高电平期间读取SDA的值;
  4. 拉低SCL,以便准备下一个数据位;

代码实现:

// 接收确认信号
bit Dri_IIC_ReceiveAck() {
    bit ack;
    SDA = 1;
    SCL = 1;
    ack = SDA;
    SCL = 0;
    return ack;
}

接收一个字节

  1. 主设备释放SDA,以允许从设备驱动SDA
  2. 从设备会在SCL低电平期间准备好下一个数据位;
  3. 拉高SCL,并在SCL高电平期间读取SDA的值;
  4. 拉低SCL,以便从设备准备下一个数据位;

代码实现:

// 接收一个字节
u8 Dri_IIC_ReceiveByte()
{
    u8 byte = 0;
    u8 i;
    SDA = 1;
    for (i = 0; i < 8; i++) {
        SCL = 1;
        byte = (byte << 1) | SDA;
        SCL = 0;
    }
    return byte;
}

发送确认信号

  1. SCL低电平期间,准备确认信号;
  2. 拉高SCL,并在SCL高电平期间保持SDA稳定;
  3. 拉低SCL,以便准备下一个数据位;

代码实现:

// 发送确认信号
void Dri_IIC_SendAck(bit ack) {
    SDA = ack;
    SCL = 1;
    SCL = 0;
}

发送结束信号

  1. SCL低电平期间,拉低SDA,准备发送结束信号;
  2. 拉高SCL,准备发送结束信号;
  3. SCL 高电平期间,拉高SDA,发送结束信号;

代码实现:

// 发送结束信号
void Dri_IIC_Stop() {
    SDA = 0;
    SCL = 1;
    SDA = 1;
}

IIC协议—EEPROM

EEPROM(Electrically Erasable Programmable Read-Only Memory,电可擦写可编程只读存储器)是一种非易失性存储器(断电后仍能保留数据),可以多次写入和擦除数据。

EEPROM:叫做只读存储器(ROM),但EEPROM是即可读又可写的。

ROM基本指非易失性存储器。

AT24C020CN:存储容量2K(2048位,256字节)。

引脚功能:

内存结构:

写操作

Byte Write

Byte Write 写入一个字节。

Page Write

page Write 连续写入一页数据:

读操作

AT24C02CN提供了3种读操作,分别是Current Address Read(读当前地址)、Random Read(随机读)、Sequential Read(连续读)。

Current Address Read(读当前地址)

EEPROM内部有一个Address Register(地址寄存器),用于记录当前操作(读/写)的字节地址,每当完成一个字节的操作后(读/写),该地址会自动指向下一个字节。

Current Address Read读取的就是Address Register中的地址所指向的这一个字节

Random Read(随机读)

Random Read用于读取任意指定地址的一个字节。

Sequential Read(连续读)

Sequential Read用于读取连续的多个字节。其起始位置可以是Address Register记录的当前地址,也可以是用户指定的任意位置,指定起始位置的方式仍然是在Sequential Read前增加一个Dummy Write操作

先向EEPROM写入点阵LED中的“喜欢你”三个字的字模,再将其读取来并显示在点阵LED上。

原理图:

代码实现

// Int_EEPROM.c
#include "Int_EEPROM.h"
#include "Dri_IIC.h"
#define DEV_ADDR  0XA0
#define PAGE_SIZE 16

void Int_EEPROM_WritePage(u8 addr, u8 bytes[], u8 len)
{
    u8 i;
    Dri_IIC_Start();
    Dri_IIC_SendByte(DEV_ADDR);
    Dri_IIC_ReceiveAck();
    Dri_IIC_SendByte(addr);
    Dri_IIC_ReceiveAck();
    for (i = 0; i < len; i++) {
        Dri_IIC_SendByte(bytes[i]);
        Dri_IIC_ReceiveAck();
    }
    Dri_IIC_Stop();
    // 写入周期twr 3ms 需要延时
    Com_Util_Delay1ms(5);
}

void Int_EEPROM_WriteBytes(u8 addr, u8 bytes[], u8 len)
{
    u8 page_remain = PAGE_SIZE - addr % PAGE_SIZE;
    if (len > page_remain) {
        // 当前页空间不足
        // 写满当前页
        Int_EEPROM_WritePage(addr, bytes, page_remain);
        // 写剩余内容
        Int_EEPROM_WriteBytes(addr + page_remain, bytes + page_remain, len - page_remain);
    } else {
        // 当前页空间充足
        Int_EEPROM_WritePage(addr, bytes, len);
    }
}

void Int_EEPROM_ReadBytes(u8 addr, u8 bytes[], u8 len)
{
    u8 i;
    Dri_IIC_Start();
    Dri_IIC_SendByte(DEV_ADDR);
    Dri_IIC_ReceiveAck();
    Dri_IIC_SendByte(addr);
    Dri_IIC_ReceiveAck();
    Dri_IIC_Start();
    Dri_IIC_SendByte(DEV_ADDR + 1);
    Dri_IIC_ReceiveAck();
    for (i = 0; i < len; i++) {
        bytes[i] = Dri_IIC_ReceiveByte();
        Dri_IIC_SendAck(i == len - 1 ? 1 : 0); 
    }
    Dri_IIC_Stop();
}

// Int_EEPROM.h
#ifndef __INT_EEPROM_H__
#define __INT_EEPROM_H__
#include "Com_Util.h"
/**
 * @brief 发送多个字节
 * 
 * @param addr 字节地址
 * @param bytes 待发送的数据
 * @param len 数组长度
 */
void Int_EEPROM_WriteBytes(u8 addr, u8 bytes[], u8 len);
/**
 * @brief 接收多个字节
 * 
 * @param addr 字节地址
 * @param bytes 用于接收结果的数组
 * @param len 要读取的长度
 */
void Int_EEPROM_ReadBytes(u8 addr, u8 bytes[], u8 len);

#endif /* __INT_EEPROM_H__ */

// main.c
#include "Int_LEDMatrix.h"
#include "Dri_Timer0.h"
#include "Int_EEPROM.h"

u8 picture[26] = {0xF8, 0x0A, 0xEC, 0xAF, 0xEC, 0x8A, 0xF8, 0x00,
                  0x10, 0xF9, 0x97, 0xF1, 0x88, 0xAA, 0xFF, 0xAA,
                  0x88, 0x00, 0x14, 0x0A, 0xF5, 0x92, 0x92, 0xF5,
                  0x0A, 0x14};
u8 buffer[26];
void main()
{
    u8 i;
    Dri_Timer0_Init();
    Int_LEDMatrix_Init();
    Int_EEPROM_WriteBytes(0, picture, 26);
    Int_EEPROM_ReadBytes(0, buffer, 26);
    while (1) {
        for (i = 0; i < 26; i++) {
            Int_LEDMatrix_Shift(buffer[i]);
            Com_Util_Delay1ms(200);
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值