Saturday 2 June 2012

Embedded C for beginners - Using ATmega32

Introduction To Embedded C
-Yogesh Sharma



A  Register

A  register is simply a collection of some bits (mostly 8 bits in case of 8bit MCUs). Either each

different bit in a register has some purpose or the register as a whole holds a value. Registers serves as connection between a CPU and a Peripheral device (like ADC or TIMER). By modifying the register the CPU is actually instructing the PERIPHERAL to do something or it is configuring it in some way. And by reading a register, the CPU can know the state of peripheral or read associated data.

Fig.: CPU writing to Peripheral Register   
Fig.: CPU Reading from Peripheral Register


Binary Numbers in C

When you write a=110; in C it means you are setting the value of variable"a" to "one hundred and ten" (in decimal). Many time in embedded programming we are not interested in the value of a variable but the state of each bits in the variable. Like when you want to set the bits of a register (MYREG) to a bit pattern like 10010111 (binary). Then you cannot write MYREG=10010111. Because compiler will interpret 10010111 as decimal. To specify a binary number in C program you have to prefix it with 0b (zero followed by b). So if you write

MYREG=0b10010111;

it assigns the bit pattern 10010111 to the bits of Register MYREG.

HEX Numbers in C

In same way if you prefix a number by 0x (a zero followed by x) then compiler interpret it like a HEX number. So

MYREG=0x10; (10 in HEX is 16 in decimal) MYREG=0xFF;(Set all bits to 11111111 or decimal 255)

Setting a BIT in Register

Here our aim is to set (set to logical 1) any given bit (say bit 5) of a given register (say MYREG). The syntax is

MYREG=MYREG | 0b00100000;

The above code will SET bit 5 to 1 leaving all other bits unchanged. What the above code does is that it ORs each Bit of MYREG with each bit of 0b00100000 and store the value back in MYREG. If you know how logical OR works then you will get it.

In short you can write the same code as MYREG|=0b00100000;

Now lets come to practical usage. In practice each bit has got a name according to its work/function. Say our BIT (the 5th bit) has got name ENABLE, and what it does is clear by its name,when we set it to 1 it enables the peripheral and when cleared (0) it disables it. So the right way to set it is.

MYREG|=(1<<ENABLE);

The << is called left shift operator. It shifts the bits of LHS variable left by the amount on its RHS variable. If you write

b=1<<3;

then, 1 whose binary value is 00000001 is shifted 3 places to left which results in 00001000 So if ENABLE is defined as 5 (as enable is 5th bit) then

MYREG|=(1<<ENABLE); will result in MYREG|=(1<<5);

which again result in MYREG|=(0b00100000);


Now a beginner would ask "What's the Advantage ?". And once you know it you would realize that advantage is immense!

1.  Readability of code: MYREG|=(1<<ENABLE); gives a clue that we are enabling the peripheral while MYREG|=0b00100000; does not give any clue what it is doing, we have to go to data sheet and find out which bit actually ENABLEs the peripheral. While ENABLE=5 is already defined in header files by the developer of compiler by carefully studying the datasheets of device.
2.   Easier Portability: Suppose you use this code many times in your program (and your program is reasonably large and uses other register also) and you now want the same code to run on some other MCU model. The new MCU is of similar family but has slightly different bit scheme, say ENABLE is bit 2 instead of bit 5. Then you have to find all occurrence of MYREG|=(0b00100000); and change that to MYREG|=(0b00000100); But if you have used the other method then you simply need to inform the compiler (by its setting options) that you are going to use the other MCU and compiler will automatically get the definitions for the new device. And in this definition ENABLE=2 will already be defined by the compiler developer. So it will be lot easier.

Clearing a BIT in Register

For clearing a bit logical AND(symbol &) operator is used in place of logical OR (symbol |). The syntax is as follows

MYREG&=~(1<<ENABLE); 



Fig.: How to clear (0) a bit in C language.


This will clear (i.e. set to value 0) a given bit (identified by name ENABLE) in a register called MYREG. This operation will not affect any other bits of register except ENABLE.

Let us see how it works with the help of following diagram.

Fig.: "Clearing a BIT" how it works?


So now you know how you can selectively clear any bit in any given register. If you want to clear more than one bit at a time you can write like this

//This will clear bits ENABLE,FAST_MODE and BUSY, leaving all other bits untouched

MYREG&=(~((1<<ENABLE)|(1<<FAST_MODE)|(1<<BUSY)));

Similarly the syntax for setting(set to 1) multiple bits at a time is as follows



//This will set bits ENABLE,FAST_MODE and BUSY, leaving all other bits untouched MYREG|=((1<<ENABLE)|(1<<FAST_MODE)|(1<<BUSY));


Testing The Status of a Bit.

Till now we were modifying the registers either setting or clearing bits. Now we will learn how can be know that a specific bit is 0 or 1. To Know if a bit is 0 or 1 we AND it with a AND MASK. Suppose if we want to check bit 5 of a register MYREG then the AND MASK would be 0b00100000. If we AND this value with the current value of MYREG then result will be non-zero only if the 5th bit in MYREG is '1' else the result will be '0'.

The syntax would be like this.

if(MYREG  &  (1<<ENABLE))

{

//ENABLE  is  '1'  in  MYREG

...

}

else

{

//ENABLE  is  '0'  in  MYREG

}

So now you know the basic operation on bits, they are widely used in firmware programming and will help you understand other codes on my web site. And Please don't forget to post your comment regarding any doubts, or reporting errors in the above article, or simply to tell how you liked the stuff.


Data Types:



The primary data types required in Embedded C are:
Data Types

Size

Range


bits

bytes



char or signed char
8

1
-128

to
127
unsigned char
8

1
0.420

to
255
int or signed int
16

2
-32768

to
32767
unsigned int
16

2
0

to
65535
float
32

4
3.4 e(-38)

to
3.4 e(+38)
double
64

8
1.7 e(-308)

to
1.7 e(+308)

User defined type declaration

C language supports a feature where user can define an identifier that characterizes an existing data type. This user defined data type identifier can later be used to declare variables. In short its purpose is to redefine the name of an existing data type.

Syntax: typedef <type> <identifier>; like typedef int number;

Now we can use number in lieu of int to declare integer variable. For example: “int x1” or “number x1” both statements declaring an integer variable. We have just changed the default keyword “int” to declare integer variable to “number”.


Typecasting:

Typecasting is a way to make a variable of one type, such as an int, act like another type, such as a char, for one single operation. To typecast something, simply put the type of variable you want the actual variable to act as inside parentheses in front of the actual variable. (char)a will make 'a' function as a char.

One use for typecasting for is when you want to use the ASCII characters. For example, what if you want to create your own chart of all 256 ASCII characters. To do this, you will need to use to typecast to allow you to print out the integer as its character equivalent.


#include  <stdio.h>

int  main()

{

for  (  int  x  =  0;  x  <  256;  x++  )  {

/*  Note  the  use  of  the  int  version  of  x  to  output  a  number  and  the  use

*  of  (char)  to  typecast  the  x  into  a  character  which  outputs  the

*  ASCII  character  that  corresponds  to  the  current  number

*/

printf(  "%d  =  %c\n",  x,  (char)x  );

}

getchar();

}



Typecasting is dangerous because - You might temporarily lose part of the data - such as truncating a float when typecasting to an int.

One good use for typecasting is - To allow division of two integers to return a decimal value.

Eg: 3/2=1.5


Following conversions are possible using typecasting:
    int to float
    float to int
    char to float


Storage Class:

Storage class provides information about their location and visibility. The storage class divides the portion of the program within which the variables are recognized.

auto :

    It is a local variable known only to the function in which it is declared.

    Auto is the default storage class when a variable is declared in a function (i.e. local variables).

     It is realized everytime the function is called – That is memory is allocated to the variable when it enters the function and it is deallocated when it leaves the function.

static :

    Local variable which exists and retains its value even after the control is transferred to the calling function.

    Static is the default storage class for global variables.

    Static variables can also be defined within a function.

    When defined inside the function the variable is initialized at run time and not reinitialized when the function is called again.

     Thus inside a function a static variable retain its value during the various function calls.

    Static variables are inialized to zero automatically.

    Following example shows the use of static variable



void func(void);
static count=10; /* Global variable - static is the default */

main() {
while (count--) {
func();
}
}

void func( void ) {
static i = 5; i++;
printf("i is %d and count is %d\n", i, count);
}

This will produce following result
i is 6 and count is 9
i is 7 and count is 8
i is 8 and count is 7
i is 9 and count is 6
i is 10 and count is 5
i is 11 and count is 4
i is 12 and count is 3 
i is 13 and count is 2
i is 14 and count is 1
i is 15 and count is 0

extern :

    Extern is like a global variable known to all functions in the file.

    Extern is used to reference a local variable that is visible to all the program files in that project.

    Extern variables are not initialized because they just point to another variable of the same name present in some other file.

    Extern is useful when one wants to use a variable or a function from another file.

    All functions in C are extern by default. Hence they are accessible by all the files in the project.

    Following example illustriates the use of extern variable:

File 1: main.c

int count=5;
main() {
write_extern();
}


File 2: write.c

void write_extern(void)

extern int count; 

void write_extern(void) {
printf("count is %i\n", count);
}


Here extern keyword is being used to declare count in another file. Now compile these two files as follows

gcc  main.c  write.c  -o  write

This fill produce write program which can be executed to produce result.

Count in 'main.c' will have a value of 5. If main.c changes the value of count - write.c will see the new value


register :

    Register is used to indicate the local variables which are to be stored in a register instead of RAM.

    Hence,



maximum size of variable = size of register


Constant and Volatile:

constant variable:

const means that something is not modifiable.

Hence a const variable once assigned a value, that value cannot be changed even if you want to change it.

volatile variable:

A volatile variable is the one whose values may be changed at any time by some external sources.

volatile  int  num;
The value of data may be altered by some external factor, even if it does not appear on the left hand side of the assignment statement. When we declare a variable as volatile the compiler will examine the value of the variable each time it is encountered to see if an external factor has changed the value.


Use of volatile:

- An object that is a memory-mapped I/O port
- An object variable that is shared between multiple concurrent processes
- An object that is modified by an interrupt service routine
-  An automatic object declared in a function that calls setjmp and whose value is-changed between the call
to setjmp and a corresponding call to longjmp


*With volatile keyword in the declaration the compiler knows that the value of a variable must be read from memory every time it is referenced.

The following program shows a typical example of using a volatile variable: Interrupt service routines often set variables that are tested in main line code.

One problem that arises as soon as you use interrupt is that interrupt routines need to communicate with rest of the code .

A interrupt may update a variable num that is used in main line of code . An incorrect implementation of this might be:

static lon int num ;
void interrupt update(void) {
++num ;
}

main() {
long val;
val= num ;

while(val !=num);
val = num ;
 return val ;
}

Problem and Solution:

       When compilation system execute while statement, the optimizer in compiler may notice that it read the value num once already and that value is still in the register.

       Instead of re-reading the value from memory, compiler may produce code to use the (possibly messed up) value in the register defeating purpose of original C program.

      Some compiler may optimize entire while loop assuming that since the value of num was just assigned to val , the two must be equal and condition in while statement will therefore always be false .

      To avoid this ,you have to declare num to be volatile that warns compilers that certain variables may change because of interrupt routines.


static volatile long int num ;

With volatile keyword in the declaration the compiler knows that the value of num must be read from memory every time it is referenced.


Pointers:

Pointers are surprisingly simple, all you have to do is remember that you're working with memory addresses. For example, let us use an imaginary block of memory (while at the same time showing off my beautiful artistic abilities)

-----------------------------------

|  123  ||  124  ||  125  ||  126  ||  127  |

-----------------------------------


Now, when you declare a regular variable, for example: char ch;


The name 'ch' becomes a synonym for the memory location it is given. If ch is given the location 123 you can imagine the imaginary memory block to look like this:

----------------------------------

|  ch  ||  124  ||  125  ||  126  ||  127  |

----------------------------------


Now, a pointer is a separate variable that refers to another location in memory. However, when it comes down to everything, a pointer is just another variable. You declare it using the pointer notation *:

char  *pch;


Since pch is just another variable, our imaginary memory block might look like this:

-----------------------------------------

|  ch(123)  ||  pch(124)||  125  ||  126  ||  127  |

-----------------------------------------


To give ch a value, all you do is assign that value to the memory location (cleverly disguised by the name 'ch'):

ch  =  'A';


The process is exactly the same with pointers except instead of arbitrary values, their value is an actual address in memory. To obtain the address of a variable, you prefix it with &. So to assign the address of ch to pch you would do this:

pch  =  &ch;


Now pch's value is the address of ch, exactly like ch's value is 'A'. If you access pch, you'll get that address:

printf("%p\n",  pch);




Now, the great thing about pointers is that since they are references to the address they contain, you can "dereference" them by prefixing the * character. This accesses the memory address contained


by the pointer. The following printf statements output the exact same thing:

printf("%c\n", ch); printf("%c\n", *pch);


Why? Because ch is the memory location that we assigned 'A'. By using a dereference, *pch is also the memory location that we assigned 'A'.

This is the basic idea behind the pointer. Any data type in C can have a pointer to that data type (even functions!). Let's look at a few uses of pointers.

Before going any further, it's a good idea to mention two special variations of a pointer. The first is a type of pointer and the second is a special value.

First is the void pointer:

void *pv;

A void pointer is special in that it is the generic pointer, you can assign any pointer value to void and then cast it back to the original type.
 The reason this cast is required is because void pointers cannot be dereferenced:

void *pv; int *pi;

int      i  =  10;

pi
=  &i;

pv
=  pi;

printf("%d\n",  *pi);
/*  Prints  10  */
printf("%d\n",  *(int*)pv);
/*  Also  prints  10  */  ->Typecasting


Note that the cast is made *before* the dereference is performed. This is important, if the cast were

(int*)*pv


You would be dereferencing a void pointer and returning a pointer to int from that invalid reference. Both are quite wrong.


Next is the null pointer:

A null pointer is a value that you assign to a pointer variable when you want it to point to nothing in particular. This value is used as a safe starting value for pointer variables, and erroneous return values from functions that return pointers, such as malloc. A null pointer is any integral value that evaluates to 0 (zero). The two common ways of using null are the integral constant 0, and a macro defined in several standard headers called NULL:

#include  <stddef.h>  /*  For  NULL  */
 int *pi = 0; char *pc = NULL;

A null pointer cannot be dereferenced because null is an invalid location in memory to access. You can only assign null and test for it:



if  (pc  !=  NULL)


/*  Do  something  */

if  (pc  !=  0)

/*  Do  something  */

Since both NULL and 0 are both null pointer values, even if you assign NULL to a pointer variable, you can still test it against 0 to see if it's a null pointer.

Now that all of that is out of the way, a few uses of pointers. :)


Pointers as Function parameters and return types:

Pointers are useful as function parameters and return types in two ways.

First is a size issue, imagine that you have a structure variable that is big, say, 30 bytes. If you pass it to a function the usual way

void  f(struct  MYSTRUCT  mys);

...

f(var);

A copy of the entire 30 bytes is made. This can be wasteful in both memory and performance.

By passing a pointer to that variable only the size of its memory address is copied and passed. Using our imaginary memory setup, instead of 30 bytes, only 2 bytes are copied.

This is faster and more conservative of memory.

void  f(struct  MYSTRUCT  *pmys);

...

f(&var);

Notice that the syntax of the pointer hasn't changed from our original examples.

The second help with pointers as function parameters and return types is the referencing feature. By passing mys in the first example, you get a *copy* of the variable passed to the function. So if you want to make any changes to the original variable, you have to return the copy with changes made and assign it to the original:

struct  MYSTRUCT  f(struct  MYSTRUCT  mys);

...

var  =  f(var);

By passing a pointer to the function, you are really copying the memory address of the original variable. Since you're able to access the memory address of the original variable, you can make *changes* to it from within another function!

int  f(struct  MYSTRUCT  *pmys);

...

errorcode  =  f(&var);

Return values work the same way with pointers, you can return a pointer for improved efficiency, and so that a variable can be changed outside of the function. There is one problem that you should take into consideration though -

Local variables are destroyed and their memory reclaimed at the end of the enclosing block, so returning a pointer to a local variable is not correct:


int *f(void) {
int i = 10;
return &i; /* No! Returning a local variable is bad! */
}

Returning memory that you control (such as memory returned by malloc and calloc) is okay though:


int *f(void) {
int *i = malloc(sizeof (int));
return i; /* Okay, you control when the memory is released */
}


Dynamic data structures

Pointers are used heavily for data structures that can grow and shrink dynamically at runtime. This dynamic sizing requires the programmer to handle memory through pointers using malloc/calloc, realloc, and free. Such data structures can be node based (linked lists, binary trees, etc...) or block based (resizable arrays).

To use a pointer to create an array, simply malloc enough memory for it by multiplying the size of the element type by the number of elements you want:


int *parray = malloc(10 * sizeof (int));
if (parray != NULL){
/* Okay, use it */
}


Note that malloc, calloc, and realloc all return null pointers if they fail. The above code is basically the same thing as using static arrays:

int  array[10];

The only big differences are that you have control of the size of parray at runtime, you can grow it or shrink it by using realloc.

The second big difference is that you *must* remember to free the memory you allocate using malloc, calloc, or realloc.


Iterating through an array and saved locations

Pointers are helpful for iterating through arrays and saving things that you want to come back to. For example, if you have the following string:

char  s[]  =  "This  is  a  test";

And you want a pointer to the beginning of each word, you can simply do this:
char *a = s; 
char *b = s + 5;
char *c = s + 8;

char  *d  =  s  +  10;

Now a points to "This is a test", b points to "is a test", c points to "a test", and d points to "test". Note that pointers can have arithmetic performed on them. The name of an array is a pointer to the first element, so if you add 1 to it you get the second element. The above could also have been written as



char  *a  =  &s[0];
char *b = &s[5]; 
char *c = &s[8]; 
char *d = &s[10];


Arrays and pointers are closely related but are *not* the same. Keep this in mind when mixing them.

If a beginner in C wanted to print each element in an array, he would most likely do this:

int array[10] = {1,2,3,4,5,6,7,8,9,-1}; int i;
for (i = 0; i < 10; i++) 
printf("%d\n", array[i]);
or
for (i = 0; array[i] != -1; i++) 
printf("%d\n", array[i]);

This can also be done using pointers:

int array[10] = {1,2,3,4,5,6,7,8,9,-1}; 
int *pi;

for (pi = array; *pi != -1; pi++) printf("%d\n", *pi);


By using arithmetic on a pointer to the first element of the array, you can walk every element in the array by adding one. Note that by adding one to a pointer, the pointer moves forward by the size of its data type. If char is one byte and int is two bytes

char *pc = &c;
 int *pi = &i;
pc++; /* Moves forward 1 byte */
 pi++; /* Moves forward 2 bytes */

This is important to remember because you don't have to manage how much memory is jumped over, the compiler does all of this for you.

Miscellaneous:

Pointers can be constant, but since a pointer really has two parts (the pointer itself and the address it points to), one or both or neither can be constant. You do it like this:

const int *p; /* The pointed-to value is const */ 
int * const p; /* The pointer itself is const */ 
int *p; /* Both are not const */

const  int  * const  p;  /*  Both  are  const  */


Pointer and Array:

Pointer to an Array:

Declaration : (int*) a[5];

Representaion:



a ->



100









101





102





103



104





a is a pointer pointing to address  a[0], that is it is pointing to address of 1.

Array of Pointers:



Declaration : int* a[5];



Representaion:


a =

->


100










101
->
Pointers pointing to





102
->


other memory





103
->
location





104
->














a is and array containing pointers
101 is a pointer pointing to address  of another memory location.


Pointer and Functions:

Pointer to a function:

There are two uses of a function, call it, or take its address. If you aren't calling a function then you can assign its address to a pointer. The declaration for a pointer to a function is complex, just pretend you're writing a function prototype, then prefix the name with * and surround both with parentheses:



void (*pf)(void); /* Pointer to a function that takes no args and returns nothing */

Now you can assign a function with the same type:

void f(void) {
printf("Hello, world!\n");
}
pf = f; /* Not calling f, must be taking the address */


Now that pf points to f, you can call f *through* pf:

(*pf)(); or pf(); //both are same


Pointers can also point to other pointers:

The standard example of a pointer to a pointer is in the case of memory allocation. Say you want to pass a pointer to a function and allocate memory to it. You want to return an error code, so returning the freshly allocated memory isn't an option:

int  allocme(int  *parray)

{

parray  =  malloc(10  *  sizeof  (int));

if (parray == NULL) return -1;

return  0;

}

...

int  *pa;

if  (allocme(pa)  !=  -1)

/*  All  is  well,  use  pa  */


This looks like it should work fine. By passing a pointer to int you can change the original, right? Right, but that's not what we want to do. We want to change the *pointer* that points to int, not the int that it points to. This is a problem because the pointer is a copy. The solution is the same as when we wanted to make changes to the original structure variable, pass a pointer to it. :)

int  allocme(int  **pparray)

{

*pparray  =  malloc(10  *  sizeof  (int));

if (*pparray == NULL) return -1;

return  0;

}

...

int  *pa;

if  (allocme(&pa)  !=  -1)

/*  All  is  well,  use  pa  */


Once again note that the syntax for a double pointer is consistent with a single pointer, just tack on an extra * for the declaration and dereference. Just make sure that you dereference the outer pointer first. Pointers to pointers to structures can cause problems because of this and when you want to access a member you have to surround the first dereference with parentheses:

struct  MYSTRUCT  **ppmys;

...
(*ppmys)->member; /* Works okay */ 
*ppmys->member; /* Doesn't work right */



No comments:

Post a Comment