Sunday, March 8, 2015

Arduino Retro Computer: Program Memory (FRAM)

Arduinos have a very limited amount of internal RAM. Between the code for my video output, sound output, joystick input, keyboard input, and BASIC interpreter, I am running out of memory for the user's programs. FRAM is this very nice memory I found on Adafruit's website. It is very fast, non-volatile (does not lose memory when unpowered), and comes in large enough sizes for decent sized BASIC programs.

Adafruit has 2 types of interfaces for their FRAMs: I2C and SPI. I went with I2C to make it easy to expand the computer with additional FRAM chips if I want to in the future.

https://learn.adafruit.com/adafruit-i2c-fram-breakout?view=all

I soldered leads onto the FRAM chip.


I'm connecting pins as follows:
FRAM SDA to Arduino SDA (Digital Pin 20)
FRAM SCL to Arduino SCL (Digital Pin 21)
FRAM VCC to Arduino 5V
FRAM GND to Arudino GND
Uses I2C address 0x50 - Default

FRAM A0/A1/A2 control the I2C address so you can have multiples connected at once.



(I know my little computer is turning into a mess of wires; I'll work on a case for it soon.)

The FRAM library is available via the Adafruit website, I included it in my OS sketch:

// FRAM Library
#include <Wire.h>
#include <Adafruit_FRAM_I2C.h>

However at compile time it threw an error of not being able to find Wire.


It turns out my version of the Arduino IDE (1.0.5) is so old it didn't contain new standard libraries that the FRAM uses. I downloaded / installed the new IDE (1.6.0), but then I got a different error:
'prog_ucar' has not been declared


A type definition that Gameduino was using was deprecated and removed in the latest IDE.

To solve this problem, I modified my Gameduino library's GD.h file; at the top I added:

typedef const unsigned char prog_uchar;


Now that the upgrade errors have been resolved, I was able to create a global FRAM object:

Adafruit_FRAM_I2C fram = Adafruit_FRAM_I2C();

Initialize the object in setup():

void setup()
{
  ...
  fram.begin();
  ...
}

And finally make a simple test to make sure it was reading and writing.

  fram.write8(0, 'a');
  fram.write8(1, 'b');
  fram.write8(2, 'c');
  char test0 = (char)fram.readu(0);
  char test1 = (char)fram.readu(1);
  char test2 = (char)fram.readu(2);
  Serial.print(test1);
  Serial.print(test0);
  Serial.print(test2);

The output was "bac", perfect!

That's all for now. Up next for the computer will either be a case or the start of the BASIC interpreter. The interpreter will store the user's BASIC code into the FRAM so I can keep the Arduino's SRAM available for operating system expansion.

Thursday, February 26, 2015

Arduino Retro Computer: Video Output

A little overdue, but I'm now going to cover how I handle the output to a monitor. I'm piggybacking off the ScreenModel described in the "Command Input" post; I recommend reviewing that code to get a more complete picture.

Quick recap - the ScreenModel class contains a display buffer, command buffer, and functions to interact between the two. Multiple screens are defined that the user can switch between by using PageUp and PageDown. To do this, I have a character array defined in the ScreenModel that holds the contents of the entire screen. When you switch between screens if flips between which buffer is drawn.

Output Window

Window Divider

Command Window


I'm using a Gameduino and its GD library for the interface to an external monitor. It should be fairly easy to port this code to another display interface by just replacing a handful of Gameduino specific library calls (and possibly adjusting a few of the constants for number of rows/columns).


Initializing the Gameduino



Pretty simple initialization for the Gameduino, include the library headers and a few commands in the setup() function.

// Gameduinio Library
#include <SPI.h>
#include <GD.h>

void setup()
{
  GD.begin();
  GD.ascii();
  GD.fill(0, ' ', 4096);

  ...
}


Define Constants for the Screen



These constants work well with the Gameduino, but they may require adjusting for different graphics displays.

const int numberOfScreenRows = 37;
const int numberOfScreenColumns = 50;
const int numberOfCommandRows = 2;
const int numberOfOutputRows = numberOfScreenRows - numberOfCommandRows - 1;
const int sizeOfOutputColumnArray = numberOfOutputColumns + 1; // Include null terminator.


ScreenModel Class



Similar to the command input, the bulk of the logic resides in the ScreenModel class. Again rather than pasting it all as one big block, I'm going to look at functions individually. All of the remaining code belongs in the ScreenModel class defined here:

class ScreenModel
{
  public:

  char outputArray[numberOfOutputRows][sizeOfOutputColumnArray];
  char blankLine[sizeOfOutputColumnArray];
  byte screenIndex;

  .. A whole bunch of additional functions described below ..
};


ScreenModel Init



The ScreenModel init assigns the screen index and clears the input and output buffers so they'll be ready for use. I'm also defining a blankLine that is used as a template for empty output (I must explicitly draw the spaces so it clears them off the screen).

So the user isn't completely thrown to the wolves, I give a small hint to type "help"! The addOutputLine function is the go-to function for me to write to the screen and let the ScreenModel handle updating the screen buffers and redrawing the screen.

Between the output and input is a divider line of "="s with the current screen index.

  bool init(byte newIndex)
  {
    screenIndex = newIndex;

    // Define the blank line.
    memset(blankLine, ' ', sizeOfOutputColumnArray - 1);
    blankLine[sizeOfOutputColumnArray - 1] = 0;
    
    clearOutput();
    clearCommand();
    
    addOutputLine("System Ready");
    addOutputLine("Type 'help' for commands.");

    drawWindowDivider();
    redrawCommandWindow();

  .. A whole bunch of other initializing function calls ..
  }


Clearing Output



The clearOutput() function handles everything necessary to clear the output of a given screen. It's currently only called on init(), but I see adding a new a "CLS" or similar command that will also clear the output.

  bool clearOutput()
  {
    // Assign the blank line to every output line.
    for (int rowLoop = 0; rowLoop < numberOfOutputRows; rowLoop++)
    {
      strcpy(outputArray[rowLoop], blankLine);
    }

    // Force a window refresh.
    redrawOutputWindow();
    
    return true;
  }


Switching Screens



Pressing the PageUp or PageDown key switches the active screen. After the activeScreen pointer is switched, a call is made to activeScreen->switchToScreen(). This function forces the output and input windows to be redrawn. The command divider must also be redrawn so it shows the correct screen index.

  void switchToScreen()
  {
    // need to redraw command window when switching screens
    redrawOutputWindow();
    drawWindowDivider();
    redrawCommandWindow();
    return;
  }
  
  bool redrawOutputWindow()
  {
    for (int rowLoop = 0; rowLoop < numberOfOutputRows; rowLoop++)
    {
      GD.putstr(0, rowLoop, outputArray[rowLoop]);
    }

    return true;
  }
  
  bool drawWindowDivider()
  {
    char indexString[12];
    itoa(screenIndex, indexString, 10);

    // Draw a divider line.
    char dividerLine[sizeOfOutputColumnArray];
    memset(dividerLine, '=', sizeOfOutputColumnArray - 1);
    
    // Null terminate.
    dividerLine[sizeOfOutputColumnArray - 1] = 0;
    
    // Add screen index to near the beginning.
    memcpy(&dividerLine[1], indexString, strlen(indexString));
    
    GD.putstr(0, 34, dividerLine);

    return true;
  }


  bool redrawCommandWindow()

  {
    int startRowCommandWindow = numberOfScreenRows - numberOfCommandRows;
    for (int rowLoop = 0; rowLoop < numberOfCommandRows; rowLoop++)
    {
      char drawString[sizeOfOutputColumnArray];
      memcpy(drawString, &commandArray[rowLoop * numberOfScreenColumns], numberOfScreenColumns);
      drawString[sizeOfOutputColumnArray - 1] = 0;
      GD.putstr(0, startRowCommandWindow + rowLoop, drawString);
    }


    return true;
  }


Displaying Command Line Input



As the user types keys for a command, the following code takes those key presses and displays them in the command window section. The drawCommandCharacter function is called whenever a command key is pressed (addCharacter), a command character is removed (removeCharacter), or the entire command line is cleared (clearCommand). 

What makes the code a bit more complicated is that it supports a multiline command. Also rather than forcing the the entire command window to be redrawn with each key press, I'm only drawing the character that has changed.

  bool drawCommandCharacter(int charPosition, char commandChar)
  {
    int drawPositionRow = numberOfScreenRows - numberOfCommandRows;
    int drawPositionColumn = charPosition;

    while (drawPositionColumn >= numberOfScreenColumns)
    {
      // Command array wraps to next line.
      drawPositionColumn -= numberOfScreenColumns;
      drawPositionRow ++;
    }

    char commandString[2];
    commandString[0] = commandChar;
    commandString[1] = 0;
    GD.putstr(drawPositionColumn, drawPositionRow, commandString);
    return true;
  }


Displaying Program Output



Last but not least is the code to actually output to the screen.

  bool addOutputLine(char *outputLineString)
  {
    // Move all existing lines up one.
    for (int rowLoop = 1; rowLoop < numberOfOutputRows; rowLoop++)
    {
      memcpy(outputArray[rowLoop-1], outputArray[rowLoop], sizeOfOutputColumnArray);
    }

    // Get the output length (limited to the width of the screen).
    int outputLength = strlen(outputLineString);
    if (outputLength >= sizeOfOutputColumnArray)
    {
      outputLength = sizeOfOutputColumnArray - 1;
    }
    
    // Take a blank line with null termination. Copy the output string to
    // the blank line, but don't include the output line's null termination!
    // That way we maintain the trailing empty spaces of the blank line.
    char outputLine[sizeOfOutputColumnArray];
    strcpy(outputLine, blankLine);
    memcpy(outputLine, outputLineString, outputLength);
    
    // Add new line to the bottom.
    memcpy(outputArray[numberOfOutputRows-1], outputLine, sizeOfOutputColumnArray);
    
    // Force a window refresh.
    redrawOutputWindow();
    
    return true;
  }


Copyright (c) 2015 Clinton Kam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Tuesday, February 17, 2015

Arduino Retro Computer: Calculator

The most basic function of a computer is to be... a calculator. (For an interesting story, learn about the history of VisiCalc and the Apple 2.)

I have to do calculations for work frequently throughout the day, and I usually have several Windows Calculators up at one time. This retro computer can be perfect for that role.


For my initial implementation, I'm supporting the basic math operators: +, -, *, and /. The user will be able to set the initial value and/or perform those math operations. Each screen of the OS will keep track of its last calculated math operation. That way users can continuously operate on running results. (And if they switch to a different screen, they can have a different running calculation going.)

The following code is a follow-up to the previous post "Arduino Retro Computer: Command Input". I highly recommend reading that post to understand this one better.

The basic premise is if the command string parsed by submitCommand() starts with the character "=", then the command string gets sent to commandMath() for processing.

The following commands are available:
=###   (Set the previousMathResult to the given ###.)
=+###   (Increment the previousMathResult by the given ###.)
=-###   (Decrement the previousMathResult by the given ###.)
=*### (Multiply the previousMathResult by the given ###.)
=/### (Divide the previousMathResult by the given ###.)
=###+### (Add two numbers, store the result in previousMathResult.)
=###-### (Subtract two numbers, store the result in previousMathResult.)
=###*### (Multiply two numbers, store the result in previousMathResult.)
=###/### (Divide two numbers, store the result in previousMathResult.)

Math commands can also be compounded:
=###+###-### (Perform addition followed by subtraction.)


What's Bad / Future Improvements


  • The calculator does not adhere to proper "Operator Precedence". http://en.wikipedia.org/wiki/Order_of_operations Calculations are always performed from the left to the right, regardless of the operation. (And no ability to use parenthesis to denote the order.)
  • More advanced functions such as sin/cos/tan, power, and sqrt aren't (yet) supported.
  • The calculator does not (yet) report hex and binary values. This is important to my work so I'll eventually get that added.
  • Doing =-### subtracts the previous result by ### rather than sets the current value to -###. (It thinks the user wants to perform a subtraction operation rather than set a negative value.)

(I have a strong feeling a "Calculator Part 2" will be posted in the near future.)


Screen Shot



At the start you can see I set the previousMathResult to 5. I then perform several additions on the running value.

I am also able to perform a calculation on 2 numbers regardless of the last result. (2+2=4)


The Code


class ScreenModel
{
  public:
  ..
  double previousMathResult;

  bool init(byte newIndex)
  {
    ..
    previousMathResult = 0.0;
    ..
  }

  bool submitCommand()
  {
    ..
    // Command Formatted is what the user entered with a null terminator.
    // The commandFormatted is incremented by one when it is passed into
    // commandMath so it doesn't have to reparse the leading "=".
    else if (strncmp(commandFormatted, "=", 1) == 0)
    {
      commandMath(commandFormatted+1);
    }
    ..
  }

  double commandMath(char *commandString)
  {
    // Determine the starting number and store in the result variable.
    // If an operator, we'll begin with the result of the previous math calculation.
    double result;
    switch (*commandString)
    {
      case 0:
      case '+':
      case '-':
      case '*':
      case '/':
        // First character is terminator or operator.
        result = previousMathResult;
        break;
      default:
        // Read the starting number. Also advance the commandString pointer
        // to the end of the number / location of the operator.
        result = strtod(commandString, &commandString);
        break;
    }
    
    // Ensure the operator and the next digit are not a terminator.
    while ((*commandString != 0) && (*(commandString+1) != 0))
    {
      // Next character should be the operatot.
      char *operatorChar = commandString;
      // Increment the commandString pointer passed the operator and read the number;
      double number = strtod(++commandString, &commandString);
      switch (*operatorChar)
      {
        case '+':
          result += number;
          break;
        case '-':
          result -= number;
          break;
        case '*':
          result *= number;
          break;
        case '/':
          // Divide by 0 protection.
          if (number != 0.0)
          {
            result /= number;
          }
          break;
        default:
          // Invalid operator - possibly a space. Continue processing to the next
          // character. Even if we have multiple spaces between the numbers and the
          // operators, the while loop will eventually increment the commandString
          // pointer to the next valid operation.
          break;
      }
    }

    // Completed processing the string. Format the result and display!
    char mathString[sizeOfOutputColumnArray];
    // Can't use snprintf - Arduino does not support floats in sprintf.
    //snprintf(mathString, sizeOfOutputColumnArray, "%lf", result);
    dtostrf(result, sizeOfOutputColumnArray-1, 8, mathString);
    addOutputLine(mathString);
    

    previousMathResult = result;
    
    return result;
  }

};


Copyright (c) 2015 Clinton Kam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Sunday, February 8, 2015

Arduino Retro Computer: Command Input

I've created a ScreenModel class that contains a display buffer, command buffer, and functions to interact between the two. My current plan is to have 2 operating modes: OS Mode and Program Mode. If a BASIC program is actively running, then the screen is in Program Mode, otherwise it's in OS Mode. The mode determines where keyboard inputs are processed.

I can define multiple screens and switch between them using the PageUp and PageDown keys. (Similar to how command line Linux works with the function keys.) That way a BASIC program may be running on screen #1, but the user can switch to screen #2 and enter system commands and/or execute a second program.

Please note: All of this is very subject to change. Also, the following code just highlights the command input; we'll cover the video output at a later time.

If anyone is curious my Arduino Sketch size is now 16,856 bytes (of a 258,048 byte maximum). I've got plenty of room for additional development!

Reminder of what the OS looks like (with the output window above and command window below):



Initializing the Keyboard


We covered this code earlier in the keyboard posts, but here's a quick recap in how we initialize the keyboard for input. We can poll the keyboard input with keyboard.available() and keyboard.read() - which we'll do in the primary Arduino loop() (covered below).

#include <PS2Keyboard.h>

const int KeyboardDataPin = 30;
const int KeyboardIRQpin =  2;

PS2Keyboard keyboard;

void setup()
{
  ...
  keyboard.begin(KeyboardDataPin, KeyboardIRQpin);

  // Initialize the serial input with the computer (for debugging).
  Serial.begin(9600);
  ...
}


Initializing the Screens


At this time, I'm defining only 2 screens. It's enough to test the Arduino functions without swallowing up even more of the limited RAM. I may keep it at 2 or I may increase it. The plan for this operating system is to be multitasking, not multithreading. Multiple user programs will be able to be executing at the same time, but it's all on one thread. The OS will have to alternate between the screens to process the BASIC commands.

I keep a pointer to the active screen. Pointers are defined by including a * after the variable type / before the variable name. That keeps the code a bit cleaner. Rather than keeping track of the active screen index and doing something such as:
screen[activeScreen].inputKeyboard(inputKey);
I can do:
activeScreen->inputKeyboard(inputKey);

There are many more uses of pointers throughout the code - especially when handling strings. If you don't know what they are, then I highly recommend you learn about them. (So far I've used them in very simple situations in this project; they can actually be quite powerful.) Unfortunately pointers have a bad reputation, but be cautious of anyone that tells you to "never use" a certain coding feature.

The code to initialize the screen:

const byte numberOfScreens = 2;

// Define the screens. (Each can operate in OS mode or run a program.)
ScreenModel screen[numberOfScreens];
// Keep track of which screen is active to know where to send inputs to.
ScreenModel *activeScreen;

void setup()
{
  ...
  // Initialize each of the screens (pass in the index of the screen to the constructor).
  screen[1].init((byte)1);
  screen[0].init((byte)0);

  // Set the default screen to index 0 and trigger a screen change.
  activeScreen = &screen[0];
  activeScreen->switchToScreen();
  ...
}


Main Program Loop


The main program loop monitors for input from the keyboard. If it's a Page Up or Page Down, then it switches the active screen. Otherwise, it sends the keyboard input to the active screen for processing.

void loop()
{
  // Loop through each screen and process pending actions in running programs. (Not done yet.)
  // Then check keyboard input for the current screen.
  // If the current screen is in OS mode, process inputs as OS commands.
  // If the current screen is in Program mode, send inputs to the Program.
  
  if (keyboard.available())
  {
    // read the next key
    char inputKey = keyboard.read();
    
    // Check if need to swap between OS screens.
    if (inputKey == PS2_PAGEDOWN)
    {
      // Switch to the next screen.
      Serial.print("[PgDn]");
      byte newScreenIndex = activeScreen->screenIndex + 1;
      if (newScreenIndex >= numberOfScreens)
      {
        newScreenIndex = 0;
      }
      activeScreen = &screen[newScreenIndex];
      activeScreen->switchToScreen();
    }
    else if (inputKey == PS2_PAGEUP)
    {
      // Switch to the previous screen.
      Serial.print("[PgUp]");
      byte newScreenIndex;
      if (activeScreen->screenIndex == 0)
      {
        newScreenIndex = numberOfScreens - 1;
      }
      else
      {
        newScreenIndex = activeScreen->screenIndex - 1;
      }
      activeScreen = &screen[newScreenIndex];
      activeScreen->switchToScreen();
    }
    else
    {
      // Send the command to the screen.
      activeScreen->inputKeyboard(inputKey);
    }

  }
  
  return;
}


ScreenModel Class


The bulk of the logic resides in the ScreenModel class. Rather than paste it all as one big block (then try to explain it out of the block), I'm going to look at functions individually. All of the remaining code belongs in the ScreenModel class defined here:

class ScreenModel
{

  public:

  byte screenIndex;

  bool init(byte newIndex)
  {
    screenIndex = newIndex;
    
    operatingMode = operatingModeOS;

    clearCommand();

    .. More init stuff ..

    return true;
  }

  .. A whole bunch of additional variables and functions described below ..

};


ScreenModel Mode Variables


Each screen needs to know which operating mode it is in to know how to process commands (and know if it's actively executing a program!). I've defined a simple enumeration of the operating mode and a byte to store the state.


  enum operatingModeEnum
  {
    operatingModeOS = 0,
    operatingModeProgram = 1
  };
  byte operatingMode;


ScreenModel Keyboard Input


Keyboard presses that are received in the main loop() are passed into the ScreenModel function inputKeyboard(inputKey).

inputKeyboard(inputKey) will relay that input to either  inputKeyboardOS(inputKey) or inputKeyboardProgram(inputKey) depending on the operating mode.

  // Called from the main loop() with the latest keyboard press.
  // Process the key in the OS or currently running program depending
  // on the current mode. 
  void inputKeyboard(char inputKey)
  {
    switch(operatingMode)
    {
      case operatingModeOS:
        inputKeyboardOS(inputKey);
        break;
      case operatingModeProgram:
        inputKeyboardProgram(inputKey);
        break;
    }
    return;
  }


ScreenModel Keyboard Program Input


inputKeyboardProgram(inputKey) hasn't been developed yet. At this point I just have a place holder such that if ESC is pressed it exits out of program mode.

  // Keyboard input intended to be processed by the currently running program.
  void inputKeyboardProgram(char inputKey)
  {
    if (inputKey == PS2_ESC)
    {
      operatingMode = operatingModeProgram;
    }
    // -- Add code to send to active program. --
    return;
  }


ScreenModel Keyboard OS Input


inputKeyboardOS(inputKey) will submitCommand() if enter is pressed, clearCommand() if ESC is pressed, removeCharacter() if delete is pressed, or addCharacter(inputKey) if a character / digit is pressed.

  // Keyboard input intended to be processed by the OS.
  void inputKeyboardOS(char inputKey)
  {
    // check for some of the special keys
    if (inputKey == PS2_ENTER)
    {
      // Submit the current command.
      Serial.println();
      submitCommand();
    }
    else if (inputKey == PS2_TAB)
    {
      // Autofill commands.
      Serial.print("[Tab]");
    }
    else if (inputKey == PS2_ESC)
    {
      // Clear the current command.
      clearCommand();
      Serial.print("[ESC]");
    }
    else if (inputKey == PS2_LEFTARROW)
    {
      // Navigate left through the command array.
      Serial.print("[Left]");
    }
    else if (inputKey == PS2_RIGHTARROW)
    {
      // Navigate right through the command array.
      Serial.print("[Right]");
    }
    else if (inputKey == PS2_UPARROW)
    {
      // Load previous command.
      Serial.print("[Up]");
    }
    else if (inputKey == PS2_DOWNARROW)
    {
      // Load next command.
      Serial.print("[Down]");
    }
    else if (inputKey == PS2_DELETE)
    {
      // Delete the command array character.
      Serial.print("[Del]");
      removeCharacter();
    }
    else if (inputKey >= 32 && inputKey <= 126)
    {
      // Type commands into the command array.
      Serial.print(inputKey);
      addCharacter(inputKey);
    }

    return;    
  }


ScreenModel Command Array


The lower portion of the screen is the command window. It's two lines, 50 characters per line, supporting 100 characters of input total. Each screen has its own command array.

We want the video output to specifically draw empty characters (to clear out what was previously drawn). Thus nowhere in the 100 characters will there ever be a null terminator (0), it will be an empty space instead. You will see many calls to drawCommandCharacter to specifically clear a character from the output. Again, video output will be described in a later post.

For better or worse, the entire command array can be filled; meaning there is no null terminator (not even at the last index of the array). When we have to do additional processing on the command array, we'll add in the null terminator as necessary. I'm not super excited about this approach so it's very likely to change, but it is how I wrote it a while ago (this is some of the first code I wrote for the Retro Computer, before I started the blog).

const int sizeOfCommandArray = 100;

  char commandArray[sizeOfCommandArray];
  int cursorPosition;

  // Character or digit was typed, add it to the command array.
  int addCharacter(char newChar)
  {
    commandArray[cursorPosition] = newChar;
    drawCommandCharacter(cursorPosition, newChar);
    
    return incrementCursorPosition();
  }
  
  // Delete key was pressed, delete from the command array.
  int removeCharacter()
  {
    int newPosition;
    if (cursorPosition == (sizeOfCommandArray - 1) &&
        commandArray[cursorPosition] != ' ')
    {
      // Special case if we're on the last character.
      // Delete the last character but don't decrement back.
      newPosition = cursorPosition;
      commandArray[cursorPosition] = ' ';
      drawCommandCharacter(cursorPosition, ' ');
    }
    else
    {
      // Standard case.
      // Delete the previous character and decrement back.
      newPosition = decrementCursorPosition();
      commandArray[cursorPosition] = ' ';
      drawCommandCharacter(cursorPosition, ' ');
    }
    return newPosition;
  }

  int incrementCursorPosition()
  {
    if (cursorPosition < (sizeOfCommandArray - 1))
    {
      cursorPosition ++;
    }
    return cursorPosition;
  }
  
  bool decrementCursorPosition()
  {
    if (cursorPosition > 0)
    {
      cursorPosition --;
    }
    return cursorPosition;
  }

  bool clearCommand()
  {
    for (int cursorLoop = 0; cursorLoop < sizeOfCommandArray; cursorLoop++)
    {
      if (commandArray[cursorLoop] != ' ')
      {
        drawCommandCharacter(cursorLoop, ' ');
      }
      commandArray[cursorLoop] = ' ';
      
    }
    cursorPosition = 0;

    return true;
  }


ScreenModel Submitting Commands


When an operating system command is submitted (by pressing enter), I want to display it in the output window, execute the command, and clear the input. Ultimately I also want to store the command in a history so I can jump between previously entered commands.

We're straying a little bit into how the video output works, but to be able to understand the submit code at all I need to define a couple constants:
const int numberOfScreenColumns = 50;
const int numberOfOutputColumns = numberOfScreenColumns;
const int sizeOfOutputColumnArray = numberOfOutputColumns + 1;  // Include null terminator.

The following is the start of the submit command. As more operating system commands are created, this list will grow. (And as it grows, I'll investigate alternatives to a bunch of if/elseif strcmp - a slow and CPU intensive operation.)

My next post will dive into the details of a few of these initial commands. I then may address the video output.

  bool submitCommand()
  {
    // Display the command entered in the output window.
    if (cursorPosition == 0)
    {
      // Empty command, just add a space to the output window.
      addOutputLine("");
    }
    else
    {
      // The command array is up to 100 characters (2 lines x 50 characters per line).
      // We have to split up the output into two separate output lines if the user exceeds
      // the size of one line (50 characters). 
      for (int cursorIndex = 0; cursorIndex < cursorPosition; cursorIndex += numberOfOutputColumns)
      {
        char commandLineOutput[sizeOfOutputColumnArray];
        memset(commandLineOutput, 0, sizeOfOutputColumnArray); // Ensure null terminated.
        // On the first loop through, copy the first 50 characters (numberOfOutputColumns).
        // If our cursor position exceeds 50 characters (numberOfOutputColumns), then
        // we'll loop through a second time to copy the remaining 50 characeters.
        memcpy(commandLineOutput, &commandArray[cursorIndex], numberOfOutputColumns);
        addOutputLine(commandLineOutput);
      }
    }
    
    // Parse Command
    // The command array is not null terminated, so we'll define a commandFormatted
    // array that we'll ensure is null terminated. commandFormatted is what the actual
    // command processing will be executed against.
    char commandFormatted[sizeOfCommandArray + 1];
    memcpy(commandFormatted, commandArray, sizeOfCommandArray);
    // Add null terminated.
    if (cursorPosition == (sizeOfCommandArray - 1) &&
        commandArray[cursorPosition] != ' ')
    {
      // Special case if we completely filled the command array.
      commandFormatted[sizeOfCommandArray] = 0;
    }
    else
    {
      // Standard case.
      commandFormatted[cursorPosition] = 0;
    }
    
    if (strcmp(commandFormatted, "help") == 0)
    {
      commandHelp();
    }
    else if (strcmp(commandFormatted, "memory") == 0)
    {
      commandMemory();
    }
    else if (strcmp(commandFormatted, "screen") == 0)
    {
      commandScreen();
    }
    else if (strncmp(commandFormatted, "=", 1) == 0)
    {
      commandMath(commandFormatted+1);
    }
    
    // Clear Command (And eventually save it into history.)
    clearCommand();
    
    return true;
  }


Copyright (c) 2015 Clinton Kam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Saturday, January 24, 2015

Orbital Aero Model: Vector Model

** Update 21 Jan 2017: This Vector class has been deprecated, new version posted here. **

I created a Vector class that is used extensively in the code. Absolute locations, relative locations, velocities, accelerations, and forces all use this same class to hold values in 3 dimensional space. My vector is a simple 1 dimensional array of 3 values. Using an array makes things easier when dealing with matrix math (which will be introduced later). Also keeping it generic means those values could be considered an x/y/z or phi/theta/psi (orientation).

Operator Overloading


Operator overloading allows the programmer to use standard math operators on structures or classes that the compiler would normally not know what to do with. I use them extensively with vectors in the Orbital Aero Model; it helps to both condense the code and make it more readable.

For example, I'm trying to add vectors b and c together to store them in a.

Instead of writing code such as:
a.x = b.x + c.x;
a.y = b.y + c.y;
a.z = b.z + c.z;

I could write:
a = b + c;

The overload for that case would be:
Vector Vector::operator+(const Vector &vectorToAdd)
{
return Vector(this->value[0] + vectorToAdd.value[0], this->value[1] + vectorToAdd.value[1], this->value[2] + vectorToAdd.value[2]);
}

The addition operation is being performed on vector b (this) and vector c (vectorToAdd). It is returning a new vector of the sum that is stored in a.

A similar example is to add the value of b into a.

Instead of writing code such as:
a.x += b.x;
a.y += b.y;
a.z += b.z;

I could write:
a += b;

The overload for that case would be:
Vector &Vector::operator+=(const Vector &vectorToAdd)
{
this->value[0] += vectorToAdd.value[0];
this->value[1] += vectorToAdd.value[1];
this->value[2] += vectorToAdd.value[2];
return *this;
}

The addition operation is being performed on vector a (this) and vector b (vectorToAdd). The summed value is stored directly in a.

Since so much of the code uses my vector class that has operator overloading, I figured it would be best to share that class first. As I extend the functionality of my vector math, I'll update this post to include the latest additions.

More Resources


For more information on vector math, I recommend learning the following concepts:
Vector
Dot Product
Cross Product

If you've never seen a Dot Product or Cross Product before, you may be questioning what they're for. They're actually both VERY important concepts when doing 3-dimensional math. You'll see examples of them in action in the code that follows.

VectorModel.h


#pragma once

class Vector
{
private:

public:
double value[3];

Vector();

Vector(double value0, double value1, double value2);

Vector &Vector::operator=(double newValue);

Vector &Vector::operator=(const Vector &thatVector);

Vector Vector::operator-();

Vector Vector::operator+(const Vector &vectorToAdd);

Vector Vector::operator-(const Vector &vectorToSubtract);

Vector Vector::operator*(double valueToMultiply);

Vector Vector::operator*(const Vector &vectorToMultiply);

Vector Vector::operator/(double valueToDivide);

Vector &Vector::operator+=(const Vector &vectorToAdd);

Vector &Vector::operator-=(const Vector &vectorToSubtract);

Vector &Vector::operator*=(double valueToMultiply);

Vector &Vector::operator/=(double valueToDivide);

double magnitude();

Vector unit();

Vector sign();

static double dotProduct(Vector vector1, Vector vector2);
};


VectorModel.c


#include "math.h"
#include "VectorModel.h"

Vector::Vector()
{
this->value[0] = 0.0;
this->value[1] = 0.0;
this->value[2] = 0.0;
return;
}

Vector::Vector(double value0, double value1, double value2)
{
this->value[0] = value0;
this->value[1] = value1;
this->value[2] = value2;
return;
}

Vector &Vector::operator=(double newValue)
{
this->value[0] = newValue;
this->value[1] = newValue;
this->value[2] = newValue;
return *this;
}

Vector &Vector::operator=(const Vector &thatVector)
{
// Protect against self-assignment. (Otherwise bad things happen when it's reading from memory it has cleared.)
if (this != &thatVector)
{
this->value[0] = thatVector.value[0];
this->value[1] = thatVector.value[1];
this->value[2] = thatVector.value[2];
}
return *this;
}

Vector Vector::operator-()
{
return Vector(-this->value[0], -this->value[1], -this->value[2]);
}

Vector Vector::operator+(const Vector &vectorToAdd)
{
return Vector(this->value[0] + vectorToAdd.value[0], this->value[1] + vectorToAdd.value[1], this->value[2] + vectorToAdd.value[2]);
}

Vector Vector::operator-(const Vector &vectorToSubtract)
{
return Vector(this->value[0] - vectorToSubtract.value[0], this->value[1] - vectorToSubtract.value[1], this->value[2] - vectorToSubtract.value[2]);
}

Vector Vector::operator*(double valueToMultiply)
{
return Vector(this->value[0] * valueToMultiply, this->value[1] * valueToMultiply, this->value[2] * valueToMultiply);
}

Vector Vector::operator*(const Vector &vectorToMultiply)
{
return Vector(this->value[0] * vectorToMultiply.value[0], this->value[1] * vectorToMultiply.value[1], this->value[2] * vectorToMultiply.value[2]);
}

Vector Vector::operator/(double valueToDivide)
{
return Vector(this->value[0] / valueToDivide, this->value[1] / valueToDivide, this->value[2] / valueToDivide);
}

Vector &Vector::operator+=(const Vector &vectorToAdd)
{
this->value[0] += vectorToAdd.value[0];
this->value[1] += vectorToAdd.value[1];
this->value[2] += vectorToAdd.value[2];
return *this;
}

Vector &Vector::operator-=(const Vector &vectorToSubtract)
{
this->value[0] -= vectorToSubtract.value[0];
this->value[1] -= vectorToSubtract.value[1];
this->value[2] -= vectorToSubtract.value[2];
return *this;
}

Vector &Vector::operator*=(double valueToMultiply)
{
this->value[0] *= valueToMultiply;
this->value[1] *= valueToMultiply;
this->value[2] *= valueToMultiply;
return *this;
}

Vector &Vector::operator/=(double valueToDivide)
{
this->value[0] /= valueToDivide;
this->value[1] /= valueToDivide;
this->value[2] /= valueToDivide;
return *this;
}

double Vector::magnitude()
{
return sqrt( (this->value[0] * this->value[0]) + (this->value[1] * this->value[1]) + (this->value[2] * this->value[2]) );
}

Vector Vector::unit()
{
double mag = magnitude();
Vector unitVector;
unitVector.value[0] = this->value[0] / mag;
unitVector.value[1] = this->value[1] / mag;
unitVector.value[2] = this->value[2] / mag;
return unitVector;
}

Vector Vector::sign()
{
Vector signVector;
signVector.value[0] = sgn(this->value[0]);
signVector.value[1] = sgn(this->value[1]);
signVector.value[2] = sgn(this->value[2]);
return signVector;
}

double Vector::dotProduct(Vector vector1, Vector vector2)
{
return ((vector1.value[0] * vector2.value[0]) + (vector1.value[1] * vector2.value[1]) + (vector1.value[2] * vector2.value[2]));
}


Copyright (c) 2015 Clinton Kam
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Thursday, January 22, 2015

Orbital Aero Model: Intro

Back in college (a little over a decade ago), I made an orbital mechanics / space simulation into a MUD (Multi User Dungeon / Multi User Dimension). I used SocketMUD as a base and added in the concept of "bodies" that interact with each other. The bodies would pull on one another with their gravity (based on mass) and could bounce off each other (based on mass and coefficient of restitution). There was also a simple implementation of electromagnetic waves that could originate from a body and propagate through space to impact other bodies (secondary bodies would cause shadows in the electromagnetic wave).

I think I had the sun, planets, Earth's moon, and a couple spacecraft in continuous motion (real time). Users could log in, look at positions and velocities of the bodies, create new bodies, and delete existing bodies. For the year or so I had the MUD running, I think I had one other person log in and play around with the simulation. So I can't say it was the most popular, but most of the time MUDs are meant for killing goblins and leveling up - not watching x/y/z coordinates change.

The original code worked for what it was, but you can learn a lot about coding techniques in 10 years. So this past year I've been dusting off the old stuff and bringing it back to life. It's no longer a MUD, but I do have a simple visualizer...


In this video you can see several spacecraft orbiting the Earth (currently a perfect sphere, but that will change too). The spacecraft nearest to the Earth are actually being dragged down because they're within the Earth's atmosphere. Later in the video you can see a spacecraft orbiting the moon which is orbiting the Earth. I find the history trails of the spacecraft orbiting the moon to be very interesting.

Why call it Orbital Aero?


I want to continue to build as realistic simulation as possible for vehicles travelling on planets, through the atmosphere of planets, and through space. Hence orbital for space and aero for atmospheric effects near a planet. The goal would be to accurately simulate a spacecraft taking off from the Earth, travelling through space, and landing on Mars. (Or to a planet in another solar system!)

80% Solution


I'm developing based on what I'll call the "80% solution". To get near a 100% accurate simulation is extremely expensive (both in development hours and CPU clock cycles). However, to get an 80% accurate simulation isn't nearly as difficult and usually suffices for most customers needs. (Obviously what is "80%" is very subjective.)

I'm going to tackle one concept of the simulation and get it to what I consider 80%, then move on to the next concept. As I go around on the different parts of the simulation (atmospheric modeling or physical shapes of bodies), I'll revisit previous code and essentially redefine 80%.

For example: Right now every body in the simulation is a perfect sphere. To have basic spacecraft orbit a planet, transfer between planets, or escape the solar system, it's good enough. That's what the simulation can do now. However to do interactions between spacecraft and a planet (GPS modeling for example), it's important to have an ellipsoid shape Earth (such as the WGS-84 model). So when the goal is to do GPS, what suffices for 80% is redefined and work will have to be completed to reach that point. To further this point, a WGS-84 round Earth model will suffice for many requirements, but if you're trying to drive a car on the surface of a planet its woefully inadequate. At that point you'll need to load some kind of elevation data such as SRTM (Shuttle RADAR Topology Mission) or DTED (Digital Terrain Elevation Data).

A little background on myself... I got my degree in Aerospace Engineering and have been working on simulation software professionally ever since. This is another long term project. I'll be posting updates on the simulation and the physics behind it as it progresses. If you have questions, comments, suggestions for improvements, or reports of errors then please let me know! (Side note, the Arduino computer is still in the works. I said I've got too many projects!)

Saturday, January 3, 2015

Arduino Retro Computer: Second Joystick

I've been able to build and wire up my second joystick!


Learning some lessons from the first version, I did redesign the lid so it fits better into the case. Rather than the prongs in the corners, the lid now has 4 tabs along the top and bottom of the joystick. I can drill holes through the joystick case and into the tabs to add a set screw to keep the lid in position (in the picture above you can see a set screw at the top left and bottom right of each controller).


The guts of the two controllers...


I printed a new lid for the first joystick as well so they're the same. The wiring inside the two controllers are identical. I did change the pin layout at the Arduino for joystick #1 so it would be grouped with joystick #2. My new pin layout:

const int Joystick1SelectPin = 40;
const int Joystick1RightPin = 41;
const int Joystick1TopPin = 42;
const int Joystick1LeftPin = 43;
const int Joystick1BottomPin = 44;
const int Joystick1AnalogX = 0; // Analog 0
const int Joystick1AnalogY = 1; // Analog 1

const int Joystick2SelectPin = 45;
const int Joystick2RightPin = 46;
const int Joystick2TopPin = 47;
const int Joystick2LeftPin = 48;
const int Joystick2BottomPin = 49;
const int Joystick2AnalogX = 2; // Analog 2
const int Joystick2AnalogY = 3; // Analog 3


Initializing my joysticks with the Joystick Class (described in the previous post):

  joysticks[0].init(Joystick1SelectPin, Joystick1RightPin, Joystick1TopPin, 
                    Joystick1LeftPin, Joystick1BottomPin, Joystick1AnalogX, Joystick1AnalogY);
  joysticks[1].init(Joystick2SelectPin, Joystick2RightPin, Joystick2TopPin, 
                    Joystick2LeftPin, Joystick2BottomPin, Joystick2AnalogX, Joystick2AnalogY);

And of course someone became very interested while I was doing the final wiring.