Programming Background
This page contains fundamental background knowledge that should already be known before entering the class. This page is meant to serve as a refresher and highlight concepts you will see throughout the course.
Contents
Source Files
Source files end with a .c extension and contain code that is built into a binary file that is executed on the processor (.hex file).
In your source files you create functions that create the logic defining the flow of your program. At a very minimum you should have a main.c file with an int main() function.
Header Files
Header files can be used to connect multiple source files together and act as a list of function declarations for everything in the corresponding source file. This is helpful when trying to organize code and prevents all code from going in the main.c file.
To create a header file, create an empty .h file and add the following changing instances of example to the name of your file.
/* * Example.h * * Created on: Jun 23, 2014 * Author: ewillcox */ #ifndef EXAMPLE_H_ #define EXAMPLE_H_ //Function declarations go here //Example: int calculateArea(int length, int width); #endif /* EXAMPLE_H_ */
To include a header file in a source file an #include statement is needed at the top of the source file.
#include Example.h
Functions
Functions create blocks of code that can be called from within other functions to break up code. To call another function, a declaration should be made in a header file for everything except main() as it is called automatically.
Functions can be written in the following form.
return_type func_name(arg1, arg2, ..., argn){ //logic return value; }
Variables
Variables are ways to store data that can be modified at a later time. There are multiple types of variables that have use different amounts of space on different platforms. For 3001, the following list holds true.
1 Byte
char
2 Bytes
short
int
4 Bytes
long
float
double
long double
8 Bytes
long long
Void
The final variable type you should be familiar with is void. It means no type and can be used for function types and variable types. When used with a function it means that it doesn't return anything such as the following example.
void setter(int pt_value){ global_rectangle_start = pt_value; //Sets the starting point }
The function does not return anything as it only sets something, however it is valid to add a
return
to the end of the function which explicitly states that the function is done. If there were no arguments to the function, it is also possible to declare that it takes in
void
which means nothing is needed for it to work. This is helpful so that others know you didn't mistype anything and did mean to take in nothing.
Scope
The scope of a variable means where it is visible. Global variables can be seen anywhere in a program while local variables can only be seen wherever the locality is. This is best seen in an example which illustrates all cases.
int var = 7; //Global void func2(){ printf("func2 - var: %d\n", var); //Uses global } void func1(){ int var = 24; printf("func1 - var: %d\n", var); //Uses local to func1 } int main(){ int var = 365; printf("main - var: %d\n", var); //Uses local to main func1(); func2(); for(int var=0; var<1; var++) printf("for - var: %d\n", var); //Uses local to loop printf("main is unchanged - var: %d\n", var); //Uses local to main if(var){ int var = 99; printf("if - var: %d\n", var); //Uses local to if } printf("main is unchanged - var: %d\n", var); //Uses local to main return 0; }
Naming variables all the same is of course bad practice and should never be done. The only types of variables that should be reused in a file is for counters in loops which are only temporary!
Conditionals
Conditionals are a way to check if something is true or false and are as follows.
Operator | Name | Example | Expected Return |
== | Equal to | x == 5 | Returns 1 if x is equal to 5 |
!= | Not equal to | x != 5 | Returns 1 if x is not equal to 5 |
< | Less than | x < 5 | Returns 1 if x is less than 5 |
<= | Less than or equal to | x <=5 | Returns 1 if x is less than 5 or equal to 5 |
> | Greater than | x > 5 | Returns 1 if x is greater than 5 |
>= | Greater than or equal to | x >=5 | Returns 1 if x is greater than 5 or equal to 5 |
Comments
Comments are written for benefit of the reader and have no bearing on the behavior of the code. Comments in the header file should say what the function does while comments in the source file should say how the function operates. While writing your code and submitting it for your TA to review, try to follow Rubber Duck Debugging to make their lives easier as they have to read the code of the entire class. This is especially important when you email for help as many students do not comment their code and this makes it difficult on staff to troubleshoot your code when providing email help and they do not have a board to test your code on.
Once a comment is started, anything written after it until the end of line is reached is ignored when using a single line comment as shown following.
//this is a comment, let's add 1 to i i++; //this comment can go here and not affect the code //but not here!!! included in the comment -> return i;
Multi-line comments can be used to write over multiple lines without having to keep using the // statement.
/* Start of comment / This comment goes over multiple lines / End of comment, this time the return statement is ran! */ return i++;
Math
It's possible to do the following operations without any additional libraries on the board: assignment, addition (=), subtraction (-), shifts (left: << OR right: >>), multiplication (*), division (/) and modulo (%).
Shifting
Shifting is a simple way to shift the bits left or right a specified amount. This is equal to dividing or multiplying by the power of 2 value of the shift amount and can speed up your code if used intelligently. As multiplication takes multiple cycles, instead of dividing your variable by 2 you can just shift right by 1 to achieve the same result assuming you are not using floating point values.
x = 100; x = x >> 1; //expected result: 50
Modulo
Modulo is a special form of division that gives the remainder and is very useful for finding if a variable is divisible evenly by a number. An often used case is to convert one unit into another, such as milliseconds to seconds.
//count seconds from the millisecond timer //if there is no remainder after dividing by 1000, add 1 second if(millis%1000 == 0) seconds++;
Math.h
More functions can be used by including the math.h library which has functions such as sin, cos and power. The API can be found here.
Loops
Loops are used to repeat an action multiple times, typically until a condition is met. There are two main types of loops that both achieve the same end result and which one to use is a matter or preference. As an example for each, it will be shown a way to count to 100 and print out "fizz" if the value is divisible by 4, "buzz" if divisible by 3 and "fizzbuzz" if divisible by both 3 and 4. (Note: this is a common interview style question!)
While
The while loop is the most basic type of loop and takes a condition as an argument and will continue to run until the condition is false. It is most commonly used when monitoring some value such as a sensor reading waiting for it to get to a desired value. To run forever as might be desired in the main() function, just use while(1).
One solution to the previous problem statement:
int main(){ int i = 0; while(i<=100){ if(i%3==0 || i%4==0) printf("%d: ", i); //Print the value of i if(i%3==0) printf("fizz"); //Checks for divisible by 3 if(i%4==0) printf("buzz"); //Checks for divisible by 4 if(i%3==0 || i%4==0) printf("\n"); //Makes a new line if divisible by either i++; //Manually increment i } return 0; }
Do...While
The do...while loop is just a special case of the while loop which checks the condition at the end of the loop instead of the beginning. It has the following form.
do{ //loop logic } while(conditional);
For
The for loop is most commonly used when a code segment needs to be run a set number of times. for() takes 3 arguments to run which do the following:
arg1 - Declare a new variable or set a previously declared one to a value (optional). (Note: if not using c99, you can't declare your variable in the for statement and must do it similar to the while loop. You can however assign a variable to a value.)
arg2 - Condition to watch for
arg3 - What to do at the end of the loop
As shown below in the solution to the previous problem, a for loop is created with a variable i which is incremented to 100.
int main(){ for(int i=0; i<=100; i++){ if(i%3==0 || i%4==0) printf("%d: ", i); //Print the value of i if(i%3==0) printf("fizz"); //Checks for divisible by 3 if(i%4==0) printf("buzz"); //Checks for divisible by 4 if(i%3==0 || i%4==0) printf("\n"); //Makes a new line if divisible by either //Notice we don't increment i manually! It's done in the for statement! } return 0; }
Break and Continue
Two commonly used statements within loops are continue and break. Using continue restarts the loop back at the beginning and will reevaluate the conditional. The break statement exits the loop immediately and will start running whatever code is after the loop.
Special Keywords
There are four keywords that you can attach to a variable type that will further define them.
Static
The static keyword is given a fixed address at build time, and is useful when using pointers as it will always be at this address. Caution should be taken to where you place static variables as unexpected behavior could be encountered.
Const
To make a variable that is unmodifiable, declare it as a const so it will be fixed. This is much better than a #define statement as it is type-safe.
Volatile
Volatile variables are never optimized by the compiler, meaning that whenever you apply changes to the variable they will always be calculated during run-time. This is necessary when repeatedly changing a variable in a short amount of time as a compiler may try to optimize your loops to get rid of redundant changes such as with a timer.
Extern
Used to mark a variable as external to the current file and can be found declared in one of the included header files. Typically used when accessing variables such as globals in other files or a shared resource.
Includes
Header files can be included by using #include <file.h> or #include "file.h" at the top of a source file or after the #define in a header file. Source files should never be included.
Defines
Define statements are used to create constant values such as
#define num_classmates 30
Which makes a constant called num_classmates which has a value of 30. This type however is not type safe and caution should be exercised when using them.
Macros
Macros are a special type of function using the #define keyword that are useful to do simple mathematical computations. An example follows which converts radians to degrees.
#define rad_deg(x) ((x) * 57.296)
Extreme caution should be exercised while using macros as they are not safe, and are difficult to debug. You should typically use a function unless you have a reason not to.
Interrupts
Interrupts will be used throughout the course, mainly with timers which keep track of elapsed time since the system started. They are a way for the AVR chip to stop whatever code it is currently executing to handle a special event called an interrupt which calls an Interrupt Service Routine (ISR) such as when a button is pressed and then return to running the code as normal. An ISR should be as short as possible and not include prints (lengthy calls) as you want the processor to get back to your normal code ASAP.
A typical ISR may look like the following which is an ISR for the overflow vector for timer 0. The TIMER0_OVF_vect is a predefined name you can find in the datasheet for the processor and you cannot use your own names. There is a defined name for all possible ISRs in the datasheet as well as AVRs provided header files such as iomxx4.h.
ISR(TIMER0_OVF_vect){ //increase the timer -> we got an interrupt globals.counter ++; }
To enable interrupts on the 3001 board you must call the built-in function sei(), and to stop interrupts you can call cli().
Pointers
Pointers are a way to pass data around so that it can be modified by another function without it being a global. Pointers are vital to C and it is a requirement in 3001 to use them to avoid globals whenever possible. They work by pointing to the memory address of where the data is stored so that a function that normally doesn't have access to it can see it. A simple example is shown.
void calcs(int *x, int *y, int *z){ *x = (*x * 100)/3; *y += 5; *z = *x; printf("Mem we see from helper\n"); printf("*x: %p\t*y: %p\t*z: %p\n\n\n\n", &*x,&*y,&*z); printf("Forgetting the pointer is bad. x: %d\n", x); x=30; printf("But we can edit this and not affect anything \ outside this function: %d\n\n\n\n", x); } int main(){ int x = 52; int y = 300; int z = 0; printf("Start\n"); printf("x: %4d\ty: %4d\tz: %4d\n", x,y,z); printf("Mem\n"); printf("x: %p\ty: %p\tz: %p\n\n\n\n", &x,&y,&z); calcs(&x, &y, &z); printf("End\n"); printf("x: %4d\ty: %4d\tz: %4d\n", x,y,z); printf("Mem\n"); printf("x: %p\ty: %p\tz: %p\n\n", &x,&y,&z); return 0; }
Arrays
Arrays are a way to put variables into a list-like format in n-dimensions. Each position (index) can be accessed by calling array[i] where i is the index you want to look at.
A basic 1-d example is shown below. A random generator was used to fill the arrays with data, if you do not understand it that is okay.
int main(){ srand(time(NULL)); //Seed the random gen int black[10]; //Create an array called "black" with 10 positions int white[10]; //Similarly create a "white" one //Fill the arrays with int data from 0 - 255 for(int i=0; i<10; i++){ black[i] = rand()%255; //black[i] accesses the i-th position of the array white[i] = rand()%255; } //Print back the arrays to see what is in them for(int i=0; i<10; i++){ printf("Black[%d]: %d\t", i,black[i]); printf("White[%d]: %d\n", i,white[i]); } return 0; }
An important thing to note is that arrays start at 0, not 1. They are useful for storing lots of things that go together with one another, and next are shown how to work with them in pairs.
Multi-Dimensional Arrays
With the last example, black and white are meant to be a pair for color balances. We can then group them together. This means black[0] goes with white[0] and so on, therefore it makes sense to group these into a 2-D array. Next is an example showing how to group them together.
int main(){ srand(time(NULL)); const int BLACK = 0; //Constant for accessing black by name const int WHITE = 1; //Constant for accessing white by name int black_white[10][2]; for(int i=0; i<10; i++){ black_white[i][BLACK] = rand()%255; //Put data into the black position black_white[i][WHITE] = rand()%255; //Put data into the white position } for(int i=0; i<10; i++){ printf("Black[%d]: %d\t", i,black_white[i][BLACK]); //Pull data from black positions printf("White[%d]: %d\n", i,black_white[i][WHITE]); //Pull data from white positions } return 0; }
This can be expanded out to however dimensions you need, however make sure to keep track of your indicies properly! Creating constants as was done in the 2-D example can help a lot, especially when other people read your code or you look back at something you wrote weeks ago when going to the final.
Passing an array
If you want to pass an array to another function, it works similar to a pointer however working with it is simpler. Following is an example of how to pass a 2-D array to a helper function which accomplishes the same task as the previous. One thing to note however is instead of passing x and y as I did for dimensions, you can remove those and explicitly give the size in the argument similar to declaration but this does not allow for variable sizes.
const int BLACK = 0; const int WHITE = 1; void helper(int x, int y, int arr[x][y]){ for(int i=0; i<x; i++){ arr[i][BLACK] = rand()%255; arr[i][WHITE] = rand()%255; } } int main(){ srand(time(NULL)); int black_white[10][2]; helper(10, 2, black_white); for(int i=0; i<10; i++){ printf("Black[%d]: %d\t", i,black_white[i][BLACK]); printf("White[%d]: %d\n", i,black_white[i][WHITE]); } return 0; }
Strings
Strings can be created by making a character pointers point to a string or by using a character array. An example is shown using both options along with some of the more useful functions within string.h you may use during the class.
int main(){ char name[65]; char major[65]; char *hometown; strncpy(name, "Eric", 64);//Makes sure we don't overflow strcpy(major, "RBE/ECE");//Doesn't check for overflow hometown = "Westfield, MA";//Tell pointer hometown to point memory address containing "Westfield, MA" //Print out our data printf("Name: %s\n", name); printf("Hometown: %s\n", hometown); printf("Major: %s\n", major); //Let's combine the info into one string char combinedInfo[257]; //Very useful function for 3001 That can format your print statements sprintf(combinedInfo, "%s, %s, %s", name, major, hometown); printf(combinedInfo); //Let's check a few things //Is the name Archer? int nameArcher = strcmp(name, "Archer"); //0 = true, else = false printf("\nName is 'Archer'? %d\t1 = No, 0 = Yes\n", nameArcher); //How long is our name? (Doesn't count '\0') printf("Name is %d characters long\n", strlen(name)); return 0; }
A very important thing to note with strings it that they are null terminated meaning that appended to everything is a hidden "\0". This is important when doing compares and copies as you need to remember to not accidentally cut it out of the string on accident (or on purpose). Here is an illustration.
int main(){ char nameBad[4] = {'E', 'R', 'I', 'C'}; //Initialize array char nameGood[5]; //Will contain a good copy of "Eric" char copyBad[257]; //Will demonstrate that copy breaks strcpy(nameGood, "Eric"); int match = strcmp(nameBad, nameGood); //This will report we have a match when we really don't //as nameBad is not null-terminated if(match == 1) printf("Names do not match!\n\n"); else printf("Names match!\n\n"); //Errors will happen here though as shown in the output strcpy(copyBad, nameBad); //Copy the bad name to the copyBad array printf("%s", copyBad); return 0; }
Sturcts
Structs are a way to make a complex datatype that groups a set of variables under one name in memory. This allows for variables to be grouped in a way that make sense, such as a line struct containing two points, a color and a width.
Example
An example for creating one is shown below for making a struct called globals that contains all global variables used within the program and would reside in a header file. Grouping globals like this is handy as it makes your code explicitly clear that you are calling global variables.
typedef struct { int time; // Notice we do not initialize to a value! Done in source files } Globals; extern Globals globals; // Declaration so we can access the struct in other files void initGlobals(); // Function to call to initialize globals
The corresponding source file would look similar to the following.
#include "globals.h" Globals globals; void initGlobals() { //initialize time to 0 globals.time= 0; }
Passing Structs
Structs are passed using pointers as shown in the following example. Care should be taken while accessing your data.
struct ship{ char type[25]; //String for type char name[25]; //and name long worth; //Net worth of ship char alive; //Y/N for alive }; typedef struct ship Ship; //Make a ship datatype void killShip(Ship *aliveShip){ aliveShip ->alive = 'N'; //Simple way to access data (*aliveShip).worth = 0; //Alternative way to access data strcpy(aliveShip->name, "Wreck of Interceptor"); //Copy in the string to the char array strcpy(aliveShip->type, "Wreck"); } int main(){ Ship Slicer; //Create a ship Slicer.worth = 20000; strncpy(Slicer.name, "Slicer", 25); //Copy in it's name strncpy(Slicer.type , "Interceptor", 25); //And type Slicer.alive = 'Y'; //Ship is alive Ship *slicerPtr = &Slicer; //Create a pointer we can pass later //Print out our ship info printf("Starting\n"); printf("Name: \t%s\n", Slicer.name); printf("Class: \t%s\n", Slicer.type); printf("Worth: \t%d\n", Slicer.worth); printf("Alive: \t%c\n\n", Slicer.alive); killShip(slicerPtr); //Kill the ship printf("Ship killed!\n"); printf("Name: \t%s\n", Slicer.name); printf("Class: \t%s\n", Slicer.type); printf("Worth: \t%d\n", Slicer.worth); printf("Alive: \t%c\n\n", Slicer.alive); return 0; }
Taking in a Variable Number of Arguments
Sometimes it is useful to be able to take in a variable number of arguments such as with setting pin directions on an embedded chip or with a function like printf(). To use a variable number of arguments, you need to include stdarg.h which gives access to new datatype and functions.
The first thing to do is to use the datatype va_list which allows for you to get the arguments taken in. Next, you use the function va_start(list, length) with the list you created and telling it how long the list is.
At this point, you can start to pull out your data using va_arg(list, datatype) where datatype can be a double or an int and it returns the argument as that type. Do not use chars or floats, you need to be very careful with this function or you will get unexpected behavior. If you want character, and don't want to be promoting the value up to an int, you can use a type of char*. For additional information about the small nuances like this, search Google for how to solve your individual problem.
Once you are done with your list, you should call va_end(list) to make sure nothing else will access it.
void printPins(char port, char numPins, ...){ //Define a variable list of data called ap va_list ap; //Set how long the list is va_start(ap, numPins); //Will store our pin numbers in the loop int pin; for(int i = 0; i < numPins; i++){ //Grab the next value from the list and treat it as an int pin = va_arg(ap, int); //Print out what we received printf("Pin %c%d\n\r", port, pin); } //Stop all access to the list va_end(ap); } int main(){ printPins('A', 3, 0, 2, 7); printf("\n\r"); printPins('B', 2, 1, 4); return 0; }
Linking Multiple Files
Here is a short example of how to link multiple files together as mentioned earlier in the headers and source files sections.
main.c
/* * main.c * Author: ewillcox */ #include <stdio.h> #include <stdlib.h> #include "equations.h" #include "globals.h" Globals globals; int main(){ //Initialize our globals struct initGlobals(); //Set the time to 30 setTime(30); //Print out the area of a 30 x 20 rectangle and print the time printf("Area: %d\tTime: %d\n", calcArea(30, 20), globals.time); return 0; }
equations.h
/* * equations.h * Author: ewillcox */ #ifndef EQUATIONS_H_ #define EQUATIONS_H_ #include "globals.h" //Calculates the area of a rectangle int calcArea(int width, int base); //Sets the time to a given value void setTime(int time); #endif /* EQUATIONS_H_ */
equations.c
#include "equations.c" Globals globals; //Area = width * base int calcArea(int width, int base){ return (width * base); } //Assign the time from the global struct void setTime(int time){ globals.time = time; }
globals.h
/* * globals.h * Author: ewillcox */ #ifndef GLOBALS_H_ #define GLOBALS_H_ typedef struct { long long time; } Globals; extern Globals globals; //Initializes the default values for the globals struct void initGlobals(); #endif /* GLOBALS_H_ */
globals.c
/* * globals.c * Author: ewillcox */ #include "globals.h" Globals globals; void initGlobals() { //Initialize the time to 0 globals.time = 0; }
Different Directories
A good idea that was skipped in this example as it is a smaller project compared to the 3001 final project is to make separate folders for source and header files. If this is done, then a relative path to header files needs to be included from source files and header files to get to one another.
If source files were in
../final/src
and headers in
../final/headers
then to include a header file from a source your include would look like
#include "../headers/myHeader.h"
and including a header from a header would not need a path as they are together in the same folder.