소프트웨어 스택
블록코딩(Blockly/Entry 호환) → Python 자동 생성 → ROS2 토픽 → BLDC 제어
Cite as: mecha4wd software v26.06a · wiki/software
1. 레이어 구조 — 한 화면에
L7 UIBlockly 웹 에디터 (Chromium 110+, 7세그)
L6 CodeGenBlock-to-Python Transpiler (Blockly API)
L5 RuntimePython asyncio 스케줄러 (50Hz tick)
L4 PerceptionPi Camera v3 + VL53L0X ×4 + BNO055
L3 InferenceONNX Runtime (RKNN NPU optional) — RL 정책
L2 ROS2cyclonedds · 4 node (cam, imu, policy, motor)
L1 MCUPico 2 — 1 kHz BLDC PWM + WS2812 ring control
L0 HW4 BLDC + rubber tires + 3S LiPo + Pi Camera v3
2. ROS2 토픽 흐름
┌───────────────┐
[BT keyboard / RC RX] ──Twist──►│ /cmd_vel_in │
└────┬──────────┘
│
┌───────▼──────────┐
│ /cmd_vel_priority │ ← block_orchestrator merges sources
└───────┬──────────┘
│
┌───────▼────────────▼──────────────┐
│ │
┌───▼────────┐ ┌─────────▼────────┐
│ block_node │ │ policy_node │
│ (Blockly │ │ ONNX 50Hz │
│ exec loop) │ └─────────┬────────┘
└────┬────────┘ │
│ Twist / Toggle │
└────────────┬──────────────────────┘
│
┌────────▼────────┐
│ /motor_targets │ Float32[4]
└────────┬────────┘
│
┌────────▼────────┐
│ controller_node │ (motor PID + safety)
└────────┬────────┘
│ USB-CDC
[Pico 2 firmware]
│ PWM ×4
┌────────▼────────┐
│ 4 BLDC + tires │
└──────────────────┘
[sensors]
[Pi Camera v3] ──Image──► /camera/image_raw ──► perception_node ────► /detections
[BNO055 IMU] ──Imu──► /imu/data ────► state_estimator ────► /odom
[VL53L0X ×4] ──Range──► /scan (LaserScan) ──► costmap_node
3. Blockly → Python 자동 생성 코드 예
학생이 블록 코딩으로 구성
[Start]
[Forever]
[If distance < 30 cm]
[Stop] [Wait 1 sec] [Turn left 90°] [Wait 1 sec]
[Else]
[Forward at 0.4 m/s]
Python 코드 자동 생성
# Auto-generated from Blockly XML
from mecha4wd import robot, sensors, lcd
import asyncio
async def main():
await robot.start()
while True:
d = sensors.tof_front.distance_cm()
if d is not None and d < 30:
await robot.stop()
await asyncio.sleep(1.0)
await robot.rotate_degrees(-90)
await asyncio.sleep(1.0)
else:
await robot.go_forward_at(0.4)
await asyncio.sleep(0.05) # 20 Hz sensing tick
asyncio.run(main())
4. 의존성 — install on Rock 5C
# Debian 12 on Radxa Rock 5C Lite 8GB
sudo apt update && sudo apt upgrade -y
# ROS2 Humble + cyclonedds
sudo apt install -y software-properties-common curl
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/keys/ros-keyring.gpg -o /usr/share/keyrings/ros-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/ros-keyring.gpg] http://packages.ros.org/ros2/ubuntu bookworm main" | sudo tee /etc/apt/sources.list.d/ros2.list
sudo apt update
sudo apt install -y ros-humble-desktop ros-humble-ros-base python3-colcon-common-extensions
sudo apt install -y ros-humble-rmw-cyclonedds-cpp
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp
# Python AI / block / perception deps
pip install --break-system-packages \
onnxruntime==1.18.0 \
rknn-toolkit2 \
pyserial numpy ultralytics==8.0.0 \
rpi_ws281x adafruit-circuitpython-bno055 adafruit-circuitpython-vl53l0x
# Pi Camera v3 (CSI)
sudo apt install -y libcamera-apps ros-humble-camera-info-manager
5. Pico 2 펌웨어 — BLDC + WS2812 control
// Pico 2 SDK · USB-CDC → Rock 5C + I2C bridge
#include "pico/stdlib.h"
#include "hardware/pwm.h"
#include "hardware/i2c.h"
#define I2C_PORT i2c0
#define SDA_PIN 0
#define SCL_PIN 1
void motor_pwm(float duty[4]) {
// 4 슬라이드 PWM out → DRV8871 IN1/IN2 4개
for (int i = 0; i < 4; i++) {
uint slice = pwm_gpio_to_slice_num(2 + i);
uint chan = pwm_gpio_to_channel(2 + i);
pwm_set_chan_duty(chan, (uint16_t)(duty[i] * 65535));
}
}
int main() {
stdio_init_all();
i2c_init(I2C_PORT, 400000);
gpio_set_function(SDA_PIN, GPIO_FUNC_I2C);
gpio_set_function(SCL_PIN, GPIO_FUNC_I2C);
for (int i = 0; i < 4; i++) {
gpio_set_function(2 + i, GPIO_FUNC_PWM);
pwm_set_wrap(pwm_gpio_to_slice_num(2 + i), 65535);
pwm_set_enabled(pwm_gpio_to_slice_num(2 + i), true);
}
// USB-CDC로 4-float HM 패킷 수신: "M 0.4 -0.4 0.0 0.0\n"
char buf[64];
int idx = 0;
while (true) {
int c = getchar_timeout_us(100);
if (c == EOF) continue;
if (c == '\n') {
buf[idx] = 0;
float a,b,c2,d;
if (sscanf(buf, "M %f %f %f %f", &a, &b, &c2, &d) == 4) {
float duty[4] = {a, b, c2, d};
motor_pwm(duty);
}
idx = 0;
} else if (idx < 63) {
buf[idx++] = c;
}
}
}
6. 데이터 흐름 — 카메라 인지 → block input
Pi Camera v3 CSI → libcamera → Python ROS2 /camera/image_raw 발행. 학생은 block-coding에서 다음 두 가지 입력을 자연스럽게 사용:
- YOLO detection —
sensors.camera.detect("person")returns bool - Color detection —
sensors.camera.find_color("red", tol=0.1)returns 좌표(x, y) - Infrared lane —
sensors.camera.lane_offset()returns lane center에서 deviation in cm
7. RL 학습 호환 — 등반할 부분
고급 사용자(고학년·연구)는 ONNX 기반 RL 정책으로 자동차의 ‘자율 lanekeeping’· ‘표지 sign 처리’ 같은 학습으로 확장 가능. 학습 시뮬은 Isaac Lab + rsl_rl로 PC에서 진행, export ONNX → Rock 5C에서 RKNN NPU 추론.
설계 차이 — 4WD는 4족 보행과 다른 두 가지 차이: (a) 동작 차원·선택지가 더 제한적 (직진·회전·정도), (b) 카메라·라인 인지 task가 훨씬 쉬움. 학급용 block-coding 핵심은 (a)에서 시작해 (b)·RL로 확장하는 자연스러운 progression.