开漏输出:让输出端口有效输出高电平(上拉电阻的作用)且保证不短路(相当于VCC与GND之间多了一个电阻),且开漏输出能构成线与,简化电路。
短路原因:由于多个GPIO口连接到一根线上,当一个IO输出低电平时,另一个IO输出高电平就会造成短路。
线与,即两个输出端(包括两个以上)直接互连就可以实现“AND”的逻辑功能。
在通信的过程中以下细节:
1、总线空闲状态时,SDA与SCL处于高电平状态。
2、SCL高电平期间,SDA须保持稳定。当SDA状态需要改变时最好在SCL低电平状态(SCL高电平时,SDA状态改变会被认为是有效信号,起始或者停止信号)(这点对数据传输很重要)。
3、在一个在字节的数据传输完成后从机都会向主机发送一个应答信号。
附上完整的驱动代码(stm32f407):
iic.c
/*****************************
函数说明:
PB8 -- SCL -- 时钟线
PB9 -- SDA -- 数据线
******************************/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
//打开GPIOB组时钟
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9; //引脚9 10
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽输出
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //速度
GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//改变端口模式 --- 输出/输入
void IIC_CHANGE_MODE(GPIOMode_TypeDef mode)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9; //第9号引脚
GPIO_InitStructure.GPIO_Mode = mode; //输入/输出模式
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; //推挽输出,增强驱动能力,引脚的输出电流更大
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; //引脚的速度最大为100MHz
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL; //没有使用内部上拉电阻
GPIO_Init(GPIOB, &GPIO_InitStructure);
}
/**************
IIC起始信号
***************/
void IIC_START(void)
{
IIC_SDA_OUT(); //设置成输出模式
SCL_H;
SDA_H;
delay_us(5);
SDA_L;
delay_us(5);
SCL_L;
}
/**************
IIC停止信号
***************/
void IIC_STOP(void)
{
IIC_SDA_OUT(); //设置成输出模式
SCL_H;
SDA_L;
delay_us(5);
SDA_H;
delay_us(5);
SDA_L;
}
/**********************************
函数说明:
主机:发送应答信号/发送非应答信号
***********************************/
void SEND_NACK_OR_ACK(int flag_bit)
{
IIC_SDA_OUT(); //设置成输出模式
SCL_L;
if(flag_bit == 0)
{
SDA_L;
}
if(flag_bit == 1)
{
SDA_H;
}
delay_us(5);
SCL_H;
delay_us(5);
SCL_L;
}
/**********************************
函数说明:
从机:接收应答信号/发送非应答信号
***********************************/
u8 REC_NACK_OR_ACK(void)
{
u8 ack = 0;
IIC_SDA_IN(); //设置成输入模式
SCL_L;
delay_us(5);
SCL_H;
delay_us(5);
if(SDA_IN == 0)
{
ack = 0;
}
if(SDA_IN == 1)
{
ack = 1;
}
SCL_L;
return ack;
}
/**********************************
函数说明:
发送一个字节
***********************************/
void IIC_SEND_BYTE(u8 data)
{
u8 i;
IIC_SDA_OUT(); //设置成输入模式
SCL_L;
for(i=0;i<8;i++) //发送8为数据
{
if(data & (0x01 << (7-i)))
{
SDA_H;
}
else
{
SDA_L;
}
delay_us(5);
SCL_H;
delay_us(5);
SCL_L;
}
}
/**********************************
函数说明:
接收一个字节
***********************************/
u8 IIC_REC_BYTE(void)
{
u8 i;
u8 data = 0x00;
IIC_SDA_IN(); //设置成输入模式
SCL_L;
for(i=0;i<8;i++)
{
delay_us(5);
SCL_H;
delay_us(5);
if(SDA_IN == 1)
{
data |= (0x01 << (7-i));
}
SCL_L;
}
return data;
}
iic.h
#ifndef __IIC_H
#define __IIC_H
#include "stm32f4xx.h"
#include "delay.h"
#define IIC_SDA_OUT() IIC_CHANGE_MODE(GPIO_Mode_OUT)
#define IIC_SDA_IN() IIC_CHANGE_MODE(GPIO_Mode_IN)
#define SDA_IN GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)
//PB9
#define SDA_H GPIO_SetBits(GPIOB,GPIO_Pin_9)
#define SDA_L GPIO_ResetBits(GPIOB,GPIO_Pin_9)
//PB8
#define SCL_H GPIO_SetBits(GPIOB,GPIO_Pin_8)
#define SCL_L GPIO_ResetBits(GPIOB,GPIO_Pin_8)
void IIC_Init(void);
void IIC_CHANGE_MODE(GPIOMode_TypeDef mode);
void IIC_START(void);
void IIC_STOP(void);
void SEND_NACK_OR_ACK(int flag_bit);
u8 REC_NACK_OR_ACK(void);
void IIC_SEND_BYTE(u8 data);
u8 IIC_REC_BYTE(void);
#endif