Editorial: Linguagens de Programação para Sistemas Embarcados

Linguagens para sistemas embarcados

Hoje em dia temos disponível um conjunto considerável de linguagens para sistemas embarcados, principalmente quando um sistema operacional é empregado na solução. Ao passo que num sistema bare-metal poucas alternativas nos restam. Isso tem um motivo. Neste artigo pretendemos detalhar as vantagens e desvantagens de cada uma dessas linguagens de programação, assim como apontar o uso para o qual elas vêm sendo empregadas no mercado de trabalho.

 

 

Introdução

 

Antes de apontarmos as linguagens para sistemas embarcados, precisamos, antes, entender o que é um sistema embarcado e a sua diferença para um sistema desktop.

 

O quão diferente é programar para sistemas embarcados e desenvolver aplicativos para ambiente Desktop? No que se refere à linguagem em si, seja ela qual for, não existe diferença sintática. No entanto, o comportamento e restrições apresentados nesses ambientes podem ser ligeiramente ou consideravelmente distintos.

 

Um sistema embarcado apresenta restrições de hardware, seja de memória (volátil e não-volátil), seja de processamento, o que faz com que o programador precise se preocupar com o binário/bytecode gerado, de modo a utilizar o hardware de forma mais eficiente. Ao passo que num sistema Desktop tem-se  a sensação de que os recursos são “ilimitados”.

 

Quando se trabalha com sistemas Desktop, o leque de sistemas operacionais é pequeno, ao passo que o mercado de sistemas embarcados oferece uma gama enorme de sistemas operacionais, sejam eles de uso genérico ou de tempo real. Cada um tem uma peculiaridade, e isso colabora para determinar as linguagens que podem ser utilizadas neles.

 

Sistemas embarcados possuem características distintas um dos dos outros, fazendo-se necessário, com frequência, mas não sempre, estar em contato direto com o hardware do produto. Existem linguagens que endereçam essa necessidade.

 

Exemplos de linguagens que atendem esses tipos de requisitos, e que foram criadas tendo em mente o uso em sistemas embarcados, são: 

  • PL/M: Criada em 1972 para microprocessadores Intel, oferecendo acesso direto à qualquer posição de memória;
  • Ada: Criada por uma necessidade do Departamento de Defesa dos EUA, na década de 70, para ser usada em seus projetos) e;
  • B#: Escrita em ANSI C, é focada em dispositivos com pequeno footprint de memórias Flash e RAM, oferece opcodes de 8 bits e é interpretada por uma Embedded Virtual Machine - EVM B#.

 

Elencamos algumas linguagens que estão sendo empregadas no mercado de trabalho e têm muita importância e aplicabilidade para a área de sistemas embarcados. Essa informação é muito importante para quem atua diretamente na área, principalmente no desenvolvimento de software em projetos. Por isso que no estudo realizado pelo próprio Embarcados em 2014 foi disponibilizada a pergunta “Como o projeto embarcado atual é programado?”, a qual foi respondida por uma amostra de 657 pessoas.

 

Veja nesse estudo que foi apontado pelo público que a linguagem C é usada na maioria dos projetos onde atuam, mais de 70%. Não é uma surpresa, visto as suas características, que serão mostradas logo a seguir.

 

Para efeito de comparação e conhecimento, confiram também o índice TIOBE, que mede a popularidade das linguagens de programação pelo mundo uma vez por mês.

 

Segue uma lista das linguagens que selecionamos.

 

 

Assembly

 

Como o contato direto com o hardware é uma característica frequente encontrada em sistemas embarcados, o contato com a linguagem Assembly torna-se praticamente inevitável.

 

Para se desenvolver um firmware, faz-se necessário, de alguma forma, escrever uma sequência de comandos na memória de programa do microcontrolador. Esses comandos são códigos de máquina, executados de forma sequencial. Cada instrução possui um código em hexadecimal e podem aceitar ou não parâmetros. O fluxo do programa é controlado por instruções de teste e chamadas de funções.

 

Ficar escrevendo programa em códigos numéricos é pouco prático, correto? Para isso foi criada a linguagem Assembly, com a qual são usados mnemônicos, com uma tradução de um para um com o correspondente código de máquina.

 

Utilizando uma linguagem de baixo nível como Assembly é preciso dominar os detalhes da arquitetura interna do núcleo e o mapeamento de memória, o que é conhecido como Programmers Model. Detalhes como alinhamento de memória, controle do stack, troca de contexto, etc, entram nessa lista.

 

Todo compilador de uma linguagem de alto nível, no entanto, gera como saída o respectivo código em Assembly para verificação do programador.

 

Atualmente, em projetos profissionais, o uso de Assembly é bastante restrito, sendo utilizado em código de inicialização dos microcontroladores e em funções/módulos que precisam oferecer performance alta e conhecida. Também pode ser empregado em projetos com microcontroladores com baixíssima quantidade de memória, que apenas realizam um número restrito de operações.

 

Todavia, Assembly ainda é também empregado na academia, no ensino de disciplinas que tratam de microcontroladores em cursos de graduação nas áreas de Engenharia Elétrica, Mecatrônica e Computação, pelo fato de que a programação em Assembly leva o aluno a conhecer a relação e vínculo dos componentes internos do microcontrolador com um conjunto específico de comandos, instruções, que resultam no programa desejado.

 

 

C

 

A linguagem C foi criada em 1973 por Dennis Ritchie a partir da linguagem B (criada por Ken Thompson) e BCPL, com a finalidade de evitar futuras reescritas do sistema operacional UNIX em Assembly quando um novo hardware fosse criado para ser suportado.

 

Ela tem sido evoluída, visto que o seu primeiro padrão foi estabelecido em 1989/1990 pela ANSI e ISO, conhecido como C89 ou C90. O padrão mais atual é o C11, estabelecido pela ISO em 2011.

 

É uma linguagem estruturada e de uso geral, mas que tem encontrado no nicho de sistemas embarcados um grande cliente, com compiladores disponíveis para os diversos microcontroladores e microprocessadores (RISC e CISC) existentes no mercado.

 

C é flexível, portável entre arquiteturas computacionais distintas, e enxuta, possuindo somente 44 keywords. Os compiladores tendem a gerar código eficiente, visto que a linguagem possui estruturas que são mapeadas diretamente em instruções de máquina, razão pela qual tem sido utilizada em projetos que faziam uso da linguagem Assembly.

 

Mas alguns pontos merecem atenção do programador:

  • C oferece uma pobre verificação de run-time;
  • O uso de ponteiros pode facilitar o desenvolvimento de uma aplicação, mas também dificultar (e muito) o seu entendimento. Com eles é possível acessar posições aleatórias da memória mapeada da arquitetura utilizada, microcontrolador ou microprocessador. O mal uso pode ser a corrupção de dados ou execução opcodes inexistentes, por exemplo;
  • O uso da pilha do sistema, pois é uma região de memória não controlada pela linguagem nem pelo compilador. Por isso que existe técnicas que ajudam a mensurar o uso dessa região de memória pela aplicação e identificar que o seu uso excedeu (buffer overflow) os seus limites, o que é chamado de stack smashing;
  • As implementações de alocação dinâmica de memória não são oferecidas pela linguagem, e sim pelo C runtime do ambiente. Ou seja, o sistema operacional utilizado ou uma implementação bare-metal deve prover tal estrutura.

 

C tem sido utilizado largamente no mercado de sistemas embarcados, pois é o que constatamos, além do estudo realizado pelo Embarcados, em eventos, conversas com profissionais e divulgação de oportunidades de emprego. Visto seu vasto uso e enormes vantagens, deve perdurar por muito tempo no mercado. Em vista disso ele tem sido, inclusive, sendo usada para criar novas linguagens, tal como Lua e B#.

 

 

C++

 

Criada por Bjarne Stroustrup em 1983, inicialmente conhecida como “C with classes”, é uma linguagem para uso genérico. Como C, C++ é uma linguagem compilada, oferecendo as mesmas vantagens de acesso à hardware, flexibilidade e performance. No entanto, ela traz consigo conceitos nativos de orientação a objeto, programação genérica e metaprogramação, o que, a nível de codificação, proporciona um sistema de mais fácil manutenção, reuso e ampliação.

 

Oferece encapsulamento e controle de acesso a dados privados de uma classe, checagem forte de tipos e portabilidade.

 

Algumas implementações oferecidas pela linguagem podem causar perda de performance e alto uso de memória, como, por exemplo, dynamic cast, exceções e RTTI (Run-Time Type Identification).

 

Bibliotecas e frameworks gráficos, como Qt, fazem uso de C++, justamente por questões de performance e programação genérica.

 

 

C#

 

C# foi desenvolvida em 1999 por uma equipe montada por Anders Hejlsberg na Microsoft, com o objetivo de criar uma nova linguagem inicialmente chamada COOL, sigla para "C-Like Object Oriented Language". Em meados de 2000, com a criação da plataforma .Net, a Microsoft resolveu renomear a linguagem para C#.

 

A linguagem de programação C# foi desenvolvida com o propósito de ser simples, moderna, de propósito-geral e orientada a objetos, com capacidades tais como tratamento de exceções, coleta de lixo de memória (garbage collection), e foi projetada tendo em vista a capacidade de ser adequada para a escrita de aplicações nativas e embarcadas.

 

Tanto é que tão logo quanto saiu, já era então possível escrever aplicações em C# para dispositivos com WindowsCE, e anos depois para os famosos PocketPCs, chegando ao ponto em que estamos com sistemas Windows Embedded, Windows Phone e Windows RT, todos eles compatíveis com a plataforma .Net, e, consequentemente, compatíveis com programas escritos em C#.

 

A título de exemplo, a empresa Toradex usa muito o sistema Windows Embedded em suas soluções embarcadas, o que pode ser bem visto aqui e aqui.

 

Indo mais além ainda, com as versões mais recentes do Visual Studio e da plataforma .Net, que são Visual Studio 2015 e .Net 4.6, a Microsoft tem trabalhado em uma solução capaz de tornar possível a compilação de programas escritos em .Net (o que abrange C#) para a execução em ambientes Android, iOS, Windows Phone e Windows para PC. Dessa forma será possível ter um maior reuso de código, economia com tempo de desenvolvimento, dentre outroas coisas mais. Se isso tudo dará certo e cumprirá com o prometido, só o tempo dirá.

 

Complementar com o que foi dito, a Microsoft também tem investido na área da Internet das Coisas, tornando possível, por exemplo, instalar o Windows 10 em SBCs Raspberry Pi 2, e criar programas usando Visual Studio 2015, os quais podem ser escritos em C#, e fazer uso dos recursos da placa, tais como GPIOs, I2C, Rede, etc.

 

A linguagem é tida como muito similar à Java, de modo que muitos programadores relatam a rápida curva de aprendizado entre ambas as linguagens.

 

 

Java

 

Desenvolvida por James Gosling na Sun Microsystems, em 1995, Java nasceu com o objetivo de ser uma linguagem simples, robusta, orientada a objetos, independente de arquitetura, interpretada e dinâmica. Um programa compilado deve ser executado em qualquer arquitetura, sem necessidade de recompilação ("write once, run anywhere" - WORA).

 

Em sistemas embarcados Java é encontrada largamente em Smart Cards (tecnologia Java Card) e em dispositivos móveis, principalmente após o advento do sistema operacional Android. Com a onda de IoT (Internet das Coisas) e com o ganho de performance da máquina virtual JavaVM, ele tem sido adotada também em dispositivos de automação residencial e wearables.

 

Para mostrar o quanto a linguagem tem sido importante para embarcados, a ARM criou uma tecnologia chamada Jazelle, oferecendo em hardware uma máquina virtual Java para ganho de performance na execução de aplicações Java no microprocessador. Alguns cores da ARM possuem esse hardware incluso.

 

Java não traz consigo o conceito de ponteiros, talvez aí uma das dificuldades encontradas por programadores C que já estão acostumados com essa implementação. No entanto é uma linguagem puramente orientada a objetos. A alocação de memória é tratada por um Garbage Colector especificado pela linguagem. O código Java compilado torna-se um bytecode que é interpretado por uma máquina virtual. Por este motivo, perde-se em performance quando comparado binários executáveis nativos. A fim de mitigar esse problema, Java usa o conceito de compilação JIT (Just-In-Time), onde a VM gera um código de máquina sob demanda durante a execução do bytecode.

 

A Oracle está vendo potencial no mundo dos embarcados, tanto é que lançaram a ferramenta JRECreate, com a qual pode-se customizar um JRE para ARMv5/v6/v7 e x86 com footprint de aproximadamente 11MB a 48MB, variando em suportes e recursos disponíveis.

 

Paralelo à isso, Java também é a linguagem de programação utilizada para a criação de aplicações Android. Para tal, um código Java é compilado para bytecodes JVM, que depois são traduzidos para o formato bytecode compatível com máquinas virtuais Dalvik, e seu sucessor, Android Runtime (ART), ficando então no formato *.dex. A forma como que a máquina virtual Dalvik opera é otimizada para a execução em dispositivos com recursos mais limitados quanto à memória e processamento.

 

A vantagem de criar programar Android reside no fato de que uma boa e crescente parcela de dispositivos móveis no mercado faz uso deste sistema operacional, tais como smartphones, smartwatches, smarttvs, e até mesmo veículos passarão a vir com estações multimídia com sistema Android. Desta forma, a sua aplicação pode ser adaptada para várias plataformas num mesmo ecossistema Android, fazendo uso de APIs que facilitam o acesso a recursos de rede WiFi, Bluetooth, câmera, NFC, dentre vários outros.

 

A título de exemplo, SBCs tais como BeagleBoard xM, BeagleBoneBlack, CubieBoard2, dentre outras, já suportam a execução do sistema Android com acesso aos seus componentes e drivers de tal forma que é possível, por exemplo, fazer uma aplicação Android que realize o controle de GPIOs, por exemplo. De certa forma, é só questão de tempo até o Android ser portado para a Raspberry Pi 2.

 

 

Python

 

É uma linguagem moderna, interpretada, funcional, orientada a objetos, com tipagem dinâmica e forte, criada por Guido van Rossum em 1991. O que ouvimos com frequência quando comenta-se de Python, principalmente no mundo dos embarcados, é: “Mas Python é lento, C é mais rápido!”.

 

Entendemos que há uns 3 anos atrás concordávamos com essa definição, mas hoje? Se alguém afirma isso, podemos simplesmente dizer: “Vamos trocar algumas strings e tomar um café!”. Na maioria das vezes dá certo!

 

Nem sempre “velocidade” é o ponto crítico da solução. Se o sinal que sai pelo GPIO tem que ter uma frequência alta, então usa um microcontrolador dedicado e escreva um firmware em C/Assembly e o middleware da solução em Python, sendo a interface entre a comunicação do GPIO e a conexão com um banco de dados, criando sockets UDP, TCP, SNMP (pysnmp), AMQP (amqp), MQTT (mosquitto), criando pacotes e, em seguida, criptografando e enviando para um servidor N.

 

Mas Python pode chegar lá embaixo também, com, por exemplo, pyserial para comunicação serial e pymodbus para comunicação Modbus (que por sinal utiliza pyserial). Facilmente você pode interagir com o GPIO da sua Raspberry Pi, Beaglebone Black, Intel Galileo, usando módulos prontos como rpi.GPIO, Adafruit_BBIO, PinGO (esta suporta todas as demais), além de módulos para I2C (SMBus), SPI (spidev), CAN (python-can), entre outros.

 

Pode-se ir mais além e com poucas linhas configurar, abrir, escrever ou ler, e fechar um arquivo do SysFS referente a um pino do GPIO. Ou ainda pode-se utilizar MMAP (Memory Mapped Files), usando o endereço de um pino do GPIO aplicando as mesmas operações do mmap() do C em Python.

 

E se mesmo assim insistirem em melhorar a velocidade em Python, pode-se utilizar:

  • PyPy: Utilizando o conceito JIT. Veja um link polêmico agora;
  • Cython: Velocidade do C em linguagem Python;
  • CPython: É a implementação principal da linguagem Python, escrito em C;
  • Pyrex: Produzir código Python e executar como C. Cython é um fork de PyRex.

 

Veja outras implementações Python:

  • Jython: Implementação 100% java da linguagem Python;
  • IronPython: Implementação do Python para plataforma .Net;
  • Ctypes: Interagindo com bibliotecas compartilhadas do SO em Python, permitindo chamada de funções e acesso a variáveis globais.

 

Uma análise interessante da Coverity resultou em uma classificação de Python numa linguagem de boa qualidade.

 

Muitas vezes se percebe um recuo sobre o uso de outra linguagem até que sejam mostrados casos de sucesso, ou áreas que usem a ferramenta/linguagem. Para Python existe um link que indica empresas/soluções que utilizam Python e pode-se ver a diversidade de áreas.

 

Podemos citar três empresas que estão usando Python para suas soluções embarcadas:

 

Existe ainda o projeto MicroPython, que é a linguagem Python para microcontroladores.

 

Quem utiliza Yocto Project, customiza seu projeto e cria/customiza receitas. Quando é executado o comando bitbake, (horas depois) é entregue uma imagem prontinha para sua placa. Esse binário, bitbake, é escrito em Python. O Toaster é uma interface web para o OpenEmbedded e o Bitbake, e usa Python por meio do poderoso framework web Django.

 

Como já apontamos, nem sempre a velocidade é o ponto forte da aplicação, ainda mais nessa era da IoT (Internet das Coisas). A menos que a fração de tempo seja muito restritiva para interagir com o hardware, usando Python, com módulos e algumas linhas, é possível ter todo o ecossistema de uma solução que interage com o hardware, gera dados para web, realiza CRUD em um banco de dados, e, ao mesmo tempo, envia mensagens a um broker e renderiza numa tela com PyGTK, PySide, wxPython ou PyQt. E isso tudo numa placa com Linux Embarcado, e, é claro, atentando-se aos recursos disponíveis. É interessante agora pensar no tempo que seria necessário para escrever em C o que foi citado acima.

 

Parte dessas análises sobre a linguagem Python teve como base um texto escrito por Mahmoud Hashemi (Engenheiro do PayPal), sobre “10 mitos sobre o Python”.

 

Aprofunde mais sobre o assunto:

 

 

JavaScript

 

JavaScript é uma linguagem de programação interpretada, desenvolvida por Brendan Eich em meados de 1995. Foi inicialmente uma linguagem de peso para programação web client-side e, anos depois, ganhou força em server-side com o famoso Node.js. Linguagem orientada a objetos baseada em protótipos, tipagem fraca e dinâmica, funções de primeira classe, onde os objetos possuem propriedades e métodos, podendo ser passados como argumentos e atribuídos a variáveis.

 

O primeiro JavaScript engine (interpretador para JavaScript) foi criado por Brendan Eich na Netscape com o codinome SpiderMonkey, implementado em linguagem C, e desde então ganhou vertentes. Rhino, uma versão em Java do interpretador, e anos depois a Google criou o interpretador V8, sendo que esse JavaScript Engine deu vida em 2009 para o Node.js, o qual foi criado por Ryan Dahl, sob a licença MIT e é usado como objeto de JavaScript do lado servidor.

 

No mundo dos embarcados e de IoT, JavaScript vem conquistando seu espaço e a tecnologia que vem se destacando é o Node.js. Em uma placa com Linux Embarcado podemos incluí-lo usando Yocto Project e instalando o pacote nodejs, ou mesmo com Buildroot, disponível em Target packages > Interpreter languages  and scripting.

 

Mehmet Fatih KARAGOZ da Aselsan apresentou na Embedded Linux Conference Europe vantagens, desvantagens e detalhes da integração de Node.js em um dispositivo com Linux Embarcado. Veja o slide da apresentação em Node.JS Appliances on Embedded Linux Devices.

 

Temos a Cloud9, por exemplo, uma IDE online que possui suporte a mais de 40 linguagens, escrita em JavaScript com Node.js. É a IDE padrão da Bealgebone Black para produzir script de interação, chamado BoneScript.

 

Na área da robótica, ainda com Node.js, temos o Cylon.js com suporte a mais de 36 plataformas diferentes como ARDrone, Arduino, Beaglebone Black, Intel Galileo, Intel Edison, Raspberry Pi, Tessel, Philips Hue. Sim, tem suporte para interagir a com lâmpada Philips Hue!

 

O uso de JavaScript na indústria de dispositivos embarcados já está presente, e podemos citar 4 exemplos: 

  • Espruino: Espruino utiliza um motor próprio, o Espruino JavaScript, que foi customizado para dispositivos pequenos com 128kB de Flash e 8kB de RAM; 
  • Tessel: Tessel utiliza LuaJIT e JavaScript para fornecer soluções pequenas e rápidas com Wi-Fi, utilizando MIPS e ARM Cortex-M; 
  • Kinoma Create: Projeto da Marvell Semicondutores e Kinoma com foco no mercado IoT, utiliza como motor o Marvell Semicondutors XS JavaScript; 
  • Netflix: Sim, Netflix utiliza Node.js, e um artigo muito bom é Nodejs in Flames publicado pelo Engenheiro do Netflix sobre problemas e solução com Node.js.

 

Uma desvantagem do JavaScript em sistemas embarcados é o uso de memória, que é uma das preocupações de todo desenvolvedor da área. O JavaScript Engine, para interpretar os scripts, pode ir de dezenas de kBytes a muitos MBytes, devido à sua tipagem dinâmica e sobrecarga de memória.

 

Uma vantagem seria a infinidade de recursos customizáveis para integrar dispositivos embarcados de diferentes plataformas. Ainda é recente o interesse por JavaScript, mas uma linguagem existente há mais de 20 anos está ganhando forças conforme o poder de processamento/recursos embarcados aumentam.

 

Um projeto que colabora para isso é o Duktape, uma implementação do ECMAScript 5 que pode ser embarcado em qualquer projeto C, podendo ser executado em plataformas com 256kB de Flash e 96kB de RAM. Segundo Sami Vaarala a motivação de criar a ferramenta era de ter uma implementação como Lua para JavaScript, onde Tim Caswell estendeu Duktape para um Node.js para dispositivos pequenos chamando de Dukluv, ligando libuv ao Duktape.

 

Questão de tempo para vermos um Ardu.js?

 

 

Wiring

 

O Wiring é um sistema de código aberto (hardware e software) criado por Hernando Barragán no Interaction Design Institute Ivrea, na Itália. Essa plataforma serviu de base para criação da plataforma Arduino e ficou conhecida como a Linguagem Wiring. Essa linguagem foi desenvolvida com base no Processing e trata-se de uma abstração de hardware desenvolvida em C/C++ para programação de microcontroladores.

 

Criada para que pessoas com pouco conhecimento de eletrônica e microcontroladores possam, com poucas linhas de código criar projetos interativos usando variados sensores e atuadores sem se preocupar com a configuração dos registradores ou funcionamento do microcontrolador.

 

Com a popularização do Arduino a linguagem foi cada vez mais utilizada, inclusive para programação de outras plataformas, se tornando um padrão para projeto para o público maker, onde não há preocupação com desempenho ou tamanho do código gerado ou plataforma utilizada.

 

 

Conclusões

 

É incontestável que o uso de C dentre as linguagens para sistemas embarcados é predominante e que tem seu espaço garantido por muito tempo, dadas as características e requisitos exigidos pela maioria dos projetos eletrônicos atuais. Além disso, tem sido utilizada para se criar novas linguagens.

 

No entanto, prezado leitor, apesar da multiplicidade de linguagens para sistemas embarcados, devemos sempre considerar a aplicação fim, não esquecendo dos seus trade-offs, como tempo de engenharia e o tempo de entrada do produto no mercado. Nesse sentido, na grande maioria das vezes, a melhor linguagem é aquela com a qual o desenvolvedor está mais familiarizado e a que melhor atende os requisitos solicitados para o sistema a se desenvolver.

 

 

Agradecimentos

 

Escreveram este texto: André Curvello, Cleiton BuenoFábio SouzaHenrique Rossi, Rodrigo Pereira e Thiago Lima.

 

 

Referências