返回列表 发新帖我要提问本帖赏金: 40.00元(功能说明)

[技术讨论] STM32硬件IIC和软件IIC的比较

[复制链接]
2398|1
 楼主| 王小琪 发表于 2023-3-30 21:48 | 显示全部楼层 |阅读模式
STM32的硬件IIC和软件IIC都可以用来实现IIC通信,但它们有一些不同之处。
一、简单比较
A硬件IIC
硬件IIC是由STM32内部的硬件模块实现的,使用CPU的时钟信号来控制数据传输和时序,通信速度较快,可以达到几十MHz的速度。硬件IIC的实现相对简单,无需编写复杂的代码,因此在实现IIC通信时,硬件IIC通常是首选的方式。硬件IIC的主要优点有:
1速度快,可以实现高速通信;
2实现简单,无需编写复杂的代码;
3稳定性好,不容易出现通信错误。
B软件IIC
软件IIC是由CPUGPIO模拟实现的,通过CPU的软件来控制时序和数据传输,通信速度相对较慢,一般在几十kHz到几百kHz之间。软件IIC的实现相对复杂,需要编写复杂的代码,因此在实现IIC通信时,软件IIC通常是在硬件IIC无法满足需求时才采用的方式。软件IIC的主要优点有:
1可以实现多路IIC通信,硬件IIC一般只能实现单路通信;
2可以在STM32的任何GPIO上实现IIC通信,相对灵活;
3可以实现任意时序,更加灵活。
总的来说,硬件IIC和软件IIC各有优缺点,选择哪种方式要根据具体的应用需求进行选择。如果需要高速通信,建议选择硬件IIC;如果需要多路通信或者灵活的时序控制,建议选择软件IIC
二、各自实现方式
A、硬件IIC
实现硬件IIC的代码需要使用STM32的内部硬件模块,具体步骤如下:
配置GPIO用于IIC通信,将SCLSDA引脚分别配置为复用推挽输出模式;
配置I2C控制器,包括I2C时钟频率、I2C地址、I2C工作模式等参数;
启动I2C控制器,并发送数据或接收数据。
以下是一个简单的STM32硬件IIC的代码实现,以STM32F1为例:
  1. #include "stm32f10x.h"
  2. void i2c_init(void)
  3. {
  4.     GPIO_InitTypeDef GPIO_InitStructure;
  5.     I2C_InitTypeDef I2C_InitStructure;
  6.    
  7.     // 打开GPIOB和I2C1时钟
  8.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  9.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE);
  10.    
  11.     // 配置PB6和PB7为复用推挽输出模式
  12.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  13.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_OD;
  14.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  15.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  16.    
  17.     // 配置I2C1控制器
  18.     I2C_InitStructure.I2C_Mode = I2C_Mode_I2C;
  19.     I2C_InitStructure.I2C_DutyCycle = I2C_DutyCycle_2;
  20.     I2C_InitStructure.I2C_OwnAddress1 = 0x00;
  21.     I2C_InitStructure.I2C_Ack = I2C_Ack_Enable;
  22.     I2C_InitStructure.I2C_AcknowledgedAddress = I2C_AcknowledgedAddress_7bit;
  23.     I2C_InitStructure.I2C_ClockSpeed = 100000;    // I2C时钟频率为100kHz
  24.     I2C_Init(I2C1, &I2C_InitStructure);
  25.    
  26.     // 启动I2C1控制器
  27.     I2C_Cmd(I2C1, ENABLE);
  28. }
  29. void i2c_write_byte(uint8_t addr, uint8_t reg, uint8_t data)
  30. {
  31.     // 发送起始信号
  32.     I2C_GenerateSTART(I2C1, ENABLE);
  33.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  34.    
  35.     // 发送设备地址和写命令
  36.     I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
  37.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
  38.    
  39.     // 发送寄存器地址
  40.     I2C_SendData(I2C1, reg);
  41.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  42.    
  43.     // 发送数据
  44.     I2C_SendData(I2C1, data);
  45.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  46.    
  47.     // 发送停止信号
  48.     I2C_GenerateSTOP(I2C1, ENABLE);
  49. }
  50. uint8_t i2c_read_byte(uint8_t addr, uint8_t reg)
  51. {
  52.     uint8_t data;
  53.    
  54.     // 发送起始信号
  55.     I2C_GenerateSTART(I2C1, ENABLE);
  56.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  57.    
  58.     // 发送设备地址和写命令
  59.     I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Transmitter);
  60.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));
  61.    
  62.     // 发送寄存器地址
  63.     I2C_SendData(I2C1, reg);
  64.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
  65.    
  66.     // 发送重复起始信号
  67.     I2C_GenerateSTART(I2C1, ENABLE);
  68.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));
  69.    
  70.     // 发送设备地址和读命令
  71.     I2C_Send7bitAddress(I2C1, addr, I2C_Direction_Receiver);
  72.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_RECEIVER_MODE_SELECTED));
  73.    
  74.     // 读取数据
  75.     I2C_AcknowledgeConfig(I2C1, DISABLE);
  76.     I2C_GenerateSTOP(I2C1, ENABLE);
  77.     while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_RECEIVED));
  78.     data = I2C_ReceiveData(I2C1);
  79.    
  80.     return data;
  81. }
以上代码实现了STM32硬件IIC的初始化、写入一个字节和读取一个字节的操作。其中,i2c_init()函数用于初始化I2C控制器和GPIOi2c_write_byte()函数用于写入一个字节,i2c_read_byte()函数用于读取一个字节。在实际应用中,可以根据需要修改这些函数来实现不同的IIC操作。

B、软件IIC
实现软件IIC的代码需要通过GPIO模拟IIC时序,具体步骤如下:
配置GPIO用于IIC通信,将SCLSDA引脚分别配置为推挽输出模式;
实现IIC起始信号、停止信号、发送ACK信号、发送数据、接收数据等操作;
编写具体的IIC外设读写函数,根据需要调用起始信号、停止信号、发送数据、接收数据等操作。
以下是一个简单的STM32软件IIC的代码实现,以STM32F1为例:
  1. #include "stm32f10x.h"
  2. #include "delay.h"
  3. #define IIC_SCL_H() GPIO_SetBits(GPIOB, GPIO_Pin_6)    // SCL线置高
  4. #define IIC_SCL_L() GPIO_ResetBits(GPIOB, GPIO_Pin_6)  // SCL线置低
  5. #define IIC_SDA_H() GPIO_SetBits(GPIOB, GPIO_Pin_7)    // SDA线置高
  6. #define IIC_SDA_L() GPIO_ResetBits(GPIOB, GPIO_Pin_7)  // SDA线置低
  7. #define IIC_SDA_READ() GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7)  // 读取SDA线状态
  8. void iic_init(void)
  9. {
  10.     GPIO_InitTypeDef GPIO_InitStructure;
  11.    
  12.     // 打开GPIOB时钟
  13.     RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
  14.    
  15.     // 配置PB6和PB7为推挽输出模式
  16.     GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
  17.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  18.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  19.     GPIO_Init(GPIOB, &GPIO_InitStructure);
  20.    
  21.     // 初始化IIC总线
  22.     IIC_SCL_H();
  23.     IIC_SDA_H();
  24. }
  25. void iic_start(void)
  26. {
  27.     IIC_SDA_H();
  28.     IIC_SCL_H();
  29.     delay_us(5);
  30.     IIC_SDA_L();
  31.     delay_us(5);
  32.     IIC_SCL_L();
  33. }
  34. void iic_stop(void)
  35. {
  36.     IIC_SDA_L();
  37.     IIC_SCL_H();
  38.     delay_us(5);
  39.     IIC_SDA_H();
  40.     delay_us(5);
  41. }
  42. void iic_send_ack(void)
  43. {
  44.     IIC_SCL_L();
  45.     delay_us(5);
  46.     IIC_SDA_L();
  47.     delay_us(5);
  48.     IIC_SCL_H();
  49.     delay_us(5);
  50.     IIC_SCL_L();
  51.     delay_us(5);
  52. }
  53. void iic_send_nack(void)
  54. {
  55.     IIC_SCL_L();
  56.     delay_us(5);
  57.     IIC_SDA_H();
  58.     delay_us(5);
  59.     IIC_SCL_H();
  60.     delay_us(5);
  61.     IIC_SCL_L();
  62.     delay_us(5);
  63. }
  64. uint8_t iic_send_byte(uint8_t data)
  65. {
  66.     uint8_t ack;
  67.    
  68.     for(int i = 0; i < 8; i++)
  69.     {
  70.         if(data & 0x80)
  71.             IIC_SDA_H();
  72.         else
  73.             IIC_SDA_L();
  74.         data <<= 1;
  75.         delay_us(5);
  76.         IIC_SCL_H();
  77.         delay_us(5);
  78.         IIC_SCL_L();
  79.         delay_us(5);
  80.     }
  81.    
  82.     IIC_SDA_H();
  83.     delay_us(5);
  84.     IIC_SCL_H();
  85.     delay_us(5);
  86.     ack = IIC_SDA_READ();
  87.     IIC_SCL_L();
  88.     delay_us(5);
  89.    
  90.     return ack;
  91. }
  92. uint8_t iic_receive_byte(uint8_t ack)
  93. {
  94.     uint8_t data = 0;
  95.    
  96.     for(int i = 0; i < 8; i++)
  97.     {
  98.         data <<= 1;
  99.         IIC_SCL_H();
  100.         delay_us(5);
  101.         if(IIC_SDA_READ())
  102.             data |= 0x01;
  103.         IIC_SCL_L();
  104.         delay_us(5);
  105.     }
  106.    
  107.     if(ack)
  108.         iic_send_ack();
  109.     else
  110.         iic_send_nack();
  111.    
  112.     return data;
  113. }
  114. uint8_t iic_write_reg(uint8_t addr, uint8_t reg, uint8_t data)
  115. {
  116.     uint8_t ack;
  117.    
  118.     iic_start();
  119.     ack = iic_send_byte(addr << 1);
  120.     if(ack)
  121.     {
  122.         iic_stop();
  123.         return 1;
  124.     }
  125.    
  126.     ack = iic_send_byte(reg);
  127.     if(ack)
  128.     {
  129.         iic_stop();
  130.         return 2;
  131.     }
  132.    
  133.     ack = iic_send_byte(data);
  134.     if(ack)
  135.     {
  136.         iic_stop();
  137.         return 3;
  138.     }
  139.    
  140.     iic_stop();
  141.     return 0;
  142. }
  143. uint8_t iic_read_reg(uint8_t addr, uint8_t reg, uint8_t *data)
  144. {
  145.     uint8_t ack;
  146.    
  147.     iic_start();
  148.     ack = iic_send_byte(addr << 1);
  149.     if(ack)
  150.     {
  151.         iic_stop();
  152.         return 1;
  153.     }
  154.    
  155.     ack = iic_send_byte(reg);
  156.     if(ack)
  157.     {
  158.         iic_stop();
  159.         return 2;
  160.     }
  161.    
  162.     iic_start();
  163.     ack = iic_send_byte((addr << 1) | 0x01);
  164.     if(ack)
  165.     {
  166.         iic_stop();
  167.         return 3;
  168.     }
  169.    
  170.     *data = iic_receive_byte(0);
  171.     iic_stop();
  172.     return 0;
  173. }
以上代码实现了STM32软件IIC的初始化、起始信号、停止信号、发送ACK信号、发送数据、接收数据、写寄存器、读寄存器等操作。其中,iic_init()函数用于初始化GPIOiic_start()函数用于发送起始信号,iic_stop()函数用于发送停止信号,iic_send_ack()函数用于发送ACK信号,iic_send_nack()函数用于发送NACK信号,iic_send_byte()函数用于发送一个字节,iic_receive_byte()函数用于接收一个字节,iic_write_reg()函数用于写一个寄存器,iic_read_reg()函数用于读一个寄存器。在实际应用中,可以根据需要修改这些函数来实现不同的IIC操作。

本帖子中包含更多资源

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

×

打赏榜单

21ic小管家 打赏了 40.00 元 2023-04-17

lfc315 发表于 2023-4-19 08:58 | 显示全部楼层
“将SCL和SDA引脚分别配置为复用推挽输出模式”
为什么不是配置引脚在OC输出模式呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

232

主题

585

帖子

7

粉丝
快速回复 在线客服 返回列表 返回顶部