Touchscreen based menu, how to make a library

I got hands on an Adafruit TFT shield with capacitive touch. The shield uses an ILI9341 controller for the TFT, and an FT6206 controller for the capacitive touch, for which the Adafruit_ILI9341 and Adafruit_FT6206 libraries are used. The setup is perfectly working with the given example on an Arduino Uno.

However, I’m trying to write a library for this shield (named TouchMenu), such that a menu can be made using for example buttons and sliders. I want to be able to use the TFT and touch controller instances both in the main arduino sketch, and in the library. So, the idea is to pass a pointer to both controllers to the library via the constructor. I haven’t written an Arduino (or C++) library before, so I’m stuck now on getting the library working properly. The code I’ve written so far is as follows:

TouchMenu.h:

#ifndef TouchMenu_h
#define TouchMenu_h

#include <Arduino.h>
#include "Adafruit_ILI9341.h"
#include "Adafruit_FT6206.h"

#define BTN_BEEP_DELAY1 150
#define BTN_BEEP_DELAY2 15

#define BTN_RADIUS 10

#define COL_BTN_BG       0x000A
#define COL_BTN_BORDER   ILI9341_WHITE
#define COL_BTN_PRESSED  ILI9341_DARKGREY

class TouchMenu
{
  public:
    TouchMenu(Adafruit_ILI9341 *tft, Adafruit_FT6206 *ctp, int8_t buzzer_pin);
 void beep(uint16_t beepDelay);
 
 Adafruit_ILI9341 *_tft;
 Adafruit_FT6206 *_ctp;
 int8_t _buzzer_pin;
};


class Button : public TouchMenu {
  public:
    Button(int16_t x, int16_t y, int16_t w, int16_t h, char *text);
    void draw();
  private:
    int16_t _x;
    int16_t _y;
    int16_t _w;
    int16_t _h;
    char *_text;
};


 
#endif

TouchMenu.cpp:

#include "Arduino.h"
#include "TouchMenu.h"

TouchMenu::TouchMenu(Adafruit_ILI9341 *tft, Adafruit_FT6206 *ctp, int8_t buzzer_pin)
{
 _tft = tft;
 _ctp = ctp;
 _buzzer_pin = buzzer_pin;
 pinMode(_buzzer_pin, OUTPUT);
}

void TouchMenu::beep(uint16_t beepDelay)
{
  digitalWrite(_buzzer_pin, HIGH);
  delayMicroseconds(beepDelay);
  digitalWrite(_buzzer_pin, LOW);
}

Button::Button(int16_t x, int16_t y, int16_t w, int16_t h, char *text) : TouchMenu(_tft, _ctp, _buzzer_pin)
{
  _x = x;
  _y = y;
  _w = w;
  _h = h;
  _text = text;
}

void Button::draw()
{
  _tft->fillRoundRect(_x, _y, _w, _h, BTN_RADIUS, COL_BTN_BG);
  _tft->drawRoundRect(_x, _y, _w, _h, BTN_RADIUS, COL_BTN_BORDER);
  _tft->drawCentreString(_text, _x+_w/2, _y+_h/2, 2);
}

Arduino sketch:

#include <Adafruit_GFX.h>     // Core graphics library
#include <SPI.h>       
#include <Adafruit_ILI9341.h>
#include <Wire.h>     
#include <Adafruit_FT6206.h>
#include "colors.h"
#include <TouchMenu.h>

// Capacitive touch driver
Adafruit_FT6206 ctp = Adafruit_FT6206();

// The display also uses hardware SPI, plus #9 & #10
#define TFT_CS 10
#define TFT_DC 9
Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);

#define BUZZER_PIN 8
TouchMenu tm(&tft, &ctp, BUZZER_PIN);

Button btn(185, 280, 50, 40, "Abc");

void setup(void) {
  Serial.begin(115200);
  Serial.println(F("Cap Touch Paint!"));
  
  tft.begin();

  if (! ctp.begin(40)) {  // pass in 'sensitivity' coefficient
    Serial.println("Couldn't start FT6206 touchscreen controller");
    while (1);
  }

  Serial.println("Capacitive touchscreen started");
  
  tft.fillScreen(COL_BLACK);
  tft.setRotation(2);
  tm.beep(15);
  
  btn.draw();
  
}

The loop routine doesn’t contain any relevant code, problems arise during initialization.

The TouchMenu class thus has a child class Button. Both classes have constructors to which parameters have to be passed. In the TouchMenu class constructor, the references to the TFT and touch controller objects are passed. However, in the Button constructor, the parameters for the TouchMenu need to be repeated, which is where problems arise (I guess). I don’t know how to fix this due to my limited knowledge of C++. Could anyone help me out here? Thanks in advance!

Edit: The actual problem is that the sketch restarts over and over again, the button is never drawn. The program crashes at the command tft->fillRoundRect(…) in Button::draw().

Well right now it draws the button, so now you need a function that checks if the button is pressed or not.

You can make another function called status or check (whatever you want really) that calls for the screens touch data, and compares the touched coordinates to the buttons coordinates.

Example:

bool Button::Check()
{
  TS_Point p = _ctp->getPoint();  // TS_Point is what my library uses for its touch points, but I don't know what your touchscreen library uses, so you will need to find that out.

  if ((p.x >= _x) && (p.x <= (_x + _w)) && (p.y >= _y) && (p.y <= (_y + _h)) ) return true; // If the buttons coords are touched, return true.
  return false;
}

Edit:

The actual problem is that the sketch restarts over and over again, the button is never drawn. The program crashes at the command tft->fillRoundRect(…) in Button::draw().

Ok, but look at the coords you are setting your button to,
Button btn(185, 280, 50, 40, “Abc”);

How big is your screen? 240 x 320? If so, make the width and height of the button smaller like 25 and 20, then see if it draws on the screen.

I already have some code for checking if the button is pressed, which works fine. I left it out of the posted code, since the problem isn't located in the actual drawing of the button, but in the initalization of the classes. The button drawing/checking worked fine, until I put everything in a library.

The screen is has 240 by 320 pixels.

You need to post everything and not just parts.

The other parts of the code are never called (checked it via serial communication). My guess is that the stuff with pointers in the constructors of the classes goes wrong, but I don't know how to do this properly since pointers/references are still a new subject to me.

To explain it some more: I want to initialize an object of the TM class once, with which the _tft and _ctp pointers are set correctly to the TFT and touch controllers used in the main sketch. This is done in the constructor of the TouchMenu class, and it's working correctly, since I can draw something using a function within the TouchMenu class.

When the child class Button is used, when a Button object is made, its constructor is called. The constructor of its parent class, TouchMenu, is also called. This parent class' constructor is then also called, which needs (addresses of) the TFT and touch controllers. The problem is that I don't want to send these parameters, since the _tft and _ctp pointers are already initialized with the constructor of the TM class, which was called once.

So, what is a workaround for the problem described above? I'm not really good with C++ yet, so if anything is still unclear, please ask.

No, I understand what you are trying to do, I have made my own TFT library. What I don’t understand is why yours is not working.

Maybe im overlooking something very obvious.

Oh wait, in your Button class, you don’t have anything that points to _tft or _ctp.

Try something like this: (Example from my library)

class Base
{
  public:
    Base() {}
    Base(ITDB02 *Disp, ITDB02_Touch *Touch): _Disp(Disp), _Touch(Touch) {}

    ITDB02 * getDisplay() 
    {
      return _Disp;
    }

    ITDB02_Touch * getTouch() 
    {
      return _Touch;
    }

  protected:
    ITDB02	  *_Disp;
    ITDB02_Touch  *_Touch;
};

class Button : public Base
{
  public:
    Button(Base * _b): B(_b) { locked = 0; }

    void Coords(int x1, int y1, int x2, int y2)
    {
      X1 = x1;
      Y1 = y1;
      X2 = x2;
      Y2 = y2;
    }

    void Colors(word c1, word c2, bool round, bool fill, byte padding = 0)
    {
      C1 = c1;
      C2 = c2;
      Round = round;
      Fill = fill;
      Padding = padding;
    }
     
    . . .

    void Draw()
    {
      _Disp->setColor((State ? C1 : C2));
      Fill ? _Disp->fillCircle(X, Y, Radius) : _Disp->drawCircle(X, Y, Radius);
    }

  private:
    int X1, Y1, X2, Y2;
    byte Padding;
    word C1, C2;
    bool Round, Fill;
    bool Lock;
    bool locked;

  protected:
    Base * B;
    ITDB02	  *_Disp = B->getDisplay(); 
    ITDB02_Touch  *_Touch = B->getTouch();
    bool State;
}

In sketch:

#include <ITDB02_Graph16.h>
#include <ITDB02_Touch.h>
extern uint8_t SmallFont[];

ITDB02 myGLCD(A1, A2, A0, A3, A5, ITDB32S);
ITDB02_Touch  myTouch(13, 10, 11, 12, A4);

Base B(&myGLCD, &myTouch);
Button myButton(&B);

void setup()
{
  /* stuff to get the screen started */
  . . .

  myButton.Coords(50, 100, 100, 150);
  myButton.Colors(0xD7E0, 0x001F, 1, 1, 4);
  myButton.Draw();
}