Mein Projekt kann in zwei Teile unterteilt werden. Das erste ist ein Telefon, und ich möchte es in so wenig Ressourcen wie möglich einbauen. Die zweite ist die Entwicklung einer minimalen Benutzeroberfläche, mit der Sie einen Anruf entgegennehmen und kommunizieren können.
Telefon an Bord von STM32F769I-Discovery
Embox ist ein konfigurierbares Betriebssystem für eingebettete Systeme. Eine Besonderheit ist, dass Sie Linux-Software verwenden können, ohne den Quellcode auf Systemen mit begrenzten Ressourcen zu ändern.
Eines der beliebtesten VOIP- Telefonprojekte ist PJSIP . Wir werden es für unsere Zwecke verwenden.
Erstellen von PJSIP unter Linux
Zuerst müssen Sie den Hauptteil herunterladen, erstellen und ausführen - PJSIP, einen Open-Source-SIP-Stack. Laden Sie die neueste Version herunter . Im Moment ist dies Version 2.10.
Als nächstes müssen Sie das Projekt erstellen. Dies ist für Ihr Host-Betriebssystem einfach. In meinem Fall ist es Linux.
$ ./configure --prefix=~/pj_build
Hier habe ich keine Optionen außer dem Präfix angegeben, den Pfaden, in denen die kompilierten Bibliotheken und Header-Dateien installiert werden. Dies ist notwendig, um die Dinge zu analysieren, die möglicherweise in unserem Mikrocontroller landen.
Dann führen wir aus
$ make dep $ make
Ausführen von PJSIP unter Linux
Wenn alles erfolgreich abgeschlossen wurde, haben wir ein PJSIP sowie eine Demo-Anwendung zusammengestellt.
Beginnen wir mit etwas Einfachem und Funktionalem. Wir brauchen einen Anruf in beide Richtungen, nimm pjsip-apps / src / samples / simple_pjsua.c. Dies ist eine einfache Anwendung mit automatischer Anrufbeantwortung. Bearbeiten wir das ausgewählte Beispiel simple_pjsua.c, um das SIP-Konto anzugeben. Dafür sind folgende Zeilen verantwortlich:
#define SIP_DOMAIN "example.com"
#define SIP_USER "alice"
#define SIP_PASSWD "secret"
Wir bauen um und laufen:
$ ./pjsip-apps/bin/samples/x86_64-unknown-linux-gnu/simple_pjsua
Ähnliches sollte erscheinen:
15:21:22.181 pjsua_acc.c ....SIP outbound status for acc 0 is not active 15:21:22.181 pjsua_acc.c ....sip:bob@sip.linphone.org: registration success, status=200 (Registration successful), will re-register in 300 seconds 15:21:22.181 pjsua_acc.c ....Keep-alive timer started for acc 0, destination:91.121.209.194:5060, interval:15s
Jetzt können Sie eingehende Anrufe empfangen.
Erstellen von PJSIP auf Embox
Lassen Sie uns dasselbe auf Embox zusammenstellen. Um sich keine Gedanken über die Speicherkapazität zu machen, erstellen wir zunächst eine Baugruppe für den Qemu-Emulator.
Embox verfügt über einen Mechanismus zum Verbinden externer Projekte. Sie können einen Link zum Herunterladen von Projekten festlegen, bei Bedarf Patches anwenden und Regeln für drei Phasen festlegen: Konfigurieren, Erstellen, Installieren.
Um diesen Mechanismus verwenden zu können, reicht es aus, anzugeben, dass Sie in der Anmerkung '@Build' 'script = $ (EXTERNAL_MAKE)' verwenden müssen.
@Build(stage=2,script="$(EXTERNAL_MAKE) PJSIP_ENABLE_CXX=false") @BuildArtifactPath(cppflags="-I$(abspath $(EXTERNAL_BUILD_DIR))/third_party/pjproject/core/install/include/") module core_c extends core { depends pjsip_dependencies }
Dies ist das Makefile, mit dem die Assembly auf Embox portiert wird:
PKG_NAME := pjproject
PKG_VER := 2.10
PKG_SOURCES := https://github.com/pjsip/pjproject/archive/$(PKG_VER).tar.gz
PKG_MD5 := 13e5c418008ae46c4ce0c1e27cdfe9b5
include $(EXTBLD_LIB)
PKG_PATCHES := pjproject-$(PKG_VER).patch \
sha256_error_fix-$(PKG_VER).patch \
addr_resolv_sock-$(PKG_VER).patch
…
DISABLE_FEATURES := \
l16-codec \
ilbc-codec \
speex-codec \
speex-aec \
gsm-codec \
g722-codec \
g7221-codec \
libyuv \
libwebrtc
$(CONFIGURE) :
export EMBOX_GCC_LINK=full; \
cd $(BUILD_ROOT) && ( \
./configure \
CC=$(EMBOX_GCC) \
CXX=$(EMBOX_GXX) \
--host=$(AUTOCONF_TARGET_TRIPLET) \
--target=$(AUTOCONF_TARGET_TRIPLET) \
--prefix=$(PJSIP_INSTALL_DIR) \
$(DISABLE_FEATURES:%=--disable-%) \
--with-external-pa; \
)
touch $@
$(BUILD) :
cd $(BUILD_ROOT) && ( \
$(MAKE) dep; \
$(MAKE) MAKEFLAGS='$(EMBOX_IMPORTED_MAKEFLAGS)'; \
)
touch $@
$(INSTALL) :
...
Wie Sie sehen können, handelt es sich hierbei um die gleiche Konfiguration, make dep, make wie für Linux. Bei der Konfiguration weisen wir natürlich darauf hin, dass Sie für die Zielplattform eine Cross-Kompilierung (--host, --target, CC, CXX) verwenden müssen.
Darüber hinaus können Sie einen weiteren Unterschied feststellen. Wir geben --with-external-pa an, das heißt, wir sagen, dass Sie für Audio Treiber von Embox verwenden müssen. Die Audiotreiber in Embox bieten die Portaudio-Oberfläche, die auch unter Linux verfügbar ist.
Wie Sie sehen können, haben wir die Erstellung der Bibliotheken libyuv und libwebrtc deaktiviert. Wir werden auch alle unnötigen Audio-Codecs außer PCMA / PCMU im Voraus deaktivieren. Wir überprüfen die Richtigkeit der Konfiguration unter Linux:
$ ./configure \
--prefix=$PREFIX \
--disable-l16-codec \
--disable-ilbc-codec \
--disable-speex-codec \
--disable-speex-aec \
--disable-gsm-codec \
--disable-g722-codec \
--disable-g7221-codec \
--disable-libyuv \
--disable-libwebrtc
$ make dep && make
Um die Arbeit mit der Anwendung simple_pjsua zu vereinfachen, verschieben wir den Code in Embox. Aus den Änderungen übertragen wir einfach die Einstellung der SIP-Kontoparameter aus dem C-shny-Code in die Datei 'simple_pjsua_sip_account.inc', die wir in die Konfigurationsdateien einfügen. Das heißt, um eine Anwendung mit einem anderen Konto zu erstellen, müssen Sie nur diese Datei ändern. Der Inhalt bleibt gleich:
#define SIP_DOMAIN <sip_domain>
#define SIP_USER <sip_user>
#define SIP_PASSWD <sip_passwd>
Führen Sie die Anwendung simple_pjsua wie zuvor unter Linux aus. Wenn es funktioniert, ist PJSIP korrekt konfiguriert. Diese Konfigurationsoptionen können dann problemlos auf das Makefile in Embox zurückportiert werden.
Final Makefile unter dem Spoiler
PKG_NAME := pjproject
PKG_VER := 2.10
PKG_SOURCES := https://github.com/pjsip/pjproject/archive/$(PKG_VER).tar.gz
PKG_MD5 := 13e5c418008ae46c4ce0c1e27cdfe9b5
include $(EXTBLD_LIB)
PKG_PATCHES := pjproject-$(PKG_VER).patch \
sha256_error_fix-$(PKG_VER).patch \
addr_resolv_sock-$(PKG_VER).patch
ifeq ($(PJSIP_ENABLE_CXX),false)
PKG_PATCHES += pjsua2_disable-$(PKG_VER).patch
endif
DISABLE_FEATURES := \
l16-codec \
ilbc-codec \
speex-codec \
speex-aec \
gsm-codec \
g722-codec \
g7221-codec \
libyuv \
libwebrtc \
#g711-codec
BUILD_ROOT := $(BUILD_DIR)/$(PKG_NAME)-$(PKG_VER)
PJSIP_INSTALL_DIR := $(EXTERNAL_BUILD_DIR)/third_party/pjproject/core/install
$(CONFIGURE) :
export EMBOX_GCC_LINK=full; \
cd $(BUILD_ROOT) && ( \
./configure \
CC=$(EMBOX_GCC) \
CXX=$(EMBOX_GXX) \
--host=$(AUTOCONF_TARGET_TRIPLET) \
--target=$(AUTOCONF_TARGET_TRIPLET) \
--prefix=$(PJSIP_INSTALL_DIR) \
$(DISABLE_FEATURES:%=--disable-%) \
--with-external-pa; \
)
cp ./config_site.h $(BUILD_ROOT)/pjlib/include/pj/config_site.h
touch $@
$(BUILD) :
cd $(BUILD_ROOT) && ( \
$(MAKE) -j1 dep; \
$(MAKE) -j1 MAKEFLAGS='$(EMBOX_IMPORTED_MAKEFLAGS)'; \
)
touch $@
$(INSTALL) :
cd $(BUILD_ROOT) && $(MAKE) install
# Remove AUTOCONF_TARGET_TRIPLET from file names to use them in Mybuild
for f in $(PJSIP_INSTALL_DIR)/lib/*-$(AUTOCONF_TARGET_TRIPLET).a; do \
fn=$$(basename $$f); \
cp $$f $(PJSIP_INSTALL_DIR)/lib/$${fn%-$(AUTOCONF_TARGET_TRIPLET).a}.a; \
done
# Copy binaries and
# remove AUTOCONF_TARGET_TRIPLET from file names to use them in Mybuild
for f in $(BUILD_ROOT)/pjsip-apps/bin/samples/$(AUTOCONF_TARGET_TRIPLET)/*; do \
cp $$f $(PJSIP_INSTALL_DIR)/$$(basename $$f).o; \
done
for f in $(BUILD_ROOT)/pjsip-apps/bin/*-$(AUTOCONF_TARGET_TRIPLET); do \
fn=$$(basename $$f); \
cp $$f $(PJSIP_INSTALL_DIR)/$${fn%-$(AUTOCONF_TARGET_TRIPLET)}.o; \
done
touch $@
Final Mybuild unter dem Spoiler
package third_party.pjproject module pjsip_dependencies { depends embox.net.lib.getifaddrs depends embox.compat.posix.pthreads depends embox.compat.posix.pthread_key depends embox.compat.posix.pthread_rwlock depends embox.compat.posix.semaphore depends embox.compat.posix.fs.fsop depends embox.compat.posix.idx.select depends embox.compat.posix.net.getaddrinfo depends embox.compat.posix.net.gethostbyname depends embox.compat.posix.util.gethostname depends embox.compat.posix.proc.pid depends embox.compat.posix.proc.exit depends embox.compat.libc.stdio.fseek depends embox.compat.posix.time.time depends embox.kernel.thread.thread_local_heap depends embox.driver.audio.portaudio_api } @DefaultImpl(core_c) abstract module core { } @Build(stage=2,script="$(EXTERNAL_MAKE) PJSIP_ENABLE_CXX=false") @BuildArtifactPath(cppflags="-I$(abspath $(EXTERNAL_BUILD_DIR))/third_party/pjproject/core/install/include/") module core_c extends core { depends pjsip_dependencies } /* Currently not used. It will be used for PJSUA2 if required. */ @Build(stage=2,script="$(EXTERNAL_MAKE) PJSIP_ENABLE_CXX=true") @BuildArtifactPath(cppflags="-I$(abspath $(EXTERNAL_BUILD_DIR))/third_party/pjproject/core/install/include/") @BuildDepends(third_party.STLport.libstlportg) module core_cxx extends core { depends pjsip_dependencies depends third_party.STLport.libstlportg } @BuildDepends(core) @Build(stage=2,script="true") static module libpjsip { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libpjsip.a", "libpjsip-simple.a", "libpjsip-ua.a" @NoRuntime depends core } @BuildDepends(core) @Build(stage=2,script="true") static module libpjsua { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libpjsua.a" @NoRuntime depends core } @BuildDepends(core) @Build(stage=2,script="true") static module libpjlib_util { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libpjlib-util.a" @NoRuntime depends core } @BuildDepends(core) @Build(stage=2,script="true") static module libpj { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libpj.a" @NoRuntime depends core } @BuildDepends(core) @Build(stage=2,script="true") static module libpjmedia { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libpjmedia.a", "libpjmedia-codec.a", "libpjmedia-audiodev.a" @NoRuntime depends core } @BuildDepends(core) @Build(stage=2,script="true") static module libpjnath { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libpjnath.a" @NoRuntime depends core } @BuildDepends(core) @Build(stage=2,script="true") static module libpj_third_party { @AddPrefix("^BUILD/extbld/third_party/pjproject/core/install/lib") source "libresample.a", "libsrtp.a" @NoRuntime depends core } @BuildDepends(libpjsua) @BuildDepends(libpjsip) @BuildDepends(libpjmedia) @BuildDepends(libpj) @BuildDepends(libpjlib_util) @BuildDepends(libpjnath) @BuildDepends(libpj_third_party) @Build(stage=2,script="true") static module libpj_all { @NoRuntime depends libpjsua, libpjsip, libpjmedia, libpj, libpjlib_util, libpjnath, libpj_third_party } @AutoCmd @Cmd(name="streamutil", help="", man="") @BuildDepends(core) @Build(stage=2,script="true") module streamutil { source "^BUILD/extbld/third_party/pjproject/core/install/streamutil.o" depends core } @AutoCmd @Cmd(name="pjsua", help="", man="") @BuildDepends(core) @Build(stage=2,script="true") module pjsua { source "^BUILD/extbld/third_party/pjproject/core/install/pjsua.o" } @AutoCmd @Cmd(name="pjsip_simpleua", help="", man="") @BuildDepends(core) @Build(stage=2,script="true") module simpleua { source "^BUILD/extbld/third_party/pjproject/core/install/simpleua.o" depends core }
Jetzt können Sie eingehende Anrufe empfangen, jedoch über Embox.
Starten von PJSIP auf STM32F769I-Discovery
Die Embox-Konfiguration muss noch von PJSIP für QEMU in die Konfiguration für eine bestimmte Karte geändert werden - STM32F769I-Discovery. Zum Konfigurieren von Embox benötigen Sie mehrere Komponenten:
- Kompilierungsflags-Datei (build.conf).
- Linker-Datei, die beschreibt, welcher Speicher verfügbar ist und wie sich das endgültige Bild (lds.conf) darin befindet.
- Konfigurationsdatei für Embox-Module (mods.conf).
- PJSIP-Konfiguration.
Die ersten beiden Punkte sind normalerweise leicht herauszufinden. Dies sind Compiler- und Linker-Optionen, die sich selten von Projekt zu Projekt für dieselbe Karte ändern. Außer vielleicht für die Kompilierungsflags. Die Hauptarbeiten zur Festlegung der Merkmale des endgültigen Systems werden im dritten und vierten Absatz durchgeführt.
Schauen wir uns zunächst die Embox-Konfiguration an. Was ist hier der Unterschied zu Linux? Unter Linux hatten wir fast unendlich viel Speicher, die Anzahl der Aufgaben, die zugewiesene Speichermenge usw. waren uns egal. Jetzt haben wir nur noch 2 MB ROM und 512 MB RAM ohne externen Speicher. Dementsprechend muss festgelegt werden, wie viele Ressourcen wir für bestimmte Anforderungen benötigen.
Beispielsweise wird PJSIP in einem eigenen Thread ausgeführt. Für jede neue Verbindung gibt es einen anderen Stream. Und noch ein Thread für die Arbeit mit Audio. Somit benötigen wir auch bei einer Verbindung mindestens 3 Threads. Als nächstes möchten wir DHCP hinzufügen - wir wählen einen weiteren Stream aus. Insgesamt schon 4. All dies wird natürlich auf die Konfiguration übertragen:
include embox.kernel.thread.core(thread_pool_size=5,thread_stack_size=12000)
Wir haben die Stapel auf eine feste Größe eingestellt. Sie können verschiedene Dinge fragen. Es hängt alles von der Aufgabe ab.
Wählen Sie als Nächstes die Anzahl der erforderlichen Pakete aus:
include embox.net.skbuff(amount_skb=28) include embox.net.skbuff_data(amount_skb_data=28)
Legen Sie die Heap-Größe fest (von wo aus malloc () funktioniert):
include embox.mem.heap_bm include embox.mem.static_heap(heap_size=0x3C000)
Ansonsten bleibt die Konfiguration dieselbe wie bei QEMU.
Ermitteln der Heap-Größe
Die Hauptfrage, die sich beim Erstellen einer Konfiguration stellt, ist, wie die erforderlichen Parameter ausgewählt werden sollen. Warum ist der Heap beispielsweise 0x3C000, die Anzahl der Netzwerkpakete beträgt 28 und der Stapel beträgt 12 KB? Ich gehe oft folgendermaßen vor. Der erste Schritt besteht darin, sich mit den Stapeln und dem Haufen zu befassen. Mit Valgrind können zunächst einige Dinge unter Linux untersucht werden. Hierfür können Sie den Valgrind-Massif-Profiler verwenden. Es bearbeitet zu bestimmten Zeitpunkten „Schnappschüsse“ und zeigt an, welche Funktion wie viel Speicher benötigt.
Starten Sie valgrind mit unserer Anwendung:
$ valgrind --tool=massif --time-unit=B --massif-out-file=pjsip.massif ./pjsip-apps/bin/samples/x86_64-unknown-linux-gnu/simple_pjsua
Nach dem Ausführen der Anwendung visualisieren wir die Daten mit dem Massif-Visualizer:
$ massif-visualizer pjsip.massif
Hier können Sie sehen, dass Speicher nicht nur für PJSIP, sondern auch für die Standardbibliothek sowie für libasound (dies ist der Host-Sound - ALSA) verwendet wird. PJSIP selbst fragt nach Abmessungen aus der unteren roten Nebenhandlung. Dies ist auf dem Höhepunkt von 600 Kb und während der Verbindung etwa 320 Kb. Unser Board verfügt über 512 KB RAM. Daher versuchen wir, PJSIP zu konfigurieren, um den Speicherverbrauch zu reduzieren ...
Ich habe die folgende Konfiguration vorgenommen:
#define PJ_LOG_USE_STACK_BUFFER 0
#define PJ_LOG_MAX_LEVEL 6
#define PJ_POOL_DEBUG 0
#define PJ_HAS_POOL_ALT_API 0
/* make PJSUA slim */
#define PJSUA_MAX_ACC 3
#define PJSUA_MAX_CALLS 1
#define PJSUA_MAX_VID_WINS 0
#define PJSUA_MAX_BUDDIES 1
#define PJSUA_MAX_CONF_PORTS 4
#define PJSUA_MAX_PLAYERS 1
#define PJSUA_MAX_RECORDERS 1
/* Changing to #if 0 will increase memory consumption
* but insreases communication speed. */
#if 1
/* This sample derived from pjlib/include/pj/config_site_sample.h: */
#define PJ_OS_HAS_CHECK_STACK 0
#define PJ_ENABLE_EXTRA_CHECK 0
#define PJ_HAS_ERROR_STRING 0
#undef PJ_IOQUEUE_MAX_HANDLES
#define PJ_IOQUEUE_MAX_HANDLES 8
#define PJ_CRC32_HAS_TABLES 0
#define PJSIP_MAX_TSX_COUNT 15
#define PJSIP_MAX_DIALOG_COUNT 15
#define PJSIP_UDP_SO_SNDBUF_SIZE 4000
#define PJSIP_UDP_SO_RCVBUF_SIZE 4000
#define PJMEDIA_HAS_ALAW_ULAW_TABLE 0
#endif
Kopieren Sie es nach PJSIP in die Datei pjlib / include / pj / config_site.h. Wir bauen wieder auf und rennen. Analyse des Ergebnisses:
In der Spitze sind es bereits ca. 300 KB, die auf ein Board passen können.
Als Nächstes habe ich einen Heap von ca. 300 KB in Embox abgelegt und die Debug-Pools festgelegt, um festzustellen, ob etwas überläuft (beachten Sie, dass die Größe des Heapspeichers dadurch auf 240 KB reduziert wurde). Das Debuggen von Pools wird mit der folgenden Option aktiviert:
#define PJ_POOL_DEBUG 1
in derselben pjlib / include / pj / config_site.h.
Okay, alles was bleibt ist, die Thread-Stapel und die Anzahl der Netzwerkpakete zu konfigurieren. Hier müssen Sie die verbleibenden Ressourcen korrekt verteilen. Wenn beispielsweise zu wenige Netzwerkpakete vorhanden sind, wird der Sound einfach "gedrosselt". Wenn Sie zu viele Pakete zuweisen, bleibt nichts mehr für die Stapel übrig. Die Priorität sind natürlich die Stapel. Wenn der Stapel schlecht wird, ist alles weg.
Daher beginnen wir mit der maximal möglichen Stapelgröße und reduzieren sie dann, bis die Software unter Last ausgeführt wird. Wenn wir den Stapel beschädigen, hören wir auf. Wir legen Interrupts auf einen separaten Stapel, um Unvorhersehbarkeit zu minimieren. Das heißt, Ihr Stack ist nur für Ihr Programm.
@Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=1024)
Danach geben wir die verbleibenden Ressourcen an Netzwerkpakete weiter. Wie ich oben erwähnt habe, haben wir 28 davon.
Alles, der erste Teil wurde erfolgreich abgeschlossen. Die Anwendung Simple_pjsia wird erfolgreich auf STM32F769I-Discovery im internen Speicher von 512 KB ausgeführt.
Wir stellen das SIP-Telefon fertig. Wir fügen die Benutzeroberfläche hinzu.
Nach dem erfolgreichen Start der Konsolenversion müssen Sie die Benutzeroberfläche irgendwie hinzufügen. Der Einfachheit halber nehmen wir an, dass es Folgendes enthält. Wenn die Anwendung gestartet wird, sollte auf dem Bildschirm eine erklärende Beschriftung angezeigt werden. Zum Beispiel "PJSIP DEMO". Wenn ein Anruf eingeht, wird auf dem Bildschirm angezeigt, woher der Anruf kam, und zwei Schaltflächen mit Symbolen werden angezeigt: "Annehmen", "Ablehnen". Der Anruf kann entweder angenommen oder abgelehnt werden. Wenn der Anruf angenommen wird, beginnt das Gespräch, die Kontaktinformationen zum Teilnehmer werden angezeigt und eine Schaltfläche bleibt auf dem Bildschirm - "Hang". Wenn der Anruf ursprünglich abgelehnt wurde - hier ist alles trivial - kehren wir mit „PJSIP DEMO“ zum Ausgangsbild zurück.
Hier ist ein Beispiel, wie dies aussehen sollte.
Entwicklung eines Prototyps unter Linux
Da Embox Nuklear bereits unterstützt, habe ich mich für dieses Projekt entschieden. Obwohl wir bereits eine funktionierende Konsolenversion des Telefons auf dem Mikrocontroller haben, ist es hier wichtig, dass es viel einfacher ist, die Benutzeroberfläche unter Linux zu ändern, wie dies bereits bei der obigen PJSIP-Einstellung der Fall war.
Nehmen wir dazu zwei Beispiele. Das erste Beispiel ist simple_pjsua von PJSIP. Das zweite Beispiel ist die Demo / x11_rawfb / von Nuklear. Jetzt ist es unsere Aufgabe, sie unter Linux zusammenarbeiten zu lassen.
Als erstes habe ich die automatische PJSIP-Anrufbeantwortung durch ein externes Ereignis (z. B. einen Tastendruck) ersetzt. Als nächstes schrieb ich die Logik auf Nuklear.
Dabei stellte sich heraus, dass die Symbole aus irgendeinem Grund nicht in die Schaltflächen gezeichnet wurden. In der Abbildung unten sehen Sie die Telefonsymbole in den grünen und roten Tasten. Dies sind gewöhnliche Bilder, auf denen bis auf den Telefonhörer alles 100% transparent ist. Gleichzeitig wurden anfangs nur weiße Quadrate gezeichnet. Es ging um die Implementierung des rawfb-Plugins. Anscheinend ist es nicht sehr beliebt, so dass nur der Cursor darin gezeichnet wird. Ich habe einen Code hinzugefügt, der einfach den Inhalt des Bildes in den richtigen Nuklear-Speicherbereich kopiert.
Als Ergebnis erhielt ich nach einem Tag Arbeit an dem Projekt Folgendes:
Da ich weiß, dass der STM32F76I-Discovery eine Bildschirmgröße von 800 x 480 und in QEMU 800 x 600 hat, habe ich sofort die erforderlichen Abmessungen in Nuklear festgelegt, damit die Navigation beim Erstellen dynamischer Beschriftungen und Schaltflächen einfacher ist. Der resultierende Code lautet wie folgt:
if (nk_begin(ctx, "Demo", nk_rect(0, 0, WIN_WIDTH, WIN_HEIGHT),
NK_WINDOW_NO_SCROLLBAR)) {
int answer_pressed = 0, decline_pressed = 0;
if (!draw_mouse) {
nk_style_hide_cursor(ctx);
}
nk_layout_row_static(ctx,
(WIN_HEIGHT - CALL_BTN_HEIGHT - 2 * CALL_INFO_TEXTBOX_HEIGHT - WIN_HEIGHT / 4), 15, 1);
nk_layout_row_dynamic(ctx, CALL_INFO_TEXTBOX_HEIGHT, 1);
nk_style_set_font(ctx, &rawfb_fonts[RAWFB_FONT_DEFAULT]->handle);
switch (call_info->state) {
case CALL_INACTIVE:
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 56;
nk_label(ctx, "PJSIP demo", NK_TEXT_CENTERED);
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 32;
break;
case CALL_INCOMING:
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 32;
nk_label(ctx, "Incoming call from:", NK_TEXT_CENTERED);
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 38;
nk_label(ctx, call_info->incoming, NK_TEXT_CENTERED);
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 32;
break;
case CALL_ACTIVE:
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 32;
nk_label(ctx, "Active call:", NK_TEXT_CENTERED);
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 38;
nk_label(ctx, call_info->remote_uri, NK_TEXT_CENTERED);
rawfb_fonts[RAWFB_FONT_DEFAULT]->handle.height = 32;
break;
}
if (call_info->state != CALL_INACTIVE) {
nk_layout_row_static(ctx, (WIN_WIDTH - 9 * 4) / 9, (WIN_WIDTH - 9 * 4) / 9, 9);
switch (call_info->state) {
case CALL_INCOMING:
nk_spacing(ctx, 2);
demo_nk_accept_btn(ctx, im_accept, &answer_pressed);
nk_spacing(ctx, 3);
demo_nk_decline_btn(ctx, im_decline, &decline_pressed);
nk_spacing(ctx, 2);
break;
case CALL_ACTIVE:
nk_spacing(ctx, 4);
demo_nk_decline_btn(ctx, im_decline, &decline_pressed);
nk_spacing(ctx, 4);
break;
default:
break;
}
}
if (answer_pressed && call_info->state == CALL_INCOMING) {
demo_pj_answer();
}
if (decline_pressed) {
demo_pj_hang();
}
}
nk_end(ctx);
An Bord starten
Es bleibt, das Projekt zuerst an QEMU und dann an den Vorstand zu übertragen. Wir haben bereits alles für die Konsolenversion bereit, also übertragen wir einfach die neue Anwendung von Linux. Erstellen Sie dazu einfach eine Mybuild-Datei im Embox-Build-System:
@AutoCmd @Cmd(name="sip_nuklear", help="", man="") @BuildDepends(third_party.pjproject.libpj_all) @BuildDepends(third_party.lib.nuklear) @Build(stage=2) module sip_nuklear { @InitFS source "icons/phone-accept-80.png", "icons/phone-decline-80.png", "fonts/Roboto-Regular.ttf" source "main.c" source "nuklear_main.c" @IncludePath("$(CONF_DIR)") @DefineMacro("PJ_AUTOCONF=1") source "pjsua.c" @NoRuntime depends third_party.pjproject.libpj_all @NoRuntime depends third_party.lib.nuklear depends embox.driver.input.core depends rawfb_api }
Wie Sie sehen können, sind die Quellen aufgelistet. Symbole und Schriftarten befinden sich im internen Dateisystem und sind als normale schreibgeschützte Dateien verfügbar. Außerdem wurden Abhängigkeiten von pjsip- und nuklear-Bibliotheken hinzugefügt.
Nachdem ich die Anwendung auf dem Board ausgeführt hatte, bemerkte ich, dass die Standardschrift von Nuclear auf dem STM32F769I-Bildschirm schrecklich aussieht. Einige der Briefe gingen einfach verloren. Zum Beispiel sah "1" wie "|" und "m" wie "n" aus. Ich musste Schriftarten aus ttf-Dateien verbinden - Roboto-Regular.ttf. Diese Schriftart benötigt ca. 150 KB Flash-Speicher, der Text ist jedoch lesbar.
Nachdem ich unter Linux nachgesehen hatte, entschied ich, dass dies eine kleine Gebühr ist. Und ich habe versucht, verschiedene Schriftgrößen 32 und 38 zu verwenden. Aber ich habe einen Segfault. Am Ende gab ich die Idee auf, mehrere Schriftgrößen aus einer Datei zu laden, und lud nur die 32. und skalierte sie.
Funktionen zum Starten auf Hardware
Kehren wir zum Board-Start zurück. Hier ist es wichtig zu verstehen, dass im Fall der Benutzeroberfläche ein Framebuffer benötigt wird. Da wir den Vollbildmodus wünschen und der Bildschirm 800 x 480 ist, selbst mit einer 1-Byte-RGB-Palette, benötigten wir 800 * 480 * 1 = 384000 Bytes, dh 375 KB. Wenn man bedenkt, dass wir bereits fast den gesamten internen Speicher von 512 KB für die Anforderungen von PJSIP belegt haben, funktioniert es nicht, einen Platz für den Framebuffer zu finden. Aus diesem Grund werden wir SDRAM verwenden. Auf STM32F76I-Discovery stehen 16 MB zur Verfügung. Da wir bereits externen Speicher verwenden, werden wir nicht viel sparen und RGBA 32 Bit setzen. Somit beträgt der Framebuffer 800 * 480 * 4 = 1536000 Bytes oder 1,5 MB.
In unserer Konfiguration befindet sich SDRAM unter der Adresse 0x60000000. Wir geben es als Adresse des Framebuffers an.
@Runlevel(1) include embox.driver.video.stm32f7_lcd( fb_base=0x60000000, width=800, height=480, ltdc_irq=88, bpp=32 ) include embox.driver.video.fb
Ich habe bereits die Auswirkungen des Flackerns bei der Verwendung eines Puffers in einem anderen Artikel behandelt. Daher werden wir berücksichtigen, dass das System doppelte Pufferung verwendet, und daher benötigen wir zusätzlichen Speicher für einen weiteren 1,5-MB-Puffer. Darüber hinaus benötigen die Schriftarten weitere 256 KB. Insgesamt müssen Sie den Heap um 2 MB erhöhen. Wir platzieren es auch im externen Speicher:
@Runlevel(2) include embox.driver.input.touchscreen.stm32f7cube_ts @Runlevel(2) include embox.driver.input.input_dev_devfs
Jetzt ist der Touchscreen das Gerät / dev / stm32-ts im devfs-Dateisystem in Embox, und Sie können mit ihm über das übliche open () / read () arbeiten.
Damit ist die Konfiguration abgeschlossen. Warum fast? Tatsächlich haben wir alle Nuancen aus dem Gedächtnis berücksichtigt, aber die Leistung nicht berücksichtigt. Wenn im Fall von PJSIP der Ton gut übertragen wurde, erstickt er, wenn Sie versuchen, ihn mit Grafiken zu starten. Dieser Effekt ist unter Linux natürlich sehr schwer zu debuggen. Aber es stellte sich heraus, dass es ausreichte, nur um die Caches auf unserem Board zu aktivieren.
@Runlevel(0) include embox.arch.arm.armmlib.armv7m_cpu_cache( log_level=4, sram_nocache_section_size=0x10000 )
In Embox befinden sich Netzwerkpaketdeskriptoren und -daten sowie Audiopuffer, mit denen DMA arbeitet, in einem speziellen Speicherbereich, der in der MPU als nicht zwischenspeicherbarer Speicher gekennzeichnet ist. Dies ist erforderlich, damit der Status der Objekte in diesem Speicher sowohl für die CPU als auch für den DMA immer korrekt war.
Als Ergebnis erhalten wir ein sehr einfach funktionierendes SIP-Telefon mit einer Benutzeroberfläche mit Tasten, die recht gut funktioniert.
Unten habe ich versucht, die endgültige Speicherzuordnung darzustellen:
Entwicklung auf einem Hostsystem
Mein Entwicklungsprozess lief auf das hinaus, was in der Abbildung gezeigt wird.
Und es dauerte sehr wenig Zeit. Ein Tag für eine Anwendung unter Linux und ein weiterer Tag für Verbesserungen an der ausgewählten Plattform. Ja, Embox hatte bereits Display-, Netzwerkkarten- und Audiotreiber für diese Karte. Die Entwicklung dieser Teile dauert aber auch etwas länger, nicht mehr als eine Woche für jeden Fahrer. Es dauert viel länger, solche Funktionen direkt auf der Platine zu entwickeln. In unserem Fall wird der größte Teil der Funktionalität in einer praktischen Host-Systemumgebung entwickelt. Dadurch konnten wir die Entwicklungszeit erheblich verkürzen.
Am Anfang des Artikels befindet sich ein Video der Ergebnisse. Wenn Sie möchten, können Sie alles selbst gemäß den Anweisungen im Wiki reproduzieren .