Python e Arduino – Comunicação Serial

python e arduino

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

Arduino UNO

Introdução ao Arduino – Primeiros Passos

Comunicação Serial no Arduino

Referências

https://pyserial.sourceforge.net/
https://playground.arduino.cc/interfacing/python#.UzNiv86Gcno
https://cleitonbueno.wordpress.com/arduino-sensor-de-temperatura-parte1/
https://cleitonbueno.wordpress.com/arduino-sensor-de-temperatura-parte2/
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
21 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Gabriel
Gabriel
09/09/2020 12:30

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

lel
lel
26/10/2019 12:11

ser.write(str.encode(flagCharacter))

Isaac Oliveira
Isaac Oliveira
30/09/2019 09:01

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

Andrei
Andrei
Reply to  Isaac Oliveira
25/11/2019 15:43

Tu adicionou as dependências e imports corretamente? adicionou as bibliotecas necessárias? talvez o problema esteja no import.

Leandro
Leandro
Reply to  Isaac Oliveira
17/04/2020 17:44

é Serial.begin(9600); e não serial! tem que ser de maiúscula não é?

Alexandre
Alexandre
22/07/2018 20:15

Fala rapaz, tudo bem? Dá pra fazer um controlador de válvula com arduíno escrito em linguaguem python? grande abraço

Alexandre
Alexandre
Reply to  Cleiton Bueno
23/07/2018 15:37

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.

Home » Hardware » Placas de desenvolvimento » Python e Arduino – Comunicação Serial

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: