Author Topic: Non-Blocking Updates to a Character LCD  (Read 4468 times)

0 Members and 1 Guest are viewing this topic.

Offline xquercusTopic starter

  • Contributor
  • Posts: 47
  • Country: us
Non-Blocking Updates to a Character LCD
« on: May 12, 2012, 08:32:32 pm »
In projects I've done which involve updating a character LCD with a microcontroller, I've usually rewritten the entire display even if only a single character has required updating.  That is, on a 16 character LCD, I would update every character in turn (waiting 40us between each character) for a total delay of over 640us.  In a sense, I've been treating the LCD as a blocking output device.  My software does absolutely nothing for that 640us except wait and update the LCD.  This method certainly works, but it's fairly wasteful.  I'm at the point now where I want to do "other things" during that 640us period.

I'm sure others have dealt with this exact situation.  Here's the solution I've come up with and was wondering if it could be improved upon.  If the microcontroller platform is of interest, I've mostly been using the PIC18F series.

My plan is to keep a 16 character buffer, containing the data I wish to display on the LCD, in memory.  Additionally, I plan to keep a set of 16 flags -- each flag associated with one character in the buffer.  In order to update the LCD I first write my output to the buffer and set the flag associated with each character which has changed and thus needs to be updated on the LCD display.

Concurrently, I'll use one of the timers to schedule interrupts every 40us (or likely much longer).  When the interrupt is called, it will check each flag in turn until it finds one which is set (signifying a byte which needs to be updated on the LCD) or it determines that all flags have been cleared (no updates are required).  If the interrupt finds a set flag, it will update the corresponding character on the LCD, unset the flag, clean up the interrupt control bit/s, reset the timer, and exit.  If all flags are cleared (no updated are necessary) the interrupt handler will simply clean up the interrupt control bit/s, reset the timer and exit. Each interrupt will update, at most, a single character.  The interrupt will then exit so the micro is free to do "other things".

Does this seem like a reasonable approach to update the character LCD or is there an alternate method I should consider?
 

Offline Kremmen

  • Super Contributor
  • ***
  • Posts: 1289
  • Country: fi
Re: Non-Blocking Updates to a Character LCD
« Reply #1 on: May 12, 2012, 08:57:02 pm »
That would work i guess. The way i would approach this is that updating a display happens in the "human" timeframe. So the time it takes to update is not critical because it will be faster than the user can perceive, whatever way you do it. I am a fan of the FreeRTOS operating system and in that environment things like display updates can be done painlessly as part of the UI task. There it makes no sense/difference to optimize things in the way you describe because you don't really gain anything by doing that. The scheduler of the OS lets higher priority tasks to run as needed, asyncronously to the display update. So the problem kind of goes away. Of course you would need to refactor yor app for such an OS so this could be a less than easy solution.
Nothing sings like a kilovolt.
Dr W. Bishop
 

Offline free_electron

  • Super Contributor
  • ***
  • Posts: 8545
  • Country: us
    • SiliconValleyGarage
Re: Non-Blocking Updates to a Character LCD
« Reply #2 on: May 12, 2012, 09:39:45 pm »
There is no need to update the display 'immediately'. the human eye cannot follow anyway.
Here is how i do it often :
Have a ram buffer containing lcd data.
Have a routine that can copy this buffer to the lcd.
Call this routine on every pass through the main loop. The main loop is a timed event.

In circumstances where an immediate update is required you call the update routine locally.

if i know the main loop is short and cycles fast there is a special update routine that only sends one character to the lcd. so a 16x2 character display takes 32 loops to fully refresh. I make this routine in such a way that i can pass an argument how many characters are allowed to be updated ,and at which offsett.
i can invoke the 'offset' routine when i know i have just update a 3 character field and this is important to show the user . the other characters will then follow bit by bit as called by the mainloop using the single character algorithm.

code below is in a kind of 'pseudo language'. i don't know what you use to write code .
Code: [Select]
Sub UpdateFull()
    select_line_1
    for x = 0 to 15
        write_char buffer_line_1(x)
    next x
    select_line_2
    for x = 0 to 15
        write_char buffer_line_2(x)
   next x
end sub

sub Updatestd()
    static line as integer, static character as byte
    character = character +1
    if character = 16 then
        character=0
        line=not(line)
        if line then select_line1 else select_line2
    end if
    if line then write_char buffer_line_2(x)
    else write_char buffer_line_1(x)
end sub

sub Updateblock(line,offset,length)
    if line then select_line1 else select_line2
   for x = offset to length
        if line then write_char buffer_line_2(x)
        else write_char buffer_line_1(x)
  next x
end sub


the above is the 'base' logic.

i apply some other trickery . i use array pointers... so i can avoid the if-then -else constructions. i force the two buffer lines to hard addresses via a compiler directive. And then i store the bases of both line buffers as constants. this avoids runtime calculations. everything is calculated at compile time and no runtime is wasted on any pointer arithmetic.
       
Code: [Select]
 
dim line_1_buffer[15] @ 0x0100  ' location 256
dim line_2_buffer[15] @ 0x0110  ' location 272

const line_1 = 0x0100
const line_2 = 0x0110



sub Updateblock(line,offset,length)
  if line = line_1 then
    write_lcd_cmd(selectline1)
  else
    write_lcd_cmd(selectline1)
    memptr=line + offset
    while length >0
        write_lcd_data(memread(memptr)
        length--
    end while
[code]

what i do above is create a pointer and load it with the line and the start offset.
all i need to do then is copy bytes from this pointer to the lcd and increment the pointer
i code it up as a decrement of 'length'. it depends a bit on the compiler how it gets optimized but you can 'shave off' loop constructions this way. if the cpu has two dptrs i sometimes code it so that i manipulate the second dptr myself.

now .. that 40 microseconds is grossly exagerated. this is true if you have a real hd48870 chip. that thing is slow ... the newer KS0xx or other 'hd48870 compatible' based lcds are much faster.

another thing : if you use premade libraries for lcd's that allow you to do arbitrary pin selections : those waste a tremendous amount of cycles.
i always use the lcd in 8 bit mode , use a full port for data and use the E and RS line from another port that is bit adressable.

Doing that has another advantage : i set the data on the port : toggle the EN line high and go off doing something else. the display looks only at the edge... that means i can go off do other things..
as long as my main loop takes longer than the lcd processing time for a single character i am ok.
[code]

sub updatestd
    lcd_en=false
   sub Updatestd()
    static line as integer, static character as byte
    character = character +1
    if character = 16 then
        select_line2
    end if
    if character = 32 then
        character = 0
        select_line1
    end if
   portx = memread (line_1+character)
  lcd_enable = true
end sub


mainloop :
  updatestd
  .........
goto mainloop.

the above code combines both techniques.
the lcd data is stored as two 16 char lines in a single 32 bit buffer.
i disable the lcd first.
increment character pointer and then figure out two things :
did i hit 16 ? -> tell lcd to select line 2
did i hit 32 -> roll back to 0 and select line 1
send the contents of the buffer directly to the i/o port
enable the lcd

the LCD gets the full length of the main loop to do whatever it needs to do...

Now this technique requires that the main program is a statemachine that runs in an endless loop. i typically code up my programs that way. i make the logic in suach a way that on every pass i do the following in this order :

-scan keyboard matrix and other 'inputs'
- check scheduled processes
- update lcd
- enter statemachine and process inputs , generate outputs and updat lcd buffers if needed.

for example a program that can read two keys and can change the blinking speed of an led on an io port. it also updates a display with text :
Code: [Select]
while 1
  ' timed process
  if blink_enable then
      if blink_counter >0 then
         decr blink_counter
      else
         led = not (led)  ' led is an io pin
      blink_counter = blinkrate
  end if
 
 
  scankeys
  refresh_lcd
 
  case state
      idle :      blink_enable=0
                  if key = key_1 then
                     state = blinking
                     blink_enable=1
                     lcd_text = "blinking"
                  end if
      blinking :  case key
                     key_1  : state = idle
                              blink_enable =0
                              lcd_text = "idle    "
                     key_2  : if blinkrate = 100 then blinkrate =200 else blinkrate = 100
                              lcd_text2 = blinkrate
                  endcase
   endcase
       
wend
[code]

key 1 toggles between blinking and not blinking.
if we are blinking key 2 can change the blinkrate. if we are not blinking then key 2 does nothing.

the above technique works perfect to make complex menu systems in a non blocking fashion. depending on the state you are in you have  a select case that reacts to the keypresses. this whole system is non blocking.
it is also somewhat deterministic you know upfront how long is spent in the command loop. the only jitter comes from the state switching.
if i need exact timed processes i typically run a hardware counter that creates interrupts. the interrupt processor does the scheduled jobs and then exits.
any time i am not in the interrupt process the main loop is cycling through the state machine and does the 'slow' things.
« Last Edit: May 12, 2012, 09:45:09 pm by free_electron »
Professional Electron Wrangler.
Any comments, or points of view expressed, are my own and not endorsed , induced or compensated by my employer(s).
 

Offline Rufus

  • Super Contributor
  • ***
  • Posts: 2095
Re: Non-Blocking Updates to a Character LCD
« Reply #3 on: May 12, 2012, 10:35:22 pm »
Does this seem like a reasonable approach to update the character LCD or is there an alternate method I should consider?

How long does it take to write a character to the LCD (especially sequential characters if it has auto increment address or whatever)?

If it is writing a port and pulsing a strobe for a couple of cycles it would be simpler to continuously write all 16 characters to the LCD one per interrupt at some rate appropriate for the response time of the LCD glass and human perception. The interrupt will likely take a fraction of a percent of your processing power and what the rest of your code places in the buffer will automatically appear on the LCD within a few ms. No messing with flags required. 
 

Online nctnico

  • Super Contributor
  • ***
  • Posts: 27641
  • Country: nl
    • NCT Developments
Re: Non-Blocking Updates to a Character LCD
« Reply #4 on: May 12, 2012, 10:37:15 pm »
In projects I've done which involve updating a character LCD with a microcontroller, I've usually rewritten the entire display even if only a single character has required updating.  That is, on a 16 character LCD, I would update every character in turn (waiting 40us between each character) for a total delay of over 640us.  In a sense, I've been treating the LCD as a blocking output device.  My software does absolutely nothing for that 640us except wait and update the LCD.  This method certainly works, but it's fairly wasteful.  I'm at the point now where I want to do "other things" during that 640us period.
I always use the goto XY function of a display and update what is necessary. Are you just waiting 40us or are you polling the busy bit? In most cases the display will take less than 40us to write a byte so polling the busy bit is usually faster.
There are small lies, big lies and then there is what is on the screen of your oscilloscope.
 

Offline xquercusTopic starter

  • Contributor
  • Posts: 47
  • Country: us
Re: Non-Blocking Updates to a Character LCD
« Reply #5 on: May 12, 2012, 11:23:42 pm »
Quote
The way i would approach this is that updating a display happens in the "human" timeframe. So the time it takes to update is not critical because it will be faster than the user can perceive, whatever way you do it.
Yes, the way I have been updating displays is fine from perspective of a human user.  Blocking the entire microcontroller on the LCD for 640us might be an eternity, however, for an application which has other I/O to deal with.  That's why I'm looking at other approaches to updating the display.

Quote
There is no need to update the display 'immediately'. the human eye cannot follow anyway.
Here is how i do it often :
Have a ram buffer containing lcd data.
Have a routine that can copy this buffer to the lcd.
Call this routine on every pass through the main loop. The main loop is a timed event.

OK.  This is similar to my proposal except that you update a character on the LCD every time you go through the main loop whether it requires updating or not.  I like it.  My plan to keep track of flags for character which require updating is actually much more inefficient in the long run.  There's no need.  Just update each character location on the LCD in turn -- one character per iteration of the main loop.  I might do this every ms as an ISR just because it's more my style but your simplified approach makes much more sense than keeping track of flags.

Quote
If it is writing a port and pulsing a strobe for a couple of cycles it would be simpler to continuously write all 16 characters to the LCD one per interrupt at some rate appropriate for the response time of the LCD glass and human perception.
Definitely.  The idea I had of using update flags was misguided.  It makes much more sense to update a single character per interrupt or main loop iteration (if it's slow enough) than it does to deal with keeping track of flags.

Glad I asked about this as the solutions proposed are much simpler and make much more sense than my original.
 

Offline xquercusTopic starter

  • Contributor
  • Posts: 47
  • Country: us
Re: Non-Blocking Updates to a Character LCD
« Reply #6 on: May 12, 2012, 11:26:56 pm »
Quote
Are you just waiting 40us or are you polling the busy bit? In most cases the display will take less than 40us to write a byte so polling the busy bit is usually faster.
I've been simply waiting the full 40us.  I've tried polling the busy flag but have been unsuccessful for some reason -- on multiple displays.  In any case, if I move to an interrupt handler which updates one character every ms (or something along those lines) I'll have no need for polling.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf