Author Topic: Single Button interface for ATmega32  (Read 1811 times)

0 Members and 1 Guest are viewing this topic.

Offline MrOmnosTopic starter

  • Frequent Contributor
  • **
  • Posts: 268
  • Country: np
  • BE in Electronics and Communication
Single Button interface for ATmega32
« on: January 04, 2018, 01:31:42 pm »
So, I am trying to make a wrist watch and at first I had decided to use a standalone RTC module for it but someone one here suggested that AVR can keep time in low power modes. So, I looked in to it and turns out AVR's Asynchronous timers are optimized to be used with 32.768kHz oscillator. And software RTC can be implemented with power saver sleep mode.

Here's the documentation for it http://www.atmel.com/images/atmel-1259-real-time-clock-rtc-using-the-asynchronous-timer_ap-note_avr134.pdf

But the example code is written for Atmega128. But ATmega32 has different Timer/Counter that works with external crystal. So, I changed few things and ported it to work with Atmega32.

Code: [Select]
#include "atmel_start.h"
#include <avr/sleep.h>

static char not_leap(void);
static void init(void);

typedef struct {
unsigned char second;
unsigned char minute;
unsigned char hour;
} time;

time t;

int main(void)
{
/* Configure all eight pins of port B as outputs */
system_init();

/* Initialize registers and configure RTC. */
init();
while (1) {
/* Enter sleep mode.
* (Will wake up from timer overflow interrupt) */
sleep_mode();

/*The MCU should always be awake for a minimum of one
* TOSC cycle, before re-entering sleep mode. Otherwise,
* the interrupt flag will not be cleared, and the MCU
* will stay in sleep mode until it is reset. We solve
* this by doing a dummy write to the TCCR0 register
* and waiting until the ASSR register reports that TC0
* has been updated, before going to sleep*/

/* Dummy write the desired prescaler */
TCCR2 = (1 << CS20) | (1 << CS22);

/* Wait until TC0 is updated */
while (ASSR & ((1 << TCN2UB) | (1 << OCR2UB) | (1 << TCR2UB))) {
}
}
}

static void init(void)
{
/* Wait for external clock crystal to stabilize */
for (uint8_t i = 0; i < 0x40; i++) {
for (int j = 0; j < 0xFFFF; j++) {
/* Do a nop instruction to keep the empty
* loop from being optimized away */
asm volatile("nop");
}
}

/* Make sure all TC0 interrupts are disabled */
TIMSK &= ~((1 << TOIE2) | (1 << OCIE2));

/* set Timer/counter0 to be asynchronous from the CPU clock.
* This will clock TC0 from the external 32,768 kHz crystal. */
ASSR |= (1 << AS2);

/* Reset timer */
TCNT2 = 0;

/* Prescale the timer to be clock source/128 to make */
/* TC0 overflow precisely once every second. */
TCCR2 = (1 << CS20) | (1 << CS22);

/* Wait until TC0 is updated */
while (ASSR & ((1 << TCN2UB) | (1 << OCR2UB) | (1 << TCR2UB))) {
}

/* Enable Timer/Counter0 Overflow Interrupts */
TIMSK |= (1 << TOIE2);

/* Set the Global Interrupt Enable Bit */
sei();

/* Setting the sleep mode to be used to power save mode. */
set_sleep_mode(SLEEP_MODE_PWR_SAVE);

/* Enabling sleep mode */
sleep_enable();
}

/* keep track of time, date, month, and year */
ISR(TIMER2_OVF_vect)
{
if (++t.second == 60) {
t.second = 0;
if (++t.minute == 60) {
t.minute = 0;
if (++t.hour == 24) {
t.hour = 0;

}
What this code does is, wakes up microcontroller when the counter overflows and then increments the seconds variable in timer ISR and then goes back to sleep.
This can keep track of Hours , Minutes and Seconds using small amount of power. Now I need a single button interface so when the button is pressed once the watch displays time and when the button is pressed twice, you can set the minutes and hour variables to set time. At first I thought, I would use a hardware interrupt to wake the microcontroller if it is in sleep mode and then display the time. But then I don't know how I would implement setting time functionality. Can you use Interrupt pin in ISR as I/O pin? So, when I press interrupt, MC wakes up goes into hardware ISR and then looks for button press on the same pin, if the button is pressed once more it ecexutes the 'time setting code'.
How do I do it?
« Last Edit: January 04, 2018, 02:55:44 pm by MrOmnos »
 

Offline frozenfrogz

  • Frequent Contributor
  • **
  • Posts: 936
  • Country: de
  • Having fun with Arduino and Raspberry Pi
Re: Single Button interface for ATmega32
« Reply #1 on: January 04, 2018, 02:33:47 pm »
I am not a firmware developer but only tinker with Arduino for quick and dirty prototypes.

You could introduce an interrupt counter that keeps track of how often the ISR was called and then clear it after a certain amount of time.
Here are parts of my code for a simple dimming circuit:

Code: [Select]
volatile int count = 0;         // Count button presses, initialize to zero
volatile int setLevel = 0;     // Set new target brightness, initialize to zero
const int full = 255;           // Configure brightness levels
const int mid = 80;
const int low = 10;
const int timeOut = 1000;  // Time frame for multi-click detection
unsigned long time;           // Keep track of loop time

void loop() {
  time = millis();
  switch (count) {
    case 0: //Off
      setLevel = 0;
      break;
    case 1: // Full on
      setLevel = full;
      break;
    case 2: // Medium on
      setLevel = mid;
      break;
    case 3: // Low on
      setLevel = low;
      break;
    }
  fade(setLevel, time);
} // end loop

void buttonPressed() {  // This is the ISR
  static unsigned long lastInterrupt = 0;    // Time since last interrupt
  unsigned long interruptTime = millis();   // Time when interrupt occurred
  // If interrupts come faster than 200ms, assume it's a bounce and ignore
  if (interruptTime - lastInterrupt > 200) {
    if (count == 0) {
      lastInterrupt = interruptTime;
      count++;
    }
    else if (count > 0 && count < 3 && interruptTime <= lastInterrupt+timeOut) {
      count++;
    }
    else if (count > 0 && interruptTime >= lastInterrupt+timeOut) {
      lastInterrupt = interruptTime;
      count = 0;
    }
  }
} // end buttonPressed

This way you can also implement long button presses (detect if the button was pressed longer than X)
It is most likely not an elegant way to do it though :)
He’s like a trained ape. Without the training.
 

Online Nusa

  • Super Contributor
  • ***
  • Posts: 2417
  • Country: us
Re: Single Button interface for ATmega32
« Reply #2 on: January 04, 2018, 02:55:23 pm »
Glad you found my suggestion useful and practical.

As for the button, first thing you have to worry about is switch bounce. I assume you don't want to do that in hardware, so software has to do it. Just a matter of waiting until the switch is stable for a long enough time. Then you have to determine what's a long or a short press, if you're differentiating, and probably a lack of presses as well (e.g. timeout = exit setting mode?). Or counting presses if you're using a sequence...the details of the button and led human interface are up to you.

Once the AVR is awake, you could just do all this in your main loop, polling that button input and have your button ISR do nothing if not in sleep mode.

Another approach would be to set up your ISR to record both rising and falling edges with timestamps, then letting your main loop use those values instead of direct polling.

Or you could try to do it all in the ISR like frozenfrogz suggested. The viability of that will depend on the button interface you design.


An separate idea would be to require reboot to initiate time setting. In this case that would require removing the battery long enough to force a power down. You'd still have to write button code, of course.
« Last Edit: January 04, 2018, 03:04:20 pm by Nusa »
 

Offline MrOmnosTopic starter

  • Frequent Contributor
  • **
  • Posts: 268
  • Country: np
  • BE in Electronics and Communication
Re: Single Button interface for ATmega32
« Reply #3 on: January 05, 2018, 04:04:54 am »
Code: [Select]
int main(void)
{
/* Configure all eight pins of port B as outputs */
system_init();

/* Initialize registers and configure RTC. */
init();
while (1) {
/* Enter sleep mode.
* (Will wake up from timer overflow interrupt) */
sleep_mode();

/*The MCU should always be awake for a minimum of one
* TOSC cycle, before re-entering sleep mode. Otherwise,
* the interrupt flag will not be cleared, and the MCU
* will stay in sleep mode until it is reset. We solve
* this by doing a dummy write to the TCCR0 register
* and waiting until the ASSR register reports that TC0
* has been updated, before going to sleep*/

/* Dummy write the desired prescaler */
TCCR2 = (1 << CS20) | (1 << CS22);

/* Wait until TC0 is updated */
while (ASSR & ((1 << TCN2UB) | (1 << OCR2UB) | (1 << TCR2UB))) {
}
}
}
This is my main function.

init();
does all the setup for counter overflow interrupt and selecting the sleep mode.

then

sleep_mode();
puts the micro controller to sleep. Now if, I attach an external interrupt and wake up the microcontroller where will it return? On top of the main or in the while(1). I guess in while(1). The first thing in while(1) is sleep_mode(); which will put it back to sleep. But the code I used has few instructions even after the sleep_mode(). It is clear in the comments what those instructions are there for but does the micro controller execute instructions even after the sleep_mode(). How does it work?

In short, I am having a hard time understanding the program flow with the sleep mode involved. When the micro controller wakes up, where does it start? I guess in while(1). I feel so dumb right now. I have so many questions. If I use hardware interrupt to wake up the microcontroller will the RTC lose time because the overflow will not be able to trigger interrupt or does it happen in parallel?
« Last Edit: January 05, 2018, 04:07:16 am by MrOmnos »
 

Offline MrOmnosTopic starter

  • Frequent Contributor
  • **
  • Posts: 268
  • Country: np
  • BE in Electronics and Communication
Re: Single Button interface for ATmega32
« Reply #4 on: January 05, 2018, 07:47:25 am »
Glad you found my suggestion useful and practical.

As for the button, first thing you have to worry about is switch bounce. I assume you don't want to do that in hardware, so software has to do it. Just a matter of waiting until the switch is stable for a long enough time. Then you have to determine what's a long or a short press, if you're differentiating, and probably a lack of presses as well (e.g. timeout = exit setting mode?). Or counting presses if you're using a sequence...the details of the button and led human interface are up to you.

Once the AVR is awake, you could just do all this in your main loop, polling that button input and have your button ISR do nothing if not in sleep mode.

Another approach would be to set up your ISR to record both rising and falling edges with timestamps, then letting your main loop use those values instead of direct polling.

Or you could try to do it all in the ISR like frozenfrogz suggested. The viability of that will depend on the button interface you design.


An separate idea would be to require reboot to initiate time setting. In this case that would require removing the battery long enough to force a power down. You'd still have to write button code, of course.

Hi, I have decided what I am going to do. I enabled IN2 external interrupt. I want increment a variable everytime the interrupt occurs. I will use this variable to do the job assigned to that no of button press. After the job is finished the variable will be reset to 0 like frogz suggested. But I will still need to debounce the button. How do I debounce the button?
 

Offline MrOmnosTopic starter

  • Frequent Contributor
  • **
  • Posts: 268
  • Country: np
  • BE in Electronics and Communication
Re: Single Button interface for ATmega32
« Reply #5 on: January 05, 2018, 12:03:14 pm »


Once the AVR is awake, you could just do all this in your main loop, polling that button input and have your button ISR do nothing if not in sleep mode.


Hi, thank you for your advice of using the mc as RTC. So, here's what I am doing. I am debouncing the button using a cap because I want to spend less time in ISR. How do I make hardware interrupt not do anything when awake?  Do I disable it then enable it again before going to sleep?
« Last Edit: January 05, 2018, 12:10:03 pm by MrOmnos »
 

Online Nusa

  • Super Contributor
  • ***
  • Posts: 2417
  • Country: us
Re: Single Button interface for ATmega32
« Reply #6 on: January 05, 2018, 12:57:54 pm »


Once the AVR is awake, you could just do all this in your main loop, polling that button input and have your button ISR do nothing if not in sleep mode.


Hi, thank you for your advice of using the mc as RTC. So, here's what I am doing. I am debouncing the button using a cap because I want to spend less time in ISR. How do I make hardware interrupt not do anything when awake?  Do I disable it then enable it again before going to sleep?

You got it. Disabling/enabling the ISR as required is straightforward. On the other hand, if you still have a purpose for the interrupt trigger, then you put a conditional inside the ISR.  e.g. if (lowPowerMode) then {wake up stuff} else {already awake stuff}
 

Offline MrOmnosTopic starter

  • Frequent Contributor
  • **
  • Posts: 268
  • Country: np
  • BE in Electronics and Communication
Re: Single Button interface for ATmega32
« Reply #7 on: January 05, 2018, 02:22:38 pm »


Once the AVR is awake, you could just do all this in your main loop, polling that button input and have your button ISR do nothing if not in sleep mode.


Hi, thank you for your advice of using the mc as RTC. So, here's what I am doing. I am debouncing the button using a cap because I want to spend less time in ISR. How do I make hardware interrupt not do anything when awake?  Do I disable it then enable it again before going to sleep?

You got it. Disabling/enabling the ISR as required is straightforward. On the other hand, if you still have a purpose for the interrupt trigger, then you put a conditional inside the ISR.  e.g. if (lowPowerMode) then {wake up stuff} else {already awake stuff}

Here's what I did. I used a variable to be incremented when interrupt occurred and also recorded the time stamp in seconds (using the RTC already running) of the moment the interrupt occurred. Now that interrupt has occurred counter has increased from 0 to 1 indicating the button has been pressed at least once so I  display the time and I set the counter back to 0.

Now I check if 2 seconds in RTC time has passed since last interrupt and also poll the button to see if it is still pressed. If yes, disable interrupt. This takes the user in to time setting mode. Now I can poll the pin and read inputs the way I like.

After setting time the MC will go back to sleep and enable the Interrupt [yet to be implemented in code]

This way the time will be displayed every time the button is pressed and go into time setting mode if the button is kept pressed for 2 seconds or more.

Can you please look at my code and give me suggestion, it not that big and I used screenshot.



made a small mistake.

Code: [Select]
interruptFlag = 0 
not
Code: [Select]
interruptFlag == 0
« Last Edit: January 05, 2018, 02:24:55 pm by MrOmnos »
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf