Electronics > Microcontrollers

Non-Blocking Updates to a Character LCD

(1/2) > >>

xquercus:
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?

Kremmen:
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.

free_electron:
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: ---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


--- End code ---

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: --- 
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.

--- End code ---

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: ---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.
--- End code ---

Rufus:

--- Quote from: xquercus on May 12, 2012, 08:32:32 pm ---Does this seem like a reasonable approach to update the character LCD or is there an alternate method I should consider?
--- End quote ---

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. 

nctnico:

--- Quote from: xquercus 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.

--- End quote ---
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.

Navigation

[0] Message Index

[#] Next page

There was an error while thanking
Thanking...
Go to full version
Powered by SMFPacks Advanced Attachments Uploader Mod