模拟IIC——关于模拟IIC的IO口的配置选取推挽输出还是开漏输出,以及是否需要更改IO口输入输出模式和是否需要对IO配置上拉

在使用模拟IIC的时候,观看别人的程序的时候发现了程序之间的一些不一样的地方

——————————————————————————————————代码1————————————————————————————————————

//IO方向设置
#define SDA_IN()  {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=0<<9*2;}	//PB9输入模式
#define SDA_OUT() {GPIOB->MODER&=~(3<<(9*2));GPIOB->MODER|=1<<9*2;} //PB9输出模式
//IO操作函数	 
#define IIC_SCL    PBout(8) //SCL
#define IIC_SDA    PBout(9) //SDA	 
#define READ_SDA   PBin(9)  //输入SDA 
———————————————————————————————————————————————————————————————————————————
——————————————————————————————————代码2————————————————————————————————————

#define BH1750_I2C_SCL_1()  GPIO_SetBits(GPIOB, GPIO_Pin_6)		/* SCL = 1 */
#define BH1750_I2C_SCL_0()  GPIO_ResetBits(GPIOB, GPIO_Pin_6)	/* SCL = 0 */
	
#define BH1750_I2C_SDA_1()  GPIO_SetBits(GPIOB, GPIO_Pin_7)		/* SDA = 1 */
#define BH1750_I2C_SDA_0()  GPIO_ResetBits(GPIOB, GPIO_Pin_7)	/* SDA = 0 */
	
#define BH1750_I2C_SDA_READ()  GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_7)	
/* 读SDA口线状态 */
———————————————————————————————————————————————————————————————————————————

代码1中间有一个对SDA数据线的模式的配置, 也就是输入输出的设置;

在向从设备写数据的时候会先调用SDA_OUT(),将引脚配置为输出模式;

在向从设备写数据的时候会先调用SDA_IN(),将引脚配置为输入模式;

但是问题来了,代码2中间并没有对引脚的输入输出模式进行改变,代码2也能和从机进行正常的通信,这是为什么呢?模拟IIC通信时对引脚的输入输出模式的配置是否有必要呢?

我仔细对比了两份代码,发现两份代码在引脚的初始化部分不一样。

——————————————————————————————————代码1————————————————————————————————————
void SHT3x_Init(void)
{    
    RCC->APB2ENR|=1<<6;              //使能PORTE时钟					 
	GPIOE->CRL&=0XFF00FFFF;          //PE4,PE5
	GPIOE->CRL|=0X00330000;	         //推挽输出 
	GPIOE->ODR|=3<<4;                //将PE4、PE5设为1
}
——————————————————————————————————代码2————————————————————————————————————

static void I2C_BH1750_GPIOConfig(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;

	RCC_APB2PeriphClockCmd(BH1750_RCC_I2C_PORT, ENABLE);	/* 打开GPIO时钟 */

	GPIO_InitStructure.GPIO_Pin = BH1750_I2C_SCL_PIN | BH1750_I2C_SDA_PIN;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;  	    /* 开漏输出 */
	GPIO_Init(BH1750_GPIO_PORT_I2C, &GPIO_InitStructure);

	/* 给一个停止信号, 复位I2C总线上的所有设备到待机模式 */
	i2c_Stop();
}

可以发现:推挽输出对应->需要切换输入输出模式;开漏输出对应->不需要切换输入输出模式;

我学习IIC的时候明明记得IIC需要使用开漏输出和接上拉电阻的;如果使用推挽输出,当多个设备

连接到一个总线上面时,如果一个设备输出低电平一个设备输出高电平就会出现短路的情况。而且

不能推挽输出实现线与。

为什么这里可以使用推挽呢?我猜测这是模拟IIC和硬件IIC的不同了。

我使用模拟IIC一般来说不会出现需要多个设备连接到一个总线上面情况,也就不会出现上面的短

路的可能以及线与的需求了。所以模拟IIC这里是可以使用推挽输出的。

那为什么推挽输出需要切换输入输出模式,开漏输出不需要切换输入输出模式呢?

我们首先需要了解到GPIO口的输入和输出模式有什么不同以及推挽输出和开漏输出的不同:

下面对于GPIO口的内容引用GPIO 口的输入,输出模式及其说明_星空闪耀&的博客-CSDN博客_gpio输入输出模式有几种https://blog.csdn.net/qq_42384937/article/details/82428812

在输入模式下只有红色圈出的部分处于工作状态,也就是说下半部分的输出电路,实际上是与端口处于隔离状态,不能工作,这个时候我们不能去读取端口的电平。

 在输出模式下,图的上半部,施密特触发器处于开启状态,这意味着CPU可以在“输入数据寄存器”的另一端,随时监控I/O端口的状态;

  

我们可以发现:

在输出模式下,施密特触发器处于开启状态,这意味着CPU可以在“输入数据寄存器”的另一端,随时监控I/O端口的状态,也就是可以读取IO口的值;

但是对于推挽输出而言:推挽输出是强输出电流模式,在此模式下的输出通道上的推挽结构MOS管,属于强上拉和强下拉的,这会影响读取IDR时的值,强上拉意味着会将来自外部的低电平输入强制置高,强下拉意味着会将来自外部的高电平输入强制置低

在开漏模式下,实现了虚拟的I/O端口双向通信:只要CPU输出逻辑“1”,由于N-MOS管处于关闭状态,I/O端口的电平将完全由外部电路决定,因此,CPU可以在“输入数据寄存器”读到外部电路的信号,而不是它自己输出的逻辑“1”。

了解lstm32的双向io口https://blog.csdn.net/weixin_30443813/article/details/96729719

请问STM32F4的GPIO管脚可以同时配置成输入、输出模式吗https://bbs.21ic.com/icview-726196-1-1.html

详细一点的内容可以看这两个链接

现在我们可以分析两个代码的差异:推挽输出对应->需要切换输入输出模式;开漏输出对应->不需要切换输入输出模式;

因为设置为推挽输出时 输出通道上的推挽结构MOS管,属于强上拉和强下拉的,这会影响读取IDR时的值,强上拉意味着会将来自外部的低电平输入强制置高,强下拉意味着会将来自外部的高电平输入强制置低。所以我们去读取IO口的值会是输出的值,并不能得到外部电路的值。所以在IIC通信时需要读取外部数据的时候需要将IO的模式配置成输入模式;

但是对于开漏输出来说,只要CPU输出逻辑“1”,由于N-MOS管处于关闭状态,I/O端口的电平将完全由外部电路决定,因此,CPU可以在“输入数据寄存器”读到外部电路的信号,而不是它自己输出的逻辑“1”。所以不需要去将IO口的模式配置成输入模式

这也就造成了上面两个代码的差异。

上拉电阻:查看F4的中文参考手册可以知道

 IO口配置成上拉的时候可以解决推挽输出输出高电平时高阻态没办法拉高电平的情况。

注意:

使用开漏输时,如果需要读取电平值,最好输出高电平,由于N-MOS管处于关闭状态,I/O端口的电平将完全由外部电路决定,这时读到的才是外部电路的电平值。

遇到的问题:

问题1:

软件模拟I2C据说IO口没有设置为开漏很容易损坏芯片,大家有没碰到过? (amobbs.com 阿莫电子论坛)https://www.amobbs.com/thread-5659439-1-1.html在上面这个链接说IO口没有设置为开漏很容易损坏芯片,这种损坏是不是指的就是搭载多设备的时候如果有多个设备配置成推挽输出会又可以造成短路?

还是说会有别的情况也会对芯片造成损坏?

问题2:

在使用SHT30温湿度传感器的时候,想要采用开漏输出,但是怎么配置都有问题。希望能有大佬给我一份例程。

好的,关于STM32模拟IIC,下面是一份示例代码,你可以参考一下。 首先,需要在STM32的CubeMX中配置IIC引脚,选择PB6PB7作为SCLSDA引脚。 接下来,打CubeMX的“Pinout”选项卡,在左侧的“Categories”中选择“I2C1”,将PB6PB7分别设置为SCLSDA引脚。然后点击“Generated Code”生成代码。 在生成的代码中,你需要找到以下部分: ``` /* I2C1 GPIO Configuration PB6 ------> I2C1_SCL PB7 ------> I2C1_SDA */ HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); ``` 这是IIC引脚的配置代码,确保你的引脚与这里的配置相同。 接下来,是IIC初始化读写数据的代码: ``` I2C_HandleTypeDef hi2c1; void MX_I2C1_Init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } } void I2C_WriteData(uint8_t addr, uint8_t reg, uint8_t data) { HAL_I2C_Mem_Write(&hi2c1, addr << 1, reg, I2C_MEMADD_SIZE_8BIT, &data, 1, 1000); } void I2C_ReadData(uint8_t addr, uint8_t reg, uint8_t* data, uint8_t len) { HAL_I2C_Mem_Read(&hi2c1, addr << 1, reg, I2C_MEMADD_SIZE_8BIT, data, len, 1000); } ``` 其中,MX_I2C1_Init()函数用于初始化IICI2C_WriteData()函数用于向设备写入数据,I2C_ReadData()函数用于从设备读取数据。 使用示例: ``` uint8_t data = 0x12; I2C_WriteData(0x50, 0x20, data); //向地址为0x50的设备的0x20寄存器写入0x12 uint8_t readData[2] = {0}; I2C_ReadData(0x50, 0x20, readData, 2); //从地址为0x50的设备的0x20寄存器读取2个字节的数据 ``` 你可以根据你的具体需求修改代码中的地址、寄存器数据等参数。
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

我和你拼了'

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值