Programming Background

From robotics


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.


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).


Example .c file
An example of a .c source file in Eclipse

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.

Printing output for scope showing how var is affected
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;
}

Output of the while fizzbuzz loop


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.

Results of pointer example
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.

Simple array results
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.

2-D array output
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.

Output of string example
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.

Output of a compare and copy operation with a bad string
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;
}

Structs

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.

Results of passing a struct and editing it
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.

Results of taking in a variable number of arguments
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;
}

Results of having multiple files

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.