Ajoutez modbus à Embox RTOS et utilisez-le sur STM32 et pas seulement

image

On nous demande souvent en quoi Embox diffère des autres systèmes d'exploitation de microcontrôleur, par exemple FreeRTOS? Il est bien entendu correct de comparer les projets entre eux. Mais les paramètres par lesquels la comparaison est parfois proposée, me plongent personnellement dans un léger désarroi. Par exemple, de combien de mémoire Embox a-t-il besoin pour fonctionner? Quel est le temps de basculer entre les tâches? Embox prend-il en charge le modbus? Dans cet article, en utilisant l'exemple d'une question sur modbus, nous voulons montrer que la différence entre Embox est une approche différente du processus de développement.



Développons un appareil qui comprendra un serveur Modbus. Notre appareil sera simple. Après tout, il est uniquement destiné à la démonstration de Modbus.Cet appareil permettra de contrôler les LED en utilisant le protocole Modbus. Pour communiquer avec l'appareil, nous utiliserons une connexion Ethernet.



Modbus est un protocole de communication ouvert. Il est largement utilisé dans l'industrie pour organiser la communication entre les appareils électroniques. Il peut être utilisé pour transférer des données via des lignes de communication série RS-485, RS-422, RS-232 et réseaux TCP / IP (Modbus TCP).



Le protocole modbus est assez simple à implémenter vous-même. Mais comme toute nouvelle implémentation de la fonctionnalité peut contenir des bogues, utilisons quelque chose de prêt à l'emploi.



L'une des implémentations les plus populaires du protocole modbus est le projet open source libmodbus . Nous allons l'utiliser. Cela réduira le temps de développement et les erreurs. Dans le même temps, nous pourrons nous concentrer sur la mise en œuvre de la logique métier, et non sur l'étude du protocole.



Notre projet sera conservé dans un référentiel séparé . Si vous le souhaitez, vous pouvez tout télécharger et jouer vous-même.



Développement de prototype Linux



Commençons par développer un prototype sur l'hôte. Afin de pouvoir utiliser libmodbus comme bibliothèque, vous devez le télécharger, le configurer et le construire.

Pour cela, j'ai esquissé un 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 ..;
      
      





En fait, à partir des paramètres de configuration, nous n'utilisons que le préfixe pour construire la bibliothèque localement. Et puisque nous voulons utiliser la bibliothèque non seulement sur l'hôte, nous en construirons une version statique.



Nous avons maintenant besoin d'un serveur Modbus. Il y a des exemples dans le projet libmodbus, faisons notre implémentation basée sur un serveur simple.



    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);
      
      





Tout est standard ici. Quelques points d'intérêt sont les fonctions mb_mapping_getstates et leddrv_updatestates. C'est exactement la fonctionnalité que notre appareil implémente.



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;
    }
}

      
      





Ainsi, nous avons besoin de leddrv_updatestates, qui définit l'état des LED, et de leddrv_getstates, qui obtient l'état des LED.




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;
}
      
      





Puisque nous voulons que notre logiciel fonctionne à la fois sur la carte et sur l'hôte, nous avons besoin de différentes implémentations des fonctions pour définir et obtenir l'état des LED. Stockons l'état de l'hôte dans un fichier normal. Cela permettra d'obtenir l'état des LED dans d'autres processus.



Par exemple, si nous voulons vérifier les états via un site Web, nous lancerons le site Web et spécifierons ce fichier comme source de données.



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';
    }
}
      
      





Nous devons spécifier le fichier dans lequel l'état initial des LED sera enregistré. Le format de fichier est simple. Les états des LED sont répertoriés séparés par des virgules, 1 - LED est allumée et 0 - éteinte. Notre appareil dispose de 80 LED, plus précisément de 40 paires de LED. Supposons que par défaut les LED paires soient éteintes et les impaires allumées. Contenu du fichier



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
      
      







Nous démarrons le serveur

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

      
      





Nous avons maintenant besoin d'un client pour gérer notre appareil. Il est également très facile de le développer à partir d'un exemple de 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);

      
      





Nous lançons le client. Installez 78 LED, qui est éteint par défaut



./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
      
      





Sur le serveur, nous verrons:



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





Autrement dit, la LED est installée. Désactivons-le.



./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
      
      





Sur le serveur, nous verrons un message concernant le changement:



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





Commençons le serveur http. Nous avons parlé du développement de sites Web dans l' article . De plus, nous n'avons besoin du site Web que pour une démonstration plus pratique du fonctionnement du modbus. Par conséquent, je n'entrerai pas dans les détails. Je vais immédiatement donner un script 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
      
      





Et laissez-moi vous rappeler que vous pouvez commencer à utiliser n'importe quel serveur http avec le support CGI. Nous utilisons le serveur intégré de python. Exécutez avec la commande suivante:



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





Ouvrons notre site Web dans un navigateur:







Installez 78 LED à l'aide du client:



./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
      
      





réinitialiser la DEL 79:



./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
      
      





Sur le site, nous verrons la différence: en







fait, tout, notre bibliothèque fonctionne très bien sous Linux.



Adaptation à Embox et exécution sur l'émulateur



Bibliothèque Libmodbus



Nous devons maintenant déplacer le code vers Embox. commençons par le projet libmodbus lui-même.

C'est simple. Nous avons besoin d'une description du module (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
}
      
      





Nous utilisons l'annotation Construire(script = "$ (EXTERNAL_MAKE)") nous indiquons que nous utilisons le Makefile pour travailler avec des projets externes.



Utiliser l'annotation ConstruireArtifactPath ajoute des chemins pour trouver les fichiers d'en-tête aux modules qui dépendront de cette bibliothèque.



Et nous disons que nous avons besoin de la bibliothèque source "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 $@
      
      





Le makefile de construction est également simple et direct. La seule chose que je note est que nous utilisons le compilateur interne ( $ (EMBOX_GCC) ) Embox et que la plate-forme ( --host ) nous transmettons celui défini dans Embox ( $ (AUTOCONF_TARGET_TRIPLET) ).



Nous connectons le projet à Embox



Permettez-moi de vous rappeler que pour faciliter le développement, nous avons créé un référentiel distinct. Pour le connecter à Embox, il suffit d'indiquer à Embox où se trouve le projet externe.



Cela se fait à l'aide de la commande



make ext_conf EXT_PROJECT_PATH=<path to project> 
      
      





à la racine d'Embox. Par exemple,



 make ext_conf EXT_PROJECT_PATH=~/git/embox_project_modbus_iocontrol
      
      





serveur modbus



Le code source du serveur Modbus ne nécessite aucune modification. Autrement dit, nous utilisons le même code que nous avons développé sur l'hôte. Nous devons ajouter 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
}
      
      





Dans lequel, à l'aide d'annotations, nous indiquons que c'est notre commande, et aussi que cela dépend de la bibliothèque libmodbus.



Nous aurons également besoin de bibliothèques d'émulation. Je ne leur donnerai pas Mybuild pour eux, ils sont triviaux, notez simplement que les sources sont également utilisées sans modifications.



Nous devons également construire notre système avec un serveur Modbus.



Ajoutez nos modules à 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
      
      





Et nous mettons notre fichier leds.txt avec les états des LED dans le système de fichiers racine. Mais comme nous avons besoin d'un fichier mutable, ajoutons un disque RAM et copions notre fichier sur ce disque. Contenu de 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",
      
      





C'est suffisant pour exécuter Embox sur qemu:



./scripts/qemu/auto_qemu
      
      





Les serveurs modbus et httpd démarrent automatiquement au démarrage. Définissons les mêmes valeurs en utilisant le client modbus, uniquement en spécifiant l'adresse de notre QEMU (10.0.2.16):



./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
      
      





et en conséquence



./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>
      
      





Ouvrons le navigateur:







comme prévu, tout est pareil. Nous pouvons contrôler l'appareil via le protocole Modbus déjà sur Embox.



Fonctionnement sur un microcontrôleur



Pour fonctionner sur un microcontrôleur, nous utiliserons la découverte STM32F4. Dans les captures d'écran ci-dessus des pages du navigateur, vous pouvez voir que 80 jambes de sortie sont utilisées, combinées par paires, et vous pouvez également remarquer que ces paires ont d'autres propriétés, par exemple, vous pouvez définir un nom, ou la paire peut être Souligné. En fait, le code a été extrait d'un projet réel et les parties inutiles en ont été supprimées pour plus de simplicité. 80 broches de sortie ont été obtenues en utilisant des circuits intégrés de registre à décalage supplémentaires.



Mais il n'y a que 4 voyants sur la carte de découverte STM32F4. Il serait pratique de régler le nombre de LED pour ne pas modifier le code source Embox dispose d'un mécanisme permettant de paramétrer les modules. Vous devez ajouter l'option dans la description du module (Mybuild)



package iocontrol.modbus.lib

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





Et il sera possible d'utiliser dans le code



#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
      
      





Dans ce cas, vous pouvez modifier ce paramètre en le spécifiant dans le fichier mods.conf



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





si le paramètre n'est pas spécifié, alors celui qui est défini dans le module par défaut est utilisé, c'est-à-dire 80.



Nous devons également contrôler les lignes de sortie réelles. Le code est comme suit:



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);
    }
}
      
      





Dans le fichier mods.conf, nous avons besoin de la configuration de notre carte. Nous y ajoutons nos modules:



    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
      
      





En fait, les mêmes modules que pour ARM QEMU, à l'exception du pilote, bien sûr.



Nous collectons, flash, lançons. Et avec l'aide du même client modbus, nous contrôlons les LED. Il vous suffit de mettre la bonne adresse, et n'oubliez pas que nous n'avons que 4 LED sur la carte.



Le fonctionnement de la carte stm32f4-discovery peut être vu dans cette courte vidéo:





résultats



En utilisant cet exemple simple, nous avons essayé de montrer quelle est la principale différence entre Embox et d'autres systèmes d'exploitation pour microcontrôleurs. Y compris ceux qui sont compatibles POSIX. Après tout, nous avons essentiellement pris un module prêt à l'emploi, développé une logique métier sous Linux en utilisant plusieurs applications. Et nous avons tout lancé sur notre plateforme cible. Ainsi, simplifier et accélérer considérablement le développement lui-même.



Oui, bien sûr, l'application est démo et pas compliquée. Le protocole Modbus lui-même pourrait également être implémenté indépendamment. Mais dans ce cas, nous aurions besoin de comprendre le protocole Modbus. Et notre approche permet à chaque spécialiste de se concentrer sur sa part. Et bien sûr, la plupart des problèmes sont résolus sur l'hôte, ce qui est bien plus pratique que de développer directement sur la carte.



All Articles