Teléfono SIP con GUI en STM32F7

Hubo una de las noches de coronavirus que pasé en autoaislamiento. La placa STM32F769I-Discovery estaba sobre la mesa . Lo miré y pensé, es un teléfono inteligente. Hay una pantalla con una pantalla táctil de 800x480, hay una interfaz de audio, hay una interfaz de red, incluso si no es inalámbrica. Todo esto se basa en un microcontrolador, por lo que es más confiable en términos de condiciones de temperatura. Y tiene menos consumo. Solo falta el software. Por supuesto, ningún Android se acercará siquiera a esta placa. Y decidí probar qué tan rápido se puede desarrollar la funcionalidad requerida para el teléfono para esta placa en Embox .





Mi proyecto se puede dividir en dos partes. El primero es un teléfono y quiero incluirlo en la menor cantidad de recursos posible. El segundo es el desarrollo de una interfaz de usuario mínima que le permite recibir una llamada y comunicarse.



Teléfono a bordo STM32F769I-Discovery



Embox es un SO configurable para sistemas integrados. Una característica distintiva es que le permite usar software Linux sin cambiar el código fuente en sistemas con recursos limitados.



Uno de los proyectos de telefonía VOIP más populares es PJSIP . Lo usaremos para nuestros propósitos.



Construyendo PJSIP en Linux



Primero debe descargar, compilar y ejecutar la parte principal: PJSIP, una pila SIP de código abierto. Descargue la última versión . Por el momento esta es la versión 2.10.



A continuación, debe construir el proyecto. Es fácil de hacer para su sistema operativo host. En mi caso, es Linux.



$ ./configure --prefix=~/pj_build
      
      





Aquí no especifiqué ninguna opción excepto el prefijo, las rutas donde se instalarán las bibliotecas compiladas y los archivos de encabezado. Esto es necesario para analizar aquellas cosas que potencialmente terminan en nuestro microcontrolador.



Entonces ejecutamos



$ make dep
$ make
      
      





Ejecutando PJSIP en Linux



Si todo se completó correctamente, habremos compilado un PJSIP, así como una aplicación de demostración.



Comencemos con algo simple pero funcional. Necesitamos una llamada en ambas direcciones, tome pjsip-apps / src / samples / simple_pjsua.c. Esta es una aplicación sencilla con contestador automático de llamadas. Editemos el ejemplo seleccionado simple_pjsua.c para especificar la cuenta SIP. Las siguientes líneas son responsables de esto:



 #define SIP_DOMAIN   "example.com"
 #define SIP_USER     "alice"
 #define SIP_PASSWD   "secret"

      
      





Reconstruimos y ejecutamos:



$ ./pjsip-apps/bin/samples/x86_64-unknown-linux-gnu/simple_pjsua
      
      





Debería aparecer algo similar:



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
      
      





Ahora puedes recibir llamadas entrantes.



Construyendo PJSIP en Embox



Pongamos lo mismo en Embox. Primero, para no preocuparnos por la cantidad de memoria, haremos un ensamblaje para el emulador Qemu.



Embox tiene un mecanismo para conectar proyectos externos. Le permite establecer un enlace para descargar proyectos, aplicar parches si es necesario y establecer reglas para tres etapas: configurar, construir e instalar.



Para utilizar este mecanismo, basta con indicar que en la anotación '@Build' es necesario utilizar 'script = $ (EXTERNAL_MAKE)'.



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





Este es el Makefile utilizado para portar el ensamblado a Embox:



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) :
...

      
      





Como puede ver, estos son los mismos configure, make dep, make que para Linux. Por supuesto, al configurar, le indicamos que debe utilizar la compilación cruzada (--host, --target, CC, CXX) para la plataforma de destino.



Además, se puede notar una diferencia más. Especificamos --with-external-pa, es decir, decimos que para el audio es necesario utilizar controladores de Embox. Los controladores de audio de Embox proporcionan la interfaz de audio portátil, que también está disponible en Linux.



Como puede ver, hemos desactivado la construcción de las bibliotecas libyuv y libwebrtc. También deshabilitaremos todos los códecs de audio innecesarios por adelantado, excepto PCMA / PCMU. Comprobamos la corrección de la configuración en 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
      
      





Para trabajar más fácilmente con la aplicación simple_pjsua, muevamos el código a Embox. A partir de las modificaciones, simplemente transferiremos la configuración de los parámetros de la cuenta SIP del código C-shny al archivo 'simple_pjsua_sip_account.inc', que colocaremos en los archivos de configuración. Es decir, para construir una aplicación con una cuenta diferente, solo necesita cambiar este archivo. El contenido sigue siendo el mismo:



#define SIP_DOMAIN 	<sip_domain>
#define SIP_USER   	<sip_user>
#define SIP_PASSWD 	<sip_passwd>

      
      





Ejecute la aplicación simple_pjsua como antes en Linux. Si funciona, entonces PJSIP está configurado correctamente. Estas opciones de configuración se transfieren fácilmente a Makefile en Embox.



Makefile final debajo del 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 bajo el 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
}

      
      







Ahora puede recibir llamadas entrantes, pero en Embox.



Lanzamiento de PJSIP en STM32F769I-Discovery



Queda por cambiar la configuración de Embox de PJSIP para QEMU a la configuración de una placa específica: STM32F769I-Discovery. Para configurar Embox necesita varios componentes:



  • Archivo de indicadores de compilación (build.conf).
  • Archivo de vinculador que describe qué memoria está disponible y cómo se ubicará en ellos la imagen final (lds.conf).
  • Archivo de configuración de módulos Embox (mods.conf).
  • Configuración de PJSIP.


Los dos primeros puntos suelen ser fáciles de entender. Estas son opciones de compilador y enlazador, y rara vez cambian de un proyecto a otro para la misma placa. Excepto quizás por las banderas de compilación. El trabajo principal de establecimiento de las características del sistema final se realizará en los párrafos tercero y cuarto.



Primero, echemos un vistazo a la configuración de Embox. ¿En qué se diferencia de la ejecución en Linux? En Linux, teníamos una cantidad de memoria casi infinita, no nos importaba la cantidad de tareas, la cantidad de memoria asignada, etc. Ahora solo tenemos 2 MB de ROM y 512 MB de RAM, excluyendo la memoria externa. En consecuencia, es necesario establecer cuántos recursos necesitamos para necesidades específicas.



Por ejemplo, PJSIP se ejecuta en su propio hilo. Para cada nueva conexión hay otro flujo. Y un hilo más para trabajar con audio. Por lo tanto, incluso con una conexión, necesitamos al menos 3 hilos. A continuación, queremos agregar DHCP; seleccionamos una transmisión más. En total, ya 4. Todo esto se transfiere naturalmente a la configuración:



include embox.kernel.thread.core(thread_pool_size=5,thread_stack_size=12000)
      
      





Hemos configurado las pilas a un tamaño fijo. Puedes preguntar cosas diferentes. Todo depende de la tarea.



A continuación, seleccione la cantidad de paquetes requeridos:



	include embox.net.skbuff(amount_skb=28)
	include embox.net.skbuff_data(amount_skb_data=28)
      
      





Establezca el tamaño del montón (desde donde trabaja malloc ()):



	include embox.mem.heap_bm
	include embox.mem.static_heap(heap_size=0x3C000)
      
      





El resto de la configuración sigue siendo la misma que en QEMU.



Descubrir el tamaño del montón



La principal pregunta que surge al elaborar una configuración es cómo elegir los parámetros necesarios. Por ejemplo, ¿por qué el montón es 0x3C000, la cantidad de paquetes de red es 28 y la pila es 12 Kb? A menudo adopto el siguiente enfoque. El primer paso es lidiar con las pilas y el montón. Se pueden explorar un montón de cosas para empezar en Linux usando Valgrind. Puede usar el generador de perfiles Valgrind-Massif para esto. Funciona en "instantáneas" en determinados momentos y muestra qué función solicitó cuánta memoria.



Lanzamiento valgrind con nuestra aplicación:



$ valgrind --tool=massif --time-unit=B --massif-out-file=pjsip.massif ./pjsip-apps/bin/samples/x86_64-unknown-linux-gnu/simple_pjsua
      
      





Después de ejecutar la aplicación, visualizamos los datos usando el visualizador masivo:



$ massif-visualizer pjsip.massif
      
      









Aquí puede ver que la memoria se gasta no solo en PJSIP, sino también en la biblioteca estándar, así como en libasound (este es el sonido del host: ALSA). El propio PJSIP solicita las dimensiones de la subtrama roja inferior. Esto es en el pico de 600 Kb, y durante la conexión alrededor de 320 Kb. Nuestra placa tiene 512kB de RAM. Por tanto, estamos intentando configurar PJSIP, reduciendo el consumo de memoria ... Realicé



la siguiente configuración:



#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
      
      





Cópielo en PJSIP en el archivo pjlib / include / pj / config_site.h. Reconstruimos y corremos. Analizando el resultado:







en el pico ya es de unos 300 KB, que pueden caber en una placa.



A continuación, coloco un montón de aproximadamente 300 KB en Embox y configuro los grupos de depuración para ver si algo se desborda (tenga en cuenta que, como resultado, el tamaño del montón se redujo a 240 KB). La depuración de grupos está habilitada con la opción:



#define PJ_POOL_DEBUG    	1
      
      





en el mismo pjlib / include / pj / config_site.h.



Bien, todo lo que queda es configurar las pilas de subprocesos y el número de paquetes de red. Aquí debe distribuir correctamente los recursos restantes. Por ejemplo, si hay muy pocos paquetes de red, el sonido simplemente se "ahogará". Si asigna demasiados paquetes, no quedará nada para las pilas. La prioridad son, por supuesto, las pilas. Si la pila se estropea, todo se acaba.



Por lo tanto, comenzamos con el tamaño de pila máximo posible y luego lo reducimos hasta que el software se ejecuta bajo carga. Si detectamos daños en la pila, nos detenemos. Colocamos las interrupciones en una pila separada para minimizar la imprevisibilidad. Es decir, su pila es solo para su programa.



@Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=1024)
      
      





Después de eso, damos los recursos restantes a los paquetes de red. Como mencioné anteriormente, tenemos 28 de ellos.



Todo, la primera parte se ha completado con éxito. La aplicación Simple_pjsia se ejecuta correctamente en STM32F769I-Discovery en una memoria interna de 512 KB.



Estamos finalizando el teléfono SIP. Agregamos la interfaz de usuario.



Después de iniciar con éxito la versión de la consola, debe agregar de alguna manera la interfaz de usuario. Para simplificar, asumiremos que incluye lo siguiente. Cuando se inicia la aplicación, debe haber algún tipo de inscripción explicativa en la pantalla. Por ejemplo, “PJSIP DEMO”. Si hay una llamada entrante, la pantalla muestra el origen de la llamada y aparecen dos botones con íconos: "Aceptar", "Rechazar". La llamada puede aceptarse o rechazarse. Si se acepta la llamada, comienza la conversación, se muestra la información de contacto del suscriptor y queda un botón en la pantalla: "Colgar". Si la llamada fue rechazada inicialmente - todo es trivial aquí - volvemos a la imagen inicial con “PJSIP DEMO”.



Aquí hay un ejemplo de cómo debería verse esto.







Desarrollo de un prototipo en Linux



Dado que Embox ya tenía soporte para Nuklear, decidí utilizar este proyecto. Aunque ya tenemos una versión de consola funcional del teléfono en el microcontrolador, es importante aquí que sea mucho más fácil modificar la interfaz de usuario en Linux, como ya estaba con la configuración de PJSIP anterior.



Para hacer esto, tomemos dos ejemplos. El primer ejemplo es simple_pjsua de PJSIP. El segundo ejemplo es demo / x11_rawfb / de Nuklear. Ahora nuestra tarea es hacer que funcionen juntos bajo Linux.



Lo primero que hice fue reemplazar la respuesta automática de llamada PJSIP con un evento externo (como presionar un botón). A continuación, escribí la lógica en Nuklear.



En el proceso, resultó que por alguna razón los íconos no estaban dibujados dentro de los botones. En la imagen de abajo, puede ver los íconos del teléfono dentro de los botones verde y rojo. Estas son imágenes ordinarias en las que todo es 100% transparente, excepto el auricular del teléfono. Al mismo tiempo, inicialmente solo se dibujaron cuadrados blancos en lugar de ellos. Se trataba de la implementación del complemento rawfb. Aparentemente, no es muy popular, por lo que solo se dibuja el cursor en él. He agregado un código que simplemente copia el contenido de la imagen en la región de memoria correcta de Nuklear.



Como resultado, después de un día de trabajo en el proyecto, obtuve lo siguiente:







Sabiendo que el STM32F76I-Discovery tiene un tamaño de pantalla de 800x480, y en QEMU 800x600, configuro inmediatamente las dimensiones requeridas en Nuklear, para que sea más fácil navegar en la creación de etiquetas y botones dinámicos. El código resultante es el siguiente:



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

      
      





Lanzamiento a bordo



Queda por transferir el proyecto primero a QEMU y luego a la junta. Ya tenemos todo listo para la versión de consola, así que simplemente transferiremos la nueva aplicación desde Linux. Para hacer esto, simplemente cree un archivo Mybuild en el sistema de compilación Embox:



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

      
      





Como puede ver, se enumeran las fuentes. Los iconos y las fuentes se encuentran en el sistema de archivos interno y estarán disponibles como archivos normales de solo lectura. También se agregaron dependencias en bibliotecas pjsip y nuklear.



Después de ejecutar la aplicación en el tablero, noté que la fuente predeterminada de Nuclear se ve terrible en la pantalla STM32F769I. Algunas de las letras simplemente se perdieron. Por ejemplo, "1" parecía "|" y "m" parecía "n". Tuve que conectar fuentes de archivos ttf: Roboto-Regular.ttf. Esta fuente ocupa aproximadamente 150 KB de memoria flash, pero el texto es legible.



Después de verificar Linux, decidí que se trataba de una pequeña tarifa. Y traté de usar diferentes tamaños de fuente 32 y 38. Pero obtuve una segfault. Al final, abandoné la idea de cargar varios tamaños de fuente desde un archivo, y solo cargué el 32 y lo escale.



Características del lanzamiento en hardware.



Volvamos al lanzamiento del tablero. Lo importante que hay que entender aquí es que, en el caso de la interfaz de usuario, se necesita un framebuffer. Como queremos el modo de pantalla completa y la pantalla es de 800x480, incluso con una paleta RGB de 1 byte, necesitábamos 800 * 480 * 1 = 384000 bytes, es decir, 375 KB. Teniendo en cuenta que ya ocupamos casi la totalidad de la memoria interna de 512 KB para las necesidades de PJSIP, no funcionará encontrar un lugar para el framebuffer. Por esta razón, usaremos SDRAM. 16 MB disponibles en STM32F76I-Discovery. Como ya estamos usando memoria externa, no ahorraremos mucho y pondremos RGBA 32 bits. Por tanto, el framebuffer será 800 * 480 * 4 = 1536000 bytes o 1,5 MB.



En nuestra configuración, SDRAM se encuentra en la dirección 0x60000000. Lo especificamos como la dirección del framebuffer.



	@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
      
      





Ya he cubierto los efectos del parpadeo cuando se usa un búfer en otro artículo. Por tanto, tendremos en cuenta que el sistema utiliza doble búfer y, por lo tanto, necesitamos memoria adicional para otro búfer de 1,5 MB. Además, las fuentes requerirán otros 256 KB. En total, debe aumentar el montón en 2 MB. También lo colocamos en memoria externa:



	@Runlevel(2) include embox.driver.input.touchscreen.stm32f7cube_ts
	@Runlevel(2) include embox.driver.input.input_dev_devfs
      
      





Ahora la pantalla táctil será el dispositivo / dev / stm32-ts en el sistema de archivos devfs en Embox, y puede trabajar con él a través del habitual open () / read ().



Esto completa la configuración. ¿Por qué casi? De hecho, tomamos en cuenta todos los matices de la memoria, pero no tomamos en cuenta el rendimiento. Si en el caso de PJSIP el sonido se transmitió bien, entonces cuando intente ejecutarlo con gráficos, se ahogará. Este efecto es, por supuesto, muy difícil de depurar en Linux. Pero resultó ser suficiente solo para habilitar las cachés en nuestra placa.



	@Runlevel(0) include embox.arch.arm.armmlib.armv7m_cpu_cache(
    	log_level=4,
    	sram_nocache_section_size=0x10000
	)
      
      





En Embox, los datos y descriptores de paquetes de red, así como los búferes de audio con los que trabaja DMA, se encuentran en una sección especial de memoria marcada como memoria no almacenable en caché en la MPU. Esto es necesario para que el estado de los objetos en esta memoria sea siempre correcto tanto para la CPU como para el DMA.



Como resultado, obtenemos un teléfono SIP de trabajo muy simple con una interfaz de usuario con botones que funciona bastante bien.



A continuación intenté representar la asignación de memoria final:







Desarrollo en un sistema anfitrión



Mi proceso de desarrollo se redujo a lo que se muestra en la figura.







Y tomó muy poco tiempo. Un día para una aplicación en Linux y un día más para mejoras en la plataforma elegida. Sí, Embox ya tenía controladores de pantalla, tarjeta de red y audio para esta tarjeta. Pero el desarrollo de estas piezas también lleva un poco de tiempo, no más de una semana para cada conductor. Se necesita mucho más tiempo para desarrollar dicha funcionalidad directamente en la placa. En nuestro caso, la mayor parte de la funcionalidad se desarrolla en un entorno de sistema host conveniente. Esto nos permitió reducir significativamente el tiempo de desarrollo.



Hay un video de los resultados al comienzo del artículo. Si lo desea, puede reproducirlo todo usted mismo de acuerdo con las instrucciones de la wiki .



All Articles