NerdKits - electronics education for a digital generation

You are not logged in. [log in]

NEW: Learning electronics? Ask your questions on the new Electronics Questions & Answers site hosted by CircuitLab.

Microcontroller Programming » How to create a loop timer while checking 2 pin inputs

March 22, 2012
by Blackbird
Blackbird's Avatar

1st time posting. I have done a lot of searching and playing with code and can't figure out how to get this to do what I want. I've got the if && to check for high on both pins and count using a delay, but it is not what I need.

What I need:

  1. A loop that continually monitors 2 pins, PB1 & PC1.
  2. If PB1 & PC1 are held high for 3 seconds do this (PC0 high)
  3. If PB1 is high for 3 seconds and PC1 is low, do this (PB3 & PB2 high)
  4. If PC1 is high for 3 seconds and PB1 is low, do this (PB3 & PC2 high)
  5. If PB1 is high then low, do this (PB2 high)
  6. If PC1 is high then low, do this (PB2 High)

Loop and monitor, no problem. Monitor inputs and turn on outputs, no problem. Figuring out what statement to use to monitor and time the input pin so it will go to the next 'if' if the time is held for only 2 seconds--problem.

I don't want a 3 second delay, I need a 3 second counter.

It is probably a no brainer for someone on here and I just can't seem to put it together.

Thanks for looking. Love the kit. I have burned many hours. Too much on this last part though.

March 22, 2012
by JimFrederickson
JimFrederickson's Avatar

It is easiest to use interrupts to do what you want.

In your case, it seems that you have only 'a single event at a time' to measure so it it will be pretty straight forward. (If you have more than 'a single event to time' then this approach would still work, but you would have to us this basic skeleton to configure multiple 'software timers'.)

    //  Global Variables

    #define TIMER_TIMER0MAX     200

    volatile uint16_t system_timertick;

    void timer0_init() {

    //  TCCR0A  -  Timer 0 Counter Control Register A
    //  TCCR0B  -  Timer 0 Counter Control Register B
    //                          CS0     CS1     CS2
    //  No Clock                0       0       0
    //  clk (no prescale)       1       0       0
    //  clk/8                   0       1       0
    //  clk/64                  1       1       0
    //  clk/256                 0       0       1
    //  clk/1024                1       0       1
    //  clk on TO Falling Edge  0       1       1
    //  clk on T0 Rising Edge   1       1       1

    //  Timer 0 Prescaler

    //  Timer 0 Counter
    //  TCNTO

    //  TIMSK0  -  Timer Mask Register
    //  OCIE0  Timer 0 Comparison Interrupt
    //  TOIE0  Timer 0 Overflow Interrupt

    // setup Timer0:
    // CTC (Clear Timer on Compare Match mode)
    // Match Value set by OCR0A register
    TCCR0A |= (1<<WGM01);

    // clocked from System Clock clk/8
    TCCR0B = 0;
    TCCR0B |= (1<<CS01);

    // clock comparator value
    OCR0A = TIMER_TIMER0MAX;

    TIMSK0 |= (1<<OCIE0A);

    timeclear();
    }

    ISR(TIMER0_COMPA_vect)
    {
    //  Timer 0 Interrupt Overflow Service Routine

    //  One tick

    //  Our Timer is setup as follows:

    //  Timer0 Prescaler is set to  /8 and the comparator value is 200
    //  So that means there will be a 'timeout/interrupt' every 1,600
    //  Close cycles.  So for my 16mhz clock that is 10,000 interrupt/sec

    //  system_timertick is an unsigned integer so its max is 65,536

    system_timertick++;

    if (system_timertick > 59999) {
        system_timertick = 0;
            }
    }

So basically this should allow system_timertick to count up to 6 seconds. (In 1/10,000 of a second intervals).

NOTE: As mentioned in the code I am using a 16mhz crystal NOT the standard Nerdkits Crystal.

This should help you to 'get started'.

NOTE: The 'volatile' keyboard must be used on the system_timertick. What 'volatile' does is to tell the compiler that the program will ALWAYS need to load the system_timertick value from memory and avoid optimizations on teh value. The reason for this is because the Interrupt routine can change this value, and the compiler cannot predict when this will occur, so if the compiler decided to optimize that value by keeping in an AVR Register then the program will never see the change in the value.

So use this you could simply clear the system_timertick, and then do what you need to do. After you have an event that you wanted to time then check the value.

NOTES:

1 - When you check the value you will need to either 'disable interrupts' or 'write the value to a temporary variable. The reason for this is because the Interrupts will continue to fire, and the value could change if you reference more than once in your inline code. It is MUCH BETTER to just write it to a temporary variable in your mainline code that way the interrupt can continue along it's merry way. WHICH YOU WOULD NEED TO ALLOW TO HAPPEN if you needed to have multiple/software timers.

2 - If there is a possibility that your timed value can exceed the 6 seconds, or timeout at a certain point, you will need to check the system_timertick in your main loop to determine if a timeout has occurred.

March 22, 2012
by pcbolt
pcbolt's Avatar

Blackbird -

If you don't need 1/10,000 sec resolution, you could use the NK code from the "realtime_clock" project (in tutorial section). This will only give you 1/100 sec resolution but will not "reset" until more than 200 days has elapsed.

You'll also benefit by using Pin Change Interrupts. This will trigger each time PC1 or PB1 changes state. Fortunately for you, by using PC1 and PB1, you can actually use two separate interrupt service routines (ISR) to monitor the pins. You can initialize the interrupts using:

void pin_change_init(){

//Enable interrupts 1 and 0
PCICR |= (1<<PCIE1) | (1<<PCIE0);
//Set the mask on Pin change interrupt 1 so that only PCINT9 (PC1) triggers
PCMSK1 |= (1<<PCINT9);
//Set the mask on Pin change interrupt 0 so that only PCINT1 (PB1) triggers
PCMSK0 |= (1<<PCINT1);

return;
}

As Jim stated above, the ISR's work best by simply updating global (and volatile) variables. So the following "pseudocode" can be used:

ISR(PCINT1_vect){
//Check if PC1 is high or low
//If high - store current time as "start_time"
//If low - set a variable so step 6 can be detected
}

ISR(PCINT0_vect){
//Check if PB1 is high or low
//If high - store current time as "start_time"
//If low - set a variable so step 5 can be detected
}

Then in your monitoring loop you just test all the possibilities:

monitoring_loop(){
//For step 2 - check if PB1 and PC1 are high
//If so, get current time, subtract "start_time" set from ISR 
//Check if that value is greater than 3 seconds
//More code here...

You'll most likely need more flag variables and tests but it's a start. Sounds like you're making an interesting project.

March 23, 2012
by Blackbird
Blackbird's Avatar

Wow. Thanks for the quick responses. I see now that I have bit off a bit more than I can chew at the moment since some of the coding and terms are over my head, but I will dig into the examples and learn about each term. I read a bit on the interrupts and it seemed like what I needed, but could not figure out how to integrate the timer, so thanks again for getting me going.

I little more backgound for the curious.
I am on my second version of a scoreboard for my friends horseshoe pits. First version was with simple counter and BCD 4511 chips and IR diodes. I have since opened my mouth furthur saying it should be improved, and here we are.

I have already made the 2 LED displays, 4" digits, (both scores pointing towards each pit) and decided to make the reset cooler and add reverse (the source of my current issues), oh and add speach and sound.

The scoring is done via laser diodes and IR receivers (already working) at each pit that you just wave your hand/finger through. (had to use a debounce chip for that (i love that chip,works great)).

To keep things simple (for me, and to keep pins open on the 168) I decided to use chips to drive the displays and use the 168 as the brains.

That is where the current code comes into play. Since it is so easy to increment a score, people love to blast past the mark (it will score as fast as you can wave your fingers through, even 4 at a time), so I thought reverse would be cool, by holding your hand in one beam for 3 seconds it would start slowly counting backwards and by blocking both beams for 3 seconds it would reset.

The speech/sound is (hopefully) going to come from a RC Systems Doubletalk RC8660 voice chip. Already have that and can program via the development board. It says it communicates via Standard serial (UART) and 8-bit bus interfaces, so it will be my next learing phase once the scoring is working.

Thanks again for the help and I am more than willing to accept any helpful advice or suggestions.

I have found it much easier for me to learn new stuff when I want a result, rather than being force fed, and since there are so many distractions in life, it is good to have a goal, even if it is just a hobby.

Great community. Glad I decided on the NerdKit.

May 21, 2012
by Blackbird
Blackbird's Avatar

I have a large portion of the scoreboard working, including the sound interface (UART code, not all the variables) but I am having a problem with the score not responding after some time.

I changed the code a bit to spit the output back to the computer and it appears that at some point the time starts counting backwards back to 0 then back up. After this occurs, the scoring wont work. It will still recognize the input (blocking the laser) but does not change score or count blue or green 'hold' time. It happens right around the 5 minute mark. The timer code is from the 'real time clock'.

Time=system time, Green score, Green input y/n, green 'Y' time, Blue score, blue input, blue 'y' time

Here is the screen shot when it changes.

Time:32766 Gnscr: 4 Gnhg: 0 Gntm: 0 bluscr: 9 bluhg: 0 blutm: 0

Time:32767 Gnscr: 4 Gnhg: 0 Gntm: 0 bluscr: 9 bluhg: 0 blutm: 0

Time:-32768 Gnscr: 4 Gnhg: 0 Gntm: 0 bluscr: 9 bluhg: 0 blutm: 0

Time:-32768 Gnscr: 4 Gnhg: 0 Gntm: 0 bluscr: 9 bluhg: 0 blutm: 0

Time:-32767 Gnscr: 4 Gnhg: 0 Gntm: 0 bluscr: 9 bluhg: 0 blutm: 0

Because I am still testing and modifing the code, I have a lot of lines that are just // so I don't have to retype them if I use them later.

When I use the LCD display, the same freeze happens. The time continues to count (down) so I know the chip is not locking up, but the negative numbers are messing with my If/Then statements.

Any thoughts would be appreciated.

// Scoreboard.c

// for LW Pitts with ATmega168 & RCSystems RC8660 voice chip
// sbuelow@comcast.net

#define FOSC 14745600 // clock speed
// #define BAUD 2400 // 2400 Baud

#include <stdio.h>
#include <stdlib.h>
#include <avr/io.h>
#include <inttypes.h>
#include "../libnerdkits/uart.h"  
#include "../libnerdkits/lcd.h"
#include "../libnerdkits/delay.h"
#include <avr/pgmspace.h>
#include <avr/interrupt.h>

// PIN DEFINITIONS:
// 
// PC0-- Score reset - output-
// PC1-- Green score trigger  -input-
// PC2-- Green score count -output-
//
// PB1-- Blue score trigger  -input-
// PB2-- Blue score count -output-
// PB3-- Reverse -output-
//

void realtimeclock_setup() {
  //setup Timer0:
  // CTC (Clear Timer on Compare Match mode)
  // TOP set by OCR0A register
  TCCR0A |= (1<<WGM01);
  // clocked from CLK/1024
  // which is 14745600/1024, or 14400 increments per second
  TCCR0B |= (1<<CS02) | (1<<CS00);
  // set TOP to 143
  // because it counts 0, 1, 2, ... 142, 143, 0, 1, 2 ...
  // so 0 through 143 equals 144 events
  OCR0A = 143;
  // enable interrupt on compare event
  // (14400 / 144 = 100 per second)
  TIMSK0 |= (1<<OCIE0A);
  }

volatile int32_t the_time;

SIGNAL(SIG_OUTPUT_COMPARE0A) {
  // when Timer0 gets to its Output Compare value,
  // one one-hundredth of a second has elapsed (0.01 seconds).
  the_time++;
}

int main (){
realtimeclock_setup();

// Define variables
int bluetime;
int greentime;
int bluehigh;
int greenhigh; 
int bluestart;
int greenstart;
int bluescore;
int greenscore;
int lowtime;   // Time of no trigger
int nullhigh; // Status of no trigger

 // Outputs
  DDRB |= (1<<PB2); // Blue count
  DDRB |= (1<<PB3); // Reverse
  DDRB |= (1<<PB4); // Loop Led
  DDRC |= (1<<PC2); // Green count
  DDRC |= (1<<PC0); // Score reset

  // Inputs
  DDRB &= ~(1<<PB1); // Blue trigger
  DDRC &= ~(1<<PC1); // Green trigger

  // turn on interrupt handler
  sei();

// start up the LCD
//  lcd_init();
//  FILE lcd_stream = FDEV_SETUP_STREAM(lcd_putchar, 0, _FDEV_SETUP_WRITE);
//  lcd_home();

 // Start up the serial port   
uart_init();   
FILE uart_stream = FDEV_SETUP_STREAM(uart_putchar, uart_getchar, _FDEV_SETUP_RW);   
stdin = stdout = &uart_stream;

// UBRR0H = 1;   
// UBRR0L = 127; // for 2400bps with 14.7456MHz clock

// enable uart RX and TX   
UCSR0B = (1<<RXEN0)|(1<<TXEN0);   
// set 8N1 frame format   
UCSR0C = (1<<UCSZ01)|(1<<UCSZ00);

 // reset routine
reset:
  PORTC |=(1<<PC0);
  delay_ms(200);
  PORTC &= ~(1<<PC0);
  bluehigh=0; bluestart=0; bluetime=0; bluescore=0;
  greenhigh=0; greenstart=0; greentime=0; greenscore=0; 
  nullhigh=0;lowtime=0; 
  delay_ms(4000);
// ??? if the time > XX special reset sound file?

while (1) {         
whileloop:

 // monitor loop
 if (PINB & (1<<PB1)) {bluehigh=1; nullhigh=0;} 
    else {bluehigh=0;}

 if (PINC & (1<<PC1)) {greenhigh=1; nullhigh=0;}    
    else {greenhigh=0;}

 if (bluehigh==1 && bluestart==0) {bluestart=the_time;}
 if (greenhigh==1 && greenstart==0) {greenstart=the_time;}

 if (bluehigh==1 && bluestart > 0) {bluetime=the_time - bluestart;}
 if (greenhigh==1 && greenstart > 0) {greentime=the_time - greenstart;}

 if (bluehigh==0 && bluetime !=0 && bluetime < 300) {
    PORTB |=(1<<PB2); bluescore++;
    PORTB &= ~(1<<PB2); 
    bluehigh=0; bluestart=0; bluetime=0;
    nullhigh=1; lowtime=the_time;}

 if (greenhigh==0 && greentime !=0 && greentime < 300) {
    PORTC |=(1<<PC2); greenscore++;
    PORTC &= ~(1<<PC2); 
    greenhigh=0; greenstart=0; greentime=0;
    nullhigh=1; lowtime=the_time;}

// Reverse checks 
 if (bluehigh==1 && greenhigh==0 && bluetime > 300) {goto bluereverse;}
 if (greenhigh==1 && bluehigh==0 && greentime > 300) {goto greenreverse;}

// Reset check
 if (bluehigh==1 && greenhigh==1 && bluetime > 300 && greentime > 300) {goto reset;}

  // PORTB |= (1<<PB4);
  // delay_ms(100);
  // PORTB &= ~(1<<PB4);

    // write message to LCD
   //  lcd_home();
    // lcd_write_string(PSTR("ADC: %"),greenscore);

   //  fprintf_P(&lcd_stream, PSTR("Green: % "),greenscore);

  //   lcd_line_two();
  //   lcd_write_string(PSTR("The time=%"), the_time);

  // Write message to serial

  printf_P(PSTR("Time:%.d "), the_time);

  printf_P(PSTR("Gnscr: %.d "), greenscore);
  printf_P(PSTR("Gnhg: %.d "), greenhigh);
  printf_P(PSTR("Gntm: %.d "), greentime);
  printf_P(PSTR("bluscr: %.d "), bluescore);
  printf_P(PSTR("bluhg: %.d "), bluehigh);
  printf_P(PSTR("blutm: %.d \r\n"), bluetime);

goto whileloop;

bluereverse:
// Blue reverse loop

if (PINB & (1<<PB1)) { // if blue trigger high
  PORTB |= (1<<PB3); // turn on reverse
  delay_ms(100);  
  PORTB |=(1<<PB2);  // turn on blue score
  bluescore--;       // subtract 1 from blue
  PORTB &= ~(1<<PB2);  // turn off blue score
  delay_ms(400);
  goto bluereverse;}

 else {
   bluehigh=0; bluestart=0; bluetime=0;  // zero out blue inputs
   PORTB &= ~(1<<PB3); // turn off reverse
   nullhigh=1; lowtime=the_time;
   goto whileloop;}

greenreverse:
// Green reverse loop

if (PINC & (1<<PC1)) {  // if green trigger high
  PORTB |=(1<<PB3);   // turn on reverse
  delay_ms(100);   
  PORTC |=(1<<PC2);  // turn on green score
  greenscore--;      // subtract 1 from green 
  PORTC &= ~(1<<PC2);  // turn off green score
  delay_ms(400);
  goto greenreverse;}

 else {
   greenhigh=0; greenstart=0; greentime=0;  // zero out green inputs
   PORTB &= ~(1<<PB3);  // turn off reverse
   nullhigh=1; lowtime=the_time;
   goto whileloop;}

goto whileloop;

// soundloop:

delay_ms(4000);

uart_write(1); printf_P(PSTR("0O\r"));  //voice change to perfect paul
printf_P(PSTR("Welcome to the Wagners perfect paul\r")); // text string speak

 //printf_P(PSTR("\r")); //uart_write(13);
 printf_P(PSTR("\r\n")); // carrige return & line return

 //delay_ms(2000);
//uart_write(72); uart_write(73); uart_write(00);  // hi

 delay_ms(2000);

uart_write(1); 
uart_write(57);
uart_write(79);
uart_write(13);

printf_P(PSTR("Welcome to the Wagners, Alvin\r"));

//printf_P(57); 
//printf_P(2&); 
//printf_P(01h);
printf_P(PSTR("\r\n"));

// uart_write(34); 
//uart_write(1); 
//uart_write(94);
//uart_write(13); //uart_write(13);

uart_write(1); printf_P(PSTR("100&\r")); // Play sound file-recording
delay_ms(2000);

uart_write(1); printf_P(PSTR("1&\r")); // Play sound file-recording
delay_ms(2000);

} 
return 0;
}
May 21, 2012
by pcbolt
pcbolt's Avatar

Blackbird -

Great project. Glad to see you've progressed so far. I think what is happening is when you declared your time variables (bluetime, greentime etc) you declared them as "int". In AVR programming, an "int" is a signed 16-bit variable. This means it can only hold values from -32,768 to 32,767. In hex representation -32768 is 0x8000 and 32767 is 0x7FFF, so it "kind of" makes sense that after 0x7FFF the timer will go to 0x8000. The easiest way to change this is to make any time variable 32-bits (signed or unsigned...won't really matter here), by declaring them int32_t (just like "the_time"). I'm surprised the compiler didn't holler at you. BTW, did you start programming in assembly? I only ask because the way you use the "goto" routines reminds me of assembly. Some "C" programmers don't like to use "goto" but I say if it works, more power to you.

May 22, 2012
by Blackbird
Blackbird's Avatar

Thanks. I will make those changes tonight and try it again. As far as programming, this is my first time. BASIC was as close as I got back in the 80's. I am learning C as I go, on an 'as-needed' basis. I seem to get more from reading in the forums and reverse engineering then from the numerous 'quick starts' I have found online, although I pick up a tidbit each time I read one. I am more into elecrtonics, led's, and gadgets, but this microcontroller has so many possibilities. I am trying to get a better grasp of C, but with it being a hobby it is hard to dedicate the time to learn it the way I should. If you think of a book or guide that may be helpful to me, let me know.

Thanks again for your help.

May 22, 2012
by Blackbird
Blackbird's Avatar

One more question as I am sitting here thinking, this all occurs with me not providing any input to the controller. The 'time' value that is counting up to 32767 then going to -32768 and counting backwards is 'the_time' which is already set at 32-bits. I am missing how the variables I created are causing 'the_time' to change and count backwards. I could see it locking up or something, but I thought the timer was part of the chip hardware. Thanks for your insight.

May 22, 2012
by pcbolt
pcbolt's Avatar

Blackbird -

You're right, the timer is part of the chip hardware and actually operates independently of the main code. The timer you're using (Timer0) is actually only 8-bits, it only counts from 0 to 255 (max), then either counts down or restarts at zero. The way it is set up now, it stops at 143 then restarts at 0. Whenever it hits 143 (once every 1/100th of a second) the interrupt is triggered and "the_time" is incremented. When you assign one of your 16-bit time variables to the 32-bit "the_time" value, the upper 16-bits are lost. If a variable is "signed" the processor looks at the left-most bit to see if the number is positive or negative (0 is positive, 1 is negative). So when you count up "the_time" goes from 0x00007FFF to 0x00008000 and the left-most bit is 0 in both cases. When you copy those numbers into a 16-bit variable, you lose the left-most 16-bits and get 0x7FFF and 0x8000 (rememeber each hex digit is 4-bits). 0x7FFF in binary is 0111 1111 1111 1111 and positive. 0x8000 is 1000 0000 0000 0000 and therefore negative. I know it's odd but it actually saves time in the processor.

May 22, 2012
by Blackbird
Blackbird's Avatar

That makes sense to me for the variables that I assign to equal 'the_time' but not for 'the_time' itself.

I thought this statment,

volatile int32_t the_time;

made 'the_time' a 32 bit variable and would therefore not run out of space for a couple hundred days?

Are you saying that my assigning a variable like 'bluetime' to equal the variable 'the_time' is causing an issue with 'the_time'?

The explaination may be beyond me at the moment, so feel free to use a bigger hammer. :)

I will try the modifications to my assigned variables and try it out. I just hope I am not misunderstanding your direction.

Just out of curiosity, how would you redirect multiple subroutines to the same point without 'goto'? Seems to make the logic flow much easier for me, but I also did not know another way.

Thanks again.

May 22, 2012
by pcbolt
pcbolt's Avatar

Blackbird -

Ah...I see now where the confusion is. "the_time" should NOT change when another variable is assigned to it, it will have enough space to last a couple of hundred days. When you use the 16-bit variables in the "if" tests, you will run into trouble.

So the question is "why does 'the_time', a 32-bit variable, start to count backwards?" The answer is "it doesn't". It's just getting displayed wrong by the "printf_P" statement on line 161 above. The "%.d" assumes a 16-bit variable. To get it to display correctly, try using "%ld" (that's a lower "L" not 1). You won't need the decimal point in it.

As for the "multiple subroutines to one point" question, just format it a little differently. Take your "bluereverse" block, and format it like this:

void bluereverse()
  while (1) {
    if (PINB & (1<<PB1)) {
      PORTB |= (1<<PB3);
      delay_ms(100);
      PORTB |=(1<<PB2);
      bluescore--;
      PORTB &= ~(1<<PB2);
      delay_ms(400);
    }
    else {
      bluehigh=0;
      bluestart=0;
      bluetime=0;
      PORTB &= ~(1<<PB3);
      nullhigh=1;
      lowtime=the_time;
      return;
    }
  }
}

Now anytime you want to execute it place this in your main loop:

bluereverse();

When "bluereverse()" is finished, it jumps to the line after the code that called the subroutine.

But like I said before....if it is easier for you just stick to what's comfortable.

May 22, 2012
by Blackbird
Blackbird's Avatar

I made the changes to only the time variables as you suggested.

// Define variables
volatile int32_t bluetime;
volatile int32_t greentime;
int bluehigh;
int greenhigh; 
volatile int32_t bluestart;
volatile int32_t greenstart;
int bluescore;
int greenscore;
volatile int32_t lowtime;   // Time of no trigger
int nullhigh; // Status of no trigger

It appears to work still @ 14 minutes, so I am feeling better thanks to your help.

The PC output still goes up to 32k and then starts counting down to 0, then goes back up.

I am thinking that it is my print string causing the display issue,

printf_P(PSTR("Time:%.d "), the_time);

I am still curious as to what was happening with the assigned variables if you feel like trying to explain it again.

Thanks again for getting me past this road block.

By the way, I also make decals as a hobby and would be HAPPY to send you some if you would like. I know you do this stuff to help others, but I really appreciate your efforts and sharing your knowledge.

Still working at 22 min. :)

May 22, 2012
by pcbolt
pcbolt's Avatar

Correction - Line 1 should read:

void bluereverse() {

Can't forget the braces :-)

May 22, 2012
by Blackbird
Blackbird's Avatar

I posted while you were responding.

Thanks.

Post a Reply

Please log in to post a reply.

Did you know that sound travels via pressure waves in the air, and you can make these with a piezoelectric buzzer? Learn more...