SPI Conflicts when using an interrupt

Status
Not open for further replies.

Davidelvig

Well-known member
This code simplifies the problem as much as I can.
It need to use two SPI peripherals:

  • Teensy Touch Display: here
  • HP Pressure Sensor: here

While it's unlikely you have the latter "in stock"...
...can anyone identify where I'm going wrong using these libraries
#include <SPI.h>
#include <ILI9341_t3.h>
#include <XPT2046_Touchscreen.h>​

Paul, you had a post here instructing to use SPI.usingInterrupt() before attachInterrupt().

I don't see any of the libraries using SPI.usingInterrupt(), though the touch library uses attachInterrupt().

So I tried adding SPI.usingInterrupt() in this test code... to no avail.

Here's the code:
Code:
#include <SPI.h>
#include <ILI9341_t3.h>
#include <XPT2046_Touchscreen.h>

// pressure sensor uses SPISettings(800000, MSBFIRST, SPI_MODE3)    below
// TFT uses             SPISettings(clock, MSBFIRST, SPI_MODE0)     in ILI9341_t3.h             where clock is 2000000 for reads and 30000000 for writes
// touchscreen uses     SPISettings(2000000, MSBFIRST, SPI_MODE0)   in XPT2046_Touchscreen.cpp

#define TOUCH_CS_PIN        8
#define TOUCH_IRQ_PIN       2
#define TFT_CS_PIN          10
#define TFT_DC_PIN          9
#define PRESSURE_CS_PIN     7

ILI9341_t3 tft = ILI9341_t3(TFT_CS_PIN, TFT_DC_PIN);

//#define USE_TOUCH_IRQ     // <== uncomment this line to demonstrate failure - where pressure input is intermittently scrambled
#ifdef USE_TOUCH_IRQ
    XPT2046_Touchscreen ts(TOUCH_CS_PIN, TOUCH_IRQ_PIN);
#else // USE_TOUCH_IRQ
    XPT2046_Touchscreen ts(TOUCH_CS_PIN);
#endif // USE_TOUCH_IRQ

void setup() {
    Serial.begin(9600);
    while (!Serial && (millis() <= 1000));

    pressureBegin(PRESSURE_CS_PIN); 
        
    tft.begin();
    tft.setRotation( 0 );
    Serial.printf("TFT started with:\nTFT_CS_PIN: %2d\nTFT_DC_PIN: %2d\n", TFT_CS_PIN, TFT_DC_PIN); 

    if (ts.begin()) { 
        ts.setRotation( 2 );
        Serial.printf("Touchscreen started with:\nTOUCH_CS_PIN: %2d\n", TOUCH_CS_PIN); 
        
        
#ifdef USE_TOUCH_IRQ                        // based on https://forum.pjrc.com/threads/31634-ILI9341-and-XPT2046-for-Teensy-Touchscreen-320x240-display/page3?highlight=usingInterrupt%28
        SPI.usingInterrupt(TOUCH_IRQ_PIN);  // <== where should this line go?  I've tried it here, and in between lines 39 & 40 of XPT2046_Touchscreen.cpp
        Serial.printf("Touchscreen using IRQ on:\nTOUCH_IRQ_PIN: %2d\n", TOUCH_IRQ_PIN); 
#endif // USE_TOUCH_IRQ


    } 
    else { 
        Serial.println("Unable to start touchscreen.");
    }
}

TS_Point    p;
int         x = 0, lastX = 0, 
            y = 0, lastY = 0, 
            pressure = 0, lastPressure = 0;
void loop() {
    pressure = readPressure();
    if (ts.tirqTouched()) {     // Note this should always be true if no irq defined
        if (ts.touched()) {
            getTouch();
        } 
    }
    if ((x != lastX) || 
        (y != lastY) || 
        (abs(pressure - lastPressure) > 50)) { // the pressure transducer will vary by as much as 50 from read to read.  this last condition minimizes reporting jitter
        drawScreen();
        Serial.printf("readPressure(): %d\t", pressure);
        Serial.printf("getTouch(): %d/%d\t", x, y);
        Serial.printf("drawScreen(): %d/%d\t%d\n", x, y, pressure);
        lastPressure = pressure;
        lastX = x;
        lastY = y;
    }
}

void pressureBegin(int csPin) {
    pinMode (csPin, OUTPUT);
    digitalWrite(csPin, HIGH);
    SPI.begin();
}

uint16_t readPressure(void) { // Using Honeywell's ABPMANN060KGSA3 (60mBar, SPI, 3v)
    uint16_t _p;
    byte a, b, c, d, s;
   
    SPI.beginTransaction(SPISettings(800000, MSBFIRST, SPI_MODE3));
          digitalWrite(PRESSURE_CS_PIN, LOW); 
                a = SPI.transfer(0);
                b = SPI.transfer(0);
                c = SPI.transfer(0);
                d = SPI.transfer(0);
                s = a >> 6;
                if (s > 0) {
                    Serial.printf("Error: pressure transducer returns %d (binary %d%d) and should return 0.\n",
                                    s, s >> 1, s & 0b00000001);
                    Serial.printf("a: %d\t(%d : %d)\nb: %d\nc: %d\nd: %d\n", a, a>>6, a & 0b00111111, b, c, d);
                    delay(500);
                }
                _p = ((a & 0b00111111) << 6) + b; // 6 bits of byte 1, plus all bits of byte 2.                
          digitalWrite(PRESSURE_CS_PIN, HIGH);
    SPI.endTransaction();
    return (_p);
}

// calibration for my particular touchscreen
#define MIN_TS_X            350
#define MIN_TS_Y            310
#define MAX_TS_X            3820
#define MAX_TS_Y            3750
#define TFT_WIDTH           240
#define TFT_HEIGHT          320

void getTouch(void) {
    p = ts.getPoint();
    x = map(constrainInt(p.x, MIN_TS_X, MAX_TS_X),
            MIN_TS_X, 
            MAX_TS_X, 
            0, 
            TFT_WIDTH);
    y = map(constrainInt(p.y, MIN_TS_Y, MAX_TS_Y), 
            MIN_TS_Y, 
            MAX_TS_Y, 
            0, 
            TFT_HEIGHT);
}

int constrainInt(int val, int low, int high) {
      if      (val < low)  { return(low);  }
      else if (val > high) { return(high); }
      else                 { return(val);  }
}

#define COLOR_BLACK         0x0000
#define COLOR_WHITE         0xFFFF

void drawScreen(void) {
    tft.fillScreen(COLOR_BLACK);
    tft.setTextColor(COLOR_WHITE, COLOR_BLACK);
    tft.setTextSize(2);
    tft.setCursor(x, y);
    tft.print(x);
    tft.print("/");
    tft.print(y);
    tft.print("/");
    tft.print(pressure);
}
 
The attachInterrupt() for <XPT2046_Touchscreen.h> is on a device feedback pin, outside the SPI functionality.

It is used to identify when the touch hardware identifies a touch, giving notice to the Teensy to follow up when touch input is desired, rather than continuously polling the touch hardware over SPI when no touch may be present.

User code if using that interrupt feature can then make explicit SPI requests for touch data as desired, between other SPI operations. Between meaning the CS for other devices on the bus must be held HIGH so they do not interfere.

This thread may have relevant info: pjrc.com/threads/58398-A-microSD-puzzle
 
Thanks, @defragster. I've read through your references and links from there. Intuitively, it makes sense to alert SPI that an interrupt may happen - and hook that interrupt into SPI before the interrupt is attached to its intended code.
I've modified the XPT2046_touchscreen.cpp code, as follow (one line added).
Code:
bool XPT2046_Touchscreen::begin()
{
	SPI.begin();
	pinMode(csPin, OUTPUT);
	digitalWrite(csPin, HIGH);
	if (255 != tirqPin) {
		pinMode( tirqPin, INPUT );
        SPI.usingInterrupt(tirqPin); // <== added this line
        attachInterrupt(digitalPinToInterrupt(tirqPin), isrPin, FALLING);
		isrPinptr = this;
	}
	return true;
}

Unfortunately with no positive effect. The pressure reading of SPI is still corrupted every few samples.

I would note that the XPT2046_Touchscreen object instantiates early - statically, before setup(). So unless I dynamically allocate an XPT2046_Touchscreen object in setup, my options are limited for invoking SPI.usingInterrupt(...); before attachInterrupt(...);

Perhaps I could remove the attachInterrupt(...) from the library, and invoke it in the setup() method of the sketch?

Any other clues for a better place to add SPI.usingInterrupt(tirqPin) - or a different approach to keep the interrupt-enabled touchscreen from stomping on my other SPI use?

By the way, I've not tested for a floating chip-select pin, since I'm explicitly calling the pressure CS pin high with:
Code:
void pressureBegin(int csPin) {
    pinMode (csPin, OUTPUT);
    digitalWrite(csPin, HIGH);
    SPI.begin();
}
Thanks!
 
Not sure what SPI.usingInterrupt() relates to - but pretty sure it isn't the tirqPin in the touch driver as it is just a signal indicator using a pin independent of anything else.

It isn't part of the SPI interface during any SPI transfer.

As implemented it just gates actual SPI calls - you can call the touch test code as often as desired for proper response times - and if the interrupt hasn't fired the call returns without even doing an SPI read of the touch controller.

The tIRQ pin usage is optional in which case any call ( except when the driver rejects as too fast IIRC ) will issue SPI transactions to poll the touch controller to determine if there is touch pressure present.

Not sure what is seen as the problem and what is behind it - assumption is it relates to the "HP Pressure Sensor" code - as I'm not seeing it as related to the touch driver calls - unless they are somehow being called during other SPI operations - like when SPI DMA updates to the display are in progress. In which case there is a use for the Touch interrupt detect that will return TRUE without immediately reading the Touch points - allowing the user code to stop the DMA updates or work between them before issuing the TouchRead.
 
It is unclear from me, what your issue is or may be. As at least to me there are a lot of unknowns.
So sorry in advance for this throwing darts answer, but maybe one of the darts will help you hit the target.

As PJRC actually sells the ILI9341 display which uses the XPT2046 touch controller, needless to say it normally works.

I don't think the usingInterrupt call is what you are needing. Again as the Arduino documents mention: https://www.arduino.cc/en/Reference/SPIusingInterrupt
Its use is for calling SPI while processing your interrupt.

In the case of the XPT2046, as @defragster mentions, the attachInterrupt (which is optional), the processing of it is pretty simple:
Code:
void isrPin( void )
{
	XPT2046_Touchscreen *o = isrPinptr;
	o->isrWake = true;
}
It simply remembers that an interrupt happened, by setting a flag isrWake...

That is it...

Next, the attachInterrupt happens when you call the begin method.
Its constructor does nothing other than to save away the two parameters you passed in (cspin, tirq).

Floating CS pins? Again I have not seen your hardware setup... Do you have external Pull up resistors on your CS pins? If not AND you have multiple SPI devices on the same SPI buss, then you need to put code in the beginning that sets all of them up to be HIGH before you start initializing some of your devices. Else potentially the ILI9341 code may see some of your messages meant for the touch controller. Likewise if you have other SPI devices setup...

Again maybe it will help to see a picture of your actual setup. Along with additional information, like which Teensy? How is it Wired? Which computer with which version of Arduino and Teensyduino?

Again saying SPI is corrupted every few samples does not tell me much? How is it corrupted? Simply unexpected values returned? Or do you have it hooked up to scope or Logic Analyzer?

Again depending on things like wiring, and which of the devices may have Pull Up resistors or not, wire lengths, Breadboard? Wires not making good contact or pins not soldered to Teensy, or ...

Or maybe you have a device plugged in to your SPI that does not play well with others (like screws up MISO)...

Again seeing how things are wired up, and the like, might help some of us to maybe have an idea what is happening.

Sorry not much help
 
Thanks, both of you for your quick responses.

My investigation around SPI.usingInterrupt started with @Paul's comment"
If you do any SPI stuff inside the interrupt, don't forget to call SPI.usingInterrupt() before attachInterrupt().
in this thread.

I can use the touch library without interrupts without error, so I have some time.
I'll try to clarify my questions and get a more thorough problem description in the meantime.

Thanks again!
 
Just noticing what Teensy is in use isn't apparent?

Thanks, both of you for your quick responses.

My investigation around SPI.usingInterrupt started with @Paul's comment"

If you do any SPI stuff inside the interrupt, don't forget to call SPI.usingInterrupt() before attachInterrupt().
in this thread.

I can use the touch library without interrupts without error, so I have some time.
I'll try to clarify my questions and get a more thorough problem description in the meantime.

Thanks again!

Paul's comment makes sense - 'when within interrupt code' - calling out to SPI code could be trouble - and wouldn't generally be ideal - but if done needs special attention. That doesn't apply to the Touch Device in any way where KurtE points to the simple code I wrote to record the touch reporting interrupt.

The touch interrupts do occur repeatedly on touch or in fact each time a read command is sent to the touch controller - which is great for spastic recursion and led to the code as written to not get caught in that trap. And without calling the touch read code, or touching the display nothing should be triggering that TirqPin. That would be important to know - but the triggering of that interrupt should have minimal effect and hasn't caused trouble on T_3.x's as tested … T4 less tested and possible anomaly there.
 
Teensy 3.2.

And your message triggered a thought. The erroneous, intermittent pressure readings happen only when the touch screen is instantiated in interrupt mode, but they happen whether the screen is touched or untouched.

I’ve got some more thinking to do.

Thanks again
 
Status
Not open for further replies.
Back
Top