Experimente mit dem Super Starterkit Teil 4 - Taster, Folientastatur und Joystick - AZ-Delivery
This article is about the input elements included in the kit. Debouncing of pushbuttons, determination of the pressed key with a key matrix, and two-dimensional analog input with the joystick.

Hardware needed

Number

Component

Note

1

Super Starter Kit


1

9V block battery or 9V power supply unit

optional


Simple pushbutton

Five of these pushbuttons are included in the starter kit. Pressing the button connects ports 1 and 2. Due to the design, each connector exists twice.
When closing a mechanical contact, it can happen that the connection is not immediately established permanently, but is briefly interrupted again. This is called bouncing. When using a pushbutton with a microcontroller, bouncing must be taken into account.

Switching the LED on and off with a pushbutton

In the first example, an LED should always light up when the push button is pressed. This task can also be solved without a microcontroller. The example should only show the function digitalRead(pin number); explain.

 

The cathode of the LED is connected to GND and the anode is connected to pin 3 via a 1kOhm resistor. The push button is also connected to GND and on the other side to pin 2.
The program is very simple.

#define LED 3
#define TASTER 2

First, the used pins are defined.

void setup() {
pinMode(LED, OUTPUT);
pinMode(TASTER, INPUT_PULLUP);
}

In the setup function, the tasks of the pins are defined. The pin for the button is defined as input with a  pullup resistor. That means the input is connected to +5V via a resistor in the microcontroller. This ensures that the pin is at +5V when the pushbutton is not pressed. If the button is pressed, the pin is connected to GND.

void loop() {
boolean on = ! digitalRead(TASTER);
digitalWrite(LED, on);
}

In the main loop boolean on = !digitalRead(TASTER); the inverted state of the pin is read. If the button is pressed, the pin is at 0. So 0 or false is read in. By the negation operator „!“ the value is reversed and on becomes true. If now on is output, the pin goes to +5V and the LED lights up.

It requires a little more programming effort if the LED is to be switched on permanently and switched off again permanently with a pushbutton. For this, it is necessary to recognize the pressing of the push button. When the button is pressed, the voltage at the pin changes from +5V to 0V. When released, the voltage changes from 0V to +5V.

#define BUTTON 2
#define LED 3

After defining the used pins, two global variables are defined.

boolean led = false;
int cnt = 0;


The variable led stores the current state of the LED. The variable cnt is a counter that counts the number of button operations.

void setup() {
Serial.begin(9600);
pinMode(TASTER, INPUT_PULLUP);
pinMode(LED, OUTPUT);
}

The setup function initializes the serial interface and defines the pin mode

void loop() {
if (digitalRead(TASTER) == 0) {
delay(10);
if (digitalRead(TASTER)==1) {
led = !led;
cnt++;
Serial.println(cnt);
    }
digitalWrite(LED,led);
  }
}

In the main loop, a change at the push button pin is detected. Whenever the button is pressed, i.e. 0, the state is read again after a delay of 10 ms. If it is now 1, the push button was released. Thus the edge from 0 to 1 has been detected. The counter is incremented by one and the status of the LED is inverted. The new status is output to the pin for the LED. The delay of 10 ms prevents multiple counts due to contact bounce.


Membrane keypad

 

The foil keyboard included in the set consists of a grid with four rows and four columns. By pressing on the crossing points, a conductive connection is established between the respective row and column.
For readout, the columns are connected to inputs of the microcontroller with pullup resistors. The rows are connected to outputs. One by one, one of the rows is set to 0. While a row is at 0, a pressed key can be detected by reading the column inputs, since the read value in this case is also 0.

Simple calculator

If one combines the four-digit seven-segment display (see part 3) with the membrane keyboard, one can realize a simple calculator, which can add and subtract. The layout of the keyboard should look like this.

For the control of the four rows, the digit selection of the seven-segment display can be used, so that only four inputs are needed to read the columns.

 

For the program, the four-digit seven-segment display from part 3 is extended.

First, all used pins are defined again. For the columns and rows, only one pin number is defined each, because the further columns and rows use consecutive pins.

#define SER 2
#define RCLK 3
#define SRCLK 4


#define DIG1 5
#define COL1 9

The following is the table with the bit patterns for the seven-segment display and a two-dimensional table that assigns the key number to the columns and rows.

byte segmente[18] = {
// hgfedcba
 0B00111111, //0
 0B00000110, //1
 0B01011011, //2
 0B01001111, //3
 0B01100110, //4
 0B01101101, //5
 0B01111101, //6
 0B00000111, //7
 0B01111111, //8
 0B01101111, //9
 0B01110111, //A
 0B01111100, //b
 0B00111001, //C
 0B01011110, //d
 0B01111001, //E
 0B01110001, //F
 0B01000000, //-
 0B000000, //empty
};

int keys[4][4] = {
  {1,2,3,10},
  {4,5,6,11},
  {7,8,9,12},
  {14,0,15,13}
};

Global variables follow.

int number, sum;
int taste;
boolean add;

the variable number contains the number that is currently being entered and sum the current sum. The variable key contains the last key pressed. This is necessary so that a keystroke is evaluated only once. The variable add is set to true, if addition is to be performed or to false for a subtraction.

The function print7() is identical to the example in part 3.

void print7(byte number, boolean dot) {
 byte bits;
 if (number < 18) {
 bits = segments[number];
 if (dot) bits = bits | 0B10000000;
 for (byte i = 0; i < 8; i++) {
 digitalWrite(SER, bits & 128);
 digitalWrite(SRCLK, 1);
 digitalWrite(SRCLK, 0);
 bits = bits << 1;
    }
 digitalWrite(RCLK, 1);
 digitalWrite(RCLK, 0);
  }
}

The function int displayDigit(int pos, int value, boolean show) displays a digit with the value at the specified position pos an. With the parameter show parameter can be used to prevent the digit from being displayed at all. In addition, the keyboard is used for the digits specified by pos is read in and returned as the result of the function.

int displayDigit(int pos, int value, boolean show) {
int res = -1;
boolean k;
if (show) {
print7(value, false);
} else {
print7(17 , false);
  }
digitalWrite(DIG1+pos,0);
delay(2);
for (int c = 0; c<4; c++) {
k = digitalRead(COL1+r);
if (!k) res = keys[pos][c];
  }
digitalWrite(DIG1+pos,1);
return res;
}

The return value is preset with -1 (which means no key is pressed). If the parameter show is set to true, the digit is output, otherwise, the display remains empty (code 17);
With  digitalWrite(DIG1+pos,0); the cathode of the seven-segment display and the corresponding row line of the keyboard matrix is set to 0. This activates the seven-segment display. All column lines are now checked one after the other in a loop. If a column line is at 0, the corresponding key in this column was pressed. The number of the key is displayed in res is stored. If several keys are pressed simultaneously in one line, the key furthest to the right is returned as the result. After all four columns have been read, the following is done with digitalWrite(DIG1+pos,1); the output to the cathode of the seven-segment display and to the row line of the keyboard matrix is set to 5V again. The determined key value or -1 if no key was pressed is returned as result.

The function int displayNum(int number) outputs a signed number of up to three digits to the seven-segment display and checks whether a key has been pressed.

int displayNum(int Number) {
int z;
int k;
int res = -1;
boolean show = true;
boolean err = false;
err = ((number < -1000) || (Nummer > 999));
if (!err && (number < 0)) {
k = displayDigit(0,16,true);
Nummer = Nummer * -1;
} else {
if (err) {
k = displayDigit(0,14,true);
} else {
k = displayDigit(0,0,false);
    }
  }
if (k >= 0) res = k;
for (int i = 3; i>0; i--) {
z = number % 10;
if (err) {
k = displayDigit(i,0,false);
} else {
k = displayDigit(i,z,show);
    }
if (k >= 0) res = k;
number = number / 10;
show = number > 0;
  }
return res;
}

First, local variables are created. In z the respective digit to be displayed is stored. The variable k is used as an auxiliary memory for the key detection and is stored in res the determined key is stored. The default value stored here is -1, i.e. no key pressed. The Boolean variable show is used to control from which position no zero should be output. The Boolean variable err is set to true if there is an error. Now the check is made whether the number is between 999 and -999. If this is not the case err to true set.
Now the leftmost digit is output. If there is no error and the number is less than 0, a "-" is output and the number is multiplied by -1 to make it positive.
If there is an error, an "E" is output, otherwise, the digit remains empty. The return value of the display function is always stored in the variable k variable. If the value of k is not negative, then a key was pressed in the top line. The value is used for the return in res stored.
Now the display of the other digits follows. One begins with the rightmost digit, which contains the units. The loop command for (int i = 3; i>0; i--) starts with the value 3 for i. Then i is decreased by one with each pass as long as i is greater than 0. With the "%" operator, which returns the remainder of a division, in this case by 10, you get the digit of the least significant digit. If there is an error, "0" is returned, otherwise the digit. If the return value is not negative, it is written in res for the return.
Now the number is divided by 10 so that one gets the next digit in the next run. Since the division is an integer division, if the result is less than 1, the result is 0. In this case, show to false so that no leading zeros are displayed. After the loop has run, the value specified in res is returned

In the setup() function the mode is set for the pins used. Since consecutive pins are used for the row outputs and for the column inputs, these can be set in a loop. Thereby also the row outputs are set to 1. The two clock outputs are set to 0. The global variables get their initial values.

void setup() {
Serial.begin(9600);
pinMode(SER, OUTPUT);
pinMode(RCLK, OUTPUT);
pinMode(SRCLK, OUTPUT);
for (int i = 0; i<4;i++) {
pinMode(DIG1+i, OUTPUT);
digitalWrite(DIG1+i,1);
pinMode(COL1+i, INPUT_PULLUP);
  }
digitalWrite(RCLK, 0);
digitalWrite(SRCLK, 0);
zahl = 0;
summe = 0;
taste = -1;
add = true;
}

In the main loop, the value of the global variable number is displayed. If the key number returned by the function is different from the last recognized key, a corresponding action is required.

void loop() {
int k = displayNum(zahl);
if (k != taste) {
taste = k;
if (taste >= 0) {
Serial.print(taste);Serial.print(" -> ");Serial.println(number);
if (key < 10) number = number*10+key;
if (key == 15) number = number/10;
if (key == 14) { number = 0; sum = 0;}
if (key == 10) {add = true; sum = number; number = 0;}
if (key == 11) {add = false; sum = number; number = 0;}
if (key == 13) {
if (add) {
number = sum + number;
} else {
number = sum - number;
        }
sum = number;
      }
    }
  }
}

After detecting that the key number has changed, the new key number for comparison is stored in the variable taste is stored. If the key number is negative, this means that the key has been released again. No action is performed. If the key number is positive, processing must now take place. The keys with the numbers 0 to 9 are the numeric keys. The new digit = key number is appended to the existing number. For this purpose, the number is multiplied by 10 and the key number is added. Key numbers greater than 9 are special keys. If the key number is equal to 15, the last digit of the number should be deleted. This is achieved by dividing the number by 10. If the key number is equal to 14, everything should be deleted. The number and sum are set to 0. If the key number is equal to 10, an addition should be prepared. The number is stored as the first summand in sum. Then the number to enter the second summand is set to 0. The add-flag is set to true is set. If the key number is equal to 11,  subtraction should be prepared. The number is stored as minuend in sum. Then the number to enter the subtrahend is set to 0. The add-flag is set to false is set. If the key number is 13, the operation should be completed. Depending on add-flag, the number is added to or subtracted from the sum.


Joystick

 

The joystick is an analog input device, which means the delivered output voltage is proportional to the movement of the joystick. There is a potentiometer for each axis, which acts as a voltage divider between +5V and GND. In the idle state, the potentiometers are in the center position, which means the voltage for the X and Y axes is about 2.5V each. When the control stick is pressed, the switch is closed. The SW terminal is then connected to GND. If the switch is used as input for the microcontroller, a pull-up resistor is necessary, otherwise, the state is undefined when the switch is open.

Besides X and Y you can also determine the angle and radius. This can be used for example to control vehicles. The angle determines the direction and the inclination of the speed. In this case, one takes the rest position as center.

Color controller

In the following example, the joystick should be used to set the color of an RGB LED. The angle determines the color, the inclination the brightness. In an idle state, the LED should be dark. Pressing the joystick should hold the current color. Pressing it again should allow the color to change again. The RGB LED is set as shown in Part 2 the RGB LED is connected to pins 9, 10 and 11 via resistors. These pins support PWM. Since the joystick supplies analog voltages at VRx and VRy, these pins must be connected to analog inputs. The inputs A0 and A1 are used. For the switch the input A2 is used. No analog input is needed for the switch, but the analog inputs can also be used as digital inputs.

Circuit:

In the microcontroller, the analog inputs are connected to a 10-bit analog-to-digital converter. To read data from analog inputs use the command analogRead(pin). This command returns an 11-bit integer value. The reference voltage is 5V. That means at 0V you get 0 and at 5V you get 1023. With the call float x = analogRead(A0) / 1024 * 5 you get the voltage at A0 in volts.

Color circle:

The color spectrum can be displayed on a circle. The angle can be used to select a specific color. At 0 degrees, only the red LED lights up and at 120 degrees, only the green LED lights up. In between, the brightness of the red LED decreases continuously and the brightness of the green LED increases at the same rate. At 60 degrees, both are equally bright, which is perceived as yellow. Between 120 and 240 degrees, the same happens with the green and blue LEDs, and between 240 and 360 degrees with the blue and red LEDs.

Calculate angle:

The output voltage of the joystick changes between 0 and 5V and thus the read digital value is between 0 and 1023. With the command X = 2 * (analogRead(A0) / 1024.0 - 0.5); you get values between -1 and +1 and therefore 0 in the idle state. The specification of the constant 1024.0 with the decimal place is necessary so that the compiler does not use integer division. The following figure shows the values obtained by moving the joystick.


To calculate the angle, circle equations must be applied. Since the inverse angle functions are best used only between 0 and 90 degrees, each quadrant is considered separately.


For the first quadrant, α = arcsin(b / r) or α = arccos(a / r). The radius r = √(a2 + b2).
For the second quadrant β = arcsin(d/ r) + 90 or β = arccos(c / r) + 90.
The radius r = √(c2 + d2). You can see that in the first quadrant, the y-value is related to arcsin, and in the second quadrant the x-value is related to arcsin. In the third quadrant, the same is true as in the first with the difference that 180 is to be added. In the fourth quadrant, the same applies as in the second with the difference that 270 is to be added. Based on these fundamentals, the following sketch results.

#define UX A0
#define UY A1
#define TASTER A2
#define RED 11
#define GREEN 10
#define BLUE 9

First, the used pins are defined again. The global variables follow.

uint32_t last = 0;
boolean t;
boolean selection = true;
int R,G,B;
float angle,x,y;

The 32-bit integer variable last is used to store a timestamp. The Boolean variable t is needed to recognize a keystroke. The Boolean variable selection represents the current status. It distinguishes whether the color can currently be set. If it is set to false, the color value is held and does not change when the joystick is moved. R, G, and B contain the current proportions for red, green, and blue. The floating point variables angle, x, and y indicate the current angle and the measured values for x and y.
This is followed by the setup() function.

void setup() {
Serial.begin(9600);
pinMode(UX, INPUT);
pinMode(UY, INPUT);
pinMode(TASTER, INPUT_PULLUP);
pinMode(RED,OUTPUT);
pinMode(GREEN,OUTPUT);
pinMode(BLUE,OUTPUT);
}

In the setup() function the serial interface is activated and all pins are set to the corresponding mode.
The reaction to the joystick inputs is done in the loop() function.

void loop() {
boolean t1 = digitalRead(TASTER);
if (t1 != t) {
delay(10);
boolean t2 = digitalRead(TASTER);
if (t1 == t2) {
t = t1;
if (! t) selection = ! selection;
    }
  }

In the beginning, the switch of the joystick is evaluated. With the local variables t1 and t2 the necessary debouncing is performed. If the key was pressed, the status is switched.
If the color selection is active, the voltages are read from the joystick and converted into the value range -1 to +1, as explained above. The corresponding radius is calculated. For this calculation, the function sqrt() is used.
 if (selection) {
angle = 0;
x = 2*(analogRead(UX)/1024.0-0.5);
y = 2*(analogRead(UY)/1024.0-0.5);
float r = sqrt(x * x + y * y);

Now the calculation of the angle is done. For this, it is necessary to know the corresponding quadrants. The first and third quadrants can be recognized by the fact that x and y values have the same sign, i.e. when x * y is positive. To calculate the angle, the functions asin() and acos() are used. Since these functions return the angle in radians, the result must be multiplied by 180 * π to get the angle in degrees. Either asin() or acos() can be used to calculate the angle. Since the accuracy is worse for small values, the larger value is always used. Finally, 180 degrees must be added in the third quadrant. The third quadrant is recognized by the negative x-value.
The calculation for the second and fourth quadrants is analogous to the difference that x and y are swapped and additionally 90 degrees are added.

 if (x * y >= 0) { //1st or 3rd quadrant
if (x > y) angle = acos(abs(x)/r)*180/PI; else angle = asin(abs(y)/r)*180/PI;
if (x < 0) angle += 180;
} else { //2th or 4th quadrant
if (y > x) angle = acos(abs(y)/r)*180/PI; else angle = asin(abs(x)/r)*180/PI;
angle += 90;
if (y < 0) winkel += 180;
    }

With the angle determined in this way, the color value can be calculated. First, it is ensured that the angle is a maximum of 359 degrees. Likewise, the radius to be used for the brightness is limited to 1. If the angle is smaller than 120 degrees, a mixture of red and green is made depending on the angle. Blue is 0. Red decreases from 255 to 0, while green increases from 0 to 255.

 if (winkel > 359) angle -= 360;
if (r > 1) r=1;
float w = angle;
if (angle < 120) {
R = (120-w)*255/120;
G = (w * 255/120);
B = 0;

If the angle is less than 240 degrees, 120 degrees is subtracted first, and then, depending on the remaining angle, a blend is made between green and blue. Red is 0. Green decreases from 255 to 0, while blue increases from 0 to 255.

 } else if (angle < 240) {
w = angle -120;
G = (120-w)*255/120;
B = (w * 255/120);
R = 0;

For angles greater than 240 degrees, 240 degrees is subtracted first and then a blend is made between blue and red depending on the remaining angle. Green is 0. Blue decreases from 255 to 0, while red increases from 0 to 255.

 } else {
w = angle -240;
B = (120-w)*255/120;
R = (w * 255/120);
G = 0;
    }

Now the determined values can be output to the LEDs. The multiplication with the radius influences the brightness.

 analogWrite(RED,R * r);
analogWrite(GREEN,G * r);
analogWrite(BLUE,B * r);
  }

For control purposes, the current values are output to the serial interface every second.


 if ((millis() - l) > 1000) {
l = millis();
Serial.print("angle = ");
Serial.println(angle);
Serial.print("Ux = "); Serial.print(x);
Serial.print(" Uy = "); Serial.println(y);
Serial.print("R = "); Serial.print(R);
Serial.print(" G = "); Serial.print(G);
Serial.print(" B = "); Serial.println(B);
  }
}

So, that's it for this part. Have fun experimenting. In the next part, analog sensors will follow.

 Article as PDF

Für arduinoProjekte für anfänger

Leave a comment

All comments are moderated before being published

Recommended blog posts

  1. ESP32 jetzt über den Boardverwalter installieren - AZ-Delivery
  2. Internet-Radio mit dem ESP32 - UPDATE - AZ-Delivery
  3. Arduino IDE - Programmieren für Einsteiger - Teil 1 - AZ-Delivery
  4. ESP32 - das Multitalent - AZ-Delivery