amforth

amforth is a forth language implementation for the Atmel AVR family of microcontrollers. http://amforth.sourceforge.net

all input and output is in hex by default (this can be changed with the decimal and hex words).

when defining new words, the words are written immediately to flash. so they are still there after losing power.

you can set a startup procedure (called the “turnkey” in amforth) that will get automatically executed when the Microcontroller powers up, making this actually useful

(: ms n -- , pause for n ms 1- 0 do 1ms loop ; 

NOTE: there is a more sophisticated version of this example in the blocks/pollin.frt file in the amforth distribution

since it's customary to blink an LED as your first microcontroller program, here is code to do it. memory addresses are for an ATMega8.

this code assumes you have an LED connected to pin 28 (PC5, or PORTC5).

in short

: ms n -- , pause for n ms 1- 0 do 1ms loop ;
: blink -- , blink PORTC5 for 100ms 20 35 c! 64 ms 0 35 c! 64 ms ;
: blinks n -- , blink n times 0 do blink loop ;

\ set the pin to output mode
20 34 c!

10 BLINKS
 

in detail

PORTC5 is controlled with bit 5 of the PORTC register.

in the ATMega8, the PORTC register is mapped to memory address 0x35 (see “Register Summary” in the ATMega8 datasheet).

additionally we need to set the pin to be an output pin, the Data Direction Register for PORTC (DDRC) is at 0x34.

to set the direction of PORTC5, we need to write “1” to bit 5 of DDRC. we can only write bytes, so we need to write 00100000 in binary (bit 0 is on the right), which is 0x20 in hex.

20 34 c! 

PORTC5 is now in output mode.

by default, the pin should be low.

if your LED (and resistor) are connected to +V, your LED should be lit, as the pin is low and should be sinking current. if it's connected to ground, it should be off.

now we can set the pin high by writing 0x20 to PORTC:

20 35 c! 

if your LED is connected to ground, the LED should be lit now, otherwise it should be off.

and set it low again by writing 0.

0 35 c! 

we can define a word that blinks the LED.

first we need the word from the above example, so if you don't already have it:

(you don't have to bother typing the stuff in the brackets, it's just a comment)

: ms n -- , pause for n ms 1- 0 do 1ms loop ; 

then,

: blink -- , blink PORTC5 for 100ms 20 35 c! 64 ms 0 35 c! 64 ms ; 

(64 ms)

NOTE: 100 is 0x64 in hex, so pauses for 100ms.

then a word to blink multiple times:

: blinks n -- , blink n times 0 do blink loops ; 

so now

10 blinks 

should blink your LED 16 times (as 10 in hex is 16 in decimal.. ah, gotcha!

  • how do you remove a word?
    • implementations of forget and marker are found in the blocks/ans.frt file

there are device specific constants (like the ones i needed to use in the blinking example) in the devices/ directory.

there are several files of useful definitions in the blocks/ directory. you need to “upload” these onto the Microcontroller via the terminal. but you have to be careful not to overload the connection, as there is no flow control.

pv 

under linux you can limit the upload using

($ pv -L 30 blocks/ans.frt > /dev/ttyS0 

</code>that limits the output to a roaring 30 chars/sec. which is probably overly cautious. it's hard to say because it seems to have more to do with the number of lines rather than the number of chars. i came up with 30 cps by sending lots of short commands to find the worst case. my controller is running at 4MHz at couldn't handle lots of 42 .<code> commands at 40cps.

amforth-upload.py.txt python upload script for amforth

NOTE: rename this script to amforth-upload.py when you have downloaded it. twiki won't let me have a .py file as an attachment for security reasons rolls eyes

even better than using pv , is this script i just made. it watches the output from amforth, and uses the character echo as a kind of software flow-control. it also waits for a prompt after entering a line, to give the compiler a chance to run.

here is an example that will loads everything into my ATMega8 (run in the amforth-1.3/ directory).

(python amforth-upload.py devices/atmega8.frt blocks/*.frt 

for help, run

(python amforth-upload.py -h 

i added these backup/restore rules to my makefile, they use avrdude to read the memory off of the controller into a file which i can later restore:

PROG=dapa

backup:
	$(AVRDUDE) -c $(PROG) -p atmega8 -P /dev/parport0 -U flash:r:backup.hex:i -U eeprom:r:backup.eep.hex:i 

restore: backup.hex backup.eep.hex
	$(AVRDUDE) -c $(PROG) -p atmega8 -P /dev/parport0 -e -U flash:w:backup.hex:i -U eeprom:w:backup.eep.hex:i
 

you need to set PROG to whatever programmer you have. i use the el-cheapo resistors-in-a-parallel-cable version

–PiX - 01 Mar 2007



Libarynth > Libarynth Web > AmForth r4 - 09 Mar 2007 - 11:56



#!/usr/bin/env python

# amforth-upload.py
#
#   uploads text files containing forth commands to a microcontroller running amforth (v1.3)
#   monitors the output from the microcontroller and uses the character echo to implement a kind of software flow control
#   also waits for a prompt after each line to allow the compiler a chance to run
#
#   in theory it should never overrun the input buffer on the controller, 
#   but it will easily get stuck if the output from the controller is unusual
#
#   you have to kill any other processes that are accessing the serial port before running this,
#   otherwise it will miss characters that are stolen by the other process and get stuck
#
#   perhaps a better way to implement this would have been as a program that could be plugged into a terminal program
#   like x/y/zmodem for minicom (except for the difficulty of killing it when it gets confused). oh well.
#
#   mailto:pix@test.at
#   http://pix.test.at/


import sys
import getopt
import os
import re

def write_line_flow(string,dest):
	# strip comments
	# these probably will strip comment-like structures out of ." .." strings as well.

	if debug:
		print >>sys.stderr, "line before comment stripping: "+string

	string = re.sub("(^| )\( .*?\)"," ",string)
	string = re.sub("(^| )\( [^\)]*$"," \n",string)
	string = re.sub("(^| )\\\\ .*","",string)

	if re.match("^\s*$",string):
		if verbose:
			print >>sys.stderr, "skipping empty line"
		return	

	if debug:
		print >>sys.stderr, "line after comment stripping: "+string

	if verbose: 
		print >>sys.stderr, "sending line: "+string
	for o in list(string):
		dest.write(o);

		if o == "\t":
			o = " "
		
		while True:
			i = dest.read(1)
			#print "<"+i+"]",
			#print >>sys.stderr, "["+i+"]"
			sys.stdout.write(i)
			sys.stdout.flush()
			if i == o:
				#print "|",
				break	
	#dest.write("\n")
	if verbose:
		print >>sys.stderr, "waiting for prompt"

	start, nl, space, o, gt = range(5)

	state = start

	while True:
		#print >>sys.stderr, "{"+str(state)+"}"
		#dest.write("")
		i = dest.read(1)
		#print "<"+i+"]",
		#print >>sys.stderr, "["+i+"]"
		sys.stdout.write(i)
		sys.stdout.flush()
		if state == start:
			if i == "\r":
				state = nl
			elif i == " ":
				state = space
			continue
		elif state == nl:
			if i == ">":
				state = gt
			else:
				state = start
			continue
		elif state == gt:
			if i == " ":
				if debug:
					print >>sys.stderr, "<matched '^> '>"
				break
			else:
				state = start
			continue
		elif state == space:
			if i == "o":
				state = o
			else:
				state = start
			continue
		elif state == o:
			if i == "k":
				if debug:
					print >>sys.stderr, "<matched ' ok'>"
				break
			else:
				state = start
	
	


def write_file_flow(in_file,dest):
	while(True):
		line = in_file.readline()
		if len(line)>0:
			write_line_flow(line,dest)
		else:
			break		

def main(argv):

	global verbose, debug

	#in_file = file("file.frt")
	#tty_dev = file("/dev/ttyS0","w+",0)

	tty_dev_name = "/dev/ttyS0"
	force = False
	verbose = False
	debug = False

	try:
		opts, args = getopt.getopt(argv,"ht:vfd")
	except getopt.GetoptError:
		print >>sys.stderr, "unknown option. try -h"
		sys.exit(1)

	for opt, arg in opts:
		if opt == "-h":
			print >>sys.stderr, "usage: amforth-upload [-h] [-v] [-f] [-t tty] [file1] [file2] [...]"
			print >>sys.stderr, "\n   default tty is "+tty_dev_name
			print >>sys.stderr, "\n   if no files are specified, input is read from the the terminal"
			print >>sys.stderr, "\n   -f will run without checking for other processes accessing the tty"
			print >>sys.stderr, "\n   -v will print extra information during execution"
			sys.exit(1)
		elif opt == "-t":
			tty_dev_name = arg
		elif opt == "-v":
			verbose = True
		elif opt == "-f":
			force = True
		elif opt == "-d":
			debug = True

	if not force:	
		if not os.system("which fuser >/dev/null 2>&amp;1"):
			if not os.system("fuser -u "+tty_dev_name):
				print >>sys.stderr, "the above process is accessing "+tty_dev_name+"."
				print >>sys.stderr, "please stop the process and try again."
				sys.exit(1)
		else:
			print >>sys.stderr, "couldn't find fuser. so i can't check if "+tty_dev_name+" is in use."
			print >>sys.stderr, "run with the -f option to force execution anyway"	


	tty_dev = file(tty_dev_name,"r+",0)

	if len(args)<1:
		if verbose:
			print >>sys.stderr, "processing stdin"
		write_file_flow(sys.stdin,tty_dev)
	else:
		for filename in args:
			in_file = file(filename,"r")
			if verbose:
				print >>sys.stderr, "processing "+filename
			write_file_flow(in_file,tty_dev)
			in_file.close()



if __name__ == "__main__":
	main(sys.argv[1:])
  • amforth.txt
  • Last modified: 2007-07-24 16:10
  • by nik