feat: update documentation for GPIO, PCA9685, and AS5600 nodes; add README files and message definitions
parent
89a12c85cc
commit
f4f808e42c
|
|
@ -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) |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
82
readme.md
82
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 <repo> ~/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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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 <bus>`.
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
```
|
||||
Loading…
Reference in New Issue