How to Prevent Variable Roll Over

There is probably a very simply solution to this problem, but I'm going to get it over with and ask.

Basically I'm trying to keep a Variable (right now its an int), from going to back to 0 or 2000 when I reaches is limit. I just want the value to stop at 2000 or 0 when it reaches the end.

I'm designing a digital adjustable power supply based on Arduino, with rotary encoders and a Dual DAC (MCP4912) to control the CC/CV. I've got the DAC working just fine with a library I found for the MCP49xx series with the rotary encoders, but when I over turn the knob its either pulls the voltage to 5V from 0.5V, or the opposite way.

I've done alot of looking around on the Internet, but can not find anything about my issue.

Thanks In Advance,

If anyone has any advice on how to build this power supply I would love to hear it, as well as suggestions for the PSU circuit as I haven't put alot of time into that part quite yet. I'm shooting for 0V- 25V and 0A - 2A if possible, with individual knobs for CV/CC control. An LCD with set values and output values are a must, I'd really like to implement a Graphical LCD with a good looking UI.

Use an if statement to discover whether the variable has reached your end points, and only increment/decrement it if it hasn't.

PeterH: Use an if statement to discover whether the variable has reached your end points, and only increment/decrement it if it hasn't.

You could also do something like this (depends on what is returned from the comparison - might have to flip signs):

x += (1 - (x >= limit));

Basically, this increments "x" by "1" until "x" is greater-than-or-equal to some defined "limit" value, in which case the comparison returns "1". When that occurs, it will try to increment "x" by "0" - not incrementing it at all.

A similar statement could be made for decrementing; although such "concise" code may not be not be necessary for this application, it does illustrate how you can achieve a result in a slightly non-traditional manner.

:D

Thanks for the Suggestions,

I actually was able to get the lower end of my variable to stay at 0 when I turn the encoder too far, but have not been able to get it to stay at the top when I reach the limit. The constrain() function works well for the bottom limit, but does not work for the top. I tried constrain before, but I had the syntax wrong.

void loop() 
{

    valtoWrite = map(encoderValue, 0, 2000, 0, 1023);
  
    valtoWrite = constrain(valtoWrite, 0, 2000);
     
    ValtoDisplay = valtoWrite * 0.0047116;
     
    ValtoDisplay = constrain(ValtoDisplay, 0, 4.82);
     
    dac.outputA(valtoWrite);
    dac.outputB(valtoWrite);
    dac.latch();
     
    mySerial.print("VSET:");
    mySerial.print(ValtoDisplay);
    mySerial.print("V");
    delay(50);
    mySerial.write(12);
    delay(5);
  // dac.outputB(384);
  // dac.latch(); // To synchonize the two outputs
}

Right now I have my LCD displaying the Voltage very accurately with just 1 line of code and a floating point variable. I'm very happy with how that is working. Now I just need to get the other Encoder wired up to dac.outputB() and get my lcd to display my CC setting accurately.

If you have any idea why constrain() will limit my bottom limit but not my top limit please let me know.

Thanks,

Can you show your whole sketch please? How is ValtoDisplay declared?

How to use this forum

You are mapping the value in encoderValue to the range 0 to 1023. The result, in valToWrite, may be outside that range. Then, you are constraining valToWrite to the range 0 to 2000. Why? Shouldn't you be constraining it to the range 0 to 1023?

Kinda new at this whole arduino stuff so be nice. My current sketch is a mix between 2 sample sketches for libraries and a tutorial on rotary encoders.

// Example for the MCP49x2 *dual* DACs

#include <SoftwareSerial.h>
#include <SPI.h>         
#include <DAC_MCP49xx.h>

const int TxPin = 6;
SoftwareSerial mySerial = SoftwareSerial(255, TxPin);

int encoderPin1 = 2; 
int encoderPin2 = 3;
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
long lastencoderValue = 0;
int lastMSB = 0;
int lastLSB = 0;

float ValtoDisplay; 


// The Arduino pin used for the slave select / chip select
#define SS_PIN 10

int valtoWrite = 0;

// The Arduino pin used for the LDAC (output synchronization) feature
#define LDAC_PIN 7

// Set up the DAC. 
// First argument: DAC model (MCP4902, MCP4912, MCP4922)
// Second argument: SS pin (10 is preferred)
// (The third argument, the LDAC pin, can be left out if not used)
DAC_MCP49xx dac(DAC_MCP49xx::MCP4912, SS_PIN, LDAC_PIN);

void setup() {
  
  pinMode(TxPin, OUTPUT);
  digitalWrite(TxPin, HIGH);
  mySerial.begin(9600);
  mySerial.write(12);
  delay(5);
  mySerial.write(17);
  mySerial.print("Adjustable PSU");
  delay(2000);
  mySerial.write(12);
  delay(5);
  
  pinMode(encoderPin1, INPUT); 
  pinMode(encoderPin2, INPUT);



  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  digitalWrite(encoderPin2, HIGH); //turn pullup resistor on

  //call updateEncoder() when any high/low changed seen
  //on interrupt 0 (pin 2), or interrupt 1 (pin 3) 
  attachInterrupt(0, updateEncoder, CHANGE); 
  attachInterrupt(1, updateEncoder, CHANGE);
  
  // Set the SPI frequency to 1 MHz (on 16 MHz Arduinos), to be safe.
  // DIV2 = 8 MHz works for me, though, even on a breadboard.
  // This is not strictly required, as there is a default setting.
  dac.setSPIDivider(SPI_CLOCK_DIV16);
  
  // Use "port writes", see the manual page. In short, if you use pin 10 for
  // SS (and pin 7 for LDAC, if used), this is much faster.
  // Also not strictly required (no setup() code is needed at all).
  dac.setPortWrite(true);

  // Pull the LDAC pin low automatically, to synchronize output
  // This is true by default, however.
  dac.setAutomaticallyLatchDual(true);
}


void loop() 
{

    valtoWrite = map(encoderValue, 0, 2000, 0, 1023);
  
    valtoWrite = constrain(valtoWrite, 0, 2000);
     
    ValtoDisplay = valtoWrite * 0.0047116;
     
    ValtoDisplay = constrain(ValtoDisplay, 0, 4.82);
     
    dac.outputA(valtoWrite);
    dac.outputB(valtoWrite);
    dac.latch();
     
    mySerial.print("VSET:");
    mySerial.print(ValtoDisplay);
    mySerial.print("V");
    delay(50);
    mySerial.write(12);
    delay(5);
  // dac.outputB(384);
  // dac.latch(); // To synchonize the two outputs
}

void updateEncoder(){
  int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit

  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

  lastEncoded = encoded; //store this value for next time
}

Thanks for the advice, I’ll check back later.

Looks like you did nothing to address the concerns brought by PaulS.

No I have not yet done that yet, haven't had to time sit down and edit my code today. I think paul is right tho, it makes sense now that the bottom limit works but not the top.

Nick asked to see the whole sketch so I just posted it really quick. I'll do some editing later tonight, and post my results.

Wouldn’t it be better to constrain the encoder value where you add/subtract from it?

eg, change:

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

to:

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011)   
    {
    if (encoderValue < 1023)
      encoderValue++;
    }
  else
    {
    if (encoderValue > 0)
      encoderValue--;  
    }

I removed the second lot of tests, after all what else could those values be?

That part came from a tutorial online for rotary encoders. I'll give that a try too. Another problem I'm having is that i need 2 encoders, and the uno only has 2 external interrupts, so I might need to find another way of interfacing the encoders without interrupts or figure something else out.

Thanks Paul for the fix, That was the problem. I knew it would be a simple fix. I'll post the code later tonight.

Here is my most current version of Code. The constrain() is working perfectly now, I can now change the value in the map() function to adjust for a course or fine adjustment and everything stays very accurate.

Nick,
I tried your suggestion, but it didn’t work right. It would change the value but not reliably at all. I could get it to adjust, but It only adjusted the voltage when I spun the encoder very fast, slow movements on the encoder would not be registered. I’m not sure why its not working, but it you know why it was wigging out please let me know.

If anyone has suggestions of how to add another encoder with interrupts let me know. I was planning on pulling out the ATmega328p out of the arduino and running it straight from the PCB when I’m done, but now I’m either going to go for a Mega chip or use a button to cycle between CC setting and CV setting.

// Example for the MCP49x2 *dual* DACs

#include <SoftwareSerial.h>
#include <SPI.h>         
#include <DAC_MCP49xx.h>

const int TxPin = 6;
SoftwareSerial mySerial = SoftwareSerial(255, TxPin);

int encoderPin1 = 2; 
int encoderPin2 = 3;
volatile int lastEncoded = 0;
volatile long encoderValue = 0;
long lastencoderValue = 0;
int lastMSB = 0;
int lastLSB = 0;

float ValtoDisplay; 


// The Arduino pin used for the slave select / chip select
#define SS_PIN 10

int valtoWrite = 0;

// The Arduino pin used for the LDAC (output synchronization) feature
#define LDAC_PIN 7

// Set up the DAC. 
// First argument: DAC model (MCP4902, MCP4912, MCP4922)
// Second argument: SS pin (10 is preferred)
// (The third argument, the LDAC pin, can be left out if not used)
DAC_MCP49xx dac(DAC_MCP49xx::MCP4912, SS_PIN, LDAC_PIN);

void setup() {
  
  pinMode(TxPin, OUTPUT);
  digitalWrite(TxPin, HIGH);
  mySerial.begin(9600);
  mySerial.write(12);
  delay(5);
  mySerial.write(17);
  
  pinMode(encoderPin1, INPUT); 
  pinMode(encoderPin2, INPUT);



  digitalWrite(encoderPin1, HIGH); //turn pullup resistor on
  digitalWrite(encoderPin2, HIGH); //turn pullup resistor on

  //call updateEncoder() when any high/low changed seen
  //on interrupt 0 (pin 2), or interrupt 1 (pin 3) 
  attachInterrupt(0, updateEncoder, CHANGE); 
  attachInterrupt(1, updateEncoder, CHANGE);
  
  // Set the SPI frequency to 1 MHz (on 16 MHz Arduinos), to be safe.
  // DIV2 = 8 MHz works for me, though, even on a breadboard.
  // This is not strictly required, as there is a default setting.
  dac.setSPIDivider(SPI_CLOCK_DIV16);
  
  // Use "port writes", see the manual page. In short, if you use pin 10 for
  // SS (and pin 7 for LDAC, if used), this is much faster.
  // Also not strictly required (no setup() code is needed at all).
  dac.setPortWrite(true);

  // Pull the LDAC pin low automatically, to synchronize output
  // This is true by default, however.
  dac.setAutomaticallyLatchDual(true);
}


void loop() 
{

    valtoWrite = map(encoderValue, 0, 1500, 0, 1023);
  
    valtoWrite = constrain(valtoWrite, 0, 1023);
     
    ValtoDisplay = valtoWrite * 0.004692;
     
    ValtoDisplay = constrain(ValtoDisplay, 0, 4.80);
     
    dac.outputA(valtoWrite);
    dac.outputB(valtoWrite);
    dac.latch();
     
    mySerial.print("VSET:");
    mySerial.print(ValtoDisplay);
    mySerial.print("V");
    delay(50);
    mySerial.write(12);
    delay(5);
  // dac.outputB(384);
  // dac.latch(); // To synchonize the two outputs
}

void updateEncoder(){
  int MSB = digitalRead(encoderPin1); //MSB = most significant bit
  int LSB = digitalRead(encoderPin2); //LSB = least significant bit

  int encoded = (MSB << 1) |LSB; //converting the 2 pin value to single number
  int sum  = (lastEncoded << 2) | encoded; //adding it to the previous encoded value

  if(sum == 0b1101 || sum == 0b0100 || sum == 0b0010 || sum == 0b1011) encoderValue ++;
  if(sum == 0b1110 || sum == 0b0111 || sum == 0b0001 || sum == 0b1000) encoderValue --;

  lastEncoded = encoded; //store this value for next time
}

You can use pin-change interrupts as well, although they are slightly less fine-grained than interrupts 0 and 1. By which I mean any change to a group of 8 pins causes an interrupt, then you have to figure out which pins changed.

It's possible to use a single interrupt pin plus a regular pin per encoder. The theory is that each time the interrupt is triggered you read and compare the interrupt pin with the other pin. If they are the same level, the movement was in one direction. If they were different the movement was in the other direction. Some poeple claim that this doesn't work so well but it has worked fine for me. Hardware debouncing is necessary though.

Here's a working exampe along with some other drivel: http://perhof.wordpress.com/2012/11/01/using-rotary-encoders-with-arduino/

If you can get the debounce working you can expand the code with a second interrupt for a second encoder.

Yes, I came across that site during my research. I have yet to give it a try though, I found a library that I thought would work, but haven't got it to work with my dac and Lcd yet. If I can't that to work on the 328p, do you know If the 168p has more than 2 interrupts? Or would it be difficult to burn a mega bootloader onto a Atmega 2560 and Mount it on a pcb like many have done with the 328 and 168? I got a feeling the code will be too bulky for a 328 without 4 dedicated interrupts or a serious debouncing set up for it to work reliably. I'm leaning towards the mega 2560 right now if I can get it to work on a single pcb.

do you know If the 168p has more than 2 interrupts?

It has the same number (2) of external interrupts as the c328, just less memory.

Or would it be difficult to burn a mega bootloader onto a Atmega 2560 and Mount it on a pcb like many have done with the 328 and 168?

If you can manage surface mount soldering, then, yes a 2560 is a good choice. There are other DIP chips, like the 644P that might be suitable, if surface mount is not an option.

I really don't understand why you don't develop the code/project using a real Arduino, first, and then migrate to a separate chip on a custom PCB. But, that's just me, I guess.

Oh that exactly what I'm doing. I'm building everything up on an arduino then I'll take the chip off and install it on a pcb.

Are the other chips like the 644p compatible with arduino? I would definitely prefer DIP, but I'm not scared of surface mount. I just want everything to be right, no cutting corners because I wouldn't go with surface mount. If I skipped on the arduino requirement, would it be more difficult to learn to program non arduino avr's? I've always sticked with arduino's limited line of chips because I knew how to use them more then I did anything else.

I like the atmega 644p idea, it is the perfect in between for the 328 and the 2560. Thanks for the suggestion, this for sure deserves some more research.