Forth

From eLinux.org
Revision as of 11:11, 18 July 2013 by Skvamme (talk | contribs) (More Fun)
Jump to: navigation, search

Forth on RPi

sudo apt-get install wget
wget http://www.fourmilab.ch/atlast/download/1.2/atlast-1.2.tar.gz
tar -xzvf atlast-1.2.tar.gz
cd atlast-1.2/
make

Start the Forth interpreter with ./atlast and exit with Ctrl-d

atlast.html and atlast.pdf is included and is an extensive atlast-forth manual. Read it online [1] and read about Forth itself here [2] and here [3] (Beware that the Forth dialect in the book Starting Forth is a bit outdated compared to Atlast Forth). Leo Brodie wrote another book, Thinking Forth, read it here [4]

Adding new words

Most of the power of Atlast Forth derives from the ease with which C coded primitives can be added to the language. In my case I will add some words for controlling my Gertboard. There is a detailed description on how to do this in the atlast-forth manual. And you can copy much of the word implementations from the gertboard_sw directory if you have downloaded the gertboard demo files. It is easy to add your own words, just add a "define GERTBOARD" to atlast.c around line 56.

#define EVALUATE                      /* The EVALUATE primitive */
#define FILEIO                        /* File I/O primitives */
#define GERTBOARD                     /* Gertboard functions */

Include the gb_common.h right after include <math.h>

#ifdef MATH
#include <math.h>
#endif

#ifdef GERTBOARD
#include "gb_common.h"
#endif

Then add your own word definitions at the end of the section with word definitions, around line 2704, right after #endif /* COMPILERW */ in atlast.c

#ifdef GERTBOARD

prim P_gert_io() // state ---
{ // Setup and restore io
  Sl(1);
  if(S0 == 1)
    setup_io(); // Map the I/O sections
  else
    restore_io(); // Unmap and free memory
  Pop;
}

prim P_gert_setport() // channel state ---
{ // Set a digital io port to a specified state
  int rev;
  Sl(2);
  if (S1 == 21)
    { // find out which rev of RPi we have
      rev = pi_revision();
      if (rev != 1)
        S1 = 27; // GP21 on Gertboard is controlled by GPIO27
  }
  INP_GPIO(S1);
  OUT_GPIO(S1);
  if (S0 == 1)
    GPIO_SET0 = (1<<S1);
  else
    GPIO_CLR0 = (1<<S1);
  Pop2;
}

prim P_gert_getport() // channel --- state
{ // Get a digital io port
  int rev;
  Sl(1);
  if (S0 == 21)
    { // find out which rev of RPi we have
      rev = pi_revision();
      if (rev != 1)
        S0 = 27; // GP21 on Gertboard is controlled by GPIO27
  }
  INP_GPIO(S0);
  S0 = !!(GPIO_IN0 & (1 << S0));
}

#endif /* GERTBOARD */

And finally, add the actual words to the Table of primitive words, right after #endif /* EVALUATE */ at line 2960 or so.

#ifdef EVALUATE
    {"0EVALUATE", P_evaluate},
#endif /* EVALUATE */

#ifdef GERTBOARD
	 {"0GERTBOARD", P_gert_io},	
	 {"0SETIO", P_gert_setport},		
	 {"0GETIO", P_gert_getport},		
#endif /* GERTBOARD */

As we are using code from the Gertboard demos, copy the files gb_common.o and gb_common.h from the gertboard_sw directory to atlast-1.2 directory (it's there if you have run make in this directory as well).

Add gb_common.o to the file Makefile in atlast-1.2.

ATLOBJ = atlast.o gb_common.o atlmain.o

Now, save and run "make" again to recompile atlast.c.

Test the new words

Wire up the Gertboard according to the information you get when you run the command sudo ./leds in the Gertboard demo directory.

Run sudo ./atlast in the atlast-1.2 directory.

Type 1 gertboard

Type 22 1 setio and press enter, the corresponding LED will go on.

Type 22 0 setio and the LED will go off.

Type 0 gertboard

Play with it

Define your own LED demo, start the interpreter with sudo ./atlast. Define these words:

: use 1 ;
: free 0 ;
: leds 25 24 23 22 21 18 17 11 10 9 8 7 ;
: on 12 0 do 1 setio loop ;
: off 12 0 do 0 setio loop ;

Now type:

use gertboard 
leds on
leds off 
free gertboard

A real Use Case

I have a kWh meter that I would like to read with the RPi. On the meter there is a small light that blinks once per 3.6 sec at 1 kW. So I need a way to detect time between pulses. I mounted a simple LDR (2k - 20k) for around €2 over the blinking light and connected it to Gertboard Buf1 and ground. Set B1 as an input with a jumper on the board and connect GP25 to B1.

Now that you know how to add a primitive word to atlast I just list the code for the word:

prim P_gert_getkwh() // channel --- clocks  clocks_per_sec
{ // Get a digital io port negative edge
  unsigned int i;
  clock_t start, end;

  start = clock();
  Sl(1);
  if (S0 == 21)
    { // find out which rev of RPi we have
      rev = pi_revision();
      if (rev != 1)
        S0 = 27; // GP21 on Gertboard is controlled by GPIO27
  }
  INP_GPIO(S0);
  i = 0;
  while(GPIO_IN0 & (1 << S0))
  {
    i++;
    if(i > 100000000) break; // Timeout after 10 seconds
  }
  long_wait(1);
  end = clock();
  S0 = (double) (end - start);
  So(1);
  Push = (stackitem) CLOCKS_PER_SEC;
}
 
Add #include <time.h> at line 20 in atlast.c
Add fflush(stdout); to prim P_cr() at line 1460 or so.

prim P_cr()                           /* Carriage return */
{
    V printf("\n");
    fflush(stdout);
}

Start the interpreter with sudo ./atlast. Test the commands

1 gertboard

( The first 25 getkwh is for detecting an edge, drop the result and find next edge. Convert the two integers to float and divide them.  )
( 3.6 divided by the result. The result is load in kilowatts )
: kw 3.6 25 getkwh 2drop 25 getkwh float 2 roll float 2swap f/ f/ f. cr ;

kw

( The same thing done with integers only, here the result is in W )
: w 25 getkwh 2drop 25 getkwh 36 * swap 100 / / ." "w=" . cr ;

w

0 gertboard

Result: Jolly good, or as they say in USA, Awesome! Tests shows an accuracy down to a single watt.

However, having the kW load on the command line in a terminal is not good enough, I want it online on the web or as a mobile app. If you are interested, follow me over to the Erlang page where I vill use this Atlast Forth application as an Erlang Port to access Gertboard from a web page.

More Fun

To play a little more with the demo I need another primary word: SLEEP that takes one item on the stack, sleep time in microseconds. This very simple word should have been the word to start with, it actually shows 3 fundamental things for a primary word in three lines of code. Sl(1) to make sure there is at least one item on the stack. usleep(S0) is using the top stack item S0. Pop; pops the S0 stack item off the stack when it has been used.

Add this primary word to atlast.c

prim P_sleep() // microsec ---
{ 
  Sl(1);
  usleep(S0);
  Pop;
}

Now we can do:

sudo ./atlast

: use 1 ;
: free 0 ;
: leds 25 24 23 22 21 18 17 11 10 9 8 7 ;
: on 12 0 do 1 setio 1000000 sleep loop ;
: off 12 0 do 0 setio 1000000 sleep loop ;

Now define the word leddemo:

: leddemo use gertboard leds on leds off leds on leds off free gertboard ;

( and try it )
leddemo