Arduino FORTH: Register Access

Edit after more research: the approach below might not be best possible approach when working with GPIO pins, as it might not be taking advantage of special opcodes for this purpose. Also, due to the two byte FVM cell size, I think there might be some potential for strange effects in nearby registers of a different type (like, the PINC register being after the PORTB register). Heading back to the data sheets…

Edit 2: It looks like FORTH (including Arduino-FVM) has the C@ and C! commands which are single-byte (char) versions of @ and !.

The Atmel specific keywords that come with Arduino-FVM are rather limited – basically just PINMODE, DIGITALREAD, and DIGITALWRITE. So, to do much of interest you would want to start playing around with the atmel microprocessor registers. This is mostly straightforward, however, as the registers are accessible simply as locations in ram, meaning you just write or read bits to ram to get what you want. Doing this in FORTH, you need a few tools

  • @ word: read from a memory location
  • ! word: write to a memory location
  • HEX word: switch to hexidecimal interpretation of the stack input/output (DECIMAL to switch back).
  • Pinout for your arduino board (available on the internet)
  • Data sheet for your specific microcontroller (available on the internet)

Here is a quick example of lighting up the LED through a register write. First, we need to know our pin number and also which register we are dealing with, available from the pinout.

Arduino Nano Pinout

There we have pin number 13 (decimal) for the LED, and register PB5. First, to keep this post a bit shorter, we we simply use the PINMODE keyword to set pin 13 to output mode:

true 13 pinmode

Now we look at the datasheet to get the memory location for register PB5.

A page from 328P Datasheet

So, PORTB register is at 0x25 (hex) and bit five is what we want. Now, let’s see what is already in that memory location. On my chip, I got:

hex
0x25 @ . 0

Just keep in mind however, as you can see using the CELL command, that you have actually read in two bytes. So, if you get something like 0x2000, you are only interested in the first byte (the right-most 00).

As a general practice, I only want to write to the bit of interest, preserving other bit states. So, I drop a 1 for that bit on the stack, and use an OR to light up just that bit. A 1 in bit 5 is hex 0x20.

0x25 @ 0x20 or 0x25 ! 

And the LED lights up. We can see now what we have in register memory:

0x25 @ . 20

That is a trivial example, but I think you would need to go into this memory “peeking” and “poking” in order to do more advanced things like control the interrupt system or special purpose pins. At least, I find the idea more appealing than having to write C interface functions on the backend. Of course, you would want to hide the details inside nice forth functions.

Learning FORTH on Arduino

Some output from Nano running FORTH (no command echo)

FORTH is interesting as a language not as ugly and boring as C, yet more lightweight than lisp. For the uno (328PU) ulisp requires 97% of the program store space, and 74% of your dynamic memory, while Arduino-FVM needs 36% of program storage space and 69% of dynamic memory. There is more for me to explore as far as space efficiency goes, but anyway I’m giving FORTH a try.

There are a few different FORTH implementations for Arduino – I was looking for the easiest path to get set-up. I landed on Arduino-FVM simply because it was the first one I found that was an Arduino sketch/library which I load quickly without having to learn anything new about how to upload to the 328. I just had to install the FVM library, and then compile a sketch which gives me the actual interpreter to load on the microcontroller.

https://github.com/mikaelpatel/Arduino-FVM

You can’t really learn the fundamentals of FORTH programming from just looking at Arduino-FVM documentation; so I installed gforth on my Guix desktop computer and used the gforth info page that has nice reference material and an extensive tutorial. Then I looked at the Arduino-FVM source code, which documents the parameters for the arduino-specific words.

This one liner will blink the LED on and off:

: blink true 13 pinmode begin 500 delay 13 digitaltoggle again ; blink

For those total unfamiliar with FORTH, it is sort of like doing programming on a reverse polish notation calculator, but with a larger stack available.

Minimalist Arduino Board

Atmel 328PU microcontroller with minimal circuitry for 16Mhz operation

I wanted to get a feel for using the 328P without the arduino board, and practice soldering. I used this tutorial:

https://www.arduino.cc/en/Tutorial/BuiltInExamples/ArduinoToBreadboard/

16Mhz operation requires two capacitors, a quartz crystal, and a 10k resistor, as represented above. It is possible to cut down on the circuitry by using the internal 8Mhz oscillator, but I didn’t do that.

Up close view of top of minimalistic 328P board.

In my prototype, to supply power I simply clip 5V and GND leads onto the pads on the sides. The two wires coming out are the UART read and write lines (serial communication).

I plugged it into power and a USB-serial device (the TPE-USBSERIAL from ThinkPenguin) and to my surprise it worked the first time. I had already loaded ulisp on the microcontroller, and was able to run some lisp commands through the serial connection.

I was surprised it simply worked, mainly because of my frightening magnet-wire solder work.

Scary looking magnet wire mess on back of prototype board.

Methinks I might have to use a bigger board next time, or figure out a different approach.

Reading old EPROM with Arduino

Hand made Arduino reader for a 1980’s EPROM chip. Chip not shown in photo. Ribbon cables were not readily available, but some electrical tape made the wires manageable. Push button at top right initiates a full chip read with a hex dump to serial output.

My rather interesting new job involves maintaining some 1980’s full-motion flight simulator technology. The Digital Control Loading system for this simulator — the part which controls movement and force feedback of the flight controls — is made up of large TTL circuit cards (a Fokker system).

The mathematical procedures which calculate the feedback, etc., are not done in the main simulator computer, but are done by Channel CPU cards, which each contain an original intel 8086-2 microcontroller. Those microcontrollers run firmware which is burned onto old 32k and 64k EPROM memory chips.

1980’s era Channel CPU card. Performs mathematical functions to calculate positioning and force-feedback for flight simulator controls. Original Intel 8086-2 microcontroller at top left. 32KB and 64KB EPROM chips are the bottom four chips with labels on them.

We don’t have the source code for the math on the EPROMs, so we needed to make backups of the data on them. I was able without too much difficulty to put together a reader for the 32KB chips with an Arduino Mega and a few spare parts. I haven’t done the 64KB reader yet, but I do not think it will be difficult.

Reading these old EPROMs is straightforward: basically just setting address pins to the byte address you want, toggling the OUTPUT ENABLE pin, and then reading the byte from the 8 output pins. I have a button inserted onto the Mega which causes it to begin reading all the bytes on by one and dumping them to the serial output, in an ASCII format (0xHH).

Creating an Arduino programmer (burner) for the EPROM chips will be more challenging, I expect, since programming them requires higher voltage pulses (21V for the 32KB chip). But I should be able to manage that with a relay, I’m thinking.

In summary, it was an interesting and fun project illustrating how Arduino / AVR skills can be used in the work-a-day world.

Eight digit Max7219 Seven-Segment Displays

Eight-digit seven-segment display driven by Uno SPI.

I have been experimenting with these 8 digit displays driven by the Max7219 driver chip. The driver chip allows you to control the segments with persistent serial data commands, rather than having to drive the segments yourself regularly from your main microcontroller.

They are driven over an SPI bus, which requires only three lines, not including vcc and gnd. SPI is built into the 328P microcontroller on the UNO. One line is for the serial data out, one is a synchronizing clock, and one line is the Chip Select line. (SPI uses another line for serial data in, but I don’t need that in this case.) The Max7219 doesn’t actually have full SPI compatibility, as it does not have a Chip Select line, but rather a LOAD line, which is a little different, and is problematic if you wanted to put more than one device on your SPI bus. I ordered some Max7222 chips also, which have full SPI compatibility; but in this use case it doesn’t really matter as the Max7219 drivers support daisy chaining of the shift registers, meaning I can hook up multiple 8 digital displays using only one SPI device slot.

The arduino reference material on the SPI library is a little confusing, so it took some time for me to figure out how to program the SPI communication correctly. Also, I was confused for a while because the display kept showing up as randomly garbled. It turned out that there wasn’t really anything wrong fundamentally with my code, but just when the Max7219 boots up, the segment registered are pre-filled with seemingly random data, so I needed to blank or overwrite the digits first to see only what you want to see.

Here is the test code.

/*
  Copyright 2020 Christopher Howard

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include <SPI.h>

#define CH_H 0b00110111
#define CH_I 0b00010000
#define CH_BLK 0b00000000
#define CH_0 0b01111110
#define CH_1 0b00110000
#define CH_2 0b01101101
#define CH_3 0b01111001
#define CH_4 0b00110011
#define CH_POINT 0b10000000

void setup ()
{
	pinMode(8, OUTPUT);
	SPI.begin();
	SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0));
	digitalWrite(8, LOW);
	/* Normal Mode */
	SPI.transfer(0b00001100);
	SPI.transfer(0b00000001);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	/* Scan limit: all digits */
	SPI.transfer(0b00001011);
	SPI.transfer(0b00000111);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	/* 25/32 intensity */
	SPI.transfer(0b00001010);
	SPI.transfer(0b00001100);
	digitalWrite(8, HIGH);
	/* Write some characters */
	digitalWrite(8, LOW);
	SPI.transfer(0b00000001);
	SPI.transfer(CH_0);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00000010);
	SPI.transfer(CH_1);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00000011);
	SPI.transfer(CH_2 | CH_POINT);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00000100);
	SPI.transfer(CH_3);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00000101);
	SPI.transfer(CH_4 | CH_POINT);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00000110);
	SPI.transfer(CH_BLK);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00000111);
	SPI.transfer(CH_I);
	digitalWrite(8, HIGH);
	digitalWrite(8, LOW);
	SPI.transfer(0b00001000);
	SPI.transfer(CH_H);
	digitalWrite(8, HIGH);
	SPI.endTransaction();
	SPI.end();
}

void loop ()
{
}