feat: hardware BOM + fix critical daemon bugs (gaze dead code, FIST mapping)
- Add hardware/ directory structure (cad, cam, parts, sim, docs) - Add hardware/bom/merlin-bci-rev0.csv — ADS1299 + ESP32-WROOM-32E full BOM with passive values for 8-ch EEG front-end, isolated ±5V supply, WiFi streaming - Fix: wire gaze_est.estimate(face) into camera loop (was instantiated but never called) - Fix: add fist, gaze_left/right/up/down to DEFAULT_MAPPINGS - Add: ActionExecutor.backend property (replace _backend direct access in /status) - Add: scroll_left/right, key_ctrl_z/c/v to xdotool + pyautogui backends
This commit is contained in:
parent
6188f332ba
commit
451b7ee341
21 changed files with 157 additions and 1 deletions
0
hardware/.gitkeep
Normal file
0
hardware/.gitkeep
Normal file
0
hardware/bom/.gitkeep
Normal file
0
hardware/bom/.gitkeep
Normal file
126
hardware/bom/merlin-bci-rev0.csv
Normal file
126
hardware/bom/merlin-bci-rev0.csv
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
RefDes,Qty,Value / MPN,Package,Description,Notes
|
||||||
|
U1,1,ADS1299IPAGR,TQFP-64,8-ch 24-bit delta-sigma EEG/ECG ADC,"TI; DVDD 1.8–3.6V, AVDD ±(2.5–5.25V)"
|
||||||
|
U2,1,ESP32-WROOM-32E (4MB),SMD module (18×25.5mm),"Dual-core 240MHz, WiFi+BT MCU","brainflow CUSTOM_BOARD via WiFi; or use devkit during firmware dev"
|
||||||
|
U3,1,MGJ2D121505SC,12-SMD SIP,"Isolated DC/DC 5V in → ±5V out, 2W","Murata; patient isolation barrier for AVDD/AVSS supply"
|
||||||
|
U4,1,REF3325AIDBZR,SOT-23-5,"2.5V precision ref/LDO, 50ppm/°C","TI; AVDD rail (patient side)"
|
||||||
|
U5,1,TPS60403DBVR,SOT-23-5,"Inverting charge pump −2.5V (AVSS)","TI; no inductor; in from isolated +5V"
|
||||||
|
U6,1,TLV1117-33DCYR,SOT-223,"3.3V 800mA LDO for DVDD + MCU","Input from isolated +5V rail"
|
||||||
|
Y1,1,2.048MHz ±50ppm,3225 4-pad SMD,"Crystal for ADS1299 CLK","Ties to CLK pin; CLKSEL → DVDD"
|
||||||
|
C_CLK1,1,12pF C0G 0402,0402,Crystal load cap X1,Match to crystal spec (typically 8–18pF)
|
||||||
|
C_CLK2,1,12pF C0G 0402,0402,Crystal load cap X2,
|
||||||
|
R_IN1,1,10kΩ 1% 0402,0402,IN1P input series protection,
|
||||||
|
R_IN2,1,10kΩ 1% 0402,0402,IN1N input series protection,
|
||||||
|
R_IN3,1,10kΩ 1% 0402,0402,IN2P input series protection,
|
||||||
|
R_IN4,1,10kΩ 1% 0402,0402,IN2N input series protection,
|
||||||
|
R_IN5,1,10kΩ 1% 0402,0402,IN3P input series protection,
|
||||||
|
R_IN6,1,10kΩ 1% 0402,0402,IN3N input series protection,
|
||||||
|
R_IN7,1,10kΩ 1% 0402,0402,IN4P input series protection,
|
||||||
|
R_IN8,1,10kΩ 1% 0402,0402,IN4N input series protection,
|
||||||
|
R_IN9,1,10kΩ 1% 0402,0402,IN5P input series protection,
|
||||||
|
R_IN10,1,10kΩ 1% 0402,0402,IN5N input series protection,
|
||||||
|
R_IN11,1,10kΩ 1% 0402,0402,IN6P input series protection,
|
||||||
|
R_IN12,1,10kΩ 1% 0402,0402,IN6N input series protection,
|
||||||
|
R_IN13,1,10kΩ 1% 0402,0402,IN7P input series protection,
|
||||||
|
R_IN14,1,10kΩ 1% 0402,0402,IN7N input series protection,
|
||||||
|
R_IN15,1,10kΩ 1% 0402,0402,IN8P input series protection,
|
||||||
|
R_IN16,1,10kΩ 1% 0402,0402,IN8N input series protection,
|
||||||
|
C_AC1,1,10µF X5R 0402,0402,"AC coupling / HPF ch1P (fc ≈ 1.6Hz @ 10kΩ)","Between R_IN and ADS1299 pin"
|
||||||
|
C_AC2,1,10µF X5R 0402,0402,AC coupling / HPF ch1N,
|
||||||
|
C_AC3,1,10µF X5R 0402,0402,AC coupling / HPF ch2P,
|
||||||
|
C_AC4,1,10µF X5R 0402,0402,AC coupling / HPF ch2N,
|
||||||
|
C_AC5,1,10µF X5R 0402,0402,AC coupling / HPF ch3P,
|
||||||
|
C_AC6,1,10µF X5R 0402,0402,AC coupling / HPF ch3N,
|
||||||
|
C_AC7,1,10µF X5R 0402,0402,AC coupling / HPF ch4P,
|
||||||
|
C_AC8,1,10µF X5R 0402,0402,AC coupling / HPF ch4N,
|
||||||
|
C_AC9,1,10µF X5R 0402,0402,AC coupling / HPF ch5P,
|
||||||
|
C_AC10,1,10µF X5R 0402,0402,AC coupling / HPF ch5N,
|
||||||
|
C_AC11,1,10µF X5R 0402,0402,AC coupling / HPF ch6P,
|
||||||
|
C_AC12,1,10µF X5R 0402,0402,AC coupling / HPF ch6N,
|
||||||
|
C_AC13,1,10µF X5R 0402,0402,AC coupling / HPF ch7P,
|
||||||
|
C_AC14,1,10µF X5R 0402,0402,AC coupling / HPF ch7N,
|
||||||
|
C_AC15,1,10µF X5R 0402,0402,AC coupling / HPF ch8P,
|
||||||
|
C_AC16,1,10µF X5R 0402,0402,AC coupling / HPF ch8N,
|
||||||
|
C_EMI1,1,4.7nF C0G 0402,0402,EMI shunt ch1P to AGND,
|
||||||
|
C_EMI2,1,4.7nF C0G 0402,0402,EMI shunt ch1N to AGND,
|
||||||
|
C_EMI3,1,4.7nF C0G 0402,0402,EMI shunt ch2P to AGND,
|
||||||
|
C_EMI4,1,4.7nF C0G 0402,0402,EMI shunt ch2N to AGND,
|
||||||
|
C_EMI5,1,4.7nF C0G 0402,0402,EMI shunt ch3P to AGND,
|
||||||
|
C_EMI6,1,4.7nF C0G 0402,0402,EMI shunt ch3N to AGND,
|
||||||
|
C_EMI7,1,4.7nF C0G 0402,0402,EMI shunt ch4P to AGND,
|
||||||
|
C_EMI8,1,4.7nF C0G 0402,0402,EMI shunt ch4N to AGND,
|
||||||
|
C_EMI9,1,4.7nF C0G 0402,0402,EMI shunt ch5P to AGND,
|
||||||
|
C_EMI10,1,4.7nF C0G 0402,0402,EMI shunt ch5N to AGND,
|
||||||
|
C_EMI11,1,4.7nF C0G 0402,0402,EMI shunt ch6P to AGND,
|
||||||
|
C_EMI12,1,4.7nF C0G 0402,0402,EMI shunt ch6N to AGND,
|
||||||
|
C_EMI13,1,4.7nF C0G 0402,0402,EMI shunt ch7P to AGND,
|
||||||
|
C_EMI14,1,4.7nF C0G 0402,0402,EMI shunt ch7N to AGND,
|
||||||
|
C_EMI15,1,4.7nF C0G 0402,0402,EMI shunt ch8P to AGND,
|
||||||
|
C_EMI16,1,4.7nF C0G 0402,0402,EMI shunt ch8N to AGND,
|
||||||
|
D1,1,BAV99,SOT-23,"Dual Schottky clamp ch1 (INP+INN to AVDD/AVSS)","1 BAV99 = 2 diodes; clamp one diff pair"
|
||||||
|
D2,1,BAV99,SOT-23,Dual Schottky clamp ch2,
|
||||||
|
D3,1,BAV99,SOT-23,Dual Schottky clamp ch3,
|
||||||
|
D4,1,BAV99,SOT-23,Dual Schottky clamp ch4,
|
||||||
|
D5,1,BAV99,SOT-23,Dual Schottky clamp ch5,
|
||||||
|
D6,1,BAV99,SOT-23,Dual Schottky clamp ch6,
|
||||||
|
D7,1,BAV99,SOT-23,Dual Schottky clamp ch7,
|
||||||
|
D8,1,BAV99,SOT-23,Dual Schottky clamp ch8,
|
||||||
|
R_BIAS1,1,820kΩ 1% 0402,0402,Input bias ch1P (BIASIN network to BIASOUT),
|
||||||
|
R_BIAS2,1,820kΩ 1% 0402,0402,Input bias ch1N,
|
||||||
|
R_BIAS3,1,820kΩ 1% 0402,0402,Input bias ch2P,
|
||||||
|
R_BIAS4,1,820kΩ 1% 0402,0402,Input bias ch2N,
|
||||||
|
R_BIAS5,1,820kΩ 1% 0402,0402,Input bias ch3P,
|
||||||
|
R_BIAS6,1,820kΩ 1% 0402,0402,Input bias ch3N,
|
||||||
|
R_BIAS7,1,820kΩ 1% 0402,0402,Input bias ch4P,
|
||||||
|
R_BIAS8,1,820kΩ 1% 0402,0402,Input bias ch4N,
|
||||||
|
R_BIAS9,1,820kΩ 1% 0402,0402,Input bias ch5P,
|
||||||
|
R_BIAS10,1,820kΩ 1% 0402,0402,Input bias ch5N,
|
||||||
|
R_BIAS11,1,820kΩ 1% 0402,0402,Input bias ch6P,
|
||||||
|
R_BIAS12,1,820kΩ 1% 0402,0402,Input bias ch6N,
|
||||||
|
R_BIAS13,1,820kΩ 1% 0402,0402,Input bias ch7P,
|
||||||
|
R_BIAS14,1,820kΩ 1% 0402,0402,Input bias ch7N,
|
||||||
|
R_BIAS15,1,820kΩ 1% 0402,0402,Input bias ch8P,
|
||||||
|
R_BIAS16,1,820kΩ 1% 0402,0402,Input bias ch8N,
|
||||||
|
R_DRL1,1,1MΩ 1% 0402,0402,DRL feedback resistor (RLDFB),
|
||||||
|
R_DRL2,1,680Ω 1% 0402,0402,DRL output protection,
|
||||||
|
C_DRL1,1,4.7nF C0G 0402,0402,DRL loop stability capacitor,In parallel with R_DRL1
|
||||||
|
C_VREFP1,1,10µF X5R 6.3V 0805,0805,VREFP bulk bypass,
|
||||||
|
C_VREFP2,1,100nF C0G 0402,0402,VREFP HF bypass,
|
||||||
|
C_AVDD1,1,10µF X5R 10V 0805,0805,AVDD bulk bypass #1,
|
||||||
|
C_AVDD2,1,10µF X5R 10V 0805,0805,AVDD bulk bypass #2,
|
||||||
|
C_AVDD3,1,100nF C0G 0402,0402,AVDD HF bypass #1,
|
||||||
|
C_AVDD4,1,100nF C0G 0402,0402,AVDD HF bypass #2,
|
||||||
|
C_AVDD5,1,100nF C0G 0402,0402,AVDD HF bypass #3,
|
||||||
|
C_AVSS1,1,10µF X5R 10V 0805,0805,AVSS bulk bypass #1 (to AGND),
|
||||||
|
C_AVSS2,1,10µF X5R 10V 0805,0805,AVSS bulk bypass #2,
|
||||||
|
C_AVSS3,1,100nF C0G 0402,0402,AVSS HF bypass #1,
|
||||||
|
C_AVSS4,1,100nF C0G 0402,0402,AVSS HF bypass #2,
|
||||||
|
C_AVSS5,1,100nF C0G 0402,0402,AVSS HF bypass #3,
|
||||||
|
C_DVDD1,1,100nF C0G 0402,0402,DVDD bypass pin 1,
|
||||||
|
C_DVDD2,1,100nF C0G 0402,0402,DVDD bypass pin 2,
|
||||||
|
C_DVDD3,1,100nF C0G 0402,0402,DVDD bypass pin 3,
|
||||||
|
C_DVDD4,1,10µF X5R 0805,0805,DVDD bulk bypass,
|
||||||
|
R_SPI1,1,33Ω 0402,0402,SPI MOSI series termination,MCU side of trace
|
||||||
|
R_SPI2,1,33Ω 0402,0402,SPI MISO series termination,
|
||||||
|
R_SPI3,1,33Ω 0402,0402,SPI SCLK series termination,
|
||||||
|
R_SPI4,1,33Ω 0402,0402,SPI CS series termination,
|
||||||
|
R_DRDY,1,1kΩ 0402,0402,DRDY pull-up to DVDD,
|
||||||
|
C_DRDY,1,100pF C0G 0402,0402,DRDY glitch filter,
|
||||||
|
R_PU1,1,10kΩ 0402,0402,RESET pull-up to DVDD,
|
||||||
|
R_PU2,1,10kΩ 0402,0402,START pull-up to DVDD,
|
||||||
|
R_PU3,1,10kΩ 0402,0402,PWDN pull-up to DVDD,
|
||||||
|
R_PU4,1,10kΩ 0402,0402,CLKSEL tie to DVDD (ext crystal mode),"Can use 0Ω; 10kΩ keeps GPIO optional"
|
||||||
|
C_MCU1,1,100nF X5R 0402,0402,ESP32 VDDA bypass,
|
||||||
|
C_MCU2,1,100nF X5R 0402,0402,ESP32 3V3 bypass #1,
|
||||||
|
C_MCU3,1,100nF X5R 0402,0402,ESP32 3V3 bypass #2,
|
||||||
|
C_MCU4,1,100nF X5R 0402,0402,ESP32 3V3 bypass #3,
|
||||||
|
C_MCU5,1,10µF X5R 0805,0805,ESP32 3V3 bulk bypass,
|
||||||
|
C_ISO1,1,10µF X5R 10V 0805,0805,Isolated +5V rail bulk cap,After MGJ2D121505SC output
|
||||||
|
C_ISO2,1,100nF C0G 0402,0402,Isolated +5V HF bypass,
|
||||||
|
C_ISO3,1,10µF X5R 10V 0805,0805,Isolated −5V rail bulk cap,
|
||||||
|
C_ISO4,1,100nF C0G 0402,0402,Isolated −5V HF bypass,
|
||||||
|
J1,1,USB4085-GF-A,USB-C mid-mount SMD,USB-C connector to host,"GCT; data carried wirelessly (WiFi); USB only for firmware flash"
|
||||||
|
J2,1,3.5mm TRRS × 9,TH or panel mount,"Electrode jacks: 8 channels + DRL ref","Or Touchproof Ag/AgCl snap connector header"
|
||||||
|
J3,1,2×5 2.54mm header,TH,SWD/JTAG debug header for ESP32,Optional
|
||||||
|
J4,1,2×2 2.54mm header,TH,BOOT / EN jumpers for ESP32 flash mode,
|
||||||
|
FB1,1,600Ω @ 100MHz 0805,0805,Ferrite bead on DVDD supply,Isolate digital noise from analog rails
|
||||||
|
FB2,1,600Ω @ 100MHz 0805,0805,Ferrite bead on AGND bridge to DGND,Star-ground point; single-point bond
|
||||||
|
0
hardware/cad/.gitkeep
Normal file
0
hardware/cad/.gitkeep
Normal file
0
hardware/cad/2d/.gitkeep
Normal file
0
hardware/cad/2d/.gitkeep
Normal file
0
hardware/cad/3d/.gitkeep
Normal file
0
hardware/cad/3d/.gitkeep
Normal file
0
hardware/cad/pcb/.gitkeep
Normal file
0
hardware/cad/pcb/.gitkeep
Normal file
0
hardware/cad/schematic/.gitkeep
Normal file
0
hardware/cad/schematic/.gitkeep
Normal file
0
hardware/cam/.gitkeep
Normal file
0
hardware/cam/.gitkeep
Normal file
0
hardware/cam/drill/.gitkeep
Normal file
0
hardware/cam/drill/.gitkeep
Normal file
0
hardware/cam/gerbers/.gitkeep
Normal file
0
hardware/cam/gerbers/.gitkeep
Normal file
0
hardware/cam/pick-and-place/.gitkeep
Normal file
0
hardware/cam/pick-and-place/.gitkeep
Normal file
0
hardware/docs/.gitkeep
Normal file
0
hardware/docs/.gitkeep
Normal file
0
hardware/parts/.gitkeep
Normal file
0
hardware/parts/.gitkeep
Normal file
0
hardware/parts/datasheets/.gitkeep
Normal file
0
hardware/parts/datasheets/.gitkeep
Normal file
0
hardware/parts/footprints/.gitkeep
Normal file
0
hardware/parts/footprints/.gitkeep
Normal file
0
hardware/parts/symbols/.gitkeep
Normal file
0
hardware/parts/symbols/.gitkeep
Normal file
0
hardware/sim/.gitkeep
Normal file
0
hardware/sim/.gitkeep
Normal file
|
|
@ -32,6 +32,8 @@ _XDOTOOL_CLICK: dict[str, list[str]] = {
|
||||||
"middle_click": ["xdotool", "click", "2"],
|
"middle_click": ["xdotool", "click", "2"],
|
||||||
"scroll_up": ["xdotool", "click", "4"],
|
"scroll_up": ["xdotool", "click", "4"],
|
||||||
"scroll_down": ["xdotool", "click", "5"],
|
"scroll_down": ["xdotool", "click", "5"],
|
||||||
|
"scroll_left": ["xdotool", "click", "6"],
|
||||||
|
"scroll_right": ["xdotool", "click", "7"],
|
||||||
}
|
}
|
||||||
|
|
||||||
_XDOTOOL_KEY: dict[str, str] = {
|
_XDOTOOL_KEY: dict[str, str] = {
|
||||||
|
|
@ -39,6 +41,9 @@ _XDOTOOL_KEY: dict[str, str] = {
|
||||||
"key_escape": "Escape",
|
"key_escape": "Escape",
|
||||||
"key_space": "space",
|
"key_space": "space",
|
||||||
"key_tab": "Tab",
|
"key_tab": "Tab",
|
||||||
|
"key_ctrl_z": "ctrl+z",
|
||||||
|
"key_ctrl_c": "ctrl+c",
|
||||||
|
"key_ctrl_v": "ctrl+v",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -51,6 +56,10 @@ class ActionExecutor:
|
||||||
else:
|
else:
|
||||||
self._backend = backend
|
self._backend = backend
|
||||||
|
|
||||||
|
@property
|
||||||
|
def backend(self) -> Backend:
|
||||||
|
return self._backend
|
||||||
|
|
||||||
def execute(self, action: str) -> None:
|
def execute(self, action: str) -> None:
|
||||||
"""Execute a named action. Silently ignores unknown action names."""
|
"""Execute a named action. Silently ignores unknown action names."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -86,11 +95,21 @@ class ActionExecutor:
|
||||||
pyautogui.scroll(3)
|
pyautogui.scroll(3)
|
||||||
elif action == "scroll_down":
|
elif action == "scroll_down":
|
||||||
pyautogui.scroll(-3)
|
pyautogui.scroll(-3)
|
||||||
|
elif action == "scroll_left":
|
||||||
|
pyautogui.hscroll(-3)
|
||||||
|
elif action == "scroll_right":
|
||||||
|
pyautogui.hscroll(3)
|
||||||
elif action == "key_enter":
|
elif action == "key_enter":
|
||||||
pyautogui.press("enter")
|
pyautogui.press("enter")
|
||||||
elif action == "key_escape":
|
elif action == "key_escape":
|
||||||
pyautogui.press("escape")
|
pyautogui.press("escape")
|
||||||
elif action == "key_space":
|
elif action == "key_space":
|
||||||
pyautogui.press("space")
|
pyautogui.press("space")
|
||||||
|
elif action == "key_ctrl_z":
|
||||||
|
pyautogui.hotkey("ctrl", "z")
|
||||||
|
elif action == "key_ctrl_c":
|
||||||
|
pyautogui.hotkey("ctrl", "c")
|
||||||
|
elif action == "key_ctrl_v":
|
||||||
|
pyautogui.hotkey("ctrl", "v")
|
||||||
else:
|
else:
|
||||||
logger.warning("pyautogui: unknown action %r", action)
|
logger.warning("pyautogui: unknown action %r", action)
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,11 @@ DEFAULT_MAPPINGS: dict[str, str] = {
|
||||||
"head_tilt_right": "scroll_down",
|
"head_tilt_right": "scroll_down",
|
||||||
"open_palm": "key_space",
|
"open_palm": "key_space",
|
||||||
"pinch": "drag_start",
|
"pinch": "drag_start",
|
||||||
|
"fist": "key_ctrl_z",
|
||||||
|
"gaze_left": "scroll_left",
|
||||||
|
"gaze_right": "scroll_right",
|
||||||
|
"gaze_up": "scroll_up",
|
||||||
|
"gaze_down": "scroll_down",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,12 @@ def _camera_loop() -> None:
|
||||||
if action:
|
if action:
|
||||||
_executor.execute(action)
|
_executor.execute(action)
|
||||||
|
|
||||||
|
gaze = gaze_est.estimate(face)
|
||||||
|
if gaze.label != "center":
|
||||||
|
action = _mapper.map(f"gaze_{gaze.label}")
|
||||||
|
if action:
|
||||||
|
_executor.execute(action)
|
||||||
|
|
||||||
hands = hands_det.detect(rgb)
|
hands = hands_det.detect(rgb)
|
||||||
if hands:
|
if hands:
|
||||||
gesture = hand_det.detect(hands[0].points)
|
gesture = hand_det.detect(hands[0].points)
|
||||||
|
|
@ -119,7 +125,7 @@ app.add_middleware(
|
||||||
def status() -> dict[str, Any]:
|
def status() -> dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"running": _running,
|
"running": _running,
|
||||||
"backend": _executor._backend,
|
"backend": _executor.backend,
|
||||||
"config": {
|
"config": {
|
||||||
"camera_device": _config.camera_device,
|
"camera_device": _config.camera_device,
|
||||||
"blink_threshold": _config.blink_threshold,
|
"blink_threshold": _config.blink_threshold,
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue