M5Core2 library fork that supports multi-touch and provides Arduino-style virtual buttons.



  • If you use this fork of the M5Core2 library, you get a Touch object that not only supports the existing API but also a newer API that reads up to two simultaneous touches from the touch sensor. (Limitations apply, see below.)

    The three touch circles are available as BtnA - BtnC and you get a TouchButton object class that lets you create additional Arduino-style buttons for any rectangle on the screen, and then you can use the regular Arduino functions like isPressed() etc. You can also define handler functions for the pressed and released events for any given TouchButton. You can see everything demonstrated if you run the example sketch from the library examples (also copied below).

    It's a drop-in replacement: the old touch API still works and the M5Stack factory test compiles fine. I have filed a Pull request on GitHub, so hopefully this all goes into the next version of the stock library.

    
    
    

    Touch object documentation (from src/touch.h)

    M5Stack Core2 touch library
    
    	Touch is provided by a FocalTech FT6336 chip, which supports two
    	simultaneous touches (see below for limitation).
    
    	This library is initialised in M5Core2.h. An instance "M5.Touch" exists
    	and can be used after you call "M5.begin()".
    
    
    Two-touch API
    
    	TouchPoint_t
    		Variable type to hold a touchpoint. Has members x and y that hold
    		the coordinates of a touch.
    
    	M5.update()
    		In the loop() part of your sketch, call "M5.update()". This is the
    		only part that talks to the touch interface. It updates the data
    		used by the rest of the two-touch API.
    
    	uint8_t M5.Touch.points
    		Contains the number of touches detected: 0, 1 or 2.
    
    	TouchPoint_t M5.Touch.point[0], M5.Touch.point[1]
    		M5.Touch.point[0] and M5.Touch.point[1] hold the detected touches.
    		
    	bool M5.Touch.inBox(x0, y0, x1, y1)
    		true if any valid touch is found in the supplied rectangle. Can be
    		used directly, but you'll be happier with the TouchButton library.
    
    	bool M5.Touch.hasChanged()
    		true if anything has moved on the touch screen since the last time
    		this function was called.
    
    
    Note about multi-touch
    
    	The M5Stack Core2 touch display is only multi-touch in one dimension.
    	What that means is that it can detect two separate touches only if they
    	occur on different vertical positions. This has to do with the way the
    	touch screen is wired, it's not something that can be changed in
    	software. So you will only ever see two points if they do not occur
    	side-by-side. Touches that do happen side-by-side blend into one touch
    	that is detected somewhere between the actual touches.
    
    	While this limits multi-touch somewhat, you can still create multiple
    	buttons and see two that are not on the same row simultaneously. You
    	could also use one of the buttons below the screen as a modifier for
    	something touched on the screen.
    
    
    Legacy single touch API
    
    	TouchPoint_t
    		Variable type to hold a touchpoint. Has members x and y that hold
    		the coordinates of a touch.
    
    	bool M5.Touch.ispressed()
    		true when the touch screen is pressed.
    
    	TouchPoint_t M5.Touch.getPressedPoint()
    		Returns the point that is being pressed, or (-1,-1) is nothing is
    		pressed. In case two points are pressed, this will return one of
    		them.
    
    	HotZone_t
    		Defines a zone on the screen, comes with built-in functions to test
    		if a given point is within that zone. Similar but more enhanced
    		functionality is provided by the TouchButton library, see below.
    
    
    

    TouchButton documentation (from src/utility/M5TouchButton.h)

    M5Stack Core2 M5TouchButton library version 1.0
    
    	Implements Arduino button library compatible buttons for any chosen
    	rectangle on the M5Stack Core2 touch screen. Up to two buttons can be
    	pressed simultaneously.
    
    
    Basic usage
    
    	For this to work, M5.update() has to be ran to scan for button presses,
    	so make sure to put that in the loop() part of your sketch.
    
    	To create a button, just create a button variable that defines the area
    	on the touch screen for the button. E.g.:
    
    		TouchButton testButton(0, 0, 50, 50);
    
    	The format is x0, y0, x1, y1 where x0, y0 is the left top of the button
    	and x1, y1 is the right bottom. From here on you can use all the
    	standard Arduino button functions such that testButtton.isPressed()
    	will now tell you if the top left of the screen is touched.
    
    	Buttons will be deleted from the list if their variables go out of
    	focus, so if you define buttons in a subroutine, they will not be in
    	your way anywhere else.
    
    	If button areas overlap, both buttons will become pressed if the
    	overlap is touched. Note that you cannot ever press two non-overlapping
    	buttons simultaneously because the M5Core2 touch screen is not
    	multi-touch.
    
    	The three buttons BtnA, BtnB and BtnC from the older M5Stack units come
    	already implemented as buttons that lie just below the screen where the
    	three circles are. If you want them to be a little bigger and also
    	cover the area of the screen where you may be showing labels for the
    	buttons, simply raise the top of the buttons like this:
    
    		 M5.BtnA.y0 = M5.BtnB.y0 = M5.BtnC.y0 = 220;
    
    	The screen is 320 x 240 pixels, the touch sensor is 320 x 280, 40
    	pixels are below the screen.
    	
    
    Note about multi-touch
    
    	The M5Stack Core2 touch display is only multi-touch in one dimension.
    	What that means is that it can detect two separate touches only if they
    	occur on different vertical positions. This has to do with the way the
    	touch screen is wired, it's not something that can be changed in
    	software. So you will only ever see two points if they do not occur
    	side-by-side. Touches that do happen side-by-side blend into one touch
    	that is detected somewhere between the actual touches.
    
    	While this limits multi-touch somewhat, you can still create multiple
    	buttons and see two that are not on the same row simultaneously.
    	
    
    Calling functions automatically
    
    	In addition to the coordinates, you can optionally specify a
    	function to be called when a button is pressed, a function for when
    	it's released, as well as an id number to figure out what button was
    	pressed if multiple buttons are handled by the same function.
    	
    	Naturally you can also have a separate function for each key in which
    	case you doon't need to supply an id number (it defaults to 0).
    	Function pointers and id can also be retrieved or set later, with
    	btn.fnPress, btn.fnRelease and fn.id. So to attach a function to the
    	leftmost button under the screen, simply say:
    	
    		M5.BtnA.fnPress = myFunction;
    
    
    
    

    Touch example (examples/Basics/Touch/touch.ino)

    #include <M5Core2.h>
    
    void setup() {
      M5.begin();
    }
    
    void loop() {
      M5.update();
      if (M5.Touch.hasChanged()) {
        if (!M5.Touch.points) Serial.print("--");
        if (M5.Touch.points) Serial.printf("x: %d, y: %d        ",  M5.Touch.point[0].x, M5.Touch.point[0].y);
        if (M5.Touch.points == 2) Serial.printf("x: %d, y: %d",  M5.Touch.point[1].x, M5.Touch.point[1].y);
        Serial.println();
      }
    }
    
    void btnHandler(TouchButton &btn) {
      M5.Lcd.fillRect(btn.x0, btn.y0, btn.x1 - btn.x0, btn.y1 - btn.y0, btn.isPressed() ? WHITE : BLACK);
    }
    
    TouchButton lt = TouchButton(0, 0, 159, 119, btnHandler, btnHandler);
    TouchButton lb = TouchButton(0, 120, 159, 240, btnHandler, btnHandler);
    TouchButton rt = TouchButton(160, 0, 320, 119, btnHandler, btnHandler);
    TouchButton rb = TouchButton(160, 120, 320, 240, btnHandler, btnHandler);
    


  • @rop

    Very nice enhancement! With multi-touch, someone could also implement pinch, spread, and swipe gestures for zooming, scrolling, etc., just like smart phones. Great work Rop.



  • @world101

    Glad you like it. Note (as indicated above) that this touch sensor can only see touches as separate if they are not side-by-side, so that limits applications somewhat. But many things still possible, such as using one of the below screen buttons as modifier for touch on screen and many other things.



  • Rop - excellent work!
    Feedback: I noticed you used left, top, right, bottom for rectangular dimensions of the TouchButtons, a common scheme.
    But throughout the M5 library, rectangles seem to be described with left, top, width, height.
    The difference may to lead to frequent coding errors.



  • Actually, I have a bug report now.
    I am trying to integrate this with a gesture recognizer I'm working on. In most respects it seems significantly more reliable; I am getting much more accurate samples, allowing me to reliably recognize swipe-in without heuristics (hacks.)
    However, one issue is significant. The last sample in a while(M5.Touch.ispressed()) loop is always wrong.
    In this example program (using your fork of M5Core2):

    #include "M5Core2.h"
    
    void setup() {
      M5.begin();
      M5.Lcd.fillScreen(BLACK);
      M5.Lcd.setTextColor(WHITE, BLACK);
    }
    
    void loop() {
      bool draw = false;
      TouchPoint_t pt = { 0, 0 };
      M5.update();
      while(M5.Touch.ispressed()) {
        pt = M5.Touch.point[0];
        draw = true;
      }
      if(draw) {
        M5.Lcd.drawCentreString(String("    ") + pt.x + "," + pt.y + "    ", 160, 40, 4);
        draw = false;
      }
    }
    

    The screen will always display -1, -1.
    What I expect, when ispressed() is true, is to get a valid measurement.
    I see that the functions are decoupled in the libaray, and one might expect a race condition. But the result is always -1, -1.
    I think this is a typical application, so it would be great if the library could do its best to handle this situation.
    If I have to test every value for -1,-1 whenispressed() is true, I don't really need ispressed().