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:



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 array1nbsp;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 array2nbsp;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:

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:

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 ~ array16nbsp;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