Raspberry Pi e a biblioteca Java RxTx
Veja nesse artigo como trabalhar com o Raspberry Pi + Conversor USB/RS232 usando a biblioteca Java RxTx.
A comunicação através da porta serial é um requisito muito importante para diversas aplicações, notadamente nas áreas de automação industrial, telemetria, engenharia, robótica, sistemas embarcados, etc. Veremos nesse artigo como realizar essa tarefa com Raspberry Pi Modelo B (com duas portas USB) através de um cabo conversor UBS/RS232 (Figura 1) usando:
- Biblioteca RxTx para Java;
- Sistema Operacional Raspbian (última versão);
- Java 8.
Nesse artigo não será abordada a instalação do Raspbian e do Java 8 no Raspberry Pi. Na seção Links você encontra o link do artigo com essas instruções.
Figura 1. Raspberry Pi Modelo B (com duas portas USB) e um cabo conversor UBS/RS232
Instalando o RxTx
O RxTx é uma biblioteca Java utilizada para criar aplicações que necessitam de comunicação com as portas serial/paralela do computador. A instalação do RxTx no Raspbian é extremamente simples, sendo necessário apenas utilizar o apt-get install. Para isso, abra um terminal do shell no Raspbian e execute o comando a seguir:
sudo apt-get install librxtx-java
A biblioteca RxTx usa libs nativas através da API JNI (Java Native Interface) para interagir com as portas serial/paralela. Essas libs, após o comando apt-get, são instaladas no diretório /usr/lib/jni, como mostra a Listagem 1.
Listagem 1. As bibliotecas nativas do RxTx (shared objects)
pi@raspberrypi ~/dist
array1
nbsp;ls-ltrh/usr/lib/jni/
total108K
rwxrwxrwx1rootroot24Jun132012librxtxSerial.so->librxtxSerial-2.2pre1.so
lrwxrwxrwx1rootroot23Jun132012librxtxRS485.so->librxtxRS485-2.2pre1.so
lrwxrwxrwx1rootroot21Jun132012librxtxRaw.so->librxtxRaw-2.2pre1.so
lrwxrwxrwx1rootroot26Jun132012librxtxParallel.so->librxtxParallel-2.2pre1.so
lrwxrwxrwx1rootroot21Jun132012librxtxI2C.so->librxtxI2C-2.2pre1.so
-rw-r--r--1rootroot45KJun132012librxtxSerial-2.2pre1.so
-rw-r--r--1rootroot16KJun132012librxtxRS485-2.2pre1.so
-rw-r--r--1rootroot16KJun132012librxtxRaw-2.2pre1.so
-rw-r--r--1rootroot9.7KJun132012librxtxParallel-2.2pre1.so
-rw-r--r--1rootroot16KJun132012librxtxI2C-2.2pre1.so
O jar da biblioteca está em /usr/share/java, como mostra a Listagem 2.
Listagem 2. O jar da biblioteca RxTx
pi@raspberrypi ~/dist
array2
nbsp;ls-ltrh/usr/share/java/
total64K
lrwxrwxrwx1rootroot20Jun132012RXTXcomm.jar->RXTXcomm-2.2pre2.jar
-rw-r--r--1rootroot60KJun132012RXTXcomm-2.2pre2.jar
Ao instalar via apt-get, é criado o link simbólico RXTXcomm.jar, que aponta para o jar real, o RXTXcomm-2.2pre2.jar. Um modo fácil de garantir que qualquer aplicação Java no Raspberry Pi tenha acesso ao jar é copiando-o para o seguinte diretório:
sudo cp /usr/share/java/RXTXcomm-2.2pre2.jar /usr/lib/jvm/jdk-8-oracle-arm-vfp-hflt/jre/lib/ext/
Outra alternativa é colocar o jar no classpath da aplicação que deseja usar o RxTx (através do parâmetro -cp). Veremos mais a frente como usar o parâmetro -cp.
Identificando a porta serial
Para verificar as portas disponíveis, utilizaremos o código da Listagem 3.
Listagem 3. Imprimindo as portas seriais disponíveis
import gnu.io.CommPortIdentifier;
import java.util.Enumeration;
public class ListarPortas {
public static void main(String[] args) {
Enumeration portList = CommPortIdentifier.getPortIdentifiers();
while (portList.hasMoreElements()) {
CommPortIdentifier portId = (CommPortIdentifier) portList.nextElement();
if (portId.getPortType() == CommPortIdentifier.PORT_SERIAL) {
System.out.println(portId.getName());
}
}
}
}
Este código serve para identificar todas as portas seriais que a biblioteca RxTx reconheceu no computador (no caso o Raspberry Pi), e é útil para verificar se o Java realmente pode usar o conversor RS232/USB.
Para que o programa detecte a porta serial, conecte o cabo conversor USB/RS232 numa das entradas USB do Raspberry. Se ao conectar, ainda assim o programa não reconhecer a porta serial, reinicie o Raspberry com o conversor ligado na USB para que o sistema reconheça o adaptador serial.
Ao conectar o conversor na entrada USB e executar o programa, a saída será:
/dev/ttyUSB0
Se o adaptador não estiver conectado, nada será impresso na saída do programa.
Para compilar e rodar o programa, utilize os comandos da Listagem 4. Mas antes devemos passar o jar da biblioteca RxTx através do comando -cp, como a seguir:
-cp "/usr/share/java/RXTXcomm.jar"
Listagem 4. Compilando e executando o programa
# Compilando
javac -cp "/usr/share/java/RXTXcomm.jar" ListarPortas.java
#Rodando
java -Djava.library.path=/usr/lib/jni -cp "/usr/share/java/RXTXcomm.jar:." ListarPortas
Para executar o programa, além da biblioteca devemos indicar a localização das shared libraries (.so) nativas que o RxTx utilizará. Para isso, é necessário usar o seguinte parâmetro:
-Djava.library.path=/usr/lib/jni
Testando
Uma vez instalado o RxTx, conectado o cabo conversor USB/RS232 (Figura 2) numa das entradas USB do Raspberry, estaremos aptos para desenvolver uma aplicação de teste.
Figura 2. Cabo conversor USB/RS232 usado nesse artigo.
Para testar a API e o conversor foi utilizado um cabo loopback junto ao conversor USB/RS232. Com esse cabo é possível testar o envio/recebimento de dados sem precisar de outro equipamento com comunicação serial. Como o pino de transmissão do cabo loopback está conectado ao pino de recepção, toda string enviada para o cabo será recebida de volta (echo test).
O programa da Listagem 5 (baseado no fabricante do cabo loopback) serve para testar o envio/recebimento pela serial, usando o cabo loopback.
Listagem 5. Programa para teste via loopback
001. import gnu.io.CommPortIdentifier;
002. import gnu.io.NoSuchPortException;
003. import gnu.io.PortInUseException;
004. import gnu.io.SerialPort;
005. import gnu.io.UnsupportedCommOperationException;
006. import java.io.Closeable;
007. import java.io.IOException;
008. import java.io.InputStream;
009. import java.io.OutputStream;
010.
011. public class LoopbackTest {
012.
013. private SerialPort serialPort;
014. private OutputStream outStream;
015. private InputStream inStream;
016.
017. // Abrindo a porta serial
018. public void open(String portName) throws IOException {
019. try {
020. CommPortIdentifier portId = CommPortIdentifier.getPortIdentifier(portName);
021. serialPort = (SerialPort) portId.open("serial", 5000);
022. setSerialPortParameters();
023. outStream = serialPort.getOutputStream();
024. inStream = serialPort.getInputStream();
025. } catch (NoSuchPortException | PortInUseException e) {
026. throw new IOException(e.getMessage());
027. } catch (IOException e) {
028. serialPort.close();
029. throw e;
030. }
031. }
032.
033. public InputStream getSerialInputStream() {
034. return inStream;
035. }
036.
037. public OutputStream getSerialOutputStream() {
038. return outStream;
039. }
040.
041. private void closeSafely(Closeable resource) {
042. try {
043. resource.close();
044. } catch (IOException ex) {}
045. }
046.
047. public void close() {
048. if (serialPort != null) {
049. closeSafely(outStream);
050. closeSafely(inStream);
051. serialPort.close();
052. }
053. }
054.
055. private void setSerialPortParameters() throws IOException {
056. int baudRate = 9600;
057. try {
058. serialPort.setSerialPortParams(
059. baudRate,
060. SerialPort.DATABITS_8,
061. SerialPort.STOPBITS_1,
062. SerialPort.PARITY_NONE);
063. serialPort.setFlowControlMode(SerialPort.FLOWCONTROL_NONE);
064. } catch (UnsupportedCommOperationException ex) {
065. throw new IOException("Unsupported serial port parameter");
066. }
067. }
068.
069. public static void main(String[] args) {
070.
071. String testString = "O rato roeu a roupa do rei de Roma";
072.
073. final int TIMEOUT_VALUE = 1000;
074.
075. LoopbackTest loopbackTest = new LoopbackTest();
076. try {
077. // A porta Serial é referenciada por /dev/ttyUSB0
078. loopbackTest.open("/dev/ttyUSB0");
079.
080. // Obtem os streams para leitura/escrita
081. InputStream inStream = loopbackTest.getSerialInputStream();
082. OutputStream outStream = loopbackTest.getSerialOutputStream();
083.
084. // Envia caracter por caracter do testString
085. for (int i = 0; i < testString.length(); i++) {
086.
087. outStream.write(testString.charAt(i));
088.
089. long startTime = System.currentTimeMillis();
090. long elapsedTime;
091.
092. // Aguarda um tempo e testa se ha dado disponível na porta (inStream.available)
093. do {
094. elapsedTime = System.currentTimeMillis() - startTime;
095. } while ((elapsedTime < TIMEOUT_VALUE) && (inStream.available() == 0));
096.
097. // Verifica se a leitura foi feita antes do tempo expirar
098. if (elapsedTime < TIMEOUT_VALUE) {
099. int readChar = inStream.read();
100. System.err.println("Received " + readChar + " Sent:" + testString.charAt(i));
101. }
102. else {
103. System.err.println("Sem dados na porta Serial");
104. break;
105. }
106. }
107. } catch (IOException ex) {
108. ex.printStackTrace();
109. }
110.
111. System.out.println("Fim\n");
112. loopbackTest.close();
113. }
114.}
A API do RxTx gira em torno das classes SerialPort e ParallelPort, que abstraem a comunicação com as portas serial/paralela. Como estamos interessados na porta serial, trabalharemos com SerialPort (linha 013).
O método open (linhas 018 a 031) serve para obter um objeto SerialPort e seus streams de leitura/escrita. Para isso, devemos passar o local onde iremos estabelecer a comunicação serial, no caso "/dev/ttyUSB0"(portName) para o Raspberry Pi. Essa string deverá ser passada para o método CommPortIdentifier.getPortIdentifier(), que irá devolver um objeto do tipo CommPortIdentifier. Esse novo objeto disponibiliza o método open, que recebe dois parâmetros:
- String owner -> Identifica o "proprietário" da porta;
- int delay -> Aguarda x milissegundos para liberação da porta (no caso da porta estar em uso).
O método open irá devolver um objeto CommPort, que será convertido num SerialPort, através de type casting:
(SerialPort) portId.open("serial", 5000);
Agora estamos aptos a interagir com a porta serial.
Nas linhas 055 a 067 configuramos os parâmetros de comunicação com a porta serial através do método setSerialPortParameters().
Para os testes efetuados nesse artigo, estabelecemos as seguintes configurações:
- Baud Rate = 9600 (velocidade de comunicação / taxa de transferência)
- DataBits = 8
- Stop Bits = 1
- Paridade = Nenhuma
- FlowControle = Nenhuma
Esses atributos podem variar, dependendo do tipo de equipamento conectado ao cabo serial.
O método main (linhas 069 a 114) simplesmente envia uma string, caractere por caractere, para o cabo USB/RS232. Como conectamos um cabo loopback junto ao conversor, cada caractere enviado será devolvido para o conversor nos pinos de recepção.
Ao enviar um caractere, o programa aguarda até 1000 milissegundos pela resposta. Se dentro desse período a serial receber o dado de resposta (o próprio caractere), o laço de espera será interrompido e o mesmo será impresso na tela.
Para verificar se há dados disponíveis para leitura, usamos o método inStream.available().
Compilaremos esse código no Raspberry PI através do comando a seguir caso tenha copiado a lib para a pasta ext:
javac LoopbackTest.java
Ou também podemos usar o seguinte código:
javac -cp /usr/share/java/RXTXcomm-2.2pre2.jar:. LoopbackTest.java
Para executar o programa use o comando a seguir, caso tenha copiado a lib para a pasta ext:
java -Djava.library.path=/usr/lib/jni LoopbackTest
Ou também é possível usar o comando a seguir:
java -Djava.library.path=/usr/lib/jni -cp /usr/share/java/RXTXcomm-2.2pre2.jar:. LoopbackTest
Ao executar o programa é necessário informar onde se encontram as libs nativas que o RxTx depende. Isso é feito a partir do seguinte parâmetro:
-Djava.library.path=/usr/lib/jni
A saída do programa será a mesma apresentada na Listagem 7.
Listagem 7. A saída do programa
pi@raspberrypi ~
array16
nbsp;java-Djava.library.path=/usr/lib/jni-cp"/usr/share/java/RXTXcomm.jar:."LoopbackTest
Received:79Sent:O
Received:32Sent:
Received:114Sent:r
Received:97Sent:a
Received:116Sent:t
Received:111Sent:o
Received:32Sent:
Received:114Sent:r
Received:111Sent:o
Received:101Sent:e
Received:117Sent:u
Received:32Sent:
Received:97Sent:a
Received:32Sent:
Received:114Sent:r
Received:111Sent:o
Received:117Sent:u
Received:112Sent:p
Received:97Sent:a
Received:32Sent:
Received:100Sent:d
Received:111Sent:o
Received:32Sent:
Received:114Sent:r
Received:101Sent:e
Received:105Sent:i
Received:32Sent:
Received:100Sent:d
Received:101Sent:e
Received:32Sent:
Received:82Sent:R
Received:111Sent:o
Received:109Sent:m
Received:97Sent:a
Esse programa é satisfatório para testar o Loopback. Porém, para uma aplicação mais profissional, o exemplo da Listagem 8, que faz uso de Thread e eventos da serial, é mais interessante (baseado no código fornecido no próprio site do RxTx).
Listagem 8. Lendo dados a partir de evento
import gnu.io.CommPort;
import gnu.io.CommPortIdentifier;
import gnu.io.SerialPort;
import gnu.io.SerialPortEvent;
import gnu.io.SerialPortEventListener;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* This version of the TwoWaySerialComm example makes use of the
* SerialPortEventListener to avoid polling.
*
*/
public class TwoWaySerialComm {
public TwoWaySerialComm() {
super();
}
void connect(String portName) throws Exception {
CommPortIdentifier portIdentifier = CommPortIdentifier.getPortIdentifier(portName);
if (portIdentifier.isCurrentlyOwned()) {
System.out.println("Error: Port is currently in use");
} else {
CommPort commPort = portIdentifier.open(this.getClass().getName(), 2000);
if (commPort instanceof SerialPort) {
SerialPort serialPort = (SerialPort) commPort;
serialPort.setSerialPortParams(9600, SerialPort.DATABITS_8, SerialPort.STOPBITS_1, SerialPort.PARITY_NONE);
InputStream in = serialPort.getInputStream();
OutputStream out = serialPort.getOutputStream();
(new Thread(new SerialWriter(out))).start();
serialPort.addEventListener(new SerialReader(in));
serialPort.notifyOnDataAvailable(true);
} else {
System.out.println("Error: Only serial ports are handled by this example.");
}
}
}
/**
* Handles the input coming from the serial port. A new line character is
* treated as the end of a block in this example.
*/
public static class SerialReader implements SerialPortEventListener {
private final InputStream in;
private final byte[] buffer = new byte[1024];
public SerialReader(InputStream in) {
this.in = in;
}
@Override
public void serialEvent(SerialPortEvent arg0) {
int data;
try {
int len = 0;
while ((data = in.read()) > -1) {
if (data == '\n') {
break;
}
buffer[len++] = (byte) data;
}
System.out.print(new String(buffer, 0, len));
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
}
public static class SerialWriter implements Runnable {
private final OutputStream out;
public SerialWriter(OutputStream out) {
this.out = out;
}
@Override
public void run() {
try {
int c;
while ((c = System.in.read()) > -1) {
this.out.write(c);
}
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
}
public static void main(String[] args) {
try {
(new TwoWaySerialComm()).connect("/dev/ttyUSB0");
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
O código mais importante nessa listagem é:
serialPort.addEventListener(new SerialReader(in));
serialPort.notifyOnDataAvailable(true);
Nele registramos um listener para os eventos da serial. Qualquer classe que implemente a interface SerialPortEventListener é um candidato. No caso do programa, a classe SerialReader implementa a interface e é passado como ouvinte dos eventos. Como a classe SerialReader recebe o InputStream do objeto SerialPort, ela terá acesso aos dados de entrada da serial.
Quando um dado é recebido na serial, o código a seguir é ativado:
@Override
public void serialEvent(SerialPortEvent arg0) {
int data;
try {
int len = 0;
while ((data = in.read()) > -1) {
if (data == '\n') {
break;
}
buffer[len++] = (byte) data;
}
System.out.print(new String(buffer, 0, len));
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
Esse código vai salvando os dados num buffer, até que o caractere de fim de linha '\n' seja enviado. Note que nesse código não fizemos a verificação para garantir que o buffer não estoure.
Os dados são enviados pela serial através da classe SerialWriter, que lê os dados do teclado:
@Override
public void run() {
try {
int c;
while ((c = System.in.read()) > -1) {
this.out.write(c);
}
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
}
Com o cabo loopback, é perfeitamente possível testar essa aplicação.
Nesse artigo vimos a possibilidade de usar uma das portas USB em conjunto com um conversor USB/RS232 para realizar leitura/escrita de dados em equipamentos que possuam porta serial. No modelo B do Raspberry PI estamos limitados a apenas duas portas serias, o que pode ser um problema, visto que geralmente essas entradas são usadas para teclado e mouse. Porém, o modelo B+ resolve essa questão ao fornecer quatro entradas USB.
Abraços e até a próxima!
Links
Instalando Java 8 e Raspbian no Raspberry PI
https://www.devmedia.com.br/instalando-java-no-raspberry-pi/31187
Página Oficial do RxTx
http://rxtx.qbang.org/wiki/index.php/Main_Page
Serial em Java
http://en.wikibooks.org/wiki/Serial_Programming/Serial_Java
Porta Serial
http://www.rogercom.com/PortaSerial/PortaSerial.htm
Serial Loopback
https://embeddedfreak.wordpress.com/2008/08/28/rxtx-loopback-test-program1/
Serial RxTx Code
http://rxtx.qbang.org/wiki/index.php/Event_Based_Two_Way_Communication
Artigos relacionados
-
Artigo
-
Artigo
-
Vídeo
-
Artigo
-
DevCast