Category Archives: Uncategorized

Magnetic Card Reader (TTL) with Arduino – Part II

<- Part 1

Introduction

In the first part, I did some troubleshooting to fix an issue with the particular card readers that I have on hand. In part two, I will go over some code changes which allow the card reader to work without needing to add a capacitor to the data line and allowing the Arduino Nano to support two card readers.

I submitted an issue/comment to the magstripelib library in GitHub and was promptly received replies from Yoan Tournade and Carlos Rodrigues. Yoan suggested using INPUT_PULLUP on the interrupt pins may help. I modified the call to pinMode to use INPUT_PULLUP rather than INPUT but it made no difference. I suspect that there would be no difference as the card reader is pulling the pin up to the +5V rail so it is unnecessary to do in internal to the Arduino. It was worth a shot and I appreciated the suggestion.

Carlos commented that it is best to use an interrupt on the data pin as it reduces the amount of time spent in the clock interrupt handler.  This is true but the I decided to rewrite the code to read the data pin inside the clock interrupt to solve two problems for my particular instance.

  • Eliminate the need for the capacitor on the data line
  • Allow an Uno or Nano to be able to read two card readers

I had planned to fork Carlos’s original code and make some changes to have him pull back in if he felt they added to the magstripelib but felt that the changes that I wanted to make serve a different goal from Carlos’s code. The changes that I made are for a very small audience of users if any at all so I chose to simply include modified header and class files into the Arduino project.

On to the Code

The following code is available on GitHub at https://github.com/richteel/MagCardReader.

Allowing the Arduino Nano to read two card readers was the real motivation for making the changes to the code. It did present a challenge that I had not anticipated. I had to figure out how to use the same interrupt code to read from a different data pin depending on this pin which fired the interrupt.

I wanted to be able to support 2 card readers on one Arduino Nano as the card readers that I have can be setup to read track 1 or track 2. The cover holding the reader head may be removed. The cover contains two sets of notches which allow the head to be in one of two positions. Since I have two readers, I configured one for track 1 and the other for track 2.

Below is a list of some of the code changes made from the magstripelib.

  • Change class name from MagStripe to MagCardRead
  • Added volatile variable to hold the current data pin number
  • Modified the constructor to allow the assigning of the card detect, strobe (aka clock), and data pins for the reader
  • Removed the data interrupt handler
  • Read the data pin in the strobe interrupt handler

MagCardRead.h

/*
   MagCardRead - Read data from a magnetic stripe card.

   Copyright (c) 2020 Richard Teel <richteel@teelsys.com>

   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.


   Modified from Carlos Rodrigues <cefrodrigues@gmail.com> original MagStripe library
   Based on this: http://cal.freeshell.org/2009/11/magnetic-stripe-reader/
     ...and this: http://www.abacus21.com/Magnetic-Strip-Encoding-1586.html
*/

#ifndef MAGCARDREAD_H
#define MAGCARDREAD_H

#include 

// Default pins used by init
// The data and strobe pins depend on the board model...
#if defined(__AVR_ATmega32U4__)
// Arduino Leonardo and Arduino Micro:
#  define MAGSTRIPE_RDT 3  /* data pin (yellow) */
#  define MAGSTRIPE_RCL 2  /* strobe pin (orange) */
#else
// Arduino Uno and Arduino Mega:
#  define MAGSTRIPE_RDT 2  /* data pin (yellow) */
#  define MAGSTRIPE_RCL 3  /* strobe pin (orange) */
#endif

#  define MAGSTRIPE_CLS 4  /* card present pin (green) */

// Cards can be read in one of these possible ways...
enum ReadDirection { READ_UNKNOWN, READ_FORWARD, READ_BACKWARD };

class MagCardRead {
  public:
    MagCardRead(uint8_t cardDetectPin = MAGSTRIPE_CLS, uint8_t strobePin = MAGSTRIPE_RCL, uint8_t dataPin = MAGSTRIPE_RDT);

    // Initialize the library (attach interrupts)...
    void begin(uint8_t track);

    // Deinitialize the library (detach interrupts)...
    void stop();

    // Check if there is a card present for reading...
    bool available();

    // Read the data from the card as ASCII...
    short read(char *data, uint8_t size);

    // The direction of the last card read...
    enum ReadDirection read_direction();

  private:
    uint8_t _cardDetect_pin;
    uint8_t _strobe_pin;
    uint8_t _data_pin;

    uint8_t _track;
    enum ReadDirection direction;

    void reverse_bits();
    bool verify_parity(uint8_t c);
    bool verify_lrc(short start, short length);
    short find_sentinel(uint8_t pattern);
    short decode_bits(char *data, uint8_t size);
};


#endif  /* MAGCARDREAD_H */


/* vim: set expandtab ts=4 sw=4: */

MagCardRead.cpp

/*
   MagCardRead - Read data from a magnetic stripe card.

   Copyright (c) 2020 Richard Teel <richteel@teelsys.com>

   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.


   Modified from Carlos Rodrigues <cefrodrigues@gmail.com> original MagStripe library
   Based on this: http://cal.freeshell.org/2009/11/magnetic-stripe-reader/
     ...and this: http://www.abacus21.com/Magnetic-Strip-Encoding-1586.html
*/

#include "MagCardRead.h"

#include 

// Enough bits to read any of the three tracks...
#define BIT_BUFFER_LEN 800


// Variables used by the interrupt handlers...
static volatile bool next_bit = 0;                       // next bit to read
static volatile unsigned char bits[BIT_BUFFER_LEN / 8];  // buffer for bits being read
static volatile short num_bits = 0;                      // number of bits already read
static volatile uint8_t vdata_pin = 0;                   // data pin to be read by interrupt


// Manipulate the bit buffer...
static void bits_set(short index, bool bit);
static bool bits_get(short index);

// The interrupt handlers...
static void handle_strobe(void);

MagCardRead::MagCardRead(uint8_t cardDetectPin, uint8_t strobePin, uint8_t dataPin)
{
  _cardDetect_pin = cardDetectPin;
  _strobe_pin = strobePin;
  _data_pin = dataPin;

  direction = READ_UNKNOWN;
}

void MagCardRead::begin(uint8_t track)
{
  _track = track;

  pinMode(_cardDetect_pin, INPUT_PULLUP);
  pinMode(_strobe_pin, INPUT_PULLUP);
  pinMode(_data_pin, INPUT_PULLUP);

  // Reading is more reliable when using interrupts...
  attachInterrupt(digitalPinToInterrupt(_strobe_pin), handle_strobe, FALLING);  // strobe pin
}


void MagCardRead::stop()
{
  detachInterrupt(digitalPinToInterrupt(_strobe_pin));
}


bool MagCardRead::available()
{
  vdata_pin = _data_pin;
  return digitalRead(_cardDetect_pin) == LOW;
}


short MagCardRead::read(char *data, unsigned char size)
{
  // Fail if no card present...
  if (!available()) {
    return -1;
  }

  // Empty the bit buffer...
  num_bits = 0;
  next_bit = 0;

  // Wait while the data is being read by the interrupt routines...
  while (available()) {}

  // Decode the raw bits...
  short chars = decode_bits(data, size);
  direction = READ_FORWARD;

  // If the data looks bad, reverse and try again...
  if (chars < 0) {
    reverse_bits();
    chars = decode_bits(data, size);
    direction = READ_BACKWARD;
  }

  // The card could not be read successfully...
  if (chars < 0) {
    direction = READ_UNKNOWN;
  }

  return chars;
}


enum ReadDirection MagCardRead::read_direction()
{
  return direction;
}


void MagCardRead::reverse_bits()
{
  for (short i = 0; i < num_bits / 2; i++) {
    bool b = bits_get(i);

    bits_set(i, bits_get(num_bits - i - 1));
    bits_set(num_bits - i - 1, b);
  }
}


bool MagCardRead::verify_parity(unsigned char c)
{
  unsigned char parity = 0;

  for (unsigned char i = 0; i < 8; i++) { parity += (c >> i) & 1;
  }

  // The parity must be odd...
  return parity % 2 != 0;
}


bool MagCardRead::verify_lrc(short start, short length)
{
  unsigned char parity_bit = (_track == 1 ? 7 : 5);

  // Count the number of ones per column (ignoring parity bits)...
  for (short i = 0; i < (parity_bit - 1); i++) {
    short parity = 0;

    for (short j = i; j < length; j += parity_bit) {
      parity += bits_get(start + j);
    }

    // Even parity is what we want...
    if (parity % 2 != 0) {
      return false;
    }
  }

  return true;
}


short MagCardRead::find_sentinel(unsigned char pattern)
{
  unsigned char bit_accum = 0;
  unsigned char bit_length = (_track == 1 ? 7 : 5);

  for (short i = 0; i < num_bits; i++) { bit_accum >>= 1;                               // rotate the bits to the right...
    bit_accum |= bits_get(i) << (bit_length - 1);  // ...and add the current bit

    // Stop when the start sentinel pattern is found...
    if (bit_accum == pattern) {
      return i - (bit_length - 1);
    }
  }

  // No start sentinel was found...
  return -1;
}


short MagCardRead::decode_bits(char *data, unsigned char size) {
  short bit_count = 0;
  unsigned char chars = 0;
  unsigned char bit_accum = 0;
  unsigned char bit_length = (_track == 1 ? 7 : 5);

  short bit_start = find_sentinel(_track == 1 ? 0x45 : 0x0b);
  if (bit_start < 0) {  // error, start sentinel not found
    return -1;
  }

  for (short i = bit_start; i < num_bits; i++) { bit_accum >>= 1;                                 // rotate the bits to the right...
    bit_accum |= (bits_get(i) << (bit_length - 1)); // ...and add the current bit bit_count++; if (bit_count % bit_length == 0) { if (chars >= size) {  // error, the buffer is too small
        return -1;
      }

      // A null means we reached the end of the data...
      if (bit_accum == 0) {
        break;
      }

      // The parity must be odd...
      if (!verify_parity(bit_accum)) {
        return -1;
      }

      // Remove the parity bit...
      bit_accum &= ~(1 << (bit_length - 1));

      // Convert the character to ASCII...
      data[chars] = bit_accum + (_track == 1 ? 0x20 : 0x30);
      chars++;

      // Reset...
      bit_accum = 0;
    }
  }

  // Turn the data into a null-terminated string...
  data[chars] = '\0';

  if (data[chars - 2] != '?') {  // error, the end sentinel is not in the right place
    return -1;
  }

  // Verify the LRC (even parity across columns)...
  if (!verify_lrc(bit_start, chars * bit_length)) {
    return -1;
  }

  return chars;
}


static void bits_set(short index, bool bit)
{
  volatile unsigned char *b = &bits[index / 8];
  unsigned char m = 1 << (index % 8);

  *b = bit ? (*b | m) : (*b & ~m);
}


static bool bits_get(short index)
{
  return bits[index / 8] & (1 << (index % 8)); } static void handle_strobe() { // Avoid a crash in case there are too many bits (garbage)... if (num_bits >= BIT_BUFFER_LEN) {
    return;
  }

  next_bit = !digitalRead(vdata_pin);

  bits_set(num_bits, next_bit);
  num_bits++;
}


/* vim: set expandtab ts=4 sw=4: */

MagCardReader.ino

/*
   MagCardRead - Read data from a magnetic stripe card.

   Copyright (c) 2020 Richard Teel <richteel@teelsys.com>

   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.


   Modified from Carlos Rodrigues <cefrodrigues@gmail.com> original MagStripe library
   Based on this: http://cal.freeshell.org/2009/11/magnetic-stripe-reader/
     ...and this: http://www.abacus21.com/Magnetic-Strip-Encoding-1586.html
*/

#include "MagCardRead.h"


#define pinCardDetect1 4
#define pinStrobe1 2
#define pinData1 5

#define pinCardDetect2 6
#define pinStrobe2 3
#define pinData2 7

// Visual feedback when the card is being read...
static const byte READ_LED = 8;  //13
static const byte ERROR_LED = 9; //12

//MagCardRead card;
//MagCardRead card(pinCardDetect);
MagCardRead card(pinCardDetect1, pinStrobe1, pinData1);
MagCardRead card2(pinCardDetect2, pinStrobe2, pinData2);

/*
   Track 3 is the one that can contain the most characters (107).
   We add one more to accomodate the final '\0', as the data is a C string...
*/
static const byte DATA_BUFFER_LEN = 108;
static char data[DATA_BUFFER_LEN];


void readCard(MagCardRead crd) {
  // Don't do anything if there isn't a card present...
  if (!crd.available()) {
    return;
  }

  // Show that a card is being read...
  digitalWrite(READ_LED, HIGH);
  Serial.println("-- Read Completed --");

  // Read the card into the buffer "data" (as a null-terminated string)...
  short chars = crd.read(data, DATA_BUFFER_LEN);

  // Show that the card has finished reading...
  digitalWrite(READ_LED, LOW);

  // If there was an error reading the card, blink the error LED...
  if (chars < 0) {
    digitalWrite(ERROR_LED, HIGH);
    delay(250);
    digitalWrite(ERROR_LED, LOW);
    Serial.println("Read Error...");
    Serial.println(chars);

    return;
  }

  Serial.println("Good Read...");
  Serial.print("Read Direction: ");
  //Serial.println(crd.read_direction());
  Serial.println(crd.read_direction() == 1 ? "Forward" : crd.read_direction() == 2 ? "Backward" : "Unknown");


  // Send the data to the computer...
  Serial.println(data);
}

void setup()
{
  pinMode(READ_LED, OUTPUT);
  pinMode(ERROR_LED, OUTPUT);

  // The card data will be sent over serial...
  Serial.begin(9600);

  // Initialize the library for reading track 2...
  card.begin(1);
  card2.begin(2);

  // Start with the feedback LEDs off...
  digitalWrite(READ_LED, LOW);
  digitalWrite(ERROR_LED, LOW);
}


void loop()
{
  readCard(card);
  readCard(card2);
}


/* EOF - MagCardRead.ino */

Conclusion

Most applications with card readers and Arduinos should make use of the magstripelib library by Carlos Rodrigues. If you have a project that uses one of the card readers mentioned in the original blog post then add a capacitor to the data line as mentioned in the original post. If for some reason, you cannot add a capacitor as mentioned or you need to read from two readers, then the code mentioned here may be a good solution.

Magnetic Card Reader (TTL) with Arduino

Introduction

I was cleaning up and found a project I abandoned about 20 years ago. I was attempting to read magnetic strip cards using the PC Parallel Port. I had done it previously with a different card reader but I had some trouble with these two particular units. I wanted to test them before I decided to get rid of them so I broke out an Arduino Nano and found the magstripelib library written by Carlos Rodrigues on GitHub. I could not get the readers to work with the code so I looked at the libraries but found no issue with them. I then found an issue with the hardware that was rather easy to solve.

The Magnetic Card Readers

I purchased the card readers from either All Electronics or Electronic Goldmine. (Both places are great for obtaining low cost surplus electronics.) The readers are made by Tokin and have a part number of MCS-131P-3. The PCB contains 1550-B014-00-1 and YEC YE-34V N markings.

Card Reader Left Side View   Card Reader Top View   Card Reader Right Side View

I had recalled that there was an issue with the connector cable on the readers that I wanted to resolve. The red wire is connected to ground and the brown wire is connected to Vcc (+5V). This was verified by checking with an ohm meter. Additionally, there is a card sense connection but there is no wire. With a simple modification, we can switch the wires and add a new wire for the card sense connection. While we are at it, We will make the readers breadboard friendly by changing the connector to a 5 pin male header.

Modifying the Magnetic Card Reader

Step 1 – remove the PCB

Use a plastic tool or very carefully use a screwdriver to remove the hot glue holding the PCB in place and carefully slide the card out of the reader.

Card Reader Right Side View   Plastic Tool   Separating PC Board

Once the card is free, you can see that the board layout is made to accommodate the correct connections but there was an effort made to switch the ground and Vcc wires.

Separated PC Board   Close-up of the wires connecting to the PCB

Step 2 – Remove the strain relief zip-tie

Cut the zip-tie and remove.

   

Step 3 – Switch the red and brown wires

Unsolder the red and brown wires, switch them, and solder in place.

   

Step 4 – Add the card sense wire

Cut a length of stranded 26 AWG wire, strip the ends and solder to the card-sense connection.

Step 5 – Add the strain relief

Use a zip-tie to reapply the strain relief to the wire connector using the two holes in the PCB.

             

Step 6 – Reinstall the PCB

Carefully return the PCB to the plastic assembly by reinserting into the slot.

Step 7 – Remove the old connector

Cut off the old connector.

Step 8 – Prep the wires

Strip the ends of the wire and tin with solder.

Step 9 – Cut Heat-Shrink Tubing

Cut 5 pieces of 1/16″ heat-shrink to about 1/4″ in length and one or two pieces of 3/16″ heat-shrink tubing about 3/4″ in length.

Step 10 – Prepare male header pins

Cut a single-row male header to 5 positions.

Step 11 – Solder wires to header

Tin the short pins with solder. Slide the 3/16″ heat-shrink piece(s) over all of the wires together. Solder each wire to the tinned header pins by first sliding a 1/16″ heat-shrink tubing over each wire.

Step 12 – Shrink the heat-shrink tubing

You may want to use a breadboard to keep the pins aligned while heating the tubing as the plastic may become pliable and allow the pins to move. Heat the tubing to fix them in place.

Testing with the Arduino

Wire up the Arduino

The wiring will depend on the processor on the Arduino. If you are uncertain as to which processor your Arduino has, check out the Wikipedia article at https://en.wikipedia.org/wiki/List_of_Arduino_boards_and_compatible_systems.

The Arduino Nano uses the following wiring.

Wire Color Function Arduino Pin
Brown Ground GND
Red Vcc 5V
Green Card Detect 4
Orange Strobe 3
Yellow Data 2

Load the example sketch

Connect the Arduino to the PC and start the Arduino IDE and add the magstripelib library by going to the menu Tools > Manage Libraries… Search for MagStrip and install the library by Carlos Rodrigues.

Find and Install MagStripe Library

Load the MagStripe example by going to the menu File > Examples > MagStripe > MagStripeReader.

Make certain the correct board and port are selected then upload the code to the Arduino. Once the code is loaded, open the serial monitor and run a card through the reader to see if there is any data read. I did not receive any data so I added some lines of code to let me know that there was an issue. Below is the modified code. The modified code let me know that the card was detected and it attempted to read it but ran into an issue decoding the data.

/*
 * MagStripeReader - Read data from a magnetic stripe card (track 1, 2 or 3).
 *
 * Copyright (c) 2010 Carlos Rodrigues <cefrodrigues@gmail.com>
 *
 * 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.
 */


#include 


// Visual feedback when the card is being read...
static const byte READ_LED = 13;
static const byte ERROR_LED = 12;

MagStripe card;

/*
 * Track 3 is the one that can contain the most characters (107).
 * We add one more to accomodate the final '\0', as the data is a C string...
 */
static const byte DATA_BUFFER_LEN = 108;
static char data[DATA_BUFFER_LEN];


void setup()
{
  pinMode(READ_LED, OUTPUT);
  pinMode(ERROR_LED, OUTPUT);
  
  // The card data will be sent over serial...
  Serial.begin(9600);
  
  // Initialize the library for reading track 2...
  card.begin(2);

  // Start with the feedback LEDs off...
  digitalWrite(READ_LED, LOW);
  digitalWrite(ERROR_LED, LOW);
}

 
void loop()
{
  // Don't do anything if there isn't a card present...
  if (!card.available()) {
    return;
  }
  
  // Show that a card is being read...
  digitalWrite(READ_LED, HIGH);
  
  // Read the card into the buffer "data" (as a null-terminated string)...
  short chars = card.read(data, DATA_BUFFER_LEN);
  
  // Show that the card has finished reading...
  digitalWrite(READ_LED, LOW);
    Serial.println("-- Read Completed --");
  
  // If there was an error reading the card, blink the error LED...
  if (chars < 0) {
    digitalWrite(ERROR_LED, HIGH);
    delay(250);
    digitalWrite(ERROR_LED, LOW);
    Serial.println("Read Error...");
    Serial.println(chars);

    return;
  }
  
  Serial.println("Good Read...");
  Serial.print("Read Direction: ");
  //Serial.println(card.read_direction());
  Serial.println(card.read_direction()==1 ? "Forward" : card.read_direction()==2 ? "Backward" : "Unknown");

  // Send the data to the computer...
  Serial.println(data);
}


/* EOF - MagStripeReader.ino */

The result was always a failed read no mater which card was read.

-- Read Completed --
Read Error...
-1

Troubleshooting

As mentioned in the introduction, I first focused on the code to see if I could find something that was amiss. I also attempted to switch the strobe and clock wires to see if I had identified them incorrectly. I then tried using an Arduino Uno to see if the Nano was a problem but I still could not successfully read a card. I then turned my attention to the hardware. The first thing that I did was to use my Tenma 72-8705 oscilloscope to look at the strobe and data lines. I noted that it looked like the data line had narrow spikes on some clock edges. I did not think much of it at the time.


Channel 1 is Strobe and Channel 2 is Data

I decided to break out the Saleae Logic Analyzer to capture the strobe and data lines and manually decode the data to see if the readers are working properly. I saw the same spikes on the data line which occurs with the rising clock edge.


Channel 0 is Strobe, Channel 1 is Data, and Channel 2 is Card Sense

Since the spikes occur on a rising clock edge, it really should not be an issue as the data is only valid on a falling clock edge. I exported the captured data into a CSV file and looked at it in Excel. I was able to decode the data and verified that the data was reading the card properly. To know that the data was read properly, it was necessary to read some of the information from Magtek.

I took another look at the code and see that the data values are being flipped from the previous value. I was unable to determine what the width of the spikes are but I suspect that the spikes may flip the data value in the code but are not wide enough to raise the hardware interrupt on the Arduino when it returns to +5V. If this is the case then it is understandable why there are issues reading the cards. This could be fixed using hardware or software. I took the hardware approach by connecting a 0.01uF capacitor between the data line and Vcc. Once the capacitor was added to remove the spikes from the data line, the code was able to read the cards successfully.

Going Further

It may be possible to resolve the issue observed with this particular card reader by removing the interrupt from the data pin and reading the data value on the clock interrupt instead. This in turn would free up the hardware interrupt used for the data pin and allow any digital pin to be used for the data line. This may allow the reading of two track from readers which read multiple tracks or allow the use of more than one card reader.

Part II ->

eBay Review – 1602 Serial Blue Backlight LCD Display Keypad 4 Arduino Uno R3 Mega 2560 Shield

I purchased an LCD Shield for Arduino from eBay a few weeks ago. I wanted to do a review to help others to get the shield to run on their Arduino or other microprocessor.

Overall Impression

Works as stated and is a good value. I would recommend this shield if you are in the market for a low cost shield with buttons for user input. It is a great shield to get a project going.

Overview

I am not certain who designed the original shield but I assume that it is open-source as there are many similar shields on the market. It appears that most are built from the same schematic that I found on SainSmart’s website at https://www.sainsmart.com/collections/all-for-arduino/products/lcd-keypad-shield-for-arduino-duemilanove-uno-mega2560-mega1280.

The shield uses one analog pin to determine which of the five switches the user has pressed. A series of resistors creates a voltage divider which in turn creates specific voltages for each switched pressed. The pins in use on the board are as follows.

Pin Function Pin Function
Reset Reset Button 10 LCD – Backlight
5V Power 9 LCD – Enable
Ground Ground 8 LCD – RS
A0 Buttons 7 LCD – D7
– SELECT 6 LCD – D6
-LEFT 5 LCD – D5
– UP
– RIGHT
– DOWN
4 LCD – D4
 WARNING: The shield works on 5VDC. Not all Arduinos are 5V tolerant so you may damage your Arduino if you use this shield on an Arduino with a microcontroller running on 3.3V such as the Due. If you do not know what voltage the microprocessor on your Arduino is running at, check the Wikipedia article at https://en.wikipedia.org/wiki/List_of_Arduino_boards_and_compatible_systems.

Code for Testing

The following code was found on SainSmart’s website. It was modified slightly to monitor the value of the A0 pin over the serial connection with the PC.

/*
  The circuit:
 * LCD RS pin to digital pin 8
 * LCD Enable pin to digital pin 9
 * LCD D4 pin to digital pin 4
 * LCD D5 pin to digital pin 5
 * LCD D6 pin to digital pin 6
 * LCD D7 pin to digital pin 7
 * LCD BL pin to digital pin 10
 * KEY pin to analogl pin 0
 */

#include 

LiquidCrystal lcd(8, 13, 9, 4, 5, 6, 7);

char msgs[5][16] = {"Right Key OK ",
                    "Up Key OK    ",               
                    "Down Key OK  ",
                    "Left Key OK  ",
                    "Select Key OK" };

int adc_key_val[5] ={50, 200, 400, 600, 800 };
int NUM_KEYS = 5;
int adc_key_in;
int key=-1;
int oldkey=-1;

void setup()
{
  // open the serial port at 9600 bps:
  Serial.begin(9600);
  
  lcd.clear(); 
  lcd.begin(16, 2);
  lcd.setCursor(0,0); 
  lcd.print("ADC key testing"); 

  //analogReadResolution(12);
}

void loop()
{
  adc_key_in = analogRead(0);    // read the value from the sensor 
  Serial.println(adc_key_in);
  key = get_key(adc_key_in);  // convert into key press
 
  if (key != oldkey)   // if keypress is detected
   {
    delay(50);  // wait for debounce time
    adc_key_in = analogRead(0);    // read the value from the sensor 
    key = get_key(adc_key_in);    // convert into key press
    if (key != oldkey)    
    {   
      lcd.setCursor(0, 1);
      oldkey = key;
      if (key >=0){
           lcd.print(msgs[key]);              
      }
    }
  }
 delay(100);
}

// Convert ADC value to key number
int get_key(unsigned int input)
{
    int k;
   
    for (k = 0; k < NUM_KEYS; k++)
    {
      if (input < adc_key_val[k]) { return k; } } if (k >= NUM_KEYS)k = -1;  // No valid key pressed
    return k;
}

The code does not perform any debouncing of the button presses so you may need to modify the code if it becomes a problem in your project. Another limitation is the shield and/or the code will not be able to determine if more than one switch has been pressed. I have not tested this, but my hypothesis is that the switch with the lowest resistance will be detected.

Issue with 3.3V Adruino Boards

First off, read the warning above as you may damage your Arduino if the microcontroller is running at 3.3V.

After receiving the shield from the seller, frentaly , on eBay, I pulled out my Arduino Due as it happened to be the first Arduino board that I grabbed. I ran the above code and had some strange results. Pressing the “SELECT” button did nothing. Pressing the “LEFT” button registered “SELECT” was pushed. Likewise, pressing “DOWN” registered that the “LEFT” button was pressed. The “UP” and “RIGHT” buttons worked as expected. I sent a message to frentaly as I believed there was a problem with the shield. I then observed that the resistors for the voltage divider were the correct values so I pulled out a Mega board and ran the same test sketch. I found that the LCD button shield worked as expected on the Mega board. I did some investigating and found that the microprocessor on the Due board is running at 3.3V therefore the highest voltage that may be read on any analog pin is 3.3V. I went a bit further to get a bit more information.

The table below provides some information regarding the ADC values reported on A0 with 5V and 3.3V microprocessor Arduino boards. In the table below there is not difference between no button pressed and the “SELECT” button being pressed.

Button

R ID

R (Ohms)

Total R

I

VDC

ADC 5V

ADV 3.3V

NONE R2 Open Infinite  

5

1023

1023

SELECT R6 3,300 5,250

0.000690

3.621

740

1023

LEFT R5 1,000 1,950

0.001266

2.468

505

765

DOWN R4 620 950

0.001695

1.610

329

499

UP R3 330 330

0.002146

0.708

144

219

RIGHT    

0.002500

0.000

0

0

Here are some slides which provide some details on how the buttons work on the shield.

  • No buttons pressed
  • RIGHT button pressed
  • UP button pressed
  • DOWN button pressed
  • LEFT button pressed
  • SELECT button pressed

The schematic is from the SainSmart web page referenced above.

Below is the data presented above shown in a different format.

Setting up ESP32 with Arduino IDE

I purchased a few things on eBay recently, including some ESP32s for $7.99 each from eBay seller, miniduino. I have not worked with the ESP32 but I know that it can work with the Arduino IDE and can run CircuitPython. I am familiar with the Arduino IDE so I wanted to get the ESP32 to work with the Arduino IDE so I can test them out and make certain that they work fine.

Doing a Google search on ESP32 and Arduino IDE returned many results which helped to get me going. The process for getting the ESP32 up and running is nearly the same as with the Teensy boards. The exception is that the Teensy boards have one nice executable to get things setup. The high-level steps to get ESP32 working with Arduino are the following.

  1. Install the latest Arduino IDE if you do not already have it installed. (https://www.arduino.cc/)
  2. Depending on your operating system, you may need to install the driver. I am running Windows 10 so I needed to install the driver for the Silicon Labs CP2102 from https://www.silabs.com/products/development-tools/software/usb-to-uart-bridge-vcp-drivers.
    BTW: The boards that I purchased have no markings on the CP2102 chip. I fear that the chips are counterfeit or a lower grade chip. The first one I tested works so I’ll keep my fingers crossed.
  3. Once the driver was installed, I needed install the Arduino libraries for the ESP32 by cloning the GitHub repository at https://github.com/espressif/arduino-esp32.
    NOTE: A better way it to follow the “Installation instructions using Arduino IDE Boards Manager” instructions on the GitHub page.
  4. Program the ESP32 with the blink example.
    1. Load the blink example in the Arduino IDE and modify the example to use pin 2 for the led.
/*
  Blink
  Turns on an LED on for one second, then off for one second, repeatedly.
 
  This example code is in the public domain.
 */
 
// Pin 13 has an LED connected on most Arduino boards.
// Pin 11 has the LED on Teensy 2.0
// Pin 6  has the LED on Teensy++ 2.0
// Pin 13 has the LED on Teensy 3.0
// give it a name:
int led = 2;

// the setup routine runs once when you press reset:
void setup() {                
  // initialize the digital pin as an output.
  pinMode(led, OUTPUT);     
}

// the loop routine runs over and over again forever:
void loop() {
  digitalWrite(led, HIGH);   // turn the LED on (HIGH is the voltage level)
  delay(1000);               // wait for a second
  digitalWrite(led, LOW);    // turn the LED off by making the voltage LOW
  delay(1000);               // wait for a second
}
  1. Pick the “ESP32 Dev Module” from the “Boards” menu option
  2. Select the COM port for your board
  3. Upload the program to the ESP32 by clicking the upload button then press and hold the boot button on the ESP32 board. You may release the button once the upload starts.

Pipboy 3000

OVERVIEW

My daughter wanted to cosplay as a character from Fallout 3 for Awesomecon 2018 in Washington D.C. We found Noe and Pedro Ruiz’s design on learn.adafruit.com 1 and decided to build it. She decided do this cosplay a few days before the event so it was going to be a challenge to complete it in time but we did it.

I thought getting the electronics in time was going to be the biggest challenge but the biggest challenge was reducing the time for the 3D prints. We started on Sunday and did not finish until Wednesday afternoon. (We did not print continuously so the overall print time was less but not by much.) Noe and Pedro have some tips on reducing print time and they worked in combination with tweaking the infill and layer height.

3D PRINTING

My 3D printer is the XYZprinting daVinci 1.0. The printer is a few years old now and is not the best 3D printer by far but it does a fair job. The printer only supports ABS fulfillment and XYZprinting filament cartridges. I have yet to take time to change the head to support PLA or hack the firmware so I can use other filaments. This limits me to the colors of filament that I may use in the printer. We looked for a brown or green filament to use for the Pipboy but did not find any to my daughter’s liking. We decided to print it in white and spray paint it. We found some metallic paint which seemed to work well. Additionally, we printed the screen visor in black and the LED diffuser in natural.

As a side note, I started using Simplify3D Slicing Software with the printer and it has made a world of difference with the print quality and control of the printer. The prints come out much closer to the designed measurements than the XYZprinting software could accomplish. The software gives more options than the XYZprinting software which really came in handy for this project. Noe and Pedro recommend removing some of the supports for the armband and base-main pieces. The XYZprinting software is all or nothing regarding supports but the Simplify3D software allowed me to remove some supports while leaving others in place.

HARDWARE

Noe and Pedro do a good job in the write-up but as with most build instructions, a few things are left out or are not clear. Here are a few notes that I made along the way but it is not a complete list as we were under a very restrict time constraint to get this project done. Unfortunately that means take notes and documenting issues was not a priority.

Additional Materials needed but not listed in the materials list in the overview.

  • Electronic Parts & Components
  • Tools & Supplies
    • (Optional) Hot Glue and Hot Glue Gun
    • Adhesive foam such as weatherstripping for doors and windows

Below are the connections to the 3.5″ LCD Shield that were used in our build.

PypBoy Function Pin Pin Function PypBoy
LED + 3V3 1 2 5V  
ENC A GPIO2 3 4 5V PB 1000C +
ENC B GPIO3 5 6 GND PB 1000C –
ROT 1 GPIO4 7 8 GPIO14 ENC SW
LED – GND 9 10 GPIO15  
ROT 2 GPIO17 11 12 GPIO18  
ROT 3 GPIO27 13 14 GND ROT –
ROT 4 GPIO22 15 16 GPIO23 ROT 5
  3V3 17 18 GPIO24  
  GPIO10 19 20 GND  
  GPIO9 21 22 GPIO25  
  GPIO11 23 24 GPIO8  
ENC – GND 25 26 GPIO7  

SOFTWARE

We made a few tweaks to the software but they were to change the map and display some different text on some screens and adding some missing code to change other displayed text. I plan to post those minor changes in a future post. The more major changes implemented were to get the pypboy program to launch on start and wire up the rotary encoder so we can safely shutdown the Raspberry Pi.

The shutdown code was modified from Inderpreet Singh’s code on Element14’s website2.

REFERENCES

  1. Noe and Pedro’s writeup on learn.adafruit.com: https://learn.adafruit.com/raspberry-pi-pipboy-3000
  2. Inderpreet Singh’s Shutdown Code on Element14’s site: https://www.element14.com/community/docs/DOC-78055/l/adding-a-shutdown-button-to-the-raspberry-pi-b

Glowforge

My Glowforge finally arrived this week. I pre-ordered it in October 2015 so it took a little over two years of waiting to finally get it.

I designed and printed the obligatory escutcheon for the print button. I chose to name my Glowforge Scotty in honor of James Doohan. His character was my inspiration for becoming an engineer in the first place so I felt it was fitting.


The escutcheon after being printed.


The escutcheon after removing the tape covering.

A few thoughts on the Glowforge interface.
The interface and workflow will take a bit of getting use to but I think it will be fine once I’m more familiar with it. At first I was getting frustrated as I created a PNG image to scale. When I imported it, I thought it was too small until I realized the scale is in inches not mm. Once I realized my error, I looked for an option to change it but could not find one. I attempted to find a way to scale it to the correct size but there is no option to type in the size or view the exact measurement. It is necessary to guess what size it is by looking at the ruler on the screen which is not very accurate. What I ended up doing was using the 1:1 scale printout and placing it in the Glowforge so I could attempt to scale it correctly. Once I did that, I could not figure out how to do cuts where I wanted them. I did some reading and found that an SVG is needed for cuts. I then used Inkscape to create an SVG from a modified image with only the cuts. This worked and I was able to scale it exactly. I then created another PNG file with just the engraving and uploaded both files. I still needed to scale the engraving but that was easy and not critical. Once everything looked good, I pressed the print button. The Glowforge performed the cuts but not the engraving. I then removed the cut image and clicked print again. This time the engraving was done. On my next print, I will need to see what I did wrong here so I do not make that mistake again.

Overall it was easy to use the Glowforge but I do have a few concerns. Firstly, a desktop application to prepare the print would afford a better setup experience. Secondly, it is not possible to print if your network connection is down for any reason or if Glowforge goes out of business. While it is nice to use a device this way, it does leave users vulnerable to the existence of the company and the health of the web servers.


The original PNG file I created.


A modified PNG for the cuts. This file could not be used for the cuts as PNG files may only be used for engraving. I needed to convert this file to SVG using Inkscape.


The modified PNG for the engraving.

Overall, I’m pleased with the Glowforge. I just hope that the company and the web-service run well for many years to come so I may continue to use the Glowforge.

BTW: The size of the escutcheon is 119.903 x 119.903 mm. I was not able to upload the SVG for the cuts so if you decide to use the PNG, you will need to convert it to SVG and resize it.

All Electronics LCD-101 (256×128 LCD) with Arduino

All Electronics has a rather large LCD display which will work great in a Jeopardy! like game that I am building. The display should be rather easy to use with an Arduino or Raspberry Pi but searching for Arduino or Raspberry Pi projects using the display turns up very few details. Fortunately the SED1330F datasheet is fairly well written. With some experimentation, it is possible to figure out how to get it to work. Especially helpful is table 32 in section 9.1.2. Some of the parameters need to be changed but it is a great example of how to get the display to work.

Here is a very short video of the LCD running from an Arduino UNO. The video starts with the display showing the result from the test2 function from the sample code below. I then upload the code again with the test2 function call commented out and the testDataSheetSection9 function call uncommented.

test2(511);

testDataSheetSection9();

#include 

// All Electronics LCD-101
// HG25504 with SED1330F

// LCD Pins
#define d0 14
#define d1 15
#define d2 2
#define d3 3
#define d4 4
#define d5 5
#define d6 6
#define d7 7
#define res 8
#define rd 9
#define wr 10
#define cs 11
#define a0 12

// LCD Comands
#define SYSTEM_SET  0x40
#define SLEEP_IN    0x53
#define DISP_OFF    0x58
#define DISP_ON     0x59
#define SCROLL      0x44
#define CSRFORM     0x5D
#define CGRAM_ADR   0x5C
#define CSRDIR_R    0x4C
#define CSRDIR_L    0x4D
#define CSRDIR_U    0x4E
#define CSRDIR_D    0x4F
#define HDOT_SCR    0x5A
#define OVLAY       0x5B
#define CSRW        0x46
#define CSRR        0x47
#define MWRITE      0x42
#define MREAD       0x43

// LCD Parameters
#define LCD_RES_W 256
#define LCD_RES_H 128
#define CHAR_BITS_WIDE 8  
#define CHARS_PER_LINE  32//8 bit
#define TEXT_ROWS 16

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  Serial.println("Hello world");
  delay(2000);// Give reader a chance to see the output.
  
  // Set pins for output
  setDataPinsForOutput();
  pinMode(res, OUTPUT);
  pinMode(rd, OUTPUT);
  pinMode(wr, OUTPUT);
  pinMode(cs, OUTPUT);
  pinMode(a0, OUTPUT);

  lcdReset();
  lcdInit();

  testDataSheetSection9();
  //test2(511);
}

void loop() {
  // put your main code here, to run repeatedly:
}

/*** Functions ***/

void clearGraphicsLayer() {
  // Set Start at 03E8H
  lcdWriteCommand(CSRW);
  lcdWriteData(0x03);
  lcdWriteData(0xE8);

  // Write 00H (blank data) for 8000 bytes
  lcdWriteCommand(MWRITE);
  for(int i=0; i<8000; i++) {
    lcdWriteData(0x00);
  }
}

void clearTextLayer() {
  // Set Start at 0000H
  lcdWriteCommand(CSRW);
  lcdWriteData(0x00);
  lcdWriteData(0x00);

  // Write 20H (space character) for 1000 bytes
  lcdWriteCommand(MWRITE);
  for(int i=0; i<1000; i++) {
    lcdWriteData(0x20);
  }
}

void lcdInit() {
  Serial.println("Step  3");
  //  3 Initialize LCD Sequence
  lcdWriteCommand(SYSTEM_SET);  // C
  lcdWriteData(0x30);  // P1 M0, M1, M2, W/S, IV, T/L, & DR
  lcdWriteData(0x87);  // P2 FX & WF
  lcdWriteData(0x07);  // P3 FY
  lcdWriteData(0x1F);  // P4 (C/R) Address range covered by one line
  lcdWriteData(0x23);  // P5 (TC/R) Length of one line
  lcdWriteData(0x7F);  // P6 (L/F) Frame height in lines
  lcdWriteData(0x20);  // P7 (APL)
  lcdWriteData(0x00);  // P8 (APH)
}

void lcdReset() {
  digitalWrite(res, LOW);
  // Set init state for wr & cs
  digitalWrite(wr, LOW);
  digitalWrite(cs, LOW);
  delay(50);
}

void lcdWriteCommand(byte command) {
  lcdWriteCtrl(0x05);
  lcdWriteJustData(command);
  digitalWrite(wr, HIGH); // Latch Data
  //delay(10);
}

void lcdWriteCtrl(byte ctrl) {
  digitalWrite(cs, LOW);
  digitalWrite(res, HIGH);
  
  digitalWrite(a0, ctrl & 0x04);
  digitalWrite(wr, ctrl & 0x02);
  digitalWrite(rd, ctrl & 0x01);
}

void lcdWriteData(byte data) {
  lcdWriteCtrl(0x01);
  lcdWriteJustData(data);
  digitalWrite(wr, HIGH); // Latch Data
  //delay(10);
}

void lcdWriteJustData(byte data) {
  digitalWrite(d7, (data & 0x80) == 0x80);
  digitalWrite(d6, (data & 0x40) == 0x40);
  digitalWrite(d5, (data & 0x20) == 0x20);
  digitalWrite(d4, (data & 0x10) == 0x10);
  digitalWrite(d3, (data & 0x08) == 0x08);
  digitalWrite(d2, (data & 0x04) == 0x04);
  digitalWrite(d1, (data & 0x02) == 0x02);
  digitalWrite(d0, (data & 0x01) == 0x01);
}

void setDataPinsForOutput() {
  pinMode(d0, OUTPUT);
  pinMode(d1, OUTPUT);
  pinMode(d2, OUTPUT);
  pinMode(d3, OUTPUT);
  pinMode(d4, OUTPUT);
  pinMode(d5, OUTPUT);
  pinMode(d6, OUTPUT);
  pinMode(d7, OUTPUT);
}

void testDataSheetSection9() {
  Serial.println("Running Test 2");

  Serial.println("Step  4");
  //  4 Set display start address and display regions
  lcdWriteCommand(SCROLL);
  lcdWriteData(0x00);     // P1 (SAD 1 L)
  lcdWriteData(0x00);     // P2 (SAD 1 H)
  lcdWriteData(0x80);     // P3 (SL 1)
  lcdWriteData(0x00);     // P4 (SAD 2 L)
  lcdWriteData(0x10);     // P5 (SAD 2 H)
  lcdWriteData(0x80);     // P6 (SL 2)
  lcdWriteData(0x00);     // P7 (SAD 3 L)
  lcdWriteData(0x04);     // P8 (SAD 3 H)
  //lcdWriteData(0x00);     // P9 (SAD 4 L)
  //lcdWriteData(0x30);     // P10 (SAD 4 H)
  
  Serial.println("Step  5");
  //  5 Set Horizontal Scroll position
  lcdWriteCommand(HDOT_SCR);
  lcdWriteData(0x00);
  
  Serial.println("Step  6");
  //  6 Set display overlay format
  lcdWriteCommand(OVLAY);
  lcdWriteData(0x01);
  
  Serial.println("Step  7");
  //  7 Set display off
  lcdWriteCommand(DISP_OFF);
  lcdWriteData(0x56);

  Serial.println("Step  8");
  //  8 Clear data in first layer with 20H (space character)
  clearTextLayer();

  Serial.println("Step  9");
  //  9 Clear data in second layer with 00H (blank data)
  clearGraphicsLayer();

  Serial.println("Step 10");
  // 10 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x00);
  lcdWriteData(0x00);

  Serial.println("Step 11");
  // 11 Set Cursor type
  lcdWriteCommand(CSRFORM);
  lcdWriteData(0x04);
  lcdWriteData(0x86); 
  
  Serial.println("Step 12");
  // 12 Set display on
  lcdWriteCommand(DISP_ON);

  Serial.println("Step 13");
  // 13 Set Cursor direction - Right
  lcdWriteCommand(CSRDIR_R);

  Serial.println("Step 14");
  // 14 Write characters
  lcdWriteCommand(MWRITE);
  lcdWriteData(0x20);
  lcdWriteData(0x45);
  lcdWriteData(0x50);
  lcdWriteData(0x53);
  lcdWriteData(0x4F);
  lcdWriteData(0x4E);

  Serial.println("Step 15");
  // 15 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x00);
  lcdWriteData(0x10);

  Serial.println("Step 16");
  // 16 Set Cursor direction - Down
  lcdWriteCommand(CSRDIR_D);
  
  Serial.println("Step 17");
  // 17 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 18");
  // 18 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x01);
  lcdWriteData(0x10);

  Serial.println("Step 19");
  // 19 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 20");
  // 20 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x02);
  lcdWriteData(0x10);

  Serial.println("Step 21");
  // 21 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 22");
  // 22 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x03);
  lcdWriteData(0x10);

  Serial.println("Step 23");
  // 23 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 24");
  // 24 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x04);
  lcdWriteData(0x10);

  Serial.println("Step 25");
  // 25 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 26");
  // 26 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x05);
  lcdWriteData(0x10);

  Serial.println("Step 27");
  // 27 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 28");
  // 28 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x06);
  lcdWriteData(0x10);

  Serial.println("Step 29");
  // 29 Fill square
  lcdWriteCommand(MWRITE);
  for(int i0=0; i0<9; i0++) {
    lcdWriteData(0xFF);
  }

  Serial.println("Step 30");
  // 30 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x00);
  lcdWriteData(0x01);

  Serial.println("Step 31");
  // 31 Set Cursor direction - Right
  lcdWriteCommand(CSRDIR_R);

  Serial.println("Step 32");
  // 32 Write more text
  lcdWriteCommand(MWRITE);
  lcdWriteData(0x44);
  lcdWriteData(0x6F);
  lcdWriteData(0x74);
  lcdWriteData(0x20);
  lcdWriteData(0x4D);
  lcdWriteData(0x61);
  lcdWriteData(0x74);
  lcdWriteData(0x72);
  lcdWriteData(0x69);
  lcdWriteData(0x78);
  lcdWriteData(0x20);
  lcdWriteData(0x4C);
  lcdWriteData(0x43);
  lcdWriteData(0x44);  
  
  
  Serial.println("Done with Datasheet Section 9 Sample");
}

void test2(int testNum) {
  
  Serial.println("Running Test 2");

  Serial.println("Step  4");
  //  4 Set display start address and display regions
  lcdWriteCommand(SCROLL);
  lcdWriteData(0x00);     // P1 (SAD 1 L)
  lcdWriteData(0x00);     // P2 (SAD 1 H)
  lcdWriteData(0x80);     // P3 (SL 1)
  lcdWriteData(0x00);     // P4 (SAD 2 L)
  lcdWriteData(0x10);     // P5 (SAD 2 H)
  lcdWriteData(0x80);     // P6 (SL 2)
  lcdWriteData(0x00);     // P7 (SAD 3 L)
  lcdWriteData(0x04);     // P8 (SAD 3 H)
  
  Serial.println("Step  5");
  //  5 Set Horizontal Scroll position
  lcdWriteCommand(HDOT_SCR);
  lcdWriteData(0x00);
  
  Serial.println("Step  6");
  //  6 Set display overlay format
  lcdWriteCommand(OVLAY);
  lcdWriteData(0x01);
  
  Serial.println("Step  7");
  //  7 Set display off
  lcdWriteCommand(DISP_OFF);
  lcdWriteData(0x56);

  Serial.println("Step  8");
  //  8 Clear data in first layer with 20H (space character)
  clearTextLayer();

  Serial.println("Step  9");
  //  9 Clear data in second layer with 00H (blank data)
  clearGraphicsLayer();

  Serial.println("Step 10");
  // 10 Set cursor address
  lcdWriteCommand(CSRW);
  lcdWriteData(0x00);
  lcdWriteData(0x00);

  Serial.println("Step 11");
  // 11 Set Cursor type
  lcdWriteCommand(CSRFORM);
  lcdWriteData(0x04);
  lcdWriteData(0x86); 
  
  Serial.println("Step 12");
  // 12 Set display on
  lcdWriteCommand(DISP_ON);
  //lcdWriteData(0x16);

  Serial.println("Step 13");
  // 13 Set Cursor direction - Right
  lcdWriteCommand(CSRDIR_R);

  Serial.println("Step 14");
  // 14 Write characters
  writeNumbers(testNum);

  
  Serial.println("Done with Test 2");
}


void writeNumbers(int numQty) {
  byte numZero = 0x30;
  int idx = 0;
  byte data = 0x00;
  byte offset = 1;
  
  lcdWriteCommand(MWRITE);
  while(idx < numQty) {
    if(offset > 9)
      offset = 0;

    data = numZero + offset;
    lcdWriteData(data);
    
    offset++;
    idx++;
  }
}

I plan to post more information as the project progresses. I do want to mention a few things that I found out in regards to the display.

  • You may wonder if the HG25504 is single or dual-panel display. It is a one panel display. This becomes obvious when you look at the ICs on the back of the display. The columns are driven by four HD66204FC Dot Matrix LCD column driver with 80-channels. If each column was used on these chips, they could drive 320 columns. This is 64 more columns than the display has but no where near 512 columns which would be required for a dual-panel configuration.
  • Included ICs and function
    • HD66204FC (Qty 4) Dot Matrix LCD column driver with 80-channels
    • HD66205FC (Qty 2) Dot Matrix LCD common driver with 80-channels
    • SED1330F (Qty 1) LCD Controller
    • HY6264A (Qty 1) Static RAM (8K bytes)
    • KA324D (Qty 1) Quad Operational Amplifier
  • Vo (LCD Contrast Voltage) – You really do need to apply at least -10V to Vo in respect to ground. There are some posts regarding this display stating that tying it to ground is enough but it is not. I had applied a negative voltage but was only seeing something when Vo was near ground potential. I was able to initialize the LCD but could not see anything displayed. I knew the screen was initialized because with Vo being close to ground potential, I saw one or more lines on the LCD when it was initially powered up. When I initialized the LCD, the line(s) were gone. I was getting frustrated as I could not display anything on the screen after initializing it. When I finally used a different power supply, I could see that I had been doing things right.
  • Power Requirements (You may have different results)
    • LCD Contrast (-10.5VDC @ 3.5mA)
    • LCD Logic (5VDC @ 10mA)
    • Arduino Uno (5VDC @ 10mA
  • The SED1330F supports 8080 and 6800 family processors. This matters as the LCD is wired for one or the other and the control lines change function based on the wiring of the LCD. Section 2.4.3 of the datasheet specifies that SEL1 and SEL2 determine the operation. Both SEL1 and SEL2 are connected to ground on the LCD therefore it is operating in 8080 mode.

Today’s Tricorder Project Update

Ticorder

Progress has been slow on revision 2 of the TOS Tricorder. I have been working on the software, particularly the plugins for the sensors. I am currently working on the I2C sensors. I have 2 of the 8 sensors coded but found an issue with detecting the I2C devices in the init method without getting the I2C bus into a stuck state. I finally figured out this issue this morning and updated the Raspberry Pi forum post were I found the original code that I used. If you are interested, you may check out the post at https://www.raspberrypi.org/forums/viewtopic.php?f=32&t=114401.

Another quick note, I found the lambda function in Python. I may put this to some good use. I had to do something similar but found a way around it as I never stumbled on this gem. In many ways, I wish I stuck to C++ for this project but it is good to get to know yet another language.
References:
https://stackoverflow.com/questions/30325351/ioerror-errno-5-input-output-error-while-using-smbus-for-analog-reading-thr
http://www.secnetix.de/olli/Python/lambda_functions.hawk

GitLab Source: https://gitlab.com/richteel/TOSTricorder/tree/v2.0.0.0

CNC Machine

I have had a MyDIYCNC Machine sitting around for a few years now. I was having some problems with the controller boards so I set it aside and am just getting back to take another look at it. I found that one of the boards that was sent to me was indeed bad but MyDIYCNC no longer sells machines or parts. I then decided to go to Amazon and pick out a controller board to try. I picked the SainSmart CNC TB6560 3 Axis Stepper Motor Driver Controller Board & Cable. This board seems to work quite well. I have been having a bit of a time getting it to work properly in LinuxCNC but I have got it to home and move but it seems to be at 1/2 scale. I am still tweaking with the settings to see if I can get it to work 100%.

A few notes about wiring and hardware. The MyDIYCNC instructions refer to the motor which moves the Z axis carrier as the Y axis. It appears that it should really be the X axis and the table should be the Y axis. If it were the Y axis, the home position would be in the wrong corner of the machine. I played with the configuration a bit to see if I could get it to work as expected but I had no luck. Finally when I decided to try to swap the X and Y axes did home line up in the correct corner. I could have swapped A & B around on the Y motor and that may have reversed the direction of the motor but I did not want to go there. Swapping the axes made the most sense.

Here is a short video of a test of the CNC. The spindle has a ink cartage in it and is simply running the default LinuxCNC project. The drawing should be 5.3 inches wide but is only half that size.

Here are the settings for LinuxCNC.

001
Base Information

  • Machine Name: MyDIYCNC_inches
  • Axis configuration: XYZ
  • Reset Default machine units: Inch
  • Driver type: Other
  • Driver Timing Settings (The Stepper Drive Timing page on linux.org states that 150,000 ns should be used for all values however the current version of LinuxCNC has a max value of 100,000.)
    • Step Time: 100000
    • Step Space: 100000
    • Direction Hold: 100000
    • Direction Setup: 100000
  • Base Period Maximum Jitter: 9000

002
Parallel Port 1

  • Outputs (PC to Mill):
    • Pin 1: Amplifier Enable (Invert)
    • Pin 2: X Step
    • Pin 3: X Direction
    • Pin 4: Y Step
    • Pin 5: Y Direction
    • Pin 6: Z Step
    • Pin 7: Z Direction
    • Pin 8: Unused
    • Pin 9: Unused
    • Pin 14: Spindle ON (Invert)
    • Pin 16: Unused
    • Pin 17: Unused
  • Inputs (Mill to PC):
    • Pin 10: Both Limit + Home X (Invert)
    • Pin 11: Both Limit + Home Y (Invert)
    • Pin 12: Both Limit + Home Z (Invert)
    • Pin 13: ESTOP In
    • Pin 15: Unused
  • Parport Base Address: 0
  • Output pinout presets: Sherline (The value here is not important. It is actually used with the “Preset” button to load values.)

003
Options

Selections here really do not matter. I have not figured out how to use these yet. I may look into it more in the future.

004
Axis X

  • Motor steps per revolution: 800 (Steppers included with MyDIYCNC are 50 steps per revolution. The TB6560 board has the switches set for 1/16 microstepping. 50 x 16 = 800)
  • Driver Microstepping: 2.0 (Someone posted that 2 meant that microsteping was being used. I had this set to 32 earlier.)
  • Pulley teeth (Motor:Leadscrew): 1.0:1.0 (All axes are direct drive so they are all 1:1.)
  • Leadscrew Pitch: 20.0
  • Maximum Velocity: 0.4
  • Maximum Acceleration: 30.0
  • Home location: 0.125 This took awhile to get. If using the limit switches as home switches as well, we need to back the machine off so that the limit switches are not active when at home position. I choose to back them off 1/8 inch on all axes.)
  • Table travel: 0.0 to 5.5
  • Home Switch Location: 0.0
  • Home Search velocity: -0.05
  • Home Latch direction: Same

005
Axis Y

  • Motor steps per revolution: 800
  • Driver Microstepping: 2.0
  • Pulley teeth (Motor:Leadscrew): 1.0:1.0
  • Leadscrew Pitch: 20.0
  • Maximum Velocity: 0.4
  • Maximum Acceleration: 30.0
  • Home location: 0.125
  • Table travel: 0.0 to 8.0
  • Home Switch Location: 0.0
  • Home Search velocity: -0.05
  • Home Latch direction: Same

006
Axis Z

  • Motor steps per revolution: 800
  • Driver Microstepping: 2.0
  • Pulley teeth (Motor:Leadscrew): 1.0:1.0
  • Leadscrew Pitch: 20.0
  • Maximum Velocity: 0.4
  • Maximum Acceleration: 30.0
  • Home location: -0.125
  • Table travel: -4.0 to 0.0
  • Home Switch Location: 0.0
  • Home Search velocity: 0.05
  • Home Latch direction: Same

007
Almost Done

008
Do you want to quit?

Screenshot added for completeness

I plan to continue to look into what is going on with the scaling and settle on the correct settings or at least settings which will work.

Test Video

I would like to add more videos to the blog. One of the problems that I have had is the time it takes to perform post processing on videos to add different sources and information. I found the Open Broadcaster Software application and started messing around with it and found that it is quite good, once you get use to how it works. Here is my first attempt at creating a video with the software.

BTW: Yes, I totally ripped off some ideas from Adafruit. I’m not too creative so I copied what I like. I will attempt to change it but it is tough as Limor and Phil have created a very nice format for the shows that Adafruit produces. I also like Ben Heck and Dave Jones (EEVBlog) videos as well but I seem to watch more of the videos Adafruit puts out.

Here is my first recording.