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.