The QBNews Page 1 Volume 1, Number 4 September 3, 1990 ---------------------------------------------------------------------- B e g i n n e r s C o r n e r ---------------------------------------------------------------------- The BASICS of QB'S Serial Communications by Ranjit Aiyagari QuickBASIC has always been recognized for having a unique feature which most other languages (i.e. Pascal, C, Fortran) lacked: full serial communications support built in to the language. In this article, I'll explain BASIC's communications functions and, more importantly, their uses. If you always wanted to write a BBS or a terminal program in QuickBASIC but never had the faintest idea how, keep reading. In order to use the modem (or any other serial device) from BASIC, you must first OPEN it, treating it as you would treat a file. The flexible syntax of the OPEN COM statement is explained well in Microsoft's manuals, but I'll give a most common syntax of it: OPEN "COM1:2400,N,8,1,OP0,DS0" FOR RANDOM AS #FileNumber% LEN=1024 The above statement, as you can probably gather from its simple syntax, will open communications port number 1 at a most common setting of 2400 bps, 8 data bits, 1 stop bit, and no parity. The OP0 and DS0 parameters signify, respectively, for BASIC not to wait at all before communications lines become active, and for BASIC to ignore (for simplicity's sake) the DSR (Data Set Ready) line. Lastly, the LEN parameter specifies the length of the input buffer. Now that you've opened it, output to the modem is easy. To send an "AT".. command, just do a PRINT #1, ModemCommand$. Getting input from the modem, however, can be done in multiple ways: Character by character: This is a method I prefer, getting characters one at a time and dealing with them. To get one character from the modem, use the INPUT$ function in the following manner: Char$ = INPUT$(1, #FileNumber%) [where FileNumber% is of course the file number under which you opened the communications port. Everything waiting in the modem buffer: With this method, you can store whatever is waiting in the input buffer in a string, and then deal with the string as you wish using BASIC's string manipulation functions. This is done by using the LOC function. Normally returning the position of the last byte read for files, LOC changes meaning when used with communications devices. Instead, it returns the number of characters waiting in the input buffer. To input the modem input buffer to a string, do the following: ModemInput$ = INPUT$(LOC(FileNumber%), #FileNumber%). Now you can use INSTR to figure out whether certain characters were returned, or MID$ to extract part of the input. A commonly asked question is "How do I recover from a carrier drop?" Since the CD (carrier detect) option in the OPEN statement The QBNews Page 2 Volume 1, Number 4 September 3, 1990 defaults to zero, your program seems to have no way of knowing whether the other side is connected or not. The method I recommend for this is using an ON TIMER routine, such as this: ON TIMER(15) GOSUB CheckCarrier ' Remember to compile with /v when using ON TIMER Now, we use a low-level method of checking whether the DCD (data carrier detect) line is up or down. You must know the communications port base address first, so to figure it out, do the following (assuming the communications port is in a variable ComPort%): DEF SEG = &H40 BaseAddress% = PEEK((ComPort-1)*2) + PEEK((ComPort-1)*2+1)*256 ' The base address is usually &H3F8 for COM1 and &H2F8 for COM2 Use the INP function: CheckCarrier: IF (INP(BaseAddress+6) AND 128) = 0 Then Print "Carrier lost" Connected% = 0 ELSE Connected% = -1 END IF RETURN Another common question: "How do I wait for a ring and then answer?" This is far simpler, and uses the EOF function, which returns false if characters are waiting in the input buffer (You should make sure to have the statement CONST False = 0, True = NOT False at the top of your program). To wait for the ring: PRINT #FileNumber%, "ATE0" SLEEP 2 ' Wait for the modem to respond Dummy$ = INPUT$(LOC(FileNumber%), #FileNumber%) ' Discard "OK" ModemInput$ = "" DO IF NOT EOF(FileNumber%) THEN ' Characters are waiting, so get them ModemInput$ = ModemInput$ + Input$(1, #FileNumber%) END IF LOOP UNTIL INKEY$ <> "" OR INSTR(ModemInput$, "RING") Dummy$ = INPUT$(LOC(FileNumber%), #FileNumber%) ' Clear the buffer ' Now, to answer: PRINT #FileNumber%, "ATA" Lastly, probably the most frequently asked question about QB communications: "How do I change the BPS rate while connected?" This requires some low-level UART (Universal Asynchronous Receiver- Transmitter) accessing [the UART is the chip in control of the serial port]. The best use of this is to read the modem's "CONNECT..." message and change the BPS rate accordingly. It requires a series of The QBNews Page 3 Volume 1, Number 4 September 3, 1990 four OUT statements: FUNCTION JustNumbers%(X$) STATIC ' Takes a string and returns the numeric value of the numeric ' characters inside it. Temp$ = "" FOR CheckString% = 1 TO LEN(X$) Char$ = MID$(X$, CheckString%, 1) IF INSTR("1234567890", Char$) THEN Temp$ = Temp$ + Char$ NEXT CheckString% IF Temp$ = "" THEN JustNumbers% = -1 ELSE JustNumbers% = VAL(Temp$) Temp$ = "": X$ = "" ' Empty the strings END FUNCTION ' Assume we've just sent the "ATA" ModemInput$ = "" DO IF NOT EOF(FileNumber%) THEN ModemInput$ = ModemInput$ + INPUT$(1, #FileNumber%) END IF Connect% = INSTR(ModemInput$, "CONNECT") LOOP UNTIL Connect% AND_ INSTR(Connect%, ModemInput$, CHR$(13)+CHR$(10)) NewBpsRate% = JustNumbers%(ModemInput$) IF NewBpsRate% = -1 THEN NewBpsRate% = 300 ' Just a "CONNECT" ' Now, change the BPS rate. Since we already initialized at 2400 ' don't bother changing it if the other side is also at 2400. ' Also, assume the base address is stored in BaseAddress% ' (discussed above) IF NewBpsRate% <> 2400 THEN OUT BaseAddress% + 3, (INP(BaseAddress% + 3) OR &H80)' Enable DLAB OUT BaseAddress%, (115200 \ NewBpsRate%) MOD &H100 ' Send LSB OUT BaseAddress% + 1, (115200 \ NewBpsRate%) \ &H100 ' Send MSB OUT BaseAddress% + 3, (INP(BaseAddress% + 3) AND &H7F)'Disable DLAB END IF Now, for an explanation. The first and the fourth OUT statements, as you can see, modify the DLAB, the divisor latch access bit. The reason for this is that when the DLAB is enabled, the first two registers (BaseAddress% and BaseAddress+1), instead of being the receive buffer/transmit holding and interrupt enable registers, change their meaning to the low and high byte of the BPS divisor (also called the LSB and MSB). The BPS divisor is calculated by dividing 115200, the maximum serial port speed, by the BPS rate desired. Thus, these four statements will modify the BPS rate to any value between 1 and 115200. This can be useful when writing both terminal programs and BBS's, when you want your software to "fall The QBNews Page 4 Volume 1, Number 4 September 3, 1990 back", that is, be able to transmit at any speed lower than the highest. If you need help on any topic dealing with serial communications in QuickBASIC/BASIC PDS, please feel free to send me a message on the echo. I've written BBS's and terminal programs in QuickBASIC, along with small communications functions in Assembler, and probably would be able to help you.