IIC(Inter-Integrated Circuit)总线是一种由 PHILIPS 公司开发的两线式串行线,用于连接微控制器及其外围设备。它是由数据线 SDA 和时钟 SCL 构成的串行总线,可发送和接收数据。在 CPU 与被控 IC 之间、 IC 与 IC 之间进行双向传送, 高速 IIC 总线一般可达 400kbps 以上。
但是有一个问题,就是IIC并不是免费的啊!照ST公司宁可自己设计IIC都不肯给飞利浦专利费,那我估计也价格也并不便宜,但是STM32设计的IIC非常不稳定,时常出现丢失信号的问题,所以现在来使用IO口对IIC进行模拟
模拟状态如下:
1.IIC启动时
2.IIC停止时
3.主机向从机发送数据
4.接收数据
5.主机向从机发不应答信号
6.主机向从机发应答信号
7.从机发应答给主机,主机判断应答信号
具体代码实现如下:
#include "stm32f4xx_rcc.h"
#include "stm32f4xx_gpio.h"
#include "IIC.h"
//SDA:PB9 --> 开漏输出
//SCL:PB8 --> 开漏输出
static void iic_delayus(u16 us)
{
u16 i=42*us;
while(i--);
}
static void iic_delayms(u16 ms)
{
while(ms--)
iic_delayus(1000);
}
void IIC_init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType=GPIO_OType_OD;
GPIO_InitStructure.GPIO_Pin=GPIO_Pin_8|GPIO_Pin_9;
GPIO_InitStructure.GPIO_PuPd=GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed=GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStructure);
GPIO_SetBits(GPIOB,GPIO_Pin_8|GPIO_Pin_9);
}
//在SCL高电平期间,SDA从高电平到低电平跳转
void IIC_Start()
{
GPIO_SetBits(GPIOB,GPIO_Pin_8);//SCL
GPIO_SetBits(GPIOB,GPIO_Pin_9);//SDA
iic_delayus(5);
GPIO_ResetBits(GPIOB,GPIO_Pin_9);//在SCL高电平期间,拉低SDA
iic_delayus(5);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL也拉低,避免产生误动作(SCL低电平期间,允许SDA任意变化)
iic_delayus(5);
}
void IIC_Stop()
{
GPIO_ResetBits(GPIOB,GPIO_Pin_9);//SDA低
GPIO_SetBits(GPIOB,GPIO_Pin_8);//SCL高
iic_delayus(5);
GPIO_SetBits(GPIOB,GPIO_Pin_9);//SDA由低翻转到高
iic_delayms(5); //这里延时5ms,为了兼容AT24CXX写周期
}
//主机向从机发送数据
//iic先传高位,以字节为单位进行发送,SCL低电平期间发送
void IIC_SendByte(u8 dat)
{
u8 i=0;
for(i=0;i<8;i++)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//数据在SCL低电平发送
iic_delayus(5);
if(dat&0x80)
GPIO_SetBits(GPIOB,GPIO_Pin_9);//发送1
else
GPIO_ResetBits(GPIOB,GPIO_Pin_9);//发送0
iic_delayus(5);
GPIO_SetBits(GPIOB,GPIO_Pin_8);//拉高时钟,使接收方在高SCL电平期间读
iic_delayus(5);
dat <<=1;
}
GPIO_ResetBits(GPIOB,GPIO_Pin_8);
iic_delayus(5);
}
//接收数据
iic先传高位,以字节为单位进行发送,SCL高电平接收
u8 IIC_RecieveByte(void)
{
u8 i=0,temp=0;
for(i=0;i<8;i++)
{
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,让从机发数据过来
iic_delayus(5);
//读数据之前,先向SDA写1,将输出通道切断,此时模式切换为输入
GPIO_SetBits(GPIOB,GPIO_Pin_9);
temp<<=1; //temp左移一位,空出最低位,用于存放接收到的数据
GPIO_SetBits(GPIOB,GPIO_Pin_8);//拉高时钟,接收数据
iic_delayus(5);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==Bit_SET)
{
temp+=1;
}
iic_delayus(5);
}
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,避免产生误动作(SCL低电平期间,允许SDA任意变化)
iic_delayus(5);
return temp;
}
//接收器发应答信号
//主机做接收器(即:主机发应答信号)
//主机向从机发不应答信号
void IIC_NoAckToSlave()
{
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,准备发应答信号
iic_delayus(5);
GPIO_SetBits(GPIOB,GPIO_Pin_9);//主机输出1,发不应答信号
iic_delayus(5);
GPIO_SetBits(GPIOB,GPIO_Pin_8);//SCL拉高,从机接收应答信号
iic_delayus(5);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,避免产生误动作(SCL低电平期间,允许SDA任意变化)
iic_delayus(5);
}
//主机向从机发应答信号
//主机向从机发低电平
void IIC_AckToSlave()
{
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,准备发应答信号
iic_delayus(5);
GPIO_ResetBits(GPIOB,GPIO_Pin_9);//主机输出0,发应答信号
iic_delayus(5);
GPIO_SetBits(GPIOB,GPIO_Pin_8);//SCL拉高,从机接收应答信号
iic_delayus(5);
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,避免产生误动作(SCL低电平期间,允许SDA任意变化)
iic_delayus(5);
}
//从机发应答给主机,主机判断应答信号
u8 IIC_CheckAck()
{
u8 ack=0;
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,准备发应答信号
iic_delayus(5);
GPIO_SetBits(GPIOB,GPIO_Pin_9);//主机在应答前,先切断输出通道,切换为输入模式
GPIO_SetBits(GPIOB,GPIO_Pin_8);//SCL拉高,准备接收应答信号
iic_delayus(5);
if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_9)==Bit_SET)
ack=1;
else
ack=0;
GPIO_ResetBits(GPIOB,GPIO_Pin_8);//SCL拉低,避免产生误动作(SCL低电平期间,允许SDA任意变化)
iic_delayus(5);
return ack;
}