Fleury LCD library and interrupt timing

Go To Last Post
14 posts / 0 new
Author
Message
#1
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Hi all,

 

I have an interesting problem using Fleury's excellent LCD library:  the LCD functions seem to take so long to finish that they cause the external interrupt readings to glitch if I am also writing to the LCD about every second.  See code below -- works well as long as I keep lines around 171 commented out and don't see the every second reading.    I realize I could read button input on regular tick, but I want to get experience with interrupts and it is just toy code.

 

My main question -- How would one calculate the timing necessary to run the LCD library?  In the LCD code, there are lots of busy wait / delay functions, do folks add those up somehow?

 

I also wondered -- Does anyone have  design ideas to handle problems with output like this?  I am thinking about setting up two microcontrollers, one to run the LCD, one to communicate via serial.  Might be fun and educational anyway, but I wonder if this approach would translate to a real application.

 

Looking forward to everyone's ideas!

 

/*
    lcd1.c
    Synopsis:  Show stuff on lcd, increment counter with each push, reset with long hold
    Created: 2/10/2018 7:38:09 PM
    Author : wsprague

    HARDWARE DESCRIPTION
        PD2      (4) is button infput / extern interrupt
        PC5     (28) power on led
        PBx      (*) lcd control pins   

    Notes:

        2018-03-11 wws:  Still very flaky with hold down functionality.
            TODO:  pull out lcd update into function.

        2018-03-08 wws: Sort of working.  Need to rework state machine because currently just
            pressing not doing anything on release, but have the state/ event structure for latter.

        2018-02-10 wws:  to do hold => reset correctly, need to set timer and interrupt,
                along with a state where button held down but timer elapsed.  First doing
                button push increments only, then adding reset functionality.

 */

#define F_CPU 1000000UL  // delay.h supplies reasonable default, but this fixes warning

#include <stdint.h>
#include <stdlib.h>
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/sleep.h>
#include <avr/eeprom.h>
#include <util/atomic.h>

// include header file for lcd library
#include "lcd.h"

// define state names and numbers
#define _0QUIES    0
#define _1HOLD     1

// define event names and numbers
#define _0NULL     0
#define _1PUSH     1
#define _2REL      2
#define _3TICK     3
#define _4CLRTIMR  4

#define RADIX       15    // radix for lcd numeric output, want hex
#define HOLD_TICKS 1000    // how many ticks to wait for hold before reset
#define TICK_TICKS  3    // how many ticks before heartbeat

// globals
volatile uint8_t   state      = _0QUIES;  // current state
volatile uint8_t   event      = _0NULL;   // latest event
volatile uint32_t  tick       = 0;        // ticks (~10ms) since reset, incremented each wake up.  Overflows on 1.36 years
volatile uint16_t  pcount     = 0;        // count of button pushes,
// volatile uint32_t  ptick      = 0;        // tick at button push
uint16_t EEMEM eeprom_pcount  = 0 ;       // eeprom memory allocation 

// progmem something with name of project

// define short nop macro
#define _NOP() do { __asm__ __volatile__ ("nop"); } while (0)

//
int main () {
    char buffer[32];  // to store lcd stuff

    // start state in 0, waiting for input
    state = _0QUIES;

    // initialize lcd
    lcd_init(LCD_DISP_ON);
    lcd_clrscr();
    // lcd_gotoxy(0,0);
    // lcd_puts("Hello wife!");

    // update lcd with pcount and progmem info on program
    // get pcount from EEPROM  -- TODO busy wait until ready if nec, check error somehow
    // eeprom_is_ready()
    pcount = eeprom_read_word (& eeprom_pcount );
    if (pcount == 0xFFFF) {
        pcount = 1;
        eeprom_update_word (& eeprom_pcount, pcount );
    }
    lcd_gotoxy(0,1);
    itoa(pcount, buffer, RADIX);
    lcd_puts(buffer);

    // power on indicator light on PC5 (28), interrupt toggle PC4 (27)
    DDRC  |= ( 1 << PC5) ;
    PORTC |= ( 1 << PC5);
    DDRC  |= ( 1 << PC4) ;

    // config  external interrupt (4)
    DDRD  |= (0 << PD2 );    // input pin
    PORTD |= (1 << PD2 );    // set pull up
    EICRA |= (1 << ISC01);    // 10  falling edge
    EIMSK |= (1 << INT0);     // Allow external interrupt 

    // config TC0 ticking at every something  CTC.  No pin output, just interrupt
    TCCR0A |= (1 << WGM01);                // mode 2 CTC
    OCR0A   = TICK_TICKS;                           // turn over when hit OCROA, maybe every sec??? just dorking
    TCCR0B |= (1 << CS02)  | (1 << CS00);  // turn on, 1024 prescale (think 64 prescale too high to do anything else)
    TIMSK0 |= (1 << OCIE0A);               // fire off interrupt on match

    // config TC1 for simple timer for hold time, ~1 sec, but don't start until button push
    // defaults to mode 0 timer
    TIMSK1 |= (1 << OCIE1A);   // enable timer interrupt
    OCR1A = HOLD_TICKS;              // set match to delay 1 sec. XXX make constant or define
    TCNT1 = 0;                 // set start at 0

    // set sleep mode for later
    set_sleep_mode(SLEEP_MODE_IDLE);

    sei();
    while (1) {
        // sleep, waking on interrupts

        // sleep drill
        sleep_enable();
        sleep_cpu();
        sleep_disable();

        // turn off interrupts
        cli();

        // do state transitions and associated actions

        // ... push button
        if        (event == _1PUSH) {
            event = _0NULL;      // clear event flag
            state = _1HOLD;      // set next state. XXX THIS IS CAUSING WEIRD DOUBLE PUSH THING

            // increment counter and write to eeprom
            ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
                pcount = pcount+1;
                eeprom_update_word (& eeprom_pcount, pcount );
                lcd_gotoxy(0,1);
                itoa(pcount, buffer, RADIX);
                lcd_puts(buffer);
            }

            // set interrupt to fire on rising edge (11)
            EICRA |= (1 << ISC01 );
            EICRA |= (1 << ISC00 );

            // start timer for hold and clear process
            // TCNT1 = 0;
            // TCCR1B |= (1 << CS12) | (1 << CS10);          // 1024 prescale
        // ... release button.
        } else if (event == _2REL) {
            event = _0NULL;      // clear event flag
            state = _0QUIES;     // set next state

            // set interrupt to fire on down edge (10)
            EICRA |= (1 << ISC01 );
            EICRA &= ~(1 << ISC00 );

            // stop timer for hold and clear, zero tcnt
            // TCNT1 = 0;                                                 // zero counter
            // TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));      // zero => stop
        } else if (event == _3TICK) {
            event = _0NULL;       // clear event flag
            // leave state alone
            // tick updated in ISR

            // XXX Following block screws up regular increment if uncommented
            // ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
            //     lcd_gotoxy(0,0);
            //     itoa(tick/1000, buffer, RADIX); // should use bit ops for speed
            //     lcd_puts(buffer);
            // }
        } else if (event == _4CLRTIMR) {
            event = _0NULL;      // clear event flag
            // state = _0QUIES;     // leave state alone 

            // ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
            //     // zero counter and write to eeprom
            //     pcount = 0;
            //     eeprom_update_word (& eeprom_pcount, pcount );
            //     lcd_gotoxy(0,1);
            //     itoa(pcount, buffer, RADIX);
            //     lcd_puts(buffer);
            // }
            // stop timer for hold and clear, zero tcnt
            // TCNT1 = 0;                                                 // zero counter
            // TCCR1B &= ~((1 << CS12) | (1 << CS11) | (1 << CS10));      // zero => stop
        } else  {
        }                                                

        // turn on interrupts
        sei();
    }
}

ISR(INT0_vect){

    PINC |= (1 << PC4);  // toggles led

    // set event based on current state
    if (state == _0NULL) {
        event = _1PUSH;
    } else if (state == _1HOLD) {
        event = _2REL;
    }
}

// ISR for TC0 CTC at 10ms
ISR(TIMER0_COMPA_vect){
    event = _3TICK;
    tick++;
}

// ISR for TC1 hold timer
ISR(TIMER1_COMPA_vect){
    event = _4CLRTIMR;
}

 

This topic has a solution.
Last Edited: Mon. Apr 16, 2018 - 04:03 AM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 1

IMO/IME the "solution" is pretty straightforward -- don't keep interrupts turned off for a long period of time.  I've done scores of character LCD AVR apps over the years.  I just let the "update display" code section be interrupted by USART/timer/ADC/etc.  Never noticed a timing problem.

You can put lipstick on a pig, but it is still a pig.

I've never met a pig I didn't like, as long as you have some salt and pepper.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

theusch wrote:
don't keep interrupts turned off for a long period of time.
+1

        // turn off interrupts
        cli();

        // do state transitions and associated actions

Looks like bad design when there's a ton of work after the cli() before you reach:

        }                                                

        // turn on interrupts
        sei();

If this is about atomic protection of the state variable then just use ATOMIC_BLOCK() around some code in main() that takes a copy (atomically) of the interrupt updated variable and then, with interrupts re-enable, process the state change.

 

EDIT: actually "event" is a unit8_t so should be naturally atomic anyway.

Last Edited: Wed. Mar 14, 2018 - 05:16 PM
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

I think the others have solved your "problem".

 

But, to the general question:

forkandwait wrote:
How would one calculate the timing necessary to run <some piece of code> 

To actually calculate it, you would have to look at the machine instructions, and add up the cycles for each - a very laborious process.

 

A more practical way to find the time taken by a piece of code is to set an output pin at the start, and clear it at the end - and then look at the timing on an oscilloscope or logic analyser.

 

An alternative is to sample a suitably-fast timer at the start, and again at the end - and deduce the time from there.

 

Tools are available to help with this.

 

https://en.wikipedia.org/wiki/Profiling_(computer_programming)

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Another way (but only if there's no external hardware dependency) is to run it in the Simulator which has a CPU cycle counter and Stopwatch facility.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Thanks everyone for the advice.  I got the glitchiness to go away, at least with manual testing.  I am still working on the overall project, which includes getting a long push reset functionality, but once it works (or I hit a stumbling block), I will post the finished code.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Use your 10ms tick to time the hold time - no need to use another timer unless you want microsecond precision.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

Use your 10ms tick to time the hold time - no need to use another timer unless you want microsecond precision.

This is what everyone recommends, but I don't see the benefit except to free up another timer.  Could you / someone explain the advantage to this approach?  I will have to try it, maybe the next project, as long as I am just dorking around anyway.

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

forkandwait wrote:
Could you / someone explain the advantage to this approach?
Because there is a law of nature that says "the chip you chose always has one fewer timers/UART/SPI/... than you actually require!" :-)

 

As you get to more advanced projects you will see this happen so "normal" practice is simply to run one timer for a "system tick" (it may be doing other things like PWM at the same time) and then you co-ordinate a lot of different activities off that one timer. In fact this is such a common requirement that in ARM processors (Coretx M0, M3, M4 etc) ARM actually went as far as giving all the CPUs a "system tick" counter as standard!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

forkandwait wrote:
I don't see the benefit except to free up another timer. 

As clawson says. that is exactly the benefit!

 

 

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

By using one timer you’ve paid the entrance fee. Using it to count other software timers virtually comes for free. The ‘entrance fee’ in this instance is the interrupt overhead. Currently, you’ve got around 20-40 cycles of overhead for maybe 10 cycles of actual work. ( note: i pulled the numbers out of my arse).

Here’s something i wrote earlier that might explain some more hings-
https://www.avrfreaks.net/forum/tutc-multi-tasking-tutorial-part-1?page=all

This reply has been marked as the solution. 
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

It is a little late, but with everyone's good advice, I used a 10ms tick interrupt to wake up and check button input being pulled low, counting for 4 cycles (approx 35ms) before incrementing the variable for the button.  Thanks!

  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

So you can now mark the solution?

 

See Tip #5

Top Tips:

  1. How to properly post source code - see: https://www.avrfreaks.net/comment... - also how to properly include images/pictures
  2. "Garbage" characters on a serial terminal are (almost?) invariably due to wrong baud rate - see: https://learn.sparkfun.com/tutorials/serial-communication
  3. Wrong baud rate is usually due to not running at the speed you thought; check by blinking a LED to see if you get the speed you expected
  4. Difference between a crystal, and a crystal oscillatorhttps://www.avrfreaks.net/comment...
  5. When your question is resolved, mark the solution: https://www.avrfreaks.net/comment...
  6. Beginner's "Getting Started" tips: https://www.avrfreaks.net/comment...
  • 1
  • 2
  • 3
  • 4
  • 5
Total votes: 0

clawson wrote:
If this is about atomic protection of the state variable then just use ATOMIC_BLOCK() around some code in main() that takes a copy (atomically) of the interrupt updated variable and then, with interrupts re-enable, process the state change. EDIT: actually "event" is a unit8_t so should be naturally atomic anyway.
I'd make a copy anyway.
What is the code supposed to do if event changes
after the first test and before the last?
Making event part of a switch would have much the same effect.
I'd expect the switch to produce less code.

 

Also PINC |= (1 << PC4)  should be  PINC = (1 << PC4) .
A sensible translation of the former would clear every bit of PORTC.

 

Also, why PC4?  Hasn't anyone heard of 4?

International Theophysical Year seems to have been forgotten..
Anyone remember the song Jukebox Band?