Compare commits
4
Commits
6642691436
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e2f6c846c | ||
|
|
0a50476bb5 | ||
|
|
139f2b25e5 | ||
|
|
b0f47ec271 |
@@ -0,0 +1,149 @@
|
||||
# Linear actuator — bring-up & run-book
|
||||
|
||||
The workshop board can drive a **linear actuator** (stepper on a lead screw/belt) as the
|
||||
**"Adapt"** half of the Sense→Forecast→Adapt loop: the ADXL355 senses, the agent decides, the
|
||||
actuator moves. This is open-loop motion with a **calibrated software safety envelope** so it can
|
||||
never overrun its ends.
|
||||
|
||||
> **Read this before powering an actuator-equipped board.** The one rule that bites: a restart
|
||||
> resets the position zero (see [Operating rules](#operating-rules)).
|
||||
|
||||
---
|
||||
|
||||
## Hardware
|
||||
|
||||
| Part | Detail |
|
||||
|---|---|
|
||||
| Driver | **TB6600 / PB6600** (PUL / DIR / ENA, optically isolated). No feedback, no stall detection. |
|
||||
| Wiring | **PUL → digital pin 4**, **DIR → digital pin 5** (common-cathode: signal `-` pins to GND). ENA left free (driver enabled). |
|
||||
| Motion | `dir=1` = **into the rail** (away from the zero end) · `dir=0` = **back toward zero**. |
|
||||
| Speed | ~830 steps/s (600 µs half-period), moderate — safe for most drivers without missed steps. |
|
||||
| Sensor (same board) | ADXL355 @ `0x1d` behind a **PCA9548A mux** (`0x70`) on channel 0. Unrelated bus (I²C on SDA/SCL); doesn't compete with D4/D5. |
|
||||
|
||||
The **TB6600 has no way to sense position or the ends** — that's why travel is bounded in
|
||||
firmware, not hardware. If you ever add physical limit switches, that becomes the robust upgrade;
|
||||
until then the envelope below is the guard.
|
||||
|
||||
---
|
||||
|
||||
## How it's controlled
|
||||
|
||||
The resident MCU sketch (`firmware/zeroclaw-node/sketch/sketch.ino`) runs the pulse train and
|
||||
enforces the limits. Two ways to reach it:
|
||||
|
||||
**Agent tools** (cloud brain, within limits):
|
||||
- `stepper(steps, dir)` — move `steps` (1–4000) in direction `dir` (1 into rail / 0 toward zero).
|
||||
- `stepper_status()` — read position + limit without moving.
|
||||
|
||||
**Relay commands** (`:9999`, for setup/calibration from the host — `nc`/socket):
|
||||
- `step <count> <dir>` · `zero` · `pos` · `setmax <n>`
|
||||
- `osc <amp_steps> <freq_cHz> <cycles>` — open-loop sinusoidal excitation (shaker mode)
|
||||
- `oscm <amp> <freq_cHz> <cycles>` — oscillate while measuring the ADXL355 (per-axis p-p mg)
|
||||
- `accel` — one ADXL355 sample (mg); `dvf <gain> <secs>` — closed-loop damping (below)
|
||||
|
||||
### Direct Velocity Feedback (`dvf`) — active damping
|
||||
Closes the loop: ADXL355 X-accel @ ~250 Hz → leaky-integrated velocity → stepper
|
||||
commands `-g·v` (velocity feedback adds damping, c → c+g). Envelope-clamped and
|
||||
slew-limited. **Runs must be ≤ 8 s** — the RouterBridge RPC times out at 10 s.
|
||||
|
||||
Bench-measured gain range:
|
||||
| gain | behaviour |
|
||||
|---|---|
|
||||
| 500 | gentle |
|
||||
| **1000–2000** | **authoritative and stable — recommended** |
|
||||
| 4000 | **UNSTABLE** — self-excites off its own step-vibration (velPk 30→1442, 26k steps); the soft-limit envelope catches the runaway |
|
||||
|
||||
Actuator FRF note: open-loop amplitude rolls off with frequency — full ~10 mm
|
||||
holds to ~2–3 Hz, only a few mm by 10 Hz.
|
||||
|
||||
**Shaker-table campaign findings (2 Hz base excitation):**
|
||||
- The control law is **position-target DVF**: carriage position target =
|
||||
`center − K·v` (reaction force ∝ −v = true damping). A velocity-command law
|
||||
(carriage velocity ∝ v) is force ∝ −a = *added mass* — no dissipation; it
|
||||
amplified the response at every gain/sign. Don't regress to it.
|
||||
- **Safe reference setting: `dvf 30 8`** with the slew limit at 8 steps/tick
|
||||
(~2000 steps/s). Ran 2 min continuous, silent, self-centering, no runaway.
|
||||
Slew 25 grinds the motor (lost steps → position corrupted).
|
||||
- **Sensor placement is the binding constraint:** a deck-mounted sensor near
|
||||
the rail reads the carriage's own motion (~856 mg open-loop) louder than the
|
||||
structure sway (~435 mg), so closed-loop damping can't be scored (or cleanly
|
||||
fed back). Mount the feedback sensor at the structure's max-sway point (tower
|
||||
top), ideally a second ADXL355 on mux channel 1.
|
||||
- `listen <secs>` (≤8 s) = passive baseline/ring-down instrument. Old numbers:
|
||||
tap tests gave stable-looking g up to ~2000, but that predates the shaker
|
||||
campaign — trust the shaker findings.
|
||||
|
||||
Every move is **clamped to `[0, stepMax]`** and the reply reports position, e.g. `pos=1234
|
||||
max=9635`, ending in `LIMIT` if it hit the soft limit.
|
||||
|
||||
---
|
||||
|
||||
## Calibrated envelope (this actuator)
|
||||
|
||||
```
|
||||
0 ─────────────────────────────── 9635 ····· 9685
|
||||
zero (right end) armed safe max hard end
|
||||
↑ 50-step margin ↑
|
||||
```
|
||||
|
||||
`stepMax = 9635` is **baked into the sketch** (armed on every boot). Hard end measured at ~9685
|
||||
steps; armed 50 short so a move never reaches the physical stop.
|
||||
|
||||
---
|
||||
|
||||
## Daily bring-up (safe sequence)
|
||||
|
||||
1. **Wire the actuator first, then start the app.** (Touching the bus on a running board resets the
|
||||
MCU and crashes the app container — wire cold.)
|
||||
2. Bring the app up (`arduino-app-cli app start …`); confirm `pos` reports `max=9635`.
|
||||
3. **Home it:** manually park the carriage at the **right end**, then send **`zero`**.
|
||||
- Now `pos=0` matches reality; the envelope is already armed. Ready.
|
||||
|
||||
That's it — the agent can now drive it safely.
|
||||
|
||||
---
|
||||
|
||||
## Operating rules
|
||||
|
||||
- **Park at the right end and `zero` BEFORE any restart.** A container `app restart` **resets the
|
||||
MCU position to 0** while the carriage stays where it is. If it was parked anywhere but the right
|
||||
end, firmware `pos` and reality now disagree — and a `dir=1` move would drive into the far stop.
|
||||
The **envelope (max) survives** a restart; the **zero does not**.
|
||||
- **Don't stall it.** Open-loop means a stall against a stop **loses steps**, so the zero drifts.
|
||||
The soft limit exists precisely to avoid this — keep it armed.
|
||||
- **One session = one home.** Re-`zero` at the start of each session (there's no home switch).
|
||||
|
||||
### Recovery — firmware/reality mismatch
|
||||
If a restart left `pos=0` but the carriage isn't at the right end:
|
||||
1. `setmax -1` — disarm the clamp temporarily.
|
||||
2. Jog **`dir=0`** in bursts back to the **right end** (watch it; stop at the end).
|
||||
3. `zero`, then `setmax 9635` to re-arm.
|
||||
|
||||
---
|
||||
|
||||
## Calibrating a *different* actuator
|
||||
|
||||
If the rail, motor, or TB6600 microstep DIP changes, re-measure:
|
||||
1. Park at the right end → `zero`.
|
||||
2. Jog `dir=1` toward the far end — coarse (`step 200 1`) then fine (`step 10 1`) as it nears —
|
||||
watching. The firmware sums position for you; read it with `pos`.
|
||||
3. Stop a hair short of the hard stop. Take that `pos`, subtract a ~50-step margin → that's the max.
|
||||
4. Bake it: set `long stepMax = <value>;` in the sketch and reflash (push sketch → `app restart`,
|
||||
~50s = a real recompile+flash).
|
||||
|
||||
---
|
||||
|
||||
## Rebuilding the binary (when the agent tools change)
|
||||
|
||||
The `stepper` / `stepper_status` tools live in the ZeroClaw binary
|
||||
(`crates/zeroclaw-hardware/src/peripherals/uno_q_bridge.rs`). Cross-build for the board:
|
||||
|
||||
```sh
|
||||
cd <zeroclaw>
|
||||
cargo zigbuild --target aarch64-unknown-linux-gnu --profile release-fast --features hardware --bin zeroclaw
|
||||
```
|
||||
|
||||
`cargo-zigbuild` + zig is the working cross path on macOS (the `aarch64-linux-gnu-gcc` linker isn't
|
||||
installed). `--features hardware` is **required** or the peripheral tools are stripped. Then push
|
||||
the binary to `…/apess-onboard/bin/zeroclaw` and `app restart`, or repackage the distributable app
|
||||
with `package-onboard-app.sh`.
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
> **The setup runbook.** For the participant *journey* (each screen's job, the
|
||||
> module→ADD-layer map, open design questions), see
|
||||
> [`WORKSHOP-FLOW.md`](./WORKSHOP-FLOW.md).
|
||||
> [`WORKSHOP-FLOW.md`](./WORKSHOP-FLOW.md). For a board with the **linear
|
||||
> actuator** (stepper bring-up, calibration, and the park-and-`zero`-before-restart
|
||||
> rule), see [`ACTUATOR.md`](./ACTUATOR.md).
|
||||
|
||||
A team needs two things running: the **APESS stack on their laptop** and the
|
||||
**ZeroClaw node on their Uno Q**. Both are containers. Nothing installs to a host.
|
||||
|
||||
@@ -203,8 +203,8 @@ prompt_injection_mode = "compact"
|
||||
|
||||
[risk_profiles.default]
|
||||
level = "supervised"
|
||||
allowed_tools = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search"]
|
||||
auto_approve = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search", "memory_recall", "web_search_tool", "web_fetch", "calculator", "glob_search", "image_info", "weather", "tool_search", "browser", "browser_open"]
|
||||
allowed_tools = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "stepper", "stepper_status", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search"]
|
||||
auto_approve = ["matrix_pattern", "matrix_text", "matrix_count", "i2c_scan", "stepper", "stepper_status", "uno_q_flash", "sysfs_led", "camera", "network", "i2cdetect", "read_skill", "file_read", "content_search", "memory_recall", "web_search_tool", "web_fetch", "calculator", "glob_search", "image_info", "weather", "tool_search", "browser", "browser_open"]
|
||||
allowed_commands = ["git", "npm", "cargo", "ls", "cat", "grep", "find", "echo", "pwd", "wc", "head", "tail", "date", "df", "du", "uname", "uptime", "hostname", "python", "python3", "pip", "node", "free"]
|
||||
allowed_roots = []
|
||||
always_ask = []
|
||||
|
||||
Reference in New Issue
Block a user