采购的传感器模块一般都只需要连接 4个引脚,就是标准的 I2C 通讯的 :SCL、 SDA、VCC、GND 。
博主本次采用的传感器其实也是采购的成品小板子,但是正真的最后还是会自己画的,到时候有机会也来记录一下自己画的用作低功耗的小板子:
二、工作流程及指令
我们完成硬件的基本了解,可以了解一下传感器的工作流程级操作指令。
2.1 工作流程分析
在资料手册中,列出了 Measurement Procedure 部分,如下图:
对于低功耗的产品,我们都会使用单次测量模式,可以最大限度的控制功耗。
我们可以直接总结出来,我们如果使用单次测量命令的话,我们在初始化传感器的时候就可以使用指令让模块进入单次测量模式:
初始化:
模块上电 ——> 发送指令使模块进入单次测量模式
正常工作流程:
发送指令使模块进入上电模式 ——> 发送测量命令 ——> 模块测量完成会会自动进入掉电模式(单次测量模式)
2.2 操作命令
在手册中给出了操作命令,但是根据我们前面的分析,我们需要用到的指令并不多,在图中我圈出了我们可能需要用到的指令:
其中我们只需要记住单次测量的命令就可以了,使用什么分辨率根据自己的情况而定。
2.3 单次测量示例解析
为了更好的说明命令怎么用,在手册中也已经举了例子,我给他加上了中文注释说明测量流程:
传感器手册读到这里,基本上已经满足我们的开发需求,我们下面就开始根据上面所分析的资料进行程序设计。
三、程序设计
本次的测试基于 STM32L051 ,但是其实在驱动程序中体现不出来,因为用的软件 I2C ,驱动基本上可以移植到各种单片机平台。
在我们使用 i2c 传感器的时候,一般都是有一个通用驱动文件,然后还有一个传感器的驱动文件,如下图(d6t44l 是一个 I2C 传感器):
对于 I2C 的基础知识,本文不做过多讨论,大家应该比较熟悉,不熟悉的可以复习一下基础知识。
3.1 I2C 通用驱动
通用驱动就包括了 ,I2C 启动,结束,发送等待 ACK ,发送不等待 ACK ,读取,等待 ACK 等等这些函数,这个其实在很多开源的项目中找一个就可以了。
我们这里先确定一下 i2c.h 文件,针对自己的设备连接的 IO 口进行定义:
这里因为大家都很方便的可以找到开源的驱动,唯独需要注意上图中的定义,改成自己测试的 IO 口,所以这里就不过多解释,直接把通用驱动放上 :
i2c.h:
#ifndef \_I2C\_H\_INCLUDED
#define \_I2C\_H\_INCLUDED
#include "main.h"
#include "Datadef.h"
#define MYIIC\_CLK\_HIGH HAL\_GPIO\_WritePin(SCL\_GPIO\_Port,SCL\_Pin,GPIO\_PIN\_SET)
#define MYIIC\_CLK\_LOW HAL\_GPIO\_WritePin(SCL\_GPIO\_Port,SCL\_Pin,GPIO\_PIN\_RESET)
#define MYIIC\_DATA\_HIGH HAL\_GPIO\_WritePin(SDA\_GPIO\_Port,SDA\_Pin,GPIO\_PIN\_SET)
#define MYIIC\_DATA\_LOW HAL\_GPIO\_WritePin(SDA\_GPIO\_Port,SDA\_Pin,GPIO\_PIN\_RESET)
#define MYIIC\_DATA\_STATE HAL\_GPIO\_ReadPin(SDA\_GPIO\_Port,SDA\_Pin)
// ------------------------
#define DONOTHING() {;}
// ------------------------
// command's
#define I2C\_WRITE 0
#define I2C\_READ 1
#define I2C\_ACK 0
#define I2C\_NACK 1
void MYIIC\_Start(void);
void MYIIC\_Stop(void);
u8 MYIIC\_Wait\_Ack(void);
void IIC\_NAck(void);
void IIC\_Ack(void);
void IIC\_Send\_Byte(u8 txd);
u8 IIC\_Read\_Byte(unsigned char ack);
#endif
i2c.c:
#include "i2c.h"
void MYIIC\_Start(void)
{
MYIIC_DATA_HIGH;
delay\_us(5);
MYIIC_CLK_HIGH;
delay\_us(10);
MYIIC_DATA_LOW;
delay\_us(10);
MYIIC_CLK_LOW; //使SCL置低,准备发送或者接受数据
delay\_us(10);
}
void MYIIC\_Stop(void)
{
MYIIC_DATA_LOW;
delay\_us(5);
MYIIC_CLK_LOW;
delay\_us(10);
MYIIC_CLK_HIGH;
delay\_us(5);
MYIIC_DATA_HIGH;
delay\_us(10);
}
//读1个字节,ack=1时,发送ACK,ack=0,发送nACK
u8 IIC\_Read\_Byte(unsigned char ack)
{
unsigned char i,receive=0;
// MYSDA\_IN;//SDA设置为输入
for(i=0;i<8;i++ )
{
MYIIC_CLK_LOW; //SCL为由低变高,在SCL高的时候去读 SDA的数据
delay\_us(10);
MYIIC_CLK_HIGH;
receive<<=1; //第一次这里还是0,第二次开始每次接收的数据做移动一位,从高位开始接收
if(MYIIC_DATA_STATE)receive++; //如果数据为1,++以后就是1,数据为0,不执行就是0;
delay\_us(10);
}
if (!ack)
IIC\_NAck();//发送nACK
else
IIC\_Ack(); //发送ACK
return receive;
}
u8 MYIIC\_Wait\_Ack(void)
{
u8 ucErrTime=0;
delay\_us(5);
MYIIC_DATA_HIGH;delay\_us(5); //MCU DATA 置高,外面高就是高,外面低就是低
MYIIC_CLK_HIGH; delay\_us(5); //CLK 高电平期间数据有效
while(MYIIC_DATA_STATE) //低电平为有应答,高电平无应答
{
ucErrTime++;
if(ucErrTime>250)
{
MYIIC\_Stop();
return 1;
}
}
delay\_us(10);
MYIIC_CLK_LOW;
return 0;
}
void IIC\_Ack(void)
{
MYIIC_CLK_LOW; //SCL为低,SDA为低,SCL为高,SDA为低,应答低电平有效,SCL为低,产生应答信号
// MYSDA\_OUT;
MYIIC_DATA_LOW;
delay\_us(10);
MYIIC_CLK_HIGH;
delay\_us(10);
MYIIC_CLK_LOW;
delay\_us(10);
MYIIC_DATA_HIGH;
}
void IIC\_NAck(void)
{
MYIIC_CLK_LOW; //SCL为低,SDA为高,SCL为高,SCL为低
// MYSDA\_OUT;
MYIIC_DATA_HIGH;
delay\_us(10);
MYIIC_CLK_HIGH;
delay\_us(10);
MYIIC_CLK_LOW;
}
//IIC发送一个字节
//返回从机有无应答
//1,有应答
//0,无应答
void IIC\_Send\_Byte(u8 txd)
{
u8 t;
// MYSDA\_OUT;
MYIIC_CLK_LOW; //拉低时钟开始数据传输 ,SCL为低,SDA变高或者变低(数据位),SCL变高,SCL变低,期间SDA为1既1,为0既0
for(t=0;t<8;t++) //一个字节8位,一位一位发送
{
MYIIC_CLK_LOW;
if((txd&0x80)>>7) //从最高位开始发送,如果是1,发送高电平
MYIIC_DATA_HIGH;
else
MYIIC_DATA_LOW;
txd<<=1; //SDA处理完毕,此时可以将SCL拉高接受数据,拉高以后延时拉低
delay\_us(10); //
MYIIC_CLK_HIGH;
delay\_us(10);
MYIIC_CLK_LOW;
delay\_us(5);
}
}
3.2 BH1750 驱动
完成了通用驱动,我们现在就可以完全根据前面的分析流传进行 BH1750 的驱动设计。
我们新建一个 bh1750.c 和 bh1750.h 文件,在头文件中进行一些必要的宏定义:
把可能需要用到的都给他定义一遍,后面需要改正再说。
然后开始在 c 文件中写驱动,首先当然是传感器初始化,上电就得设置为单次测量模式,那么发送数据就得按照前面给出的分析。
首先传感器上电初始化给他写个函数:
然后读取函数,因为只需要读取2个字节,所以也比较简单:
最后在需要的地方调用函数即可:
这里还是把 传感器测试驱动源码放上。
bh1750.c
#include "bh1750.h"
#include <stdio.h>
/\*
说明,测试程序,函数不判断是否成功
\*/
void bh1750\_init()
{
MYIIC\_Start();
IIC\_Send\_Byte(BH1750_ADDRESS << 1); //地址,和读写指令
MYIIC\_Wait\_Ack();
delay\_us(150);
// IIC\_Send\_Byte(BH1750\_CMD\_POWERON); // 电不一定需要,这里测试看看
// MYIIC\_Wait\_Ack();
// delay\_us(150);
IIC\_Send\_Byte(BH1750_MODE_ONE_H_RES); //单次测量
MYIIC\_Wait\_Ack();
HAL\_Delay(BH1750_MEASURE_DURATION_MS);
MYIIC\_Stop();
}
void bh1750\_read(uint16\_t \*lux)
{
uint8\_t read_buffer[2];
MYIIC\_Start();
IIC\_Send\_Byte(BH1750_ADDRESS << 1); //地址,和读写指令
MYIIC\_Wait\_Ack();
delay\_us(150);
IIC\_Send\_Byte(BH1750_MODE_ONE_H_RES); //单次测量
MYIIC\_Wait\_Ack();
MYIIC\_Stop();
HAL\_Delay(BH1750_MEASURE_DURATION_MS);
MYIIC\_Start();
IIC\_Send\_Byte((BH1750_ADDRESS << 1)|1); //地址,和读写指令
MYIIC\_Wait\_Ack();
read_buffer[0] = IIC\_Read\_Byte(1);
delay\_us(120);
read_buffer[1] = IIC\_Read\_Byte(0);
delay\_us(120);
MYIIC\_Stop();
uint32\_t lv_lux = ((read_buffer[0] << 8) | read_buffer[1]) \* 10 / 12;
\*lux = (uint16\_t)lv_lux;
}
bh1750.h
#ifndef \_\_BH1750\_H
#define \_\_BH1750\_H
#include "i2c.h"
#include "main.h"
// BH1750 working mode
#define BH1750\_MODE\_CONT\_H\_RES 0x10
#define BH1750\_MODE\_CONT\_H\_RES2 0x11
#define BH1750\_MODE\_CONT\_L\_RES 0x13
#define BH1750\_MODE\_ONE\_H\_RES 0x20
#define BH1750\_MODE\_ONE\_H\_RES2 0x21
#define BH1750\_MODE\_ONE\_L\_RES 0x23
#define BH1750\_MEASURE\_DURATION\_MS 120 // Max. 180ms
#define BH1750\_CMD\_POWERDOWN 0x00
#define BH1750\_CMD\_POWERON 0x01
#define BH1750\_CMD\_RESET 0x07
#define BH1750\_ADDRESS 0x23 // 0x23 (ADDR='L') or 0x5C (ADDR='H')
void bh1750\_init();
void bh1750\_read(uint16\_t \*lux);
#endif
3.3 测试效果
测试是比较简单的,直接通过串口助手看结果就行了,本次根据手册设计的驱动算是成功的:
结语
收集整理了一份《2024年最新物联网嵌入式全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升的朋友。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人
都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!