Translate

Saturday, November 10, 2018

Very few wires for a numeric keypad for Arduino


Very few wires for a numeric keypad for Arduino

Giovanni Carrera, 23/11/2018
The techniques for reading numerical keyboards with a reduced number of Arduino pins as analog inputs are described.

Introduction

The aim of this study is to drastically reduce the number of pins required by a numeric keypad. This is because we often need many I/O pins compared to those available on the Arduino Uno or Nano boards.
With the help of some resistor you can turn the keypad to use only a few analog inputs, saving several digital pins. In some cases, as we will see, just one analogue input will suffice. Programs to manage them are also described.

Matrix keyboards

The ordinary numerical keypads are structured in matrix because they require less wires and are organized in rows by columns. Usually the lines are configured as input and the columns as output. The program scans the array to see if a key has been pressed. A 4x3 keypad, like the membrane one shown in the figure, requires 7 Arduino digital pins. Numerous libraries are available on the net to use it with Arduino.


There are also several keyboard applications with one analogue output, but in my opinion they are very unreliable for this type of keyboards. To simulate them I created a template and a spreadsheet program to try out the functionality. The following figure shows the scheme and the equivalent model of a 4x3 keyboard with an analogue output.
The biggest problem is that the range of variation is very limited and, consequently, also the intervals corresponding to each key. It is also difficult to distinguish the keys because the intervals may overlap in some cases. Having written the analysis program, I verified that the values they had chosen in some projects found on the network had these drawbacks, with minimum intervals of only 10mV.

My solution
The solution I propose is much better even if it uses three analog inputs, with a circuit like the one shown in the following figure.
In this case you save only 4 pins against six of the previous version but it's worth it because the intervals are now only four, in addition to the rest position that is the zero volts (no key pressed). In the equivalent scheme (b) the R1e indicates one of the four resistors R1-R4, while R2e is one of the three resistors R5-R7.
Now let's see how to make the four widest possible intervals. First of all the resistors R2e = R5 = R6 = R7 can be made equal, then we can set R1 = 0 to have the outputs corresponding to the first line at 5V. With the values shown in the table:
R1 =
0 W
R2 =
330 W
R3 =
1  kW
R4 =
3  kW
R5,R6,R7 =
1  kW
Vcc =
5.000 
The following intervals are obtained for each output:
Key
Vx [V]
DV [V]
NADC
DN
NADC-100
NADC+100
[1]-[2]-[3]
5.00
0.000
1023
0
923
1023
[4]-[5]-[6]
3.76
1.241
769
254
669
869
[7]-[8]-[9]
2.50
1.259
512
257
412
612
[*]-[0]-[#]
1.25
1.250
256
256
156
356
As you can see, the intervals on the three outputs are the largest possible and you can use six standard resistors with a tolerance of ±5%. With an extra analogue input and another resistor, a 4x4 keyboard can be used. The following figure shows the connections with Arduino.

In the diagram the resistor R1 is connected to line 2 because the one on line 1 has drawn it with a wire, so the resistor references have been scaled by one. Pin assignments can be modified according to needs, as long as they are pin configurable as analog.
Of course, if Arduino were powered at 3.3V, nothing would change because the ADC converter as default uses the supply voltage and the ADC numbers don’t change.
To test the test program, not having a keyboard of this type, I built it with recycled keys, the figure below shows my prototype. The 5-pin right connector is used to connect it to the Arduino.

The Analog4x3Keyb.ino program
I reported the lower (NADCm100) and higher (NADCp100) limits on two numeric arrays and one character array for the 12 keys, arranged in columns. As you can see, in my keypad I used two different keys because they were more useful for my applications than the standard '*' and '#' keys that are more suitable for telephone applications.
To scan the three analogue channels, I initially used an index loop, but it didn’t work, so I used the KeyScan function. Double conversion is necessary to avoid the bounce of the buttons. For applications it is better to turn everything into a function.
I consider a button pressed if the value of one of the three outputs is greater than zero, for safety I put the value 40. The program first verifies on which column the key was pressed, then identifies the key itself.

/* program Analog4x3Keyb
 *  test for 4x3 keys keyboard with 3 analog outs
 *  G. Carrera - 2/11/2018
 */

int ledPin = 13; // internal LED
// limits of keyboard output values:
const int NADCm100[4] = {923,669,412,156};
const int NADCp100[4] = {1023,869,612,356};
const char key[13] = {'1','4','7','C','2','5','8','0','3','6','9','E'};
int keyval[3];
int i,colp,val;


void setup(){
 pinMode(ledPin, OUTPUT);
 Serial.begin(9600); // used with serial monitor
 digitalWrite(ledPin, LOW);// led off
}

void loop() {
  KeyScan();// read analog keyboard
  if (keyval[0]+keyval[1]+keyval[2] > 40){ // a key was pressed
  delay(10);// antibounce
  KeyScan();// reread analog keyboard
  for (i=0; i < 3; i++){//identify which column it belongs to
    if (keyval[i] > 40){
      colp= i;
      val= keyval[i];// this is the corresponding value
      for (int j=0; j < 4; j++){// identify which key was pressed on the column
        if (val >= NADCm100[j] && keyval <= NADCp100[j]){
          digitalWrite(ledPin, HIGH);// led on
          Serial.print("col= "); Serial.print(colp);
          Serial.print(", val= "); Serial.print(val);
          Serial.print(", key= "); Serial.println(key[colp*4+j]);
          delay(500);
          digitalWrite(ledPin, LOW);// led off
          break;
        }
      }
    }
  }
 }
}

void KeyScan(){// read analog keyboard
  keyval[0]= analogRead(A0);
  delay(1);
  keyval[1]= analogRead(A1);
  delay(1);
  keyval[2]= analogRead(A2);
  delay(1); 
}
Keyboards with single common
I had a strange little keyboard organized as 12x1. After thinking a little bit about it, the simplest solution was to make a voltage divider and read the output voltage with an analog input.
I realized the circuit illustrated in the following figure, as you can see, only three wires connect it with Arduino. Naturally, this system can be easily adapted to even fewer keys, I do not recommend using too many keys because the intervals become too small. The keyboards organized in matrix, for example 3x4 or 4x4 have great difficulty for the analogue output, as already seen.

How to calculate the voltage ranges
To calculate the limits of the values used to identify the keys, I wrote a program in Excel.
The following figure shows the equivalent circuits of the divider (a) and the particular case of the keyboard (b).

In the case of the divider of figure (a), the output voltage Vout is:
Vout = Vin* R2p/(R1p+R2p)

If in our resistive network we press the key [2], we can refer to the previous divider formula if we put: R1p = 2*R
R2p = 10*R*Rz/(10*R+Rz)

In fact, the series of resistors of the low branch is in parallel with Rz. The resistor Rz (R13 in the diagram) serves to bring the input voltage to zero, ie it acts as an analog pull-down.
Using these formulas for each key, appropriately adapted, the following table is obtained.

Key
Vo [V]
DV [V]
R2p [kW]
R1p [kW]
NADC
DN
NADC-20
NADC+20
[0]
5.00
0.000
4.98
0
1023
0
1003
1023
[1]
4.54
0.458
4.60
0.464
929
94
909
949
[2]
4.10
0.443
4.22
0.928
839
90
819
859
[3]
3.67
0.431
3.84
1.392
751
88
731
771
[4]
3.25
0.421
3.44
1.856
665
86
645
685
[5]
2.84
0.413
3.04
2.32
580
85
560
600
[6]
2.43
0.407
2.63
2.784
497
83
477
517
[7]
2.03
0.403
2.21
3.248
414
83
394
434
[8]
1.62
0.401
1.79
3.712
332
82
312
352
[9]
1.22
0.401
1.35
4.176
250
82
230
270
[*]
0.82
0.403
0.91
4.64
168
82
148
188
[#]
0.41
0.407
0.46
5.104
84
84
64
104
The NADC number is what can be read from the Arduino ADC converter (10 bit). The numerical interval DN between one key and the other goes from 84 to 94, even though all the resistors are equal, in my case R = 464 W, these intervals are slightly different towards the high values because of R13. For safety I chose the limits within ± 20, obtaining the NADC-20 and NADC + 20 values.
The experimental measurements on the prototype gave values very close to those calculated. The following figure shows my prototype detached from the keyboard for visibility reasons. It connects to the keyboard with a 13-pin female strip and a second 3-pin connector connects it to the Arduino.

Components list
·        1 x keypad 12x1 keys or 12 single keys connected to a common
·        12 x resistors R1..R12 = 470 W ±1%, in my case I used 464 W.
·        1 x resistor  R13= 47 kW ±5%.
·        1 x 100 nF capacitor.
·        strip connectors

The Analog12x1Keyb.ino program
The program required some attention as regards the bounce phenomena on the keys. The pressed key leads to values higher than zero + possible noise, even here I have placed 40 as a limit. In the program I created two numeric arrays with the calculated limits and a third of ascii characters to assign the key. I only wrote a system test program and tested it on an Arduino Uno.

/* program Analog12x1Keyb
 *  test for analog 12x1 keys keyboard
 *  G. Carrera - 27/10/2018
 */
int keybPin = A0;// keyboard analog input
int ledPin = 13; // internal LED
// limits of keyboard output values:
const int NADCm20[12] = {1003,909,819,731,645,560,477,394,312,230,148,64};
const int NADCp20[12] = {1023,949,859,771,685,600,517,434,352,270,188,104};
const char key[13] = {'0','1','2','3','4','5','6','7','8','9','*','#'};

void setup(){
 pinMode(ledPin, OUTPUT);
 Serial.begin(9600); // used with serial monitor
 digitalWrite(ledPin, LOW);// led off
}

void loop() {
  int val = analogRead(keybPin);// read analog keyboard
  if (val > 40){ // a key was pressed
    delay(10);// antibounce
    val = analogRead(keybPin);// re-read
    for (int i=0; i < 12; i++){
      if (val >= NADCm20[i] && val <= NADCp20[i]){// the key[i] was pressed
        digitalWrite(ledPin, HIGH);// led on
        Serial.print(val);
        Serial.print(" , ");
        Serial.println(key[i]);
        delay(500);
        digitalWrite(ledPin, LOW);// led off
        break;
      }
    }  
  }
}

With a few modifications you can create a function that, if called, returns the character and if no key has been pressed returns a thirteenth character to indicate this state, for example 'N'.
This solution is particularly suitable in cases where you want to make a small custom keyboard, so we can organize it as we see fit. Another useful application is for 8-pin microcontrollers like ATtiny that have very few I/O pins.

Keyboards with a few keys
With a few buttons, such as 4x1, you can also use a different configuration, as shown in the following figure. The table is the result of optimization with Excel with the standard values: R1 = 470W, R2 = 1 kW, R3 = 3 kW and R4 = 1.5 kW.

With this circuit a resistor is saved, but the values of the upper branch of the divider are not all the same as in the circuit discussed above. To have equal intervals must be:
R3 = 2 * R4, R2 = R3 / 3 and R1 = R2 / 2
But it is difficult to use all standard values. The ranges are wide and the resistors can have a tolerance of ±5%. The standard 3 kW resistor can be obtained with two 1.5 kW resistors in series or a 3.3 kW resistor in parallel with a 33 kW one.

The Analog4x1Keyb.ino program
/* program Analog4x1Keyb
 *  test for 4x1 keys keyboard with 3 analog outs
 *  G. Carrera - 8/11/2018
 */

int ledPin = 13; // internal LED
int keybPin = A0;// keyboard analog input
// limits of keyboard output values:
const int NADC_L[4] = {907,646,393,118};
const int NADC_H[4] = {1023,897,636,383};
const char key[5] = {'1','2','3','4'};
int i,keyval;


void setup(){
 pinMode(ledPin, OUTPUT);
 Serial.begin(9600); // used with serial monitor
 digitalWrite(ledPin, LOW);// led off
}

void loop() {
  if (analogRead(keybPin) > 40){ // a key was pressed
    delay(10);// antibounce
    keyval= analogRead(keybPin);// reread analog keyboard
    if (keyval > 40){
      for (int i=0; i < 4; i++){// identify which key was pressed on the column
        if (keyval >= NADC_L[i] && keyval <= NADC_H[i]){
          digitalWrite(ledPin, HIGH);// led on
          Serial.print("val= "); Serial.print(keyval);
          Serial.print(", key= "); Serial.println(key[i]);
          delay(500);
          digitalWrite(ledPin, LOW);// led off
          break;
        }
      }
    }
  }
}

Saturday, June 16, 2018

A touch numeric keyboard for Arduino or Teensy


 A system for entering numerical values in our programs on Arduino or Teensy is described. To test this function I wrote also a simple analog data logger program.


The touchscreen keyboard

Very often, for our programs, we need a system to set parameters, usually of a numerical type. A 4x4 keyboard requires some space and then we also need a display. Here is the idea of using a touchscreen display to do both.
I have then written the GetNum function that allows you to print a prompt message and to type an integer number.
To test this function I wrote a simple analog data logger program that required two parameters, the first is the sampling period and the second the number of samples. In this example the number of channels to be scanned is set to three, but the program can be modified to request a third parameter with the number of channels.
As hardware a 2.8" TFT touch shield for Arduino with resistive touch screen can be used.  On the market there are many types, you only need to use the libraries suitable for the controllers that are mounted on the shield.
To test the prototype I used a 3.2" touch screen interfaced to a Teensy LC board. In my case, I had to do a lot more soldering work, but I like it and it is creative too. For more details of my display refer to the post "Adapt a 3.2" TFT display for Raspberry to Arduino ", in my blog ArduPicLab.

The touch keyboard graphics
For the numeric keyboard I used 12 keys, arranged on 3 rows and 4 columns according to the following figure.
In addition to the numeric keys, I added the "C" key to delete the set number and the "ok" key to accept it. The upper space is occupied by the printing of the number to be set and the prompt message that tells the operator what is required by the program. The numbers on the keys are written in tft.setTextSize (3) while the prompt message is written in tft.setTextSize (2) and has a maximum of 19 characters in length. The message is printed in yellow on a black background while the number to be set is printed in black on a cyan background tft.setTextSize (4). The previous figure shows the appearance of an example that requires sampling time in [ms] and 250 was keyed.

The touchscreen calibration
This operation is necessary to know the coordinates of the vertices of the keyboard for which it is necessary to write a simple program that prints the coordinates of the contact points on the display. . To do this I made a program that traces the keyboard on the screen and prints the x, y coordinates on the top. With the stylet I pressed on the grid of the keyboard and I wrote the coordinates on a sheet and then I averaged the values by row and by column. The results are shown in the following figure.
These numbers are indispensable for identifying keys with the DetectButtons () function. It proceeds by column, starting from the left. For each column, examine the rows starting from the lowest. Using other displays the numbers could be very different from those I obtained, so it is important to calibrate the touch screen.

The GetNum function
This function has the following syntax:
unsigned int GetNum (String prompt)
The prompt string is passed from the calling program. The routine turns black the screen, prints the prompt, draws the keys and waits in loop for the required number to be keyed. By pressing the "ok" button the data is returned to the program.
This function accepts unsigned integers from 0 to 65535, but with some minor changes it can work with larger numbers. Things get complicated with numbers with decimals: one more key is needed.

The hardware
As I said, if you buy the shield for Arduino you do not have to do any work because everything is ready, but in my case I had to work a lot.
The graphics resolution of this display is good, QVGA means ¼ of VGA, ie half the pixels on each side, so 240x320 points with 64K or 256K colors. Many shields have the ILI9340 chip as a graphic controller and the XPT2046 as a touch screen controller. The interface used is SPI. The shields for Arduino also have a SD card connector on the back, very convenient for loading images or saving data.
But the card for Raspberry I used did not have the micro SD connector and I had to add it.
The electrical scheme of my prototype is that of the following figure. If I had used a shield for Arduino, things would have been much simpler, I should not have used the solder.

I expected to have three analog channels, so I used a 5-pin connector also to power any sensors.
The button key0 I have dedicated to duplicate the "program" button that is no longer accessible because below the display.
Unlike Arduino Uno, Teensy has a 12-bit converter. The following figure shows the board of my prototype.

The datalogger program
I used the Adafruit libraries. As an example of using the GetNum function, I wrote a program to acquire three analog signals, converted with 12-bit ADC (10 bits for Arduino Uno ADC), and transfer them to the micro SD card. The program asks for the sampling period in [ms], checking the number, then asking for the number of samples.
Then it makes the acquisition, also printing the data on the screen for long times. The file name is always the same, and new acquisitions are added in the queue.
Of course you can improve by asking for other parameters, such as the number of channels or the file number.

Code
/***************************************************
TeensyTFTacq and uSD test program
based on Adaftuit TFT library
Giovanni Carrera, rev 15/06/2018
/****************************************************/

#include "SPI.h"
#include <SD.h>
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9340.h"
#include <XPT2046_Touchscreen.h>

#if defined(__SAM3X8E__)
    #undef __FlashStringHelper::F(string_literal)
    #define F(string_literal) string_literal
#endif

// These are the pins used for the TeensyTFT32
#define _sclk 13
#define _miso 12
#define _mosi 11
#define _cs 10
#define _dc 7
#define _rst 8
// for TeensyTFT32 touchscreen
#define CS_PIN  4
#define TIRQ_PIN  3
// for TeensyTFT32 uSD card
const int chipSelect = 9;

unsigned long startMillis, ns;
unsigned long deltat = 200;// period in ms
unsigned int value,NSamples;
boolean SDok = false;
int X,Y;
long Number;
boolean ok = false;

File logFile;

XPT2046_Touchscreen ts(CS_PIN, TIRQ_PIN);// interrupt enabled polling

//Adafruit_ILI9340 tft = Adafruit_ILI9340(_cs, _dc, _mosi, _sclk, _rst, _miso);
// Use hardware SPI
Adafruit_ILI9340 tft = Adafruit_ILI9340(_cs, _dc, _rst);

void setup() {
  analogReadResolution(12);
  tft.begin();// initialize ILI9340
  ts.begin();// initialize XPT2046_Touchscreen
  tft.fillScreen(ILI9340_BLACK);
  tft.setCursor(0, 100);
  tft.setTextColor(ILI9340_GREEN); tft.setTextSize(2);
  tft.println("TeensyTFT32 logger");
  tft.println("Initializing SD");
  // check if the SD is present and functioning
  if (!SD.begin(chipSelect)) {
    tft.setTextColor(ILI9340_RED);
    tft.println("Bad or absent SD");
    // does not do anything else
    return;
  }
  tft.println("Card initialized");
  delay(3000);
  ns= 0;
  value = GetNum("Sample Period [ms]=");
  if (ok){
    if (value==0) value = 1;// minimum value 1ms
    deltat= value;
    ok= false;
  }
  value = GetNum("Number of samples=");
  if (ok){
    if (value==0) value = 1;// minimum value 1
    NSamples= value;
    ok= false;
  }
  String dataString = "Sample Period = "+String(deltat)+" [ms]";
  logFile = SD.open("datalog.txt", FILE_WRITE);
  logFile.print(dataString);
  tft.fillScreen(ILI9340_BLACK);
  tft.setRotation(1);// 90°
  tft.setCursor(0, 100);
  tft.setTextColor(ILI9340_GREEN); tft.setTextSize(2);
  tft.println(dataString);
  dataString = "Number of samples = "+String(NSamples);
  logFile.println(", " + dataString);
  tft.println(dataString);
  SDok = true;
}

void loop(void) {
  startMillis = millis();
  while( millis() - startMillis < deltat);// loop for deltat msec
  String dataString = String(ns)+ ", ";
  int val;
  for (int i=0; i <= 2; i++){
    val = analogRead(i);
    val = map(val,0,4095,0,3299);// convert values in mV
    dataString += String(val);
    if (i < 2) dataString += ", ";
  }
  if (deltat >= 500){ // print only for long periods
    tft.fillRect(0, 0, 479,70, ILI9340_BLACK);// clear previous prints
    tft.setCursor(0, 0);
    tft.setTextColor(ILI9340_YELLOW); tft.setTextSize(2);
    tft.println(dataString);
  }
  ++ns;
  if (SDok){
    logFile.println(dataString);// save nsample,A0,A1,A2
    if (ns== NSamples){
      logFile.close();
      SDok= false;// stop acquisition
      tft.setCursor(20, 200);
      tft.setTextColor(ILI9340_BLUE); tft.setTextSize(2);
      tft.println("End of acquisition");
    }
  }         
}

// Functions used
unsigned int GetNum(String prompt){
// print a numeric keyboard and a prompt message
// and get a numeric unsigned integer
 tft.fillScreen(ILI9340_BLACK);
 Number=0;
 draw_BoxNButtons();// draw the virtual keyboard
 tft.setCursor(10, 20);
 tft.setTextSize(2);
 tft.setTextColor(ILI9340_YELLOW);
 tft.println(prompt); //print prompt
 do { // repeat until ok is pressed
  if (ts.touched()) {
    TS_Point p = ts.getPoint();
    X= p.y; Y= p.x;// invert the axes
    DetectButtons();
  }
 }while (!ok);
 return Number;
 }

void draw_BoxNButtons() {
  String symbol[3][4] = {
    { "6", "7", "8", "9" },
    { "2", "3", "4", "5" },
    { "C", "0", "1", "ok" }
  };
  //Draw the Result Box
  tft.fillRect(0, 60, 240, 80,ILI9340_CYAN);

 //Draw keys
  tft.fillRect  (0,260,60,60,ILI9340_RED);
  tft.fillRect  (0,140,240,120,ILI9340_BLUE);
  tft.fillRect  (60,260,120,60,ILI9340_BLUE);
  tft.fillRect  (180,260,60,60,ILI9340_GREEN);

  //Draw Horizontal Lines
  for (int h=140; h<=320; h+=60)
  tft.drawFastHLine(0, h, 240,ILI9340_WHITE);

  //Draw Vertical Lines
  for (int v=0; v<=240; v+=60)
  tft.drawFastVLine(v, 140, 1800,ILI9340_WHITE);

  //Display keypad lables
  for (int j=0;j<3;j++) {
    for (int i=0;i<4;i++) {
      tft.setCursor(22 + (60*i), 160 + (60*j));
      tft.setTextSize(3);
      tft.setTextColor(ILI9340_WHITE);
      if (j==2 & i==3) tft.setTextColor(ILI9340_BLACK);// ok is more visible
      tft.println(symbol[j][i]);
    }
  }
}

void DetectButtons(){
  int TSx[] = {319,1174,2097,3031,3838 };// Touch Screen x intervals
  int TSy[] = {1764,2445,3124,3752};// Touch Screen y intervals
 
  if (X<TSx[1] && X>TSx[0]){ //detecting Buttons on Column 1
    if (Y>TSy[2] && Y<TSy[3]) Number= 0;//If Cancel button is pressed
    if (Y>TSy[1] && Y<TSy[2]) Number= (Number*10)+2;// button 2 is pressed
    if (Y>TSy[0] && Y<TSy[1]) Number= (Number*10)+6;// button 6 is pressed
   }
  if (X<TSx[2] && X>TSx[1]){ //detecting buttons on Column 2
    if (Y>TSy[2] && Y<TSy[3]) Number= (Number*10)+0;// button 0 is pressed
    if (Y>TSy[1] && Y<TSy[2]) Number= (Number*10)+3;// button 3 is pressed
    if (Y>TSy[0] && Y<TSy[1]) Number= (Number*10)+7;// button 7 is pressed
  }
  if (X<TSx[3] && X>TSx[2]){ //detecting Buttons on Column 3
    if (Y>TSy[2] && Y<TSy[3]) Number= (Number*10)+1;// button 1 is pressed
    if (Y>TSy[1] && Y<TSy[2]) Number= (Number*10)+4;// button 4 is pressed
    if (Y>TSy[0] && Y<TSy[1]) Number= (Number*10)+8;// button 8 is pressed
  }
  if (X<TSx[4] && X>TSx[3]){ //Detecting Buttons on Column 4
    if (Y>TSy[2] && Y<TSy[3]) ok = true;// button ok is pressed  
    if (Y>TSy[1] && Y<TSy[2]) Number= (Number*10)+5;// button 5 is pressed
    if (Y>TSy[0] && Y<TSy[1]) Number= (Number*10)+9;// button 9 is pressed
  }
  tft.fillRect(0, 60, 240, 80, ILI9340_CYAN);  //clear result box
  tft.setCursor(10, 80);
  tft.setTextSize(4);
  tft.setTextColor(ILI9340_BLACK);
  tft.println(Number); //update new value
  delay(100);
}


References
3)      “Arduino Touch Screen Calculator using TFT LCD”,  B.Aswinth Raj,  https://circuitdigest.com/microcontroller-projects/arduino-touch-screen-calculator-tft-lcd-project-code

4)      “How to use the TFT display 2.2" QVGA with Arduino”, G. Carrera,  http://ardupiclab.blogspot.it/