Single Button interface for ATmega32 - Page 1
Forum section: electronics / projects

author: MrOmnos
author_rank: Frequent Contributor
timestamp: January 04, 2018, 01:31:42 pm
content: 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 ithttp://www.atmel.com/images/atmel-1259-real-time-clock-rtc-using-the-asynchronous-timer_ap-note_avr134.pdfBut 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:#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?

author: frozenfrogz
author_rank: Frequent Contributor
timestamp: January 04, 2018, 02:33:47 pm
content: 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:volatile int count = 0; // Count button presses, initialize to zerovolatile int setLevel = 0; // Set new target brightness, initialize to zeroconst int full = 255; // Configure brightness levelsconst int mid = 80;const int low = 10;const int timeOut = 1000; // Time frame for multi-click detectionunsigned long time; // Keep track of loop timevoid loop() {time = millis();switch (count) {case 0: //OffsetLevel = 0;break;case 1: // Full onsetLevel = full;break;case 2: // Medium onsetLevel = mid;break;case 3: // Low onsetLevel = low;break;}fade(setLevel, time);} // end loopvoid buttonPressed() { // This is the ISRstatic unsigned long lastInterrupt = 0; // Time since last interruptunsigned long interruptTime = millis(); // Time when interrupt occurred// If interrupts come faster than 200ms, assume it's a bounce and ignoreif (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 buttonPressedThis 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

author: Nusa
author_rank: Super Contributor
timestamp: January 04, 2018, 02:55:23 pm
content: 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.

author: MrOmnos
author_rank: Frequent Contributor
timestamp: January 05, 2018, 04:04:54 am
content: Code: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.thensleep_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?

author: MrOmnos
author_rank: Frequent Contributor
timestamp: January 05, 2018, 07:47:25 am
content: Quote from: Nusa on January 04, 2018, 02:55:23 pmHi, 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?

author: MrOmnos
author_rank: Frequent Contributor
timestamp: January 05, 2018, 12:03:14 pm
content: Quote from: Nusa on January 04, 2018, 02:55:23 pmHi, 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?

author: Nusa
author_rank: Super Contributor
timestamp: January 05, 2018, 12:57:54 pm
content: Quote from: MrOmnos on January 05, 2018, 12:03:14 pmYou 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}

author: MrOmnos
author_rank: Frequent Contributor
timestamp: January 05, 2018, 02:22:38 pm
content: Quote from: Nusa on January 05, 2018, 12:57:54 pmHere'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:interruptFlag = 0notCode:interruptFlag == 0