简介
本章节将尝试完成电子测光表的进阶任务。 实际上一共三个进阶任务。 即
1- 使用手机控制舵机拍照
2- 菜单切换BH1750测光模式
3- 蓝牙控制舵机
但是实际上如果要使用Circuit Python的话, 这个ESP32S3 - TFT feature 开发板并不支持蓝牙。
所以如果不使用Arduino , 或者其他固件的话即无法使用蓝牙功能, 或者像群里某个大佬一样实现使用WEB网页的方式控制, 不过这种较为麻烦。所以本文将带来,BH1750 切换测光模式的实现和 使用按钮控制舵机的实现。
BH1750 测光菜单切换测光模式和分辨率
BH1750 的测光,在库定义中一共有三个模式, 第一个是停止模式, 第二个是连续测光,第三个是单次测光。
对于分辨率的设置一共也是有三种,反别是Low (低), MID (中) 和, HIGH (高)
我们可以修改代码, 根据按键的状态来切换分辨率和测光的模式。
直接在之前的code.py 中修改, 主要是增加了按键的控制切换。
import time import math import board import adafruit_bh1750 import digitalio import terminalio from adafruit_display_text import bitmap_label import displayio # 初始化 I2C 和 BH1750 传感器 i2c = board.I2C() # 使用板载 SCL 和 SDA sensor = adafruit_bh1750.BH1750(i2c) # 计算 EV (曝光值) 的函数 def calculate_ev(lux): return 2 + math.log(lux / 10, 2) if lux > 0 else 0 # 计算快门速度的函数,基于 EV、光圈值和 ISO def calculate_shutter_speed(ev, aperture, iso): # Shutter speed = (aperture ^ 2) / (ISO * (2 ^ EV)) return (aperture ** 2) / (iso * (2 ** ev)) # 初始化按钮 D13 和 D12 button_up = digitalio.DigitalInOut(board.D13) button_up.direction = digitalio.Direction.INPUT button_up.pull = digitalio.Pull.UP # 启用上拉电阻 button_down = digitalio.DigitalInOut(board.D12) button_down.direction = digitalio.Direction.INPUT button_down.pull = digitalio.Pull.UP # 启用上拉电阻 # 初始化显示 group = displayio.Group() board.DISPLAY.root_group = group # 创建不同文本区域的标签,设置 scale=1 统一字体大小 shutter_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) shutter_label.x = 10 shutter_label.y = 10 group.append(shutter_label) aperture_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) aperture_label.x = 10 aperture_label.y = 30 group.append(aperture_label) iso_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) iso_label.x = 10 iso_label.y = 50 group.append(iso_label) mode_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) mode_label.x = 10 mode_label.y = 70 group.append(mode_label) resolution_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) resolution_label.x = 10 resolution_label.y = 90 group.append(resolution_label) # 初始设置 aperture = 3.5 # 固定光圈值 iso = 100 # 初始 ISO 值 # 定义 BH1750 测光模式 modes = [ "Continuous", # 连续高分辨率模式 "One Time", # 单次高分辨率模式 "STOP" # 停止模式 ] current_mode_index = 0 # 默认使用 "Continuous" 模式 # 定义 BH1750 分辨率模式 resolutions = [ "LOW", # 低分辨率模式 "MID", # 中分辨率模式 "HIGH" # 高分辨率模式 ] current_resolution_index = 0 # 默认使用 "LOW" 分辨率模式 # 切换测光模式的函数 def switch_mode(): global current_mode_index current_mode_index = (current_mode_index + 1) % len(modes) # 根据选择的模式设置传感器 if current_mode_index == 0: sensor.mode = adafruit_bh1750.Mode.CONTINUOUS elif current_mode_index == 1: sensor.mode = adafruit_bh1750.Mode.ONE_SHOT # 切换分辨率模式的函数 def switch_resolution(): global current_resolution_index current_resolution_index = (current_resolution_index + 1) % len(resolutions) # 根据选择的分辨率设置传感器 if current_resolution_index == 0: sensor.resolution = adafruit_bh1750.Resolution.LOW elif current_resolution_index == 1: sensor.resolution = adafruit_bh1750.Resolution.MID elif current_resolution_index == 2: sensor.resolution = adafruit_bh1750.Resolution.HIGH # 主循环 while True: lux = sensor.lux # 从传感器读取 Lux 值 ev = calculate_ev(lux) # 从 Lux 计算 EV 值 # 切换测光模式(使用按钮 D12) if not button_down.value: # D12 按钮被按下 switch_mode() time.sleep(0.2) # 防抖延迟 # 切换分辨率模式(使用按钮 D13) if not button_up.value: # D13 按钮被按下 switch_resolution() time.sleep(0.2) # 防抖延迟 # 基于固定光圈计算快门速度 shutter_speed = calculate_shutter_speed(ev, aperture, iso) # 更新显示 shutter_label.text = f"Shutter: 1/{int(1 / shutter_speed)}s" if shutter_speed > 0 else "Shutter: --" aperture_label.text = f"Aperture: f/{aperture:.1f}" # 固定光圈值 iso_label.text = f"ISO: {iso}" mode_label.text = f"Mode: {modes[current_mode_index]}" resolution_label.text = f"Resolution: {resolutions[current_resolution_index]}" time.sleep(0.1)
这样当用户按下按键的时候就可以切换BH1750的测光模式和分辨率。
360度舵机控制
Adafruit家提供的库非常丰富, 我们可以下载adafruit-circuitpython-bundle-9.x-mpy-20241121 地址: https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases 这个压缩包里包括了几乎所有的Cpy的库和demo,我们需要用到的是舵机的相关库。
把这个文件夹, 拷贝到开发板的lib目录下。
然后可以使用下述代码来控制舵机的正转和反转。
import time import board import digitalio import pwmio # 设置 D12 和 D13 按钮 button_rotate = digitalio.DigitalInOut(board.D12) button_rotate.direction = digitalio.Direction.INPUT button_rotate.pull = digitalio.Pull.UP # 上拉电阻 button_stop = digitalio.DigitalInOut(board.D13) button_stop.direction = digitalio.Direction.INPUT button_stop.pull = digitalio.Pull.UP # 上拉电阻 # 设置 D11 用于 PWM 信号控制舵机 pwm = pwmio.PWMOut(board.D11, duty_cycle=2**15, frequency=50) # 变量来跟踪舵机当前的旋转方向,True 表示正转,False 表示反转 is_forward = True # 控制舵机旋转的函数 def move_servo(duty_cycle): pwm.duty_cycle = duty_cycle # 调整 PWM 占空比来控制旋转 # 主循环 while True: # 检查 D12 按钮是否被按下,切换方向 if not button_rotate.value: # 按下时切换方向 if is_forward: print("Switching to reverse") move_servo(8000) else: print("Switching to forward") move_servo(4000) is_forward = not is_forward # 切换方向 time.sleep(0.2) # 防抖延迟 # 如果 D13 按钮被按下,舵机停止 elif not button_stop.value: print("Stopping") move_servo(5000) # 停止,占空比接近 1.5ms(相对于 50Hz 的频率) time.sleep(0.2) # 防抖延迟
由于这个舵机是一个360度的舵机,所有没办法控制具体的角度, 只能通过PWM的占空比等来调整方向和速度。上文中代码设置的反转的速度是正转的两倍, 我们可以通过控制修改这个占空比来调整速度。
屏幕显示部分
- 更新日期 2024 / 12 /2 日
- 更新日志 : 增加了WIFI链接和MQTT集成Home Assistant, 通过Home Assistant实现控制舵机和数据的回显。
代码如下
import time import math import board import adafruit_bh1750 import digitalio import terminalio from adafruit_display_text import bitmap_label import displayio import os import ssl import socketpool import wifi import json import pwmio import adafruit_minimqtt.adafruit_minimqtt as MQTT aio_username = "你的MQTT账号" aio_key = "你的MQTT密码" print(aio_username) wifi.radio.connect("你的wifi账号","你的wifi密码") print(wifi.radio.ipv4_address) topic = "user/motor" sensor_topic = "user/sensor" # Define callback methods which are called when events occur def connected(client, userdata, flags, rc): # This function will be called when the client is connected # successfully to the broker. print(f"Connected to MQTT Server! Listening for topic changes on {topic}") # Subscribe to all changes on the onoff_feed. client.subscribe(topic) print("Connected to motor topic") client.subscribe(sensor_topic) print("Connected to sensor topic") def disconnected(client, userdata, rc): # This method is called when the client is disconnected print("Disconnected from Adafruit IO!") is_forward = True pwm = pwmio.PWMOut(board.D11, duty_cycle=2**15, frequency=50) # 控制舵机旋转的函数 def move_servo(duty_cycle): pwm.duty_cycle = duty_cycle # 调整 PWM 占空比来控制旋转 def message(client, topic, message): print(f"New message on topic {topic}: {message}") global is_forward if topic == "user/motor": if message == "Switch": if is_forward: move_servo(8000) # 如果是前进方向,设置 PWM 占空比 else: move_servo(4000) # 如果是后退方向,设置 PWM 占空比 is_forward = not is_forward # 切换方向 elif message == "OFF": print("Turning servo OFF") move_servo(5000) # 关闭舵机(可根据需求调整) time.sleep(0.2) pool = socketpool.SocketPool(wifi.radio) ssl_context = ssl.create_default_context() # Set up a MiniMQTT Client mqtt_client = MQTT.MQTT( broker="192.168.1.113", port=1883, socket_pool=pool, ssl_context=None, ) # Setup the callback methods above mqtt_client.on_connect = connected mqtt_client.on_disconnect = disconnected mqtt_client.on_message = message # Connect the client to the MQTT broker. print("Connecting to MQTT Server...") mqtt_client.connect() print("Connected...") photocell_val = 0 # 变量来跟踪舵机当前的旋转方向,True 表示正转,False 表示反转 is_forward = True # 初始化 I2C 和 BH1750 传感器 i2c = board.I2C() # 使用板载 SCL 和 SDA sensor = adafruit_bh1750.BH1750(i2c) # 计算 EV (曝光值) 的函数 def calculate_ev(lux): return 2 + math.log(lux / 10, 2) if lux > 0 else 0 # 计算快门速度的函数,基于 EV、光圈值和 ISO def calculate_shutter_speed(ev, aperture, iso): # Shutter speed = (aperture ^ 2) / (ISO * (2 ^ EV)) return (aperture ** 2) / ((2 ** ev)) # 初始化按钮 D13 和 D12 button_up = digitalio.DigitalInOut(board.D13) button_up.direction = digitalio.Direction.INPUT button_up.pull = digitalio.Pull.UP # 启用上拉电阻 button_down = digitalio.DigitalInOut(board.D12) button_down.direction = digitalio.Direction.INPUT button_down.pull = digitalio.Pull.UP # 启用上拉电阻 # 初始化显示 group = displayio.Group() board.DISPLAY.root_group = group # 创建不同文本区域的标签,设置 scale=1 统一字体大小 shutter_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) shutter_label.x = 10 shutter_label.y = 10 group.append(shutter_label) aperture_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) aperture_label.x = 10 aperture_label.y = 30 group.append(aperture_label) iso_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) iso_label.x = 10 iso_label.y = 50 group.append(iso_label) mode_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) mode_label.x = 10 mode_label.y = 70 group.append(mode_label) resolution_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) resolution_label.x = 10 resolution_label.y = 90 group.append(resolution_label) lux_label = bitmap_label.Label(terminalio.FONT, text="", scale=1) lux_label.x = 10 lux_label.y = 110 # 显示在最后一行 group.append(lux_label) # 初始设置 aperture = 3.5 # 固定光圈值 iso = 100 # 初始 ISO 值 # 定义 BH1750 测光模式 modes = [ "Continuous", # 连续高分辨率模式 "One Time", # 单次高分辨率模式 "STOP" # 停止模式 ] current_mode_index = 0 # 默认使用 "Continuous" 模式 # 定义 BH1750 分辨率模式 resolutions = [ "LOW", # 低分辨率模式 "MID", # 中分辨率模式 "HIGH" # 高分辨率模式 ] current_resolution_index = 0 # 默认使用 "LOW" 分辨率模式 # 切换测光模式的函数 def switch_mode(): global current_mode_index current_mode_index = (current_mode_index + 1) % len(modes) # 根据选择的模式设置传感器 if current_mode_index == 0: sensor.mode = adafruit_bh1750.Mode.CONTINUOUS elif current_mode_index == 1: sensor.mode = adafruit_bh1750.Mode.ONE_SHOT # 切换分辨率模式的函数 def switch_resolution(): global current_resolution_index current_resolution_index = (current_resolution_index + 1) % len(resolutions) # 根据选择的分辨率设置传感器 if current_resolution_index == 0: sensor.resolution = adafruit_bh1750.Resolution.LOW elif current_resolution_index == 1: sensor.resolution = adafruit_bh1750.Resolution.MID elif current_resolution_index == 2: sensor.resolution = adafruit_bh1750.Resolution.HIGH # 主循环 while True: mqtt_client.loop(timeout=1) lux = sensor.lux # 从传感器读取 Lux 值 ev = calculate_ev(lux) # 从 Lux 计算 EV 值 # 切换测光模式(使用按钮 D12) if not button_down.value: # D12 按钮被按下 switch_mode() time.sleep(0.2) # 防抖延迟 # 切换分辨率模式(使用按钮 D13) if not button_up.value: # D13 按钮被按下 switch_resolution() time.sleep(0.2) # 防抖延迟 # 基于固定光圈计算快门速度 shutter_speed = calculate_shutter_speed(ev, aperture, iso) # 更新显示 shutter_label.text = f"Shutter: 1/{int(1 / shutter_speed)}s" if shutter_speed > 0 else "Shutter: --" aperture_label.text = f"Aperture: f/{aperture:.1f}" # 固定光圈值 iso_label.text = f"ISO: {iso}" mode_label.text = f"Mode: {modes[current_mode_index]}" resolution_label.text = f"Resolution: {resolutions[current_resolution_index]}" lux_label.text = f"Light: {lux:.2f} Lux" # 更新光照值显示 data = { "lux": lux, "ev": ev, "shutter_speed": f"1/{int(1 / shutter_speed)}" if shutter_speed > 0 else "--", "aperture": aperture, "iso": iso, "mode": modes[current_mode_index], "resolution": resolutions[current_resolution_index], } mqtt_client.publish(sensor_topic, json.dumps(data)) # 发送 JSON 数据 time.sleep(0.1)
效果展示
手机端效果展示
视频展示
https://www.bilibili.com/video/BV1TD6MYVET1/?vd_source=fa81ee8dac5a78e9ccb692c6642f6fe2