Touch Buttons Project

hi, I’m looking for some guidance on my Touch Buttons Project
I’m using ILI9341 display and stm32 bluepill board

I currently have a touch grid set up that prints 1-16 on screen
up button prints 1
select button prints 2
down button prints 3
the other buttons I don’t need

what I want is up and down to cycle through 16 numbers (this i’m struggling with)

and the text; shimmer reverb, dwell, pitch, blend, to all change based on the 1-16 position

so position 2 would print; hall reverb, size, depth, reflection
and so on (this I can probably figure out when I have the above figured out)

load button will keep 1-16 position and info on screen,
if load is not pressed within 5 seconds while scrolling through the 16 presets the display is to default to the last loaded position

I also want 3 b100k potentiometers 0v - 3.3v displayed as 0 - 100 values (I have pin 2 from the pots connected to analog inputs, pin 1 to gnd, pin 3 to 3.3v

I’ve tried a few things in the code, commented out, that didn’t quite work out
so just hoping for some advice for things to try or things to read up on
thank you

(I will also be sending high or low to 4 output pins based on the 1-16 position, they connect to a fv1 chip for loading the algorithms, I’ve done this previously using a rotary encoder so should be able to figure this part out)
my previous finished project;
http://www.madbeanpedals.com/forum/index.php?topic=29972.0

/**
 * XPT2046 touch screen buttons.
 *
 * It divides the touch screen into COLUMNS * LINES areas (4*2=8 buttons) and creates virtual buttons.   
 * if the touch screen area is pressed and prints on Serial Port 1 the X,Y coordinates.
 *
 * Copyright (c) 02 Dec 2015 by Vassilis Serasidis
 * Home: http://www.serasidis.gr
 * email: avrsite@yahoo.gr
 *
 * The sketch example has been created for using it with STM32Duino (http://www.stm32duino.com)
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "XPT2046_touch.h"
#include <SPI.h>
#include <Fonts/FreeMonoBoldOblique12pt7b.h>;

// For the Adafruit shield, these are the default.
#define TFT_DC PB9
#define TFT_CS PB8

#define CS_PIN  PB6  // Chip Select pin
#define LINES     4
#define COLUMNS   3
  

//char sensorPrintout[4];

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
SPIClass mySPI(1); //Create an SPI instance on SPI1 port.
XPT2046_touch ts(CS_PIN, mySPI); // Chip Select pin, SPI port

uint16_t xy[2];

void setup() {
  Serial.begin(9600);
  Serial.println("-------------------------------------------------");
  Serial.println("XPT2046 touch screen buttons");
  Serial.println("Copyright (c) 02 Dec 2015 by Vassilis Serasidis");
  Serial.println("Home: http://www.serasidis.gr");
  Serial.println("-------------------------------------------------");

   tft.begin();


  ts.begin(); //Begin TouchScreen.
  ts.setButtonsNumber(COLUMNS, LINES); //Divide the Touch screen area into 4 columns and 2 lines and make them act as buttons. 
    tft.fillScreen(ILI9341_BLACK);
    tft.setRotation(0);
   // tft.drawRoundRect(20, 20, 20, 20, 20, 20);
    //   tft.drawRoundRect(50, 50, 50, 50, 50, 50);
  tft.fillRoundRect(170, 10, 78, 60, 8, 0xFC28);
    tft.fillRoundRect(170, 90, 78, 60, 8, 0x968B);
      tft.fillRoundRect(170, 170, 78, 60, 8, 0xCA00);
       tft.setFont(&FreeMonoBoldOblique12pt7b);
       tft.setCursor(25, 300);
      // int color = 0xF800;
  tft.setTextColor(ILI9341_YELLOW);
  tft.setTextSize(1);
  //tft.setTextWrap(true);
  tft.print("Shimmer Reverb");
   tft.setTextColor(ILI9341_PINK);
  tft.setCursor(10, 30);
  tft.print("Dwell 1");
    tft.setCursor(10, 70);
  tft.print("Pitch 2");
      tft.setCursor(10, 110);
  tft.print("Blend 3");
  
   tft.setTextColor(ILI9341_BLACK);
   tft.setCursor(175, 50);
   tft.print("UP");

     tft.setTextColor(ILI9341_BLACK);
   tft.setCursor(175, 130);
   tft.print("Load");

       tft.setTextColor(ILI9341_BLACK);
   tft.setCursor(175, 200);
   tft.print("DOWN");
   
  //tft.fillTriangle(63, 63, 20, 20, 106, 20, ILI9341_CYAN);
    //tft.fillRoundRect;

   //   tft.setCursor(10, 10);
}

void loop() {
  
  if(ts.read_XY(xy)){ //If the touch screen is preesed, read the X,Y coordinates and print them on Serial port.
    uint8_t buttonNumber = ts.getButtonNumber();
    if(buttonNumber > 0)
      Serial.print("Button: ");
      Serial.println(buttonNumber);
      

            tft.fillRoundRect(-10, 180, 60, 30, 8, ILI9341_RED);
             tft.setTextColor(ILI9341_GREEN);
             tft.setCursor(10, 200);
             tft.println(buttonNumber);

            
             
      Serial.print("X: ");
      Serial.println(xy[0]); //Print X value
      
      Serial.print("Y: ");
      Serial.println(xy[1]); //Print Y value
      
      Serial.println();
      

//int yPosit;
  
    // for(int y = 0; y < 87; y++){
     //  yPosit = 20 + y;
     //  tft.drawTriangle(63, 63, 20, yPosit, 106, yPosit, ILI9341_CYAN);
      // tft.drawTriangle(63, 63, 20, yPosit, 106, yPosit, ILI9341_BLACK); }
       delay(250);  

        
//  int sensorValue = analogRead(PA0);

//  int voltage = sensorValue * (26 / 1023.0);
//  if(sensorValue > 0)
//  Serial.println(voltage);
//  tft.setCursor(10, 70);
//  tft.println(voltage);

 // String sensorVal = String(analogRead(PA0));

 // sensorVal.toCharArray(sensorPrintout, 4);
 //  tft.fillRoundRect(0, 10, 78, 30, 8, ILI9341_BLACK);
   // delay(250);
   //tft.println(sensorPrintout);

    }
    //delay(200);
  }

got a pot working-ish!

void loop() {



      int (PotValue); 
            int (NewValue);
           PotValue = analogRead(PA0); 
           NewValue = map(PotValue, 0, 3892, 0, 100);
           tft.print(NewValue);
           
             tft.fillRoundRect(-10, 230, 70, 30, 8, ILI9341_BLACK);
             tft.setTextColor(ILI9341_GREEN);
             tft.setCursor(10, 250);
             tft.print(NewValue);    
             Serial.println(NewValue);


  if(ts.read_XY(xy)){ //If the touch screen is preesed, read the X,Y coordinates and print them on Serial port.
    uint8_t buttonNumber = ts.getButtonNumber();
    if(buttonNumber > 0)
      Serial.print("Button: ");
      Serial.println(buttonNumber);
      

            tft.fillRoundRect(-10, 180, 60, 30, 8, ILI9341_RED);
             tft.setTextColor(ILI9341_GREEN);
             tft.setCursor(10, 200);
             tft.println(buttonNumber);

            
             
      Serial.print("X: ");
      Serial.println(xy[0]); //Print X value
      
      Serial.print("Y: ");
      Serial.println(xy[1]); //Print Y value
      
      Serial.println();
   delay(250);

How can I get the value to be 0-100 ?
Mine goes to 98 or 99 ?
And how to stop it jumping from 98 - 99 ?

How can I get the value to be 0-100 ?

I don’t think you can because that is 101 values and it was only designed for 100 values. You could try a map function.

And how to stop it jumping from 98 - 99 ?

Any A/D converter is only plus or minus the least significant digit, it is how they all work. You could try averaging a few readings, or putting a small capacitor on the wiper of your pot to ground.

Grumpy_Mike:
Any A/D converter is only plus or minus the least significant digit, it is how they all work. You could try averaging a few readings, or putting a small capacitor on the wiper of your pot to ground.

Only averaging is not sufficient. Even if the amplitude of the noise is reduced to below one ADC unit, if the average is very close to the edge between two ADC values, the result will fluctuate between these two values.

You’ll need some kind of hysteresis to counteract that. Here’s the implementation I’m using:
https://github.com/tttapa/Control-Surface/blob/master/src/Helpers/Hysteresis.hpp

Pieter

Only averaging is not sufficient.

Yes it is, given enough samples to average.

Even if the amplitude of the noise is reduced to below one ADC unit

That is an implicit assumption, waste of time if it didn't.

I don't think you understand what I meant.

Imagine that your filter gets the noise below 0.1 unit, and the actual average voltage corresponds to 511.99. Most of the time, the ADC will measure 511, but if the noise is greater than 0.01 unit, the ADC will measure 512 (assuming an ideal ADC, of course). So the measurement will fluctuate between 511 and 512.

You could improve your filter, so the noise is below 0.01 unit, for example, but then you'll still be able to position the potentiometer in such a way that the noise is able to increase the value beyond the threshold (for example, 511.999).

If the position of the potentiometer is such that the average value is 511.5, there is no problem, because as long as the noise is smaller than 0.5, the measured value is not going to change. But you cannot find a filter that can ensures that for all positions, because you can always end up in a position arbitrarily close to a threshold, meaning that your noise should be arbitrarily small to prevent fluctuations between two ADC values.

I don't think you understand what I meant.

Yes I think you too don't understand.

Any noise on the sample will produce an alternating reading. The number of times a reading will be above or below the actual value will be proportional to how close the input is to the real reading. By taking enough samples you can actually improve the resolution of a given A/D converter. This is basic information theory, and can be summed up by saying that with an infinite number of samples you can get infinite resolution.

I'm not saying that you cannot improve the resolution of the ADC, I'm saying that you cannot prevent the ADC value jumping to a neighboring value just by averaging.

Imagine that the analog value is on average very close to 512, but a little higher, say μ = 512.1, and the noise is relatively small, say σ = 0.05. Most of the time (95% if the noise is assumed Gaussion), the absolute value of the noise will be less than 0.1, and the ADC will read 512. However, 2.5% of the time, the noise will be greater than 0.1 in magnitude and negative, so the ADC will read 511.
The lower the standard deviation of the noise σ, the less likely the ADC is to read 511. However, by rotating the potentiometer, μ - 512 can become arbitrarily small, and the ADC will still occasionally read 511 instead of 511.

Let's say you're filtering using a digital simple moving average filter of length N (using a flooring division), and you get the following input:

───────────────┐┌──── 512
               └┘     511
├───N───┤             ∑x = N×512 → x̅ = 512
 ├───N───┤            ∑x = N×512 → x̅ = 512
  ...
        ├───N───┤     ∑x = N×512-1 → x̅ = 511

Most of the time, all samples within the SMA window are 512, so the average x̅ is 512. However, because of the flooring division, as soon as only one of the N samples reads 511 instead of 512, the average will be floored to 511.
There is nothing you can do to your filter to prevent that. (Rounding division doesn't help, that just shifts the threshold value by 0.5.)

You can make the SMA longer, and that will decrease the probability that μ is too close to the threshold, but you cannot eliminate it. Finally, you arrive to a point where you still have the problem where sometimes it fluctuates between two values, and the filter introduces too much lag to be useful. At that point, you have to use something like hysteresis to eliminate the flickering.

how would I cycle through 16 numbers?
screen currently prints based on the number of the touch grid I press,
what I want is touch button 1 “UP” to cycle up through the numbers 1-16
and touch button 3 “DOWN” to cycle down through the numbers 1-16

#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"
#include "XPT2046_touch.h"
#include <SPI.h>
#include <Fonts/FreeMonoBoldOblique12pt7b.h>;

// For the Adafruit shield, these are the default.
#define TFT_DC PB9
#define TFT_CS PB8

#define CS_PIN  PB6  // Chip Select pin
#define LINES     4
#define COLUMNS   3
  

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC);
SPIClass mySPI(1); //Create an SPI instance on SPI1 port.
XPT2046_touch ts(CS_PIN, mySPI); // Chip Select pin, SPI port

uint16_t xy[2];

void setup() {

   tft.begin();
   ts.begin(); //Begin TouchScreen.
   ts.setButtonsNumber(COLUMNS, LINES); //Divide the Touch screen area into 4 columns and 2 lines and make them act as buttons. 
  
    tft.fillScreen(ILI9341_BLACK);
    tft.setRotation(0);
   // tft.drawRoundRect(20, 20, 20, 20, 20, 20);
    //   tft.drawRoundRect(50, 50, 50, 50, 50, 50);
    tft.fillRoundRect(170, 10, 78, 60, 8, 0xCA00);
    tft.fillRoundRect(170, 90, 78, 60, 8, 0x968B);
    tft.fillRoundRect(170, 170, 78, 60, 8, 0xCA00);
    tft.setFont(&FreeMonoBoldOblique12pt7b);
    tft.setCursor(25, 300);
    tft.setTextColor(ILI9341_GREEN);
   
  
    tft.setTextColor(ILI9341_BLACK);
    tft.setCursor(175, 50);
    tft.print("UP");

    tft.setTextColor(ILI9341_BLACK);
    tft.setCursor(175, 130);
    tft.print("SAVE");

    tft.setTextColor(ILI9341_BLACK);
    tft.setCursor(175, 200);
    tft.print("DOWN");
   
}

void loop() {


if(ts.read_XY(xy)){ //If the touch screen is preesed, read the X,Y coordinates and print them on tft screen
     uint8_t buttonNumber = ts.getButtonNumber();
    //if(buttonNumber > 0)

            tft.fillRect(0, 150, 90, 60, ILI9341_BLACK);
           tft.setTextColor(ILI9341_GREEN);
           tft.setTextSize(2);
            tft.setCursor(10, 200);
            tft.print(buttonNumber);
           // delay(250);
            }

  }

Leetut, good job on your previous project — looks great!

It sounds like you are trying to tackle a couple tasks with your interface:

1: Display multiple pages of data & touch controls
2: Timed default transition between pages
3: Capturing analog potentiometer values

From your update, I assume you have (3) figured out.

From what I understand of your project, you will let the user step through 16 “pages”, each providing a different audio preset mode:

  • The UP/DOWN buttons will step through each page. If a user waits on a single page for more than 5 seconds without pressing LOAD, then the page will automatically transition back to the last selected page.
  • A “preset” page will drive 4 GPIOs to the external device to select the current mode.
  • Each page offers 3 analog setting values that are sampled directly from the potentiometer inputs. Presumably these are also communicated somehow to the external device.
  • QUESTION: when you are transitioning between pages (with UP/DOWN), are you simply re-sampling the latest potentiometer values or are you trying to preserve the previous “presets” (ie. The potentiometer values you last had on that page)? If the latter, a rotary encoder might be easier to use than a potentiometer, but I could just be misunderstanding your intended design.

Assuming the above is roughly what you’re after...

For the interface (1) I have listed out a couple general ideas that might be worth considering. There are countless ways to do this, and using a GUI library could certainly make it much easier. Nonetheless, I thought I would share some simple strategies first.

I would recommend having a global variable to keep track of what “page” you are on (eg. curPage). This will be 0..15 depending on which setting page you’d like to show.

Then, you can create a drawPage() function that renders the screen according to curPage. I would also create a global variable pageNeedRedraw to indicate that something has changed requiring you to update it.

In your main loop, you could call drawPage() if pageNeedRedraw is set (drawPage would clear this variable when done). Then, you could check the state of the button inputs. If UP / DOWN, then you would increment/decrement curPage and set pageNeedRedraw to true.

Depending on how you have your potentiometers set up, your main loop probably also continuously reads the ADCs to get the 3 values. You would likely want to set pageNeedRedraw if one of the potentiometer values changed by more than a certain amount (this is the “hysteresis” that PieterP referred to, which can avoid some flickering/noisy output).

For (2), I would save the current millis() into a “lastPageTime” variable any time you update curPage to keep track of how long you have shown the current page. In your main loop if the delta from current millis() and curPageTime is >5000 then force curPage to selectedPage and set pageNeedRedraw.

When the user presses LOAD, then selectedPage can be set to curPage.

Just a few general ideas to get you started...