Currently I have Atmega8 1MHz Microcontroller. I'm working with C language. So basically my program shows numbers on the 7 segment display. And each time you press the button it increases number 1
Right now I want to measure time and if user presses button for 3 seconds. Only thing I need is how to measure if interrupt reached to 3 seconds. I need logic behind it, should I use if statement with delay? or I don't know maybe something else
Here is my code:
#define F_CPU 1000000UL
#define IRQ1 INT0_vect
#define IRQ2 INT1_vect
#include <avr/io.h>
#include <util/delay.h>
#include <avr/interrupt.h>
volatile int the_count = 0;
volatile int i;
volatile int k;
ISR(IRQ1){
if (the_count < 100){
the_count ;
i = the_count % 10;
k = the_count / 10;
}
else{
the_count = 0;
i = the_count % 10;
k = the_count / 10;
}
}
ISR(IRQ2){
if (the_count < 100){
the_count = the_count;
i = the_count % 10;
k = the_count / 10;
}
else{
the_count = 0;
i = the_count % 10;
k = the_count / 10;
}
}
void init()
{
DDRD = 0b00000111;
DDRB = 0b11111111;
PORTB = 255;
_delay_ms(2000);
PORTB = 0;
GICR = 0xc0;
MCUCR = 0x08;
IRQ1;
IRQ2;
}
int main(void){
init();
GICR = 0xc0;
MCUCR = 0x08;
sei();
int digit[] = {0b000000101, 0b10111101, 0b00100110, 0b10100100, 0b10011100, 0b11000100, 0b01000100, 0b10101101, 0b00000100, 0b10001100};
int dig1 = 0b00000110; //first digit area on display
int dig2 = 0b00000101; //second digit area on display
while (1)
{
if (k < 1){
PORTB = digit[0];
PORTD = dig1;
_delay_ms(1);
PORTD = dig2;
PORTB = digit[i];
_delay_ms(1);
}
else if (k>=1 && i==0 && the_count<100){
PORTB = digit[k];
PORTD = dig1;
_delay_ms(1);
PORTD = dig2;
PORTB = digit[0];
_delay_ms(1);
}
else if (k>=1 && i!=0 && the_count<100){
PORTB = digit[k];
PORTD = dig1;
_delay_ms(1);
PORTD = dig2;
PORTB = digit[i];
_delay_ms(1);
}
else {
the_count = 0;
}
}
}
CodePudding user response:
I am not about to wade through all that code to answer your question - not least because your naming convention, lack of comments or I/O abstraction make it very difficult to determine what I/O is for what or what code does what. But the "logic" would be as follows (replace timing and I/O calls with those available to, or implemented by you as appropriate):
bool buttonPressHoldDetect( uint32_t hold_time_millisec )
{
static bool release_pending = false ;
bool button_hold_time_event = false ;
static bool previous_button_pressed_state = isButtonPressed() ;
bool button_pressed = isButtonPressed() ;
// If button down...
if( button_pressed )
{
// If it was previously up...
if( !previous_button_pressed_state )
{
// Timestamp the button down event
uint32_t button_down_start = getTickMillisec() ;
}
// If button held down for hold time...
if( !release_pending &&
(getTickMillisec() - button_down_start) > hold_time_millisec )
{
button_hold_time_event = true ;
release_pending = false ;
}
}
else
{
// button released, allow a subsequent press to be timed
release_pending = false ;
}
// Retain previous state for event detection
previous_button_pressed_state = button_pressed ;
// Return true when the button has been
// held for hold time
return button_hold_time_event;
}
Then you would have a polling loop of the form:
for(;;)
{
// If button held for three seconds...
if( buttonPressHoldDetect( 3000 ) ;
{
// Do button held for three second stuff
}
// Do other stuff
}
The zero-delay / no busy-wait polling allows you to perform other work in the loop. Of course the other work should also avoid delays and busy-waits - your loop-time (and therefore the button polling rate) is the sum of all the work done in the loop (including delays) so for the most responsive system you need to minimise the work done and make the loop as deterministic (constant loop time) as possible.
Specifically:
you do not need to use interrupts (other then a tick interrupt for timing perhaps). You could, and the interrupt might simply set a flag that is returned by
isButtonPressed()
for example, but it only adds complexity (especially w.r.t. switch de-bounce), and I would advise simply polling it,you should not use a delay - during a delay you can do no other useful work. (unless you are using a multi-thread scheduler). Use timestamping and elapsed time instead, and avoid "busy-waits" of any kind.
You do need to consider switch bounce, and that can be handles similarly:
bool isButtonPressed()
{
static const uint32_t DEBOUNCE_MILLISEC = 20 ;
static bool button_pressed = readInput( BUTTON ) != 0 ;
static uint32_t button_event_time = 0 ;
// Get the current time and button state
uint32_t now = getTickMillisec() ;
bool current_button_state = readInput( BUTTON ) != 0 ;
// If button changes state after the debounce period...
if( (now - button_event_time) > DEBOUNCE_MILLISEC
&& current_button_state != button_pressed )
{
// Change the button state and timestamp the event
button_pressed = current_button_state ;
button_event_time = now ;
}
return button_pressed ;
}
Further if the work done in the loop is minimal, but you want a specific and deterministic loop time that is longer then the body execution time (that would be essential for example in a PID control loop), then:
static const int LOOP_TIME_MILLISEC = 20 ; // loop 50 times per second
int loop_start_time = 0 ;
for(;;)
{
uint32_t now = getTickMillisec() ;
// if time to start loop iteration...
if( (now - loop_start_time) >= LOOP_TIME_MILLISEC )
{
loop_start_time = now ;
// If button held for three seconds...
if( buttonPressHoldDetect( 3000 ) ;
{
// Do button held for three second stuff
}
// Do other stuff
}
}
I have not used an AVR in a long time, but if you do not already have a suitable timing function the getTickMillisec()
(along with initialisation and ISR) can be implemented in the following manner:
#include <avr/io.h> ;
#include <avr/interrupt.h> ;
// Timer reload value for 1ms
#define SYSTICK_RELOAD (1000000UL / 1000)
// Millisecond counter
volatile uint32_t tick_millisec = 0 ;
ISR (TIMER1_COMPA_vect)
{
tick_millisec ;
}
void sysTickInit()
{
// CTC mode, Clock/1
TCCR1B |= (1 << WGM12) | (1 << CS10);
// Load the output compare
OCR1AH = (SYSTICK_RELOAD >> 8);
OCR1AL = SYSTICK_RELOAD ;
// Enable the compare match interrupt
TIMSK1 |= (1 << OCIE1A);
// Enable interrupts
sei();
}
uint32_t getTickMillisec()
{
uint32_t now = 0 ;
// Read tick count and re-read if it is not consistent
// (due interrupt pre-emption and update during non-atomic access)
do
{
now = tick_millisec ;
} while( now != tick_millisec ) ;
return now ;
}
Note the loop in getTickMillisec()
- access to tick_millisec
is non-atomic on an 8 bit device, so it is possible that the ISR updates part way through it being read. The loop re-reads it until it is consistent (i.e. the same value read twice). Alternatively you could simply disable interrupts:
uint32_t getTickMillisec()
{
// Read tick count with interrupts disabled to ensure consistency
cli() ;
uint32_t now = tick_millisec ;
sei() ;
return now ;
}
But that can affect the timing of interrupt handlers in general so might be best avoided.
For details of the 16 bit TIMER1 see the ATmega8 datasheet from page 75.