Monthly Archives: January 2015

CoCo/OS-9 escavation update…

Floppies. Lots of floppies!

A few weeks ago I dug out my old Tandy Color Computer 3 and began the project of archiving all my old floppy disks to disk image files. I am using Darren Atkinson’s CoCoSDC interface for this project, and it has been an amazing pleasure to work with so far.

I have backed up probably well over 300 Disk BASIC floppy disks, and have hundreds more OS-9 disks to get to next. I am also dealing with files that are on Compact Flash cards plugged in to the Cloud-9 SuperIDE interface (I never bothered with a physical IDE drive by the time I got that interface in 2004), and even some on my old SyQuest EZ135 SCSI drive using a KenTon SCSI interface.

IDE, SD, SCSI and Floppy interfaces.
IDE, SD, SCSI and Floppy interfaces.

This mixture of controllers, device drivers and “hard drives” has given me much time to revisit how things worked back then. Right now, I have a fresh 128MB .dsk image on an SD card in my CoCoSDC and I am backing up an EZ135 SCSI disk to it. It is taking hours (and just finished as I type this), but the end result is an exact clone sector-by-sector, including any deleted files I may want to peek at again. (It would probably have been much faster just to use a file copy tool, like “arc”, and go “arc -am /h0 /sd1”).

Along the way, I have discovered programs I had forgotten writing. For instance, in my pre-OS-9 days, I knew I had written a MIDI librarian program for the Kawai K1 synthesizer. It was sold by Rulaford Research:

I wrote a MIDI librarian for the Kawai K1.
I wrote a MIDI librarian for the Kawai K1.

…but I had forgotten I wrote a Layaway program for an East Texas furniture store:

Apparently, I wrote a layaway program, too.
Apparently, I wrote a layaway program, too. This was before spell checkers, and as a teenager, my spelling were not good.

During my disk-cavations, I unearthed many more projects I had forgotten about. Some were completed and deployed (freeware tools, music demos, utilities), and others were work-in-progress items.

An unfinished CoCo 3 OS-9 3-D maze game inspired by the old "Phantom Slayer" by MED SYSTEMS.
An unfinished CoCo 3 OS-9 3-D maze game inspired by the old “Phantom Slayer” by MED SYSTEMS.

And so much more! I have also found old documentation to the Sub-Etha products, and discovered I wrote an MM/1 program I had completely forgotten about – MegaBanners (an updated MiniBanners, but using Joel Hegberg’s high-resolution font engine). Wild.

I guess since most of this was twenty years ago, and has been packed away since then, I can be forgiven for my faulty memories. It sure has been fun rediscovering things.

After a few more weeks, I expect to have all of my old floppies and hard drives archived to the CoCoSDC. After that, a huge project begins: sorting and organizing everything.

Once I get to this point, I plan to put together some .dsk images of the old Sub-Etha Software items for folks to check out. They will be made available as shareware with the hope of making a few dollars. We’ll see if that works in 2015.

More to come…

PCLEAR 0 to get more CoCo BASIC memory

Updates:

  • 2021-12-15: Added screen shot of BASIC ROM assembly.

On the Radio Shack Color Computer, Extended Color BASIC added new commands to access high resolution graphics modes. The following modes of the CoCo’s Motoroal 6847 VDG chip (video display generator) were implemented:

  • PMODE 0 – 128×96 2-color (1536 bytes)
  • PMODE 1 – 128×96 4-color (3072 bytes)
  • PMODE 2 – 128×192 2-color (3072 bytes)
  • PMODE 3 – 128×192 4-color (6144 bytes)
  • PMODE 4 – 256×192 2-color (6144 bytes)

Extended Color BASIC allows a program to allocate up to eight 1536 byte pages of memory for graphics. If you wanted to use a single 128×96 PMODE 0 screen, you would want to reserve on page of memory for it (PCLEAR 1). If you wanted to use a 256×192 PMODE 4 screen, you would want to reserve four pages of memory (PCLEAR 4).

In BASIC, you could reserve eight pages (PCLEAR 8), and then draw on eight different PMODE 0 screens and flip between them, creating simple page-flipping animation. It was amazingly fun back then.

But this isn’t an article about graphics (though now that I think about it, I really want to write one).

By default, BASIC reserves four pages of graphics memory (6144 bytes) which, I guess, saves a BASIC program from having to do “PCLEAR 4” in it before using PMODE 4. Proper BASIC programs always did the PCLEAR anyway just to make sure the memory was available (for instance, if you typed PCLEAR 1 before you ran, the program would error out if it was assuming PCLEAR 4 was available). There have always been bad programmers.

The point of this article is to point out that, by default, BASIC has 6K less memory available to it. On startup, a 32K or 64K disk-based CoCo shows 22823 bytes free to BASIC:

On startup, the CoCo has 22823 bytes available for BASIC.
On startup, the CoCo has 22823 bytes available for BASIC.

64K NOTE: The reason BASIC memory is the same for 32K and 64K is due to legacy designs. The 6809 processor can only address 16-bits of memory space (64K). The BASIC ROMs started in memory at $8000 (32768, the 32K halfway mark). This allowed the first 32K to be RAM for programs, and the upper 32K was for BASIC ROM, Extended BASIC ROM, Disk BASIC ROM and Program Pak ROMs. Early CoCo hackers figured out how to piggy-pack 32K RAM chips to get 64K RAM in a CoCo, but by default that RAM was “hidden” under the ROM address space. In assembly language, you could map out the ROMs and access the full 64K of RAM. But, since a BASIC program needed the BASIC ROMs, only the first 32K was available.

To get the most memory possible for BASIC we would want to not reserve any graphics pages. However, the PCLEAR command does not allow typing PCLEAR 0. The best we can do is PCLEAR 1, which still reserves 1536 bytes. Doing” PCLEAR 1″ and then “PRINT MEM” will show 27431 bytes free. I am not really sure why PCLEAR 0 was not implemented, but without it, there is always 1.5 K of memory wasted for BASIC programs that do not use high-resolution graphics.

However, it is very simple to achieve a PCLEAR 0 by using a few bytes of assembly language. The short program I use to do it is this:

10 CLS:FORA=0TO8:READA$:POKE1024+A,VAL("&H"+A$):NEXTA:EXEC1024:DATAC6,1,96,BC,1F,2,7E,96,A3

This program reads the 9-byte assembly code and POKEs it in to memory, then EXECutes the routine. I chose to store it at memory location 1024, which is the start of the 32 column text screen. As a result, when it runs, it will put garbage on the first 9 characters of the screen. I just chose that memory since I knew no other program would use it (unless it was a temporary thing like this). If you understand the CoCo memory map, you can change that 1024 to any other safe location in memory and avoid having the text screen temporarily corrupted.

After running this, now a “PRINT MEM” will show 28967. Now we have 6144 bytes extra for our program! Big win.

However … 28K still isn’t quite the 32K we may have hoped for. This is because there is also memory reserved for the text screen (512 bytes, 1/2 K), cassette load buffers, BASIC input buffers, etc. There is additional memory reserved for Disk BASIC, so you actually have a bit more BASIC memory on a cassette-only system.

On startup, a cassette-based CoCo has 24871  bytes available for BASIC.
On startup, a cassette-based CoCo has 24871 bytes available for BASIC.

As you see above, 24871 bytes are available on a cassette-based CoCo on startup, which means there is about 2K of overhead to support Disk Extended Color BASIC. (Note to self: check these numbers.)

If we do the PCLEAR 0 on a cassette-based CoCo, we end up with 31015 bytes available to BASIC, and that is the most we can get (easily). If you do this:

PRINT PEEK(25)*256+PEEK(26)

…you will see what memory location your BASIC program starts at. After a PLCEAR 0 on a cassette-based CoCo, the value returned is 1537. The 32-column text screen is in memory from 1024 to 1536, meaning this is the very earliest in memory that a BASIC program can start. The only way to get more memory would be to extend the end, and we can’t because at the 32K mark, the BASIC ROMs begin. (Thus, 1537 to 32676 in memory is 31230, which is 215 bytes still missing. 200 bytes is reserved for strings, but a CLEAR 0 removes that, meaning there are only 15 bytes of BASIC overhead we can’t actually use.)

Not bad.

BONUS: Here is the nine bytes of assembly that my program POKEs in:

ldb #1
lda <$bc
tfr d,y
jmp >$96a3

Thanks to William Astle (Lost Wizard Enterprises, creator of LWTools) for translating my POKE bytes back in to the assembly code for me. It’ s been so long, I couldn’t remember what it was doing. In this case, it’s setting up the Y register and jumping in to a ROM routine that handles the PCLEAR, which I assume is being done to bypass the “?FC ERROR” check if the value of 0 is used from BASIC.

Here is the routine from Extended Color BASIC Unravelled:

BASIC word wrap test program

This article is part of a series. Be sure to check out part 1part 2part 3part 4 and part 5.

Here is a new word wrap test program. It has new test string cases, and now reports the code space used and variable memory used in addition to time. In order to properly report code space, it may require some tweaking (unless you have it entered 100% byte-for-byte like I typed it). I will make a .DSK image available for download soon.

Your submission should be a subroutine that starts at line 1 and expects A$ to be the string to word wrap, WD to be the screen width to wrap to, and RETURNs and the end back to the caller.

The new test program records the start and end time (to determine speed), start and end memory (to determine variable usage), and displays the code size of the program minus the number of bytes of the test program (line 0, lines 100-end). It has the size of the “empty” test program (nothing from line 1-99) hard coded so it can reflect the overhead of your routine in those lines.

NOTE: The memory usage shown by this program is just variables, and not string space. Each variable used takes 7 bytes, and string data goes in the CLEAR xxx block of memory. If memory used shows 16, but you had to do a CLEAR 600 to make your routine work, you do require more than 16 bytes but I couldn’t

Configuring the Test

  • CODE SPACE: The code space value printed is hard coded to subtract the size of the test program with a value in line 260. If you retype the test program and change any spaces or anything that would alter the size, that constant value needs to be adjusted. You want it to print 0 when you have nothing in lines 1-99 and type GOTO 280. If it does not print 0, adjust the value subtracted at the end of line 280. (If it prints 4, add 4 to the value that is there. If it prints -2, subtract 2.)
  • MEMORY: If your program uses more than the default 200 bytes reserved for variables, adjust the CLEAR command in LINE 100. If you are pre-DIMensioning variables you plan to use, you can also add them to LINE 100 but this will count against your code size. It is a good thing to do for speed.

The Tests

The test program will perform the following tests:

  1. An empty string.
  2. A short string that does not need to word wrap.
  3. A multi-line string of words that will need to word wrap. Its has words with characters ending in position 32 to test if the wrap routine uses that column without skipping extra spaces between lines.
  4. A string with a word longer than 32 characters and one longer than 64 characters to test chopping of long words (where it just splits it in the middle).

I believe these will test all possible conditions, and will help us compare all the versions for code size, variable usage and execution speed.

WWTEST10.BAS – Version 1.0

0 GOTO 100:REM WW-TEST 1.0
100 CLS:CLEAR 200:DIM A$,M1,M2,T1,T2,WD
110 INPUT"SCREEN WIDTH [32]";WD
120 IF WD=0 THEN WD=32
130 TIMER=0:T1=TIMER
140 M1=MEM
150 PRINT "EMPTY STRING:"
160 A$="":GOSUB 1
170 PRINT "SHORT STRING:"
180 A$="THIS SHOULD NOT NEED TO WRAP.":GOSUB 1
190 PRINT "LONG STRING:"
200 A$="THIS IS A STRING WE WANT TO WORD WRAP. EACH LINE CONTAINS EXACTLY 32 CHARACTERS. IT SHOULD USE THE LAST COLUMN AND SHOW FOUR LINES.":GOSUB 1
210 PRINT "WORD > WIDTH:"
220 A$="SUPERCALIFRAGILISTICEXPIALIDOCIOUS IS A WORD TOO LONG TO FIT ON ONE LINE. THIS ONE TAKES OVER TWO: ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890. DID IT WORK?":GOSUB 1
230 A$=""
240 T2=TIMER
250 PRINT"TIME TAKEN:"T2-T1
260 M2=MEM
270 PRINT"MEMORY USE:"M1-M2
280 PRINT"CODE SPACE:"PEEK(27)*256+PEEK(28)-PEEK(25)*256+PEEK(26)-767
290 END

 Current Submissions

Allen Huffman version 1 (MID$):

1 IFA$=""THENPRINT:RETURNELSEZS=1
2 ZE=LEN(A$):IFZE-ZS+1<=WD THENPRINTMID$(A$,ZS,ZE-ZS+1);:IFZE-ZS+1<WD THENPRINT:RETURN
3 FORZE=ZS+WD TOZS STEP-1:IFMID$(A$,ZE,1)<>" "THENNEXT:ZC=0ELSEZE=ZE-1:ZC=1
4 IFZE<ZS THENZE=ZS+WD-1
5 PRINTMID$(A$,ZS,ZE-ZS+1);:IFZE-ZS+1<WD THENPRINT
6 ZS=ZE+1+ZC:GOTO2

Allen Huffman version 2 (LEFT$/RIGHT$) – required additional string space:

1 IFA$=""THENPRINT:RETURN
2 ZE=LEN(A$):IFZE<=WD THENPRINTA$;:IFZE<WD THENPRINT:RETURN
3 FORZE=WD+1TO1STEP-1:IFMID$(A$,ZE,1)<>" "THENNEXT:ZP=0ELSEZE=ZE-1:ZP=1
4 IFZE=0THENZE=WD
5 PRINTLEFT$(A$,ZE);:IF ZE<WD THENPRINT
6 A$=RIGHT$(A$,LEN(A$)-ZE-ZP):GOTO2

Jim Gerrie version 3 (does not use the last character of the row):

1 C1=1:CC=WD+1
2 CC=CC-1:ON-(MID$(A$,CC,1)<>""ANDMID$(A$,CC,1)<>" "ANDCC>C1)GOTO2:C2=CC-C1:IFCC=C1 THENC2=31:CC=C1+WD-2
3 PRINTMID$(A$,C1,C2):C1=CC+1:CC=C1+WD:ON-(C1<=LEN(A$))GOTO2:RETURN

Darren Atkinson version 1 (VARPTR):

1 C1=1:CC=WD+2:VP=VARPTR(A$):VP=PEEK(VP+2)*256+PEEK(VP+3)-1:LN=LEN(A$)-1:SP=32
2 CC=CC-1:IFCC<LN ANDPEEK(VP+CC)<>SP ANDCC>C1 THEN2ELSEC2=CC-C1:IFCC=C1 THENC2=WD:CC=C1+WD-1
3 PRINTMID$(A$,C1,C2);:C1=CC+1:CC=C1+WD:IFC2<>WD ORLN+1<WD THENPRINT
4 IFC1<LN THEN2ELSERETURN

Darren Atkinson version 2 (INSTR):

1 ST=1:LN=LEN(A$)+1:FORPP=1TOLN:LW=INSTR(PP,A$," "):IFLW THENIFLW-ST<WD THENPP=LW:NEXTELSEELSEPRINTMID$(A$,ST):RETURN
2 IFLW-ST=WD THENPRINTMID$(A$,ST,LW-ST);:PP=LW:ST=LW+1:NEXTELSEIFPP<>ST THENPRINTMID$(A$,ST,PP-ST-1):ST=PP:PP=PP-1:NEXTELSEPRINTMID$(A$,ST,LW-ST)" ";:PP=LW:ST=LW+1:NEXT

Current Results (Time/Mem/Code)

  •  AH1 – 129 / 21 / 228
  • AH2 – 119 / 14 / 186 * actually 114 memory (CLEAR 300)
  • JG3 – 308 / 21 / 164
  • DA1 – 260 / 42 / 224
  • DA2 – 72 / 28 / 219

Fastest: Darren Atkinson’s #2

Lowest Memory Usage: Allen Huffman’s #1 and Jim Gerrie’s #3.

Smallest Code Space: Jim Gerrie’s #3.

Configuring the TP-LINK TL-WR702N nano router for Arduino

2015-02-07 Update: Added default WiFi password.

The two most-viewed pages on this site are often the following to Arduino articles:

The first one deals with a bug (?) I found in in the Arduino Ethernet library that prevented it from properly handling multiple incoming connections to the same port. The second was sharing my discovery of the $20 TP-LINK TL-WR702N nano router. It seems I am not the only one not happy that Arduino WiFi shields can be as much as $90, while cheap ethernet shields can be found for around $10.

The TP-LINK can be configured to connect to a WiFi network and then plug in to an Ethernet-only device and link it to the WiFi. Folks use them to get Ethernet printers on WiFi (such as the Lexmark printer I have — the official Ethernet module for it is $50, but I can use this TP-Link and be wireless printing for $20). I use one to get my Arduino on WiFi. Note that the Arduino will not have any control over the WiFi connection and won’t be able to select WiFi

This article is a quick guide to getting the TP-LINK set up for use with the Arduino.

TP-LINK TL-WR702N nano router box.
TP-LINK TL-WR702N nano router box, pictured next to a pen for scale.

1. Buy the TP-LINK WR702N. I got mine from Amazon for $19.99. It comes in a tiny box and is packed almost as nicely as an Apple product.

Inside the box you will find the tiny router (about 2″x2″x.5″), a micro USB cable, an Ethernet cable, and a USB power supply. There is also a mini-CD and a few small quick start guides. The guides are put together very well and have plenty of photos.

On the back of the TP-LINK unit will be the MAC address, but unless you have multiple units, you won’t need to know this.

2. Power up the TP-LINK by either plugging it up with the USB micro cable to a USB port (on your computer or a hub), or via the power supply.

3. The unit will boot up and after a few moments a new wireless network will appear that starts with “TP-LINK_xxxxxx” with the last part being the end of the MAC address of the router. Connect your computer to it. You will be prompted for a WiFi password, which you will find under the tiny barcode on the back of the unit. (It will be the last 8 HEX digits of the Mac address.) Give it a few more moments to connect and get an IP address. (Make sure you aren’t getting an internet connection some other way, like Ethernet. There are also ways to configure this router via Ethernet, but you’ll have to check TL-LINK guides for that.)

4. Open a web browser and go to: tplinklogin.net  It should prompt you for a username and password. You will find these on the back of the router, but they should be admin and admin.

TP-LINK WR702N login
Enter “admin” for the username and “admin” for the password.

5. You should see an admin webpage that is coming from your router. The first thing we need to do is configure the router to connect to your WiFi access point and connect to the internet. Click on Quick Setup under Basic Settings and click Next.

TP-LINK WR702N quick setup
Select Quick Setup.

6. The first screen is to select the Working Mode. We want Client, so choose it then click Next.

TP-LINK WR702N working mode
Select Client mode.

6. Next we will do Wireless Client setup and choose which WiFi network this nano should connect to. You can manually type it all in, but it’s easier to just click the Survey button so it scans for networks that are available. From that list, select your own WiFi network and then you will have an easy spot to enter your WiFi network password.

TP-LINK WR702N wireless client
Click the Survey button to obtain a list of available WiFi networks.
TP-LINK WR702N survey
Select your WiFi network from the list.
TP-LINK WR702N wifi password
Enter the password to your WiFi network.

7. The unit will then reboot and attempt to connect to your WiFi network. When it does, it will begin passing the WiFi connection out to the Ethernet port. To test this, turn off WiFi on your computer, and plug it up to the TP-LINK nano router via the included Ethernet cable. You will need to configure your computer’s Ethernet connection for DHCP. If it is working, after a few moments you will get an IP address assigned to your computer by the nano. Once the connection is made, you can test by going to Google.com or a known-working site.

8. Once you know it’s working, you should update the firmware on the nano to the latest. You can download the firmware here:

http://www.tp-link.us/support/download/?model=TL-WR702N&version=V1

Look for the latest version. It should look something like this:

TP-LINK WR702N firmware
Look for the latest version of the firmware for the TP-LINK TL-WR702N on the TP-LINK website.

Download this .zip file, then extract it somewhere you will be able to find it. It will create a folder with a few files inside, including a “.bin” which is the actual firmware update.

Log back in to tplinklogin.net and go to System Tools and Firmware then browse to the .bin file you just downloaded.

TP-LINK WR702N update
Browse to the .bin file you downloaded and update the firmware.

After it updates, the nano will reboot once again. On mine, this reset all the router settings, and I had to log back in and set it up again. There is probably a better set of steps to do this, but this is how I went through it and took screen shots so that is what I am sharing.

Once this is done, you can unplug and switch your computer back to normal internet connection. Now the TP-LINK can be plugged to power and the Arduino Ethernet shield and you can use the Ethernet library to make connections via your WiFi network.

NOTE: Since the nano requires using a computer and web browser to select which WiFi it connects to, this is not a portable solution. You cannot choose what WiFi to connect with (or the password or anything) from the Arduino. If you wanted to take your Arduino somewhere else and get it online, you would have to have a computer available to connect to the TP-LINK nano and reprogram which WiFi network it connects to. If you have a real WiFi shield for the Arduino, you can do this in software.

I hope these quick notes help…

CoCoSDC for TRS-80 Color Computer part 6

See also: part 1part 2part 3part 4, part 5 and part 6.

When we last left off, I was discussing how the CoCo’s drive controller accessed multiple physical floppy drives by using drive select lines.

In the early days of CoCo disk drives, each drive was configured to respond to the same drive select (drive select 1, for instance). A special ribbon cable was used which flipped some wires at each connection, moving different drive select lines from the controller to the same (drive select 1, for instance) line of the drive. The first plug (for DRIVE 0) would have drive select 1 go to that drive’s drive select 1 pin. The second connector would have some wires twisted and flipped so drive select 2 would go to the drive’s drive select 1 pin. This was done so you did not have to configure each drive (by opening it up and moving a jumper connector) to respond to a different drive selects.

It was this method with a “4-drive cable” that CoCo users could plug up four of the same single-sided drives (all configured as drive 1s) and where they plugged in on the cable would determine which drive they responded to from Disk BASIC.

If you used a normal (and cheaper!) flat cable that passed all four drive select lines to each drive, then the drives had to be configured to know which select they should respond to. A double-sided drive had three drive select options (1, 2 or 3) and also responded to the side select line.

As previously discussed, the CoCoSDC hardware honors some of these drive/side select lines, but SDC-DOS’ disk routines bypass several places where normal Disk BASIC would be using them to pass along to the drive hardware. Because of this, the standard POKEs we used on real hardware to access the back sides of double-sided drives from BASIC do not work. While the hardware is willing the software is not.

But, if you were to boot the CoCo using standard Disk Extended Color BASIC, it will work. The CoCoSDC hardware honors the first two drive select lines and side select. The problem is, without SDC-DOS, you don’t have a way to mount disk images or enable/disable the virtual SD card drives when you want to access a physical drive.

SDC-DOS handles communication with the CoCoSDC firmware. That firmware handles the disk image files and making them look like physical sectors to whatever software is running on the CoCo (Disk BASIC, OS-9, copy protected software using its own disk routines, etc.).

Fortunately, CoCoSDC designer Darren Atkinson has provided a simple BASIC program that can send commands to the CoCoSDC firmware. It’s not as simple as typing in DRIVE0,”GAMES.DSK”,NEW but it does allow the major functions to be done when you are not running SDC.DOS. He shared the following:

Below is a BASIC program I sometimes use when SDC-DOS is not present. It’s a little slow and definitely more cumbersome than entering commands in SDC-DOS, but it gets the job done.  Save this program somewhere on your CF card or SCSI disk so you can run it when using RGB/HDB DOS.
 

The program is:

10 OS=PEEK(&HFF7F):INPUT "MPI SLOT#";SL
20 IF SL<1 OR SL>4 THEN 10
25 SL=SL-1
30 INPUT "SDC DRIVE#";DR
40 IF DR<>0 AND DR<>1 THEN 30
50 LINE INPUT "COMMAND? ";C$
60 IF LEN(C$)<2 OR MID$(C$,2,1)<>":" THEN 50
70 C$=C$+CHR$(0):P=VARPTR(C$)
80 P=PEEK(P+2)*256+PEEK(P+3)
90 POKE &HFF7F,OS AND 240 OR SL
100 POKE &HFF40,67:POKE &HFF49,0
110 POKE &HFF4A,0:POKE &HFF48,DR+224
120 A=&HFF4A:B=&HFF4B
130 FOR I=P TO P+254 STEP2:POKE A,PEEK(I):POKE B,PEEK(I+1):NEXT
140 ST=PEEK(&HFF48):IF ST AND 1 THEN 140
150 POKE &HFF40,0:POKE &HFF7F,OS
160 IF ST AND 128 THEN PRINT "ERROR";ST
 (I sure hope WordPress doesn’t eat the program listing again.)
 
He provides the following instructions:
 
When you run the program you will be asked for the MPI slot number containing the CoCo SDC (1-4).  You could edit line 10 to remove the INPUT statement and just set variable SL equal to the slot number.Next you will be asked for the SDC Drive Number (0 or 1) to which the command will be applied. Enter 0 for commands that don’t apply to a specific drive number, or just press ENTER.Finally you will be asked to enter a command string. Command strings begin with a letter and a colon (:). Immediately following the colon is a path name to a file or directory on the SD card.

 

Commands Available:

D:  set current directory on SD card
K:  create new directory
M:  mount or eject a disk image
N:  create (if necessary) and mount a DSK file
X:  delete a file or an empty directory

Examples:

——————————————–
Set current directory to /GAMES/ARCADE

SDC DRIVE#? 0
COMMAND? D:/GAMES/ARCADE

——————————————–
Mount Donkey Kong image in drive 0

SDC DRIVE#? 0
COMMAND? M:DONKEY.DSK

——————————————–
Eject the disk image in drive 1

SDC DRIVE#? 1
COMMAND? M:

There is also a Rename command (R:).  That one requires a second null-terminated string (new leaf name) to be included in the 256 byte data block immediately following the first string.

 

C$ = “R:PATH/TO/OBJECT.EXT”+CHR$(0)+”NEWNAME.EXT”+CHR$(0)

This will be a key piece to handling RS-DOS doubled sided disks and backing up the individual sides to CoCoSDC disk images.

He also provided a way to call the additional DSKCON routines that SDC-DOS adds:

Here is a Basic subroutine which calls DSKCON to mount the file named DN$ into drive 1. Explanation of the code follows:

1000 DK=PEEK(&HC004)*256+PEEK(&HC005)
1010 SD$=”M:”+DN$+CHR$(0)
1020 P=VARPTR(SD$)
1030 POKE 238,PEEK(P+2):POKE 239,PEEK(P+3)
1040 POKE 234,&H85
1050 POKE 235,1
1060 EXEC DK
1070 ST=PEEK(240):RETURN

1000 assign DSKCON code address to variable DK
1010 build command string (SD$) to mount the file
1020 get pointer to the string descriptor (P)
1030 set DSKCON buffer to the command string data
1040 set DSCKON opcode to transmit a command string
1050 set DSKCON drive number to 1
1060 call DSKCON
1070 read DSKCON status result into ST and return

Meaning of bits in the status code:

Bit 7: set on any error.
Bit 5: set if file is already open in other drive.
Bit 4: set if file or directory was not found.
Bit 3: set on various hardware errors.
Bit 2: set if file or path name is invalid.

– Darren

You may be wondering why we might want to call SDC-DOS’s DSKCON directly. After all, if you are running SDC-DOS, you already have these commands. In my situation, I needed a way to detect if an image exist. There is no such call, so the only thing you can do is attempt to mount the image (DRIVE 0,”IMAGE.DSK”) and if it fails with a not found error, it doesn’t exist. On a CoCo 3, there is an ONERR command one could use to error trap that call so you might do something like this:

100 REM See if image DN$ exists.
110 ONERR GOTO 150
120 DRIVE 0,DN$
130 REM If here, we were able to mount, so it must exist.
140 PRINT "Image exists."
150 END
...
200 REM If here, we were not able to mount.
210 ONERR 'Turn off ONERR
220 PRINT "Error mounting image.":END

This works, but ONERR was only added in the CoCo 3’s Super Extended Color Basic. For the CoCo 1 and 2, this technique wouldn’t work. Thus, Darren provided me his DSKON routine so I could try to mount an image that way and read the returned status code.

Now I should have all the pieces I need to do what I want to do, which means this is a good place to say…

To be continued…

Even more BASIC word wrap versions

(Hello, Reddit.com visitors!)

Be sure to check out part 1part 2part 3 and part 4.

Darren Atkinson's second word wrap routine.
Darren Atkinson’s second word wrap routine.

Behold, the new champion of BASIC word wrap routines! Darren Atkinson sends in this two line wrap routine which makes use of the INSTR() function to find spaces in a string. It uses more integer variables, but does not use any strings. And, it’s FAST! It parses the test cases with a count of around 60 — half the time of the previous fast version!  Size-wise, it clocks in at 231 bytes, which is five bytes smaller than his previous version. Jim Gerrie’s still has the edge in the size category, but to get this much more speed for just a few bytes more might be a worthy tradeoff.

Darren does note:

I’m not sure the speed increase will be as dramatic when printing strings with average length words.

– Darren

I believe this is because his routine zips through long lines immediately, but would spend more time searching for spaces in a normal sentence. I will do some benchmarks using normal sentences to see how it stacks up.

Here is the full version:

0 GOTO100
1 ST=1:LN=LEN(A$)+1:FORPP=1TOLN:LW=INSTR(PP,A$," "):IFLW THENIFLW-ST<WD THENPP=LW:NEXTELSEELSEPRINTMID$(A$,ST):RETURN
2 IFLW-ST=WD THENPRINTMID$(A$,ST,LW-ST);:PP=LW:ST=LW+1:NEXTELSEIFPP<>ST THENPRINTMID$(A$,ST,PP-ST-1):ST=PP:PP=PP-1:NEXTELSEPRINTMID$(A$,ST,LW-ST)" ";:PP=LW:ST=LW+1:NEXT
100 CLS
110 INPUT"SCREEN WIDTH [32]";WD
120 IF WD=0 THEN WD=32
130 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
140 TIMER=0:TM=TIMER
150 PRINT "SHORT STRING:"
160 A$="THIS SHOULD NOT NEED TO WRAP.":GOSUB 1
170 PRINT "LONG STRING:"
180 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
190 PRINT "WORD > WIDTH:"
200 A$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
210 PRINT"TIME TAKEN:"TIMER-TM
220 END

Hi approach uses a FOR/NEXT loop to scan through each character position. By doing an INSTR(A$,PP,” “) (PP being the position 1-length), he checks to see if that position would be past the end of the line and, if not, he updates the PP position so it continues from there. This lets the assembly BASIC routine rapidly scan for the spaces instead of the BASIC interpreter doing it one byte a at a time. Very clever!

Great job, Darren!

His routine gave me another idea, and I will be providing an updated test program to try a few other things and see how we all stack up.

Until then…

More BASIC word wrap versions

Welcome to another exciting installment of text word wrapping in Color BASIC! This time, I will share another word wrap submission, then compare all four versions in speed and code size.

Be sure to check out part 1, part 2, and part 3.

I originally shared some BASIC code I was writing to do word wrapping of text. My program needed to run on 32, 40 or 80 columns on the CoCo, and I did not want to hard code versions of every screen for each screen width.

CoCo/MC-10 BASIC wiz Jim Gerrie passed along his three-line version, and now CoCoSDC designer Darren Atkinson shares some more tweaks. He writes:

Hi Allen,

Here is a submission for your Word Wrap article. It’s not my own design. I just took the liberty of making a couple changes to Jim Gerrie’s code:

1. It will now use the last column in a line.
2. It runs a bit faster.

By focusing on speed, the trade-off is somewhat larger code size and the allocation of a few more variables.

– Darren

His update looks like this:

0 CLEAR200:DIMCC,VP,C1,LN,SP:GOTO10
1 C1=1:CC=WD+2:VP=VARPTR(M$):VP=PEEK(VP+2)*256+PEEK(VP+3)-1:LN=LEN(M$)-1:SP=32
2 CC=CC-1:IFCC<LN ANDPEEK(VP+CC)<>SP ANDCC>C1 THEN2ELSEC2=CC-C1:IFCC=C1 THENC2=WD:CC=C1+WD-1
3 PRINTMID$(M$,C1,C2);:C1=CC+1:CC=C1+WD:IFC2<>WD ORLN+1<WD THENPRINT
4 IFC1<LN THEN2ELSERETURN
10 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 M$="This should not need to wrap.":GOSUB 1
90 PRINT "LONG STRING:"
100 M$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
110 PRINT "WORD > WIDTH:"
120 M$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
130 PRINT"TIME TAKEN:"TIMER-TM
140 END
Word-wrap by Darren Atkinson.
Word-wrap by Darren Atkinson.

Hi version is four lines of actual code (1-4) and uses a few more variables. He is using a technique I had not seen before. Rather than check characters using MID$(), he gets the address of the string using VARPTR() and then PEEKs memory. I will have to benchmark this and see the time differences. His version clocks in with a count of 192.

Let’s see how all four versions stack up, from fastest to slowest.

  1. My version 2 (LEFT$/RIGHT$): 107
  2. My version 1 (MID$): 114
  3. Darren Atkinson’s four line version: 192
  4. Jim Gerrie’s three line 3rd version: 248

That takes care of speed, but what about memory usage? In order to do a true apples-to-apples comparison, I need to alter my versions so they use the same starting line number at Jim’s and Darren’s. Jim moves subroutines to the start of the program so they are found quicker. He also numbered by 1 as a space-spacing step (GOSUB1 takes three bytes less than GOSUB1000). Since my versions are too long to fit before the start of the test code at line 10, I am going to make the test code start at 100, and GOSUB to line 1 for the word wrap routine. The new test program will look like this:

100 CLS
110 INPUT"SCREEN WIDTH [32]";WD
120 IF WD=0 THEN WD=32
130 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
140 TIMER=0:TM=TIMER
150 PRINT "SHORT STRING:"
160 A$="THIS SHOULD NOT NEED TO WRAP.":GOSUB 1
170 PRINT "LONG STRING:"
180 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
190 PRINT "WORD > WIDTH:"
200 A$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
210 PRINT"TIME TAKEN:"TIMER-TM
220 END

Line 0 will have any needed CLEAR command (for extra string space), and could use DIM to pre-declare any variables used later. Then it should GOTO 100 to start the test routine. The word wrap routine will start at line 1.

Jim provided me with an existing routine he had, so he altered the test program to use M$ for the message to wrap. In order to keep all cases the same, I have changed Jim’s (and Darren’s) test programs to use A$. Now all four word wrap routines will be as similar as possible.

I also removed the DIMs for now, since I wanted everything in the .BAS file to be the same except for the word wrap routine.

My version 1 (LEFT$/RIGHT$):

1 REM WORD WRAP V1
2 '
3 'IN : A$=MESSAGE
4 ' UC=1 UPPERCASE
5 ' WD=SCREEN WIDTH
6 'OUT: LN=LINES PRINTED
7 'MOD: ZS, ZE, ZC
8 '
9 LN=1
10 IF A$="" THEN PRINT:RETURN ELSE ZS=1
11 IF UC>0 THEN FOR ZC=1 TO LEN(A$):ZC=ASC(MID$(A$,ZC,1)):IF ZC<96 THEN NEXT ELSE MID$(A$,ZC,1)=CHR$(ZC-32):NEXT
12 ZE=LEN(A$)
13 IF ZE-ZS+1<=WD THEN PRINT MID$(A$,ZS,ZE-ZS+1);:IF ZE-ZS+1<WD THEN PRINT:RETURN
14 FOR ZE=ZS+WD TO ZS STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZC=0 ELSE ZE=ZE-1:ZC=1
15 IF ZE<ZS THEN ZE=ZS+WD-1
16 PRINT MID$(A$,ZS,ZE-ZS+1);
17 IF ZE-ZS+1<WD THEN PRINT
18 LN=LN+1
19 ZS=ZE+1+ZC
20 GOTO 12

My version 2 (MID$):

1 REM WORD WRAP V2
2 '
3 'IN : A$=MESSAGE
4 ' UC=1 UPPERCASE
5 ' WD=SCREEN WIDTH
6 'OUT: LN=LINES PRINTED
7 'MOD: ZC, ZE, ZS
8 '
9 LN=1
10 IF A$="" THEN PRINT:RETURN
11 IF UC>0 THEN FOR ZS=1 TO LEN(A$):ZC=ASC(MID$(A$,ZS,1)):IF ZC<96 THEN NEXT ELSE MID$(A$,ZS,1)=CHR$(ZC-32):NEXT
12 ZE=LEN(A$)
13 IF ZE<=WD THEN PRINT A$;:IF ZE<WD THEN PRINT:RETURN
14 FOR ZE=WD+1 TO 1 STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
15 IF ZE=0 THEN ZE=WD
16 PRINT LEFT$(A$,ZE);
17 IF ZE<WD THEN PRINT
18 LN=LN+1
19 A$=RIGHT$(A$,LEN(A$)-ZE-ZP)
20 GOTO 12

Jim Gerrie’s three line version:

1 C1=1:CC=WD+1
2 CC=CC-1:ON-(MID$(A$,CC,1)<>""ANDMID$(A$,CC,1)<>" "ANDCC>C1)GOTO2:C2=CC-C1:IFCC=C1 THENC2=31:CC=C1+WD-2
3 PRINTMID$(A$,C1,C2):C1=CC+1:CC=C1+WD:ON-(C1<=LEN(A$))GOTO2:RETURN

Darren Atkinson’s four line version:

1 C1=1:CC=WD+2:VP=VARPTR(A$):VP=PEEK(VP+2)*256+PEEK(VP+3)-1:LN=LEN(A$)-1:SP=32
2 CC=CC-1:IFCC<LN ANDPEEK(VP+CC)<>SP ANDCC>C1 THEN2ELSEC2=CC-C1:IFCC=C1 THENC2=WD:CC=C1+WD-1
3 PRINTMID$(A$,C1,C2);:C1=CC+1:CC=C1+WD:IFC2<>WD ORLN+1<WD THENPRINT
4 IFC1<LN THEN2ELSERETURN

To determine program size, I will PRINT MEM before loading,and then load the full test program and delete line 0 (the CLEAR/GOTO) and anything past 100 (the test program). I will print MEM again and subtract.

Here they are again, from smallest code size to largest:

  1. Jim Gerrie’s three line version: 176 bytes
  2. Darren Atkinson’s four line version:  236 bytes
  3. My version 2 (LEFT$/RIGHT$): 500 bytes
  4. My version 1 (MID$): 542 bytes

Now the order is nearly reversed, with Jim’s and Darren’s versions substantially smaller than my versions. But, my version has those big REM statements and plenty of spaces and lines that could be combined. Mine also returns the number of lines printed (LN) and has the code for uppercase conversion and a check at the start to make sure we aren’t passed an empty string. If an empty string is passed to Jim’s, it gives “?FC ERROR IN 2”. Darren’s just returns. I will have to inspect his code and see if that was intentional. I should probably add an empty string to the test case.

I will remove the uppercase (UC) and line count (LN) code from mine, and try to pack things together. I want to keep the empty string check since error checking is good and since it seems Darren’s does this.

My optimized version 1 (MID$) now looks like this:

1 IFA$=""THENPRINT:RETURNELSEZS=1
2 ZE=LEN(A$):IFZE-ZS+1<=WD THENPRINTMID$(A$,ZS,ZE-ZS+1);:IFZE-ZS+1<WD THENPRINT:RETURN
3 FORZE=ZS+WD TOZS STEP-1:IFMID$(A$,ZE,1)<>" "THENNEXT:ZC=0ELSEZE=ZE-1:ZC=1
4 IFZE<ZS THENZE=ZS+WD-1
5 PRINTMID$(A$,ZS,ZE-ZS+1);:IFZE-ZS+1<WD THENPRINT
6 ZS=ZE+1+ZC:GOTO2

My optimized version 2 (LEFT$/RIGHT$) now looks like this:

1 IFA$=""THENPRINT:RETURN
2 ZE=LEN(A$):IFZE<=WD THENPRINTA$;:IFZE<WD THENPRINT:RETURN
3 FORZE=WD+1TO1STEP-1:IFMID$(A$,ZE,1)<>" "THENNEXT:ZP=0ELSEZE=ZE-1:ZP=1
4 IFZE=0THENZE=WD
5 PRINTLEFT$(A$,ZE);:IF ZE<WD THENPRINT
6 A$=RIGHT$(A$,LEN(A$)-ZE-ZP):GOTO2

Now let’s see how things stack up.

  • My version 1 (MID$): size 240 bytes / speed 107
  • My version 2 (LEFT$/RIGHT$): size 199 bytes / speed 99

Wow. By removing extra functionality, REMs, removing spaces, and packing lines together, I went from 542 to 240 for version 1, and from 500 to 199 in the second version. For speed, version went from 114 to 107, and version 2 went from 107 to 99.

Out of string space!
Out of string space!

…but I should point out that, with the default 200 bytes reserved to variables in BASIC, my second version crashed with an “?OS ERROR” (out of string space). I had to CLEAR255 to make it work. The actual number has to be large enough to hold the biggest string being passed in, plus the overhead for the string manipulation using LEFT$/RIGHT$. To really know how much memory a BASIC program takes up, we really do need to consider its variable usage.

And now the new rankings for code size (smallest to largest):

  1. Jim Gerrie’s three line 3rd version: 176 bytes
  2. My version 2 (LEFT$/RIGHT$): 199* bytes (see note below)
  3. Darren Atkinson’s four line version: 236 bytes
  4. My version 1 (MID$): 240 bytes

…and for speed:

  1. My version 2 (LEFT$/RIGHT$): 99 (was 107)
  2. My version 1 (MID$): 107 (was 114)
  3. Darren Atkinson’s four line version: 192
  4. Jim Gerrie’s three line 3rd version: 248

NOTE: Since my version 2 required me to do a CLEAR255 for it to run, that means it actually took up 254 bytes (code, plus the extra 55 bytes allocated past the default 200 for variables).

It is tricky to calculate variable usage since the sizes of strings passed in will be a factor, and maybe it would have ran with CLEAR 210 or CLEAR 220 to save a bit more. To crash-proof a program, you need to ensure CLEAR is done to cover the maximum size of string usage in the worst case. Here, we’re not that concerned.

In conclusion, right now if ever byte of program space matters (such as Jim’s case when programming in 4K), his is best. For overall speed, my version 2 is best (but it uses the most memory between code and variable storage). Even my version 1 (with no extra string space required is still faster than the next fastest.

Can we do better? Can you do better? Let’s find out.

I will make a .DSK image (that works in the XRoar emulator, and should be loadable via CoCoSDC on a real CoCo) as soon as I figure out how to post one in WordPress.

To be continued… Probably…

Word wrap revisited…

  1. 2015/1/5 Update: Jim’s v3 code has been fixed.

In the “CoCo Community”, Jim Gerrie is well-known for his prolific BASIC programs. He and his son have cranked out an endless assortment of games over the years for both the Radio Shack Color Computer and the MC-10. Since the MC-10 also started out as a 4K computer, he has long been since writing programs to fit in such little memory. He is the first one to submit programs to the 1980 4K CoCo Programming Challenge I am hosting.

After he saw my word wrap article and its follow-up with updated code, he was kind enough to share a simple word wrap routine that he uses. His looks like this:

...
1 C1=1:CC=WD
2 FORCC=CC TOC1+2STEP-1:IFMID$(M$,CC,1)<>""ANDMID$(M$,CC,1)<>" "THENNEXT
3 PRINTMID$(M$,C1,CC-C1):C1=CC+1:CC=C1+(WD-1):IFC1<=LEN(M$)THEN2
4 RETURN
...

FOUR lines compressed of code. Since he works with low-memory BASIC so often, he demonstrates plenty of the optimizing techniques I mentioned earlier and some I didn’t mention. Not only does he number his program by 1s, he also places the subroutine at the start of the program. Every time you GOTO or GOSUB, Color BASIC will either search forward (if the line number you are going to is higher than the one you are currently on), or start at the TOP of your program and search forward. This means if you had something like this:

100 REM A REALLY BIG PROGRAM
...
150 GOSUB 1000
...
995 END
1000 REM MY SUBROUTINE
1010 PRINT "HELLO!"
1020 RETURN

…when you called “GOSUB 1000” from line 150, BASIC would then have to scan forward until it finds line 1000. If you have hundreds of lines of BASIC code, this is a big waste of CPU time.

However, if you placed that routine at the start of the program (and had a line at the beginning to jump past it), every time you called the routine it would find it much quicker:

19 GOTO 100
20 REM MY SUBROUTINE
30 PRINT "HELLO!"
40 RETURN
100 REM A REALLY BIG PROGRAM
...
150 GOSUB 20
...
995 END

Keep in mind, there are always tradeoffs. GOTO works the same way, and ant complex BASIC program is usually full of GOTOs. Every GOTO has to do the same scanning (forward, or starting at the top) to find the routine. This means each GOTO to an earlier line number now has to scan past all your subroutine lines every time you GOTO. For the fastest code, you need to take in to consideration what happens more often — GOSUBing to functions, or GOTOs to higher line numbers.

As to how significant the time savings can be, I plan to write another article in the near future with some simple benchmarks.

Here is Jim’s complete program, which uses my WRAPTEST code to process the same strings:

0 CLEAR200:DIMC1,CC,M$:GOTO10
1 C1=1:CC=WD
2 FORCC=CC TOC1+2STEP-1:IFMID$(M$,CC,1)<>""ANDMID$(M$,CC,1)<>" "THENNEXT
3 PRINTMID$(M$,C1,CC-C1):C1=CC+1:CC=C1+(WD-1):IFC1<=LEN(M$)THEN2
4 RETURN
10 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 M$="This should not need to wrap.":GOSUB 1
90 PRINT "LONG STRING:"
100 M$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
110 PRINT "WORD > WIDTH:"
120 M$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
130 PRINT"TIME TAKEN:"TIMER-TM
140 END
Jim Gerrie's CoCo tiny and fast word-wrap routine.
Jim Gerrie’s tiny and fast word-wrap routine for CoCo BASIC.

His routine is small and fast. It does not implement UC uppercase conversion, does not use the last character of the line, and has issues with words longer than the line length — but just look at the code size and speed savings! This is a great, efficient approach when you can control the output. As long as you aren’t displaying “supercalifragilisticexpialidocious” it works great.

However, Jim seems up to the challenge, and he made some small changes to allow his wrap routine to handle words longer than the screen width. Again, just four lines of code:

0 CLS:CLEAR255:DIMCC,C1,C2,M$:GOTO10
1 C1=1:CC=WD+1
2 CC=CC-1:ON-(MID$(M$,CC,1)<>""ANDMID$(M$,CC,1)<>" "ANDCC>C1)GOTO2:C2=CC-C1:IFCC=C1 THENC2=31:CC=C1+WD-2
3 PRINTMID$(M$,C1,C2):C1=CC+1:CC=C1+WD:ON-(C1<=LEN(M$))GOTO2:RETURN
10 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 M$="This should not need to wrap.":GOSUB 1
90 PRINT "LONG STRING:"
100 M$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1
110 PRINT "WORD > WIDTH:"
120 M$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER "
121 M$=M$+"ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1
130 PRINT"TIME TAKEN:"TIMER-TM
140 END
Jim Gerrie's third version.
Jim Gerrie’s (v3) tiny and fast word-wrap routine for CoCo BASIC.

His new version, while still not using the last character of a line, now will break up words like supercalifragilisticexpialidocious. Because, you know, you have words like that in your text adventures all the time. And speaking of text adventures, he sent in this link to an archive of text adventures including many Jim (and his son) wrote. For many years he was using his original word wrap routine just fine (since he controlled the output) so all his additions are really just un-needed hoops to jump through for my abusing text case :)

Thanks, Jim, for sharing this with us! If anyone else wants to take a shot at it, feel free to send me a .DSK or .CAS image of your version. I have been using the XRoar emulator to do this testing (and take screen shots) on my Mac, and XRoar is also available for Windows, Linux and other systems.

Until next time, I hope things will be…

OK

…with you!

Optimizing Color BASIC – part 1

See also: part 1, part 2, part 3, part 4, part 5, part 6, part 7, part 8 and part 9.

A recent post by Art “ADOS” Flexser on the CoCo mailing list mentioned a few commercial programs for the Radio Shack Color Computer that were designed to optimize BASIC programs:

On Jan 2, 2015, at 12:26 AM, Arthur Flexser wrote:

A little googling identified the utility I was referring to as “The Stripper” from Eigen Systems. Apparently, there was also a similar program by Bob van der Poel called “Packer”. Maybe one or the other is available online someplace.

These should be useful for those finding tight memory for Basic programs in
the 4K contest.

The 4K contest he mentions is a programming challenge I recently organized to see what someone could do if they had the original 1980 4K Color Computer. You can read about it here:

http://www.cocopedia.com/wiki/index.php/1980_4K_CoCo_Programming_Challenge

In addition to the programs Art mentioned, I also had one given to me by Carl England. I used it on one of my programs at Sub-Etha Software. I do not know how it compares with The Stripper or Packer, but beyond true optimization of the code, it did about all you could do. Here are the tricks he used which produce the smallest and fastest version of a BASIC program, but they usually rendered it un-editable and hard to read.

NOTE: Code can be written for size or speed. Carl’s program optimized for size which in some cases made it faster since BASIC had less to parse, but as you will see, some optimizations might increase execution time making it slower. Maybe.

Remove all spaces

The less bytes to parse, the smaller and faster it will run. It has to be smart enough to know when spaces are required, such as “FORA=1TOQ STEP1” when a variable needs a space after it before another keyword.

Pack/combine lines where possible.

Any line called by a GOTO had to remain at the start, by following lines would be combined up to the max size of a BASIC line. Only when it had to be broken by logic (ELSE, etc.) would it not be combined. For example:

10 FOR I=1 TO 100
20 GOSUB 50
30 NEXT I
40 END
50 REM PRINT SOMETHING
60 PRINT "HELLO WORLD!"
70 RETURN

…would end up as:

10 FORI=1TO100:GOSUB60:NEXTI:END
60 PRINT"HELLO WORLD!":RETURN

Remove all REMs.

Obvious. I *think* his program would adjust a GOTO/GOSUB that went to a REM line to go to the next line after it, and remove the REM:

10 GOSUB 1000
20 END
1000 REM PRINT SOMETHING
1010 PRINT "HELLO"
1020 RETURN

…would become like:

10 GOSUB1010:END
1010 PRINT"HELLO":RETURN

NOTE: Even without packing, I learned not to GOTO or GOSUB to a REM since it required the interpreter to parse through the line. I would GO to the first line of code after the REM:

10 GOSUB 1010
...
1000 REM MY ROUTINE
1010 PRINT "HERE IS WHERE IT STARTS"
...

Every thing you do like that saves a bit of parsing time since it doesn’t have to start parsing 1000 and look up a token then realize it can ignore the rest of the line.

Not bad so far

But that’s not all!

The BASIC input buffer is some maximum number of bytes (250?). When you type a line to max, it stops you from typing more. When you hit ENTER, it is tokenized and could be much smaller. For instance, “PRINT” turns in to a one-byte token instead of the five characters. Carl’s program would combine and pack the tokens up to the max line size. Thus, it created lines that BASIC could run which were IMPOSSIBLE to enter on the keyboard. If I recall, you could LIST and they would print (?) but if you did an EDIT you were done.

Renumber by 1.

GOSUB2000 would take up one bye for the token, then four bytes for the line number. If you renumber by 1s, it might make that function be GOSUB582 and save a byte. Multiply that by every use of GOTO/GOSUB/ON GOTO/etc. and you save a bit.

Even without one of these compressors, try a before/after just doing a RENUM 1,1,1. I recently posted an article with a short (27 line) word wrap routine in BASIC. Doing this renum saved 7 bytes.

Removing NEXT variables.

I am not sure if this is just something I knew, or if his program also did it. If you use FOR/NEXT and it is used normally, you don’t need the NEXT variables like this:

10 FOR D=0 TO 3 'DRIVES
20 FOR T=0 TO 34 'TRACKS
30 FOR S=1 TO 18 'SECTORS
40 DSKI$ D,T,S,S1$,S2$
50 NEXT S
60 NEXT T
70 NEXT D

The NEXTs could also be

50 NEXT S,T,D

Or

50 NEXT:NEXT:NEXT

Ignoring spaces, “NEXT:NEXT:NEXT” takes up one less byte than “NEXTS,T,D” (NEXT is turned in to a token, so it is TOKEN COLON TOKEN COLON TOKEN versus TOKEN BYTEVAR COMMA BYTEVAR COMMA BYTEBAR”.

This takes up less memory, but there is a speed difference:

10 T=TIMER
20 FOR I=1 TO 10000
30 REM DO NOTHING
40 NEXT I
50 PRINT TIMER-T

Run this and it shows something in the 1181 range (speed varies based on other things BASIC does; pound on keys while it runs and it takes more time, for example). Change the “NEXT I” to “NEXT” and it shows something in the range of 1025. The more FOR variables, the more the savings, too.

10 TM=TIMER
20 FOR D=0 TO 3
30 FOR T=0 TO 34
40 FOR S=1 TO 18
50 REM DO SOMETHING
60 NEXT S
70 NEXT T
80 NEXT D
90 PRINT TIMER-TM

…shows around 345. Changing it to:

60 NEXTS,T,D

…changes it to around 336 – slightly smaller and faster since it is not parsing three different lines.

60 NEXT:NEXT:NEXT

…changes it to around 293! One byte smaller and slightly faster.

AND THERE’S MORE! But this post is already too long.

What I think would be cool is an actual BASIC OPTIMIZER that could do some smart things to programs, much like we do with the C language optimizers for asm and C. For example, noticing stupid things:

A$ = B$+"."+"."+C$

to

A$ = B$+".."+C$

And removing NEXT variables, or doing minor restructuring based on what it knows about the interpreter.

I suppose, back in the 80s, processors were so slow and memory so limited that doing such an optimizer might not be practical. But today, imagine how you could optimize a .BAS file on a modern computer (just like we have 6809 cross-hosted compilers that compile and link in the link of an eye).

Maybe this has already been done for some other flavor of BASIC? Hmmm. I may have to do some Googling.

A tale of two word wrap routines

  • 2015/1/4 Update: After reading this, you can check out the next installment, too.
  • 2015/1/5 Update: The BASIC listings should be fixed now.

In a recent article, I demonstrated a simple way to do a word wrap output routine in Extended Color BASIC on a Radio Shack Color Computer. When I went to try to use the routine, I found a few issues with it that I would like to address here.

My first version was able to word wrap all the way to the last character on a line, but I failed to test against a string that was longer than the screen width (with no spaces in it). This would only happen if you had a very long word on a very narrow screen (supercalifragilisticexpialidocious would be a word too long to fit on a 32-column CoCo screen, for example). I fixed this issue, and also optimized the routine to handle short strings better by checking for them at the start instead of the end.

I now have three test cases to check for: short strings that do not need to wrap, long strings that do need to wrap (with a word ending on the final 32nd column), and strings with word(s) longer than the screen width.

The first version I presented tried to avoid using any additional strings. Simply doing something like A$=A$+”BACON” causes a new larger string buffer to be allocated, the original data copied over, new data appended, and original string assigned to it and the old one released. I wanted to avoid that, so my routine worked just by printing out sections of the original string.

The end result was a routing that worked, but due to the nature of BASIC, was quite slow. Text did not just appear – it paused a bit before printing each line. In my test program, this was a bit annoying, so I decided to see if I could make it a bit faster.

While it’s true that the CPU overhead of doing string manipulation is significant, much more CPU time is spent processing tokenized BASIC. If you could save some BASIC time by doing string manipulation, you should come out ahead with a faster problem.

First, here is the test I am using for both my word wrap routines:

10 CLEAR200
20 CLS
30 INPUT"SCREEN WIDTH [32]";WD
40 IF WD=0 THEN WD=32
50 INPUT"UPPERCASE ([0]=NO, 1=YES)";UC
60 TIMER=0:TM=TIMER
70 PRINT "SHORT STRING:"
80 A$="This should not need to wrap.":GOSUB 1000
90 PRINT "LONG STRING:"
100 A$="This is a string we want to word wrap. I wonder if I can make something that will wrap like I think it should?":GOSUB 1000
110 PRINT "WORD > WIDTH:"
120 A$="123456789012345678901234567890123 THAT WAS TOO LONG TO FIT BUT THIS IS EVEN LONGER ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234 SO THERE.":GOSUB 1000
130 PRINT"TIME TAKEN:"TIMER-TM
140 END

And this is the updated original word wrap routine that does not allocate any extra strings. I have also renamed the variables in my subroutines, starting all the temporary variables with Z (ZS for start of string, ZE for end of string, etc.). I did this to avoid conflicts with my larger program.

1000 REM WORD WRAP V1
1010 '
1020 'IN : A$=MESSAGE
1030 ' UC=1 UPPERCASE
1040 ' WD=SCREEN WIDTH
1050 'OUT: LN=LINES PRINTED
1060 'MOD: ZS, ZE, ZP
1070 '
1080 LN=1
1090 IF A$="" THEN PRINT:RETURN ELSE ZS=1
1100 IF UC>0 THEN FOR ZP=1 TO LEN(A$):ZP=ASC(MID$(A$,ZP,1)):IF ZP<96 THEN NEXT ELSE MID$(A$,ZP,1)=CHR$(ZP-32):NEXT
1110 ZE=LEN(A$)
1120 IF ZE-ZS+1<=WD THEN PRINT MID$(A$,ZS,ZE-ZS+1);:IF ZE-ZS+1<WD THEN PRINT:RETURN
1130 FOR ZE=ZS+WD TO ZS STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
1140 IF ZE<ZS THEN ZE=ZS+WD-1
1150 PRINT MID$(A$,ZS,ZE-ZS+1);
1160 IF ZE-ZS+1<WD THEN PRINT
1170 LN=LN+1
1180 ZS=ZE+1+ZP
1190 GOTO 1110
CoCo_wordwrp1_mem
Size of BASIC word wrap program version 1.

When I start up a 32K (or larger) CoCo 1/2, PRINT MEM shows 22823 bytes available (some memory is reserved for graphics screens, so there are ways to get even more memory to BASIC if you aren’t doing graphics).

After loading the program, I check memory again and determined this version took 1101 bytes.

CoCo_wordwrp1_time
BASIC word wrap program version 1 output.

When I run the program, it handles all the test cases as I expected, and reports that the time taken as 113 (counts of the TIMER, with each number being based on the screen interrupt – 60hz on the US machines. Is it different on PAL machines that run at 50mhz?)

This gives me an idea of large the program is and how fast it is to run.

I then took this version and converted it (keeping the lines the same as much as possible) to one that used LEFT$() and RIGHT$() to chop the input string up as it went, making the code simpler at the cost of pushing more work on the BASIC string manipulation routines. It looked like this:

1000 REM WORD WRAP V2
1010 '
1020 'IN : A$=MESSAGE
1030 ' UC=1 UPPERCASE
1040 ' WD=SCREEN WIDTH
1050 'OUT: LN=LINES PRINTED
1060 'MOD: ST, EN
1070 '
1080 LN=1
1090 IF A$="" THEN PRINT:RETURN
1100 IF UC>0 THEN FOR ST=1 TO LEN(A$):EN=ASC(MID$(A$,ST,1)):IF EN<96 THEN NEXT ELSE MID$(A$,ST,1)=CHR$(EN-32):NEXT
1110 ZE=LEN(A$)
1120 IF ZE<=WD THEN PRINT A$;:IF ZE<WD THEN PRINT:RETURN
1130 FOR ZE=WD+1 TO 1 STEP-1:IF MID$(A$,ZE,1)<>" " THEN NEXT:ZP=0 ELSE ZE=ZE-1:ZP=1
1140 IF ZE=0 THEN ZE=WD
1150 PRINT LEFT$(A$,ZE);
1160 IF ZE<WD THEN PRINT
1170 LN=LN+1
1180 A$=RIGHT$(A$,LEN(A$)-ZE-ZP)
1190 GOTO 1110
CoCo_wordwrp2_mem
Size of BASIC word wrap program version 2.

When I load this version and check memory, it gives me 1058 — 43 bytes less than the first version. This also means there will be less bytes for the interpreter to have to process so it might run faster, too.

CoCo_wordwrp2_os
BASIC word wrap program version 2 out of string space error.

However, when I tried to run it, the program crashed with an Out of String Space error (?OS ERROR). Even though the BASIC program was smaller, it was now needing to allocate additional/temporary strings to do things like A$=LEFT$(A$,xxx). By default, Color BASIC sets aside 200 bytes for strings, and that was not enough for this version.

CoCo_wordwrp2_time
BASIC word wrap program version 2 output.

To fix this, I had to change the “CLEAR 200” on line 10 to be something larger, like “CLEAR 300”. That was enough for the program to run and I saw it’s time used was around 106. This was only slightly faster than the method that did not require extra string space so it really would only be useful if you needed that teeny tiny speed improvement and had the extra memory to spare.

Now, if the extra string space required was no more than the size saved by the smaller code, it would end up being an advantage. For my program, I do not expect memory to be an issue (it will be a fairly small program) so I may very well end up optimizing as many functions as I can for speed over memory.

Speaking of optimizations, coming up soon will be some notes on easy ways you can optimize BASIC to make your program take up less memory and/or run faster.

Until then, I’ll see you at the “OK” prompt…