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.

Raspberry Pi Modelo B
(com duas portas USB) e um cabo conversor UBS/RS232

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.

Cabo conversor
USB/RS232 usado nesse artigo

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
http://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