[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()