modbusをEmboxRTOSに追加し、STM32だけでなく使用します

画像

Emboxが他のマイクロコントローラーオペレーティングシステム(FreeRTOSなど)とどのように違うのかとよく聞かれます もちろん、プロジェクトを相互に比較することは正しいことです。しかし、比較が時々提供されるパラメータは、個人的に私をわずかな戸惑いに陥らせます。たとえば、Emboxを実行するにはどのくらいのメモリが必要ですか?タスクを切り替える時間は何ですか?Emboxはmodbusをサポートしていますか?この記事では、modbusに関する質問の例を使用して、Emboxの違いが開発プロセスへの異なるアプローチであることを示したいと思います。



modbusサーバーを含むデバイスを開発しましょう。私たちのデバイスはシンプルになります。結局のところ、これはmodbusのデモンストレーションのみを目的としています。このデバイスでは、Modbusプロトコルを使用してLEDを制御できます。デバイスと通信するには、イーサネット接続を使用します。



Modbusはオープンな通信プロトコルです。これは、電子デバイス間の通信を整理するために業界で広く使用されています。シリアル通信回線RS-485、RS-422、RS-232およびTCP / IPネットワーク(Modbus TCP)を介してデータを転送するために使用できます。



modbusプロトコルは、自分で実装するのに十分シンプルです。ただし、この機能の新しい実装にはバグが含まれている可能性があるため、既製のものを使用しましょう。



modbusプロトコルの最も人気のある実装の1つは、オープンソースのlibmodbusプロジェクト です。使用します。これにより、開発時間が短縮され、エラーが減少します。同時に、プロトコルの研究ではなく、ビジネスロジックの実装に集中できるようになります。



私たちのプロジェクトは別のリポジトリに保存され ます必要に応じて、すべてを自分でダウンロードして再生できます。



Linuxプロトタイプ開発



ホスト上でプロトタイプを開発することから始めましょう。libmodbusをライブラリとして使用できるようにするには、ライブラリをダウンロード、構成、およびビルドする必要があります。

この目的のために、私はMakefileをスケッチしました



libmodbus-$(LIBMODBUS_VER).tar.gz:
    wget http://libmodbus.org/releases/libmodbus-$(LIBMODBUS_VER).tar.gz

$(BUILD_BASE)/libmodbus/lib/pkgconfig/libmodbus.pc : libmodbus-$(LIBMODBUS_VER).tar.gz
    tar -xf libmodbus-$(LIBMODBUS_VER).tar.gz
    cd libmodbus-$(LIBMODBUS_VER); \
    ./configure --prefix=$(BUILD_BASE)/libmodbus --enable-static --disable-shared; \
    make install; cd ..;
      
      





実際には、構成パラメーターから、ローカルでライブラリーを構築するためにプレフィックスのみを使用します。また、ホストだけでなくライブラリも使用したいので、静的バージョンを作成します。



次に、modbusサーバーが必要です。libmodbusプロジェクトには例がありますが、いくつかの単純なサーバーに基づいて実装を作成しましょう。



    ctx = modbus_new_tcp(ip, port);
    header_len = modbus_get_header_length(ctx);
    query = malloc(MODBUS_TCP_MAX_ADU_LENGTH);

    modbus_set_debug(ctx, TRUE);

    mb_mapping = mb_mapping_wrapper_new();
    if (mb_mapping == NULL) {
        fprintf(stderr, "Failed to allocate the mapping: %s\n",
                modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }

    listen_socket = modbus_tcp_listen(ctx, 1);
    for (;;) {
        client_socket = modbus_tcp_accept(ctx, &listen_socket);
        if (-1 == client_socket) {
            break;
        }

        for (;;) {
            int query_len;

            query_len = modbus_receive(ctx, query);
            if (-1 == query_len) {
                /* Connection closed by the client or error */
                break;
            }

            if (query[header_len - 1] != MODBUS_TCP_SLAVE) {
                continue;
            }

            mb_mapping_getstates(mb_mapping);

            if (-1 == modbus_reply(ctx, query, query_len, mb_mapping)) {
                break;
            }

            leddrv_updatestates(mb_mapping->tab_bits);
        }

        close(client_socket);
    }
    printf("exiting: %s\n", modbus_strerror(errno));

    close(listen_socket);
    mb_mapping_wrapper_free(mb_mapping);
    free(query);
    modbus_free(ctx);
      
      





ここではすべてが標準です。興味深い場所は、mb_mapping_getstates関数とleddrv_updatestates関数です。これはまさに私たちのデバイスが実装する機能です。



static modbus_mapping_t *mb_mapping_wrapper_new(void) {
    modbus_mapping_t *mb_mapping;
    mb_mapping = modbus_mapping_new(LEDDRV_LED_N, 0, 0, 0);

    return mb_mapping;
}

static void mb_mapping_wrapper_free(modbus_mapping_t *mb_mapping) {

    modbus_mapping_free(mb_mapping);
}

static void mb_mapping_getstates(modbus_mapping_t *mb_mapping) {
    int i;

    leddrv_getstates(mb_mapping->tab_bits);

    for (i = 0; i < mb_mapping->nb_bits; i++) {
        mb_mapping->tab_bits[i] = mb_mapping->tab_bits[i] ? ON : OFF;
    }
}

      
      





したがって、LEDの状態を設定するleddrv_updatestatesと、LEDの状態を取得するleddrv_getstatesが必要です。




static unsigned char leddrv_leds_state[LEDDRV_LED_N];

int leddrv_init(void) {
    static int inited = 0;
    if (inited) {
        return 0;
    }
    inited = 1;
    leddrv_ll_init();

    leddrv_load_state(leddrv_leds_state);
    leddrv_ll_update(leddrv_leds_state);

    return 0;
}

...
int leddrv_getstates(unsigned char leds_state[LEDDRV_LED_N]) {
    memcpy(leds_state, leddrv_leds_state, sizeof(leddrv_leds_state));
    return 0;
}

int leddrv_updatestates(unsigned char new_leds_state[LEDDRV_LED_N]) {
    memcpy(leddrv_leds_state, new_leds_state, sizeof(leddrv_leds_state));
    leddrv_ll_update(leddrv_leds_state);
    return 0;
}
      
      





ソフトウェアをボードとホストの両方で動作させたいので、LEDの状態を設定および取得するための関数のさまざまな実装が必要です。ホストの状態を通常のファイルに保存しましょう。これにより、LEDのステータスを他のプロセスで取得できるようになります。



たとえば、Webサイトを介して状態を確認する場合は、Webサイトを起動し、このファイルをデータソースとして指定します。



void leddrv_ll_update(unsigned char leds_state[LEDDRV_LED_N]) {
    int i;
    int idx;
    char buff[LEDDRV_LED_N * 2];
    
    for (i = 0; i < LEDDRV_LED_N; i++) {
        char state = !!leds_state[i];
        fprintf(stderr, "led(%03d)=%d\n", i, state);
        buff[i * 2] = state + '0';
        buff[i * 2 + 1] = ',';
    }
    idx = open(LED_FILE_NAME, O_RDWR);
    if (idx < 0) {
        return;
    }

    write(idx, buff, (LEDDRV_LED_N * 2) - 1);

    close(idx);
}

...

void leddrv_load_state(unsigned char leds_state[LEDDRV_LED_N]) {
    int i;
    int idx;
    char buff[LEDDRV_LED_N * 2];

    idx = open(LED_FILE_NAME, O_RDWR);
    if (idx < 0) {
        return;
    }
    read(idx, buff, (LEDDRV_LED_N * 2));
    close(idx);
    
    for (i = 0; i < LEDDRV_LED_N; i++) {
        leds_state[i] = buff[i * 2] - '0';
    }
}
      
      





LEDの初期状態を保存するファイルを指定する必要があります。ファイル形式は単純です。LEDの状態は、コンマで区切られてリストされています。1-LEDはオン、0-オフです。私たちのデバイスには80個のLED、より正確には40ペアのLEDがあります。デフォルトでは、偶数のLEDがオフになり、奇数のLEDがオンになると仮定します。ファイルの内容



0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1,0,1
      
      







サーバーを起動します

./led-server
led(000)=0
led(001)=1
...
led(078)=0
led(079)=1

      
      





次に、デバイスを管理するためのクライアントが必要です。libmodbusの例に基づいて開発することも非常に簡単です



ctx = modbus_new_tcp(ip, port);
    if (ctx == NULL) {
        fprintf(stderr, "Unable to allocate libmodbus context\n");
        return -1;
    }

    modbus_set_debug(ctx, TRUE);
    modbus_set_error_recovery(ctx,
            MODBUS_ERROR_RECOVERY_LINK |
            MODBUS_ERROR_RECOVERY_PROTOCOL);

    if (modbus_connect(ctx) == -1) {
        fprintf(stderr, "Connection failed: %s\n",
                modbus_strerror(errno));
        modbus_free(ctx);
        return -1;
    }


    if (1 == modbus_write_bit(ctx, bit_n, bit_value)) {
        printf("OK\n");
    } else {
        printf("FAILED\n");
    }

    /* Close the connection */
    modbus_close(ctx);
    modbus_free(ctx);

      
      





クライアントを起動します。デフォルトでオフになっている78個のLEDを取り付けます



./led-client set 78
Connecting to 127.0.0.1:1502
[00][01][00][00][00][06][FF][05][00][4E][FF][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><4E><FF><00>
OK
      
      





サーバー上に次のように表示されます。



...
led(076)=0
led(077)=1
led(078)=1
led(079)=1
Waiting for an indication...
ERROR Connection reset by peer: read
      
      





つまり、LEDが取り付けられています。オフにしましょう。



./led-client clr 78
Connecting to 127.0.0.1:1502
[00][01][00][00][00][06][FF][05][00][4E][00][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><4E><00><00>
OK
      
      





サーバー上で、変更に関するメッセージが表示されます。



...
led(076)=0
led(077)=1
led(078)=0
led(079)=1
Waiting for an indication...
ERROR Connection reset by peer: read
      
      





httpサーバーを起動しましょう。記事でウェブサイトの開発について話しました さらに、modbusがどのように機能するかをより便利にデモンストレーションするために必要なのはWebサイトだけです。したがって、詳細については説明しません。すぐにcgiスクリプトを提供します。



#!/bin/bash

echo -ne "HTTP/1.1 200 OK\r\n"
echo -ne "Content-Type: application/json\r\n"
echo -ne "Connection: close\r\n"
echo -ne "\r\n"

if [ $REQUEST_METHOD = "GET" ]; then
    echo "Query: $QUERY_STRING" >&2
    case "$QUERY_STRING" in
        "c=led_driver&a1=serialize_states")
            echo [ $(cat ../emulate/conf/leds.txt) ]
            ;;
        "c=led_driver&a1=serialize_errors")
            echo [ $(printf "0, %.0s" {1..79}) 1 ]
            ;;
        "c=led_names&a1=serialize")
            echo '[ "one", "two", "WWWWWWWWWWWWWWWW", "W W W W W W W W " ]'
            ;;
    esac
elif [ $REQUEST_METHOD = "POST" ]; then
    read -n $CONTENT_LENGTH POST_DATA
    echo "Posted: $POST_DATA" >&2
fi
      
      





また、CGIをサポートする任意のhttpサーバーの使用を開始できることをお知らせします。Pythonの組み込みサーバーを使用しています。次のコマンドで実行します。



python3 -m http.server --cgi -d .
      
      





ブラウザでウェブサイトを開きましょう:







クライアントを使用して78個のLEDをインストールします。



./led-client -a 127.0.0.1 set 78
Connecting to 127.0.0.1:1502
[00][01][00][00][00][06][FF][05][00][4E][FF][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><4E><FF><00>
OK
      
      





79LEDをリセットします。



./led-client -a 127.0.0.1 clr 79
Connecting to 127.0.0.1:1502
[00][01][00][00][00][06][FF][05][00][4F][00][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><4F><00><00>
OK
      
      





ウェブサイトで違いがわかります。







実際、私たちのライブラリはすべてLinuxでうまく機能します。



Emboxへの適応とエミュレーターでの実行



Libmodbusライブラリ



次に、コードをEmboxに移動する必要があります。libmodbusプロジェクト自体から始めましょう。

それは簡単です。モジュール(Mybuild)の説明が必要です。



package third_party.lib

@Build(script="$(EXTERNAL_MAKE)")
@BuildArtifactPath(cppflags="-I$(ROOT_DIR)/build/extbld/third_party/lib/libmodbus/install/include/modbus")
module libmodbus {
    @AddPrefix("^BUILD/extbld/^MOD_PATH/install/lib")
    source "libmodbus.a"

    @NoRuntime depends embox.compat.posix.util.nanosleep
}
      
      





アノテーションを使用しています ビルド(script = "$(EXTERNAL_MAKE)") Makefileを使用して外部プロジェクトを操作すること示します。



注釈の使用 ビルドArtifactPathは、このライブラリに依存するモジュールにヘッダーファイルを検索するためのパスを追加します。



そして、ソースライブラリ「libmodbus.a」が必要だと言います



PKG_NAME := libmodbus
PKG_VER  := 3.1.6

PKG_SOURCES := http://libmodbus.org/releases/$(PKG_NAME)-$(PKG_VER).tar.gz
PKG_MD5     := 15c84c1f7fb49502b3efaaa668cfd25e

PKG_PATCHES := accept4_disable.patch

include $(EXTBLD_LIB)

libmodbus_cflags = -UHAVE_ACCEPT4

$(CONFIGURE) :
    export EMBOX_GCC_LINK=full; \
    cd $(PKG_SOURCE_DIR) && ( \
        CC=$(EMBOX_GCC) ./configure --host=$(AUTOCONF_TARGET_TRIPLET) \
        prefix=$(PKG_INSTALL_DIR) \
        CFLAGS=$(libmodbus_cflags) \
    )
    touch $@

$(BUILD) :
    cd $(PKG_SOURCE_DIR) && ( \
        $(MAKE) install MAKEFLAGS='$(EMBOX_IMPORTED_MAKEFLAGS)'; \
    )
    touch $@
      
      





ビルドメイクファイルもシンプルで簡単です。私が注意する唯一のことは、内部コンパイラ($(EMBOX_GCC)Emboxを使用 し、プラットフォーム(--hostとして Embox$(AUTOCONF_TARGET_TRIPLET))に設定されたものを渡すこと です。



プロジェクトをEmboxに接続します



開発の便宜のために、別のリポジトリを作成したことを思い出してください。Emboxに接続するには、外部プロジェクトがどこにあるかをEmboxに伝えるだけで十分です。



これは、コマンドを使用して行われます



make ext_conf EXT_PROJECT_PATH=<path to project> 
      
      





Emboxのルートにあります。例えば、



 make ext_conf EXT_PROJECT_PATH=~/git/embox_project_modbus_iocontrol
      
      





modbusサーバー



modbusサーバーのソースコードを変更する必要はありません。つまり、ホストで開発したものと同じコードを使用しています。Mybuildを追加する必要があります。



package iocontrol.modbus.cmd

@AutoCmd
@Build(script="true")
@BuildDepends(third_party.lib.libmodbus)
@Cmd(name="modbus_server")
module modbus_server {
    source "modbus_server.c"

    @NoRuntime depends third_party.lib.libmodbus
}
      
      





ここでは、注釈の助けを借りて、これが私たちのコマンドであり、libmodbusライブラリに依存していることを示しています。



エミュレーションライブラリも必要になります。私はそれらにMybuildを提供しません、それらは些細なことです、ただソースも変更なしで使用されることに注意してください。



また、modbusサーバーと一緒にシステムを構築する必要があります。



モジュールをmods.confに追加します。



    include iocontrol.modbus.http_admin
    include iocontrol.modbus.cmd.flash_settings
    include iocontrol.modbus.cmd.led_names
    include third_party.lib.libmodbus
    include iocontrol.modbus.cmd.modbus_server
    include iocontrol.modbus.cmd.led_driver

    include embox.service.cgi_cmd_wrapper(cmds_check=true, allowed_cmds="led_driver led_names flash_settings")

    include iocontrol.modbus.lib.libleddrv_ll_stub
      
      





そして、LEDステータスのleds.txtファイルをルートファイルシステムに配置します。ただし、変更可能なファイルが必要なので、RAMディスクを追加して、ファイルをそのディスクにコピーしましょう。System_start.incコンテンツ:



"export PWD=/",
"export HOME=/",
"netmanager",
"service telnetd",
"service httpd http_admin",
"ntpdate 0.europe.pool.ntp.org",
"mkdir -v /conf",
"mount -t ramfs /dev/static_ramdisk /conf",
"cp leds.txt /conf/leds.txt",
"led_driver init",
"service modbus_server",
"tish",
      
      





これはqemuでEmboxを実行するのに十分です:



./scripts/qemu/auto_qemu
      
      





modbusおよびhttpdサーバーは起動時に自動的に起動します。QEMU(10.0.2.16)のアドレスを指定するだけで、modbusクライアントを使用して同じ値を設定しましょう:



./led-client -a 10.0.2.16 set 78
Connecting to 10.0.2.16:1502
[00][01][00][00][00][06][FF][05][00][4E][FF][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><4E><FF><00>
OK
      
      





それに応じて



./led-client -a 10.0.2.16 clr 79
Connecting to 10.0.2.16:1502
[00][01][00][00][00][06][FF][05][00][4F][00][00]
Waiting for a confirmation...
<00><01><00><00><00><06><FF><05><00><4F><00><00>
      
      





ブラウザを開きましょう:







予想通り、すべてが同じです。すでにEmboxにあるmodbusプロトコルを介してデバイスを制御できます。



マイクロコントローラーで実行



マイクロコントローラーで実行するには、STM32F4-discoveryを使用します。上記のブラウザページのスクリーンショットでは、80レッグの出力が使用され、ペアで組み合わされていることがわかります。また、これらのペアには他のプロパティがあることもわかります。たとえば、名前を設定したり、ペアを設定したりできます。強調表示されます。実際、コードは実際のプロジェクトから取得され、簡単にするために不要な部分が削除されています。追加のシフトレジスタICを使用して80個の出力ピンが得られました。



ただし、STM32F4検出ボードには4つのLEDしかありません。ソースコードを変更しないようにLEDの数を設定すると便利です。Emboxにはモジュールをパラメータ化できるメカニズムがあります。モジュールの説明にオプションを追加する必要があります(Mybuild)



package iocontrol.modbus.lib

static module libleddrv {
    option number leds_quantity = 80
...
}
      
      





そして、コードで使用することが可能になります



#ifdef __EMBOX__
#include <framework/mod/options.h>
#include <module/iocontrol/modbus/lib/libleddrv.h>
#define LEDDRV_LED_N OPTION_MODULE_GET(iocontrol__modbus__lib__libleddrv,NUMBER,leds_quantity)
#else
#define LEDDRV_LED_N 80
#endif
      
      





この場合、mods.confファイルで指定することでこのパラメーターを変更できます。



    include  iocontrol.modbus.lib.libleddrv(leds_quantity=4)
      
      





パラメータが指定されていない場合は、デフォルトでモジュールに設定されているもの、つまり80が使用されます



。実際の出力ラインも制御する必要があります。コードは次のとおりです。



struct leddrv_pin_desc {
    int gpio; /**< port */
    int pin; /**< pin mask */
};

static const struct leddrv_pin_desc leds[] = {
    #include <leds_config.inc>
};


void leddrv_ll_init(void) {
    int i;
    for (i = 0; i < LEDDRV_LED_N; i++) {
        gpio_setup_mode(leds[i].gpio, leds[i].pin, GPIO_MODE_OUTPUT);
    }
}

void leddrv_ll_update(unsigned char leds_state[LEDDRV_LED_N]) {
    int i;

    for (i = 0; i < LEDDRV_LED_N; i++) {
        gpio_set(leds[i].gpio, leds[i].pin,
                leds_state[i] ? GPIO_PIN_HIGH : GPIO_PIN_LOW);
    }
}
      
      





mods.confファイルで、ボードの構成が必要です。モジュールを追加します。



    include iocontrol.modbus.http_admin
    include iocontrol.modbus.cmd.flash_settings
    include iocontrol.modbus.cmd.led_names
    include third_party.lib.libmodbus
    include iocontrol.modbus.cmd.modbus_server
    include iocontrol.modbus.cmd.led_driver

    include embox.service.cgi_cmd_wrapper(cmds_check=true, allowed_cmds="led_driver led_names flash_settings")

    include iocontrol.modbus.lib.libleddrv(leds_quantity=4)
    include iocontrol.modbus.lib.libleddrv_ll_stm32_f4_demo
      
      





実際、もちろん、ドライバーを除いて、ARMQEMUと同じモジュールです。



収集、フラッシュ、起動します。そして、同じmodbusクライアントの助けを借りて、LEDを制御します。正しいアドレスを入力するだけで、ボードには4つのLEDしかないことを忘れないでください。



stm32f4-discoveryボードの操作は、次の短いビデオで見ることができます。





調査結果



この簡単な例を使用して、Emboxと他のマイクロコントローラー用オペレーティングシステムの主な違いを示しました。POSIXに準拠しているものを含みます。結局のところ、私たちは基本的に既製のモジュールを採用し、いくつかのアプリケーションを使用してLinux上でビジネスロジックを開発しました。そして、すべてをターゲットプラットフォームでリリースしました。したがって、開発自体を大幅に簡素化および高速化します。



はい、もちろん、アプリケーションはデモであり、複雑ではありません。modbusプロトコル自体も独立して実装できます。ただし、この場合、modbusプロトコルを理解する必要があります。そして私たちのアプローチは、各スペシャリストが自分の部分に集中することを可能にします。そしてもちろん、ほとんどの問題はホスト上で解決されます。これは、ボード上で直接開発するよりもはるかに便利です。



All Articles