Implementing UI on an LCD - lost in translation

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

Well, I'm trying to develop a basic LCD-and-buttons interface - and no matter what optimization techniques I use, the code size bloats unreasonably - it's already bigger than the functional part, and that's just for implementing a user interface! What I'm going to have is a button set like this:

             [ ^ ]

[Mode]  [<]  [Set]  [>]

             [ v ]

A HD44780 based LCD will display a few user changeable settings like this:

|Parm1:A|Parm2:B |
|Parm3:C|Parm4:D |

The [Set] button makes a setting blink, [Left]/[Right] buttons change the blinking setting, and [Up]/[Down] change the setting value. The [Mode] button changes the whole screen to another set of settable values. What I'm stuck with is a reasonably sized code to handle the stuff happening on LCD - blinking the values (redrawing values on timer), changing and outputting right values, etc. I use most code optimization techniques (like grouping global vars in structures, avoiding globals at all if possible, etc), but I still have a gut feeling I'm very wrong with my ideas. In the end it all boils down to throwing in a load of "ifs" to handle all possible conditions, and there must be a neater way.

Is there an open source project including some sort of LCD based UI that I could check? Maybe I'm just an idealist, but I just can't have that beast written the way it's now.

Artyom

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

What code optimization technique avoids globals? I though the object oriented purists eschewed global variables because of their publicness and danger of accidental writes. Seems that any variable that retains its value pass to pass and is read by two or more routines should be global. If it is persistant and used only in one routine, it can be static, but it still lives in bss, so no win over global in that case. Your observation that the UI takes more work than the rest of the program was abundantly clear in the early days of windows! It does seem there should be some compact lcd ui though. I think this is a project worthy of collaboration.... if you show us what you have we promise to only offer constructive criticism... honest....

Imagecraft compiler user

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

This sounds awfully familiar. I once made an intelligent beer bottle opener with my friend for an uni course called "Modern user interface electronics". The bottle opener calculated how big and strong (alc-%) beer bottles were opened and tried to approximate the user's blood alcohol level and time when it is safe to drive again.

The biggest part codewise both in C code lines and compiled assembler code was the user interface part. Before I removed floating point math codes and used fixed point longints.

If the menu system is relatively small, it might be best to stay with ifs and so on, but if the menu system grows it will be best to keep the menu screens and navigation system in some kind of array. A list of stuff what will be written to screen in which menu and what buttons do what functionalities (maybe run subroutines or change variables or change menu screens). It all depends what you need. If you play your cards right, you can put the array containing the menu system in flash, so that it only contains pointers to what functions to run or variables to change.

- Jani

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

If you arrange the menus as a structure:

something like this:

struct tmenu{
char *menu name;
char display format;
void *var; //points to variable to display/alter
void *on_up(void); //code to execute on up button
void *on_down(void); //code to execute on down button
void *on_left(void); //code to execute on left button
void *on_right(void); //code to execute on right button
void *on_mode(void); //code to execute on mode button
void *on_set(void); //code to execute on set button
};

Then just have an array of this struct and fill in the fields. Have a curr_menu variable that has the index to the array of structures. Your code can manipulate this value to select the next menu. Remember those menu strings eat up code memory quickly!

This is probably similar to what Jepael was describing.
This technique is called 'table driven'. Once you've done a few menus, you'll probably figure out ways of making the code even smaller. The more 'regular' your menu system is the easier it is to code.

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

I have done several lcd menus in assembler & it is not too bad. I don't use tables since it is tougher to use than my method. I basically set up a parsed language that lets me "draw" a state machine. There are both display states (menu items)& action states (code routines).

Since memory is scarce I uses it VERY sparingly & rely on defualts as much as possible to save every byte possible. I typically only use uppercase letters, but can use lowercase If I like preceding with the ' char. The lowercase chars are reserved for the "language", except as just noted.

Let bnxx=button #n going to next state xx
/= option field separator
c=clear LCD display
rxx=recall present option setting from nibble xx
k=keep (do not update) present option setting
,xx= non button nextstate xx
= option field end marker, or end of state definition
txx=message delay timer

As an example:
ST00: "c,00b201PLEASE/EAT/DRINK/LAUGH= AT JOES BY="
ST01: "t22,05r03b307/TONIGHT/FRIDAY/cCLOSED,08/WEDb355/kABORTb300=="

ST21: "c,21SET UP:/BAUDb325/PARITYb326/cDUMMY!=
ST25: "c,25b328r09BAUD IS:/300/600/2400/9600/kb321ABORT=BITS PER SEC="

In ST00 you see the LCD is cleared (c) the next state with no button pressed is again 00. I had inly a scroll left/right key & several selction buttons. Here pressing button 2 for any selection will take you to state 01 (b201). So as you scroll left/right you will see the different selections & eventually must move on to state 01. ST01 shows the next state will be 05 (when no buton is pressed). This allows compound messages to be created (assuming the next message deos not contain "c-clear"). Except that CLOSED does not follow the 05 default it will go to state 08 & also clears out the display for its own unique message. When coming into state 01 the present selection is contained in the (optional) nibble 03 (r03). When you scroll & leave the state the new setting is saved in r03, except that the ABORT slection does not--it uses the "k-keep old option value" & also goes to its own differnet state if selected (back to ST00). I used button3 for most of these example since I was just trying to create a quiclk example. Above a certain number (say 50) the states are action (code rotuines). I can also mark them with axx (a55=action state to sound the buzzer for 1 sec, a57=turn off the pump motor, a73=turn on the flamethrower...) So you can put these in as well and make things happen as well, rahter thanm just having a bunch of do-nothing menus.

Once you define your language & have you parser it is quick and easy to add you own commands & no messing around with a bunch of tables. Also this is extremely memory efficient.

Hoyt

When in the dark remember-the future looks brighter than ever.   I look forward to being able to predict the future!

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

Thank you all for your replies - indeed, I think this a thing worth collaborating upon. Bob, I only meant avoiding globals in favor of local statics if possible, but certainly I still use them as a way to have globally accessible data between functions.

Let me shed some light on the intended device - it's going to control digital cameras via USB interface (PTP protocol to be precise, I already made that part on an AT90USB1287), particularly implementing a so called "time lapse" mode. In this mode, the device commands camera to make a shot each Hr:Min:Sec, for a total of up to X shots. H, M, S and X are the settings we need changed on LCD. In an attempt to generalize menu description, I made the following set of data structures:

typedef struct {
	unsigned char display_start_position;
	unsigned char display_length;
} UI_changeable_setting;

typedef struct {
	unsigned char const __flash * teaser;
	unsigned char const __flash * work_screen;
	
	unsigned char num_changeable_settings;
	
	void* changeable_settings;
	
} UI_mode_screen;

The above contains two predefined strings - a teaser that appears shortly while switching modes, and the work screen, that appears after teaser if user stops scrolling through the modes. Both lines are 32 chars long, to fit 16x2 HD44780 display. Work screen string has some white space in place of intended settings - a full look would be like this:

|020x 00:30:00int|
|T-Lap           |

Here is how the data structure looks:


const __flash UI_mode_screen ui_mode_screens[USER_INTERFACE_MODE_MAX+1] = {
	{
		(unsigned char const __flash *)"Time-Lapse      >mult exposures ",
		(unsigned char const __flash *)"   x   :  :  intT-Lap|    |     ",
		
		4,
		
		&(UI_changeable_setting settings[] =
			{
				{
					0,3
				},
				{
					6,2
				},
				{
					9,2
				},
				{
					11,2
				},
			}
		)*
	}
	
};

The mess in the end is supposed to be a pointer to an array of UI_changeable_setting structures - but it slips my poor mind about how should I do it, considering that they:

a) need to be in flash memory,

b) need to be preinitialized, since I (presumably) cannot initialize flash memory segment from within the code.

Currently compiler (IAR) complains about some dead wrong syntax in this array - no surprise to me, but I didn't yet figure how to describe it correctly.

No loud laughing please - giggling is welcome though :)

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

not sure how much work it would be to get working on IAR (and then some additional to tweak to have everything work out of flash) but I have a project, "tinymenu", in the projects that might be worth a few minutes to look at (though it wouldn't fit your requirements exactly).

It compiles down quite small & is pretty flexible. i'm afraid though that the example code with it isn't the cleanest... (hopefully will remedy that very shortly, plus make it so it won't un-tar into the current working directory -- wasn't sure when i put it up what the preferred way of bundling things on here was).

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

tymm, I would really appreciate having a look at the menu you built - even if it's different, maybe it will spark a few ideas :)

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

This is the link; let me know if you have any questions or anything's not clear. I have a newer (but as of yet untested) version of the example code I can send you too if you wish; it is better commented & may make some things easier to understand.

https://www.avrfreaks.net/index.php?module=Freaks%20Academy&func=viewItem&item_type=project&item_id=249

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

Have a look at the source for the AVR Butterfly. They do a very nice implementation of a state-machine driven interface. Umm, finding said source is an exercise left to the reader ...

OK, here it is

http://www.atmel.com/dyn/products/tools_card.asp?tool_id=3146

Dean 94TT
"Life is just one damn thing after another" Elbert Hubbard (1856 - 1915)

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

First off, thank you all for your valuable comments and suggestions - I might have ended up with a much less efficient solution without that. I feel I owe something more than a simple thanks to the community - let me try to share some parts of code I written during this time. I chose the approach suggested by Kartman, so here is how I made the screen structure in the end:

const __flash UI_mode_screen ui_mode_screens[USER_INTERFACE_MODE_MAX+1] = {
{
	.teaser = "Time-Lapse      >mult exposures ",
	.work_screen = "   x   :  :  intT-Lap|    |     ",
		
	.num_changeable_settings = 4,
	
	.on_draw_values = &ui_mode_time_lapse_draw_values,
	.on_flash_selected_setting = &ui_mode_time_lapse_flash_selected_setting,
		
	.on_btn_up = &ui_mode_time_lapse_btn_up,
	.on_btn_down = &ui_mode_time_lapse_btn_down,
},

{
	.teaser = "High DR Sequence>mult exposures ",
	.work_screen = "  x . EV<>1/2000HDR|M-Up|ISO100 ",
	
	.num_changeable_settings = 2,

	.on_draw_values = &ui_mode_hdr_draw_values,
	.on_flash_selected_setting = &ui_mode_hdr_flash_selected_setting,
		
	.on_btn_up = &ui_mode_hdr_btn_up,
	.on_btn_down = &ui_mode_hdr_btn_down,
}
};

Each mode draws its own values, and a global scheduler calls .on_draw_values as needed.

On pressing SET, a global "uint8 setting_being_changed" is turned 1 or 0, LEFT and RIGHT scroll it through 1 to .num_changeable_settings. A timer is implemented to call .on_flash_selected_setting which is spelled like this:

void ui_mode_time_lapse_flash_selected_setting() {
  static const SettingDisplayPosition setting_positions[4] = {
	{
		0, 3
	},

	{
		5, 2
	},

	{
		8, 2
	},

	{
		11, 2
	},
  };
	
  unsigned char idx = user_interface_runtime_info.setting_being_changed-1;

  ui_flash_setting(setting_positions[idx].start, setting_positions[idx].length);
}

UP/DOWN button presses are handled by the mode-specific routines pointed to by .on_btn_up and .on_btn_down in the mode-specific structures above - they do a basic range check and increment variables as needed.

Once again, thanks everyone for your help!

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

I also wanted to share the button handling code I written in process. It implements the following features for up to 8 buttons (should be enough for a simple design):

1. Software debounce

2. Delayed per-button repeat

3. Simulated up and down per-button events.

4. Buttons can be separately enabled or disabled.

Overall I tried to make it somewhat similar to the Windows approach, where there is one or more DOWN events (>1 DOWN event if the key was held down for >TIMEOUT_VALUE), and an UP event when it's up.

Usage is simple:

1. Call buttons_init() somewhere at the start.

2. Set up a timer like 50Hz or something, and call buttons_poll().

3. Configure buttons_conf.h to contain required values for every sort of delays.

4. Check for the following to work with events:

Buttons_is_button_down_event(button)
Buttons_is_button_up_event(button)

The DOWN event will be fired repeatedly if the certain button is allowed to fire repeated down events. Check out buttons.h for more useful macros and buttons.c for an overall idea about how it works.

I hope someone finds it useful for their own projects :)

Attachment(s):