Acessando Dispositivos Externos com Computadores Pessoais

Acessando Dispositivos Externos com Computadores Pessoais

Eduardo Augusto Bezerra, Faculdade de Informatica e Faculdade de Engenharia, PUC-RS

Porto Alegre, Agosto de 2004.

http://www.inf.pucrs.br/~eduardob/disciplinas/ProgPerif/Texto_Interface_SW_HW/serial.htm


3. Acesso às portas de entrada/saída usando a linguagem C/C++

        Nessa seção é utilizada a abordagem botton-up na apresentação dos exemplos de programa nas linguagens C e C++ para acesso às portas serial (RS-232C) e paralela de um computador pessoal.


Porta Serial RS-232C

acesso direto ao hardware – inportb, outportb

linguagem C – MS-DOS/Windows 9x

·        O primeiro exemplo, listado a seguir, usa a porta serial via acesso direto ao hardware do computador, e os exemplos seguintes utilizam recursos de mais alto nível como, por exemplo, interrupções e chamadas de sistema (function call). O sistema operacional e compilador utilizados estão identificados nos comentários no início do programa.

Obs. Uma chamada de sistema é uma rotina implementada em software, fazendo parte do sistema operacional ou como um driver, que é utilizada pelo sistema operacional ou pela aplicação para acesso ao hardware. Uma chamada de sistema pode solicitar uma interrupção, que por sua vez é originada no firmware do computador pessoal, ou fornecida pelo driver em software. A função tanto da chamada de sistema quanto da interrupção é a mesma, ou seja, chegar na interface a nível de hardware e utilizar características específicas dessa porta de entrada e saída.


/*
       serial_io.c
       Programa em C para gerencia da porta serial RS-232C utilizando acesso direto
ao hardware pelas instrucoes in e out.
       Eduardo Augusto Bezerra, Maio de 2003
       compilador Turbo C++ versao 1.0 (Borland)
*/
#include <stdio.h>
#include <dos.h>
#include <process.h>
#include <conio.h>
#define end_ctrl_50  0x03FD        /* Enderecos da UART 8250 */
#define end_dado_50  0x03F8
#define LRC                0x03FB
#define MSB                0x03F9
#define LSB                0x03F8
#define MCR                0x03FC
#define LSR                0x03FD
#define MSR                0x03FE
#define IOR                0x03F8
#define IIR                0x03FA
#define IER                0x03F9
 
void inic8250(){                         /* inicializacao da 8250   */
       outportb(LRC, 0x80);       /* DLAB = 1, programa velocidade em MSB e LSB */
       outportb(MSB, 0x00);      
       outportb(LSB, 0x0C);       /* 00H 0CH == 9600 bps */
       outportb(LRC, 0x07);              /* 8 bits de dados, 1 stop bit, sem paridade */
}     
 
void reseta8250(){                       /* reseta a 8250     */
       inportb(LSR);
       inportb(MSR);
       inportb(IOR);
       inportb(IIR);
}     
 
void enable8250(){                       /* habilita a 8250   */
       outportb(IER, 0x01);
}     
 
void disable8250(){                      /* desabilita interrupcoes da 8250 */
       outportb(IER, 0x00);
}     
 
void transm_dado(unsigned char caracter){              /* transmite um caracter */
       while (!(32 & inportb(end_ctrl_50)) == 32);
       outportb(end_dado_50, caracter);
}
 
unsigned char status(){                  /* Le status da 8250 */
       return (inportb(end_ctrl_50) & 0x01);
}
 
int main(){
       int opcao = 0;
       unsigned char caracter = '';
       unsigned char aux = '';
      
       inic8250();
       reseta8250();
       outportb(IER, 0x00);       /* sem interrupcoes */
       while (opcao != 4){
              clrscr();
              printf("Entre com:\n");
              printf("\t\t\t1 - Transmitir um caracter pela serial\n");
              printf("\t\t\t2 - Receber um caracter pela serial\n");
              printf("\t\t\t3 - Transmitir arquivo pela serial\n");
              printf("\t\t\t4 - Sair do programa\n");
              opcao = getch() - '0';
              caracter = '';
              /* transmissao de um caracter */
              if (opcao == 1)
                     while (caracter != 0x1B){         /* repete ate' pressionar ESC */
                           disable8250();
                           printf("\nEntre com o carcter a ser transmitido ... ");
                           caracter = getch();
                           printf("%c", caracter);
                           transm_dado(caracter);            /* envia um caracter */
                     }
              /* recepcao de um caracter */
              if (opcao == 2){
                     printf("\n\nAguardando caracter na porta serial ... ");
                     for (;;){
                           while(!status()) if (kbhit()) break;
                           if (kbhit() && getch()) break;
                           aux = inportb(end_dado_50); /* recebe um caracter */
                           printf("\nRecebido o caracter --> %c", aux);
                     }
              }
              /* transmissao de arquivo */
              if (opcao == 3){
                     FILE *fp;
                     char arq[20];
                     printf("\n\nNome do arquivo: "); gets(arq);
                     if (!(fp = fopen(arq, "r"))){
                           printf("\nArquivo nao existe!");
                           exit(-1);
                     }
                     while(!feof(fp)){          /* transmite arquivo */
                           caracter = fgetc(fp);
                           disable8250();
                           transm_dado(caracter);
                           if (status()) putch(inportb(end_dado_50));
                     }
                     fclose(fp);
                     printf("\nPressione qualquer tecla para o menu.");
                     while(!kbhit());
              }
       }
    return 0;
}

·        A função outportb (int porta, unsigned char valor); envia para o endereço especificado em porta um byte especificado em valor. A função inportb (int porta); le um byte de um endereço especificado em porta. Com essas duas funcoes é possível acessar todos os registradores da 8250, enviando palavras de controle para sua programação, bem como enviando e recebendo bytes da interface RS-232C. Os endereços utilizados no programa acima foram discutidos na seção 1 (portas de entrada/saida, porta serial RS-232C em detalhes).


Porta Serial RS-232C

Uso de interrupcoes – int 14H

linguagem C – MS-DOS/Windows 9x

·        Uma outra forma de acessar a UART 8250 é por intermédio dos serviços da ROM-BIOS (interrupção 14H da BIOS), listados na tabela a seguir:

INT 14H

Entrada

Saida

1

Inicializa parametros da porta serial

AL = parametro

AH = 00

DX = nr. da porta.

AX = status da porta

2

Transmite um caracter

AL = caracter

AH = 01

DX = nr. da porta

AH = status: passa/falha

3

Recebe um caracter

AH = 02

DX = nr. da porta

AH = status: passa/falha

AL = caracter

4

Obtem status da porta serial

AH = 03

DX = nr. da porta

AH = codigo de status

 

Inicialização – AL:

7

6

5

4

3

2

1

0

Bits    0 .. 1  : tamanho da palavra                           10 = 7 bits

                                                                              11 = 8 bits

Bit     2        : stop bits                                              0 = 1 stop bit

                                                                                1 = 2 stop bits

Bits    3 .. 4  : paridade                                      00, 10 = sem paridade

                                                                               01 = paridade impar

                                                                               11 = paridade par

Bits    5 .. 7  : taxa de transferencia                         000 = 110 bps

                                                                             001 = 150 bps

                                                                             010 = 300 bps

                                                                             011 = 600 bps

                                                                             100 = 1200 bps

                                                                             101 = 2400 bps

                                                                             110 = 4800 bps

                                                                             111 = 9600 bps

·        No exemplo a seguir a 8250 é programada para transferências a 9600 bps, 1 stop bit, 8 bits de dados e sem paridade.


/*

       serial_bios.c

       Programa em C para gerencia da porta serial RS-232C

       utilizando a INT 14H da BIOS.

      

       Eduardo Augusto Bezerra

       Maio de 2003

      

       compilador Turbo C++ versao 1.0 (Borland)

*/

#include <stdio.h>

#include <dos.h>

#include <conio.h>

#define SERIAL 0x14

#define ESC 0x1B

int main(){

       union REGS r;

       unsigned char caracter = '';

                                                       /*  7654321                     */

       r.h.ah = 0x000;                                 /*  11100011   9600 bps,        */

       r.h.al = 0x0E3;                                 /*     =       8 bits de dados, */

       r.x.dx = 0x000;                                 /*    E3H      1 stop bit,      */

       int86(SERIAL, &r, &r);                          /*             sem paridade     */

       while (caracter != ESC)    {

              printf ("\nEntre com o caracter: ");

              caracter = getch();

              printf("%c", caracter);

              r.h.ah = 1;                       /* AH = 1, transmite caracter */

              r.h.al = caracter;

              r.x.dx = 0;

              int86(SERIAL, &r, &r);            /* Transmite o caracter */

       }

    return 0;

}

 

·        A função int int86(int nr. interrupção, union REGS *inregs, union REGS *outregs); realiza um pedido de interrupção para a CPU. Essa segunda forma é bem mais simples do que o acesso direto à 8250 mostrado anteriormente.


Porta Serial RS-232C

acesso direto ao hardware – inb, outb

linguagem C – Linux

·         A listagem do programa para Linux a seguir apresenta uma versao resumida do codigo listado anteriormente para utilizacao da porta serial, via acesso direto ao hardware.

 

/*

       serial_io.c

       Programa em C para gerencia da porta serial RS-232C

       utilizando acesso direto ao hardware pelas instrucoes

       in e out.

      

       Eduardo Augusto Bezerra

       Maio de 2003

      

       compilador gcc version 3.2 20020903 (Red Hat Linux 8.0 3.2-7)

*/

#include <stdio.h>

#include <stdlib.h>

#include <sys/io.h>

#include <unistd.h>

#include <malloc.h>

#include <string.h>

#define end_ctrl_50  0x03FD        /* Enderecos da UART 8250 */

#define end_dado_50  0x03F8

#define LRC          0x03FB

#define MSB          0x03F9

#define LSB          0x03F8

#define MCR          0x03FC

#define LSR          0x03FD

#define MSR          0x03FE

#define IOR          0x03F8

#define IIR          0x03FA

#define IER          0x03F9

int main(){

       unsigned char caracter = ' ';

       int a;

       a = setuid(0);

       printf("setuid = %d\n", a);

       if (ioperm(end_dado_50, 2, 1)) {

             perror("ioperm");

             printf("Erro: Nao liberou a porta serial!");

       }

       /* inicializacao da 8250 */

       outb(0x80, LRC);           /* 8 bits de dados */

       outb(0x00, MSB);           /* 1 stop bit           */

       outb(0x0C, LSB);           /* sem paridade         */

       outb(0x07, LRC);

       /* reseta a 8250     */

       inb(LSR); inb(MSR); inb(IOR); inb(IIR);

       outb(0x00, IER);           /* sem interrupcoes */

       caracter = ' ';

       /* transmissao de um caracter */

       while (caracter != 0x1B){     /* repete ate' pressionar ESC */

              printf("\nEntre com o caracter a ser transmitido ... ");

              scanf("%c", &caracter);

              printf("%c", caracter);

              outb(caracter, end_dado_50);      /* envia um caracter */

       }

       return 0;

}

·         Alguns comentarios sobre o programa acima e o uso da porta serial no Linux.

·         A porta serial RS-232C nao pode ser acessada diretamente por usuarios comuns. Apenas usuarios pertencente ao grupo uucp possuem acesso a essa porta. Uma solucao e desenvolver uma aplicacao ou driver que seja executado, por exemplo, em background como um daemon, e que responda a chamadas de sistema realizadas por programas dos usuarios. Essa aplicacao devera ser disparada por um usuario do grupo uucp de preferencia na inicializacao do sistema.

·         A solucao utilizada para o exemplo listado anteriormente, foi a criacao de um usuario especial pertencente ao grupo uucp, e a execucao do programa realizada por esse usuario.

·         Como o programa utiliza acesso direto ao hardware via instrucoes inb e outb, apenas usuarios pertencentes ao grupo root possuem permissao para executar esse programa. Uma solucao para possibilitar a execucao por usuarios comuns (no caso o usuario pertencente ao grupo uucp), foi a utilizacao da funcao setuid na linha 35. Essa funcao altera a identidade do processo atual (process ID) para o valor passado como argumento. No caso o valor 0 informa ao Linux que o processo pertence ao root. Nesse caso um usuario root tera que compilar o programa, e logo a seguir alterar as permissoes do arquivo para 4755 (chmod 4755 nome_do_arquivo_executavel). Isso possibilitara que um usuario comum execute o programa com funcoes para acesso direto ao hardware.

·         A funcao ioperm na linha 37 e utilizada para fornecer acesso a portas para usuarios que nao possui privilegios de root (ver man ioperm para maiores informacoes).


Porta Serial RS-232C

Acesso via descritores de arquivos – open/read/write/close

linguagem C++ – Linux

·         A listagem a seguir apresenta a implementacao para as funcoes membro da classe RobotLinux, utilizada para controlar o braco robo que e um dos exemplos utilizados no curso. O computador pessoal se comunica com o circuito de controle do braco robo via porta serial RS-232C.

/************************************************************

 * File RobotLinux.cpp - Implementation for the RobotLinux class

 *

 * This class has the implementation for virtual

 * methods from Robot.h (and Robot.cpp)

 *

 * Project: Fischer Arm

 *

 * Author: Eduardo Augusto Bezerra

 * Date: 04/04/2003

 *

 * Last change: Eduardo Augusto Bezerra

 * Date: 26/04/2003

 *

 * Methods for controlling the robot in C++ under linux

 * This code has been tested on linux Red Hat 8.0

 *

 *************************************************************/

#include "RobotLinux.h"

/************************************************************

 * Constructor

 *************************************************************/

RobotLinux::RobotLinux(void){

   motorWord = 0;

   fd = -1;

}

/************************************************************

 * Destructor

 *************************************************************/

RobotLinux::~RobotLinux(void){

   close(fd);

}

/************************************************************

 * void RobotLinux::openSerial(int ser)

 *

 * open the serial connection;

 * sets the serial parameters to 9600 Baud, no parity, 8, 1;

 * and turns all motors off

 *

 *************************************************************/

void RobotLinux::openSerial(int ser){

   // serial names

   const char *com1="/dev/ttyS0", *com2="/dev/ttyS1",

              *com3="/dev/ttyS2", *com4="/dev/ttyS3";

   struct termios options;

   switch(ser) {

        case 1: strcpy(serial,com1);

                break;

        case 2: strcpy(serial,com2);

                break;

        case 3: strcpy(serial,com3);

                break;

        case 4: strcpy(serial,com4);

                break;

        default: cout << "Error! valid ports are 1, 2, 3 and 4. "

                      << "Will try to open port 1." << endl;

      }

   fd = open(serial, O_RDWR|O_NOCTTY|O_NDELAY);

   if (fd == -1)      // ERROR!!

      cout << "Error opening " << serial << endl;

   else

      fcntl(fd, F_SETFL, 0);

   cout << "Serial port in use: " << serial << endl << endl;

   // Program serial port to 9600, 8, 1, no parity

   //

   // Get the current options for the port

   tcgetattr(fd, &options);

   // Set the baud rate to 9600

   cfsetispeed(&options, B9600);

   cfsetospeed(&options, B9600);

   // Enable the receiver and set local mode

   options.c_cflag |= (CLOCAL | CREAD);

   // Setting parity checking (no parity) 8N1

   options.c_cflag &= ~PARENB;        /* no parity */

   options.c_cflag &= ~CSTOPB;        /* 1 stop bit */

   options.c_cflag &= ~CSIZE;         /* Mask the character size bits */

   options.c_cflag |= CS8;            /* Select 8 data bits */

  

   // Setting raw input

   options.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);

   // Setting raw output

   options.c_oflag &= ~OPOST;

   // Set the new options for the port

   tcsetattr(fd, TCSANOW, &options);

   motorsOff();                // motors off

}

/************************************************************

 * char sendCommand(char motorWord)

 *

 * gets a full word representing all inputs (1..8)

 *************************************************************/

char RobotLinux::sendCommand(char motorWord){

   char commandWord[2];

   commandWord[0] = '\xc1';         // 1st byte: 193 in hexadecimal

   commandWord[1] = motorWord;      // 2nd byte: motorWord

   int i;

   if (fd == -1)

      cout << "Erro: the serial port is closed. Please, "

           << "use the openSerial() method to open it. " << endl;

   else{

      int n = write(fd, commandWord, 2);  // send 2 bytes command & motors

      if (n < 0)

         cout << "Error! write() command and motor bytes failed." << endl;

      else {

         fcntl(fd, F_SETFL, FNDELAY);

         i = read(fd, commandWord, 1); // read 1 Byte interface status

         fcntl(fd, F_SETFL, 0);

         commandWord[1] = 0;      /* set end of string, so we can printf */  

      }

   }

   return commandWord[0];

}


·         De forma semelhante ao exemplo de acesso direto ao hardware, a solucao utilizada no caso da aplicacao para controle do braco robo foi a criacao de um usuario especial pertencente ao grupo uucp, e a execucao dos programas para acesso a serial (ao braco robo) realizada por esse usuario.

·         No programa anterior, nas linhas 46 e 47 estao listados os nomes dos arquivos que identificam as portas seriais com1 a com 4 no Linux. Esses nomes substituem os antigos /dev/cua0 a /dev/cua3.

·         Uma das portas (1, 2, 3 ou 4) e' aberta na linha 64, de acordo com a selecao do usuario. O flag O_RDWR faz com que a porta seja aberta para entrada e saida. O flag O_NOCTTY garante que o programa nao sera o “terminal em controle” da porta. Se esse flag nao for utilizado, outras entradas de dados (ex. ^C enviado pelo teclado) pode afetar o programa. O flag O_NDELAY avisa ao Linux que o programa ira ignorar o sinal DCD da RS-232C. Se esse flag nao for utilizado, o processo ficara desativado ate que o outro lado da comunicacao ative o pino DCD (coloque 0 nesse pino).

·         A funcao fcntl na linha 68 e utilizada para configurar a porta serial para possiveis operacoes de leitura. O flag F_SETFL e utilizado em conjunto com o terceiro argumento para ativar ou desativar o modo de leitura da porta. Ao se utilizar 0 como terceiro argumento na funcao fcntl, uma operacao de leitura na porta serial (read) ira bloquear a execucao do programa ate que um caracter seja recebido, um intervalo de tempo expire, ou um erro ocorra. Para realizar leituras nao bloqueantes na serial, utlizar o flag FNDELAY, no lugar do 0.

·         A funcao tcgetattr na linha 75 e' utilizada para ler os parametros associados ao descritor de arquivo fd (no caso a porta serial aberta), e armazena-los na variavel options. Esses dados correspondem ao status atual da porta serial.

·        Logo a seguir, na linha 78, a funcao cfsetispeed e' utilizada para programar a porta serial para velocidade de recepcao de 9600 bps. Na linha seguinte a funcao cfsetospeed e utilizada para programar a porta serial para velocidade de transmissao de 9600 bps.

·        Nas linhas seguintes os demais parametros para a comunicacao serial sao armazenados no registro options. A porta serial do braco robo esta configurada para comunicacao a 9600 bps, com 8 bits de dados, 1 stop bit e sem paridade.

·        A funcao tcsetattr na linha 97 realiza a escrita do registro options, programando assim a UART do computador pessoal de acordo com os dados do protocolo estabelecido pelo circuito do braco robo.

·        O envio de comandos para o braco robo e realizado na linha 118 por intermedio da funcao write que envia o comando a ser realizado, utilizando para isso o descritor do arquivo aberto na linha 64. A funcao write funcao o numero de bytes enviados, ou –1 caso tenha ocorrido um erro.

·        Notar o uso do flag FNDELAY na funcao fcntl (linha 122) para configurar a operacao de leitura nao-bloqueante do status da serial. Esse flag faz com que a funcao read retorne 0, caso nao existam dados disponiveis para serem lidos na porta serial.

·        A seguir sao apresentados programas para acesso a porta paralela. Caso os programas nao consigam escrever/ler o dado desejado na porta, verificar as possiveis causas:

o       Escrita no endereco errado (378H equivale a 888 em decimal).

o       O cabo/conectores podem estar ligados de forma incorreta. Verificar!

o       O dispositivo conectado a porta esta causando o problema. Desconectar o dispositivo, e medir os bits da porta para ver se eles estao sendo alterados corretamente pelo programa no computador pessoal.

o       Os bits 0, 1 e 3 no registrador de controle das portas sao invertidos entre o registrador e o conector. Ao se escrever no registrador de controle, esses bits apareceram nos pinos do conector com valores invertidos em relacao aos escritos.

o       A porta suporta modo PS/2 e foi escrito 1 no bit 5 do registrador de controle. Isso desabilita a porta para saida de dados. Escrever 0 no bit 5 do registrador de controle para habilitar a saida.

o       Um driver de baixo-nivel do Windows esta proibindo o acesso a porta.

·        Caso o valor lido da porta nao e o esperado verificar, alem dos itens acima:

o       Os bits 0, 1 e 3 no registrador de controle das portas, e o bit 7 do registrador de status sao invertidos entre o conector e os respectivos registradores. Ao se ler desses registradores, os bits mencionados possuirao valores invertidos em relacao aos existentes nos pinos do conector.

o       A porta nao aceita o modo PS/2 e nao e possivel desabilitar o modo de saida (unidirecional). O valor lido sera sempre o ultimo valor escrito.

o       A porta suporta o modo PS/2, mas o bit 5 do registrador de controle nao foi setado para 1 de forma a desabilitar o modo de saida da porta. Escrever 1 nesse bit de forma a habilitar a leitura nos pinos de dados da porta.