You can do just about anything with FANUC’s TP programming language, but there are some things it just can’t do. Enter KAREL.
KAREL is a lower-level language very similar to Pascal. It features strongly typed variables, constants, custom types, procedures, functions, and gives you access to all sorts of useful built-ins for things you may not be able to do with TP. (By the way, if you’re interesting in TP programming, please check out the book I wrote on programming FANUC robots.)
KAREL is a compiled language; the source must be translated from a KAREL source file (.KL) into p-code (.PC) before it can be loaded and executed on the controller. Once your KAREL program has been loaded onto the controller, it acts like a black box; you can’t see the source or step through it like a TP program.
As of the R-30iB controller your robot must have the KAREL software option in order to load your own custom KAREL programs.
Hello, world
We all know that outputting “Hello, world” is absolutely vital to learning any new programming language. Let’s make it happen in KAREL:
-- hello_world.kl
PROGRAM hello_world
BEGIN
WRITE('Hello, world!',CR)
END hello_world
Program Structure
Let’s take a quick look at this program. The PROGRAM statement followed by the name of the program must be the first statement in any KAREL program. The END statement must use the same identifier.
Any statements within the BEGIN and END statements are executed by the KAREL program. You can probably guess what the WRITE statement does. You can optionally provide it with a FILE to write to, but by default it will write to the default TPDISPLAY, the USER screen. Each item you want to write can be separated by commas, and the CR stands for ‘carriage return.’ Any subsequent WRITE statements will take place on the next line.
Translating
You can either edit and translate your KAREL source files directly in ROBOGUIDE or use the ktrans command line utility that comes with it.
> ktrans hello_world.kl
Running your program
You can test your program in ROBOGUIDE or on a real controller. Just transfer the .PC file to the controller via your favorite method (FTP, USB stick, PCMCIA card, etc.), then run it or CALL it from a TP program. If you go to the USER screen (Menu > 9), you should see “Hello, world!” on the screen.
Details
Here’s how a KAREL program is structured:
- PROGRAM statement
- Translator directives (optional)
- CONST, TYPE, and/or VAR declarations (optional)
- ROUTINE declarations (optional)
- BEGIN
- Executable statements (optional)
- END statement
- ROUTINE declarations (optional)
Let’s create a quick program that demonstrates all of these options.
-- hello.kl
PROGRAM hello
-- this is a comment
-- translator directive
%COMMENT = 'Hello'
-- const declarations
CONST
LANG_EN = 1
LANG_ES = 2
LANG_FR = 3
-- custom type
TYPE
person_t = STRUCTURE
name : STRING[16]
lang_id : INTEGER
ENDSTRUCTURE
-- program vars
VAR
people : ARRAY[3] OF person_t
-- custom routines
-- Returns the proper greeting for a given language
ROUTINE greeting(lang_id : INTEGER) : STRING
BEGIN
SELECT lang_id OF
CASE(LANG_EN):
RETURN('Hello')
CASE(LANG_ES):
RETURN('Hola')
CASE(LANG_FR):
RETURN('Bonjour')
ENDSELECT
END greeting
-- Greets a person in their language
--
-- Example:
-- person.name = 'John'
-- person.lang_id = LANG_EN
--
-- greet(person)
-- # => Hello, John
ROUTINE greet(person : person_t)
BEGIN
WRITE(greeting(person.lang_id),', ',person.name,CR)
END greet
BEGIN
-- setup people[] array
-- notice KAREL arrays are 1-based, not 0-based.
people[1].name = 'John'
people[1].lang_id = LANG_EN
people[2].name = 'Jose'
people[2].lang_id = LANG_ES
people[3].name = 'Jaques'
people[3].lang_id = LANG_FR
-- greet each person
greet(people[1])
greet(people[2])
greet(people[3])
END hello
-- you could also put your routines here
There’s a lot going on in this program, so let’s go through it section by section.
Translator Directives
The %COMMENT directive changes the comment that displays on the right of the teach pendant SELECT menu.
CONST Declarations
Constants are values that cannot be changed in the program. I’ve created a unique ID for each language that I know won’t be changed.
TYPE Declarations
I created a custom type for a person. Each person data structure can have a name of type STRING[16] and a language_id of type INTEGER. You can access elements of your custom type with “dot” syntax (e.g. type.element).
VAR Declarations
I defined a variable named people that is an array of up to 3 person_t data structures.
ROUTINE Declarations
I defined two routines: greeting, a FUNCTION which returns a STRING value, and the PROCEDURE greet, which just WRITEs a message to the screen.
Executable Statements
Since I’ve created a 3-slot array of people, I have to initialize those values. (Alternatively, you could set the KAREL VARs directly from the DATA screen, but this is more demonstrative for this contrived example.) I then call the greet() PROCEDURE, which takes a person_t as an argument.
The output should be:
Hello, John.
Hola, Jose.
Bonjour, Jaques.
Gotchas
KAREL is an old language, so it has some pretty nasty limitations that are remnants of an era when memory was hard to come by.
Character Limits
Identifiers (program name, constant names, variable names, routine names, etc.) are all limited to 12 characters. Naming is hard. It’s even harder when you have to smash things into 12 characters.
Program Length Limits
If your program gets too long, you’ll have to split it up into multiple programs.
Variable Clashes
You may run into issues if you change a variable’s type or do something like expanding an array, etc. If you run into any errors like this, you’ll have to delete both the .PC and .VR file for your program before re-loading it.
Cumbersome API
Many of the built-in procedures aren’t exactly easy or intuitive to use. Take for example, the GET_VAR built-in for getting a variable:
GET_VAR(entry, prog_name, var_name, value, status)
Personally, I like to use assignment over calling procedures. I’d generally write some general-purpose ROUTINEs to simplify things e.g.
ROUTINE get_int(prog_name : STRING[16]; var_name : STRING[16]) : INTEGER
VAR
entry : INTEGER
i : INTEGER
status : INTEGER
BEGIN
GET_VAR(entry, prog_name, var_name, i, status0
IF status<>0 THEN
WRITE('Could not get int [',prog_name,']',var_name,'status:',status,CR)
ENDIF
RETURN(i)
END get_int
-- which option looks better?
-- option 1:
GET_VAR(entry,'test_prog','test_int',my_int,status)
IF status<>0 THEN
WRITE('Could not get [test_prog]test_int status:',status,CR)
ENDIF
-- option 2:
my_int = get_int('test_prog','test_int')
Lack of Support Built-Ins
While KAREL does provide quite a few built-in procedures, it’s missing many common features you might expect from a programming language for things like Arrays, Strings, etc. You may find yourself implementing rudimentary methods over and over again before developing your own environment libraries.
Conclusion
This is just the tip of the KAREL iceberg. Hopefully this example will get you thinking how KAREL may be the right choice for certain programming tasks where TP may not be so good. If you really want to learn KAREL, see if you can get a copy of the KAREL manual from your local FANUC rep or integrator.
More on KAREL
I’ve written several other articles on FANUC KAREL programming. If you are already familiar with KAREL or you want to look at some more programming examples, check out KUnit, an open-source unit testing framework for FANUC KAREL. If you want to learn more about unit testing, read this article about testing FANUC TP and KAREL code.
TP Programming
If you enjoyed this post and are interested in TP Programming, please checkout the book I wrote, Robot Whispering: The Unofficial Guide to Programming FANUC Robots.