How to set 1 second timer interrupt in ATtiny24/44/84

Hello I am struggling with register programming. I've read the document and looked for examples but most of them are for Attiny85 so I would love to be guided. The code I could write is below:

void setup() {
 TCCR1A &= ~((1<<COM1A1)|(1<<COM1A0)); //not sure if relevant
  TCCR1A &= ~((1<<COM1B1)|(1<<COM1B0)); //not sure if relevant
  
  TIMSK1 |= (1<<TOIE1);  //Timer/Counter0 Overflow interrupt is enabled
  TCCR1B &= ~((1<<CS10)|(1<<CS11)|(1<<CS12)); // prescaler is set to 0 (timer is off at startup)
  TCCR1B |=(1<<WGM12); // set CTC mode
  OCR1A = 15624; // (1000000/64)/15625 = 1
}

ISR(TIM1_COMPA_vect)
{
 counter--;
  if (counter<=0) {
    waitingTimerResponse = true;
    timer(false);
    }
}

void timer(bool command1){ // start/stop function to control timer
  if (command1)  
  {
//set prescaler to 64
    TCCR1B |= (1<<CS11)|(1<<CS10);
    TCCR1B &= ~(1<<CS12); 
  }
  else {
    TCCR1B &= ~((1<<CS10)|(1<<CS11)|(1<<CS12));
    }
  }

So is this appropriate way to set the timer, control the timer and get the desired interrupt? Thanks.

Have you tested the code? What does it do?

Always post ALL the code. Don't forget to declare variables shared with interrupt routines as "volatile", and protect them from corruption while being accessed in then main loop.

I don't have the ATtiny24A yet in my hands. I am pre programming it. I don't want any mistakes because I will probably build the circuit with hard work (drawing, printing, etching, tinning, components placement) and I don't want to waste hard work. The whole code is a complete alarm system, It would over complicate things but sure anyone can see, I am open to suggestions. (Editing the code according to suggestions)

#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <util/delay.h>

volatile uint8_t state = 1; //1: idle     2: to be armed     3: armed/to be rang/ringing
uint16_t armDelay = 30;
uint16_t alarmDelay;
uint16_t alarmDuration;

uint16_t counter;

unsigned long t1;
unsigned long t1_;

volatile bool flagBUT = false;
volatile bool flagPIR = false;
volatile bool flagTIM = false;

#define LEDButtonPin PA3
#define BuzzerPin PA4
#define AlarmPin PA5
#define PotEnablePin PA2
#define PIRPin PB2
#define ButtonPin PA7
#define pot_DEL_pin PA0
#define pot_DUR_pin PA1
  
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif


void setup() {
  TCCR1A = (0<<WGM11)|(0<<WGM10);
  TCCR1B = ((0<<CS10)|(0<<CS11)|(0<<CS12))|(1<<WGM12)|(0<<WGM13)|(0<<ICNC1)||(0<<ICES1); //Set CTC by WGM12       
  
  TIMSK1 |= (1<<TOIE1); //Timer/Counter0 Overflow interrupt is enabled
  OCR1A = 15624;

  pinMode(LEDButtonPin,OUTPUT);
  pinMode(AlarmPin,OUTPUT);
  pinMode(PotEnablePin,OUTPUT);
  pinMode(PIRPin, INPUT);
  pinMode(ButtonPin, INPUT_PULLUP);

  sbi(GIMSK, PCIE0); //Turn on pin change interrupt
  
  GIMSK |= (1<<INT0); //INT0

  sei(); //enable global interrupt
  
  Led (1);
  beep(1,100);
}

ISR(PCINT0_vect){ // button interrupt
  flagBUT = true;  
  }

ISR(INT0_vect){ // sensor interrupt
  if (digitalRead(PIRPin))  flagPIR = true;
  }
  
void loop() {
  if (flagBUT || flagPIR || flagTIM){
  if (flagBUT){
    t1 = millis();
    if (abs(t1-t1_) < 800) {flagBUT = false; t1_= t1; return;}
    else {
      t1_= t1;
      flagBUT = false;
      if (state == 1) state = 2;
      else state = 1;
      }
  }      
           
      switch (state) {
      case 1:
        cbi(PCMSK0,PCINT7); // Turn On PIR sensor interrupt
        timer(false);
        alarm(false);
        Led (1);
        beep(1,100);
      break;

      case 2:
      counter = armDelay;
        timer(true);
        beep(2,500);
        while (!flagTIM){
          Led(1);
          delay(1000);
          if (flagBUT) return;
          Led(0);
          delay(1000);
          if (flagBUT) return;
          }        
        flagTIM = false;
        digitalWrite(PotEnablePin, HIGH);
        sbi(ADCSRA,ADEN); // Switch ADC on
        delay (200);
        alarmDelay = map(analogRead(pot_DEL_pin),0,1023,0,60);
        alarmDuration = map(analogRead(pot_DUR_pin),0,1023,0,3600);
        cbi(ADCSRA,ADEN); //Switch ADC off
        digitalWrite(PotEnablePin, LOW);
        sbi(PCMSK0,PCINT7); // Turn On PIR sensor interrupt
        beep(3,100);
        state = 3;
        sleep();
      break;

      case 3:
          flagPIR = false;
          counter = alarmDelay;
          timer(true);
          beep(5,80);
          while (!flagTIM){
              Led(1);
              delay(200);            
              Led(0);
              delay(200);
              if (flagBUT) return;
            }
          alarm(1);
          counter = alarmDuration;
          timer(true);
         
          while (!flagTIM){
              if (flagPIR) counter = alarmDuration;
              if (flagBUT) return;
            }
          alarm(0);
          beep(3,100);
          sleep();          
      break;
      }
    } else delay(10);
}
  
ISR(TIM1_COMPA_vect)
{
 counter--;
  if (counter<=0) {
    flagTIM = true;
    timer(false);
    }
}


void timer(bool command1){
  if (command1)  
  {
    TCCR1B |= (1<<CS11)|(1<<CS10);
    TCCR1B &= ~(1<<CS12); //set prescaler
  }
  else {
    TCCR1B &= ~((1<<CS10)|(1<<CS11)|(1<<CS12));
    }
  }

void beep (uint8_t repeat, uint16_t interval){
  pinMode(BuzzerPin,OUTPUT);
  for (int i = repeat; i > 0; i--){
      digitalWrite(BuzzerPin, HIGH);
      delay(interval);
      digitalWrite(BuzzerPin, LOW);
      if (i > 1) delay(interval);
    }
  }

void alarm(bool on_off){
    if (on_off == 1){
      digitalWrite(AlarmPin, HIGH);
    } else{
      digitalWrite(AlarmPin, LOW);
      }
    }

void Led (uint8_t  ledmode){
      if (ledmode) digitalWrite(LEDButtonPin, HIGH);
      else digitalWrite(LEDButtonPin, LOW);
    }

void sleep(){
    set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode
    sleep_mode(); //System sleep
    }

The suspected parts are: Timer interrupt, button interrupt and sensor interrupt. Attention on them please, thanks.

That's not doing what you think.

Why all the interrupts? You could easily poll your button and PIR sensor

Just a "==" mistake, I am gonna correct it. I need interrupts because most of the time system is sleeping and it must be woken up by button or motion detection. I don't know what "poll"ing is btw.

In general, you should initialze the timer registers completely, instead of setting and clearing individual bits in them when "other code" (like the Arduino initialization) may have set them to "random" values. That would be something like:

  TCCR1A = 0;  //concise but un-obvious
  TCCR1B = (1<<WGM12); // set CTC mode
//or verbose and mentioning all the bits
  TCCR1A = (0<<COM1A1)|(0<<COM1A0) | (0<<COM1B1)|(0<<COM1B0)
           | (0<<WGM11)|(0<<WGM10);
  TCCR1B = (0<<CS10)|(0<<CS11)|(0<<CS12))   // prescaler is set to 0 (timer is off at
           | (1<<WGM12)|(0<<WGM13)          // set CTC mode
           | (0<<ICNC1)||(0<<ICES1);        // No capture

Thank you sir, I corrected my code. Are the registers correct btw? I understood the prescaler, I understood OCR1A is the limit the timer counts to, I know CTC restarts timer in each overflow, I didn't understand TCCR1A it seems to be there to configure the timer. TIMSK1 is there to enable the interrupt.

Are all these in correct harmony to achieve a timer? Any wrong registers or missing registers? Thank you.

Also could you check my "INT0" and "PCIE0" interrupts for the sensor and button? For example to I used DigitalRead() for ISR(INT0_vect) which is an interrupt pin. Is reading from interrupt pin valid?

The bits in that register are fully described in the data sheet. You really need to study it closely. You don't need to or even want to set bits like COM1A1 if you don't need timer output on the digital pins (not required for interrupt generation).

Well I've read the descriptions and I decided I didn't want to override any digital pins, I guess I just don't understand what kind of an override that is that's why I asked. Removing them from code...

Polling is when you read sensors each time through loop() to see if they have changed. Given you have several loops and delay() calls in your code, your code isn't sleeping while all that is happening.

Since both those variables are unsigned, the result can never be negative so you are doing extra work there by calling abs().

You also need to take care of making copies of variables you use in your ISR and main code (like t1) since the processor can not access a multi-byte variable atomically.

...
  if (waitingbuttonresponse){
  nointerrupts();
  unsigned long t1_copy = t1;
  interrupts();
    if (t1_copy-t1_ < 800) {waitingbuttonresponse = false; t1_= t1_copy; return;}
    else {
      t1_= t1_copy;
      waitingbuttonresponse = false;
 ...

Look at case 2: of "state" variable, at the end it takes the controller to sleep. I can't go through loops to read sensors after that.

What if t1_ is around the limits of millis() and the t1 is the restarted millis() for example t1_ = 256248975 and t1 = 1200 then wouldn't t1-t1_ be negative? Can't calculations with unsigned values give negative results? I searched in internet some say it will give negative number some say it will give the largest unsigned int as output. I am confused. My test is below and Its confusing.

#include <stdio.h>

int main()
{
unsigned long t1 = 10;
unsigned long t2 = 1500;
unsigned long a = t1-t2;

 printf("Integer value is %d\n" , (t1-t2));
 printf("Integer value is %d\n" , a);

}

And it gives -1490 for both (t1-t2) and "a"

I don't really understand this. Why do I need to disable interrupts for a brief moment and create the variable from scratch each time? I am now searching for atomically variables as this is a new thing to me.

Btw I am really happy that nobody yet pointed out a critical mistake about my interrupts, I am so hyped, I'm getting confident in my script will run as soon as I upload it :smiley:

Thanks.

Because the interrupt WILL modify and corrupt the variable while it is being accessed by the main loop. It is a random event, but it is absolutely guaranteed to happen. We do not give such advice lightly on this forum.

nobody yet pointed out a critical mistake about my interrupts

@blh64 just did. Failure to protect variables is a critical mistake.

BTW the correct call to turn off interrupts is as follows. Case is important.
noInterrupts();

First of all I modified the flag varible names, was hard to read (ex waitingbuttonresponse --> flagBUT)

Alright I see your point now but my solution is much simpler. I just moved "t1 = millis();" from the interrupt to "void loop()" I edited the code in my second post accordingly. I don't think I can copy or move the flag variables because they are the flags themselves which trigger stuff and they have to be included in both the interrupt and loop. For example, I need to equalize the second flag variable which I am supposed to create to the original one in each loop so that still may cause interrupt to access the variable during the flagBUT1 = flagBUT2 occasion. I've added a 10ms delay to the loop() when no flag is raised (else condition at the bottom). That delay will mean that the interrupts will occur during the delay (as the process happens 1/1000000th of a second it is unlikely to occur at the if part of the code). Was it necessary? Or a good solution?

This is the sketch to operate TC1 in Normal Mode for generating Time Tick Interrupt at 1-sec interval.

volatile bool flag = false;

void setup()
{
  Serial.begin(9600);
  TCCR1A = 0x00;        //up counting Normal Mode operation; see Fig-2
  TCCR1B = 0x00;        //TC1 is STOP; see Fig-3
  TCNT1 = 0xC2F7;         //presetCount for 1 sec timeDelay at clkTC1 = 16MHz/1024
  bitSet(TIMSK1, TOIE1);  //local interrupu enable bit is active; see Fig-1
  TCCR1B = 0x05;          //Start TC1 with division factor 1024; see Fig-3
}

void loop()
{
  if (flag == true)
  {
    Serial.println("1-sec TimeTick interrupt.");
    flag = false;
  }
}

ISR(TIMER1_OVF_vect) //ISR Routine for TOV1 interrupt
{
  TCNT1 = 0xC2F7;  //re-load presetCount for 1-sec timeDelay 
  flag = true;
}


Figure-1:

tccr1a
Figure-2:


Figure-3:


Figure-4:

1 Like

You are missing the point of unsigned variables. They can NEVER be negative, by design. For simplicity, look at an 8 bit value (applies to 32 bit as well).
uint8_t goes from 0..255. If t1 = 5 and t1_ = 250, the result is 5 - 250, which is 11.
Think of the hands on a clock, If you turn them backwards, they don't go negative, they go 4,3,2,1,60,59,58,...

You are using the wrong format specifier there. %d is for a signed integer, but you have an unsigned long integer which would be %lu Format specifiers in C

Better yet, run a sketch. You can simulate everything here: sketch.ino - Wokwi ESP32, STM32, Arduino Simulator

unsigned long t1 = 1;
unsigned long t2 = 100000UL;

unsigned long differ = t1 - t2;
signed long differ_signed = t1 - t2;

void setup()
{
  Serial.begin (9600);
  Serial.println("Ready");

  Serial.print( "t1 = "); Serial.println(t1);
  Serial.print( "t2 = "); Serial.println(t2);
  Serial.print( "unsigned differance = "); Serial.println(differ);
  Serial.print( "  signed differance = "); Serial.println(differ_signed);
}

void loop()
{
}

Which gives you

Ready
t1 = 1
t2 = 100000
unsigned differance = 4294867297
  signed differance = -99999

Please do not do that. It messes up the flow of code, comments, etc. since you re-wrote history. Better to post the new code as a reply so the journey moves forward.

I understood what you did there. You took clock frequency 16MHZ but mine will be 1MHZ anyway. The difference between our methods is, you manipulate the counting value which the timer starts from (TCNT1) and I manipulate the value which the timer stops at and restarts (OCR1A). And they seem to be both valid.

There is another difference that our interrupt vectors are different. You always overflow the timer as the timer reach to the biggest 16 bit number. On the other hand I compare the timers value with OCR1A register. That's why I use a ISR(TIM1_COMPA_vect), a comparator vector; you use (TIMER1_OVF_vect), an overflow vector.

I hope I understood registers better, I hope I didn't say ridiculous things :smiley:

What shall I do to prevent millis() overflow ruining my debounce code? Aah wait a sec it will never give error on debounce code. So lets say t1_ = 18,446,744,073,709,551,000 and t1 = 1000, the equation will be 1000-18,446,744,073,709,551,000 = 1616 (1616>800) since the greatest unsigned long is 18,446,744,073,709,551,615. So I don't have to worry about the millis() overflow at all. Great, I can live without the "abs()".

Your thread is requesting for a way of generating 1-sec Time Tick interrupt which I have simply provided using TC1's Normal Mode. I have neither criticized your way of generating the same thing using CTC Mode nor your level of understanding the register details.

Yeah I know what you did, I am glad you did. Simple and efficient. I've just shared my experience reading your code. Thanks.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.