传感器学习周记-三大传感器实验深度解析
传感器课程学习周记:三大传感器实验深度解析
本周的传感器原理与应用技术课程,我完成了三个经典的传感器实验——光强检测、可燃气体检测和光电开关检测。从I2C数字通信到GPIO模拟输入,从半导体气敏原理到红外光电检测,每一个实验都让我对"感知层是物联网的眼睛和皮肤"这句话有了更深刻的理解。
一、光强传感器实验(BH1750)
实验目标
第一个实验是基于BH1750FVI数字光强传感器的环境光照检测。核心目标是掌握I2C总线通信原理,学会通过软件I2C(GPIO模拟)读取传感器数据,并理解照度(lux)的换算方法。
核心原理
BH1750传感器概述:这是ROHM公司生产的数字型环境光传感器IC,采用I2C总线接口,测量范围1-65535 lx。内部集成了光电二极管、积分运放、16位ADC和I2C接口,光谱响应曲线接近人眼,峰值波长560nm。
照度计算公式:
Illuminance(lx) = raw_data / 1.2
高分辨率模式下,16位原始ADC值除以1.2即可得到勒克斯值。这个1.2的系数来自数据手册的Measurement Accuracy典型值。
软件I2C通信:实验没有使用STM32的硬件I2C外设,而是用GPIO模拟I2C时序——SCL接PB13,SDA接PB14。SDA引脚通过宏动态切换输入/输出模式,这是半双工通信的关键技巧。通信时序包括:
- Start信号:SCL高电平时SDA下降沿
- Stop信号:SCL高电平时SDA上升沿
- 数据传输:SCL高电平期间采样SDA,MSB先发
- ACK应答:每8位数据后等待从机拉低SDA
I2C从机地址:BH1750支持两种地址,由ADDR引脚电平决定。本实验使用ADDR=LOW对应的0x23(7位地址),写地址为0x46(0x23<<1 | 0),读地址为0x47(0x23<<1 | 1)。
关键代码实现
指令集与地址定义:
// bh1750.c 第38-45行
#define BH1750_ADD_WRITE 0x46 // 写数据(地址0x23<<1 | 0)
#define BH1750_ADD_READ 0x47 // 读数据(地址0x23<<1 | 1)
#define BH1750_POWER_ON 0x01 // 打开模块
#define BH1750_POWER_DOWN 0x00 // 关闭模块
#define BH1750_MODE_H1 0x10 // 高分辨率1,单位1lx
#define BH1750_MODE_H2 0x11 // 高分辨率2,单位0.5lx
#define BH1750_MODE_L 0x13 // 低分辨率,单位4lx
GPIO初始化:
// bh1750.c 第105-126行
static void BH1750_GPIO_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
// PB13(SCL) + PB14(SDA) 配置为推挽输出+上拉
GPIO_InitStruct.Pin = BH1750_SCL_Pin | BH1750_SDA_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(BH1750_SCL_GPIO_Port, &GPIO_InitStruct);
}
SDA方向动态切换宏:
#define BH1750_SDA_IN() {GPIOB->MODER&=~(3<<(14*2)); GPIOB->MODER|=0<<14*2;}
#define BH1750_SDA_OUT() {GPIOB->MODER&=~(3<<(14*2)); GPIOB->MODER|=1<<14*2;}
发送指令(写报文):
// bh1750.c 第310-318行
static void BH1750_Write_Command(uint8_t cmd) {
BH1750_IIC_Start(); // ST: 起始信号
BH1750_Write_Byte(BH1750_ADD_WRITE); // 0100011 + 0 = 0x46(写)
BH1750_IIC_Wait_Ack(); // Ack: 等待应答
BH1750_Write_Byte(cmd); // 指令码
BH1750_IIC_Wait_Ack(); // Ack: 等待应答
BH1750_IIC_Stop(); // SP: 停止信号
}
对应数据手册报文格式:ST | 0100011 | 0(W) | Ack | 00010000 | Ack | SP
读取数据(读报文):
// bh1750.c 第355-369行
static uint16_t Read_BH1750(void) {
uint16_t light_buf = 0;
BH1750_IIC_Start(); // ST: 起始信号
BH1750_Write_Byte(BH1750_ADD_READ); // 0100011 + 1 = 0x47(读)
BH1750_IIC_Wait_Ack(); // Ack: 等待应答
light_buf = BH1750_Read_Byte(ACK); // High Byte + 发送ACK
light_buf <<= 8; // 左移8位
light_buf |= BH1750_Read_Byte(NOACK); // Low Byte + 发送NACK
BH1750_IIC_Stop(); // SP: 停止信号
return light_buf; // 返回16位原始数据
}
对应数据手册报文格式:ST | 0100011 | 1(R) | Ack | High Byte | Ack | Low Byte | NACK | SP
完整测量流程:
uint16_t BH1750_Handle(void) {
uint16_t result_lx = 0;
Start_BH1750(); // 发送上电(0x01) + 高分辨率模式(0x10)
delay_ms(180); // 等待最大180ms确保测量完成
result_lx = Read_BH1750(); // 读取16位原始数据
result_lx = (float)(result_lx / 1.2); // 照度换算
return result_lx;
}
180ms取自数据手册H-Resolution Mode测量时间的最大值(典型120ms,最大180ms),用最大值作为等待时间确保最坏情况下测量也能完成。
三种场景实测结果
| 场景 | 光强范围(lx) | 稳定值(lx) | 波动范围 |
|---|---|---|---|
| 正常环境光 | 469 - 470 | 470 | ±1 lx |
| 模拟强光 | 2570 - 6868 | 6864 | ±8 lx |
| 模拟暗光 | 7 - 477 | 25 | ±0 lx |
正常室内日光灯环境下读数约470 lx,符合300-500 lx的室内照明标准;手机闪光灯照射下达到6864 lx,远未达到65535 lx的量程上限;用手遮挡时最低可达7 lx,验证了高分辨率模式的低光灵敏度。三种场景下读数波动均小于1%,稳定性优于数据手册标称的±20%。
二、气体传感器实验(MQ-5)
实验目标
第二个实验是基于MQ-5的可燃气体检测。目标是理解半导体气敏元件的工作机制,掌握LM393比较器在信号调理中的作用,以及STM32 GPIO输入检测的基本操作。
核心原理
MQ-5工作原理:传感器采用SnO2(二氧化锡)半导体气敏材料。内部加热丝将材料加热至约300°C工作温度。清洁空气中SnO2阻值较高;当环境中存在可燃气体(LPG、甲烷、一氧化碳等)时,气体分子在材料表面发生化学吸附,电子浓度增加,阻值下降。气体浓度越高,阻值下降越明显。
五级信号处理链路:
- 气体感知:SnO2气敏电阻感知可燃气体浓度,阻值变化
- 信号转换:气敏电阻+固定电阻组成分压电路,阻值→电压(AO模拟量)
- 阈值比较:LM393APSR比较器将AO与电位器参考电压比较
- MCU读取:STM32通过PD15引脚读取数字输出(DO)
- 信息输出:LCD显示 + 串口输出
LM393比较器逻辑:
- INA+(正相输入,第3脚)接MQ-5的AO模拟电压
- INA-(反相输入,第2脚)接电位器设定的阈值参考电压
- OUTA(第1脚)输出至STM32的PD15引脚
比较器工作逻辑:
- 当INA+ > INA-(无气体/浓度低)→ 输出高电平(1)→ 安全
- 当INA+ < INA-(有气体/浓度高)→ 输出低电平(0)→ 报警
电位器旋钮可以调节阈值大小,从而调整传感器灵敏度。
关键代码实现
引脚定义与初始化:
// mq_5.c
#define MQ5_Pin GPIO_PIN_15 // PD15引脚
#define MQ5_GPIO_Port GPIOD // GPIOD端口
#define MQ5_STATE() HAL_GPIO_ReadPin(MQ5_GPIO_Port, MQ5_Pin)
void MQ5_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOD_CLK_ENABLE(); // 使能GPIOD时钟
GPIO_InitStruct.Pin = MQ5_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉电阻
HAL_GPIO_Init(MQ5_GPIO_Port, &GPIO_InitStruct);
}
状态读取函数:
uint8_t MQ5_Read_State(void) {
return MQ5_STATE(); // 返回: 1=无可燃气体, 0=有可燃气体
}
业务处理函数:
// handle.c
void MQ5_Handle(void) {
uint8_t mq5_detect = 0;
static uint8_t mq2_detect_rev = 3; // 状态缓存,初始值3(非0非1)
static uint32_t tickstart;
if((HAL_GetTick() - tickstart) >= 1000) { // 每1秒检测一次
tickstart = HAL_GetTick();
if(MQ5_Read_State() == 0) { // PD15低电平 → 有可燃气体
LCD_draw_bmp(96, 120, 48, gImage_yanwu); // 显示有气体图标
Show_Str(85, 178, BLUE, WHITE, "有可燃气体", 16, 1);
} else { // PD15高电平 → 无可燃气体
LCD_draw_bmp(96, 120, 48, gImage_yanwu_hui); // 灰色图标
Show_Str(85, 178, BLUE, WHITE, "无可燃气体", 16, 1);
}
mq5_detect = MQ5_Read_State();
// 仅在状态变化时串口输出,避免刷屏
if((!mq5_detect) != mq2_detect_rev) {
printf("** 杨斌 combustible gas detect:%d\r\n", !mq5_detect);
mq2_detect_rev = !mq5_detect;
}
led_toggle(LED1_PORT); // LED1翻转指示运行
}
}
printf串口重定向:
// usart.c
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
return ch;
}
重定向fputc后,printf输出的每个字符都会通过USART2(PA2/PA3,波特率115200)发送出去。
主函数流程:
int main(void) {
HAL_Init();
SystemClock_Config();
LCD_Display_Init(); // 屏幕初始化
gpio_module_open(LEDS); // LED引脚初始化
uart_open(uart2, 115200); // 串口初始化
printf("\r\n 可燃气体检测实验 \r\n");
printf("杨斌 开始本实验:\r\n\r\n");
MQ5_Init(); // MQ-5传感器初始化(PD15)
while(1) {
MQ5_Handle(); // 主循环检测
}
}
三、光电开关传感器实验(E18-D80NK)
实验目标
第三个实验是基于E18-D80NK的红外光电开关检测。目标是掌握光电开关模块的电路原理,理解开漏输出与上拉电阻的配合关系,以及STM32 GPIO输入检测的编程方法。
核心原理
三线制接线:
| JP7引脚 | 连接到 | 线色 | 功能 |
|---|---|---|---|
| Pin 1 | +5V | 棕色 | 电源正极 |
| Pin 2 | PB0 | 黑色 | 信号输出 |
| Pin 3 | GND | 蓝色 | 地 |
Pin2到+5V之间接有10K上拉电阻。
开漏输出与上拉电阻:这是本实验最重要的知识点。E18-D80NK的输出为开漏(Open Collector)结构,自身无法输出高电平,必须外接10K上拉电阻将信号线拉高到5V。MCU内部也配置了GPIO_PULLUP上拉模式,双重保障信号稳定性。
数据手册原文:“OUTPUT is Open Collector; it has to connect R 10K Pull Up at Output”
距离-输出逻辑:
- 距离 ≤ 检测距离(有遮挡)→ LED ON → OUTPUT = 0(低电平)
- 距离 ≥ 检测距离(无遮挡)→ LED OFF → OUTPUT = 1(高电平)
传感器利用红外反射原理检测物体,检测距离可通过尾部电位器调节(3-80cm范围)。
关键代码实现
驱动层完整代码:
// e18_d80nk.c
/* IO引脚宏定义 */
#define E18_D80NK_Pin GPIO_PIN_0
#define E18_D80NK_GPIO_Port GPIOB
#define E18_D80NK_STATE() HAL_GPIO_ReadPin(E18_D80NK_GPIO_Port, E18_D80NK_Pin)
/* 初始化函数 - 配置PB0为上拉输入模式 */
void E18_D80NK_Init(void) {
GPIO_InitTypeDef GPIO_InitStruct;
E18_D80NK_Port_Clk_Enable; // 使能GPIOB时钟
GPIO_InitStruct.Pin = E18_D80NK_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT; // 输入模式
GPIO_InitStruct.Pull = GPIO_PULLUP; // 上拉(匹配开漏输出)
HAL_GPIO_Init(E18_D80NK_GPIO_Port, &GPIO_InitStruct);
}
/* 读取传感器状态 - 返回0(有遮挡)或1(无遮挡) */
uint8_t E18_D80NK_Read_State(void) {
return E18_D80NK_STATE();
}
业务处理函数:
// handle.c
void Infrared_Switch_Handle(void) {
uint8_t e18_d80nk_state = 0;
static uint8_t e18_d80nk_state_rev = 2; // 状态缓存,初始值2(非0非1)
static uint32_t tickstart;
if((HAL_GetTick() - tickstart) >= 1000) { // 1000ms检测一次
tickstart = HAL_GetTick();
e18_d80nk_state = E18_D80NK_Read_State(); // 读取传感器状态
if(e18_d80nk_state == 0) { // 有遮挡
LCD_draw_bmp(96, 120, 48, gImage_guangdian); // 遮挡图标
Show_Str(96, 178, BLUE, WHITE, "有遮挡", 16, 1);
beep_open(); // 蜂鸣器报警
} else { // 无遮挡
LCD_draw_bmp(96, 120, 48, gImage_guangdianhui); // 无遮挡图标
Show_Str(96, 178, BLUE, WHITE, "无遮挡", 16, 1);
beep_close(); // 关闭蜂鸣器
}
/* 状态变化时串口打印,避免刷屏 */
if(e18_d80nk_state != e18_d80nk_state_rev) {
printf("** 读取光电开关传感器数据\r\n");
if(e18_d80nk_state == 0)
printf("** 状态:有遮挡\r\n\r\n");
else
printf("** 状态:无遮挡\r\n\r\n");
e18_d80nk_state_rev = e18_d80nk_state; // 更新缓存
}
led_toggle(LED1_PORT); // LED1闪烁指示运行
}
}
状态变化检测机制是这段代码的亮点——用静态变量缓存上次状态,初始值设为2(既不是0也不是1),确保第一次检测时必然触发状态变化输出。后续只有状态真正改变时才打印,完美解决了刷屏问题。
四、学习收获与总结
通信方式对比
三个实验分别使用了不同的通信/读取方式,正好可以对比学习:
| 传感器 | 通信方式 | 数据类型 | 引脚 | 特点 |
|---|---|---|---|---|
| BH1750 | 软件I2C | 数字量(16位) | PB13/PB14 | 协议复杂,数据精度高 |
| MQ-5 | GPIO输入 | 数字量(1位) | PD15 | 简单直接,仅能判断阈值 |
| E18-D80NK | GPIO输入 | 数字量(1位) | PB0 | 开漏输出,需上拉电阻 |
技术收获
-
I2C协议深度理解:通过软件I2C的实现,真正搞懂了Start/Stop时序、ACK/NACK应答、SDA方向切换等底层细节,比直接用硬件I2C库函数收获大得多。
-
数据手册阅读能力:BH1750实验中对照数据手册逐一验证了指令集、I2C地址、报文格式、照度公式、测量时间等,学会了"从数据手册找依据"的工程方法。
-
传感器信号调理链路:MQ-5实验展示了完整的信号链——从物理量(气体浓度)→ 化学效应(阻值变化)→ 电路转换(分压电压)→ 比较器(数字阈值)→ MCU读取。这是模拟传感器数字化的典型路径。
-
开漏输出的工程意义:E18-D80NK的开漏输出设计让我理解了为什么工业传感器常用这种接口——可以实现电平转换(5V传感器→3.3V MCU)、线与逻辑、以及更高的抗干扰能力。
-
分层架构与代码复用:三个实验都采用了驱动层+业务层的分层设计,结构清晰。状态变化检测、定时采样、LCD局部刷新等模式在三个实验中反复出现,体现了嵌入式开发的通用范式。
遇到的问题
- 光强传感器:串口显示470 lx但LCD显示210 lx,排查后发现是拍LCD照片时手机遮挡了传感器,属于操作层面的问题,也提醒了实验数据采集时要避免人为干扰。
- 气体传感器:上电初期1-2分钟输出不稳定,这是半导体气敏传感器的预热特性,通过状态变化检测机制过滤掉了无效输出。另外还遇到了串口中文乱码问题,通过将源文件保存为UTF-8编码解决。
- 光电开关:原代码直接打印数字0/1可读性差,修改为if-else判断输出中文"有遮挡/无遮挡",调试体验大幅提升。
五、扩展思考
应用场景拓展
-
智能照明系统:BH1750 + PWM调光LED,实现根据环境光自动调节室内亮度,节能效果显著。代码中已有LED控制函数,只需增加PWM输出和光强阈值判断即可实现闭环控制。
-
厨房燃气安全联动:MQ-5 + 继电器 + 燃气电磁阀,检测到泄漏时自动切断气源并联动排风扇。继电器控制只需调用HAL_GPIO_WritePin,与现有LED控制逻辑完全相同。
-
智慧车位检测:E18-D80NK朝下安装在每个车位上方,检测车辆是否停放。通过ESP8266 Wi-Fi模块上报到云平台,实现停车场车位引导。
-
流水线产品计数:光电开关安装在传送带两侧,产品经过时计数。检测到"无遮挡→有遮挡"的边沿时count++,还能通过两次遮挡间隔时间判断卡料异常。
安全与可靠性改进
-
传感器健康自诊断:MQ-5加热丝长期使用会老化,可通过ADC检测加热丝工作电流,偏差超过20%时提示维护。
-
多传感器融合:将光强、气体、光电开关三种传感器数据融合,实现更智能的环境监测——比如有人遮挡且光线变暗时自动开灯。
-
低功耗优化:利用STM32L4的STOP模式,RTC定时唤醒采样,将待机功耗降至微安级,支持电池长期运行。
更多推荐

所有评论(0)