From f4f808e42c939fb1d1b9d73450b4a0cd962c0743 Mon Sep 17 00:00:00 2001 From: a2nr Date: Mon, 16 Mar 2026 20:08:19 +0700 Subject: [PATCH] feat: update documentation for GPIO, PCA9685, and AS5600 nodes; add README files and message definitions --- DOCUMENTATION.md | 6 +-- readme.md | 82 -------------------------------- src/as5600_node/README.md | 80 +++++++++++++++++++++++++++++++ src/blockly_interfaces/README.md | 28 ++++++++++- src/gpio_node/README.md | 71 +++++++++++++++++++++++++++ src/pca9685_node/README.md | 79 ++++++++++++++++++++++++++++++ 6 files changed, 260 insertions(+), 86 deletions(-) create mode 100644 src/as5600_node/README.md create mode 100644 src/gpio_node/README.md create mode 100644 src/pca9685_node/README.md diff --git a/DOCUMENTATION.md b/DOCUMENTATION.md index 6e67002..a5885ff 100644 --- a/DOCUMENTATION.md +++ b/DOCUMENTATION.md @@ -17,9 +17,9 @@ See [readme.md](readme.md) for project overview and status. | `blockly_app` — creating custom blocks (full guide + reference) | [src/blockly_app/BLOCKS.md](src/blockly_app/BLOCKS.md) | | `blockly_executor` — file reference, handlers & testing guide | [src/blockly_executor/README.md](src/blockly_executor/README.md) | | `blockly_interfaces` — ROS2 action & message interfaces | [src/blockly_interfaces/README.md](src/blockly_interfaces/README.md) | -| `gpio_node` — Raspberry Pi GPIO node (C++, libgpiod) | [src/gpio_node/](src/gpio_node/) | -| `pca9685_node` — PCA9685 16-channel PWM controller (C++, I2C) | [src/pca9685_node/](src/pca9685_node/) | -| `as5600_node` — AS5600 12-bit magnetic encoder (C++, I2C) | [src/as5600_node/](src/as5600_node/) | +| `gpio_node` — Raspberry Pi GPIO node (C++, libgpiod) | [src/gpio_node/README.md](src/gpio_node/README.md) | +| `pca9685_node` — PCA9685 16-channel PWM controller (C++, I2C) | [src/pca9685_node/README.md](src/pca9685_node/README.md) | +| `as5600_node` — AS5600 12-bit magnetic encoder (C++, I2C) | [src/as5600_node/README.md](src/as5600_node/README.md) | --- diff --git a/readme.md b/readme.md index e4d867b..ecb0bd2 100644 --- a/readme.md +++ b/readme.md @@ -44,88 +44,6 @@ this list is short by priority # Feature Task -## 1 Bug Fix: Blockly Debug Mode — Step Into for Function Blocks : [x] -Debug mode tidak bisa step into ke function blocks karena `highlightBlock()` bersifat synchronous — tidak bisa pause execution. Hanya `executeAction()` yang bisa pause, sehingga blocks tanpa `executeAction()` (function calls, variables, math) tidak bisa di-debug. Fix ini mengubah arsitektur debug engine: - -1. **Async `highlightBlock()`** — menjadi universal pause point. Semua block generators menggunakan `await highlightBlock()` sehingga setiap block bisa di-breakpoint dan di-step. -2. **Call depth tracking** — `enterFunction()/exitFunction()` di-inject ke generated code di procedure calls. Step Over menggunakan `callDepth` untuk skip function bodies. -3. **Step modes** — `stepMode` state machine ('into'|'over'|'continue') menggantikan monkey-patching `highlightBlock` di setiap step function. -4. **Auto-pause at first block** — debug mode langsung pause di block pertama (tidak perlu breakpoint untuk mulai stepping). -5. **Run = Continue** — Run button saat paused berfungsi sebagai Continue (resume sampai breakpoint berikutnya). - -### Definition Of Done -- `debug-engine.js` di-rewrite: async `highlightBlock()` override di `runDebug()`, `callDepth` tracking, `stepMode` state machine -- `enterFunction()`/`exitFunction()` global helpers tersedia untuk generated code -- `async-procedures.js`: `procedures_callreturn` wrapped dengan async IIFE + `highlightBlock()` + depth tracking -- `async-procedures.js`: `procedures_callnoreturn` menggunakan `await highlightBlock()` + `enterFunction()/exitFunction()` dengan try/finally -- Block generators (`digitalOut.js`, `delay.js`, `mainProgram.js`) menggunakan `await highlightBlock()` -- `ui-controls.js`: Run button enabled saat paused (Continue behavior), `onRunClick()` memanggil `continueExecution()` -- Step Into pada function call block → pause di block pertama dalam function body -- Step Over pada function call block → skip function body, pause di block berikutnya -- Debug mode pause di block pertama tanpa perlu breakpoint -- Non-debug mode (`runProgram()`) tidak terpengaruh — `await` pada synchronous `highlightBlock()` adalah no-op -- `pixi run build-app` berhasil tanpa error - -## 2 Enhancement: Port gpio_node to C++ : [x] -gpio_node di-port dari Python (`ament_python`, `rclpy`, `gpiod` Python binding) ke C++ (`ament_cmake`, `rclcpp`, `libgpiod` **C API**). Node ini hardware-only — hanya berjalan di Raspberry Pi dengan akses ke `/dev/gpiochipX`. - -### Implementasi - -#### A. Package Structure (C++, ament_cmake) -``` -src/gpio_node/ -├── CMakeLists.txt # ament_cmake, pkg_check_modules(libgpiod), build executable -├── package.xml # depend: rclcpp, blockly_interfaces -├── include/gpio_node/ -│ └── gpio_node.hpp # GpioNode class — rclcpp::Node + gpiod C API raw pointers -└── src/ - ├── gpio_node.cpp # GpioNode implementation (setup_gpio, write_callback, read_callback) - └── main.cpp # main() — rclcpp::spin(node) -``` - -Dihapus: semua file Python (`gpio_node.py`, `__init__.py`, `setup.py`, `setup.cfg`, `resource/`) - -#### B. C++ Node — Same ROS2 API Surface -- **Subscribe** `/gpio/write` (`GpioWrite`) — `gpiod_line_request_set_value()` untuk set pin output -- **Publish** `/gpio/state` (`GpioRead`) — `gpiod_line_request_get_value()` via polling timer (10 Hz default) -- **Parameters**: `output_pins` (int array), `input_pins` (int array), `input_publish_rate` (double), `gpio_chip` (string, default `/dev/gpiochip0`) -- Pin tidak terdaftar di `output_pins` → log warning, ignore write -- Cleanup: `gpiod_line_request_release()` di destructor (manual memory management, bukan C++ RAII) - -#### C. Catatan: libgpiod C API, bukan C++ Bindings -conda-forge `libgpiod` **hanya menyertakan C library** (`gpiod.h`), bukan C++ bindings (`gpiod.hpp` / `libgpiodcxx`). Oleh karena itu implementasi menggunakan gpiod v2 **C API** langsung. Semua resource (`gpiod_line_request`, `gpiod_line_config`, dll.) di-manage manual via `gpiod_*_free()`. - -#### D. Platform-Specific Tasks & System Dependencies -Cross-compilation ROS2 C++ tidak praktis (butuh full aarch64 sysroot). Build dilakukan **native di Raspberry Pi**. - -pixi.toml menggunakan **platform-specific task sections**: -- `[target.linux-64.tasks]` — desktop tasks (build-app, executor, app, test) -- `[target.linux-aarch64.tasks]` — Pi tasks (setup-dep, build-gpio, gpio-node) - -`setup-dep` task menginstall system libraries via `apt` yang tidak tersedia / tidak lengkap di conda-forge: -```bash -sudo apt install -y liblttng-ust-dev lttng-tools libgpiod-dev gpiod -``` -- `libgpiod-dev` — header + `.so` untuk linking (conda package tidak menyertakan `.so` symlink untuk linker) -- `liblttng-ust-dev` + `lttng-tools` — dibutuhkan oleh `rclcpp` (RoboStack) yang di-build dengan LTTng tracing support - -```bash -# Di Pi: clone repo + install deps + build + run -git clone ~/amr-ros-k4 && cd ~/amr-ros-k4 -pixi install && pixi run build-gpio # setup-dep runs automatically -pixi run gpio-node -``` - -### Definition Of Done -- [x] `src/gpio_node/` berisi `CMakeLists.txt`, `package.xml`, `include/`, `src/` — tidak ada file Python -- [x] `pixi.toml` menyertakan `ros-jazzy-rclcpp` di `linux-aarch64` dependencies -- [x] `pixi.toml` tidak lagi menyertakan `gpiod` di `linux-aarch64` pypi-dependencies -- [x] `setup-dep` task menginstall `libgpiod-dev`, `liblttng-ust-dev` via apt -- [x] `pixi run build-gpio` berhasil di Raspberry Pi (native build) tanpa error -- [x] Node berjalan: `pixi run gpio-node` — subscribe `/gpio/write`, publish `/gpio/state` -- [x] Parameter `output_pins`, `input_pins`, `input_publish_rate`, `gpio_chip` berfungsi via `--ros-args -p` -- [x] Executor (`blockly_executor`) tetap berfungsi tanpa perubahan — interface ROS2 identik - ## 3 Enhancement: PCA9685 — 16-Channel PWM Controller (I2C) : [ ] PCA9685 adalah 16-channel, 12-bit PWM controller via I2C. Motor DC kiwi wheel menggunakan 6 channel (3 motor × 2: PWM + direction), sehingga 10 channel tersedia untuk extensi (servo, LED, dll). Node ini general-purpose — mengontrol channel mana saja via Blockly block dengan parameter address, channel, dan PWM value. diff --git a/src/as5600_node/README.md b/src/as5600_node/README.md new file mode 100644 index 0000000..2be84f8 --- /dev/null +++ b/src/as5600_node/README.md @@ -0,0 +1,80 @@ +# as5600_node — AS5600 12-bit Magnetic Rotary Encoder (C++, I2C) + +ROS2 node for reading AS5600 magnetic rotary position sensors via Linux I2C (`ioctl`). Publishes angle data periodically. Supports multiple encoders on separate I2C buses. + +**Platform:** linux-aarch64 only (Raspberry Pi) + +## ROS2 Interface + +| Direction | Topic | Message | Description | +|---|---|---|---| +| Publish | `/encoder/state` | `EncoderRead` | Periodic angle readings per encoder | + +``` +# EncoderRead.msg (as5600_node → executor) +uint8 encoder_id # Encoder index (0, 1, 2, ...) +float32 angle # Angle in degrees (0.0-360.0) +uint16 raw_angle # Raw 12-bit value (0-4095) +``` + +## Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `i2c_devices` | string[] | `["/dev/i2c-1"]` | List of I2C device paths, one per encoder | +| `publish_rate` | double | `10.0` | Publish frequency Hz | + +## File Structure + +``` +src/as5600_node/ +├── CMakeLists.txt # ament_cmake — NO external lib dependency +├── package.xml # depend: rclcpp, blockly_interfaces +├── include/as5600_node/ +│ └── as5600_node.hpp # As5600Node class + I2C helpers + register constants +└── src/ + ├── as5600_node.cpp # I2C init, timer_callback, read_raw_angle + └── main.cpp # rclcpp::spin(node) +``` + +## Build & Run + +```bash +pixi run build-as5600 # installs system deps (apt) + builds +pixi run as5600-node # start node with default parameters (1 encoder) + +# 3 encoders on separate buses, 20 Hz +source install/setup.bash +ros2 run as5600_node as5600_node --ros-args \ + -p i2c_devices:="['/dev/i2c-1', '/dev/i2c-3', '/dev/i2c-4']" \ + -p publish_rate:=20.0 +``` + +## AS5600 Register Map + +| Register | Address | Description | +|---|---|---| +| RAW_ANGLE | `0x0C`-`0x0D` | 12-bit raw angle (0-4095) | +| STATUS | `0x0B` | Magnet detect status | +| AGC | `0x1A` | Automatic gain control | + +## Implementation Notes + +- AS5600 has a **fixed I2C address (0x36)** — cannot be changed. For multiple encoders, each must be on a **separate I2C bus** +- Uses Linux I2C via `ioctl()` + `linux/i2c-dev.h` — no external library dependency +- `I2C_SLAVE` address is set once per fd at startup (each fd is dedicated to one bus/encoder) +- Raw angle read: write register `0x0C`, read 2 bytes → `((buf[0] & 0x0F) << 8) | buf[1]` (12-bit) +- Angle conversion: `raw * 360.0 / 4096.0` degrees +- Timer callback iterates all fds and publishes one `EncoderRead` per encoder with `encoder_id` matching the index in `i2c_devices` + +## Raspberry Pi I2C Setup + +The Pi has one hardware I2C bus (`/dev/i2c-1`) by default. For multiple AS5600 encoders, enable extra I2C buses via GPIO bit-banging overlays in `/boot/config.txt`: + +``` +# Extra I2C buses using GPIO pins +dtoverlay=i2c-gpio,bus=3,i2c_gpio_sda=17,i2c_gpio_scl=27 +dtoverlay=i2c-gpio,bus=4,i2c_gpio_sda=22,i2c_gpio_scl=23 +``` + +After reboot, verify with `ls /dev/i2c-*` and `i2cdetect -y `. diff --git a/src/blockly_interfaces/README.md b/src/blockly_interfaces/README.md index 1d3077b..5d0b622 100644 --- a/src/blockly_interfaces/README.md +++ b/src/blockly_interfaces/README.md @@ -38,6 +38,32 @@ bool state Used for communication between the executor's GPIO handlers and the `gpio_node` running on Raspberry Pi. +## PwmWrite.msg + +Defined in [`msg/PwmWrite.msg`](msg/PwmWrite.msg): + +``` +# PwmWrite — PWM duty cycle command (executor → pca9685_node) +uint8 address # I2C device address (e.g. 0x40) +uint8 channel # PWM channel (0-15) +uint16 value # Duty cycle (0-4095, 12-bit) +``` + +Used for communication between the executor's PWM handler and the `pca9685_node` running on Raspberry Pi. + +## EncoderRead.msg + +Defined in [`msg/EncoderRead.msg`](msg/EncoderRead.msg): + +``` +# EncoderRead — rotary encoder angle (as5600_node → executor) +uint8 encoder_id # Encoder index (0, 1, 2, ...) +float32 angle # Angle in degrees (0.0-360.0) +uint16 raw_angle # Raw 12-bit value (0-4095) +``` + +Used for communication between the `as5600_node` running on Raspberry Pi and the executor's encoder handler. + ## Building ```bash @@ -48,7 +74,7 @@ This must be run before building any other package. The generated Python modules ```python from blockly_interfaces.action import BlocklyAction -from blockly_interfaces.msg import GpioWrite, GpioRead +from blockly_interfaces.msg import GpioWrite, GpioRead, PwmWrite, EncoderRead ``` ## Usage diff --git a/src/gpio_node/README.md b/src/gpio_node/README.md new file mode 100644 index 0000000..021a263 --- /dev/null +++ b/src/gpio_node/README.md @@ -0,0 +1,71 @@ +# gpio_node — Raspberry Pi GPIO Node (C++, libgpiod) + +ROS2 node for digital I/O on Raspberry Pi via libgpiod v2 C API. Subscribes to write commands and publishes input pin states. + +**Platform:** linux-aarch64 only (Raspberry Pi) + +## ROS2 Interface + +| Direction | Topic | Message | Description | +|---|---|---|---| +| Subscribe | `/gpio/write` | `GpioWrite` | Set output pin HIGH/LOW | +| Publish | `/gpio/state` | `GpioRead` | Polled input pin states | + +``` +# GpioWrite.msg (executor → gpio_node) +uint8 pin +bool state + +# GpioRead.msg (gpio_node → executor) +uint8 pin +bool state +``` + +## Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `output_pins` | int[] | `[17, 27, 22]` | GPIO pins configured as output | +| `input_pins` | int[] | `[5, 6, 13]` | GPIO pins configured as input | +| `input_publish_rate` | double | `10.0` | Input polling frequency (Hz) | +| `gpio_chip` | string | `/dev/gpiochip0` | GPIO chip device path | + +## File Structure + +``` +src/gpio_node/ +├── CMakeLists.txt # ament_cmake, pkg_check_modules(libgpiod) +├── package.xml # depend: rclcpp, blockly_interfaces +├── include/gpio_node/ +│ └── gpio_node.hpp # GpioNode class + gpiod C API raw pointers +└── src/ + ├── gpio_node.cpp # setup_gpio, write_callback, read_callback + └── main.cpp # rclcpp::spin(node) +``` + +## Build & Run + +```bash +pixi run build-gpio # installs system deps (apt) + builds +pixi run gpio-node # start node with default parameters + +# Custom parameters +source install/setup.bash +ros2 run gpio_node gpio_node --ros-args \ + -p output_pins:="[17, 27]" \ + -p input_pins:="[5, 6]" \ + -p gpio_chip:=/dev/gpiochip4 +``` + +`build-gpio` automatically runs `setup-dep` which installs system libraries via apt: +```bash +sudo apt install -y libgpiod-dev gpiod liblttng-ust-dev lttng-tools +``` + +## Implementation Notes + +- Uses **libgpiod v2 C API** (`gpiod.h`), not C++ bindings (`gpiod.hpp`/`libgpiodcxx`) — conda-forge only ships the C library +- All gpiod resources (`gpiod_line_request`, `gpiod_line_config`, etc.) are managed manually via `gpiod_*_free()` in the destructor +- Output and input pins are requested as **single batch** line requests for efficiency +- Writes to pins not listed in `output_pins` are ignored with a warning +- Input pins are polled via a `create_wall_timer` at the configured rate — each poll publishes one `GpioRead` message per input pin diff --git a/src/pca9685_node/README.md b/src/pca9685_node/README.md new file mode 100644 index 0000000..3e6fe0c --- /dev/null +++ b/src/pca9685_node/README.md @@ -0,0 +1,79 @@ +# pca9685_node — PCA9685 16-Channel PWM Controller (C++, I2C) + +ROS2 node for controlling PCA9685 PWM channels via Linux I2C (`ioctl`). Subscribes to write commands — no external library needed, uses kernel headers only. + +**Platform:** linux-aarch64 only (Raspberry Pi) + +## ROS2 Interface + +| Direction | Topic | Message | Description | +|---|---|---|---| +| Subscribe | `/pwm/write` | `PwmWrite` | Set PWM duty cycle on a channel | + +``` +# PwmWrite.msg (executor → pca9685_node) +uint8 address # I2C device address (e.g. 0x40) +uint8 channel # PWM channel (0-15) +uint16 value # Duty cycle (0-4095, 12-bit) +``` + +PCA9685 is write-only — no read/feedback message needed. + +## Parameters + +| Parameter | Type | Default | Description | +|---|---|---|---| +| `i2c_device` | string | `/dev/i2c-1` | Linux I2C device path | +| `frequency` | int | `50` | PWM frequency Hz (all channels) | + +## File Structure + +``` +src/pca9685_node/ +├── CMakeLists.txt # ament_cmake — NO external lib dependency +├── package.xml # depend: rclcpp, blockly_interfaces +├── include/pca9685_node/ +│ └── pca9685_node.hpp # Pca9685Node class + I2C helpers + register constants +└── src/ + ├── pca9685_node.cpp # I2C init, configure_frequency, write_callback, set_pwm + └── main.cpp # rclcpp::spin(node) +``` + +## Build & Run + +```bash +pixi run build-pca9685 # installs system deps (apt) + builds +pixi run pca9685-node # start node with default parameters + +# Custom parameters +source install/setup.bash +ros2 run pca9685_node pca9685_node --ros-args \ + -p i2c_device:=/dev/i2c-0 \ + -p frequency:=1000 +``` + +## PCA9685 Register Map + +| Register | Address | Description | +|---|---|---| +| MODE1 | `0x00` | Sleep/restart, auto-increment | +| LED0_ON_L | `0x06` | Channel 0 ON timing (4 registers per channel) | +| PRE_SCALE | `0xFE` | PWM frequency: `prescale = round(25MHz / (4096 * freq)) - 1` | + +## Implementation Notes + +- Uses Linux I2C via `ioctl()` + `linux/i2c-dev.h` — no external library dependency +- **Multi-address support**: I2C slave address is sent per-message (`ioctl(I2C_SLAVE)` on every write), so one node can control multiple PCA9685 boards (0x40-0x7F, configurable via solder bridges) +- Frequency is configured once at startup on default address 0x40 (sleep → set prescale → wake + auto-increment → restart) +- Channel validation: values > 15 are rejected, values > 4095 are clamped +- PWM duty cycle: ON=0, OFF=value (simple duty cycle mode, 4 bytes per channel with auto-increment) + +## I2C Debugging + +```bash +# Check available I2C buses +ls /dev/i2c-* + +# Scan devices on bus 1 (needs i2c-tools) +i2cdetect -y 1 +```