quarta-feira, novembro 22, 2017

Adendo: Controlando Servomotor com o Raspberry Pi

Na minha ingenuidade eu pensava que seria fácil um programa Python controlar um servomotor ligado diretamente ao Raspberry. Não contava com algumas limitações do hardware e com a dispersão e falta de atualização das bibliotecas. Resumo aqui as informações que eu achei.


Controle de Servomotor

Os servomotores normalmente usados utilizam um controle bastante simples, baseado na largura de um pulso de comando. Uma largura de cerca de 500 us coloca o servo na posição inicial e uma largura de 2500 us na posição final (os valores exatos variam conforme o modelo e a unidade do servo). Estes pulsos devem ser repetidos 50 vezes por segundo (aproximadamente, isto não costuma ser crítico) até o servo chegar à posição desejada. Trata-se, portanto de um sinal PWM com período de 20 ms onde o duty cycle determina a posição do servo.

Limitações e Recursos do Hardware

A melhor maneira de gerar os pulsos de comando é usar um recurso de PWM do hardware. Entretanto, o Raspberry possui no seu conector apenas dois pinos que suportam PWM (GPIO12 e GPIO18). Para complicar ainda mais, o hardware de PWM é usado para gerar áudio analógico nos modelos 1, 2 e 3 (o Zero não tem conector de áudio analógico, só fornece áudio via HDMI).

Na falta do PWM, a opção usada normalmente com microcontroladores é pulsar o sinal manualmente. Para ter uma temporização precisa é necessário usar uma interrupção de tempo com taxa elevada ou ficar em um loop com interrupções desabilitadas. Estas duas opções não são muito compatíveis com um sistema operacional multitarefa como o Linux.

Uma forma que alguns desenvolvedores encontraram é usar o recurso de DMA, que permite mover dados entre a memória e um periférico sem envolver diretamente o processador e com uma boa precisão de tempo. O controle dos pinos de entrada e saída (GPIO) utiliza dois registradores (entre outros). Quando uma escrita é feita no registrador GPSET0, os pinos correspondentes ao bits com valor "1" são colocados em nível alto e os pinos correspondentes ao bits com valor "0" mantém o nível anterior. Quando uma escrita é feita no registrador GPCLR0, os pinos correspondentes ao bits com valor "1" são colocados em nível baixo e os pinos correspondentes ao bits com valor "0" mantém o nível anterior.  Movendo os valor adequados para estes registradores nos momentos corretos podemos gerar os sinais necessários.

Diferenças Entre os Modelos e Revisões

O hardware do Raspberry passou por várias mudanças ao longo dos anos e algumas delas afetam rotinas de baixo nível como as que estamos falando.

Uma primeira mudança foi no conector de expansão. Inicialmente existia somente um conector de 26 pinos (P1). Na revisão 2 do modelo B foi acrescentado lugar para soldar um conector adicional de 8 pinos (P5).  A partir do modelo B+ o conector P1 passou a ter 40 pinos e este tem sido o padrão até o momento.

Uma outra questão é o mapa de endereços. O endereço dos registradores que controlam os periféricos mudou do Raspberry 1 para o Raspberry 2. Atualmente existem funções para retornar o endereço base, mas alguns softwares usam endereços fixos no código.

Como identificar o modelo do Raspberry? Seguindo a tradição do Unix, o Raspbian retorna esta informação (e muitas outras) através do pseudo device /proc/cpuinfo. A informação mais precisa é dada por "revision", mas ela muda com bastante frequência. Uma segunda informação usada por alguns softwares é "Hardware", mas ela (que já não distinguia todos os modelos) passou a ser igual em todos os modelos.

Tudo isto se combina com o problema de dispersão e falta de atualização das bibliotecas disponíveis na internet para aumentar a probabilidade da biblioteca que você escolher não funcionar com o seu modelo.

RPi.GPIO

A RPi.GPIO já vem instalada no Raspbian, e isto é tudo o que tem de bom para falar sobre o seu uso no controle de servos. Ela suporta um "software PWM", mas não é recomendado para algo além de piscar um LED. A precisão é bem baixa e os pulsos apresentam variação (dá para ver o servo mudando de posição). Além disto, no momento existem vários bugs não corrigidos.

Tenho a impressão que esbarrei em um destes bugs. Eu prefiro não ficar enviando indefinidamente pulsos iguais, pois a imprecisão do próprio servo fará o motor ser acionado continuamente. Fiz um teste usando sequências do tipo start() sleep() stop(), mas o servomotor deixou de se movimentar corretamente. Usando o método ChangeDutyCycle (e mantendo os pulsos continuamente) funcionou.

Aparentemente esta biblioteca não vem recebendo aperfeiçoamentos nem manutenção faz algum tempo.

RPIO.PWM

A RPIO é uma biblioteca que se propõe a ser superconjunto da RPi.GPIO com funções mais avançadas. Além dos métodos da RPi.GPIO ela inclui suporta a interrupções por mudanças no pinos digitais, suporte a comunicação TCP (o que me parece esquisito estar aqui) e suporte a PWM via DMA.

Fiz vários testes com a RPIO.PWM. Instruções de instalação aqui ... só que esta versão não suporta o Raspberry Pi Zero! Mas tem um fork mais recente. Que dá mais trabalho para instalar e tem um errinho:

sudo apt-get install python-dev
git clone -b v2 https://github.com/JamesGKent/RPIO.git
cd RPIO
nano source/c_common/cpuinfo.c
(inserir no início a linha #include <stdint.h>)
sudo python setup.py install

O resultado é bem melhor que o Rpi.GPIO, porém ainda está longe de ser perfeito. Um problema é que ela não toma cuidados especiais quando é feita uma mudança de duty cycle, gerando pulsos com tamanho incorreto.

servoblaster

Ao contrário das bibliotecas acima, este software tem como alvo o controle de servos. Aqui está a versão original, que inclui um "quick hack for Pi-Zero". Tem uma derivação mais atual, que tem vários aperfeiçoamentos e usa as funções bcm_host para obter o endereço base dos registradores.

O servoblaster não é uma biblioteca Python. O módulo principal é um deamon que cria o dispositivo /dev/servoblaster. Escritas neste dispositivo controlam o servo. Num primeiro momento pode parecer confuso, mas isto tem vantagens: apenas o deamon precisa rodar com permissões elevadas e qualquer linguagem que permita escrita em arquivos pode controlar os servos.

Internamente o código me pareceu um pouco melhor que o do RPIO.PWM. Em particular, são tomados cuidados para que não seja gerado um pulso com largura errada quando é feita mudança no duty cycle.

No momento esta é a minha opção preferida para controlar servos no Raspberry Pi.

Soluções de Hardware

Acrescentar mais hardware tira um pouco da graça de usar o Raspberry, mas acho obrigatório mencionar algumas opções. Você pode apelar e conectar serialmente (serial assíncrona, I2C ou SPI) um microcontrolador (inclusive um Arduino) para cuidar da geração dos sinais. Existem componentes específicos para isto, como o PCA9685 (com 16 PWM de 12 bits e interface I2C) usado nesta placa da Adafruit.

Um comentário:

Richard disse...

Preocupante este comentário sobre atualizações das bibliotecas.