I2C 的两个引脚(SCL 引脚和 SDA 引脚)需要既能输出又能输入,为了避免复杂的配置操作需要把该引脚配置为开漏输出模式,该模式的说明如下图所示:

当单片机的 SDA 引脚配置为低电平时,SDA 线被拉低;
当单片机的 SDA 引脚配置为高电平时,引脚端口为高阻态,SDA 线通过上拉电阻被 VCC 拉高。
因此一定要注意在进行 I2C 通讯时确保 SDA 线和 SCL 线外接上拉电阻。
my_i2c.h
#pragma once
#include "main.h"
#include "gpio.h"
#define I2C_SCL_PORT GPIOA /* GPIO端口 */
#define I2C_SCL_PIN GPIO_PIN_9 /* 连接到SCL时钟线的GPIO */
#define I2C_SDA_PIN GPIO_PIN_10 /* 连接到SDA数据线的GPIO */
#define i2c_scl_high() I2C_SCL_PORT->BSRR = I2C_SCL_PIN /* SCL = 1 */
#define i2c_scl_low() I2C_SCL_PORT->BRR = I2C_SCL_PIN /* SCL = 0 */
#define i2c_sda_high() I2C_SCL_PORT->BSRR = I2C_SDA_PIN /* SDA = 1 */
#define i2c_sda_low() I2C_SCL_PORT->BRR = I2C_SDA_PIN /* SDA = 0 */
#define i2c_read_sda() ((I2C_SCL_PORT->IDR & I2C_SDA_PIN) != 0) /* 读SDA口线状态 */
void i2c_gpio_Init(void);
void i2c_Start(void);
void i2c_Stop(void);
uint8_t i2c_WaitAck(void);
void i2c_Ack(void);
void i2c_NAck(void);
void i2c_SendByte(uint8_t data);
uint8_t i2c_ReadByte(void);
my_i2c.c
#include "my_i2c.h"
/**
* @brief 模拟I2C延时
*
*/
static void i2c_Delay(void)
{
volatile uint8_t i;
for (i = 0; i < 5; i++)
;
}
/**
* @brief 软件模拟I2C初始化
*
*/
void i2c_gpio_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
/*Configure GPIO pins : SCL_Pin SDA_Pin */
GPIO_InitStruct.Pin = GPIO_PIN_9 | GPIO_PIN_10;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
/**
* @brief CPU发起I2C总线启动信号
* _____
* SDA \_____________
* __________
* SCL \________
*/
void i2c_Start(void)
{
i2c_sda_high();
i2c_scl_high();
i2c_Delay();
i2c_sda_low();
i2c_Delay();
i2c_scl_low();
i2c_Delay();
}
/**
* @brief CPU发起I2C总线停止信号
* _______
* SDA __________/
* ____________
* SCL _____/
*/
void i2c_Stop(void)
{
i2c_sda_low();
i2c_scl_high();
i2c_Delay();
i2c_sda_high();
}
/**
* @brief I2C 等待响应(CPU产生一个时钟,并读取器件的ACK应答信号)
*
* @return uint8_t
*/
uint8_t i2c_WaitAck(void)
{
uint8_t re;
i2c_sda_high();
i2c_Delay();
i2c_scl_high();
i2c_Delay();
if (i2c_read_sda())
{
re = 1;
}
else
{
re = 0;
}
i2c_scl_low();
i2c_Delay();
return re;
}
/**
* @brief I2C 响应
* @retval none
* @author Mr.W
* @date 2020-10-12
*/
/**
* @brief CPU产生一个ACK信号
* ____
* SCL ______/ \______
* ____ _____
* SDA \_______/
*/
void i2c_Ack(void)
{
i2c_sda_low();
i2c_Delay();
i2c_scl_high();
i2c_Delay();
i2c_scl_low();
i2c_Delay();
i2c_sda_high();
}
/**
* @brief I2C 不响应(CPU产生1个NACK信号)
* ____
* SCL ______/ \______
* __________________
* SDA
*/
void i2c_NAck(void)
{
i2c_sda_high();
i2c_Delay();
i2c_scl_high();
i2c_Delay();
i2c_scl_low();
i2c_Delay();
}
/**
* @brief CPU向I2C总线设备发送8bit数据
*
* @param data
*/
void i2c_SendByte(uint8_t data)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
if (data & 0x80)
{
i2c_sda_high();
}
else
{
i2c_sda_low();
}
i2c_Delay();
i2c_scl_high();
i2c_Delay();
i2c_scl_low();
if (i == 7)
{
i2c_sda_high();
}
data <<= 1;
i2c_Delay();
}
}
/**
* @brief CPU从I2C总线设备读取8bit数据
*
* @return uint8_t
*/
uint8_t i2c_ReadByte(void)
{
uint8_t i, data = 0;
for (i = 0; i < 8; i++)
{
data <<= 1;
i2c_scl_high();
i2c_Delay();
if (i2c_read_sda())
{
data++;
}
i2c_scl_low();
i2c_Delay();
}
return data;
}
本文介绍了一种基于单片机的I2C通信模拟实现方法,包括开漏输出模式的配置、延时函数、引脚初始化、启动及停止信号的生成等关键步骤,并提供了详细的代码示例。
640

被折叠的 条评论
为什么被折叠?



