5.4 ADC program revisited
6.2.2 Uses for structs
The uses for structs are many and varied. Already mentioned was the pos- sibility of emulating C++ or Java objects and storing references to functions, however these are not the purpose of introducing structs herein. Instead this text will introduce their use as another method of accessing SFRs (see chapter 2) and using them as the sole argument to functions, especially those in libraries referenced by different projects.
SFR access
Thus far access to individual bits of SFRs has been a clunky troublesome prospect requiring bitwise logic, left-shifting and masking. With a properly prepared library the bit fields and individual bits of the various SFRs become trivial to access. It has previously been mentioned that any data type can be included in a struct. This includes bit fields of any width, signed or unsigned. To create a bit field in a struct use the signed or unsigned keyword as appro- priate (in this usage they will all be unsigned) and follow the field name with a colon and the number of bits wide the field is. Here is an example using the ADCSRA register (Analog-Digital Converter Status Register A):
t y p e d e f s t r u c t { u n s i g n e d _ A D P S 0 :1; u n s i g n e d _ A D P S 1 :1; u n s i g n e d _ A D P S 2 :1; u n s i g n e d _ A D I E :1; u n s i g n e d _ A D I F :1; u n s i g n e d _ A D A T E :1;
u n s i g n e d _ A D S C :1; u n s i g n e d _ A D E N :1; } _ _ A D C S R A b i t s _ t ;
e x t e r n v o l a t i l e _ _ A D C S R A b i t s _ t A D C S R A b i t s
_ _ a s m _ _ ( " 0 x7A " ) _ _ a t t r i b u t e _ _ (( s e c t i o n ( " sfr " ) ) ) ;
The struct defined above consists of eight 1-bit wide bit-fields with the names of each field being the same (with the addition of an underscore) as are in the datasheet for the ATMega644p. It is vital to note that the least significant bit (bit 0) is the first listed in the definition. The final line of the above code instantiates the struct under the name ADCSRAbits and tells the compiler what memory address to reference. Specifically, each part of the final line mean the following:
extern: This keyword explicitly states that the variable defined on this line is a global variable. The variable is therefore accessible from anywhere in the program without special effort.
volatile: Often times a compiler will attempt to optimize the code. This can cause the compiler to store a value retrieved from memory in a temporary variable for usage multiple times, rather than reloading it from memory each time. The volatile keyword prevents this optimization which is important in this case as things outside the code can affect what is stored in the register.
ADCSRAbits t: The name of the type as defined by the typedef above. ADCSRAbits: The variable name. This is name of the variable that can be
referenced in the code.
asm ("0x7A"): This is a special command to the compiler instructing it to include what is in the parenthesis directly into the compiled code as is. In this instance it is telling the compiler that the variable ADCSRAbits refers to the register found at the memory address 0x7A.
attribute ((section("sfr"))): This provides additional informa- tion to the compiler for error checking regarding what the variable is for, and is not required.
Useful as this is, this struct provides no method of reading or writing the entire register at once. Another thing that would be useful is to be able to address all three ADPSn bits simultaniously. In order to do this another keyword must be introduced: union. A union allows multiple different data types to address the same memory. In this case the different data types will all be structs though with differing bit-fields. A unionis declared in much the same way as a struct, however each member of a union is a separate struct which does not require a name. Here is the same type as above ( ADCSRAbits t) adapted to allow easier access to larger bit-fields.
t y p e d e f u n i o n { s t r u c t { u n s i g n e d _ A D P S 0 :1; u n s i g n e d _ A D P S 1 :1; u n s i g n e d _ A D P S 2 :1; u n s i g n e d _ A D I E :1; u n s i g n e d _ A D I F :1; u n s i g n e d _ A D A T E :1; u n s i g n e d _ A D S C :1; u n s i g n e d _ A D E N :1; }; s t r u c t { u n s i g n e d _ A D P S :3; u n s i g n e d :5; }; s t r u c t { u n s i g n e d _w :8; }; } _ _ A D C S R A b i t s _ t ;
The first struct is the same as before. The second struct provides access to all three ADPSn bits simultaneously. This leaves five bits that already have been named in the first struct individually and don’t require further naming. Thus the name is left out of that line. Finally a third struct groups the entire byte into a single field. All three of these structs provide access to the same register, just with different layouts.
AVR, the makers of the ATMega644p, do not provide a header file with these definitions. It therefore falls upon the individual user to create or find such a header file. While it seems a daunting task the author created such a file with less than ten hours of work, and has proven to be very useful. As an example, in order to set the ADC prescaler to a division factor of 8 (ADPS2:0 = 0b011) without changing any other bit in the register now requires only
A D C S R A b i t s . _ A D P S = 0 b 0 1 1 ; rather than
A D C S R A = 0 b 0 1 1 | ( A D C S R A & 0 b 1 1 1 1 1 0 0 0 );
While both take a single line, the new method is easier to understand and to code.
Function Arguments
When first creating libraries it is not unusual to unintentionally limit the use- fulness by limiting the functions, especially those responsible for initializing peripherals, to specific tasks. It is much more useful to have a single function used for initialization of a peripheral with arguments for the various options and always use that function. This reduces the number of functions required for any particular task as only one is required as opposed to a separate function for each slightly different possibility.
There are two issues with doing this however. First, there could be numerous options, and while from the compiler’s side it isn’t an issue, it can cause problems for the programmer or whomever has to look at the code later. The second problem comes into play when an additional argument is required at a later date. This could be caused by a change to the hardware, be it update or change to a different controller, or due to a previously overlooked option. If suddenly a new argument is required, either a new function has to be written or all instances of function calls to the old version must be updated. The alternative to this is to only require a single argument for such a function - a struct containing all the information about the various options. If something changes in the future, a new field can be added to the struct in the library and old code using the functions will not require modification. The old code will simply ignore that additional field. A further benefit is that the struct can be used to store data being returned from the function. This is especially helpful when more than a single value must be returned.
Here is an example of how this can be used in initializing the ADC. All options available on the ATmega644p’s ADC are available via this struct and function except for the DIDR0 register which is purely optional anyways (its function is merely to save a tiny bit of power).
/* *
* \ t y p e d e f A D C _ i n i t _ s t r u c t
* s t r u c t c o n t a i n i n g all c o n f i g o p t i o n s for ADC */ t y p e d e f s t r u c t { c h a r c h a n n e l ; // / < C h a n n e l to use c h a r l e f t _ a d j u s t ; // / < l e f t or r i g h t a d j u s t r e s u l t c h a r r e f e r e n c e ; // / < r e f e r e n c e v o l t a g e s o u r c e c h a r a u t o _ t r i g g e r ; // / < s t a r t c o n v e r s i o n b a s e d // / < on e x t e r n a l s o u r c e c h a r a u t o _ s o u r c e ; // / < s o u r c e for a u t o c o n v e r s i o n c h a r e n a b l e _ i n t e r r u p t ; // / < e n a b l e s c o n v e r s i o n // / < c o m p l e t e i n t e r r u p t c h a r p r e s c a l e r ; // / < ADC c l o c k p r e s c a l e r } A D C _ i n i t _ s t r u c t ; int m a i n ( v o i d ) { A D C _ i n i t _ s t r u c t A D C _ s t r u c t ; A D C _ s t r u c t . c h a n n e l = 0; A D C _ s t r u c t . l e f t _ a d j u s t = 0; // r i g h t a d j u s t A D C _ s t r u c t . r e f e r e n c e = 1; // A V C C A D C _ s t r u c t . a u t o _ t r i g g e r = 0; // off A D C _ s t r u c t . a u t o _ s o u r c e = 0; // d o e s n't matter A D C _ s t r u c t . e n a b l e _ i n t e r r u p t = 0; // off A D C _ s t r u c t . p r e s c a l e r = 0 b 1 1 0 ; // d i v i s o r = 4 A D C _ i n i t ( A D C _ s t r u c t ); } /* *
* \ f u n c A D C _ i n i t * \ b r i e f I n i t i a l i z e s ADC and r u n s f i r s t ( e x t r a l o n g ) * c o n v e r s i o n * \ p a r a m c o n f i g s t r u c t c o n t a i n i n g all * c o n f i g u r a t i o n i n f o r m a t i o n * * A c c e p t s a a s t r u c t ( A D C _ i n i t _ s t r u c t ) * c o n t a i n i n g o p t i o n s for e v e r y ADC c o n f i g u r a t i o n * e x c e p t i n g o n l y D I D R 0 */ v o i d A D C _ i n i t ( A D C _ i n i t _ s t r u c t c o n f i g ) { A D M U X b i t s . _ M U X = c o n f i g . c h a n n e l ; A D M U X b i t s . _ A D L A R = c o n f i g . l e f t _ a d j u s t ; A D M U X b i t s . _ R E F S = c o n f i g . r e f e r e n c e ; A D C S R A b i t s . _ A D A T E = c o n f i g . a u t o _ t r i g g e r ; A D C S R B b i t s . _ A D T S = c o n f i g . a u t o _ s o u r c e ; A D C S R A b i t s . _ A D I E = c o n f i g . e n a b l e _ i n t e r r u p t ; A D C S R A b i t s . _ A D P S = c o n f i g . p r e s c a l e r ; A D C S R A b i t s . _ A D E N = 1; A D C S R A b i t s . _ A D S C = 1; // b e g i n f i r s t c o n v e r s i o n . // D o n e for t i m i n g p u r p o s e s w h i l e ( A D C S R A . _ A D I F == 0) {} // w a i t u n t i l c o n v e r s i o n c o m p l e t e }
The uses for both arrays and structs are far more numerous than mentioned here, and there is a wealth of information available on each for the interested. This has only been a brief introduction including some uses that may not be immediately obvious.