记录一下IIC比较典型应用
传感器相关介绍
//引脚定义 硬件IC--》复用开漏 普通IO---》通用推挽
#define B_LUX_V20_SCL0_O {\
GPIO_InitTypeDef GPIO_ST; \
GPIO_ST.GPIO_Pin = GPIO_Pin_1;\
GPIO_ST.GPIO_Mode = GPIO_Mode_Out_PP; \
GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
GPIO_Init(GPIOB, &GPIO_ST); } //GPIOB10 开漏输出
#define B_LUX_V20_SCL0_H GPIO_SetBits(GPIOB, GPIO_Pin_1)
#define B_LUX_V20_SCL0_L GPIO_ResetBits(GPIOB, GPIO_Pin_1)
#define B_LUX_V20_SCL0_I {\
GPIO_InitTypeDef GPIO_ST; \
GPIO_ST.GPIO_Pin = GPIO_Pin_1;\
GPIO_ST.GPIO_Mode = GPIO_Mode_IN_FLOATING; \
GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
GPIO_Init(GPIOB, &GPIO_ST); } //GPIOB10 浮空输入
#define B_LUX_V20_SCL0_DAT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1)
#define B_LUX_V20_SDA0_O {\
GPIO_InitTypeDef GPIO_ST; \
GPIO_ST.GPIO_Pin = GPIO_Pin_2;\
GPIO_ST.GPIO_Mode = GPIO_Mode_Out_PP; \
GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
GPIO_Init(GPIOB, &GPIO_ST); } //GPIOB11 开漏输出
#define B_LUX_V20_SDA0_H GPIO_SetBits(GPIOB, GPIO_Pin_2)
#define B_LUX_V20_SDA0_L GPIO_ResetBits(GPIOB, GPIO_Pin_2)
#define B_LUX_V20_SDA0_I {\
GPIO_InitTypeDef GPIO_ST; \
GPIO_ST.GPIO_Pin = GPIO_Pin_2;\
GPIO_ST.GPIO_Mode = GPIO_Mode_IN_FLOATING; \
GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
GPIO_Init(GPIOB, &GPIO_ST); } //GPIOB11 浮空输入
#define B_LUX_V20_SDA0_DAT GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_2)
#define B_LUX_V20_ADDR_O {\
GPIO_InitTypeDef GPIO_ST; \
GPIO_ST.GPIO_Pin = GPIO_Pin_13;\
GPIO_ST.GPIO_Mode = GPIO_Mode_Out_PP; \
GPIO_ST.GPIO_Speed = GPIO_Speed_50MHz;\
GPIO_Init(GPIOC, &GPIO_ST); } //GPIOC13 推免输出
#define B_LUX_V20_ADDR_H GPIO_SetBits(GPIOC, GPIO_Pin_13)
#define B_LUX_V20_ADDR_L GPIO_ResetBits(GPIOC, GPIO_Pin_13)
`
这个是引脚定义,这里有一个点是比较奇怪的,可能是第一次遇见,我平常都是用没有硬件集成的IIC引脚,也就是用普通的IO口模拟IIC去通信,这里的代码是用PB10和PB11,这两个引脚复用功能都是有IIC的功能,我移植到普通的IO的时候就直接不行了(虽然代码用的是硬件IIC口,但是没有用32的库,也是软件模拟的)后来检查了一下是IO口的模式设置的问题,我的最后测试结果就是:硬件IC–》复用开漏 普通IO—》通用推挽输出,(模块已经有上拉电阻)。
模块时序
起始信号代码:
/*---------------------------------------------------------------------
功能描述: 起始信号
参数说明: 无
函数返回: 无
---------------------------------------------------------------------*/
vid B_LUX_V20_Start()
{
B_LUX_V20_SDA0_H; //拉高数据线
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SDA0_L; //产生下降沿
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L; //拉低时钟线
}
起始信号的定义:SCL处于高电平的时候,SDA拉低,出现一个下降沿,这个时候生成一个开始信号
代码一开始就是要把两根线拉高,(一般来说,三个信号我都会将SCL拉高,才开始去做延时),延时的时间一般来说按照100K的速度来延时就可以了,也就是5US。
停止信号代码
/*---------------------------------------------------------------------
功能描述: 停止信号
参数说明: 无
函数返回: 无
---------------------------------------------------------------------*/
vid B_LUX_V20_Stop()
{
B_LUX_V20_SDA0_L; //拉低数据线
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SDA0_H; //产生上升沿
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L;
B_LUX_V20_Delay5us();
}
停止信号的定义是SCL处于高电平期间,SDA有一个上升沿的变化(我一开始是分不清楚的,但是后面想想IIC的两根线空闲状态处于高电平状态,而停止信号就是停止通信,所以这下就记清楚了)。
应答信号
/*---------------------------------------------------------------------
功能描述: 发送应答信号
参数说明: ack - 应答信号(0:ACK 1:NAK)
函数返回: 无
---------------------------------------------------------------------*/
vid B_LUX_V20_SendACK(uint8 ack)
{
if (ack&0x01) B_LUX_V20_SDA0_H; //写应答信号
else B_LUX_V20_SDA0_L;
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L; //拉低时钟线
B_LUX_V20_SDA0_H;
B_LUX_V20_Delay5us(); //延时
}
/*---------------------------------------------------------------------
功能描述: 接收应答信号
参数说明: 无
函数返回: 返回应答信号
---------------------------------------------------------------------*/
uint8 B_LUX_V20_RecvACK()
{
uint8 CY = 0x00;
uint16 vConter = 1000;
B_LUX_V20_SDA0_H;
B_LUX_V20_SDA0_I;
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
while (vConter)
{
vConter--;
CY |= B_LUX_V20_SDA0_DAT; //读应答信号
if(!CY) break;
}
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L; //拉低时钟线
B_LUX_V20_SDA0_O;
return CY;
}
应答信号需要设置SDA的IO口另一个模式:浮空输入,用来检测IIC器件是否向主机发送了一个应答信号,如果发送了主机就可以检测到,
发送一个字节
/*---------------------------------------------------------------------
功能描述: 向IIC总线发送一个字节数据
参数说明: dat - 写字节
函数返回: 无
---------------------------------------------------------------------*/
uint8 B_LUX_V20_SendByte(uint8 dat)
{
uint8 vRval = 0x00;
uint8 i;
for (i=0; i<8; i++) //8位计数器
{
if (dat&0x80) B_LUX_V20_SDA0_H;
else B_LUX_V20_SDA0_L; //送数据口
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L; //拉低时钟线
B_LUX_V20_Delay5us(); //延时
dat <<= 1; //移出数据的最高位
}
vRval = B_LUX_V20_RecvACK();
return vRval;
}
根据IIC的定义,数据变化必须发生在SCL的低电平时候
解释一下:
if (dat&0x80)
{B_LUX_V20_SDA0_H;}
else
{B_LUX_V20_SDA0_L; }
这个是用位操作进行的,一个字节的数据和0X80相与可以得到最高的位一个二进制数是1还是0,如果是1的话SDA输出1,如果不是(也就是0)SDA输出0。
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
B_LUX_V20_SCL0_L; //拉低时钟线
B_LUX_V20_Delay5us(); //延时
dat <<= 1; //移出数据的最高位
这几行代码比较关键
首先就是上一步已经确定了数据线输出的数据是1或者是0,这个时候必须时SCL保持高电平才能保证数据正确传输,延时之后就开始拉低SCL,这个时候数据右移,下一个送出的数据就是次高位,这个时候SCL是低电平,数据可以变化,依次循环八次,就可以发送完一个字节,最后进行应答部分。
主机从IIC器件读取数据
/*---------------------------------------------------------------------
功能描述: 从IIC总线接收一个字节数据
参数说明: 无
函数返回: 接收字节
---------------------------------------------------------------------*/
uint8 B_LUX_V20_RecvByte()
{
uint8 i;
uint8 dat = 0;
B_LUX_V20_SDA0_I;
B_LUX_V20_SDA0_H; //使能内部上拉,准备读取数据,
for (i=0; i<8; i++) //8位计数器
{
B_LUX_V20_SCL0_H; //拉高时钟线
B_LUX_V20_Delay5us(); //延时
dat |= B_LUX_V20_SDA0_DAT; //读数据
B_LUX_V20_SCL0_L; //拉低时钟线
B_LUX_V20_Delay5us(); //延时
if (i<7) dat <<= 1;
}
B_LUX_V20_SDA0_O;
return dat;
}
这个接收和上面发送差不多,这里不在累赘。
上面的是这个模块的通信协议
数据格式:地址+读写位+应答+数据位+应答
地址有两个的7位数据,看模块的ADR接线,如果是往模块写数据(发指令、配置寄存器)就让W置位,不然就是让R置位。然后主机接收一个应答信号,接下来就是一个字节的数据,最后是一个应答
地址一般是7位,加上读写位就是八位,应答信号是独立的,最后的数据也是八位的数据。
/*---------------------------------------------------------------------
功能描述: 写BH1750
参数说明: REG_Address - 寄存器地址
函数返回: 无
---------------------------------------------------------------------*/
uint8 B_LUX_V20_Single_Write(uint8 REG_Address)
{
uint8 vRval = 0;
B_LUX_V20_Start(); //起始信号
vRval += B_LUX_V20_SendByte(B_LUX_V20_SlaveAddress); //发送设备地址+写信号
vRval += B_LUX_V20_SendByte(REG_Address); //内部寄存器地址,
// BH1750_SendByte(REG_data); //内部寄存器数据,
B_LUX_V20_Stop(); //发送停止信号
return vRval;
}
/*---------------------------------------------------------------------
功能描述: 连续读出BH1750内部数据
参数说明: 无
函数返回: 无
---------------------------------------------------------------------*/
uint8 B_LUX_V20_Multiple_read(vid)
{
uint8 vRval = 0;
uint8 i;
B_LUX_V20_Start(); //起始信号
vRval += B_LUX_V20_SendByte(B_LUX_V20_SlaveAddress+1); //发送设备地址+读信号
for (i=0; i<3; i++) //连续读取6个地址数据,存储中BUF
{
m_LUX_V20_BUF[i] = B_LUX_V20_RecvByte(); //BUF[0]存储0x32地址中的数据
if (i == 0x02)
{
B_LUX_V20_SendACK(1); //最后一个数据需要回NOACK
}
else
{
B_LUX_V20_SendACK(0); //回应ACK
}
}
B_LUX_V20_Stop(); //停止信号
//B_LUX_V20_Delay5ms();
return vRval;
}
只需要记住每一次通信都是:起始信号、收发数据、应答信号、停止信号,只要按着这个步骤写,并且地址和读写位和数据位都正确一般都可以写得出来。
和BH1750FVI相近的模块是MAX44009,两个模块基本差不多。
(欠解决问题:IO口的模式应该如何正确配置)
这里附上完整的代码工程和数据手册(提取码:37go)
BH1750FVI代码和手册资料,