RTC Website

Programming the high-level User Interface

TX

The objectives of this lab exercise are:

  1. To write the highest-level user interface (UI) functions (device drivers) for the T1 target system
  2. To learn how the keypad and LCD function in the solution to the design problem of section 1.9
  3. To apply what has been learned about computing thus far
  4. To gain experience programming in C

The T1 target system will be required, but only the components listed in appendix B are needed for this lab exercise. Set up each component in accordance with the instructions for your specific T1 version in section A.2. Connect your components in the manner described in the “Online Resources” section of the web page for this lab exercise: https://rtcbook.org/ph.

Introduction

Very often in an interaction between a computer and a user, a message or “prompt” is written on a display and the user is expected to respond by entering an appropriate decimal number through a keypad. In this lab exercise, you will write a C function, called double_in(), to perform the complete keypad/LCD procedure.

In this and the next two lab exercises, we will also write the functions needed to implement double_in(). For an overview of how these work in context of the design problem, see section 1.9, section 1.11. These functions are provided by the T1 C library (section 0.6, section 1.11) in executable form (i.e., without source code), but we will be replacing the library versions as we work through the lab exercises. This allows us to use the lower-level functions without writing them first.

With reference to the functional UI design (see figure 2.2), we are writing the user “input numbers” function, double_in(), and the “display prompts and messages” function, printf_lcd(). These functions have already been described briefly in section 1.11. We will also write a main() program to test double_in() and printf_lcd(). These functions should be written in the main.c file of the “stub” project workspace/lab1.

Writing the double_in function for user number entry

The double_in() function will interactively get a string of characters from the user’s key presses on the keypad ( in the introduction of this book), displaying them immediately to the LCD (). Here is a description of how the function should work:

  1. A user prompt (a string of ASCII characters) should be written on line 1 of the LCD. A pointer to the string corresponding to this prompt should be the only argument of the double_in() function.

  2. A floating-point number should be accepted from the keypad in response to the prompt. The number is entered as a string of ASCII characters that may include the decimal digits \(0\)\(9\), a decimal point, and a minus sign, and is terminated by keyboard_return.

    If the input string contains an error, the display should be cleared, an error message written on line 2, and the prompt issued again on line 1.

  3. This string should be converted to the equivalent floating-point number: C data type double.

  4. The return value of double_in() is the floating-point number.

For instance, two calls to double_in(), using a string literal, might be

vel = double_in("Enter Vel:");
press = double_in("Enter Pres:");

The values entered by the user would be assigned to the variables vel and press.

Therefore, the prototype of the double_in() function should be

double double_in(char *prompt); // prompt is a string pointer

A general strategy for double_in() is given in . The details that follow help clarify how to write the double_in() program.

Algorithm 3: double_in pseudocode
Algorithm

Handling keypad input

Under normal circumstances (no user entry errors), double_in() should work as shown in figure 1.14. The string prompt input is printed to the LCD with printf_lcd(), a user keys in a floating-point number and presses keyboard_return, fgets_keypad() gets the string from the keypad, and then the C library function sscanf() converts this to a double floating-point number, which is returned to the calling program.

 Figure 1.14
Figure 1.14: The double_in() function when the user enters a valid number.

  1. The prototype of fgets_keypad() is

    char * fgets_keypad(char *buf, int buflen);

    The first argument is a pointer to a string (buffer) that holds the user-entered characters. The second argument is the maximum number of characters to be read (including the null character) into the buffer.

  2. The prototype of sscanf()1 is

    int sscanf(const char *s, const char *format, ...);

    The first argument s is a pointer to a string (e.g., "50.75"); the second argument is a conversion format specifier (e.g., "%lf" for a long float, a double); and the third argument should be a pointer to a double variable that will store the converted floating-point number (e.g., &val for a double val).

Handling input errors

When designing a user interface it is important to anticipate possible entry errors. When an invalid number is entered, double_in should operate as illustrated in figure 1.15.

 Figure 1.15
Figure 1.15: The double_in() function when the user enters an invalid number.

In the case of entering a floating-point number on the keypad, there are four possible entry errors, as shown in table 1.11. The table also specifies an error message for each case. The double_in() function must detect each of these errors, print the corresponding error message on the LCD, and prompt the user again.

Our goal here is that the user must enter a valid number string before it can be converted to a double and returned to the calling program. Detecting error conditions is a matter of testing the string written by fgets_keypad().

Three functions from the C standard library header string.h2 will be helpful for these tests. The strpbrk() function3 prototype is

char* strpbrk(const char *s1, const char *s2);

The function looks for the first occurrence of any character of string s1 in string s2 and, if found, returns a pointer to the string. If none is found, strpbrk() returns the null pointer, which is typically given the constant NULL. The and keys will be assigned ASCII characters [ and ]; therefore, if we look for "[]" in a string, strpbrk() returns NULL only if neither character appears in the string.

Using strpbrk() to detect the presence of bad characters (keys) works well. However, we must also detect multiple decimal points. One decimal point is valid, but two is too many. The strchr() and strrchr() functions4 are useful for detecting multiple decimal points. Their prototypes are

char* strchr(const char *s, int c);   // first occurrence of c in s
char* strrchr(const char *s, int c);  // last occurrence of c in s

These look for the first (strchr()) or last (strrchr()) occurrence of character5 c in string s and return a pointer to the character, if found. Otherwise, they return NULL. For example, the following program prints the substrings corresponding to the first and last occurrences of the character 't' in the string "attracting".

int main(void) {
  const char *s = "attracting";
  char *tf = strchr(s, 't');
  char *tl = strrchr(s, 't');
  printf("Truncated '%s':\nat first 't': %s\nat last 't': %s",
    s, tf, tl);
  return 0;
}

Its output is

Truncated 'attracting':
at first 't': ttracting
at last 't': ting

How might we use strchr() to determine if the user’s string contains a minus sign ('-') past the first character? How might we use strchr() and strrchr() to determine if the user’s string contains multiple decimal points? Hint: Consider what strchr() and strrchr() return when they both find the same decimal point. What if there is no decimal point? What if there are multiple decimal points?

Putting all these ideas together, let’s expand into a more detailed strategy for double_in():

  1. Begin by using the printf_lcd() function to clear the LCD screen (see subsection L1.4 for the list of escape sequences that can be used).

  2. Set a flag indicating that the keypad entry process has not been completed. For example, using an integer variable: err = 1;

  3. Enter and stay in a while loop while err == 1;

    Within the loop (as described next), prompt the user and issue error messages until a string with no errors has been entered, and then set the flag err to 0.

  4. After the loop, use sscanf() to perform the ASCII-string-to-double conversion, and return the result.
    Hint: Recall that because sscanf() is converting to a variable of type double, you must use the format %lf (long float).

Within the while loop:

  • Use printf_lcd() to move the cursor to the start of line 1 and display the prompt. Then, check for the four specific errors as follows:

    1. Use fgets_keypad() “Get String” to obtain the string from the keypad.

      Note: If no digits are entered, fgets_keypads() returns a NULL, a string of zero length.

    2. Use the strpbrk() “String Pointer Break” to detect or in the string. Note: is returned by fgets_keypad() as the ASCII character '[', and as ']'.

    3. Use the strchr() to detect minus signs '-' the first character.

    4. Use the combination of strchr() and strrchr() to detect multiple decimal points.

  • If any of the four errors occur, clear the display, move the cursor to line 2, and print the appropriate error message shown in Table 1.11. Alternatively, if the user-entered string passes all of the tests, set err=0.

Note: printf_lcd() and fgets_keypad() work like the standard C functions printf() and fgets(), and they are linked to your program from the T1 C library.

Writing the printf_lcd display function

Our second task is to write the printf_lcd() function used by double_in(). The C standard library function printf() prints to the standard output device (in our case the Console pane of the Eclipse integrated development environment (IDE)).

We want printf_lcd() to operate exactly as printf(), except that it will print to the LCD. To do this, we want printf_lcd() to accept a format string with a variable number of arguments. Therefore, the prototype for printf_lcd() will be

int printf_lcd(const char *format, ...);

The format argument is a format string specifying how to interpret the data, and the ellipsis ... represents the variable list of arguments specifying data to print. The return value is an int equal to the number of characters written if successful, or a negative error code if an error occurred.

For example, to clear the display, move the cursor to the beginning of line 1, and print floats a and b, we could write

n = printf_lcd("\fa = %f, b = %f", a, b);

Broadly, our strategy for implementing printf_lcd() is as follows:

  1. Use the C function vsnprintf() to write the data to a C string.

  2. Use the LCD driver function putchar_lcd() to successively write each character in the string to the LCD.

Parsing the variable argument list with vsnprintf()

The C function vsnprintf() writes formatted data from the variable argument list to a buffer (string) of a specified size.6 For instance, after invoking va_start() with argument pointer ap, char string[80], and int n,

va_list args;
   
va_start(args, format);
  n = vsnprintf(string, 80, format, args);
va_end(args);

This writes the arguments to a formatted string of length 80 chars according to format (i.e., the first argument of printf_lcd()). If successful, the returned value n is the number of characters written in the string. However, if an encoding error occurs, a negative error code n will be returned.

Printing each character with putchar_lcd()

Now that the arguments are parsed, we must write the resulting string to write to the LCD. The lower-level display driver function putchar_lcd() writes a single character to the LCD. Placed in a loop, putchar_lcd() can write successive characters of the string until the \0 null character that ends the string is encountered.

Although array subscripts and the standard C function strlen might be used to implement this loop, the use of pointers makes the task simple and efficient. Here is a useful idiom for looping through an entire string using a pointer p:

char *p = string;               // point to string start
while (*p) putchar_lcd(*p++);   // loop/put until char is \0

Notice that the \0 char evaluates to false (whereas all others are true), so this loop stops when the string is finished. The expression *p++, equivalent to (*p)++, evaluates to the dereferenced p first, then increments the pointer p (see section 2.4 for operator precedence and associativity rules).

The function putchar_lcd() places a single character corresponding to its argument on the LCD. Its prototype is

int putchar_lcd(int c);

For example, calls to putchar_lcd() might be

ch = putchar_lcd('m');          // put and assign 'm' to ch
putchar_lcd('\n');              // new line, ignore return value

All this culminates in the pseudocode shown in .

Algorithm 4: printf_lcd pseudocode
Algorithm

Background: special characters for the display

ASCII control characters can be used in similar ways to control many ASCII display devices. Each display make/model may interpret some ASCII control characters differently, but many will function similarly. Control characters can be sent to a display to perform specific tasks, such as clearing the display, starting a new line, and moving the cursor.

Special characters called escape sequences correspond to ASCII character codes. These allow us to write more descriptive code because it is easier to interpret the meaning of the C escape sequences than the ASCII character codes. In our display drivers, we will use a few escape sequences, but translate them in the low-level function putchar_lcd() for the specific target display. For now, we must only know the function of each sequence that we will later program into putchar_lcd(). These are shown in table 1.12.

Laboratory exploration and evaluation

Before you write your double_in() and printf_lcd() functions, write a main program that will test it by calling it twice from the main() function, assigning each result to a different variable. Then, as a check, print the values of both variables on the LCD with printf_lcd(), and on the console using printf(). See for main() pseudocode.

Because the T1 C library already includes double_in() and printf_lcd() functions, you can fully write main() before double_in(). The standard C library header files stdio.h and stdarg.h and the T1 library header file T1.h should be #included. The latter provides the double_in() and printf_lcd() functions, which will be overwritten by your own in problem L1.2, problem L1.3.

Algorithm 5: main pseudocode
Algorithm

TODO explain

Reference the listing: lst. ¿lst:lab-1-main?

Reference a line number: lab1:nifpga_status

Write and debug the double_in() function as described in subsection L1.2. The definition of the function in the main.c file will supersede the library version.

Write and debug the printf_lcd() function as described in subsection L1.3. The definition of the function in the main.c file will supersede the library version.

When writing double_in() in problem L1.2, strchr() and strrchr() were used to find multiple decimal points. Alternatively, the C function strstr() could be used. What assumption is being made if we choose to detect extra decimal points with strstr() and ".." as the second argument? Is this a valid assumption? Write a program using strstr() that correctly identifies the extra decimal point without looping through each character. Use the string "1.2.3" as a test case.

The assumption being made is that an extra decimal point will occur next to an original. Although this is probably the most common case, it is not a valid assumption, in general, because an entry like "1.2.3" would not be detected.

An extra decimal point can be detected by invoking strstr() twice: first to locate a first occurrence and—if there is one—again, this time on the remaining part of the string, to detect a second.

ISO, and ANSI. 2000. International Standard ISO/IEC 9899:1999. 2nd ed. American National Standards Institute (ANSI). http://www.open-std.org/jtc1/sc22/WG14/www/docs/n1256.pdf.

  1. See c99.↩︎

  2. See c99.↩︎

  3. See c99.↩︎

  4. See c99.↩︎

  5. The int c arguments of both strchr() and strrchr() are converted to char.↩︎

  6. For parsing the variable argument list (see subsection 1.10.6), use the C standard library function vsnprintf() (ISO and ANSI 2000, sec. 7.19.6.12).↩︎

Online Resources for Section L1

No online resources.