Antes de trabalhar com Python e Arduino para comunicação serial, é importante entender esse linguagem. Python é uma linguagem dinâmica e robusta que foi criada por Guido Van Rossum (1991) e escrita em C (CPython). É interpretada e multiplataforma. Sim! “Roda” em Linux, Windows, Mobile, Web, embedded, VxWorks e dentre outras plataformas.
É fácil de aprender, muito poderosa e produtiva por diversos aspectos. Vamos citar alguns: sua sintaxe; tipagem forte; funções e blocos de códigos delimitados por indentação; é uma linguagem dinâmica; e oferece estruturas de dados de alto nível (tuplas, listas e dicionários). Precisa de algo rápido para prototipação e validação? Vá de Python!
Com poucas linhas e com módulos poderosos é possível implementar fácilmente aplicações que façam uso de comunicação cliente-servidor TCP/UDP, FTP, XML, REST, PDI, threads, GUI, Signal, IPC, banco de dados e comunicação com protocolos industriais, tais como Modbus, RS485 e RS232. Também é possível desenvolver aplicações Web.
E no mundo embarcado, Python interpretado é lento! Será? Python não é apenas interpretado (vem novidade por aí, aguarde), você consegue ter acesso ao GPIO pela interface /sys/class/gpio, manipulando de uma maneira muito simples e elegante o seu hardware. Agora vamos conhecer um pouco do leque de opções desse framework começando com uma comunicação serial.
Vamos ver do que precisamos e em poucas linhas como comunicamos via serial entre Python e Arduino.
Configuração do ambiente host
Para este artigo será utilizada a distribuição Ubuntu 13.04 (Raring Ringtail) de 64 bits e a seguir são listados os seus pré-requisitos:
- Python instalado (eu estou usando a versão 2.7.2);
- A library python-serial (pyserial);
- Arduino UNO;
- Firmware para comunicação serial gravado no Arduino. Na próxima seção do post mostraremos seu sketch.
Confirmando a versão do Python e do Pyserial:
cleiton@linuxVM:~/projetos/python/communicationSerial$ python --version Python 2.7.2+ cleiton@linuxVM:~/projetos/python/communicationSerial$ pip freeze | grep serial pyserial==2.5
Se por algum motivo o comando –version do python não exibir a versão e/ou o pip não existir na máquina, podemos instalá-los com os seguintes passos:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo apt-get update && apt-get install python2.7-dev python2.7 python-pip ... cleiton@linuxVM:~/projetos/python/communicationSerial$ python --version && pip --version Python 2.7.2+ pip 1.3.1 from /usr/lib/python2.7/dist-packages (python 2.7)
Agora Python e pip (Install Python Package) estão instalados. Caso não apareça nada com o comando “pip freeze | grep serial”, você deve executar o comando abaixo:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo apt-get install -f python-serial
Sketch Arduino
Para o nosso exemplo usaremos o seguinte programa gravado no Arduino:
/* Temperatura em Celsius */
int PinAnalogLM35 = 0; //Setando Pino A0
float valAnalog = 0; // Iniciando variavel valAnalog como 0
float temp = 0; //Iniciando variavel temp como 0
void setup(){
serial.begin(9600);
}
void loop(){
if (Serial.available() > 0){
if (Serial.read() == 116){ // letra t
// Lento o pino A0, aqui estamos obtendo o valor
valAnalog = analogRead(PinAnalogLM35);
temp = (valAnalog * 500) / 1023;
Serial.println(temp);
}
}
}
O firmware exemplo irá retornar a temperatura quando for enviado o caractere ‘t’ pela porta serial.
Programas de Exemplo com Python e Arduino para comunicação serial
Vejamos um exemplo básico para trabalhar com o módulo pyserial:
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import serial
ser = serial.Serial('/dev/ttyUSB0', 9600)
ser.write('5')
#ser.write(b'5') #Prefixo b necessario se estiver utilizando Python 3.X
ser.read()
Vamos entender como funciona o código acima. Primeiro, na linha 4, antes de qualquer coisa, é importado o módulo serial. Depois deve-se iniciar a conexão com a porta serial com a chamada ao método serial.Serial(PORTA_SERIAL, BAUD_RATE). Como estamos usando Linux e o Arduino é um hardware da classe Serial USB CDC, o dispositivo serial criado é /dev/ttyUSB0. Esse processo não foi testado no Windows, mas me parece que é apenas o número sem o COM. Após feita essa configuração e o baudrate ajustado corretamente, que no meu caso é 9600 bps, na linha seguinte 7 é enviado o caractere ‘5’ ao dispositivo pela porta serial. Se tudo ocorrer bem e algum dispositivo estiver programado para responder, na linha 9 é realizada a leitura dos dados que chegarem na porta serial do computador. Facil não? Python é demais!
Agora, utilizando o firmware exemplo, vamos escrever uma aplicação para comunicar com ele.
Código (exemplo01.py):
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import time
import serial
# Iniciando conexao serial
comport = serial.Serial('/dev/ttyUSB0', 9600)
#comport = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) # Setando timeout 1s para a conexao
PARAM_CARACTER='t'
PARAM_ASCII=str(chr(116)) # Equivalente 116 = t
# Time entre a conexao serial e o tempo para escrever (enviar algo)
time.sleep(1.8) # Entre 1.5s a 2s
#comport.write(PARAM_CARACTER)
comport.write(PARAM_ASCII)
VALUE_SERIAL=comport.readline()
print '\nRetorno da serial: %s' % (VALUE_SERIAL)
# Fechando conexao serial
comport.close()
Saída:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo python ./exemplo01.py Retorno da serial: 27.37 cleiton@linuxVM:~/projetos/python/communicationSerial$
Beleza, funcionou! Agora vamos entender o que mudou nesse script porque tem coisa nova. Importamos o módulo time (explicaremos a frente o porquê) e para iniciar a porta serial foi realizado o mesmo procedimento do código anterior, porém logo abaixo, comentado, tem o mesmo código de inicialização, com uma diferença. Foi acrescentado um parâmetro novo, o timeout=1, porque no caso de tentarmos conectar e o dispositivo não responder, o programa vai ficar ocioso aguardando, de forma blocante. Porém se usamos timeout=1 tem-se um tempo de 1s para a conexão responder.
Foram criadas duas variáveis, uma com o caractere ‘t’ na linha 11 e outra na linha 12 com o mesmo conteúdo, só que usando o valor em ASCII. Na linha 15 implementamos algo que não foi encontrado na documentação, mas como já havia passado por isso antes, achei interessante mencionar. É o tempo necessário para conexão, criando um delay para a escrita de dados. No caso deste exemplo, valores entre 1.5 s e 1.8 s causou um resultado perfeito, mas já teve caso que um delay de .2 ou .5 causarem o mesmo resultado. Na linha 17 ou 18 escrevemos na serial usando codificação ASCII ou o caractere. Logo na sequência são lidos dados da porta serial por meio do método comport.readline() na linha 20, que recebe o valor lido e o armazena na variável VALUE_SERIAL, cujo valor, em seguida, é exibido na tela por meio da função print. Tudo isso ocorrendo sem grandes emoções, terminamos indicando o fim da conexão com comport.close() na linha 25.
Só mais um detalhe sobre a conexão estabelecida na linha 8. As ações com a porta serial podem ser manipuladas pela variável comport, a qual recebe essa conexão e podemos dizer que ela é o seu file descriptor até o momento em que encerramos a comunicação.
Agora se você prestar atenção no exemplo básico que criamos e no nosso programa, a parte que trata a recepção é diferente. No primeiro exemplo foi utilizado o método ser.read() e depois comport.readline(). É a mesma coisa? Não!
Vamos ver o que muda usando o método comport.read() no exemplo01.py.
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import time
import serial
# Iniciando conexao serial
comport = serial.Serial('/dev/ttyUSB0', 9600)
#comport = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) # Setando timeout 1s para a conexao
PARAM_CARACTER='t'
PARAM_ASCII=str(chr(116)) # Equivalente 116 = t
# Time entre a conexao serial e o tempo para escrever (enviar algo)
time.sleep(1.8) # Entre 1.5s a 2s
#comport.write(PARAM_CARACTER)
comport.write(PARAM_ASCII)
#VALUE_SERIAL=comport.readline()
VALUE_SERIAL=comport.read()
#VALUE_SERIAL=comport.read(3)
print '\nRetorno da serial: %s' % (VALUE_SERIAL)
# Fechando conexao serial
comport.close()
Saída:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo python ./exemplo01.py Retorno da serial: 2 cleiton@linuxVM:~/projetos/python/communicationSerial$
Agora usando o terceiro modo, comport.read(3):
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import time
import serial
# Iniciando conexao serial
comport = serial.Serial('/dev/ttyUSB0', 9600)
#comport = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) # Setando timeout 1s para a conexao
PARAM_CARACTER='t'
PARAM_ASCII=str(chr(116)) # Equivalente 116 = t
# Time entre a conexao serial e o tempo para escrever (enviar algo)
time.sleep(1.8) # Entre 1.5s a 2s
#comport.write(PARAM_CARACTER)
comport.write(PARAM_ASCII)
#VALUE_SERIAL=comport.readline()
#VALUE_SERIAL=comport.read()
VALUE_SERIAL=comport.read(3)
print '\nRetorno da serial: %s' % (VALUE_SERIAL)
# Fechando conexao serial
comport.close()
Saída:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo python ./exemplo01.py Retorno da serial: 26. cleiton@linuxVM:~/projetos/python/communicationSerial$
Conseguiu notar a diferença? Vamos listar o que acontece usando cada um dos métodos utilizados.
- comport.readline(): Efetua a leitura dos dados da porta serial até que o caractere ‘\n’ seja recebido, ou seja, uma linha inteira é lida;
- comport.read(): Realiza a leitura de somente 1 byte da porta serial;
- comport.read(N): Um número limitado de bytes são lidos, informado pelo parâmetro N do método.
No nosso caso “27.37” contém 5 caracteres, portanto, 5 bytes. Vamos tirar a prova.
#!/usr/bin/python
# -*- coding: iso-8859-1 -*-
import time
import serial
# Iniciando conexao serial
comport = serial.Serial('/dev/ttyUSB0', 9600)
#comport = serial.Serial('/dev/ttyUSB0', 9600, timeout=1) # Setando timeout 1s para a conexao
PARAM_CARACTER='t'
PARAM_ASCII=str(chr(116)) # Equivalente 116 = t
# Time entre a conexao serial e o tempo para escrever (enviar algo)
time.sleep(1.8) # Entre 1.5s a 2s
#comport.write(PARAM_CARACTER)
comport.write(PARAM_ASCII)
#VALUE_SERIAL=comport.readline()
#VALUE_SERIAL=comport.read()
VALUE_SERIAL=comport.read(5)
print '\nRetorno da serial: %s' % (VALUE_SERIAL)
# Fechando conexao serial
comport.close()
Saída:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo python ./exemplo01.py Retorno da serial: 27.41 cleiton@linuxVM:~/projetos/python/communicationSerial$
Além dessas opções de leitura, contendo derivações da função read(), temos outras que poucos conhecem/utilizam quando se usa pyserial. Entre elas podemos citar isOpen, name, o proprio file descriptor e as N opções de configuração da conexão. Vamos analisar de acordo com exemplo abaixo (exemplo02.py):
#!/usr/bin/python # -*- coding: iso-8859-1 -*- import sys import time import serial """ VARIAVEIS GLOBAIS (NESTE EXEMPLO) """ DEVICE='/dev/ttyUSB0' def InfoComSerial(): print '\nObtendo informacoes sobre a comunicacao serial\n' # Iniciando conexao serial comport = serial.Serial(DEVICE, 9600, timeout=1) time.sleep(1.8) # Entre 1.5s a 2s print '\nStatus Porta: %s ' % (comport.isOpen()) print 'Device conectado: %s ' % (comport.name) print 'Dump da configuracao:\n %s ' % (comport) print '\n###############################################\n' # Fechando conexao serial comport.close() """ main """ if __name__ == '__main__': InfoComSerial()
Saída:
cleiton@linuxVM:~/projetos/python/communicationSerial$ sudo python ./exemplo02.py Obtendo informacoes sobre a comunicacao serial Status Porta: True Device conectado: /dev/ttyUSB0 Dump da configuracao: Serial<id=0x9438f4c, open=True>(port='/dev/ttyUSB0', baudrate=9600, bytesize=8, parity='N', stopbits=1, timeout=1, xonxoff=False, rtscts=False, dsrdtr=False) ############################################### cleiton@linuxVM:~/projetos/python/communicationSerial$
São opções interessantes pois com isOpen pode-se inspecionar se a porta já está aberta antes de prosseguir com algo. Usa-se name para se obter o device name da porta conectada e, tendo-se em mãos o nome do file descriptor da conexão, pode-se exibir as suas configurações que estão sendo utilizadas no momento.
E se eu quiser mudar algum parâmetro da configuração da conexão serial?
#!/usr/bin/python # -*- coding: iso-8859-1 -*- import sys import time import serial """ VARIAVEIS GLOBAIS (NESTE EXEMPLO) """ DEVICE='/dev/ttyUSB0' BAUD_RATE='9600' TIMEOUT='1' PARITY='N' STOPBITS='1' BYTESIZE='8' def InfoComSerial(): print '\nObtendo informacoes sobre a comunicacao serial\n' # Iniciando conexao serial #comport = serial.Serial(DEVICE, 9600, timeout=1) comport = serial.Serial(DEVICE, int(BAUD_RATE), timeout=int(TIMEOUT), bytesize=int(BYTESIZE), stopbits=int(STOPBITS), parity=PARITY) # Alem das opcoes rtscts=BOOL, xonxoff=BOOL, e dsrdtr=BOOL # Link: https://pyserial.sourceforge.net/pyserial_api.html#constants time.sleep(1.8) # Entre 1.5s a 2s print '\nStatus Porta: %s ' % (comport.isOpen()) print 'Device conectado: %s ' % (comport.name) print 'Dump da configuracao:\n %s ' % (comport) print '\n###############################################\n' # Fechando conexao serial comport.close() """ main """ if __name__ == '__main__': InfoComSerial()
Adicionamos algumas variáveis globais com as configurações desejadas nas linhas 11 a 16 e aplicamos os novos parâmetros em serial.Serial().
Bom, acredito que abordei um conteúdo consistente sobre conexão serial usando Python e de fácil reprodução. Espero que, assim, todos consigam usufruir. Fique à vontade para publicar a sua dúvida nos comentários.
Conclusão
Como Python é um framework presente na maioria das distribuições Linux para PC, e também nas distribuições padrões de boards como RaspberryPI e BeagleBone Black, é muito fácil fazer algo funcionar! E não se engane pela simplicidade e elegância da linguagem porque ela pode te surpreender em performance e recursos.
Agradecimentos
Gostaria de agradecer ao Diego Sueiro e Thiago Lima pela oportunidade, Fábio Souza pela motivação e o Henrique Rossi pela paciência, dicas e ajuda na revisão.
Saiba Mais
Introdução ao Arduino – Primeiros Passos
