KLBasic for the Atmel AVR devices
(Last modified 9 May 2012)


Years ago, Gordon Doughman created a target-resident BASIC interpreter for the Motorola 68hc11 devices.  The ability to create, debug, and run programs on the target, without having to have expensive dev software (or even a PC; just an RS-232 monitor) helped a lot of programmers get started in embedded design.  His BASIC had a lot of features for its day and, given the slow MCU clock rates of the time, gave good enough performance to get things done.  I never used Gordon's BASIC, but knew people who had and they spoke highly of his work.

Many of the features of Gordon's BASIC are still useful today.  I had been wanting to create a target-resident development environment for the Atmel AVR devices for some time.  I worked on the amforth project, contributing the initial user's gude, and I like using amforth.  But I wanted another option, and I kept thinking about Bill Gates' BASIC interpreter for the TRS-80 Model 100.  It reminded me of Gordon't work so I did some checking on the web.  I found the assembly language source for his BASIC on the web.  Thank you, Gordon, for releasing the source!



Updated 9 May 2012
I have decided to release the source for KLBasic to the community.  You can find the source files for the core (target-independent) routines here.  You can find the source files for the AVR target implementation here.

The AVR target archive contains makefiles for creating KLB images for the ATmega128, '90can128, and 'mega1284p MCUs.  If you use AVRStudio4 for buiilding your KLB code, you should first move these makefiles into the /default folder in your project folder before building.

The core file set contains two readme files.  readme.txt is Gordon Doughman's original release notes for BASIC11.  readme2.txt is my release notes for KLBasic.  Please read both files before attempting to edit KLB.

As mentioned in my release notes, please create your own name for your version of this Basic interpreter.  I will retain the name KLBasic for my own version.  This is to prevent conflicts later on between any changes I make to my version of this Basic interpreter and your changes to your version.

When I started work on KLBasic back in 2008, I was trying to solve what I saw as a long-standing problem.  I did not have a target-resident tool for working with MCUs that provided immediate feedback and interaction with the low-level parts of the device.  There are variants of Forth, of course, and I have done some work with amforth for the AVRs.  Very much low-level, but I also wanted a tool that was friendly for beginners, and Forth can have a steep learning curve.

And, frankly, I have very fond memories of tools from decades ago, such as TinyBasic, Radio Shack's TRS-80 Model 100, and QuickBasic.  Those tools started my interest in microcontrollers, which has not slackened yet.  Who knows, perhaps KLBasic or some variant of it will inspire a new generation of kids to get started with MCUs.

My goal with this release is to hand to the community the source code for a working, target-resident interpreter/compiler for a simple language.  The hope is that others will build on this work, taking the design in new directions and adding new features and improvements.

The files in the AVR set show how to create a target-specific implementation of KLBasic.  To port the interpreter to a new device, you will need to create your own equivalent of targetavr.c, which will include the necessary functions modified to run on your target hardware.  I hope there is enough information in the release notes and in my AVR file set to show how you can do this.

When you create your new version, please include within the source files acknowledgement of Gordon's original BASIC11 and my work on KLBasic.

Unfortunately, the code is not as clean as I would like it to be.  I wanted to spend time to clean it up, but that task would never end and it was important that others have a chance to start playing with the code, so I decided to just let it go as-is.

I will try to answer questions or provide guidance if I can; drop me an email if you get stuck.

Good luck and have fun...


Updated 2 May 2012
I just updated the 'xmega .zip files below, as I noticed I had built with -O0 rather than -Os, which basically caused the 'xmega version at 32 MHz to run much slower than the 'mega1284p version at 20 MHz.



Updated 30 Apr 2012
I have updated the KLBasic image for the 'xmega128a1 (Xmega-A1 Xplained board); link to the new hex file can be found below.  This is version 0.8 of the 'xmega AVR code, and adds support for the EEPORM configuration scheme in the following paragraph.  HOWEVER, you should always use a F_CPU value of 32000000 for the 'xmega128 EEPROM configuration!  The current build always uses a core clock of 32 MHz but the console baud rate is calculated using the EEPROM value for F_CPU.  If these two values differ, your console baud rate will be incorrect.

I have updated the KLBasic images for the 'mega128, the 'mega1284p, and the '90can128; links to the new hex files can be found below.  Version 0.8 of the AVR release adds:
Creating a suitable EEPROM image for configuring KLBasic for your target is simple.  Here is an example project that shows how to set up the EEPROM array.  You can compile with AVR GCC (such as AVRStudio4), then copy ONLY the resulting .eep file to your target.  Do NOT copy the resulting .hex file, as it is not needed and will simply overwrite any existing KLBasic image.  Reboot your target and your new console baud rate and/or system frequency will take effect.



Updated 15 Apr 2012

I have updated the KLBasic images for the 'mega128, the 'mega1284p, and the '90can128; links to the new hex files can be found below.  The new version of KLBasic adds:
NOTE:  I have not yet updated the 'xmega128a1 version!

Note also that this will probably be the last release for the 'mega128 and '90can128.  They simply have so little RAM (used for storing the program's tokens) that it just isn't worth the effort.  I'll concentrate instead on the '1284p and the 'xmega128a1 versions.



KLBasic
My BASIC is a C rewrite of Gordon's assembly language program.  Because the interpreter is written in C, the code size is larger than a good assembly language equivalent.  For example, the version for the 'mega1284p is just under 29 KB.

I tried to stay as faithful to Gordon's original code as possible.  I have added a few things that I find useful in embedded development, and I have also stuck in a feature or two that I think are an improvement on the older BASIC design.  Here is a short list of features:
I don't have everything implemented yet, however.  Some of the items missing include:
Note that Gordon's design kept the interpreted code in RAM.  I followed this convention, which means that on the older AVR MCUs, program space can be limited to just a few hundred bytes.  Additionally, most AVR MCUs don't have a lot of EEPROM, which limits the size of the program you can save to that memory.  The best devices to use for KLBasic are newer chips, such as the ATmega1284p or the ATxmega128a1. 



Trying it out
For those of you who want to dabble with KLBasic, I've built a few images.  In all cases, you need to hook up a serial terminal (such as Hyperterm or TeraTerm) to USART0 of the target; set the comm program to 38.4 Kb, 8N1.  Apply power to the target and you should see the header and an input prompt.

Since there is no full-screen editor, you need to use line numbers for your BASIC program.  (Yes, I know, that is so '80s, but without a common screen editor, you're stuck with it).  Here is a sample program for blinking an LED.  This program takes 321 bytes of program space and 38 bytes of data space.  Although the comment calls out the '90can128, this program should run as-is on any of the 'mega MCUs listed below.

 1000 '
 1010 '  Program to blink an LED of an ATmega90CAN128
 1020 '
 1200 p = addr(porta) :  ' use indirection just for fun
 1210 ddra = $ff :       ' make all port lines outputs
 1220 led = $01
 1300 while 1
 1310   timer0 = 250 :      ' delay of 250 msecs
 1320   if timer0 <> 0 then 1320
 1330   @p = @p eor led
 1340 endwh

The above example shows several important features of KLBasic.  Line 1200 loads a variable with the address of a port; all subsequent port accesses use the address in the variable, rather than the name of the port.  So if you want to change the port you are using, just change the address loaded in line 1200.  Note that I could have done the same thing with the data-direction register in line 1210.  Line 1330 shows the use of the eight-bit indirection operator @.  Note that the operator works on both sides of the assignment.  This line reads the 8-bit contents of the address in variable P, exclusive-ORs that value with the value in variable LED, then writes the new 8-bit value to the address in variable P.  No PEEK, no POKE, and I think this is much cleaner.  Note that I'm not claiming to have invented this; it's so obvious that I'm sure it's used in some other BASIC somewhere.

To start, enter NEW to remove any existing program.  After you enter the program, save it somewhere, such as flash file 0 (SAVE  FL0) or EEPROM (SAVE  EE).  Then enter RUN to see the LED blink.  After you've enjoyed the light show for a while, hit CTRL-C on your comm program and KLBasic will break the program and return to the command prompt.  If you want to get fancy, you can tell KLBasic to autostart the program (AUTOST  FL0  or  AUTOST  EE), then reset your target; your program will start right up.  Disable the autostart by entering AUTOST  OFF.  To recover the program, use LOAD FL0 or LOAD EE, as appropriate.

KLBasic is fast enough to do simple embedded devices such as HVAC, hot-house controllers, or simple robots.  The TRS-80 Model 100 BASIC on a 2.45 MHz 8085 could hit 300 empty loops per second.  KLBasic on a 32 MHz ATxmega128a1 hits about 70,000 empty loops per second.

This is a bare beginning to the user manual.  Hopefully, there is enough here to get you started with KLBasic.  I will try to get a manual finished soon, but can't promise when you'll see it here.  Just keep checking in...


Various KLBasic images
ATmega90CAN128 -- 1500 bytes of program space, 200 bytes of variable (data) space, 400 bytes of array space.  Built for a 16 MHz clock.  Includes all PORT, DDR, and PIN registers, all A/D registers, and all SPI registers; if you need access to others, use their addresses from the Atmel docs.  Supports three flash files (FL0, FL1, and FL2) and EEPROM file (EE).

ATmega1284p -- 10,000 bytes of program space, 400 bytes of variable (data) space, 1000 bytes of array space (these values changed in the 15 Apr 2012 build).  Built for a 20 MHz clock.  Includes all PORT, DDR, and PIN registers, all A/D registers, and all SPI registers; if you need access to others, use their addresses from the Atmel docs.  Supports three flash files (FL0, FL1, and FL2) and EEPROM file (EE).

ATmega128 -- 1500 bytes of program space, 200 bytes of variable (data) space, 400 bytes of array space.  Built for a 16 MHz clock.  Includes all PORT, DDR, and PIN registers, all A/D registers, and all SPI registers; if you need access to others, use their addresses from the Atmel docs.  Supports three flash files (FL0, FL1, and FL2) and EEPROM file (EE).

ATxmega128a1 -- (Xplained dev board, not tested on the older Xplain board)  16,000 bytes of program space, 2000 bytes of variable (data) space, 2000 bytes of array space.  Built for the internal 32 MHz clock.  Includes all PORT, DDR, and PIN registers, all A/D registers, and all SPI registers; if you need access to others, use their addresses from the Atmel docs.  Supports three flash files (FL0, FL1, and FL2) in external serial flash (requires a Microchip 25vf040b device soldered on the pads labeled AT25DF on the Xplained PWB) and EEPROM file (EE).  Note that you have access to all 8 MB of on-board SDRAM by using the indirection operators.

Here is the above LED blinking program rewritten for the 'xmega128a1.  This version blinks LED0 on the Xplained dev board:

 1000 '
 1010 '  Program to blink an LED of an ATxmega128a1
 1020 '
 1200 p = addr(porte_out)
 1210 led = $01
 1220 porte_dirset = led
 1300 while 1
 1310   timer0 = 250 :      ' delay of 250 msecs
 1320   if timer0 <> 0 then 1320
 1330   @p = @p eor led
 1340 endwh


Here is a larger program used to test some of KLBasic's capabilities.  This program will only run in the 'mega1284p or 'xmega128a1 devices.  Note that line 2900 uses the address of an open area of RAM; the value here ($fff0) works for the 'xmega128a1.  To run on the 'mega1284p, you should change this value to something in the legal range, such as $3ff0.  (The following file was updated with the 15 Apr 2012 release; added code to read/write to EEPROM (see lines 3000-3030).)

  110 '  Test of KLBasic functions and operators
 120 '
 1020 dim arr(10)
 1030 failed = 0
 1100 data -5, -4, -3, -2, -1, 0
 1105 data 1, 2, 3, 4, 5
 1110 data 6, 7, 8, 9, 10, 11, 12, -100
 1120 if 3000 * 2 <> 6000 then print "Fail in multiplication" : gosub 9000
 1130 if 3000 / 2 <> 1500 then print "Fail in division" : gosub 9000
 1140 if -3000 * 2 <> -6000 then print "Fail in multiplication (2)" : gosub 9000
 1150 if -3000 / 2 <> -1500 then print "Fail in division (2)" : gosub 9000
 1160 if 67 mod 10 <> 7 then print "Fail in MOD" : gosub 9000
 1170 if -67 mod 10 <> -7 then print "Fail in MOD (2)" : gosub 9000
 1200 if $40 <> 64 then print "Fail in hex notation" : gosub 9000
 1210 if ($55 and $ff) <> $55 then print "Fail in AND" : gosub 9000
 1220 if ($55 and $00) <> $00 then print "Fail in AND (2)" : gosub 9000
 1230 if ($55 or $ff) <> $ff then print "Fail in OR" : gosub 9000
 1240 if ($55 or $00) <> $55 then print "Fail in OR (2)" : gosub 9000
 1250 if ($55 eor $ff) <> $aa then print "Fail in EOR" : gosub 9000
 1260 if ($55 eor $00) <> $55 then print "Fail in EOR (2)" : gosub 9000
 1280 if not $55 <> $ffffffaa then print "Fail in NOT" : gosub 9000
 1300 if -$55 <> $ffffffab then print "Fail in negation" : gosub 9000
 1400 c = 0 : for n = 1 to 10 : c = c + n : next n
 1410 if c <> 55 then print "Fail in FOR loop; c = "; c : gosub 9000
 1420 c = 0 : for n = 10 to 1 step -1 : c = c + n : next n
 1430 if c <> 55 then print "Fail in FOR loop, negative STEP" : gosub 9000
 1440 c = 0 : for n = -1 to -10 step -1 : c = c + n : next n
 1450 if c <> -55 then print "Fail in FOR loop, negative STEP (2)" : gosub 9000
 1500 c = 0 : n = 1
 1502 while n < 11
 1504   c = c + n : n = n + 1
 1506 endwh
 1510 if c <> 55 then print "Fail in WHILE loop" : gosub 9000
 1600 print "Using GOSUB to add values in a loop."
 1605 c = 0 : for n = 1 to 10 : gosub 9200
 1610 next n : print
 1630 if c <> 55 then print "Fail in FOR/GOSUB loop" : gosub 9000
 1700 print "Restoring data (first time)..." : restore
 1702 print "Reading data and summing values"
 1705 c = 0 : for n = 0 to 10 : read x : c = c + x : next n
 1710 if n <> 11 then print "Fail in FOR/READ loop, index is wrong" : gosub 9000
 1720 if c <> 0 then print "Fail in FOR/READ loop, sum is wrong" : gosub 9000
 1800 print "Restoring data (second time)..." : restore
 1802 print "Reading data and summing values"
 1810 c = 0 : for n = 0 to 10 : read x : c = c + x : next n
 1820 if n <> 11 then print "Fail in RESTORE/FOR/READ loop, index is wrong" : gosub 9000
 1830 if c <> 0 then print "Fail in RESTORE/FOR/READ loop, sum is wrong" : gosub 9000
 1900 print "Turning on trace"
 1910 tron
 1920 for n = 0 to 3
 1930   c = 0
 1940 next n
 1950 troff
 1960 print : print "Trace is now off"
 2000 print "Testing ON-GOTO"
 2010 print "ON-GOTO not implemented, test skipped"
 2100 c = 1 : gosub 9300
 2110 c = -1 : gosub 9300
 2120 c = $7fffffff : gosub 9300
 2130 c = $7fffffff + 1 : gosub 9300
 2140 c = $12345678 : gosub 9300
 2200 print "Using timer0 for one-second delay"
 2210 timer0 = 1000
 2220 while timer0 <> 0
 2230 endwh
 2240 print "Using timer1 for one-second delay"
 2250 timer1 = 1000
 2260 while timer1 <> 0
 2270 endwh
 2280 print "Using timer2 for one-second delay"
 2290 timer2 = 1000
 2300 while timer2 <> 0
 2310 endwh
 2320 print "Using timer3 for one-second delay"
 2330 timer3 = 1000
 2340 while timer3 <> 0
 2350 endwh
 2400 print "Filling array with 0-9"
 2410 for n = 0 to 9 : arr(n) = n : next n
 2420 for n = 0 to 9 : print arr(n); : next n
 2430 print
 2500 print "Input a number"; : input c
 2510 print "You entered "; c
 2520 input "(Using input() with string) Input a number", c
 2530 print "You entered "; c
 2600 print "Program will now stop; enter CONT to continue."
 2610 stop
 2620 print "Program resumes..."
 2700 if abs(1) <> 1 then print "Fail in ABS(1)" : gosub 9000
 2710 if abs(-1) <> 1 then print "Fail in ABS(-1)" : gosub 9000
 2720 if abs(-$7fffffff) <> 1 then print "Fail in ABS(-$7fffffff)" : gosub 9000
 2730 if abs(-555) <> 555 then print "Fail in ABS(-555)" : gosub 9000
 2800 if sgn(0) <> 0 then print "Fail in SGN(0)" : gosub 9000
 2810 if sgn(20) <> 1 then print "Fail in SGN(20)" : gosub 9000
 2820 if sgn(-33) <> -1 then print "Fail in SGN(-33)" : gosub 9000
 2900 c = $37ff : ' point to open area of RAM
 2910 @32 c = $12345678
 2920 if @32 c <> $12345678 then print "Fail in @32" : gosub 9000
 2930 if @16 c <> $5678 then print "Fail in @16" : gosub 9000
 2940 if @c <> $78 then print "Fail in @" : gosub 9000
 3000 eep32(4090) = $12345678
 3010 if eep32(4090) <> $12345678 then print "Fail in eep32()" : gosub 9000
 3020 if eep16(4090) <> $1234 then print "Fail in eep16()" : gosub 9000
 3030 if eep(4090) <> $12 then print "Fail in eep()" : gosub 9000
 8000 '
 8010 ' end of mainline program; put subroutines below here
 8020 '
 8910 print "Test is complete, fail count = "; failed
 8999 end
 9000 '
 9010 ' Common code for counting failures
 9020 '
 9100 failed = failed + 1
 9110 return
 9200 '
 9210 ' helper subroutine for testing loops
 9220 '
 9230 c = c + n
 9235 print ".";
 9240 return
 9300 '
 9310 ' print c in various forms of hex
 9320 '
 9350 print "c = ";c; " HEX() = "; hex(c); " HEX4() = "; hex4(c); " HEX2() = "; hex2(c)
 9360 return



Home