Encoder.h and SPI display conflict?

mikemontauk

Active member
[Hardware - Teensy LC w/ Bourns PEC12R-2120F-S0012 quadrature encoder; encoder to Teensy LC pins 21 and 12; filter circuit for the encoder is per the datasheet]

When I upload the code attached a clockwise turn of the encoder one increment (detent) yields -4 in the serial monitor, as expected from a quadrature encoder. However, when I uncomment the code for the SSD1306 display, the same experiment yields -3.

Expected output (with the display code commented out):
SerialMonitor20221223_good.jpg

Concerning output (with the display code included):
SerialMonitor20221223_bad.jpg

The scope seems to indicate the same signals on pins 21 and 12, regardless of the display code being excluded or included:

Display code included; 1 clockwise detent increments to -3, not -4:
SDS00009.png

Display code excluded; 1 clockwise detent increments to -4, as expected:
SDS00010.png

I first identified this issue on a custom MKL26Z64VFT4 board with the teensy bootloader, and then reproduced it on a genuine Teensy LC.

This issue does not occur if I use, for example, pins 20 and 21 for the encoder. Furthermore, using "#define ENCODER_OPTIMIZE_INTERRUPTS" did not seem to change anything.

In view of all of the evidence above, my assumption is that some part of the display code (such as the SPI, SSD1306, or GFX bits) is/are initializing pin 12 for MISO and maybe that is affecting the use of that pin for the encoder?

Any input would be greatly appreciated.
 

Attachments

  • EncoderIssue_20221224.ino
    1.5 KB · Views: 25
Most likely pin12 will be in a different state after the tft is started up.

Quick look at encoder library, the pins are init during the constructor. Which I personally don't like, but...


Wonder what would happen if you simply did:
Code:
  //  SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }
[COLOR="#FF0000"]  pinMode(12, INPUT_PULLUP);[/COLOR]
As that is what the encode library sets it up as...
 
Wonder what would happen if you simply did:
Code:
  //  SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if (!display.begin(SSD1306_SWITCHCAPVCC)) {
    Serial.println(F("SSD1306 allocation failed"));
    for (;;)
      ;  // Don't proceed, loop forever
  }
[COLOR="#FF0000"]  pinMode(12, INPUT_PULLUP);[/COLOR]
As that is what the encode library sets it up as...

Thank you for the suggestion, Kurt. I just tried it and unfortunately nothing changed.
 
The reference manual ( hopefully I am reading the correct one, KL26 sub-family ) seems to indicate you could regain use of the MISO pin by setting some bits in control register 2.

SPI0_C2 |= 1<<SPCO + 1<<BIDIROE;
 
The reference manual ( hopefully I am reading the correct one, KL26 sub-family ) seems to indicate you could regain use of the MISO pin by setting some bits in control register 2.

SPI0_C2 |= 1<<SPCO + 1<<BIDIROE;

Hi, I am grateful for your input, but the code you indicated is above my present level. Can it be entered directly into the Arduino IDE? in the setup?

*Edit 2022-12-24 @19:03 - I gave it a shot and it did not compile. I'm probably missing something obvious..
 
Sorry, (and there is still a chance I am looking at the incorrect reference doc) but it seems those bits are not defined for the user ( and it is SPC0 zero not O as I previously wrote )
but the register is defined. You can try SPI0_C2 |= 1 + 8;


Edit: It seems those bits are defined like this (and the compiler didn't like the addition operator):

SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
 
Last edited:
SPI0_C2 |= 1 + 8;
SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );

I tried both of these at the end of the setup. Both compiled, but unfortunately nothing changed.

I could use a different pin (not 12) but it would be a fairly involved change at the present stage of my project. I only posted the relevant part of my code, but its 1500 lines and the custom MKL26Z64VFT4 board has $150 of components on it. I also don't have any extra available pins, so to move encoder pin 12 to a different interrupt pin may mean sacrificing some other functionality. If I have to go that route I will, but I'm holding out a little longer for a Christmas miracle for pin 12.

Thanks again for your support, rcarr. Merry Christmas & happy holidays.
 
My only other idea is to follow the setting of the bits in the SPI command register with the pinMode setting as KurtE suggested.

Code:
SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
pinMode(12, INPUT_PULLUP);
 
Sorry, I don't have the encoder, but... SO can only throw darts.

Did you try combining both:
Code:
  SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
  pinMode(12, INPUT_PULLUP);

Adding delay help?
Code:
  SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
  pinMode(12, INPUT_PULLUP);
  delay(50); //
If it helps, try reducing the delay, like does it still work with delay(5); ...


Isolate: does just starting SPI library cause the issue?
Code:
#include <Encoder.h>
Encoder multiPEncoder(21, 12);  //pins A and B of PEC12R-2120F-S0012, respectively

long oldEncoderValue = 0;
long newEncoderValue = 0;

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_DC 14
#define OLED_CS 10
#define OLED_RESET 16
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS);  //MOSI pin 11; SCK pin 13;

void setup() {

  Serial.begin(9600);
  SPI.begin();
  multiPEncoder.write(0);
}
...

If so as an experiment what happens if you do:
Code:
void setup() {

  Serial.begin(9600);
  SPI.setMISO(8);  // the other valid MISO pin
  SPI.begin();
  multiPEncoder.write(0);
}
Does Pin 12 work back to normal?

If SPI.begin() does not cause the issue, then maybe we need to look at what if anything Adafruit library is doing with MISO...

Sorry again only darts.

And Merry Christmas
 
My only other idea is to follow the setting of the bits in the SPI command register with the pinMode setting as KurtE suggested.

Code:
SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
pinMode(12, INPUT_PULLUP);

I'm excited about the ideas you and Kurt provided. Thank you again. I will try them first thing tomorrow morning. For today, I need to finish my beef wellingtons before the family arrives. :)
 
Sorry, I don't have the encoder, but... SO can only throw darts.

Did you try combining both:
Code:
  SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
  pinMode(12, INPUT_PULLUP);

Adding delay help?
Code:
  SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
  pinMode(12, INPUT_PULLUP);
  delay(50); //
If it helps, try reducing the delay, like does it still work with delay(5); ...


Isolate: does just starting SPI library cause the issue?
Code:
#include <Encoder.h>
Encoder multiPEncoder(21, 12);  //pins A and B of PEC12R-2120F-S0012, respectively

long oldEncoderValue = 0;
long newEncoderValue = 0;

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_DC 14
#define OLED_CS 10
#define OLED_RESET 16
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS);  //MOSI pin 11; SCK pin 13;

void setup() {

  Serial.begin(9600);
  SPI.begin();
  multiPEncoder.write(0);
}
...

If so as an experiment what happens if you do:
Code:
void setup() {

  Serial.begin(9600);
  SPI.setMISO(8);  // the other valid MISO pin
  SPI.begin();
  multiPEncoder.write(0);
}
Does Pin 12 work back to normal?

If SPI.begin() does not cause the issue, then maybe we need to look at what if anything Adafruit library is doing with MISO...

Sorry again only darts.

And Merry Christmas

Thanks Kurt, I will give this a try tomorrow morning. For today I am tied up cooking a holiday dinner. Cheers!
 
My only other idea is to follow the setting of the bits in the SPI command register with the pinMode setting as KurtE suggested.

Code:
SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
pinMode(12, INPUT_PULLUP);

Unfortunately, this did not resolve it. Thanks for your suggestion.
 
Did you try combining both:

Code:
  SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
  pinMode(12, INPUT_PULLUP);

Unfortunately, this did not work.

Adding delay help?
Code:
  SPI0_C2 |= ( 1<<SPI_C2_SPC0 | 1<<SPI_C2_BIDIROE );
  pinMode(12, INPUT_PULLUP);
  delay(50); //

The delay did not change the output.

Isolate: does just starting SPI library cause the issue?
Code:
#include <Encoder.h>
Encoder multiPEncoder(21, 12);  //pins A and B of PEC12R-2120F-S0012, respectively

long oldEncoderValue = 0;
long newEncoderValue = 0;

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_DC 14
#define OLED_CS 10
#define OLED_RESET 16
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS);  //MOSI pin 11; SCK pin 13;

void setup() {

  Serial.begin(9600);
  SPI.begin();
  multiPEncoder.write(0);
}
...

No, I only see the issue with the first clockwise turn of the encoder by 1 detent.

If so as an experiment what happens if you do:
Code:
void setup() {

  Serial.begin(9600);
  SPI.setMISO(8);  // the other valid MISO pin
  SPI.begin();
  multiPEncoder.write(0);
}

I tried this anyway, but I did not observe any change to the output.

Thanks again for your suggestions.
 
I can reproduce this behaviour. As mentioned by others above, it looks like your display libraries are doing things to pin 12.

Since the encoder library is setting up its pins in the constructor the setup might get overridden by the graphic libs. To make sure that the encoder setup is done after the setup of the graphic libs you can simply construct your encoder on the heap which will give you full control over when it will be initialized:

The following changes make it work for me:
Code:
#include "Arduino.h"
#include "Encoder.h"

Encoder* multiPEncoder;  // <======= change to pointer

long oldEncoderValue = 0;
long newEncoderValue = 0;

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_DC 14
#define OLED_CS 10
#define OLED_RESET 16
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS);  //MOSI pin 11; SCK pin 13;


void setup() {

  Serial.begin(9600);
  Serial.println("start");

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
     if (!display.begin(SSD1306_SWITCHCAPVCC)) {
       Serial.println(F("SSD1306 allocation failed"));
       for (;;)
         ;  // Don't proceed, loop forever
     }

     multiPEncoder = new Encoder(21, 12);  // <========= constructor is called NOW (after the graphic libs)

     // testdrawrect();  // Draw logo bitmap image

      multiPEncoder->write(0); //<=========== need to use -> since multiPEncoder is a pointer now
}

void loop() {
  newEncoderValue = multiPEncoder->read();    //reads encoder for change  <=========== need to use -> since multiPEncoder is a pointer now
  if (newEncoderValue != oldEncoderValue) {  //executes for turn of encoder dial
    Serial.print("newEncoderValue: ");
    Serial.println(newEncoderValue);
    oldEncoderValue = newEncoderValue;
  }
}
/*
void testdrawrect(void) {
  display.clearDisplay();

  for (int16_t i = 0; i < display.height() / 2; i += 2) {
    display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1306_WHITE);
    display.display();  // Update screen with each newly-drawn rectangle
    delay(1);
  }
}
*/

Tested on a T-LC, no graphics connected
 
Solution

I can reproduce this behaviour. As mentioned by others above, it looks like your display libraries are doing things to pin 12.

Since the encoder library is setting up its pins in the constructor the setup might get overridden by the graphic libs. To make sure that the encoder setup is done after the setup of the graphic libs you can simply construct your encoder on the heap which will give you full control over when it will be initialized:

The following changes make it work for me:
Code:
#include "Arduino.h"
#include "Encoder.h"

Encoder* multiPEncoder;  // <======= change to pointer

long oldEncoderValue = 0;
long newEncoderValue = 0;

#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128  // OLED display width, in pixels
#define SCREEN_HEIGHT 64  // OLED display height, in pixels
#define OLED_DC 14
#define OLED_CS 10
#define OLED_RESET 16
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &SPI, OLED_DC, OLED_RESET, OLED_CS);  //MOSI pin 11; SCK pin 13;


void setup() {

  Serial.begin(9600);
  Serial.println("start");

    // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
     if (!display.begin(SSD1306_SWITCHCAPVCC)) {
       Serial.println(F("SSD1306 allocation failed"));
       for (;;)
         ;  // Don't proceed, loop forever
     }

     multiPEncoder = new Encoder(21, 12);  // <========= constructor is called NOW (after the graphic libs)

     // testdrawrect();  // Draw logo bitmap image

      multiPEncoder->write(0); //<=========== need to use -> since multiPEncoder is a pointer now
}

void loop() {
  newEncoderValue = multiPEncoder->read();    //reads encoder for change  <=========== need to use -> since multiPEncoder is a pointer now
  if (newEncoderValue != oldEncoderValue) {  //executes for turn of encoder dial
    Serial.print("newEncoderValue: ");
    Serial.println(newEncoderValue);
    oldEncoderValue = newEncoderValue;
  }
}
/*
void testdrawrect(void) {
  display.clearDisplay();

  for (int16_t i = 0; i < display.height() / 2; i += 2) {
    display.drawRect(i, i, display.width() - 2 * i, display.height() - 2 * i, SSD1306_WHITE);
    display.display();  // Update screen with each newly-drawn rectangle
    delay(1);
  }
}
*/

Tested on a T-LC, no graphics connected

It's working! I am immensely grateful for your help. I believe I generally followed your explanation to boot. Happy holidays!
 
Back
Top