441 lines
12 KiB
Plaintext
441 lines
12 KiB
Plaintext
/** @page primertcl OpenOCD TCL Primer
|
|
|
|
The @subpage scripting page provides additional TCL Primer material.
|
|
|
|
@verbatim
|
|
|
|
****************************************
|
|
****************************************
|
|
|
|
This is a short introduction to 'un-scare' you about the language
|
|
known as TCL. It is structured as a guided tour through the files
|
|
written by me [Duane Ellis] - in early July 2008 for OpenOCD.
|
|
|
|
Which uses the "JIM" embedded Tcl clone-ish language.
|
|
|
|
Thing described here are *totally* TCL generic... not Jim specific.
|
|
|
|
The goal of this document is to encourage you to add your own set of
|
|
chips to the TCL package - and most importantly you should know where
|
|
you should put them - so they end up in an organized way.
|
|
|
|
--Duane Ellis.
|
|
duane@duaneellis.com
|
|
|
|
****************************************
|
|
****************************************
|
|
|
|
Adding "chip" support - Duane Ellis July 5 - 2008.
|
|
|
|
The concept is this:
|
|
In your "openocd.cfg" file add something like this:
|
|
|
|
source [find tcl/chip/VENDOR/FAMILY/NAME.tcl]
|
|
|
|
For example...
|
|
source [find tcl/chip/atmel/at91/at91sam7x256.tcl]
|
|
|
|
You'll notice that it makes use of:
|
|
|
|
tcl/cpu/arm/<NAME>.tcl.
|
|
|
|
Yes, that is where you should put "core" specific things.
|
|
Be careful and learn the difference:
|
|
|
|
THE "CORE" - is not the entire chip!
|
|
|
|
Definition:
|
|
That "file" listed above is called a "CHIP FILE".
|
|
|
|
It may be standalone, or may need to "source" other "helper" files.
|
|
|
|
The reference [7/5/2008] is the at91sam7x256.tcl file.
|
|
|
|
****************************************
|
|
****************************************
|
|
=== TCL TOUR ===
|
|
Open: at91sam7x256.tcl
|
|
=== TCL TOUR ===
|
|
|
|
A walk through --- For those who are new to TCL.
|
|
|
|
Examine the file: at91sam7x256.tcl
|
|
|
|
It starts with:
|
|
source [find path/filename.tcl]
|
|
|
|
In TCL - this is very important.
|
|
|
|
Rule #1 Everything is a string.
|
|
Rule #2 If you think other wise See #1.
|
|
Reminds you of:
|
|
Rule #1: The wife is correct.
|
|
Rule #2: If you think otherwise, See #1
|
|
|
|
Any text contained inside of [square-brackets]
|
|
is just like `back-ticks` in BASH.
|
|
|
|
Hence, the [find FILENAME] executes the command find with a single
|
|
parameter the filename.
|
|
|
|
========================================
|
|
|
|
Next you see a series of:
|
|
|
|
set NAME VALUE
|
|
|
|
It is mostly "obvious" what is going on.
|
|
|
|
Exception: The arrays.
|
|
|
|
You would *THINK* Tcl supports arrays.
|
|
In fact, multi-dim arrays. That is false.
|
|
|
|
For the index for"FLASH(0,CHIPSELECT)" is actually the string
|
|
"0,CHIPSELECT". This is problematic. In the normal world, you think
|
|
of array indexes as integers.
|
|
|
|
For example these are different:
|
|
|
|
set foo(0x0c) 123
|
|
set foo(12) 444
|
|
|
|
Why? Because 0x0c {lowercase} is a string.
|
|
Don't forget UPPER CASE.
|
|
|
|
You must be careful - always... always... use simple decimal
|
|
numbers. When in doubt use 'expr' the evaluator. These are all the
|
|
same.
|
|
|
|
set x 0x0c
|
|
set foo([expr $x]) "twelve"
|
|
|
|
set x 12
|
|
set foo([expr $x]) "twelve"
|
|
|
|
set x "2 * 6"
|
|
set foo([expr $x]) "twelve"
|
|
|
|
**************************************************
|
|
***************************************************
|
|
=== TCL TOUR ===
|
|
Open the file: "bitsbytes.tcl"
|
|
|
|
There is some tricky things going on.
|
|
===============
|
|
|
|
First, there is a "for" loop - at level 0
|
|
{level 0 means: outside of a procedure/function}
|
|
|
|
This means it is evaluated when the file is parsed.
|
|
|
|
== SIDEBAR: About The FOR command ==
|
|
In TCL, "FOR" is a funny thing, it is not what you think it is.
|
|
|
|
Syntactically - FOR is a just a command, it is not language
|
|
construct like for(;;) in C...
|
|
|
|
The "for" command takes 4 parameters.
|
|
(1) The "initial command" to execute.
|
|
(2) the test "expression"
|
|
(3) the "next command"
|
|
(4) the "body command" of the FOR loop.
|
|
|
|
Notice I used the words "command" and "expression" above.
|
|
|
|
The FOR command:
|
|
1) executes the "initial command"
|
|
2) evaluates the expression if 0 it stops.
|
|
3) executes the "body command"
|
|
4) executes the "next command"
|
|
5) Goto Step 2.
|
|
|
|
As show, each of these items are in {curly-braces}. This means they
|
|
are passed as they are - KEY-POINT: unevaluated to the FOR
|
|
command. Think of it like escaping the backticks in Bash so that the
|
|
"underlying" command can evaluate the contents. In this case, the FOR
|
|
COMMAND.
|
|
|
|
== END: SIDEBAR: About The FOR command ==
|
|
|
|
You'll see two lines:
|
|
|
|
LINE1:
|
|
set vn [format "BIT%d" $x]
|
|
|
|
Format is like "sprintf". Because of the [brackets], it becomes what
|
|
you think. But here's how:
|
|
|
|
First - the line is parsed - for {braces}. In this case, there are
|
|
none. Then, the parser looks for [brackets] and finds them. The
|
|
parser then evaluates the contents of the [brackets], and replaces
|
|
them. It is similar to this bash statement.
|
|
|
|
EXPORT vn=`date`
|
|
|
|
LINE 2 & 3
|
|
set $vn [expr {1024 * $x}]
|
|
global $vn
|
|
|
|
In line 1, we dynamically created a variable name. Here, we are
|
|
assigning it a value. Lastly Line 3 we force the variable to be
|
|
global, not "local" within the "for command body"
|
|
|
|
===============
|
|
The PROCS
|
|
|
|
proc create_mask { MSB LSB } {
|
|
... body ....
|
|
}
|
|
|
|
Like "for" - PROC is really just a command that takes 3 parameters.
|
|
The (1) NAME of the function, a (2) LIST of parameters, and a (3) BODY
|
|
|
|
Again, this is at "level 0" so it is a global function. (Yes, TCL
|
|
supports local functions, you put them inside of a function}
|
|
|
|
You'll see in some cases, I nest [brackets] a lot and in others I'm
|
|
lazy or wanted it to be more clear... it is a matter of choice.
|
|
===============
|
|
|
|
|
|
**************************************************
|
|
***************************************************
|
|
=== TCL TOUR ===
|
|
Open the file: "memory.tcl"
|
|
===============
|
|
|
|
Here is where I setup some 'memory definitions' that various targets can use.
|
|
|
|
For example - there is an "unknown" memory region.
|
|
|
|
All memory regions must have 2 things:
|
|
|
|
(1) N_<name>
|
|
(2) NAME( array )
|
|
And the array must have some specific names:
|
|
( <idx>, THING )
|
|
Where: THING is one of:
|
|
CHIPSELECT
|
|
BASE
|
|
LEN
|
|
HUMAN
|
|
TYPE
|
|
RWX - the access ability.
|
|
WIDTH - the accessible width.
|
|
|
|
i.e.: Some regions of memory are not 'word'
|
|
accessible.
|
|
|
|
The function "address_info" - given an address should
|
|
tell you about the address.
|
|
|
|
[as of this writing: 7/5/2008 I have done
|
|
only a little bit with this -Duane]
|
|
|
|
===
|
|
MAJOR FUNCTION:
|
|
==
|
|
|
|
proc memread32 { ADDR }
|
|
proc memread16 { ADDR }
|
|
proc memread8 { ADDR }
|
|
|
|
All read memory - and return the contents.
|
|
|
|
[ FIXME: 7/5/2008 - I need to create "memwrite" functions]
|
|
|
|
**************************************************
|
|
***************************************************
|
|
=== TCL TOUR ===
|
|
Open the file: "mmr_helpers.tcl"
|
|
===============
|
|
|
|
This file is used to display and work with "memory mapped registers"
|
|
|
|
For example - 'show_mmr32_reg' is given the NAME of the register to
|
|
display. The assumption is - the NAME is a global variable holding the
|
|
address of that MMR.
|
|
|
|
The code does some tricks. The [set [set NAME]] is the TCL way
|
|
of doing double variable interpolation - like makefiles...
|
|
|
|
In a makefile or shell script you may have seen this:
|
|
|
|
FOO_linux = "Penguins rule"
|
|
FOO_winXP = "Broken Glass"
|
|
FOO_mac = "I like cat names"
|
|
|
|
# Pick one
|
|
BUILD = linux
|
|
#BUILD = winXP
|
|
#BUILD = mac
|
|
FOO = ${FOO_${BUILD}}
|
|
|
|
The "double [set] square bracket" thing is the TCL way, nothing more.
|
|
|
|
----
|
|
|
|
The IF statement - and "CATCH" .
|
|
|
|
Notice this IF COMMAND - (not statement) is like this:
|
|
[7/5/2008 it is this way]
|
|
|
|
if ![catch { command } msg ] {
|
|
...something...
|
|
} else {
|
|
error [format string...]
|
|
}
|
|
|
|
The "IF" command expects either 2 or 4 parameters.
|
|
|
|
=== Sidebar: About "commands" ===
|
|
|
|
Take a look at the internals of "jim.c"
|
|
Look for the function: Jim_IfCoreCommand()
|
|
And all those other "CoreCommands"
|
|
|
|
You'll notice - they all have "argc" and "argv"
|
|
|
|
Yea, the entire thing is done that way.
|
|
|
|
IF is a command. SO is "FOR" and "WHILE" and "DO" and the
|
|
others. That is why I keep using the phase it is a "command"
|
|
|
|
=== END: Sidebar: About "commands" ===
|
|
|
|
Parameter 1 to the IF command is expected to be an expression.
|
|
|
|
As such, I do not need to wrap it in {braces}.
|
|
|
|
In this case, the "expression" is the result of the "CATCH" command.
|
|
|
|
CATCH - is an error catcher.
|
|
|
|
You give CATCH 1 or 2 parameters.
|
|
The first 1st parameter is the "code to execute"
|
|
The 2nd (optional) is where to put the error message.
|
|
|
|
CATCH returns 0 on success, 1 for failure.
|
|
The "![catch command]" is self explanatory.
|
|
|
|
|
|
The 3rd parameter to IF must be exactly "else" or "elseif" [I lied
|
|
above, the IF command can take many parameters they just have to
|
|
be joined by exactly the words "else" or "elseif".
|
|
|
|
The 4th parameter contains:
|
|
|
|
"error [format STRING....]"
|
|
|
|
This lets me modify the previous lower level error by tacking more
|
|
text onto the end of it. In this case, i want to add the MMR register
|
|
name to make my error message look better.
|
|
|
|
---------
|
|
Back to something inside show_mmr32_reg{}.
|
|
|
|
You'll see something 'set fn show_${NAME}_helper' Here I am
|
|
constructing a 'function name' Then - I look it up to see if it
|
|
exists. {the function: "proc_exists" does this}
|
|
|
|
And - if it does - I call the function.
|
|
|
|
In "C" it is a lot like using: 'sprintf()' to construct a function name
|
|
string, then using "dlopen()" and "dlsym()" to look it up - and get a
|
|
function pointer - and calling the function pointer.
|
|
|
|
In this case - I execute a dynamic command. You can do some cool
|
|
tricks with interpretors.
|
|
|
|
----------
|
|
|
|
Function: show_mmr32_bits()
|
|
|
|
In this case, we use the special TCL command "upvar" which tcl's way
|
|
of passing things by reference. In this case, we want to reach up into
|
|
the callers lexical scope and find the array named "NAMES"
|
|
|
|
The rest of the function is pretty straight forward.
|
|
|
|
First - we figure out the longest name.
|
|
Then print 4 rows of 8bits - with names.
|
|
|
|
|
|
**************************************************
|
|
***************************************************
|
|
=== TCL TOUR ===
|
|
Open the file: "chips/atmel/at91/usarts.tcl"
|
|
===============
|
|
|
|
First - about the AT91SAM series - all of the usarts
|
|
are basically identical...
|
|
|
|
Second - there can be many of them.
|
|
|
|
In this case - I do some more TCL tricks to dynamically
|
|
create functions out of thin air.
|
|
|
|
Some assumptions:
|
|
|
|
The "CHIP" file has defined some variables in a proper form.
|
|
|
|
i.e.: AT91C_BASE_US0 - for usart0,
|
|
AT91C_BASE_US1 - for usart1
|
|
... And so on ...
|
|
|
|
Near the end of the file - look for a large "foreach" loop that
|
|
looks like this:
|
|
|
|
foreach WHO { US0 US1 US2 US3 US4 .... } {
|
|
|
|
}
|
|
|
|
In this case, I'm trying to figure out what USARTs exist.
|
|
|
|
Step 1 - is to determine if the NAME has been defined.
|
|
i.e.: Does AT91C_BASE_USx - where X is some number exist?
|
|
|
|
The "info exists VARNAME" tells you if the variable exists. Then -
|
|
inside the IF statement... There is another loop. This loop is the
|
|
name of various "sub-registers" within the USART.
|
|
|
|
Some more trick are played with the [set VAR] backtick evaluation stuff.
|
|
And we create two variables
|
|
|
|
We calculate and create the global variable name for every subregister in the USART.
|
|
And - declare that variable as GLOBAL so the world can find it.
|
|
|
|
Then - we dynamically create a function - based on the register name.
|
|
|
|
Look carefully at how that is done. You'll notice the FUNCTION BODY is
|
|
a string - not something in {braces}. Why? This is because we need TCL
|
|
to evaluate the contents of that string "*NOW*" - when $vn exists not
|
|
later, when the function "show_FOO" is invoked.
|
|
|
|
Lastly - we build a "str" of commands - and create a single function -
|
|
with the generated list of commands for the entire USART.
|
|
|
|
With that little bit of code - I now have a bunch of functions like:
|
|
|
|
show_US0, show_US1, show_US2, .... etc ...
|
|
|
|
And show_US0_MR, show_US0_IMR ... etc...
|
|
|
|
And - I have this for every USART... without having to create tons of
|
|
boiler plate yucky code.
|
|
|
|
****************************************
|
|
****************************************
|
|
END of the Tcl Intro and Walk Through
|
|
****************************************
|
|
****************************************
|
|
|
|
FUTURE PLANS
|
|
|
|
Some "GPIO" functions...
|
|
|
|
@endverbatim
|
|
|
|
*/
|