查看: 1463|回复: 1
收起左侧

第三十七章:CH32V103应用教程——I2C-软件模拟I2C读写EEPROM

[复制链接]

  离线 

  • TA的每日心情
    慵懒
    2021-7-23 17:16
  • 签到天数: 17 天

    [LV.4]

    发表于 2021-4-27 21:13:46 | 显示全部楼层 |阅读模式

    有人预言,RISC-V或将是继Intel和Arm之后的第三大主流处理器体系。欢迎访问全球首家只专注于RISC-V单片机行业应用的中文网站

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    本帖最后由 草帽王子 于 2021-9-10 17:24 编辑

    前面章节第14章已经进行过硬件IIC读写EEPROM的实验,本章教程将使用软件模拟IIC读写EEPROM,并通过串口调试助手将读写结果打印显示。


    1、I2C简介及相关函数介绍

    内部集成电路总线(I2C)是一种两线式串行总线,可用于微控制器及其外围设备之间的通信。I2C总线由数据线SDA和时钟线SCL构成,可进行数据发送和接收,其通过上拉电阻接电源,当I2C总线空闲时,会输出高组态,此时数据线SDA和时钟线SCL均处于高电平。

    I2C总线在使用过程中具有以下状态和信号:
    • 空闲状态:数据线SDA和时钟线SCL均处于高电平;
    • 起始信号:时钟线SCL为高电平,数据线SDA由高电平向低电平跳变;
    • 停止信号:时钟线SCL为高电平,数据线SDA由低电平向高电平跳变;
    • 应答信号:应答信号分为ACK信号和NACK信号两种。主机在数据传输时会产生时钟,在产生第九个时钟时(即主机每发送完8位数据或地址),数据发送端会释放SDA的控制权,由数据接收端控制SDA,此时若SDA为高电平,则表示NACK应答信号,若SCL为低电平,则表示ACK应答信号。当为ACK信号时,发送方会继续发送下一个数据;为NACK信号时,则发送方会产生一个停止信号,结束数据传输。

    关于硬件I2C和软件模拟I2C的区别,第14章已经进行过介绍,在此不再赘述。

    关于I2C具体信息,可参考CH32V103应用手册。I2C标准库函数在第十四章节已介绍,在此不再赘述。


    2、硬件设计

    本章教程使用软件模拟I2C读写24C02,程序中配置PA1作为SDA线,PA2作为SCL线,连接方式如下:
    • PA1连接J5的SCL引脚
    • PA2连接J5的SDA引脚


    3、软件设计

    软件模拟I2C读写24C02相较于硬件I2C读写24C02在程序代码量上多了很多,具体程序如下:
    iic.h文件
    1. #ifndef __IIC_H
    2. #define __IIC_H

    3. #include "ch32v10x_conf.h"

    4. #define  IIC_SDA_H  GPIO_SetBits(GPIOA,GPIO_Pin_1)    //配置SDA接口高电平
    5. #define  IIC_SDA_L  GPIO_ResetBits(GPIOA,GPIO_Pin_1)  //配置SDA接口低电平

    6. #define  IIC_SCL_H  GPIO_SetBits(GPIOA,GPIO_Pin_2)    //配置SCL接口高电平
    7. #define  IIC_SCL_L  GPIO_ResetBits(GPIOA,GPIO_Pin_2)  //配置SCL接口低电平

    8. #define  I2C_SDA_READ()  GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)  //读SDA口线状态

    9. void IIC_Init(void);         //IIC初始化函数
    10. void IIC_Idle_State(void);   //IIC空闲状态
    11. void IIC_Start(void);        //IIC开始信号函数
    12. void IIC_Stop(void);         //IIC停止信号函数
    13. void IIC_SendByte(u8 data);  //IIC发送一个字节
    14. u8   IIC_ReadByte(void);     //IIC读取一个字节
    15. u8   IIC_WaitAck(void);      //等待响应信号(ACK或者NACK)
    16. void IIC_ACK(void);          //IIC发出ACK信号
    17. void IIC_NACK(void);         //IIC发出NACK信号

    18. #endif
    复制代码
    iic.h文件主要进行相关宏定义和函数声明;
    iic.c文件
    1. #include "iic.h"

    2. //初始化IIC
    3. void IIC_Init(void)
    4. {
    5.     GPIO_InitTypeDef GPIO_InitStructure;

    6.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

    7.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;//使用PA1和PA2作为模拟IIC引脚,PA1对应SDA,PA2对应SCL
    8.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ;   //开漏输出模式
    9.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    10.     GPIO_Init(GPIOA, &GPIO_InitStructure);

    11.     IIC_Idle_State();
    12. }

    13. //IIC空闲状态
    14. //当IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为IIC总线的空闲状态
    15. void IIC_Idle_State()
    16. {
    17.     IIC_SDA_H;
    18.     IIC_SCL_H;

    19.     Delay_Us(4);
    20. }

    21. //IIC开始信号
    22. //当IIC SCL线处于高电平时,SDA线由高电平向低电平跳变,为IIC开始信号,配置开始信号前必须保证IIC总线处于空闲状态
    23. void IIC_Start()
    24. {
    25.     IIC_SDA_H;
    26.     IIC_SCL_H;

    27.     Delay_Us(4);

    28.     IIC_SDA_L;
    29.     Delay_Us(4);
    30.     IIC_SCL_L;
    31.     Delay_Us(4);
    32. }

    33. //IIC停止信号
    34. //当IIC SCL线处于高电平时,SDA线由低电平向高电平跳变,为IIC停止信号
    35. void IIC_Stop()
    36. {
    37.     IIC_SDA_L;
    38.     IIC_SCL_H;

    39.     Delay_Us(4);

    40.     IIC_SDA_H;
    41. }

    42. //IIC发送一个字节数据(即8bit)
    43. void IIC_SendByte(u8 data)
    44. {
    45.     u8 i;
    46.     //先发送字节的高位bit7
    47.     for (i = 0; i < 8; i++)
    48.     {
    49.         if (data & 0x80)  //判断8位数据每一位的值(0或1)
    50.         {
    51.             IIC_SDA_H;
    52.         }
    53.         else
    54.         {
    55.             IIC_SDA_L;
    56.         }

    57.         Delay_Us(4);      //控制SCL线产生高低电平跳变,产生通讯时钟,同时利用延时函数在SCL为高电平期间读取SDA线电平逻辑
    58.         IIC_SCL_H;
    59.         Delay_Us(4);
    60.         IIC_SCL_L;

    61.         if (i == 7)
    62.         {
    63.             IIC_SDA_H;    //控制SDA线输出高电平,释放总线,等待接收方应答信号
    64.         }

    65.         data <<= 1;       //左移一个bit
    66.         Delay_Us(4);
    67.     }
    68. }

    69. //IIC读取一个字节
    70. u8 IIC_ReadByte(void)
    71. {
    72.     u8 i;
    73.     u8 value;

    74.     //读到第1个bit为数据的bit7
    75.     value = 0;
    76.     for(i = 0; i < 8; i++)
    77.     {
    78.         value <<= 1;
    79.         IIC_SCL_H;
    80.         Delay_Us(4);
    81.         if (I2C_SDA_READ()) //利用延时函数在SCL为高电平期间读取SDA线电平逻辑
    82.         {
    83.             value++;
    84.         }
    85.         IIC_SCL_L;
    86.         Delay_Us(4);
    87.     }
    88.     return value;
    89. }


    90. //IIC等待应答信号
    91. u8 IIC_WaitAck(void)
    92. {
    93.     uint8_t rvalue;

    94.     IIC_SDA_H;     //发送端释放SDA总线,由接收端控制SDA线
    95.     Delay_Us(4);
    96.     IIC_SCL_H;     //在SCL为高电平期间等待响应,若SDA线为高电平,表示NACK信号,反之则为ACK信号
    97.     Delay_Us(4);
    98.     if(I2C_SDA_READ())  //读取SDA线状态判断响应类型,高电平,返回去,为NACK信号,反之则为ACK信号
    99.     {
    100.         rvalue = 1;
    101.     }
    102.     else
    103.     {
    104.         rvalue = 0;
    105.     }
    106.     IIC_SCL_L;
    107.     Delay_Us(4);
    108.     return rvalue;
    109. }

    110. //产生应答信号ACK
    111. void IIC_ACK(void)
    112. {
    113.     IIC_SDA_L;
    114.     Delay_Us(4);
    115.     IIC_SCL_H;   //在SCL线为高电平期间读取SDA线为低电平,则为ACK响应
    116.     Delay_Us(4);
    117.     IIC_SCL_L;
    118.     Delay_Us(4);
    119.     IIC_SDA_H;
    120. }

    121. //产生非应答信号NACK
    122. void IIC_NACK(void)
    123. {
    124.     IIC_SDA_H;
    125.     Delay_Us(4);
    126.     IIC_SCL_H;   //在SCL线为高电平期间读取SDA线为高电平,则为NACK响应
    127.     Delay_Us(4);
    128.     IIC_SCL_L;
    129.     Delay_Us(4);
    130. }

    复制代码
    iic.c文件主要进行模拟I2C对应GPIO引脚初始化以及I2C在数据读写传输过程中各状态和信号的表示函数,具体如下:

    1、因为采用模拟I2C,且设置PA1对应SDA线,PA2对应SCL线,因此需要对PA1和PA2进行GPIO初始化设置,具体如下:
    1. //初始化IIC
    2. void IIC_Init(void)
    3. {
    4.     GPIO_InitTypeDef GPIO_InitStructure;

    5.     RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOA, ENABLE );

    6.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1|GPIO_Pin_2;//使用PA1和PA2作为模拟IIC引脚,PA1对应SDA,PA2对应SCL
    7.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD ;   //开漏输出模式
    8.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    9.     GPIO_Init(GPIOA, &GPIO_InitStructure);

    10.     IIC_Idle_State();
    11. }
    复制代码
    此处注意设置PA1和PA2的GPIO模式为开漏输出模式,因为I2C总线一般可以连接多个器件,如果选用推挽输出模式,可能会产生很大的电流从而导致器件毁坏,而开漏输出在不接上拉电阻情况下无法输出高电平,加上拉电阻时电流由上拉电阻决定,因此使用开漏输出模式。还有一个原因就是开漏输出可以匹配电平,满足电流型的驱动。

    2、I2C空闲状态函数,当IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为IIC总线的空闲状态,函数具体如下:
    1. //IIC空闲状态
    2. //当IIC总线的SDA和SCL两条信号线同时处于高电平时,规定为IIC总线的空闲状态
    3. void IIC_Idle_State()
    4. {
    5.     IIC_SDA_H;
    6.     IIC_SCL_H;

    7.     Delay_Us(4);
    8. }
    复制代码

    3、I2C开始信号函数,当IIC SCL线处于高电平时,SDA线由高电平向低电平跳变,为IIC开始信号,配置开始信号前必须保证IIC总线处于空闲状态,函数具体如下:

    1. //IIC开始信号
    2. //当IIC SCL线处于高电平时,SDA线由高电平向低电平跳变,为IIC开始信号,配置开始信号前必须保证IIC总线处于空闲状态
    3. void IIC_Start()
    4. {
    5.     IIC_SDA_H;
    6.     IIC_SCL_H;

    7.     Delay_Us(4);

    8.     IIC_SDA_L;
    9.     Delay_Us(4);
    10.     IIC_SCL_L;
    11.     Delay_Us(4);
    12. }
    复制代码

    4、I2C停止信号函数,当IIC SCL线处于高电平时,SDA线由低电平向高电平跳变,为IIC停止信号,函数具体如下:
    1. //IIC停止信号
    2. //当IIC SCL线处于高电平时,SDA线由低电平向高电平跳变,为IIC停止信号
    3. void IIC_Stop()
    4. {
    5.     IIC_SDA_L;
    6.     IIC_SCL_H;

    7.     Delay_Us(4);

    8.     IIC_SDA_H;
    9. }
    复制代码

    5、I2C发送一个字节数据,一个字节数据即8位,对每一位值进行判断其为0还是1(从最高位起),并根据判断值控制SDA线高低电平变化,每判断完成一位,则左移一位。在此期间,要控制SCL线高低电平变化,具有通讯时钟的效果。同时利用延时函数在SCL线为高电平期间读取SDA线值大小。待8位数据传输完成之后,释放总线,等待接收方应答信号。具体函数如下:
    1. //IIC发送一个字节数据(即8bit)
    2. void IIC_SendByte(u8 data)
    3. {
    4.     u8 i;
    5.     //先发送字节的高位bit7
    6.     for (i = 0; i < 8; i++)
    7.     {
    8.         if (data & 0x80)  //判断8位数据每一位的值(0或1)
    9.         {
    10.             IIC_SDA_H;
    11.         }
    12.         else
    13.         {
    14.             IIC_SDA_L;
    15.         }

    16.         Delay_Us(4);      //控制SCL线产生高低电平跳变,产生通讯时钟,同时利用延时函数在SCL为高电平期间读取SDA线电平逻辑
    17.         IIC_SCL_H;
    18.         Delay_Us(4);
    19.         IIC_SCL_L;

    20.         if (i == 7)
    21.         {
    22.             IIC_SDA_H;    //控制SDA线输出高电平,释放总线,等待接收方应答信号
    23.         }

    24.         data <<= 1;       //左移一个bit
    25.         Delay_Us(4);
    26.     }
    27. }
    复制代码

    6、I2C读取一个字节数据,一个字节数据即8位,利用for循环在SCL为高电平期间对SDA数据线进行读取,依次判断每一位值为0还是1(共八位),最后将读取结果返回。具体函数如下:
    1. //IIC读取一个字节
    2. u8 IIC_ReadByte(void)
    3. {
    4.     u8 i;
    5.     u8 value;

    6.     //读到第1个bit为数据的bit7
    7.     value = 0;
    8.     for(i = 0; i < 8; i++)
    9.     {
    10.         value <<= 1;
    11.         IIC_SCL_H;
    12.         Delay_Us(4);
    13.         if (I2C_SDA_READ()) //利用延时函数在SCL为高电平期间读取SDA线电平逻辑
    14.         {
    15.             value++;
    16.         }
    17.         IIC_SCL_L;
    18.         Delay_Us(4);
    19.     }
    20.     return value;
    21. }
    复制代码

    7、I2C等待应答信号函数,当发送端发送完一个字节数据后,会释放对SDA线的控制权,及SDA线拉高,为1,此时由接收方控制SDA线,并控制其产生高低电平。在SCL线为高电平期间,发送端读取SDA线高低电平信号,若为低电平,表示ACK信号,若为高电平,表示NACK信号。具体函数如下:
    1. //IIC等待应答信号
    2. u8 IIC_WaitAck(void)
    3. {
    4.     uint8_t rvalue;

    5.     IIC_SDA_H;     //发送端释放SDA总线,由接收端控制SDA线
    6.     Delay_Us(4);
    7.     IIC_SCL_H;     //在SCL为高电平期间等待响应,若SDA线为高电平,表示NACK信号,反之则为ACK信号
    8.     Delay_Us(4);
    9.     if(I2C_SDA_READ())  //读取SDA线状态判断响应类型,高电平,返回去,为NACK信号,反之则为ACK信号
    10.     {
    11.         rvalue = 1;
    12.     }
    13.     else
    14.     {
    15.         rvalue = 0;
    16.     }
    17.     IIC_SCL_L;
    18.     Delay_Us(4);
    19.     return rvalue;
    20. }
    复制代码

    8、I2C应答信号函数和非应答信号函数。I2C 的数据和地址传输都带响应。响应包括“应答(ACK)”和“非应答(NACK)”两种 信号。作为数据接收端时,当设备(无论主从机)接收到 I2C 传输的一个字节数据或地址后, 若希望对方继续发送数据,则需要向对方发送“应答(ACK)”信号,发送方会继续发送下 一个数据;若接收端希望结束数据传输,则向对方发送“非应答(NACK)”信号,发送方接 收到该信号后会产生一个停止信号,结束信号传输。在SCL线为高电平期间读取SDA线为低电平,则为ACK响应;在SCL线为高电平期间读取SDA线为高电平,则为NACK响应。关于应答信号和非应答信号函数具体如下:
    1. //产生应答信号ACK
    2. void IIC_ACK(void)
    3. {
    4.     IIC_SDA_L;
    5.     Delay_Us(4);
    6.     IIC_SCL_H;   //在SCL线为高电平期间读取SDA线为低电平,则为ACK响应
    7.     Delay_Us(4);
    8.     IIC_SCL_L;
    9.     Delay_Us(4);
    10.     IIC_SDA_H;
    11. }

    12. //产生非应答信号NACK
    13. void IIC_NACK(void)
    14. {
    15.     IIC_SDA_H;
    16.     Delay_Us(4);
    17.     IIC_SCL_H;   //在SCL线为高电平期间读取SDA线为高电平,则为NACK响应
    18.     Delay_Us(4);
    19.     IIC_SCL_L;
    20.     Delay_Us(4);
    21. }
    复制代码
    以上就是iic.c文件的各个函数,其表示I2C协议的各个环节。
    eeprom.h文件
    1. #ifndef __EEPROM_H
    2. #define __EEPROM_H

    3. #include "ch32v10x_conf.h"

    4. #define AT24C01     127
    5. #define AT24C02     255
    6. #define AT24C04     511
    7. #define AT24C08     1023
    8. #define AT24C16     2047
    9. #define AT24C32     4095
    10. #define AT24C64     8191
    11. #define AT24C128    16383
    12. #define AT24C256    32767

    13. //CH32V103开发板使用的是24C02,所以定义TYPE为AT24C02
    14. #define TYPE AT24C02
    15. #define SIZE 256

    16. u8   AT24CXX_ReadOneByte(u16 raddr);
    17. void AT24CXX_WriteOneByte(u16 waddr,u8 data);
    18. void AT24CXX_WriteLenByte(u16 waddr,u32 data,u8 len);
    19. u32  AT24CXX_ReadLenByte(u16 raddr,u8 len);
    20. u8   AT24CXX_Check(void);
    21. void AT24CXX_Read(u16 raddr,u8 *pBuffer,u16 num);
    22. void AT24CXX_Write(u16 waddr,u8 *pBuffer,u16 num);
    23. u8   AT24CXX_Test(void);

    24. #endif
    复制代码
    eeprom.h文件包含各种宏定义和函数声明;
    eeprom.c文件
    1. #include "eeprom.h"
    2. #include "iic.h"

    3. //在AT24CXX指定地址读出一个数据
    4. //raddr :开始读数的地址
    5. //返回值  :读到的数据
    6. u8 AT24CXX_ReadOneByte(u16 raddr)
    7. {
    8.     u8 temp=0;
    9.     IIC_Start();
    10.     if(TYPE>AT24C16) //对芯片容量进行判断,芯片容量决定了设备地址和数据地址的分配方法
    11.     {
    12.         //当芯片容量大于16Kbit,设备地址则只起到一个标志读写操作的作用,由两位数据地址表示读写字节的地址
    13.         //因此此处首先发送0XA0,然后发送要写入地址的高八位,此处是将16位地址数据右移8位实现此操作
    14.         IIC_SendByte(0XA0);    //发送写命令
    15.         IIC_WaitAck();
    16.         IIC_SendByte(raddr>>8);//发送高地址
    17.         IIC_WaitAck();
    18.     }
    19.     else
    20.     {
    21.         //当芯片容量小于16K时,设备地址中可能用于页寻址的位
    22.         IIC_SendByte(0XA0+((raddr/256)<<1));   //发送器件地址0XA0,写数据
    23.     }
    24.     IIC_WaitAck();
    25.     IIC_SendByte(raddr%256);   //发送低地址
    26.     IIC_WaitAck();
    27.     IIC_Start();
    28.     IIC_SendByte(0XA1);        //进入接收模式
    29.     IIC_WaitAck();
    30.     temp=IIC_ReadByte();
    31.     IIC_Stop();                //产生一个停止信号
    32.     return temp;
    33. }

    34. //在AT24CXX指定地址写入一个数据
    35. //waddr :写入数据的目的地址
    36. //data  :要写入的数据
    37. void AT24CXX_WriteOneByte(u16 waddr,u8 data)
    38. {
    39.     IIC_Start();
    40.     if(TYPE>AT24C16)
    41.     {
    42.         IIC_SendByte(0XA0);    //发送写命令
    43.         IIC_WaitAck();
    44.         IIC_SendByte(waddr>>8);//发送高地址
    45.     }else
    46.     {
    47.         IIC_SendByte(0XA0+((waddr/256)<<1));   //发送器件地址0XA0,写数据
    48.     }
    49.     IIC_WaitAck();
    50.     IIC_SendByte(waddr%256);  //发送低地址
    51.     IIC_WaitAck();
    52.     IIC_SendByte(data);       //发送字节
    53.     IIC_WaitAck();
    54.     IIC_Stop();               //产生一个停止信号
    55.     Delay_Ms(10);
    56. }

    57. //在AT24CXX里面的指定地址开始读出长度为Len的数据,该函数用于读出16bit或者32bit的数据.
    58. //raddr :开始读出的地址
    59. //len   :要读出数据的长度2,4
    60. //返回值  :数据
    61. u32 AT24CXX_ReadLenByte(u16 raddr,u8 len)
    62. {
    63.     u8 t;
    64.     u32 temp=0;
    65.     for(t=0;t<len;t++)
    66.     {
    67.         temp<<=8;
    68.         temp+=AT24CXX_ReadOneByte(raddr+len-t-1);
    69.     }
    70.     return temp;
    71. }

    72. //在AT24CXX里面的指定地址开始写入长度为len的数据,该函数用于写入16bit或者32bit的数据.
    73. //waddr :开始写入的地址
    74. //data  :数据数组首地址
    75. //len   :要写入数据的长度2,4
    76. void AT24CXX_WriteLenByte(u16 waddr,u32 data,u8 len)
    77. {
    78.     u8 t;
    79.     for(t=0;t<len;t++)
    80.     {
    81.         AT24CXX_WriteOneByte(waddr+t,(data>>(8*t))&0xff);
    82.     }
    83. }

    84. //检查AT24CXX是否正常,这里用了24XX的最后一个地址(255)来存储标志字.如果用其他24C系列,这个地址要修改
    85. //返回1:检测失败
    86. //返回0:检测成功
    87. u8 AT24CXX_Check(void)
    88. {
    89.     u8 temp;
    90.     temp=AT24CXX_ReadOneByte(255);//避免每次开机都写AT24CXX
    91.     if(temp==0X55)return 0;
    92.     else //排除第一次初始化的情况
    93.     {
    94.         AT24CXX_WriteOneByte(255,0X55);
    95.         temp=AT24CXX_ReadOneByte(255);
    96.         if(temp==0X55)return 0;
    97.     }
    98.     return 1;
    99. }

    100. //在AT24CXX里面的指定地址开始读出指定个数的数据
    101. //raddr   :开始读出的地址 对24c02为0~255
    102. //pBuffer :数据数组首地址
    103. //num     :要读出数据的个数
    104. void AT24CXX_Read(u16 raddr,u8 *pBuffer,u16 num)
    105. {
    106.     while(num)
    107.     {
    108.         *pBuffer++=AT24CXX_ReadOneByte(raddr++);
    109.         num--;
    110.     }
    111. }

    112. //在AT24CXX里面的指定地址开始写入指定个数的数据
    113. //waddr   :开始写入的地址 对24c02为0~255
    114. //pBuffer :数据数组首地址
    115. //num     :要写入数据的个数
    116. void AT24CXX_Write(u16 waddr,u8 *pBuffer,u16 num)
    117. {
    118.     while(num--)
    119.     {
    120.         AT24CXX_WriteOneByte(waddr,*pBuffer);
    121.         waddr++;
    122.         pBuffer++;
    123.     }
    124. }


    125. //AT24C02 读写测试
    126. //正常返回1,异常返回0

    127. u8 AT24CXX_Test(void)
    128. {
    129.     u16 i;
    130.     u8 readbuf[SIZE];
    131.     u8 writebuf[SIZE];


    132.     /* 填充测试缓冲区 */
    133.     for (i = 0; i < SIZE; i++)
    134.     {
    135.         writebuf[i] = i;
    136.     }

    137.     AT24CXX_Write(0,(u8*)writebuf,SIZE);

    138.     AT24CXX_Read(0,(u8*)readbuf,SIZE);

    139.     for (i = 0; i < SIZE; i++)
    140.       {
    141.           if(readbuf[i] != writebuf[i])
    142.           {
    143.               printf("0x%4d ", readbuf[i]);
    144.               printf("错误:AT24C02读出与写入的数据不一致");
    145.           }

    146.           printf("%4d", readbuf[i]);

    147.           if ((i&15) == 15)
    148.           {
    149.               printf("\r\n");
    150.           }
    151.       }
    152.     printf("AT24C02读写测试成功\r\n");
    153.     return 1;

    154. }
    复制代码
    eeprom.c主要包含从EEPROM读取数据函数、向EEPROM写入数据函数以及EEPROM检测和测试函数,具体介绍如下:
    CH32V103开发板所用EEPROM芯片型号为AT24C02,其设备地址一共有7位,如下图,其中高4位固定为:1010b,低3位则由A0/A1/A2信号线的电平决定。根据CH32V103开发板原理图连接方式,A0、A1、A2均接GND,为0,所以设备地址为:1010000b。但由于I2C通讯通常将地址跟读写方向连在一起构成一个8位数,且当R/W位为0 时,表示写方向,加上7位地址,其值为“0xA0”,常称该值为I2C设备的“写地址”;当 R/W 位为 1 时,表示读方向,加上7位地址,其值为“0xA1”,常称该值为“读地址”。
    CH32V CH573单片机芯片-第三十七章:CH32V103应用教程——I2C-软件模拟I2C读写EEPROMrisc-v单片机中文社区(1)

    1、关于根据AT24C02指定地址读取一个数据流程,首先对芯片容量进行判断,芯片容量决定了设备地址和数据地址的分配方法。当芯片容量大于16Kbit,设备地址则只起到一个标志读写操作的作用,由两位数据地址表示读写字节的地址,因此此处首先发送0XA0,然后发送要写入地址的高八位,此处是将16位地址数据右移8位实现此操作;当芯片的容量小于16k的时候,设备地址中可能用于页寻址的位:当芯片型号为AT24C02,则设备地址中无页寻址的位,则raddr的高八位均为0,则((raddr/256)<<1)也为0,因此最后就是发送0XA0。

    当芯片型号为AT24C04,则设备地址中有一位页寻址的位,假如是1,则(raddr/256)为0X01,再左移一位为0x02,则最终发送的位0XA2。正好对应P0位为1,下面型号以此类推。关于从AT24Cxx中读取数据,却最先发送0XA0(写地址),解释如下:随机读需要设定需要读的地址,然后有一次伪写入过程,这个伪写入是为了修改存储器内部的工作指针。具体程序如下:
    1. //在AT24CXX指定地址读出一个数据
    2. //raddr :开始读数的地址
    3. //返回值  :读到的数据
    4. u8 AT24CXX_ReadOneByte(u16 raddr)
    5. {
    6.     u8 temp=0;
    7.     IIC_Start();
    8.     if(TYPE>AT24C16) //对芯片容量进行判断,芯片容量决定了设备地址和数据地址的分配方法
    9.     {
    10.         //当芯片容量大于16Kbit,设备地址则只起到一个标志读写操作的作用,由两位数据地址表示读写字节的地址
    11.         //因此此处首先发送0XA0,然后发送要写入地址的高八位,此处是将16位地址数据右移8位实现此操作
    12.         IIC_SendByte(0XA0);    //发送写命令
    13.         IIC_WaitAck();
    14.         IIC_SendByte(raddr>>8);//发送高地址
    15.         IIC_WaitAck();
    16.     }
    17.     else
    18.     {
    19.         //当芯片容量小于16K时,设备地址中可能用于页寻址的位
    20.         IIC_SendByte(0XA0+((raddr/256)<<1));   //发送器件地址0XA0,写数据
    21.     }
    22.     IIC_WaitAck();
    23.     IIC_SendByte(raddr%256);   //发送低地址
    24.     IIC_WaitAck();
    25.     IIC_Start();
    26.     IIC_SendByte(0XA1);        //进入接收模式
    27.     IIC_WaitAck();
    28.     temp=IIC_ReadByte();
    29.     IIC_Stop();                //产生一个停止信号
    30.     return temp;
    31. }
    复制代码

    2、关于根据AT24C02指定地址写入一个数据流程,与读取流程类似,在此不再介绍,具体程序如下:
    1. //在AT24CXX指定地址写入一个数据
    2. //waddr :写入数据的目的地址
    3. //data  :要写入的数据
    4. void AT24CXX_WriteOneByte(u16 waddr,u8 data)
    5. {
    6.     IIC_Start();
    7.     if(TYPE>AT24C16)
    8.     {
    9.         IIC_SendByte(0XA0);    //发送写命令
    10.         IIC_WaitAck();
    11.         IIC_SendByte(waddr>>8);//发送高地址
    12.     }else
    13.     {
    14.         IIC_SendByte(0XA0+((waddr/256)<<1));   //发送器件地址0XA0,写数据
    15.     }
    16.     IIC_WaitAck();
    17.     IIC_SendByte(waddr%256);  //发送低地址
    18.     IIC_WaitAck();
    19.     IIC_SendByte(data);       //发送字节
    20.     IIC_WaitAck();
    21.     IIC_Stop();               //产生一个停止信号
    22.     Delay_Ms(10);
    23. }
    复制代码
    其他函数大都在这两个函数基础上进行,在此不再介绍。
    main.c文件
    1. int main(void)
    2. {
    3.     u8 datatemp[size];

    4.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    5.     Delay_Init();
    6.         USART_Printf_Init(115200);
    7.         IIC_Init();

    8.         printf("SystemClk:%d\r\n",SystemCoreClock);

    9.     if(AT24CXX_Check()==1)
    10.     {
    11.         /* 没有检测到EEPROM */
    12.         printf("24C02 Check Failed,Please Check!\n");

    13.     }
    14.     else
    15.     {
    16.         /* 没有检测到EEPROM */
    17.         printf("24C02 Check Succeed,Please Continue!\n");

    18.     }

    19.     if(AT24CXX_Test() == 0)
    20.     {
    21.         printf("24C02 Test Failed,Please Check!\n");
    22.     }
    23.     else
    24.     {
    25.         printf("24C02 Test Succeed,Please Check!\n");
    26.     }

    27.     AT24CXX_Write(0,(u8*)TEXT_Buffer,size);

    28.     AT24CXX_Read(0,datatemp,size);

    29.     printf("The Data Readed Is:%s\n",datatemp);

    30.         while(1)
    31.     {
    32.         }
    33. }
    复制代码
    main.c文件主要进行函数初始化以及AT24C02检测和读写测试。


    4、下载验证

    将编译好的程序下载到开发板并复位,同时将PA1连接J5的SCL引脚,PA2连接J5的SDA引脚,串口打印情况具体如下:

    CH32V CH573单片机芯片-第三十七章:CH32V103应用教程——I2C-软件模拟I2C读写EEPROMrisc-v单片机中文社区(2)
    IIC-(软件模拟IIC读写EEPROM).rar
    CH32V CH573单片机芯片-第三十七章:CH32V103应用教程——I2C-软件模拟I2C读写EEPROMrisc-v单片机中文社区(3) 36、IIC-(软件模拟IIC读写EEPROM).rar (499.7 KB, 下载次数: 19)
    链接:https://pan.baidu.com/s/1OjvqpaCxSnh3pwUNTnn5yA
    提取码:odia
    复制这段内容后打开百度网盘手机App,操作更方便哦







    上一篇:第三十六章:CH32V103应用教程——ADC-间断模式
    下一篇:第三十八章:I2C-7位地址模式,主机发送从机接收
    RISCV作者优文
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复

    使用道具 举报

    RISC-V隐身侠  发表于 2021-5-28 00:13:59

    -

    Yes you are talented
    全球首家只专注于RISC-V单片机行业应用的中文网站
    回复 支持 反对

    使用道具

    高级模式
    B Color Image Link Quote Code Smilies

    本版积分规则

    关闭

    RISC-V单片机中文网上一条 /2 下一条



    版权及免责声明|RISC-V单片机中文网 |网站地图

    GMT+8, 2024-4-25 05:02 , Processed in 1.060102 second(s), 50 queries .

    快速回复 返回顶部 返回列表