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:
- Reads F_CPU and console baud rate from EEPROM at boot time.
You can now customize your target's KLBasic to use any crystal
frequency and console baud rate, simply by creating a 12-byte EEPROM
file and copying it to your target device in the lowest 12 bytes of
on-chip EEPROM.
- Fixed a bug that caused a crash if you entered an immediate-mode
command containing a mid-EOL (':') followed by one or more spaces.
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:
- Ability to list port names (LIST PORTS), list variables (LIST
VARS), and list the first line of each program stored in flash memory
(LIST FILES)
- Multibyte reads/writes involving EEPROM now always use MSB first
- Various bug fixes
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:
- 32-bit integers
- Variable names to 14 chars
- Single-dimension integer arrays; must define arrays with the DIM
statement
- Saving of programs to EEPROM, internal flash, or (depending on
the device and your design) external serial flash or EEPROM
- Autostart of a saved program
- Use of Ctrl-C to break a program during execution
- Trace ability
- PRINT with lots of options, including literal strings and
function calls
- Hex numbers supported (start with $, as in $1234)
- Output of hex numbers supported (HEX, HEX2, and HEX4 functions)
- Access to common AVR ports by name (porta = $ff)
- Four 32-bit down-counting timers for event timing
- 32-bit up-time counter; counts msecs since last reset
- Variable indirection using @, @16, and @32 (replaces PEEK and
POKE)
- ADDR() function for finding the address of ports and vaiables
I don't have everything implemented yet, however. Some of the
items missing include:
- No BASIC-level interrupt support
- Console I/O is limited to the first USART (no IODEV)
- No string variables (though strings are allowed as part of PRINT
and INPUT)
- PEEK and POKE have been removed
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