Sunday, May 10, 2015

Electric Car 240V Charger

What happens when you buy an electric car?

You fill up the tank in your gas car so rarely wasps build a nest under the gas cap!


The Ford Focus electric came with a 120V charger that can plug into a standard U.S. outlet, but it takes 20 hours to charge the car from empty. We had a few times in which we couldn't take a second trip in the car because it was still charging; it was time to get a 240V charger.

I decided on the Juicebox Classic 30 Amp:
 - Rather than being hard wired into the house, it plugs into a standard NEMA 14-50 outlet (common among RV parks). You can also use an adapter to plug into typical dryer outlets. That gives me more options on where to charge.
 - The 30 Amp model meets/exceeds the ability of the car's onboard charger (without going overboard).
 - Long charging cable.
 - It's simple!
 - It's cheap!

Some more advanced chargers allow you to set a delay before charging or report to a smart phone app the charging status, but I didn't need any of that. The car itself allows me to set charging times, and I can use the Ford mobile app to check the charging status. There isn't even a power switch on the Juicebox Classic. When you plug the J1772 into your car, charging starts. Unplug, charging stops.

Please note: I am not an electrician. I just did lots of research on the Internet, spoke with a couple electricians, and managed to put something together that passed the city inspection. (And most importantly not burn down my house.) Don't trust anything I say or did!

Thankfully the distribution panel in my garage has plenty of room.


There are 2 hot leads coming into the panel: the thick red/black wire connected to the top left of the panel and the thick black wire connected to the top right of the panel. Each lead is 120V AC. The 2 leads combined supply 240V.

The white wires along the right side of my panel are the neutral. A 120V outlet will use one hot (either one) and the neutral. The Juicebox doesn't need a neutral wire as it doesn't require a 120V source. However I'll be including the neutral in my 14-50R outlet as who knows what it will be used for in the future.

The bare copper wires along the left side of my panel are the ground.

You can see each one of the hot leads is connected to a bus bar with blades that criss-cross down the panel for the circuit breakers to snap into. A 120V circuit breaker will only snap into one of those blades. A 240V circuit breaker will snap into both blades.

Very educational site / video on this: http://www.askmediy.com/install-220-volt-outlet-4-wire-dryer-outlet/

Shopping list:
 - NEMA 14-50 Outlet
 - Outlet Enclosure
 - 2x Romex Connectors
 - 6/3 gauge Romex cable (6 gauge for the 2 hots and neutral; ground slightly thinner)
 - Cover plate
 - 40 Amp 240V Breaker (Although my plug and wire are rated up to 50 Amp, my charger should only be pulling 30 amps.)
Total about $40

I cut away a section of drywall below my panel to install the new outlet.


AFTER SHUTTING OFF POWER TO THE DISTRIBUTION PANEL....
 - Mounted the outlet enclosure into the side of the stud below the panel.
 - Punched out a hole in the bottom of the panel.
 - Fastened the two connectors (one at the new hole in the bottom of the panel and one at the top of the outlet enclosure).
 - Pulled the Romex cable through the enclosure and into the electrical panel.
 - Tightened the two connectors onto the cable.
 - Connected the wires to the 14-50R outlet. It was very easy - there were labels for the two hots, the neutral, and the ground. Just strip back a little bit of insulation and tighten down on the terminal with a screwdriver. Honestly the hardest part was jamming the outlet with those fat 6 gauge wires into the enclosure.
 - Triple checked everything.
 - Connected the wires to the distribution panel. I stripped back the insulation from the wires as necessary to connect the 2 hot wires directly to the circuit breaker and the last 6 gauge wire to the white neutral bar. The bare copper wire connected to the ground bar. (You'll notice I tried to keep things tidy with the wires going along the outside of the panel.)
 - Triple checked everything again.
 - Reattached the drywall.
 - Turned on power to the panel.
 - Triple checked everything yet again. (You've got a multimeter, right?)
 - I was done! (Well I had to wait for the inspector the next day, but I was basically done.)


JuiceBox installation was stupid easy. You attach the mount to your wall (on studs of course) then the box just slides in from above. Plug in the 14-50 cable into your new outlet and it's powered.


Charging from empty is now only 3.5 hours! Multiple long trips in the car in one day are no longer a problem.

Again, I'm not an electrician so don't trust a word I'm saying. Consider me nothing more than another data point on your quest to get a 240V charger installed. :-)

Tuesday, May 5, 2015

Arduino Retro Computer: BASIC Interpreter (Program Mode)

My first step for the BASIC interpreter was to get the "Program Mode" working. To do that I need to know when I'm in program mode, I need be able to switch how key presses are interpreted (OS vs program), I need to know which line number I'm processing, and I need to parse/process lines of code of running programs.

For initial code commands, I want to be able to print, goto, and end out of a running program.

Which will allow me to create the most important first program of all:

10 PRINT "Hello World!"
20 PRINT "..."
30 GOTO 10


Current Memory Address


I added a new variable to the ScreenMode to keep track of my current program memory address. I will execute code sequentially through the memory blocks (unless I reach a goto / similar loop command).

class ScreenModel
{
  .
  .
  int programMemoryAddressCurrent_bytes;

  bool init(byte newIndex)
  {
    .
    .
    programMemoryAddressCurrent_bytes = programMemoryAddressStart_bytes;
  }

Starting / Stopping Program Mode


Functions were created for starting and stopping the program mode. When called they change the operating mode of that particular ScreenModel, and in the case of execute, it resets the memory address to the start of the program.

Since the program mode and the current program memory address are stored in the ScreenModel, each screen can run (or not run) their own program. Multitasking!

  bool startProgram()
  {
    programMemoryAddressCurrent_bytes = programMemoryAddressStart_bytes;

    operatingMode = operatingModeProgram;

    return true;
  }

  bool stopProgram()
  {
    operatingMode = operatingModeOS;
   
    return true;
  }

To begin executing a program, the user will type "run" in OS mode. The submitCommand() of OS mode interprets the "run" and calls startProgram().

  bool submitCommand()
  {
    .
    .
    else if (strcmp(commandFormatted, "run") == 0)
    {
      startProgram();
    }
    .
    .
  }

When the program is running, the operating mode is operatingModeProgram and thus keyboard inputs get funneled to inputKeyboardProgram rather than inputKeyboardOS.

  // 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;
  }

For the user to halt execution of an active program, they can just push the ESC key.

  // Keyboard input intended to be processed by the currently running program.
  void inputKeyboardProgram(char inputKey)
  {
    if (inputKey == PS2_ESC)
    {
      stopProgram();
    }
    return;
  }

Executing Program Code


Within the main loop() function, I loop through all the screens and call their processEvents().

void loop()
{
  .
  .
  for (int screenIndex = 0; screenIndex < numberOfScreens; screenIndex++)
  {
    screen[screenIndex].processEvents();
  }
}

Within the ScreenModel:

  bool processEvents()
  {
    switch(operatingMode)
    {
      case operatingModeOS:
        // No events to do.
        break;
      case operatingModeProgram:
        programExecuteCurrentMemoryAddress();
        break;
    }
    return true;
  }

processEvents() of the ScreenModel calls programExecuteCurrentMemoryAddress() which gets the program memory string and calls parseProgramString to interpret it.

  bool programExecuteCurrentMemoryAddress()
  {
    char programString[sizeOfCommandArray];
    memset(programString, 0, sizeOfCommandArray);
    getProgramMemoryCommandString(programMemoryAddressCurrent_bytes, programString);

    if (programString[0] == 0)
    {
      addOutputLine("-- Program Ended --");
      stopProgram();
      return false;
    }
   
    return parseProgramString(programString);
  }

Interpreting the Code


parseProgramString increments past the line number and blank spaces to get to the actual command. It then goes through a series of string compares to determine what command the user is trying to do and processes it as appropriate.

  bool parseProgramString(char *programString)
  {
    if (strlen(programString)==0)
    {
      addOutputLine("-- Program Ended --");
      stopProgram();
      return false;
    }
   
    // Skip past the line number and blank spaces.
    char *digitIterator;
    for (digitIterator = programString; (isdigit(*digitIterator) || *digitIterator == ' '); ++digitIterator)
    {
      // Digit or space, we'll advance to the next.
    }
    // Reassign programString pointer to digitIterator (which has passed all digits and spaces).
    programString = digitIterator;
   
    // Convert the command of the program string to lowercase.
    for (char *charIterator = programString; *charIterator != '\0' && *charIterator != ' '; ++charIterator)
    {
      *charIterator = tolower(*charIterator);
    }
   
    if (!strncmp(programString, "print \"", 7))
    {
      parseProgramPrint(programString + 7);
    }
    else if (!strcmp(programString, "end"))
    {
      parseProgramEnd();
    }
    else if (!strncmp(programString, "goto ", 5))
    {
      parseProgramGoto(programString + 5);
    }
    else
    {
      char errorString[sizeOfOutputColumnArray];
      snprintf(errorString, sizeOfOutputColumnArray, "Bad statement, line: %i",
               getProgramMemoryLineNumber(programMemoryAddressCurrent_bytes));
      addOutputLine(errorString);
      stopProgram();
    }
   
    return true;
  }

  bool parseProgramPrint(char *printString)
  {
    char *printChar = printString;
    while (*printChar != 0)
    {
      if (*printChar == '"')
      {
        // Found closing quotes.
        // Clear the quote and break out of the while.
        *printChar = 0;
        break;
      }
      printChar++;
    }
    addOutputLine(printString);

    advanceNextProgramMemoryAddress();

    return true;
  }

  bool parseProgramEnd()
  {
    stopProgram();

    return true;
  }

  bool parseProgramGoto(char *gotoString)
  {
    int targetLineNumber = atoi(gotoString);
    if (targetLineNumber > 0)
    {
      int lastAddress_bytes = getLastProgramMemoryAddress(programMemoryAddressStart_bytes);

      int loopAddress_bytes = programMemoryAddressStart_bytes;
      while (loopAddress_bytes <= lastAddress_bytes)
      {
        byte loopCommandLength_bytes = fram.read8(loopAddress_bytes);

        // Check for end of program.    
        if (loopCommandLength_bytes == 0)
        {
          // End of program, failed goto.
          addOutputLine("Goto line does not exist.");
          stopProgram();
          break;
        }

        // Check if we're replacing or inserting at this line.
        int loopLineNumber = getProgramMemoryLineNumber(loopAddress_bytes);
     
        if (targetLineNumber == loopLineNumber)
        {
          // Goto to this memory location.
          programMemoryAddressCurrent_bytes = loopAddress_bytes;
          break;
        }
   
        // Increment to the next data line.
        loopAddress_bytes += 1 + 4 + loopCommandLength_bytes + 1;
      }
     
    }
    else
    {
      // Error parsing goto.
      addOutputLine("Goto line invalid.");
      stopProgram();
      //advanceNextProgramMemoryAddress();
    }
 
    return true;
  }

Most of the program lines will call advanceNextProgramMemoryAddress() so that the programMemoryAddressCurrent can be incremented to the next line of code. Exceptions would be goto, for, and while statements.

  bool advanceNextProgramMemoryAddress()
  {
    byte commandLength_bytes = fram.read8(programMemoryAddressCurrent_bytes);
    if (commandLength_bytes == 0)
    {
      return false;
    }
 
    // Advance to next program address.
    programMemoryAddressCurrent_bytes += 1 + 4 + commandLength_bytes + 1;

    return true;
  }

Seeing it in Action


Gotta start with Hello World!


Next is a simple demonstration of the goto command skipping a line of code.


And then using the goto command as a loop.


And finally using the end command to halt execution.


Additional Thoughts


I didn't demonstrate it here, but my Arduino computer is a functioning multitasking operating system. I can run my looping hello world program on one screen and then hit PgUp/PgDown to switch to the other screen to enter OS commands or run other programs.

My screen model has been expanding in functionality quite a bit. Ultimately I'll pull the BASIC interpreter code out of the screen model so it can act as a stand-alone useful class.

I've got a solid start on handling variables inside my BASIC programs. My blogging is lacking a bit behind my actual progress but hopefully an update will come soon.


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.