Notes About Working with Various Arduino & Netduino Microcontroller Boards

Friday, June 5, 2015

C Program for the Arduino Galileo to Read RFID Cards with an Innovations ID20-LA & Sparkfun RFID USB Reader Board

The Sparkfun RFID USB reader board provides an easy way to hook up an Innovations RFID reader chip.  While its main feature is the USB connection, the RFID USB reader board also provides a break out of the serial and power connections.  In this example, I present a C program for the Arduino Galileo that runs at the Linux command line (not an Arduino sketch) that configures the Galileo's serial pins, creates a serial connection, and reads the ID numbers of a 125kHz RFID card brought near the reader.  To reduce complexity, this code simply prints the card's number to the terminal.

Once again, I found information on Sergey Kiselev's blog - this time "Configuring the Serial Port in Linux" - very helpful.  Working with the RFID reader is by itself fairly simple, but configuring the GPIO pins for the serial connection and setting up the serial connection is a bit more complicated. The code for configuring and opening the serial connection is derived (and updated) from the Linux Documentation Project's program examples.

The program below runs, reads cards, and prints card IDs until the user presses control-c to quit.  The signal() handler restores the serial port's original settings and unexports the GPIO pins from sysfs before the program ends.

Connections


Reader Board   Arduino Galileo
VCC            3.3V
TX             RX (Digital 0)
TXR            TX (Digital 1)
GND            GND

Code 


Here is the C code. Again, this is designed to be compiled and run at the Galileo's Linux command prompt and is not an Arduino sketch.

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

/* Galileo's digital pins 0 & 1 connect to /dev/ttyS0 */
#define SERIAL_DEV "/dev/ttyS0"

/* baudrasettings are defined in <asm/termbits.h>, which is
   included by <termios.h>. Change as needed, keep B 
*/
#define BAUDRATE B9600

/* POSIX compliant source */
#define _POSIX_SOURCE 1

// Function declarations
void configPins();
int openSerial(const char *);
void handleSIGINT(int);

/* Declare variable for original serial port settings here
   so it can be accessed from handleSIGINT function 
*/
struct termios savedTio;

/* Make descripter for serial connection global so
   handleSIGINT() can access it to restore original serial
   settings when program ends. 
*/ 
int serialHandle;

// Set loop that reads serial port to run indefinitely
int run = 1; 

int main() {
   int fd, c, res;
   char buf[255];

   printf("\nScan a tag to see its ID number. Press ctrl-c to quit.\n\n");

   // Program runs until user presses ctrl-c
   signal(SIGINT, handleSIGINT);
   
   configPins();
   serialHandle = openSerial(SERIAL_DEV);

   /* Loop continuously to handle serial input */
   while (run) {     
      /*  read blocks program execution until a line terminating character is
          input, even if more than 255 chars are input. If the number
          of characters read is smaller than the number of chars available,
          subsequent reads will return the remaining chars. res will be set
          to the actual number of characters actually read. 
      */
      res = read(serialHandle, buf, 255);
      /* set end of string, so we can printf */
      buf[res] = 0;
      printf("%s", buf, res);
   }
}

/* Function to configure the Galileo's serial pins (gpio40, gpio41). gpio4 
   is multiplexer that controls how serial pins behave.
*/
void configPins() {
   char *gpio[] = {"4", "40", "41"};
   char path[30];

   // Export ports for sysfs access
   int fd = open("/sys/class/gpio/export", O_WRONLY);
   int c;

   for(c = 0; c < 3; c++) {
      write(fd, gpio[c], strlen(gpio[c]));
   }
   close(fd);

   // Set direction
   for(c = 0; c < 3; c++) {
      sprintf(path, "/sys/class/gpio/gpio%s/direction", gpio[c]);
      fd = open(path, O_WRONLY);
      write(fd, "out", 3);
      close(fd);
   }

   // Set drive to strong for gpio40 & gpio41
   for(c = 1; c < 3; c++) {
      sprintf(path, "/sys/class/gpio/gpio%s/drive", gpio[c]);
      fd = open(path, O_WRONLY);
      write(fd, "strong", 6);
      close(fd);
   }

   // Turn on level shifter controlled by gpio4. Needed for serial.
   fd = open("/sys/class/gpio/gpio4/value", O_WRONLY);
   write(fd, "1", 1);
   close(fd);

   // Set mux for serial by setting gpio40 & gpio41 to 0
   for(c = 1; c < 3; c++) {
      sprintf(path, "/sys/class/gpio/gpio%s/value", gpio[c]);
      fd = open(path, O_WRONLY);
      write(fd, "0", 1);
      close(fd);
   }
}

// Configure & open serial port. Run configPins() first.
int openSerial(const char *serial) {
   struct termios tio;
   /* Open device for reading and writing and not as controlling tty
      because we don't want to get killed if linenoise sends CTRL-C. */
   int fd = open(serial, O_RDWR | O_NOCTTY );
   if (fd < 0) { 
      printf("\nError opening %s.\n", serial); 
      exit(1); 
   }
   /* save current serial port settings to global var so they can be 
      restored when the program ends (see handleSIGINT). . 
   */
   tcgetattr(fd, &savedTio);
   /* clear struct for new port settings */
   memset(&tio, 0, sizeof(tio));

   /* CRTSCTS : output hardware flow control
      CS8     : 8n1 (8bit,no parity,1 stopbit)
      CLOCAL  : local connection, no modem contol
      CREAD   : enable receiving characters 
   */
   tio.c_cflag = BAUDRATE | CRTSCTS | CS8 | CLOCAL | CREAD;

   /* IGNPAR  : ignore bytes with parity errors
      otherwise make device raw (no other input processing) 
   */
   tio.c_iflag = IGNPAR;

   /*  Raw output  */
   tio.c_oflag = 0;

   /* ICANON  : enable canonical input disable all echo functionality, and 
      don't send signals to calling program 
   */
   tio.c_lflag = ICANON;
   /* now clean serial line & activate settings for the port */
   tcflush(fd, TCIFLUSH);
   tcsetattr(fd,TCSANOW, &tio);
   return fd;
}

// Function to clean up & end program when user presses ctrl-C
void handleSIGINT(int signum) {
   char *gpio[] = {"4", "40", "41"};
   char path[30];
   tcsetattr(serialHandle, TCSANOW, &savedTio);
   printf("\nProgram ending...\n");
   // Export ports for sysfs access
   int fd = open("/sys/class/gpio/unexport", O_WRONLY);
   int c;
   for(c = 0; c < 3; c++) {
      write(fd, gpio[c], strlen(gpio[c]));
   }
   close(fd);
   exit(0);
}



Thursday, June 4, 2015

C Program to Read an MPR121 Capacitive Touch Keypad Connected to an Arduino Galileo

This example presents C code that reads input from a Sparkfun MPR121 capacitive touch keypad. There are sample Arduino sketches around, but I wanted to use the keypad with a C program running at the Linux command prompt on the Galileo.

I have tried to keep this code as simple as possible.  This example does not use an interrupt but continuously polls the keypad to check for input and prints out the character pressed. The code doesn't make use of any bells and whistles to track the press and release of the keys. This program treats the keypad like a phone keypad.

For I2C access, this example uses the smbus library compiled as a shared library discussed in this post.

Sparkfun's MPR121 Hookup Guide has been very helpful. My understanding of the configuration parameters is limited, so I have taken the settings from the Sparkfun material. For understanding the Galileo's GPIO pins,  Sergey Kisilev's blog post is a critical piece of the puzzle.

Connections


MPR121  Arduino Galileo
GND     GND
SDA     SDA
SCL     SCL
VCC     3.3V 


Code       


Header File (mpr121.h)

/*
    MPR121.h
April 8, 2010
by: Jim Lindblom
*/

// MPR121 Register Defines
#define MHD_R 0x2B
#define NHD_R 0x2C
#define NCL_R 0x2D
#define FDL_R 0x2E
#define MHD_F 0x2F
#define NHD_F 0x30
#define NCL_F 0x31
#define FDL_F 0x32
#define ELE0_T 0x41
#define ELE0_R 0x42
#define ELE1_T 0x43
#define ELE1_R 0x44
#define ELE2_T 0x45
#define ELE2_R 0x46
#define ELE3_T 0x47
#define ELE3_R 0x48
#define ELE4_T 0x49
#define ELE4_R 0x4A
#define ELE5_T 0x4B
#define ELE5_R 0x4C
#define ELE6_T 0x4D
#define ELE6_R 0x4E
#define ELE7_T 0x4F
#define ELE7_R 0x50
#define ELE8_T 0x51
#define ELE8_R 0x52
#define ELE9_T 0x53
#define ELE9_R 0x54
#define ELE10_T 0x55
#define ELE10_R 0x56
#define ELE11_T 0x57
#define ELE11_R 0x58
#define FIL_CFG 0x5D
#define ELE_CFG 0x5E
#define GPIO_CTRL0 0x73
#define GPIO_CTRL1 0x74
#define GPIO_DATA 0x75
#define GPIO_DIR 0x76
#define GPIO_EN 0x77
#define GPIO_SET 0x78
#define GPIO_CLEAR 0x79
#define GPIO_TOGGLE 0x7A
#define ATO_CFG0 0x7B
#define ATO_CFGU 0x7D
#define ATO_CFGL 0x7E
#define ATO_CFGT 0x7F


// Global Constants
#define TOU_THRESH 0x06
#define REL_THRESH 0x0A

C Source Code (mpr121.c)

#include "mpr121.h"
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <smbus.h>  // smbus.h copied to /usr/include
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdint.h>
#include <stdbool.h>

int main() {
   configPins();
   int deviceHandle = openI2C(0x5A);
   mpr121QuickConfig(deviceHandle);
   while(1) {
      unsigned char lsb = i2c_smbus_read_byte_data(deviceHandle, 0);
      unsigned char msb = i2c_smbus_read_byte_data(deviceHandle, 1);
      short touchStatus = (msb << 8) | lsb;
      int c;
      for(c = 0; c < 12; c++) {
         if(touchStatus & (1 << c)) {
            printf("%c\n", KEY[c]);
            break; // only 1 key even if more pressed at a time
         }
      }
      // Pause for 0.15 second before reading again
      usleep(150000);
   }
   return 0;


// Configure multiplexing of Galileo's GPIO pins for I2C
void configPins() {
   // Set multiplexing for i2c pins via gpio29. Start by exporting gpio29 
   // so it can be accessed via sysfs.
   int fd = open("/sys/class/gpio/export", O_WRONLY);
   write(fd, "29", 2);
   // Export I2C pins, too, so they can be set to pullup
   write(fd, "48", 2);
   write(fd, "49", 2);
   close(fd);
   // Set gpio29's direction to out
   fd = open("/sys/class/gpio/gpio29/direction", O_WRONLY);
   write(fd, "out", 3);
   close(fd);

   // Set gpio29's value to 0 to enable i2c via Quark chip
   fd = open("/sys/class/gpio/gpio29/value", O_WRONLY);
   // Note that 0 is passed as a string/char.
   write(fd, "0", 1);
   close(fd);

   // Enable pullups on I2C pins. The default drive mode is pullup, 
   // so these lines may not be necessary. 
   fd = open("/sys/class/gpio/gpio48/drive", O_WRONLY);
   write(fd, "pullup", 6);
   close(fd);
   fd = open("/sys/class/gpio/gpio49/drive", O_WRONLY);
   write(fd, "pullup", 6);
   close(fd);
}

// Open connection to I2C device & return handle
int openI2C(unsigned char addr) {
   // On Galileo (Clanton), the I2C bus is /dev/i2c-0
   int deviceHandle = open("/dev/i2c-0", O_RDWR);
   // Connect to device at address
   if(ioctl(deviceHandle, I2C_SLAVE, addr) < 0) {
      fprintf(stderr, "Failed to open I2C device as slave.\n");
      exit(1);
   }
   return deviceHandle;
}

void mpr121QuickConfig(int dh) {
  i2c_smbus_write_byte_data(dh, ELE_CFG, 0x00); 

  // This group controls filtering when data is > baseline.
  i2c_smbus_write_byte_data(dh, MHD_R, 0x01);
  i2c_smbus_write_byte_data(dh, NHD_R, 0x01);
  i2c_smbus_write_byte_data(dh, NCL_R, 0x00);
  i2c_smbus_write_byte_data(dh, FDL_R, 0x00);

  // This group controls filtering when data is < baseline.
  i2c_smbus_write_byte_data(dh, MHD_F, 0x01);
  i2c_smbus_write_byte_data(dh, NHD_F, 0x01);
  i2c_smbus_write_byte_data(dh, NCL_F, 0xFF);
  i2c_smbus_write_byte_data(dh, FDL_F, 0x02);
  
  // This group sets touch and release thresholds for each electrode
  i2c_smbus_write_byte_data(dh, ELE0_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE0_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE1_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE1_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE2_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE2_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE3_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE3_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE4_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE4_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE5_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE5_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE6_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE6_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE7_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE7_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE8_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE8_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE9_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE9_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE10_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE10_R, REL_THRESH);
  i2c_smbus_write_byte_data(dh, ELE11_T, TOU_THRESH);
  i2c_smbus_write_byte_data(dh, ELE11_R, REL_THRESH);

  // Set the Filter Configuration
  i2c_smbus_write_byte_data(dh, FIL_CFG, 0x04);
  // Enable all 12 Electrodes
  i2c_smbus_write_byte_data(dh, ELE_CFG, 0x0C); 
  // Enable Auto Config and auto Reconfig
  i2c_smbus_write_byte_data(dh, ELE_CFG, 0x06);
}

To compile the program, run the gcc compiler like this -


gcc -lsmbus -o mpr121 mpr121.c

Wednesday, June 3, 2015

C Program to Read a 4x4 Keypad Connected to an Arduino Galileo

The Arduino keypad library (written in C++) works with Arduino sketches running on the Galileo, but I wanted to access a 4x4 keypad from a standard C program.  The following code shows how to configure the GPIO pins and read the keypad. The program just reads keypresses and prints the characters to the console (at the command line, not the Arduino serial monitor).

The biggest challenge was getting digital pins 2 through 9 configured correctly. The configPins() function in the code below handles the necessary GPIO configuration.

For this sketch, which runs from the Linux command line, to function correctly, make sure that you don't have an Arduino sketch running that accesses digital pins 2 through 9.  Uploading the BareMinimum via the Arduino IDE or deleting the files in the /sketch directory at the command line should take care of any conflicts.

To compile the code on the Galileo, you will need a Yocto image that includes gcc or have a look at this post about installing gcc on Clanton using opkg.

Connections


With the 4x4 keypad face up and the wires pointing down, connect the wires to the Arduino Galileo's digital pins 2 through 9.  It could be connected with wires running from right to left, but then the GPIO numbers and 2-dimensional layout array would need to be revised.

Code


#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>

#define KEY_PRESS_DELAY 100000 // Pause microseconds to debounce
                               // Adjust to control repeat rate
#define KEYPAD_COLUMNS 4
#define KEYPAD_ROWS    4

// Base of path to GPIO devices 
const char *gpioBase = "/sys/class/gpio"; 

// sysfs gpio# for       col3   col2  col1  col0
const char *gpioCols[] = {"32", "18", "28", "17"};
// sysfs gpio# for       row3   row2  row1  row0
const char *gpioRows[] = {"24", "27", "26", "19"};
// sysfs gpio# for mux for col3 and col2
const char *gpioMux[]  = {"31", "30"};

/* This layout assumes that keypad is connected to Galileo with leftmost 
   pin (when looking at front of keypad with wires pointed downward) to 
   Galileo digital 2. The other wires are connected in order on digital 
   pins 3 through 9. 
*/
const char layout[KEYPAD_ROWS][KEYPAD_COLUMNS] = {
   {'1','2','3','A'},
   {'4','5','6','B'},
   {'7','8','9','C'},
   {'*','0','#','D'}
};

// Function declarations
void configPins();  // Configure Galileo's GPIO pins 
char getKey();      // Read keypres

int main() {
   configPins();
   while(1) {
      printf("%c\n", getKey());
   }
   return 0;
}

void configPins() {
   /* Configuration involves the following:
1. Export gpio ports for sysfs access
        2. Set direction (out) for mux ports
        3. Set  value (1) for mux ports 
           that control pins 2 & 3
        4. Set port direction (out) 
        5. Set value (1) for columns
        6. Set drive mode for columns to pullup
        7. Set direction (in) for rows
        8. Set drive mode for rows to pullup

        Note that all configuration values are passed as strings.
  
        I have left out error handling if open() fails.
   */
   // path string for gpio devices & settings
   char path[30];
   int fd; // file handle
   int c;  // loop variable
   // Export gpio ports to sysfs
   sprintf(path, "%s/export", gpioBase);
   fd = open(path, O_WRONLY);
   // Loop to export pins for columns & rows
   for(c = 0; c < KEYPAD_COLUMNS; c++) {
      // export column pin
      write(fd, gpioCols[c], 2);
   }
   for(c = 0; c < KEYPAD_ROWS; c++) {
      // export row pin
      write(fd, gpioRows[c], 2);
   }
   // export multiplex pins
   for(c = 0; c < 2; c++) {
      write(fd, gpioMux[c], 2);
   }
   close(fd);
   // Set direction for the 2 mux ports
   for(c = 0; c < 2; c++) {
      sprintf(path, "%s/gpio%s/direction", gpioBase, gpioMux[c]);
      fd = open(path, O_WRONLY);
      write(fd, "out", 3);
      close(fd);
   }
   // Set value (1) for the 2 mux ports
   for(c = 0; c < 2; c++) {
      sprintf(path, "%s/gpio%s/value", gpioBase, gpioMux[c]);
      // Route pins 2 & 3 through Cypress CY8C9540A rather than Quark by
      // setting value to 1. This makes pins 2 & 3 avail. as sysfs 
      // gpio# 32 & 18.
      fd = open(path, O_WRONLY);
      write(fd, "1", 1);
      close(fd);
   }
   // Set direction (out) for ports for columns
   for(c = 0; c < KEYPAD_COLUMNS; c++) {
      sprintf(path, "%s/gpio%s/direction", gpioBase, gpioCols[c]);
      fd = open(path, O_WRONLY);
      write(fd, "out", 3);
      close(fd);
   }
   // Set colum pins to HIGH (1)
   for(c = 0; c < KEYPAD_COLUMNS; c++) {
      sprintf(path,"%s/gpio%s/value", gpioBase, gpioCols[c]);
      fd = open(path, O_WRONLY);
      write(fd, "1", 1);
      close(fd);
   }
   // Set drive mode for column pins to pullup
   for(c = 0; c < KEYPAD_COLUMNS; c++) {
      sprintf(path, "%s/gpio%s/drive", gpioBase, gpioCols[c]);
      fd = open(path, O_WRONLY);
      write(fd, "pullup", 6);
      close(fd);
   }
   // Set direction of row pins to input (in)
   for(c = 0; c < KEYPAD_ROWS; c++) {
      sprintf(path, "%s/gpio%s/direction", gpioBase, gpioRows[c]);
      fd = open(path, O_WRONLY);
      write(fd, "in", 2);
      close(fd);
   }
   // Set drive mode for row pins to pullup
   for(c = 0; c < KEYPAD_ROWS; c++) {
      sprintf(path, "%s/gpio%s/drive", gpioBase, gpioRows[c]);
      fd = open(path, O_WRONLY);
      write(fd, "pullup", 6);
      close(fd);
   }
}

char getKey() {
   // Need 2 file handles for nested loops to read columns & rows
   int fd, fd2;
   // String to hold path to gpio device & setting  
   char path[30];
   // Initialize key to space so loop runs until key pressed
   char key = ' ';
   int c, d; // loop variables
   // Wait for a key
   while(key == ' ') {
      // Loop through columns
      for(c = 0; c < 4; c++) {
         sprintf(path, "%s/gpio%s/value", gpioBase, gpioCols[c]);
         fd = open(path, O_WRONLY);
         // Set column LOW (0)
         write(fd, "0", 1);
         // Loop through rows & check for 0 to identify key
         int d;
         for(d = 0; d < 4; d++) {
            char rowPath[35];
            char value[1];
            sprintf(rowPath, "%s/gpio%s/value", gpioBase, 
                     gpioRows[d]);
            fd2 = open(rowPath, O_RDONLY);
            read(fd2, value, 1);
            close(fd2);
            if(value[0] == '0') {
               // wait & read again to confirm key press
               usleep(KEY_PRESS_DELAY);
               read(fd2, value, 1);
               if(value[0] == '0') {            
                  key = layout[c][d];
               }
            }
         }
         // Set column pin back to HIGH (1)
         write(fd, "1", 1);
         close(fd);
      }
   }
   return key;
}

Note: This code uses usleep(), which is deprecated by POSIX, so compiling with the -std=c99 switch won't work.  I tried to use nanosleep(), but I ran into problems: nanosleep() kept coming up as undeclared and the definition for struct timespec wasn't found.