Título: Comunicação
Serial
Linguagem: C/C++
S.O.:
DOS
Autor(es): Wenderson Teixeira
Uma das perguntas mais frequentes que fazem é sobre comunicação serial, coisas do tipo, "Eu tenho um aparelho e queria enviar comandos e receber dados através da serial, como faço isso?" ou ainda "Eu queria fazer um emulador de terminal para poder comunicar um micro com outro", pois bem, resolvi colocar aqui uma resposta que deverá esclarecer essas e outras dúvidas.
O Borland C++ já possui funções de comunicação serial, chamadas bioscom
e _bios_serialcom
, pois bem, minha primeira recomendação é, evitem utilizá-las, pois elas fazem a comunicação utilizando as interrupções do DOS e por Pooling, o que torna a comunicação lenta, somente até 9600, limitada e nem sempre funciona corretamente, principalmente com Windows 95/98/NT. Se você quiser um exemplo, procure no Help do Borland C++.
Vamos fazer agora um exemplo bem melhor, pois utiliza interrupção, tem pouquíssima falha, e atinge velocidades até 115200 bps, para isso podemos utilizar um série de rotinas que se encontram disponíveis para download no site da Inprise, antiga Borland, abaixo você vai encontrar uma versão com algumas correções e alterações que eu fiz nessas rotinas, para melhor atender às nossas necessidades, o código fonte completo pode ser pego na seção Download ou clicando aqui.
/*------------------------------------------------------------------* SERIAL.H Some definitions used by SERIAL.CPP *------------------------------------------------------------------*/ #define COM1 1 #define COM2 2 #define COM1BASE 0x3F8 /* Base port address for COM1 */ #define COM2BASE 0x2F8 /* Base port address for COM2 */ /* The 8250 UART has 10 registers accessible through 7 port addresses. Here are their addresses relative to COM1BASE and COM2BASE. Note that the baud rate registers, (DLL) and (DLH) are active only when the Divisor-Latch Access-Bit (DLAB) is on. The (DLAB) is bit 7 of the (LCR). o TXR Output data to the serial port. o RXR Input data from the serial port. o LCR Initialize the serial port. o IER Controls interrupt generation. o IIR Identifies interrupts. o MCR Send control signals to the modem. o LSR Monitor the status of the serial port. o MSR Receive status of the modem. o DLL Low byte of baud rate divisor. o DHH High byte of baud rate divisor. */ #define TXR 0 /* Transmit Register (WRITE) */ #define RXR 0 /* Receive Register (READ) */ #define IER 1 /* Interrupt Enable */ #define IIR 2 /* Interrupt ID */ #define LCR 3 /* Line Control */ #define MCR 4 /* Modem Control */ #define LSR 5 /* Line Status */ #define MSR 6 /* Modem Status */ #define DLL 0 /* Divisor Latch Low */ #define DLH 1 /* Divisor Latch High */ /*------------------------------------------------------------------* Bit values held in the Line Control Register (LCR). bit meaning --- ------- 0-1 00=5 bits, 01=6 bits, 10=7 bits, 11=8 bits. 2 Stop bits. 3 0=parity off, 1=parity on. 4 0=parity odd, 1=parity even. 5 Sticky parity. 6 Set break. 7 Toggle port addresses. *------------------------------------------------------------------*/ #define NO_PARITY 0x00 #define EVEN_PARITY 0x18 #define ODD_PARITY 0x08 /*------------------------------------------------------------------* Bit values held in the Line Status Register (LSR). bit meaning --- ------- 0 Data ready. 1 Overrun error - Data register overwritten. 2 Parity error - bad transmission. 3 Framing error - No stop bit was found. 4 Break detect - End to transmission requested. 5 Transmitter holding register is empty. 6 Transmitter shift register is empty. 7 Time out - off line. *------------------------------------------------------------------*/ #define RCVRDY 0x01 #define OVRERR 0x02 #define PRTYERR 0x04 #define FRMERR 0x08 #define BRKERR 0x10 #define XMTRDY 0x20 #define XMTRSR 0x40 #define TIMEOUT 0x80 /*------------------------------------------------------------------* Bit values held in the Modem Output Control Register (MCR). bit meaning --- ------- 0 Data Terminal Ready. Computer ready to go. 1 Request To Send. Computer wants to send data. 2 Auxillary output #1. 3 Auxillary output #2. (Note: This bit must be set to allow the communications card to send interrupts to the system.) 4 UART ouput looped back as input. 5-7 Not used. *------------------------------------------------------------------*/ #define DTR 0x01 #define RTS 0x02 #define MC_INT 0x08 /*------------------------------------------------------------------* Bit values held in the Modem Input Status Register (MSR). bit meaning --- ------- 0 Delta Clear To Send. 1 Delta Data Set Ready. 2 Delta Ring Indicator. 3 Delta Data Carrier Detect. 4 Clear To Send. 5 Data Set Ready. 6 Ring Indicator. 7 Data Carrier Detect. *------------------------------------------------------------------*/ #define CTS 0x10 #define DSR 0x20 /*------------------------------------------------------------------* Bit values held in the Interrupt Enable Register (IER). bit meaning --- ------- 0 Interrupt when data received. 1 Interrupt when transmitter holding reg. empty. 2 Interrupt when data reception error. 3 Interrupt when change in modem status register. 4-7 Not used. *------------------------------------------------------------------*/ #define RX_INT 0x01 /*------------------------------------------------------------------* Bit values held in the Interrupt Identification Register (IIR). bit meaning --- ------- 0 Interrupt pending. 1-2 Interrupt ID code. 00=Change in modem status register, 01=Transmitter holding register empty, 10=Data received, 11=reception error, or break encountered. 3-7 Not used. *------------------------------------------------------------------*/ #define RX_ID 0x04 #define RX_MASK 0x07 /* These are the port addresses of the 8259 Programmable Interrupt Controller (PIC). */ #define IMR 0x21 /* Interrupt Mask Register port */ #define ICR 0x20 /* Interrupt Control Port */ /* An end of interrupt needs to be sent to the Control Port of the 8259 when a hardware interrupt ends. */ #define EOI 0x20 /* End Of Interrupt */ /* The (IMR) tells the (PIC) to service an interrupt only if it is not masked (FALSE). */ #define IRQ3 0xF7 /* COM2 */ #define IRQ4 0xEF /* COM1 */ /*-----------------------------------------------------------------*/ #define VERSION 0x0101 #ifndef __cplusplus #define INTERRUPTPARAM #else #define INTERRUPTPARAM ... #endif #define NOERROR 0 /* No error */ #define BUFOVFL 1 /* Buffer overflowed */ #define ASCII 0x007F /* Mask ASCII characters */ #define SBUFSIZ 0x4000 /* Serial buffer size */ #define ESC 0x1B #define BACKSPACE 0x08 #define CR 0x0D #define LF 0x0A #define DEL 0x07 /*-----------------------------------------------------------------*/ #if (__BORLANDC__ <= 0x460) || !defined(__cplusplus) typedef enum { false, true } bool; #endif typedef struct { int port; long speed; int parity; int bits; int stopbits; } TSerialSettings; void interrupt com_int(INTERRUPTPARAM); void SerialInit(void); void SerialClose(void); bool SerialSetPort(int Port); bool SerialSetSpeed(long Speed); bool SerialSetOthers(int Parity, int Bits, int StopBit); bool SerialSetup(const TSerialSettings *Settings); bool SerialOut(char x); bool SerialString(char *string); int SerialGetChar(void); void setvects(void); void resvects(void); void i_enable(int pnum); void i_disable(void); void comm_on(void); void comm_off(void); int c_break(void); extern int SError; /*-----------------------------------------------------------------*/
/*-------------------------------------------------------------* SERIAL.CPP * Compile this program with Test Stack Overflow OFF. *-------------------------------------------------------------*/ #include <dos.h> #include <stdio.h> #include "serial.h" int SError = NOERROR; int portbase = 0; void interrupt(*oldvects[2])(INTERRUPTPARAM); static char ccbuf[SBUFSIZ]; unsigned int startbuf = 0; unsigned int endbuf = 0; /* Handle communications interrupts and put them in ccbuf */ void interrupt com_int(INTERRUPTPARAM) { disable(); if((inportb(portbase + IIR) & RX_MASK) == RX_ID) { if(((endbuf + 1) & SBUFSIZ - 1) == startbuf) SError = BUFOVFL; ccbuf[endbuf++] = inportb(portbase + RXR); endbuf &= SBUFSIZ - 1; } /* Signal end of hardware interrupt */ outportb(ICR, EOI); enable(); } /* Start communication */ void SerialInit(void) { endbuf = startbuf = 0; setvects(); comm_on(); } /* End communication */ void SerialClose(void) { comm_off(); resvects(); } /* Set the port number to use */ bool SerialSetPort(int Port) { int Offset, far *RS232_Addr; switch (Port) { /* Sort out the base address */ case COM1: Offset = 0x0000; break; case COM2: Offset = 0x0002; break; default: return false; } RS232_Addr = (int *)MK_FP(0x0040, Offset); /* Find out where the port is. */ if(*RS232_Addr == NULL) return false;/* If NULL, then port not used. */ portbase = *RS232_Addr; /* Otherwise, set portbase. */ return true; } /* This routine sets the speed; will accept funny baud rates. */ /* Setting the speed requires that the DLAB be set on. */ bool SerialSetSpeed(long Speed) { char c; int divisor; if(portbase == 0 || Speed == 0) /* Avoid divide by zero */ return false; else divisor = (int)(115200L / Speed); disable(); c = inportb(portbase + LCR); outportb(portbase + LCR, (c | 0x80)); /* Set DLAB */ outportb(portbase + DLL, (divisor & 0x00FF)); outportb(portbase + DLH, ((divisor >> 8) & 0x00FF)); outportb(portbase + LCR, c); /* Reset DLAB */ enable(); return true; } /* Set other communications parameters */ bool SerialSetOthers(int Parity, int Bits, int StopBit) { int setting; if(portbase == 0) return false; if(Bits < 5 || Bits > 8) return false; if(StopBit != 1 && StopBit != 2) return false; if(Parity != NO_PARITY && Parity != ODD_PARITY && Parity != EVEN_PARITY) return false; setting = Bits - 5; setting |= ((StopBit == 1) ? 0x00 : 0x04); setting |= Parity; disable(); outportb(portbase + LCR, setting); enable(); return true; } /* Set up the port */ bool SerialSetup(const TSerialSettings *Settings) { if(!SerialSetPort(Settings->port)) return false; if(!SerialSetSpeed(Settings->speed)) return false; if(!SerialSetOthers(Settings->parity, Settings->bits, Settings->stopbits)) return false; return true; } /* Output a character to the serial port */ bool SerialOut(char x) { long int timeout = 0x0000FFFFL; outportb(portbase + MCR, MC_INT | DTR | RTS); /* Este trecho e utilizado somente com modem's */ /* Wait for Clear To Send from modem */ /* while((inportb(portbase + MSR) & CTS) == 0) if(!(--timeout)) return false; */ timeout = 0x0000FFFFL; /* Wait for transmitter to clear */ while((inportb(portbase + LSR) & XMTRDY) == 0) if(!(--timeout)) return false; disable(); outportb(portbase + TXR, x); enable(); return true; } /* Output a string to the serial port */ bool SerialString(char *string) { while(*string) if(!SerialOut(*string++)) return false; return true; } /* This routine returns the current value in the buffer */ int SerialGetChar(void) { int res; if(endbuf == startbuf) return (-1); res = (int)ccbuf[startbuf++]; startbuf %= SBUFSIZ; return res; } /* Install our functions to handle communications */ void setvects(void) { oldvects[0] = getvect(0x0B); oldvects[1] = getvect(0x0C); setvect(0x0B, com_int); setvect(0x0C, com_int); } /* Uninstall our vectors before exiting the program */ void resvects(void) { setvect(0x0B, oldvects[0]); setvect(0x0C, oldvects[1]); } /* Turn on communications interrupts */ void i_enable(int pnum) { int c; disable(); c = inportb(portbase + MCR) | MC_INT; outportb(portbase + MCR, c); outportb(portbase + IER, RX_INT); c = inportb(IMR) & (pnum == COM1 ? IRQ4 : IRQ3); outportb(IMR, c); enable(); } /* Turn off communications interrupts */ void i_disable(void) { int c; disable(); c = inportb(IMR) | ~IRQ3 | ~IRQ4; outportb(IMR, c); outportb(portbase + IER, 0); c = inportb(portbase + MCR) & ~MC_INT; outportb(portbase + MCR, c); enable(); } /* Tell modem that we're ready to go */ void comm_on(void) { int c, pnum; pnum = (portbase == COM1BASE ? COM1 : COM2); i_enable(pnum); c = inportb(portbase + MCR) | DTR | RTS; outportb(portbase + MCR, c); } /* Go off-line */ void comm_off(void) { i_disable(); outportb(portbase + MCR, 0); } /* Control-Break interrupt handler */ int c_break(void) { i_disable(); fprintf(stderr, "\nStill online.\n"); return false; }
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <conio.h> #include <dos.h> #include <graphics.h> #include "serial.h" #define IsStringEqual(a,b) (strcmpi((a), (b)) == 0) void ProcessExit() { switch (SError) { case NOERROR: printf("\nbye.\n"); break; case BUFOVFL: printf("\nBuffer Overflow.\n"); break; default: printf("\nUnknown Error, SError = %d\n", SError); } SerialClose(); } bool Save(char *buffer, int count) { FILE *fp = fopen("log.txt", "at"); if(!fp) return false; fwrite(buffer, sizeof(char), count, fp); return true; } TSerialSettings ReadParams(int ArgC, char *ArgV[]) { TSerialSettings settings = { COM2, 115200L, NO_PARITY, 8, 1 }; char *strSettings, *pTok; if(ArgC < 2) return settings; if(IsStringEqual(ArgV[1], "/h")) { printf("Uso:\n serial Port:Speed,Bits,Parity,Stopbits\n" " Port - COM1, COM2*\n" " Speed - 1200, 2400, 9600, 19200, 38400, 57600, 115200*\n" " Bits - 5,6,7,8*\n" " Parity - n*, e, o\n" " Stopbits - 1*, 2\n\n" "Ex.: serial COM2:19200,8,n,1\n"); exit(0); } strSettings = strdup(ArgV[1]); if(!strSettings) return settings; pTok = strtok(strSettings, ":"); settings.port = pTok ? (IsStringEqual(pTok, "com1") ? COM1 : COM2 ) : COM2; pTok = strtok(0, ","); settings.speed = pTok ? atol(pTok) : 115200L; pTok = strtok(0, ","); settings.bits = pTok ? atoi(pTok) : 8; pTok = strtok(0, ","); settings.parity = pTok ? (IsStringEqual(pTok, "n") ? NO_PARITY : IsStringEqual(pTok, "e") ? EVEN_PARITY : IsStringEqual(pTok, "o") ? ODD_PARITY : NO_PARITY) : NO_PARITY; pTok = strtok(0, ","); settings.stopbits = pTok ? atoi(pTok) : 1; free(strSettings); return settings; } int main(int ArgC, char *ArgV[]) { TSerialSettings settings = ReadParams(ArgC, ArgV); char *buffer = 0; int byteCounter = 0; unsigned long totalCounter = 0; int c, done = false; buffer = (char *)malloc(sizeof(char) * 1024); if(!buffer) { printf("Erro de alocação de memória.\n\r"); return (99); } if(!SerialSetup(&settings)) { printf("Erro de inicialização da serial.\n\r"); return (99); } atexit(ProcessExit); SerialInit(); printf("//////////// Termial Simples \\\\\\\\\\\\\\\\\\\\\\\\\n"); printf("\nMODO - COM%d:%ld,%d,%s,%d\n", settings.port, settings.speed, settings.bits, (settings.parity == NO_PARITY ? "None" : (settings.parity == EVEN_PARITY ? "Even" : "Odd")), settings.stopbits); printf("\n ...Pressione [ESC] para sair... \n"); ctrlbrk(c_break); do { if(kbhit()) { c = getch(); switch(c ? c : getch()) { default: if(!SerialOut(c)) printf("\nFalha na transmissão.\n"); break; case ESC: done = true; if(byteCounter) Save(buffer, byteCounter); break; } } c = SerialGetChar(); if (c != -1) { int y; buffer[byteCounter++] = c == CR ? LF : c; switch(c) { //case LF: //case CR: //break; case DEL: clrscr(); break; default: printf("%c", c /*& ASCII*/); } if(byteCounter == 1024 || c == LF || c == CR) { Save(buffer, byteCounter); byteCounter = 0; } totalCounter++; } } while(!done && !SError); printf("\n\nBytes Recebidos: %lu", totalCounter); if(SError == NOERROR) return 0; else return 99; }
Novamente peço que verifique o modo de memória, certifique-se de que seja o modo Large, pois em modo Small, provavelmente não irá funcionar corretamente.
Mais um lembrete, para transmitir um caracter use:
bool SerialOut(char x);
Para transmitir uma string use:
bool SerialString(char *string);
Para receber um caracter use:
int SerialGetChar(void);
Sugestões para aprimoramentos:
Você pode usar SerialOut
para criar uma rotina que transmita um buffer, esta rotina pode se chamar, por exemplo, SerialOutBuffer
, e receber como parâmetros, o endereço do buffer e o tamanho do mesmo, assim como foi feito com SerialString
.
Com estas rotinas, pode-se implementar um protocolo de transmissão, como XMODEM ou KERMIT, e realizar transferência de arquivos, execução de comandos remotamente e coisas do tipo.