Non-Blocking Updates to a Character LCD - Page 1
Forum section: electronics / general

author: xquercus
author_rank: Contributor
timestamp: May 12, 2012, 08:32:32 pm
content: 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?

author: Kremmen
author_rank: Super Contributor
timestamp: May 12, 2012, 08:57:02 pm
content: 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.

author: free_electron
author_rank: Super Contributor
timestamp: May 12, 2012, 09:39:45 pm
content: 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_1for x = 0 to 15write_char buffer_line_1(x)next xselect_line_2for x = 0 to 15write_char buffer_line_2(x)next xend subsub Updatestd()static line as integer, static character as bytecharacter = character +1if character = 16 thencharacter=0line=not(line)if line then select_line1 else select_line2end ifif line then write_char buffer_line_2(x)else write_char buffer_line_1(x)end subsub Updateblock(line,offset,length)if line then select_line1 else select_line2for x = offset to lengthif line then write_char buffer_line_2(x)else write_char buffer_line_1(x)next xend subthe 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 @ 0x0100 ' location 256dim line_2_buffer @ 0x0110 ' location 272const line_1 = 0x0100const line_2 = 0x0110sub Updateblock(line,offset,length)if line = line_1 thenwrite_lcd_cmd(selectline1)elsewrite_lcd_cmd(selectline1)memptr=line + offsetwhile length >0write_lcd_data(memread(memptr)length--end whilewhat 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 pointeri 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.sub updatestdlcd_en=falsesub Updatestd()static line as integer, static character as bytecharacter = character +1if character = 16 thenselect_line2end ifif character = 32 thencharacter = 0select_line1end ifportx = memread (line_1+character)lcd_enable = trueend submainloop :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 2did i hit 32 -> roll back to 0 and select line 1send the contents of the buffer directly to the i/o portenable the lcdthe 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 processif blink_enable thenif blink_counter >0 thendecr blink_counterelseled = not (led) ' led is an io pinblink_counter = blinkrateend ifscankeysrefresh_lcdcase stateidle : blink_enable=0if key = key_1 thenstate = blinkingblink_enable=1lcd_text = "blinking"end ifblinking : case keykey_1 : state = idleblink_enable =0lcd_text = "idle "key_2 : if blinkrate = 100 then blinkrate =200 else blinkrate = 100lcd_text2 = blinkrateendcaseendcasewendkey 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.

author: Rufus
author_rank: Super Contributor
timestamp: May 12, 2012, 10:35:22 pm
content: Quote from: xquercus on May 12, 2012, 08:32:32 pmHow 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.

author: nctnico
author_rank: Super Contributor
timestamp: May 12, 2012, 10:37:15 pm
content: Quote from: xquercus on May 12, 2012, 08:32:32 pmI 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.

author: xquercus
author_rank: Contributor
timestamp: May 12, 2012, 11:23:42 pm
content: QuoteYes, 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.QuoteOK. 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.QuoteDefinitely. 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.

author: xquercus
author_rank: Contributor
timestamp: May 12, 2012, 11:26:56 pm
content: QuoteI'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.