news 2026/5/15 1:47:17

CircuitPython I2C与HID实战:从TSL2591传感器到键盘鼠标模拟

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
CircuitPython I2C与HID实战:从TSL2591传感器到键盘鼠标模拟

1. 项目概述与核心价值

如果你正在玩转像Adafruit ItsyBitsy、Metro这类小巧但功能强大的CircuitPython开发板,并且想让它们不仅仅是运行几行简单的脚本,而是真正地与外部世界“对话”——比如读取一个高精度的环境传感器数据,或者干脆把你的硬件项目变成一个能控制电脑的键盘或鼠标——那么,这篇文章就是为你准备的。我花了相当多的时间在I2C通信和HID设备模拟这两个核心主题上,它们可以说是连接物理世界与数字世界、让硬件项目“活”起来的关键桥梁。今天,我就以TSL2591高动态范围数字光传感器为例,带你从最基础的连线开始,一步步实现I2C设备扫描、数据读取,并最终将开发板打造成一个能模拟键盘输入和鼠标操作的人机交互设备。无论你是想制作一个自动记录光照数据的环境监测站,还是一个通过物理按钮触发复杂快捷键的宏键盘,这里面的思路和代码都能直接拿来用。

2. I2C通信基础与硬件连接

2.1 I2C协议核心原理浅析

在动手接线之前,花两分钟理解I2C(Inter-Integrated Circuit)到底是怎么工作的,能让你在后续调试时事半功倍。你可以把它想象成一个老师(主设备)和一群学生(从设备)在教室里的问答环节。老师手里握着唯一的哨子(SCL,时钟线),他吹一下哨子,就代表可以开始或结束一次提问。数据线(SDA)就像是传递纸条的通道。每个学生(从设备)都有一个唯一的学号(7位I2C地址)。当老师想找某个学生时,他会先吹哨示意开始,然后对着全班喊出这个学号。只有学号匹配的学生才会起立应答,并通过纸条通道传递答案(数据)。其他学生则保持安静。这就是I2C最基本的“主-从”通信模式。

它的两大优势在于节省引脚支持多设备。传统的并行通信可能需要8根、16根数据线,而I2C只用两根线(SCL和SDA)就能搞定,这对于引脚资源紧张的微控制器(比如我们用的这些小巧开发板)来说简直是福音。同时,理论上你可以在同一条总线上挂载上百个设备(受限于地址空间和总线电容),通过地址来区分它们,这使得扩展多个传感器变得非常整洁。

2.2 TSL2591传感器与开发板接线实战

我们今天的“学生”是TSL2591,一个非常灵敏的数字光传感器,它能同时感知可见光和红外光,计算出精确的照度值(Lux)。它的“学号”(I2C地址)通常是0x29。现在,我们需要把它和“老师”(我们的CircuitPython开发板)连接起来。

接线是硬件项目的第一步,也是最容易出错的一步。务必在断电状态下操作。以下是针对不同型号Adafruit开发板的接线方法,核心就四根线:电源、地、时钟线(SCL)、数据线(SDA)。

对于 ItsyBitsy M0 Express 和 ItsyBitsy M4 Express:

  • 开发板 USB 接口->传感器 VIN:给传感器供电。注意,这里是用开发板的USB电压(通常是5V)直接给传感器的VIN引脚供电。
  • 开发板 G->传感器 GND:共地,为电路提供参考零点。
  • 开发板 SCL->传感器 SCL:连接I2C时钟线。
  • 开发板 SDA->传感器 SDA:连接I2C数据线。

对于 Metro M0 Express 和 Metro M4 Express:

  • 开发板 5V->传感器 VIN:Metro板上有明确的5V引脚。
  • 开发板 GND->传感器 GND:接地。
  • 开发板 SCL->传感器 SCL:时钟线连接。
  • 开发板 SDA->传感器 SDA:数据线连接。

注意:很多传感器,包括TSL2591,也支持3.3V逻辑电平。如果你看到传感器上既有VIN也有3Vo引脚,并且你的开发板逻辑电平是3.3V(像ItsyBitsy、Metro的I/O引脚都是3.3V),那么将传感器连接到3.3V电源和地也是完全可以的,有时甚至更安全。但遵循官方指南连接VIN到5V通常能保证传感器工作在最稳定的状态。

接好线后,先别急着写代码。肉眼检查一遍所有杜邦线是否插紧,有没有虚接或短路(比如相邻引脚不小心碰在一起)。硬件连接是后续所有软件工作的基石,这里马虎不得。

3. CircuitPython I2C设备扫描与寻址

3.1 编写I2C扫描程序

硬件连接妥当后,第一件事不是读数据,而是“点名”。我们需要确认开发板是否真的“看”到了传感器,并且知道它的地址。这就是I2C扫描。下面这个脚本是你在CircuitPython里进行I2C调试的“瑞士军刀”,务必保存好。

# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # SPDX-License-Identifier: MIT """CircuitPython I2C Device Address Scan""" import time import board # 使用板子默认的I2C总线(大多数开发板) i2c = board.I2C() # 这会自动使用 board.SCL 和 board.SDA 引脚 # 如果你使用的是板载的STEMMA QT连接器,可以取消下面这行的注释 # i2c = board.STEMMA_I2C() # 如果需要手动指定引脚创建I2C总线(例如使用非标引脚),可以这样: # import busio # i2c = busio.I2C(board.SCL1, board.SDA1) # 适用于QT Py RP2040等有额外I2C端口的板子 # 尝试锁定I2C总线。锁定后,只有当前代码可以访问总线,防止冲突。 while not i2c.try_lock(): pass try: while True: # 执行扫描,并将找到的地址转换为十六进制格式显示 found_addresses = [hex(addr) for addr in i2c.scan()] print("I2C addresses found:", found_addresses) time.sleep(2) # 每2秒扫描一次 finally: # 无论是否出错,最终都要解锁I2C总线,这是一个好习惯 i2c.unlock()

将这段代码保存为你的code.py,然后通过串行控制台(如Mu编辑器、Thonny或screen/putty)查看输出。如果一切正常,你应该会看到类似I2C addresses found: ['0x29']的输出。这说明总线通信正常,并且成功找到了地址为0x29的设备。

实操心得:如果扫描结果为空列表[],首先别慌,按以下步骤排查:

  1. 复查接线:这是最常见的问题。确保SCL接SCL,SDA接SDA,电源和地没有接反或接错位置。
  2. 检查电源:确认传感器供电正常(VIN有电压)。可以用万用表量一下。
  3. 检查上拉电阻:I2C总线需要上拉电阻(通常4.7kΩ到10kΩ)将SDA和SCL线拉到高电平。幸运的是,绝大多数Adafruit的CircuitPython开发板(如ItsyBitsy, Metro)已经在板载的I2C引脚上内置了上拉电阻。但如果你是自己用像RP2040 Pi Pico这样的裸芯片搭建电路,或者使用了非标I2C引脚,就必须在SDA和SCL线上分别外接上拉电阻到3.3V。
  4. 尝试手动解锁:有时I2C总线会卡在锁定状态。你可以在REPL(串行控制台)中手动输入以下命令解锁:
    import board board.I2C().unlock()
  5. 降低时钟频率:在极少数情况下,传感器可能不支持默认的100kHz高速模式。你可以在初始化I2C时指定一个更低的频率,例如i2c = busio.I2C(board.SCL, board.SDA, frequency=10000)(10kHz)。

3.2 理解I2C初始化的“单例模式”

代码中i2c = board.I2C()这一行值得深入说一下。在CircuitPython中,board.I2C()是一个“单例”(Singleton)设计模式的实现。这意味着无论你在代码的哪个地方、调用多少次board.I2C(),它返回的都是同一个I2C总线对象。这避免了资源冲突,确保整个程序里只有一个实体在控制那组物理引脚。这是一种非常简洁和安全的设计,你不需要担心重复初始化导致的问题。

4. 读取TSL2591传感器数据

4.1 安装依赖库与项目代码

确认传感器在线后,我们就可以开始读取光照数据了。CircuitPython的强大之处在于其丰富的“驱动库”生态系统。对于TSL2591,Adafruit提供了专门的adafruit_tsl2591库,它封装了所有复杂的寄存器操作,让我们用几行简单的代码就能获取专业级的光照数据。

最方便的方法是下载“项目包”(Project Bundle)。这通常是一个zip文件,里面包含了所有必要的库文件(放在lib文件夹)和写好的示例代码(code.py)。你只需要解压,并将整个内容复制到你的CIRCUITPYU盘根目录即可。对于手动安装,你需要将adafruit_tsl2591.mpy和它的依赖库adafruit_bus_device复制到CIRCUITPY驱动器的lib文件夹中。

4.2 数据读取代码解析

以下是读取TSL2591数据的核心代码,我添加了详细的注释:

# SPDX-FileCopyrightText: 2017 Limor Fried for Adafruit Industries # SPDX-License-Identifier: MIT """CircuitPython Essentials I2C sensor example using TSL2591""" import time import board import adafruit_tsl2591 # 导入TSL2591专用库 # 初始化I2C总线 i2c = board.I2C() # 可选:先扫描一次,确认传感器存在(调试用) while not i2c.try_lock(): pass print("I2C addresses found:", [hex(addr) for addr in i2c.scan()]) i2c.unlock() # 扫描完记得解锁,因为后续库会重新锁定 # 创建传感器对象。这是最关键的一步,库会通过这个对象与传感器通信。 # 将我们初始化的i2c总线对象传递给它。 tsl = adafruit_tsl2591.TSL2591(i2c) # 现在,你可以通过这个`tsl`对象访问传感器的各种属性和方法了。 print("Sensor initialized. Starting to read data...") while True: # 直接读取照度值(单位:Lux)。库已经帮我们完成了所有光电转换和计算。 lux_value = tsl.lux print(f"Lux: {lux_value:.2f}") # 格式化输出,保留两位小数 # 除了lux,你还可以读取原始的红外光和全光谱光值,用于更高级的应用 # ir = tsl.infrared # full_spectrum = tsl.visible # print(f"IR: {ir}, Full: {full_spectrum}") time.sleep(1.0) # 每秒读取一次

将代码上传到板子并打开串口监视器,你应该能看到不断输出的Lux值。用手盖住传感器,数值会骤降;用手电筒照射它,数值会飙升。这直观地证明了你的I2C通信和数据读取链路完全畅通。

注意事项:TSL2591是一个高灵敏度传感器,它的增益和积分时间是可以配置的,这直接影响测量范围和精度。默认设置适合大多数室内环境。如果你在非常暗或非常亮的环境下使用,可能需要调整。你可以通过tsl.gain = adafruit_tsl2591.GAIN_LOWtsl.integration_time = adafruit_tsl2591.INTEGRATIONTIME_100MS等属性进行调整。具体可查阅adafruit_tsl2591库的文档。不当的设置可能导致读数溢出(显示为None)或精度不足。

5. CircuitPython HID设备模拟实战

5.1 键盘模拟:从物理按键到键盘输入

让开发板模拟成键盘,意味着你可以通过连接一个简单的按钮或触摸传感器,来触发键盘上的任意按键甚至组合键,实现物理快捷键、宏命令等功能。这在自动化测试、辅助输入设备制作上非常有用。

核心库与对象:实现HID键盘功能需要三个库:usb_hidadafruit_hid.keyboardadafruit_hid.keyboard_layout_us(或其他语言布局)。Keyboard对象用于发送单个键码(如Keycode.A),而KeyboardLayoutUS对象用于处理字符串输入(如直接打出“Hello World”),它会自动将字符转换为一系列键击。

代码实战与解析:下面这个例子使用两个引脚(A1和A2)连接按钮(或直接接地触发),分别模拟按下“A”键和输入字符串“Hello World!”。

import time import board import digitalio import usb_hid from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS from adafruit_hid.keycode import Keycode # --- 配置部分 --- # 1. 定义用于触发按键的引脚 keypress_pins = [board.A1, board.A2] # 2. 定义每个引脚触发时对应的动作:Keycode对象或字符串 keys_pressed = [Keycode.A, "Hello World!\n"] # \n 代表回车键 # 3. 定义需要同时按下的修饰键(如Shift, Ctrl),这里用Shift实现大写A control_key = Keycode.SHIFT # --- 初始化 --- time.sleep(1) # 重要!给主机一点时间识别HID设备,避免竞争条件 keyboard = Keyboard(usb_hid.devices) keyboard_layout = KeyboardLayoutUS(keyboard) # 使用美式键盘布局 # 初始化引脚为输入模式,并启用内部上拉电阻(默认高电平,按下按钮时接地变为低电平) key_pin_array = [] for pin in keypress_pins: key_pin = digitalio.DigitalInOut(pin) key_pin.direction = digitalio.Direction.INPUT key_pin.pull = digitalio.Pull.UP # 启用内部上拉 key_pin_array.append(key_pin) # 初始化板载LED作为状态指示 led = digitalio.DigitalInOut(board.LED) led.direction = digitalio.Direction.OUTPUT print("Waiting for key pin...") # --- 主循环 --- while True: for key_pin in key_pin_array: if not key_pin.value: # 检测引脚是否被拉低(按钮按下) key_index = key_pin_array.index(key_pin) print(f"Pin #{key_index} is grounded.") led.value = True # 点亮LED表示正在处理 # 等待按钮释放(引脚恢复高电平),实现按下-释放一次触发 while not key_pin.value: pass # 执行按键动作 key_action = keys_pressed[key_index] if isinstance(key_action, str): # 如果是字符串,使用键盘布局对象“敲出”整个字符串 keyboard_layout.write(key_action) else: # 如果是Keycode(如Keycode.A),则配合修饰键按下 keyboard.press(control_key, key_action) # 同时按下Shift和A keyboard.release_all() # 释放所有按键!!!这一步至关重要! led.value = False # 熄灭LED time.sleep(0.01) # 短暂延迟,降低CPU占用

关键点解析与避坑指南:

  1. time.sleep(1):在创建Keyboard对象前等待一秒。这是为了确保计算机的USB主机栈有足够时间识别并枚举你的开发板作为一个HID设备。省略这一步可能导致前几次按键无效。
  2. 上拉电阻 (pull = digitalio.Pull.UP):将引脚配置为内部上拉,这样当按钮未按下时,引脚通过电阻连接到3.3V,读取为高电平(True1)。当按钮按下,引脚直接接地,读取为低电平(False0)。这是一种非常常见的按钮检测电路,无需外部电阻。
  3. keyboard.release_all():这是整个键盘模拟中最容易忘记但后果最严重的一步。keyboard.press()相当于用手指按下了键,但如果你不“抬起手指”(即release),电脑会认为这个键一直被按着,导致“按键粘滞”。务必在每次press操作后,尽快调用release_all()。对于组合键(如Ctrl+C),也是先press(Keycode.CONTROL, Keycode.C),再release_all()
  4. 非美式键盘布局:如果你需要模拟其他语言的键盘(如德语、法语),可以从adafruit_hid库中导入相应的布局类,例如KeyboardLayoutDE

5.2 鼠标模拟:用摇杆控制光标

将开发板模拟成鼠标,结合一个模拟摇杆(Joystick),你可以制作自定义的游戏控制器、演示笔或者无障碍辅助设备。

硬件连接:需要一个双轴模拟摇杆(通常带有按钮)。连接方式如下:

  • 摇杆VCC-> 开发板3.3V
  • 摇杆GND-> 开发板GND
  • 摇杆X轴输出-> 开发板A0(模拟输入)
  • 摇杆Y轴输出-> 开发板A1(模拟输入)
  • 摇杆选择按钮(SW)-> 开发板A2(数字输入,内部上拉)

代码实战与解析:鼠标模拟的核心是将摇杆的模拟电压值(0-3.3V)映射为光标的移动速度和方向。

import time import analogio import board import digitalio import usb_hid from adafruit_hid.mouse import Mouse mouse = Mouse(usb_hid.devices) # 初始化摇杆引脚 x_axis = analogio.AnalogIn(board.A0) y_axis = analogio.AnalogIn(board.A1) select_button = digitalio.DigitalInOut(board.A2) select_button.direction = digitalio.Direction.INPUT select_button.pull = digitalio.Pull.UP # 按钮未按下时为高电平 # 摇杆电压范围校准(可能需要根据你的具体硬件微调) # 摇杆居中时电压通常在VCC/2左右,即约1.65V pot_min = 0.00 # 理论上最小值 pot_max = 3.29 # 理论上最大值(接近3.3V) step = (pot_max - pot_min) / 20.0 # 将整个电压范围划分为20个“步进” def get_voltage(pin): """将ADC读取的原始值(0-65535)转换为电压值(0-3.3V)""" return (pin.value * 3.3) / 65536 def steps(axis_voltage): """将电压值映射到0-20的整数步进,中心点约为10""" return round((axis_voltage - pot_min) / step) while True: # 读取当前电压 x_voltage = get_voltage(x_axis) y_voltage = get_voltage(y_axis) # 处理按钮点击(内部上拉,按下时为低电平False) if not select_button.value: mouse.click(Mouse.LEFT_BUTTON) time.sleep(0.2) # 简单防抖,防止误触发多次点击 # 处理X轴移动(左右) x_step = steps(x_voltage) if x_step > 11: # 轻微向右偏 mouse.move(x=1) # 向右移动1像素 elif x_step < 9: # 轻微向左偏 mouse.move(x=-1) # 向左移动1像素 elif x_step > 19: # 大幅度向右偏 mouse.move(x=8) # 快速向右移动 elif x_step < 1: # 大幅度向左偏 mouse.move(x=-8) # 快速向左移动 # 处理Y轴移动(上下)注意:屏幕坐标系Y轴向下为正 y_step = steps(y_voltage) if y_step > 11: # 轻微向下偏(摇杆通常向上推电压降低) mouse.move(y=1) # 向下移动1像素 elif y_step < 9: # 轻微向上偏 mouse.move(y=-1) # 向上移动1像素 elif y_step > 19: # 大幅度向下偏 mouse.move(y=8) # 快速向下移动 elif y_step < 1: # 大幅度向上偏 mouse.move(y=-8) # 快速向上移动 time.sleep(0.01) # 主循环延迟

代码逻辑与调优:

  1. 电压映射get_voltage函数将ADC的16位原始值(0-65535)转换为实际的电压值。steps函数则将连续的电压值离散化为0-20的整数,中心位置(摇杆松开)大约是10。这样设计是为了简化后续的逻辑判断。
  2. 移动逻辑:代码设置了两个移动阈值。当摇杆轻微偏离中心(步进值9或11)时,鼠标以慢速(1像素/次)移动;当摇杆推到极限(步进值0或19)时,鼠标快速移动(8像素/次)。这种“慢-快”两档速度提升了操控体验。
  3. 方向注意:在屏幕坐标系中,Y轴正方向是向下的。所以代码中y_step值大(电压高)对应mouse.move(y=1)(向下移动),这符合直觉:摇杆向前推(通常电压降低)让光标向上走,向后拉让光标向下走。如果你觉得方向反了,可以交换y=1y=-1的条件。
  4. 校准pot_minpot_max是理论值。实际中,摇杆可能无法达到完整的0V和3.3V。你可以通过串口打印x_voltagey_voltage的实时值,观察摇杆在四个极限位置时的读数,并用这些实际值替换理论值,让映射更精确。

6. 高级话题与深度优化

6.1 探索更多I2C引脚的可能性

大多数CircuitPython兼容的板子(如SAMD21, SAMD51, nRF52840)的I2C引脚并不固定于标有“SDA”/“SCL”的那一对。你可以使用其他引脚通过“软件I2C”(bitbang)功能来实现。Adafruit提供了一个非常实用的脚本来扫描板子上所有可能的引脚组合,找出哪些可以用于硬件I2C。运行这个脚本,你会得到一个长长的列表,里面是所有可用的SCL和SDA引脚对。这对于当你需要多个I2C总线,或者默认引脚被其他功能占用时特别有用。

6.2 调整I2C时钟速度

默认的I2C时钟速度(通常是100kHz)对绝大多数传感器(如TSL2591)来说绰绰有余。但在某些特定场景下,你可能需要调整它:

  • 超长导线:如果传感器离主控板很远,导线电容会导致信号边沿变缓,降低通信速度。此时可以调低频率(如10kHz)以提高稳定性。
  • 高速设备:某些设备支持快速模式(400kHz)甚至高速模式(1MHz以上)。在确认设备和布线都支持的情况下,提高时钟速度可以加快数据读取。

在CircuitPython中,如果你使用busio.I2C()手动创建总线对象,可以在初始化时指定frequency参数,例如i2c = busio.I2C(scl_pin, sda_pin, frequency=400000)。但请注意,使用board.I2C()这个单例时,无法直接指定频率,它使用板子预设的默认频率。

6.3 系统监控与数据持久化

你的CircuitPython板子还能做一些很酷的“内省”和记录工作。

读取CPU温度:几乎所有现代微控制器内部都有一个温度传感器。在REPL中,两行代码就能读到它:

import microcontroller print(microcontroller.cpu.temperature) # 单位是摄氏度

这个温度反映的是芯片内核的温度,通常比环境温度高一些,但在没有专用温度传感器时,可以用来估算环境温度或监控芯片是否过热。

使用存储模块进行数据记录:你想让板子脱离电脑独立运行,并把传感器数据(比如刚才读到的温度和光照)记录下来吗?这就需要用到storage模块。核心思路是:在boot.py文件中,通过一个物理开关(或跳线)的状态,来决定CIRCUITPY驱动器是对电脑可写(方便你改代码),还是对CircuitPython程序可写(方便程序记录数据到文件)。

一个典型的boot.py如下:

import board import digitalio import storage # 使用一个数字引脚(如D2)连接一个开关或跳线到GND write_pin = digitalio.DigitalInOut(board.D2) write_pin.direction = digitalio.Direction.INPUT write_pin.pull = digitalio.Pull.UP # 内部上拉,默认高电平 # 如果引脚连接到GND(值为False),则允许CircuitPython写入文件系统 # 如果引脚悬空或为高电平(值为True),则文件系统对电脑可写,对CircuitPython只读 storage.remount("/", readonly=write_pin.value)

设置好后,当引脚接地时,你可以在code.py中用普通的Python文件操作(如open('data.txt', 'a').write(...))来记录数据。当你需要把数据拷贝出来时,只需让引脚断开接地(恢复高电平),重新插拔板子,CIRCUITPY就会重新以对电脑可写的模式挂载,你就能像操作U盘一样复制data.txt文件了。

重要警告:切勿在电脑正在访问CIRCUITPY驱动器(比如你在用编辑器打开上面的文件)时,让CircuitPython尝试写入它。这极有可能导致文件系统损坏,丢失所有数据。boot.py机制正是为了防止这种同时访问的情况发生。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/15 1:45:04

JESD204B高速串行接口技术解析与应用实践

1. JESD204B接口技术深度解析JESD204B作为第三代高速串行接口标准&#xff0c;正在彻底改变数据转换器与逻辑器件之间的连接方式。我在实际项目中使用过ADC16DX370和DAC38J84等多款支持JESD204B的器件&#xff0c;深刻体会到这种接口带来的设计变革。相比传统的LVDS或CMOS并行接…

作者头像 李华
网站建设 2026/5/15 1:44:03

如何配置浏览器PT插件实现高效种子下载:从入门到精通

如何配置浏览器PT插件实现高效种子下载&#xff1a;从入门到精通 【免费下载链接】PT-Plugin-Plus PT 助手 Plus&#xff0c;为 Microsoft Edge、Google Chrome、Firefox 浏览器插件&#xff08;Web Extensions&#xff09;&#xff0c;主要用于辅助下载 PT 站的种子。 项目地…

作者头像 李华
网站建设 2026/5/15 1:42:44

OBS Advanced Timer:6种专业计时模式让直播时间管理更精准

OBS Advanced Timer&#xff1a;6种专业计时模式让直播时间管理更精准 【免费下载链接】obs-advanced-timer 项目地址: https://gitcode.com/gh_mirrors/ob/obs-advanced-timer OBS Advanced Timer是一款专为OBS Studio设计的Lua脚本计时器插件&#xff0c;为直播主播和…

作者头像 李华
网站建设 2026/5/15 1:42:44

衍射光栅散射光与杂散光:产生根源、量化评估与全链路抑制策略

1. 项目概述与核心价值在光学设计、光谱分析以及精密测量领域&#xff0c;衍射光栅是一个核心元件&#xff0c;它的性能直接决定了整个系统的精度和信噪比。然而&#xff0c;无论是闪耀光栅、全息光栅还是体全息光栅&#xff0c;一个绕不开的“幽灵”始终存在&#xff0c;那就是…

作者头像 李华