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 board
https://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.