ESP8266 com PIC

ESP8266 na protoboard
ESP8266 na protoboard

Nesse post disponho um exemplo de comunicação entre uma controladora PIC e um módulo WiFi ESP8266, trocando informações com uma aplicação remota (feita em Qt) através de comunicação socket. A programação do PIC está sendo feita através da MikroC IDE, que permite fazer a programação da MCU em linguagem C, além de possuir bibliotecas para muitas funções, tal qual em Arduino.

Utilizo mais uma vez o módulo ESP8266-01, citado anteriormente nesse outro post, onde foi utilizado um buffer não inversor CD4050 para baixar a tensão de 5v para 3.3v na comunicação entre o Arduino e o ESP8266. Dessa vez não será necessário utilizar o buffer porque a microcontroladora PIC utilizada é a P16F883, que trabalha de 2v a 5.5v, então, utilizaremos diretamente 3.3v para a alimentação de ambos os componentes.




Utilizarei aqui o sensor de linha, descrito nesse post para exemplificar a coleta e emissão de dados através da comunicação via socket.

Ao final do artigo, disponibilizarei os links para download dos códigos de exemplo, portanto, sinta-se livre para desfrutar da leitura.

PIC 16F883

PIC 16F883
PIC 16F883

Escolhi essa MCU porque das pequenas que tenho é a que me oferece um recurso indispensável; UART port hardware. Do mesmo modo que com Arduino, é possível utilizar o recurso de softserial, mas em altas velocidades começa a gerar ruído e isso é uma das coisas que não deve acontecer nessa comunicação. Além disso, a MCU será configurada para utilizar o oscilador interno (que é outro recurso que considero indispensável para simplificação da prototipagem e economia de energia) em 8MHz, podendo ser ajustado até 32KHz, mas testes com a UART deverão ser executados para tal. Rodando em 32kHz a 2v, a corrente é de 11uA, mas não seria possível utilizar a UART À 38400 kbauds. É uma MCU extremamente econômica; utilizando qualquer timer que não o TMR0, pode-se fazê-la dormir e levantar em uma interrupção, porém não será necessário esse extremo. Em outros posts sobre PIC veremos como espremê-lo até ligá-lo a um limão :-)

 

Repare no desenho que cada pino possui diversas funcionalidades. Para utilizar uma delas especificamente, deve-se configurar a MCU previamente. O clock também é configurado por software, por isso costumo criar uma função setup() como a do Arduino para inicializar as configurações previamente (mas nada que não pudesse ser feito diretamente em main() – e assim será). Não vou colocar todo o código no post senão ficará enorme, mas vou citar as partes que acho importante.

Para iniciar, crie um projeto novo na IDE MikroC, selecionando o modelo da MCU (PIC16F883) e clock interno a 8MHz na configuração de bits. Ainda na configuração de bits, desabilite o Brown-Out, que é um fusível para reiniciar o PIC quando ele estiver abaixo de 4.5v (isso também é configurável, mas desse modo já basta). Após isso, uma função setup contendo as configurações descritas:

Desabilitar comparadores

 

 

Configuração da leitura analógica

O pino analógico 0 será utilizado para fazer a leitura do sensor de linha, os demais pinos serão configurados como digital:

Inicialização do módulo AD

Uma vez que a leitura analógica será utilizada, deve-se iniciar o módulo AD:

Configuração de 1NPUT 0UTPUT dos pinos

Em PIC define-se em TRISx se o pino será input ou output. Para fácil assimilação, pense em 1nput e 0utput. Colocando 0xFF, todos os bits são setados como 1nput no TRIS da porta A e C, como exemplificado abaixo:

Habilitar interrupções

A interrupção será habilitada apenas para fazer a leitura de RX. Há como fazê-lo sem interrupção, uma vez que todo o código seguirá um fluxo e nesse caso, a interrupção poderia ser dispensada. Mas é válida como exemplo porque poderia haver um caso de interrupção externa gerada por um sensor digital, por exemplo. Assim que tiver uma oportunidade, escrevo algo a respeito, mas inicialmente um bom exemplo sobre interrupções pode ser visto aqui, ou aqui, também aqui, mais um aqui e um que gosto bastante, bem aqui.

Basicamente, será habilitada a interrupção externa no RX, desabilitada no TX e periféricamente. Por fim, a chave geral das interrupções é ligada.

Configurar frequência do oscilador interno

As configurações não são feitas “de cabeça”. Obviamente eu li o datasheet para executá-las, ainda mais que programo um monte de modelos de PIC, então não quero e não vejo razão para decorar tudo, uma vez que está documentado, bastando saber o quê procurar na documentação. Para configurar o oscilador interno a 8MHz, o seguinte conjunto de bits deve ser disposto no registrador OSCCON:

Esse tunning pode ser necessário dependendo da frequência selecionada (qualquer coisa que não 8MHz). O datasheet descreve de forma sublime a configuração interna do oscilador interno.

Poderia sem dúvida alguma ser feita no loop principal porque o código
não foi escrito para ser multithread, mas não por isso deixarei de me divertir um pouco com os recursos
da controladora. Abaixo, a configuração do timer0, descrito nesse post.




Inicialização do UART por hardware

Tal qual em Arduino, devemos inicializar a UART, mas como estamos utilizando uma MCU virgem, sem bootloader nem cristal, será necessário verificar o baud rate suportado conforme o clock utilizado. O cálculo do baud rate foi exemplificado nesse outro post.

O ESP8266 já está previamente configurado para entrar na rede, portanto a única preocupação desse programa é garantir que seja possível trocar mensagens com o servidor através do ESP8266. Para tal, simplesmente valida-se as configurações de multiplas conexões e a comunicação TCP estabelecida com o socket remoto. Simplesmente estou enviando os comandos AT,CIPMUX,TCPSTART e CIPSEND. Consegui deixar espaço o suficiente na memória para processar tratamento de erros e tomada de decisão, mas não vou implementar agora porque tenho vários outros códigos pra por a mão e no momento me basta que isso funcione.

Ainda que o servidor (que recebe a conexão socket) caia ou seja reiniciado, do modo burro que o programa está operando agora, automaticamente se reconectará e em algum momento dentro de 30 segundos já estará devidamente sincronizado com a aplicação. Criei uma função que chama outras, sendo essa a rotina:

Voltando ao início do código, algumas definições foram criadas, relacionadas às configurações de rede. O ID do dispositivo, IP do servidor, porta de conexão remota e ID da conexão TCP com o ESP8266.

A diretiva DEFINE não ocupará memória do programa, ele é tratado previamente à compilação. Essa diretiva é útil para quando se quer ter uma visão clara de uma função ou para não ter que decorar valores, ou para digitar pouco, etc.

Diversas variáveis estão acessíveis de forma global:

Costumo fazer um rascunho da MCU utilizada para me guiar durante a codificação:

Aliases (ou “apelidos”) para as portas e pinos são bastante úteis para não ter que decorar os nomes dos respectivos bits, assim como são facilitadores para trocar os pinos em questão, bastando alterá-lo na definição do alias e não sendo necessário tocar no restante do código.

Na função main(), alguns ajustes mais relacionados aos TRIS e PORT podem ser vistos em detalhes baixando o código nesse link.

Assim, estão finalizadas as configurações iniciais. Agora podemos partir para o código que fará a leitura do sensor e o envio através do ESP8266.

Limpar variáveis
Pode parecer preciosismo e talvez até seja, mas deixe-me iniciar com minhas considerações:
– Não existe string em C, existe array de chars.
– Se utilizarmos sizeof(var) teremos o tamanho de alocação da variável em memória.
– Se utilizarmos strlen(var) teremos o valor utilizado do array.

Pode-se então utilizar strncpy em conjunção a um outro recurso da linguagem e preencher o buffer, mas não existe mágica, de alguma forma haverá uma interação de uma dessas funções junto com a sua. Como eu sei o tamanho do array, prefiro ir diretamente ao ponto, fazendo um loop e preenchendo o array de char com 0 (porque ‘\0’ é o 0 literal). Por isso crio essa função sempre:

O ESP8266 responde?

A primeira etapa da comunicação entre a MCU e o ESP8266 é o status serial através do comando ‘AT’, sem parâmetros. Observe que o ESP826 espera CR (carriege return) e LF (Line Feed), portanto devemos somar os bytes da mensagem excluindo “rn”, mas não deixando de enviá-los.

Como citei anteriormente, fiz questão de colocar pra funcionar primeiro e os tratamentos de excessões serão feitos posteriormente, mas essa função é a primeira necessária para a validação.




A função write() é um auxiliador na escrita, nada muito significativo. A variável word é uma flag; ‘O’ quer dizer que na resposta deverá ser esperado por “OK”. Mais uma vez – não está sendo tratado ainda nessa versão do programa.

A resposta ao comando AT é tratada pela função reader(), invocada através da flag em interrupts(), e verificada posteriormente na função main(). Nesse ponto, existe uma possibilidade de falha; o que acontece se a rede não responder? Bem, isso não está sendo tratado agora e utilizar um timer para temporizar a leitura seria interessante. Ainda há a possibilidade de haver resposta, mas ser diferente da esperada. Como tratar um erro de resposta do comando AT? A única coisa que me ocorre no momento é um reset():

Após o reset (convenhamos que fazê-lo no pino é menos trabalhoso), em dado momento o envio de comandos AT por parte do PIC voltará a ser efetivo, por isso considerei rodar essa versão do programa sem tratamento de excessões.

O próximo passo que tomei foi habilitar multiplas conexões. Em uma chamada cíclica da função sendSample() todas as rotinas de comunicação se repetem, porém o único comando dessa rotina que é aplicado é o cipsend. Mas não importa, porque não causa erros de funcionamento e garante que em caso de reset de qualquer ponto, tudo volte a funcionar sem tratar logicamente qualquer condição.

A função de conexão TCP é a próxima da lista:

– “Porque essa linguiça de código?” – você pode dizer. Bem, eu digo. Esse PIC só tem 240 bytes de memória, eu já estava chegando no limite, então reduzi o tamanho do meu array de chars (que comportaria as variaáveis grandes) para caber na MCU. Porém, acabei batendo de cara em outro problema; a memória é divida em bancos e não tinha mais espaço para comportar a variável do tamanho pretendido. Então testei enviar comandos para o ESP8266 e mandar o ‘rn’ só no final. E deu certo! Com isso, economizei MUITA memória e depois fui batendo o código. Em suma, de quase 240 bytes utilizados, consegui reduzir para 162 bytes. Essa é uma coisa importante a considerar. Certa vez um diretor de uma empresa que trabalhei me disse que “qualidade infinita tem custo infinito”. E assim é. Atingi o objetivo com menos qualidade, mas também com menor custo, senão haveria de comprar um PIC mais parrudo. Do modo que está, creio que caiba em um PIC12F (8 pinos) que tenho aqui, me resta verificar se os pinos sobresalentes são suficientes para as flags (porque utilizei pinos da MCU como flags, de forma a poupar memória).

Apesar de não ser complexo, o envio de uma mensagem TCP é dividida em duas etapas; primeiro informa-se ao ESP8266 o ID da conexão a utilizar seguido do comprimento (em bytes) da mensagem a ser enviada – lembrando que CF/LF não devem ser contabilizados. Aguarda-se então a resposta e abrir-se-á um pseudo-prompt “>”. Nesse momento a mensagem já pode ser enviada. MAS, e tão somente “MAS”, eu mandei tudo sem validação, como já citei 4 outras vezes.

Ficou convencionado o formato de mensagem “esp8266:id,status” (porque a definição é minha e eu escrevi tudo sozinho, então eu quero que seja assim) :-)

A função reader() é quem terá um pouco mais de inteligência, uma vez que deve tratar as respostas da interrupção, mas agora está um tanto quanto imprestável:

A interrupção não mantém código, mas existe um conjunto de regras a obedecer (ver referencias anteriormente citadas):
1 – desligar a chave geral das interrupções, pois interrupção tem prioridade máxima e todo o restante do processamento é parado para recebê-la.
2 – limpar a flag da interrupção, senão ao voltar a chave geral, haverá uma interrupção imediatamente
3 – tratar a causa da interrupção (que é o esperado). Aí entra a função reader()
4 – levantar a chave geral para aguardar por novas interrupções:

O servidor

App em Qt para comunicar com o ESP
App em Qt para comunicar com o ESP

A conexão TCP é feita como exemplificado nesse post. Mas aqui tem algumas implementações importantes, principalmente relacionado à manipulação das células dessa tabela que mantém o status dos dispositivos conectados.

Estou disponiblizando o programa em Qt sem finalizar também, porque nesse momento é só a prova de conceito para um projeto, mas adianto que as celulas receberão um label baseado em arquivo, utilizando o QSettings. Desse modo, poderia ser monitoramento de portas, por exemplo; ID 1 seria porta do banheiro, então o label seria BANHEIRO, mas se trocar o sensor de lugar pra monitorar a garagem, basta trocar o valor do label ID1 no arquivo labels.ini.

O código (bem simples) em Qt está disponível nesse link.

E mais uma vez, o código completo para o PIC16F883 (que pode facilmente ser adaptado para outros modelos).

Obviamente esse código será melhorado, a aplicação em Qt receberá mais recursos etc. Essa foi apenas uma prova de conceito utilizando um PIC médio. Assim que eu fiz o video com alguns sensores, atualizo esse post.

Se gostou, não deixe de compartilhar nas redes sociais e acompanhe mais posts seguindo-nos pelo DobitAoByte no facebook.

Comments

comments

Djames Suhanko

Djames Suhanko é Perito Forense Digital. Já atuou com deployer em sistemas de missão critica em diversos países pelo mundão. Programador Shell, Python, C, C++ e Qt, tendo contato com embarcados ( ora profissionalmente, ora por lazer ) desde 2009.

Deixe uma resposta