mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-04-14 10:07:39 +08:00
Updted tests mostly for the spec overview
This commit is contained in:
parent
f54a170361
commit
cf4bae6d3e
@ -108,7 +108,14 @@ Tests in `test_generators.py` use the `snapshot` fixture from `conftest.py`.
|
||||
- `src/timer_config.cpp` — timer groups and channels
|
||||
- `default.px4board` — Kconfig board settings (serial labels, RC, GPS, drivers)
|
||||
- `nuttx-config/include/board.h` — flow control GPIO definitions
|
||||
- `init/rc.board_sensors` — sensor driver start commands
|
||||
- `init/rc.board_sensors` — sensor driver start commands; comments immediately
|
||||
preceding a sensor start line are parsed for port labels (e.g.
|
||||
`# External compass on GPS1/I2C1:` → `port_label: 'GPS1'` on that sensor entry);
|
||||
power monitor drivers (INA226/INA228/INA238) are also captured in
|
||||
`sensor_bus_info['power_monitor']`
|
||||
- `src/i2c.cpp` — authoritative I2C bus routing:
|
||||
`initI2CBusExternal(N)` = external connector; `initI2CBusInternal(N)` = on-board only.
|
||||
When present, stored in `i2c_bus_config` and enables the detailed per-bus I2C output.
|
||||
|
||||
- **Metadata JSON** in `metadata/` caches parsed data (`*_data.json`) and
|
||||
wizard-supplied overrides (`*_wizard.json`). Wizard data persists across runs
|
||||
@ -139,3 +146,11 @@ Tests in `test_generators.py` use the `snapshot` fixture from `conftest.py`.
|
||||
1. All existing tests must pass: run `pytest` from `docs/scripts/fc_doc_generator/`
|
||||
2. New functionality must have new tests (unit tests and/or snapshot tests as appropriate)
|
||||
3. Update this `CLAUDE.md` if the change affects how to run the script, the architecture, extension patterns, or conventions
|
||||
4. **Wizard JSON backward compatibility** — `metadata/*_wizard.json` files are persisted user
|
||||
data. A new tool version must work correctly with an old wizard JSON:
|
||||
- **Adding** a new field to `_WIZARD_CACHE_FIELDS`: safe — missing keys are read via
|
||||
`cached.get(...)` which returns `None`, triggering re-prompting.
|
||||
- **Renaming** an existing field or **changing its structure** (e.g. the shape of
|
||||
`i2c_buses_wizard` entries): **breaking change** — old data is silently lost or
|
||||
misinterpreted. This must be explicitly flagged in the plan and requires a migration
|
||||
strategy (e.g. read both old and new key names, or add a one-time upgrade step).
|
||||
|
||||
@ -829,6 +829,10 @@ _SENSOR_CHIP_NAMES = {
|
||||
# OSD chips
|
||||
'ATXXXX': 'AT7456E',
|
||||
# MSP_OSD is a protocol adapter, not a chip — intentionally omitted
|
||||
# Power monitors
|
||||
'INA226': 'INA226',
|
||||
'INA228': 'INA228',
|
||||
'INA238': 'INA238',
|
||||
}
|
||||
|
||||
# CONFIG_DRIVERS_<CATEGORY> prefix → sensor type key
|
||||
@ -863,6 +867,10 @@ _CHIP_TO_CATEGORY: dict[str, str] = {
|
||||
'MMC5983MA': 'mag', 'VCM1193L': 'mag', 'IIS2MDC': 'mag',
|
||||
# OSD
|
||||
'ATXXXX': 'osd',
|
||||
# Power monitors
|
||||
'INA226': 'power_monitor',
|
||||
'INA228': 'power_monitor',
|
||||
'INA238': 'power_monitor',
|
||||
}
|
||||
|
||||
|
||||
@ -949,7 +957,7 @@ def parse_rc_board_sensors(board_path: Path) -> dict:
|
||||
chip_key = m.group(1).upper() # e.g. 'ms5611' → 'MS5611'
|
||||
category = _CHIP_TO_CATEGORY.get(chip_key)
|
||||
display_name = _SENSOR_CHIP_NAMES.get(chip_key)
|
||||
if category and display_name and display_name not in result[category]:
|
||||
if category and display_name and category in result and display_name not in result[category]:
|
||||
result[category].append(display_name)
|
||||
|
||||
return result
|
||||
@ -992,6 +1000,32 @@ def _parse_sensor_line(line: str) -> dict | None:
|
||||
}
|
||||
|
||||
|
||||
def _extract_port_label_from_comment(comment: str | None) -> str | None:
|
||||
"""Extract a port label from a comment preceding a sensor start command.
|
||||
|
||||
Looks for GPS port labels first (GPS1, GPS2), then TELEM labels (TELEM1),
|
||||
then numbered I2C port labels (I2C1, I2C2). Returns the first match, or None.
|
||||
|
||||
Examples:
|
||||
"External compass on GPS1/I2C1: ..." → "GPS1"
|
||||
"External sensors on I2C1" → "I2C1"
|
||||
"Internal magnetometer on I2C" → None (no digit suffix)
|
||||
"""
|
||||
if not comment:
|
||||
return None
|
||||
m = re.search(r'\bGPS(\d+)\b', comment, re.IGNORECASE)
|
||||
if m:
|
||||
return f'GPS{m.group(1)}'
|
||||
m = re.search(r'\bTELEM(\d+)\b', comment, re.IGNORECASE)
|
||||
if m:
|
||||
return f'TELEM{m.group(1)}'
|
||||
# Numbered I2C port (I2C1, I2C2, ...) — a digit suffix distinguishes port name from generic "on I2C"
|
||||
m = re.search(r'\bI2C(\d+)\b', comment, re.IGNORECASE)
|
||||
if m:
|
||||
return f'I2C{m.group(1)}'
|
||||
return None
|
||||
|
||||
|
||||
def parse_rc_board_sensor_bus(board_path: Path) -> dict:
|
||||
"""Parse sensor bus type and number from init/rc.board_sensors.
|
||||
|
||||
@ -1003,13 +1037,19 @@ def parse_rc_board_sensor_bus(board_path: Path) -> dict:
|
||||
the same chip on different buses (e.g. internal vs external IST8310):
|
||||
{
|
||||
'imu': [{'name': ..., 'bus_type': 'SPI'|'I2C'|None,
|
||||
'bus_num': int|None, 'external': bool}],
|
||||
'bus_num': int|None, 'external': bool,
|
||||
'port_label': str|None}],
|
||||
'baro': [...],
|
||||
'mag': [...],
|
||||
'osd': [],
|
||||
}
|
||||
port_label is auto-detected from the comment immediately preceding the
|
||||
sensor start command (e.g. "# External compass on GPS1/I2C1:" → "GPS1").
|
||||
power_monitor entries (INA226/INA228/INA238) are included in a
|
||||
``power_monitor`` category alongside the standard sensor categories.
|
||||
"""
|
||||
result: dict[str, list] = {k: [] for k in _DRIVER_CATEGORY_MAP.values()}
|
||||
result['power_monitor'] = []
|
||||
seen: set = set()
|
||||
|
||||
rc_sensors = board_path / 'init' / 'rc.board_sensors'
|
||||
@ -1018,34 +1058,46 @@ def parse_rc_board_sensor_bus(board_path: Path) -> dict:
|
||||
|
||||
variant_depth = 0 # depth counter for hwtypecmp if-blocks (lines inside are skipped)
|
||||
other_depth = 0 # depth of other if-blocks (still counted to match fi correctly)
|
||||
last_comment: str | None = None
|
||||
|
||||
text = rc_sensors.read_text(errors='ignore')
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#') or line.startswith('//'):
|
||||
if not line:
|
||||
last_comment = None
|
||||
continue
|
||||
if line.startswith('#') or line.startswith('//'):
|
||||
last_comment = line.lstrip('#/').strip()
|
||||
continue
|
||||
|
||||
# Track if/fi structure to skip variant-specific blocks
|
||||
if re.match(r'^if\s+ver\s+hwtypecmp\b', line):
|
||||
variant_depth += 1
|
||||
last_comment = None
|
||||
continue
|
||||
if re.match(r'^if\b', line):
|
||||
if variant_depth > 0:
|
||||
other_depth += 1 # nested block inside a variant block
|
||||
last_comment = None
|
||||
continue
|
||||
if line == 'fi':
|
||||
if other_depth > 0:
|
||||
other_depth -= 1
|
||||
elif variant_depth > 0:
|
||||
variant_depth -= 1
|
||||
last_comment = None
|
||||
continue
|
||||
if line in ('then', 'else', 'do'):
|
||||
last_comment = None
|
||||
continue
|
||||
|
||||
if variant_depth > 0:
|
||||
last_comment = None
|
||||
continue # skip all lines inside hwtypecmp blocks
|
||||
|
||||
entry = _parse_sensor_line(line)
|
||||
port_label = _extract_port_label_from_comment(last_comment)
|
||||
last_comment = None
|
||||
if entry is None:
|
||||
continue
|
||||
|
||||
@ -1057,6 +1109,7 @@ def parse_rc_board_sensor_bus(board_path: Path) -> dict:
|
||||
'bus_type': entry['bus_type'],
|
||||
'bus_num': entry['bus_num'],
|
||||
'external': entry['external'],
|
||||
'port_label': port_label,
|
||||
})
|
||||
|
||||
return result
|
||||
@ -1090,7 +1143,9 @@ def parse_sensor_variant_blocks(board_path: Path) -> dict:
|
||||
variant dict(s) where they were explicitly listed.
|
||||
"""
|
||||
def _empty_cat_dict() -> dict:
|
||||
return {k: [] for k in _DRIVER_CATEGORY_MAP.values()}
|
||||
d = {k: [] for k in _DRIVER_CATEGORY_MAP.values()}
|
||||
d['power_monitor'] = []
|
||||
return d
|
||||
|
||||
unconditional = _empty_cat_dict()
|
||||
variants: dict[str, dict] = {}
|
||||
@ -1104,10 +1159,15 @@ def parse_sensor_variant_blocks(board_path: Path) -> dict:
|
||||
# context_stack entries: {'type': 'hwtypecmp'|'other', 'codes': [...], 'in_else': bool}
|
||||
context_stack: list[dict] = []
|
||||
|
||||
last_comment: str | None = None
|
||||
text = rc_sensors.read_text(errors='ignore')
|
||||
for line in text.splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#') or line.startswith('//'):
|
||||
if not line:
|
||||
last_comment = None
|
||||
continue
|
||||
if line.startswith('#') or line.startswith('//'):
|
||||
last_comment = line.lstrip('#/').strip()
|
||||
continue
|
||||
|
||||
# Shell control flow
|
||||
@ -1115,22 +1175,29 @@ def parse_sensor_variant_blocks(board_path: Path) -> dict:
|
||||
if m_htc:
|
||||
codes = m_htc.group(1).split()
|
||||
context_stack.append({'type': 'hwtypecmp', 'codes': codes, 'in_else': False})
|
||||
last_comment = None
|
||||
continue
|
||||
if re.match(r'^if\b', line):
|
||||
context_stack.append({'type': 'other', 'codes': [], 'in_else': False})
|
||||
last_comment = None
|
||||
continue
|
||||
if line == 'else':
|
||||
if context_stack and context_stack[-1]['type'] == 'hwtypecmp':
|
||||
context_stack[-1]['in_else'] = True
|
||||
last_comment = None
|
||||
continue
|
||||
if line == 'fi':
|
||||
if context_stack:
|
||||
context_stack.pop()
|
||||
last_comment = None
|
||||
continue
|
||||
if line in ('then', 'do'):
|
||||
last_comment = None
|
||||
continue
|
||||
|
||||
entry = _parse_sensor_line(line)
|
||||
port_label = _extract_port_label_from_comment(last_comment)
|
||||
last_comment = None
|
||||
if entry is None:
|
||||
continue
|
||||
|
||||
@ -1149,6 +1216,7 @@ def parse_sensor_variant_blocks(board_path: Path) -> dict:
|
||||
'bus_type': entry['bus_type'],
|
||||
'bus_num': entry['bus_num'],
|
||||
'external': entry['external'],
|
||||
'port_label': port_label,
|
||||
})
|
||||
elif htc_ctx['in_else']:
|
||||
# else branch → store under __other__
|
||||
@ -1165,6 +1233,7 @@ def parse_sensor_variant_blocks(board_path: Path) -> dict:
|
||||
'bus_type': entry['bus_type'],
|
||||
'bus_num': entry['bus_num'],
|
||||
'external': entry['external'],
|
||||
'port_label': port_label,
|
||||
})
|
||||
else:
|
||||
# Inside a specific variant block — add to each matching code
|
||||
@ -1180,6 +1249,7 @@ def parse_sensor_variant_blocks(board_path: Path) -> dict:
|
||||
'bus_type': entry['bus_type'],
|
||||
'bus_num': entry['bus_num'],
|
||||
'external': entry['external'],
|
||||
'port_label': port_label,
|
||||
})
|
||||
|
||||
has_variants = bool(variants)
|
||||
@ -1253,6 +1323,38 @@ def parse_interface_config(board_path: Path) -> dict:
|
||||
}
|
||||
|
||||
|
||||
def parse_i2c_bus_config(board_path: Path) -> dict:
|
||||
"""Parse authoritative I2C bus routing from src/i2c.cpp.
|
||||
|
||||
Reads ``initI2CBusExternal(N)`` and ``initI2CBusInternal(N)`` entries to
|
||||
determine which I2C buses go to external connectors (user-accessible) and
|
||||
which are wired only to on-board chips (not externally accessible).
|
||||
|
||||
Returns
|
||||
-------
|
||||
{
|
||||
'external': [1, 2, 4], # bus numbers routed to external connectors
|
||||
'internal': [3], # bus numbers wired only to on-board chips
|
||||
}
|
||||
Returns an empty dict ``{}`` when ``src/i2c.cpp`` is absent or contains no
|
||||
recognised entries.
|
||||
"""
|
||||
i2c_cpp = board_path / 'src' / 'i2c.cpp'
|
||||
if not i2c_cpp.exists():
|
||||
return {}
|
||||
|
||||
text = i2c_cpp.read_text(errors='ignore')
|
||||
external = sorted(
|
||||
int(m) for m in re.findall(r'initI2CBusExternal\s*\(\s*(\d+)\s*\)', text)
|
||||
)
|
||||
internal = sorted(
|
||||
int(m) for m in re.findall(r'initI2CBusInternal\s*\(\s*(\d+)\s*\)', text)
|
||||
)
|
||||
if not external and not internal:
|
||||
return {}
|
||||
return {'external': external, 'internal': internal}
|
||||
|
||||
|
||||
def compute_groups(timers: list, channels: list) -> list:
|
||||
"""Group PWM output channels by their shared hardware timer.
|
||||
|
||||
@ -2283,6 +2385,20 @@ def _fmt_list(items: list) -> str:
|
||||
return ', '.join(str(i) for i in items if i)
|
||||
|
||||
|
||||
def _fmt_list_counted(items: list) -> str:
|
||||
"""Format a list of names, collapsing consecutive duplicates with a count suffix.
|
||||
|
||||
E.g. ['MS5611', 'MS5611'] → 'MS5611 (2)'
|
||||
['ICM-42688P', 'MS5611'] → 'ICM-42688P, MS5611'
|
||||
"""
|
||||
from collections import Counter
|
||||
counts = Counter(str(i) for i in items if i)
|
||||
return ', '.join(
|
||||
f'{name} ({n})' if n > 1 else name
|
||||
for name, n in counts.items()
|
||||
)
|
||||
|
||||
|
||||
def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
"""Generate a '## Specifications {#specifications}' markdown section.
|
||||
|
||||
@ -2371,18 +2487,14 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
"""
|
||||
wizard_val = ow.get(wizard_key)
|
||||
if wizard_val:
|
||||
display = _fmt_list(wizard_val) if isinstance(wizard_val, list) else wizard_val
|
||||
display = _fmt_list_counted(wizard_val) if isinstance(wizard_val, list) else wizard_val
|
||||
sensor_line = f'- **{label}**: {display}'
|
||||
# Note any detected drivers not confirmed by the wizard — may be unnecessary firmware bloat
|
||||
confirmed_upper = {w.upper() for w in (wizard_val if isinstance(wizard_val, list) else [wizard_val])}
|
||||
detected_raw = entry.get(driver_key) or []
|
||||
unused = [d for d in detected_raw if d.upper() not in confirmed_upper]
|
||||
if unused:
|
||||
sensor_line += (
|
||||
f'\n<!-- TODO: These {label.lower()} drivers are built into the firmware'
|
||||
f' but were not confirmed as installed: {", ".join(unused)}.'
|
||||
f' Remove from board config if not needed. -->'
|
||||
)
|
||||
sensor_line += f' <!-- TODO: Unnecessary driver(s) in firmware?: {", ".join(unused)}. -->'
|
||||
return sensor_line
|
||||
|
||||
if _has_variants:
|
||||
@ -2451,41 +2563,36 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
return None
|
||||
|
||||
# --- Processor ---
|
||||
lines.append('### Processor')
|
||||
lines.append('')
|
||||
lines.append('- **Processor**')
|
||||
chip_model = entry.get('chip_model')
|
||||
specs = CHIP_SPECS.get(chip_model) if chip_model else None
|
||||
if chip_model and specs:
|
||||
lines.append(
|
||||
f'- **Main FMU Processor**: {chip_model} '
|
||||
f' - **Main FMU Processor**: {chip_model} '
|
||||
f'(32-bit Arm\u00ae {specs["core"]}, {specs["mhz"]} MHz, '
|
||||
f'{specs["flash"]} flash, {specs["ram"]} RAM)'
|
||||
)
|
||||
elif chip_model:
|
||||
lines.append(f'- **Main FMU Processor**: {chip_model}')
|
||||
lines.append(f' - **Main FMU Processor**: {chip_model}')
|
||||
else:
|
||||
lines.append('- **Main FMU Processor**: TODO: chip model')
|
||||
lines.append(' - **Main FMU Processor**: TODO: chip model')
|
||||
|
||||
if entry.get('has_io_board'):
|
||||
lines.append(f'- **IO Processor**: {_IO_PROC_SPEC}')
|
||||
lines.append('')
|
||||
lines.append(f' - **IO Processor**: {_IO_PROC_SPEC}')
|
||||
|
||||
# --- Sensors ---
|
||||
lines.append('### Sensors')
|
||||
lines.append('')
|
||||
lines.append('- **Sensors**')
|
||||
imu_line = _resolve_sensor('imu', 'sensor_imu_drivers', 'IMU', category='imu', required=True)
|
||||
baro_line = _resolve_sensor('baro', 'sensor_baro_drivers', 'Barometer', category='baro', required=True)
|
||||
mag_line = _resolve_sensor('mag', 'sensor_mag_drivers', 'Magnetometer', category='mag', required=True)
|
||||
osd_line = _resolve_sensor('osd', 'sensor_osd_drivers', 'OSD', category='osd', required=False)
|
||||
if imu_line: lines.append(imu_line)
|
||||
if baro_line: lines.append(baro_line)
|
||||
if mag_line: lines.append(mag_line)
|
||||
if osd_line: lines.append(osd_line)
|
||||
lines.append('')
|
||||
if imu_line: lines.append(' ' + imu_line)
|
||||
if baro_line: lines.append(' ' + baro_line)
|
||||
if mag_line: lines.append(' ' + mag_line)
|
||||
if osd_line: lines.append(' ' + osd_line)
|
||||
|
||||
# --- Interfaces ---
|
||||
lines.append('### Interfaces')
|
||||
lines.append('')
|
||||
lines.append('- **Interfaces**')
|
||||
# Use group-derived count — DIRECT_PWM_OUTPUT_CHANNELS in board_config.h
|
||||
# counts all FMU timer channels, which matches groups since
|
||||
# initIOTimerChannelCapture entries are also counted as outputs.
|
||||
@ -2494,15 +2601,18 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
io_out = entry.get('io_outputs', 0) or 0
|
||||
has_io = entry.get('has_io_board', False)
|
||||
if has_io and io_out:
|
||||
lines.append(f'- **PWM outputs**: {fmu_out + io_out} ({fmu_out} FMU + {io_out} IO)')
|
||||
lines.append(f' - **PWM outputs**: {fmu_out + io_out} ({fmu_out} FMU + {io_out} IO)')
|
||||
elif fmu_out:
|
||||
lines.append(f'- **PWM outputs**: {fmu_out} (FMU)')
|
||||
lines.append(f' - **PWM outputs**: {fmu_out} (FMU)')
|
||||
|
||||
num_serial = len(entry.get('serial_ports') or [])
|
||||
if num_serial:
|
||||
lines.append(f'- **Serial ports**: {num_serial}')
|
||||
lines.append(f' - **Serial ports**: {num_serial}')
|
||||
|
||||
_BUS_CAT_LABEL = {'imu': 'IMU', 'baro': 'barometer', 'mag': 'magnetometer', 'osd': 'OSD'}
|
||||
_BUS_CAT_LABEL = {
|
||||
'imu': 'IMU', 'baro': 'barometer', 'mag': 'magnetometer',
|
||||
'osd': 'OSD', 'power_monitor': 'power monitor',
|
||||
}
|
||||
# For bus sub-bullets, use unconditional sensors when variant data is available,
|
||||
# so variant-specific sensors don't appear in the "always-present" bus list.
|
||||
_bus_sub_source = _sv_unconditional if _has_variants else (entry.get('sensor_bus_info') or {})
|
||||
@ -2512,21 +2622,121 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
for e in entries if e.get('bus_type') == 'I2C']
|
||||
|
||||
num_i2c = entry.get('num_i2c_buses', 0)
|
||||
lines.append(f'- **I2C ports**: {num_i2c}' if num_i2c else '- **I2C ports**: TODO: number of I2C ports')
|
||||
|
||||
# Build per-bus maps from sensor data
|
||||
_ext_bus_map = {} # bus_num(int) → [(cat, sensor_name), ...]
|
||||
_int_sensors = [] # (cat, sensor_name) for internal I2C sensors (bus_num=None)
|
||||
for _cat, _e in i2c_sensors:
|
||||
_i2c_parts = ['external' if _e.get('external') else 'internal']
|
||||
if _e.get('bus_num') is not None:
|
||||
_i2c_parts.append(f'bus {_e["bus_num"]}')
|
||||
lines.append(f' - {_e["name"]} ({_BUS_CAT_LABEL[_cat]}, {", ".join(_i2c_parts)})')
|
||||
_bn = _e.get('bus_num')
|
||||
if _e.get('external') and _bn is not None:
|
||||
_ext_bus_map.setdefault(_bn, []).append((_cat, _e['name']))
|
||||
elif not _e.get('external'):
|
||||
_int_sensors.append((_cat, _e['name']))
|
||||
_n_ext = len(_ext_bus_map)
|
||||
|
||||
_i2c_wizard = entry.get('i2c_buses_wizard') or []
|
||||
_wizard_by_bus = {b['bus_num']: b['label'] for b in _i2c_wizard}
|
||||
# Auto-detected port labels from source comments (e.g. "# External compass on GPS1/I2C1:")
|
||||
_src_port_labels: dict[int, str] = {}
|
||||
for _cat_entries in _bus_sub_source.values():
|
||||
for _e in _cat_entries:
|
||||
_bn2 = _e.get('bus_num')
|
||||
_pl = _e.get('port_label')
|
||||
if _pl and _bn2 is not None and _bn2 not in _src_port_labels:
|
||||
_src_port_labels[_bn2] = _pl
|
||||
|
||||
_i2c_bus_config = entry.get('i2c_bus_config') # {'external': [...], 'internal': [...]} or None
|
||||
|
||||
if _i2c_bus_config:
|
||||
# DETAILED PATH: authoritative per-bus routing from src/i2c.cpp
|
||||
_ext_buses_cfg = set(_i2c_bus_config.get('external', []))
|
||||
_int_buses_cfg = set(_i2c_bus_config.get('internal', []))
|
||||
_all_buses = sorted(_ext_buses_cfg | _int_buses_cfg)
|
||||
_n_ext_cfg = len(_ext_buses_cfg)
|
||||
_n_int_cfg = len(_int_buses_cfg)
|
||||
_total = len(_all_buses)
|
||||
|
||||
if not _total:
|
||||
lines.append(' - **I2C ports**: TODO: number of I2C ports')
|
||||
elif _n_ext_cfg > 0 and _n_int_cfg > 0:
|
||||
lines.append(f' - **I2C ports**: {_total} ({_n_ext_cfg} external, {_n_int_cfg} internal)')
|
||||
elif _n_ext_cfg > 0:
|
||||
lines.append(f' - **I2C ports**: {_total} ({_n_ext_cfg} external)')
|
||||
else:
|
||||
lines.append(f' - **I2C ports**: {_total} ({_n_int_cfg} internal)')
|
||||
|
||||
for _bn in _all_buses:
|
||||
_is_ext = _bn in _ext_buses_cfg
|
||||
_routing = 'external' if _is_ext else 'internal'
|
||||
_label = _wizard_by_bus.get(_bn) or _src_port_labels.get(_bn)
|
||||
_paren = f'{_routing}, {_label}' if _label else _routing
|
||||
if _is_ext:
|
||||
_sensor_parts = _ext_bus_map.get(_bn, [])
|
||||
if _sensor_parts:
|
||||
_notes = ', '.join(
|
||||
f'{_nm} ({_BUS_CAT_LABEL.get(_c, _c)})' for _c, _nm in _sensor_parts
|
||||
)
|
||||
_bus_str = f'I2C{_bn} ({_paren}): {_notes}'
|
||||
if _label and re.match(r'^GPS\d+$', _label):
|
||||
_bus_str += ' — on GPS connector'
|
||||
else:
|
||||
_bus_str = f'I2C{_bn} ({_paren}): free (no sensor detected)'
|
||||
else:
|
||||
# Internal bus — try to attach known internal sensors when only one internal bus
|
||||
_bus_str = f'I2C{_bn} ({_paren})'
|
||||
lines.append(f' - {_bus_str}')
|
||||
|
||||
# For internal buses: internal sensors have bus_num=None so we can't map per-bus.
|
||||
# When exactly one internal bus, retrofit sensor list onto its bullet.
|
||||
# Otherwise add a grouped summary line.
|
||||
if _int_sensors:
|
||||
_notes = ', '.join(f'{_nm} ({_BUS_CAT_LABEL.get(_c, _c)})' for _c, _nm in _int_sensors)
|
||||
if _n_int_cfg == 1:
|
||||
_int_bn = sorted(_int_buses_cfg)[0]
|
||||
_label = _wizard_by_bus.get(_int_bn) or _src_port_labels.get(_int_bn)
|
||||
_paren = f'internal, {_label}' if _label else 'internal'
|
||||
_target = f' - I2C{_int_bn} (internal)'
|
||||
for _i in range(len(lines) - 1, -1, -1):
|
||||
if lines[_i] == _target:
|
||||
lines[_i] = f' - I2C{_int_bn} ({_paren}): {_notes}'
|
||||
break
|
||||
else:
|
||||
lines.append(f' - Internal buses: {_notes}')
|
||||
|
||||
else:
|
||||
# FALLBACK PATH: existing format when i2c_bus_config is absent
|
||||
if not num_i2c:
|
||||
lines.append(' - **I2C ports**: TODO: number of I2C ports')
|
||||
elif _n_ext > 0 and _n_ext <= num_i2c:
|
||||
_n_int = num_i2c - _n_ext
|
||||
lines.append(f' - **I2C ports**: {num_i2c} ({_n_int} internal, {_n_ext} external)')
|
||||
else:
|
||||
lines.append(f' - **I2C ports**: {num_i2c}')
|
||||
|
||||
_all_ext_bus_nums = sorted(set(list(_ext_bus_map) + list(_wizard_by_bus)))
|
||||
for _bn in _all_ext_bus_nums:
|
||||
_label = _wizard_by_bus.get(_bn) or _src_port_labels.get(_bn) or f'TODO: label for I2C bus {_bn}'
|
||||
_sensor_parts = _ext_bus_map.get(_bn, [])
|
||||
_bus_str = f'{_label} (I2C{_bn}, external)'
|
||||
if _sensor_parts:
|
||||
_notes = ', '.join(f'{_nm} ({_BUS_CAT_LABEL[_c]})' for _c, _nm in _sensor_parts)
|
||||
_bus_str += f': {_notes}'
|
||||
if re.match(r'^GPS\d+$', _label):
|
||||
_bus_str += ' — on GPS connector'
|
||||
lines.append(f' - {_bus_str}')
|
||||
|
||||
if _int_sensors:
|
||||
_notes = ', '.join(f'{_nm} ({_BUS_CAT_LABEL[_c]})' for _c, _nm in _int_sensors)
|
||||
lines.append(f' - Internal: {_notes}')
|
||||
|
||||
num_spi = entry.get('num_spi_buses', 0)
|
||||
lines.append(f'- **SPI buses**: {num_spi}' if num_spi else '- **SPI buses**: TODO: number of SPI buses')
|
||||
lines.append(f' - **SPI buses**: {num_spi}' if num_spi else ' - **SPI buses**: TODO: number of SPI buses')
|
||||
for _cat, _e in spi_sensors:
|
||||
lines.append(f' - {_e["name"]} ({_BUS_CAT_LABEL[_cat]})')
|
||||
lines.append(f' - {_e["name"]} ({_BUS_CAT_LABEL[_cat]})')
|
||||
|
||||
num_can = entry.get('num_can_buses', 0)
|
||||
if num_can:
|
||||
lines.append(f'- **CAN buses**: {num_can}')
|
||||
lines.append(f' - **CAN buses**: {num_can}')
|
||||
# CAN omitted (no TODO) when not detected — not all FCs have CAN
|
||||
|
||||
if entry.get('has_usb'):
|
||||
@ -2534,14 +2744,23 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
connectors = ow.get('usb_connectors') or (
|
||||
[ow['usb_connector']] if ow.get('usb_connector') else []
|
||||
)
|
||||
if connectors:
|
||||
labels = ow.get('usb_labels') or []
|
||||
non_default_labels = any(l != 'USB' for l in labels)
|
||||
if non_default_labels:
|
||||
# Pair each label with its connector (if available)
|
||||
parts = []
|
||||
for i, lbl in enumerate(labels):
|
||||
conn = connectors[i] if i < len(connectors) else None
|
||||
parts.append(f'{lbl} ({conn})' if conn else lbl)
|
||||
lines.append(f' - **USB**: {", ".join(parts)}')
|
||||
elif connectors:
|
||||
from collections import Counter
|
||||
parts = [f'{c} (\u00d72)' if n > 1 else c for c, n in Counter(connectors).items()]
|
||||
lines.append(f'- **USB**: {", ".join(parts)}')
|
||||
lines.append(f' - **USB**: {", ".join(parts)}')
|
||||
else:
|
||||
lines.append('- **USB**: Yes')
|
||||
lines.append(' - **USB**: Yes')
|
||||
else:
|
||||
lines.append('- **USB**: TODO: confirm USB connector type')
|
||||
lines.append(' - **USB**: TODO: confirm USB connector type')
|
||||
|
||||
# RC input
|
||||
has_rc = entry.get('has_rc_input', False)
|
||||
@ -2551,34 +2770,32 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
if has_io:
|
||||
# IO processor handles multi-protocol RC; list all supported protocols
|
||||
if has_ppm:
|
||||
lines.append('- **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST, PPM (via IO)')
|
||||
lines.append(' - **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST, PPM (via IO)')
|
||||
else:
|
||||
lines.append('- **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST (via IO)')
|
||||
lines.append(' - **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST (via IO)')
|
||||
elif has_common_rc and has_ppm:
|
||||
lines.append('- **RC input**: DSM/SRXL2, S.Bus/CPPM')
|
||||
lines.append(' - **RC input**: DSM/SRXL2, S.Bus/CPPM')
|
||||
elif has_common_rc:
|
||||
lines.append('- **RC input**: DSM/SRXL2, S.Bus')
|
||||
lines.append(' - **RC input**: DSM/SRXL2, S.Bus')
|
||||
elif has_ppm:
|
||||
lines.append('- **RC input**: PPM')
|
||||
lines.append(' - **RC input**: PPM')
|
||||
elif has_rc:
|
||||
lines.append('- **RC input**: Yes')
|
||||
lines.append(' - **RC input**: Yes')
|
||||
|
||||
# Analog battery monitoring
|
||||
num_power = entry.get('num_power_inputs', 0) or 0
|
||||
if num_power:
|
||||
lines.append(f'- **Analog battery inputs**: {num_power}')
|
||||
lines.append(f' - **Analog battery inputs**: {num_power}')
|
||||
|
||||
# Additional analog inputs (wizard, always TODO if not set)
|
||||
num_adc = ow.get('num_additional_adc_inputs')
|
||||
if num_adc is not None:
|
||||
lines.append(f'- **Additional analog inputs**: {num_adc}')
|
||||
lines.append(f' - **Additional analog inputs**: {num_adc}')
|
||||
else:
|
||||
lines.append('- **Additional analog inputs**: TODO: number of additional analog inputs')
|
||||
lines.append(' - **Additional analog inputs**: TODO: number of additional analog inputs')
|
||||
|
||||
if entry.get('has_ethernet'):
|
||||
lines.append('- **Ethernet**: Yes')
|
||||
|
||||
lines.append('')
|
||||
lines.append(' - **Ethernet**: Yes')
|
||||
|
||||
# --- Electrical Data (always emitted; TODO when wizard data absent) ---
|
||||
min_v = ow.get('min_voltage')
|
||||
@ -2586,17 +2803,49 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
# Backward compat: old JSONs may have voltage_range as a single string
|
||||
if not min_v and not max_v and ow.get('voltage_range'):
|
||||
max_v = ow['voltage_range']
|
||||
lines.append('### Electrical Data')
|
||||
lines.append('')
|
||||
lines.append('- **Electrical Data**')
|
||||
if min_v and max_v:
|
||||
lines.append(f'- **Input voltage**: {min_v}\u2013{max_v}')
|
||||
lines.append(f' - **Operating voltage**: {min_v}\u2013{max_v} (V)')
|
||||
elif max_v:
|
||||
lines.append(f'- **Input voltage**: TODO\u2013{max_v}')
|
||||
lines.append(f' - **Operating voltage**: TODO\u2013{max_v} (V)')
|
||||
elif min_v:
|
||||
lines.append(f'- **Input voltage**: {min_v}\u2013TODO')
|
||||
lines.append(f' - **Operating voltage**: {min_v}\u2013TODO (V)')
|
||||
else:
|
||||
lines.append('- **Input voltage**: TODO: supply voltage range')
|
||||
lines.append('')
|
||||
lines.append(' - **Operating voltage**: TODO: supply voltage range')
|
||||
# USB power input line
|
||||
if ow.get('usb_powers_fc') and ow.get('usb_pwr_min_v') and ow.get('usb_pwr_max_v'):
|
||||
lines.append(f' - **USB-C power input**: {ow["usb_pwr_min_v"]}\u2013{ow["usb_pwr_max_v"]} (V)')
|
||||
elif ow.get('usb_powers_fc'):
|
||||
lines.append(' - **USB-C power input**: TODO: USB-C voltage range')
|
||||
# Servo rail line
|
||||
if ow.get('has_servo_rail'):
|
||||
srv_max = ow.get('servo_rail_max_v')
|
||||
_srv_suffix = ' (servo rail must be separately powered and does not power the autopilot)'
|
||||
if srv_max:
|
||||
try:
|
||||
_srv_high = float(str(srv_max).rstrip('Vv').strip()) > 12
|
||||
except ValueError:
|
||||
_srv_high = False
|
||||
if _srv_high:
|
||||
lines.append(f' - **Servo rail**: High-voltage capable, up to {srv_max} (V){_srv_suffix}')
|
||||
else:
|
||||
lines.append(f' - **Servo rail**: Up to {srv_max} (V){_srv_suffix}')
|
||||
else:
|
||||
lines.append(f' - **Servo rail**: TODO: servo rail max voltage{_srv_suffix}')
|
||||
# Power supply redundancy line
|
||||
_usb_pwr = ow.get('usb_powers_fc', False)
|
||||
_n_pwr = entry.get('num_power_inputs', 1)
|
||||
_total_sources = _n_pwr + (1 if _usb_pwr else 0)
|
||||
if _total_sources >= 2:
|
||||
_pwr_wizard = entry.get('power_ports_wizard') or []
|
||||
if _pwr_wizard:
|
||||
_src_names = [p['label'] for p in _pwr_wizard]
|
||||
else:
|
||||
_src_names = [f'POWER{i+1}' if _n_pwr > 1 else 'POWER' for i in range(_n_pwr)]
|
||||
if _usb_pwr:
|
||||
_src_names.append('USB-C')
|
||||
_level = {2: 'Dual', 3: 'Triple'}.get(_total_sources, f'{_total_sources}-way')
|
||||
lines.append(f' - **Power supply**: {_level} redundant ({", ".join(_src_names)})')
|
||||
|
||||
# --- Mechanical Data (always emitted; TODO when wizard data absent) ---
|
||||
dim_w = ow.get('width_mm')
|
||||
@ -2610,27 +2859,25 @@ def generate_specifications_section(board_key: str, entry: dict) -> str:
|
||||
elif len(_parts) == 2:
|
||||
dim_w, dim_l = _parts[0], _parts[1]
|
||||
weight = ow.get('weight_g')
|
||||
lines.append('### Mechanical Data')
|
||||
lines.append('')
|
||||
lines.append('- **Mechanical Data**')
|
||||
if any([dim_w, dim_l, dim_h]):
|
||||
_dim_parts = [str(p) if p else 'TODO' for p in [dim_w, dim_l, dim_h]]
|
||||
lines.append(f'- **Dimensions**: {" \u00d7 ".join(_dim_parts)} mm')
|
||||
lines.append(f' - **Dimensions**: {" \u00d7 ".join(_dim_parts)} (mm)')
|
||||
else:
|
||||
lines.append('- **Dimensions**: TODO: dimensions (mm)')
|
||||
lines.append(f'- **Weight**: {weight} g' if weight is not None else '- **Weight**: TODO: weight (g)')
|
||||
lines.append('')
|
||||
lines.append(' - **Dimensions**: TODO: dimensions (mm)')
|
||||
lines.append(f' - **Weight**: {weight} (g)' if weight is not None else ' - **Weight**: TODO: weight (g)')
|
||||
|
||||
# --- Other (conditional, omit section if nothing applies) ---
|
||||
other_lines = []
|
||||
if entry.get('has_sd_card'):
|
||||
other_lines.append('- **SD card**: Yes')
|
||||
other_lines.append(' - **SD card**: Yes')
|
||||
if entry.get('has_heater'):
|
||||
other_lines.append('- **Onboard heater**: Yes')
|
||||
other_lines.append(' - **Onboard heater**: Yes')
|
||||
if other_lines:
|
||||
lines.append('### Other')
|
||||
lines.append('')
|
||||
lines.append('- **Other**')
|
||||
lines.extend(other_lines)
|
||||
lines.append('')
|
||||
|
||||
lines.append('')
|
||||
|
||||
# Source data comment for traceability / future re-runs
|
||||
source_data = {
|
||||
@ -2875,7 +3122,7 @@ def apply_sections_to_docs(data: dict, sections: list = None, doc_filter: str =
|
||||
if wizard_doc_data:
|
||||
entry = dict(entry) # shallow copy — don't mutate shared data
|
||||
for _wf in ('rc_ports_wizard', 'gps_ports_wizard',
|
||||
'power_ports_wizard', 'overview_wizard'):
|
||||
'power_ports_wizard', 'overview_wizard', 'i2c_buses_wizard'):
|
||||
if wizard_doc_data.get(_wf) is not None:
|
||||
entry[_wf] = wizard_doc_data[_wf]
|
||||
|
||||
@ -2931,7 +3178,7 @@ def _wizard_prompt(label: str, default: str = "", required: bool = False) -> str
|
||||
_WIZARD_CACHE_FIELDS = (
|
||||
"manufacturer", "product", "board", "fmu_version", "since_version",
|
||||
"manufacturer_url", "rc_ports_wizard", "gps_ports_wizard", "power_ports_wizard",
|
||||
"overview_wizard",
|
||||
"overview_wizard", "i2c_buses_wizard",
|
||||
)
|
||||
|
||||
|
||||
@ -3215,6 +3462,76 @@ def _run_wizard(args) -> None:
|
||||
else:
|
||||
args.gps_ports_wizard = None
|
||||
|
||||
# --- I2C external bus labels ---
|
||||
_bus_info_raw = _rc_entry.get('sensor_bus_info') or {}
|
||||
_ext_buses = {} # bus_num → [(cat, name)] for buses with detected external sensors
|
||||
_detected_labels = {} # bus_num → port_label auto-detected from source comments
|
||||
for _cat, _entries in _bus_info_raw.items():
|
||||
for _e in _entries:
|
||||
_bn = _e.get('bus_num')
|
||||
if _e.get('bus_type') == 'I2C' and _e.get('external') and _bn is not None:
|
||||
_ext_buses.setdefault(_bn, []).append((_cat, _e['name']))
|
||||
if _e.get('port_label') and _bn is not None and _bn not in _detected_labels:
|
||||
_detected_labels[_bn] = _e['port_label']
|
||||
|
||||
_STANDARD_I2C_LABELS = ['GPS1', 'GPS2', 'I2C', 'I2C1', 'I2C2', 'POWER']
|
||||
_cached_i2c = {b['bus_num']: b['label'] for b in (cached.get('i2c_buses_wizard') or [])}
|
||||
_num_i2c = _rc_entry.get('num_i2c_buses', 0)
|
||||
# Load authoritative bus routing from i2c.cpp if available in cached entry or board path
|
||||
_wiz_i2c_cfg = _rc_entry.get('i2c_bus_config') or {}
|
||||
if not _wiz_i2c_cfg and args.board:
|
||||
_wiz_i2c_cfg = parse_i2c_bus_config(BOARDS / args.board)
|
||||
_wiz_ext_buses_cfg = set(_wiz_i2c_cfg.get('external', []))
|
||||
_wiz_int_buses_cfg = set(_wiz_i2c_cfg.get('internal', []))
|
||||
|
||||
i2c_buses_wizard = []
|
||||
# Ask about all enabled I2C buses (not just sensor-bearing ones) so standalone
|
||||
# external ports (e.g. I2C3, I2C4) can be labelled even without detected sensors.
|
||||
_buses_to_ask = sorted(set(
|
||||
list(_ext_buses) + list(_cached_i2c) +
|
||||
list(_wiz_ext_buses_cfg) + list(_wiz_int_buses_cfg) +
|
||||
(list(range(1, _num_i2c + 1)) if _num_i2c > 0 else [])
|
||||
))
|
||||
if _buses_to_ask:
|
||||
print()
|
||||
if _ext_buses:
|
||||
print(" External I2C buses detected from source:")
|
||||
for _bn in sorted(_ext_buses):
|
||||
_sensor_desc = ', '.join(_nm for _, _nm in _ext_buses[_bn])
|
||||
_pl = _detected_labels.get(_bn)
|
||||
_pl_str = f' [detected port: {_pl}]' if _pl else ''
|
||||
print(f" I2C bus {_bn}: {_sensor_desc}{_pl_str}")
|
||||
if _wiz_i2c_cfg:
|
||||
if _wiz_ext_buses_cfg:
|
||||
print(f" External buses (from src/i2c.cpp): {sorted(_wiz_ext_buses_cfg)}")
|
||||
if _wiz_int_buses_cfg:
|
||||
print(f" Internal buses — no external connector: {sorted(_wiz_int_buses_cfg)}")
|
||||
elif _num_i2c > 0:
|
||||
print(f" I2C buses enabled: 1–{_num_i2c}")
|
||||
print(f" Common port labels: {', '.join(_STANDARD_I2C_LABELS)}")
|
||||
print()
|
||||
for _bn in _buses_to_ask:
|
||||
_cached_label = _cached_i2c.get(_bn, '')
|
||||
_detected_label = _detected_labels.get(_bn, '')
|
||||
_default = _cached_label or _detected_label or ''
|
||||
_hint = f' [detected: {_detected_label}]' if _detected_label and not _cached_label else ''
|
||||
_sensor_info = ', '.join(_nm for _, _nm in _ext_buses.get(_bn, []))
|
||||
_sensor_hint = f' (sensor: {_sensor_info})' if _sensor_info else ''
|
||||
if _bn in _wiz_int_buses_cfg:
|
||||
_routing_note = ' [internal bus — no external connector, Enter to skip]'
|
||||
elif _bn in _wiz_ext_buses_cfg and not _sensor_info:
|
||||
_routing_note = ' [external — may be a free connector]'
|
||||
else:
|
||||
_routing_note = ''
|
||||
_label = _wizard_prompt(
|
||||
f" Label for I2C bus {_bn} port as printed on board{_sensor_hint}{_hint}{_routing_note}",
|
||||
default=_default
|
||||
)
|
||||
if _label:
|
||||
i2c_buses_wizard.append({'bus_num': _bn, 'label': _label})
|
||||
|
||||
args.i2c_buses_wizard = i2c_buses_wizard or None
|
||||
|
||||
# --- Sensors & Physical Specs (for ## Key Features section) ---
|
||||
print()
|
||||
print(" === Sensors & Physical Specs ===")
|
||||
@ -3279,7 +3596,8 @@ def _run_wizard(args) -> None:
|
||||
print()
|
||||
|
||||
def _wizard_list(prompt_label: str, detected: list = None,
|
||||
cached_list: list = None) -> list:
|
||||
cached_list: list = None, max_items: int = None,
|
||||
item_hints: list = None) -> list:
|
||||
"""Prompt for a multi-value list; blank entry ends input.
|
||||
|
||||
Displays detected drivers as a numbered menu (1-N). Each prompt
|
||||
@ -3288,6 +3606,9 @@ def _run_wizard(args) -> None:
|
||||
|
||||
Entering 0 stops immediately without applying any cache fallback,
|
||||
letting the user clear previously entered items.
|
||||
|
||||
max_items: stop after this many items (no trailing open-ended prompt).
|
||||
item_hints: per-item hint strings shown in parentheses before the default.
|
||||
"""
|
||||
items = []
|
||||
explicitly_done = False
|
||||
@ -3302,9 +3623,14 @@ def _run_wizard(args) -> None:
|
||||
print(" (press Enter to accept each default, or 0 to clear all)")
|
||||
idx = 1
|
||||
while True:
|
||||
# Stop once we've reached the maximum allowed items
|
||||
if max_items is not None and idx > max_items:
|
||||
break
|
||||
# Show the corresponding cached entry as the default for this position
|
||||
cached_default = str(cached_list[idx - 1]) if cached_list and idx <= len(cached_list) else ""
|
||||
val = _wizard_prompt(f" item {idx}", default=cached_default)
|
||||
hint = item_hints[idx - 1] if item_hints and idx <= len(item_hints) else None
|
||||
label = f" item {idx} ({hint})" if hint else f" item {idx}"
|
||||
val = _wizard_prompt(label, default=cached_default)
|
||||
# 0 = explicit stop (skips cache fallback)
|
||||
if val == "0":
|
||||
explicitly_done = True
|
||||
@ -3336,25 +3662,52 @@ def _run_wizard(args) -> None:
|
||||
# Pre-populate from cached split fields, or parse old dimensions_mm string
|
||||
_dims_cached = cached_ov.get('dimensions_mm') or ''
|
||||
_dims_parts = re.split(r'\s*[xX]\s*', _dims_cached.strip()) if _dims_cached else []
|
||||
dim_w = _wizard_prompt("Width mm (optional, e.g. 38.0)",
|
||||
dim_w = _wizard_prompt("Width (mm)",
|
||||
default=str(cached_ov.get('width_mm') or '')
|
||||
or (_dims_parts[0] if len(_dims_parts) > 0 else ''))
|
||||
dim_l = _wizard_prompt("Length mm (optional, e.g. 38.0)",
|
||||
dim_l = _wizard_prompt("Length (mm)",
|
||||
default=str(cached_ov.get('length_mm') or '')
|
||||
or (_dims_parts[1] if len(_dims_parts) > 1 else ''))
|
||||
dim_h = _wizard_prompt("Height mm (optional, e.g. 7.0)",
|
||||
dim_h = _wizard_prompt("Height (mm)",
|
||||
default=str(cached_ov.get('height_mm') or '')
|
||||
or (_dims_parts[2] if len(_dims_parts) > 2 else ''))
|
||||
weight_raw = _wizard_prompt("Weight g (optional)",
|
||||
weight_raw = _wizard_prompt("Weight (g)",
|
||||
default=str(cached_ov.get('weight_g') or ''))
|
||||
min_v = _wizard_prompt("Minimum input voltage (optional, e.g. 4.5V, 7V)",
|
||||
min_v = _wizard_prompt("Minimum input voltage (V)",
|
||||
default=cached_ov.get('min_voltage') or '')
|
||||
max_v = _wizard_prompt("Maximum input voltage (optional, e.g. 17.5V, 42V)",
|
||||
max_v = _wizard_prompt("Maximum input voltage (V)",
|
||||
default=cached_ov.get('max_voltage') or '')
|
||||
_usb_pwr_cached = 'y' if cached_ov.get('usb_powers_fc') else 'n'
|
||||
_usb_pwr_raw = _wizard_prompt("Can USB power the flight controller? (y/n)",
|
||||
default=_usb_pwr_cached)
|
||||
usb_powers_fc = _usb_pwr_raw.strip().lower() in ('y', 'yes')
|
||||
if usb_powers_fc:
|
||||
usb_pwr_min = _wizard_prompt("USB minimum power voltage (V)",
|
||||
default=cached_ov.get('usb_pwr_min_v') or '4.75')
|
||||
usb_pwr_max = _wizard_prompt("USB maximum power voltage (V)",
|
||||
default=cached_ov.get('usb_pwr_max_v') or '5.25')
|
||||
else:
|
||||
usb_pwr_min = None
|
||||
usb_pwr_max = None
|
||||
_servo_cached = 'y' if cached_ov.get('has_servo_rail', True) else 'n'
|
||||
_servo_raw = _wizard_prompt("Does the board have a servo/output rail? (y/n)",
|
||||
default=_servo_cached)
|
||||
has_servo_rail = _servo_raw.strip().lower() in ('y', 'yes')
|
||||
if has_servo_rail:
|
||||
servo_rail_max_v = _wizard_prompt("Maximum servo rail voltage (V)",
|
||||
default=cached_ov.get('servo_rail_max_v') or '')
|
||||
else:
|
||||
servo_rail_max_v = None
|
||||
_usb_cached = (cached_ov.get('usb_connectors')
|
||||
or ([cached_ov['usb_connector']] if cached_ov.get('usb_connector') else None))
|
||||
usb_list = _wizard_list("USB connector type(s) (e.g. USB-C, Micro-USB)",
|
||||
[], cached_list=_usb_cached)
|
||||
_usb_labels_default = (cached_ov.get('usb_labels')
|
||||
or (['USB'] * len(usb_list) if usb_list else ['USB']))
|
||||
usb_labels = _wizard_list("USB port label(s) (e.g. USB, USB2)",
|
||||
[], cached_list=_usb_labels_default,
|
||||
max_items=len(usb_list) if usb_list else None,
|
||||
item_hints=usb_list or None)
|
||||
adc_raw = _wizard_prompt("Additional analog inputs beyond battery monitoring (optional, integer)",
|
||||
default=str(cached_ov.get('num_additional_adc_inputs') or ''))
|
||||
|
||||
@ -3383,7 +3736,13 @@ def _run_wizard(args) -> None:
|
||||
'weight_g': weight_g,
|
||||
'min_voltage': min_v or None,
|
||||
'max_voltage': max_v or None,
|
||||
'usb_powers_fc': usb_powers_fc,
|
||||
'usb_pwr_min_v': usb_pwr_min or None,
|
||||
'usb_pwr_max_v': usb_pwr_max or None,
|
||||
'has_servo_rail': has_servo_rail,
|
||||
'servo_rail_max_v': servo_rail_max_v or None,
|
||||
'usb_connectors': usb_list or None,
|
||||
'usb_labels': usb_labels or None,
|
||||
'num_additional_adc_inputs': num_additional_adc,
|
||||
'sensor_variant_labels': variant_labels or None,
|
||||
}
|
||||
@ -3641,6 +4000,7 @@ def gather_board_data() -> dict:
|
||||
if _chip not in sensor_cfg[_cat]:
|
||||
sensor_cfg[_cat].append(_chip)
|
||||
iface_cfg = parse_interface_config(board_path)
|
||||
i2c_bus_cfg = parse_i2c_bus_config(board_path)
|
||||
sensor_bus_cfg = parse_rc_board_sensor_bus(board_path)
|
||||
sensor_variant_info = parse_sensor_variant_blocks(board_path)
|
||||
|
||||
@ -3701,7 +4061,11 @@ def gather_board_data() -> dict:
|
||||
"sensor_osd_drivers": sensor_cfg.get('osd', []),
|
||||
"sensor_bus_info": sensor_bus_cfg,
|
||||
"sensor_variant_info": sensor_variant_info,
|
||||
"num_i2c_buses": iface_cfg.get('num_i2c_buses', 0),
|
||||
"i2c_bus_config": i2c_bus_cfg or None,
|
||||
"num_i2c_buses": (
|
||||
len(i2c_bus_cfg.get('external', [])) + len(i2c_bus_cfg.get('internal', []))
|
||||
if i2c_bus_cfg else iface_cfg.get('num_i2c_buses', 0)
|
||||
),
|
||||
"num_spi_buses": iface_cfg.get('num_spi_buses', 0),
|
||||
"num_can_buses": iface_cfg.get('num_can_buses', 0),
|
||||
"has_usb": iface_cfg.get('has_usb', False),
|
||||
@ -3776,7 +4140,8 @@ def create_stub_doc(manufacturer: str, product: str,
|
||||
rc_ports_wizard: list = None,
|
||||
gps_ports_wizard: list = None,
|
||||
power_ports_wizard: list = None,
|
||||
overview_wizard: dict = None) -> Path | None:
|
||||
overview_wizard: dict = None,
|
||||
i2c_buses_wizard: list = None) -> Path | None:
|
||||
"""Create a stub FC documentation page in docs/en/flight_controller/."""
|
||||
# Auto-discover board key first so fmu_version can be inferred before the
|
||||
# filename is built (px4/fmu-v6x → fmu_version="fmu-v6x" → filename suffix).
|
||||
@ -3801,7 +4166,7 @@ def create_stub_doc(manufacturer: str, product: str,
|
||||
if not entry:
|
||||
print(f"WARNING: board '{board_key}' not found in parsed data; PWM section will be empty.")
|
||||
|
||||
if rc_ports_wizard or gps_ports_wizard or power_ports_wizard or overview_wizard:
|
||||
if rc_ports_wizard or gps_ports_wizard or power_ports_wizard or overview_wizard or i2c_buses_wizard:
|
||||
entry = dict(entry) # shallow copy — don't mutate cached data
|
||||
if rc_ports_wizard:
|
||||
entry['rc_ports_wizard'] = rc_ports_wizard
|
||||
@ -3811,6 +4176,8 @@ def create_stub_doc(manufacturer: str, product: str,
|
||||
entry['power_ports_wizard'] = power_ports_wizard
|
||||
if overview_wizard:
|
||||
entry['overview_wizard'] = overview_wizard
|
||||
if i2c_buses_wizard:
|
||||
entry['i2c_buses_wizard'] = i2c_buses_wizard
|
||||
|
||||
# Infer manufacturer URL from existing docs when not explicitly provided
|
||||
if not manufacturer_url:
|
||||
@ -3834,6 +4201,7 @@ def create_stub_doc(manufacturer: str, product: str,
|
||||
'gps_ports_wizard': gps_ports_wizard,
|
||||
'power_ports_wizard': power_ports_wizard,
|
||||
'overview_wizard': overview_wizard,
|
||||
'i2c_buses_wizard': i2c_buses_wizard,
|
||||
}
|
||||
if any(v is not None for v in wizard_data.values()):
|
||||
content = _embed_wizard_data_comment(content, wizard_data)
|
||||
@ -4447,7 +4815,8 @@ if __name__ == "__main__":
|
||||
rc_ports_wizard=getattr(args, 'rc_ports_wizard', None) or _wiz_cache.get('rc_ports_wizard'),
|
||||
gps_ports_wizard=getattr(args, 'gps_ports_wizard', None) or _wiz_cache.get('gps_ports_wizard'),
|
||||
power_ports_wizard=getattr(args, 'power_ports_wizard', None) or _wiz_cache.get('power_ports_wizard'),
|
||||
overview_wizard=getattr(args, 'overview_wizard', None) or _wiz_cache.get('overview_wizard'))
|
||||
overview_wizard=getattr(args, 'overview_wizard', None) or _wiz_cache.get('overview_wizard'),
|
||||
i2c_buses_wizard=getattr(args, 'i2c_buses_wizard', None) or _wiz_cache.get('i2c_buses_wizard'))
|
||||
else:
|
||||
data = gather_board_data()
|
||||
meta_dir = args.output_dir / "metadata" if args.output_dir else METADATA_DIR
|
||||
|
||||
@ -8,5 +8,7 @@
|
||||
board_adc start
|
||||
icm42688p -s start
|
||||
ms5611 -s start
|
||||
# Internal compass
|
||||
ist8310 -I start
|
||||
# External compass on GPS1/I2C1: standard compass puck
|
||||
ist8310 -X -b 1 start
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,59 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 2
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: 7.0–55.0 (V)
|
||||
- **USB-C power input**: 4.75–5.25 (V)
|
||||
- **Servo rail**: High-voltage capable, up to 36 (V) (servo rail must be separately powered and does not power the autopilot)
|
||||
- **Power supply**: Triple redundant (POWER1, POWER2, USB-C)
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: 46 × 64 × 22 (mm)
|
||||
- **Weight**: 75.0 (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 0,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": {
|
||||
"min_voltage": "7.0",
|
||||
"max_voltage": "55.0",
|
||||
"usb_powers_fc": true,
|
||||
"usb_pwr_min_v": "4.75",
|
||||
"usb_pwr_max_v": "5.25",
|
||||
"has_servo_rail": true,
|
||||
"servo_rail_max_v": "36",
|
||||
"width_mm": "46",
|
||||
"length_mm": "64",
|
||||
"height_mm": "22",
|
||||
"weight_g": 75.0
|
||||
}
|
||||
}
|
||||
-->
|
||||
@ -0,0 +1,55 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 2
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: 4.3–5.4 (V)
|
||||
- **USB-C power input**: 4.75–5.25 (V)
|
||||
- **Servo rail**: Up to 10.5 (V) (servo rail must be separately powered and does not power the autopilot)
|
||||
- **Power supply**: Triple redundant (POWER1, POWER2, USB-C)
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 0,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": {
|
||||
"min_voltage": "4.3",
|
||||
"max_voltage": "5.4",
|
||||
"usb_powers_fc": true,
|
||||
"usb_pwr_min_v": "4.75",
|
||||
"usb_pwr_max_v": "5.25",
|
||||
"has_servo_rail": true,
|
||||
"servo_rail_max_v": "10.5"
|
||||
}
|
||||
}
|
||||
-->
|
||||
@ -0,0 +1,48 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: 4 (3 external, 1 internal)
|
||||
- I2C1 (external, GPS1): IST8310 (magnetometer) — on GPS connector
|
||||
- I2C2 (external, POWER): INA226 (power monitor)
|
||||
- I2C3 (internal): BMP388 (barometer)
|
||||
- I2C4 (external): free (no sensor detected)
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 4,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": null
|
||||
}
|
||||
-->
|
||||
@ -0,0 +1,46 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: 4 (3 internal, 1 external)
|
||||
- GPS1 (I2C1, external): IST8310 (magnetometer) — on GPS connector
|
||||
- Internal: BMP388 (barometer), RM3100 (magnetometer)
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 4,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": null
|
||||
}
|
||||
-->
|
||||
@ -0,0 +1,46 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: 4 (3 internal, 1 external)
|
||||
- TODO: label for I2C bus 1 (I2C1, external): IST8310 (magnetometer)
|
||||
- Internal: BMP388 (barometer), RM3100 (magnetometer)
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 4,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": null
|
||||
}
|
||||
-->
|
||||
@ -1,33 +1,24 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: IST8310 (I2C, internal), IST8310 (I2C, bus 1, external)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **I2C ports**: 4
|
||||
- IST8310 (magnetometer, internal)
|
||||
- IST8310 (magnetometer, external, bus 1)
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: IST8310 (I2C, internal), IST8310 (I2C, bus 1, external)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: 4 (3 internal, 1 external)
|
||||
- TODO: label for I2C bus 1 (I2C1, external): IST8310 (magnetometer)
|
||||
- Internal: IST8310 (magnetometer)
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -1,34 +1,25 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **IO Processor**: STM32F100 (32-bit Arm® Cortex®-M3, 24 MHz, 8KB SRAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **PWM outputs**: 8 (0 FMU + 8 IO)
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST (via IO)
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **IO Processor**: STM32F100 (32-bit Arm® Cortex®-M3, 24 MHz, 8KB SRAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **PWM outputs**: 8 (0 FMU + 8 IO)
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST (via IO)
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -1,34 +1,25 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **IO Processor**: STM32F100 (32-bit Arm® Cortex®-M3, 24 MHz, 8KB SRAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **PWM outputs**: 8 (0 FMU + 8 IO)
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST, PPM (via IO)
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **IO Processor**: STM32F100 (32-bit Arm® Cortex®-M3, 24 MHz, 8KB SRAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **PWM outputs**: 8 (0 FMU + 8 IO)
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **RC input**: SBUS, DSM/DSMX, ST24, SUMD, CRSF, GHST, PPM (via IO)
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -1,31 +1,22 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: ICM-42688P
|
||||
- **Barometer**: MS5611
|
||||
- **Magnetometer**: IST8310
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 3
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: ICM-42688P
|
||||
- **Barometer**: MS5611
|
||||
- **Magnetometer**: IST8310
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 3
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -0,0 +1,51 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: MS5611 (2)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 2
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 0,
|
||||
"num_spi_buses": 2,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [
|
||||
"MS5611"
|
||||
],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": {
|
||||
"baro": [
|
||||
"MS5611",
|
||||
"MS5611"
|
||||
]
|
||||
}
|
||||
}
|
||||
-->
|
||||
@ -1,33 +1,24 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: ICM-42688P (SPI)
|
||||
- **Barometer**: MS5611 (SPI)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 4
|
||||
- ICM-42688P (IMU)
|
||||
- MS5611 (barometer)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: ICM-42688P (SPI)
|
||||
- **Barometer**: MS5611 (SPI)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 4
|
||||
- ICM-42688P (IMU)
|
||||
- MS5611 (barometer)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -0,0 +1,62 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: ICM-20649, ICM-20689, BMI088 <!-- TODO: Unnecessary driver(s) in firmware?: ICM-20948, ICM-42688P. -->
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: RM3100 <!-- TODO: Unnecessary driver(s) in firmware?: IST8310. -->
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 3
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 0,
|
||||
"num_spi_buses": 3,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": false,
|
||||
"sensor_drivers": {
|
||||
"imu": [
|
||||
"ICM-20649",
|
||||
"ICM-20689",
|
||||
"BMI088",
|
||||
"ICM-20948",
|
||||
"ICM-42688P"
|
||||
],
|
||||
"baro": [],
|
||||
"mag": [
|
||||
"RM3100",
|
||||
"IST8310"
|
||||
],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": {
|
||||
"imu": [
|
||||
"ICM-20649",
|
||||
"ICM-20689",
|
||||
"BMI088"
|
||||
],
|
||||
"mag": [
|
||||
"RM3100"
|
||||
]
|
||||
}
|
||||
}
|
||||
-->
|
||||
@ -0,0 +1,53 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: USB (USB-C), Debug USB (Micro-USB)
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 0,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": true,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": {
|
||||
"usb_connectors": [
|
||||
"USB-C",
|
||||
"Micro-USB"
|
||||
],
|
||||
"usb_labels": [
|
||||
"USB",
|
||||
"Debug USB"
|
||||
]
|
||||
}
|
||||
}
|
||||
-->
|
||||
@ -0,0 +1,51 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: TODO: list imu(s)
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: TODO: number of SPI buses
|
||||
- **USB**: USB-C
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
"board": "test/fixture",
|
||||
"chip_model": "STM32H753",
|
||||
"has_io_board": false,
|
||||
"total_outputs": null,
|
||||
"fmu_servo_outputs": 0,
|
||||
"io_outputs": 0,
|
||||
"has_sd_card": false,
|
||||
"has_ethernet": false,
|
||||
"has_heater": false,
|
||||
"num_i2c_buses": 0,
|
||||
"num_spi_buses": 0,
|
||||
"num_can_buses": 0,
|
||||
"has_usb": true,
|
||||
"sensor_drivers": {
|
||||
"imu": [],
|
||||
"baro": [],
|
||||
"mag": [],
|
||||
"osd": []
|
||||
},
|
||||
"overview_wizard": {
|
||||
"usb_connectors": [
|
||||
"USB-C"
|
||||
],
|
||||
"usb_labels": [
|
||||
"USB"
|
||||
]
|
||||
}
|
||||
}
|
||||
-->
|
||||
@ -1,35 +1,26 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: ICM-42688P (SPI); BMI088 (SPI, hardware revision Rev 1.0), ICM-20602 (SPI, hardware revision Rev 1.1)
|
||||
- **Barometer**: MS5611 (SPI)
|
||||
- **Magnetometer**: IST8310 (I2C, internal), IST8310 (I2C, bus 1, external)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **I2C ports**: 4
|
||||
- IST8310 (magnetometer, internal)
|
||||
- IST8310 (magnetometer, external, bus 1)
|
||||
- **SPI buses**: 5
|
||||
- ICM-42688P (IMU)
|
||||
- MS5611 (barometer)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: ICM-42688P (SPI); BMI088 (SPI, hardware revision Rev 1.0), ICM-20602 (SPI, hardware revision Rev 1.1)
|
||||
- **Barometer**: MS5611 (SPI)
|
||||
- **Magnetometer**: IST8310 (I2C, internal), IST8310 (I2C, bus 1, external)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: 4 (3 internal, 1 external)
|
||||
- TODO: label for I2C bus 1 (I2C1, external): IST8310 (magnetometer)
|
||||
- Internal: IST8310 (magnetometer)
|
||||
- **SPI buses**: 5
|
||||
- ICM-42688P (IMU)
|
||||
- MS5611 (barometer)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -1,35 +1,26 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: ICM-42688P (SPI); BMI088 (SPI, variant HW000001), ICM-20602 (SPI, other variants)
|
||||
- **Barometer**: MS5611 (SPI)
|
||||
- **Magnetometer**: IST8310 (I2C, internal), IST8310 (I2C, bus 1, external)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **I2C ports**: 4
|
||||
- IST8310 (magnetometer, internal)
|
||||
- IST8310 (magnetometer, external, bus 1)
|
||||
- **SPI buses**: 5
|
||||
- ICM-42688P (IMU)
|
||||
- MS5611 (barometer)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: ICM-42688P (SPI); BMI088 (SPI, variant HW000001), ICM-20602 (SPI, other variants)
|
||||
- **Barometer**: MS5611 (SPI)
|
||||
- **Magnetometer**: IST8310 (I2C, internal), IST8310 (I2C, bus 1, external)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: 4 (3 internal, 1 external)
|
||||
- TODO: label for I2C bus 1 (I2C1, external): IST8310 (magnetometer)
|
||||
- Internal: IST8310 (magnetometer)
|
||||
- **SPI buses**: 5
|
||||
- ICM-42688P (IMU)
|
||||
- MS5611 (barometer)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -1,32 +1,23 @@
|
||||
## Specifications {#specifications}
|
||||
|
||||
### Processor
|
||||
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
|
||||
### Sensors
|
||||
|
||||
- **IMU**: ICM-42688P
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
|
||||
### Interfaces
|
||||
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 3
|
||||
- ICM-42688P (IMU)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
|
||||
### Electrical Data
|
||||
|
||||
- **Input voltage**: TODO: supply voltage range
|
||||
|
||||
### Mechanical Data
|
||||
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
- **Processor**
|
||||
- **Main FMU Processor**: STM32H753 (32-bit Arm® Cortex®-M7, 480 MHz, 2MB flash, 1MB RAM)
|
||||
- **Sensors**
|
||||
- **IMU**: ICM-42688P
|
||||
- **Barometer**: TODO: list barometer(s)
|
||||
- **Magnetometer**: TODO: list magnetometer(s)
|
||||
- **Interfaces**
|
||||
- **I2C ports**: TODO: number of I2C ports
|
||||
- **SPI buses**: 3
|
||||
- ICM-42688P (IMU)
|
||||
- **USB**: TODO: confirm USB connector type
|
||||
- **Analog battery inputs**: 1
|
||||
- **Additional analog inputs**: TODO: number of additional analog inputs
|
||||
- **Electrical Data**
|
||||
- **Operating voltage**: TODO: supply voltage range
|
||||
- **Mechanical Data**
|
||||
- **Dimensions**: TODO: dimensions (mm)
|
||||
- **Weight**: TODO: weight (g)
|
||||
|
||||
<!-- overview-source-data
|
||||
{
|
||||
|
||||
@ -721,6 +721,25 @@ def _spec_base_entry():
|
||||
}
|
||||
|
||||
|
||||
def _entry_i2c_detailed():
|
||||
"""Entry with mixed internal/external I2C sensors and a wizard label for the external bus."""
|
||||
e = _spec_base_entry()
|
||||
e.update({
|
||||
"num_i2c_buses": 4,
|
||||
"sensor_bus_info": {
|
||||
"imu": [],
|
||||
"baro": [{"name": "BMP388", "bus_type": "I2C", "bus_num": None, "external": False}],
|
||||
"mag": [
|
||||
{"name": "IST8310", "bus_type": "I2C", "bus_num": 1, "external": True},
|
||||
{"name": "RM3100", "bus_type": "I2C", "bus_num": None, "external": False},
|
||||
],
|
||||
"osd": [],
|
||||
},
|
||||
"i2c_buses_wizard": [{"bus_num": 1, "label": "GPS1"}],
|
||||
})
|
||||
return e
|
||||
|
||||
|
||||
class TestGenerateSpecificationsSection:
|
||||
def test_spi_sensors_with_subbullets(self, snapshot):
|
||||
"""SPI-flagged sensors produce bus annotations and SPI sub-bullets."""
|
||||
@ -786,6 +805,211 @@ class TestGenerateSpecificationsSection:
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_wizard_override.md", result)
|
||||
|
||||
def test_duplicate_sensors_counted(self, snapshot):
|
||||
"""Duplicate sensor names in wizard list collapse to Name (N) format."""
|
||||
entry = _spec_base_entry()
|
||||
entry.update({
|
||||
"num_spi_buses": 2,
|
||||
"sensor_baro_drivers": ["MS5611"],
|
||||
"overview_wizard": {
|
||||
"baro": ["MS5611", "MS5611"],
|
||||
},
|
||||
})
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_sensor_counted.md", result)
|
||||
|
||||
def test_unused_driver_todo_inline(self, snapshot):
|
||||
"""Drivers detected but not in wizard list get an inline TODO comment."""
|
||||
entry = _spec_base_entry()
|
||||
entry.update({
|
||||
"num_spi_buses": 3,
|
||||
"sensor_imu_drivers": ["ICM-20649", "ICM-20689", "BMI088", "ICM-20948", "ICM-42688P"],
|
||||
"sensor_mag_drivers": ["RM3100", "IST8310"],
|
||||
"overview_wizard": {
|
||||
"imu": ["ICM-20649", "ICM-20689", "BMI088"],
|
||||
"mag": ["RM3100"],
|
||||
},
|
||||
})
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_unused_driver_todo.md", result)
|
||||
|
||||
def test_electrical_full(self, snapshot):
|
||||
"""Full electrical data: operating voltage, USB power, high-voltage servo rail, triple redundancy."""
|
||||
entry = _spec_base_entry()
|
||||
entry.update({
|
||||
"num_power_inputs": 2,
|
||||
"has_redundant_power": True,
|
||||
"power_ports_wizard": [
|
||||
{"label": "POWER1", "connector_type": "6-pin Molex CLIK-Mate"},
|
||||
{"label": "POWER2", "connector_type": "6-pin Molex CLIK-Mate"},
|
||||
],
|
||||
"overview_wizard": {
|
||||
"min_voltage": "7.0",
|
||||
"max_voltage": "55.0",
|
||||
"usb_powers_fc": True,
|
||||
"usb_pwr_min_v": "4.75",
|
||||
"usb_pwr_max_v": "5.25",
|
||||
"has_servo_rail": True,
|
||||
"servo_rail_max_v": "36",
|
||||
"width_mm": "46",
|
||||
"length_mm": "64",
|
||||
"height_mm": "22",
|
||||
"weight_g": 75.0,
|
||||
},
|
||||
})
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_electrical_full.md", result)
|
||||
|
||||
def test_electrical_normal_servo_rail(self, snapshot):
|
||||
"""Servo rail ≤12 V gets 'Up to' label (not High-voltage)."""
|
||||
entry = _spec_base_entry()
|
||||
entry.update({
|
||||
"num_power_inputs": 2,
|
||||
"has_redundant_power": True,
|
||||
"overview_wizard": {
|
||||
"min_voltage": "4.3",
|
||||
"max_voltage": "5.4",
|
||||
"usb_powers_fc": True,
|
||||
"usb_pwr_min_v": "4.75",
|
||||
"usb_pwr_max_v": "5.25",
|
||||
"has_servo_rail": True,
|
||||
"servo_rail_max_v": "10.5",
|
||||
},
|
||||
})
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_electrical_normal_servo.md", result)
|
||||
|
||||
def test_usb_multi_labels(self, snapshot):
|
||||
"""Multiple USB ports with distinct labels show Label (ConnectorType) pairs."""
|
||||
entry = _spec_base_entry()
|
||||
entry.update({
|
||||
"has_usb": True,
|
||||
"overview_wizard": {
|
||||
"usb_connectors": ["USB-C", "Micro-USB"],
|
||||
"usb_labels": ["USB", "Debug USB"],
|
||||
},
|
||||
})
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_usb_multi_labels.md", result)
|
||||
|
||||
def test_usb_single_default_label(self, snapshot):
|
||||
"""Single USB port with default label 'USB' shows connector type only."""
|
||||
entry = _spec_base_entry()
|
||||
entry.update({
|
||||
"has_usb": True,
|
||||
"overview_wizard": {
|
||||
"usb_connectors": ["USB-C"],
|
||||
"usb_labels": ["USB"],
|
||||
},
|
||||
})
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, entry)
|
||||
snapshot("spec_usb_single_label.md", result)
|
||||
|
||||
def test_i2c_external_with_label(self, snapshot):
|
||||
"""External I2C bus with wizard label shows label + sensor; internal summarised."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_detailed())
|
||||
snapshot("spec_i2c_external_label.md", result)
|
||||
|
||||
def test_i2c_external_no_label(self, snapshot):
|
||||
"""External I2C bus without wizard label shows TODO placeholder."""
|
||||
e = _entry_i2c_detailed()
|
||||
del e['i2c_buses_wizard']
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, e)
|
||||
snapshot("spec_i2c_external_no_label.md", result)
|
||||
|
||||
def test_i2c_count_line_split(self):
|
||||
"""Count line shows internal/external split when external buses are detected."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_detailed())
|
||||
assert '**I2C ports**: 4 (3 internal, 1 external)' in result
|
||||
|
||||
def test_i2c_external_sub_bullet(self):
|
||||
"""External bus sub-bullet shows wizard label, bus number, and sensor purpose."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_detailed())
|
||||
assert 'GPS1 (I2C1, external): IST8310 (magnetometer)' in result
|
||||
|
||||
def test_i2c_gps_connector_note(self):
|
||||
"""External I2C bus labelled GPS1 appends '— on GPS connector' note."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_detailed())
|
||||
assert 'GPS1 (I2C1, external): IST8310 (magnetometer) — on GPS connector' in result
|
||||
|
||||
def test_i2c_internal_summary_line(self):
|
||||
"""Internal sensors appear on a single summary sub-bullet."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_detailed())
|
||||
assert ' - Internal: BMP388 (barometer), RM3100 (magnetometer)' in result
|
||||
|
||||
def test_i2c_port_label_auto_fill_from_source(self):
|
||||
"""port_label in sensor_bus_info auto-fills label when no wizard label is set."""
|
||||
e = _entry_i2c_detailed()
|
||||
del e['i2c_buses_wizard']
|
||||
# Add port_label to the external sensor entry
|
||||
e['sensor_bus_info']['mag'][0]['port_label'] = 'GPS1'
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, e)
|
||||
# Should use auto-detected label instead of TODO placeholder
|
||||
assert 'GPS1 (I2C1, external): IST8310 (magnetometer) — on GPS connector' in result
|
||||
assert 'TODO: label for I2C bus 1' not in result
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Specifications section — I2C bus config (detailed) path
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _entry_i2c_bus_config():
|
||||
"""Entry with i2c_bus_config: buses 1/2/4 external, bus 3 internal."""
|
||||
e = _spec_base_entry()
|
||||
e.update({
|
||||
"num_i2c_buses": 4,
|
||||
"i2c_bus_config": {"external": [1, 2, 4], "internal": [3]},
|
||||
"sensor_bus_info": {
|
||||
"imu": [],
|
||||
"baro": [{"name": "BMP388", "bus_type": "I2C", "bus_num": None,
|
||||
"external": False, "port_label": None}],
|
||||
"mag": [{"name": "IST8310", "bus_type": "I2C", "bus_num": 1,
|
||||
"external": True, "port_label": None}],
|
||||
"osd": [],
|
||||
"power_monitor": [{"name": "INA226", "bus_type": "I2C", "bus_num": 2,
|
||||
"external": True, "port_label": None}],
|
||||
},
|
||||
"i2c_buses_wizard": [
|
||||
{"bus_num": 1, "label": "GPS1"},
|
||||
{"bus_num": 2, "label": "POWER"},
|
||||
],
|
||||
})
|
||||
return e
|
||||
|
||||
|
||||
class TestI2cBusConfigPath:
|
||||
def test_count_line_external_internal_split(self):
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_bus_config())
|
||||
assert '**I2C ports**: 4 (3 external, 1 internal)' in result
|
||||
|
||||
def test_free_external_bus_marked(self):
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_bus_config())
|
||||
assert 'I2C4 (external): free (no sensor detected)' in result
|
||||
|
||||
def test_external_bus_with_sensor_and_gps_note(self):
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_bus_config())
|
||||
assert 'I2C1 (external, GPS1): IST8310 (magnetometer) — on GPS connector' in result
|
||||
|
||||
def test_external_bus_with_power_monitor(self):
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_bus_config())
|
||||
assert 'I2C2 (external, POWER): INA226 (power monitor)' in result
|
||||
|
||||
def test_internal_bus_with_internal_sensor(self):
|
||||
"""Single internal bus gets the internal sensor list retrofitted onto it."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_bus_config())
|
||||
assert 'I2C3 (internal): BMP388 (barometer)' in result
|
||||
|
||||
def test_fallback_path_unchanged(self):
|
||||
"""Entry without i2c_bus_config uses existing format."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_detailed())
|
||||
assert '**I2C ports**: 4 (3 internal, 1 external)' in result
|
||||
assert 'GPS1 (I2C1, external): IST8310 (magnetometer) — on GPS connector' in result
|
||||
|
||||
def test_snapshot(self, snapshot):
|
||||
"""Full snapshot of i2c_bus_config present path."""
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _entry_i2c_bus_config())
|
||||
snapshot("spec_i2c_bus_config.md", result)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Specifications section — IO board RC input
|
||||
@ -888,7 +1112,7 @@ class TestGenerateSpecSectionVariants:
|
||||
result = fcdg.generate_specifications_section(BOARD_KEY, _spec_variant_entry())
|
||||
lines = result.splitlines()
|
||||
spi_idx = next(i for i, l in enumerate(lines) if '**SPI buses**' in l)
|
||||
subbullets = [l for l in lines[spi_idx+1:] if l.startswith(' -')]
|
||||
subbullets = [l for l in lines[spi_idx+1:] if l.startswith(' -')]
|
||||
sub_names = [l.strip('- ').split(' ')[0] for l in subbullets]
|
||||
assert 'ICM-42688P' in sub_names # unconditional IMU
|
||||
assert 'MS5611' in sub_names # unconditional baro
|
||||
|
||||
@ -466,7 +466,7 @@ class TestParseRcBoardSensorBus:
|
||||
|
||||
def test_missing_file_returns_empty(self, tmp_path):
|
||||
result = fcdg.parse_rc_board_sensor_bus(tmp_path)
|
||||
assert result == {'imu': [], 'baro': [], 'mag': [], 'osd': []}
|
||||
assert result == {'imu': [], 'baro': [], 'mag': [], 'osd': [], 'power_monitor': []}
|
||||
|
||||
def test_skips_variant_block(self, board_stm32h7_variant):
|
||||
"""Sensors inside if ver hwtypecmp blocks must not appear in sensor_bus_info."""
|
||||
@ -478,6 +478,147 @@ class TestParseRcBoardSensorBus:
|
||||
# icm42688p is unconditional → must be present
|
||||
assert 'ICM-42688P' in imu_names
|
||||
|
||||
def test_external_port_label_detected_from_comment(self, board_stm32h7_all_dshot):
|
||||
"""External sensor preceded by 'GPS1/I2C1' comment gets port_label='GPS1'."""
|
||||
result = fcdg.parse_rc_board_sensor_bus(board_stm32h7_all_dshot)
|
||||
external = [e for e in result['mag'] if e['external']]
|
||||
assert len(external) == 1
|
||||
assert external[0]['port_label'] == 'GPS1'
|
||||
|
||||
def test_internal_sensor_port_label_none(self, board_stm32h7_all_dshot):
|
||||
"""Internal sensor preceded by 'Internal compass' comment gets port_label=None."""
|
||||
result = fcdg.parse_rc_board_sensor_bus(board_stm32h7_all_dshot)
|
||||
internal = [e for e in result['mag'] if not e['external']]
|
||||
assert len(internal) == 1
|
||||
assert internal[0]['port_label'] is None
|
||||
|
||||
def test_spi_sensor_port_label_none(self, board_stm32h7_all_dshot):
|
||||
"""SPI sensor with no GPS/I2C comment gets port_label=None."""
|
||||
result = fcdg.parse_rc_board_sensor_bus(board_stm32h7_all_dshot)
|
||||
assert result['imu'][0]['port_label'] is None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# parse_i2c_bus_config
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestParseI2cBusConfig:
|
||||
def test_missing_file_returns_empty_dict(self, tmp_path):
|
||||
"""No src/i2c.cpp → empty dict."""
|
||||
assert fcdg.parse_i2c_bus_config(tmp_path) == {}
|
||||
|
||||
def test_external_buses_parsed(self, tmp_path):
|
||||
(tmp_path / 'src').mkdir(parents=True)
|
||||
(tmp_path / 'src' / 'i2c.cpp').write_text(
|
||||
'constexpr px4_i2c_bus_t buses[] = {\n'
|
||||
' initI2CBusExternal(1),\n'
|
||||
' initI2CBusExternal(2),\n'
|
||||
'};\n'
|
||||
)
|
||||
result = fcdg.parse_i2c_bus_config(tmp_path)
|
||||
assert result['external'] == [1, 2]
|
||||
assert result['internal'] == []
|
||||
|
||||
def test_internal_buses_parsed(self, tmp_path):
|
||||
(tmp_path / 'src').mkdir(parents=True)
|
||||
(tmp_path / 'src' / 'i2c.cpp').write_text(
|
||||
'constexpr px4_i2c_bus_t buses[] = {\n'
|
||||
' initI2CBusInternal(3),\n'
|
||||
'};\n'
|
||||
)
|
||||
result = fcdg.parse_i2c_bus_config(tmp_path)
|
||||
assert result['external'] == []
|
||||
assert result['internal'] == [3]
|
||||
|
||||
def test_mixed_buses_parsed_and_sorted(self, tmp_path):
|
||||
(tmp_path / 'src').mkdir(parents=True)
|
||||
(tmp_path / 'src' / 'i2c.cpp').write_text(
|
||||
'constexpr px4_i2c_bus_t buses[] = {\n'
|
||||
' initI2CBusExternal(1),\n'
|
||||
' initI2CBusExternal(2),\n'
|
||||
' initI2CBusInternal(3),\n'
|
||||
' initI2CBusExternal(4),\n'
|
||||
'};\n'
|
||||
)
|
||||
result = fcdg.parse_i2c_bus_config(tmp_path)
|
||||
assert result['external'] == [1, 2, 4]
|
||||
assert result['internal'] == [3]
|
||||
|
||||
def test_whitespace_in_args(self, tmp_path):
|
||||
(tmp_path / 'src').mkdir(parents=True)
|
||||
(tmp_path / 'src' / 'i2c.cpp').write_text(
|
||||
'initI2CBusExternal( 1 );\ninitI2CBusInternal( 2 );\n'
|
||||
)
|
||||
result = fcdg.parse_i2c_bus_config(tmp_path)
|
||||
assert 1 in result['external']
|
||||
assert 2 in result['internal']
|
||||
|
||||
def test_empty_file_returns_empty_dict(self, tmp_path):
|
||||
(tmp_path / 'src').mkdir(parents=True)
|
||||
(tmp_path / 'src' / 'i2c.cpp').write_text('// no bus entries\n')
|
||||
assert fcdg.parse_i2c_bus_config(tmp_path) == {}
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# power_monitor category in parse_rc_board_sensor_bus
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestPowerMonitorSensorBus:
|
||||
def test_ina226_classified_as_power_monitor(self, tmp_path):
|
||||
(tmp_path / 'init').mkdir(parents=True)
|
||||
(tmp_path / 'init' / 'rc.board_sensors').write_text(
|
||||
'ina226 -X -b 2 -t 1 -k start\n'
|
||||
)
|
||||
result = fcdg.parse_rc_board_sensor_bus(tmp_path)
|
||||
assert len(result['power_monitor']) == 1
|
||||
pm = result['power_monitor'][0]
|
||||
assert pm['name'] == 'INA226'
|
||||
assert pm['bus_type'] == 'I2C'
|
||||
assert pm['bus_num'] == 2
|
||||
assert pm['external'] is True
|
||||
|
||||
def test_ina228_classified_as_power_monitor(self, tmp_path):
|
||||
(tmp_path / 'init').mkdir(parents=True)
|
||||
(tmp_path / 'init' / 'rc.board_sensors').write_text(
|
||||
'ina228 -X -b 1 start\n'
|
||||
)
|
||||
result = fcdg.parse_rc_board_sensor_bus(tmp_path)
|
||||
assert result['power_monitor'][0]['name'] == 'INA228'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# _extract_port_label_from_comment
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
class TestExtractPortLabelFromComment:
|
||||
def test_gps_port_extracted(self):
|
||||
assert fcdg._extract_port_label_from_comment(
|
||||
'External compass on GPS1/I2C1: standard puck') == 'GPS1'
|
||||
|
||||
def test_gps2_extracted(self):
|
||||
assert fcdg._extract_port_label_from_comment('compass on GPS2') == 'GPS2'
|
||||
|
||||
def test_telem_extracted_when_no_gps(self):
|
||||
assert fcdg._extract_port_label_from_comment('sensor on TELEM1') == 'TELEM1'
|
||||
|
||||
def test_i2c_numbered_extracted_when_no_gps_or_telem(self):
|
||||
assert fcdg._extract_port_label_from_comment('I2C2 ist8310 magnetometer') == 'I2C2'
|
||||
|
||||
def test_i2c_without_digit_returns_none(self):
|
||||
# "Internal magnetometer on I2C" — no digit suffix
|
||||
assert fcdg._extract_port_label_from_comment('Internal magnetometer on I2C') is None
|
||||
|
||||
def test_none_input_returns_none(self):
|
||||
assert fcdg._extract_port_label_from_comment(None) is None
|
||||
|
||||
def test_empty_comment_returns_none(self):
|
||||
assert fcdg._extract_port_label_from_comment('') is None
|
||||
|
||||
def test_gps_takes_priority_over_i2c(self):
|
||||
# Comment mentions both GPS1 and I2C1 — GPS should win
|
||||
assert fcdg._extract_port_label_from_comment(
|
||||
'External compass on GPS1/I2C1 (the 3rd external bus)') == 'GPS1'
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# parse_sensor_variant_blocks
|
||||
@ -527,7 +668,7 @@ class TestParseSensorVariantBlocks:
|
||||
def test_missing_file_returns_has_variants_false(self, tmp_path):
|
||||
result = fcdg.parse_sensor_variant_blocks(tmp_path)
|
||||
assert result['has_variants'] is False
|
||||
assert result['unconditional'] == {'imu': [], 'baro': [], 'mag': [], 'osd': []}
|
||||
assert result['unconditional'] == {'imu': [], 'baro': [], 'mag': [], 'osd': [], 'power_monitor': []}
|
||||
assert result['variants'] == {}
|
||||
|
||||
def test_graceful_fail_sensors_moved_to_variant(self, board_stm32h7_graceful_fail):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user