STM32 软件模拟 I2C 从机实现

STM32 软件模拟 I2C 从机实现

基于 STM32 的软件模拟 I2C 从机实现代码。这个实现使用 GPIO 引脚模拟 I2C 协议,可以让 STM32 作为从设备与其他 I2C 主设备通信。

硬件连接

首先,确保正确连接硬件:

STM32 引脚 I2C 功能 连接说明
PB6 SCL 时钟线,需要外部上拉电阻
PB7 SDA 数据线,需要外部上拉电阻

完整代码实现

1. 头文件定义 (i2c_slave.h)

#ifndef I2C_SLAVE_H
#define I2C_SLAVE_H

#include "stm32f1xx_hal.h"  // 根据你的STM32系列调整

// I2C 从机地址定义
#define I2C_SLAVE_ADDRESS 0x50  // 7位地址

// 函数声明
void I2C_Slave_Init(void);
void I2C_Slave_Process(void);
void I2C_Slave_SetData(uint8_t reg, uint8_t data);
uint8_t I2C_Slave_GetData(uint8_t reg);

// 回调函数类型定义
typedef void (*I2C_RxCallback)(uint8_t register_addr, uint8_t data);
typedef uint8_t (*I2C_TxCallback)(uint8_t register_addr);

// 设置回调函数
void I2C_Slave_SetRxCallback(I2C_RxCallback callback);
void I2C_Slave_SetTxCallback(I2C_TxCallback callback);

#endif /* I2C_SLAVE_H */

2. 源文件实现 (i2c_slave.c)

#include "i2c_slave.h"
#include "main.h"

// GPIO 定义
#define I2C_SDA_PIN GPIO_PIN_7
#define I2C_SDA_PORT GPIOB
#define I2C_SCL_PIN GPIO_PIN_6
#define I2C_SCL_PORT GPIOB

// 状态定义
typedef enum {
  I2C_STATE_IDLE,
  I2C_STATE_ADDRESS,
  I2C_STATE_REGISTER,
  I2C_STATE_DATA_RX,
  I2C_STATE_DATA_TX
} I2C_State;

// 变量声明
static volatile I2C_State i2c_state = I2C_STATE_IDLE;
static volatile uint8_t i2c_address = 0;
static volatile uint8_t i2c_register = 0;
static volatile uint8_t i2c_data[256];  // 从机数据存储
static volatile uint8_t i2c_bit_counter = 0;
static volatile uint8_t i2c_byte_buffer = 0;
static volatile uint8_t i2c_rw_bit = 0;

// 回调函数
static I2C_RxCallback rx_callback = NULL;
static I2C_TxCallback tx_callback = NULL;

// 设置SDA为输入模式
static void SDA_IN(void) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = I2C_SDA_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct);
}

// 设置SDA为输出模式
static void SDA_OUT(void) {
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  GPIO_InitStruct.Pin = I2C_SDA_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
  HAL_GPIO_Init(I2C_SDA_PORT, &GPIO_InitStruct);
}

// 读取SDA引脚状态
static uint8_t SDA_READ(void) {
  return HAL_GPIO_ReadPin(I2C_SDA_PORT, I2C_SDA_PIN);
}

// 设置SDA引脚状态
static void SDA_WRITE(uint8_t value) {
  HAL_GPIO_WritePin(I2C_SDA_PORT, I2C_SDA_PIN, value ? GPIO_PIN_SET : GPIO_PIN_RESET);
}

// 读取SCL引脚状态
static uint8_t SCL_READ(void) {
  return HAL_GPIO_ReadPin(I2C_SCL_PORT, I2C_SCL_PIN);
}

// 初始化函数
void I2C_Slave_Init(void) {
  // 初始化GPIO
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  
  // SCL引脚初始化
  GPIO_InitStruct.Pin = I2C_SCL_PIN;
  GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
  GPIO_InitStruct.Pull = GPIO_PULLUP;
  HAL_GPIO_Init(I2C_SCL_PORT, &GPIO_InitStruct);
  
  // SDA引脚初始化
  SDA_IN();
  
  // 初始化状态
  i2c_state = I2C_STATE_IDLE;
  i2c_bit_counter = 0;
  i2c_byte_buffer = 0;
  
  // 初始化数据缓冲区
  for(int i = 0; i < 256; i++) {
    i2c_data[i] = 0;
  }
}

// 主处理函数,需要在主循环中调用
void I2C_Slave_Process(void) {
  static uint8_t scl_prev = 1;
  uint8_t scl_current = SCL_READ();
  
  // 检测SCL下降沿
  if(scl_prev && !scl_current) {
    // SCL下降沿处理
    switch(i2c_state) {
      case I2C_STATE_IDLE:
        // 检测起始条件
        if(SDA_READ() == 0) {
          i2c_state = I2C_STATE_ADDRESS;
          i2c_bit_counter = 0;
          i2c_byte_buffer = 0;
        }
        break;
        
      case I2C_STATE_ADDRESS:
        // 接收地址位
        if(i2c_bit_counter < 8) {
          i2c_byte_buffer <<= 1;
          i2c_byte_buffer |= SDA_READ();
          i2c_bit_counter++;
        } else {
          // 检查地址是否匹配
          if((i2c_byte_buffer >> 1) == I2C_SLAVE_ADDRESS) {
            i2c_rw_bit = i2c_byte_buffer & 0x01;
            
            // 发送ACK
            SDA_OUT();
            SDA_WRITE(0);
            
            if(i2c_rw_bit) {
              // 主机要读取数据
              i2c_state = I2C_STATE_DATA_TX;
            } else {
              // 主机要写入数据
              i2c_state = I2C_STATE_REGISTER;
            }
            
            i2c_bit_counter = 0;
            i2c_byte_buffer = 0;
          } else {
            // 地址不匹配,返回空闲状态
            i2c_state = I2C_STATE_IDLE;
          }
        }
        break;
        
      case I2C_STATE_REGISTER:
        // 接收寄存器地址
        if(i2c_bit_counter < 8) {
          i2c_byte_buffer <<= 1;
          i2c_byte_buffer |= SDA_READ();
          i2c_bit_counter++;
        } else {
          i2c_register = i2c_byte_buffer;
          
          // 发送ACK
          SDA_OUT();
          SDA_WRITE(0);
          
          i2c_state = I2C_STATE_DATA_RX;
          i2c_bit_counter = 0;
          i2c_byte_buffer = 0;
        }
        break;
        
      case I2C_STATE_DATA_RX:
        // 接收数据
        if(i2c_bit_counter < 8) {
          i2c_byte_buffer <<= 1;
          i2c_byte_buffer |= SDA_READ();
          i2c_bit_counter++;
        } else {
          // 存储接收到的数据
          i2c_data[i2c_register] = i2c_byte_buffer;
          
          // 如果有回调函数,调用它
          if(rx_callback != NULL) {
            rx_callback(i2c_register, i2c_byte_buffer);
          }
          
          // 发送ACK
          SDA_OUT();
          SDA_WRITE(0);
          
          i2c_register++;
          i2c_bit_counter = 0;
          i2c_byte_buffer = 0;
        }
        break;
        
      case I2C_STATE_DATA_TX:
        // 在SCL下降沿不需要做特别处理
        break;
        
      default:
        break;
    }
  }
  // 检测SCL上升沿
  else if(!scl_prev && scl_current) {
    // SCL上升沿处理
    switch(i2c_state) {
      case I2C_STATE_DATA_TX:
        // 发送数据
        if(i2c_bit_counter < 8) {
          // 发送数据位
          SDA_OUT();
          SDA_WRITE((i2c_byte_buffer & 0x80) ? 1 : 0);
          i2c_byte_buffer <<= 1;
          i2c_bit_counter++;
        } else {
          // 释放SDA线,准备接收ACK
          SDA_IN();
          
          // 检查主设备是否发送了ACK
          if(SDA_READ() == 0) {
            // ACK收到,准备发送下一个字节
            i2c_bit_counter = 0;
            
            // 如果有回调函数,获取下一个要发送的数据
            if(tx_callback != NULL) {
              i2c_byte_buffer = tx_callback(i2c_register);
            } else {
              i2c_byte_buffer = i2c_data[i2c_register];
            }
            
            i2c_register++;
          } else {
            // NACK收到,停止发送
            i2c_state = I2C_STATE_IDLE;
          }
        }
        break;
        
      default:
        break;
    }
  }
  
  // 检测停止条件
  if(scl_current && scl_prev && SDA_READ() && i2c_state != I2C_STATE_IDLE) {
    // 停止条件检测
    i2c_state = I2C_STATE_IDLE;
    SDA_IN();  // 确保SDA是输入模式
  }
  
  scl_prev = scl_current;
}

// 设置数据到指定寄存器
void I2C_Slave_SetData(uint8_t reg, uint8_t data) {
  i2c_data[reg] = data;
}

// 从指定寄存器获取数据
uint8_t I2C_Slave_GetData(uint8_t reg) {
  return i2c_data[reg];
}

// 设置接收回调函数
void I2C_Slave_SetRxCallback(I2C_RxCallback callback) {
  rx_callback = callback;
}

// 设置发送回调函数
void I2C_Slave_SetTxCallback(I2C_TxCallback callback) {
  tx_callback = callback;
}

3. 主程序示例 (main.c)

#include "main.h"
#include "i2c_slave.h"

// 全局变量
UART_HandleTypeDef huart1;

// 函数声明
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_USART1_UART_Init(void);

// 自定义回调函数
void my_rx_callback(uint8_t register_addr, uint8_t data) {
  // 当主设备写入数据时调用
  // 这里可以添加你的处理逻辑
  char msg[50];
  sprintf(msg, "Received: reg=0x%02X, data=0x%02X\r\n", register_addr, data);
  HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
}

uint8_t my_tx_callback(uint8_t register_addr) {
  // 当主设备请求数据时调用
  // 这里可以返回请求的数据
  char msg[50];
  sprintf(msg, "Requested: reg=0x%02X\r\n", register_addr);
  HAL_UART_Transmit(&huart1, (uint8_t*)msg, strlen(msg), HAL_MAX_DELAY);
  
  // 返回寄存器值加上一个固定值作为示例
  return i2c_data[register_addr] + 1;
}

int main(void) {
  HAL_Init();
  SystemClock_Config();
  MX_GPIO_Init();
  MX_USART1_UART_Init();
  
  // 初始化I2C从机
  I2C_Slave_Init();
  
  // 设置回调函数
  I2C_Slave_SetRxCallback(my_rx_callback);
  I2C_Slave_SetTxCallback(my_tx_callback);
  
  // 初始化一些示例数据
  for(int i = 0; i < 10; i++) {
    I2C_Slave_SetData(i, i * 10);
  }
  
  HAL_UART_Transmit(&huart1, (uint8_t*)"I2C Slave Ready\r\n", 17, HAL_MAX_DELAY);
  
  while (1) {
    // 主循环中处理I2C从机通信
    I2C_Slave_Process();
    
    // 其他任务...
    HAL_Delay(1);
  }
}

// 系统时钟配置
void SystemClock_Config(void) {
  // 根据你的STM32型号进行配置
}

// GPIO初始化
static void MX_GPIO_Init(void) {
  // 初始化其他GPIO...
}

// USART初始化
static void MX_USART1_UART_Init(void) {
  huart1.Instance = USART1;
  huart1.Init.BaudRate = 115200;
  huart1.Init.WordLength = UART_WORDLENGTH_8B;
  huart1.Init.StopBits = UART_STOPBITS_1;
  huart1.Init.Parity = UART_PARITY_NONE;
  huart1.Init.Mode = UART_MODE_TX_RX;
  huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
  huart1.Init.OverSampling = UART_OVERSAMPLING_16;
  HAL_UART_Init(&huart1);
}

参考代码 基于STM32的IIC通信,软件模拟IIC接口,从机代码 www.youwenfan.com/contentzhe/56663.html

使用

  1. 初始化:调用 I2C_Slave_Init() 初始化I2C从机功能。
  2. 主循环处理:在主循环中频繁调用 I2C_Slave_Process() 处理I2C通信。
  3. 数据访问:使用 I2C_Slave_SetData()I2C_Slave_GetData() 访问数据寄存器。
  4. 回调函数:设置回调函数以在数据接收或请求时执行自定义操作。

特点

注意

  1. 确保SCL和SDA线路上有适当的上拉电阻(通常4.7kΩ)。
  2. 在主循环中频繁调用 I2C_Slave_Process() 以确保及时响应I2C通信。
  3. 根据实际应用调整I2C从机地址。
  4. 如果需要更高的通信速率,可以考虑使用硬件I2C外设。

 

专注于matlab/simulink,电子电路,编程