Telefone SIP com GUI em STM32F7

Houve uma das noites de coronavírus que passei em isolamento. A placa STM32F769I-Discovery estava sobre a mesa . Eu olhei para ele e pensei, é um smartphone. Há uma tela com touchscreen de 800x480, há uma interface de áudio, há uma interface de rede, mesmo que não seja wireless. Tudo isso baseado em um microcontrolador, portanto, é mais confiável em termos de condições de temperatura. E tem menos consumo. Apenas o software está faltando. Claro, nenhum Android chegará perto desta placa. E decidi testar a rapidez com que a funcionalidade necessária para o telefone pode ser desenvolvida para esta placa na Embox .





Meu projeto pode ser dividido em duas partes. O primeiro é um telefone e quero colocá-lo no mínimo de recursos possível. O segundo é o desenvolvimento de uma interface de usuário mínima que permite receber uma chamada e se comunicar.



Telefone a bordo STM32F769I-Discovery



Embox é um sistema operacional configurável para sistemas embarcados. Uma característica distintiva é que permite usar o software Linux sem alterar o código-fonte em sistemas com recursos limitados.



Um dos projetos de telefone VOIP mais populares é o PJSIP . Nós o usaremos para nossos propósitos.



Construindo PJSIP no Linux



Primeiro você precisa baixar, construir e executar a parte principal - PJSIP, uma pilha SIP de código aberto. Baixe a versão mais recente . No momento, esta é a versão 2.10.



Em seguida, você precisa construir o projeto. É fácil de fazer para o seu sistema operacional host. No meu caso, é o Linux.



$ ./configure --prefix=~/pj_build
      
      





Aqui, não especifiquei nenhuma opção, exceto prefixo, os caminhos onde as bibliotecas compiladas e os arquivos de cabeçalho serão instalados. Isso é necessário para analisar as coisas que potencialmente acabam em nosso microcontrolador.



Então nós executamos



$ make dep
$ make
      
      





Executando PJSIP no Linux



Se tudo for concluído com sucesso, então compilamos um PJSIP, bem como um aplicativo de demonstração.



Vamos começar algo simples, mas funcional. Precisamos de uma chamada em ambas as direções, pegue pjsip-apps / src / samples / simple_pjsua.c. Este é um aplicativo simples com atendimento automático de chamadas. Vamos editar o exemplo selecionado simple_pjsua.c para especificar a conta SIP. As seguintes linhas são responsáveis ​​por isso:



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

      
      





Nós reconstruímos e executamos:



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





Algo semelhante deve aparecer:



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
      
      





Agora você pode receber chamadas.



Construindo PJSIP no Embox



Vamos fazer o mesmo no Embox. Primeiramente, para não nos preocuparmos com a quantidade de memória, faremos um assembly para o emulador Qemu.



A Embox possui um mecanismo para conectar projetos externos. Ele permite que você defina um link para baixar projetos, aplique patches se necessário e defina regras para três estágios: configurar, construir, instalar.



Para utilizar este mecanismo, basta indicar que na anotação '@Build' é necessário 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 é o Makefile usado para portar a montagem para 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 você pode ver, esses são os mesmos configure, make dep, make do Linux. Claro, ao configurar, indicamos que você precisa usar compilação cruzada (--host, --target, CC, CXX) para a plataforma de destino.



Além disso, você pode notar outra diferença. Especificamos --with-external-pa, ou seja, dizemos que para áudio você precisa usar drivers da Embox. Os drivers de áudio no Embox fornecem a interface portaudio, que também está disponível no Linux.



Como você pode ver, desativamos a construção das bibliotecas libyuv e libwebrtc. Também desabilitaremos todos os codecs de áudio desnecessários com antecedência, exceto PCMA / PCMU. Verificamos a exatidão da configuração no 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 trabalhar mais facilmente com o aplicativo simple_pjsua, vamos mover o código para Embox. A partir das modificações, vamos simplesmente transferir a configuração dos parâmetros da conta SIP do código C-shny para o arquivo 'simple_pjsua_sip_account.inc', que colocaremos nos arquivos de configuração. Ou seja, para construir um aplicativo com uma conta diferente, você só precisa alterar este arquivo. O conteúdo permanece o mesmo:



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

      
      





Execute o aplicativo simple_pjsua como antes no Linux. Se funcionar, o PJSIP está configurado corretamente. Essas opções de configuração são facilmente transportadas de volta para o Makefile no Embox.



Makefile final sob o 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 $@
      
      







Mybuild final sob o 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
}

      
      







Agora você pode receber chamadas, mas na Embox.



Lançando PJSIP em STM32F769I-Discovery



Resta alterar a configuração do Embox de PJSIP para QEMU para a configuração de uma placa específica - STM32F769I-Discovery. Para configurar o Embox, você precisa de vários componentes:



  • Arquivo de sinalizadores de compilação (build.conf).
  • Arquivo vinculador que descreve qual memória está disponível e como a imagem final (lds.conf) será localizada neles.
  • Arquivo de configuração dos módulos Embox (mods.conf).
  • Configuração PJSIP.


Os primeiros dois pontos são geralmente fáceis de descobrir. Essas são opções de compilador e vinculador e raramente mudam de projeto para projeto para a mesma placa. Exceto talvez pelos sinalizadores de compilação. O trabalho principal de definição das características do sistema final será feito no terceiro e quarto parágrafos.



Primeiro, vamos dar uma olhada na configuração do Embox. Qual é a diferença aqui de rodar no Linux? No Linux tínhamos uma quantidade quase infinita de memória, não nos importamos com o número de tarefas, a quantidade de memória alocada, etc. Agora temos apenas 2 MB de ROM e 512 MB de RAM, excluindo a memória externa. Assim, é necessário definir quantos recursos precisamos para necessidades específicas.



Por exemplo, o PJSIP é executado em seu próprio encadeamento. Para cada nova conexão, há outro fluxo. E mais um tópico para trabalhar com áudio. Assim, mesmo com uma conexão, precisamos de pelo menos 3 threads. Em seguida, queremos adicionar DHCP - selecionamos mais um fluxo. No total, já 4. Tudo isso é naturalmente transferido para a configuração:



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





Definimos as pilhas com um tamanho fixo. Você pode perguntar coisas diferentes. Tudo depende da tarefa.



Em seguida, selecione o número de pacotes necessários:



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





Defina o tamanho do heap (de onde malloc () funciona):



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





Caso contrário, a configuração permanece a mesma do QEMU.



Descobrindo o tamanho do heap



A principal questão que surge ao traçar uma configuração é como escolher os parâmetros necessários? Por exemplo, por que o heap é 0x3C000, o número de pacotes de rede é 28 e a pilha é 12Kb? Costumo seguir a seguinte abordagem. A primeira etapa é lidar com as pilhas e o heap. Muitas coisas podem ser exploradas para começar no Linux usando Valgrind. Você pode usar o criador de perfil Valgrind-Massif para isso. Funciona em “instantâneos” em determinados momentos e mostra qual função solicitou quanta memória.



Inicie o valgrind com nosso aplicativo:



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





Depois de executar o aplicativo, visualizamos os dados usando o massif-visualizer:



$ massif-visualizer pjsip.massif
      
      









Aqui você pode ver que a memória é gasta não apenas no PJSIP, mas também na biblioteca padrão, bem como no libasound (este é o som do host - ALSA). O próprio PJSIP pede dimensões da subparcela vermelha inferior. Isso está no pico de 600 Kb, e durante a conexão cerca de 320 Kb. Nossa placa possui 512kB de RAM. Portanto, estamos tentando configurar o PJSIP, reduzindo o consumo de memória ...



Fiz a seguinte configuração:



#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
      
      





Copie-o para PJSIP no arquivo pjlib / include / pj / config_site.h. Nós reconstruímos e corremos. Analisando o resultado:







No pico já tem cerca de 300 KB, que pode caber em uma placa.



Em seguida, coloquei um heap de cerca de 300 KB no Embox e defini os pools de depuração para ver se algo estourou (observe que, como resultado, o tamanho do heap foi reduzido para 240 KB). A depuração de pools é ativada com a opção:



#define PJ_POOL_DEBUG    	1
      
      





no mesmo pjlib / include / pj / config_site.h.



Ok, tudo o que resta é configurar as pilhas de threads e o número de pacotes de rede. Aqui você precisa distribuir corretamente os recursos restantes. Por exemplo, se houver poucos pacotes de rede, o som irá simplesmente "engasgar". Se você alocar muitos pacotes, não haverá mais nada para as pilhas. A prioridade são, claro, as pilhas. Se a pilha estragar, tudo se foi.



Assim, começamos com o tamanho máximo de pilha possível e então o reduzimos até que o software esteja rodando sob carga. Se detectarmos danos à pilha, paramos. Colocamos interrupções em uma pilha separada para minimizar a imprevisibilidade. Ou seja, sua pilha é apenas para seu programa.



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





Depois disso, damos os recursos restantes aos pacotes de rede. Como mencionei acima, temos 28 deles.



Tudo, a primeira parte foi concluída com sucesso. O aplicativo Simple_pjsia é executado com êxito no STM32F769I-Discovery na memória interna de 512 KB.



Estamos finalizando o telefone SIP. Adicionamos a interface do usuário.



Depois de lançar com sucesso a versão do console, você precisa adicionar de alguma forma a interface do usuário. Para simplificar, assumiremos que inclui o seguinte. Ao iniciar o aplicativo, deve haver algum tipo de inscrição explicativa na tela. Por exemplo, “PJSIP DEMO”. Se houver uma chamada recebida, a tela exibe de onde veio a chamada e dois botões com ícones aparecem - “Aceitar”, “Recusar”. A chamada pode ser aceita ou rejeitada. Se a chamada for aceita, a conversa é iniciada, as informações de contato do assinante são exibidas e um botão permanece na tela - "Desligar". Se a chamada foi inicialmente rejeitada - tudo é trivial aqui - voltamos à imagem inicial com “PJSIP DEMO”.



Aqui está um exemplo de como isso deve ser.







Desenvolvimento de um protótipo em Linux



Como a Embox já tinha suporte para Nuklear, decidi usar este projeto. Embora já tenhamos uma versão de console funcionando do telefone no microcontrolador, é importante aqui que seja muito mais fácil modificar a IU no Linux, pois já era com a configuração PJSIP acima.



Para fazer isso, vamos dar dois exemplos. O primeiro exemplo é simple_pjsua de PJSIP. O segundo exemplo é demo / x11_rawfb / da Nuklear. Agora nossa tarefa é fazê-los trabalhar juntos no Linux.



A primeira coisa que fiz foi substituir o atendimento automático de chamadas PJSIP por um evento externo (como o pressionamento de um botão). Em seguida, escrevi a lógica no Nuklear.



No processo, descobriu-se que, por algum motivo, os ícones não eram desenhados dentro dos botões. Na imagem abaixo, você pode ver os ícones do telefone dentro dos botões verde e vermelho. São fotos comuns, nas quais tudo é 100% transparente, exceto o receptor do telefone. Ao mesmo tempo, inicialmente apenas quadrados brancos foram desenhados em vez deles. Era sobre a implementação do plugin rawfb. Aparentemente, não é muito popular, então apenas o cursor é desenhado nele. Eu adicionei um código que simplesmente copia o conteúdo da imagem para a região de memória Nuklear correta.



Como resultado, após um dia de trabalho no projeto, obtive o seguinte:







Sabendo que o STM32F76I-Discovery tem um tamanho de tela de 800x480, e no QEMU 800x600, imediatamente configuro as dimensões necessárias no Nuklear, para que seja mais fácil navegar na criação de rótulos e botões dinâmicos. O código resultante é o seguinte:



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

      
      





Lançar a bordo



Resta transferir o projeto primeiro para QEMU e, em seguida, para o conselho. Já temos tudo pronto para a versão do console, então vamos apenas transferir o novo aplicativo do Linux. Para fazer isso, basta criar um arquivo Mybuild no sistema de compilação da 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 você pode ver, as fontes estão listadas. Ícones e fontes estão localizados no sistema de arquivos interno e estarão disponíveis como arquivos normais somente leitura. Também foram adicionadas dependências nas bibliotecas pjsip e nuklear.



Depois de executar o aplicativo na placa, percebi que a fonte padrão da Nuclear fica horrível na tela do STM32F769I. Algumas das cartas foram simplesmente perdidas. Por exemplo, "1" parecia "|" e "m" parecia "n". Tive que conectar fontes de arquivos ttf - Roboto-Regular.ttf. Essa fonte ocupa cerca de 150 KB de memória flash, mas o texto é legível.



Depois de verificar o Linux, decidi que essa é uma pequena taxa. Tentei usar diferentes tamanhos de fonte 32 e 38. Mas obtive um segfault. No final, desisti da ideia de carregar vários tamanhos de fonte de um arquivo, carreguei apenas a 32ª e a dimensionei.



Recursos de lançamento em hardware



Vamos voltar ao lançamento da placa. O importante a entender aqui é que, no caso da IU, um framebuffer é necessário. Como queremos o modo de tela inteira e a tela tem 800x480, mesmo com uma paleta RGB de 1 byte, precisamos de 800 * 480 * 1 = 384000 bytes, ou seja, 375 KB. Considerando que já ocupamos quase toda a memória interna de 512 KB para as necessidades do PJSIP, não vai funcionar encontrar um lugar para o framebuffer. Por esse motivo, usaremos SDRAM. 16 MB estão disponíveis no STM32F76I-Discovery. Como já estamos usando memória externa, não economizaremos muito e colocaremos RGBA 32 bits. Assim, o framebuffer será 800 * 480 * 4 = 1536000 bytes ou 1,5 MB.



Em nossa configuração, SDRAM está localizada no endereço 0x60000000. Nós o especificamos como o endereço do 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
      
      





Já abordei os efeitos da cintilação ao usar um buffer em outro artigo. Portanto, levaremos em consideração que o sistema usa buffer duplo e, portanto, precisamos de memória adicional para outro buffer de 1,5 MB. Além disso, as fontes exigirão outros 256 KB. No total, você precisa aumentar o heap em 2 MB. Também o colocamos na memória externa:



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





Agora a tela sensível ao toque será o dispositivo / dev / stm32-ts no sistema de arquivos devfs no Embox, e você pode trabalhar com ela através do usual open () / read ().



Isso completa a configuração. Por que quase? Na verdade, levamos em consideração todas as nuances de memória, mas não levamos em consideração o desempenho. Se no caso do PJSIP o som foi bem transmitido, quando você tentar iniciá-lo com gráficos, ele irá engasgar. Esse efeito é, obviamente, muito difícil de depurar no Linux. Mas acabou sendo o suficiente apenas para habilitar os caches em nossa placa.



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





Na Embox, os descritores e dados do pacote de rede, bem como os buffers de áudio com os quais o DMA trabalha, estão localizados em uma seção especial da memória marcada como memória não armazenável no MPU. Isso é necessário para que o estado dos objetos nesta memória esteja sempre correto para a CPU e DMA.



Como resultado, obtemos um telefone SIP funcional muito simples com uma IU com botões que funcionam muito bem.



Abaixo, tentei descrever a alocação final de memória:







Desenvolvimento em um sistema host



Meu processo de desenvolvimento se resumiu ao que é mostrado na figura.







E demorou muito pouco tempo. Um dia para uma aplicação em Linux e mais um dia para melhorias na plataforma escolhida. Sim, a Embox já tinha display, placa de rede e drivers de áudio para esta placa. Mas o desenvolvimento dessas peças também leva algum tempo, não mais do que uma semana para cada piloto. Leva muito mais tempo para desenvolver essa funcionalidade diretamente na placa. Em nosso caso, a maior parte da funcionalidade é desenvolvida em um ambiente de sistema host conveniente. Isso nos permitiu reduzir significativamente o tempo de desenvolvimento.



Há um vídeo dos resultados no início do artigo. Se desejar, você pode reproduzir tudo sozinho de acordo com as instruções no wiki .



All Articles