Regardless of your ROM version, you can load BASIC into the Gigatron using the Pluggy McPlugface keyboard adapter. This works because the adapter has a BASIC image in it, and it can speak the Loader protocol. To load BASIC using this method, first start Loader from the main menu and then hit [Ctrl-F2].
If your board runs ROM v3, it’s easier to select BASIC directly from the main menu.
*** Tiny BASIC v2 9504 bytes free Ok █
At startup, BASIC counts and displays the amount of free program memory. It prompts ‘Ok’ and shows a cursor symbol to indicate that it’s ready to receive commands.
The Gigatron has 32K of working memory or RAM. About 19K of that is taken by the video display. 3K is used for the BASIC interpreter and almost 1K is needed for things like system variables and stack space. This leaves a bit over 9K for BASIC programs and variables.
Tiny BASIC v2
Most of the commands are pretty standard, but every BASIC dialect is a bit different. The full set of supported commands by the Gigatron is:
Original Tiny BASIC: PRINT LET INPUT GOTO REM END IF/THEN GOSUB/RETURN USR() RUN LIST NEW Gigatron Tiny BASIC v2: AT PUT FOR/TO/NEXT MODE RND() CLS POKE/PEEK() SAVE LINE : ? ' % A(I)
We’ll walk through all of these next.
Lets type in a simple program:
10 rem Test program 20 print "I like Gigatron!" 30 goto 10 █
All program lines in BASIC begin with a line number that must be typed in as well. We normally use multiples of 10 to make it easier to insert lines later. If you enter a lower line number than the last one, BASIC will automatically insert it at the right place.
If you enter a line number that’s already present, the older one is removed. This way you can modify or delete a line.
Type LIST to see your program in the correct order.
RUN starts the entered program. Our BASIC is case-insensitive, so ‘run’ means the same as ‘RUN’.
run I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! I like Gigatron! ?Break error in 10 Ok █
The GOTO 10 command in line 30 keeps the program running in an infinite loop. Without it, the program would print the message just once and then end.
You can abort a running program with [Ctrl-C]. If you have a long program, you can stop LIST with [Ctrl-C] as well.
The REM (‘remark’) command is used for comments: whatever follows on the line is ignored.
The PRINT command writes text and numbers to the screen.
If you omit the line number, the command you typed will be executed immediately. This way you can quickly see what a command does.
print "Hello" Hello Ok █
For convenience, PRINT can be abbreviated to ‘?’. REM can be abbreviated to a single quote (‘).
?"The answer is:",42 The answer is: 42 Ok ?"abc";123 abc123 Ok █
PRINT arguments separated by a comma will be spaced apart when written to the screen. Arguments separated by a semicolon will be smashed together.
You can use AT to indent a given number of pixels.
at 80:print "@" @ Ok at 86:put 64 @ Ok █
As you can see, PUT writes a character with a given ASCII code (32…127).
Also note that you can place more than one command on a line as long as you separate them by a colon (‘:’). The line must not become wider than the screen.
If the line gets too long, a backslash (‘\’) is shown at the end to indicate the issue. Mind that the line will not have been accepted when this happens!
PRINT, LET and all other commands taking a number as an argument can also do calculations. You just give them an arithmetic expression that will be evaluated by the command each time it runs. Operators can be + (add), – (subtract), * (multiply), / (divide) and % (modulo).
The order of operation is as in school: left to right, but multiplication and division go before addition or subtraction. Use parentheses to enforce a different evaluation order.
print -1+2*(3+4*5) 45 Ok █
All calculations are done with integers. So 15 / 7 gives 2, not 2.142857. To get the remainder after division, use the modulo operator %.
print 15/7, 15%7 2 1 Ok █
With RND(n) you can randomly draw a number smaller than n.
print rnd(6) 4 Ok print rnd(6) 0 Ok █
Integers are signed and 16-bits wide, meaning that they can range from -32768 to 32767. Calculations quietly wrap around on overflow.
You can use variables to store the result of a calculation.
let y=1975 Ok let x=(2018-y)*12 Ok let y=0 Ok print x, y 516 0 Ok █
The LET keyword is optional and can be left out.
Variables are identified by a single letter and are case-insensitive as well: ‘a’ denotes the same variable as ‘A’.
INPUT prompts the user to type in new values for one or several variables.
input a,b a? 123 b? 456 Ok ?a+b 579 Ok █
With FOR, TO and NEXT you can let a variable walk through a range of numbers.
10 input n 20 for i=1 to 10 30 print i;"*";n;"=";i*n 40 next i run n? 7 1*7=7 2*7=14 3*7=21 4*7=28 5*7=35 6*7=42 7*7=49 8*7=56 9*7=63 10*7=70 Ok █
FOR loops can be nested if each loop uses its own variable.
10 for y=0 to 2 20 for x=0 to 2 30 print 3*y+x+1, 40 next x 50 print 60 next y run 1 2 3 4 5 6 7 8 9 Ok █
CLS clears the screen and moves the writing position to the top of the screen.
We’ve already seen AT x. When given a second argument y, it moves the writing position to the x, y pixel coordinate. The origin 0, 0 is the top-left corner.
This bouncing ball example uses AT to position the ball:
10 REM BOUNCING BALL 20 X=2:V=6 30 CLS:ATX,60 40 FOR I=1 TO 25 50 AT X:PUT 32:REM CLEAR 60 X=X+V 70 AT X:PUT 79:REM DRAW O 80 NEXT I 90 V=-V 100 GOTO 40
LINE x, y draws a line starting from the current writing position, x pixels to the right and y pixels down. These values may be negative, and lines wrap around the edges of the screen when needed.
With AT x, y you normally set the starting point first. If you omit it, the end point will become the next starting point.
Finally, MODE n changes the video mode, where n denotes the number of black scan lines per pixel line. It can be 0, 1, 2 or 3. The default mode is 1. The higher this number, the dimmer the screen, but the faster programs run. This is because applications only run when the Gigatron is not busy sending pixels to the screen.
The user can always change the video mode with the [Select] button on the game controller, or with the [PageDown] key on the keyboard.
PEEK and POKE
With PEEK and POKE you can access the memory directly. PEEK reads the byte value stored at the given memory address. POKE writes a byte value to the specified memory location.
You should be careful here: Most memory locations have a designated purpose for Gigatron operation, and unexpected things can happen if you interfere with those. But with some you can do useful things that aren’t possible with other BASIC commands.
For example, memory location 43 holds the pen color for writing text and drawing lines. There are 64 colors, numbered 0 to 63. They are RGB values calculated as Red + 4*Green + 16*Blue. Above 63 the colors just repeat, so 64 is the same as 0 (black).
print peek(43) 53 Ok █
53 is the default pen color, with Red=1, Green=1 and Blue=3
10 for i=0 to 19 20 x=4+8*i:y=3+6*i 30 poke43,peek(43)+7 40 at x,119:line 159-x,-y 50 next i 60 goto 10 run
This program draws lines in ever-changing colors.
Unlike the home computers of the early 1980s, in the Gigatron pixels are addressable as bytes. This means that it’s easy to plot points with the POKE command: just calculate the address, and write a color value there.
Default video memory starts at address 2048, with each 256-byte memory page holding a line of 160 pixels. The remaining 256 – 160 = 96 bytes in each page are used by BASIC and we’ll briefly discuss that later on as well.
So the simple way to make a central pixel black is:
cls Ok x=160/2:y=120/2 Ok poke 2048+y*256+x,0 Ok █
Video indirection table
While the above works, there’s a catch: the mapping from x, y coordinate to memory address is not necessarily fixed. The Gigatron video generation loop uses an indirection scheme that goes through a table. This video table makes it possible to scroll the screen with very little effort. The only reason the above example works is because the CLS command restores the vertical mapping to its default state.
The video table starts at memory address 256, and it has a pair of bytes for each of the horizontal 120 pixel lines, for a total of 240 bytes. The first byte in each pair, at the even address, is the page number where pixel data will be read for that line. It typically has a value between 8 and 127. The second byte is an offset in horizontal direction. This offset is relative to the previous line, and normally it’s just 0.
When BASIC scrolls the screen, it manipulates the video table instead of moving pixel data around, which would be very slow.
With that in mind, a somewhat better way to calculate a pixel address is as follows:
p=peek(256+2*y)*256+x Ok poke p,0 Ok █
This still ignores the horizontal offsets, but normally that’s ok.
It can be quite interesting to play with this video indirection scheme. For example, you can scroll the entire screen horizontally by writing to address 257.
poke 257,-10 Ok poke 257,0 Ok █
The first command shifted the entire screen 10 pixels to the right. The second command restored it.
If you want to turn the bottom 8 lines into a scroller, you can do this:
v=257+112*2 Ok fori=0to256:pokev,i:nexti Ok █
The garbage pixels you see scroll by are the bytes used by BASIC itself. The flashing pixel is where the loop variable ‘i’ is stored! It can be interesting to see how BASIC is organised by making its memory always visible:
cls Ok poke 257,160 Ok █
The guts of BASIC are now live on the display. Part of the BASIC interpreter itself can be seen on top. The input buffer is next and you can see its contents change while typing. Following is the BASIC program. Variables are at the bottom. The arrays stretch vertically in the unused area in between. You can experiment with running or changing a program to see what happens, and you can see the effect of vertical scrolling and the CLS command.
The four LEDs normally run in a fixed pattern, but you can change that. First, memory address 47 controls their speed. With the following commands they first run fast, then slow, and then go back to normal.
poke 47,5 Ok poke 47,120 Ok poke 47,9 Ok █
Much more interesting is that you can pause the sequence, and then control the lights directly yourself. Please note that the following doesn’t work in ROM v1, but there’s a work-around method explained here.
First we stop the sequence:
poke 46,1 Ok █
Then we turn off all LEDs. Address 20 controls which LEDs are on and off. You can set it to a value from 0 to 15 for each possible combination.
poke 20,0 Ok █
Only turn on the left LED:
poke 20,1 Ok █
All lights on:
poke 20,15 Ok █
Restart the standard sequence:
poke 46,0 Ok █
We’ve already seen FOR/TO/NEXT and GOTO as commands to control the order of execution. There are a few more useful ones:
IF/THEN compares two numbers and only executes the rest of the line when the comparison holds. For example, to set M to the smallest of A and B:
M=A: IF B<A THEN M=B
The comparison operator can be =, <, <=, >, >= or <>. The command after THEN can be any type or group of commands, as long as it all fits on the same line. It’s very common to use just a GOTO here to hop to another line number instead. In that case the THEN keyword is often omitted, as it’s optional anyway.
IF I<N GOTO 40
Specific for all Tiny BASICs is that the line number can come from a variable or even a complex expression. Some interesting control flows are possible with this.
GOSUB is much like GOTO, except that it remembers where it came from using the stack. The target lines are called a subroutine. Subroutines are handy when the same group of commands are needed in different parts of the program. You can keep the logic in one place, and use GOSUB to execute it where needed. After doing its thing the subroutine gives program control back to where it came from using the RETURN command.
GOSUBs can be nested as long as stack space permits. A ‘stack error’ will be given once that space is exhausted, or when a RETURN is attempted without a matching GOSUB. As the Gigatron can go more than 50 levels deep, this error most likely indicates a logic error somewhere in the BASIC program.
The last control command is END. This immediately stops the program and goes back to the ‘Ok’ prompt. Normally we don’t need END, because this also happens after executing the last line of a program.
With these we can make more complex programs, which will come in handy in the next section:
To replay the Gigatron start-up chime for 1 second:
poke 44,60 Ok █
Address 44 is the sound countdown timer. Normally it reads 0 and that means that all sound is off. If it’s positive however, all four software-generated sound channels will be forwarded to the extended output port. The timer counts down 60 times per second.
The four channels are in fact always busy, but address 44 controls if they will be heard or not. The sound you just heard is therefore the last sound that was programmed in them during reset: a G-major chord (G-3, G-4, B-4 and D-5) in sawtooth waveform.
Each sound channel is independent, and can be programmed with a waveform, a frequency and some harmonics altering attributes. You can’t really switch a channel off, but you can set their frequency to 0 which is effectively the same. For experimentation, lets first switch off channel 2, 3 and 4. This is easiest with a little program.
new 1 for c=2 to 4 2 p=c*256+252 3 poke p,0:poke p+1,0 4 next c run Ok poke 44,60 Ok █
You now effectively only heard the first channel.
To change the tone from a sawtooth into a pulse, type:
p=256+250 Ok poke p+1,2:poke 44,60 Ok █
The value 2 sets the pulse tone. The difference is easy to hear because it’s the clearest of all. You can also set 1 for a triangle, or back to 3 for a sawtooth. The value 0 creates a metallic sound that can be used to generate noise.
The following program changes the TTL computer into a single octave piano.
'Tiny Piano demo 1 for c=1 to 4 2 p=c*256+252 3 poke p,0:poke p+1,0 4 next c 5 mode 2:cls 6 ?" 2 3 5 6 7" 7 ?"Q W E R T Y U I" 8 poke p-2,2 9 k=peek(15):'serialRaw 10 if k>126 goto 9 11 gosub k 12 poke p,a:poke p+1,b 13 poke 44,30:goto 9 50 a=11 :b=9 :return 51 a=25 :b=10:return 53 a=16 :b=12:return 54 a=78 :b=13:return 55 a=35 :b=15:return 101 a=103:b=10:return 105 a=19 :b=17:return 113 a=73 :b=8 :return 114 a=57 :b=11:return 116 a=108:b=12:return 117 a=23 :b=16:return 119 a=80 :b=9 :return 121 a=53 :b=14:return
Address 15 is where the system stores, once per video frame, the contents of the 74HC595 (U39) shift register that serves as the input port. When a key is pressed on the keyboard, it will read as its ASCII code. The idle state is 255. If a game controller is connected, each bit corresponds to one of the buttons.
XXX TODO How to calculate the frequency
Quite advanced programs are possible once you can store values in arrays. Arrays are like lists or tables of values that are accessed by appending parenthesis to the array name with an index between them:
a(i) = x
In Tiny BASIC v2 arrays are one-dimensional, meaning there’s no such thing as a(i,j). Like variables, their names can be chosen from ‘a’ to ‘z’, but they are still different entities from the regular variables. Indexing starts at 0 and their size is determined by the remaining free memory. By default, the maximum index is 98 on the 32K system and this number becomes lower the more space your program needs for itself. If you try to read or write an array beyond the available space, you’ll get a ‘value error’. If your Gigatron has 64K, its arrays can hold over 200 items each.
There’s no need to set arrays up with a DIM command, as is the case in some other BASICs. Unlike regular variables, arrays will not be cleared at the start of a program.
Storing a program
Type SAVE to store the current program into the keyboard adapter’s or Arduino’s EEPROM area. Note that the Pluggy McPlugface keyboard adapter has just enough space for one small program of approximately 20-30 lines (0.5K). The Tiny Piano program above will (just) fit.
If you hookup an Arduino-compatible device, you can save larger programs. You can also send them to your computer over the Arduino’s USB cable.
While saving, video output might flicker, depending on your monitor brand. This is because the transmitted data is piggybacked on a signal that is shared with the video connector.
A ‘break error’ indicates that saving has failed. This can happen if a key was pressed while saving, or if the program is too large for the EEPROM.
Use NEW to erase the current program from memory. Be careful with this! There’s no command to undo this once you’ve hit enter.
Hit [Ctrl-F3] to load the stored program back into BASIC.
XXX TODO USR()
XXX TODO: Arduino / sendFile.py
XXX TODO list of examples
There are several small example programs in GitHub for study. Only TicTacToe is a large program. However, you don’t need to type it in, as it’s included in ROM v3. When it’s running you can interrupt it with [Ctrl-C]. However, when waiting for user input, stopping the program with [Ctrl-C] won’t work. In that case, the easiest way out is to type something non-sensical (such as ‘?’) to get a syntax error.
Did you know:
Microsoft’s first product was their BASIC interpreter for the Alair 8800 microcomputer.
There was a 4K version and a 8K version and it had to be loaded using paper tape.
Input and output were done through a Teletype standing next to the machine.
Many hobbyists preferred to copy it rather than buy it. This prompted Bill Gates to write an “Open Letter to Hobbyists”.
Dennis Allison designed Tiny BASIC as a free alternative to Microsoft’s BASIC.
The birth of Tiny BASIC also marked the start of the Dr. Dobbs Journal in January 1976.
It was typically just 2-3 kilobytes in size, making it easy to reimplement on different architectures.
There were versions created for many different microcomputer systems by many different programmers.
Tiny BASIC can be considered as the first success story for open-source software.
Our Tiny BASIC v2 is an extended version specially written for the Gigatron.
It’s written as a 16-bit vCPU assembly program using GCL notation. Like most implementations, it doesn’t use the Intermediate Language (IL) from the original proposal.
It’s still just under 3 kilobytes in size, and probably the only version with a line drawing command.
While -32768 is a valid 16-bit integer value, you’ll have to enter it as -32767-1 to avoid getting a syntax error.
The unary minus can’t immediately follow an operator. For example, the expression ‘a * -b’ must be entered as ‘a * (-b)’.
For compatibility with some versions of Tiny BASIC, ‘@’ is accepted as a valid variable name. So there are in fact 27 variables in total.
The backquote character (`) happens to be recognised as the “lowercase of @”. Using this as a variable name is just silly.
When asking for a value with INPUT, you can enter any valid expression and it will be evaluated on the spot. You can even reference program variables in it, use RND(), PEEK() etcetera.
When the INPUT command is waiting for user input, stopping the program with [Ctrl-C] won’t work. In that case, the easiest way out is to type something non-sensical to get a syntax error.
FOR loops won’t iterate correctly over more than 32768 values.
Tiny BASIC is a bit slow because it parses every line again and again while executing. In interpreter jargon: there’s no tokenizer. However, this simplicity also allowed it to be tiny and easy to port to new computers (such as the Gigatron).