[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.

Must I use an expander if I use 2 rotary encoders? RSS feed
Forum Index » IoAbstraction & TaskManagerIO
Author Message
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
It's a dumb question but still isn't clear to me.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
The multiple encoder support was kindly contributed to the library some time ago, so I'm not that familiar with it as we've never really needed it in any of our projects. But it works and has been used by several projects that I'm aware of.

For the question, the answer is no, you should not need to use an expander in order to use more than one encoder, they should work perfectly with device pins. However, everyone I'm aware of who's used them has invariably used an expander. If you intend to use this with device pins, can you try it and feedback any errors here, also indicating which target device you are building for. If there are any problems, I'll build the circuit with the closest device I have on hand, and test it myself as it would be good to have it working for all. I'm assuming it's not AVR as they don't have enough interrupt capable pins for wiring up more than one encoder.

The only restriction should be that pinA of each encoder must be on an interrupt capable pin. I'll also add a task to clean up the documentation a bit around this.

I'd also suggest that you maybe search this forum and the IoAbstraction repo issues for multiple rotary encoders, because there's been a few discussions around this in the past.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
Thank you @davetcc. I wanted to ask the question in generic terms before posting a more detailed explanation and code only to find out that I did indeed need to use an expander in the first place.

I'm using the following SAMD21G based boardhttps://robotdyn.com/samd21-m0-mini-soldered.html
Here's the schematic https://robotdyn.com/pub/media/0G-00005516==SAMD21-MINI/DOCS/Schematic==0G-00005516==SAMD21-MINI.pdf

I've verified that each of the encoders can be used to traverse the menu through separate compilations.

Here's a photo of the project so far and an explanation of how it's supposed to work
- This outputs 2x PWM signals each of which can be separately be varied in terms of frequency, duty cycle, and phase
- Bottom Encoder is for menu navigation only
- Button is for
- quick press gives back display to the menu and continues to output signals
- long press will (eventually when implemented) ceases output and resets all timer settings
- Top Encoder is for making adjustments
-clockwise increments value
-counter clockwise... excuse me "anti clockwise" decrements value
-pressing rotary switch changes to the next value to be adjusted
-adjustment modes are PWMout1 Freq, PWMout1 Duty, PWMout1 Phase, PWMout2 Freq, PWMout2 Duty, PWMout2 Phase,
-Right now only the first 2 modes above have code but since they don't work I don't want to move forward

Here's how I'm trying to set things up:
void setup() {
	
	pinMode(PWM_OUT,OUTPUT);
	digitalWrite(PWM_OUT, 0);
	
	pinMode(PIN_WIRE_SDA,INPUT);
	pinMode(PIN_WIRE_SCL,INPUT);
	
	
	//pinMode(ROTARY_SW, INPUT_PULLUP);
	pinMode(ROTARY2_SW,INPUT_PULLUP);
	
	//pinMode(ROTARY_CLK, INPUT);
	//pinMode(ROTARY2_CLK, INPUT);
	
	
	//We're going to use switches so this needs to get initialized
	//switches.initialise(ioUsingArduino(),true); //polled? IDK
	switches.initialiseInterrupt(ioUsingArduino(), true); 
	
	// Rotary 2 push button changes between adjustment modes on output screens
	//ex: adjust pulse width / Duty cycle, adjust frequency / Duty cycle, adjust frequency / phase
	switches.addSwitch(ROTARY2_SW, onRotary2SwitchPressed);
	
	//Enable/Disable button press brings the user back to the menu
	// button hold disables output and resets all timer registers
	switches.addSwitch(PIN_WIRE_SCL, enableDisableButtonPressed); //pin 21 SCL
		
	//Setup the encoder and tell it where to go when it gets indexed
	//setupRotaryEncoderWithInterrupt(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
	

	//taskManager.addInterrupt(switches.getIoAbstraction(),ROTARY2_CLK,CHANGE);
	
	// changes what is being adjusted while still outputting PWM	
	taskManager.addInterrupt(switches.getIoAbstraction(),ROTARY2_SW, FALLING); //set as pullup	
	taskManager.addInterrupt(switches.getIoAbstraction(), ENABLE_DISABLE_BUTTON,FALLING);



	//setup screen color if wanted
	prepareAdaMonoGfxConfigLoRes(&colorConfig);
	gfx.initR(INITR_144GREENTAB); //Adafruit 1.44"
	gfx.setRotation(1);

	setupMenu();

}


Here are the callback functions"

void CALLBACK_FUNCTION onRotary2SwitchPressed(pinid_t pin, bool heldDown){
	if((rotaryMode+1) > MAX_ROTARY_MODES)
		rotaryMode=0;
	else
		rotaryMode++;
}


void CALLBACK_FUNCTION onRotary2Changed(int newValue){
	switch (rotaryMode){
		case 0: // Pulse Width adjust
			if (newValue){ // 1=up
				wPER++;
				break;
			}
			else if(newValue==-1){
				wPER--;
				break;
			}
			else
				break;			
		case 1: // Duty Cycle adjust
			if (newValue){ // 1=up
				wDC++;
				break;
			}
			else if(newValue==-1){
				wDC--;
				break;
			}
			else
				break;		
	}

}


void CALLBACK_FUNCTION enableDisableButtonPressed(){ 
	static unsigned long lastSWInterruptTime = 0;
	unsigned long SWinterruptTime = millis();
	
	if ((SWinterruptTime - lastSWInterruptTime) > 20)  {
		backOut = 1; // This needs to get set back to 0 Manually!
		}

}


Enabling the output is called when the user selects "Enable" from a menu

void CALLBACK_FUNCTION enablePWMOutputNow(int id) {
	//Friendly name for the mode, may end up being in a widget
	theMode="Fine Tune PWM Output";
	
	//TODO: wPER needs to be calculated for what the user selected for pulse width
	//wPER = menuPWMOutByPulseWidthDuration.getLargeNumber()->getWhole();
	
	//setup pwm output pin registers and pinmux
	wPER = 25000;
	wDC = wPER/2;
	virtualwPER = wPER;
	virtualwDC = wDC;
	pScaler = 4;
	dFactor = 8;
	
	configurePWMWidthMode(4,pScaler,dFactor,wPER,wDC);
	
	//In a moment another screen is going to take over, when in that mode the knob no longer control the menu
	// mode is changed from tune PWM Width to tune Duty cycle by PRESSING the rotary knob (ROTARY_SW)
	// When rotaryMode == 1 the knob adjusts the Pulse width
	// WHen rotaryMode == 0 it adjusts the Duty Cycle
	
	//switches.changeEncoderPrecision(1,2*wPER,wPER);
	// I don't need care about value, just encoder direction
	// rotaryMode will determine what the actual encoder does anyway
	RotaryEncoder2->changePrecision(0,0);

	renderer.takeOverDisplay(PWMOutputNow); //Hand over control
}


And this is what it hands over control to

void PWMOutputNow(unsigned int encoderValue, RenderPressMode pressType){
	
	//Black the screen out
	gfx.fillScreen(BLACK);
	
	//Stay in the loop as long as backOut==0
	//backOut gets toggled when the enableDisable button is pressed
	while(!backOut){
		gfx.setTextColor(WHITE, BLACK);
		gfx.setCursor(0, 10);
		gfx.print(theMode); 

		gfx.setCursor(0, 20);
		gfx.print("rotaryMode=");
		gfx.print(rotaryMode);

		gfx.setCursor(0, 40);
		gfx.print("wPER=");
		gfx.print(wPER);
 
		gfx.setCursor(0, 50);
		gfx.print("frequency=");
		gfx.print(convertPWMToFrequency(wPER));

		gfx.setCursor(0, 60);
		gfx.print("wDC=");
		gfx.print(wDC);

		gfx.setCursor(0, 70);
		gfx.print("DutyCycle=");
		gfx.print(calculateDutyCycle(wPER,wDC));
	}
	
	//If we're here then the disable button should have been pressed
	// prohibit the button from causing problems
	//detachInterrupt(digitalPinToInterrupt(DISABLE_BUTTON));
	backOut = 0; //Reset this shit
  
	//This should really only toggle off the PWM output
	//REG_TCC1_CTRLA ^= TCC_CTRLA_ENABLE; //I don't like this here.
	//while (TCC1->SYNCBUSY.bit.ENABLE);
 
	//Give the Encoder back to tcMenu, there might be a better way
	//menuMgr.initForEncoder(&renderer, &menuPWMOut, 5, 6, 4);
 
	//Give back the display to the caller
	renderer.giveBackDisplay();
}


This isn't related to where I think my problem is but here's the configure timer function
void configurePWMWidthMode(int genericClock, int userPrescaler, int divideFactor, int userTopCounter, int userDutyCycle){
	REG_GCLK_GENDIV = GCLK_GENDIV_DIV(divideFactor) | GCLK_GENDIV_ID(genericClock);
	while (GCLK->STATUS.bit.SYNCBUSY);
	
	//Enables Generic Clock 4 and it Sets 48Mhz clock source
	REG_GCLK_GENCTRL = GCLK_GENCTRL_IDC | GCLK_GENCTRL_GENEN |GCLK_GENCTRL_SRC_DFLL48M |GCLK_GENCTRL_ID(genericClock);
	while (GCLK->STATUS.bit.SYNCBUSY);
	
	// Enable the port multiplexer for the digital out pin
	// See http://ww1.microchip.com/downloads/en/DeviceDoc/SAM_D21_DA1_Family_DataSheet_DS40001882F.pdf
	// Page 30 lists the Multiplex functions and pins - CHECK YOUR BOARD SCHEMATIC
	// I'm using - https://robotdyn.com/samd21-m0-mini-soldered.html
	// My Schematic https://robotdyn.com/pub/media/0G-00005516==SAMD21-MINI/DOCS/Schematic==0G-00005516==SAMD21-MINI.pdf
	// I'm using D2 which is PA14. Function F is TCC0
	PORT->Group[g_APinDescription[PWM_OUT].ulPort].PINCFG[g_APinDescription[PWM_OUT].ulPin].bit.PMUXEN = 1;
	PORT->Group[g_APinDescription[PWM_OUT].ulPort].PMUX[g_APinDescription[PWM_OUT].ulPin >> 1].reg = PORT_PMUX_PMUXO_F | PORT_PMUX_PMUXE_F;

	//Enable Generic clock 4 and tie it to TCC0 and TCC1
	// This can be abstracted more if this function is to really be able to configure other generic clocks besides 4
	REG_GCLK_CLKCTRL = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_ID_TCC0_TCC1;
	while (GCLK->STATUS.bit.SYNCBUSY);              // Wait for synchronization
	
	// Reverse the output polarity on all TCC1 outputs
	//Set for Single slope PWM operation: timers or counters count up to TOP value and then repeat
	REG_TCC1_WAVE |= TCC_WAVE_WAVEGEN_NPWM | TCC_WAVE_POL(0xF);
	while (TCC1->SYNCBUSY.bit.WAVE);               // Wait for synchronization
	
	// Each timer counts up to a maximum or TOP value set by the PER register,
	// this determines the frequency of the PWM operation:
	REG_TCC1_PER = userTopCounter;
	while (TCC1->SYNCBUSY.bit.PER);
	
	//The Compare and Capture registers determine the Actual Duty Cycle
	REG_TCC1_CC1 = userDutyCycle;
	while (TCC1->SYNCBUSY.bit.CC1);
	
	REG_TCC1_CC0 = userDutyCycle;
	while (TCC1->SYNCBUSY.bit.CC0);
	
	//enable interrupts
	REG_TCC1_INTENSET = TCC_INTENSET_OVF; //Set up interrupt at TOP of each PWM cycle
	
	enable_interrupts(); //enable in NVIC, make PWM output the highest priority
	
	// GCLK4 has already been enabled and wired to TCC1
	//Set the pre-scaler and enable the actual motherfucking output already!
	//REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER_DIV1 | TCC_CTRLA_ENABLE;
	REG_TCC1_CTRLA |= TCC_CTRLA_PRESCALER(userPrescaler) | TCC_CTRLA_ENABLE;
	while (TCC1->SYNCBUSY.bit.ENABLE);
	
}


I don't think I want have the encoder proxy for the actual "virtual" value of each of the 6 adjustments. I just want to know which way it was turned, and keep track of the mode.

I feel like I'm doing something out of order or missing can get passed to callback functions.

In any case I've been at it for 2 weeks now and have read everything on the site.

I can upload all the code no problem but this is pretty much the project.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
And here are my pin definitions
#define GFX_DC 8
#define GFX_CS 10
#define GFX_MOSI 11
#define GFX_MISO 12
#define GFX_CLK 13 //sck


#define PWM_OUT  2
#define ROTARY_SW  4 
#define ROTARY_CLK  5
#define ROTARY_DT  6

#define ENABLE_DISABLE_BUTTON PIN_WIRE_SCL
#define ROTARY2_CLK 7
#define ROTARY2_DT 9
#define ROTARY2_SW PIN_WIRE_SDA

#define MAX_ROTARY_MODES 1
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
The serial debugger isn't reporting any of the interrupt functions are being called even though they are on interrupt pins.

void setup() 
pinMode(ENABLE_DISABLE_BUTTON,INPUT_PULLDOWN);

//We're going to use switches so this needs to get initialized
//switches.initialise(ioUsingArduino(),true); //polled? IDK
switches.initialiseInterrupt(ioUsingArduino(), false);

//Enable/Disable button press brings the user back to the menu
// button hold disables output and resets all timer registers
switches.addSwitch(ENABLE_DISABLE_BUTTON, enableDisableButtonPressed); //pin 21 SCL

// Rotary 2 push button changes between adjustment modes on output screens
//ex: adjust pulse width / Duty cycle, adjust frequency / Duty cycle, adjust frequency / phase
switches.addSwitch(ROTARY2_SW, onRotary2SwitchPressed);

//Setup the encoder and tell it where to go when it gets indexed
//setupRotaryEncoderWithInterrupt(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

taskManager.addInterrupt(switches.getIoAbstraction(),ROTARY2_CLK,CHANGE);
taskManager.addInterrupt(switches.getIoAbstraction(),ROTARY2_SW, FALLING); //set as pullup
taskManager.addInterrupt(switches.getIoAbstraction(), ENABLE_DISABLE_BUTTON,RISING);


//setup screen color if wanted
prepareAdaMonoGfxConfigLoRes(&colorConfig);
gfx.initR(INITR_144GREENTAB); //Adafruit 1.44"
gfx.setRotation(1);

setupMenu();

I'm also confused about whats gets passed *to* those functions in the event of a switch, or an encoder
I have them defined like this. Otherwise I get errors.
void CALLBACK_FUNCTION enableDisableButtonPressed(pinid_t pin, bool heldDown) {//code}
void CALLBACK_FUNCTION onRotary2SwitchPressed(pinid_t pin, bool heldDown) {//code}
void CALLBACK_FUNCTION onRotary2Changed(int newValue){//code}


They definitely aren't being called and like always it's probably something silly, or way more complicated (to me)
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
Thanks for the detailed response. I’ll try and setup a dual rotary encoder arrangement on a 32 bit arm board as soon as I can. I will find the most similar hardware to what you have.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
Thank you for replying.

I'm just going in circles now. I over complicate the simple and oversimplify the complicated.

As far as I can see the Arduino M0 Pro and Arduino Zero are most similar.

If I'm sending the menu to another screen I would expect the interrupt/callbacks to run.

I thought it might have something to do with what was set as pullup or pulldown using pinMode() but it doesn't seem to make a difference either way.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
I got a few expanders in and will see how I do once I get those figured out. Then I can hopefully figure this thing out so I can try to figure the next thing out after that.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
I'm really sorry for the lack of responses this week, we've had a really busy few days, I've put some time aside to build the double encoder circuit later today and test it.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
No big deal @davecc. I appreciate any level of support for what look to be very useful libraries that cost me free dollars.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
I've just done the most simple sketch that uses two encoders. I have to say I'd not really tried this before, as this was contributed smilie It works well for me.

Can you try this sketch with your arrangements, ensure that enc1pinA and enc1pinB are on their own interrupt capable pins.

https://github.com/davetcc/tcLibraryDev/blob/master/twoEncodersMega/twoEncoders.ino

EDIT forgot to say that no expander was used here, everything directly connected to pins. However, you could very easily convert this sketch to use an expander should you want to. Let me know what your end goal is in terms of connecting the encoders, and I'll adjust accordingly and try again. I have a couple of PCF8574 and MCP23017's on hand to try it with.

Output of sketch from mega2560 (what I had to hand, but should work on most boards without change):

Starting setup
Adding switches
0:add 0 57 0
0:add 1 58 0
Setup encoder 1
Encoder 1 change 100
Setup encoder 2
Finished setup
Encoder 2 change 
-1
Encoder 2 change 
1
Encoder 2 change 
-1
Encoder 2 change 
1
Encoder 1 change 101
Encoder 1 change 105
Encoder 1 change 107
Encoder 1 change 109
Encoder 1 change 111
Encoder 1 change 113
Encoder 1 change 115
Encoder 2 change 
-1
Encoder 2 change 
-1
Encoder 2 change 
1
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
A few things I noticed on the twoEncoders.ino you provided.

You're a fan of the lambda functions. I understand them when I read them in general but I just don't use them but no need to get hung up there.

What does "auto" for us here?
auto secondEncoder = new HardwareRotaryEncoder(enc2pinA, enc2pinB, [](int newVal) {
        Serial.println("Encoder 2 change ");
        Serial.println(newVal);
    });


On the same example before you put the 2nd encoder in slot 1 you add the interrupt using
taskManager.addInterrupt(ioUsingArduino(), enc1pinB, CHANGE);


While on this page and this page you did it in this way which I followed

taskManager.addInterrupt(switches.getIoAbstraction(), additionalPinA, CHANGE);


which I just don't get.

So far I'm not understanding what "switches.getIOAbstraction()" actually returns in the previous line of code vs the other where ioUsingArduino() was used instead. If there are multiple encoders or switches how does taskManager.addInterrupt() actually know what switch you're talking about if you have several? This may be my main sticking point.

I would like my 2nd encoder, it's button, and a separate momentary button to each have a global variable name. While I understand reserving an encoder using "HardwareRotaryEncoder* encoderName" and then assigning it later, how can I add a "SwitchInput* buttonName" or is that just not the way? I tried creating IoAbstraction objects manually and then assigning a key to them but I have enough problems as it is lol.

Anyway thanks for your thoughtful comments and attention.
kritischer


Joined: Oct 4, 2020
Messages: 25
Offline
I had sent you a message through your site forum asking about commercial options. I'd be interested to know what's available.

To be honest I've only been at this for a few months and as I whore my way around a several different architectures and environments I've come to appreciate Arduino for making libraries and boards accessible but have come to rather despise how the layers of abstraction make it hard to act with the hardware on a lower level. Don't get me wrong I'm grateful we live in such a wonderful time and I do understand there is a trade off between accessible projects with fast learning curves and understanding how to configure the parts of the system Arduino hides in %appdata%. I'm trying move away from it in favor of Atmel Studio 7 and the ASF. To bridge the gap I've been using Visual Micro but I haven't been able to figure out how to use the Actual ASF along with any Arduino libraries. Do you have any advice as to how I might use these libraries in AS7? I would not expect it for free.
davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
You're a fan of the lambda functions. I understand them when I read them in general but I just don't use them but no need to get hung up there.


Yep comes from doing a lot of Java, C# and Scala!

What does "auto" for us here?


It is C11 type inference, it basically infers the type at compile time without needing write it out twice, so in this case:

auto secondEncoder = new HardwareRotaryEncoder(enc2pinA, enc2pinB, [](int newVal)


could be thought of as:

HardwareRotaryEncoder* secondEncoder = new HardwareRotaryEncoder(enc2pinA, enc2pinB, [](int newVal)


On the same example before you put the 2nd encoder in slot 1 you add the interrupt using


Yep apologies, my mistake, I should really have used switches.getIoAbstraction()

There is no difference in this case, because when we initialised switches we set the IoAbstraction parameter to ioUsingArduino()

So far I'm not understanding what "switches.getIOAbstraction()" actually returns in the previous line of code vs the other where ioUsingArduino()


"switches.getIoAbstraction()" returns whatever was passed to the switches.initialise(..) function. In this case it was:

switches.initialise(ioUsingArduino(), true);


The IoAbstraction is nothing more than a means of setting up and using a devices IO, there are abstractions for Arduino pins, and there are abstractions for IO expanders, there's even one that combines both into a single IoAbstraction.

davetcc


Joined: Jan 19, 2019
Messages: 686
Offline
I you a message through your site forum asking about commercial options. I'd be interested to know what's available.


I sent you a private message around this in the forum. Just go to private messages at the top of the page to view it. I’ve tried to provide a bit of constructive feedback and some options.

Thanks
Dave
 
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.