I need help with my countdown timer project!

Thats a good sign. That is 0100 (Just where on and off are reversed).

Have you changed setDigit() and setSegments() back to how you had them before?

void setDigit (int digit)

{

for (int i = 0; i < 4; i++)

{

digitalWrite(displayPins*, (digit != i));*
}
}
void setSegments (int n)
{
for (int i = 0; i < 8; i++)
{
digitalWrite(segmentPins, ! digits[n]); //write the 8 outputs
}
delay(1); //leave the display on a little bit (without this they were only on for ~1uS - too short to be visible
}
You aren’t useless at it, the program is fine on the whole, there are just little things that you will pick up with time.

Sorry about the muffed URL - thanks for fixing it.

Your questions point out a real weakness in the Arduino “loop” approach. It’s actually not an arduino problem per se but I see Arduino people getting trapped by it all the time and the tutorials don’t really help that much.

When driving all your program activities from a single loop you will often run into places where it blocks doing one thing or another. This is where you will need to learn about interrupts (actually, multitasking). In order for your system to operate smoothly, there are 3 processes going on that MUST proceed concurrently: display multiplexing, input device reading and counting. Each of these can be driven from an interrupt (though, you could do one in the loop if you wished). You can probably get the thing working with out interrupts but the code will be inflexible. Every time you tweak it, something will change or break.

Display multiplexing is the most complex (though not that hard). First you pick an update rate. I like 60hz but anything above 30 seems to be ok. Movies run at 24 hz. Lets use 50 hz because the numbers work out simply. 50 hz rate = 20 mS period so you set up a timer to interrupt every 20 mS. In each 20 mS time slot you illuminate a different digit. So your interrupt routine (often called an Interrupt service routine or ISR) turns off the current digit, moves to the next one and turns on the appropriate segment(s). That’s all it does, it just goes on forever lighting up each digit in turn. Because it goes so fast, it appears that all the digits are on. For debugging, I like to set up a really slow rate (like 1 hz) so I can see each digit getting lighted in turn.

Reading the input devices is similar. You want to look at it/them frequently so that you don’t miss anything. If you don’t check quickly enough, the input feels laggy at best. Since I do debouncing in my input routines, I like a 10 mS period. Your ISR checks the input device and updates a globally accessible variable (or a class object like quad.direction, quad.clicks - you’d have to create the class).

By the way, I’ve noticed that a number of the quad encoder tutorials use interrupts from the quad’s switches but that’s a bad idea as switch bounce can cause a lot of interrupts in a very short period of time. Also, if you have multiple switches, you will often run out of interrupt pins. Using a timer ISR to poll the switches works pretty well.

Counting via an interrupt routine isn’t strictly necessary but you want to update the actual count as precisely as possible since humans can see “jitter” pretty easily and it is disturbing. Jitter in this case would be delay in showing the current count. Even a couple hundred milliseconds delay is perceivable. So, your ISR simply would update the global time variable (or again, via a class object like timer.count) and your display multiplexing ISR would pick it up the next time it runs (every 20 mS).

I know this may seem like a lot of changes but you will get a smoothly running system. To me, cobbling something together is dissatisfying and putting together a smoothly oiled machine gives joy in craftsmanship.

Phil

I changed the values back to what they were before and nothing appeared on the display unfortunately.

How do I go about setting up an interrupt then?

My mistake, only the setSegments() one should have been changed back, try this:

void setDigit (int digit)
{
 for (int i = 0; i < 4; i++)
 {
 digitalWrite(displayPins[i], (digit == i));
 }
}

void setSegments (int n)
{
 for (int i = 0; i < 8; i++)
 {
 digitalWrite(segmentPins[i], ! digits[n][i]); //write the 8 outputs
 }
 delay(1); //leave the display on a little bit (without this they were only on for ~1uS - too short to be visible
}

Nope still doesn’t display anything if I change segment only.

Ok then, one more combination then is to only change the setDigit(), so the two as follows. What a bizzare problem :S :shock:

void setDigit (int digit)
{
 for (int i = 0; i < 4; i++)
 {
 digitalWrite(displayPins[i], (digit != i));
 }
}

void setSegments (int n)
{
 for (int i = 0; i < 8; i++)
 {
 digitalWrite(segmentPins[i], digits[n][i]); //write the 8 outputs
 }
}

Philba:
By the way, I’ve noticed that a number of the quad encoder tutorials use interrupts from the quad’s switches but that’s a bad idea as switch bounce can cause a lot of interrupts in a very short period of time. Also, if you have multiple switches, you will often run out of interrupt pins. Using a timer ISR to poll the switches works pretty well.

Phil

I was pondering how I’d implement a quadrature decoding scheme using an MCU and I basically came up with something very similar to that which was given as the “Fast encoder reading: using just interrupts” at your URL. About the only differences are that I’d disable the “A interrupt” when the clocking edge was detected and enable the “B interrupt” (in the A ISR). Then when any B edge is detected, the position/count is incremented/decremented (depending on the state of B), the “B interrupt” is then disabled and the “A interrupt” re-enabled (in the B ISR). This effectively does the debouncing and prevents time wasted servicing useless interrupts. So long as any position dithering is less than 1/2 the resolution (ie - for a 24 count/rev encoder, < 7.5 deg) then it won’t falsely ratchet up/down the position. I’m sure with a little thought that 2X decoding (doubling the resolution) could be done as well.

The relevant parts of carnevaledaniele’s code reproduced below

//To speed up even more, you may define manually the ISRs
// encoder A channel on interrupt 0 (arduino's pin 2)
  attachInterrupt(0, doEncoderA, RISING);
// encoder B channel pin on interrupt 1 (arduino's pin 3)
  attachInterrupt(1, doEncoderB, CHANGE); 
}

void loop()
{  
 //your stuff....ENJOY! :D
}

//you may easily modify the code  get quadrature..
//..but be sure this whouldn't let Arduino back! 
void doEncoderA()
{
     PastB ? encoder0Pos--:  encoder0Pos++;
}

void doEncoderB()
{
     PastB = !PastB; 
}

I now return you to the previously scheduled discussion on display problems. :mrgreen:

I think that I am going to try and get the Rotary Encoder working with the 30 seconds version of the code. That displays digits correctly.

Hmmm, that’s not a bad idea at all. effectively, you get an int from a switch and turn off ints from that one until the other one has interrupted - effectively ignoring all the bounces. Only take rising ints. The only downside I see to that is when the shaft reverses you miss 1/2 a quadrature period (misses the disabled int’s edge). Well, actually you miss, on average, 3/8s of period. If the quad’s resolution is low and the rotational speed is low it might be noticeable. Kind of analogous to backlash in a CNC machine. I wouldn’t use it if the quad encoder was on a motor shaft but for user input (front panel knob for example) it might just be ok.

Yeah, the schemes from that link have poor resolution in that they only use half the information. 24 count per channel is actually 15 degrees - pretty coarse. It’s very straightforward to use all phases of the quad to determine direction.

Personally, I’d still run the quad off of a timer interrupt but that’s more preference at this point.

One thing I was thinking about. Some quad encoders use optical or hall sensors. If that’s the case then there may not be a bounce problem at all (assuming they do something rational like use a schmidt trigger). I know some of the cheap ones are mechanical, though. This one http://www.sparkfun.com/products/9117 looks to be mechanical. While this one http://www.sparkfun.com/products/10932 is probably optical. The cheap one only has 12 counts/rev so it’s going to be really bad on the backlash

Mee_n_Mac:
I was pondering how I’d implement a quadrature decoding scheme using an MCU and I basically came up with something very similar to that which was given as the “Fast encoder reading: using just interrupts” at your URL. About the only differences are that I’d disable the “A interrupt” when the clocking edge was detected and enable the “B interrupt” (in the A ISR). Then when any B edge is detected, the position/count is incremented/decremented (depending on the state of B), the “B interrupt” is then disabled and the “A interrupt” re-enabled (in the B ISR). This effectively does the debouncing and prevents time wasted servicing useless interrupts. So long as any position dithering is less than 1/2 the resolution (ie - for a 24 count/rev encoder, < 7.5 deg) then it won’t falsely ratchet up/down the position. I’m sure with a little thought that 2X decoding (doubling the resolution) could be done as well.

Conway6288:
I think that I am going to try and get the Rotary Encoder working with the 30 seconds version of the code. That displays digits correctly.

I think this is a good idea, As was suggested above first try to figure out functional blocks that need to be encoded as software, This becomes easier to do if you look at the existing code and figure out what it’s trying to do and how it does it. You can learn a lot this way, even if you decide not to copy the coding. Your project is simple enough you could make a flowchart of the functional blocks, each block to later be a piece of software. If you already have software that does that blocks function … and works, why then things get easier. ’

So I think you presently have a “function” that counts down, one that buzzes the buzzer and one that updates the display with the time being counted down (all working). Now you need a function that reads the encoder and stores the read-in value and then one to kick off the prior count down process, initialized with the value you stored from the encoder. Some questions that need answering … will you want to stop the countdown once it’s been started ? Will you want to be able to save the value entered from run to run ? Since you have all this counting ability, do you want a stopwatch function as well ?

If you are still interested, I have just been making an arduino timelapse camera controller, whic required a seven segment display. I thought I would share the code if you want to see it.

This used a timer interrupt to refresh the display at i think a refresh rate of 5kHz. I have stripped the code down to just the display portion here:

/*
  Created by Thomas Carpenter 2012
  
=====================================================
  Things to note:
   - millis(); will be highly inaccurate due to the frequent interrupts for the screen.
   > millisecs; should be used instead - works exactly the same, but unaffected by screen
  
=====================================================
   - strn2Disp(char string); is used to print a text string to the display.
   
   > strn2Disp("HELLO"); would display HELLO on the screen, depending of the number of digits (eg, 3 digits would display "HEL") 
  
=====================================================
   - num2Disp(int number, byte decimalPoint); is used to print a positive or negative number of the screen.
   
   > num2Disp(-100); would print [-][1][0][0] - Note, no decimal point
   > num2Disp(-100,0); would print [-][1][0][0.] - Note, decimal point after last digit
   > num2Disp(1234); would print [1][2][3][4]
   > num2Disp(100,1); would print [0][1][0.][0] - Note the number is considered as 10ths
   > num2Disp(100,2); would print [0][1.][0][0] - Note the number is considered as 100ths
   > num2Disp(1234,2); would print [1][2.][3][4] - Note the number is considered as 100ths
   
   > If the number is too big to be displayed, e.g. trying to display 1234 on a 3 digit display, it is converted to the maximum value the display can take,
     so in this case, 1234 would be displayed as 999 on a 3 digit display.
  
=====================================================
   - displayedChars[2][DIGITS]; contains what is to be printed to the screen. This can be directly editied
   
   > displayedChars[0][3] = 'A';
     displayedChars[1][3] = 0;   would set the third digit to [A]
   > displayedChars[0][2] = 'C';
     displayedChars[1][2] = 1;   would set the second digit to [C.] - Note the decimal point/full stop
   
   > In otherwords, displayedChars[0][x] contains the xth character to be printed, and
                    displayedChars[1][x] controls whether the xth decimal point is one (multiple points can be on)
=====================================================
*/





//Change these too suit==================================================================================================================================

//Number of display digits
#define DIGITS 3

//use "#define COMAN" for common Anode, or "#define COMCATH" for common Cathodes
#define COMAN

//Display Pins
const byte segmentPins[8] = {9,11,17,13,12,10,16,18}; //{a,b,c,d,e,f,g,DP};
const byte commonPins[DIGITS] = {2,1,3}; //{Digit '0',Digit '1',Digit '2',....,Digit 'n-1'}; - Add one for each digit. Example Display: [n-1]...[2][1][0]

//=======================================================================================================================================================





//Dont Change these!=====================================
volatile unsigned long millisecs; //millis() is ruined by the screen refresh interrupt, so creating my own

#if defined COMAN
#define OFF LOW
#define ON HIGH
#elif defined COMCATH
#define OFF HIGH
#define ON LOW
#else
//If neither defined, it defaults to common anode
#define COMAN
#define OFF LOW
#define ON HIGH
#endif

#define MAPSIZE 40
void num2Disp(int num, byte decimalPoint = DIGITS);
void strn2Disp(char str[]);
void updateDisplay();
void configureTimer();
const int maxPositive = pow(10,DIGITS) - 1;
const int maxNegative = pow(10,DIGITS - 1) - 1; 
char displayedChars[2][DIGITS];
char asciiNumbers[10] = {'0','1','2','3','4','5','6','7','8','9'};
//=======================================================


//You can add custom characters here, just increase 'custom' to how many custom characters there are,
//and add the character to the end of the list.
//Note: a,b,c,d,e,f,g     are the segments, where 1 = ON.
//      ID                is the ASCII character it represents.
const char custom = 0;
const char characterMap[MAPSIZE + custom][8] = {
// a b c d e f g ID
  {1,1,1,1,1,1,0,'0'},
  {0,1,1,0,0,0,0,'1'},
  {1,1,0,1,1,0,1,'2'},
  {1,1,1,1,0,0,1,'3'},
  {0,1,1,0,0,1,1,'4'},
  {1,0,1,1,0,1,1,'5'},
  {1,0,1,1,1,1,1,'6'},
  {1,1,1,0,0,0,0,'7'},
  {1,1,1,1,1,1,1,'8'},
  {1,1,1,1,0,1,1,'9'},
  {0,0,0,0,0,0,0,' '},
  {0,0,0,0,0,0,0,'.'},
  {0,0,0,0,0,0,1,'-'},
  {0,0,0,1,0,0,1,'='},
  {1,1,1,0,1,1,1,'A'},
  {1,1,1,1,1,1,1,'B'},
  {0,0,0,1,1,0,1,'C'},
  {0,1,1,1,1,0,1,'D'},
  {1,0,0,1,1,1,1,'E'},
  {1,0,0,0,1,1,1,'F'},
  {1,0,1,1,1,1,0,'G'},
  {0,0,1,0,1,1,1,'H'},
  {0,0,1,0,0,0,0,'I'},
  {0,0,1,1,0,0,0,'J'},
  {0,0,0,0,0,0,0,'K'},
  {0,0,0,1,1,1,0,'L'},
  {0,0,0,0,0,0,0,'M'},
  {0,0,1,0,1,0,1,'N'},
  {0,0,1,1,1,0,1,'O'},
  {1,1,0,0,1,1,1,'P'},
  {1,1,1,0,0,1,1,'Q'},
  {0,0,0,0,1,0,1,'R'},
  {1,0,1,1,0,1,1,'S'},
  {0,0,0,1,1,1,1,'T'},
  {0,0,1,1,1,0,0,'U'},
  {0,0,0,0,0,0,0,'V'},
  {0,0,0,0,0,0,0,'W'},
  {0,0,0,0,0,0,0,'X'},
  {0,1,1,1,0,1,1,'Y'},
  {1,1,0,1,1,0,1,'Z'}
};




//Main Program--------------------------------------------------------------------------------

void setup() {
  //Display (Dont change)=================================================
  for(byte i = 0;i < 8;i++){
    pinMode(segmentPins[i],OUTPUT); 
  }
  for(byte i = 0;i < DIGITS;i++){
    pinMode(commonPins[i],OUTPUT);
    digitalWrite(commonPins[i],OFF); //Digit off
  }
  for(byte i = 0;i < DIGITS;i++){
    displayedChars[0][i] = ' '; //Display is blank to begin with
    displayedChars[1][i] = 0; //No Decimal Point
  }
  configureTimer();
  //======================================================================
  
  //rest of setup here
}

void loop() {
  //main program
}

//--------------------------------------------------------------------------------------------





//Display--------------------------------------------------------------------------------

void strn2Disp(char str[]){
  char string[DIGITS]; //Create input array
  strncpy(string,str,DIGITS); //Truncate or pad the input string
  char *input = string; //Pointer to the input string
  char *output = displayedChars[0] + DIGITS; //Pointer to the last character in display string
  for(char i = 0; i < DIGITS;i++){
    *--output=*input++; //Copy string to display, reversing it as digits are in reverse order
  }
  for(char i = DIGITS - 1;i >= 0;i--){
    if(displayedChars[0][i] == '.'){
      displayedChars[1][i] = 1;
    } else {
      displayedChars[1][i] = 0;
    }
  }
}


void num2Disp(int num, byte decimalPoint){
  byte sign = 0;
  if(num > maxPositive){
    num = maxPositive; //Max displayable number
  } else if(num < 0){
    sign = 1;
    displayedChars[0][DIGITS - 1] = '-'; //Minus sign as most significant
    if(num < -maxNegative){
      num = maxNegative; //If there is a sign, then |max| number is 999 
    } else {
      num = -num; //Switch to positive as sign taken into account
    }
  }
  for(byte digit = 0;digit < (DIGITS - sign);digit++){
    //Convert the remainder when divided by 10 into ASCII and save it to the digit
    byte digitNumber = num % 10;
    if(decimalPoint == digit){
      displayedChars[1][digit] = 1; //decimal point on this one
    } else {
      displayedChars[1][digit] = 0; //No decimal point
    }
    displayedChars[0][digit] = asciiNumbers[digitNumber];
    num /= 10; //Integer divide the number by 10 to reveal next digit 
  }
}

void updateDisplay(){
  for(byte digit = 0;digit < DIGITS;digit++){ //Count through each display digit
    byte ID;
    for(ID = 0;ID < (MAPSIZE+custom);ID++){ //lookup character ID
      if(characterMap[ID][7] == displayedChars[0][digit]){
        break; //ID found, so break loop early to save time
      }
    }
    if(ID == (MAPSIZE+custom)){
      ID = 10; //blank digit as non printable character
    }
    for(byte segment = 0;segment < 7;segment++){
#if defined COMAN
      digitalWrite(segmentPins[segment],!characterMap[ID][segment]); 
#elif defined COMCATH
      digitalWrite(segmentPins[segment],characterMap[ID][segment]); 
#endif
    }
#if defined COMAN
    digitalWrite(segmentPins[7],!displayedChars[1][digit]); 
#elif defined COMCATH
    digitalWrite(segmentPins[7],displayedChars[1][digit]); 
#endif
    digitalWrite(commonPins[digit],ON); //digit on
    for(byte j = 0;j < 100;j++){
      //leave the digit on for a little bit
    } 
    digitalWrite(commonPins[digit],OFF); //digit off
  }
}

//--------------------------------------------------------------------------------------------





//Timer Interrupt-----------------------------------------------------------------------------

/*Enable the the timer interrupt to be used for refreshing the screen at 1ms intervals*/
void configureTimer(){
  TIMSK2 &= ~(1<<TOIE2); //disable timer so it can be configured
  //set to normal counting mode
  TCCR2A &= ~((1<<WGM21) | (1<<WGM20));
  TCCR2B &= ~(1<<WGM22);
  //Clock source is system clock
  ASSR &= ~(1<<AS2);
  //Disable compare interrupt (only interested in overflow)
  TIMSK2 &= ~(1<<OCIE2A);
  //Set prescaler to F_CPU/64
  TCCR2B |= (1<<CS22); //1
  TCCR2B &= ~(1<<CS21);//0
  TCCR2B &= ~(1<<CS20);//0
  //This equates to 0.2ms exactly per overflow
  TCNT2 = 206;
  //Enable timer and overflow interrupt
  TIMSK2 |= (1<<TOIE2);
  millisecs = 0;
}

/*Timer Interrupt Vector (1/5th millisecond)*/
ISR(TIMER2_OVF_vect) {
  static byte fifths = 0;
  TCNT2 = 206; //reset timer straight away to avoid compounding errors
  fifths++; //another fifth of a millisecond passed
  if(fifths == 5){
    fifths = 0;
    millisecs++; //one millisecond has passed
  }
  updateDisplay(); //Update Display
}

//--------------------------------------------------------------------------------------------