Hello everybody,
I've been working on a project which is based on a Teensy 4.0 and that uses a linear encoder (AMS AS5311) with a magnet strip together with a TFT to show the actual value (ST7789). The Adafruit 1.14" TFT is used in Hardware SPI mode.
The setup is as follows:
The linear encoder chip moves along the magnet strip and every 20ms the current position value is shown on the display. Besides that a button is attached to set the zero position.
For better testing I've build a jig with a digital caliper where the linear encoder chip is mounted on the moving jaw.
I use the hardware quadrature library from Mike https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library and the TFT library ST7735_t3
https://github.com/PaulStoffregen/ST7735_t3. This lib offers the possibility to work with Frame buffering/DMA.
As you may see in the screenshot the code works pretty good. But now comes the problem. When is use Framebuffering/DMA for the display the encoder totally freaks out. The counter becomes wrong and after 2-3 seconds evrything freezes.
If framebuffering/DMA isn't used, everything works fine except that the display flickers. I also tried to use Pauls encoder library https://www.pjrc.com/teensy/td_libs_Encoder.html instead of the hardware encoder lib, but the problem remains the same.
Does anyone know what I'm doing wrong?
Thanks for your help.
Michael
This is my code so far:
Main.cpp
Screen.h
Screen.cpp
Button.h
Button.cpp
I've been working on a project which is based on a Teensy 4.0 and that uses a linear encoder (AMS AS5311) with a magnet strip together with a TFT to show the actual value (ST7789). The Adafruit 1.14" TFT is used in Hardware SPI mode.
The setup is as follows:
The linear encoder chip moves along the magnet strip and every 20ms the current position value is shown on the display. Besides that a button is attached to set the zero position.
For better testing I've build a jig with a digital caliper where the linear encoder chip is mounted on the moving jaw.
I use the hardware quadrature library from Mike https://github.com/mjs513/Teensy-4.x-Quad-Encoder-Library and the TFT library ST7735_t3
https://github.com/PaulStoffregen/ST7735_t3. This lib offers the possibility to work with Frame buffering/DMA.
As you may see in the screenshot the code works pretty good. But now comes the problem. When is use Framebuffering/DMA for the display the encoder totally freaks out. The counter becomes wrong and after 2-3 seconds evrything freezes.
If framebuffering/DMA isn't used, everything works fine except that the display flickers. I also tried to use Pauls encoder library https://www.pjrc.com/teensy/td_libs_Encoder.html instead of the hardware encoder lib, but the problem remains the same.
Does anyone know what I'm doing wrong?
Thanks for your help.
Michael
This is my code so far:
Main.cpp
Code:
#include <Arduino.h>
#include "Button.h"
#include "Screen.h"
#include <EEPROM.h>
#include "QuadEncoder.h"
void ButtonPressed(); // Called when the ZERO button was pressed.
// == Variables & Constants ===========
QuadEncoder posEnc(1, 1, 0); // PinA and PinB is swapped so the value becomes positiv when moving to the right
Button btnZero(3, ButtonPressed); // Use Pin 3 as Interrupt and handle button press in ButtonPressed
Screen scr; // TFT Display
elapsedMillis valueChangeTime; // update position in certain intervals only
int32_t posCounter = 0; // Encoder-Position-Counter
float position = 0.0; // Position in mm
static const uint8_t VALUE_UPDATE_TIME = 20; // Milliseconds
static const uint8_t MAGNET_WIDTH = 2; // Magnet pitch 2mm
static const uint16_t MAGNET_RESOLUTION = 1024; // 1024 pulses
/* ===============================
/ Configuration of all needed
/ components
/ ===============================*/
void setup()
{
Serial.begin(115200);
while (!Serial)
; // Wait until serial is ready
Serial.println("Ready..");
scr.Begin(); // Start TFT display
posEnc.setInitConfig(); //Loads default configuration for the encoder channel
posEnc.init(); //Initializes the encoder channel
}
/* ===============================
/ Endless loop
/
/ ===============================*/
void loop()
{
if (valueChangeTime >= VALUE_UPDATE_TIME)
{
posCounter = posEnc.read();
// Calc position in millimeters
position = MAGNET_WIDTH / (float)MAGNET_RESOLUTION * posCounter;
Serial.println(posCounter);
// Show value on display
scr.ShowValue(position);
valueChangeTime = 0; // Reset interval
}
}
/* ===============================
/ Handle button press.
/ Sets counter back to zero
/ ===============================*/
void ButtonPressed()
{
// Zero position
posEnc.write(0);
}
Screen.h
Code:
#ifndef _SCREEN_h
#define _SCREEN_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "arduino.h"
#else
#include "WProgram.h"
#endif
class Screen
{
public:
void Begin();
void ShowValue(float distance);
private:
void DrawUnit();
void CalcMeasValueTextSize();
void SetMeasValueFont();
};
#endif
Screen.cpp
Code:
#include "Screen.h"
#include "ST7789_t3.h"
#include "st7735_t3_font_Arial.h"
#include <SPI.h>
// Use Hardware SPI on Teensy 4.0
#define TFT_T4_DC 6
#define TFT_T4_CS 10
#define TFT_T4_RST 8
// -- Colors
#define COLOR_BACKGROUND 0x0841 // Grey
#define COLOR_SEPARATOR 0x73AE // Light Blue
#define COLOR_UNIT 0x73AE
#define COLOR_MEAS_VALUE 0xFFFF // White
// ---
const String UNIT = "mm";
const uint16_t DISPLAY_HEIGHT = 135; // 1.14" Display
const uint16_t DISPLAY_WIDTH = 240;
bool isMeasValFont = false;
uint16_t measValTextWidth;
uint16_t measValTextHeight;
String lastMeasValue = "";
ST7789_t3 tft = ST7789_t3(TFT_T4_CS, TFT_T4_DC, TFT_T4_RST);
/**
* Setup TFT display
*/
void Screen::Begin()
{
tft.init(DISPLAY_HEIGHT, DISPLAY_WIDTH); // Set dimensions
//tft.useFrameBuffer(true); // use buffering with DMA for speed reasons
tft.fillScreen(COLOR_BACKGROUND); // Set background to black
tft.setRotation(3); // Use Landscape orientation
tft.setTextWrap(false);
tft.setTextDatum(TR_DATUM); // set Reference point for text drawings to TOP RIGHT (default is TOP LEFT)
// draw horizontal separator
tft.drawFastHLine(0, DISPLAY_HEIGHT * 2 / 3, DISPLAY_WIDTH, COLOR_SEPARATOR);
// Show initial values
DrawUnit();
ShowValue(0.0); // Must be done after DrawUnit, cause then we do not need to set Font and color each time (faster)
}
/**
* Show new measurement value on the TFT
*/
void Screen::ShowValue(float distance)
{
// Buffer for distance as string. max. distance = 6 chars + NULL at the end
char strBuf[7] = "";
// convert float to string with 2 decimal digits, e.g. "12.34"
dtostrf(distance, 6, 2, strBuf);
if (!isMeasValFont)
{
// Set correct font
SetMeasValueFont();
CalcMeasValueTextSize();
}
// Only draw if the given value differs from new value
if (lastMeasValue != strBuf)
{
// "erase" last drawing
tft.fillRect(0, 15, DISPLAY_WIDTH, DISPLAY_HEIGHT * 2 / 3 - 30, COLOR_BACKGROUND);
// Show new value
tft.drawString(strBuf, DISPLAY_WIDTH - 40, (DISPLAY_HEIGHT * 2 / 3 - measValTextHeight) / 2);
//tft.updateScreenAsync();
lastMeasValue = strBuf;
}
}
/**
* Draw unit indicator, e.g. mm
*/
void Screen::DrawUnit()
{
tft.setTextColor(COLOR_UNIT, COLOR_BACKGROUND);
tft.setFont(Arial_24);
isMeasValFont = false;
// calculate position after setting font
int16_t x, y;
uint16_t txtHeight, txtWidth;
tft.getTextBounds(UNIT, 0, 0, &x, &y, &txtWidth, &txtHeight);
tft.drawString(UNIT, (DISPLAY_WIDTH + txtWidth) / 2, DISPLAY_HEIGHT * 5 / 6 - txtHeight / 2);
//tft.updateScreenAsync();
}
/**
* Calculate text metrics for msx. possible measurement value
*/
void Screen::CalcMeasValueTextSize()
{
if (!isMeasValFont)
{
SetMeasValueFont();
}
// calculate position after setting correct font
int16_t x, y;
char tmp[7] = "999.99"; // for ease of use we stay with the metrics of the max. (widest) possible number
tft.getTextBounds(tmp, 0, 0, &x, &y, &measValTextWidth, &measValTextHeight);
}
/**
* Set color and font for measurement value
*/
void Screen::SetMeasValueFont()
{
tft.setFont(Arial_40);
tft.setTextColor(COLOR_MEAS_VALUE, COLOR_BACKGROUND);
isMeasValFont = true;
}
Button.h
Code:
#ifndef _BUTTON_h
#define _BUTTON_h
#if defined(ARDUINO) && ARDUINO >= 100
#include "arduino.h"
#else
#include "WProgram.h"
#endif
class Button
{
public:
Button(); // Constructor (If no interrupt is used, PIN must be a constant value, see NO_INTERRUPT_BUTTON_PIN)
Button(uint8_t interruptPin, void (*btnPressedCallback)());
bool IsPressed(); // Indicates if button was pressed
void IsrButtonPressed(); // ONLY FOR INTERNAL USE.
private:
static const uint8_t NO_INTERRUPT_BUTTON_PIN = 3; // Must be const otherwise digitalReadFast isn't fast
uint8_t _interruptButtonPin; // Pin where button is connected to when interrupt is used
uint8_t _buttonState = HIGH; // the current reading from the input pin. At beginning HIGH when pin is pulled up
uint8_t _lastButtonState = HIGH; // the previous reading from the input pin. At beginning HIGH when pin is pulled up
uint32_t _lastDebounceTime = 0; // the last time the output pin was toggled
uint32_t _debounceDelay = 20; // the debounce time
uint32_t _lastIsrCall; // time when ISR was lastly called
void (*BtnPressed)(); // Callback to handle button pressed
};
#endif
Button.cpp
Code:
Button *ptrButton;
/*
/ Global interrupt handler (must be global and outside of the class)
*/
void GlobalInterruptHandler()
{
// reroute to function inside of the class
ptrButton->IsrButtonPressed();
}
#include "Button.h"
/* --------------------------------------------------------------
Constructor for use without interrupts
The pin for the button must be const, otherwise digitalReadFast
isn't faster as normal digitalRead
-------------------------------------------------------------- */
Button::Button()
{
pinMode(NO_INTERRUPT_BUTTON_PIN, INPUT_PULLUP);
}
/* --------------------------------------------------------------
Constructor for use with interrupts
-------------------------------------------------------------- */
Button::Button(uint8_t interruptPin, void (*btnPressedCallback)())
{
ptrButton = this;
_lastIsrCall = 0;
_interruptButtonPin = interruptPin;
pinMode(_interruptButtonPin, INPUT_PULLUP);
BtnPressed = btnPressedCallback;
//Pull pin high and use it as input
pinMode(interruptPin, INPUT_PULLUP);
//Attach global interupt callback
attachInterrupt(interruptPin, GlobalInterruptHandler, FALLING);
}
/* --------------------------------------------------------------
Indicates if the button was pressed
Button is debounced via software
-------------------------------------------------------------- */
bool Button::IsPressed()
{
bool isPressed = false;
uint8_t reading = digitalReadFast(NO_INTERRUPT_BUTTON_PIN);
// check to see if you just pressed the button
// (i.e. the input went from HIGH to LOW), and you've waited
// long enough since the last press to ignore any noise:
// If the switch changed, due to noise or pressing:
if (reading != _lastButtonState)
{
// reset the debouncing timer
_lastDebounceTime = millis();
}
if ((millis() - _lastDebounceTime) > _debounceDelay)
{
// whatever the reading is, it's been there for longer
// than the debounce delay, so take it as the actual current state
// if the button state has changed:
if (reading != _buttonState)
{
_buttonState = reading;
// Pin is pulled up, so LOW means pressed
if (_buttonState == LOW)
{
isPressed = true;
}
}
}
// save the reading.
_lastButtonState = reading;
return isPressed;
}
/*
/ Handle button pressed and call thr user defined callback function
*/
void Button::IsrButtonPressed()
{
// Debounce button --> when button is pressed signal goes low
// debouncing : React directly to the first falling edge and again only after a waiting period of
if (millis() - _lastIsrCall < 20)
{
return;
}
_lastIsrCall = millis();
BtnPressed(); // call user defined callback
}