4.2.1 任务分析 该任务需要设计一个监测环境光强度的应用程序。使用传感器硬件 ROHM 半 导体公司的 BH1750 如图所示 4-2-1 所示,图 4-2-1(a)为传感器 模块正面,图 4-2-1(b)为传感器模块背面。
从图 4-2-1 可以看到,BH1750 光强传感器模块 5 外部接口,分别是 V CC 、GND、 SCL、SDA 和 ADDR。该模块与微控制器的硬件接线如表所示 4-2-1 所示。
任务要求微控制器每隔一隔 2s 采集一次光强,数据转换为指定格式后,通过 USART 发 送到上位机显示。环境光强度为5368 Lx”。 本任务涉及的知识点包括: ? I 2 C 总线规范; ? BH1750 光强传感器的特性; ? 编写环境光强监测应用程序的方法。 4.2.2 知识链接 1.I 2 C 总线规范 (1)I 2 C 总线概述 I 2 C (Inter-Integrated Circuit) 总线由 Philips 公司在微电子通信控制领域推出了广泛的产品 应用。I 2 C 总线是串行同步通信的一种,具有双向、两线、多主控的特点,并具有总线仲 切割机制非常适合设备之间的近距离和非常频繁的数据通信。 ① I 2 C 总线的连接方式 I 2 C 总线设备之间常用的连接方式如图所示 4-2-2 所示。
I 2 C 总线是支持多设备的总线。 4-2-2 可以看到,I 2 C 总线连接了 2 微控制器,1 门阵列电路,1 个液晶(LCD)显示屏、1 个静态RAM 或 EEPROM,共用一个这些设备 I 2 C 总线。
② I 2 C 总线物理层的特点 I 2 C 总线的物理层具有以下特点。 ? 一条 I 2 C 总线只需要两条线路:双向串行数据线(SDA)串行时钟线(SCL)。SDA 用于数据收发,SCL 同步数据收发。 ? 每个连接到总线的设备都有一个独立的地址,主机使用地址来控制不同设备的访问。 ? 总线通过上拉电阻连接到电源。I 2 C 总线在空闲时呈现高阻态,总线由上拉电阻电平 拉高。 ? I 2 C 如果两个或两个以上的主机同时初始化数据传输,总线将通过冲突检测 和仲裁决定哪种设备占用总线。 ? 8 标准模式下可以达到双向数据传输速率 100 kbit/s,在快速模式下可达 400 kbit/s, 在高速模式下可以达到 3.4 Mbit/s。 ③ I 2 C 总线相关术语 I 2 C 无论是微控制器,总线上的每个设备LCD 驱动器、存储器或键盘接口都是唯一的 识别一个地址,每个设备都可以用作发送器或接收器(由设备的功能决定)。 LCD 驱动器只能用作接收器,存储器可以用作接收器或发送器。 I 2 C 除了发送器和接收器这两个概念外,总线还有主机和从机的概念。 所谓主机,是一种初始化总线数据传输并产生允许传输的时钟信号的装置。从机是所有被主 机器所寻址的设备。 4-2-2 对 I 2 C 总结了总线的相关术语。
(2)I 2 C 通信的起始和停止条件 在 I 2 C 在通信过程中,起始条件和停止条件是两种特殊的电平状态,如图所示 4-2-3 所示。
从图4-2-3 可以看出,起始条件被定义为当SCL 高电平时,SDA 从高电平到低电平切换, 停止条件定义为当SCL 高电平时,SDA 它们通常由主机从低电平到高电平切换。
(3)I2C 总线的搜索和数据传输方向的表示 I 2 C 总线上的每个设备都有一个独立的地址。主机发起通信时,通过 SDA 发送设备地址 (slave address)从机寻址。 I 2 C 根据总线的通信协议,设备地址有两种形式,其长度 度分别为 7 bit 与 10 bit,前者广泛应用于工程实践中。 设备地址后面的数据位(第 8 位或第 10 位)用于表示数据的传输方向,我们通常称之为 数据方向位(R/ W )。当数据方向为1时, 主机从机器中读取数据;当数据方向为0时, 主机将数据写入从机器从机器地址和数据传输方向 表示如图 4-2-4 所示。
(4)I2C 通信数据的有效性 在 I 2 C 通信中,SDA 用于数据传输,SCL 用 数据同步,SDA 在 SCL 每个时钟周期传输 1 位数据。 SCL 高电平时,SDA 上述数据有效(高电平表示数据1 表示数据0); SCL 低电平时,SDA 数据无效,此时一般选择实际应用 行 SDA 电平切换,然后为下一个数据传输做好准备。 4-2-5 展示上述过程。
(5)I2C 通信的响应 I2C 总线上的数据通信必须有响应。响应时钟脉冲(如图所示 4-2-6 所示的I2C 通信响应第一 9 个时钟脉冲)由主机产生,在此脉冲期间发送器释放 SDA,保持高电平状态。
响应包括响应信号(ACK)和非响应信号(NACK)两个。对于接收器,当它收到一个字时 如果发送器希望在节数据后继续发送数据,则需要回复响应信号(ACK),发送器收到响应信号 下一个数据将继续发送。如果接收器希望在接收到字节数据后结束数据传输,则需要返回 复非响应信号(NACK),这样,发送器在收到信号后就会产生停止条件,结束数据传输。 (6)I2C 总线的数据传输顺序和格式 ① 完整的数据传输顺序 I2C 总线的数据传输遵循图 4-2-7 所示时序。
主机产生起始条件(S)发送从机地址(7bit)和数据方向位(R/ W ),然后释放 SDA, 等待从机响应。 接到从机响应后,主机开始传输每个字节的数据(8bit)数据必须等待从机 答信号。 数据传输由主机产生停止条件(P)而终止。如果主机仍希望继续占用总线,它可以直接产 重复起始条件(Sr)在不产生停止条件的情况下,找到另一个从机。I2C总线数据传输 格式有不同的阅读/写作格式组合,见后续介绍。 ② 主机将数据格式传输到从机 主机向从机传输数据的格式用于将数据传输到从机(接收器)的应用场合, 具体数据格式如图所示 4-2-8 所示。主机在 I2C总线上广播地址收到从机 响应信号后,数据开始正式传输到从机(图) 4-2-8 中的 DATA),数据包的大小为 8 位,主机每 发送字节数据后,等待从机响应信号(图 4-2-8 中的 A)。 通过重复上述数据传输过程,主机可以从机器传输 n 个字节数据, n 没有大小限制 传输后,主机产生停止条件(P),数据不再传输
③ 主机从机器中读取数据的格式 主机自从机中读取数据的格式用于主机自从机中读取数据的应用。具体的数据格式,如
图 4-2-9 所示。主机在I2C总线广播地址收到从机响应信号后,从机开始向主机传输数量 据。此时主机(发送器)转变为从机(接收器),相应地从机(接收器)转变为主机(发送器)。 每次从机发送字节数据时,都会等待主机的响应信号。 通过重复上述数据传输过程,从机可以传输到主机 n 个字节数据, n 没有大小限制 当机器想停止接收数据时,首先将非响应信号返回到从机(NACK),然后产生停止条件(P), 表示数据传输结束。
④ 主机和从机通信的复合数据格式 在实际应用中,I2C通信数据的传输方向不是一成不变的,所以通信数据被更多地使用 图 4-2-10 复合数据格式显示。
接下来以 MCU(主机)读写串行接口存储器(从机)为例 4-2-10 中复合数据格式 介绍所涉及的通信过程。 主机产生起始条件(S)之后,广播从机地址和数据传输方向明确为写入( W )”。收到从 机的应答信号后,主机向从机传输数据,这段数据为“要操作的存储器地址”,数据长度不限。 由于I2C总线下一个数据传输方向发生了变化,因此主机产生了重复启动条件(Sr)重新寻找从 机器明确数据传输方向为读取(R)。主机收到从机器的响应信号后,从机器中读取数字 数据长度不限(指定存储地址的内容)。 当主机想停止接收数据时,首先返回从机的非响应信号(NACK),然后产生停止 条件(P),表示数据传输结束。 2.BH1750 环境光强传感器 (1)BH1750 概述 BH1750 是 ROHM 基于半导体公司生产的半导体公司 I 2 C 接口的数字环境光强传感器。 产品可根据 BH1750 调整收集的环境光强度数据 LCD 以及键盘背光灯的亮度。BH1750 具有 以下特点: ① 使用 I2C总线接口(支持全速和高速模式); ② 具有接近人眼视觉灵敏度的光谱灵敏度特征; ③ 可直接输出对应光照强度的数字值; ④ 输入光范围宽(1)~65535lx)而且分辨率高; ⑤ 光源依赖性弱(可支持白炽灯、荧光灯、卤素灯、白光 LED 和日光灯等); ⑥ 有两种选择I2C从机地址; ⑦ 红外线的影响很小。 BH1750 主要电气特性如表 4-2-3 所示:
(2)BH1750 的测量流程 BH1750 测量过程如图所示 4-2-11 所,当 V CC 和 DVI 供电后的初始状态为断电模式时发送 “上电指令”,传感器切换为上电模式,此时可发送不同的测量指令使传感器完成诸如“单次测量” 或“连续测量”等操作。
(3)BH1750 的控制指令集 BH1750 的主要控制指令及其说明如表 4-2-4 所示。
(4)BH1750 应用实例 下面通过两个实例来说明主机控制 BH1750 运行在不同模式下的工作流程,流程涵盖“写入 指令”到“读取测量结果”中间的各个环节。 ① 连续高分辨率模式(ADDR 地址线为低电平) 图 4-2-12 展示了主机控制 BH1750 运行在连续高分辨率模式下的工作流程。
通过前面的学习我们知道,每个连接到 I 2 C 总线的设备都有一个独立的地址,主机利用 地址进行不同设备的访问控制。另外,BH1750 具有两种可选的 I 2 C 从机地址:当 BH1750 的 ADDR 地址线为低电平时,使用从机地址“0100011”;当 BH1750 的 ADDR 地址线为高 电平时,使用从机地址“1011100”。在图 4-2-12 展示的实例中,BH1750 的 ADDR 地址线 为低电平。 主机控制 BH1750 运行在连续高分辨率模式下的工作流程如下: 主机发送从机地址“0100011”; 主机发送“连续高分辨率模式”指令(指令代码:0x10); 主机等待 BH1750 测量完毕(测量时间典型值 120ms,最大值 180ms); 主机读取测量结果(测量结果为 16 位,每次传输 8 位,分两次传输完毕)。 假设主机读取到的高 8 位值 为“10000011”,低 8 位值 为“10010000”,则光照强度值 result 的计算方法如下: 注:上式中将读取到的测量值除以“1.2”后得到最终的光照强度值,其中除数“1.2”通过 查表 4-2-3 可得。 ② 单次低分辨率模式(ADDR 地址线为高电平) 图 4-2-13 展示了主机控制 BH1750 运行在单次低分辨率模式下的工作流程,此实例中 BH1750 的 ADDR 地址线为高电平。
主机控制 BH1750 运行在单次低分辨率模式下的工作流程如下: 主机发送从机地址“1011100”; 主机发送“单次低分辨率模式”指令(指令代码:0x23); 主机等待 BH1750 测量完毕(测量时间典型值 16ms,最大值 24ms); 主机读取测量结果(测量结果为 16 位,每次传输 8 位,分两次传输完毕)。 假设主机读取到的高 8 位值 为“00000001”,低 8 位值 为“00010000”,则光照强度值 result 的计算方法如下:
4.2.3 任务实施 1.硬件连接 参照接线表 4-2-1 将 BH1750 光照强度传感器模块与 STM32F4 系列微控制器相连。 2.编写I²C总线基本通信程序 在“HARDWARE”文件夹下新建“I²C”子文件夹,新建“bsp_iic.c”和“bsp_iic.h”两个 文件,将它们加入工程中,并配置头文件包含路径。在“bsp_iic.c”文件中编写 I²C总线的基本 通信程序,这部分程序的函数清单如表 4-2-5 所示
在“bsp_iic.c”文件中输入以下代码:
#include "bsp_iic.h"
#include "delay.h"
/**
* @brief I 2 C 总线 SCL 和 SDA 引脚初始化
* @param None
* @retval None
*/
void IIC_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(IIC_SCL_CLK | IIC_SDA_CLK,ENABLE);
// IIC_SCL:PB6 | IIC_SDA:PB7
GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(IIC_SCL_PORT, &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;
GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);
}
/**
* @brief 将 SDA 引脚配置为输入
* @param None
* @retval None
*/
void SDA_IN(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN; //PB7 配置为输入
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);
}
/**
* @brief 将 SDA 引脚配置为输出
* @param None
* @retval None
*/
void SDA_OUT(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN; //PB7 配置为输出
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(IIC_SDA_PORT, &GPIO_InitStructure);
}
/**
* @brief 主机产生起始条件 (S)
* @param None
* @retval None
*/
void IIC_Start(void)
{
SDA_OUT();
IIC_SDA=1;
IIC_SCL=1;
delay_us(4);
IIC_SDA=0; //START:when CLK is high , DATA change form high to low
delay_us(4);
IIC_SCL=0; // 钳住 I 2 C 总线,准备发送或接收数据
}
/**
* @brief 主机产生停止条件 (P)
* @param None
* @retval None
*/
void IIC_Stop()
{
SDA_OUT();
IIC_SDA=0; //STOP:when CLK is high , DATA change form low to high
IIC_SCL=1;
delay_us(4);
IIC_SDA=1; // 发送 I 2 C 总线结束信号
delay_us(4);
}
/**
* @brief 主机发送响应 ACK 或 NACK
* @param ack: 要发送的应答信号, 1 对应 NACK , 0 对应 ACK
* @retval None
*/
void IIC_SendACK(uint8_t ack)
{
SDA_OUT();
if(ack) IIC_SDA=1; // 写应答信号
else IIC_SDA=0;
IIC_SCL=1; // 拉高时钟线
delay_us(2); // 延时
IIC_SCL=0; // 拉低时钟线
delay_us(2); // 延时
}
/**
* @brief 主机读取响应信号
* @param None
* @retval data: 返回的响应信号
*/
uint8_t IIC_RecvACK(void)
{
uint8_t data;
SDA_IN(); //SDA 设置为输入
IIC_SCL=1; // 拉高时钟线
delay_us(2); // 延时
data = READ_SDA; // 读应答信号
IIC_SCL=0; // 拉低时钟线
delay_us(2); // 延时
return data;
}
/**
* @brief I 2 C 发送一个字节
* @param data :要发送的数据
* @retval None
*/
void IIC_SendByte(uint8_t data)
{
uint8_t i,bit;
SDA_OUT(); //SDA 输出
for (i=0; i<8; i++) //8 位计数器
{
bit=data&0x80;
if(bit) IIC_SDA=1;
else IIC_SDA=0;
data <<= 1; // 移出数据的最高位
IIC_SCL=1; // 拉高时钟线
delay_us(2);
IIC_SCL=0;
delay_us(2);
}
IIC_RecvACK();
}
/**
* @brief I 2 C 总线接收一个字节的数据
* @param None
* @retval 接收到的数据
*/
uint8_t IIC_RecvByte(void)
{
uint8_t i, data = 0;
SDA_IN(); //SDA 设置为输入
IIC_SDA=1; // 使能内部上拉,准备读取数据
for (i=0; i<8; i++)
{
data <<= 1;
IIC_SCL=1; // 拉高时钟线
delay_us(2); // 延时
if(READ_SDA)
data+=1;
IIC_SCL=0; // 拉低时钟线
delay_us(2); // 延时
}
return data;
}
在“bsp_iic.h”文件中输入以下代码:
#ifndef __BSP_IIC_H
#define __BSP_IIC_H
#include "sys.h"
#define IIC_SCL_CLK RCC_AHB1Periph_GPIOB
#define IIC_SCL_PORT GPIOB
#define IIC_SCL_PIN GPIO_Pin_6
#define IIC_SDA_CLK RCC_AHB1Periph_GPIOB
#define IIC_SDA_PORT GPIOB
#define IIC_SDA_PIN GPIO_Pin_7
#define IIC_SCL PBout(6)
#define IIC_SDA PBout(7)
#define READ_SDA PBin(7)
void IIC_Init(void); //I 2 C 相关引脚初始化
void SDA_IN(void); // 设置 SDA 引脚为输入
void SDA_OUT(void); // 设置 SDA 引脚为输出
void IIC_Start(void); // 产生起始条件
void IIC_Stop(void); // 产生停止条件
void IIC_SendACK(uint8_t ack); // 发送响应,应答( ACK )或者非应答( NACK )
uint8_t IIC_RecvACK(void); // 接收响应
void IIC_SendByte(uint8_t data); //I 2 C 发送一个字节的数据
uint8_t IIC_RecvByte(void); //I 2 C 接收数据
#endif
3.编写与 BH1750 传感器读写控制相关的程序 在“HARDWARE”文件夹下新建“BH1750”子文件夹,新建“bh1750.c”和“bh1750.h” 两个文件,将它们加入工程中,并配置头文件包含路径。在“bh1750.c”文件中编写与 BH1750 传感器读写控制相关的程序,输入以下代码:
#include "bsp_iic.h"
#include "bh1750.h"
#include "delay.h"
uint8_t BUF[3]; // 接收数据缓存区
/**
* @brief 向 BH1750 写入指令
* @param Command :需要写入的指令
* @retval None
*/
void BH1750_Write_Command(uint8_t Command)
{
IIC_Start(); // 产生起始条件
IIC_SendByte(SlaveAddress); // 发送设备地址 + 写信号
IIC_SendByte(Command); // 发送指令
IIC_Stop(); // 产生停止条件
}
/**
* @brief 从 BH1750 中连续读取数据
* @param None
* @retval None
*/
void BH1750_Multiple_Read(void)
{
uint8_t i;
IIC_Start(); // 产生起始条件
IIC_SendByte(SlaveAddress + 1); // 发送设备地址 + 读信号
for (i=0; i<2; i++) // 连续读取 2 个字节数据,存储在 BUF
{
BUF[i] = IIC_RecvByte(); //BUF[0] 存储高 8 位的数据
if (i == 1) //BUF[1] 存储低 8 位的数据
IIC_SendACK(1); // 最后一个数据需要回应 NACK
else
IIC_SendACK(0); // 回应 ACK
}
IIC_Stop(); // 产生停止条件
delay_ms(150);
}
/**
* @brief BH1750 初始化 (ADDR 地址线,进入通电模式 )
* @param None
* @retval None
*/
void BH1750_Init()
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(BH1750_ADDR_CLK,ENABLE);
GPIO_InitStructure.GPIO_Pin = BH1750_ADDR_PIN; //ADDR 线 GPIO 初始化
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(BH1750_ADDR_PORT,&GPIO_InitStructure);
BH1750_Write_Command(BH1750_CMD_PowOn); // 使 BH1750 进入通电状态
ADDR = 0; // 将 ADDR 线初始化拉低
}
/**
* @brief BH1750 测量一次光照强度
* @param None
* @retval 16bit 测量的光照强度值
*/
uint16_t BH1750_Measure(void)
{
float temp;
uint16_t light_ret, light_origin; // 测量的原始光照值
BH1750_Write_Command(BH1750_CMD_PowOn); //Power on
BH1750_Write_Command(BH1750_CMD_ModeH1); //H-Resolution Mode1
delay_ms(200); // 等待 200ms 后读取结果
BH1750_Multiple_Read(); // 连续读出数据,存储在 BUF 中
light_origin = BUF[0];
light_origin = (light_origin<<8) + BUF[1]; // 合成数据,即光照数据
temp = (double)light_origin / 1.2;
light_ret = (int)temp;
return light_ret;
}
在“bh1750.h”文件中输入以下代码:
#ifndef __BH1750_H
#define __BH1750_H
#include "sys.h"
#define BH1750_ADDR_CLK RCC_AHB1Periph_GPIOG
#define BH1750_ADDR_PORT GPIOG
#define BH1750_ADDR_PIN GPIO_Pin_15
#define ADDR PGout(15) //ADDR 引脚 :PG15
#define SlaveAddress 0x46 //BH1750 从机地址, ADDR 接低电平
//#define SlaveAddress 0xB8 //BH1750 从机地址, ADDR 接高电平
/*************************************************************/
//BH1750 Instruction Set 指令集定义
#define BH1750_CMD_AddWrite 0x46 // 从机地址 + 写方向位
#define BH1750_CMD_AddRead 0x47 // 从机地址 + 读方向位
#define BH1750_CMD_PowDown 0x00 // 关闭模块
#define BH1750_CMD_PowOn 0x01 // 打开模块等待测量指令
#define BH1750_CMD_Reset 0x07 // 重置数据寄存器值 在 PowerOn 模式下有效
#define BH1750_CMD_ModeH1 0x10 // 高分辨率模式 1 ,分辨率 1lx ,测量时间 120ms
#define BH1750_CMD_ModeH2 0x11 // 高分辨率模式 2 ,分辨率 0.5lx ,测量时间 120ms
#define BH1750_CMD_ModeL 0x13 // 低分辨率,分辨率 4lx ,测量时间 16ms
#define BH1750_CMD_SigModeH 0x20 // 一次高分辨率测量
#define BH1750_CMD_SigModeH 20x21 // 同上类似
#define BH1750_CMD_SigModeL 0x23 // 同上类似
/*************************************************************/
void BH1750_Init(void); //BH1750 初始化
void BH1750_Write_Command(uint8_t Command); //BH1750 写入指令
void BH1750_Multiple_Read(void); // 连续的读取内部寄存器数据
uint16_t BH1750_Measure(void); //BH1750 测量一次光照强度
#endif
4.编写 main()函数 在“main.c”文件中输入以下代码:
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "bsp_iic.h"
#include "bh1750.h"
uint16_t bh1750Light = 0; // 采集的光照值,单位 lx
char myString[50] = {0};
int main(void)
{
delay_init(168); // 延时函数初始化
LED_Init(); //LED 端口初始化
USART1_Init(115200); //USART1 初始化
IIC_Init(); //I 2 C 总线初始化
printf("System Started!\r\n");
while(1)
{
bh1750Light = BH1750_Measure();
sprintf(myString," 光照强度值为: %d \r\n",bh1750Light);
printf("%s",myString);
delay_ms(1000);
}
}
5.观察试验现象 应用程序编译无误后,下载至开发板运行。打开上位机的串口调试助手,设置好波特率等 参数,即可在接收窗口中看到 STM32F4 系列微控制器上传的光照强度测量值,如图 4-2-14 所示。