Categories
Arduino

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

I posted about the All Electronics LCD-101 display in February 2018. The other day, I noticed that they were back in stock, so I decided to dust off where I last left off with the display. I had designed an I2C backpack with a contrast voltage driver for the display and modified the test Arduino code that I had to work with the backpack. The code was incomplete, so I needed to pick up where I left off, start over, or look at extending another library. I did look at the U8g2 library (https://github.com/olikraus/U8g2_Arduino) but it does not work well with the display. It may be because it is expecting the display to be 240 x 128 rather than 256 x 128.

LCD Pins

Pin No. Symbol Level Function JP2 Pin I2C bit
1 FG 0V Frame Ground 1 N/A
2 Vss(Gnd) 0V Ground 1 N/A
3 Vdd(Vcc) +5V Power supply voltage for logic and LCD 2 N/A
4 Vo Operating voltage for LCD (variable) N/A N/A
5 /RES H/L Reset signal 3 A0
6 /RD H/L Read signal 4 A1
7 /WR H/L Write signal 5 A2
8 /CS H/L Chip select signal 6 A3
9 A0 H/L Data type select signal 7 A4
10 DB0 H/L Display data bit 0 8 B0
11 DB1 H/L Display data bit 1 9 B1
12 DB2 H/L Display data bit 2 10 B2
13 DB3 H/L Display data bit 3 11 B3
14 DB4 H/L Display data bit 4 12 B4
15 DB5 H/L Display data bit 5 13 B5
16 DB6 H/L Display data bit 6 14 B6
17 DB7 H/L Display data bit 7 15 B7

 

A0 /CS /WR /RD /RES
I2C A4 I2C A3 I2C A2 I2C A1 I2C A0 Binary MASK HEX Decimal Valid Op Valid Result Function/Note
X X X X 0 11110 0x1E 30 OR 11110 Reset
X 0 0 0 1 01111 0x0F 15 AND 00001 Invalid State
X 0 1 1 1 01111 0x0F 15 AND 00111 No Operation
X 1 X X 1 01001 0x09 9 AND 01001 No Operation
0 0 0 1 1 00011 0x03 3 EQUAL/XOR 00011 Display data and parameter write
0 0 1 0 1 00101 0x05 5 EQUAL/XOR 00101 Status flag read
1 0 0 1 1 10011 0x13 19 EQUAL/XOR 10011 Command write
1 0 1 0 1 10101 0x15 21 EQUAL/XOR 10101 Display data and cursor address read

 

 

A0 /CS /WR /RD /RES Mask Valid Result
A4 A3 A2 A1 A0 Binary HEX Decimal Valid Op Binary HEX Decimal Function/Note
X X X X 0 11110 0x1E 30 OR 11110 0x1E 30 Reset
X 0 0 0 1 01111 0x0F 15 AND 00001 0x01 1 Invalid State
X 0 1 1 1 01111 0x0F 15 AND 00111 0x07 7 No Operation
X 1 X X 1 01001 0x09 9 AND 01001 0x09 9 No Operation
0 0 0 1 1 00011 0x03 3 EQUAL/XOR 00011 0x03 3 Display data and parameter write
0 0 1 0 1 00101 0x05 5 EQUAL/XOR 00101 0x05 5 Status flag read
1 0 0 1 1 10011 0x13 19 EQUAL/XOR 10011 0x13 19 Command write
1 0 1 0 1 10101 0x15 21 EQUAL/XOR 10101 0x15 21 Display data and cursor address read

 

12 June 2022 Update

I had a question about this display, so I have updated the GitLab repository at https://gitlab.com/richteel/LCD-101/ to include the I2C Backpack that I designed for the LCD with a negative charge pump for the contrast and I2C or SPI interface for Arduino and Raspberry Pi. You may order the black PCB from Oshpark at https://oshpark.com/shared_projects/LwLCCjaW.

The schematic for the backpack is in the LCD-101/LCD-101 2020/I2C Backpack folder and is named I2C Backpack.pdf.

Categories
Arduino

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.

Categories
Arduino

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 ->

Categories
Arduino Product Review

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.h>

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.

Categories
Arduino Microcontroller

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.
Categories
Arduino

Arduino delay(), millis(), etc. cause execution to halt in a library

I ran into an interesting issue yesterday while rewriting some Arduino code into a library. I figure out what was the issue but I’m not sure as to why. If someone reads this and can point me to the reason why this was an issue, please comment on this post. I would appreciate it.

PROBLEM STATEMENT (Observation): Adding any timing function in my library file would halt the execution. The time functions includes delay(), delayMicroseconds(), micros(), and millis().

ROOT CAUSE: The time function was being called in the constructor of the class. If the time function was moved to another method and called separately, the time function would work.

RESOLUTION: Modified the code to create the object, then make a call to a public method of the class. I used begin() as I have seen in other classes.

Problem code demonstrating the issue

sample.ino

#include "mylib.h"

mylib myObject = mylib();

const int ledPin =  LED_BUILTIN;
unsigned long previousMillis = 0;
const long interval = 1000;

void setup() {
   pinMode(ledPin, OUTPUT);
}

void loop() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    digitalWrite(ledPin, !digitalRead(13));
  }
}

mylib.h

#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

class mylib {
  public:
    // Constructors
    mylib();

  private:
    void initialize();
};

mylib.cpp

#include "mylib.h"

mylib::mylib() {
  initialize();
}

mylib::initialize() {
  delay(100);
}

Working code demonstrating the solution

sample.ino

#include "mylib.h"

mylib myObject = mylib();

const int ledPin =  LED_BUILTIN;
unsigned long previousMillis = 0;
const long interval = 1000;

void setup() {
  pinMode(ledPin, OUTPUT);
  myObject.begin();
}

void loop() {
  unsigned long currentMillis = millis();
  
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    digitalWrite(ledPin, !digitalRead(ledPin));
  }
}

mylib.h

#if defined(ARDUINO) && ARDUINO >= 100
  #include "Arduino.h"
#else
  #include "WProgram.h"
#endif

class mylib {
  public:
    // Constructors
    mylib();

    void begin();

  private:
    void initialize();
};

mylib.cpp

#include "mylib.h"

mylib::mylib() {
  
}

mylib::begin() {
  initialize();
}

mylib::initialize() {
  delay(100);
}

Categories
Arduino

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.
Categories
Arduino Raspberry Pi

I2C Communications between Raspberry Pi and Arduino – Update

Over the past few days, I have been able to bit bang the I2C bus with the PIGPIO library. I found a very helpful example at http://raspberrypi.stackexchange.com/questions/3627/is-there-an-i2c-library/4052 which was posted by crj11. This example was using the pigpiod_if.h library. This worked well but required the application to be run with elevated privileges (sudo) which was not acceptable as I needed to run the application from a Python script which in turn was running every 5 minutes from a cron job. The final solution was to use the pigpiod_if2.h and run the pigpio daemon on startup.

The final solution has been running for a few days with three sensors connected over a total of 9 feet (~3 meters) of cable. The data has been logged at Adafruit.IO which I have made public. I may remove or rename the Dashboard in the near future so here is a screenshot of the page.
adafruit_io_v0.1.0

I have also updated the GitHub site for the project so there are now two releases available. Version RC_0.0.1 is the version using the hardware for I2C control and version RC_0.1.0 is the bit bang version.

I still have more work to do to make this project valuable to others. I plan to create some better documentation on the project and provide a full write-up to allow someone to follow along and build there own from start to finish. Right now, 80% to 90% is captured in various places but there are obvious gaps such as the connection to the Raspberry Pi. One can figure this missing information out by looking through the right source files and piece it together however I do not like it when I find a project write-up that is only 80% to 90% documented. It is still better than nothing or only 10% to 25% though.

I hope someone will find some of the information here useful.

Categories
Arduino Raspberry Pi

I2C Communications between Raspberry Pi and Arduino – Update, BOM, and Source Control

Update

I have been working on this project over the past couple of weeks when I have free time but have not been posting updates. This is a general update which is why the title is different from the other posts regarding this project.

My boards from OSH Park arrived last week. I was able to populate them and test them out. Fortunately I did not make any errors on the PCB or schematic so they all worked as designed. There are a few things that I would change on a future version if I choose to make another version of the board.

  1. I would put the ICSP header on the bottom of the board so it does not stick out from the front. This would make it much easier to assemble and would make it possible to not have any exposed circuitry which may allow the device to be damaged from static electricity.
  2. I would move the resistors toward the bottom of the board if possible. It would allow the DHT11 sensor to stick out further from the case.
  3. I would also try to push the ATtiny85 a little further towards the bottom for the same reason as the resistors.

Currently I am looking to bit bang the I2C bus on the Raspberry Pi. I seem to have gotten around the clock stretching issue if there is only one device connected to the I2C bus but as soon as I add another device, the clock stretching becomes an issue again. I really wish that the Pi Foundation would work with Broadcom and fix the issue with the I2C bus.

Here are some pictures.

This slideshow requires JavaScript.

Bill of Materials (BOM)

Materials List (For One Sensor)

  • QTY 1 – PCB (Source: OSH Park)
  • QTY 2 – 3.5mm 4 Pole Audio Jack, J1 & J2 (Source: MCM Electronics NOTE: I ordered 27-9485 but received ones which look like 27-9487)
  • QTY 1 – 2×3 Position Male Header 2.54mm Pitch, JP1 (Source: MCM Electronics NOTE: This is a 2×13 header. You will need to cut it for 2×3 header.)
  • QTY 1 – T-1 3mm LED, LED1 (Source: MCM Electronics NOTE: May choose another color)
  • QTY 1 – CDS Photoresistor Photoelectric 5549 GL5549, PH1 (Source: Ebay)
  • QTY 2 – Resistor 1/4W 10K Ohm, R1 & R2 (Source: MCM Electronics)
  • QTY 1 – Resistor 1/4W 220 Ohm, R3 (Source: MCM Electronics)
  • QTY 1 – DHT11 Digital Temperature & Humidity Sensor Moudle, SE1 (Source: Ebay)
  • QTY 1 – ATMEL ATtiny85, U1 (Source: Newark)

Materials List for Raspberry Pi Hat

  • Qty 1 – Perfboard (Source: Adafruit)
  • Qty 1 – GPIO Header for Raspberry Pi NOTE: The one you will need depends on which model of Raspberry Pi you are using. (Source: Adafruit 2×13 Header for original Raspberry Pi or 2×20 Header for newer Raspberry Pi)
  • QTY 1 – 3.5mm 4 Pole Audio Jack (Source: MCM Electronics NOTE: I ordered 27-9485 but received ones which look like 27-9487)
  • Optional items

Source Control

I have added the source files for the Hardware and Software onto GitHub. I did this so the community may have access to the files and any updates to them. I mainly did it because I was having a hard time remembering which set of files I last worked with especially if a few days went by when I could not work on the project. I think this is a win-win for me and anyone interested in this project.

The files are located at https://github.com/TeelSys/TeelSys_THL. When you first go to the page, it may look like there are mo project files included in the project. If that is the case it is because I am still trying to get everything working properly before I commit code to the master branch. You will see a button with the text “Branch: master” and a downward arrow. Click that button and select another branch such as “dev”. You will then see the project files in their current state.

If you wish to contribute, add a comment here or if you can request through GitHub, do that. I will reply once I see the request but keep in mind that it may be a few days.

Categories
Arduino Raspberry Pi

I2C Communications between Raspberry Pi and Arduino – Part Four

It has been two weeks since my last post but it has been out of frustration on porting the code over to the ATtiny85. The first thing that I ran into was that the Wire library is not supported on the ATtiny85. I needed to modify my code to work with the TinyWireS library. This did not seem too bad and worked once in a while. It was a bit frustrating as I followed examples and it appeared that I was doing everything correctly but that is typically how it goes when coding.

I finally took a look at the specs for the ATtiny85 and realized that memory may be my issue so I started to pare down the memory requirements. The Arduino IDE was not complaining but I recalled an posting that was published on Adafruit a couple of years ago called Arduino Memories. After rereading the article and looking at a couple of other references, I determined that I needed to tackle the memory is see if it was an issue.

At some point in my debugging, I had noticed that the examples for TinyWireS were utilizing a buffer and pointer method to do fast reads and writes. I had a significant switch statement on the request data handler so I removed that and went with the buffer option. By doing so I reserved a whopping 256 bytes for the buffer. This was a very stupid move which I realized when I took a look at the specs for the the ATtiny85. The ATtiny85 has only 512 bytes of RAM so I was consuming half of it for the buffer which did not leave much room for anything else.

I dropped the buffer size down to 32 bytes which helped a great deal. After reducing the size of the buffer, I could get communications between the ATtiny85 and the Raspberry Pi to work a few times before the communications stopped working. I further refined the code to reduce memory usage and swapped out the Adafruit DHT library for one written by Rob Tillaart for the DHT11 only.

Book1

With these modifications, I was able to get the code down to using 113 bytes of RAM and 4,918 bytes (60%) of Flash.

With these changes, the code works quite well but sometimes it appears that the ATtiny85 does not read the correct request from the Raspberry Pi. After some searching it was found that there is a known issue with the Raspberry Pi and clock stretching. It appears that there is a bug which has not been fixed yet if the slave stretches the clock at the right moment and the stretching is too short. The ATtiny85 implements I2C in software so this is going to happen at some point.One of the best articles on this issue is the Raspberry Pi I2C clock-stretching bug.

There are some suggested fixes which I need to read more to understand well enough to use. The most promising fix appears to use Python to perform I2C communication in software. The recommendation is to use the PiGPIO library.

Below is the code that I have thus far on the ATtiny85.

// Uses DHT from Rob instead of Adafruit
// http://playground.arduino.cc/Main/DHTLib
// http://playground.arduino.cc/Main/DHT11Lib


#include <TinyWireS.h>
#include <dht11.h>

#define SLAVE_ADDRESS 0x23

#define PIN_DHT 4
#define PIN_PHOTORESISTOR A3
#define PIN_LED 1

unsigned long previousMillis = 0;
#define interval 2500

#define bufferSz 32
byte dataBuffer[bufferSz] = { 32 };
uint8_t bufferIdx = 0;
boolean firstByteRead = false;

dht11 DHT11;

// Union used to convert float to byte array
union u_tag {
  byte b[4];
  float fval;
} fdata;

void setup() {
  pinMode(PIN_DHT, INPUT);
  pinMode(PIN_PHOTORESISTOR, INPUT);
  pinMode(PIN_LED, OUTPUT);

  digitalWrite(PIN_LED, HIGH);

  // initialize i2c as slave
  TinyWireS.begin(SLAVE_ADDRESS);

  // define callbacks for i2c communication
  TinyWireS.onReceive(receiveData);
  TinyWireS.onRequest(sendData);

  // Initialize dataBuffer
  for (int i = 0; i < bufferSz; i++) {
    dataBuffer[i] = 0xFF;
  }
  // Set LED to blink on each loop
  dataBuffer[3] = 2;
  // Load Model Info
  // T  S  0  0  0  0  0  1
  // 54 53 30 30 30 30 30 31
  String storeText = F("TS000001");
  bufferIdx = 0x10;
  for (int i = 0; i < storeText.length(); i++) {
    dataBuffer[bufferIdx] = storeText[i];
    bufferIdx++;
  }
  // Load Version Info
  // 0  0  0  0  0  0  0  3
  // 30 30 30 30 30 30 30 33
  storeText = F("00000003");
  bufferIdx = 0x18;
  for (int i = 0; i < storeText.length(); i++) {
    dataBuffer[bufferIdx] = storeText[i];
    bufferIdx++;
  }
}

void loop() {
  TinyWireS_stop_check();
  
  unsigned long currentMillis = millis();

  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;

    if (dataBuffer[3] == 2) {
      digitalWrite(PIN_LED, !digitalRead(PIN_LED));
    }

    ReadDHT();
    ReadLightLevel();
  }
}

// callback for received data
void receiveData(uint8_t byteCount) {
  if (byteCount != 1)
  {
    // Sanity-check
    return;
  }

  while (TinyWireS.available()) {
    bufferIdx = TinyWireS.receive();
    firstByteRead = false;

    SetLedStatus();
  }
}

// callback for sending data
void sendData() {
  if (firstByteRead) {
    bufferIdx++;
  }

  firstByteRead = true;

  if(bufferIdx < 0 || bufferIdx >= bufferSz) {
    TinyWireS.send(0xFF);
    return;
  }

  TinyWireS.send(dataBuffer[bufferIdx]);
}

void ReadDHT() {
  int chk = DHT11.read(PIN_DHT);

  if(!chk==DHTLIB_OK) {
    return;
  }
  
  float humidity = (float)DHT11.humidity;
  float temperatureCelsius = (float)DHT11.temperature;
  
  SaveFloatToBuffer(0x04, temperatureCelsius);
  SaveFloatToBuffer(0x08, humidity);
}

void ReadLightLevel() {
  int photocellReading = analogRead(PIN_PHOTORESISTOR);
  float lightReading = ((float)photocellReading / 1023.0) * 100.0;
  SaveFloatToBuffer(0x0C, lightReading);
}

void SaveFloatToBuffer(uint8_t bufIdx, float val) { 
  dataBuffer[bufIdx] = 0;
  dataBuffer[bufIdx + 1] = 0;
  dataBuffer[bufIdx + 2] = 0;
  dataBuffer[bufIdx + 3] = 0;
  
  fdata.fval = val;

  dataBuffer[bufIdx] = fdata.b[3];
  dataBuffer[bufIdx + 1] = fdata.b[2];
  dataBuffer[bufIdx + 2] = fdata.b[1];
  dataBuffer[bufIdx + 3] = fdata.b[0];
  
  //dataBuffer[bufIdx] = (int)val;
}

void SetLedStatus() {
  if (bufferIdx > 2)
    return;

  dataBuffer[3] = 2;
  if (bufferIdx < 2) {
    digitalWrite(PIN_LED, bufferIdx);
    dataBuffer[3] = 0;
    if (digitalRead(PIN_LED) == HIGH) {
      dataBuffer[3] = 1;
    }
  }
}

Here is the code on the Raspberry Pi to verify that things are working.

#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#include <math.h>

#define CMD_GET_TEMPERATURE 1
#define CMD_GET_HUMIDITY 2
#define CMD_SET_LIGHT 3
#define CMD_SET_LED_ON 4
#define CMD_SET_LED_OFF 5
#define CMD_SET_LED_FLASH 6
#define CMD_GET_MODEL 250
#define CMD_GET_VERSION 251
#define CMD_GET_HELLO_WORLD 254

// The PiWeather board i2c address
#define ADDRESS 0x23

// The I2C bus: This is for V2 pi's. For V1 Model B you need i2c-0
char *devName = "/dev/i2c-0";
int file;
int devices[128];
int sensorDevices[128];

union u_tag {
	char b[4];
	float fval;
} fdata;

float computeHeatIndex(float temperature, float percentHumidity, int isFahrenheit);
float convertCtoF(float c);
float convertFtoC(float f);
void displayConnectedI2cDevices();
void dumpDeviceInfo(int deviceAddress);
void findAllI2cDevices();
void findI2cBus();
void findSensors();
float receiveFloat();
int receiveInt();
void receiveString(char *str, int bufSize);
int sendCommand(int deviceAddress, int cmdCode);

int main(int argc, char** argv) {
	// Look for the I2C bus device

  printf("I2C: Connecting\n");
	findI2cBus();
  
  // Find Devices
  findAllI2cDevices();
  
  // Display devices found (Simlar to i2cdetect -y 0)
  displayConnectedI2cDevices();
  
  dumpDeviceInfo(0x23);
  
  sendCommand(0x23, 0x04);
  float temperature = receiveFloat();
  sendCommand(0x23, 0x08);
  float percentHumidity = receiveFloat();
  sendCommand(0x23, 0x0C);
  float lightLevel = receiveFloat();
  
  printf("Temperature = %1.2f (C)\n", temperature);
  printf("Humidity = %1.2f\n", percentHumidity);
  printf("Light Level = %1.2f\n", lightLevel);

  close(file);
  return (EXIT_SUCCESS);
}

float computeHeatIndex(float temperature, float percentHumidity, int isFahrenheit) {
  // Using both Rothfusz and Steadman's equations
  // http://www.wpc.ncep.noaa.gov/html/heatindex_equation.shtml
  float hi;

  if (isFahrenheit==0)
    temperature = convertCtoF(temperature);

  hi = 0.5 * (temperature + 61.0 + ((temperature - 68.0) * 1.2) + (percentHumidity * 0.094));

  if (hi > 79) {
    hi = -42.379 +
             2.04901523 * temperature +
            10.14333127 * percentHumidity +
            -0.22475541 * temperature*percentHumidity +
            -0.00683783 * pow(temperature, 2) +
            -0.05481717 * pow(percentHumidity, 2) +
             0.00122874 * pow(temperature, 2) * percentHumidity +
             0.00085282 * temperature*pow(percentHumidity, 2) +
            -0.00000199 * pow(temperature, 2) * pow(percentHumidity, 2);

    if((percentHumidity < 13) && (temperature >= 80.0) && (temperature <= 112.0))
      hi -= ((13.0 - percentHumidity) * 0.25) * sqrt((17.0 - abs(temperature - 95.0)) * 0.05882);

    else if((percentHumidity > 85.0) && (temperature >= 80.0) && (temperature <= 87.0))
      hi += ((percentHumidity - 85.0) * 0.1) * ((87.0 - temperature) * 0.2);
  }

  return isFahrenheit ? hi : convertFtoC(hi);
}

float convertCtoF(float c) {
  return c * (9.0/5.0) + 32;
}

float convertFtoC(float f) {
  return (f - 32) * (5.0/9.0);
}

void displayConnectedI2cDevices() {
	int idx=0;
	printf("     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f");
	for(idx=0; idx<=0x7F; idx++) {
		if(idx%16==0) {
			printf("\n%d0:",idx/16);
		}
		if(idx>0x07 && idx<0x78) {
			if(devices[idx]>0) {
				if(devices[idx]==-9) {
					printf(" UU");
				}
				else {
					printf(" %02x", idx);
				}
			}
			else {
				printf(" --");
			}
		}
		else {
				printf("   ");
		}
  }
  printf("\n");
}

void dumpDeviceInfo(int deviceAddress) {
	int i=0;
	
	sendCommand(deviceAddress, 0x03);
	
	for(i=0x03; i<0x20; i++) {
		int val = receiveInt();
		
		printf("0x%02x: 0x%02x (%d)\t%c\n", i, val, val, val);
	}
}

void findAllI2cDevices() {
	int idx=0;
  for(idx=0; idx<=0x7F; idx++) {
  	int device=0;
  	
  	if(idx>0x07 && idx<0x78) {
	  	if (ioctl(file, I2C_SLAVE, idx) < 0) {
	  		if(errno == EBUSY) {
	  			device = -9;
	  		}
	  		else {
		  		device = -1;
		  	}
	  	}
	  	else {
	  		char buf[1];
	  		if(read(file, buf, 1) == 1 && buf[0] >= 0) {
	  			device = idx;
	  		}
	  	}
  	}
  	
  	devices[idx] = device;
  }
}

void findI2cBus() {
	if ((file = open(devName, O_RDWR)) < 0) {
  	devName = "/dev/i2c-1";
  	if ((file = open(devName, O_RDWR)) < 0) {
	    fprintf(stderr, "I2C: Failed to access %d\n", devName);
	    exit(1);
	  }
  }
  
  printf("Found I2C bus at %s\n", devName);
}

void findSensors() {
	char *sensorType="TeelSys Data and Light Sensor";
	char buf[256];
	int idx=0;
	int sensorIdx=0;
	// sensorDevices
	// devices
	
	// Clear the sensorDevices array
	for(idx=0; idx<128; idx++) {
		sensorDevices[idx] = 0;
	}
	
  for(idx=0x08; idx<=0x78; idx++) {
  	int device=0;
  	
  	if(devices[idx]==idx) {
  		if(sendCommand(0x22, CMD_GET_MODEL)==1) {
  			int bufSize = sizeof(buf)/sizeof(buf[0]);
  			receiveString(buf, bufSize);
  			if(strlen(sensorType)==strlen(buf) && strcmp(sensorType, buf)==0) {
  				sensorDevices[sensorIdx]=devices[idx];
  				sensorIdx++;
  				printf("Found Sensor at: 0x%02x\n", devices[idx]);
  			}
  		}
  	}
  }
}

void receiveString(char *buf, int bufSize) {
  int charCount=0;
  
	if(read(file, buf, bufSize) == bufSize) {
		for(charCount=0; charCount<bufSize; charCount++) {
			int temp = (int) buf[charCount];
			
			if(temp==255) {
				buf[charCount]=0;
			}
		}
  }
}

int receiveChar() {
  char buf[1];
  char retVal = 0x00;
  
  if (read(file, buf, 1) == 1) {
  	retVal=buf[0];
  }
  
	usleep(10000);
  return retVal;
}

float receiveFloat() {	
	fdata.b[3] = 0;
	fdata.b[2] = 0;
	fdata.b[1] = 0;
	fdata.b[0] = 0;
	
	fdata.b[3] = receiveChar();
	fdata.b[2] = receiveChar();
	fdata.b[1] = receiveChar();
	fdata.b[0] = receiveChar();
	
	return fdata.fval;
	//return (float)fdata.b[3];
}

int receiveInt() {
  return (int)receiveChar();
}

int sendCommand(int deviceAddress, int cmdCode) {
	int retVal = 0;
	unsigned char cmd[16];
	cmd[0] = cmdCode;
	
	if (ioctl(file, I2C_SLAVE, deviceAddress) < 0) {
    fprintf(stderr, "I2C: Failed to acquire bus access/talk to slave 0x%x\n", deviceAddress);
    exit(1);
  }
  
  if (write(file, cmd, 1) == 1) {
  	// As we are not talking to direct hardware but a microcontroller we
    // need to wait a short while so that it can respond.
    //
    // 1ms seems to be enough but it depends on what workload it has
    usleep(10000);
    retVal = 1;
  }
  
  return retVal;
}

Running the Raspberry Pi program produces the following result.

pi@raspberrypi:~ $ ./testi2c07a
I2C: Connecting
Found I2C bus at /dev/i2c-0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
0x03: 0x02 (2)
0x04: 0x41 (65) A
0x05: 0xb8 (184)        ▒
0x06: 0x00 (0)
0x07: 0x00 (0)
0x08: 0x42 (66) B
0x09: 0x0c (12)

0x0a: 0x00 (0)
0x0b: 0x00 (0)
0x0c: 0x42 (66) B
0x0d: 0x2d (45) -
0x0e: 0xff (255)        ▒
0x0f: 0x80 (128)        ▒
0x10: 0x54 (84) T
0x11: 0x53 (83) S
0x12: 0x30 (48) 0
0x13: 0x30 (48) 0
0x14: 0x30 (48) 0
0x15: 0x30 (48) 0
0x16: 0x30 (48) 0
0x17: 0x31 (49) 1
0x18: 0x30 (48) 0
0x19: 0x30 (48) 0
0x1a: 0x30 (48) 0
0x1b: 0x30 (48) 0
0x1c: 0x30 (48) 0
0x1d: 0x30 (48) 0
0x1e: 0x30 (48) 0
0x1f: 0x33 (51) 3
Temperature = 23.00 (C)
Humidity = 35.00
Light Level = 43.50

 

Next step is to see if I can resolve the clock stretching issue and then connect to adafruit.io to post data. If it is not possible to address the clock stretching issue, it would be possible to identify when it occurs and reset the power to the I2C slave devices. I am trying to avoid that solution but I may need to resort to that solution.