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
使用
- 初始化:调用
I2C_Slave_Init()初始化I2C从机功能。 - 主循环处理:在主循环中频繁调用
I2C_Slave_Process()处理I2C通信。 - 数据访问:使用
I2C_Slave_SetData()和I2C_Slave_GetData()访问数据寄存器。 - 回调函数:设置回调函数以在数据接收或请求时执行自定义操作。
特点
- 完整的I2C从机协议实现
- 支持7位地址识别
- 自动处理ACK/NACK
- 支持读写操作
- 可配置的回调函数机制
- 256字节数据寄存器
注意
- 确保SCL和SDA线路上有适当的上拉电阻(通常4.7kΩ)。
- 在主循环中频繁调用
I2C_Slave_Process()以确保及时响应I2C通信。 - 根据实际应用调整I2C从机地址。
- 如果需要更高的通信速率,可以考虑使用硬件I2C外设。