ÍNDICE DE CONTEÚDO
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:
1 2 3 4 |
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:
1 2 3 4 5 |
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:
1 |
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* 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:
1 2 3 4 5 6 7 8 9 |
#!/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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
#!/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:
1 2 3 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/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:
1 2 3 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/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:
1 2 3 |
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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
#!/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:
1 2 3 |
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):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#!/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:
1 2 3 4 5 6 7 8 9 10 11 |
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?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
#!/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
Boa tarde, eu achei muito interessante a parte da comunicação serial entre o arduino e python, no caso eu precisaria usar o arduino para processamento de imagens, então eu conseguiria capturar uma imagem usando o arduino, processar no python e após isso devolver pro arduino? Estou buscando conteudos sobre,caso alguem saiba como e puder me ajudar, ficarei grato
ser.write(str.encode(flagCharacter))
Bom dia, tentei subir o sketch no meu arduino, recebo a mensagem de que:
Arduino: 1.8.10 (Windows 10), Placa:”Arduino/Genuino Uno”
exit status 1
‘serial’ was not declared in this scope
Tu adicionou as dependências e imports corretamente? adicionou as bibliotecas necessárias? talvez o problema esteja no import.
é Serial.begin(9600); e não serial! tem que ser de maiúscula não é?
Fala rapaz, tudo bem? Dá pra fazer um controlador de válvula com arduíno escrito em linguaguem python? grande abraço
Olá Alexandre, escrever em Python para Arduino não conheço, o mais próximo a isso para Sistemas Embarcados é o MicroPython, mas são microcontroladores com mais recursos.
https://micropython.org/
Um abraço.
Entendo, poderia me informar se eu poderia colocar 3 micropython no processo conectados entre si, o primeiro funcionaria como sensor, o segundo como uma central pra receber a resposta e fazer os cálculos, e o terceiro para funcionar de transmissor e enviar um comando para a válvula abrir tantos %? Desde já grande abraço.