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