garypippi.net

ラズパイ経由でArduino、ESCを制御する

2026/05/02 16:23#pippitank

#pippitank制作の続きです。
前回: Gentoo/NeovimでのArduino開発環境とラズパイ用のC言語クロスコンパイル環境を整える
今回はESCとモーターの動作確認まで。

設計

  • pippitankd: UARTでArduinoに命令を送る常駐プログラム
  • pippitank-cli: pippitankdに対してコマンド送信するプログラム
+---------------+
| pippitank-cli |
+---------------+
       | UART
+------+-----+ UART +---------+ PWM +-----+ +-------+
| pippitankd +------+ Arduino +-----+ ESC +-+ Motor |
+------------+      +---------+     +-----+ +-------+

ラズパイにはSSHで入ってcliクライアントを起動して使用します。

コマンド形式

Rpi -> Arduino命令は、L1500R1500(左ESCにPWM1500、右ESCにPWM1500)みたいな形式で進めています。
pippitank-cli -> pippitankdコマンド形式はひとまず "F 50(出力50%で前進)", "B 50(出力50%で後退)"みたいな感じで進めています。

ラズパイでシリアル通信の準備

初期状態ではGPIOでのシリアル通信は有効になってないので有効にする。

sudo raspi-config

3 Interface Options選択

I6 Serial Port Enable/disable選択

ログインシェルは有効にしない (No)

ハードウェアシリアルは有効化する (Yes)

完了。

ラズパイ、Arduinoのプログラム

メモがてらそのまま貼りつけ。
今回はpippitankdが要になっていてpippitank-cli、Arduinoスケッチはほぼ動作確認用になっています。

  • クライアントからのコマンド受付(UNIXドメインソケット)
  • Arduinoへ命令送信(UART)

pippitankd

  • protocol.hひとまずUnixドメインソケットのパス共有用
  • poll複数ファイルディスクリプタによるイベントループ
  • termios設定でcfmakeraw入れないとread()改行受信まで待ってしまう
#include <stdio.h>
#include <getopt.h>
#include <fcntl.h>
#include <termios.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <poll.h>
#include "../protocol.h"

#define MAX_CLIENTS   1
#define BASE_POLL_FDS 2
#define BUFFER_SIZE   128
#define BAUDRATE      B9600
#define PWM_MIN       1000
#define PWM_MAX       2000
#define PWM_IDLE      1500

int main(int argc, char *argv[])
{
    int no_serial = 0;
    char *serial_port_path = NULL;

    int opt;
    struct option options[] = {
        {"port",      required_argument, NULL, 'p'},
        {"no-serial", no_argument,       NULL, 'n'},
        {0,           0,                 0,     0 },
    };

    while ((opt = getopt_long(argc, argv, ":p:n", options, NULL)) != -1)
    {
        switch (opt)
        {
            case 'p':
                serial_port_path = optarg;
                break;
            case 'n':
                no_serial = 1;
                break;
            case ':':
                fprintf(stderr, "Option -%c requires argument.\n", optopt);
                return -1;
            case '?':
                fprintf(stderr, "Unknown option: -%c\n", optopt);
                return -1;
        }
    }

    // Set to -1 so poll() ignores this slot when --no-serial is given.
    int serial_port = -1;

    if (no_serial != 1)
    {
        if (serial_port_path == NULL)
        {
            fprintf(stderr, "[ERROR] Please specify the serial port path.\n");
            return -1;
        }

        struct termios termios_options;

        if ((serial_port = open(serial_port_path, O_RDWR | O_NOCTTY | O_NONBLOCK)) < 0)
        {
            fprintf(stderr, "[ERROR] Failed to open serial port: %s\n", serial_port_path);
            return -1;
        }

        tcgetattr(serial_port, &termios_options);

        cfsetispeed(&termios_options, BAUDRATE);
        cfsetospeed(&termios_options, BAUDRATE);

        // Needed because without raw mode, read() blocks until a newline,
        // and Arduino's ACK has no terminator.
        cfmakeraw(&termios_options);

        tcsetattr(serial_port, TCSADRAIN, &termios_options);
    }

    // UNIX domain socket for accepting clients.
    int server_fd;
    struct sockaddr_un sock_addr;

    if ((server_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
        perror("[ERROR] socket()");
        return -1;
    }

    sock_addr.sun_family = AF_UNIX;
    strcpy(sock_addr.sun_path, PIPPITANKD_SOCK_PATH);

    // Remove any stale socket file.
    unlink(PIPPITANKD_SOCK_PATH);

    if (bind(server_fd, (struct sockaddr*)&sock_addr, sizeof(struct sockaddr_un)) == -1) {
        perror("[ERROR] bind()");
        return -1;
    }

    if (listen(server_fd, 5) == -1) {
        perror("[ERROR] listen()");
        return -1;
    }

    int poll_num_fds = BASE_POLL_FDS;
    struct pollfd poll_fds[BASE_POLL_FDS + MAX_CLIENTS];

    poll_fds[0].fd = server_fd;
    poll_fds[0].events = POLLIN;

    poll_fds[1].fd = serial_port;
    poll_fds[1].events = POLLIN;

    while (1)
    {
        if (poll(poll_fds, poll_num_fds, -1) < 0)
        {
            perror("[ERROR] poll()");
            return -1;
        }

        // Accept incoming clients.
        if (poll_fds[0].revents & POLLIN)
        {
            int client_fd = accept(server_fd, NULL, NULL);

            if (client_fd < 0)
            {
                perror("[ERROR] accept()");
                return -1;
            }

            if (poll_num_fds < BASE_POLL_FDS + MAX_CLIENTS)
            {
                printf("[INFO] Client connected: %d\n", client_fd);
                poll_fds[poll_num_fds].fd = client_fd;
                poll_fds[poll_num_fds].events =  POLLIN;
                poll_num_fds++;
            }
            else
            {
                close(client_fd);
            }

        }

        // Handle responses from Arduino.
        if (poll_fds[1].revents & POLLIN)
        {
            char buffer[4];
            int n = read(serial_port, buffer, sizeof(buffer) - 1);

            if (n > 0)
            {
                buffer[n] = '\0';
                printf("[INFO] Response from Arduino (%d bytes): %s\n", n, buffer);
            }
        }

        // Handle requests from clients.
        for (int i = BASE_POLL_FDS; i < poll_num_fds; i++)
        {
            if (poll_fds[i].revents & POLLIN)
            {
                char buffer[BUFFER_SIZE];
                int n = read(poll_fds[i].fd, buffer, sizeof(buffer) - 1);

                if (n <= 0)
                {
                    printf("[INFO] Client disconnected: %d\n", poll_fds[i].fd);
                    close(poll_fds[i].fd);
                    poll_fds[i] = poll_fds[poll_num_fds - 1];
                    poll_num_fds--;
                    i--; // Re-check this slot — it now holds the swapped-in last entry.
                }
                else
                {
                    int cmd_value, pwm_value, ret;
                    char cmd_type;
                    char cmd_str[BUFFER_SIZE] = "";

                    buffer[n] = '\0';
                    ret = sscanf(buffer, "%c %d", &cmd_type, &cmd_value);

                    if (ret != 2)
                    {
                        continue;
                    }

                    switch (cmd_type)
                    {
                        case 'F':
                            if (cmd_value >= 0 && cmd_value <= 100)
                            {
                                pwm_value = PWM_IDLE + (PWM_MAX - PWM_IDLE) * cmd_value / 100;
                                sprintf(cmd_str, "L%d",  pwm_value);
                            }
                            break;
                        case 'B':
                            if (cmd_value >= 0 && cmd_value <= 100)
                            {
                                pwm_value = PWM_IDLE - (PWM_IDLE - PWM_MIN) * cmd_value / 100;
                                sprintf(cmd_str, "L%d",  pwm_value);
                            }
                            break;
                    }

                    if (cmd_str[0] == '\0')
                    {
                        continue;
                    }

                    if (no_serial != 1)
                    {
                        write(serial_port, cmd_str, strlen(cmd_str));
                        write(serial_port, "\n", 1);
                        printf("[INFO] Command sent: %s\n", cmd_str);
                    }
                    else
                    {
                        printf("[INFO] Command skipped: %s\n", cmd_str);
                    }
                }
            }
        }
    }

    close(server_fd);

    unlink(PIPPITANKD_SOCK_PATH);

    return 0;
}

pippitank-cli

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include "../protocol.h"

#define BUFFER_SIZE 128

// A simple pippitankd client.
// Sends whatever you type, except "q" which quits the program.
int main(int argc, char *argv[])
{
    int server_fd;
    struct sockaddr_un sock_addr;

    if ((server_fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1)
    {
        perror("ERROR: socket()");
        return -1;
    }

    sock_addr.sun_family = AF_UNIX;
    strcpy(sock_addr.sun_path, PIPPITANKD_SOCK_PATH);

    if (connect(server_fd, (struct sockaddr*)&sock_addr, sizeof(struct sockaddr_un)) == -1)
    {
        perror("ERROR: connect()");
        return -1;
    }

    char buffer[BUFFER_SIZE];

    while (1)
    {
        printf("> ");
        fflush(stdout);

        if (fgets(buffer, sizeof(buffer), stdin) == NULL)
        {
            break;
        }

        buffer[strcspn(buffer, "\n")] = '\0';

        if (strlen(buffer) == 0)
        {
            continue;
        }

        if (strcmp(buffer, "q") == 0)
        {
            break;
        }

        write(server_fd, buffer, strlen(buffer));
    }

    printf("Bye...");

    close(server_fd);

    return 0;
}

Arduinoスケッチ

#include <SoftwareSerial.h>
#include <Servo.h>

const byte rxPin = 2;
const byte txPin = 3;
const byte pwmLeftPin = 5;
const unsigned int PWM_MIN = 1000;
const unsigned int PWM_MAX = 2000;
const unsigned int PWM_IDLE = 1500;

SoftwareSerial pippiSerial (rxPin, txPin);
Servo pippiServoLeft;

void setup()
{
  pippiSerial.begin(9600);
  pippiServoLeft.attach(pwmLeftPin, PWM_MIN, PWM_MAX);
  pippiServoLeft.writeMicroseconds(PWM_IDLE);
}

void loop()
{
  if (pippiSerial.available() > 0)
  {
    char c = pippiSerial.read();

    if (c == 'L')
    {
      int pulseLeft = pippiSerial.parseInt();

      if (pulseLeft <= PWM_MAX && pulseLeft >= PWM_MIN)
      {
        pippiServoLeft.writeMicroseconds(pulseLeft);
        pippiSerial.print("A");
      }
    }

  }
}

PWMのライブラリインストール

arduino-cli lib install Servo

Tips

ラズパイにpinoutというコマンドがあって便利でした。

Description        : Raspberry Pi Zero W rev 1.1
Revision           : 9000c1
SoC                : BCM2835
RAM                : 512MB
Storage            : MicroSD
USB ports          : 1 (of which 0 USB3)
Ethernet ports     : 0 (0Mbps max. speed)
Wi-fi              : True
Bluetooth          : True
Camera ports (CSI) : 1
Display ports (DSI): 0

,--oooooooooooooooooooo---.
|  1ooooooooooooooooooo J8|
---+ PiZero W    RUN o1   c|
 sd| V1.1 +---+   TV 1o   s|
---+      |SoC|           i|
| hdmi    +---+   usb pwr |
`-|  |------------| |-| |-'

J8:
   3V3  (1) (2)  5V
 GPIO2  (3) (4)  5V
 GPIO3  (5) (6)  GND
 GPIO4  (7) (8)  GPIO14
   GND  (9) (10) GPIO15
GPIO17 (11) (12) GPIO18
GPIO27 (13) (14) GND
GPIO22 (15) (16) GPIO23
   3V3 (17) (18) GPIO24
GPIO10 (19) (20) GND
 GPIO9 (21) (22) GPIO25
GPIO11 (23) (24) GPIO8
   GND (25) (26) GPIO7
 GPIO0 (27) (28) GPIO1
 GPIO5 (29) (30) GND
 GPIO6 (31) (32) GPIO12
GPIO13 (33) (34) GND
GPIO19 (35) (36) GPIO16
GPIO26 (37) (38) GPIO20
   GND (39) (40) GPIO21

RUN:
RUN (1)
GND (2)

TV:
COMPOSITE (1)
      GND (2)

For further information, please refer to https://pinout.xyz/