Developing in C for the ATmega328P: Pointers
Where I describe how to develop use pointers in AVR_C.
Introduction
Pointers are introduced by “K&R” with the following comment “Pointers have been lumped with the goto statement as a marvelous way to create impossible- to-understand programs.” While pointers are simple in nature, they can become quite complex, quickly. I recommend going through the examples on this page, slowly and methodically as well as making changes and determine if the change had the effects you believed it would. I also recommend having a C Language reference manual open as well, as I won’t got through the usage.
Overview
Pointers contain the address of the element they point to such as a variable, a structure, a string or a function. The concept of pointing to the address of the element as compared to the value of the element, is called indirection. As a fundamental concept in computing, it enables complex concepts to be created easily and usually more clearly. It eases the use of linked lists, polymorphism and function overloading.
I’ll walk through a series of examples in how to use pointers. At the end of this entry, is the complete file to use to work with the examples. Examine the printf statements in each example, then change some of the values either in the initialization stage or in the print statement to further understand how pointers work and are constructed.
Simple Pointers
Code
|
|
Output
Simple Pointers
The address of x is 0x8eb and its value is 1235
The address of y is 0x8ed and its value is 5677
The address of z is 0 and its NULL
Discussion
This section shows pointers in their most simple form. There are two variables, x and y each with a pointer p_x and p_y, respectively. The print statements show the address and value of each variable. There is also a variable z, which has neither a value nor an address. Note that Z has an address of 0 and it has been set to NULL.
Nothing significant here, other than to understand the usage of * and the & as well as initializing pointers to a address or NULL. Note the conditional as it pertains to p_z, C allows you to easily test for NULL by if (p) (not equal to NULL) or if (!p) (equal to NULL).
Pointers to pointers
Code
|
|
Output
Pointers to pointers
The address of the pointer to x is 0x8ef
Addr: 0x8ef contains Addr: 0x8eb which contains: 1235
p_p_x points to p_x which contains x
Discussion
This section shows pointers with an additional step of indirection, pointers to pointers. It is important to understand that adding an additional * allows that pointer to point to a pointer. An example of this indirection would be in linked-lists, where the a pointer will point to the next pointer in the list. In this example, we have a pointer which points to the pointer of x.
Again, by itself, the code doesn’t have a significant impact, its simply important to understand the concept of pointers of pointers.
Pointers and Arrays
Code
|
|
Output
Pointers and Arrays
The address of numbers is 0x8f1, it contains 0 == 0th value is 0
Address 0 of numbers is 0x8f1 and 0x8f1 and the values are 0 and 0
Address 1 of numbers is 0x8f2 and 0x8f2 and the values are 1 and 1
Address 2 of numbers is 0x8f3 and 0x8f3 and the values are 2 and 2
Address 3 of numbers is 0x8f4 and 0x8f4 and the values are 33 and 33
Address 4 of numbers is 0x8f5 and 0x8f5 and the values are 4 and 4
The value of p_numbers[3] is 33
Discussion
Pointers and arrays are tightly linked, and in many situations, you could use a pointer as easily as an array reference. For example, note that in line 8 the use of the pointer *p_numbers is the same as referencing the 0th element of the array. Incrementing the pointer, allows referencing the next element. This is why it is also important to understand that a pointer has a data type. Each data type has a size in memory and the pointer will increment (or decrement) by element size, not by address byte.
Using a pointer for an array becomes important when wanting to pass an array to a function. If doing so by value, the entire array would need to be duplicated in memory while passing by reference allows the existing array to be used with a significant savings in memory.
Pointers and Structures
Code
|
|
Output
Pointers and Arrays
LED0 0x8f6 contains pin 13 state 1 on 1000 off 500 and elapsed 237
Discussion
Pointers and structures also work well together. Using a pointer to a structure is extremely common (if not mandatory), when passing a struct to a function. Much like the similarity between pointers and arrays, there is a congruity to pointers and structures in such a way, the common usage is pointer_to_struct->member_of_struct, which is a more common usage than the expected dot notation of (*pointer_to_struct).member_of_struct. You see both forms of notation in Line 22 above.
Pointers and Functions
Code
|
|
Output
Pointers and Functions
p_func points to addition at address 0x5f with 1235 + 5677 = 6912
p_func points to subtraction at address 0x62 with 1235 - 5677 = -4442
Discussion
Pointers and functions are a powerful combination. Using a pointer to determine which function to run enables polymorphism, multi-tasking or function over-loading. Its best to spend some time understanding the specific steps to make this happen:
- Setup functions and function pointer Lines 1-2 and 5
- Set the function_pointer to the address of the desired function Line 8 or 12
- Call the function with desired arguments Line 9 or 13
Structure with Pointer Element
A different situation from a struct pointer, is a pointer to a struct, is a struct with a pointer as an element. In this situation, it takes a little bit of work to determine how to manipulate the pointer.
In my situation, I had a pointer element (volatile uint8_t *port;) which contained the address of PORTD or PORTB (known collectively as PORTn) on the ATmega328P. I also needed to use the address of the data direction register, DDRD or DDRB, respectively. The address of this register is 1 less than that of the address of the PORTn register.
Seems easy enough, set the value of a variable equal to PORTn then decrement the variable and we’re good to go.
The solution was this code:
|
|
A print example
Using similar logic to the example immediately above, I wanted to have two simple macros which would convert an Uno pin to an ATmega328P PORTn and Bitn. I determined using two macros was more efficient than trying to create a function which would return the two variables. In order to test the macros pintoPort() and pintoBit(), I created an example examples/pintoPort:
|
|
I found I needed to define the port pointer separately (line 14), get its value using pintoPort() on line 17 then print the pointer as a pointer using %p in the printf() statement on line 18. It’s was helpful (if not critical) to keep the declaration and the initialization on two different lines.
Entire File of Code
|
|
Comments powered by Talkyard.