单片机
返回首页

4. 软件模拟IIC通信(stm32为例)

2025-09-19 来源:cnblogs

1. 硬件连接

1.1 从设备端引脚连接

1.2 mcu端引脚连接

2. iic初始化

 1 #include 'delay.h'

 2 

 3 #define IIC_SCL    PBout(8) //SCL(输出)

 4 #define IIC_SDA    PBout(9) //SDA(输出)   

 5 #define IIC_SDA_R  PBin(9)  //SDA(输入)

 6 

 7 void AT24C0X_IIC_Init(void)

 8 {           

 9   GPIO_InitTypeDef  GPIO_InitStructure;

10 

11   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能gpiob时钟

12   //GPIOB8,B9初始化

13   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8 | GPIO_Pin_9;

14   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT; //普通输出模式

15   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出,增加输出电流能力

16   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz,高速响应

17   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  //使能上拉电阻

18   GPIO_Init(GPIOB, &GPIO_InitStructure);        //初始化gpio

19   PBout(8)=1;       // iic_scl,时序图初始为高电平

20   PBout(9)=1;       // iic_sda,时序图初始为高电平

21 }

22 void sda_pin_mode(GPIOMode_TypeDef mode)    // GPIO_Mode_IN,GPIO_Mode_OUT

23 {

24   GPIO_InitTypeDef  GPIO_InitStructure;

25 

26   RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);//使能gpiob时钟

27   //GPIOB8,B9初始化

28   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;

29   GPIO_InitStructure.GPIO_Mode = mode;  //普通输入或出模式

30   GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;//推挽输出,增加输出电流能力

31   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;//100MHz,高速响应

32   GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;  //使能上拉电阻

33   GPIO_Init(GPIOB, &GPIO_InitStructure);        //初始化gpio

34 }


3.时序图

3.1 开始和终止的定义(Start and Stop Definition)

 


3.1.1 开始信号


 1 void iic_start(void)

 2 {

 3     //保证sda引脚为输出模式

 4     sda_pin_mode(GPIO_Mode_OUT);

 5 

 6     IIC_SCL = 1;    // 1.初始电平都为高电平

 7     IIC_SDA = 1;

 8     delay_us(5);    //   延时5us,保证电平稳定有效

 9 

10     IIC_SDA = 0;    // 2.scl为高期间,sda:1->0

11     delay_us(5);    //   产生start信号

12 

13     IIC_SCL = 0;    // 3.钳住iic总线,准备开始发送数据

14     delay_us(5);    //

15 }


3.1.2 终止信号(停止信号)


void iic_stop(void)

{

    //保证sda引脚为输出模式

    sda_pin_mode(GPIO_Mode_OUT);


    IIC_SCL = 1;    // 1.初始电平为高电平

    IIC_SDA = 0;    //   初始电平为低电平

    delay_us(5);


    IIC_SDA = 1;    // 2.scl为高期间,sda:0->1

    delay_us(5);

}


3.2 输出应答(Data Validity)

 

3.2.1 从机应答(主机等待从机应答)


 1 uint8_t iic_wait_ack(void)

 2 {

 3     uint8_t ack = 0;

 4 

 5     //保证sda引脚为输出模式

 6     sda_pin_mode(GPIO_Mode_IN);

 7     IIC_SCL = 1;        // 在scl=1期间,读取sda

 8     delay_us(5);

 9 

10     if(IIC_SDA_R)

11     {

12         ack = 1;        // 若sda_r为1,则无应答

13     }

14     else

15     {

16         ack = 0;        // 若sda_r为0,则有应答

17     }

18     return ack;

19 }


3.2.2 主机应答


void iic_ack(void)

{

    //保证sda引脚为输出模式

    sda_pin_mode(GPIO_Mode_OUT);


    IIC_SCL = 0;    // scl初始电平

    IIC_SDA = 0;    // sda初始电平

    delay_us(5);


    IIC_SCL = 1;    // scl=1期间,

    delay_us(5);    


    IIC_SDA = 0;    // sda=0,主机应答

}


3.2.3 主机不应答


 1 void iic_no_ack(void)

 2 {

 3     //保证sda引脚为输出模式

 4     sda_pin_mode(GPIO_Mode_OUT);

 5 

 6     IIC_SCL = 0;    // scl初始电平

 7     IIC_SDA = 1;    // sda初始电平

 8     delay_us(5);

 9 

10     IIC_SCL = 1;    // scl=1期间,

11     delay_us(5);

12 

13     IIC_SDA = 1;    // sda=1,主机不应答

14 }


3.3 读写时序

 

3.3.1 数据有效性(Data Validity)

3.3.2 设备地址(Device Address)

 

3.3.3 写操作


 1 void iic_write_byte(uint8_t byte)

 2 {

 3     int32_t i;

 4     //保证sda引脚为输出模式

 5     sda_pin_mode(GPIO_Mode_OUT);

 6 

 7     IIC_SCL = 0;

 8     delay_us(5);

 9 

10     for (i=7; i>=0; i--)

11     {

12         if (byte & (1<13         {

14             IIC_SDA = 1;    

15         }

16         else

17         {

18             IIC_SDA = 0;

19         }

20         delay_us(5);

21 

22         IIC_SCL = 1;        // 

23         delay_us(5);

24 

25         IIC_SCL = 0;        // 为下一次写数据做准备

26         delay_us(5);

27     }

28 }


3.3.4 读操作


 1 uint8_t iic_read_byte(void)

 2 {

 3     uint8_t byte;

 4     int32_t i;

 5 

 6     //保证sda引脚为输出模式

 7     sda_pin_mode(GPIO_Mode_IN);

 8 

 9     IIC_SCL = 0;            // 初始化为低电平

10     delay_us(5);    

11 

12     for (i=7; i>=0; i--)

13     {

14         IIC_SCL = 1;        // 只有在scl=1时,才可以读数据

15         delay_us(5);

16 

17         if (IIC_SDA_R)

18         {

19             byte |= 1<20         }

21         else

22         {

23             byte |= 0<24         }

25         IIC_SCL = 0;        // 为读下一个字节的数据做准备

26         delay_us(5);    

27     }

28     return byte;

29 }


3.4 向AT24CXX写数据

3.4.1 写一字节数据( Byte Write)

 1 uint32_t AT24CXX_Byte_Write(uint8_t addr,uint8_t byte)

 2 {

 3     uint8_t ack;

 4 

 5     // 1.发送开始信号

 6     iic_start();

 7 

 8     // 2.写设备地址,选择iic总线的设备,bit流由高到低(从最高位开始写)

 9     iic_write_byte(0xA0);

10 

11     // 3.等待从机应答(从机位EEPROM设备,主机位mcu设备)

12     ack = iic_wait_ack();

13     if (1 == ack)

14     {

15         printf('dev addr is error!rn');

16         return -1;

17     }

18 

19     // 4.写入数据地址(数据在eeprom中要存放的位置)

20     iic_write_byte(addr);

21 

22     // 5.等待从机应答

23     ack = iic_wait_ack();

24     if (1 == ack)

25     {

26         printf('data addr is error!rn');

27         return -2;

28     }

29 

30     // 6.写入要存入EEPROM中的数据

31     iic_write_byte(byte);

32 

33     // 7.等到从机应答

34     ack = iic_wait_ack();

35     if (1 == ack)

36     {

37         printf('data addr is error!rn');

38         return -3;

39     }

40 

41     // 8.发送终止信号

42     iic_stop();

43 

44     return 0;

45 }


3.4.2 写一页的数据(Page Write)

 1 int32_t AT24C0X_Page_Write(uint8_t addr,uint8_t *pbuf,uint8_t len)  

 2 {

 3     uint8_t ack;

 4     uint8_t i;

 5 

 6     // 1.发送起始信号

 7     iic_start();

 8 

 9     // 2.发送设备地址,iic总线寻址

10     iic_write_byte(0xA0);   // 写设备地址(0xA0)

11     

12     // 3.等待应答信号

13     ack = iic_wait_ack();

14     if (ack)

15     {

16         printf('dev addr is err!rn');

17         return -1;

18     }

19 

20     // 4.发送要写入的数据地址

21     iic_write_byte(addr);

22 

23     // 5.等待应答信号

24     ack = iic_wait_ack();

25     if (ack)

26     {

27         printf('data addr is err!rn');

28         return -2;

29     }

30 

31     // 6.发送要写入的数据(多字节)

32     for (i=0; i33     {

34         // 发送要写入的数据

35         iic_write_byte(pbuf[i]);

36         // 等待应答

37         ack = iic_wait_ack();

38         if (ack)

39         {

40             printf('data is err!rn');

41             return -3;

42         }               

43     }

44     // 7.发送停止信号

45     iic_stop();

46 }


3.4.3 写入多字节的数据(写入数据流)


 1 int32_t AT24CXX_Write_Data(uint8_t Addr,uint8_t *pBuf,uint16_t Len) 

 2 {

 3     while(Len--)

 4     {

 5         AT24CXX_Byte_Write(Addr,pBuf);

 6         Addr++;         // 目的地址(eeprom中存储数据的地址+1)

 7         pBuf++;         // 源地址(源数据的地址)

 8     }

 9     return 0;

10 }


3.5 从AT24CXX读数据

3.5.1 任意地址读一个byte(Random Read)

 1 uint8_t AT24CXX_Read_Byte_Random(uint8_t addr)

 2 {

 3     uint8_t byte;

 4     uint8_t ack;

 5 

 6     // 1.发送开始信号

 7     iic_start();

 8 

 9     // 2.写入要读的设备地址(硬件设备id),为写方向

10     iic_write_byte(0xA0);

11 

12     // 3.等待从机应答信号

13     ack = iic_wait_ack();

14     if (ack)

15     {

16         printf('When Master read randomly,what write slave device address is error! rn');

17         return -1;

18     }

19 

20     // 4.写入从机设备(eeprom)中,要读的位置

21     iic_write_byte(addr);

22 

23     // 5.等待从机应答信号

24     ack = iic_wait_ack();

25     if (ack)

26     {

27         printf('When Master read randomly,what write data address in slave is error! rn');

28         return -2;

29     }

30 

31     // 6.发送开始信号

32     iic_start();

33 

34     // 7.写入要读的设备地址(硬件设备id),为读方向

35     iic_write_byte(0xA1);

36 

37     // 8.等待从机应答信号

38     ack = iic_wait_ack();

39     if (ack)

40     {

41         printf('When Master read randomly,what write data address in slave is error! rn');

42         return -2;

43     }

44 

45     // 9.读取从机数据(eeprom)

46     byte = iic_read_byte();

47 

48     // 10.主机不应答

49     iic_no_ack();

50 

51     // 11.发送停止信号

52     iic_stop();

53 

54     return byte;

55 }


3.5.2 读出short或int型的数据


 1 /**

 2   * @brief 在AT24XX里面的指定地址开始读出长度为Len的数据,该函数用于读出

 3   *        读出16bit或者32bit的数据    

 4   * @param ReadAddr:开始读出的地址

 5   * @param Len:  读出数据的长度2,4

 6   *

 7   * @retval          数据             

 8   */

 9 uint32_t AT24CXX_ReadLenByte(uint16_t ReadAddr,uint8_t Len)

10 {   

11     uint8_t t;

12     uint32_t temp=0;

13     for(t=0;t14     {

15         temp<<=8;

16         temp+=AT24CXX_ReadOneByte(ReadAddr+Len-t-1);    //                     

17     }

18     return temp;                                                    

19 }


3.5.3 读出epprom中一定长度的数据


 1 /**

 2   * @brief 在AT24XX里面的指定地址开始读出指定长度为Len的数据

 3   * @param ReadAddr:开始读出的地址,对24c02为0-255

 4   * @param pData:   输出数据数组首地址   

 5   * @param Len:     要读数据的个数

 6         

 7   */

 8 void AT24CXX_Read_Data(uint16_t addr,uint8_t *pData, uint16_t DataLen)

 9 {

10     while (DataLen--)

11     {

12         *pData++ = AT24CXX_Read_Byte_Random(addr++);

13     }

14 }


进入单片机查看更多内容>>
相关视频
  • 【TI MSPM0 应用实战】智能小车+工业角度编码器+血氧仪+烟雾探测器!硬核参考设计详解!

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

  • 直播回放: Microchip Timberwolf™ 音频处理器在线研讨会

  • 基于灵动MM32W0系列MCU的指夹血氧仪控制及OTA升级应用方案分享

精选电路图
  • 1瓦线性调频增强器

  • 12V 转 28V DC-DC 变换器(基于 LM2585)

  • 红外遥控音量控制

  • LM317过压保护

  • 12V转110V/220V 500W逆变器

  • DS1669数字电位器

    相关电子头条文章