Kami sering ditanya bagaimana Embox berbeda dari sistem operasi mikrokontroler lainnya, misalnya, FreeRTOS? Memang benar membandingkan proyek satu sama lain. Tetapi parameter yang terkadang digunakan untuk membandingkan perbandingan, secara pribadi membuat saya sedikit bingung. Misalnya, berapa banyak memori yang dibutuhkan Embox untuk dijalankan? Berapa waktu untuk beralih antar tugas? Apakah Embox mendukung modbus? Pada artikel ini, menggunakan contoh pertanyaan tentang modbus, kami ingin menunjukkan bahwa perbedaan antara Embox adalah pendekatan yang berbeda dalam proses pengembangan.
Mari kembangkan perangkat yang akan menyertakan server modbus. Perangkat kami akan sederhana. Bagaimanapun, ini dimaksudkan hanya untuk demonstrasi modbus. Perangkat ini akan memungkinkan pengontrolan LED menggunakan protokol Modbus. Untuk berkomunikasi dengan perangkat, kami akan menggunakan koneksi ethernet.
Modbus adalah protokol komunikasi terbuka. Ini banyak digunakan di industri untuk mengatur komunikasi antar perangkat elektronik. Ini dapat digunakan untuk mentransfer data melalui jalur komunikasi serial RS-485, RS-422, RS-232 dan jaringan TCP / IP (Modbus TCP).
Protokol modbus cukup sederhana untuk diterapkan sendiri. Tetapi karena setiap implementasi baru dari fungsionalitas tersebut mungkin mengandung bug, mari gunakan sesuatu yang sudah jadi.
Salah satu implementasi paling populer dari protokol modbus adalah proyek libmodbus open source . Kami akan menggunakannya. Ini akan mengurangi waktu pengembangan dan mengurangi kesalahan. Pada saat yang sama, kami akan dapat fokus pada penerapan logika bisnis, dan bukan mempelajari protokol.
Proyek kami akan disimpan di repositori terpisah . Jika mau, Anda dapat mengunduh dan memainkan semuanya sendiri.
Pengembangan prototipe Linux
Mari kita mulai dengan mengembangkan prototipe pada host. Untuk dapat menggunakan libmodbus sebagai perpustakaan, Anda perlu mengunduh, mengkonfigurasi, dan membangunnya.
Untuk tujuan ini, saya membuat sketsa 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 ..;
Sebenarnya dari parameter konfigurasi, kita hanya menggunakan prefiks untuk membangun perpustakaan secara lokal. Dan karena kami ingin menggunakan pustaka tidak hanya pada host, kami akan membuat versi statisnya.
Sekarang kita membutuhkan server modbus. Ada beberapa contoh dalam proyek libmodbus, mari kita buat implementasi kita berdasarkan beberapa server sederhana.
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);
Semuanya standar di sini. Beberapa tempat menarik adalah fungsi mb_mapping_getstates dan leddrv_updatestates. Inilah fungsionalitas yang diimplementasikan perangkat kami.
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;
}
}
Jadi, kita membutuhkan leddrv_updatestates, yang mengatur status LED, dan leddrv_getstates, yang mendapatkan status 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;
}
Karena kami ingin perangkat lunak kami berfungsi baik di papan maupun di host, kami memerlukan implementasi fungsi yang berbeda untuk menyetel dan mendapatkan status LED. Mari simpan status host dalam file biasa. Ini akan memungkinkan status LED diperoleh di proses lain.
Misalnya, jika kami ingin memeriksa status melalui situs web, kami akan meluncurkan situs web tersebut dan menentukan file ini sebagai sumber data.
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';
}
}
Kita perlu menentukan file di mana status awal LED akan disimpan. Format filenya sederhana. Status LED terdaftar dipisahkan dengan koma, 1 - LED menyala, dan 0 - mati. Perangkat kami memiliki 80 LED, lebih tepatnya 40 pasang LED. Mari kita asumsikan bahwa secara default LED genap akan mati dan yang ganjil menyala. Isi file
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
Kami memulai server
./led-server led(000)=0 led(001)=1 ... led(078)=0 led(079)=1
Sekarang kami membutuhkan klien untuk mengelola perangkat kami. Juga sangat mudah untuk mengembangkannya berdasarkan contoh dari 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);
Kami meluncurkan klien. Instal 78 LED, yang mati secara default
./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
Di server kita akan melihat:
... led(076)=0 led(077)=1 led(078)=1 led(079)=1 Waiting for an indication... ERROR Connection reset by peer: read
Artinya, LED sudah terpasang. Ayo matikan.
./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
Di server, kita akan melihat pesan tentang perubahan:
... led(076)=0 led(077)=1 led(078)=0 led(079)=1 Waiting for an indication... ERROR Connection reset by peer: read
Mari mulai server http. Kami berbicara tentang pengembangan situs web di artikel . Selain itu, kami hanya memerlukan situs web untuk demonstrasi yang lebih nyaman tentang cara kerja modbus. Oleh karena itu, saya tidak akan membahas lebih detail. Saya akan segera memberikan 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
Dan izinkan saya mengingatkan Anda bahwa Anda dapat mulai menggunakan server http apa pun dengan dukungan CGI. Kami menggunakan server bawaan python. Jalankan dengan perintah berikut:
python3 -m http.server --cgi -d .
Mari buka situs web kami di browser:
Instal 78 LED menggunakan klien:
./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
setel ulang 79 LED:
./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
Di situs web, kita akan melihat perbedaannya:
Sebenarnya, semuanya, perpustakaan kami berfungsi dengan baik di Linux.
Adaptasi ke Embox dan dijalankan di emulator
Perpustakaan Libmodbus
Sekarang kita perlu memindahkan kode ke Embox. mari kita mulai dengan proyek libmodbus itu sendiri.
Itu mudah. Kami membutuhkan deskripsi modul (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 }
Kami menggunakan anotasi Membangun(script = "$ (EXTERNAL_MAKE)") kami menunjukkan bahwa kami menggunakan Makefile untuk bekerja dengan proyek eksternal.
Menggunakan anotasi MembangunArtifactPath menambahkan jalur untuk menemukan file header ke modul tersebut yang akan bergantung pada pustaka ini.
Dan kami mengatakan bahwa kami membutuhkan pustaka sumber "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 $@
Build makefile juga sederhana dan lugas. Satu-satunya hal yang saya perhatikan adalah kami menggunakan kompiler internal ( $ (EMBOX_GCC) ) Embox dan sebagai platform ( --host ) kami mengirimkan satu set di Embox ( $ (AUTOCONF_TARGET_TRIPLET ) ).
Kami menghubungkan proyek ke Embox
Izinkan saya mengingatkan Anda bahwa untuk kenyamanan pengembangan, kami telah membuat repositori terpisah. Untuk menghubungkannya ke Embox, cukup memberi tahu Embox di mana proyek eksternal berada.
Ini dilakukan dengan menggunakan perintah
make ext_conf EXT_PROJECT_PATH=<path to project>
di root Embox. Contohnya,
make ext_conf EXT_PROJECT_PATH=~/git/embox_project_modbus_iocontrol
modbus-server
Kode sumber server modbus tidak memerlukan perubahan apa pun. Artinya, kami menggunakan kode yang sama yang kami kembangkan di host. Kita perlu menambahkan 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 }
Di mana, dengan bantuan anotasi, kami menunjukkan bahwa ini adalah perintah kami, dan juga bergantung pada pustaka libmodbus.
Kami juga membutuhkan perpustakaan emulasi. Saya tidak akan memberikan Mybuild untuk mereka, mereka sepele, hanya perhatikan bahwa sumbernya juga digunakan tanpa perubahan.
Kami juga perlu membangun sistem kami bersama dengan server modbus.
Tambahkan modul kami ke 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
Dan kami menempatkan file leds.txt kami dengan status LED ke dalam sistem file root. Tetapi karena kita membutuhkan file yang bisa berubah, mari tambahkan RAM disk dan salin file kita ke disk itu. Konten 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",
Ini cukup untuk menjalankan Embox di qemu:
./scripts/qemu/auto_qemu
server modbus dan httpd mulai secara otomatis saat startup. Mari kita tetapkan nilai yang sama menggunakan klien modbus, hanya dengan menentukan alamat QEMU kita (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
dan dengan demikian
./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>
Mari kita buka browser:
Seperti yang diharapkan, semuanya sama. Kami dapat mengontrol perangkat melalui protokol modbus yang sudah ada di Embox.
Berjalan di mikrokontroler
Untuk berjalan di mikrokontroler, kita akan menggunakan STM32F4-discovery. Pada tangkapan layar di atas dari halaman browser, Anda dapat melihat bahwa 80 kaki output digunakan, digabungkan berpasangan, dan Anda juga dapat melihat bahwa pasangan ini memiliki properti lain, misalnya, Anda dapat menetapkan nama, atau pasangan dapat menjadi disorot. Faktanya, kode tersebut diambil dari proyek nyata dan bagian yang tidak perlu dihapus darinya demi kesederhanaan. 80 pin keluaran diperoleh dengan menggunakan IC register geser tambahan.
Tapi hanya ada 4 LED di papan penemuan STM32F4. Akan lebih mudah untuk mengatur jumlah LED agar tidak mengubah kode sumber Embox memiliki mekanisme yang memungkinkan Anda untuk membuat parameter modul. Anda perlu menambahkan opsi dalam deskripsi modul (Mybuild)
package iocontrol.modbus.lib static module libleddrv { option number leds_quantity = 80 ... }
Dan itu akan mungkin untuk digunakan dalam kode
#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
Dalam kasus ini, Anda dapat mengubah parameter ini dengan menentukannya di file mods.conf
include iocontrol.modbus.lib.libleddrv(leds_quantity=4)
jika parameter tidak ditentukan, maka yang digunakan dalam modul secara default, yaitu 80.
Kita juga perlu mengontrol jalur keluaran yang sebenarnya. Kodenya adalah sebagai berikut:
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);
}
}
Di file mods.conf, kita membutuhkan konfigurasi untuk papan kita. Kami menambahkan modul kami ke dalamnya:
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
Faktanya, modul yang sama seperti untuk ARM QEMU, dengan pengecualian driver, tentunya.
Kami mengumpulkan, mem-flash, meluncurkan. Dan dengan bantuan klien modbus yang sama, kami mengontrol LED. Anda hanya perlu memasukkan alamat yang benar, dan jangan lupa bahwa kami hanya memiliki 4 LED di papan.
Pengoperasian papan penemuan stm32f4 dapat dilihat di video singkat ini:
temuan
Menggunakan contoh sederhana ini, kami mencoba menunjukkan apa perbedaan utama antara Embox dan sistem operasi lain untuk mikrokontroler. Termasuk yang sesuai dengan POSIX. Bagaimanapun, pada dasarnya kami mengambil modul yang sudah jadi, mengembangkan logika bisnis di Linux menggunakan beberapa aplikasi. Dan kami meluncurkan semuanya di platform target kami. Dengan demikian, secara signifikan menyederhanakan dan mempercepat pembangunan itu sendiri.
Ya tentu saja aplikasinya demo dan tidak ribet. Protokol modbus itu sendiri juga dapat diimplementasikan secara independen. Namun dalam kasus ini, kita perlu memahami protokol modbus. Dan pendekatan kami memungkinkan setiap spesialis untuk fokus pada bagian mereka. Dan tentu saja, sebagian besar masalah diselesaikan di host, yang jauh lebih nyaman daripada berkembang langsung di papan.