Firmware hobby hacking: Difference between revisions
(Added editing words) |
No edit summary |
||
Line 4: | Line 4: | ||
For background see the draft [[Forth Lesson 13]]. |
For background see the draft [[Forth Lesson 13]]. |
||
Here's an example set of "editing words" to |
Here's an example set of "editing words" to develop Forth code from within the firmware. This code assumes you have added an SD memory card that you can store Forth source files on, but it's easy to change. Here's the vocabulary: |
||
edit foobar \ load sd:\foobar.fth into the editing buffer and open the screen editor (^C when done) |
edit foobar \ load sd:\foobar.fth into the editing buffer and open the screen editor (^C when done) |
Revision as of 04:52, 16 November 2007
This page has been created to collect notes about doing hobby programming using the Forth-based OLPC firmware. It was created to braindump & organise tips and tricks that can hopefully be polished and moved to other pages (such as Forth Lessons).
Editing
For background see the draft Forth Lesson 13.
Here's an example set of "editing words" to develop Forth code from within the firmware. This code assumes you have added an SD memory card that you can store Forth source files on, but it's easy to change. Here's the vocabulary:
edit foobar \ load sd:\foobar.fth into the editing buffer and open the screen editor (^C when done) ev \ execute the contents of the editing buffer (most recently edited file) re-ed \ reopen the screen editor
And the code:
\ ED.FTH 0 0 2value ed-file \ filename being edited 10000 constant ed-maxsize \ buffer size (max) 0 value ed-size \ amount of text in buffer create ed-buf ed-maxsize allot : read-content ( adr len name$ -- sz ) r/o open-file if 0 else dup >r fgets r> fclose then ; : write-content ( content$ name$ -- ) 2dup $delete-all $create-file >r " write" r@ $call-method drop r> close-dev ; : name>file ( $n -- $fn ) " sd:\" 2swap " .fth" $cat2 $cat2 ; : edit-string [ also hidden ] edit-file [ previous ] ; : ed$ ( -- s$ ) ed-buf ed-size ; : ed-read ( -- ) ed-buf ed-maxsize ed-file read-content is ed-size ; : ed-write ( -- ) ed$ ed-file write-content ; : ed-save? ( -- ) " Save?" confirmed? if ed-write then ; : ed-edit ( -- ) ed$ ed-maxsize edit-string is ed-size ; : edit \ name ( -- ) safe-parse-word name>file is ed-file ed-read ed-edit ed-save? ; : re-ed ( -- ) ed-edit ed-save? ; : ev ( -- ) ed$ eval ; : ed-save-as ( -- ) ed$ safe-parse-word name>file 2dup is ed-file write-content ;
You may also want to expand the Forth display to use all available screen space (maximum number of lines and columns):
screen-ih iselect 0 to window-top 0 to window-left d# 41 to #lines hidden d# 41 to display-height d# 100 to #columns d# 100 to display-width forth clear-screen
Loading code
The fl command takes URLs too. If you have an access point with SSID OLPCOFW you can run Forth code from a webserver like this:
fl http:\\192.168.0.5\\myfile.fth
Introspection
words \ print all words in the current vocabulary sifting foo \ print all words containing substring "foo" in standard vocabs shift-devs foo \ ... in device vocabularies see foo \ decompile or disassemble the word foo ' foo .calls \ list callers to word foo
Graphics
Open the display device like this:
screen-ih iselect
On booting to the 'ok' prompt everything you see on the screen is stored in a "frame buffer" containing 16-bit 565 mode (5 bits red, 6 bits green, 5 bits blue) pixel data. The frame buffer is normal RAM that the graphics hardware is putting onto the screen. Forth code is writing the pixels of text into the framebuffer. You can play with the frame buffer memory with normal Forth code:
ok screen-ih iselect ok frame-buffer-adr . \ where in RAM is the frame buffer? ff000000 ok 0 frame-buffer-adr ! \ make the two top-left pixels black
For more information about the XO's graphics hardware check out the AMD Geode LX Processors Data Book.
- Sweet! It shouldn't be too hard to come up with a vocabulary of graphics words to play around in the framebuffer. colorForth might be a good source of insight: it also works with a frame buffer at the lowest level. Google around for its source code. --IanOsgood 01:01, 20 August 2007 (EDT)
- See also a windowing demo and a Towers of Hanoi demo written in Open Firmware for the PowerPC Macintosh. Scroll to Part II of this article by Amit Singh. The same screen device methods may also work on the OLPC version of Open Firmware. --IanOsgood 12:19, 15 September 2007 (EDT)
- The aforementioned demos use these methods to fill and copy rectangles.
" screen" open-dev value myscreen \ Convenience wrapper functions : set-color ( r g b color -- ) " color!" myscreen $call-method ; \ program palette : fillrect ( color x y w h -- ) " fill-rectangle" myscreen $call-method ; \ blit factors : readrect ( addr x y w h -- ) " read-rectangle" myscreen $call-method ; : drawrect ( addr x y w h -- ) " draw-rectangle" myscreen $call-method ;
Object system
Here's an email from Mitch Bradley sketching the object system:
Each device node has 2 Forth vocabularies, one for properties and one for methods. They are organized into a tree so you can find them, and also so a device node can find its parent. In addition, a device node has a chunk of memory that holds the initial values of its "instance data", which is variables, values, defers, .. that were defined by using the prefix word "instance". The data for ordinary variables (etc) goes in a global place (the normal Forth "user" areaa), whereas the data for instance variables goes in an "instance record". When you create a device node by interpreting Forth source code or FCode, you define the instance variables and set their initial values using ordinary Forth techniques (plus the "instance" prefix when creating them). When you finish the creation of the device node, those initial values get stored in a block of memory that is part of the device node. This is analogous to compiling and linking a Linux program - the initial values of certain data items are stored in the .data segment image in the executable file. The collection of methods, properties, and initial values in a device node is called a "package". You can think of a package as a "dormant object". In order to use the package, you must first "instantiate" it. To do that, you call "open-dev" with a string argument - the string is the device tree pathname. It instantiates every node from the root on down to the leaf node, and hooks them together in an "instance chain". (Even if you use a shorthand pathname with components elided, it canonicalizes the pathname and instantiates the full path.) The reason for this is because a package nearly always require services from its parent. For example a scsi disk node needs services from the scsi host adapter node, and that needs services from the PCI bus node, and that needs services from the main root bus node. Instantiating a node allocates memory for the instance data and initializes that memory with the initial value block from the package. The "instance handle" that is the primary reference to the instance is just the address of that memory (actually, it is an address in the middle, but that is an implementation detail). In addition to the user-defined instance data, there is also some system-defined instance data that you get automatically. For example, "my-voc" is an instance value that points to the package's methods vocabulary. my-parent is an instance value that is the ihandle for the parent (this constitutes the "instance chain" linkage). my-args-adr and my-args-len refer to the optional argument string that was passed to the node in the "open-dev" argument string (after ":"; each path component can have a separate argument). And so on. open-dev returns on the stack the ihandle for the leaf node, which is the head of the "instance chain" linking up through the parents. In order to invoke one of the methods, you use "$call-method", passing a method name and ihandle. It pushes the current value of "my-self" (which is a global value) onto the return stack, sets my-self to the argument ihandle, looks up the method name in the methods vocabulary of the ihandle, executes it, and pops the return stack into my-self on return. Since "my-self" has been set to that ihandle, any uses of instance data will access the values within that instance record. $call-parent is just $call-method with "my-parent" as the ihandle argument. That is the OFW object system in a nutshell. Note that $call-method does nothing with the Forth interactive interpreter context. For debugging purposes, it is very useful to get the interactive interpreter into the act. If you already have an open instance, you can "drag the Forth interpreter into the context" with "iselect ( ihandle -- )". What that does is to set my-self to that ihandle and push the corresponding methods vocabulary onto the interpreter search order. Note that setting my-self globally in this fashion does not interfere with the ability to use $call-method in other contexts, because $call-method pushes/pops my-self as it does its job. In the normal case where you have not done an "iselect", if you say "my-self ." at the interpreter, you will see "0", meaning that there is no current instance at the topmost (interactive) level. If you don't already have an open instance, you can say "select <pathname>" which is a debugging convenience word that does open-dev and iselect. But keep in mind that "iselect" and "select" are just for debugging. The normal usage model for "applications" is to use open-dev and $call-method, without getting the interactive interpreter involved at that level.