[Logo] TCC discussion forum
  [Search] Search   [Recent Topics] Recent Topics   [Hottest Topics] Hottest Topics   [Top Downloads] Top Downloads   [Groups] Back to home page 
[Register] Register /  [Login] Login 


This forum is read only and new users cannot register, please ask all new questions either using GitHub discussions, or in Arduino forum tagging @davetcc.

2nd Encoder RSS feed
Forum Index » IoAbstraction & TaskManagerIO
Author Message
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
There's no other way to say it. It's just still not clear to me how a 2nd or 3rd encoder ties in with an 8574 expander using IOAbstraction, TaskManagerIO, and TcMenu.

My 2nd encoder CLK,DT,SW connected to 8574 pins 4,3,2 respectively cause me not to be able to scope those 2nd encoder pins like I'm able to scope the 1st encoder pins even they are both connected to the same expander.

When I disconnect CLK, DT, or SW from the 8574 I can scope those signals. Bold for emphasis because I'm loosing it. Please put me out of my mystery.

I made enough changes to the pins and hardware I'm using and added a library for manual timing/tuning of multiple PWMsignals output frequency, phase, and duty cycle. I mention it because it does use TCC0 and TCC1 and sets GCLK to 48Mhz and I don't know how that may or may not break some things in the task scheduler. For those reasons I created a new topic.

Anyway I think people that are asking these questions need a way to tie it all together.

It would be helpful if there was a TcMenu/TaskManagerIO section on this page about using 8574 w/ Interrupts when additional RotaryEncoder's are interacting with menu items/variables/classes. I mean there is [/url=https://www.thecoderscorner.com/products/arduino-libraries/tc-menu/tcmenu-plugins/encoder-switches-input-plugin/]this page about using the plugin[url] that I followed so I'm starting to suspect something about the way encoder slot 0 is initialized vs the way I'm initializing slot 1. I'm talking about pull up/down behavior on the 8574 specifically and some of the assumptions noobs might make that might be wrong. The encoders I have have 3 10k resistors on them. I'm just trying not to leave anything else out.

I'm particularly interested to know what's missing around lines 40-43 and 111-140.

#include "Arduino_menu.h"
#include "definitions.h"
#include "Clock_Setup.h"
#include "PWM_Signals.h"
#include "Dual_PWM_HW.h"

#include <IoAbstraction.h>
#include <IoAbstractionWire.h>
#include <TaskManagerIO.h>
#include <SPI.h>
#include <Wire.h>

//Pin numbers on evb
#define PWM_OUT1 9
#define PWM_OUT2 10
#define INTERRUPT_PIN_8574 11
#define GFX_CS 12
#define GFX_DC 13

//Pin Numbers on PCF8574
// 5,6,7 are the tcMenu encoder
#define ENABLE_DISABLE_BUTTON 1
#define ROTARY2_SW 2
#define ROTARY2_DT 3
#define ROTARY2_CLK 4

#define MAX_ROTARY_MODES 2 // mode definitions right below
#define FREQUENCY_ADJUST_MODE 0
#define DUTY_CYCLE_ADJUST_MODE 1
#define PHASE_ADJUST_MODE 2

uint16_t rotaryMode = 0;

volatile uint32_t virtualFrequency = DEFAULT_FREQUENCY;
volatile uint32_t virtualDutyCycle = DEFAULT_DUTYCYCLE*1000;
volatile int virtualPhase = DEFAULT_PHASE;

Clock_Setup clk;
PWM_Signals signals;
HardwareRotaryEncoder* RotaryEncoder2;

//0x20 address is means A0-A2 on chip are low
IoAbstractionRef ioExpander = ioFrom8574(0x20, INTERRUPT_PIN_8574);
Adafruit_ST7735 gfx = Adafruit_ST7735(GFX_CS, GFX_DC, -1);
AdaColorGfxMenuConfig colorConfig;


void CALLBACK_FUNCTION onRotary2Changed(int newVal){
	switch(rotaryMode){
		case FREQUENCY_ADJUST_MODE:
			virtualFrequency = newVal;
			signals.setSignalFrequency(virtualFrequency,PWM_SIGNAL_1);
			signals.setSignalDutyCycle(virtualDutyCycle/1000,PWM_SIGNAL_1);
			signals.setSignalFrequency(virtualFrequency,PWM_SIGNAL_2);
			signals.setSignalDutyCycle(virtualDutyCycle/1000,PWM_SIGNAL_2);
			break;
		case DUTY_CYCLE_ADJUST_MODE:
			virtualDutyCycle = newVal;
			signals.setSignalDutyCycle(virtualDutyCycle/1000,PWM_SIGNAL_1);
			signals.setSignalDutyCycle(virtualDutyCycle/1000,PWM_SIGNAL_2);
			break;
		case PHASE_ADJUST_MODE:
			virtualPhase = newVal;
			signals.setSignalPhase(virtualPhase,virtualDutyCycle/1000);
			break;
	}
}
//void CALLBACK_FUNCTION onRotary2Changed(int newVal){}
	
	
void CALLBACK_FUNCTION enableDisableButtonPressed(pinid_t pin, bool heldDown){
	if(heldDown){} //If holding down the enable/disable button should do something it goes here
	
	//validate that pressing the enable/disable button doesn't screw things up if it's pressed while menu item for enable output is OFF/FALSE 
	if (menuEnableOutput.getBoolean()){ 
		//toggle timer 0
		TCC0->CTRLA.reg ^= (TCC_CTRLA_ENABLE); while (TCC0->SYNCBUSY.bit.ENABLE) {}
		//toggle timer 1
		TCC1->CTRLA.reg ^= (TCC_CTRLA_ENABLE); while (TCC1->SYNCBUSY.bit.ENABLE) {}
	}
}
	
void CALLBACK_FUNCTION onRotary2SwitchPressed(pinid_t pin, bool heldDown){
	if(heldDown){}//If holding down Rotary2 should do something it goes here
	
	// if were already in the last rotaryMode adjustment mode then go back to 0
	if ((rotaryMode+1) > MAX_ROTARY_MODES){
		rotaryMode=0;
	}
	//Otherwise increment to the next fine tune adjustment mode
	rotaryMode++;
	
	switch (rotaryMode){
		case FREQUENCY_ADJUST_MODE:
			switches.changeEncoderPrecision(1,MAX_FREQUENCY,virtualFrequency);
			break;
		case DUTY_CYCLE_ADJUST_MODE:
			switches.changeEncoderPrecision(1,MAX_DUTYCYCLE*1000,virtualDutyCycle);
			break;
		case PHASE_ADJUST_MODE:
			switches.changeEncoderPrecision(1,MAX_PHASE,virtualPhase);
			break;
	}
}
	

void setup() {
	Wire.begin();
	initDualPWM(); //sets up output pins and clocks
	
	switches.initialiseInterrupt(ioFrom8574(0x20, INTERRUPT_PIN_8574),true);

	//HardwareRotaryEncoder* RotaryEncoder2 = new HardwareRotaryEncoder(ROTARY2_CLK, ROTARY2_DT, onRotary2Changed);
	RotaryEncoder2 = new HardwareRotaryEncoder(ROTARY2_CLK, ROTARY2_DT, onRotary2Changed);
	switches.setEncoder(1, RotaryEncoder2); //puts this 2nd encoder in "Slot 1"... "Slot 0" is for the menu encoder
	
/*	
	Enable/Disable button 
	PRESS disables output
	HOLD disables output and resets all the timer registers*/
//	switches.addSwitch(ENABLE_DISABLE_BUTTON,enableDisableButtonPressed,NO_REPEAT,true);
	switches.addSwitch(ENABLE_DISABLE_BUTTON,enableDisableButtonPressed,NO_REPEAT,true);
	
/*
	Rotary 2 push button changes between adjustment modes on output screens
	PRESS - adjust pulse width / Duty cycle, adjust phase
	HOLD - gives back the screen - output is still enabled */
//	switches.addSwitch(ROTARY2_SW, onRotary2SwitchPressed,NO_REPEAT,true);
	switches.addSwitch(ROTARY2_SW, onRotary2SwitchPressed,NO_REPEAT,true);
	
	//I think this makes the callback only return direction 1=CW, -1=CCW
	//switches.changeEncoderPrecision(1,0,0);
	taskManager.addInterrupt(ioExpander,ROTARY2_CLK,CHANGE);
	taskManager.addInterrupt(ioExpander,ROTARY2_SW, FALLING); 
	taskManager.addInterrupt(ioExpander, ENABLE_DISABLE_BUTTON,FALLING);
	
	//taskManager.addInterrupt(switches.getIoAbstraction(),ROTARY2_CLK,CHANGE);
	//taskManager.addInterrupt(switches.getIoAbstraction(),ROTARY2_SW, FALLING); 
	//taskManager.addInterrupt(switches.getIoAbstraction(), ENABLE_DISABLE_BUTTON,FALLING);

	prepareAdaMonoGfxConfigLoRes(&colorConfig);
	gfx.initR(INITR_144GREENTAB); //Adafruit 1.44"
	gfx.setRotation(1);
    setupMenu();
}

void initDualPWM(){
	
	pinMode(PWM_OUT1,OUTPUT); //D9 is PA7 Pin 12 Function E TCC1
	pinMode(PWM_OUT2,OUTPUT); //D10 is PA18 Pin 27 Function F TCC0
	//pinMode(ENABLE_DISABLE_BUTTON,INPUT_PULLUP);

	//There should be no output until the user sets up the output
	digitalWrite(PWM_OUT1, 0);
	digitalWrite(PWM_OUT2, 0);
	
	// Enable the peripheral multiplexer for the pins.
	PORT->Group[0].PINCFG[18].reg |= PORT_PINCFG_PMUXEN;
	PORT->Group[0].PINCFG[7].reg |= PORT_PINCFG_PMUXEN;

	// Set PA18's function to function F. Function F is TCC0/WO[2] for PA18.
	// Because it's an even numbered pin the PMUX is E (even) and the PMUX
	// index is pin number / 2, so 9.
	PORT->Group[0].PMUX[9].reg = PORT_PMUX_PMUXE_F;

	// Set PA07's function to function E. Function E is TCC1/WO[1] for PA07.
	//  Because this is an odd numbered pin the PMUX is O (odd) and the PMUX
	//  index is pin number - 1 / 2, so 3.
	PORT->Group[0].PMUX[3].reg = PORT_PMUX_PMUXO_E;
	clk.Clock_Init(); //setup system clocks
	
	//PUT THIS SOMEWHERE WHERE IT MAKES SENSE!
	//signals.PWM_Signals_Init();
}

void loop() {
    taskManager.runLoop();
}

void CALLBACK_FUNCTION enablePWMOutNow(int id) {

	signals.PWM_Signals_Init();
}

void CALLBACK_FUNCTION resetRegistersNow(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION globalResetNow(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION setPWMFrequency(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION changedPWMDutyCycle(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION changedPWMPhase(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION enableOutputFlag(int id) {
    // TODO - your menu change code
	if (menuEnableOutput.getBoolean()==false) // if it's changed to "NO" then it was "YES" so turn off output
		enableDisableButtonPressed(id,false);
}

void CALLBACK_FUNCTION beginFrequencySweep(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION beginDutyCycleSweep(int id) {
    // TODO - your menu change code
}

void CALLBACK_FUNCTION beginPhaseSweep(int id) {
    // TODO - your menu change code
}


It compiles and runs. The signals do output when I select the action from the menu. The 2nd encoder clocking and button, and additional pushbutton switch (enable/disable output that is wired straight to ground) never get called.

It's got to be something simple.

I'm not handing off the renderer to another screen yet but that's next.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
I see that in tcMenu.cpp this is how the encoder for the menu is setup
void MenuManager::initForEncoder(MenuRenderer* renderer,  MenuItem* root, pinid_t encoderPinA, pinid_t encoderPinB, pinid_t encoderButton) {
	this->renderer = renderer;
	this->currentRoot = this->rootMenu = root;

	switches.addSwitch(encoderButton, nullptr);
        switches.onRelease(encoderButton, [](pinid_t /*key*/, bool held) { menuMgr.onMenuSelect(held); });
	setupRotaryEncoderWithInterrupt(encoderPinA, encoderPinB, [](int value) {menuMgr.valueChanged(value); });

	renderer->initialise();
}


This looks quite different than how I feel instructed to create it.

switches.onRelease(encoderButton, [](pinid_t /*key*/, bool held) { menuMgr.onMenuSelect(held); });
	setupRotaryEncoderWithInterrupt(encoderPinA, encoderPinB, [](int value) {menuMgr.valueChanged(value); });


And to be honest I don't know how to read that and it feels a little personal haha. Can you demystify this?

2nd encoder pins definitely seem stuck in High Z state. I'm missng something low level. One of those things you take for granted because you know it implicitly that I don't know because it hasn't kicked my ass enough for me to never forget it...
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
I'm now able to see the 2nd encoder CLK and DT on the scope but the interrupt callbacks don't seem to run. I believe I needed to set the IOdevice pin modes to input. I might be misremembering something but recall reading that I might also need to write to them while they are set to be an input or input pullup(??)

I'm away from my machine but will try that later.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
To be honest, we cannot debug such a complex situation over the forum and need to break it down into smaller pieces.

The sketch below is too large to debug by analysis, but I can see an immediate problem at line 111 where you create a second IO expander instead of using the original one you created at line 43. However, I'm not sure that's the issue.

I would suggest you start by building the simple sketch I sent some time ago onto the device, rather than try to debug with a complicated sketch. Does the example I sent you some time back work when modified to work over the IO expander? Then does it work when using the interrupt switch support? As that sketch worked correctly when tested on my AVR MEGA board, where I added an extra encoder.

What I am going to try, is to set up a 2nd encoder on my SAMD board with a menu installed on it, over a PCF8574, and make it interrupt-based, I'll feedback when I've had a chance to run the test. If there are any bugs found as a result, I'll raise them against IoAbstraction 1.6.5 which is the next patch release. I'll need to do this anyway to write the documentation on multi encoders.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
It's so easy to make mistakes! I just spent an hour panicking thinking that there was a major bug in the PCF8574 switches support, however at the end of it, when the scope came out, there was no continuity on the earth line in my example!

Anyway, back to the task at hand. So far I have a SAMD board set up, PCF8574, two rotary encoders, one extra micro switch, and one output line from it for an LED. That uses up every pin on the device. It all works correctly in the simple isolated sketch that is now committed to master here: https://github.com/davetcc/IoAbstraction/tree/master/examples/interruptSwitchEncoder8574

So step 1, the simplest possible sketch is working. Next step: integrate that into a menu running on that board. If that works, you will even be able to run the menu example yourself and test it on your hardware, the only difference would be screen size and pinouts. This should help you to get to the bottom of your issue. Although adjusting timers should not affect taskmanager, as long as micros() and millis() return correctly all the time, you cannot rule out that this could affect one of the pins you are using for interrupt.

I'll feedback shortly with additional data once in the menu.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
So I've now modified the SAMD21 tcMenu example to have a 2nd encoder.

You can see the sample at the link below, the areas marked with "Start 2nd Encoder" and "End 2nd Encoder" were the only changes in the file.

You can even do a difference to the previous version in the link to see the changes I had to make. There should be no need to add interrupts yourself, I've confirmed this now as I have created something as close as possible to your hardware.

Example with minimum changes needed for dual encoder support: https://github.com/davetcc/tcMenuLib/tree/master/examples/colorTftEthernet32

In particular look at the colorTftEthernet32.ino sketch file, you'll see the changes I made there today. This code should work with the library versions you have.

My hardware is:

Arduino MKR SAMD21 board
   MKR Ethernet Sheild
   ST7735 display
   i2c AT24 EEPROM
   PCF8574 Device on 0x20
       0, 2, 3 -> Rotary encoder 2
       5, 6, 7 -> Rotary encoder 1
       1 -> LED

davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
Here's a few pointers:

Firstly, your sketch is reinitializing switches, which I imagine is being done by the code generator, this risks the whole thing being wrongly setup. Remove the direct initialisation in setup.

Next, as previously discussed, it is using two different IOexpander references to refer to the same 8574 device, and there could be other errors that I don't immediately see, but I'd start with those two.

Further, you're adding switches before setupMenu() is called that does the switches initialization. Only ever call into switches after setupMenu has run. I'll make that clearer in the documentation.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
Firstly, your sketch is reinitializing switches, which I imagine is being done by the code generator, this risks the whole thing being wrongly setup. Remove the direct initialisation in setup.

Line 111 -
switches.initialiseInterrupt(ioFrom8574(0x20, INTERRUPT_PIN_8574),true);

If switches doesn't need to be told to explicitly treat signals coming from this pin as interrupts and that they represent signals from an encoder over i2c then it must be these lines that designate interrupts using TaskManagerIO
taskManager.addInterrupt(ioExpander,ROTARY2_CLK,CHANGE);
	taskManager.addInterrupt(ioExpander,ROTARY2_SW, FALLING); 
	taskManager.addInterrupt(ioExpander, ENABLE_DISABLE_BUTTON,FALLING);

Hopefully this is the way to think about it.

Next, as previously discussed, it is using two different IOexpander references to refer to the same 8574 device, and there could be other errors that I don't immediately see, but I'd start with those two.

Yes as previously discussed
//43 
IoAbstractionRef ioExpander = ioFrom8574(0x20, INTERRUPT_PIN_8574)

//111
switches.initialiseInterrupt(ioFrom8574(0x20, INTERRUPT_PIN_8574),true)

Line 111 has no business being there and this important to remove. It's equally important to me that I now think I understand why

Further, you're adding switches before setupMenu() is called that does the switches initialization. Only ever call into switches after setupMenu has run. I'll make that clearer in the documentation.

Ah ha. Well... I'll be. I wouldn't have ever guessed this was important.

Documentation wise I think there are things that could be done that would have prevented someone admittedly inexperienced from having trouble. That isn't meant to be a criticism. No on one the planet knows these libraries better than you and that might make you too close to it to write documentation for plebs like me to understand. Each library is documented well on its own and there are many examples of how to use one with another. I just happened to want to use almost all of them on my first attempt and was cobbling together sections of code from different pages and in doing so initialising some things more than once, incorrectly, and in the wrong order. I'll keep and eye out for changes to documentations about what order things need to take place in just in case I can learn something else.

@davecc I can recognise that you've gone beyond what you think is appropriate for an issue being presented on this forum and the effort isn't unnoticed. I never feel entitled to free dev support so that makes me grateful for it. I'm one of those people whose feature set includes pervasive ambiguation when concepts are abstracted and specific use cases aren't explicit. I also seem to only learn from my own mistakes and tend to step in every mistake possible taking on projects above my skill level because my ambition outweighs my experience and wallet.

I just paid off an an invoice for dev work/hardware for November and December is a tough month for obvious reasons. I'm trying to get something that functions well enough so that I can send you complete source for evaluation of commercial support for bluetooth control because there's no chance I'm going to get that working. I'm looking forward to working with you in 2021. I'm going to read your new examples and hopefully you won't be hearing from me until next year.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
Thanks for the reply, I know how that goes with projects.

Apologies that a few missing points in the documentation led to this protracted situation, finally today after building on the same device that you had with the same hardware, I knew it had to be something in setup.

I suspect if you make the suggested adjustments, it should work. You can see in the tcMenu example the two commented blocks that I added, that's all you should need to add.

You can completely remove the adding of interrupts only because it's done for you by the encoder and switches. IE: adding a hardware rotary encoder also adds the interrupt on pinA. Adding a switch also adds the interrupt if needed.
 
Forum Index » IoAbstraction & TaskManagerIO
Go to:   
Mobile view
Powered by JForum 2.7.0 © 2020 JForum Team • Maintained by Andowson Chang and Ulf Dittmer

This site uses cookies to analyse traffic, serve ads by Google AdSense (non-personalized in EEA/UK), and to record consent. We also embed Twitter, Youtube and Disqus content on some pages, these companies have their own privacy policies.

Our privacy policy applies to all pages on our site

Should you need further guidance on how to proceed: External link for information about cookie management.