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 ()
{
}

Timer, 4-Digit, Photoresistor

I’m having lots of fun diving into this atmel microcontroller stuff. I might need to work on wire management at some point, however…

Now that I have a four-digit hex display that can easily display any sixteen-bit value from memory, naturally I wanted to tie that to something in the real world. The easy choice was a photoresistor, which I tied to A0 pin.

The max value that can be returned from analogRead is 1024, which is 0x3FF on my hex display. In my natural level of room lighting, and in conjunction with a 30k resistor, my readout rests at about 0x18E, about 30% of max value. If I shine my flashlight on the photoresistor, that brings it up to about 0x36D, and covering it with a piece of paper brings it down to about 0x043.

With this adventure I also took the time to learn about and make use of custom timer interrupts. At my day job, I maintain a rather ancient piece of simulator technology, so I spend a fair amount of time reading 1980’s programming and electronics reference manuals, in which there is a heavy emphasis on the precisely-choreographed timing of signals. Consequently, I’ve developed something of a fascination/obsession with timing in circuits.

Anyway, I wanted my main-loop activities to be driven by a steady interrupt signal outside of the loop itself, so I used the 328P’s 16-bit #1 timer (328P microcontroller has three timers available, 0, 1, and 2). With a 16 Mhz clock, x8 prescaler, and OCR value of 20000 (CTC), that provides a precisely 100 Hz interrupt, i.e., an interrupt every 10 ms. 100 Hz was the fastest I needed to loop in order to provide a good frame rate for the hex display. The interrupt simply sets a boolean value, acting as a sort of timing pulse for the main loop.

Ten milliseconds would be plenty of time to accomplish very many operations, even on a 16Mhz microcontroller, but in my case I just needed to drive the display and read a value from A0 pin. The analogRead function needs about 100 microseconds, according to the Arduino reference. I found I needed about 250 microseconds per hex digit in order to provide sufficient voltage to the digits in a 10ms loop, if I remove the resistors.

A side lesson here is that you don’t need resistors for your LEDs if you carefully control the duty cycle of voltage to them. An interesting implication of that would be that increasing your voltage would allow you to decrease you duty cycle as much as you want, i.e., to use shorter voltage pulses.

Here are some helpful links on understanding and using timer interrupts:

https://www.instructables.com/id/Arduino-Timer-Interrupts/

https://www.robotshop.com/community/forum/t/arduino-101-timers-and-interrupts/13072

https://www.youtube.com/watch?v=2kr5A350H7E

Here is the latest 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.
*/

/* Pins 1-12 on MSQ6X41C should be mapped to pins 2-13 on Uno */

#define LP1 2
#define LP2 3
#define LP3 4
#define LP4 5
#define LP5 6
#define LP6 7
#define LP7 8
#define LP8 9
#define LP9 10
#define LP10 11
#define LP11 12
#define LP12 13

#define SA LP11
#define SB LP7
#define SC LP4
#define SD LP2
#define SE LP1
#define SF LP10
#define SG LP5
#define SDOT LP3

enum characters
  {
   ZERO, ONE, TWO, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
   NINE, ALPHA, BRAVO, CHARLIE, DELTA, ECHO, FOXTROT, BLANK
  };

enum digit {
	D_ONE, D_TWO, D_THREE, D_FOUR
};

bool startPulse = false;

ISR(TIMER1_COMPA_vect)          // timer compare interrupt service routine
{
  startPulse = true;
}

void set_led_pins(byte ch, byte dig, bool point) {
  /* blank all digits */
  PORTD &= 0b10000011;
  PORTD |= 0b10000000;
  PORTB &= 0b11100110;
  PORTB |= 0b00100110;
	switch(dig) {
	case D_ONE:
    PORTB &= 0b11011111;
		break;
	case D_TWO:
    PORTB &= 0b11111011;
		break;
	case D_THREE:
    PORTB &= 0b11111101;
		break;
	case D_FOUR:
    PORTD &= 0b01111111;
		break;
	}
	switch(ch)
    {
    case BLANK :
      break;
    case ZERO :
      PORTD |= 0b00101100;
      PORTB |= 0b00011001;
      break;
    case ONE :
      PORTD |= 0b00100000;
      PORTB |= 0b00000001;
      break;
    case TWO :
      PORTD |= 0b01001100;
      PORTB |= 0b00010001;
      break;
    case THREE :
      PORTD |= 0b01101000;
      PORTB |= 0b00010001;
      break;
    case FOUR :
      PORTD |= 0b01100000;
      PORTB |= 0b00001001;
      break;
    case FIVE :
      PORTD |= 0b01101000;
      PORTB |= 0b00011000;
      break;
    case SIX :
      PORTD |= 0b01101100;
      PORTB |= 0b00011000;
      break;
    case SEVEN :
      PORTD |= 0b00100000;
      PORTB |= 0b00010001;
      break;
    case EIGHT :
      PORTD |= 0b01101100;
      PORTB |= 0b00011001;
      break;
    case NINE :
      PORTD |= 0b01101000;
      PORTB |= 0b00011001;
      break;
    case ALPHA :
      PORTD |= 0b01100100;
      PORTB |= 0b00011001;
      break;
    case BRAVO :
      PORTD |= 0b01101100;
      PORTB |= 0b00001000;
      break;
    case CHARLIE :
      PORTD |= 0b00001100;
      PORTB |= 0b00011000;
      break;
    case DELTA :
      PORTD |= 0b01101100;
      PORTB |= 0b00000001;
      break;
    case ECHO :
      PORTD |= 0b01001100;
      PORTB |= 0b00011000;
      break;
    case FOXTROT :
      PORTD |= 0b01000100;
      PORTB |= 0b00011000;
      break;
    }
}

void setup()
{
	pinMode(LP1, OUTPUT);
	pinMode(LP2, OUTPUT);
	pinMode(LP3, OUTPUT);
	pinMode(LP4, OUTPUT);
	pinMode(LP5, OUTPUT);
	pinMode(LP6, OUTPUT);
	pinMode(LP7, OUTPUT);
	pinMode(LP8, OUTPUT);
	pinMode(LP9, OUTPUT);
	pinMode(LP10, OUTPUT);
	pinMode(LP11, OUTPUT);
	pinMode(LP12, OUTPUT);

  // initialize timer1 
  noInterrupts();           // disable all interrupts
  TCCR1A = 0;
  TCCR1B = 0;
  TCNT1  = 0;
  OCR1A = 20000;            // compare match register 16MHz/8/100Hz
  TCCR1B |= (1 << WGM12);   // CTC mode
  TCCR1B |= (1 << CS11);    // 8 prescaler 
  TIMSK1 |= (1 << OCIE1A);  // enable timer compare interrupt
  interrupts();             // enable all interrupts
}

byte readCounter = 0;
int av;

void loop()
{
  if(startPulse)
    {
      startPulse = false;
      /* stabilize the readout display */
      if (readCounter == 9)
        {
          av = analogRead(A0);
          readCounter = 0;
        }
      else
        readCounter++;
      set_led_pins((av >> 12) & 0xF, D_ONE, false);
      delayMicroseconds(250);
      set_led_pins((av >> 8) & 0xF, D_TWO, false);
      delayMicroseconds(250);
      set_led_pins((av >> 4) & 0xF, D_THREE, false);
      delayMicroseconds(250);
      set_led_pins(av & 0xF, D_FOUR, false);
      delayMicroseconds(250);
      set_led_pins(BLANK, D_FOUR, false);
    }
}

You’ll see there that I am still using the built-in delay-timer functionality to create some delay in-between setting led pins. But the loop itself is initiated by a “pulse” to the startPulse variable which is set by my custom interrupt timer.