Lecture: 5
Objectives: Understand the syntatic structures for uninitialized and initalized 1 and 2-D arrays and how to read and write arrays.

Arrays

The array is convenient tool for arranging a related collection of information in one place.

Declaring arrays

Like all variables, you need to declare array's in C. So really, the only thing that makes an array declaration different from an regular variable is that array's have length. This requires a new syntactical structure, the square bracket.
    #define N 6

    uint8_t array1[N];				// An uninitialized 
    uint8_t array2[N] = {1, 2, 3, 4, 5, 6};	// An initialized array
    uint8_t array3[N][N];				// An uninitialized 2D array
    uint8_t array4[N][N] = { { 0,  1,  2,  3,  4,  5},
			  {10, 11, 12, 13, 14, 15},
			  {20, 21, 22, 23, 24, 25},
			  {30, 31, 32, 33, 34, 35},
			  {40, 41, 42, 43, 44, 45},
			  {50, 51, 52, 53, 54, 55}};
The first row of this declaration may be new to you, #define N 6. This is a way to declare a constant through your program. This allows you to type in "N" everywhere you want to describe the size of the array. This is particularly helpful in complex programs where you might need to change the size of the array. If you define the size of the array using a #define then you can simply modify this value, recompile your code, download it and you are done. If you use the constant "6", you would need to hunt down all occurrences and replace them with the new size.

The first array declaration for array1 creates an array of 6 elements, each element being a distinct uint8_t. This is an uninitialized array, you should assume that the values in the array are some random garbage junk values. Do not assume that uninitialized variables have a 0 value - they almost certainly will not!

The second array declaration for array2 creates an array of 6 elements, each element being a distinct uint8_t. This is an initialized array, with each array element getting one of the 6 provided values starting with element 0.

The third 2D array declaration for array3 creates a 2D array of 6x6 elements, each element being a distinct uint8_t. This is an uninitialized array, you should assume that the values in the array are some random garbage junk values. Do not assume that uninitialized variables have a 0 value - they almost certainly will not!

The fourth array declaration for array4 creates an 2D array of 6x6 elements, each element being a distinct uint8_t. This is an initialized array, with each array element getting one of the 6 provided values starting with element 0,0.

Embedded C has static memory. The important consequence of this statement is that the size of an array cannot be changed during the execution of your program; the size of an array is set when you compile your program. A programming language like MATLAB uses dynamic memory allowing you to increase the size of the array during the execution of a program. What is not obvious is that MATLAB has a host of sophisticated memory management programs running concurrent with your MATLAB program to enable dynamic memory. These memory management programs would introduce a level of complexity that is out of scale with the capabilities of an 8-bit microcontroller.

Reading and writing array values

Whenever you place a variable (or array element) on the right-hand side (RHS) of an assignment statement (equal sign), you are asking for a read of that variable. When a variable is on the LHS of an assignment statement (equal sign) you are asking to assign that variable a new value by writing a value to it. In the example below, the variable array1[0] is being read in the first line of code and is being written-to in the second line of code.
	x = array1[0];		// array1[0] is on the RHS - read array1[0]
	array1[0] = 7;		// array1[0] is on the LHS - write array1[0]
When you work with arrays you can only read or write one value at a time. Hence when you read or write an array you need to provide the name of the array along with the index that is being accessed. Here are some examples using the declarations above.
    uint8_t i, j;

    array1[0] = 7;			// array1[0] now equals 7
    array1[1] = array2[5];		// array1[1] now equals 5
    array1[2] = array2[4]+array[5];	// array1[2] now equals 11
    array1[3] += 7;			// array1[3] is still undefined
    array1[4] = array4[2][3];		// array1[4] now equals 23

    array3[0][0] = 7;			// array3[0][0] now equals 7
    array3[1][1] = array[4];		// array3[1][1] now equals 5
    array3[2][2] = array[2] + array[3];	// array3[2][2] now equals 7
    array3[3][3] += 7;			// array3[3][3] is still undefined
    array3[4][4] = array4[2][3];	// array3[4][4] now equals 23

    // Determine the new values of array1 and array3 
    for (i=0; i < N; i++) {
	array1[i] =  10 + array2[i];
	for (j=0; j < N; j++) {
	    array3[i][j] =  10 + array4[i][j];
	} // end for j
    } // end for i
When working with 2D arrays just remember that the first index is the row and the second the column. With all arrays in C do not forget that all indexing starts at 0! After this code is executed array1 and array3 have the following values.

    uint8_t array1[N]    = {11, 12, 13, 14, 15, 16};
    uint8_t array3[N][N] = { {10, 11, 12, 13, 14, 15},
			  {20, 21, 22, 23, 24, 25},
			  {30, 31, 32, 33, 34, 35},
			  {40, 41, 42, 43, 44, 45},
			  {50, 51, 52, 53, 54, 55},
			  {60, 61, 62, 63, 64, 65}};
It's important to note that while we did not do so, there is nothing prohibiting you from changing the values of an initialized array. For example, you could have the following line of code somewhere after the declaration of array1, array1[0] = 10;.

On final warning about embedded C and arrays. There is nothing prohibiting you from access indicies outside the declared length of the array. For example, if you read array2[10] (use the array declarations at the begining of this lecture), the PIC would not blink an eye and happily return a (garbage) value. This can be extremely frustrating when trying to debug a misbehaving program, because the behavior of the program will change depending on garbage values are located. Often the program will run fine when you first turn the system on because there is a reasonable chance that the volatile RAM locations will be 0. However, when the processor is reset, subsequent runs with the same input may produce erroneous values. There are sometimes called Heisenbugs.

Sorting arrays using bubble sort

While our C compiler has a built in function qsort, when dealing with small arrays, bubble sort
#define N	8

void main(void) {
    uint8_t array[N];
    uint8_t i, j, temp;

    for (i=0; i<N-1; i++) {
	for (j=0; j<N-i-1; j++) {
	    if (array[j] > array[j+1]) {
		temp = array[j+1];
		array[j+1] = array[j];
		array[j] = temp;
}   }	}   }

Character Strings

Strings are a convenient way to pack together characters and are represented in the memory of the computer as a a NULL terminated array of characters. To understand this definition let's focus on the array of characters using the following example.

void main(void) {

    char alphabet[4] = "abc";
    char oneCharacter;
    
    oneCharacter = alphabet[0];
    alphabet[1] = oneCharacter;
    alphabet[2] = alphabet[0];

} // end main
Back in Lecture 1 we saw that the ASCII code was a way to assign numerical codes to typographic characters. In that lecture we were looking at single characters and as a consequent used single quotes. For example ASCII 'a' is assigned decimal code 97, 'b' is assigned 98 and 'c' 99. While single ASCII characters are set-off using single quotes, strings are surrounded by double quotes.

In the C programming language, all strings are typed char, and the length of the string given by the number in brackets after the string's name. Strings may be initialized by including the initial string value in double quotes - the number of characters in the initial value must be at most, one less than the length of the string given in the square array brackets.

Consequently, the string alphabet in the code above is an array which holds the numbers 97, 98, 99 (represented in binary of course) and interperted as ASCII characters. The first element of the array is, by convention, the first letter in the string. Since the C programming language starts indexing arrays at 0, alphabet[0] references the character 'a'. You should verify that when the code in the example above completes its execution, alphabet = "aaa". Now we turn our attention to what is meant by NULL terminated.

There are times when you will want a string to contain a non-printable character. Non-printable characters often perform terminal control functions like tab and carriage return. In order to specify a non-printable character you will need to "escape" it. An ASCII escaped character is the "\" symbol followed by a decimal number. The compiler will treat the digit as a decimal ASCII code of the character to put in the string. For example the horizontal tab has ASCII code 9. Consequently printing a string "Pugs\9are great!" will produce the text "Pugs        are great!"

One non-printable character, NULL, has a special relationship with strings. The NULL has ASCII code 0 and signify the end of a string. A string with a NULL at the end is said to be NULL terminated. Most compilers will automatically NULL terminate your strings for you. However, you need to be aware that this is happening and leave room for this non-printable character. This explains why alphabet in the code snippet above was given 4 bytes of storage. So actually, alphabet is an array which holds the numbers 97, 98, 99, 0.

When working with strings you should pay attention to the output produced by the compiler in the console window even if the compile is "successful" For example, if you had defined char alphabet[3] = "abc";, the compiler will throw a warning warning: (340) string not terminated by null character While this may not seem a big deal, if your code relies on the NULL terminator to tell it when it has reached the end of the strings, then this warning is a big deal. For example, the standard C function printf will keep printing characters in a string until encounters a NULL. From personal experience, leaving the NULL off a sting passed to printf produces some very strange output.

Continuing on the theme of how things can go wrong, if you used the following declaration for the char alphabet[2] = "abc";, the compiler will throw an error, error: (240) too many initializers This error is a result of allocating 2 bytes for the array, but asking for the initial value to be 3 bytes.

Finally, you are welcome to explicitly declare the NULL terminator by escaping it. For example, the following declaration is perfectly acceptable, char alphabet[4] = "abc\0";. When you do this, the compiler will recognize what you have done and not add another NULL. Your initial value for the string may contain fewer characters than the length of the string. For example, the following declaration is perfectly acceptable, char alphabet[8] = "abc\0";. Note, that when you first reference the alphabet string, only the first 4 characters of the string will have know values. The first time that you reference the alphabet string, the values of alphabet[4] through alphabet[7] are indeterminate - you should assume that they contain garbage values.

There are times when you will want to store an array of strings. For example you might have the following options for a digital MP3 player.
    char commandArray[3][5] = {"run", "stop", "play"};
You need to remember to remember that the first index "[3]" tells you how many strings are in the array. The second index, "[5]" tells you that each string contains at most 4 character with one byte reserved for the NULL terminator.

Test your understanding

You can find the solutions embedded in the "source code" for this web page by right mouse clicking on this web page and selecting "view source". The solutions are in HTML comments.
  1. Write the definition of an uninitialized array with 36 elements, each element is uint16_t type.
  2. Write an array declaration for an uninitialized array with 100 elements, each an uint8_t. Then write a for-loop to initialize the array with the first array element equal to 100, and each subsequent array element one less than the previous.
  3. Assume that you have declared array as an uint8_t of length N where N is defined and less than 100. Your code has loaded the array with values, now you want to compute the average of these values. Write a for loop to compute the sum, then compute the average. Be careful not to overflow the sum by choosing appropriate data types.
  4. Evaluate each of the expressions on the following table based on the following variable declarations.
    uint8_t note[10] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};
    uint8_t lut[5]   = {0x00, 0x80, 0xB5, 0xDE, 0xFF};
    uint8_t i = 3;
    uint8_t sum = 0;
    
    Evaluate Value
    note[0]  
    lut[2]  
    note[i]  
    note[i-1]  
    note[i+1]  
    note[2*i+1]  
    lut[i] + note[i-1]  
    for(i=1; i<4; i++) sum+=note[i+2]  
    note[i+9]  
  5. Given the variable declarations below, determine the effect of each of the lettered operations on the hex array. Only fill in array entries that have changed.
    uint8_t hex[10] = {0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99};
    uint8_t i = 4;
    
    A. hex[0] = 0xAB;
    B. hex[1] = hex[0];
    C. hex[2] = hex[i]
    D. hex[3] = hex[9] – hex[8]; 				
    E. for(i=1; i<10; i++) hex[i-1] = hex[i]
    F. for(i=0; i<9; i++) hex[i+1] = hex[i]
    G. for(i=8; i>0; i--) hex[i+1] = hex[i]		
    
      ABCDEFG
    hex[0]        
    hex[1]        
    hex[2]        
    hex[3]        
    hex[4]        
    hex[5]        
    hex[6]        
    hex[7]        
    hex[8]        
    hex[9]        
  6. Write a C code snippet that takes an 8-bit integer representing an ASCII character between '0' and '9' and returns the decimal value. For example your code snippet should convert the ASCII code for '3', 51, to 3.
  7. Write a C code snippet that converts an uppercase letter into the equivlent lower case letter.
  8. An ultrasonic range finder is a device which emits a very high frequency soundwave above the range of hearing (ultrasonic) and then listens for the echo. The flight-time of the ultrasonic sound wave, combined with the known speed of sound, determines the distance between ultrasonic range finder and the object. The PIC measures the flight time using a counter that increments every 0.5us. In order to improve the accuracy of the range value, your program acquires 8 range measurements and computes an average. However, when you look at the values (shown in the table below), you notice that while most of the values are correct, a few are clearly incorrect.
    Index Value
    0 10047
    1 10006
    2 9975
    3 56358
    4 9998
    5 9974
    6 10032
    7 57244
    You need to figure out a strategy to remove these spurious values. You may assume that a majority of the values in the array are correct. The variation of the measured values should be at most 500 counts away from the actual measurement.