Attiny10: Toggle LED only after set amount of time

Hello folks!

I have started using an attiny10, programmed with the Aduino IDE, for my little LED toggle project. What i have works so far, but i want to expand its functions. This is were i need your help!

  • What i have: Attiny10 toggling an LED on/off with the press of a push button.
  • What i want: Only toggling the LED on/off after holding down the button a while. If possible, different "holding down" times lead to different actions (think RGB toggle).
  • What i've tried (to wrap my head around): Timer0 :sweat_smile:

So, here's my code, hopefully with sufficient comments:

/*
Test setup:
Button Side 1 --> Pin 2 (Ground, GND)
Button Side 2 --> Pin 3 (PB1) --> Internal Pullup Resistor

LED (+) --> 1K resistor --> Pin 1 (PB0)
LED (-) --> Pin 2 (GND)
*/



#define F_CPU 1000000UL //defines CPU frequency, here 1MHZ
#define LED_ON PORTB |= (1 << PB0) //set Bit of only PB0 to 1 (equivalent to DDRB = 0b00000001)
#define LED_OFF PORTB &=~ (1 << PB0) //set Bit of only  PB0 to 0 (equivalent to DDRB = 0b00000000) (&=~ equals "unwrite 1")

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



void setup() {
    DDRB |= (1 << PB0);   //PB0 as output, leaves other bits alone, unlike "DDRB = 0b0001"
    DDRB &=~(1 << PB1);   //PB1 as input ("unwrite 1"; set to 0)
    PUEB |= (1 << PUEB1); //activate internal pull-up resistor for button connected to Pin1
}

//debounce routine for 1 button

bool dbc() {

  //set bounce to zero, static retains value after exit
  //input on PB1 gets put into bounce
  //bounce shifted up by one (<<1)
  //new value of PB1 gets into lowest bit
  //gets "0ed" with "0xe000" (masking of top 3 unused bits)
  //ONLY if bounce = 0xf000, loop returns
  //above only happens if input accumulated a 1 followed by 12 zeros
  //Summary: tests if lower byte produces a stream of consecutive  (12) Zeros --> button contact valid

  static uint16_t bounce = 0; 
  bounce = (bounce<<1) | (PINB & (1<<PINB1)) | 0xe000;
  return (bounce == 0xF000);
}

void loop(){

static byte toggle_mem=0; //variable for button press tracking

  if (dbc()) {  
    
    toggle_mem = !toggle_mem; //invert variable for button press tracking, thus achieving a "toggle" with a push button only

    if (toggle_mem) //toogle LED ON when PB1 low
    
    LED_ON;

    else { //toggle LED OFF when PB1 high

    LED_OFF;
    }

    while (! (PINB & (1<<PINB1)) ); //If condition "Pin 1 is zero/low" is met, exit loop
    }
}

I sincerly hope you can help me and give me some pointers, on how to implenet this my brain is a bit scrambled :slight_smile:

Cheers

Define this.

Define these.

Try interrupts.

The convenience of Arduino is that you have a user-friendly abstraction layer at your disposal, with functions like millis() and digitalRead(). Why not use those? It'll make life a lot easier. Or is there a particular reason why you want/need to use direct register access instead?

Assuming you can use Arduino functions, I'd just poll the button pin and use millis() to determine how long the button was being held before it's released, and then blink the led (again using millis() for timing).

Millis() already makes use of one of the internal timers, so it's basically a user-friendly way of accessing a timer that's already configured in a useful manner.

I like your creative debounce routine.

You know you can simplify toggling the led by writing a "1" to bit PINB0?

Try it and replace your loop() function with this:

void loop() {
  if (dbc()) {
    PINB |= (1 << PINB0); // toggle led
    while (! (PINB & (1 << PINB1)) ); //If condition "Pin 1 is zero/low" is met, exit loop
  }
}

Timer0 is a nice 16bit timer, so should be able to use that. Let me dig through some old code to see if something comes up.

1 Like

Made something up with timer0.
This will use timer0 for the debounce via the Timer Input Capture pin ICP0 (PB1) with a falling edge. When a falling edge is detected the TCNT value will be stored in the ICR0 register. So during a bouncing pin, this will happen many times. When the button has settled the difference between TCNT0 and ICR0 will increase. I have selected 100 milliseconds as debounce time, which is very, very long for a debounce, but good to play with.
After a "valid debounced button press" I reset the timer0 to zero, so I can use the whole 16bits to start counting how long the button keeps being pressed. After one second PB2 will toggle and after three seconds PB0 gets toggled.

void setup() {
  DDRB |= (1 << PB2) | (1 << PB0);    // PB2 and PB0 as output
  PUEB |= (1 << PUEB1);               // activate internal pull-up
  TCCR0B |= (1 << CS02) | (1 << CS00) // prescaler clk/1024 and turns on timer0
  | (1 << ICNC0);                     // input capture noise canceler 
}

void loop() {
bool toggledB1 = 0; // flag to avoid more than one toggle
bool toggledB0 = 0; // flag to avoid more than one toggle
  if (TCNT0 - ICR0 > 100) { // valid debounce if 100 timer ticks have passed since last falling edge on ICP0 pin (= PB1)
  TCNT0 = 0; // reset the timer counter to zero
  while (!(PINB & (1 << PINB1))) { // stay in while as long as PINB1 remains LOW
      if (TCNT0 > 1000 && !toggledB1) { // one second has passed 
        PINB |= (1 << PINB2); // toggle PB2
        toggledB1 = 1; // so the led will not toggle again
      }
      if (TCNT0 > 3000 && !toggledB0) { // three seconds have passed
        PINB |= (1 << PINB0); // toggle PB0
        toggledB0 = 1; // so the led will not toggle again
      }
    }
  }
}
1 Like

In fact we don't need the whole separate debounce, as the hold down times serve as debounce as well.


void setup() {
  DDRB |= (1 << PB2) | (1 << PB0);    // PB2 and PB0 as output
  PUEB |= (1 << PUEB1);               // activate internal pull-up
  TCCR0B |= (1 << CS02) | (1 << CS00) // prescaler clk/1024 and turns on timer0
  | (1 << ICNC0);                     // input capture noise canceler 
}

void loop() {
bool toggledB1 = 0; // flag to avoid more thanm one toggle
bool toggledB0 = 0; // flag to avoid more thanm one toggle
  while (!(PINB & (1 << PINB1))) { // stay in while as long as PINB1 remains LOW
      if (TCNT0 - ICR0 > 1000 && !toggledB1) { // one second has passed 
        PINB |= (1 << PINB2); // toggle PB2
        toggledB1 = 1; // so the led will not toggle again
      }
      if (TCNT0 - ICR0 > 3000 && !toggledB0) { // three seconds have passed
        PINB |= (1 << PINB0); // toggle PB0
        toggledB0 = 1; // so the led will not toggle again
      }
    }
  }
1 Like

Since the ATTinyCore does not support the Attiny10, i use the Attiny10Core by technoblogy.

It is a minimal Arduino core for programming the ATtiny10/9/5/4. It doesn't include any Arduino functions like pinMode(), millis(). You have to use alternatives to those core functions, as listed here.

Thank you, but i can't take credit, i learned about it on here!

Oh, i did not know that! That makes it quite simple to toggle Pins, thank you very much! :slight_smile:

Theres lots of valuabe info in there for a beginner like me, thanks again! :astonished:
The input capture noise canceller is quite useful!

Whelp, that makes sense and it's even more compact. :smile:

So, thanks to your help i was able to advance my project to the next (fragile) stage:
Toggling a Two Color LED, which i wanted to do from the beginning, but i started with the basics.

What i want to do: Decide which LED's to toggle with a variable changed through a "double click" on the button.

  • First Color, Second Color, both colors at the same time

What i have:

  • Thanks to the simple pin toggling i learned from you i can adress all "3" colors easily
  • Not working: my "double click" detection
  • propably a lack of understanding about the Attiny10s timer functions & interrupts in general - but i'm trying my best to fill that gap - the datasheet is quite dense :face_with_spiral_eyes:

The following is my (non-working) code! I would really appreciate your insight, you've already helped me a ton @hmeijdam!

/*
Test setup:
Button Side 1 --> Pin 2 (Ground, GND)
Button Side 2 --> Pin 3 (PB1) --> Internal Pullup Resistor

LED (+) --> 1K resistor --> Pin 1 (PB0)
LED (-) --> Pin 2 (GND)
*/



#define F_CPU 1000000UL //defines CPU frequency, here 1MHZ
#define LED_toggle_0 PINB |= (1 << PINB0) // toggle PB0 (datasheet: 11.2.2)
#define LED_toggle_1 PINB |= (1 << PINB2); //toggle PB2

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



void setup() {
    DDRB |= (1 << PB0);   //PB0 as output, leaves other bits alone, unlike "DDRB = 0b0001"
    DDRB |= (1 << PB2);   //PB2 as output
    DDRB &=~(1 << PB1);   //PB1 as input ("unwrite 1"; set to 0)
    PUEB |= (1 << PUEB1); //activate internal pull-up resistor for button connected to Pin1

    TCCR0B |= (1 << CS02) | (1 << CS00) | (1 << ICNC0); // prescaler /1024 & timer0 on & input capture noise canceler
    TIMSK0 |= (1 << OCIE0A); // enable timer compare interrupt
                       
}

static byte pushes = 0; //counter for pushes
static byte selector = 0; //selector for turning on LED0, LED1 or both

void pushed(){ 

  if(!(PINB & (1 << PINB1))){ //if PINB1 is low, increase pushes counter by 1
    pushes++;
  }
  
  if(pushes == 1){ //if one push is detected, start counting and enable interrupt

    TCNT0 = 0; //start counting at 0 on push detected
    OCR0A = 500; // comparative value, in which a double click should happen
    TIMSK0 = 1 << OCR0A; //enable interrupt when counter matches comparative value
  }
}



ISR (TIMER0_COMPA_vect){

  TIMSK0 = 0; //dissable interrupt

  if(pushes == 2){ //if in 500ms pushes = 2

    selector++; //increase LED selector by 1
    
    if(selector == 2){ //if LED selector reaches 2 (max value), reset to 0

      selector = 0;

    }
  }

  pushes = 0; //reset pushes counter
}

void loop() {

bool toggled_0 = 0; // flag to avoid more than one toggle
bool toggled_1 = 0; // flag to avoid more than one toggle

  while (!(PINB & (1 << PINB1))) { // stay in while as long as PINB1 remains LOW

    if ((TCNT0 - ICR0 > 1000) && (selector == 0) && (!toggled_0)){ //only LED 0 (selector = 0) is toggled if button pressed for 1 second
      LED_toggle_0;
      toggled_0 = 1;
    }

    if ((TCNT0 - ICR0 > 1000) && (selector == 1) && (!toggled_1)){ //only LED 1 (selector = 1) is toggled if button pressed for 1 second
      LED_toggle_1;
      toggled_1 = 1;
    }

    if ((TCNT0 - ICR0 > 1000) && (selector == 2) && (!toggled_0) && (!toggled_1)){ //both LED 0/1 are toggled (selector = 2) if button pressed for 1 second
      LED_toggle_0;
      toggled_0 = 1;
      LED_toggle_1;
      toggled_1 = 1;
    }
  }
}

Thank you in advance and i hope you have a great day/night!

Sorry, I wasn't aware of this! The ATtiny's I've worked with so far weren't quite this tiny.

Looks like a nice challenge. To be frank, in your case, I'd probably use one of the timers to implement a rudimentary millis/systick function and then use that in conjunction with a normal polling routine for the button. The result would be less efficient than the minimal and highly integrated implementation suggested by @hmeijdam, but it might offer advantages when integrating it into a larger project, part of which could also use the millis/systick routine. The blinking of the LEDs comes to mind, which could be done that way, too.

1 Like

I added to my example that both leds toggle at the same time after a doubleclick is detected.

void setup() {
  DDRB |= (1 << PB2) | (1 << PB0);    // PB2 and PB0 as output
  PUEB |= (1 << PUEB1);               // activate internal pull-up
  TCCR0B |= (1 << CS02) | (1 << CS00) // prescaler clk/1024 and turns on timer0
            | (1 << ICNC0);                     // input capture noise canceler
}

void loop() {
  bool toggledB1 = 0; // flag to avoid more thanm one toggle
  bool toggledB0 = 0; // flag to avoid more thanm one toggle
  bool armclick = 0;
  static uint8_t doubleclick = 0;
  while (!(PINB & (1 << PINB1))) { // stay in while as long as PINB1 remains LOW
    if (TCNT0 - ICR0 > 25 && !armclick) { // 25ms have passed
      doubleclick++;
      armclick = 1; // to avoid having more doubleclick increments after one click
    }
    if (TCNT0 - ICR0 > 1000 && !toggledB1) { // one second has passed
      PINB |= (1 << PINB2); // toggle PB2
      toggledB1 = 1; // so the led will not toggle again
    }
    if (TCNT0 - ICR0 > 3000 && !toggledB0) { // three seconds have passed
      PINB |= (1 << PINB0); // toggle PB0
      toggledB0 = 1; // so the led will not toggle again
    }
  }
  if (TCNT0 - ICR0 > 500) doubleclick = 0; // reset doublecklick, it was a long click.
  else if (doubleclick == 2) { // doublecklick detected
    PINB |= (1 << PINB2); // toggle PB2
    PINB |= (1 << PINB0); // toggle PB0
    doubleclick = 0;
  }
}
1 Like

And it was fun to expand it some further into a multi-click solution, where you can add even more if you extend the switch-case statement.

It is slightly less snappy as the previous doubleclick example, as it needs to wait 500ms to determine if the user is done multi-clicking.

void setup() {
  DDRB |= (1 << PB2) | (1 << PB0);    // PB2 and PB0 as output
  PUEB |= (1 << PUEB1);               // activate internal pull-up
  TCCR0B |= (1 << CS02) | (1 << CS00) // prescaler clk/1024 and turns on timer0
            | (1 << ICNC0);                     // input capture noise canceler
}

void loop() {
  bool toggledB1 = 0; // flag to avoid more thanm one toggle
  bool toggledB0 = 0; // flag to avoid more thanm one toggle
  bool armclick = 0;
  static uint8_t multiclick = 0;
  while (!(PINB & (1 << PINB1))) { // stay in while as long as PINB1 remains LOW
    if (TCNT0 - ICR0 > 25 && !armclick) { // 25ms have passed
      multiclick++;
      armclick = 1; // to avoid having more multiclick increments after one click
    }
    if (TCNT0 - ICR0 > 1000 && !toggledB1) { // one second has passed
      PINB |= (1 << PINB2); // toggle PB2
      toggledB1 = 1; // so the led will not toggle again
    }
    if (TCNT0 - ICR0 > 3000 && !toggledB0) { // three seconds have passed
      PINB |= (1 << PINB0); // toggle PB0
      toggledB0 = 1; // so the led will not toggle again
    }
  }
  if (TCNT0 - ICR0 > 500) {
    switch (multiclick){
    case 1: // it was a single long click
      multiclick = 0; 
      break;
    case 2: // doubleclick
      PINB |= (1 << PINB0); // toggle PB0
      multiclick = 0;
      break;
    case 3: // tripleclick
      PINB |= (1 << PINB2); // toggle PB2
      multiclick = 0;
      break;
    case 4: // quadrupleclick
      PINB |= (1 << PINB2); // toggle PB2
      PINB |= (1 << PINB0); // toggle PB0
      multiclick = 0;
      break;
    default:
      multiclick = 0; // undefined # of clicks
      break;
    }
  }
}
1 Like

Then i think it would be best to use the watchdog timer for that, so that timer0 remains available and it has marginal impact on performance, as the WDT interrupt happens only every 16ms.

Here is the Arduino "blink without delay" example, but then reworked for the ATtiny10 with pseudomillis driven by the watchdog timer.

volatile unsigned int millis_16; // pseudo millis, that will increment every 16ms
unsigned int previousMillis = 0; // will store last time LED was updated
unsigned int currentMillis = 0; // snapshot millis_16 to disable ISR's as short as possible.
const unsigned int interval = 1000; // interval at which to blink (milliseconds)

void setup() {
  DDRB |= (1 << PB2) | (1 << PB0);    // PB2 and PB0 as output
  WDTCSR = _BV(WDIE) ; //turn on WDT interrupt and set value = 2K cycles (~16 ms)
  //  sei(); // turn on global interrupts
}

void loop() {
  cli();
  currentMillis = millis_16; // snapshot millis_16 to disable ISR's as short as possible.
  sei();
  if (currentMillis - previousMillis >= interval / 16) { // divided by 16, as millis_16 only increments per 16ms
    PINB |= (1 << PINB2); // toggle PB2
    previousMillis = millis_16;// save the last time you blinked the LED
  }
}

ISR(WDT_vect) { //interrupt service routine when watchdog timer overflow triggers
  millis_16++;
}
1 Like

Too difficult :slight_smile:
Classic blink (with delay :slight_smile: for Attiny10:

#define __ticks_dc  ((F_CPU) / 1e3)  // clock ticks for 1ms
const unsigned int interval = 1000; // interval at which to blink (milliseconds)

void setup() {
  DDRB |= (1 << PB2) ;    // PB2 as output
}

void loop() {
   PINB |= (1 << PINB2); // toggle PB2
   __builtin_avr_delay_cycles(__ticks_dc * interval);
}

If still too difficult :slight_smile:

#include <util/delay.h>

void setup() {
 DDRB |= (1 << PB2) ;    // PB2 as output
}

void loop() {
   PINB |= (1 << PINB2); // toggle PB2
   _delay_ms(1000);
}

Both compile to 68 bytes (that may not be a surprise as delay.h seems to use the same macro).

Thank you so much for your excellent code! It helped a ton and i learned a lot!
Sorry for the late reply :sweat_smile:

@b707 @hmeijdam Happy you had a bit of fun with my attiny LED code idea :smile:

Now on to new problems to solve, gonna make another posting soon i fear :smiling_face_with_tear:

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