[Let'sDo第3期] DIY一个电子测光表 6成果贴

很高兴能够参与由Digi-Key得捷电子联合EEPW电子产品世界举办的“Let's Do”活动,这是该系列活动的第三期,主题为DIY一个电子测光表。在这个项目中,我有幸使用了一系列高性能的电子组件,包括搭载了TFT屏幕的ESP32-S3型号5691,以及M5STACK的U136测光模块,以及DFROBOT的SER0049舵机。
这款电子测光表的核心功能是测量光照强度,并将其转换成摄影中的曝光值。通过精确的计算,它能够为摄影师提供在特定光照条件下,拍摄时所需的曝光、光圈和快门参数,从而帮助他们捕捉到理想的画面效果。
我的设计理念是将ESP32-S3 5691的三个内置按钮充分利用,实现三种不同的操作模式。第一个按钮用于启动本地测量模式,用户可以直接在设备的TFT屏幕上查看实时的光照强度数据。第二个按钮则允许用户暂停测量,短按和长按分别是调整光圈值(AV)、快门速度(TV),以便在需要时进行短暂的数据查看或分析。而第三个按钮则启动远程测量模式,通过Wi-Fi连接,用户可以在网页端实时查看测量数据以及计算得出的各项摄影参数。
在TFT屏幕上,用户可以直观地看到光照强度(LUX)、曝光值(EV)、光圈值(AV)、快门速度(TV)以及感光度(SV)。这些参数的实时显示,为用户提供了一个便捷的工具,无论是在户外摄影还是室内布光,都能够快速调整相机设置,以获得最佳的拍摄效果。
此外,通过远程测量功能,用户还可以在远离设备的地方监控光照变化,这对于需要长时间曝光或在复杂光照条件下工作的摄影师来说,无疑是一个巨大的便利。这种远程监控和控制的能力,使得电子测光表不仅是一款实用的工具,更是一个创新的解决方案,它将摄影技术与现代电子技术完美融合。
总的来说,这个项目不仅让我深入探索了电子工程的奥秘,也让我对摄影艺术有了更深的理解。我期待这个电子测光表能够成为摄影师和电子爱好者的得力助手,帮助他们在光影的世界里,捕捉每一个精彩瞬间。
以下为参加此次活动的记录贴,一步一步实现了我的预期目标:
https://forum.eepw.com.cn/thread/387676/1
[Let'sDo第3期] DIY一个电子测光表1开箱帖
https://forum.eepw.com.cn/thread/387677/1
[Let'sDo第3期] DIY一个电子测光表2过程贴(测试)
https://forum.eepw.com.cn/thread/387973/1
[Let'sDo第3期] DIY一个电子测光表3过程贴(wifi联网)
https://forum.eepw.com.cn/thread/388003/1
[Let'sDo第3期] DIY一个电子测光表4过程贴(屏幕显示)
https://forum.eepw.com.cn/thread/388145/1
[Let'sDo第3期] DIY一个电子测光表5过程贴(原理说明)
以下为最终演示视频
以下为项目代码
# 导入所需的库
import board
import digitalio
import wifi
import socketpool
from adafruit_motor import servo
from adafruit_bh1750 import BH1750
import pwmio
import time
from adafruit_bitmap_font import bitmap_font
from adafruit_display_text import label
import displayio
import adafruit_bh1750
import math
# 定义曝光值列表
AV = [1, 1.4, 2, 2.8, 4, 5.6, 8, 11, 16, 22, 32]
TV = [1, 0.5, 0.25, 0.125, 0.0667, 0.0333, 0.0167, 0.0083, 0.0042, 0.0021, 0.0011]
SV = [100, 200, 400, 800, 1600]
# 初始化曝光指数和ISO值
lux = 1000
svIndex = 0
avIndex = 5
tvIndex = 0
# 计算曝光值(EV)的函数
def calculate_ev(lux):
return round(2 + math.log(lux / 10) / math.log(2))
# 更新相机设置的函数
def update_settings(lux, svIndex, avIndex, tvIndex, press_time):
ev = calculate_ev(lux)
total_index = ev + svIndex
if press_time == 'long_press':
tvIndex = (tvIndex + 1) % len(TV)
avIndex = (avIndex + total_index - tvIndex) % len(AV)
elif press_time == 'short_press':
avIndex = (avIndex + 1) % len(AV)
tvIndex = (tvIndex + total_index - avIndex) % len(TV)
return avIndex, tvIndex
# 显示相机设置的函数
def display_settings(lux, avIndex, tvIndex, svIndex):
print("\nCurrent Settings:")
print("")
print(f"EV: {calculate_ev(lux):.1f}")
print(f"ISO (SV): {SV[svIndex]}")
print(f"Aperture (AV): {AV[avIndex]}")
print(f"Shutter Speed (TV): {TV[tvIndex]}")
text_light.text = f"(LUX): {lux:.1f}\n(EV): {calculate_ev(lux):.1f}\n(SV): {SV[svIndex]}\n(AV): {AV[avIndex]}\n(TV): {TV[tvIndex]}"
display.refresh()
# 初始化I2C接口和光照传感器
i2c = board.I2C()
light_sensor = adafruit_bh1750.BH1750(i2c)
# 初始化显示参数
display = board.DISPLAY
display.brightness = 0.75
display.rotation = 90
# 加载字体
font = bitmap_font.load_font("LeagueSpartan-Bold-16.bdf")
color_FireBrick = 0xB22222
# WiFi凭证
SSID = 'username'
PASSWORD = 'password'
# LED设置
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
# 伺服电机设置
pwm = pwmio.PWMOut(board.A0, duty_cycle=2 ** 15, frequency=50)
servo_motor = servo.Servo(pwm)
# 按钮设置
key0 = digitalio.DigitalInOut(board.D0)
key0.direction = digitalio.Direction.INPUT
key0.pull = digitalio.Pull.UP
key1 = digitalio.DigitalInOut(board.D1)
key1.direction = digitalio.Direction.INPUT
key1.pull = digitalio.Pull.DOWN
key2 = digitalio.DigitalInOut(board.D2)
key2.direction = digitalio.Direction.INPUT
key2.pull = digitalio.Pull.DOWN
# 全局变量
servo_running = False
current_angle = 0
mode = "normal" # 可以是 "normal", "stop", 或 "wifi"
# 常量
BUFFER_SIZE = 512
MAX_RETRIES = 3
RETRY_DELAY = 0.1
# 加载字体
font = bitmap_font.load_font("LeagueSpartan-Bold-16.bdf")
color_GREEN = 0x005000
color_RED = 0x500000
# 创建文本标签
text_main = label.Label(
font=font,
text=" LIGHT TEST",
x=2,
y=20,
color=color_GREEN,
)
text_light = label.Label(
font=font,
text="",
x=2,
y=60,
color=color_RED,
)
show_group = displayio.Group()
show_group.append(text_main)
show_group.append(text_light)
display.root_group = show_group
current_angle=90
angle_direction=5
# 连接WiFi的函数
def connect_wifi():
print("Connecting to WiFi...")
text_main.text = "Connecting..."
wifi.radio.connect(SSID, PASSWORD)
print("Connected to WiFi")
print("IP Address:", wifi.radio.ipv4_address)
text_main.text = "IP: " + str(wifi.radio.ipv4_address)
return socketpool.SocketPool(wifi.radio)
# 将伺服电机移动到90度的函数
def move_servo_to_90():
global current_angle
start_angle = current_angle
for angle in range(start_angle, 91, 1 if start_angle < 90 else -1):
servo_motor.angle = angle
current_angle = angle
time.sleep(0.01)
# 运行伺服电机的函数
def running():
global current_angle, angle_direction, light_sensor
if current_angle >= 180:
angle_direction = -5
elif current_angle <= 0:
angle_direction = 5
current_angle += angle_direction
servo_motor.angle = current_angle
light_level = light_sensor.lux
display_settings(lux, avIndex, tvIndex, svIndex)
# 长按阈值
LONG_PRESS_THRESHOLD=0.05
# 处理按钮按压的函数
def handle_button_press():
global servo_running, mode, lux, svIndex, avIndex, tvIndex
if not key0.value:
servo_running = True
mode = "Running"
print("Running Mode")
running()
text_main.text = " TESTING"
elif key1.value:
start_time = time.time()
while key1.value:
current_time = time.time()
if current_time - start_time > LONG_PRESS_THRESHOLD:
text_main.text = " Ajust TV"
press_time = "long_press"
avIndex, tvIndex = update_settings(lux, svIndex, avIndex, tvIndex, press_time)
display_settings(lux, avIndex, tvIndex, svIndex)
break
elif not key1.value:
text_main.text = " Ajust AV"
press_time = "short_press"
avIndex, tvIndex = update_settings(lux, svIndex, avIndex, tvIndex, press_time)
display_settings(lux, avIndex, tvIndex, svIndex)
break
elif key2.value:
servo_running = False
mode = "wifi"
print("WiFi Mode")
text_main.text = "WIFI"
time.sleep(1)
pool = connect_wifi()
web_server(pool)
# 处理客户端连接的函数
def handle_client_connection(conn, addr):
global lux, svIndex, avIndex, tvIndex, press_time
for _ in range(MAX_RETRIES):
try:
request = bytearray(BUFFER_SIZE)
request_length = conn.recv_into(request)
if request_length == 0:
return
request_text = request[:request_length].decode()
print(request_text)
headers = [
"HTTP/1.1 200 OK",
"Access-Control-Allow-Origin: *",
"Access-Control-Allow-Methods: GET",
"Content-Type: text/plain",
]
if "GET /on" in request_text:
led.value = True
response = f"(LUX): {lux:.1f}\n(EV): {calculate_ev(lux):.1f}\n(SV): {SV[svIndex]}\n(AV): {AV[avIndex]}\n(TV): {TV[tvIndex]}"
elif "GET /off" in request_text:
led.value = False
response = "STOP TESTING"
else:
response = "Invalid command"
if mode == "wifi":
lux = light_sensor.lux
print(lux)
press_time = "short_press"
avIndex, tvIndex = update_settings(lux, svIndex, avIndex, tvIndex, press_time)
display_settings
# 向HTTP响应头添加内容长度,并构造完整的HTTP响应
headers.append(f"Content-Length: {len(response)}")
http_response = "\r\n".join(headers) + "\r\n\r\n" + response
print(http_response)
# 带重试机制地发送HTTP响应
for _ in range(MAX_RETRIES):
try:
conn.send(http_response.encode())
return True
except OSError as e:
if e.errno != 11: # EAGAIN错误码
raise
time.sleep(RETRY_DELAY)
return False
# 捕获处理客户端时的异常
except OSError as e:
if e.errno != 11: # 如果不是EAGAIN错误码
print(f"Error handling client: {e}")
return False
time.sleep(RETRY_DELAY)
return False
# 定义web_server函数,用于启动和管理Web服务器
def web_server(pool):
addr = wifi.radio.ipv4_address
print(f"Starting web server at http://{addr}")
with pool.socket() as s:
s.bind((str(addr), 80))
s.listen(1)
while True:
try:
conn, addr = s.accept()
print("Client connected from", addr)
try:
success = handle_client_connection(conn, addr)
if not success:
print("Failed to handle client after retries")
finally:
# 始终关闭连接
try:
conn.close()
except OSError:
pass
except OSError as e:
if e.errno == 11: # EAGAIN错误码
# 下一次accept前小延迟
time.sleep(RETRY_DELAY)
continue
print(f"Server error: {e}")
continue# 定义main函数,作为程序的主入口点
def main():
global lux, svIndex, avIndex, tvIndex
while True:
lux = light_sensor.lux
print(lux)
handle_button_press()
time.sleep(0.1) # 小延迟以避免忙等# 检查是否为主模块,并运行main函数
if __name__ == "__main__":
main() 
我要赚赏金
