| 
 【虹星宝典】记录单片机学习之旅——目录
 
 
 I2C协议,也称iic协议。常用于短距离、低速设备间的数据传输,是有飞利浦(NXP)开发的同步串行通信协议,仅需两根线(SDA:数据线;SCL:时钟线)即可进行多设备的半双工互联通信。
 基本的通讯时序分为:起始信号(IIC_Start)、结束信号(IIC_Stop)、发送ACK应答信号(IIC_SendACK)、发送NACK应答信号(IIC_SendNoACK)、接收ACK/NACK应答信号(IIC_WaitACK)和读/写1字节数据,并可以衍生出读/写N字节数据等。
 本篇作为IIC协议入门实验的器件是AT24C128(实验箱上为AT24C02)。
 
 
 
 一、IIC部分
 【时刻周期】
 以SCL的脉冲信号为标准(这个可以决定IIC的通讯速率)。
 当SCL=0时,SDA的电平无效,此时可以随意改变SDA的电平,即从蓝色到黄色这段时间。
 当SCL=1时,SDA的电平需要保持稳定,持续到再一次SCL=0的瞬间(及SCL维持信号稳定的一小段时间),此时数据有效,将被接收方读取,
 即从黄色到红色这段时间。一个SCL周期,传输1bit数据。
 
 复制代码void IIC_Wait(void)
{
    Delay2us();
}
 
 
   
 
 【起始信号】和【结束信号】
 起始信号:先拉低SDA总线,再拉低SCL总线
 
 复制代码void IIC_Start(void)
{
    IIC_SCL = 1;
    IIC_SDA = 1;
    IIC_Wait();
    IIC_SDA = 0;
    IIC_Wait();
    IIC_SCL = 0;
    IIC_Wait();
}
 
 结束信号:先拉高SCL总线,再拉高SDA总线
 
 复制代码void IIC_Stop(void)
{
    IIC_SCL = 0;
    IIC_SDA = 0;
    IIC_Wait();
    IIC_SCL = 1;
    IIC_Wait();
    IIC_SDA = 1; 
    IIC_Wait();
}
 
 
   
 
 【发送ACK应答信号】和【发送NACK应答信号】
 发送ACK信号:1位数据,SDA=0
 
 复制代码void IIC_SendACK(void)
{
    IIC_SDA = 0;
    IIC_Wait();
    IIC_SCL = 1;
    IIC_Wait();
    IIC_SCL = 0;
    IIC_Wait();
}
 
 发送NACK信号:1位数据,SDA=1
 
 复制代码void IIC_SendNoACK(void)
{
    IIC_SDA = 1;
    IIC_Wait();
    IIC_SCL = 1;
    IIC_Wait();
    IIC_SCL = 0;
    IIC_Wait();
}
 
 
   
 
 【接收ACK/NACK应答信号】
 接收和发送原理一致。区别是作为接收方。
 
 复制代码u8 IIC_WaitACK(void)
{
    u8 ack = 0;
    IIC_SDA = 1;
    IIC_Wait();
    IIC_SCL = 1;
    IIC_Wait();
    ack = IIC_SDA;  // 0:ACK 1:NACK
    IIC_Wait();
    IIC_SCL = 0;
    IIC_Wait();
    return !ack;    // 取反后 1:有应答 0:无应答
}
 
 【读/写1字节数据】
 一般8bit数据+1bitACK信号作为一包数据。
 
 复制代码u8 IIC_R_Byte(void)
{
    u8 i = 8, dat = 0;
    IIC_SDA = 1;
    IIC_Wait();
    do
    {
        IIC_SCL = 1;
        IIC_Wait();
        dat <<= 1;
        if (IIC_SDA)
            dat |= 1;
        IIC_SCL = 0;
        IIC_Wait();
    } while (--i);
    return dat;
}
 
 
   
 
 
 复制代码void IIC_W_Byte(u8 dat)
{
    u8 i = 8;
    do
    {
        if (dat & 0x80)
            IIC_SDA = 1;
        else
            IIC_SDA = 0;
        IIC_Wait();
        dat <<= 1;
        IIC_SCL = 1;
        IIC_Wait();
        IIC_SCL = 0;
        IIC_Wait();
    } while (--i);
}
 
 
   
 
 二、AT24Cxx部分
 主要用途
 AT24CXX是一种EEPROM存储器,主要用于失去电源时,仍可以储存处理器的重要数据。使用I2C协议进行数据通信,具有宽电压(约1.7V to 5.5V)
 
 
 从机地址
 
 AT24CXX最多有3个从机地址配置引脚(A0/A1/A2),即最多挂载8个从机。
 01 = A0/A1/A2        *08-byte Page Write mode*
 02 = A0/A1/A2        *08-byte Page Write mode*
 04 = NC/A1/A2        *16-byte Page Write Mode*
 08 = NC/NC/A2        *16-byte Page Write Mode*
 16 = NC/NC/NC        *16-byte Page Write Mode*
 32 = A0/A1/A2        *32-byte Page Write Mode*
 64 = A0/A1/A2        *32-byte Page Write Mode*
 128 = A0/A1/A2        *64-byte Page Write Mode*
 256 = A0/A1/A2        *64-byte Page Write Mode*
 512 = A0/A1/A2        *128-byte Page Write Mode*
 1024 = NC/A1/NC
 
 
 
 命名含义
 
 其中XX可代表存储的容量大小,一般为XX*128byte:
 01 = 128 Byte = 1K bit
 02 = 256 Byte = 2K bit
 04 = 512 Byte = 4K bit
 08 = 1024 Byte = 8K bit
 16 = 2048 Byte = 16K bit
 32 = 4096 Byte = 32K bit
 64 = 8192 Byte = 64K bit
 128 = 16384 Byte = 128K bit
 256 = 32768 Byte = 256K bit
 512 = 65536 Byte = 512K bit
 1024 = 131072 Byte = 1M bit
 
 
 
 数据读写
 
 每个数据地址可以保存1Byte数据(即char/u8类型)。
 一般读写的协议格式为:(具体请看对应的手册)
         对于01/02/04/08/16为【从机地址】【数据地址】【数据】....
         对于32/64/128/256.等为【从机地址】【数据地址高位】【数据地址低位】【数据】....
 由于1字节(8bit)最多描述2^8=256个,所以01(128)/02(256)有3个地址配置引脚可用。
 04(512),需要9bit,所有只有2个地址配置引脚,而将【从机地址】中对应的NC位作为第9bit
 08(1024),需要10bit,同理2个NC位作为第9/10bit
 16(2048),需要11bit,3个地址配置引脚均为NC。
 而从32(4096)开始,【数据地址】使用2Byte(16bit)表示,最多可以描述2^16=65536个,因此不需要向地址配置引脚借位。1024(131072)需要借1位,当手册中有2个NC位,这个用的少不深入讨论了。
 注:AT24Cxx每次连续写入,最多写1页数据,便要等AT24Cxx完成1次擦写周期,一般为5-10ms。
 
 
 
 
 复制代码u8 AT24Cxx_R_Byte(u32 AT24Cxx_Type, u8 AT24Cxx_id, u16 AT24Cxx_addr)
{
        u8 R_Byte;
        if (AT24Cxx_addr > AT24Cxx_Type)
                return 0xFF;
        // 1字节地址长度
    if (AT24Cxx_Type <= AT24C16)
        {
                IIC_R_NByte(AT24Cxx_id | (u8)((AT24Cxx_addr >> 8) << 1), AT24Cxx_addr, IIC_REG_TYPE_1Byte, &R_Byte, 1);
        }
        // 2字节地址长度
        else
        {
                IIC_R_NByte(AT24Cxx_id, AT24Cxx_addr, IIC_REG_TYPE_2Byte, &R_Byte, 1);
        }
        return R_Byte;
}
void AT24Cxx_R_NByte(u32 AT24Cxx_Type, u8 AT24Cxx_id, u16 AT24Cxx_addr, u8 *R_buff, u32 num)
{
        if ((AT24Cxx_addr + num - 1) > AT24Cxx_Type)
                return;
        // 1字节地址长度
    if (AT24Cxx_Type <= AT24C16)
        {
                IIC_R_NByte(AT24Cxx_id | (u8)((AT24Cxx_addr >> 8) << 1), AT24Cxx_addr, IIC_REG_TYPE_1Byte, R_buff, num);
        }
        // 2字节地址长度
        else
        {
                IIC_R_NByte(AT24Cxx_id, AT24Cxx_addr, IIC_REG_TYPE_2Byte, R_buff, num);
        }
}
 
 复制代码#define Write_Cycle_Time 400        // 400us
void AT24Cxx_W_Byte(u32 AT24Cxx_Type, u8 AT24Cxx_id, u16 AT24Cxx_addr, u8 W_Byte)
{
        if (AT24Cxx_addr > AT24Cxx_Type)
                return ;
        // 1字节地址长度
    if (AT24Cxx_Type <= AT24C16)
        {
                IIC_W_NByte(AT24Cxx_id | (u8)((AT24Cxx_addr >> 8) << 1), AT24Cxx_addr, IIC_REG_TYPE_1Byte, &W_Byte, 1);
        }
        // 2字节地址长度
        else
        {
                IIC_W_NByte(AT24Cxx_id, AT24Cxx_addr, IIC_REG_TYPE_2Byte, &W_Byte, 1);
        }
        delay_us(Write_Cycle_Time);        // 等待一段写周期 一般为5-10ms 测试最短为400-500us
}
void AT24Cxx_W_NByte(u32 AT24Cxx_Type, u8 AT24Cxx_id, u16 AT24Cxx_addr, u8 *W_buff, u32 num)
{
        u16 addr_offset = 0;        // 地址偏移量
        u16 page = 0;                        // 计算要写多少页
        u8 Remainder = 0;                // 不满1页
        
        if ((AT24Cxx_addr + num - 1) > AT24Cxx_Type)
                return;
        switch (AT24Cxx_Type)
        {
        // 08-byte Page Write mode
        case AT24C01:
        case AT24C02:
                for (page = 0; page < (num / 8); page++)
                {
                        addr_offset = AT24Cxx_addr + page * 8;
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_1Byte, &W_buff[page * 8], 8);
                        delay_us(Write_Cycle_Time);
                }
                addr_offset = AT24Cxx_addr + page * 8;
                Remainder = num % 8;
                // 如果有空余数据
                if (Remainder)
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_1Byte, &W_buff[page * 8], Remainder);
                break;
        // 16-byte Page Write mode
        case AT24C04:
        case AT24C08:
        case AT24C16:
                for (page = 0; page < (num / 16); page++)
                {
                        addr_offset = AT24Cxx_addr + page * 16;
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_1Byte, &W_buff[page * 16], 16);
                        delay_us(Write_Cycle_Time);
                }
                addr_offset = AT24Cxx_addr + page * 16;
                Remainder = num % 16;
                // 如果有空余数据
                if (Remainder)
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_1Byte, &W_buff[page * 16], Remainder);
                break;
        // 32-byte Page Write mode
        case AT24C32:
        case AT24C64:
                for (page = 0; page < (num / 32); page++)
                {
                        addr_offset = AT24Cxx_addr + page * 32;
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_2Byte, &W_buff[page * 32], 32);
                        delay_us(Write_Cycle_Time);
                }
                addr_offset = AT24Cxx_addr + page * 32;
                Remainder = num % 32;
                // 如果有空余数据
                if (Remainder)
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_2Byte, &W_buff[page * 32], Remainder);
                break;
        // 64-byte Page Write mode
        case AT24C128:
        case AT24C256:
                for (page = 0; page < (num / 64); page++)
                {
                        addr_offset = AT24Cxx_addr + page * 64;
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_2Byte, &W_buff[page * 64], 64);
                        delay_us(Write_Cycle_Time);
                }
                addr_offset = AT24Cxx_addr + page * 64;
                Remainder = num % 64;
                // 如果有空余数据
                if (Remainder)
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_2Byte, &W_buff[page * 64], Remainder);
                break;
        // 128-byte Page Write mode
        case AT24C512:
                for (page = 0; page < (num / 128); page++)
                {
                        addr_offset = AT24Cxx_addr + page * 128;
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_2Byte, &W_buff[page * 128], 128);
                        delay_us(Write_Cycle_Time);
                }
                addr_offset = AT24Cxx_addr + page * 128;
                Remainder = num % 128;
                // 如果有空余数据
                if (Remainder)
                        IIC_W_NByte(AT24Cxx_id | (u8)((addr_offset >> 8) << 1), addr_offset, IIC_REG_TYPE_2Byte, &W_buff[page * 128], Remainder);
                break;
        default:
                break;
        }
        delay_us(Write_Cycle_Time);
}
 
 三、实验结果
 
 简单搞了一个串口读写和擦除的指令。
 W(写指令):往地址0-254写入0-254,对于AT24C02(ai8051u实验箱)的地址255是校验码。
 R(读指令):把地址0-255的数据读出来
 E(擦除指令):往地址0-254的数据写0
 
 
 复制代码// 写 W(0x57)
        if (RX1_Buffer[0] == 'W')
        {
            // at24c02 实验箱
            // Uart1_printf("开始写入\r\n");
            // for ( i = 0; i < 128; i++)
            // {
            //     TX1_Buffer[i] = i;
            // }
            // AT24Cxx_W_NByte(AT24C02, AT24C02_ID1, 0, TX1_Buffer, 128);
            // for ( i = 128; i < 255; i++)
            // {
            //     TX1_Buffer[i-128] = i;
            // }
            // AT24Cxx_W_NByte(AT24C02, AT24C02_ID1, 128, TX1_Buffer, 127);
            // Uart1_printf("写入完成\r\n");
            
            // at24128 
            Uart1_printf("开始写入\r\n");
            for ( i = 0; i < 128; i++)
            {
                TX1_Buffer[i] = i;
            }
            AT24Cxx_W_NByte(AT24C128, AT24C128_ID1, 0, TX1_Buffer, 128);
            for ( i = 128; i < 255; i++)
            {
                TX1_Buffer[i-128] = i;
            }
            AT24Cxx_W_NByte(AT24C128, AT24C128_ID1, 128, TX1_Buffer, 127);
            Uart1_printf("写入完成\r\n");
        }
        // 读 R(0x52)
        else if (RX1_Buffer[0] == 'R')
        {
            // at24c02 实验箱
            // for ( i = 0; i < 96; i++)
            // {
            //     TX1_Buffer[i] = AT24Cxx_R_Byte(AT24C02, AT24C02_ID1, i);
            // }
            // Uart1_Send_NByte(TX1_Buffer, 96);
            // for ( i = 0; i < 96; i++)
            // {
            //     TX1_Buffer[i] = AT24Cxx_R_Byte(AT24C02, AT24C02_ID1, 96 + i);
            // }
            // Uart1_Send_NByte(TX1_Buffer, 96);
            // for ( i = 0; i < 64; i++)
            // {
            //     TX1_Buffer[i] = AT24Cxx_R_Byte(AT24C02, AT24C02_ID1, 192 + i);
            // }
            // Uart1_Send_NByte(TX1_Buffer, 64);
            // at24c128
            for ( i = 0; i < 96; i++)
            {
                TX1_Buffer[i] = AT24Cxx_R_Byte(AT24C128, AT24C128_ID1, i);
            }
            Uart1_Send_NByte(TX1_Buffer, 96);
            for ( i = 0; i < 96; i++)
            {
                TX1_Buffer[i] = AT24Cxx_R_Byte(AT24C128, AT24C128_ID1, 96 + i);
            }
            Uart1_Send_NByte(TX1_Buffer, 96);
            for ( i = 0; i < 64; i++)
            {
                TX1_Buffer[i] = AT24Cxx_R_Byte(AT24C128, AT24C128_ID1, 192 + i);
            }
            Uart1_Send_NByte(TX1_Buffer, 64);
        }
        // 擦除 E(0x45)
        else if (RX1_Buffer[0] == 'E')
        {
            // at24c02 实验箱
            // Uart1_printf("开始擦除\xfd\r\n");
            // for ( i = 0; i < 0xff; i++)
            // {
            //     AT24Cxx_W_Byte(AT24C02, AT24C02_ID1, i, 0);
            //     delay_us(500);
            // }
            // Uart1_printf("擦除\xfd完成\r\n");
            // at24c128
            Uart1_printf("开始擦除\xfd\r\n");
            for ( i = 0; i < 0xff; i++)
            {
                AT24Cxx_W_Byte(AT24C128, AT24C128_ID1, i, 0);
                delay_us(500);
            }
            Uart1_printf("擦除\xfd完成\r\n");
        }
 
 
 结果如下:
 先读一下,数据全为0。
 
 
   再执行写命令
 
   再读一下,数据正常
 
   擦除指令
 
   再读一下,数据已全部擦除
 
   
 
 番外-有趣的实验
 
 实验箱中测试2.5M速率和50K速率。从发送读取指令,到第1次接收串口反馈的时间差。约读96字节数据
 
 2.5M:约20ms
 50K:约90ms
 
 
   
 
 
 好奇又测了几组:
 1M,约20ms
 
 
   
 
 500K,约20ms
 
   
 
 400K,约20ms
 
   
 
 250K,约30ms
 
   
 
 125K,约40ms
 
   
 
 100K,约50-60ms
 
   
 
 50K,约90-100ms
 
   
 
 结果表明,400K大约就是最大速率了,再高也会因为从机跟不上而限速了。
 
 
 如何使用?
 
   config.h里选择软硬件模式
 
 
 
   iic.h里修改引脚定义,注意修改GPIO初始化
 
 
 
   主函数初始化GPIO和IIC即可。
 具体任务在task_uart1_polling.c中
 
 
 
 
  13_IIC-AT24C128.zip
(442.34 KB, 下载次数: 4) 
  IIC总线协议手册.pdf
(1.29 MB, 下载次数: 2) 
  AT24C01_02C.pdf
(954.67 KB, 下载次数: 1) 
  AT24C04_08C.pdf
(879.92 KB, 下载次数: 1) 
  AT24C16C.pdf
(992.86 KB, 下载次数: 2) 
  AT24C32_64C.pdf
(812.67 KB, 下载次数: 1) 
  AT24C128_256C.pdf
(1.18 MB, 下载次数: 1) 
  AT24C256C.pdf
(900.91 KB, 下载次数: 2) 
  AT24C512C.pdf
(954.76 KB, 下载次数: 3) 
  AT24C1024.pdf
(212.73 KB, 下载次数: 3) 
 
 
 
 |