Is there a more efficient way to register a grid of buttons

I'm working on a touch screen step sequencer that will eventually be sending out MIDI data. I'm still working on drawing the graphics and creating a unique on/ off state for each square on my grid.
In terms of drawing and redrawing my grid I've been successful however, I'm using a bunch of nested if statements and I wonder if this could be done more efficiently somehow. Perhaps there is a way I could iterate through instead of having to write a section of code for each button... ? The section I'm talking about is in the bpress() function.

#include <stdint.h>

#include <TFTv2.h>

#include <SeeedTouchScreen.h>

#define ShowSerial Serial

#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) // mega
    #define YP A2   // must be an analog pin, use "An" notation!
    #define XM A1   // must be an analog pin, use "An" notation!
    #define YM 54   // can be a digital pin, this is A0
    #define XP 57   // can be a digital pin, this is A3

#elif defined(__AVR_ATmega32U4__) // leonardo
    #define YP A2   // must be an analog pin, use "An" notation!
    #define XM A1   // must be an analog pin, use "An" notation!
    #define YM 18   // can be a digital pin, this is A0
    #define XP 21   // can be a digital pin, this is A3
#elif defined(ARDUINO_SAMD_VARIANT_COMPLIANCE) // samd21

    #define YP A2   // must be an analog pin, use "An" notation!
    #define XM A1   // must be an analog pin, use "An" notation!
    #define YM A4   // can be a digital pin, this is A0
    #define XP A3   // can be a digital pin, this is A3
    #undef ShowSerial
    #define ShowSerial SerialUSB

#else //168, 328, something else
    #define YP A2   // must be an analog pin, use "An" notation!
    #define XM A1   // must be an analog pin, use "An" notation!
    #define YM 14   // can be a digital pin, this is A0
    #define XP 17   // can be a digital pin, this is A3

#endif

//Measured ADC values for (0,0) and (210-1,320-1)
//TS_MINX corresponds to ADC value when X = 0
//TS_MINY corresponds to ADC value when Y = 0
//TS_MAXX corresponds to ADC value when X = 240 -1
//TS_MAXY corresponds to ADC value when Y = 320 -1

#define TS_MINX 116*2
#define TS_MAXX 890*2
#define TS_MINY 83*2
#define TS_MAXY 913*2


int counter1 = 0;
int row = 0;
int col = 0;
int grid[3][3];

// For better pressure precision, we need to know the resistance
// between X+ and X- Use any multimeter to read it
// The 2.8" TFT Touch shield has 300 ohms across the X plate
TouchScreen ts = TouchScreen(XP, YP, XM, YM);

void setup(void) {
  int gridLine=0;
  int x = 0;
  int y = 0;
    ShowSerial.begin(9600);
        TFT_BL_ON;                                  // turn on the background light
    Tft.TFTinit();                              //init TFT library

/*
    Tft.drawLine(0, 0, 239, 319, RED);          //start: (0, 0) end: (239, 319), color : RED

    Tft.drawVerticalLine(240/2, 1, 319, BLUE);  // Draw a vertical line
    // start: (60, 100) length: 100 color: blue
while(gridLine < 13){
    Tft.drawHorizontalLine(1, 320/12*gridLine+4, 239, GREEN);  //Draw a horizontal line
    //start: (30, 60), high: 150, color: green
    gridLine++;
    
} */
//draw grid
for(x = 0; x < 200; x = x+50){
   for(y = 0; y < 150; y = y+50){
    delay(10);
    Tft.drawRectangle(x, y, 50, 50, BLUE);
   }
   delay(10);
   Tft.drawRectangle(x, y, 50, 50, BLUE);
}
  delay(10);
}

/*
void loop(void) {
  for(row = 0; row < 200; row = row+50){
   for(col = 0; col < 150; col = col+50){
    delay(1000);
    Tft.fillRectangle(row, col, 50, 50, BLUE);
   }
   delay(1000);
   Tft.fillRectangle(row, col, 50, 50, BLUE);
}
  delay(1000); 
}
*/

void loop(void){
      bpress();    
}


//the func that registers pressing squares on the grid
void bpress(void){
    // a point object holds x y and z coordinates
    Point p = ts.getPoint();

    //map raw values to pixel values
    p.x = map(p.x, TS_MINX, TS_MAXX, 0, 240);
    p.y = map(p.y, TS_MINY, TS_MAXY, 0, 320);

        // we have some minimum pressure we consider 'valid'
    // pressure of 0 means no pressing!
    //display mapped pixel values instead of raw values
    if (p.z > __PRESSURE) {
        ShowSerial.print("X = "); ShowSerial.print(p.x);
        ShowSerial.print("\tY = "); ShowSerial.print(p.y);
        ShowSerial.print("\tPressure = "); ShowSerial.println(p.z);

        //step 1
        if(grid[0][0] == 0){
        if(p.x <= 50 && p.x > 0 && p.y <=50 && p.y > 0){          
          Tft.fillRectangle(0, 0, 50, 50, BLUE);
          grid[0][0] = 1;
          }
        } else if(grid[0][0] == 1){
          if(p.x <= 50 && p.x > 0 && p.y <=50 && p.y > 0){ 
          Tft.fillRectangle(0, 0, 50, 50, BLACK);
            Tft.drawRectangle(0, 0, 50, 50, BLUE);
            grid[0][0] = 0;
            }
        }

            //step 2
        if(grid[0][1] == 0){
        if(p.x <= 100 && p.x > 50 && p.y <=50 && p.y > 0){          
          Tft.fillRectangle(50, 0, 50, 50, BLUE);
          grid[0][1] = 1;
          }
        } else if(grid[0][1] == 1){
           if(p.x <= 100 && p.x > 50 && p.y <=50 && p.y > 0){     
          Tft.fillRectangle(50, 0, 50, 50, BLACK);
            Tft.drawRectangle(50, 0, 50, 50, BLUE);
            grid[0][1] = 0;
            }
        } 


        //step 3
        if(grid[0][2] == 0){
        if(p.x <= 150 && p.x > 100 && p.y <=50 && p.y > 0){          
          Tft.fillRectangle(100, 0, 50, 50, BLUE);
          grid[0][2] = 1;
          }
        } else if(grid[0][2] == 1){
          if(p.x <= 150 && p.x > 100 && p.y <=50 && p.y > 0){ 
          Tft.fillRectangle(100, 0, 50, 50, BLACK);
            Tft.drawRectangle(100, 0, 50, 50, BLUE);
            grid[0][2] = 0;
            }
        }

            //step 4
        if(grid[0][3] == 0){
        if(p.x <= 200 && p.x > 150 && p.y <=50 && p.y > 0){          
          Tft.fillRectangle(150, 0, 50, 50, BLUE);
          grid[0][3] = 1;
          }
        } else if(grid[0][3] == 1){
           if(p.x <= 200 && p.x > 150 && p.y <=50 && p.y > 0){     
          Tft.fillRectangle(150, 0, 50, 50, BLACK);
            Tft.drawRectangle(150, 0, 50, 50, BLUE);
            grid[0][3] = 0;
            }
        }



        //step 5
        if(grid[1][0] == 0){
        if(p.x <= 50 && p.x > 0 && p.y <=100 && p.y > 50){          
          Tft.fillRectangle(0, 50, 50, 50, BLUE);
          grid[1][0] = 1;
          }
        } else if(grid[1][0] == 1){
          if(p.x <= 50 && p.x > 0 && p.y <=100 && p.y > 50){ 
          Tft.fillRectangle(0, 50, 50, 50, BLACK);
            Tft.drawRectangle(0, 50, 50, 50, BLUE);
            grid[1][0] = 0;
            }
        }

            //step 6
        if(grid[1][1] == 0){
        if(p.x <= 100 && p.x > 50 && p.y <=100 && p.y > 50){          
          Tft.fillRectangle(50, 50, 50, 50, BLUE);
          grid[1][1] = 1;
          }
        } else if(grid[1][1] == 1){
           if(p.x <= 100 && p.x > 50 && p.y <=100 && p.y > 50){     
          Tft.fillRectangle(50, 50, 50, 50, BLACK);
            Tft.drawRectangle(50, 50, 50, 50, BLUE);
            grid[1][1] = 0;
            }
        } 


        //step 7
        if(grid[0][2] == 0){
        if(p.x <= 150 && p.x > 100 && p.y <=100 && p.y > 50){          
          Tft.fillRectangle(100, 50, 50, 50, BLUE);
          grid[0][2] = 1;
          }
        } else if(grid[0][2] == 1){
          if(p.x <= 150 && p.x > 100 && p.y <=100 && p.y > 50){ 
          Tft.fillRectangle(100, 50, 50, 50, BLACK);
            Tft.drawRectangle(100, 50, 50, 50, BLUE);
            grid[0][2] = 0;
            }
        }

            //step 8
        if(grid[0][3] == 0){
        if(p.x <= 200 && p.x > 150 && p.y <=100 && p.y > 50){          
          Tft.fillRectangle(150, 50, 50, 50, BLUE);
          grid[0][3] = 1;
          }
        } else if(grid[0][3] == 1){
           if(p.x <= 200 && p.x > 150 && p.y <=100 && p.y > 50){     
          Tft.fillRectangle(150, 50, 50, 50, BLACK);
            Tft.drawRectangle(150, 50, 50, 50, BLUE);
            grid[0][3] = 0;
            }
        }
        delay(250); //debounce
    }
    
  }

Rather than having your grid array be of type int, make it a Point so you can have the x & y coordinates of each grid. Then, use a for loop to cycle through at test to see if it is within that square.

Also, this line

is out of bounds. a 3x3 array only has elements 0..2

The point should drive the logic. Without going blind through the code it looks like only one of the grid squares con fire.

The screen coordinates of the point can be transformed to a row and colm (or miss) with some simple Algebra.

Draw us a picture of the real estate (screen boundary coordinates) you have for the grid touch area and how many x by y areas there are and how many colors can there be.

I mean draw a picture it would be the first step for the maths.

a7

This code, with some useless delays, determines the midpoint of each "button". From the coordinates of the touch, determine the distance to each button center. If that distance is within a certain radius, you know which "button" was pressed.

for(x = 0; x < 200; x = x+50){
   for(y = 0; y < 150; y = y+50){
    delay(10);
    Tft.drawRectangle(x, y, 50, 50, BLUE);
   }
   delay(10);
   Tft.drawRectangle(x, y, 50, 50, BLUE);
}
  delay(10);
}

I believe the screen is 240x360. Right now the button squares are 50px but I intend to make them bigger to fit the screen as a 4x4 grid filling up one side with space on the side for a few extra controls.
Could you elaborate alittle futher. I am quite noob.

Here is a complete example, for an STM32 with touchscreen. It draws a 16 key keypad, labels each key, remembers the key centers, then when the touchscreen is called, it returns the button associated with a touch, if within bounds.

/**
   STM32F746NG Discovery onboard display touchscreen example
   http://www.st.com/en/evaluation-tools/32f746gdiscovery.html
   complete example by SJR

   Uses LTDC, display see AN4861 for more information.
  
   Import the Adafruit GFX library
   Sketch=>Include Libraries=>Manage Libraries=>Adafruit GFX

*/

#include "LTDC_F746_Discovery.h"  //TFT
#include "TouchScreen_F7_Discovery.h" // TOUCH

LTDC_F746_Discovery tft;
TouchScreen         ts;

struct LTDC_keypad {
  uint16_t width;
  uint16_t height;
  uint16_t X0;
  uint16_t Y0;
  uint16_t bnum;
  uint16_t bsize;
  uint16_t bcolor; //key background color
  uint16_t lcolor; //character label color
  uint16_t bc_xy[2][16]; //x and y button centers, 16 max for now
  int16_t pressed;  //-1 if not pressed, button number if pressed
  uint16_t px; //current press position
  uint16_t py; //can be anywhere on screen
  char lut[16]; //keypad label lookup table
};

LTDC_keypad kp;

  // screen buffer is in global memory

  uint16_t screen[(LTDC_F746_ROKOTECH.width * LTDC_F746_ROKOTECH.height)]={0};

void setup() {
  Serial.begin(9600);
  Serial.println("keypad tests");

  tft.begin(screen);
  tft.fillScreen(LTDC_BLACK);
  int i;
  keypad_init();

  tft.setTextColor(LTDC_WHITE);
  tft.setTextSize(2);
  tft.setCursor(0, 2);

  keypad_getkey();  //toss first touch
 Serial.println(F("activating touchpad"));
}

void loop(void) {

  int16_t key = keypad_getkey();

  if (key >= 0) {  //got a new keypress
    tft.setTextColor(LTDC_WHITE);
    tft.setCursor(0, 0);
    tft.fillRect(0, 0, 200, 16, LTDC_BLACK);
    tft.setCursor(0, 0);
    tft.setTextSize(2);
    tft.println(key);
  }
  yield();  //required by STM32
}

// draw rounded rectangle of size w about center (cx, cy)

int RoundRect(uint16_t cx, uint16_t cy, uint16_t color, uint16_t w) {
  int w2 = w / 2;
  tft.drawRoundRect(cx - w2, cy - w2, w, w, w / 8, color);
  return 0;
}

// draw filled round rectangle of size w around center (cx, cy)

int FilledRoundRect(uint16_t cx, uint16_t cy, uint16_t color, uint16_t w) {
  int w2 = w / 2;
  tft.fillRoundRect(cx - w2, cy - w2, w, w, w / 8, color);
  return 0;
}

//
// draw rounded square button centered on cx, cy, total width w pixels, with one centered character as label
// w = 40 to 100 works well
//

int DrawButton(uint16_t cx, uint16_t cy, uint16_t w, uint16_t bcolor, char* txt, uint16_t lcolor) {
  FilledRoundRect(cx, cy, bcolor, w);
  // character bitmap = 6x8, background pixels right and bottom
  // pick appropriate character size and center it on button
  int txtsize = w / 10 - 1; //pixels/bit
  int t2x = txtsize * 6 / 2 - txtsize / 2; //character center x
  int t2y = txtsize * 8 / 2 - txtsize / 2; //character center y
  tft.setTextSize(txtsize);
  tft.setTextColor(lcolor);
  tft.setCursor(cx - t2x, cy - t2y);  //upper left pixel
  tft.setTextSize(txtsize);
  tft.print(txt);
  return 0;
}

void keypad_init(void) {
  uint16_t cx, cy;
  uint16_t i;

  // set up 4x4 HEX keypad
  // keypad width, height and origin (upper left corner)
  kp.width = 270; kp.height = 270; kp.X0 = 200; kp.Y0 = 0;
  kp.bsize = 60; //button height/width in pixels
  kp.bnum = 16; //number of buttons
  kp.bcolor = LTDC_CYAN; //key background color
  kp.lcolor = LTDC_BLACK; //label character color
  //set key labels (single character)
  memcpy(kp.lut, "0123456789.<#XYZ", 16);

  // draw keypad

  for (i = 0; i < kp.bnum; i++) {
    cx = (i % 4) * kp.width / 4 + kp.bsize / 2 + kp.X0;
    cy = (i / 4) * kp.height / 4 + kp.bsize / 2 + kp.Y0;
    kp.bc_xy[0][i] = cx;  // remember the key center coords
    kp.bc_xy[1][i] = cy;
    char label[2];  //print key label
    snprintf(label, 2, "%c", kp.lut[i]); //stuff in the appropriate label
    DrawButton (cx, cy, kp.bsize, kp.bcolor, label, kp.lcolor);
    // reset to defaults
    tft.setTextColor(LTDC_WHITE);
    tft.setTextSize(1);
  }
}

int16_t keypad_getkey(void) {
  static TSPoint OldPoint, p;
  uint16_t i, dist2;
  uint16_t button_radius2 = (kp.bsize / 2) * (kp.bsize / 2); //button radius squared

  p = ts.getPoint();
  if ( OldPoint != p )
  {
    OldPoint = p;
    kp.px = p.x; //current touch coords
    kp.py = p.y;
    kp.pressed = -1;
    // find which button pressed, if any
    for (i = 0; i < 16; i++) {
      dist2 = (p.x - kp.bc_xy[0][i]) * (p.x - kp.bc_xy[0][i]) +
              (p.y - kp.bc_xy[1][i]) * (p.y - kp.bc_xy[1][i]);
      if (dist2 < button_radius2) kp.pressed = i;
    }
    return kp.pressed;
  }
  return -1;

And here is an incomplete example. It goes directly from coordinates to area number.

It compiles and seems to work, but I would use it only after understanding what it is doing.

The while loops are manifest integer division, which makes me think it was originally in assembly language. The code implements a little dead zone between the valid areas.

OIC the while loop calculates the quotient and remainder. Both are used.

The below simply figures out the rectangle the point is in, or that it is in none. All timing of such conditions and rendering to the screen is someone else's problem.

// lower left-hand corner
# define OFFX 		0
# define OFFY 		50

// rectangle dimensions
# define WHX		80
# define WHY		64

// indices run 0..(n - 1)
# define MAXROW		2		// 3 rows
# define MAXCOLM	3		// 4 colms

# define DEAD 7				// dead zone between areas

// returns 0, not on keypad or 1..N number of grid areas.
int keyboard(int x, int y)
{
	int row, colm;

	if ((x - OFFX) < 0) return (0);
	if ((y - OFFY) < 0) return (0);

	x -= OFFX;
	row = 0;

	while (x > WHX) {
		row++; x -= WHX;
	}

	if (row > MAXROW) return (0);
	if ((x - DEAD) < 0) return (0);
	if ((x + DEAD) > WHX) return (0);

	y -= OFFY;
	colm = 0;

	while (y > WHY) {
		colm++; y -= WHY;
	}

	if (colm > MAXCOLM) return (0);
	if ((y - DEAD) < 0) return (0);
	if ((y + DEAD) > WHY) return (0);

	return ((MAXROW + 1) * row + colm + 1);
}

All the coloring looks like

Tft.fillRectangle(100, 50, 50, 50, BLUE);
Tft.fillRectangle(150, 0, 50, 50, BLACK);

this is similarly amenable to an algorithm approach where you have a function that can change the color of an area number supplied to it.

Elsewhere I found

void drawCap(char legend, int key, int color)

which was used as a general drawing and feedback mechanism that was called for drawing the keyboard and whenever a touch was detected. Like in different colors.

HTH

a7

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.