Panel Project – First Successes

panel project illustration

My panel connected to a computer.

This is my first article about a project I have been working on for some little time now. I’m presenting it now because I finally got some important features of this project working.

Depicted above is my hardware for this particular project. The “control panel” was once part of a now-replaced flight training computer program. It supposedly represents some of the controls on a Cessna airplane. It was meant to be attached to an old-fashioned game controller to give it a “flight yoke.” I added the basic parts of such a game controller to the top of this panel, but am not using it yet, and probably won’t.

A Problem and a Solution

This panel is operated by a microcontroller that talks to the flight simulation computer program over an “old fashioned” serial communication line. These days microcontrollers are equipped with “flash” memory to hold their programs. This type of memory is permanent, but its contents can be changed. The older microcontrollers were programmed once and that was it. So there was no way for me to adapt this panel for my own use.

I had to rewire it with a new controller somehow. I found a way to do it (which I won’t get into here) using a Digilent product much like the Arduino Uno that I thought would have enough pins to connect to all these buttons. This product was purposely made to be programmed in a very similar way to the Arduino. As gone over earlier, this means this product uses a programming system that is a standard used for many different purposes today.

So, after installing the new microcontroller in the panel, I worked out – over a period of many weeks a few hours at a time – a program that would send out data about the panel. The code for this is gone over below.

Discovering a workable host program

I needed a program running on a computer to stand in for the flight simulation program that went with the original control panel. The original program was made for Windows. Those programs are a bit difficult to write! But there are several ways these days to achieve a similar effect. And one pretty good open source program-writing resource is Processing, which I covered briefly in a previous post.

I discovered that Processing had a little code “library” (a collection of usually short programs that adds a certain feature) for “old fashioned” serial communications. Many modern microcontrollers communicate to the computer that is used to program them through a USB cable that is set up to mimic an “old fashioned” serial communication line.

So all I had to do with the panel was program it to send “old fashioned” serial messages through its USB cable (which also powers it) to the host program written using Processing.

Below is a screenshot of a generic panel program I wrote using Processing to receive and display data from a panel of up to 64 (8X8) separate controls. Most of these controls would ordinarily be switches, and that is the only functionality I have implemented so far.

This panel has two other types of controls besides switches: Rotary switches called “encoders” and sliding controls that can be assigned numerous values depending on position. These are a little bit trickier than switches to code for, so I’ve left them for later.

This application shows a grid of colored rectangles with some text in them. They switch colors depending on data received from the physical panel. This screenshot shows that some of the positions on the panel are open or inactive (grey). A “1” from the panel results in a yellow box and a “0” results in a red box (I was going for something more orange, but I liked this color better). So this application just gives us a start at writing a host program that can take data from a control panel and act on it, do something with it.

panel display

Screenshot of the Processing application that displays the state of the panel.

The Code

With some trepidation, I present below the code more or less as I have written it so far. The data behind the slashes are “comments.” This is information that helps the program writer explain to himself or others what he or she is trying to do with a line or section of code.

The following code was written on the Multi-Platform IDE for uploading into the microcontroller that is wired into this panel.

/*
DigitalReadToSerialMonitor2
Reads digital inputs connected to the controller pins and sends
out the result on a serial communication line.
Modified extensively for the FS-100 application.
This example code is in the public domain.
This version implements simple handshaking scheme
to make sure the host is ready for the next message.
*/
// Set up global (can be seen by all other code) arrays of values:
int prevValues[8][8]; 
// 8 by 8 array holds previous state of the panel to compare to.
int valueArray[8][8]; 
// 8 by 8 array holds most current state of the panel.

void setup()
{
/* These setup steps are needed to tell the microcontroller
what its pins are connected to. I don't use all these pins 
in this particular program. */
pinMode(0, INPUT);
pinMode(1, INPUT);
pinMode(2, INPUT);
pinMode(3, OUTPUT);
pinMode(4, OUTPUT);
pinMode(5, OUTPUT);
pinMode(6, OUTPUT);
pinMode(7, OUTPUT);
pinMode(8, INPUT);
pinMode(9, INPUT);
pinMode(10, INPUT);
pinMode(11, INPUT);
pinMode(12, INPUT);
pinMode(13, INPUT);
pinMode(14, INPUT);
pinMode(15, INPUT);
// Below are the data lines that will be read by this code.
pinMode(34, INPUT);
pinMode(35, INPUT);
pinMode(36, INPUT);
pinMode(37, INPUT);
pinMode(38, INPUT);
pinMode(39, INPUT);
pinMode(40, INPUT);
pinMode(41, INPUT);

/*
The 3 volt outputs of the controller are connected to
inverting transistors ("buffer") that output 5 volt signals.
Reset needs a high pulse, so I give the buffer a low pulse.
This reset pulse is for the rotary encoders, not yet implemented.
*/
digitalWrite(7,HIGH);
delay(5); // delay() introduces a time lag in milliseconds.
digitalWrite(7,LOW); //reset the encoders just once
delay(5); // delay() helps make sure your timing works out.
digitalWrite(7,HIGH);

for (int i=0; i<8; i++) // this line implements a "loop"
{
  for (int j=0; j<8; j++)
    {
      prevValues[i][j] = 0; // This fills one array with zeros.
    }
 }
Serial.begin(9600); // This activates the serial comm line.
}

/* The Processing IDE uses a program element called draw()
to contain code that will be repeated over and over again. 
This IDE calls this loop() as it does not draw to the screen. 
Void refers to a method (function) that does things, 
but does not return any value. */

void loop()
{
/*
During each iteration of this loop, the panel is scanned
by issuing an address, then enabling the controls that are
at that address (up to 8) and reading their values.
Right now, all we are reading is digital (on or off)
controls. These data go into the valueArray.
Addresses are inverted due to inverting buffer...
Start with address 0 which is 111 inverted.
Output enable (on pin 6) is active-low. 
That means issuing a LOW turns it off (makes it HIGH).
*/
digitalWrite(6,LOW); // Ensure outputs are disabled.
delay(5);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
digitalWrite(5,HIGH);

digitalWrite(6,HIGH); // Now enable the control outputs.
delay(5);
// Below illustrates how an array is addressed in code.
valueArray[0][0] = digitalRead(34);
valueArray[0][1] = digitalRead(35);
valueArray[0][2] = digitalRead(36);
valueArray[0][3] = digitalRead(37);
valueArray[0][4] = digitalRead(38);
valueArray[0][5] = digitalRead(39);
valueArray[0][6] = digitalRead(40);
valueArray[0][7] = digitalRead(41);
digitalWrite(6,LOW);

// Repeat for address 2:
digitalWrite(6,LOW);
delay(5);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
digitalWrite(5,HIGH);

digitalWrite(6,HIGH);
delay(5);
valueArray[2][0] = digitalRead(34);
valueArray[2][1] = digitalRead(35);
valueArray[2][2] = digitalRead(36);
valueArray[2][3] = digitalRead(37);
valueArray[2][4] = digitalRead(38);
valueArray[2][5] = digitalRead(39);
valueArray[2][6] = digitalRead(40);
valueArray[2][7] = digitalRead(41);
digitalWrite(6,LOW);

// Repeat for address 4:
digitalWrite(6,LOW);
delay(5);
digitalWrite(3,HIGH);
digitalWrite(4,HIGH);
digitalWrite(5,LOW);

digitalWrite(6,HIGH);
delay(5);
valueArray[4][0] = digitalRead(34);
valueArray[4][1] = digitalRead(35);
valueArray[4][2] = digitalRead(36);
valueArray[4][3] = digitalRead(37);
valueArray[4][4] = digitalRead(38);
valueArray[4][5] = digitalRead(39);
valueArray[4][6] = digitalRead(40);
valueArray[4][7] = digitalRead(41);
digitalWrite(6,LOW);

// Repeat for address 5:
digitalWrite(6,LOW);
delay(5);
digitalWrite(3,LOW);
digitalWrite(4,HIGH);
digitalWrite(5,LOW);

digitalWrite(6,HIGH);
delay(5);
valueArray[5][0] = digitalRead(34);
valueArray[5][1] = digitalRead(35);
valueArray[5][2] = digitalRead(36);
valueArray[5][3] = digitalRead(37);
valueArray[5][4] = digitalRead(38);
valueArray[5][5] = digitalRead(39);
valueArray[5][6] = digitalRead(40);
valueArray[5][7] = digitalRead(41);
digitalWrite(6,LOW);

// Repeat for address 6:
digitalWrite(6,LOW);
delay(5);
digitalWrite(3,HIGH);
digitalWrite(4,LOW);
digitalWrite(5,LOW);

digitalWrite(6,HIGH);
delay(5);
valueArray[6][0] = digitalRead(34);
valueArray[6][1] = digitalRead(35);
valueArray[6][2] = digitalRead(36);
valueArray[6][3] = digitalRead(37);
valueArray[6][4] = digitalRead(38);
valueArray[6][5] = digitalRead(39);
valueArray[6][6] = digitalRead(40);
valueArray[6][7] = digitalRead(41);
digitalWrite(6,LOW);

/*
Next step is to loop through and compare old values to new values, etc.
*/
for (int i=0; i<8; i++)
 {
   for (int j=0; j<8; j++) 
    { 
      if (prevValues[i][j] != valueArray[i][j]) 
        { 
          String outStr = String(i)+String(j)+String(valueArray[i][j]); 
// Here is where we implement the semi-handshake. 
            if ( Serial.available() > 0)
              {
                Serial.println(outStr);
              }
            else
              {
                delay(100); 
// If we don't get an answer in 100 milliseconds, send the data anyway.
                Serial.println(outStr);
              }
        }
    }
 }
/*
Now transfer new values to old values using loops.
*/
for (int i=0; i<8; i++)
 {
   for (int j=0; j<8; j++)
    { 
      prevValues[i][j] = valueArray[i][j];
    }
 }
  delay(5);
}

And here is code written in the Processing IDE to receive this data:


/**
 * SerialReadControlPanel
 * Larry Cox 2014. Developed from "Simple Read" and other code examples.
 * Read data from the serial port and change the color of a rectangle
 * This code was tested 29 April 2014 and worked with loopback cable.
 * (Loopback = pins 2 and 3 connected.)
 */

import processing.serial.*; // This is the serial communication features.

Serial myPort;   // Create object from Serial class
int ports;       // Number of ports in list of serial ports.
String inputStr; // Data received from the serial comm line
String titleStr; // via out serial port.
int cols = 8;    // For the display grid.
int rows = 8;    // For the display grid.
Cell[][] Panel;  // Cell is a special object that displays as a grid.
String[][] labelArray = 
  {
  {"1"," 9","17","25","33","41","49","57"},
  {"2","10","18","26","34","42","50","58"},
  {"3","11","19","27","35","43","51","59"},
  {"4","12","20","28","36","44","52","60"},
  {"5","13","21","29","37","45","53","61"},
  {"6","14","22","30","38","46","54","62"},
  {"7","15","23","31","39","47","55","63"},
  {"8","16","24","32","40","48","56","64"} 
  };
int[][] valueArray = 
  {
  {1, 9,17,25,33,41,49,57},
  {2,10,18,26,34,42,50,58},
  {3,11,19,27,35,43,51,59},
  {4,12,20,28,36,44,52,60},
  {5,13,21,29,37,45,53,61},
  {6,14,22,30,38,46,54,62},
  {7,15,23,31,39,47,55,63},
  {8,16,24,32,40,48,56,64},
  };
  int rate = 0;
  int evenToggle = 0;
  
void setup() 
{
  size(800, 500); //Initial window size.
  
  ports = Serial.list().length; // Find out if any ports are active.
  if (ports > 0) // If one is, assume it's the first one (index = 0).
  {
  String portName = Serial.list()[0];
  // I create an informational title string.
  titleStr = "# of ports = " + str(ports) + " :" + portName;
  myPort = new Serial(this, portName, 9600);
  myPort.bufferUntil('\n'); // '\n' means a linefeed in the C language.
  myPort.write("000"); 
  myPort.write('\n');
/* The above lines will initiate the first serial event if 
the serial line is connected as a loopback. */
  }
  else
  {titleStr = "No serial ports currently active.";}
  Panel = new Cell[cols][rows]; // This is called a "constructor."
}

void draw()
{
  background(255);             // Set background to white.
  fill(rate);                  // Fill color for update ticker.
  ellipse(750,25,40,40);       // Draw update ticker.
  fill(0);                     // Set fill to black.
  text(titleStr,2,2,700,20);   // Draw the title.
  
                                // Create the panel.  
  for (int i = 0; i < cols; i++) // For each column in Panel...
  {
    for (int j = 0; j < rows; j++) // create a row of cells. 
    {
      // This line initializes each object in turn.
      Panel[i][j] = new Cell(i*100,(j*50)+50,100,50,labelArray[i][j],str(valueArray[i][j]));
/* Cell Constructor syntax:
Cell(float posX, float posY, float width, float height, float angle) */
      Panel[i][j].display(); // Draws the panel a rectangle at a time.
    }
  }
  // I want an indicator to flash at the speed I'm updating the display.
  int nextRate = 0;
  if (rate == 0) { nextRate = 255; }
  else { nextRate = 0; }
  rate = nextRate;
}

/*
The serialEvent() method runs automatically whenever the buffer reaches 
the byte value set in the bufferUntil() method in setup().
This is the crucial code that responds when data are received.
*/
void serialEvent(Serial myPort) 
{ 
  inputStr = myPort.readStringUntil('\n'); 
  inputStr = trim(inputStr);  // Just in case it contains junk characters.
/* The int() method will convert a numeric string to a value. */
  int i = int(inputStr.substring(0,1)); 
  int j = int(inputStr.substring(1,2)); 
  String k = inputStr.substring(2);
  valueArray[i][j]= int(k); // Updates array contents - crucial!
/* I wanted to toggle the data that gets sent out so in a loopback 
test I could see it changing on the screen. */
  int nexToggle = 0;
  if ( evenToggle == 0 )
  { 
    myPort.write("000"); // Send a string to serve as a test and to ask for more data.
    nexToggle = 1;
  }
  else 
  { 
    myPort.write("001"); // Send a different string...  
    nexToggle = 0;
  }
  myPort.write('\n');
  evenToggle = nexToggle;
} 
// A Cell object is defined below:
class Cell 
{
// A cell object knows about its location in the grid as well as its size.
  float x,y;   // x,y location
  float w,h;   // width and height
  String label; // Cell "content"
  String value; // rest of cell content

  // Cell Constructor
  Cell(float tempX, float tempY, float tempW, float tempH, 
        String tempLabel, String tempValue) 
  {
    x = tempX;
    y = tempY;
    w = tempW;
    h = tempH;
    label = tempLabel;
    value = tempValue;
  } 
  void display() 
  {
    stroke(255); // Draws a black border.
    strokeWeight(2); // One of several border options.
    fill(204); // Fill color for the next object to be drawn.
    rect(x,y,w,h); // Gives us an array of boxes on the screen.
    fill(0); // Fill color for the text.
    text(label,x+2,y+2,w-4,h-29); 
// Above line draws text superimposed over the boxes.
    text(value,x+2,y+27,w-4,h-29);
  }
}

I didn’t capture quite the latest version of my code with the yellow and red logic in it, but this is plenty for now.

Advertisements

Tags: , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s