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.