前言
最近接触到一个应用,需要在低功耗的产品上加上光照度采集,正好最近有接触到一款光照传感器 BH1750 ,性能价格都合适,那么今天就抽空来好好测试一下。
那么要写一篇测试文章,我会尽量以新手的角度从资料的获取,资料的阅读理解,以及根据资料进行驱动设计来做记录 。
我是 矜辰所致,全网同名,尽量用心写好每一系列文章,不浮夸,不将就,认真对待学知识的我们。
一、确认基本参数
传感器资料网上可以找到,实在不行可以去电子商城下载,比如某商城:
1.1 根据产品特征整体认识产品
那么其实接触一个新的传感器,在传感器文档开头部分,都会有一个 Features ,说明了产品的特征,比如 BH1750 的 Features 如下图:
上面特征我虽然全部看了,但是我画出了两条,第一条可以得知传感器的通讯方式为 I2C,第二条 具备低功耗的能力可以判断传感器是否符合我们的应用。 当然,其他的特征也得过一遍,看看是否能满足自己的应用场合。
1.2 根据工作条件确定产品的供电方式
继续往下看,我们能看到 Absolute Maximum Ratings 这个电气绝对参数,这个是规定的传感器的耐压等数据,这里了解一下即可,我们更加需要关系的是下面这个图:
通过上图我们可以直接确定传感器的供电电压。
当然还有一个重要的点就是他的 I2C 参考电压可以达到 Vcc ,这说明在我们常用的 3.3V 供电的 MCU 系统上使用这个传感器不需要做电平转换,可以直接与 MCU 的 I2C 接口连接。
再往下的一些电气参数 Electrical Characteristics 其实过一遍了解一下即可,不仔细也没有太大关系。
1.3 根据推荐电路图确定 PCB 设计
上面我们已经知道了传感器供电电压,那么如果是自己设计 硬件电路的话,那么需要了解:
各个引脚的功能定义
参考产品手册上的推荐电路图
这个在资料往下翻一大段。
引脚的定义如下:
这里加一个判别传感器 1 引脚的方法:
答案来自网上问答:将心片得背面朝上,缺口(或一条竖线)向左,则左下方第一脚为 1 脚。
在文档后面有引脚的定义:
我这里自己写一个表格做简要说明:
当 ADDR 为高电平的时候,传感器地址为 1011100
:0x5C;
当 ADDR 为低电平的时候,传感器地址为 0100011
:0x23;
接下来我们来看看推荐电路图 ,注意手册中的说明:
图中 SDA 和 SCL 以及省略了,还得注意一下 ADDR 的高低电平,因为决定了传感器的地址。
在手册中推荐了 4 种电路图,我这里推荐的是使用第四种(需要知道一下这种无法满足内部寄存器重置的操作):
如果使用这种方式,那么我们接下来操作传感器的地址就是:0x23。
对于硬件相关的东西我们确定好了以后,我们可以自己画 PCB ,要记住 I2C 通讯是需要上拉电阻的。当然如果是采购的现成的传感器模块可以省略。
采购的传感器模块一般都只需要连接 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 测试效果
测试是比较简单的,直接通过串口助手看结果就行了,本次根据手册设计的驱动算是成功的:
结语
本文我们从一个传感器的基本资料入手,完成了一个传感器的驱动设计。
算是给新手工程师们做了一个示例,如何去阅读传感器文档资料,如何获取我们有用的东西。
当然本次也只是一个基本测试,后面我还需要自己画个板子,主要是看看这个传感器的低功耗效果,而且方案需要应用在别的硬件上面,还需要做电平转换,这个后面如果有机会再来做实际应用的记录。
本文就到这里,谢谢大家! 另外,别忘了下面可以加我的技术群哦!
评论