【Raspberry Pi】モーター制御するサーバープログラムの作り方

前回でRaspberry PiのPWM機能とモータードライバーICを使って、モーターの回転数と回転方向を制御できるようになりました。しかし、レゴの車に載せるモーターとして、まだ足りない機能があります。

それはサーバー機能です。今はラズパイのターミナルに対して直接命令を出してPWM制御を行っています。しかし、レゴの車に乗せて走らせるためには、リモートから命令を受けて、その命令によって回転数や回転方向を制御しなければなりません。

というわけで今回は、モーターの回転数・回転方向の情報を受け取るサーバープログラムを作成して、リモートからモーターの回転方向を正転/逆転切り替えできるようにします。

回路の準備ができていない方は、前回の記事を参考にして作成してください。

目次

モーター制御のサーバープログラム作成

pigpioライブラリの使い方(C言語編)

前回はpigpioをPython I/Fから使いましたが、今回はC言語で作成します。といっても基本的な使い方はそこまで大きく変わらないので、安心してください。

まずpigpioライブラリを使ったPWM出力の大枠は、こんな感じのコードになります。

#include <pigpio>

#define IN1_GPIO_NO 18
#define IN2_GPIO_NO 19

#define PMW_FREQ    200000

int main(void)
{
    int ret = gpioInitialise();
    if (ret < 0)
    {
        return ret;
    }
    else
    {
        gpioHardwarePWM(IN2_GPIO_NO, PMW_FREQ, 200000);
        gpioHardwarePWM(IN1_GPIO_NO, PMW_FREQ, 1000000);
        gpioTerminate();
    }

    return ret;
}

上から順にみていきます。

まず最初にpigpioのI/Fが宣言されているヘッダファイルをインクルードします。これによりpigpioライブラリが公開しているI/Fを参照できるようになります。

次に gpioInitialise() というI/Fを呼び出しています。これはpigpioライブラリを使い始めるための初期化関数であり、pigpioライブラリを使うのであれば必ず実行します。

次に gpioHardwarePWM() というI/Fを呼び出しています。ハードウェアPWMを使ってPWM出力させるI/Fで、指定したGPIOからPWM出力を行うことができます。

最後に gpioTerminate() というI/Fを呼び出しています。これはpigpioライブラリを使い終わった時に呼ぶ終了関数です。

各I/Fの主な仕様は下記通り。

使用したpigpio I/Fの主な仕様

gpioInitialise()

pigpioライブラリの初期化処理を行います。

【引数】
なし

【戻り値】
正常終了:pigpioのバージョンナンバー
異常終了:PI_INIT_FAILED(-1)

gpioTerminate()

pigpioライブラリの終了処理を行います。

【引数】
なし

【戻り値】
なし

gpioHardwarePWM()

指定したGPIOからPWM出力を行います。

【引数】
unsigned gpio:PWM出力したいGPIO番号
unsigned PWMfreq:PWMの周波数(設定範囲:0〜125M)
unsigned PWMduty:PWMのDuty値(設定範囲:0(0%)〜1000000(100%))

【戻り値】
正常終了:0
異常終了:
PI_BAD_GPIO(指定したGPIOが不正)
PI_NOT_HPWM_GPIO(指定したGPIOがハードウェアPWMに対応していない)
PI_BAD_HPWM_DUTY(指定したDuty値が不正)
PI_BAD_HPWM_FREQ(指定したPWM周波数が不正)
PI_HPWM_ILLEGAL(予期しないエラー)

コードができたら、あとはpigpioライブラリをリンクするとラズパイからPWM出力できます。Cmakeを使っているのであれば、下記のように記述してライブラリをリンクします。

target_link_libraries(${PROJECT_NAME} pigpio pthread)

pigpioがpthreadを使うので、pthreadライブラリのリンクも忘れずに。

モーター制御するサーバープログラム

pigpioライブラリを使ってPWM制御する方法はわかったので、後はサーバー化に必要なコードを追加します。コードはこんな感じ。

/**
[sample_motor_server]

Copyright (c) [2021] [radical-kei]

This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
*/

#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <pigpio.h>

#define IN1_GPIO_NO 18
#define IN2_GPIO_NO 19

#define PMW_FREQ    200000
#define MAX_DUTY    1000000

#define QUEUELIMIT  5
#define SERVER_PORT 50001
#define BUF_SIZE    8

struct clientdata {
  int sock;
  struct sockaddr_in saddr;
};

int set_motor_rev(int t_duty)
{
    int ret = 0;
    unsigned int in1_duty = MAX_DUTY;
    unsigned int in2_duty = MAX_DUTY;

    if(t_duty < 0)
    {
        in1_duty = MAX_DUTY - abs(t_duty);
    }
    else
    {
        in2_duty = MAX_DUTY - t_duty;
    }

    printf("in1:%d in2:%d\n", in1_duty, in2_duty);
    gpioHardwarePWM(IN1_GPIO_NO, PMW_FREQ, in1_duty);
    if(ret != 0)
    {
        return ret;
    }

    gpioHardwarePWM(IN2_GPIO_NO, PMW_FREQ, in2_duty);
    if(ret != 0)
    {
        return ret;
    }

    return ret;
}

void* motor_control_thread(void* pArg)
{
    int recvMsgSize, sendMsgSize;
    struct clientdata *cdata = pArg;
    char recv_buf[BUF_SIZE];
    int req_duty;

    while(1)
    {
        memset(recv_buf, 0, BUF_SIZE);
        if ((recvMsgSize = recv(cdata->sock, &recv_buf, BUF_SIZE, 0)) < 0)
        {
            fprintf(stderr, "Failed recv().\n");
            break;
        }
        else if(recvMsgSize == 0)
        {
            fprintf(stderr, "connection closed by foreign host.\n");
            break;
        }

        req_duty = atoi(recv_buf);
        if(set_motor_rev(req_duty) != 0)
        {
            fprintf(stderr, "Failed set_motor_param().\n");
            break;
        }
    }

    close(cdata->sock);
    free(cdata);
    return NULL;
}

int motor_control_server(void){
    int servSock;
    struct clientdata* clit;
    struct sockaddr_in servSockAddr; //server internet socket address
    struct sockaddr_in clitSockAddr; //client internet socket address
    unsigned int clitLen; // client internet socket address length

    pthread_t th;

    if ((servSock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0 ){
        fprintf(stderr, "Failed socket().\n");
        return -1;
    }

    memset(&servSockAddr, 0, sizeof(servSockAddr));
    servSockAddr.sin_family      = AF_INET;
    servSockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servSockAddr.sin_port        = htons(SERVER_PORT);

    if (bind(servSock, (struct sockaddr *) &servSockAddr, sizeof(servSockAddr) ) < 0 ) {
        fprintf(stderr, "Failed bind().\n");
        close(servSock);
        return -1;
    }

    if (listen(servSock, QUEUELIMIT) < 0) {
        fprintf(stderr, "Failed listen().\n");
        close(servSock);
        return -1;
    }

    while(1) {
        clit = malloc(sizeof(struct clientdata));
        clitLen = sizeof(clit->saddr);
        if ((clit->sock = accept(servSock, (struct sockaddr *) &clit->saddr, &clitLen)) < 0) {
            fprintf(stderr, "Failed accept().\n");
            close(servSock);
            break;
        }
        printf("Connected from %s.\n", inet_ntoa(clit->saddr.sin_addr));

        if (pthread_create(&th, NULL, motor_control_thread, clit) != 0) {
            fprintf(stderr, "Failed pthread_create().\n");
            break;
        }

        if (pthread_detach(th) != 0) {
            fprintf(stderr, "Failed pthread_detach().\n");
            break;
        }
    }
    close(servSock);
    return 0;
}

int main(void)
{
    int ret = gpioInitialise();
    if (ret < 0)
    {
        return ret;
    }
    else
    {
        ret = motor_control_server();
        gpioTerminate();
    }

    return ret;
}

ソケットの使い方やスレッドを使った複数クライアントからの受付などについては、下記の参考記事を参照してください。

コードの量は増えましたが、基本的な処理の流れは「pigpioライブラリの使い方(C言語編)」で見たコードと変わっていません。PWM出力する部分がクライアントから値を受け取って、その値でPWM出力するようにしているだけです。

分かってしまえば「なんだ、そんなことか」と感じると思います。

モーター制御サーバーの動作確認

では実際に作成したサーバーの動作確認を行います。まずはラズパイ上でビルドした "motor_server" を実行します。"motor_server" ファイルがあるディレクトリに移動したら、下記コマンドでサーバーを起動します。

sudo ./motor_server

このとき注意すべきこととして、pigpioライブラリはハードウェアを触るためスーパーユーザー権限を要求してきます。したがって、pigpioライブラリを使ったプログラムは "sudo" を付ける必要があります。

"motor_server" を起動した状態でMacやWindowsのターミナルから "netcat" で接続します。私の環境はラズパイのIPアドレスが "192.168.1.43" で、サンプルコードはポート番号を50001に設定しているので、下記コマンドで "motor_server" に接続します。

nc 192.168.1.43 50001

接続できたらモーターを回すためのDuty値を入力します。値は -1000000 〜 1000000 の間になります。

たとえば、"250000" を入力すると大体25%程度の速度で正転します。逆に "-250000" と入力すると大体25%程度の速度で逆転します。

参考までに実際にモーター制御している様子を撮った動画を載せておきます。

まとめ

pigpioライブラリはとても使いやすいライブラリです。用意されているI/Fもわかりやすいので、公式の使い方を少し眺めるだけで意図通りに動作させることができます。ラズパイのgpioを操作するのであれば、抑えておきたいライブラリの1つですね。

さて、今回でモーター制御をサーバー化することができたので、次回はクライアントの作成を行います。温湿度センサーの時のようにSwiftでiOSのクライアントアプリを作成して、iPhoneからモーターの回転数と回転方向を制御できるようにします。

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

コメント

コメントする

目次