Wir werden oft gefragt, wie sich Embox von anderen Mikrocontroller-Betriebssystemen unterscheidet, zum Beispiel FreeRTOS. Es ist natürlich richtig, Projekte miteinander zu vergleichen. Aber die Parameter, anhand derer der Vergleich manchmal angeboten wird, versetzen mich persönlich in leichte Verwirrung. Wie viel Speicher benötigt Embox beispielsweise, um ausgeführt zu werden? Wann ist die Zeit, um zwischen Aufgaben zu wechseln? Unterstützt Embox Modbus? In diesem Artikel möchten wir am Beispiel einer Frage zu Modbus zeigen, dass der Unterschied zwischen Embox eine andere Herangehensweise an den Entwicklungsprozess ist.
Lassen Sie uns ein Gerät entwickeln, das einen Modbus-Server enthält. Unser Gerät wird einfach sein. Schließlich ist es nur zur Demonstration des Modbus gedacht. Dieses Gerät ermöglicht die Steuerung von LEDs mithilfe des Modbus-Protokolls. Für die Kommunikation mit dem Gerät verwenden wir eine Ethernet-Verbindung.
Modbus ist ein offenes Kommunikationsprotokoll. Es ist in der Industrie weit verbreitet, um die Kommunikation zwischen elektronischen Geräten zu organisieren. Es kann zur Datenübertragung über serielle Kommunikationsleitungen RS-485-, RS-422-, RS-232- und TCP / IP-Netzwerke (Modbus TCP) verwendet werden.
Das Modbus-Protokoll ist einfach genug, um es selbst zu implementieren. Da jedoch jede neue Implementierung der Funktionalität Fehler enthalten kann, verwenden wir etwas Fertiges.
Eine der beliebtesten Implementierungen des Modbus-Protokolls ist das Open-Source-Projekt libmodbus . Wir werden es benutzen. Dies reduziert die Entwicklungszeit und reduziert Fehler. Gleichzeitig können wir uns auf die Implementierung der Geschäftslogik konzentrieren und nicht auf das Studium des Protokolls.
Unser Projekt wird in einem separaten Repository gespeichert . Wenn Sie möchten, können Sie alles selbst herunterladen und abspielen.
Entwicklung von Linux-Prototypen
Beginnen wir mit der Entwicklung eines Prototyps auf dem Host. Um libmodbus als Bibliothek verwenden zu können, müssen Sie es herunterladen, konfigurieren und erstellen.
Zu diesem Zweck habe ich ein Makefile entworfen
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 ..;
Tatsächlich verwenden wir aus den Konfigurationsparametern nur das Präfix, um die Bibliothek lokal zu erstellen. Und da wir die Bibliothek nicht nur auf dem Host verwenden möchten, werden wir eine statische Version davon erstellen.
Jetzt brauchen wir einen Modbus-Server. Es gibt Beispiele im libmodbus-Projekt. Lassen Sie uns unsere Implementierung auf einem einfachen Server basieren.
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);
Hier ist alles Standard. Einige interessante Orte sind die Funktionen mb_mapping_getstates und leddrv_updatestates. Dies ist genau die Funktionalität, die unser Gerät implementiert.
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;
}
}
Daher benötigen wir leddrv_updatestates, das den Status der LEDs festlegt, und leddrv_getstates, das den Status der LEDs abruft.
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;
}
Da unsere Software sowohl auf der Karte als auch auf dem Host funktionieren soll, benötigen wir unterschiedliche Implementierungen der Funktionen zum Einstellen und Abrufen des Status der LEDs. Speichern wir den Status für den Host in einer regulären Datei. Dadurch kann der Status der LEDs in anderen Prozessen abgerufen werden.
Wenn wir beispielsweise den Status über eine Website überprüfen möchten, starten wir die Website und geben diese Datei als Datenquelle an.
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';
}
}
Wir müssen die Datei angeben, in der der Anfangszustand der LEDs gespeichert wird. Das Dateiformat ist einfach. Die Zustände der LEDs werden durch Kommas getrennt aufgelistet, 1 - LED leuchtet und 0 - aus. Unser Gerät verfügt über 80 LEDs, genauer gesagt 40 LED-Paare. Nehmen wir an, dass standardmäßig die geraden LEDs ausgeschaltet und die ungeraden eingeschaltet sind. Dateiinhalt
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
Wir starten den Server
./led-server led(000)=0 led(001)=1 ... led(078)=0 led(079)=1
Jetzt brauchen wir einen Client, um unser Gerät zu verwalten. Es ist auch sehr einfach, es anhand eines Beispiels von libmodbus zu entwickeln
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);
Wir starten den Client. Installieren Sie die 78 LED, die standardmäßig ausgeschaltet ist
./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
Auf dem Server werden wir sehen:
... led(076)=0 led(077)=1 led(078)=1 led(079)=1 Waiting for an indication... ERROR Connection reset by peer: read
Das heißt, die LED ist installiert. Lass es uns ausschalten.
./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
Auf dem Server wird eine Meldung über die Änderung angezeigt:
... led(076)=0 led(077)=1 led(078)=0 led(079)=1 Waiting for an indication... ERROR Connection reset by peer: read
Starten wir den http-Server. Wir haben im Artikel über die Entwicklung von Websites gesprochen . Darüber hinaus benötigen wir die Website nur, um die Funktionsweise von Modbus besser demonstrieren zu können. Daher werde ich nicht ins Detail gehen. Ich werde sofort ein CGI-Skript geben:
#!/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
Und ich möchte Sie daran erinnern, dass Sie jeden http-Server mit CGI-Unterstützung verwenden können. Wir verwenden den in Python integrierten Server. Führen Sie den folgenden Befehl aus:
python3 -m http.server --cgi -d .
Öffnen wir unsere Website in einem Browser:
Installieren Sie 78 LED mit dem 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
Setzen Sie die 79 LED zurück:
./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
Auf der Website werden wir den Unterschied sehen:
Eigentlich funktioniert unsere Bibliothek unter Linux hervorragend.
Anpassung an Embox und Ausführen auf dem Emulator
Libmodbus-Bibliothek
Jetzt müssen wir den Code in Embox verschieben. Beginnen wir mit dem libmodbus-Projekt selbst.
Es ist einfach. Wir benötigen eine Beschreibung des Moduls (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 }
Wir verwenden Anmerkungen Bauen(script = "$ (EXTERNAL_MAKE)") Wir geben an, dass wir das Makefile verwenden, um mit externen Projekten zu arbeiten.
Annotation verwenden BauenArtifactPath fügt Pfade zum Suchen von Header-Dateien zu den Modulen hinzu, die von dieser Bibliothek abhängen.
Und wir sagen, wir brauchen die Quellbibliothek "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 $@
Das Build-Makefile ist ebenfalls einfach und unkompliziert. Das einzige, was ich bemerke , ist, dass wir die interne Compiler- Embox ( $ (EMBOX_GCC) ) verwenden und als Plattform ( --host ) die in Embox ( $ (AUTOCONF_TARGET_TRIPLET) ) festgelegte übergeben.
Wir verbinden das Projekt mit Embox
Ich möchte Sie daran erinnern, dass wir zur Vereinfachung der Entwicklung ein separates Repository erstellt haben. Um es mit Embox zu verbinden, reicht es aus, Embox mitzuteilen, wo sich das externe Projekt befindet.
Dies erfolgt mit dem Befehl
make ext_conf EXT_PROJECT_PATH=<path to project>
an der Wurzel von Embox. Zum Beispiel,
make ext_conf EXT_PROJECT_PATH=~/git/embox_project_modbus_iocontrol
Modbus-Server
Der Quellcode des Modbus-Servers erfordert keine Änderungen. Das heißt, wir verwenden denselben Code, den wir auf dem Host entwickelt haben. Wir müssen Mybuild hinzufügen:
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 }
In dem wir mit Hilfe von Anmerkungen angeben, dass dies unser Befehl ist und dass er auch von der libmodbus-Bibliothek abhängt.
Wir werden auch Emulationsbibliotheken benötigen. Ich werde Mybuild nicht für sie geben, sie sind trivial, beachten Sie nur, dass die Quellen auch ohne Änderungen verwendet werden.
Wir müssen unser System auch zusammen mit einem Modbus-Server bauen.
Fügen Sie unsere Module zu mods.conf hinzu:
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
Und wir fügen unsere leds.txt-Datei mit LED-Status in das Root-Dateisystem ein. Da wir jedoch eine veränderbare Datei benötigen, fügen wir eine RAM-Disk hinzu und kopieren unsere Datei auf diese Disk. System_start.inc Inhalt:
"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",
Dies reicht aus, um Embox auf qemu auszuführen:
./scripts/qemu/auto_qemu
Modbus- und httpd-Server werden beim Start automatisch gestartet. Stellen wir die gleichen Werte mit dem Modbus-Client ein, nur indem wir die Adresse unserer QEMU (10.0.2.16) angeben:
./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
und entsprechend
./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>
Öffnen wir den Browser:
Wie erwartet ist alles gleich. Wir können das Gerät über das Modbus-Protokoll steuern, das bereits in Embox vorhanden ist.
Laufen auf einem Mikrocontroller
Um auf einem Mikrocontroller ausgeführt zu werden, verwenden wir die STM32F4-Erkennung. In den obigen Screenshots der Browserseiten sehen Sie, dass 80 Ausgabezweige verwendet werden, die paarweise kombiniert werden, und Sie können auch feststellen, dass diese Paare andere Eigenschaften haben, z. B. können Sie einen Namen festlegen oder das Paar kann sein hervorgehoben. Tatsächlich wurde der Code einem realen Projekt entnommen und der Einfachheit halber wurden unnötige Teile daraus entfernt. 80 Ausgangspins wurden unter Verwendung zusätzlicher Schieberegister-ICs erhalten.
Auf der STM32F4-Erkennungsplatine befinden sich jedoch nur 4 LEDs. Es wäre praktisch, die Anzahl der LEDs einzustellen, um den Quellcode nicht zu ändern. Embox verfügt über einen Mechanismus, mit dem Sie Module parametrisieren können. Sie müssen die Option in der Modulbeschreibung (Mybuild) hinzufügen.
package iocontrol.modbus.lib static module libleddrv { option number leds_quantity = 80 ... }
Und es wird möglich sein, im Code zu verwenden
#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
In diesem Fall können Sie diesen Parameter ändern, indem Sie ihn in der Datei mods.conf angeben
include iocontrol.modbus.lib.libleddrv(leds_quantity=4)
Wenn der Parameter nicht angegeben wird, wird derjenige verwendet, der standardmäßig im Modul festgelegt ist, dh 80.
Wir müssen auch die realen Ausgangsleitungen steuern. Der Code lautet wie folgt:
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);
}
}
In der Datei mods.conf benötigen wir die Konfiguration für unser Board. Wir fügen unsere Module hinzu:
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
In der Tat die gleichen Module wie für ARM QEMU, mit Ausnahme des Treibers natürlich.
Wir sammeln, flashen, starten. Und mit Hilfe des gleichen Modbus-Clients steuern wir die LEDs. Sie müssen nur die richtige Adresse eingeben und vergessen nicht, dass wir nur 4 LEDs auf der Platine haben.
Die Funktionsweise der stm32f4-Discovery-Karte ist in diesem kurzen Video zu sehen:
Ergebnisse
Anhand dieses einfachen Beispiels haben wir versucht zu zeigen, was der Hauptunterschied zwischen Embox und anderen Betriebssystemen für Mikrocontroller ist. Einschließlich solcher, die POSIX-kompatibel sind. Schließlich haben wir im Wesentlichen ein vorgefertigtes Modul verwendet, das unter Verwendung mehrerer Anwendungen eine Geschäftslogik unter Linux entwickelt hat. Und wir haben alles auf unserer Zielplattform gestartet. Dies vereinfacht und beschleunigt die Entwicklung selbst erheblich.
Ja, natürlich ist die Anwendung demo und nicht kompliziert. Das Modbus-Protokoll selbst könnte auch unabhängig implementiert werden. In diesem Fall müssten wir jedoch das Modbus-Protokoll verstehen. Und unser Ansatz ermöglicht es jedem Spezialisten, sich auf seinen Teil zu konzentrieren. Und natürlich werden die meisten Probleme auf dem Host gelöst, was viel praktischer ist als die direkte Entwicklung auf dem Board.