2068 BBS Program

Date: 1986
Type: Program
Platform(s): TS 2068
Tags: BBS

A complete bulletin board system (BBS) for the TS2068. Supports up to 90 stored messages with forward and reverse browsing, message entry with automatic word wrap, direct message retrieval by number, and a real-time sysop chat mode. Uses machine code for serial I/O and stores the message base to tape between sessions.

What it does

This is a fully functional BBS (Bulletin Board System) for the TS2068, designed to run over a serial/modem connection. It POKEs 56 bytes of machine code into high memory (address 65480) at startup to handle low-level RS-232 serial I/O via ports 115 and 119. It stores up to 90 messages in a string array (m$(90,300)), each up to 300 characters, and can save/load the message base to tape.

Once a caller connects, the BBS handles modem handshaking, prompts for a username, then presents a menu with six options: forward or reverse reading through messages, leaving a message (with word wrap), reading a message by number, and a sysop chat mode that pages the operator with an ascending beep sequence. The time-on-system is calculated from the TS2068’s system clock registers and displayed in the menu. Disconnection is handled gracefully with a hangup sequence on port 119.


Program Analysis

Program Structure

The program is organized into clearly separated functional blocks:

  • Lines 10–25: Initialization, machine code installation, array setup, and optional data load.
  • Lines 1000–1090: Modem handshake, carrier detect, main menu dispatch loop.
  • Lines 2100–2218: Reverse and forward message reading routines.
  • Lines 2300–2350: Leave message, with word-wrap logic.
  • Lines 2400–2418: Read message by number.
  • Lines 2500–2596: Real-time sysop chat mode.
  • Lines 8000–8024: String output subroutines (menu text, prompts).
  • Lines 9000–9950: Utility routines: save program, serial transmit loop, disconnect, input handler, message reader, time formatter, and data save.

Machine Code Installation

Lines 14–16 install 56 bytes of machine code at address 65480. The code is stored as a string of packed three-digit decimal values in l$, and a FOR loop POKEs each byte using VAL l$(3*x+1 TO 3*x+3). The entry points are stored in named variables for clarity:

VariableAddressPurpose
o65523Serial transmit USR entry
i65489Serial receive USR entry
l9100BASIC line number for string-send loop
aa1000Main loop restart target
q9200Error/disconnect handler target

Serial I/O uses ports 115 (data) and 119 (control/status), consistent with the TS2068’s built-in RS-232 interface. The variable l is used as a numeric GO SUB target (line 9100), allowing the string-send loop to be called via GO SUB l — a compact idiom that avoids a literal line number in each of the eight output subroutines.

Modem Handshake and Carrier Detect

Lines 1000–1020 implement the modem answer sequence. Port 119 is written with control bytes to initialize the serial port, and the program then polls IN 119 waiting for a carrier signal (line 1004 loops while the status byte equals 5). Lines 1010–1020 send modem command bytes and perform a short handshake delay with PAUSE 300, followed by 30 iterations of the machine code transmit routine to flush or synchronize the line. The receive loop at lines 1018–1020 checks the high bit of the status byte to detect data readiness.

Input Handler (Line 9400)

The main character input routine at line 9400 accumulates characters into l$ one at a time. It uses ON ERR GO TO q to trap serial errors (e.g., loss of carrier) and redirect to the disconnect/goodbye handler. Characters below 32 or above 122 are discarded, and lowercase letters are converted to uppercase by subtracting 32 from their code. The routine returns after a single character, requiring the caller to loop (e.g., lines 9406–9412). The received byte is stored in in via PEEK 65479 after the machine code routine deposits it there.

Message Storage

Messages are stored in m$(90,300), a 90-row by 300-character string array. Each message record is structured as a header block prepended at write time (line 2312): "TO: " + recipient + CHR$ 13 + "FROM: " + caller_name + CHR$ 13. A CHR$ 7 (BEL) sentinel is written at the end of the message body to mark its actual length. The read routine at lines 9500–9510 scans backwards from position 300 to find this sentinel, then passes only the valid prefix to the output routine.

Word-Wrap Logic

Lines 2330–2346 implement a simple word-wrap algorithm for messages longer than 32 characters. Starting from position 33, the code scans left to find a space character, replaces it with CHR$ 13 (carriage return), then copies that segment into the message array and recurses on the remainder. There is a notable bug on line 2340: LET y2=ys+c1 references the variable ys, which is never defined anywhere in the program; this should almost certainly be y2=y2+c1, meaning word-wrapped messages will likely store with a corrupted offset.

Chat Mode

Lines 2500–2596 implement a duplex chat mode. The sysop is paged with a beep sequence (line 2506 ramps pitch over 128 iterations). If the sysop presses a key, full-duplex chat begins. The machine code at address 65480 is polled via LET a=USR 65480 (line 2562), which returns a bitmask: 1 = transmit ready, 2 = receive data present, 3 = both. Incoming characters are printed with a “>” line-break on CR; outgoing characters are sent with OUT 115, CODE l$ with a short FOR delay loop (line 2592). The escape condition checks for CODE INKEY$=195, which is the BASIC keyword token for a specific key combination rather than a standard ASCII code.

Time-On Display

Subroutine 9900 reads the TS2068 frame counter from system variables at addresses 23672, 23673, and 23674, combining them as t = PEEK 23672 + 2^8*PEEK 23673 + 2^16*PEEK 23674. The counter is reset to zero at line 1012 when a caller connects, so elapsed time in seconds (at 50 frames/second) is computed and formatted as m:ss for display in the main menu. This is a standard technique for elapsed-time tracking without a real-time clock.

Notable Techniques

  • VAL "number" is used in several GO TO and GO SUB targets (e.g., GO TO VAL "1002", GO SUB VAL "8002") as a memory-saving optimization — the tokenized line number takes more space than a short string.
  • The variable l holds the numeric value 9100 and is used directly as a GO SUB target: GO SUB l. This is legal in Sinclair BASIC and avoids repeating the literal line number across many output subroutines.
  • POKE 23692,255 resets the scroll count to prevent the “scroll?” prompt from interrupting serial output.
  • Line 9201 uses the TS2068-specific ON ERR RESET keyword to clear the error state before the disconnect sequence.
  • Data persistence uses SAVE "" DATA m$() and SAVE "" DATA y() to tape, with a corresponding LOAD path at lines 20–23.

Bugs and Anomalies

  • Line 2340: LET y2=ys+c1 — variable ys is undefined; should be y2=y2+c1. This will corrupt message write offsets whenever word-wrap is triggered.
  • Line 2506: The label says p$=l$(LEN l$) is checked at line 1028 for CHR$ 13 termination, but in section 2300 the variable in is tested (lines 2310, 2320) rather than being set by the input subroutine — in is only populated inside the machine code receive path, so these checks may not behave as intended from a BASIC-only perspective.
  • Line 9506: LET p$=( TO a) is missing the array reference — should be LET p$=l$( TO a) or m$(b)( TO a). As written, this is a syntax error or will reference an unexpected variable.
  • Line 2576: The condition IF in>32 or in<123 is logically always true for any value of in and should likely be in>=32 AND in<123 to filter printable ASCII.

Content

Appears On

Related Products

Related Articles

This program creates a limited capacity message BBS using a 2068 & 2050 modem. Thanks to Randy and Lucy Gordon...

Related Content

Image Gallery

2068 BBS Program

Source Code

 10 PAPER 0: INK 7: BORDER 1: CLS
 12 PRINT "stop tape"'''"then press (ENTER)": PAUSE 0: CLEAR 65478
 14 LET l$="175219119230003079006000201219119230128200175219119230002040244219116050199255079219119230128200175219119230001040244121211115201219119230128200175219119230001040240201"
 16 FOR x=0 TO 55: POKE 65480+x,VAL l$(3*x+1 to 3*x+3): NEXT x
 18 LET o=VAL"65523": LET i=val "65489": LET l=val "9100": LET aa=val "1000": LET q=val "9200":
 20 DIM m$(90,300): DIM y(1): PRINT "load?": PAUSE 0: IF INKEY$<>"y" THEN GO TO 24
 22 LOAD "" DATA m$(): LOAD "" DATA y(): LET y1=y(1)
 23 GO TO 25
 24 LET y=10
 1000 OUT 119,34: OUT 119,0
 1001 POKE 23692,255: CLS: PRINT """2068 BBS"""
 1002 LET x=IN 119
 1004 IF x=5 THEN GO TO VAL "1002"
 1010 OUT 119,2: OUT 119,34: PAUSE 300: OUT 119,64: OUT 119,123: OUT 119,55
 1012 POKE 23674,0: POKE 23673,0: POKE 23672,0
 1014 PAUSE 120
 1016 FOR x=1 TO 30: RANDOMIZE USR o: OUT 115,0: NEXT X
 1018 LET a=IN 119: IF a<128 THEN GO TO 1020
 1020 RANDOMIZE USR o: OUT 115,28: RANDOMIZE USR o: OUT 115,31: RANDOMIZE USR o: OUT 115,28
 1021 LET x=IN 115
 1022 GO SUB VAL "8002"
 1024 GO SUB 9400
 1026 GO SUB 9405
 1028 IF CODE l$(LEN l$)<>13 THEN GO TO 1026
 1030 CLS: PRINT l$; " calling": LET u$=l$
 1032 GO SUB 8006
 1034 GO SUB 9400
 1038 IF l$="R" THEN GO TO 2100
 1040 IF l$="F" THEN GO TO 2200
 1042 IF l$="L" THEN GO TO 2300
 1044 IF l$="B" THEN GO TO 9200
 1046 IF l$="#" THEN GO TO 2400
 1048 IF l$="C" THEN GO TO 2500
 1090 GO TO 1032
 2100 REM Reverse Read
 2104 FOR b=y1 TO 1 STEP -1
 2105 GO SUB 8018
 2106 GO SUB 9500
 2114 IF l$="N" THEN NEXT b
 2116 IF l$="M" THEN GO TO 1032
 2118 GO TO 1032
 2200 REM Forward Read
 2204 FOR b=1 to y1
 2205 GO SUB 8018
 2206 GO SUB 9500
 2214 IF l$="N" THEN NEXT b
 2216 IF l$="M" THEN GO TO 1032
 2218 GO TO 1032
 2300 REM Leave Message
 2302 LET y1=y1+1: IF y1=90 THEN LET y1=1
 2304 GO SUB 8010
 2306 GO SUB 9400
 2308 GO SUB 9405
 2310 IF in<>13 THEN GO TO 2308
 2312 LET l$="TO: "+l$+CHR$ 13+"FROM: "+u$+CHR$ 13: LET y2=LEN l$: LET m$(y1, TO y2)=l$
 2314 GO SUB 8012
 2316 GO SUB 9400
 2318 GO SUB 9402
 2320 IF in<>13 THEN GO TO 2318
 2322 REM Wordwrap
 2330 IF LEN l$>299-y2+1 THEN LET l$=l$( TO 299-y2)
 2332 LET c1=33
 2334 IF l$(c1)<>" " THEN LET c1=c1-1
 2336 IF l$(c1)=" " THEN LET l$(c1)=CHR$ 13
 2338 IF CODE l$(c1)<>13 THEN GO TO 2334
 2340 LET m$(y1,y2+1 TO y2+c1)=l$( TO C1): LET y2=ys+c1
 2342 LET l$=l$(c1+1 TO )
 2344 IF LEN l$>32 THEN GO TO 2332
 2345 LET l$=l$+CHR$ 7
 2346 LET m$(y1,y2+1 TO 300)=l$
 2350 GO TO 1032
 2400 REM Read by Number
 2401 GO SUB VAL "8016"
 2402 GO SUB 9400
 2404 GO SUB 9405
 2406 IF in<>13 THEN GO TO 2404
 2408 LET b=VAL l$( TO (LEN l$-1))
 2410 GO SUB 8018
 2412 GO SUB 9500
 2414 IF l$="N" THEN GO TO VAL "2400"
 2416 IF l$="M" THEN GO TO VAL "1032"
 2418 GO TO 1032
 2500 REM Chat Mode
 2502 GO SUB 8020
 2504 FOR x=1 TO 128
 2506 BEEP .1,10+INT (x/10)
 2508 RANDOMIZE USR o
 2510 OUT 115,46
 2512 IF INKEY$<>"" THEN GO TO 2550
 2514 NEXT x
 2516 GO SUB 8022: GO TO 1032
 2550 CLS: PRINT "chat w/";u$
 2552 GO SUB 8024
 2554 PRINT "NOT to escape"
 2556 IF CODE INKEY$=195 THEN GO TO 1032
 2558 POKE 23692,255
 2560 LET r=0: LET xmit=0
 2562 LET a=USR 65480
 2564 LET xmit=1 and (a=1 or a=3)
 2565 LET r=1 and (a=2 or a=3)
 2569 IF r then GO TO 2576
 2572 GO TO 2588
 2576 RANDOMIZE USR i: LET in=PEEK 65479: IF in>32 or in<123 THEN PRINT CHR$ in;: IF in=13 THEN PRINT ">": GO TO 2556
 2588 IF xmit and INKEY$<>"" THEN GO SUB 2592
 2590 GO TO 2556
 2592 IF xmit THEN LET l$=INKEY$: PRINT l$;: OUT 115,CODE l$: FOR x=1 TO 5: NEXT x: IF CODE l$=13 THEN PRINT ">";
 2596 RETURN
 8000 REM Strings Going Out
 8002 LET p$=CHR$ 12+"TIMEX BOARD"+CHR$ 13+"TURN YOUR CR SUPPRESSOR OFF"+CHR$ 13+CHR$ 13+"YOUR NAME?"+CHR$ 13+">"+CHR$ 7: GO SUB l: RETURN
 8006 GO SUB VAL "9900": LET p$=CHR$ VAL "12"+"(B)YE BYE"+CHR$ 13+"(L)EAVE MSG."+CHR$ 13+"(F)WD. READ"+CHR$ 13+"(R)EV. READ"+CHR$ 13+"(#) READ BY #"+CHR$ 13+"(C)HAT"+CHR$ 13+"TIME ON "+l$+CHR$ 13: GO SUB l: RETURN
 8008 LET p$=CHR$ 12+"     BYE-BYE": GO SUB l: RETURN
 8010 LET p$=CHR$ VAL "12"+"WHO GETS MESSAGE?"+CHR$ 13: GO SUB l: RETURN
 8012 LET p$=CHR$ 12+"250 CHARACTERS MAX"+CHR$ 13+"(ENTER) SAVES MESSAGE"+CHR$ 13: GO SUB l: RETURN
 8014 LET p$=CHR$ 13+"(N)EXT MESSAGE OR (M)ENU"+CHR$ 13: GO SUB l: RETURN
 8016 LET p$=CHR$ 13+"INPUT MESSAGE # "CHR$ 13+"[1-90] ->": GO SUB l: RETURN
 8018 LET p$=CHR$ 13+"MESSAGE # "+STR$ B+CHR$ 13: GO SUB l: RETURN
 8020 LET p$=CHR$ 12+"PAGING SYSOP.....": GO SUB l: RETURN
 8022 LET p$="HE'S NOT HERE!": GO SUB l: RETURN
 8024 LET p$="OK, LET'S TALK...": GO SUB l: RETURN
 9000 CLEAR: SAVE "BBS" LINE 10: STOP
 9100 FOR x=1 TO LEN P$: RANDOMIZE USR o: OUT 115,CODE p$(x): NEXT x: POKE 23692,255: PRINT p$: RETURN
 9201 GO SUB 8008: ON ERR RESET: RANDOMIZE USR o: OUT 115,28: RANDOMIZE USR o: OUT 115,31: GO SUB l: OUT 119,64: OUT 119,0: OUT 119,0
 9202 BEEP .2,10: BEEP .3,-20
 9204 GO TO aa
 9400 LET l$=""
 9406 ON ERR GO TO q: RANDOMIZE USR i: LET in=PEEK 65479
 9407 IF in=13 THEN GO TO 9409
 9408 IF IN<32 or in>122 THEN GO TO 9406
 9409 POKE 23692,255: PRINT CHR$ in;
 9410 LET ca=in: IF in>96 AND in<123 THEN LET ca=in-32: LET in=ca
 9412 LET l$=l$+CHR$ in
 9414 RETURN
 9500 LET l$=m$(b)
 9504 FOR a=300 to 1 step -1: IF CODE l$(a)<>7 THEN NEXT a
 9506 LET p$=( TO a)
 9510 GO SUB l: GO SUB 8014: GO SUB 9400: RETURN
 9900 LET l$="": LET t=PEEK 23672+2^8*PEEK 23673+2^16*PEEK 23674: LET m=INT (t/3600): LET s=INT ((t/3600-m)*60): LET l$=STR$ m+":"+("0" and s<10)+STR$ s: RETURN
 9950 SAVE "Msgs" DATA m$(): LET y(1)=y1: BEEP .1,50: SAVE "count" DATA y(): STOP

Note: Type-in program listings on this website use ZMAKEBAS notation for graphics characters.

Scroll to Top