please someone unmuddle me. trying to get a rotary encoder working - Page 1
Forum section: electronics / general

author: drspastic
author_rank: Contributor
timestamp: January 16, 2025, 06:23:12 pm
content: hi, im not very good at this arduino business. i managed to get the display attached and working, setup variables.now im trying to graft in some code from:https://esp32io.com/tutorials/esp32-rotary-encoderi got so far but i dont know where to put the rest and what loop should go where. the rotary controller ultimately should change the variable vfoaCode:#include <Adafruit_GFX.h>#include <Adafruit_ST7735.h>#include <ezButton.h> // the library to use for SW pin on encoder#define CLK_PIN 34 // ESP32 pin GPIO25 connected to the rotary encoder's CLK pin#define DT_PIN 35 // ESP32 pin GPIO26 connected to the rotary encoder's DT pin#define SW_PIN 32 // ESP32 pin GPIO27 connected to the rotary encoder's SW pin#define DIRECTION_CW 0 // clockwise direction rotary#define DIRECTION_CCW 1 // counter-clockwise direction rotary#define TFT_CS 5#define TFT_RST 2#define TFT_DC 0#define vfoa 0740.00000#define vfob 2400.00000#define TFT_BL 22 // LED back-lightint counter = 0; //rotaryint direction = DIRECTION_CW; //rotaryint CLK_state; //rotaryint prev_CLK_state; //rotaryezButton button(SW_PIN); // create ezButton object that attach to pin 32;Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // hardware SPIvoid setup(void) {tft.initR(INITR_BLACKTAB); // Init ST7735S chip, black tab// configure encoder pins as inputspinMode(CLK_PIN, INPUT);pinMode(DT_PIN, INPUT);button.setDebounceTime(50); // set debounce time to 50 milliseconds// read the initial state of the rotary encoder's CLK pinprev_CLK_state = digitalRead(CLK_PIN);pinMode(TFT_BL, OUTPUT);digitalWrite(TFT_BL, HIGH);tft.setRotation(1);tft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_GREEN);tft.setTextSize(2);tft.setCursor(0, 20);tft.println(vfoa);tft.println("mhz");tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 60);tft.println(vfob);tft.println("mhz");}void loop() { }im having to work this out bit at a time learning as i go. any suggestions welcome, except ask ai because its always wrong which is why im trying to do it myself.

author: drspastic
author_rank: Contributor
timestamp: January 16, 2025, 06:25:52 pm
content: ps. im concerned about interupts and reaction time because i will need the finished unit to switch values rather fast and have the rotary knob live at all times

author: MarkF
author_rank: Super Contributor
timestamp: January 16, 2025, 09:19:48 pm
content: In the past, my approach to processing rotary encoder events falls in to two general sections.Section 1:I setup a fixed interrupt at 200 Hz or greater. (You can optimize the rate after everything is working.) When this interrupt occurs, I check if the encoder has changed. If changed, I increment or decrement a global variable that counts the changes. The interrupt also checks for switches and setting global flags. (Do switch debounce here also.) This minimizes the execution time within the interrupt only checking rotation counts and switches.I DO NOT do interrupt-on-change to detect encoder changes. The interrupts can quickly be overloaded by switch bouncing in the encoder. Noise on the encoder lines will keep the processor in the interrupt routine and prevent any other processing. (Others will argue this point but I consider interrupt-on-change a bad design in this case.)Section 2:In your main execution loop, I check if the global counter is non-zero. If non-zero, add the global count to the value being controlled. Then zero the global count and wait for more changes from the interrupt. Also, do similar processing for switch activation. This is also where I draw any displays and any other processing that needs to be done. This main loop does not have to be fast because the encoder counter is being handled at the fast interrupt rate. You just use the cumulative count at the beginning of each time through the loop.A note here:You can also add rotation acceleration in the main loop by checking if the cumulative count is greater than a triggering value and then increasing the count.Example main loop code to do encoder acceleration:int cCount=0; // global cumulative count from interruptint speed=0; // sample value being controlledloop(){// check for encoder changeif (cCount != 0) {// perform accelerationif (cCount > 2) cCount += 5;if (cCount < -2) cCount -= 5;// update speed with encoder changespeed += cCount;// restart count from interruptcCount = 0;}// TODO: more loop processing}

author: rhodges
author_rank: Frequent Contributor
timestamp: January 16, 2025, 11:22:50 pm
content: Quote from: MarkF on January 16, 2025, 09:19:48 pmBe careful of a race condition. A guard flag set and cleared around this code can alert the interrupt handler to skip the check.

author: MarkF
author_rank: Super Contributor
timestamp: January 17, 2025, 12:30:04 am
content: Quote from: rhodges on January 16, 2025, 11:22:50 pmTwo points:1) The interrupt does not check any thing and doesn't need to. It just increments or decrements the global count.2) In my actual main loop, reading the global variable is a little more complex.(If you minimize the execution time between checking the count and clearing the count, it don't think even this is required.)temp = cCount;cCount = 0;// usetempinstead ofcCountto avoid any changes during processingif (temp != 0) {. . .}This will minimize the time between reading and clearing the count from the interrupt.Also, any race condition really doesn't matter.The chance of getting an interrupt during the small time between checking and clearing the count is minimal.It would be rare and it really does not matter if you miss an encoder count or not.The user will not notice any rare missing counts.In practice, the bigger issue is switch bounce on the encoder pins.

author: SiliconWizard
author_rank: Super Contributor
timestamp: January 17, 2025, 12:34:53 am
content: Apart from what has been said, what's the ESP32 you use here? Some models have a PCNT peripheral that makes it easier/more efficient to handle rotary encoders.You can have a look at this:https://github.com/madhephaestus/ESP32Encoder

author: MarkF
author_rank: Super Contributor
timestamp: January 17, 2025, 12:38:10 am
content: Here's my actual interrupt routine code. The global variable isen0. (MCU is a pic18f2620)Code:// Read rotary encoderpin = PORTC;currA0 = (pin>>1) & 0x01;currB0 = (pin>>2) & 0x01;if (lastA0 == 0 && currA0 == 1) { // rising edge?if (currB0 != currA0) en0--; else en0++; // clockwise?}lastA0 = currA0;

author: Peabody
author_rank: Super Contributor
timestamp: January 17, 2025, 01:21:04 am
content: If the encoder servicing load is an issue because of bouncing, there's an alternative interrupt-based method that addresses that. The pin change interrupt is enabled on only one pin at a time. When the first interrupt occurs, the interrupt on that line is disabled in the ISR, and the interrupt on the other line is enabled. As a result, no interrupts are generated on the first line while it's bouncing. The other line will have transitioned some time ago, and is unlikely to still be bouncing.A problem occurs when the direction is changed because you're following the wrong line. But that is solved by expanding the typical 16-byte lookup table of the state machine model to 32 bytes.The details of this method are included in my Github repo for Atmega328P processors, which includes a PDF explaining the process.https://github.com/gbhug5a/Rotary-Encoder-Servicing-Routines/tree/master/ArduinoYou would have to modify the routine to work with the ESP32. The examples in the repo report in the serial monitor the number of interrupts that occur between two detents. The theoretical number should be four for typical encoders - one for each transition - or three if you change direction. Otherwise, a noisy encoder can generate dozens of interrupts from a single transition.You might also want to consider some form of hardware debouncing.

author: MarkF
author_rank: Super Contributor
timestamp: January 17, 2025, 03:04:35 am
content: Notice, I don't have a lookup table and I don't use a state machine.I still hold that it is not good to use interrupt-on-pin-change to detect encoder activity.It is much better to use a fixed timer interrupt to poll the encoder and more predictable.Those six lines of code are my entire interrupt routine.

author: Peabody
author_rank: Super Contributor
timestamp: January 17, 2025, 03:44:24 am
content: With polling you are polling all the time, even if the encoder isn't being turned. Using interrupts, you only spend time servicing the encoder when it's being turned. Polling may be "much better" for some, but opinions may differ on this.

author: Atlan
author_rank: Frequent Contributor
timestamp: January 17, 2025, 04:54:17 am
content: The bouncing of the encoder contacts is solved by the hardware RCR circuit. And that way uP doesn't have to spend time on unnecessary tasks in the interruption. (whether there is a bounce or not) Especially when the given circuit is listed in the DS of the encoder.

author: drspastic
author_rank: Contributor
timestamp: January 17, 2025, 01:41:15 pm
content: im using lolin lite, the usual suspect.many thanks for all your responses although most of them are discussing things at a higher level than i am conversant with at the moment. im very much a beginner using spi, and displays.the rotary acceleration is a good idea. originally i was going to use an analogue joystick but mounting it on a project box seemed awkward. the oscillator the project will control goes from 160mhz to 4400mhz and in quite tiny steps. i might need to use the push button to change the digit the encoder adjusts.it all seems a bit overwhealming

author: Atlan
author_rank: Frequent Contributor
timestamp: January 17, 2025, 04:02:31 pm
content: Try

author: shabaz
author_rank: Frequent Contributor
timestamp: January 17, 2025, 05:53:19 pm
content: That's quite a large range if it is going to be granular, so it needs some good acceleration to work well. Something with several levels of acceleration (or an acceleration curve), and probably a very free-spinning encoder (although some of them tend to be more expensive optical ones rather than with conductive wiper contacts).Although it irritated me to resort to it (I mentally prefer the idea of no buttons and rely on encoder acceleration for choosing the value, since that feels cleaner) the last time I did this with a not-very-free-spinning-encoder, I ended up using a button to select the amount of increment (you can see a purple bar in the photo, which in that case indicates increments of 1 MHz, but pressing the button allows the user to select (say) 1 Hz, 100 Hz, 10 kHz and 1 MHz increment options.If it's a DIY project, then a cheap 4x3 numeric keypad is another nice and practical option if you have the space for it. Also, control over UART can be attractive (a serial console command-line interface, or an interface to software on the PC) depending on the use-case.

author: pqass
author_rank: Super Contributor
timestamp: January 17, 2025, 06:05:01 pm
content: Seemy implementationto support multiple rotary encoders (array of structs) via one 1100Hz timer interrupt and no pin interrupts.Basically, it will record a pin state (into a temp var every 900us) and will only increment/decrement an integer value when the last four states are all zero or all ones (ie. raw pin read has settled). The main line then can just consume the integer or the switch settled state variable.

author: drspastic
author_rank: Contributor
timestamp: January 17, 2025, 08:57:08 pm
content: im happy to use the encoders button to select which number i edit. it must still be able to fine tune during use to correct drift.

author: drspastic
author_rank: Contributor
timestamp: January 24, 2025, 05:10:16 pm
content: so i got an encoder working and changing the lcd with this code:https://pastebin.com/aXw7FhVVbut then i decided to add a second encoder so i had a tuning knob for both vfos. i doubled the sections of code for the first encoder and changed the variable names for the 2nd encoder by adding a 2 at the end. i ended up after (added a } at the end because compile wasnt happy) with this code:https://pastebin.com/uqWg5HvAbut now neither encoder works!please may i beg someone to look over the code and tell me what stupid mistake i made.

author: mskeete
author_rank: Contributor
timestamp: January 25, 2025, 12:23:05 pm
content: At first glance I can see a few variable names you haven't updated on lines 65, 108 and 121

author: drspastic
author_rank: Contributor
timestamp: January 25, 2025, 11:46:00 pm
content: ok so i had some help from a kind soul. he reorganised it like this:https://pastebin.com/y1wtJsaJand both encoders now work and display.my next step was to improve granularity so i added 00000 to my starting point numbers (maximum it handles without fragging out) then tried to divide by 10000 in the display code like this:Code:tft.println(vfoa/10000);which i thought should display variable vfoa=74000000 as 740.00000 but it doesnt. infact what i really want is 740.000.00but im stumped on how to get that. i need to show all trailing and leading zeros really so the decimal separators line up.also, how can i get it to handle just one more digit so it can have granularity to 1Hz? is this a floating point arithmatic error that starts spewing random numbers?

author: DavidAlfa
author_rank: Super Contributor
timestamp: January 26, 2025, 03:48:12 am
content: https://github.com/deividAlfa/stm32_soldering_iron_controller/blob/master/Drivers/generalIO/rotary_encoder.cWhat's this? I don't really understand it.You're doing "740/10000", which is 0.Code:#define vfoa 0740.00000tft.println(vfoa/10000);vfoa is not a variable, so just declare it as string?Code:#define vfoa "0740.000.00"tft.println(vfoa);

author: drspastic
author_rank: Contributor
timestamp: January 27, 2025, 12:47:43 pm
content: got it sorted! used the long long instead of int:Code:#include <Adafruit_GFX.h>#include <Adafruit_ST7735.h>#include <ezButton.h> // the library to use for SW pin on encoder#define CLK_PIN 34 // ESP32 pin GPIO25 connected to the rotary encoder's CLK pin#define DT_PIN 35 // ESP32 pin GPIO26 connected to the rotary encoder's DT pin#define SW_PIN 32 // ESP32 pin GPIO27 connected to the rotary encoder's SW pin#define CLK_PIN2 4 // ESP32 2nd connected rotary encoder's CLK pin#define DT_PIN2 16 // ESP32 2nd connected rotary encoder's DT pin#define SW_PIN2 17 // ESP32 2nd connected rotary encoder's SW pin#define DIRECTION_CW 0 // clockwise direction rotary#define DIRECTION_CCW 1 // counter-clockwise direction rotary#define TFT_CS 5#define TFT_RST 2#define TFT_DC 0#define TFT_BL 22 // LED back-light//int counter = 0; //rotarylong long vfoa = 740000000;int direction = DIRECTION_CW; //rotaryint CLK_state; //rotaryint prev_CLK_state; //rotaryezButton button(SW_PIN); // create ezButton object that attach to pin 32;long long vfob = 2400000000;int direction2 = DIRECTION_CW; //rotary2int CLK_state2; //rotary2int prev_CLK_state2; //rotary2ezButton button2(SW_PIN2); // create ezButton object that attach to pin ;bool updateDisplay = false;Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // hardware SPIvoid setup(void) {tft.initR(INITR_BLACKTAB); // Init ST7735S chip, black tab// configure encoder pins as inputspinMode(CLK_PIN, INPUT);pinMode(DT_PIN, INPUT);button.setDebounceTime(50); // set debounce time to 50 milliseconds// read the initial state of the rotary encoder's CLK pinprev_CLK_state = digitalRead(CLK_PIN);// configure encoder pins as inputspinMode(CLK_PIN2, INPUT);pinMode(DT_PIN2, INPUT);button2.setDebounceTime(50); // set debounce time to 50 milliseconds// read the initial state of the rotary encoder's CLK pinprev_CLK_state2 = digitalRead(CLK_PIN2);pinMode(TFT_BL, OUTPUT);digitalWrite(TFT_BL, HIGH);tft.setRotation(1);tft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_GREEN);tft.setTextSize(2);tft.setCursor(0, 20);tft.println(vfoa/1000000.0,6);tft.setCursor(0, 40);tft.println("mhz");tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 70);tft.println(vfob/1000000.0,6);tft.setCursor(0, 90);tft.println("mhz");}void loop() {button.loop(); // MUST call the loop() function firstbutton2.loop(); // MUST call the loop() function first// read the current state of the rotary encoder's CLK pinCLK_state = digitalRead(CLK_PIN);// If the state of CLK is changed, then pulse occurred// React to only the rising edge (from LOW to HIGH) to avoid double countif (CLK_state != prev_CLK_state && CLK_state == HIGH) {// if the DT state is HIGH// the encoder is rotating in counter-clockwise direction => decrease the counterif (digitalRead(DT_PIN) == HIGH) {vfoa--;direction = DIRECTION_CCW;} else {// the encoder is rotating in clockwise direction => increase the countervfoa++;direction = DIRECTION_CW;}updateDisplay = true;}// save last CLK stateprev_CLK_state = CLK_state;// read the current state of the rotary encoder's CLK pinCLK_state2 = digitalRead(CLK_PIN2);// If the state of CLK is changed, then pulse occurred// React to only the rising edge (from LOW to HIGH) to avoid double countif (CLK_state2 != prev_CLK_state2 && CLK_state2 == HIGH) {// if the DT state is HIGH// the encoder is rotating in counter-clockwise direction => decrease the counterif (digitalRead(DT_PIN2) == HIGH) {vfob--;direction2 = DIRECTION_CCW;} else {// the encoder is rotating in clockwise direction => increase the countervfob++;direction2 = DIRECTION_CW;}updateDisplay = true;}// save last CLK stateprev_CLK_state2 = CLK_state2;if (updateDisplay) {updateDisplay = false;//to convert count to vfo and displaytft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 70);tft.println(vfob/1000000.0,6);tft.setCursor(0, 90);tft.println("mhz");tft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_GREEN);tft.setTextSize(2);tft.setCursor(0, 20);tft.println(vfoa/1000000.0,6);tft.setCursor(0, 40);tft.println("mhz");tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 70);tft.println(vfob/1000000.0,6);tft.setCursor(0, 90);tft.println("mhz");}if (button.isPressed()) {Serial.println("button1 is pressed");}if (button2.isPressed()) {Serial.println("button2 is pressed");}}

author: drspastic
author_rank: Contributor
timestamp: January 28, 2025, 07:28:12 pm
content: next step was seperating the variable for the display. after a lot of problems with variable type i got this:Code:#include <Adafruit_GFX.h>#include <Adafruit_ST7735.h>#include <ezButton.h> // the library to use for SW pin on encoder#define CLK_PIN 34 // ESP32 pin GPIO25 connected to the rotary encoder's CLK pin#define DT_PIN 35 // ESP32 pin GPIO26 connected to the rotary encoder's DT pin#define SW_PIN 32 // ESP32 pin GPIO27 connected to the rotary encoder's SW pin#define CLK_PIN2 4 // ESP32 2nd connected rotary encoder's CLK pin#define DT_PIN2 16 // ESP32 2nd connected rotary encoder's DT pin#define SW_PIN2 17 // ESP32 2nd connected rotary encoder's SW pin#define DIRECTION_CW 0 // clockwise direction rotary#define DIRECTION_CCW 1 // counter-clockwise direction rotary#define TFT_CS 5#define TFT_RST 2#define TFT_DC 0#define TFT_BL 22 // LED back-light//int counter = 0; //rotarylong long vfoa = 740000000;int direction = DIRECTION_CW; //rotaryint CLK_state; //rotaryint prev_CLK_state; //rotaryezButton button(SW_PIN); // create ezButton object that attach to pin 32;long long vfob = 2400000000;int direction2 = DIRECTION_CW; //rotary2int CLK_state2; //rotary2int prev_CLK_state2; //rotary2ezButton button2(SW_PIN2); // create ezButton object that attach to pin ;bool updateDisplay = false;//display variablesfloat vfoadisplay = (vfoa/1000000.0);float vfobdisplay = (vfob/1000000.0);Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST); // hardware SPIvoid setup(void) {tft.initR(INITR_BLACKTAB); // Init ST7735S chip, black tab// configure encoder pins as inputspinMode(CLK_PIN, INPUT);pinMode(DT_PIN, INPUT);button.setDebounceTime(50); // set debounce time to 50 milliseconds// read the initial state of the rotary encoder's CLK pinprev_CLK_state = digitalRead(CLK_PIN);// configure encoder pins as inputspinMode(CLK_PIN2, INPUT);pinMode(DT_PIN2, INPUT);button2.setDebounceTime(50); // set debounce time to 50 milliseconds// read the initial state of the rotary encoder's CLK pinprev_CLK_state2 = digitalRead(CLK_PIN2);//display variablesfloat vfoadisplay = (vfoa/1000000.0);float vfobdisplay = (vfob/1000000.0);pinMode(TFT_BL, OUTPUT);digitalWrite(TFT_BL, HIGH);tft.setRotation(1);tft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_GREEN);tft.setTextSize(2);tft.setCursor(0, 20);tft.println((vfoadisplay),6);tft.setCursor(0, 40);tft.println("mhz");tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 70);tft.println((vfobdisplay),6);tft.setCursor(0, 90);tft.println("mhz");}void loop() {button.loop(); // MUST call the loop() function firstbutton2.loop(); // MUST call the loop() function first// read the current state of the rotary encoder's CLK pinCLK_state = digitalRead(CLK_PIN);// If the state of CLK is changed, then pulse occurred// React to only the rising edge (from LOW to HIGH) to avoid double countif (CLK_state != prev_CLK_state && CLK_state == HIGH) {// if the DT state is HIGH// the encoder is rotating in counter-clockwise direction => decrease the counterif (digitalRead(DT_PIN) == HIGH) {vfoa--;direction = DIRECTION_CCW;} else {// the encoder is rotating in clockwise direction => increase the countervfoa++;direction = DIRECTION_CW;}updateDisplay = true;}// save last CLK stateprev_CLK_state = CLK_state;// read the current state of the rotary encoder's CLK pinCLK_state2 = digitalRead(CLK_PIN2);// If the state of CLK is changed, then pulse occurred// React to only the rising edge (from LOW to HIGH) to avoid double countif (CLK_state2 != prev_CLK_state2 && CLK_state2 == HIGH) {// if the DT state is HIGH// the encoder is rotating in counter-clockwise direction => decrease the counterif (digitalRead(DT_PIN2) == HIGH) {vfob--;direction2 = DIRECTION_CCW;} else {// the encoder is rotating in clockwise direction => increase the countervfob++;direction2 = DIRECTION_CW;}updateDisplay = true;}// save last CLK stateprev_CLK_state2 = CLK_state2;//update display variablesfloat vfoadisplay = (vfoa/1000000.0);float vfobdisplay = (vfob/1000000.0);if (updateDisplay) {updateDisplay = false;//to convert count to vfo and displaytft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 70);tft.println((vfobdisplay),6);tft.setCursor(0, 90);tft.println("mhz");tft.fillScreen(ST77XX_BLACK);tft.setTextColor(ST77XX_GREEN);tft.setTextSize(2);tft.setCursor(0, 20);tft.println((vfoadisplay),6);tft.setCursor(0, 40);tft.println("mhz");tft.setTextColor(ST77XX_YELLOW);tft.setCursor(0, 70);tft.println((vfobdisplay),6);tft.setCursor(0, 90);tft.println("mhz");}if (button.isPressed()) {Serial.println("button1 is pressed");}if (button2.isPressed()) {Serial.println("button2 is pressed");}}which gives the correct format display but now the encoders stopped working! the display flickers as i turn them so they do something but either its not displayed or its not counting

author: drspastic
author_rank: Contributor
timestamp: January 29, 2025, 12:06:25 am
content: Code:#include <Adafruit_GFX.h>#include <Adafruit_ST7735.h>#include <ezButton.h>#define CLK_PIN 34#define DT_PIN 35#define SW_PIN 32#define CLK_PIN2 4#define DT_PIN2 16#define SW_PIN2 17#define DIRECTION_CW 0#define DIRECTION_CCW 1#define TFT_CS 5#define TFT_RST 2#define TFT_DC 0#define TFT_BL 22long long vfoa = 740000000;long long vfob = 2400000000;long long adjustmentA = 1;long long adjustmentB = 1;int direction;int direction2;int prev_CLK_state, prev_CLK_state2;bool updateVFOA = false, updateVFOB = false;Adafruit_ST7735 tft(TFT_CS, TFT_DC, TFT_RST);ezButton button(SW_PIN);ezButton button2(SW_PIN2);void highlightDigitRightJustified(long long value, long long adjustment, int y, uint16_t normalColor, uint16_t highlightColor) {char buffer; // Ensure buffer size is large enough for leading zerossprintf(buffer, "%010lld", value); // Format frequency as 10 digits with leading zeros// Calculate the total text width (12 pixels per character for text size 2)int textWidth = strlen(buffer) * 12;// Starting X position for right justification (160 is screen width)int xStart = 160 - textWidth;// Find the index of the digit to highlightint digitIndex = 9 - log10(adjustment); // Determine which digit corresponds to the adjustment// Render the frequency string with highlightingfor (int i = 0, pos = 0; i < strlen(buffer); i++) {// Highlight the correct digitif (pos == digitIndex) {tft.setTextColor(highlightColor, ST77XX_BLACK);} else {tft.setTextColor(normalColor, ST77XX_BLACK);}tft.setCursor(xStart + pos * 12, y);tft.print(buffer);pos++;}}void updateDisplayVFOA() {tft.fillRect(0, 20, 160, 20, ST77XX_BLACK); // Clear the areahighlightDigitRightJustified(vfoa, adjustmentA, 20, ST77XX_GREEN, ST77XX_RED);// Correctly justify "MHz" under the VFOA displaytft.setCursor(124, 40); // Position "MHz" to be right-alignedtft.setTextColor(ST77XX_GREEN, ST77XX_BLACK);tft.print("MHz");}void updateDisplayVFOB() {tft.fillRect(0, 70, 160, 20, ST77XX_BLACK); // Clear the areahighlightDigitRightJustified(vfob, adjustmentB, 70, ST77XX_YELLOW, ST77XX_RED);// Correctly justify "MHz" under the VFOB displaytft.setCursor(124, 90); // Position "MHz" to be right-alignedtft.setTextColor(ST77XX_YELLOW, ST77XX_BLACK);tft.print("MHz");}void setup() {tft.initR(INITR_BLACKTAB);pinMode(CLK_PIN, INPUT);pinMode(DT_PIN, INPUT);pinMode(CLK_PIN2, INPUT);pinMode(DT_PIN2, INPUT);pinMode(TFT_BL, OUTPUT);digitalWrite(TFT_BL, HIGH);button.setDebounceTime(50);button2.setDebounceTime(50);prev_CLK_state = digitalRead(CLK_PIN);prev_CLK_state2 = digitalRead(CLK_PIN2);tft.setRotation(1);tft.fillScreen(ST77XX_BLACK);tft.setTextSize(2);updateDisplayVFOA();updateDisplayVFOB();}void loop() {button.loop();button2.loop();int CLK_state = digitalRead(CLK_PIN);if (CLK_state != prev_CLK_state && CLK_state == HIGH) {if (digitalRead(DT_PIN) == HIGH) {vfoa -= adjustmentA;direction = DIRECTION_CCW;} else {vfoa += adjustmentA;direction = DIRECTION_CW;}updateVFOA = true;}prev_CLK_state = CLK_state;int CLK_state2 = digitalRead(CLK_PIN2);if (CLK_state2 != prev_CLK_state2 && CLK_state2 == HIGH) {if (digitalRead(DT_PIN2) == HIGH) {vfob -= adjustmentB;direction2 = DIRECTION_CCW;} else {vfob += adjustmentB;direction2 = DIRECTION_CW;}updateVFOB = true;}prev_CLK_state2 = CLK_state2;if (updateVFOA) {updateVFOA = false;updateDisplayVFOA();}if (updateVFOB) {updateVFOB = false;updateDisplayVFOB();}if (button.isPressed()) {adjustmentA *= 10;if (adjustmentA > 1000000000) {adjustmentA = 1;}updateVFOA = true;}if (button2.isPressed()) {adjustmentB *= 10;if (adjustmentB > 1000000000) {adjustmentB = 1;}updateVFOB = true;}}any thoughts about this?

author: peter-h
author_rank: Super Contributor
timestamp: January 29, 2025, 04:39:47 pm
content: I haven't read the whole thread but normally shaft encoders are done with an arrangement with two counters, or one up-down counter with some logic in front of it.I did this about 40 years ago with the Z80-CTC which was perfect for this job (with some 4000 logic). There may even be an appnote still online somewhere. This scheme did not need interrupts; the CPU merely had to read the CTC frequently enough so the 16 bit counters did not overflow (a wrap around was inevitable but the scheme dealt with it cleverly).I imagine people do it with interrupts from the encoder edges nowadays. You have to correctly handle the case where the encoder is stationary and vibrating around one of the edges

author: drspastic
author_rank: Contributor
timestamp: January 29, 2025, 09:55:42 pm
content: half borrowed, half i did myself then i let chatgpt optimise it and tidy it up. it works ok but the red colour denoting cursor loses allignment with the active digit under certain circumstances. maybe i will need to set limit stops..i cut my teeth on z80 opcode. welcome to the grandad club!

author: peter-h
author_rank: Super Contributor
timestamp: January 30, 2025, 07:19:41 am
content: QuoteYou mean somebody did it before and chatgpt found itChatgpt cannot actually write code - it is merely a text analysis machine. But it does know sentences start with an uppercase letterFunny about grandad - you aren't wrong

author: drspastic
author_rank: Contributor
timestamp: January 30, 2025, 02:07:16 pm
content: yeah it just removed a lot of duplicate lines and made it look tidy.

author: OLderDan
author_rank: Regular Contributor
timestamp: March 02, 2025, 12:51:29 am
content: Quote from: drspastic on January 29, 2025, 09:55:42 pm61 here and still every time i see c3, i think jump!