UART-RPI é um projeto de comunicação serial utilizando a Raspberry Pi e o NodeMCU através da UART. Apesar de ser uma comunicação de curto alcance e em geral lenta, ecom esta integração, é possível recuperar informações de sensores instalados no NodeMCU e transmiti-los para a raspberry. É importante por alguns motivos, entre eles, a aquisição de dados dos sensores é mais simples ser realizada na NodeMCU e também, na disponibilidade de outro sistema de aquisição, neste caso, mais barato, conseguimos economizar GPIOs do nosso sistema central.
Segue abaixo a estrutura de diretórios do projeto
├── nodemcu
│ └── uart.ino
├── README.md
└── rpi
├── display.s
├── examples
│ └── countdown.c
├── lib
│ ├── fileio.s
│ ├── gpio.s
│ ├── lcd.s
│ └── utils.s
├── LICENSE
├── makefile
└── uart
└── uart.c
Possui a macro open_file para abertura de arquivos. Recebe no R0, o descritor do arquivo aberto, no R1, o modo de abertura do arquivo.
Possui a macro nanosleep para fazer o programa parar durante o tempo específicado. R0 é um ponteiro para quantidade de segundos e R1 é um ponteiro para quantidade de nanossegundos.
Possui macros para configurar pinos como entrada e saída, alterar o nível lógico no modo de saída e ler o nível lógico em determinado pino. A sessão de pinos tem seu array configurado da seguinte maneira:
Biblioteca principal para o controle do LCD
Programa principal para execução do contador. O valor do contador fica registrado em R1, e as flags para pausar/continuar e reiniciar contagem, estão nos registradores R6 e R5, respectivamente
Para facilitar a construção do programa, existe um makefile dentro da pasta rpi, onde é possível executar:
$ make uart
Para construção do executável. Logo em seguida basta utilizar:
$ sudo ./uartx
para executar o programa
uart: cuart
cuart: uart/uart.c lib/lcd.s
gcc -o uartx uart/uart.c lib/lcd.s -lwiringPi
Abaixo está presente os dispositivos utilizados, suas características e documentação utilizada para desenvolvimento do projeto
A plataforma NodeMCU é uma placa de desenvolvimento que combina o chip ESP8266, uma interface usb-serial e um regulador de tensão 3.3V. Mais dados sobre sua documentação podem ser encontrados aqui.
Alguns pinos utilizados na NodeMCU estão listados na tabela abaixo:
Pino | Descrição |
---|---|
D0 | Sensor Digital 1 |
D1 | Sensor Digital 2 |
A0 | Sensor Analógico 1 |
TX | Envio comunicação serial |
RX | Recebimento comunicação serial |
Baseada no processador BCM 2385, possui 54 I/O de propósito geral (GPIO), além daqueles utilizados para comunicação com o display, estão sendo utilizados mais dois para comunicação serial: TX/RX. É importante notar que, o GPIO 1 não está posicionado no PINO 1. As informações da placa são mostradas na tabela abaixo, junto da descrição sobre o uso de cada GPIO.
Pino | GPIO | Descrição |
---|---|---|
8 | 14 | TX |
10 | 15 | RX |
Devido a alguns lixos gerados na saída serial da NodeMCU foi realizado um processo de inicialização. Quando a raspberry pi inicia, fica aguardando o envio de um conjunto de palavras em sequência específica para identificar que a inicialização foi feita com sucesso.
Para troca de informações entre os dispositivos, foram definidos comandos. Cada informação é enviada com 1 byte, onde os três bits mais significativos indicam um comando:
B2 | B1 | B0 | Descrição |
---|---|---|---|
0 | 0 | 1 | Solicita status da NodeMCU |
0 | 1 | 0 | Solicita status do sensor |
0 | 1 | 1 | Solicita valor do sensor |
Os bits mais significativos B7-B3, indicam qual sensor vai ser executado o comando: 0 - 31 (32 sensores).
Como mostrado na figura, temos a SBC controlando a exibição de informações no display, enquanto se comunica através da uart com a NodeMCU que possui e faz a aquisição dos dados dos sensores.
Neste programa, o NodeMCU funciona de forma passiva, isto é, ele apenas devolve informações ao UART do raspberry conforme as requisições da SBC (Single Board Computer) controlada pelo usuário. A NodeMCU fica constantemente ouvindo o canal RX, e toda vez que recebe um pedido, efetua os procedimentos anteriores para retornar a resposta. Na imagem abaixo, ilustra-se 3 constantes que representam 3 informações as quais o usuário pode exigir:
O código '0b001' representa o estado atual da própria placa NodeMCU, '0b010' o estado atual do sensor e '0b011' o valor atual registrado pelo sensor. Os valores dos sensores e seus status são armazenados em dois vetores de 32 posições. Uma vez que a informação está presente, é recuperada de maneira genérica pela estrutura da informação, onde é separado informação e sensor associado. Desta forma, caso se queira adicionar um novo sensor, basta garantir que a informação vai estar presente na posição escolhida para o mesmo. Todos estas constantes são salvas no vetor de comandos. O objetivo deste vetor é generalizar o código pra qualquer número de novas requisições que se deseje adicionar ao NodeMCU. Dessa forma, basta adicionar o novo comando ao vetor sem a necessidade de alterar o resto do código.
A função extract_cmd é utilizada para verificar se o suposto comando enviado da Raspberry para o NodeMCU coincide com os comandos pré-estabelecidos. O algoritmo se resume a uma simples comparação do comando enviado, que é tratado com uma operação AND entre a palavra recebida e o resultado da operação de deslocamento para esquerda com o número 1 (conforme o índice atual do laço de repetição), com o vetor que armazena todos os comandos válidos. Já a função extract_sensor é utilizada para extrair o valor do sensor a partir do deslocamento em 3 posições a direita da palavra recebida.
A comunicação serial é iniciada com a taxa de transmissão de 9600. São definidos 4 pinos: 3 de entrada (D1, D2 e A0), que correspondem respectivamente, aos sensores digitais e o sensor analógico, e um 1 pino de saída (D0), que corresponde a um LED. Em sequência, o wi-fi é definido no modo de estação e é iniciado com a senha e o identificador de serviço (ssid) definidos anteriormente. Caso não haja conexão por parte do wi-fi, é realizado um pequeno sleeping no programa e a placa é reiniciada.
Após iniciar o wi-fi, a comunicação wireless é ativada. Neste processo, o LED recebe um impulso de nível lógico alto para confirmar que a NodeMCU está em execução. Porém, neste processo inicial, a placa transmite caracteres aleatórios para a UART. Tais caracteres poderiam corromper a intercomunicação entre NodeMCU e Raspberry. Devido a isso, é enviado para a UART uma palavra-chave definida como 'UNLOCK', pois, a partir deste código, pode-se assegurar que qualquer dado transmitido pela ESP dependerá somente do programa.
Finalmente, o programa permanece em looping esperando as requisições feitas pela raspberry. Inicialmente, é feito uma leitura (digitalRead para os sensores digitais e analogRead para os sensores analógicos) dos sensores pinados anteriormente. Os dados obtidos pela leitura é armazenado no vetor que registra os valores dos sensores. Em sequência, verifica se a comunicação serial está disponível e lê o caractere enviado no processo. Este caractere representa (ou deve representar) o comando solicitado pelo usuário e o tipo de sensor que se deseja obter as informações. Dessa forma, chama-se a função extract_cmd explicada anteriormente para verificar se o comando repassado é válido e, logo em seguida, chama-se a função extract_sensor para obter o valor atual do sensor escolhido.
Por fim, o NodeMCU escreve em TX o dado correspondente ao comando solicitado. Caso 'cmd' (comando) seja equivalente ao comando de status da ESP, é enviado o valor 0 para a UART. Se 'cmd' equivaler a solicitação de status, o valor de status armazenado no vetor de status é retornado. Por último, caso 'cmd' seja equivalente ao comando de valor de sensor, utiliza-se o dado armazenado no vetor de valores.
Pode-se emitir os comandos através do terminal, onde são enviados e processados pela NodeMCU através de comunicação serial utilizando o protocolo UART. Como o processo é assíncrono, é realizada uma espera ocupada de até 1 segundo (aproximadamente), de forma que se não houver nenhum tipo de resposta, é dado como um erro de tempo excedido (timeout). vez que a informação retorne, ela é exibida no terminal e no display de LCD caso esteja conectado. Em sequência, o modo de wi-fi é definido para modo de estação,
Para estabelecer a comunicação UART, utilizam-se as bibliotecas wiringPi e wiringSerial dedicadas a mapeamento de GPIOs em hardwares Raspberry. A taxa de transmissão é definida como 9600. A imagem abaixo ilustra a função responsável por mapear e retornar o valor da porta serial que representa a mini UART.
A partir da instrução serialOpen é possível obter o endereço através da porta ttyS0 a qual a UART está atribuída, este, é salvo na variável inteira serial_port. Caso o valor obtido seja negativo, isso significa que não foi possível abrir o dispositivo conectado, uma mensagem de enviada ao usuário e a função é encerrada. Outro possível erro é falhar em abrir a biblioteca wiringPI, o qual pode ser detectado através do valor '-1' ao chamar a instrução wiringPiSetup. Uma vez que se obtém a porta com sucesso, isto é, sem falhas associadas a comunicação ou a biblioteca em si, o valor é retornado.
Após o mapeamento feito anteriormente, já é possível realizar a comunicação serial. Porém, antes de tudo, é necessário realizar um tratamento de dados indesejáveis que são lançados aleatoriamente durante a execução da NodeMCU.
Para ignorar os caracteres aleatórios, é enviado uma palavra-chave 'UNLOCK'. A ideia desta palavra-chave é que, após ser lida completamente, a comunicação está limpa e pode-se requisitar dados da NodeMCU sem risco de receber informações errôneas. O algoritmo de verificação é consideravelmente simples. O objetivo é permanecer em looping enquanto a chave não for enviada. Para isso, requisita-se um caractere do NodeMCU com a instrução serialGetchar e este é comparado com o atual caractere da palavra 'unlock'. Se houver igualdade, o contador é iterado e avança para a próxima comparação, caso contrário, o contador é zerado e a comparação volta para o primeiro passo. O motivo disso é óbvio: Querendo ou não, a palavra-chave é uma maneira arriscada de resolver o problema em questão, pois há uma pequena, mas real, probabilidade do lixo enviado a UART coincidir com os caracteres estabelecidos em 'unlock'. Uma vez que a comunicação está livre de dados aleatórios, pode-se requisitar as informações do NodeMCU.
Os dados são requisitados através da interação com o usuário. Há 3 opções possíveis: 0bxxxxx001 para solicitar o estado atual da NodeMCU, 0bddddd010 para obter o estado atual do sensor digital e 0bddddd011 para obter o valor registrado pelo sensor, cada opção escolhida é enviada para o NodeMCU através da instrução serialPutchar utilizando um inteiro correspondente ao binário. O próximo passo é captar a informação devolvida ao UART, porém, é necessário um pequeno sleeping durante a execução devido a rapidez da comunicação. Esta função de sleeping é feita manualmente através de um contador de 0 a 1 bilhão.
Por fim, requisita-se os dados devolvidos pela NodeMCU e printa-se na tela do visor LCD utilizando os recursos em Assembly integrados ao código em C.
- Na pasta rpi/ execute:
$ make uart
- Em seguida execute o programa
$ sudo ./uartx
- Na pasta nodemcu/ abra o arquivo uart.ino na Arduino IDE:
- Configure as bibliotecas do NodeMCU
- Descarregue o código na plataforma
Para a Raspberry Pi se comunicar com a NodeMCU é preciso receber uma palavra chave da NodeMCU indicando uma inicialização. Devido ao fato que ao reiniciar a NodeMCU a mesma emite vários dados aleatórios nas saídas da UART.
- Inicializar o programa na Raspberry Pi
- Descarregar programa na NodeMCU
- No terminal, deve ser imprimido a mensagem: "Secret code found", indicando que a comunicação foi estabelecida
- Após o Teste 1 executado, o sistema está pronto pra execução.
- Inserir comando: 11, para transferir a informação presente no sensor de índice 0.
- Caso queira inserir o valor presente na entrada D0 da NodeMCU, basta realizar:
SENSOR_VALUE[0] = digitalRead(D0)
na função de loop da NodeMCU - Caso haja um nível lógico ativo, a resposta deve ser 1. Caso contrário, a resposta deve ser 0.
O protótipo construído é um sistema digital utilizando plataformas de desenvolvimento IoT, em que se pode adicionar até 32 sensores analógicos e/ou digitais e exibir as respectivas informações em um display de LCD. Dessa forma, através de uma estrutura genérica, a adição de novos sensores requer mínimas alterações.
A comunicação serial, apesar de simples, mas só permite a comunicação entre dois dispositivos. Caso seja necessário enviar ou receber informações de mais dispositivo se torna inviável, necessitando do uso de outros protocolos, como o caso do i2c;
Comparado a protocolos como i2c e SPI, pode haver uma diferença de velocidade de até 10 vezes, fazendo o protocolo UART ser mais lento e algumas vezes inviável dependendo da aplicação.