Introdução
O JavaScript é uma das linguagens de programação mais populares atualmente, tendo sido criada em 1995 para o antigo navegador Netscape. A intenção original era de que ela fosse usada para manipulação de elementos de páginas HTML. Porém, o surgimento do motor V8 para o Google Chrome em 2008 levou o programador Ryan Dahl a criar o interpretador Node.js em 2009, baseando-se no código do V8, o que permitiu a criação de aplicações server-side em JavaScript com alto desempenho.
Com o surgimento do Node.js e o aumento da popularidade do JavaScript em servidores, a Microsoft criou o TypeScript, que adiciona um sistema de tipagem forte ao JavaScript para melhorar a detecção de erros no código. E a própria Microsoft também resolveu levar o TypeScript para os dispositivos embarcados, criando o DeviceScript, que é o assunto deste artigo.
O restante deste artigo é organizado como se segue. Na seção 2, será abordada a instalação do DeviceScript, incluindo suas extensões com o Visual Studio Code, também da Microsoft. A seção 3 apresenta o procedimento de criação de um projeto com o DeviceScript. Na seção 4, é introduzido o uso de simuladores com a ferramenta. A seção 5 aborda a utilização de placas físicas com o DeviceScript, em especial, com ESP32. Na seção 6, é montado o projeto de um contador binário de 3 bits implementado com ESP32 e DeviceScript. Por fim, a seção 7 apresenta as conclusões.
Instalação
Para o uso do DeviceScript, alguns pacotes devem ser previamente instalados no sistema. Para este tutorial, foi usado o sistema Ubuntu Linux 20.04 LTS, porém, o procedimento é similar para outras distribuições e até mesmo outros sistemas operacionais.
Por ser baseado em TypeScript, que, por sua vez, é compilado para JavaScript para ser executado, o primeiro passo envolve a instalação do Node.js no sistema. Para o Ubuntu, apesar de o Node.js já estar presente nos repositórios oficiais, recomenda-se o uso de pacotes mais atualizados, disponibilizados em https://deb.nodesource.com/ juntamente com suas instruções de instalação. No momento desta escrita, a versão recomendada é a v20.
Além disso, é recomendado fortemente o uso do Visual Studio Code (https://code.visualstudio.com/), que possui uma extensão para integração com o DeviceScript. Tal extensão está disponível em https://marketplace.visualstudio.com/items?itemName=devicescript.devicescript-vscode e pode ser instalada pela abertura rápida do Visual Studio Code (Ctrl+P) usando o comando:
ext install devicescript.devicescript-vscode.
Após a instalação da extensão, será adicionado um botão na barra lateral esquerda, como visto na Figura 1.
Apesar da recomendação, é possível proceder usando ferramentas de linha de comando, sem um editor de texto específico. Para isso, em um terminal, já com o Node.js instalado, deve-se navegar a um diretório vazio e instalar o pacote do DeviceScript via NPM:
$ npx --yes @devicescript/cli@latest init
Este comando iniciará um projeto vazio e já instalará as ferramentas dentro do diretório. Na próxima seção, será abordada a criação do projeto via Visual Studio Code.
Criação do projeto
O próximo passo consiste em criar um projeto caso a opção tenha sido pelo uso do Visual Studio Code. Para isso, voltando à Figura 1, basta pressionar o botão Create New Project. Uma janela será aberta para selecionar o diretório no qual o projeto será armazenado. Após selecionar, será aberto um diálogo para colocar um nome para o projeto, como visto na Figura 2. Este nome é opcional e, se fornecido, será usado para criar um subdiretório no diretório selecionado no passo anterior. No nosso caso, foi usado o nome “devicescript-esp32” de modo que o código ficará em um subdiretório com esse nome.
Em seguida, será perguntado se o gerenciador de pacotes Node.js a ser usado no projeto é o npm ou o yarn, como visto na Figura 3. Na dúvida, recomenda-se seguir com o npm que é o padrão do Node.js.
Após esse procedimento, um novo projeto será criado com um código exemplo em TypeScript que, essencialmente, age como um “hello world” a cada segundo, como pode ser visto na Figura 4. Porém, é necessário haver um dispositivo conectado para executar o código, que pode ser um simulador. Nas seções 3 e 4, serão vistos o uso do simulador e placa real, respectivamente.
Uma característica interessante presente no DeviceScript e complementada pela sua extensão para o Visual Studio Code é que é possível usar simuladores já prontos durante o processo de desenvolvimento para pequenos protótipos. A próxima seção abordará este tema.
Criação e uso de simuladores para execução de scripts
Como dito anteriormente, o DeviceScript já traz simuladores prontos para prototipação e depuração de códigos desenvolvidos, e a extensão para o Visual Studio Code permite um uso mais simples dessa ferramenta pelo programador. Nesta seção, será abordado o uso do simulador para execução do código de exemplo pelo Visual Studio Code.
Para a criação do primeiro simulador, existem dois caminhos possíveis. Se o interesse for apenas em criar o simulador, sem executar o código, basta, estando com o item do DeviceScript selecionado na barra lateral esquerda, clicar em Start Simulator, como visto na Figura 5.

Com o simulador criado, o código pode ser compilado e executado de duas formas, ambas marcadas na Figura 6: por meio do painel esquerdo do Visual Studio Code, usando a opção Executar e Depurar, onde se pode clicar no botão de execução para o DeviceScript (opção 1) ou usando o botão na barra de abas, que possui opção para compilar (build), executar (run) ou depurar (debug) o código (opção 2).

Após iniciar a execução do código, o tempo de execução do DeviceScript será inicializado no simulador. A saída correspondente será mostrada no terminal do Visual Studio Code, como ilustrado na Figura 7. Como o código imprime “:)” no console a cada segundo, será possível ver esse comportamento no terminal.

Com isso, temos nosso primeiro código em execução no DeviceScript! Agora, partiremos para a execução em uma placa de desenvolvimento física, assunto da próxima seção.
Utilização de placas físicas com o DeviceScript
Além de simuladores, o DeviceScript conta com suporte a diversas placas de desenvolvimento, em especial as que usam ESP32 e RP2040 (Raspberry Pico). A grande vantagem desse suporte é que, por meio da importação de módulos pré-definidos, a programação da placa fica mais fácil, com os pinos e funcionalidades já mapeados. Uma lista completa dos microcontroladores e placas suportados nativamente pode ser conferida em https://microsoft.github.io/devicescript/devices.
Dito isso, como a linguagem JavaScript (e, por consequência, TypeScript) é interpretada, o DeviceScript precisa instalar um firmware capaz de executar esse tipo de código na placa alvo. Como exemplo do processo, usaremos uma placa ESP32-DevKitV1, igual à da Figura 8. Essa placa é suportada pelo DeviceScript como ESP32-DevKitC (https://microsoft.github.io/devicescript/devices/esp32/esp32-devkit-c). Porém, a documentação do projeto indica que há problemas com o chip serial, o que pode ter sido o responsável por problemas de comunicação com a máquina hospedeira via USB, como será visto adiante.
Para realizar a instalação do firmware na placa, na opção DeviceScript do painel esquerdo, passe o mouse em Devices e clique no botão de reticências e, em seguida, em Flash Firmware. Uma tela será mostrada no centro do Visual Studio Code com a lista dos dispositivos suportados. Em nosso exemplo, usaremos o Espressif ESP32-DevKitC (esp32_devkit_c). Esse processo é ilustrado na Figura 9.
O resultado desse processo será mostrado no console do DeviceScript, apresentado no terminal do Visual Studio Code, podendo ser acompanhado pelo desenvolvedor para verificar se há alguma falha. Para o exemplo deste artigo, uma porção da saída do console é apresentada na Figura 10. Uma característica interessante a ser citada é que o firmware do DeviceScript é programado para piscar periodicamente o LED ligado à GPIO2, logo, esta é uma forma de saber se a instalação foi bem sucedida.
Após a instalação do firmware, é possível efetuar uma conexão entre o Visual Studio Code e a placa, o que permite inspecionar suas configurações, a aplicação instalada e até mesmo pará-la ou iniciá-la. Essa conexão pode ser realizada clicando-se no botão Connect… do painel Devices da Figura 9 previamente mostrada, representado pelo símbolo de um plugue ao lado esquerdo do sinal positivo. Ao selecionar Serial, a conexão será realizada à placa e o painel será preenchido com dados do dispositivo físico, como exemplificado na Figura 11.
Estabelecida a conexão, pode-se executar o código de exemplo diretamente na placa. Para isso, deve-se realizar o mesmo procedimento feito para execução do código no simulador, mas estando com a placa conectada. Desse modo, o script será instalado nela, de modo que o campo de código mudará de “no script” para o nome do projeto (em nosso caso, “devicescript-esp32”) e os botões ao lado (verde com símbolo de play e vermelho com símbolo de stop) permitirão controlar o estado da execução do código.
Com a placa funcionando corretamente com o firmware do DeviceScript, ela está pronta para receber os códigos desenvolvidos em TypeScript/JavaScript. No entanto, deve-se ressaltar que, pela natureza da implementação da linguagem no dispositivo embarcado, o processo de envio do código sempre envolverá um passo de construção e, posteriormente, a execução na placa.
Na próxima seção, seguiremos com um exemplo mais aprofundado de código do DeviceScript para o ESP32.
Implementação de um contador binário de 3 bits com DeviceScript no ESP32
Agora é hora de partir para um exemplo mais completo de código com o DeviceScript. Nesta seção, será desenvolvido um pequeno projeto de contador de 3 bits, cujo valor corrente será apresentado em representação binária. Esse valor deverá ser acrescido a cada segundo, voltando para 0 ao chegar em 8 (2³).
Em nosso projeto, serão usados LEDs para representar os bits do contador. Como são apenas 3 bits, os LEDs selecionados foram de cores diferentes (vermelho, verde e azul), sendo cada um ligado a um GPIO do ESP32: vermelho no GPIO13, verde no GPIO14 e azul no GPIO26. Entre o polo positivo de cada LED e o respectivo GPIO, será usado um resistor de 560Ω para limitar a corrente nos LEDs. A Figura 12 apresenta o esquemático do projeto.
Como dito anteriormente, a placa utilizada no protótipo é suportada pelo DeviceScript como ESP32-DevKitC. Essa placa já possui sua pinagem pré-mapeada em um módulo TypeScript incluso no pacote do DeviceScript (@dsboard/esp32_devkit_c), que pode ser importado e usado em nosso contador binário. É muito importante ressaltar, porém, que os pinos I2C não vêm pré-configurados. Porém, é possível realizar o acesso genérico a qualquer pino de GPIO diretamente pela função gpio() do módulo @devicescript/core, porém este uso não será feito neste artigo. Mais sobre isso pode ser conferido em https://microsoft.github.io/devicescript/developer/drivers/digital-io.
Começaremos então o código do contador. No projeto “devicescript-esp32” criado na seção 2, limpe o conteúdo do arquivo main.ts e inclua o seguinte no lugar:
/* Importações relacionadas ao uso dos pinos de GPIO
* no DeviceScript. */
import { IOPin, GPIOMode } from "@devicescript/core"
// A importação do pinMode previne erros de compilação.
import { pinMode } from "@devicescript/gpio"
// Neste exemplo, será usada a placa ESP32 DevKitC.
import { pins } from "@dsboard/esp32_devkit_c"A primeira linha de código importa dois tipos que o DeviceScript usa: IOPin, que representa um pino de GPIO, e GPIOMode, que é uma enumeração dos modos nos quais o pino pode estar (Input, Output, Off, dentre outros). A segunda linha realiza a importação da função pinMode, que, apesar de não ser usada, garante a correta compilação, uma vez que alguns métodos usados para configuração do modo dos pinos e escrita de dados não serão encontrados corretamente pelo compilador sem ela. Por fim, a terceira importação é exatamente a inclusão dos pinos pré-mapeados do ESP32-DevKitC descritos anteriormente.
Internamente, o objeto pins consiste em uma interface composta de diversas variáveis Px, cada uma representando o GPIOx correspondente da placa, além de variáveis VN e VP para os pinos de mesmo nome. Sendo assim, por meio dessas variáveis, será feito o acesso de leitura e escrita aos pinos para acionar os LEDs. Para simplificar o exemplo e manter o padrão do TypeScript, pode-se assumir que o tipo dessas variáveis é IOPin (que já foi importado na primeira linha de código), porém, existem outros tipos, tais como InputPin, AnalogPin, dentre outros, todos derivando de PinBase.
Continuando o projeto, para melhor organização, os pinos a serem utilizados serão instanciados dentro de um vetor de IOPin’s, baseando-se no esquemático da Figura 12 e nas variáveis do objeto pins. Além disso, serão declaradas duas constantes indicando o número de bits (3) e o valor máximo representável por esses bits (2³-1 = 7), para que a lógica do contador possa ser executada. O código para esse processo é mostrado a seguir:
/* Em nosso exemplo, serão 3 bits (3 LEDs). Sendo assim, o
* limite superior do contador é 2^3 = 8.
* Não é a mesma coisa que o maior valor representado 2^3 - 1. */
const MAX_BITS = 3;
const MAX_VALUE = Math.pow(2, MAX_BITS);
/* pin_led é um vetor que conterá os pinos a serem usados
* em nossa aplicação. Usar vetor assim não é obrigatório,
* aqui foi somente para organização. */
let pin_led: IOPin[] = [];
// Usaremos os pinos 13, 14 e 26.
pin_led.push(pins.P13);
pin_led.push(pins.P14);
pin_led.push(pins.P26);
/* Cada pino será usado apenas para saída, já que apenas enviaremos
* sinais para ligar ou desligar o LED conectado. */
for (let i = 0; i < pin_led.length; i++) {
pin_led[i].setMode(GPIOMode.Output);
}
No código acima, as variáveis MAX_BITS e MAX_VALUE são as constantes indicadas anteriormente. O vetor pin_led contém as referências aos pinos do ESP32 e é populado sequencialmente com os pinos GPIO13, GPIO14 e GPIO26, seguindo a sintaxe do objeto pins da importação de dsboard/esp32_devkit_c, de modo que a posição de um elemento do vetor também é indicativo de um pino (pin_led[0] é GPIO13, por exemplo). Desse jeito, todos os pinos são configurados em modo somente de saída iterando no vetor pin_led no laço for do trecho acima.
Considerando-se que esses pinos assumirão apenas valores binários, a representação binária do valor atual do contador será armazenada em um vetor de number’s denominado led_bits, onde o elemento na posição i será o bit correspondente ao pino na mesma posição no vetor pin_led, o que exigirá que esse novo vetor led_bits seja populado na ordem inversa. A função factor_bits() responsável por isso é dada abaixo:
/* Função para converter um número inteiro em sua
* representação binária. Dado o número num e a quantidade
* de bits num_bits desejada, será retornado um vetor de
* 0s e 1s. Como JS/TS trabalha com tipo number, o retorno
* será um vetor de number's. Observe que, devido à fatoração,
* o vetor deve ser populado da direita para esquerda para
* refletir a ordem dos bits nos LEDs posteriormente. */
function factor_bits(num: number, num_bits: number): number[] {
let reminder: number = num;
let led_bits: number[] = [];
for (let i = num_bits - 1; i >= 0; i--) {
led_bits.push(reminder % 2);
reminder = Math.floor(reminder / 2);
}
return led_bits;
}
Uma observação a ser feita sobre essa função é que como em nosso projeto são usados poucos pinos, não há problema no uso de um vetor de number’s para representar uma string binária, no entanto, como o ESP32 é um ambiente limitado, em um projeto no qual fosse necessário manipular muito mais pinos, uma outra abordagem seria o uso de máscaras de bits. Apesar de o tipo number do TypeScript e JavaScript ser do tipo ponto flutuante de 64 bits, há uma conversão implícita para inteiro de 32 bits ao realizar operações bit a bit, o que permite o uso de máscaras de bits.
Pode-se agora seguir para a parte principal do código, que piscará os LEDs de acordo com o valor do contador. Uma variável count será usada para armazenar esse valor. Além disso, uma outra variável times também será usada para rastrear a quantidade de vezes que o contador foi incrementado, apenas para fins informativos. Uma vez que o contador deverá ser incrementado a cada segundo, será usada a função setTimeout() para execução do procedimento de manipulação dos pinos do ESP32 e atualização do contador, como pode ser visto abaixo:
// count: valor atual do contador
let count = 0;
// times: quantas vezes o contador foi incrementado
let times = 0;
/* A cada 1000ms, será executada a arrow function definida dentro
* do setInterval. Ela converterá o contador para binário e
* escreverá o valor de cada bit para o pino correspondente.
* Uma mensagem de depuração será apresentada no log.
* O contador será incrementado ao final, e se chegar ao valor máximo,
* voltará a 0. */
const interval: number = setInterval(() => {
// Determinação da representação binária do valor atual do contador
let led_bits = factor_bits(count, MAX_BITS);
/* Escrita do valor de cada bit da representação binária no
* respectivo pino. O valor do bit resultará em um LED aceso (1) ou
* apagado (0). A posição do pino é a mesma do bit na fatoração. */
for (let i = 0; i < MAX_BITS; i++) {
pin_led[i].write(led_bits[i]);
}
// Mensagem impressa no console para depuração.
console.log(`${times} - Count ${count} -> P13: ${led_bits[2]} - P14: ${led_bits[1]} - P26: ${led_bits[0]}`);
/* Incremento no contador - se o valor dele chegar ao valor máximo
* após isso, então deve ser resetado para 0. */
count++;
if (count >= MAX_VALUE) {
count = 0;
}
times++;
}, 1000);
No código acima, dentro do setTimeout(), é definida uma arrow function que é executada a cada segundo, a qual realiza o processo principal para piscar os LEDs. O primeiro passo é a conversão do valor do contador em binário utilizando a função factor_bits() definida anteriormente, de modo a obter os bits a serem usados para cada pino selecionado do ESP32. Em seguida, tais bits são usados para ativar ou desativar os LEDs ligados aos respectivos pinos, de acordo com as posições do vetor de bits (led_bits) e pinos (pin_led). Após isso, uma mensagem de depuração é apresentada no console, podendo ser visualizada no console do DeviceScript (como exemplificado na Figura 13). Por fim, o contador é incrementado, sendo que se o valor dele chegar ao máximo (MAX_VALUE), ele deverá ser resetado a 0, e o processo recomeçará.
A Figura 14 apresenta o circuito montado da Figura 12 durante a execução do nosso código, especificamente, durante um momento no qual o contador está com valor 6. Sendo 610 = 1102, tem-se então que os pinos P13 e P14 receberão um sinal alto e o P26 um sinal baixo, resultando nos leds vermelho e azul acesos e o branco apagado.
O código completo do projeto desenvolvido neste artigo está disponível em https://github.com/brunodias89/devicescript-esp32-led-count .
Conclusão
Neste artigo, foi apresentada uma introdução ao DeviceScript, que é uma implementação da linguagem TypeScript para uso em microcontroladores. Para utilização do framework, foi apresentada a criação de um projeto com ele, bem como o processo de criação de simuladores e utilização de placas reais com o auxílio do Visual Studio Code e a extensão específica do DeviceScript. Como exemplo de uso, foi apresentado um contador binário de 3 bits com ESP32, o qual tirou proveito do mapeamento de pinos fornecido já pronto pelo próprio DeviceScript para alguns dispositivos, podendo ser estendido para outros.
Alguns tópicos não foram abordados neste artigo e que poderão ser abordados no futuro, tais como a captura de dados de sensores com visualização compatível com Jupyter e o uso de displays I2C, incluindo suporte a pequenos códigos TSX/JSX.
Referências
- TypeScript: https://www.typescriptlang.org/
- DeviceScript: https://microsoft.github.io/devicescript/








Excelente artigo!