项目介绍:
项目选题:方向一:边缘智能、智能设备。细分方向是影音,然后是测光表方向。
该项目使用了树莓派PICO 2,实现了简易电子测光笔的设计,它的主要功能为:根据光照传感器BH1750计算出曝光值EV,然后根据光圈F和感光值ISO计算出快门时间T。所有的结果都动态的展示在OLED屏幕上。
项目框图:
项目的简易系统框图如下所示。
项目软件流程图
项目细节:
这部分内容主要涉及屏幕OLED的驱动,光传感器BH1750的驱动,EV曝光值和快门速度计算等诸多细节。
OLED屏幕驱动:
项目上使用的是之前硬禾学堂的树莓派PICO扩展板。资料链接: https://www.eetree.cn/platform/584
从硬件原理图看到是spi接口:
在代码中,定义相关的引脚:
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI 11
#define OLED_CLK 10
#define OLED_DC 9
#define OLED_CS 16
#define OLED_RESET 8
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT,
OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
之后在setup函数中进行屏幕的初始化工作:
// SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
if(!display.begin(SSD1306_SWITCHCAPVCC)) {
Serial.println(F("SSD1306 allocation failed"));
for(;;); // Don't proceed, loop forever
}
实物效果:
BH1750光亮度传感器驱动
光传感器是实现电子测光表的重要元件,本项目上使用的是M5STACK公司的Unit DLight模块(资料链接: https://docs.m5stack.com/zh_CN/unit/DLight%20Unit)
Unit DLight 是一款 数字环境光检测传感器,硬件采用 BH1750FVI 照度传感器 IC ( I2C 接口 ),内置 16bit AD 转换支持 ( 1 ~ 65535 lx ) 照度值检测。具有体积小,功耗低等特点。适用于各种照度检测,光控调节场景。
产品特性:
- I2C通信接口 (addr: 0x23)
- 照度数字转换
- 光源依赖性小 (检测光源: 白炽灯,荧光灯,卤素灯,白光LED,太阳光均可)
- 检测范围 (1 ~ 65535 lx)
模块的电路原理图如下所示:
扩展板的I2C接口使用的是GPIO 14, GPIO 15:
连接图:
安装驱动DLight:
编译遇到错误:
后续也尝试使用PICO2的硬件I2C1驱动BH1750始终还是有问题,后来改为使用软件I2C:
#include <SoftI2C.h>
SoftI2C SoftWire = SoftI2C(14, 15); //sda, scl
之后修改M5Stack的BH1750驱动:
extern SoftI2C SoftWire;
/*! @brief Write data to the register address. */
void M5_DLight::writeByte(byte cmd) {
SoftWire.beginTransmission(_addr);
SoftWire.write(cmd);
SoftWire.endTransmission();
}
/*! @brief Write the specified length of data to the register.*/
void M5_DLight::writeBytes(uint8_t *buffer, size_t size) {
SoftWire.beginTransmission(_addr);
SoftWire.write(buffer, size);
SoftWire.endTransmission();
}
/*! @brief Read a certain length of data to the specified register address.*/
void M5_DLight::readBytes(uint8_t *buffer, size_t size) {
SoftWire.requestFrom(_addr, size);
for (uint16_t i = 0; i < size; i++) {
buffer[i] = SoftWire.read();
}
}
主要是使用SoftWire来代替原来的Wire或者Wire1。烧录,验证。串口输出了获取的LUX值。
计算曝光值等
关于曝光值(EV)、光圈(AV)、快门(TV)、感光度(SV)、照度(Lux)等变量的关系,可以参考如下文章:【超详细公式】
EV=AV+TV−SV
项目上在ISO 100情况下,SV = 0。并且根据如下公式可以根据LUX亮度值计算出曝光值:
主要思路是根据LUX计算EV曝光值,然后光圈AV是可以通过按键进行调整,T是根据公式进行计算出来的。感光值默认是ISO 100等级。
代码部分:
if (digitalRead(12) == LOW) {
F = 1.4;
boolBtnPressed = true;
} else if (digitalRead(13) == LOW) {
F = 2;
boolBtnPressed = true;
}
lux = sensor.getLUX();
EV = 2 + log2(sensor.getLUX() / 10);
T = pow(2, log2(F * F) - EV);
快门值除了使用上述的公式计算,还可以使用查表法快速的找到对应的快门值。本项目使用第二种方法。
const char *TV[] = { "1 ", "1/2 ", "1/4 ", "1/8 ", "1/15", "1/30","1/60", ".125", ".250", ".500", "1000"};
lux = sensor.getLUX();
EV = 2 + log2(sensor.getLUX() / 10);
//T = pow(2, log2(F * F) - EV);
Tq = EV - AV;
综合显示
项目最终把结果显示在OLED屏幕上:
lux = sensor.getLUX();
EV = 2 + log2(sensor.getLUX() / 10);
T = pow(2, log2(F * F) - EV);
sprintf(info, "LUX: %d", lux);
Serial.println(info);
sprintf(info1, "EV:%d ISO%d", EV, ISO);
Serial.print("EV Value: ");
Serial.println(2 + log2(sensor.getLUX() / 10));
Serial.println(info1);
sprintf(info2, "F:%.1f T%.2f", F, T);
Serial.println(info2);
display.clearDisplay();
display.setTextSize(1); // Normal 1:1 pixel scale
display.setFont(&FreeMono9pt7b);
display.setTextColor(SSD1306_WHITE); // Draw white text
display.setCursor(24, 12); // Start at top-left corner
display.println(F("M Design"));
display.setCursor(0, 28); // Start at top-left corner
display.println(info);
display.setCursor(0, 44); // Start at top-left corner
display.println(info1);
display.setCursor(0, 60); // Start at top-left corner
display.println(info2);
此外,为了增加项目的趣味性,还增加了WS2812灯带的效果,使用的是Adafruit NeoPixel库。结合FreeRTOS,首先定义一个Task专门用于WS2812。
TaskHandle_t blinkTask;
xTaskCreate(blink, "BLINK", 256, nullptr, 1, &blinkTask);
void blink(void *param) {
(void) param;
delay(500);
pinMode(LED_BUILTIN, OUTPUT);
while (true) {
digitalWrite(LED_BUILTIN, LOW);
delay(750);
digitalWrite(LED_BUILTIN, HIGH);
delay(250);
MyNeoPixel();
vTaskDelay(10*60*1000/portTICK_PERIOD_MS);
}
}
使用vTaskDelay函数可以在不影响FreeRTOS调度的情况下,灯带和测光表可以分别正常工作,互补影响。
实物展示:
通过按键可以调节光圈小,进行计算出来的快门速度也会实时变化。
WS2812灯带展示:
总结
本次项目使用到了树莓派MCU系列最新的PICO2,结合Arduino开发环境和BH1750光传感器,快速实现了一个光度计的设计。不仅实时计算快门速度还可以通过按键模拟调节光圈大小。