Programming Tutorials

Page 1 of 2 1, 2  Next

Go down

Programming Tutorials

Post  Brendan van Ryn on Mon Feb 20, 2012 10:30 pm

I've been feeling bad about how little time the senior programmers have had for the new programmers in the past few days. Unfortunately, this is just how things tend to be: there is almost always a big rush at the end to make everything as good as it can be. At any rate, I promise to do more lessons and focus on you guys a lot more after Tuesday once the thing is shipped. I'm hoping to post some basic stuff here at least once everyday until we can get back on track with the lessons. That being said, here are some basic starting points.

For the robot:
The basic structure of code for the robot is as follows.
Code:

/* Simple example program.
  Written by Brendan van Ryn. */

// Header files
#include "WPILib.h"

// Macros (constants)
#define LEFTJOYSTICKPORT 1
#define RIGHTJOYSTICKPORT 2
#define LEFTMOTORPORT 1
#define RIGHTMOTORPORT 4


// Function prototypes
int GetEncoderSpeed(Encoder*);


// This is where we put (basically) all of our code
class SimpleTracker : public SimpleRobot
{
   /* Variables that are available to the whole "class". Our harware is commonly
   defined here. */
   Joystick *leftStick; // Left joystick
   Joystick *rightStick; // Right joystick
   Jaguar *leftMotor; // Left drive motor
   Jaguar *rightMotor; // Right drive motor

   public:
   SimpleTracker(void)
   {
      // Initialize hardware and other variables
      leftStick = new Joystick(LEFTJOYSTICKPORT); // Instantiate joysticks
      rightStick = new Joystick(RIGHTJOYSTICKPORT);
      
      leftMotor = new Jaguar(LEFTMOTORPORT); // Instantiate motors
      rightMotor = new Jaguar(RIGHTMOTORPORT);
   }
   
   // Teleop mode
   void OperatorControl(void)
   {
      // Variables for teleop only
      float lefty, righty; // Joystick y-positions

      // Repeat while in teleop
      while(IsOperatorControl())
      {
         // Tell the computer that the program hasn't frozen
         GetWatchdog.Feed();
         
         // Get y-axis positions from joysticks
         lefty = leftStick->GetRawAxis(2);
         righty = rightStick->GetRawAxis(2);
         
         // Run motors according to the positions
         leftMotor->Set(-lefty);
         rightMotor->Set(-righty);
      } // End of teleop loop
   } // End of teleop code

   // Autonomous mode
   void Autonomous(void)
   {
      // Autonomous variables only
      int time = 0; // Track how long the function has been running
      
      // Repeat while in autonomous mode
      while(IsAutonomous())
      {
         // Tell the computer that the code hasn't frozen
         GetWatchdog.Feed();
         
         // Drive forward until "time" reaches 500 counts
         if(time < 500)
         {
            leftMotor->Set(1.0f);
            rightMotor->Set(1);
            time++;
         }
         
         else
         {
            leftMotor->Set(0);
            rightMotor->Set(0.0f);
         }
      } // End of autonomous loop
   } // End of autonomous mode
};

// Tell the computer the name of our "class"--the thing with our code in it :p
START_ROBOT_CLASS(SimpleTracker);

I plan to post one example per day, if I can, so today we will look at the general layout of the robot code. Tomorrow, I plan to post information on variable declarations, loops, and if-statements (maybe some math too).

This is how the robot code appears. First, we include any libraries we need. For all intents and purposes, it is acceptable for me to say that the "#include" directive imports a set of pre-written code segments for us to use. These are things like "leftMotor->Set()". In this way, we don't have to tell the computer what "leftMotor->Set()" means, because that explanation can be found in the "WPILib.h" file. The "math.h" file contains various math functions, for example. These two should be the only two files that ever need to be included, but if you want to include another, the general form is:

"#include <NameOfFile.h>"

A shiny penny goes to the person who can tell me the difference between putting quotation marks and putting angled brackets Wink

Below that, we have our macro definitions. Think of these as constants. If I write

"#define THISMACRO 2"

Then any spot in the code that contains the word "THISMACRO" will be replaced with the value of two. You can name a macro pretty much anything and give it pretty much any value. Here are some examples:

#define PI 3.1415926535897932384626433832795028841971
#define ONEHALF (1.0 / 2.0)
#define MOTORSPEED 50
#define MYNAME "Brendan van Ryn"

There aren't really any rules for the value you assign. Whatever you put there, no matter what it is, that is the same thing that will replace the macro name in the code. If you still don't understand, ask me a question at our next meeting or look up "C preprocessor" in google.

So, you should all be roughly familiar with the layout of the robot code. It starts at the brace (curly bracket) following the line starting with "class", and ends at the corresponding brace. This is easy to distinguish because, if you've written your code correctly, each new curly brace should cause the code below it to indent one tab more than before, and unindent at the corresponding closing brace, like so:

Code:

if(something)
{
   // Indented
   someVariable = someValue;
   
   if(somethingElse)
   {
      // Indented again
      printf("This is fun");
      someOtherVariable = 0;
   }
   
   // Unindented one
   printf("Not confusing at all");
} // Unindented again

The first section in the robot code declares any variables we need. The variables declared here are available to any part of the code (both teleop and autonomous). Generally, we declare our objects here, like so:

ObjectType *objectName; // Comment explaining object
Jaguar *leftMotor; // Left drive motor
Joystick *leftStick; // Left joystick
// Encoder, Victor, DigitalInput, etc.
int pulseCount; // This is a "global" variable instead of an object because it has a datatype

Once they're declared, the next distinct section (note the added braces) assigns them. Again, as we're dealing with hardware objects generally, this section should instantiate those objects by selecting the port number, like so:

objectName = new ObjectType(portnumber); // Comment explaining object
leftMotor = new Jaguar(2); // Motor connected to PWM port 1
leftStick = new Joystick(LEFTJOYSTICKPORT); // Using the macro we defined, remember?
rightStick = new Joystick(2); // Joysticks and motors have different ports, so this is okay

Note how the LEFTJOYSTICKPORT macro will be replaced with 1 because we defined it as one. Additionally, each object must have its own unique port BUT different types of objects sometimes use different ports, so exactly where the object plugs in is important.

Then, we have our teleop code. The loop inside this section repeats over and over again so long as the robot is in teleop mode. Therefore, the code to be run in teleop is place between the braces for that loop (and indented, of course Wink). However, we do NOT want our variables to be reset over and over again, so they get declared before the loop. Declaring variables is like objects, except that the never have an instantiation line (no " = new ..."):

datatype variableName [= value] // Comment
int encoderCount; // Number of pulses from the encoder
float rollerSpeed = 0.0f; // Speed of shooting roller
double powerSetting; // Power setting for arm

The possible datatypes are int, float, and double. The first holds only integers, and the last two can hold numbers with fractional (decimal) parts. Generally, we use float. Double is more accurate, but the amount of accuracy usually isn't necessary. For most purposes, float and double act the same. I know it sounds better to just use float all the time, but for complicated reasons I might explain later in person (or you could research yourselves Wink), it's best to use an int when possible. Only use float when you NEED decimal points. Comments should be descriptive and assigning a value by adding and equals sign and a number at the end is optional. The "f" at the end of the decimal numbers is optional. If it's confusing or causes and error, just don't use it Wink

For those who don't know, a variable is an area of memory in which we can store a value--in this case, integer or fractional numbers. To assign a value to a variable, we use the equals sign:

variableName = value;
encoderCount = 10;
rollerSpeed = 51.2f;
powerSetting = PI * 10 - rollerSpeed; // Math operations are valid, and other variables too

As you can see, you can assign whatever value you'd like, and math operations are valid (+, -, *, /). You can also put other variable names in the equation and their value will be used to calculate the new value.

So, once our variables are declared, we put our code in the loop. All we're doing is reading the joystick values, and setting the motors to run according to them. The negative sign does exactly what you think: it multiplies the value of the variable by negative one. Now, the way the objects are used is important. Generally, you do the following:

[variableName =] objectName->Function([arguments]); // [] indicate optional parts
leftMotor->Set(0.5); // Set the left motor to half power
encoderValue = driveEncoder->Get(); // Store the value from an encoder in a variable
rightMotor->Set(encoderSpeed); // Set the motor speed to the value stored in a variable

For the most part, knowing what, if anything, goes in the parentheses is memory, as is knowing whether a value is returned or not. However, you can look at old code to find out. More interestingly, if you type an object name and the arrow and wait, a list of things will appear. For example, you might see in the motor list something that says: float Set(float). This means that it returns a float value (meaning you can store it using = and a variable if you want) and it requires a float value or variable in its parentheses to tell the speed. We'll learn more about this later. Also, note how sometimes I put Set(0.0f), and sometimes Set(0). Technically, since it is a float, I SHOULD put 0.0f, however 0 and 0.0 will both still work because the compiler will "cast" (convert) it into a float automatically, so don't get too worried about those details.

You'll also notice that all of the open braces have close braces matching them. This is also important, as they mark beginning and endings of sections, or "blocks", of code. The comments are also crucial for making sure that anyone reading the code understands.

For the most part, the autonomous example is simple. I will explain loops and if-statements in my next post, which will also cover any questions you guys ask me about this. Before I finish this post, I will add two more things. First, the last line is important (the "START_ROBOT_CLASS"), as it tells the computer the name of our "class" so it knows where to find our code. Additionally, I skipped over the "GetWatchdog.Feed()" lines. These are also necessary. They tell the computer on the robot that our code has not frozed. If we do not put this, the computer will assume that the code has frozen and it will disable the robot. Don't worry though: I plan to leave you guys will a template to start with for next year Wink

Also, despite all math operations, no value is actually stored without the equals sign. This is okay for something like leftMotor->Set(variableName * 2), because the "Set()" function uses the result of the multiplication. However, variableName will still have the same value. To STORE the result of multiplying by two, put variableName = variableName * 2. For negating, variableName = -variableName. The only exceptions are variableName++ and variableName-- which add and subtract one from variableName, respectively, and store it (as would variableName = variableName + 1).

Oh, Will Surmak asked me a good question today: how do you know where to put semi-colons? Generally, put a semicolon after every line EXCEPT:

Preprocessor lines (staring with "#define" or "#include")
Lines before an open brace (like the "class" line and the "while" and the "if)
Lines containing a brace in either direcition (the "{" and "}" don't have semicolons)
Comments (still go on the line if necessary, but BEFORE the comment)

If in doubt, just look at old code backups Wink I know this is a long post, but I'm trying hard to keep you guys in the loop. I'm hoping to start having meetings again where we can do lessons and test out code examples on the 2011 robot, so please keep reading this board and ask questions. I will post again tomorrow, with luck, to answer questions and explain loops/if-statements Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Variables

Post  Brendan van Ryn on Wed Feb 22, 2012 7:04 pm

Okay, round two. I'm going to summarize variable declarations and assignments and some math operations involving them .

Variables are simple enough to understand. You've all heard people talk about how much memory or "RAM" a computer has when comparing specs. This memory is used to store information for later use. For example, this year, once we've calculated the position of the target from the camera, we don't want to have to recalculate it each time that value is needed. Therefore, we can store it in memory. To do this, we declare a variable--a space in memory reserved for use by our program that is assignmed a particular name. This name allows us to refer to that memory location later.

Declaring a variable is simple: you put the datatype, desired name for the variable, and an optional initialization value. The datatype tells the compiler what type of values are stored there--for our purposes, this will either be int or float. Ints store integer values, meaning numbers like -1, 5, and 95632, but not 5.5 or 3.14. Floats, meanwhile, can store any number. Technically, both have a maximum range that can be held, but you will rarely if ever get anywhere near the limitations of these datatypes. Note that, in this year's code, we've used double before. This is basically the same thing as a float, but with a larger possible range. Meanwhile, the name can be basically anything, but it should technically follow the format of nounNoun or adjectiveNoun, with that capitalization system. There are a few rules (variables must start with an underscore or letter and can't contain certain characters), but if you stick to the system I outlined, it should all be fine. Finally, you can assign a starting or "initial" value to the variable, if you'd like. Technically, the purpose of each variable should also be commented on the same line briefly for documentation purposes. Here are some examples:
Code:

int encoderCount; // Store the number of pulses from an encoder
float motorSpeed = 0.0f; // Store the result of a calculation for the speed of a motor. This sets it to zero initially
double squareRoot; // Store the square root of some number later
int thisIsAValidVariableName = 12345; // Just an example to show it can be called anything you want
Some things to note: the name of the variable doesn't do anything it itself. The name just lets you refer to it. Calling a variable "encoderCount" but actually storing a motor speed in it is fine--it's illogical, and it makes your code hard to understand, but it's fine. Also, there is a convention for numeric constants, which are numbers you type directly into the code: they are assumed to be integers. Adding a decimal point and a zero indicates that it is a double, and adding the "f" at the end makes it a float. I'm explaining this because it is used in a lot of places in this year's code, but in the end, 0 == 0.0f. Fially, for floating-point values (float or double), you can use "e" for scientific notation, like: float thisExample = 1.52e-4. This sets the variable "thisExample" to the value 1.52x10-4.

Once a variable is declared, values can be stored in it from basically anywhere. To assign a value to a variable, like in the declaration, you use a single equals sign. In any expression or equation in C, the variable on the left is where the result is stored, and the right side of the expression is evaluated. Let me show you what I mean:
Code:

int a, b, c; // Putting a comma between them declares multiple variables of the same type
float d = 0.0f, e; // The initializations can be there too. "d" is set to zero, but "e" is not given a value.

a = 10; // "a" now contains the value 10
b = 5 + a; // "b" now contains the value 15 (5 + 10)
b = b * a; // "b" now contains the value 150 (15 * 10)
c = (a + (b + a / b)) * 0.1; // See below
45 + 7 = a; // This won't work. It must be "a = 45 + 7" instead
a += 5; // Adds five to "a"
a + 5; // There is no equals sign, so this does nothing, BUT it does not create an error (maybe a warning though)
b *= 2; // Multiply "b" by two
c -= b / 4; // Divide "b" by four and subtract the result from "c"
a++; // Add one to "a"
b--; // Subtract one from "b"

e = sqrt(a); // See below
As you can see, variables are treated like any other number in a math expression. C evaluates expressions using all of the basic operations: addition (+), subtraction (-), multiplication (*), and division (/). There are, however, a few points of interest. For one thing, the variable MUST be on the LEFT side of the equals sign--or at least, the one you intend to store the result in. For another thing, you might be confused by the line where "c" seems to be having a fractional value stored in it, even though it is declared as an int. The result of the expression there comes out to 16.00666667. In this case, the compile would convert the variable into a int automatically. To do this, the decimal places are truncated (removed). This means that something like 10.1, 10.000005, 10.8965, and 10.99999 would all be converted to just 10 as an int. There are ways to convert it using proper rounding, but we'll worry about them later. Finally, the line where "e" is being assigned a value shows another way a variable can be set: when you see something with round brackets after it's name, it's called a function. I'll talk about functions more later, but basically, they perform a pre-written set of instructions that accomplish some task. In this case, sqrt() returns the square root of the number you pass to it--we passed it the value stored in "a", which is 16. Therefore, sqrt() would calculated the root as being 4. Once functions complete their operations, some will return a result to the code line that calls it. This value can be ignored, or used in and if-statement or loop (we'll talk about them later), or stored in a variable for later. That is what we're doing here. Essentially, the function is ALSO treated like a number, because it becomes a number after it "returns" (finishes executing). In this way, the line could've easily read: "e = sqrt(a + 9) - b", as the sqrt() call is evaluated and essentially becomes the result, which would be 5 (square root of 25).

So, two more quick things. First, variables are ONLY accessible within the largest set of braces (curly brackets) in which they are declared. Basically, between the nearest opening { ABOVE where the variable is declared and the matching closing }, the variable can be accessed. Let me show you an example:
Code:


int x; // Not in any braces. Accessible everywhere in the same file.

// x is accessible here only

class SimpleTracker : public SimpleRobot
{
   int a;
   
   // x and a are accessible anywhere in the class
   
   public:
   SimpleTracker(void)
   {
      int b;
      
      // x, a, and b are all accessible here
   }
   
   void Teleop(void)
   {
      int c;
      
      // x, a, and c are all accessible here, but not b.
      
      while(IsOperatorControl())
      {
         int d;
         
         // x, a, c, and d are accessible. Still not b.
         
         if(something)
         {
            int e;
            
            // x, a, c, d, and e are accessible
         }
         
         // e no longer exists and is not accessible
      }
      
      // Only x, a, and c again. Still not b, nor e or d any more.
   }
   
   // c does not exist anymore
   
   void Autonomous(void)
   {
      int f;
      
      // x, a, and f exist here
      
      while(IsAutonomous())
      {
         int g;
         
         // x, a, f, and g
         
         if(something)
         {
            int h;
            
            // x, a, f, g, and h
            
            if(somethingElse)
            {
               print(someValue);
               
               // i does not exist yet so it is NOT accessible
               
               int i;
               
               // Now i can be accessed, as well as x, a, f, g, and h.
            }
            
            // i does not exist anymore
         }
         
         // h does not exist anymore
      }
      
      // g does not exist anymore
   }
   
   // f does not exist anymore
}

// only x is left now.

The comments list which variables are available at each level of nesting. Each time you have a "block" of code, or code between two braces, it is a new layer. For each layer, you can see that it has access to the variables in the layers above it (accentuated by the indentation) and within it, but not in areas in layers below it. Additionally, variables can only be used in lines that come AFTER the declaration lines, as also shown by the "somethingElse" if-statement's comments. For this reason, and for being able to easily find all of your variables in one spot, they should be declared RIGHT AFTER the opening brace above them, as has been done consistently in the code this year. Finally, you may wonder how to force a variable to convert from one type to another--for example, from int to float. All you need to do is put the part you want to convert in brackets, and place the type you want in front of it, like so:
float a, b, d = 12.9f;
int c = 5;

b = (float)(c / 2); // Performs 5 / 2 as an integer to get an answer of 2, THEN converts to 2.0f to store in b
a = (float)(c) / 2; // Converts 5 into 5.0f FIRST, then does the float division to get 2.5f to store in a
c = (int)(d); // Again, converting to an int removes the decimals without rounding, so c will now be equal to 12, not 13

Sometimes the compiler will handle the conversion automatically if you forget this method, called "type-casting", but it's a good habit and there are cases where neglecting to do so will cause errors or improper operation. Also, as you can see, the order in which you "type-cast" is important as the math is done according to the type of result the compiler thinks you want--hence, 5 / 2 can be 2 or 2.5 depending on what the compiler thinks you want.

I know this seems like a lot, but you've seen variables used all throughout this year. Generally, they are quite simple to use and the details are rarely, if ever, important. A variable assigns a name to a position in memory that allows us to store and retrieve values from that memory slot for use later. That's all they are. I've also provided you with a basic understanding of how expressions work in C: they are very similar to those you would see in any math class, but with restrictions on what is allowed on the left side. As always, there is a board for questions, and we will be having an at-school meeting in the very near future, so let me know if anything in this or the previous tutorial is unclear--or even if you have an unrelated question. I'll be posting another one again tomorrow Wink

Also, just quickly, here is a list of the order in which math operations are performed. In general, it follows the rules of BEDMAS: Parentheses (including functions), negation (like "-x"), division and multiplication, addition and subtraction. Other than this, operations are performed from left to right, just like in math. Other operations exist, but they generally fall in the same category as functions and aren't immediately relevant. Also, there is NOT an operation for exponentiation. There IS a function however, "pow(base, exponent)". It returns the value of "base" raised to the "exponent". This and the sqrt() function are accessed by including the math header file, "#include <math.h>".

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Re: Programming Tutorials

Post  Seikun on Thu Feb 23, 2012 9:27 am

Could you explain the prototype function getEncoderSpeed, the one you called near the very beginning after the header files and macros?

Seikun

Posts : 7
Join date : 2011-10-05
Age : 23

Back to top Go down

Re: Programming Tutorials

Post  Brendan van Ryn on Thu Feb 23, 2012 5:53 pm

I plan to do an entire, separate tutorial on comments and the preprocessor, the thing that handles #include and #define (and other) instructions. Function prototypes will also be explained in the tutorial I plan to do about functions (this is also technically the wrong board for posting questions). See, the #define directive is not the only thing the preprocessor can do. Here is a brief list of other commands, which I will explain in more detail later:
#include
#define
#if
#elif
#else
#ifdef
#ifndef
#endif
#pragma
#line
#file
#time
#assert
#undef

Again, I'll explain them more later. On top of that, the #define directive, in itself, is very flexible. I will talk about some of the more complicated macros you've seen (particularly the DISPLAYINTEGER and other macros) later, but for basic macros, it's just this format:

#define MACRONAME [value]

A value is optional as it is possible to build if-statements off of a macro that has no value (which we will see later). However, generally, MACRONAME can be any combination of characters. To make them easy to recognize, we generally capitalize macro names. The value assigned to the macro can be anything at all. Here are some examples:

#define MESSAGE "Hello world"
#define PI 3.14
#define IFSTATEMENT if(x < 4)

float x;
printf(MESSAGE); // Turns into: printf("Hello world");
x = PI * 2; // Turns into: x = 3.14 * 2;
IFSTATEMENT printf("True"); // Turns into: if(x < 4) printf("True");

As you can see, macros can be defined as entire lines (or multiple lines, as we'll see later) of code, or just a simple number. All that the preprocessor does with macros is search through the code and replace any macro names it find with the value you indicated in your #define statement. This is equivalent to creating a constant. As for function prototypes, we'll talk about them more in the functions tutorial that's coming up later, but the general idea is that the compiler has to know the parameters for a function and what it returns before it is used. For example:
Code:
int main(void)
{
   printf("%d", Abs(-5.1));
   printf("%d", Abs(+5.1));
   
   return 0;
}

float Abs(float x)
{
   if(x < 0) x = -x;
   return x;
}

This code is supposed to print the absolute (positive) value of 5.1 and -5.1, but it would not compile. As it reads through the code, the compiler sees the call to Abs(), but it doesn't know what type of variable is returned (float, int, double, etc). This makes it impossible for the compiler to know where to find the return value, due to certain conventions present in the computer hardware itself--conventions I won't bore you with. A similar problem is encountered when parsing the arguments. Therefore, a function in C cannot be used until AFTER it has been declared first--much like a variable. There are two solutions to this problem: put all of your function declarations ABOVE your main function (which would make things harder to read), or use a prototype. We do the latter because it is a much better option. The function prototype is almost the same as the line that defines the function, except for two things: you put a semi-colon right after, omitting the function code; and you don't need to provide the variable names. The new code would be:
Code:

float Abs(float x);

int main(void)
{
   printf("%d", Abs(-5.1));
   printf("%d", Abs(+5.1));
   
   return 0;
}

float Abs(float x)
{
   if(x < 0) x = -x;
   return x;
}
As you can see, the first line at the top is the only difference. That line simply lets the compiler know what type of variable is returned, and the type and number of arguments the function receives. Like I said, variable names aren't necessary in prototypes, though they do provide some self-documentation. Therefore, that top line can be written:

float Abs(float);

I hope that clears things up Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Objects

Post  Brendan van Ryn on Thu Feb 23, 2012 8:53 pm

For this tutorial, we're going to look at "Objects". Technically speaking, an object is a construct that combines both data structures and the functions that operate on them into a single, discrete entity. This is the core idea behind Object-Oriented programming. However, because it is much more complicated than procedural programming, because we have no need for it, and because I don't agree with the paradigm, we will not be focussing much on the Object-Oriented constructs we DO use. Our code is primarily procedural, and will remain that way, but if you really are interested, the computer science courses offered at our school are now being done in Java, a language that focuses around (but is not purely based on) Object-Oriented programming.

For the purposes of our robot code, we can think of an object as a special kind of variable. With just a few rare exceptions that we have seldom, if ever, used, object are "variables" that link our code to the robot's hardware. Objects are things like motors (Jaguar or Victor), encoders, light sensors, gyros, joysticks, cameras, and more. The camera is a special case when compared to the others, so I will leave it until a short section at the end, but otherwise, all of these pieces of harware are handled in the same way.

To declare an object, you perform a sequence very similar to declaring a variable, like so:

ObjectType *objectName; // Comment
Jaguar *leftMotor; // Left drive motor
Encoder *armEncoder; // Encoder for moving arm
Joystick *leftStick; // Left driver joystick

As always, the name should follow the nounNoun or adjectiveNoun format. Generally, the type of object is also found in the name (leftStick, leftMotor, etc). ObjectType, meawhile, can be a variety of things. I will provide a reference later on of a large number of the hardware we use and their types (and their associated member functions), but for now, here is a brief list:

Jaguar - Motor controller
Victor - Motor controller (different brand)
Joystick - USB driver Joystick
Gyro - Gyroscope
DigitalInput - Lightsensors, limit switches, other digital devices
AnalogChannel - Various inputs, like multi-value switches and ultrasonic sensors
Encoder - Encoder (the thing that counts pulses)
Servo - Servo (combination rheostat and motor, I believe)
Relay - A mechanically activated switch, motorized

As you can see, there is quite a wide range of things available, and that is only a short list of the things the library FIRST provides us with supports. As always, comments should be present after the declaration and AFTER the semi-colon. These declarations differ in three simple ways from the way variables are declared: the "datatype" is now one of the object types available in the library, depending on the type of hardware you need to program; there is now an asterisk (*) before the variable name; and you cannot give the object a starting value like you could with variables. Additionally, all of the objects should be declared together at the top of the code, right under the "class" line. Because of the way objects work, which is something I won't get into, it's best that you put them at the top of the "class" so that they can be used in both Teleop and Autonomous. For now, we'll ignore the asterisk. It declares the variable as a pointer instead of a normal object. Pointers are complicated, however, and it makes no difference at this point. Perhaps we'll discuss them later, but for now, just put the asterisk.

So, how do we initialize an object then, if we can't do it in the declaration? Well, we almost never--at least, for our purposes--use an object directly in an equation. Where we could use the = sign to set variables to a value and use other variables in expressions as numbers, we generally don't do that with objects. With objects, we initialize them once, and after that, we're only interested in their member functions. First, we'll look at initializing them.

Once an object is declared, it has to be initialized ("instantiated" is the more correct term). This generally means to create an instance of an objected belonging to the class we want, initializing it with it's default property values and states. To us, all this means is telling the computer which port the desired piece of hardware is connected to--the rest happens behind closed doors, and isn't relevant. So, under the "void SimpleTracker(void)" section of our code, we essentially assign all of the objects to their given ports, like so:
Code:
public SimpleTracker : public SimpleRobot
{
   Jaguar *leftMotor; // Left drive motor
   Joystick *leftStick; // Left joystick
   Encoder *steeringEncoder; // Encoder for rotating the wheels
   
   public:
   void SimlpeTracker(void)
   {
      leftMotor = new Jaguar(3); // Left drive motor is on PWM port 5
      leftStick = new Joystick(1); // Joystick port 1
      steeringEncoder = new Encoder(2, 3); // Digital input ports 2 and 3
   }
   
   // Teleop and autonomous ...
}
For simplicity, I left out the teleop and autonomous code sections. As you can see, the assignments are pretty straight-forward. Generally, the format is:

objectName = new ObjectType(PORTNUMBER); // Comment

Comments, again, are important. The object type is always the same as in the object's declaration above. The port number is then an integer indicating which port(s) the hardware is connected to. Generally, most objects use only one port. However, as you can see with the encoder, some require more. This particular encoder uses two in order to be able to determine which direction the given object is spinning. Knowing how many ports are required is simply memory, but it's safe to assume one, and then two for encoders. Additionally, you can always refer to last year's code for the exact syntax. Also, you may notice that the leftMotor and steeringEncoder objects both seem to be using port 3. This is not true. Jaguars connect to PWM ports, whereas encoders connect to digital input ports (and still further, joysticks connect to USB ports on the Driver Station). Therefore, it is important to know both the port number AND the type of port (although, the type of port is only really important for knowing which ports are occupied when new hardware is added, and for knowing where it plugs in). Generally, to make it easier to change and to ensure that all of the port numbers are in the same place, a macro is defined instead of an actual value:

#define LEFTMOTORPORT 3 // Generally, "#define OBJECTNAMEPORT number"

/* ... */

leftMotor = new Jaguar(LEFTMOTORPORT); // Macro used instead of number

This method makes things much easier to read and change later, as things will almost always get moved around on the robot. Finally, it is a good idea to group similar objects together when initializing them, like so:

// Motors
leftMotor = new Jaguar(LEFTMOTORPORT);
rightMotor = new Jaguar(RIGHTMOTORPORT);

// Joysticks
leftStick = new Joystick(LEFTJOYSTICKPORT);
rightStick = new Joystick(RIGHTJOYSTICKPORT);

// Etc.

Putting the blank lines in between make things easier to read, and makes it easier to find a specific object. On top of that, grouping objects that connect to the same type of port together can also be beneficial.

Once we have our objects declared and initialized, using them is equally simple. Each object has a series of things associated with it that are of some use to us. For our purposes, we only focus on two of these things: properties, and functions. The second is more important, so we'll look at it first.

You already know what functions are from the Variables lesson, when I used sqrt()--it's a pre-written piece of code that performs some operation and, sometimes, returns a result. Harware objects we declare all have a set of functions associated with them that allow us to interface with them. To access these functions, simple type the object's name and an arrow, made out of a hyphen and a greater-than sign. When you do so, a list of possible functions and properties will be displayed. For the functions, the name, type of variable returned, and arguments received will be listed for you (void means no return value, or no argument, depending on where it is placed). These terms will make more sense after we look at functions, but here are some examples of things you can do with an object function:

leftMotor->Set(number); // Sets the motor to turn at the speed passed in "number"
leftMotor->Set(1.0); // Turn the motor at full speed forward
leftMotor->Set(-0.5); // Turn the motor at half speed backwards
leftMotor->Set(0); // Stop the motor

x = steeringEncoder->Get(); // Store the current number of pulses from the encoder in "x", an int
steeringEncoder->Reset(); // Reset the pulse count to zero
steeringEncoder->Start(); // Start counting pulses again. Used in conjunction with Reset

leftJoystickY = leftStick->GetRawAxis(2); // Store the y-position of the left joystick (-1 to +1)

As you can see, some functions require values to be put in the brackets, and some don't. Some return a value that can be stored in a variable, and some don't. Of course, if a function DOES return a value, you don't HAVE to store it. At any rate, the exact name and purpose of each function varies from object to object. Generally speaking, however, most have a ->Set() command for enabling a piece of hardware, and most have a ->Get() command to get some sort of reading from it. Again, you can use the popup list to look for the function, or look at old code examples, or look on ChiefDelphi or in the PDFs under "Robotics 2012\Other".

Finally, you can also, sometimes, access "properties" of objects. These are, essentially, variables belonging to the object. For the sake of our discussion, you should never try to assign a value to one of these variables, but sometimes you will use the in an expression. For example, to enable a relay, you use the ->Set() function, but it's argument is not a simple number: it's an enumerated value. The ->Set() function uses kForward, kReverse, kOff, and kOn as arguments. These variables are found under the relay object's arrow:

kickerRelay->Set(kickerRelay->kForward); // Engage a kicking mechanism
magazineBelt->Set(magazineBelt->kOff); // Turn on the magazine rollers

This is one of very few examples in which a property of an object is used. Generally, they aren't needed except for very highly specialized operations, which we avoid for the most part. Nonetheless, in the case of the relay (and the DriverStationLCD, which will be talked about when I do the preprocessor and commenting tutorial), it is necessary to use the ->kForward, ->kOff, ->kBackward, and ->kOn properties. It is NOT acceptable to put:

kickerRelay->Set(1.0);

This will either generate a syntax error on the console, or will simply not work when tested.

That concludes what I have to say about objects. In summary, you declare them like variables, but with a different "datatype", an asterisk, and no initialization value. Then, you "instantiate" them with port numbers under the "void SimpleTracker(void)" section. After that, you use the arrow (->) to access it's functions and properties, which allow you to perform important tasks and, essentially, communicate with the robot's harware. As always, feel free to ask questions, and I hope to see you all after school tomorrow so we can put some of these tutorials to the test on the old 2011 robot Wink

One quick note about the camera, in case anyone is wondering: the camera uses what's called a "reference" instead of a point. It's not something I like, because I disagree with Object-Oriented programming and this idea especially, but it's the way things are. This means that the camera is declared and used like this instead:

AxisCamera &camera = AxisCamera::GetInstance(); // Initialize camera "variable"
binaryImage = camera.GetImage(); // Get the camera image

The main difference is in how it's declared--which, incidentally, means that it can't follow the rule of being declared at the top of the "class". Instead, it's declared and initialized in each of the three sections of the class. Additionally, the functions are accessed via the period instead of the arrow. It's not a huge difference, but it is different. As far as I'm aware, this is the only object that is handled this way except for the display on the Driver Station--but more on that later Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Conditional Logic and If-statements

Post  Brendan van Ryn on Sat Feb 25, 2012 1:27 am

Well, despite the lacking outcome today at the programming meeting I scheduled, I will continue these tutorials. Today, we will look at conditional logic--specifically, if-statements.

If-statements and while loops run on conditions. Conditions are like math expressions, but instead of assigning a variable a value, conditions evaluate to either a true of false value that, generally, aren't stored anywhere. Instead, they are generally placed as part of an if-statement or loop condition. In C, true and false have discrete values--actual numeric values associated with them, rather than being their own, abstract types. When it comes to condition expressions, they either evaluate to zero (which is considered "false") or non-zero, usually one (which is considered "true"). Note the importance of that statement: any value OTHER than zero is considered to be a true result. This means that 1, 2, 5, -6.7, and 2.34e45 are all considered to be TRUE. Zero (0, 0.0, 0.0f, -0.0) are all considered false.

Conditions consist of either a left side and right side, separated by a conditional operator, or just a left side. In the first case, each side of the expression is evaluted to obtain a signle value, and these results are compared. In the second case, the expression is simply evaluated, and the result is either false (equal to zero) or true (anythng else). The conditional operators I mentioned are fairly logical, and can be any of the following below:

== - Is the left equal to the right?
!= - Is the left not equal to the right?
>= - Is the left greater than or equal to the right?
<= - Is the left less than or equal to the right?
> - Is the left greater than the right?
< - If the left less than the right?
! - "Not". Turns true into false and false into true.

There are a few important notes about the above. First, it is important to realize that checking for equality as a condition uses TWO equals signs--using only one will not do what you expect. It will assign the variable on the left the value of the expression on the right, and if that value is non-zero, the condition is considered to be true. Therefore, remember to use two. Next, note difference between the > and >= signs (as well as the <= and < signs). It is a common mistake to think that if something is not less than, it must be greater than. This is not true. It is also possible to be equal to. Finally, the last operator--the exclamation point--might seem weird, but hopefully it will make more sense when we see some examples. Essentially, when you put it at the front of an expression, it reverses the value--it make an expression that is true, false, and an expression that is false, true.

To illustrate, here are some examples of expressions.

5 > 6 // Is five greater than six? This is not a useful expression, because the answer is always false. This would equal zero (false).
x < 89.5 // Is the variable or macro, x, less than 89.5? The result of this condition would depend on what is stored in x.
variableOne == variableTwo * 3 // Is the first variable equal to three times the second variable?
(x - a) / 2 >= 45 * sqrt(y) // Is half of x minus a greater than or equal to forty-five times the sqare root of y?
!(a - b > 7) // Is a minus b NOT greater than seven? Notice how the ! reverses the logic.
variable // And example of only one side to an expression. Is false if the value of "variable" is zero, and true otherwise.
!variable // Is TRUE is variable is zero, and false otherwise. See again how the ! reverses the result.

As you can see, conditions can basically be any math expression, or two math expressions separated by a conditional operator. Generally, the rules for this follow the same logic you would use when writing out something in math. However, except for some rare cases (which I'll talk about at the end), we generally have no use for storing the result of a condition expression in a variable. Instead, we use the result directly in an "if-statement" or "loop".

If-statements allow programs to run different sets of instructions depending on certain conditions. This is very important, or else a different program would need to be made each time you wanted to do something different. As it is, an if-statement simple evaluates a condition expression, just like the examples above, and if the condition evaluates to true, if performs the code in the block after it. If not, the code is skipped over. Here is a general formula, followed by an example:

// Comment
if(condition)
{
perform some action; // Comment
perform some action; // Comment
// ...
perform some action; // Comment
}

// ...

// Example
if(x > 9)
{
printf("X is relatively large");
}

printf("End of if-statement");

In the above example, x would be set to some value elsewhere in the program--perhaps it contains the reading from an encoder. At this point in the code, x would be compared to a value--in this case, eight. If x is greater than eight, the condition evaluates to true, and the code between the braces is executed; the message would be displayed, and the code would keep running, also displaying the second message. If x is NOT greater than eight (meaning that it is less than OR equal to), only the second message would be displayed--the computer would skip over the code in the braces. Of course, your if-statements can contain any given expression, and they can get quite complicated, but they are generally quite logical. For example, if you wanted to see if one variable was less than half the other variable, you would write exactly that:

if(variableOne < variableTwo /2)
{
// Code to run...
}

// OR

if(variableOne * 2 < variableTwo)
{
// Code to run...
}

As you can see, there is generally more than one way to write a given condition, so there is plenty of flexibility. As with the math expressions explained in the Variables tutorial, macros, variables, constants, function calls, and math operators are all legal in a condition, and you can still put parentheses around parts to change the order of operation. It is also important to use comments to explain the purpose of a given if-statement so others, including yourself, can understand what is being checked--in which case, it is important not to simply rewrite the condition in words. Explain exaclty what is being checked. To illustrate, here are some commented examples from this year's code to show you the kinds of conditions we look at (in this case, on the "if" line is included. The block of code following the if is omitted to save space, but the braces and code lines ARE actually there):

if(speedy > 0.0f) // Used to prevent positive values for speedy, set from a joystick position in other code
if(magazineAlternator > MAGAZINEALTERNATORLIMIT) // Used to check when a counter has reached an important value
if(SWERVEMODEBUTTON) // Checks a macro that returns whether or not a particular joystick button is pressed (0 for false or released, 1 for true or pressed
if(Abs(lefty) < JOYSTICKDEADBANDY) // Ignores small positive and negative values for the y-position of the left joystick
if(!armDownSwitch->Get()) // Performs a certain action if a switch is not being pressed
if(width * height > maxArea) // Checks if the new area (width * height) is the largest so far

Now, this is all quite useful, but it is often the case where you may want to perform one action under one condition, and another action under the opposite condition. Obviously, you could do something like the following:

if(something)
{
// Do one action
}

if(!something)
{
// Do another action
}

// OR

if(x > y)
{
// Do one action
}

if(x <= y)
{
// Do another action
}

Note, again, that the opposite of x > y is x <= y. While this method works, it is very inefficient. In the examples above, if the first condition is true, it is impossible for the second to be true, and vice-versa. Therefore, it is redundant to check both times. To resolve this, there is the "else" keyword. It works like an if-statement, but without a condition and it MUST be preceeded by an if-statement. The code in the block following the else if only executed if the condition in the if-statement above is FALSE; otherwise, the program skips ahead past the closing brace, like so:

if(something)
{
// Code to perform if it is true
}

else
{
// Code to perform if it is false
}

// Code to perform regardless

Note in both the case of the "if" and the "else" that there is no semicolon at the end of the line, which follows the rule we set a few days ago involving braces. If the condition above is true, the code in the first "if" block is executed, and the code in the second "else" block is skipped. If the condition is false, the first "if" block is skipped and the second "else" block is executed. The code after the "else" block is executed either way. In fact, this "else" structure can be piled on top of other if-statements, allowing more than two conditions to be put together, like so:

if(x == 7)
{
// Do something
}

else if(x == 9)
{
// Do something else
}

else if(x < 10)
{
// Do something else
}

else if(y != MACRO)
{
// Do something else still
}

else
{
// Do some fallback thing
}

The number of "else if" statements you have can be anything, including zero. Also, the final "else" is completely optional. The way this works is that the computer runs the block of code following the FIRST true condition, and then skips all the remaining ones. If none are true, it runs the "else" block, if it exists. Otherwise, it just skips past the if-statements. The by-product of this "first only" rule means that, in the following example, the third block could never possibly be executed:

if(x > 6)
{
// Do something
}

else if(x <= 6)
{
// Do something else
}

else if(x == 7)
{
// This will never be run
}

In this example, no matter what x is set to, one of the first two if-statements will ALWAYS be true. Therefore, the third block of code will never be run as it will always be skipped over. If you do really need to check for both, you can turn them into two independent if-statements, like so:

if(x > 6)
{
// Do something
}

else
{
// Do something else
}

if(x == 7)
{
// Do yet another thing
}

Now, it is possible for the third block to be executed, along with either of the first two. However, technically speaking, it is impossible for x to equal seven unless it is also greater than six. Therefore, we could optimize this, like so:

if(x > 6)
{
// Do something
if(x == 7)
{
// Do another thing
}
}

else
{
// Do something else
}

I also replaced the "else-if" with an else, because it is unneccesary to check that particular condition when you think about it. Note that, when you have an if-statement INSIDE (rather than below) another if-statement, it is called "nesting"--the increased indentation is important for keeping track of whether or not a specific line belongs to a certain block or not, so please remember to indent an extra tab over with each new { and unindent with each closing }. If-statements can be nested as many times as you like.

This brings up another point. Consider the example below:

if(x > 6)
{
if(y > 7)
{
// Do something
}
}

In this case, we are checking TWO conditions before executing something. In this case, we can actually combine the two conditions into one using the logic operators. These combine to expressions together. The two available are the double ampersand (&&, meaning AND) and the double vertical bar (||, meaning OR). The AND operator means that the if-statement will only be considered true if ALL of the conditions provided evaluate to true. The OR operator evaluates to true if ANY of the conditions evaluate to true. These can be used with each other to make complicated expressions, and work logically how you would expect them to. Here are some examples, without the code blocks:

if(x > 6 && y > 7) // If x > 6 is true AND y > 7 is true (both)
if(x > 6 || y > 7) // If x > 6 is true OR y > 7 is true (either one or both)
if(!(x > 6 && y > 7)) // As long as x > 6 and y > 7 aren't BOTH true (only one, or neither)
if(x > 6 || (y > 7 && z < 5)) // If x > 6 is true, or both x > 6 AND z < 5 is true (the first, or both of the second, or all three)
if(x > 6 || x == 0 || x < -5) // If x > 6 is true OR x == 0 is true OR x < -5 is true (any of the three)
if(x > 6 && y == 0 && z < -5) // If x > 6 is true AND y == 0 is true AND z < -5 is true (all of the three)

As you can see, they are pretty straight-forward, and it is possible to use parentheses to make things neat and tidy, and make sure that they are evaluated in the right order. This is important because "(x > 6 && z < 4) || y == 0" is NOT the same as "x > 6 && (z < 4 || y == 0". Try different values of true and false for the three conditions in each expression and see for yourself. You should note two important things. First, as soon is it is impossible for the expression to change, the computer stops evaluating. For example, given:

if(x > 0 || armUpSwitch->Get())
{
// Do something
}

If x > 0 is found to be true, the entire expression is automatically true regardless of the value returned by checking the switch (armUpSwitch->Get()). Therefore, the computer skips the second condition entirely. This is only a problem when the second condition contains a function that performs some operation, as that operation might not necessarily be performed, but that will not commonly be an issue for our purposes. Finally, you must also realize that the above example that we converted to "if(x > 6 && y > 7)" would not work if we had something like the following:

if(x > 6)
{
if(x > 7)
{
// Do something
}

// Do something else
}

Since we are performing one operation based on x > 6 and another on BOTH x > 6 AND x > 7, it is not possible to condense this using the logic operators, as neither would be performed unless BOTH conditions are true, which, for the second statement, is not the desired outcome. Again, it is crucial to take care and time and thought when desiging and reasoning through your if-statements, as it is all too easy to make a mistake in your logic that causes your program to behave in ways you did not intend.

Now for two quick bonus lessons! First, while I said that we don't generally store the result of a condition in a variable, it can be done. While it looks strange, it's actually not that complicated. Take the condition used in the code for running the rollers on this year's robot:

return = (Abs(topSpeed - targetSpeed) < SHOOTERDEADBAND && Abs(bottomSpeed - targetSpeed) < SHOOTERDEADBAND);

If the condition, in which both speeds are less than SHOOTERDEADBAND away in either direction (plus or minus) from the target speed, is true, then "return" would contain a non-zero value, generally one. Otherwise, return would be set to zero ("false"). This makes it easy to use the "return" variable later in another if-statement, if you so please, by putting "if(return)". If the condition that was saved to the variable was true, if(return) will also evaluate to true, logically speaking. Note that this does not actually store a condition though--using "return" in an if-statement does not re-evaluate the condition each time. "Return" is just given a value like any other number. I know the line above is not exactly what it shows in the robot code (we haven't done functions yet, that's why), and you can't actually have a variable called "return", because like "int" and "if", they are reserved words with special meanings. Nonetheless, it still illustrates the point. As one final tip, if you get annoyed by all of the curly brackets, you can have some solace: if you only have one line of code in the curly brackets, you can omit them. For example, you can write:

// This is valid
if(something) DoSomething;

// So is this
if(somethingElse)
DoSomethingElse;

// Or if you really want
if(somethingMore)
{
DoSomethingMore;
}

// This will NOT work though, because there is more than one line. You must use the braces in this case
if(somethingWrong)
DoSomethingWrong; // This line is coupled with the if-statement
DoSomethingElseWrong; // This line is not, and creates an error when you run the program, but not a syntax error

Of course, you're allowed to put the braces if you really want to. Additionally, you can put parentheses in any expression, whether they're technically necessary or not. In fact, it often makes things neater. You will also usually use them when storing the results of conditions in variables:

variableName = thisVariable == thatVariable; // Looks strange, but works. Stores true or false in variableName.
variableName = (thisVariable == thatVariable); // Also works, but looks better. It's easier to understand.

This tutorial was quite long, but bear with me. If-statements are important, because algorithms run on them, and you need to understand them properly or your logic will be flawed. Please make sure to review things you don't understand and ask questions if you need clarification Wink Also, as an ammendum to my Variables tutorial, all variable, object, and function names are CASE-SENSITIVE. Capitals matter in C Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Loops

Post  Brendan van Ryn on Sat Feb 25, 2012 12:36 pm

Today's tutorial will be about loops, which are very similar to if-statements, so this shouldn't be too long. A loop structure is a construct in programming that allows lines of code to be repeated multiple times. Sometimes, it is hard for beginners to realize the importance of loops, but consider this: without loops allowing code to repeat over and over again, programs like Microsoft Word and games like Flight Simulator couldn't exist. These programs all rely on waiting for the user to do something, and responding to the input it is given. Therefore, it must loop through a set of instructions that perform the tasks of checking the keyboard, and blinking the cursor, and rendering images on the screen, etc.

For the most part, loops are very similar to if-statements: if the condition inside the parentheses is true, the block of code is run; otherwise, the computer skips past the block and starts executing lines below it. There are a few important differences though. Firstly, loops cannot use an "else" block in the event that the condition is false. Secondly, the block of code below the loop condition is repeated over and over again so long as the condition remains true. Thirdly, the condition follows the word "while" instead of the word "if". Here is an example:

// Comment
while(condition)
{
// Do something
}

As always, commenting the purpose of the loop is an excellent idea to let anyone reading the code understand why the loop is there. In the example above, the computer would evaluate the condition, and if it is true, it'll run the code between the blocks. After this code is executed, the computer jumps back up to the "while" line and checks the condition again. If it is still true, the block of code is run again. This process repeats until the condition evaluates to false, in which case the computer skips ahead past the block of code. It is important to note that, if the condition is false to begin with, the code block is never run at all. Here is a real-world example of a loop:

// Run the motor while the limit switch is hit
while(armUpSwitch->Get())
{
armMotor->Set(0.5f);
DISPLAYSTRING(1, "Motor Running);
}

armMotor->Set(0.0f);
DISPLAYSTRING(1, "Motor Stopped");

This code would check if the switch has been hit, and would run the motor so long as it is still pressed. Once the switch is released, the loops exits, and the computer skips to the "armMotor->Set(0.0f);" line. Again, if the switch is not pressed at the point where the computer reaches the "while" for the first time, the motor would never run at all. The particular condition can be any valid condition that could be used in an if-statement. All this allows us to do is continue performing a specific action so long as some condition is true. The opposite can also be achieved: we can repeat a block of code so long as some condition is false, or "until" a condition is true, like so:

// Run the motor UNTIL the limit switch is hit
while(!armUpSwitch->Get()) // ...

I omitted the rest of th code, but you can see that using the ! to reverse the condition allows us to turn a "while" loop into an "until" loop. While we're talking about reverse-conditions, perhaps we should return to the statement I keep repeating: if the condition is false to begin with, the code never runs. Well, what about the opposite case? A common mistake in programming it to have a flaw in your loop condition, such that the condition is NEVER false, meaning that the loop would repeat indefinitely:

Code:

while(x > 6)
{
   printf("X is relatively large");
}

printf("This text is never printed");

At first, it may be difficult to see the problem here: we are checking if x is greater than six and, if so, printing text on the screen. However, this is a "while" loop, not an if-statement. If x is indeed greater than six, the line would be printed and the computer would jump back and check the condition again. Since there is no code between the braces to change the value of x, this process would repeat forever. However, if we alter the value of x somewhere in the loop, the problem is remedied, like so:

Code:

while(x > 6)
{
   printf("X is relatively large");
   x--;
}

printf("This text is now printed");

Now, if x is greater than six, the line is printed, and then x is decremented (has one subtracted from it). This means that, if x is ten, the text would be printed four times--once when x is ten, once when x is nine, once when x is eight, and once when x is seven. Once x reaches six, x > 6 is no longer true, and the loop exits. Additionally, note the indentation, similar to if-statements. Also like if-statements, you can nest loops, or combine conditions, like so:

Code:

// Nested example
while(x > 6)
{
   y = 6;
   
   while(y > 7)
   {
      printf("Y loop");
      y--;
   }
   
   x--;
}

// Combined example
while(x > 6 && y > 7)
{
   x--;
   y--;
   printf("Loop");
}

You should be able to tell by now that the two loops above do two VERY different things, but I won't bother explaining them throughly. While they might seem pointless out of context, and while the numbers may change, both are types of loop I've used before in larger programs, so you may be surprised what comes in handy sometimes.

So, so far we've seen "while" loops, or conditional loops. Technically, all loops are conditional, but it's very common for a different kind of loop, a "counted" loop, to be used. Consider this example:

Code:

x = 10;
while(x > 0)
{
   printf("This line appears ten times");
   x--;
}

As you can see, the loop will be printed ten times. The loop is executed when x = 10, 9, 8, 7, 6, 5, 4, 3, 2, and 1--ten times if you count it. While this has it's uses--generally, involving arrays, which we won't get into unless we have time after all of the other lessons--it's often more useful when we don't know how many times the loop will need to be run. For example:

Code:

x = armEncoder->Get();
while(x > 0)
{
   // Do something
   // Executed once for every pulse from the encoder
   x--;
}

While I can't think of a possible use for the exact example above, it still illustrates the point: we don't know what x is equal to, so we just repeat the loop and subtract one from x each time, meaning if x is 7, it will run seven times; if x is 300, it'll run 300 times. You can get even more flexible, like so:

Code:

// Run while x > z and the limit switch is pressed (y != 0)
while(x > z && y != 0)
{
   x--;
   // Do something
   y = limitSwitch->Get();
}

This will still limit the number of times the loops runs, but it now depends on both the value of z, and the value of x, and the position of a limit switch on the robot. As you can see, quite a lot of counted loops are possible, and if we get into arrays, they will be invaluable. In fact, counted loops are so common, another type of loop exists: the "for" loop:

Code:

for(statement; condition; statement)
{
   // Code to run
}

If you leave the "statement" sections empty, but still put the semicolons, and put a valid condition in place, the for loop acts exactly like a while loop. However, you can also use the two "statement" sections. The first "statement" section is executed before the loop starts, and the second is run after each time through the loop. Read the below examples to see:

Code:

for( ; x != 0; )
{
   printf("Running");
   x = limitSwitch->Get();
}

// Is the same as this
while(x != 0)
{
   printf("Running");
   x = limitSwitch->Get();
}


for(y = encoderName->Get(); x > y; )
{
   printf("Running");
}

// Is the same as this
y = encoderName->Get();
while(x > y)
{
   printf("Running");
}


for(i = 10; i > 0; i--)
{
   printf("Running");
}

// Is the same as this
i = 10;
while(i > 0)
{
   printf("Running");
   i--;
}

I'm using the code windows more to ensure the indentation is correct. As you can see, the for loop is pretty redundant for the most part--it generally acts the same as a while loop. It's true value comes in when you use the two "statement" sections. The third example is technically the correct use of a for loop. It is almost always used to run a counted loop. Technically, you could just memorize while loops and never worry about the for loops, but you will find them useful sometimes, and you will definitely see them used, so I suggest getting to know them as well.

Loops are rarely used in our robot code, despite being used frequently in most programming applications. This has to do with the structure of our code. You should all be aware of this part of our robot's code:

Code:

void Autonomous(void)
{
   // Variables go here
   
   while(IsAutonomous())
   {
      // Autonomous code goes here
   }
}

As you can see, the entirety of the code for autonomous is inside one loops (and the code for teleop is inside a separate loop). Since the code in this block is already repeated continously so long as we are in autonomous mode, and since the robot needs to be able to multi-task, we rarely ever put any loops inside of this loop. Consider the following differences:

Code:

// This is bad
while(IsAutonomous())
{
   while(!limitSwitch->Get()) armMotor->Set(0.5f); // This is only one line, so you don't need the braces
   lfDrive->Set(0.7f); // Motor only turns if limit switch is pressed
}

// This is good
while(IsAutonomous())
{
   if(!limitSwitch->Get()) armMotor->Set(0.5f);
   lfDrive->Set(0.7f); // Motor turns all the time
}

Note how, like if-statements, you can ignore using the braces if the loop only has one line of code connected to it. In the first example, you would find that the drive motor does not start to move until the limit switch is hit. This is because the "while" loop above it would repeat continuously until the switch is hit, preventing the computer from moving on to the next line of code until the condition becomes false. This is not good, because our robot needs to be able to do multiple things at a time rather than waiting for one thing to finish before moving on the to the next. Therefore, we use the second example. Since it is an if-statement instead of a loop, the code is NOT repeated, so the computer moves on to the next line right away. This illustrates what I said about conditions being the same for loop as they are for if-statements. Normally, the second example would be problematic, because the if-statement means that it is only checked once, instead of repeatedly like we need it to (we want it to keep setting the motor until the switch is hit). However, the condition IS checked repeatedly because the outer "IsAutonomous()" loop already repeats itself. In fact, this method is even more flexible, because the first example has no way to stop the motor afterwards. Meanwhile, we could add "else armMotor->Set(0.0f;" to the second example to stop the motor in the end.

It is for the above example that, except for the "IsAutonomous()" and "IsOperatorControl()" loops, we rarely use any loops in the robot code. We generally stick to if-else statements. There is, however, one example of a loop used in our code:

Code:

// For each particle found
for(i = 0; i < numberOfParticles; i++)
{
   // Get the width and height of the given particle
   imaqMeasureParticle(imaqImage, i, 0, IMAQ_MT_BOUNDING_RECT_WIDTH, &width);
   imaqMeasureParticle(imaqImage, i, 0, IMAQ_MT_BOUNDING_RECT_HEIGHT, &height);
          
   // If the area of this particle is the largest area so far
   if(width * height > maxArea)
   {
      // Store the dimensions and area of the particle, along with the index of said particle
      maxTargetWidth = width;
      maxTargetHeight = height;
      maxArea = width * height;
      largestParticle = i;
   }
}

This is a big jump from the basic things we've been looking at, but I'll run you through it. The for loop above starts our variable, i, at zero and adds one to it each time through the loop--a counted loop. "numberOfParticles" is a variable that contains the number of patches of colour we've found in the processed image from the camera. What this loops does is it goes to each of these particles (notice how "i" is used in the imaqMeasureParticle function) and calculates the width and height using two functions in the WPILib provided to us. The if-statement (recognize it?) then compares the area of this particle to the area of the largest particle so far--stored in "maxArea". If the area is larger, we store the dimensions, area, and particle number of this particle. In this way, this loop allows us to find the largest blotch on our camera image--which should, theoretically, be a very clear target. The width of this particle is stored and used later to calculate our distance from the target, and the particle number is used to find the x-position of the target in our field of view, for tracking purposes. This is the only loop we use in the code, and I believe we had none last year, but it's important to know if you do any programming outside of robotics, and obviously this loop has come in handly this year.

Finally, two quick things. First, I mentioned that the condition for a while loop is checked at the beginning of the loop, and that, if it isn't true in the first place, the loop is never run. There is an alternative to this: the do-while loop.

Code:

do
{
   printf("This will be displayed at least once");
   x--;
}while(x > 0);

The "do" indicates the start of the loop and allows us to put the "while" and condition at the end of the code block. In this format, the computer doesn't check the condition until AFTER the block is executed. This means that the code is always executed at least once, regardless of whether or not he condition is true. From there on, it acts like a normal loop, repeating while the condition is true and exiting once the condition becomes false. Also, notice that the "while" line has a semi-colon after it. This follows our rule, however, because in this configuration the "while" line does NOT precede an open brace. Next, there are two other keywords: "break", and "continue". The first one exits the loop early, regardless of the value of the condition, and the second jumps back to the top of the loop without executing the lines below it:

Code:

// Runs until x is zero, but also exits if x is 87. Could be written as "while(x > 0 && x != 87)"
while(x > 0)
{
   if(x == 87) break; // Exit the loop if x is equal to 87
   printf("Running");
   x--;
}

// Sets y to 1 / x, for x values between -10 and +10, but skips zero to avoid dividing by zero
for(x = 10; x >= -10; i--)
{
   if(x == 0) continue; // Skip x at zero
   y = 1 / x;
}

Many programmers scoff at the use of these keywords at all, but I would argue that, especially for the second case, there are some instances where it is advantageous to have them. However, there are almost always better ways of accomplishing the same thing WITHOUT these keywords, so don't make a habit of using them.

That just about concludes this tutorial. I feel like I lied and that this ended up being longer than I had expected, but loops are also an important topic, so just take the time to understand them. As always, questions are welcome, and there will be another tutorial posted tomorrow Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Functions

Post  Brendan van Ryn on Sun Feb 26, 2012 3:01 pm

Our next tutorial is on a concept that revolutionized programming, but is sometimes difficult to see the advantage of until you start using it. This tutorial is on functions. Functions allow you to break your code into small, discrete, independent sections. There are many advantages to functions. For one thing, they allow you to reuse the same code over an over again from completely different parts of your program without having to rewrite it, which cuts down on the overal size of your file and the time it takes for your program to compile. Generalized functions allow you to break a complicated task into smaller sections, and may sometimes even be applicable to other programs you write in the future--allowing you to simply link the function into your new program in order to perform that same task.

You have already been using functions in the robot code. We've seem them, through these tutorials, used in three places so far. The first place we've seen functions is under the arrow for an object, such as "lfMotor->Set(1.0f)" or "armEncoder->Get()". Those words that follow the arrow and have parentheses after them are the functions. You've also seen functions within our class. These lines, followed by blocks of code, indicate functions:

void SimpleTracker(void) // Constructor/initializor function
void Autonomous(void) // Autonomous function
void OperatorControl(void) // Teleop function

Finally, you've seen functions used in math expressions as examples. For instance, in "x = sqrt(a)", the "sqrt()" part is a function. As I mentioned before, a function is a pre-written section of code that performs some tasks, and possibly returns a value. Some functions also require that you pass them information inside of the parentheses, and some don't. However, we're now going to look at the process of writing your own functions.

For the sake of making things easier to explain, all of the examples here will only show the OperatorControl block, and will leave out the while loop normall found inside of it.

The first part of making of a function is defining it. All of the functions you write should be defined at the BOTTOM the class, meaning that it is below the Autonomous() function, but still inside of the braces for the class's block. Functions can be declared essentially anywhere, but this is the easiest way for our purposes. For our first function, we will start simple. This time, I will show the full class just to illustrate where exactly the function is defined. After that, I will change to the shorter version I mentioned above:

Code:

class SimpleTracker : public SimpleRobot
{
   void SimpleTracker(void)
   {
   
   }
   
   void OperatorControl(void)
   {
   
   }
   
   void Autonomous(void)
   {
   
   }
   
   // Our functions are defined here
   void OurFirstFunction(void)
   {
   
   }
};

START_ROBOT_CLASS(SimpleTracker);

As you can see, the function comes right before the closing brace for the class. You should also notice that OurFirstFunction is laid out in a similar manner to the Autonomous, SimpleTracker, and OperatorControl functions: there is a void keyword before its name, and it has a pair of parenthese with void inside of them. It then has a block of code following it, delimited by the opening and closing braces. void is a keyword we have never formally addressed before. Like int and float and double, void is a datatype--and empty datatype. Using it before the function means that it does not return a value--you could not write "x = OurFirstFunction()", because there would not be anything to store in x. The second void in the parentheses means that you do not need to put anything in the parentheses when you call it.

Let's move on to a more general example. Generally, whenever you define a function, it is done like so:

Code:

datatype FunctionName(datatype argumentName, datatype argumentName, ...)
{
   // Code to run when called
   // Optional:
   return [value];
}

// Examples
double Abs(double x)
{
   if(x < 0) return -x;
   else return x;
}

float sqrt(float number)
{
   // Calculate the square root
   // I won't bother writing out the entire algorithm
}

For now, I just want you to pay attention to the declarations of the functions and ignore what is inside of the braces. When declaring a function, "datatype" can be any variable type you know, like int, float, and double, or it can be void. If you want your function to return a value to the calling procedure, you would use the type of variable that is being returned; otherwise, you would simply indicate "void". We will ignore the arguments for now, and just use void. As for the function name, any functions that YOU create should follow the format of VerbNoun or VerbAdjectiveNoun. This allows anyone reading the code to have a vague understanding of what the function does simply by reading it's name. Additionally, the function's actual declaration should have a comment above it summarizing what the function does, with added comments inside of the function as needed.

So, let's start with a very basic example. Functions can get confusing fast, so we'll keep things simple:

Code:

void OperatorControl(void)
{
   // Call the function
   RunMotors();
   DISPLAYSTRING(1, "Function is finished");
}

// Turn left and right motors on at full power
void RunMotors(void)
{
   leftMotor->Set(1.0f);
   rightMotor->Set(1.0f);
}

When the robot enters Teleop, it will reach the "RunMotors()" call. At this point, the computer will jump down to the spot where we defined "RunMotors()" and execute the code inside of it's block. You can put any code you want inside of this block--you can call other functions, declare variables, run if-statements and loops, and you should definitely comment if it is more than a couple of lines long. After the code in the RunMotors() section is completed and the computer reaches the closing brace, it jumps back to the OperatorControl() function, because that is the function that called it, and continues at the next line--the "DISPLAYSTRING" line.

This is useful because the "RunMotors()" function can be called in multiple places. You could call it in Autonomous as well, or multiple times in the OperatorControl() function. Just like sqrt() and leftMotor->Set(), any functions you write can be used anywhere you like. You can also see that, since we used "void" for the return type and the arguments list, we didn't put anything in the parentheses when we called RunMotors() and we didn't try to store the result in a variable.

You should also note that variable scope still applies:

Code:

int a; // Class-level

void OperatorControl(void)
{
   int b; // "Local"
   
   // Can access a and b
   
   RunMotors();
}

void RunMotors(void)
{
   int c; // Local
   
   // Can access a and c, but NOT b
}

In fact, something I forgot to mention was this little trick:

Code:

void OperatorControl(void)
{
   int x;
   
   x = 10;
   RunMotors();
   DISPLAYINTEGER(1, x);
}

void RunMotors(void)
{
   int x; // This is completely separate from the x above
   
   x = 5;
   DISPLAYINTEGER(2, x);
}

On line 1 on the Driver Station, the number 10 would appear, and the number 5 would appear on line 2. This is because, just like RunMotors() could not access "b" in the previous example, the two "x" variables we declare are completely unrelated. If this is confusing to you, that's because it is. It's best to avoid using a variable name more than once, even though it's technically possible.

So, let's try something a little bit more illustrative of functions:
Code:

void OperatorControl(void)
{
   int x;
   
   x = armEncoder->Get();
   
   if(x > 50) StopArm();
   else MoveArm();
   
   RunMotors();
}

void StopArm(void)
{
   armMotor->Set(0.0f);
}

void MoveArm(void)
{
   armMotor->Set(1.0f);
}

void RunMotors(void)
{
   float y;
   
   y = sqrt(leftStick->GetRawAxis(1));
   leftMotor->Set(y);
   rightMotor->Set(y);
   
   if(y == 0.0f) MoveArm();
}

While this code doesn't do anything useful, it shows how you can have as many functions as you like. It also illustrates that you can call one function from inside another function, and you can even nest functions, like when we called sqrt() with the result of the GetRawAxis() function. In general, you can do anything inside a function declaration that you could do outside of it. They are incredibly flexible.

So, I hope things aren't going to fast yet. Essentially, we're just defining another block of code and running it when we want to by calling it by name. We can even have variables inside of these functions that, like other functions, can only be accessed inside of those functions. While we're talking about variables, though, something very important needs to be pointed out:

Code:

void OperatorControl(void)
{
   int i;
   
   DisplayVariable();
   DisplayVariable();
   DisplayVairbale();
   
   /* Some of you might think the above is a bit redundant. You're right, we could actually
   write it like this:
   
   for(i = 0; i < 3; i++) DisplayVariable();
   
   Good for anyone thinking that ;) */
}

void DisplayVariable(void)
{
   int x = 0;
   
   DISPLAYINTEGER(1, x);
   x++;
}

Even though the DisplayVariable() function is being called three times, the Driver Station would only show zero. This is because "x" is reset to zero each time the DisplayVariable() function runs--it's value is not retained after the function finishes. The rememdy to this, if you absolutely need it to maintain its value (which, trust me, is rare), is to use the "static" keyword. This means that, after the first time the variable is used, it never gets reset until the robot is rebooted:

Code:

void DisplayVariable(void)
{
   static int x = 0;
   
   DISPLAYINTEGER(1, x);
   x++;
}

This function will now operate as you expect. Be careful when using "static". Generally, you should never use it for variables inside OperatorControl() and Autonomous(). These functions are generally only called once, and have a loop inisde of them, meaning that the variables in these functions are only reset when you switch between Autonomous and Teleop or reboot the robot. When you use "static" you MUST reboot the robot to get them to reset, so it should ONLY be used in other functions you declare yourself, and only when you really need it.

So, now we have a way of writing a block of code that we can execute with a single line that we can put anywhere in the code we want. This is useful, but it's not amazingly flexible. Next, we will see about passing arguments to functions (note that, in most cases, the words "parameter" and "argument" are interchangeable, although they technically mean different things). Consider the sqrt() function: it calculates the square root of the number we pass to it in the parentheses. Passing a value to a function is called passing an "argument". This lets the function perform a different task depending on what value you pass it. For example:

Code:

void OperatorControl(void)
{
   RunMotors(0.7f);
}

void RunMotors(float motorSpeed)
{
   leftMotor->Set(motorSpeed);
   rightMotor->Set(motorSpeed);
}

Now, in the parentheses for RunMotors, we've declared a "float" variable called "motorSpeed". It is very easy to understand how arguments work if you think of them this way: variables declared in the parentheses of a function. This means that, like if the variable had been declared normally, you can use "motorSpeed" for anything you like inside the function, and the variable will also cease to exist once the function finished. The only restriction is that you CANNOT initialize variables inside of the parentheses:

Code:

// This is fine
void RunMotors(void)
{
   float motorSpeed = 0.0f;
}

// This is NOT
void RunMotors(float motorSpeed = 0.0f)
{

}

Due to a special functionality provided by Object-Oriented features, the second example won't give an error (I believe), but it's not technically correct. So, if this is the case, then why would we ever declare a variable inside of the parentheses? It's harder to read and you can't initialize it, so wouldn't it be better to just declare it in the normal spot? For the most part, yes, it would. However, there is one reason that declaring it in the parentheses is advantageous: while you CANNOT initialize the variable in the declaration, the CALLING function CAN initialize the variable. For example, in the motor speed example above, we put "RunMotors(0.7f);". This means that, when the RunMotors() code began running, the "motorSpeed" variable was already initialized to 0.7f--the OperatorControl() function initialized it when it called RunMotors(). This is incredibly useful. For our particular example, you can see how we can now chose the speed at which the RunMotors() function runs the motors by selecting it in the parentheses. Additionally, we can put an entire expression, including variables, in the parentheses:

Code:

void OperatorControl(void)
{
   int x;
   
   x = armEncoder->Get();
   if(x > 70) RunMotors(x * 2.0f);
   else RunMotors(0.0f);
   
}

void RunMotors(float motorSpeed)
{
   leftMotor->Set(motorSpeed);
   rightMotor->Set(motorSpeed);
}

As we've done before, we use the "motorSpeed" variable in the call to ->Set(). In this example, depending on the value of an encoder, the motor speed is set to either twice the value of the encoder pulses, or zero. In this way, we can change how a function operates by changing what values we send to it. Variables that are declared in the parentheses of a function are called "arguments" or "parameters". You can declare a function to have as many or as few parameters as you want. To declare more than one parameter, just separate the paramters with commas, like so:

Code:

void OperatorControl(void)
{
   int x;
   
   x = armEncoder->Get();
   
   if(x > 10) RunMotors((float)x / 20.0f, (float)x / -20.0f);
   else RunMotors(0.0f, 0.0f);
}

void RunMotors(float leftSpeed, float rightSpeed)
{
   leftMotor->Set(leftSpeed);
   rightMotor->Set(rightSpeed);
}

When you declare more than one parameter, unlike when declaring variables, you must specify the datatype of each argument, even if it is the same. In the above example, the calling function (OperatorControl) sends two values in the parentheses, separated by commas: one for the left motor, and one for the right motor. For each argument, you can still enter an entire expression, as above. This allows you to send as much information as you need to the function. Note that the compiler will error-check function calls. Given the declaration of RunMotors() above, the following calls would not work:

double x;

RunMotors(x, -x); // It needs floats but you're sending doubles
RunMotors(0.0f); // It takes two arguments, but you're only giving it one
RunMotors(0.0f, 1.0f, 5.6f); // It takes two arguments, but you're giving it three

For this reason, it is important to know the arguments a function receives. In the case of functions attached to object through the arrow (->), typing the arrow and pausing will show a list of member functions along with the "prototype", which is the declaration line. This allows you to see the return type and argument type for each given function. For other functions, you can look at the declaration in the code. For standard function--pre-written, non-object ones that come included in the C standard library--there are online and print references for their formats. Generally, searching for the function name, including the parentheses, in Google will turn up a webpage that explains how the given function is used. Additionally, I plan to post a list of common functions, objects, and macros later on.

There is something very important to realize about parameters. Just like the example above where "int x" was declared twice, in different functions, the parameters inside a function are completely separate from the arguments passed. Observe:

Code:

void OperatorControl(void)
{
   int a;
   
   a = 10;
   TryChange(a);
   DISPLAYINTEGER(1, a);
}

void TryChange(int b)
{
   b = 20;
   DISPLAYINTEGER(2, b);
}

This would display 10 on the first line of the Driver Station, and 20 on the second line. Even though we pass "a" as an argument to "TryChange()" and change that argument in the function, the value of "a" is unaffected. This is because "a" and "b" are completely separate entities--they are two different variables. Even if I declared the function as:

void TryChange(int a)

The output would still be the same: like the two x variables, the "a" in the parentheses and the "a" from OperatorControl are two separate variables that are completely unaffected by each other. If you actually desire for the value to change, you must use pointers, which is something I may or may not write a tutorial on later. At any rate, it is doubtful that you will ever need to do so.

Finally, let's look at return values. We know that some functions, like encoderName->Get() and sqrt() return values that we can store in variables. In fact, functions that return values--as we've seen--can be treated like a number, because they essentially act like a number once the function completes. You can imagine the sqrt() function as taking the square root of the number you pass to it and then changing into that value, such that:

x = 2 * sqrt(b - a);

Works as you would expect it to. So how do we make functions that do the same? So far, we've used "void" to declare our functions. This makes it so that they don't return a value, and trying to use a void function in the example above would generate an error. To have a function return a value, simply specify what type of value is being returned:

int FunctionOne(void); // Returns an int
float FunctionTwo(void); // Returns a float
double FunctionThree(void); // Returns a double
void FunctionFour(void); // Returns nothing

Once you have done that, you specify what the value of the function becomes after execution by using the "return" keyword:

Code:

void OperatorControl(void)
{
   int x;
   
   x = ReturnValue();
   DISPLAYINTEGER(1, x);
}

int ReturnValue(void)
{
   return 4;
}

The "return" keyword takes the value that follows it and inserts that value where the function was called. This means that "x = ReturnValue();" becomes "x = 4;". The value you return can be anything, like the following:

return 18.51;
return 0;
return sqrt(16);
return (x + 7) / 41;

These will all work as you expect: the value of the function becomes whatever the expression evaluates to. Returning is only necessary in functions that have a return type that is NOT void. If the return type is not void and you forget to put a "return" statement, the compiler will warn you that you have not indicated the value of the function. Additionally, if you were to put "return 4" in a void function, that would also generate an error as void functions do not return values.

You can also have multiple return statements, like below:

Code:

void OperatorControl(void)
{
   double x;
   
   x = leftMotor->Get(); // Get motor's current power setting
   x = Abs(x);
}

double Abs(double number)
{
   if(number > 0) return number;
   else return -number;
}

This function returns a different value depending on what value was passed to it. Additionally, this is the only way (without pointers) for the calling function to receive the value of another function's variable: even though OperatorControl() cannot access "number", it CAN access the return value of the Abs() function.

Void functions can also use "return", but only if nothing follows it, like so:

Code:

void MyFunction(void)
{
   // Do something
   return;
}

This is acceptable, even though the function is void, because no return value is specified. The "return" word is not followed by a number or expression. So what does this do then? Well, besides indicating the value of the function, "return" also exits the function. Watch:

Code:

void OperatorControl(void)
{
   DISPLAYMESSAGE(1, MyFunction());
}

int MyFunction(void)
{
   return 4;
   DISPLAYMESSAGE(2, 5); // This line is not run
   return 5; // This line is not run
}

The number "5" is never printed--only the "4" is printed. This is because the computer treats the "return" keyword the same way as it treats the closing brace (}) for a function. When it encounters the "return" keyword, it sets the value of the function (unless it's void) and then jumps back up to the calling function, just as though the function had ended. Therefore, if you have multiple "return" keyword, you must use if-statements to decide which to use because the function will stop at the first "return" keyword that is executed.

Like arguments, the compiler also checks the return type of a function. If you try to use the return value of a void function, or if you try to store the return value of a function in a variable of the wrong type, you will get an error. Of course, you can still use type-casting to convert return values. Let's say that Abs() returns a double:

int x;
x = (int)Abs((double)x);

First, x had to be converted to a double when it is passed to Abs(), because it requires a double argument. Then, the return value of Abs(), whic is also a double, is converted to an int.

The following are some examples of functions in the robot code for this year:

Code:

// Converts the width of the rectangle to a distance in feet
double WidthToDistance(double x)
{
   double result; // The resut of the calculations
   
   result = GOALWIDTH * IMAGEWIDTH / x / 2;
   result = result / tan(HALFCAMERAVIEWINGANGLE * Pi / 180);
   result = result * LINEARDISTANCEK + CONSTANTDISTANCEK;
   
   return result;
}

/* This function handles all of the encoder processing for the top shooting roller. The parameter, if true (any non-zero number), resets the pulse count to zero. Otherwise, it keeps counting from where it was. The return value is the current pulse count of the encoder. */
int TopShooterEncoder(int reset)
{
   // "Static" means it will retain it's value from previous calls to the function
   static int previousState = 0; // Previous state of the lightSensor
   static int pulseCount = 0; // Number of pulses recorded so far
   int currentState;
      
   // Record one pulse each time the light sensor's state changes
   currentState = topRollerEncoder->Get();
   if(currentState != previousState) pulseCount++;
   previousState = currentState;
      
   // Reset if necessary, and return the current number of pulses
   if(reset != 0) pulseCount = 0;
   return pulseCount;
}

There are many others, and some are quite long and complex, but the idea is the same for all of them. Additionally, if you're wondering, yes, you CAN pass objects to functions as an argument as well. At any rate, that about covers everything. As always, feel free to ask questions. Now might be a good time to start looking through the posted robot code some more and see if you understand things better and can pick out pieces of things you're learning. I've posted the most recent version, so take a look Wink

Oh, and in case you're wondering, it's possible for a function to call itself. It's a bit difficult to wrap your head around how this works, but I might do a special tutorial on it later. At any rate, here's an example (I certainly hope I didn't make a mistake):

Code:

// Remember how "include" works? ;)
#include <stdio.h>

// Prototype. Ignore this for now.
double Factorial(double);

// Program entry point
int main(void)
{
   double result;
   
   // Calculate the factorial of sixteen
   result = Factorial(16.0);
   printf("\nThe factorial of 16 is: %0.1f", result);
}

// Calculate x * (x - 1) * (x - 2) * (x - 3) ... * 1
double Factorial(double x)
{
   if(x > 1) return x * Factorial(x - 1); // Function is calling itself here
   return x;
}

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

The Preprocessor, Comments, Formatting, and the Comma

Post  Brendan van Ryn on Tue Feb 28, 2012 1:14 am

You have already seen the preprocessor in action. The preprocessor is simply a program that runs BEFORE the compiler to handle certain additional capabilities. You see, the C language provides a great deal of niceties to make the programmer's life easier, but some additional capabilities proposed by third parties could not be integrated into that functionality. This has to do with the elegance of the language definition and the complexity of the compiler. To circumvent these issues, a separate processing program was made that would be invoked before the compiler ever looked at the code: the preprocessor.

You have seen preprocessor directives before. They are indicated by the "#" symbol at the beginning of the line. Any line starting with that symbol is a line for the preprocessor, not the compiler. In other areas of the code, this symbol is generally an illegal character. The two types of lines you have seen involving the "#" symbol are #define and #include. I've already talked about them a bit, but this time, we'll go into more detail.

First, #include. #include simply takes the code (or whatever text is present) from the file you specify and inserts it in the exact spot where the #include line was placed. Generally, this directive is used for header files, ending in ".h", as they contain macro, datatype, and function declarations that have been pre-written. Because the preprocessor copies the text into the exact spot where it found the directive, and because--as you know--functions, variables, and other things must be declared BEFORE you use them, #include lines are generally included at the top of the program. #include lines look like this:

#include <filename.h> // Search default directory
#include "filename.h" // Search other directory

// Examples
#include "NewRobot.h" // Definitions for 2012 robot
#include <math.h> // Math functions, like sqrt()
#include "WPILib.h" // Objects and functions for robot, provided by FIRST
#include <time.h> // Date and time-related functions
#include <assert.h> // Assertions
#include <stdio.h> // Standard input and output
#include <string.h> // String-handling functions
#include <ctype.h> // Character type functions (generally inline, I think)

It's a very basic command, but it should still be commented to indicate the purpose of the file you're including. The only thing to explain, really, is the difference between the angled brackets, and the quotation marks. The angled brackets tell the preprocessor to look for the file in the C Standard Library header directory, or a directory selected in your compiler's options. Therefore, standard header files, like time.h and stdio.h and math.h, generally use this. The quotation marks tell the compiler to search in the directory of your program's source code, and generally it's subdirectories. In WindRiver, this means the folder containing our Workspace. Thus, the WPILib.h file and any header files we write ourselves (like NewRobot.h) would be there. You guys probably won't write your own header next year, though--you'll probably just put the definitions at the top of the .c file.

Next is #define. We've already talked about #define as well: you designate a macro name, and a set of characters that will be used to replace that macro name. Some examples are:

// General format
#define MACRONAME [value]

// Examples
#define LEFTMOTORPORT 4 // PWN port of left drive motor
#define RIGHTJOYSTICKPORT 1 // USB port of right driving joystick
#define ARMENCODERDEADBAND 20 // Margin of error, in pulses, for arm encoder
#define PI 3.1415f // Pi to very few digits
#define SHOOTBUTTON leftStick->GetRawButton(1) // The button we are using to shoot the ball

Again, I want to stress my rule that preprocessor lines, starting with "#", do NOT end with semi-colons. You will not get an error in the case of #define, but you might later when you try to use your macro in the code. As we've seen, the preprocesser will search the code for any instance of any macro names we've defined, and will replace any (whole word, case-sensitive) matches with the value we've indicated in the #define statement. As always, comments are a good idea.

Next, we'll talk about a seldom-used but powerful feature available to macros: function-like macros. We've already seen how functions can be written to execute a set of instructions and return a value. You can do something similar through macros, though they are a bit of a trick. These can get quite detailed, but we'll keep it simple. Declaring a macro that is like a function simply requires that the macro name be followed by an open parenthesis, with no space between them. Between the parentheses, you will list the arguments it receives (but WITHOUT datatypes). Then, you write the macro definition as usual:

// This example returns the negative value of the argument you pass it
#define FUNCTIONMACRO(a) (-a)

// Later in the code...
int x = FUNCTIONMACRO(armEncoder->Get());

In the above example, armEncoder->Get() returns the number of pulses from the encoder--let's say 500. This is passed to FUNCTIONMACRO, which the preprocessor expands. Theoretically, FUNCTIONMACRO acts as a function and converts its argument, 500, to -500 and returns it, so it is stored in x. In reality, the TEXT "armEncoder->Get()" is passed to it and converted to the macro's definition. After the preprocessor converts it, the line looks like this:

#define FUNCTIONMACRO(a) (-a)

// Before preprocessing
int x = FUNCTIONMACRO(armEncoder->Get());

// After preprocessing
int x = -armEncoder->Get();

To understand better, let's take a look at a more complicated, real-life example: DISPLAYMESSAGE. This function is a precursor to the DISPLAYINTEGER macro, which used to only display output on the first line, instead of letting you choose the line:

Code:

// The backlash "\" lets the definition continue to the next line, for easier reading
// Otherwise, the #define statement would end at the end of the line
#define DISPLAYMESSAGE(a) \
   DriverStationLCD::GetInstance->Clear(); \
   DriverStationLCD::GetInstance->Printf(DriverStationLCD::kUser_Line1, 1, "%d", a); \
   DriverStationLCD::GetInstance->UpdateLCD();

// Later in the code...
DISPLAYMESSAGE(armEncoder->Get());

// After preprocessing
DriverStationLCD::GetInstance->Clear();
DriverStationLCD::GetInstance->Printf(DriverStationLCD::kUser_Line1, 1, "%d", armEncoder->Get());
DriverStationLCD::GetInstance->UpdateLCD();

As you can see, the argument passed to the macro function, regardless of whether or not it's just a number, or an expression, or another function, or multiple lines of code, will replace that same argument in the macro's definition, no matter where it is. This allows you to design very flexible macros. It is worth noting that this is NOT a normal function. The computer doesn't jump somewhere else, perform the tasks, and jump back. Instead, the code is changed to the definition you indicated. Generally, macro functions are less efficient that normal functions, so you shouldn't need them much, but hopefully this helps you understand the DISPLAYINTEGER() and DISPLAYSTRING() and DISPLAYFLOAT() functions, which also show that you can take more than one argument. As for the funky "##" operator, it combines the "kUser_Line" with the number passed as "b" so that the result is a valid enumerated value.

Next, I will explain #undef. #undef removes a macro definition, like so:

Code:

// #undef removes the defition
#define PI 3.14

// ...
float x = 2 * PI;

// ...
#undef PI

// ...
float y = 3 * PI; // Syntax error because PI no longer exists

You can also use this to change the definition of a macro. You can #define it in one spot, the #undef it later and put another #define statement so that it holds a different value. The uses of this are generally few in number.

Finally, we'll look at #if, #ifdef, #ifndef, #else, #elif, and #endif. These directives let you build if-statements for the preprocessor. They run much like the C if-statements, but for the preprocessor. When I use an #if, all of the code between it and the corresponding #endif is compiled if-and-only-if the statement is true. This allows you to compile different sets of instructions depending on the value or presence of a macro. This is important to realize: #if statements can only use constants (like actual numbers) and macros, nothing else. Here is a short example:

Code:

#define MAX 100

void OperatorControl(void)
{
   #if MAX < 50
      DISPLAYSTRING(1, "Small value");
   #else
      DISPLAYSTRING(1, "Large value");
   #endif
}

In the above case, the second line of code would be compiled, and the first line wouldn't. Because MAX < 100 is false, any code between the #if and the #else is ignored, and the code between the #else and the #endif is compiled instead. Like C if-statements, #else is optional. Also, instead of using braces, the end of a block is indicated by #endif. Generally speaking, so long as only macros or constants are used, any valid C condition will also be valid for the preprocessor #if--">", "<", "==", "!=", "!", etc. On top of that, the "&&" and "||" operators can also be used. Finally, an additional operator exists for preprocessing only: the "defined" word. This is followed by a macro name, and if a macro has been defined with that name, it returns true--otherwise, it is false. Observe:

Code:

#define FIRSTMACRO 2
#define SECONDMACRO 4
// #define THIRDMACRO 5 // This line is commented out, so the macro is NOT defined

#if defined THIRDMACRO
   // This could would not be run because "defined THIRDMACRO" is false
#elif FIRSTMACRO < 5 && !defined SECONDMACRO
   // This would not be run either, because "!defined SECONDMACRO" is false
#elif defined FIRSTMACRO
   // This WOULD be run, because the FIRSTMACRO is defined (true)
#else
   // This would not be run, because the above #elif is true
#endif

The lack of spaces with #endif is important. Additionally, #elif acts as the analogy to "else if" for the compiler. In this way, complicated conditions can be created. The other options, "#ifdef" and "#ifndef" are equivalent to "#if defined" and "#if !defined", respectively. As with the function macros, preprocessor if-statements are rare, but there is one place we do use them, as below. This code allows us to define a macro, "ROBOT", as either NEW or OLD. According to what we decide, the preprocessor will either use the NewRobot.h header file, or the OldRobot.h header file, each of which has it's macros set up so that simply changing the word to NEW or OLD allows the compiler to automatically set all of the macros to match with the robot you are using:

Code:

#define NEW 1
#define OLD 2

#define ROBOT OLD

#if !defined ROBOT // If the ROBOT macro was never defined
#warning "Robot version not specified. Assuming new configuration..." // Display a warning
#include "NewRobot.h"
#elif ROBOT == NEW // If ROBOT was defined as NEW
#include "NewRobot.h"
#elif ROBOT == OLD // If ROBOT was defined as OLD
#include "OldRobot.h"
#else // They did not choose a valid value for "ROBOT" (neither NEW nor OLD)
#warning "Robot configuration must be either 'OLD' or 'NEW'. Assuming new..." // Display warning
#include "NewRobot.h"
#endif

If a mistake is made with the macro, a warning is displayed to the user and the "NewRobot.h" header file is used by default. Otherwise, the correct header is included based on the defined value. #warning is another directive that generates a warning in the error window. Another directive, #error, generates and error with the given message.


Next, comments. You should all be familiar with comments because you should all be using them, and should all see them in the robot code (that you guys are all obviously checking regularly, right? Wink). Comments should be used for every distinct, discrete section of code and, when in doubt, comment. Comments should always be short, but clear, and properly explain the code lines they belong to. Comments can be of two types: "batch", "multi-line", or "C-style" comments; and "single-line" or "C++ style" comments. They are shown below:

// This comment belongs just to this line
int thisVariableIsNotCommented;

/* This comment belongs
to every line it extends to
and only ends when I close it */

With single-line comments, everything on the line AFTER the two forward slashes (//) is considered a comment and is ignored by the compiler. Comments have NO effect on the code. The multi-line comments start wherever the "/*" pair is placed, and end at the nearest "*/". Comments like this cannot be nested, although such a thing would have no purpose anyway.

None of this should be new to you at all, but let's talk about comment placement. There are three main placement types, as shown below:

Code:

// In this type of commenting style
motorSpeed = leftMotor->Get();

// You place one comment
x = encoder->Get();

// Above each applicable line
rightMotor->Set(motorSpeed);

// Or each applicable section
leftEncoder->Reset();
leftEncoder->Start();
rightEncoder->Reset();
rightEncoder->Start();


/* In this commenting style, a large block of comment-text is placed
before a long section of code, explaining it's purpose and allowing the formatting
below to be devoid of other comments, making it easier to read. This also allows more
detailed explanations. However, there will still be times when small sections will need to
be commented within these larger sections, in which case, one of the other styles must be used */
motorSpeed = leftMotor->Get();
x = encoder->Get();

rightMotor->Set(motorSpeed);

leftEncoder->Reset();
leftEncoder->Start();
rightEncoder->Reset();
rightEncoder->Start();


motorSpeed = leftMotor->Get(); // In this, in-line commenting style
x = encoder->Get(); // Each line is commented AFTER the code for that line

rightMotor->Set(motorSpeed); // Acting like a compromise between the two other types

leftEncoder->Reset(); // And again, blocks can be covered by just one comment
leftEncoder->Start();
rightEncoder->Reset();
rightEncoder->Start();

The style you choose depends on your particular needs and what you happen to prefer. In this year's code, all three styles were use--each in a different section, depending on the needs. I, personally, use the first style in general, with the third style to add small anotations, and the second style for big things, such as functions or important macros. At any rate, it's up to you, just keep it neat, clear, and well-commented.

In tandem with this comes indentation, spacing, and grouping. C is a free-form language, meaning you can add spaces and blank lines wherever you want--almost literally so, as the semicolon--not the changing of the line--indicates the end of a code line. Therefore, you're free to do quite a few interesting things, but you should generally stick to a format that makes sense. The way the code is laid out this year, for the most part, is fairly clear and neat, so a similar style would be best to adopt for the sake of keeping things simple. Two issues I commonly see with other programmers though that, despite the surprising amount of controversy, are fairly logical are newlines, and operator spacing.

Newlines are really up to discretion. Code lines that are similar in function--like single-line if-statements, or variable assignments from similar ojbects--should be grouped, and newlines should be placed between them ("newlines" meaning blank lines). Newlines should also be placed before and after blocks of code, including functions, and even macro definitions should be grouped into logical sections with newlines separating them. For an example, just see this year's code. As for operator spacing, it is generally best to leave spaces between math and assignment operators, like so:

// Bad examples
x=4;
y=2*7-x;
printf("%d",x,y+2,sin(30));

// Good examples
x = 4;
y = 2 * 7 - x;
printf("%d", x, y + 2, sin(30)); // Because of the commas, this is good too: printf("%d", x, y+2, sin(30));

Those are just a few quick examples, but it's easy to see how just a bit of spacing makes things much easier to read and understand.

Finally, there has been a great deal of murming about the infamous comma operator. It's technically a cheat device in most senses, although it's perfectly valid in the right circumstances. It's main advantage is chaining expressions together such that they can be performed on the same line. I was reluctant to include it, because I KNOW it'll be misused, but technically, I've also misused it at times, and the only way to learn appropriate use of it is it to, well, use it. Just remember: with great power comes great responsibiliy.

The comma, essentially, let's you take multiple C statements (code lines) and put them on the same line. This can already be done with the semi-colon, but then, while the two statements appear on the same line in the editor, they are treated as separate lines by the compiler. This is not the case with the comma. The comma treats the statements like one line. If this statement includes and assignment, the final value of the right side is used, which is the last operation performed in order from right to left. You can look up the comma operator yourself if you don't understand, but examples are the easiest way. Only expressions that have a value are valid, so (I think) void-returning functions and keywords are not supported by the comma. If you get an error, just don't use it:

Code:

// This
x = 4; y = 5;

// Acts the same way as this
x = 4, y = 5;


// But this
if(x == 4) x = 3; y = 6;

// Acts like this
if(x == 4)
{
   x = 3;
}

y = 6; // Note that it is NOT in the if-statement. Only the first line after the if, if no braces are used, is included


// Wherease this
if(x == 4) x = 3, y = 6;

// Acts like this
if(x == 4)
{
   x = 3;
   y = 6;
}


I hope that make sense and, as usual, feel free to look at examples in the robot code to help you understand. The comma can be used multiple times in one line, and that is technically what you were doing when you used the comma in variable declarations. At any rate, except for functions, it's possible to get by without ever using the comma, so if you don't get it, just don't use it Wink

This was a mismatch of random things. That's because I wanted a few more small things out of the way. I have three more tutorials planned, but if there's anything you guys want explained, you can ask a question or write a post requestion a tutorial on a particular subject. I hope this has been helpful, and I'll see you guys tomorrow Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Common Functions and Objects

Post  Brendan van Ryn on Tue Feb 28, 2012 10:36 pm

Alright, today's tutorial is not a particularly fun one--well, they're ALL fun to me, but still. The purpose of this tutorial is to provide a reference for some common objects and functions you will find useful for various tasks. Hopefully I don't get too side-tracked: I'm a big fan of the C standard library Wink

This tutorial will all be in one code window. This window will contain a list of functions and objects. For each function, its prototype--which indicates the type of return value, if any, the name of the function, and the arguments, if any, it receives--will be shown, along with information on how the function works and, in some cases, an example. For each object, I will explain what said object is in terms of the actual hardware it abstracts to, and will show its declaration and initialization lines. On top of that, any useful functions belonging to that object will be shown. I plan to start with WPILib objects, and finish with C standard library functions. Here we go:

Code:

Joystick *joystickName; // Goes right after "class" line
joystickName = new Joystick(PORTNUMBER); // Goes inside SimpleTracker(void) block
joystickX = joystickName->GetRawAxis(1); // Read axis position
joystickY = joystickName->GetRawAxis(2);
buttonSix = joystickName->GetRawButton(6); // Read button state
buttonSeven = joystickName->GetRawButton(7);

/* This declares a joystick. Joysticks plug into the USB ports on the Driver Station. The joysticks we have support eleven buttons, and three axes. As with most objects in WindRiver, the Joystick object has a large number of functions and properties associated with it, but we generally only use the two above: ->GetRawAxis() and ->GetRawButton(). ->GetRawButton() receives, as an argument, the number of the button you want to check. This number corresponds exactly to the button number as labeled on the joystick. ->GetRawButton() returns true (non-zero) when the button is pressed, and false (zero) otherwise. ->GetRawAxis() receives the number of the axis you want: 1 is the x-axis, 2 is the y-axis, 3 is the z-axis, and so on. Our joysticks only have three, so we needn't worry about the others. ->GetRawAxis() returns a value between -1 and +1, such as 0.345, which works well because this is also the range for motor controllers. It's pretty intuitive, but I believe that both the x- and y-axes are inverted. */



Jaguar *motorName;
motorName = new Jaguar(PORTNUMBER); // Uses PWM ports

// OR

Victor *motorName;
motorName = new Victor(PORTNUMBER); // Uses PWM ports

motorName->Set(0.5f); // Set the motor power (-1 to 1)
currentSpeed = motorname->Get(); // Get the current power setting

/* This declares a motor controller. "Jaguar" and "Victor" are two different brands that look distinctly different, but the two objects act the same (you can even make a macro to allow easy switching). At any rate, the ->Set() function is used to select the power setting for a motor. After a power setting (between -1 and +1, where the sign indicates direction) has been selected, the motor maintains this power setting until you tell it otherwise; so to stop a motor, you must explicitly call ->Set(0.0f). ->Get() returns the value to which the motor is currently set. Therefore, in the example above, "currentSpeed" would have a value of 0.5f. */



Encoder *encoderName;
encoderName = new Encoder(FIRSTPORTNUMBER, SECONDPORTNUMBER); // Uses Digital IO ports
encoderName->Reset(); // Set pulse count to zero
encoderName->Start(); // Start counting pulses
pulseCount = encoderName->Get(); // Get current pulse count

/* Encoders are placed on rotating shafts and produce pulses as they turn. The exact number of pulses that are counted for each revolution varies from encoder to encoder, but are generally large numbers, like 255 or 1024. By tracking how many pulses have been produced, we can tell how far the attached shaft has rotated. This has many applications, and encoders are used pretty much every year--whenever we need information on something that rotates. In order to tell which direction the encoder is travelling, two ports are used on the Digital Sidecar, so two ports must be indicated when the encoder is initialized. The ->Get() function returns the total number of pulses that the encoder has received sine it was initialized. The ->Reset() function resets the encoder's pulse count to zero again. Be careful with this function: if it is called too many times in a row, you can crash the cRIO (robot computer). Finally, after resetting or initializing the encoder, the ->Start() function tells it to start counting pulses again. */



Gyro *gyroName;
gyroName = new Gyro(PORTNUMBER); // Uses an analogue port
gyroName->Reset(); // Set angle to zero
gyroName->SetSensitivity(0.007f); // Set sensitivity
steeringAngle = gyroName->GetAngle(); // Get current angle

/* Gyros are devices that use physics properties to measure rotational movement. When the gyro is initialized and ->Reset() is called, the internal angle for the gyro is set to zero. From there, whenever the gyro is rotated, it can determine the angle it turns AND measure the degree of that angle. ->GetAngle() returns the current distance, in degrees, that the gyro has rotated since it was last reset. Negative values are generally in the counter-clockwise direction, with positive values being clockwise. As far as I'm aware, the gyro does not reset to zero after traveling 360º, but I can't be sure. ->SetSensitivity() set the gyro's sensitivity, measured in volts per degree per second. Because this is a difficult unit to coneceptualize, the value of 0.007f has been proven to work well, so you're encouraged to use it. */



Relay *relayName;
relayName = new Relay(PORTNUMBER); // Relays have their own dedicated ports, I believe
relayName->Set(relayName->kForward); // Change the relay setting (note the property use)

/* Relays are generally connected to motors, and act as electrical switches. When a relay is active, it sends 100% of its current to the attached motor, continuously. When a relay is inactive, it sends 0% of its current to the attached motor. Additionally, a relay can reverse the current it sends to the attached motor. ->Set() lets you choose the desired operation. Unlike other ->Set() functions, you can only send one of the follow values, which are properties of the relay object: ->kForward (current on), ->kReverse (current on, reversed polarity), and ->kOff (current off). */



Digital Input *inputName;
inputName = new DigitalInput(PORTNUMBER); // Uses Digital IO ports
switchTriggered = inputName->Get(); // Get input state

/* Digital inputs are inputs that can only be active or inactive, like a switch or light sensor. Calling ->Get() returns the state of the input, which is either true (non-zero) or false (zero). While things are often wired such that "true" means "active" and "false" means "inactive", the reverse can also be true, simply depending on how the device is attached. */



Servo *servoName;
servoName = new Servo(PORTNUMBER); // Uses PWM ports
servoName->SetAngle(90.0f); // Move the servo to the indicated angle
currentAngle = servoName->GetAngle(); // Get the current servo position

/* Servos are essentially motors with positional monitoring--either through and internal encoder, or rheostat, or something--that allows you to specify a fairly accurate angle and have the servo automatically rotate to that angle. ->SetAngle() tells the servo the position to rotate to, in degrees, and ->GetAngle() returns the current position, in degrees, of the given servo. Note that these two values might not be equal--if the servo gets stuck or is in-transit, ->GetAngle() will return a position different from the value sent to ->SetAngle(). */



AxisCamera &camera = AxisCamera::GetInstance(); // Initiates camera
AxisCamera::GetInstance(); // This will work too, by itself

/* The camera has a wide variety of functions associated with it, and helper objects associated with those. It would be unreasonable for me to go into detail about these, but I did want to show you how the camera is initialized. While the first line is what we've been using, for detailed reasons I won't get into, the second line is easier and less troublesome. Simply calling this line will boot up the camera and start sending images to the Driver Station (as long as the camera is set up properly). The rest is very specific, but you can always look at the popup list and ChiefDelphi to help. One important distinction with the camera: you use the period instead of an arrow. If you used the first line above, you would type "camera.functionName" instead of "camera->functionName". If you used the second line, it would be: "AxisCamera::GetInstance().functionName" (I'm not sure about the second one...it MIGHT be an arrow still, now that I think about it). At any rate, with luck, the camera won't be a major piece next year because it is much more complicated that other objects are. */




AnalogChannel *deviceName;
deviceName = new AnalogChannel(PORTNUMBER); // Uses an analogue port
deviceName->GetVoltage(); // Gets the device reading

/* Analogue devices return a range of voltages, in contrast to the two discrete values for Digital Inputs. ->GetVoltage() returns thie voltage, a value ranging from 0.0f to, theoretically, any positive value, but in reality, the limit is either 5.0 volts or 12.0 volts--I can't quite remember. The only two analogue channel devices I've seen are ultrasonic sensors, which return a voltage that increases or decreases with distance from a wall or other solid object, and certain multi-value switches, which return a varying voltage based on their setting. Generally, some math needs to be done with these values to get a meaningful number, but otherwise, they're very straight-forward. */



Solenoid *solenoidName;
solenoidName = new Solenoid(PORTNUMBER); // I think this uses a Digital IO port
solenoidName->Set(1); // Engage or disengage the solenoid

/* Solenoids are electromagnetically-run devices. In particular, we use them to switch pneumatic valves on and off. ->Set() receives either a one or a zero--the former activates the solenoid, and the latter deactivates it. What exactly is meant by "activate" depends on the exact wiring and usage. Solenoids, like most things, retain their state until you set it again. */



DISPLAYINTEGER(1, encoderName->Get()); // Displays an integer value
DISPLAYFLOAT(2, gyroName->Get()); // Displays a floating-point value to two decimal places
DISPLAYSTRING(3, "This is a test"); // Displays the string, in quotes

/* These are macro-functions I wrote to help with displaying information on the Driver Station, which has been an invaluable tool for debugging and diagnosing problems with the code. Each of the function receives the line number (1 - 6) to display the value on, and then the value itself. This value can be an expression, function, variable, constant, or any valid combination, with the exception of DISPLAYSTRING(). Since we don't use strings much when dealing with the robot, I'll exaplain DISPLAYSTRING like this: it's second argument should be any combination of characters between double quotes. For example "Hello" or "Pi is 3.14" are valid. WindRiver may or may not turn these strings blue when you do so. This text, between the quotation marks, will be displayed as-is on the driver station. All of these values appear under the "Operation" tab, which is the same tab that is used for enabling and disabling the robot. */



Wait(5.0); // Pause for five seconds

/* Wait essentially pauses the robot's computer for the number of seconds you pass to it. You should be EXTREMELY careful when using this because no other code can run while the Wait() function is executing. This is especially bad when you place it in either the IsOperatorControl() or IsAutonomous() loops, because it will pause the designated number of seconds EACH TIME through the loop. You will rarely need this. */




GetWatchdog().Feed();

/* You've seen this line over and over again. Make sure it is somewhere inside of the IsOperatorControl() and IsAutonomous() loops. This automatically disables the robot if the code freezes, so if you neglect to include this line, the robot will never run and you will get a "Watchdog timeout" or "Watchdog expired" error in the Diagnostics tab on the DriverStation. */



/* Some c-library functions. Specifically, from <math.h> */
double sqrt(double x); // Returns the square root of "x"
double sin(double x); // Returns the sine of "x", in radians
double cos(double x); // Returns the cosine of "x", in radians
double tan(double x); // Returns the tangent of "x", in radians
double asin(double x); // Returns the arc (inverse) sine of "x", in radians
double acos(double x); // Returns the arc (inverse) cosine of "x", in radians
double atan(double x); // Returns the arc (inverse) tangent of "x", in radians
double pow(double base, double exponent); // Returns "base" raised to the power of "exponent"
double remainder(double a, double b); // Returns the remainder of "a" divided by "b"

That wasn't as long as I feared it might be, so you should all feel lucky. Hopefully, I've covered everything you're ever likely to need in the years to come, so with luck, this could be a useful reference tool in the future. On top of this, there are two PDFs in the "Robotics 2012\Other" folder that should contain some information on some of the WPILibe function, so that would also be worth a look. I hope this is all making sense, and I'll see you guys again when I post tomorrow Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Syntax and Logic Errors

Post  Brendan van Ryn on Wed Feb 29, 2012 8:03 pm

Solving programming errors is the most time-consuming task in coding, especially for robotics, where we cannot know the outcome of a program without actually testing it on the bot. On top of that, there are a wide range of hardware inconsistencies that also cause problems with testing your programs. Generally, the best strategy for dealing with programming errors is to be careful and methodic with your code, reducing the initial risk of errors occur by making sure that everything you write is well thought-out before hand.

There are two categories of errors: syntax errors, and logic errors. Syntax errors are errors with the code itself. The compiler can inform you of syntax errors when you build, and will display them in red text at the bottom of the screen. Additionally, WindRiver will place a red line beside the scroll bar at the position of the error, and will underline the error-line in red. Finally, if you double-click the error text in the build-console window at the bottom (which you can show and hide), WindRiver will jump to that line and highlight the error. Here are some examples of syntax errors:

Code:

x = 5 + * 7; // Addition AND multiplication operators together don't make sense

int i;
float j;

j = 3.14f;
i = j; // Forgot to type-case. The compiler may give an error, or may give a "warning". I'll explain warnings below. Should be: i = (int)j;

encoderName->Reset()
encoderName->Start(); // The line above is missing a semi-colon. Will say "Expected ';' at position x" with "x" set to the column or line

i + 5 = (int)(j * 2); // You can't assign a value to "i + 5". Should be "i = (int)(j * 2) - 5;".

leftMotor->Set(0.5f, -7); // ->Set() only receives one argument, but we passed two.

x = secondEncoder->Reset(); // ->Reset() doesn't return a value, so you cannot assign "x" to it

rightMotor-Set(1.0f); // The programmer missed the ">" part of the arrow

backMotor.Set(0.4f); // The dot was used instead of an arrow. See below.

The last example is worth extra explanation. In this case, the compiler would display an extremely vague error message that does not make sense to you (something along the lines of "accessing member in something that is not an enum, union, or struct". While this particular message doesn't mean much to you, unless you're further along in your C-programming books than I thought, the important part is the line number. Most of the time, the compiler should do a good job of explaining to you what the error is. However, in cases where the error message is meaningless to you, you're free to use your own judgement. For example, consider the code below:

Code:

void OperatorControl(void)
{
   while(IsOperatorControl())
   {
      leftMotor->Set(1.0f); // Drive forward continously
      rightMotor->Set(1.0f);
   // The programmer forgot the closing brace "}"
}

void Autonomous(void) // First error occurs here
{
   // Empty autonomous
}

In the above example, the closing brace is missing. Missing a closing or opening brace is a really bad error because it causes nearly every line of code following the error to be incorrect. Forgetting a brace at this position would produce a long list of errors that would confuse anyone who doesn't have a deep understand of C/C++ syntax. However, if you were to go to the line of the first syntax error in the compiler window, it would take you--in this case--to the "void Autonomous(void)" line. From there, you could back track only three lines to see that you did, in fact, forget to put a closing brace for the while loop. With just a bit of thinking, you can generally find the error yourself, just using the compiler to give you a starting point. This is an absolute worst-case scenario involving the brace (it could only be worse if you forget multiple closing braces, or some opening ones too). In the case of a lesser errror, like the "backMotor.Set(0.4f)", the compiler would give you the correct line at least, allowing you to see for yourself that you put a dot instead of an arrow. Just use your judgement instead of relying on the compiler to tell you exactly what to fix Wink

In addition to errors, the compiler can also detect syntax constructs that are probably errors, but might be correct. These are calling "warnings". Warnings, unlike errors, do NOT stop the program from compiling, but can still cause logic errors later. Warnings appear in the same places as errors, but are in an orange-yellow colour instead. An example of a warning forgetting to convert values using type-casting. While the program can still run without the conversion in place, and it might even work properly, some cases will cause significant logic errors later. Therefore, it is generally best to correct any warnings and treat them like errors. Another common warnging we get in WindRiver is when we declare a variable, but never use it for anything. This is usually only the case when we comment out the code that uses the given variable, but it does help keep things clean by telling us which variables aren't technically needed.

As for logic errors, these simply refer to the code acting in a manner that is different from what was intended. The compiler cannot detect this kind of error. A couple of examples of logic errors are as follows:

Code:

// Example one
int i;

for(i = 0; i <= 10; i++)
{
   // Perform some action
}


// Example two
if(limitSwitch->Get()) leftMotor->Set(0.0f);
if(leftStick->GetRawAxis(1) > 0.0f) leftMotor->Set(0.5f);
else leftMotor->Set(0.0f);


// Example three
float x = leftStick->GetRawAxis(2);

if(x > 0.0f) leftMotor->Set(-1.0f);
else if(x < 0.0f) leftMotor->Set(1.0f);


// Example four
int x = 5;

while(x < 4) // Do something


// Example five
int i;

for(i = 10; i > 0; i++) // Do something

So let's look at the problems with each of the above. Note that, unless I made a mistake, NONE of the above code would produce any syntax errors from the compiler. However, none of these examples would run as you expected. The first example is not technically incorrect at all. Instead, it is a common error for people as they might think that the loop would run ten times. This is incorrect; the loop would actually run elevent times: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, and 10 makes ELEVEN numbers, if you count them. To make it run ten times, you could put either "i <= 9" or "i < 10". In the second example, the programmer is trying to run a motor at half speed whenever the joystick is deflected to one side, but to stop it whenever a certain limit switch is triggered. However, because they didn't put an "else" and the ->Set(0.5f) call comes AFTER the ->Set(0.0f) call, then the limit switch will only stop the motor if the joystick isn't being moved. This defeats the purpose of the switch. The corrected code for each example is shown below. Adding the "else" connects all of the conditions together. This way, only ONE of the statements can run at a time.

In the third example, the programmer learned a lesson from before and used an "else" statement. They're goal now, it seems, is to set a motor to run either forward or backward depending on the value of "x". However, while the programming IS checking for x > 0.0f and x< 0.0f, they are forgetting x == 0.0f--or a terminating else. This means that the motor will always keep running the in the last direction it was set, even when the joystick is stationary.

In the fourth example, "x" is being set to five, and the loop below it is set to exit as soon as "x" becomes equal to or greater than four. As a result, the loop never runs at all. This example seems obvious, but in a real-life scenario, there might be many, many lines of code separating these two instances. If-statements, input from hardware, and other things might make the problem even harder to find. Therefore, it's important to make sure that your conditions are correct.

In the fifth example, the programmer was trying to make a loop that counts down from 10 to zero--and they remembered to use i > 0 instead of i >= 0 so that it runs exactly ten times instead of eleven times. However, instead of telling "i" to decrement each time, they are incrementing it--probably out of habit. This means that the condition, "i > 0", will always be true because "i" will continue to get larger indefinitely (although, it'll technically wrap around after it exceeds 2^31 - 1). This is called an "infinite loop", which I showed to you before. Since the loop never finishes, no other code ever gets to run.

Code:

// Corrections to the above

// Example one -- change "<=" to "<"
int i;

for(i = 0; i < 10; i++)
{
   // Do something
}


// Example two -- add an "else" to the second "if"
if(limitSwitch->Get()) leftMotor->Set(0.0f);
else if(leftStick->GetRawAxis(1) > 0.0f) leftMotor->Set(0.5f);
else leftMotor->Set(0.0f);


// Example three -- add a terminating "else" to set to zero
float x = leftStick->GetRawAxis(2);

if(x > 0.0f) leftMotor->Set(-1.0f);
else if(x < 0.0f) leftMotor->Set(1.0f);
else leftMotor->Set(0.0f);


// Example four -- depends on what the programmer actually wanted, but this will at least allow the loop to run
int x = 2;

while(x < 4) // Do something


// Example five -- "i--" instead of "i++"
int i;

for(i = 10; i > 0; i--) // Do something

Hopefully, this illustrates that the compiler can't always tell you when something is wrong. Even in the case of the fourth example I gave, it's impossible to know what the programmer was actually trying to do. Therefore, there are a wide range of errors that can occur because your algorithm or method of solving a problem has a flaw in it or is incorrect. Debugging and finding errors in a program can be quite a difficult process, but the best method is to break things down into small pieces and test each small section as you write it. That way, you always know which section of code caused the problem, because it's the section you just added. As always, I'm here to answer questions or take suggestions for tutorials Wink

Additionally, not many people have been showing up to the programming-member calls I've been making. I won't name names, but it tends to be the same two or three programmers each time. I hope the other new programmers start showing more committment, but if not, it's looking like the aforementioned members will be taking on the lead-programming roles next year.

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

A Tank Drive Example

Post  Brendan van Ryn on Thu Mar 01, 2012 4:49 pm

We've just about covered everything I have planned. I'd like to continue on to do some actual, hands-on practice later on in the year. I'm confident we'll have time to continue the in-class lessons after our two competitions, and that'll also free us up to perform tests on the robot itself. For today, I'm going to walk you guys through a simple example of a few things we can program on a robot with the knowledge you should already have by now.

First, let's remember the basic layout for the robot code:

Code:

// FIRST library for objects and functions
#include <WPILib.h>

class SimpleTracker : public SimpleRobot
{
   // Objects and class-level variables
   
   public:
   SimpleTracker(void)
   {
      // Objects initialized here
   }
   
   void OperatorControl(void)
   {
      // Tele-op variables
      
      while(IsOperatorControl)
      {
         // Prevent robot from disabling automatically
         GetWatchdog().Feed();
         
         // Teleop code here
      }
   }
   
   void Autonomous(void)
   {
      // Autonomous variables
      
      while(IsAutonomous)
      {
         // Prevent robot from disabling automatically
         GetWatchdog().Feed();
         
         // Autonomous code here
      }
   }
};

As we actually develop the code, we'll focus on small sections of this overall layout at a time. Specifically, we'll be looking at the variable declarations section, the SimpleTracker(void) section, and the void OperatorControl(void) section. We will not be coding anything for autonomous. Sometimes, I will write a section, omit some code, and then write the next section. In this case, the omitted code will be indicated by a comment with an ellipse: "// ...". After this tutorial is done, I'll post the whole thing again.

The first thing we're going to do is program a tank-drive system. A tank drive system runs off of two joysticks, and at least two motors--one on the left, and one on the right. This is NOT possible on this year's robot, because our robot's drive motors are separated into front and brack rather than left and right. However, it's a common system, and very simple, so it's a good candidate for next year's robot. Essentially, the left joystick in this setup controls the left motor, and the right joystick runs the right motor. This allows you to drive forward and backward by moving the joysticks in the same direction, and to turn by moving the joysticks different amounts or in oppositve directions. This system of driving is also highly intuitive.

As we mentioned, we will need two joysticks and two motors for this robot. Therefore, the first thing we should do is indicate which ports each of these pieces of hardware connect to. As before, we will define them using macros so they can be easily changed later if necessary. These definitions go BEFORE the line "class SimpleTracker...". If you don't remember how macro definitions work, you should review the tutorial I did on the Preprocessor.

Code:

#define LEFTJOYSTICKPORT 1 // USB port for left joystick
#define RIGHTJOYTSITCKPORT 2 // USB port for right joystick

#define LEFTMOTORPORT 1 // PWM port for left motor
#define RIGHTMOTORPORT 2 // PWM port for right motor

We could've assigned these peripherals to any port we wanted, but for the sake of being simple, we just went with the first two. Note how comments were used, clear names were given to the macros, and the motors and joysticks were separated by a blank line to keep things organized. Also, since joysticks use a different kind of port from motors, it's okay that the left joystick and left motor are both defined as one.

Our next task is to create the objects and initialize them. You should know how to do this, and if not, you should review the tutorial I did on Objects. The objects are declared, in a similar way to variables, right after the "class" line and before the "SimpleTracker(void)" line. The initializations take place INSIDE of the "SimpleTracker(void)" constructor.

Code:

class SimpleTracker : public SimpleRobot
{
   Joystick *leftJoystick; // Left driving joystick
   Joystick *rightJoystick; // Right driving joystick
   Jaguar *leftMotor; // Left driving motor
   Jaguar *rightMotor; // Right driving motor

   public:
   SimpleTracker(void)
   {
      leftJoystick = new Joystick(LEFTJOYSTICKPORT); // Initialize joysticks
      rightJoystick = new Joystick(RIGHTJOYSTICKPORT);
      
      leftMotor = new Jaguar(LEFTMOTORPORT); // Initialize motors
      rightMotor = new Jaguar(RIGHTMOTORPORT);
   }
   
   // ...
}

Again, comments and clear, properly capitalized names are used in declaring the variables. The initializations are also separated into sections according to the type of hardware; however, I generally don't separate the variable declarations. Whether you do or not is up to you though. Again, as per the Objects tutorial, we simple use the name of the type of object we want when delcaring the hardware. In the case of the motors, we could have used "Victor" instead, but we almost always use Jaguars for drive motors. Finally, notice the "// ..." comment that indicates that I left out the Autonomous and OperatorControl functions to save space and time.

Now that we have the hardware we need, we can program the tank steering. Since the tank steering uses the joystick positions, we should start by getting and storing these positions. To do so, we will need variables to hold that data. We're interested, specifically, in the y-axes of the two joysticks, so we'll declare two variables called "lefty" and "righty" to store the left and right y-positions, respectively. Experience, looking at past code, and experiments, if necessary, will reveal that the joystick axes give positions between -1 and +1, with decimal places (which you should also know from one of my tutorials). Therefore, we will declare two "float" variables. If you don't remember how to declare or use variables, go back and review the Variables tutorial.

Code:

void OperatorControl(void)
{
   // Teleop variables
   float lefty; // Position of y-axis on left joystick
   float righty; // Position of y-axis on right joystick
   
   while(IsOperatorControl())
   {
      // Store joystick y-axis positions
      lefty = leftJoystick->GetRawAxis(2);
      righty = rightJoystick->GetRawAxis(2);
   }
}

As you should know from either looking at this year's code or from reading the tutorials, or even from reading the drop-down list that appears when you type the arrow after the name of a joystick, the function to get a reading from a joystick axis is ->GetRawAxis(). You should also know, or know how to check, that "2" is the value for the y-axis ("1" is for the x-axis, and "3" is for the z-axis). Therefore, this code will store the joystick positions in the two variables each new time through the loop. Any movement on the joysticks will update to these variables, because the "IsOperatorControl()" loop runs continuously.

Now that we have the joystick positions, we just have to pass them to the appropriate motor. Again, as you should know from the tutorials, the function to set a motor is "motorName->Set(value)", where "value" is the power setting you want, between -1 and +1. Therefore, the tank drive should be as simple as this:

Code:

void OperatorControl(void)
{
   // Teleop variables
   float lefty; // Position of the y-axis on the left joystick
   float righty; // Position of the y-axis on the right joystick
   
   while(IsOperatorControl())
   {
      // Store the joystick y-axis positions
      lefty = leftJoystick->GetRawAxis(2);
      righty = rightJoystick->GetRawAxis(2);
      
      // Drive the robot in tank-style
      leftMotor->Set(lefty);
      rigthMotor->Set(righty);
   }
}

This is almost complete, but if you were to test this code, you would probably find that the drive is backwards--pushing a joystick forward moves that side backwards. This is because the values for the joysticks are backwards: up is negative and down is positive. The simple solution is to negate the values being sent to the motors, like so:

Code:

while(IsOperatorControl())
{
   // Store the joystick y-axis positions
   lefty = leftJoystick->GetRawAxis(2);
   righty = rightJoystick->GetRawAxis(2);
      
   // Drive the robot in tank-style
   leftMotor->Set(-lefty);
   rigthMotor->Set(-righty);
}

You should know from the Variables tutorial that this does not change the value in lefty or righty, but just changes the value sent to the motors.

And that's all there is to it. Tank driving is extremely simple compared to other drive modes. However, this tutorial has hopefully showed that you do really know enough to accomplish real, useful tasks with the robot code. I feel like there is a bit of mysticism with new programmers towards coding, especially on a robot where things can break if something goes wrong. Nonetheless, I've shown you a simple, useful example that I'm confident all of you are capable of. If you have any questions, feel free to post, and if you've forgotten any of the things used in this example, review the previous tutorials please before asking me about it. I have one more example planned, and some in-class stuff prepared, but otherwise, I'm out of tutorials. I hope these have been helpful, and let me know if there's a topic I've forgotten that you would like me to write a tutorial for Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

An Example: Arm Movement

Post  Brendan van Ryn on Fri Mar 02, 2012 7:08 pm

Today, I plan to build on the example from yesterday. This time, we're going to add code for manually controlling an arm of some sort using joystick buttons, as well as moving the arm to a couple of pre-set positions using an encoder.

For this tutorial, we'll be adding to the code from the last tutorial. Just to remind you, it looks something like this:
Code:

// Include object definitions from FIRST
#include "WPILib.h"

// Define port numbers
#define LEFTJOYSTICKPORT 1 // Port for left driver joystick
#define RIGHTJOYSTICKPORT 2 // Port for right driver joystick

#define LEFTMOTORPORT 1 // Port for left drive motor
#define RIGHTMOTORPORT 2 // Port for right drive motor

class SimpleTracker : public SimpleRobot
{
   // Class variables and objects
   Joystick *leftJoystick; // Left driver joystick
   Joystick *rightJoystick; // Right driver joystick
   Jaguar *leftMotor; // Left drive motor
   Jaguar *rightMotor; // Right drive motor
   
   public:
   SimpleTracker(void)
   {
      // Initialize objects
      leftJoystick = new Joystick(LEFTJOYSTICKPORT); // Joysticks
      rightJoystick = new Joystick(RIGHTJOYSTICKPORT);
      
      leftMotor = new Jaguar(LEFTMOTORPORT); // Motors
      rightMotor = new Jaguar(RIGHTMOTOPORT);
   }
   
   void OperatorControl(void)
   {
      // Teleop variables
      float lefty; // Y-axis of left joystick
      float righty; // Y-axis of right joystick
      
      while(IsOperatorControl())
      {
         // Prevent automatic disabling
         GetWatchdog().Feed();
         
         // Get joystick y-axis positions
         lefty = leftJoystick->GetRawAxis(2);
         righty = rightJoystick->GetRawAxis(2);
         
         // Drive tank-style
         leftMotor->Set(-lefty);
         rightMotor->Set(-righty);
      }
   }
   
   void Autonomous(void)
   {
      // Autonomous variables
      
      while(IsAutonomous())
      {
         // Prevent automatic disabling
         GetWatchdog().Feed();

      }
   }
}

As with the previous tutorial, the first step in this tutorial will be to add our hardware. At the moment, we just need to add the motor that runs the arm, but we might as well add the encoder now as well as we know we'll need it later. I'll show you the port number definitions, object declarations, and initializations all at once, as it's something you should be familiar with from the previous tutorial:

Code:

#define ARMMOTORPORT 3 // PWM port for arm motor

#define ARMENCODERPORTA 1 // Digital IO port for arm encoder (first channel)
#define ARMENCODERPORTB 2 // Digital IO port for arm encoder (second channel)

class SimpleTracker : public SimpleRobot
{
   // Class variables and objects
   Joystick *leftJoystick; // Left driver joystick
   Joystick *rightJoystick; // Right driver joystick
   Jaguar *leftMotor; // Left drive motor
   Jaguar *rightMotor; // Right drive motor
   Jaguar *armMotor; // Arm raising/lowering motor
   Encoder *armEncoder; // Arm position encoder
   
   public:
   SimpleTracker(void)
   {
      // Initialize objects
      leftJoystick = new Joystick(LEFTJOYSTICKPORT); // Joysticks
      rightJoystick = new Joystick(RIGHTJOYSTICKPORT);
      
      leftMotor = new Jaguar(LEFTMOTORPORT); // Motors
      rightMotor = new Jaguar(RIGHTMOTOPORT);
      armMotor = new Jaguar(ARMMOTORPORT);
      
      armEncoder = new Encoder(ARMENCODERPORTA, ARMENCODERPORTB); // Encoders
      
      // Initialize the encoder
      armEncoder->Reset();
      armEncoder->Start();
   }
   
   // ...
}

The only major thing to note about the above changes is that, as I mentioned in the Objects tutorial, encoders receive TWO port numbers. This is because the encoder has to send two pulses that are 90º (1/4) out of phase, which is hard to explain without getting into periodic functions and trigonometry, so we'll just leave it at "encoders need two port numbers". The ->Reset() and ->Start() calls should be familiar, also from the Objects tutorial: they start the encoder's pulse count at zero and tell it to start counting pulses.

Now, the first thing we're going to do is incredibly simple. At first, we're just going to program the arm to move up when we press one button on the joystick, and down when we press another button the joystick. As with port numbers, we generally make buttons into macros for the sake of being able to change it again later easily. To make things even easier, instead of just putting the button number, we can put the entire ->GetRawButton() call, which means we can also easily change which joystick we use. The definitions we'll use in this example are below:

Code:

// Buttons
#define ARMUPBUTTON leftJoystick->GetRawButton(5) // Button to move arm up
#define ARMDOWNBUTTON leftJoystick->GetRawButton(4) // Button to move arm down

Once the buttons are defined, the code to run the arm is actually incredibly simple. If one button is pressed, we move the arm up; if the other button is pressed, we move the arm down; if neither button is pressed, we stop the motor. I'll show the code section itself. You should realize, on your own, where this could should be inserted (in the OperatorControl() function, as it's own new section after the tank-drive code). Again, I'll show the whole program later.

Code:

// Move the arm manually
if(ARMUPBUTTON) armMotor->Set(0.3f);
else if(ARMDOWNBUTTON) armMotor->Set(-0.3f);
else armMotor->Set(0.0f);

Forgetting the final "else" is a common programming error, and it would make it impossible to stop the arm from moving once it starts--it would only be possible to reverse its direction. Now that this is working, we'll write in the button to set the arm to a preset position based on an encoder reading. The mechanics of this are pretty simple. First, we write the macro for the button; then, we write code so that pressing the button sets a variable to a certain target position value, which we should also write a macro for. In this way, for each position we need, we create a button that sets the variable to that encoder value. Once we can set the position, we can write the code to move the arm to that position.

We're going to call the variable that holds the target position "armTarget". For simplicity, we'll assume that our encoder gives us a value between zero and 256, which is also the valid range for our target positions. For now, we're only going to program one button: a setting to position the arm at it's halfway point. However, as I'll illustrate at the end, adding a new position is incredibly simple--and so is changing that position. So, let's start with our macros:

Code:

// Buttons
#define ARMUPBUTTON leftJoystick->GetRawButton(5) // Button to move arm up
#define ARMDOWNBUTTON leftJoystick->GetRawButton(4) // Button to move arm down
#define ARMMIDDLEBUTTON leftJoystick->GetRawButton(3) // Button to move arm to middle position automatically

// Arm positions
#define ARMMIDDLEPOSITION 128

Other button and position macros can be defined as needed. As always, remember good naming, proper organization, and that the macros do NOT have semicolons after them. The particular buttons and arm position we used here were arbtitrary--any would do. Now, let's write the simple if-statement that sets our target position, and declare the variable that will hold that position:

Code:

void OperatorControl(void)
{
   // Teleop variables
   float lefty; // Y-axis of left joystick
   float righty; // Y-axis of right joystick
   int armTarget = -1; // Target encoder position for arm
   
   while(IsOperatorControl())
   {
      // Prevent automatic disabling
      GetWatchdog().Feed();
      
      // Get joystick y-axis positions
      lefty = leftJoystick->GetRawAxis(2);
      righty = rightJoystick->GetRawAxis(2);
      
      // Drive tank-style
      leftMotor->Set(-lefty);
      rightMotor->Set(-righty);
      
      // Move the arm manually
      if(ARMUPBUTTON)
      {
         armMotor->Set(0.3f);
         armTarget = -1;
      }
      
      else if(ARMDOWNBUTTON)
      {
         armMotor->Set(-0.3f);
         armTarget = -1;
      }
      
      else armMotor->Set(0.0f);
      
      // Set the arm target position
      if(ARMMIDDLEBUTTON) armTarget = ARMMIDDLEPOSITION;
   }
}

Here, we declared "armTarget" to store the position (an int, because you can't have fractional parts of an encoder pulse), and put in the if-statement to set the value of this new variable to the middle position when it is pressed. Now, you might think of putting an "else" in to un-set armTarget when we want manual control back, but this has the drawback of requiring the driver to hold the button down until the arm reaches its destination. This is undesirable. Therefore, I set it up so that pressing the manual movement buttons sets armTarget to -1, the same as its initialization value. That way, one we write the approach code, the arm will continuously adjust to reach its target position until one of the manual movement buttons is pressed, which released it. Not only does this provide a good "cancel" mechanism, but we already NEED the arm to stop trying to move to a preset position once we use the manual buttons--otherwise, we would move to a new position, only to have the arm go right back once the button is erased. So, we set armTarget to -1 when we DON'T want the arm to move automatically, which is easy to check for with an if-statement like:

Code:

if(armTarget != -1)
{
   // Approach code here
}

So the only question is, why -1? Well, since the only possible values for the arm position from the encoder are in the range 0-256, it is impossible to get an encoder reading or a target position of -1, so it does not interfere with the normal operation of the approach code, which we will write now.

The obvious way to make this work is to get and store the encoder position, and move the arm in one direction or the other until it matches with our target position. The code for that would look like this (again, the dots are used to indicate omitted code):

Code:

void OperatorControl(void)
{
   // Teleop variables
   float lefty; // Y-axis of left joystick
   float righty; // Y-axis of right joystick
   int armTarget = -1; // Target encoder position for arm
   int armPosition; // Position of arm according to encoder
   
   while(IsOperatorControl())
   {
      // Prevent automatic disabling
      GetWatchdog().Feed();

      // ...
      
      // Get and store position of arm
      armPosition = armEncoder->Get();
      
      // If a target position has been set
      if(armTarget != -1)
      {
         // Move forward or backward, as necessary. Stop when finished.
         if(armPosition < armTarget) armMotor->Set(0.3f);
         else if(armPosition > armTarget) armMotor->Set(-0.3f);
         else armMotor->Set(0.0f);
      }
   }
}

You should know from the Objects tutorial that ->Get() retrieves the pulse count from an encoder. Additionally, depending on how the motor and encoder are wired, the power settings in the ->Set() function may need to be reversed. This code assumes that, when the arm moves in the direction of a positive power setting, the encoder value also increases. If this is not the case, you can reverse the power settings, or the encoder port numbers, or physically switch the wiring on either the enoder or the motor. Note again how the final "else" is important, because it stops the motor when it has reached its target.

While this code is technically correct, it probably won't work. The arm will most likely bounce up and down around the target position as it reverses direction back and forth to reach the position it needs to be in. There are a lot of techniques to fix this--most of which involve mathematic scaling, and some of which involve calculus--but the simplest solution, and a component of the more complicated solutions, is to create a "deadband". A deadband allows the arm to be one or two pulses away from where it is supposed to be. By giving the arm room for error, it doesn't need to "hunt", as the bouncing is called, because it now has a larger range in which it can stop. Deadbands should be defined as macros, and they are implemented like so:

Code:

// Hardware thresholds
#define ARMDEADBAND 2 // Allow this many pulses of error for the arm, in either direction

// ...

if(armTarget != -1)
{
   // Move forward or backward, as necessary. Stop when finished.
   if(armPosition < armTarget - ARMDEADBAND) armMotor->Set(0.3f);
   else if(armPosition > armTarget + ARMDEADBAND) armMotor->Set(-0.3f);
   else armMotor->Set(0);
}

Hopefully the math isn't too hard to understand, but we'll try an example. If "armTarget" is 128, and "armPosition" is 127, then normally, since the latter is less than the former, the motor would try to move up to increase "armPosition" to 128. However, we changed the condition to subtract the ARMDEADBAND from "armTarget". This evaluates to 128 - 2 = 126. "armPosition", at 127, is NOT less than 126. The reverse happens for the greater-than condition, which effectively creates a four-pulse area of "dead" arm motion. While this would work fairly well, especially if we lowered the movement speed, it might not be enough. Additionally, we would generally want to move the arm as fast as we could. The only way to compensate, using this method, would be to create a very large deadband, which has the side-effect of severely reducing our accuracy. Instead, we tend to run the motor at a set maximum speed (we'll keep it at 30%) until it's within a certain distance of its target position. Once it's within that target position, we slow it down. The closer it gets to its goal, the slower we go, until it's within its deadband and we stop.

To illustrate this, let's consider our example above. Given a full range of 256 pulses, we don't want to slow down too early, so let's make the distance at which we start slowing down 30 pulses. This distance is called our "approach distance", and will be another macro:

Code:

// Hardware thresholds
#define ARMDEADBAND 2 // Allow this many pulses of error for the arm, in either direction
#define ARMAPPROACHDISTANCE 30 // Start slowing down at this many pulses from the target

Once again, commenting is an excellent idea. So, how do we get this approach system to work? There are a lot of ways to do it, but I prefer the one that is the fewest lines of code and, coincidentally, the most efficient.

The first thing we need to do is figure out how far the arm is from where it should be. For our purposes, we don't care which direction it needs to move, just how far it is. To figure out the distance, we subtract the two values from each other. However, again, we don't care about which is larger; therefore, we always want a positive value. In our real code, we usually write a function called "Abs()" and put our subtraction expression inside of its parentheses when we call it. This is a good way of doing it: the subtraction is done, the result is sent to the function, and the function makes sure that it is positive when it returns the value back to us. However, for the sake of keeping things simple, we'll just put an if-statement in our code that multiplies the result by -1 if it is negative already. This way, the value is always positive. We also need a variable for it, which should probably be an "int", because we're subtracting two other "int"s, so there cannot be any decimal points. However, we're going to re-use it later in an expression that CAN have decimal points, so we'll make it a float and type-cast it (if you don't remember what type-casting is, you should review the Variables tutorial):

Code:

void OperatorControl(void)
{
   // Teleop variables
   float lefty; // Y-axis of left joystick
   float righty; // Y-axis of right joystick
   int armTarget = -1; // Target encoder position for arm
   int armPosition; // Position of arm according to encoder
   float armSpeed; // The calculated speed the arm should travel in auto-mode
   
   while(IsOperatorControl())
   {
      // Prevent automatic disabling
      GetWatchdog().Feed();

      // ...
      
      // Get and store position of arm
      armPosition = armEncoder->Get();
      
      // If a target position has been set
      if(armTarget != -1)
      {
         // Calculate the absolute distance of the arm from it's target position (same as "Abs(armPosition - armTarget)")
         armSpeed = (float)(armPosition - armTarget);
         if(armSpeed < 0) armSpeed = -armSpeed;
         
         // Move forward or backward, as necessary. Stop when finished.
         if(armPosition < armTarget) armMotor->Set(0.3f);
         else if(armPosition > armTarget) armMotor->Set(-0.3f);
         else armMotor->Set(0.0f);
      }
   }
}

Now for the cool part. Our ramping for this type of problem is generally linear--if you graphed the power setting for the motor versus the arm's distance from its target position, it would make a line. I'm not sure there's an easy way to explain this, but our line only has a slope. You should all have done lines before in math class: "y = mx + b". For our lines, "b" is zero, and "y" is not allowed to be larger than a certain amount. For us, our slope is the reciprocal of the approach distance (1/ARMAPPROACHDISTANCE). The "x" in our equation is the distance we just calculated. Finally, "y" is actually the "armSpeed" that we use to move the motor. Watch how this works (armTarget is 128 for all of these):

Position: 0
Distance: 128
Speed: 128 / 30 = 4.26667

Position: 40
Distance: 88
Speed: 88 / 30 = 2.933333

Position: 80
Distance: 48
Speed: 48 / 30 = 1.6

Position: 100
Distance: 28
Speed: 28 / 30 = 0.933333

Position: 120
Distance: 8
Speed: 8 / 30 = 0.2666666

As you can see, this does the job of slowing down as we get closer to the goal position. Something else you might notice is that all of the speed values are LARGER than 1.0 for distances greater than our approach distance. However, one our distance becomes LESS than our approach distance, the speed starts ramping down towards zero. This is exactly what we want, and these values can be sent directly to the motor. The only thing we need to do is limit the maximum speed we can get--those values that are greater than 1.0 don't make sense because our motors only accept values from -1 to +1. So, we would write something like this, which would perform the ramping (slowing down) and deadband operations, and limit the power setting to 0.75 at most (an arbitrary choice):

Code:

// Include object definitions from FIRST
#include "WPILib.h"

// Define port numbers
#define LEFTJOYSTICKPORT 1 // Port for left driver joystick
#define RIGHTJOYSTICKPORT 2 // Port for right driver joystick

#define LEFTMOTORPORT 1 // Port for left drive motor
#define RIGHTMOTORPORT 2 // Port for right drive motor
#define ARMMOTORPORT 3 // PWM port for arm motor

#define ARMENCODERPORTA 1 // Digital IO port for arm encoder (first channel)
#define ARMENCODERPORTB 2 // Digital IO port for arm encoder (second channel)

// Buttons
#define ARMUPBUTTON leftJoystick->GetRawButton(5) // Button to move arm up
#define ARMDOWNBUTTON leftJoystick->GetRawButton(4) // Button to move arm down
#define ARMMIDDLEBUTTON leftJoystick->GetRawButton(3) // Button to move arm to middle position automatically

// Arm positions
#define ARMMIDDLEPOSITION 128 // Middle position for arm auto-movement

// Hardware thresholds
#define ARMDEADBAND 2 // Allow this many pulses of error for the arm, in either direction
#define ARMAPPROACHDISTANCE 30.0f // Start slowing down at this many pulses from the target


class SimpleTracker : public SimpleRobot
{
   // Class variables and objects
   Joystick *leftJoystick; // Left driver joystick
   Joystick *rightJoystick; // Right driver joystick
   Jaguar *leftMotor; // Left drive motor
   Jaguar *rightMotor; // Right drive motor
   Jaguar *armMotor; // Arm raising/lowering motor
   Encoder *armEncoder; // Arm position encoder
   
   public:
   SimpleTracker(void)
   {
      // Initialize objects
      leftJoystick = new Joystick(LEFTJOYSTICKPORT); // Joysticks
      rightJoystick = new Joystick(RIGHTJOYSTICKPORT);
      
      leftMotor = new Jaguar(LEFTMOTORPORT); // Motors
      rightMotor = new Jaguar(RIGHTMOTOPORT);
      armMotor = new Jaguar(ARMMOTORPORT);
      
      armEncoder = new Encoder(ARMENCODERPORTA, ARMENCODERPORTB); // Encoders
      
      // Initialize the encoder
      armEncoder->Reset();
      armEncoder->Start();
   }
   
   void OperatorControl(void)
   {
      // Teleop variables
      float lefty; // Y-axis of left joystick
      float righty; // Y-axis of right joystick
      int armTarget = -1; // Target encoder position for arm
      float armSpeed; // Speed to move arm during auto-mode
      
      while(IsOperatorControl())
      {
         // Prevent automatic disabling
         GetWatchdog().Feed();
         
         // Get joystick y-axis positions
         lefty = leftJoystick->GetRawAxis(2);
         righty = rightJoystick->GetRawAxis(2);
         
         // Drive tank-style
         leftMotor->Set(-lefty);
         rightMotor->Set(-righty);
         
         // Move the arm manually
         if(ARMUPBUTTON)
         {
            armMotor->Set(0.3f);
            armTarget = -1;
         }
         
         else if(ARMDOWNBUTTON)
         {
            armMotor->Set(-0.3f);
            armTarget = -1;
         }
         
         else armMotor->Set(0.0f);
         
         // Set the arm target position
         if(ARMMIDDLEBUTTON) armTarget = ARMMIDDLEPOSITION;
         
         if(armTarget != -1)
         {
            // Determine absolute distance of arm from target position
            armSpeed = (float)(armPosition - armTarget);
            if(armSpeed < 0) armSpeed = -armSpeed;
            
            // Calcualte a power setting based on this distance, and "clamp" to a maximum of 0.75f
            armSpeed = armSpeed / ARMAPPROACHDISTANCE;
            if(armSpeed > 0.75f) armSpeed = 0.75f;
            
            // Move arm as necessary
            if(armPosition < armTarget - ARMDEADBAND) armMotor->Set(armSpeed);
            else if(armPosition > armTarget + ARMDEADBAND) armMotor->Set(-armSpeed);
            else armMotor->Set(0.0f);
         }
      }
   }

   
   void Autonomous(void)
   {
      // Autonomous variables
      
      while(IsAutonomous())
      {
         // Prevent automatic disabling
         GetWatchdog().Feed();

      }
   }
}

I wrote that all out from memory and logic right now, so please let me know if I made a mistake. Hopefully you can see now where every line came from. This type of approach code is generally (somewhat incorrectly) referred to as a PID loop, whose meaning is not important, but at least you know what they're talking about when someone mentions it. The only change I made was adding the code that divides the distance we calculated by the approach distance, and the line that makes sure that it doesn't exceed 0.75f. I think we went through this example nice and slowly, and I hope that it all made sense. If anything was confusing, please let me know or ask me in person sometime--I'll do what I can to explain it more. At any rate, I'm confident you guys all have the knowlegde base required to understand what we just did here, and you should be proud of yourselves. This type of approach system is almost EXACTLY what we used last year to extend and retract our arm to/from preset positions, as well as to pivot our arm up and down. In fact, while the variable and macro name might change, you'll probably see this method used in a lot of different places in last year's code and this year's code (look at the TrackFrontSteering() and TrackRearSteering() functions). This motor/encoder pairing system can be used for almost anything that needs to move to a preset distance--we even use something like this when the robot is trying to turn to face the target this year. So I hope you feel accomplished now that you understand one of the most complicated--and one of the most commonly used--techniques in the FIRST robotics business Wink

A few quick things to wrap up. First, adding another button is incredibly easy. I encourage you guys to think about it yourselves, but if you want to check your answers, here you go:

Code:

// Buttons
#define ARMMIDDLEBUTTON leftStick->GetRawButton(3) // Old button from above example
#define ARMHIGHBUTTON rightStick->GetRawButton(3) // New button being added (right joystick, for no particular reason)

// Arm positions
#define ARMMIDDLEPOSITION 128 // Position from example above
#define ARMHIGHPOSITION 200 // New position being added

// ...

while(IsOperatorControl())
{
   // ...
   
   // Set the arm target position
   if(ARMMIDDLEBUTTON) armTarget = ARMMIDDLEPOSITION;
   else if(ARMHIGHBUTTON) armTarget = ARMHIGHPOSITION;
   
   // ...
}

And that's all there is to it. Those three lines are all it takes: add the button, define the position, and add the if-statement that sets the target variable based on that button press.

Finally, I have exactly one more tutorial planned--another example. There is a chance that I'll think of more, but otherwise, I'm relying on either questions or requests from you guys. If not, we'll just have to wait until after our two upcoming competitions, when we can finally start doing some practical lessons with the actual robot Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

An Example: Encoders

Post  Brendan van Ryn on Sat Mar 03, 2012 7:02 pm

Today's tutorial should be relatively short. We're going to look at something we've done a few times in the past two years: manually programming an encoder. As you all should know, and encoder is something we place on a rotating shaft of some sort to measure how far it has moved. Every time the shaft travels a certain distance, the encoder gives off a pulse. A specific number of pulses are produced for each full revolution of the shaft, and we can use this for various things, like tracking how far we've travelled or moving something to a particular position or measuring something's speed. Usually, encoder's do all of the work for us automatically: we declare the encoder, reset it and tell it start counting pulses, then use ->Get() whenever we want to get the number of pulses that have past since we last called ->Reset(). However, sometimes we make our own encoders when we don't need our pulse count to be as accurate, or if we simply don't have any encoders lying around.

We make encoders by attaching a light sensor in a position that lets it hover over a disc. This disk is made of evenly-spaced black and white stripes. The black stripes do not trigger the light sensor, returning false, and the white stripes DO trigger the light sensor, returning true. By monitoring these differences, we can track the number of stripes that have passed--essentially the same as the number of pulses from an encoder. So, let's start by making our port number macro, declaring our light sensor, and initializing it. We're going to add this to the code from last time, but I'm only going to write out the new code, for the sake of time and space:

Code:

// ...

#define DRIVEENCODERPORT 3 // Port for light sensor for drive wheel encoder

// ...

class SimpleTracker : public SimpleRobot
{
   // ...
   DigitalInput *driveEncoder; // Light sensor for drive wheel encoder
   
   public:
   SimpleTracker(void)
   {
      // ...
      
      driveEncoder = new DigitalInput(DRIVEENCODERPORT); // Digital inputs
   }
   
   // ...
}

The rest of the code we write will be in the Autonomous section this time. It is important to note that the other code is still there, I just didn't type it. Instead, I put the "// ..." markings to indicate where I left stuff out. The reason this is important is that we will still be using the motors we declared in the previous two examples during the autonomous code we'll be writing right now. In this case, the light sensor is being attached to a drive wheel with a segmented disk on it to read. This will allows us to measure the distance the robot has travelled.

The first instinct for approaching this kind of problem might be to create a counter variable that we increment whenever the light sensor is tripped by a white stripe, like:

if(driveEncoder->Get()) pulseCount++;

The problem with this is that the pulseCount would repeatedly increase if the robot ever stopped with the light sensor over a white stripe. More importantly, if the disk has, for example, one white half and one black half, this would give us one pulse per rotation. What we generally do instead is similar to what CD drives do when reading disks: we measure a CHANGE in the light sensor's state, instead of the state itself. This means we get one pulse going from black to white, and another pulse going from white to black. All this requires is that we store the current state of the light sensor (->Get()) and the previous state. Whenever the current state and previous state are different, the state must have changed, so we record a pulse, as simple as that:

Code:

void Autonomous(void)
{
   // Autonomous variables
   int pulseCount = 0; // Number of pulses from encoder
   int currentState = 0; // Current state of light sensor
   int previousState = 0; // Previous state of light sensor
   
   while(IsAutonomous())
   {
      // Prevent automatic disabling
      GetWatchdog().Feed();
      
      currentState = driveEncoder->Get(); // Get current state
      if(currentState != previousState) pulseCount++; // If the state has changed, record a pulse
      previousState = currentState; // Update previous state for next time through loop
   }
}

Hopefully this is pretty straightforward. We store the state of the light sensor, and compare it to the previous state. If they aren't equal, the state has changed, and we record a pulse. Finally, we store the current state in "previousState", as the state during this loop will be the previous state the next time we run through the loop. That's all there is to it. From there, we could make the robot move a preset distance during autonomous and stop by doing something simple like this:

Code:

#define AUTONOMOUSDISTANCE 100 // Number of pulses to move in autonomous

// ...

// If we haven't traveled far enough yet, keep moving
if(pulseCount < AUTONOMOUSDISTANCE)
{
   leftMotor->Set(0.7f);
   rightMotor->Set(0.7f);
}

// Otherwise, stop
else
{
   leftMotor->Set(0.0f);
   rightMotor->Set(0.0f);
}

This encoder works just as well as any other, though with fewer pulses. We can also reset it at any time by adding "pulseCount = 0" after an if-statement. Because of the simple overhead involved in this type of encoder, we generally might make the code into a function, like so:

Code:

// Update an encoder and return the value. Reset if the argument is non-zero.
int DriveEncoder(int reset)
{
   static int pulseCount = 0; // Number of pulses
   static int previousState = 0; // Previous state of light sensor
   int currentState = 0; // Current state of light sensor
   int toReturn; // Value to return to calling procedure
   
   currentState = driveEncoder->Get(); // Get current state of light sensor
   if(currentState != previousState) pulseCount++; // If the state has changed, count a pulse
   previousState = currentState; // Update previous state
   
   toReturn = pulseCount; // Store pulse count in the variable we will return
   
   if(reset != 0) pulseCount = 0; // Reset the count if requested to do so
   
   return toReturn; // Return count to calling procedure
}

This function does almost exactly the same thing as the code we wrote in-line above ("in-line" meaning the non-function version). Note how we used the "static" keyword for the pulse count and the previous state, because we want them to keep track of their values between calls. Finally, the argument passed to the function, if not zero, allows you to reset the pulse count. If this were not included, the robot would need to be rebooted whenever the pulseCount needed to be reset. Now, the Autonomous code can call either of these two as needed:

int pulses;

pulses = DriveEncoder(0); // Gets pulse count WITHOUT resetting
pulses = DriveEncoder(1); // Gets pulse count AND resets

The first line doesn't reset the encoder, and the second line does. That's about all for this tutorial. The only other thing is the matter of determining which direction the encoder is spinning. Like with most things in programming, there are a lot of possible solutions to this problem, but the simplest one is just to call ->Get() on the motor in question. If the motor is moving in one direction, you increment, otherwise, you decrement. For the driving encoder example, just change the if-statement to this:

Code:

if(currentState != previousState) // If the state has changed, record a pulse
{
   if(leftMotor->Get() > 0.0f) pulseCount++; // Moving in the positive direction
   else pulseCount--; // Moving in the negative direction
}

The only thing this doesn't take into account is coasting, where the motor setting is zero but the wheels are still turning. The solution to THAT is to store the motor setting as being either positive (1) or negative (-1), and when the power setting is zero, you leave this variable at its current value (1 or -1). Then, you write "pulseCount += direction". This will either add one or negative one to the count, which allows us to keep track of direction even when coasting. This method of working through a more complicated problem by breaking it into smaller pieces--record pulses, determine direction, account for coasting--is a commonly used one, and is one I suggest adapting as it makes things easier to figure out, makes complex tasks seem much simpler, and makes debuging a lot easier.

Anyway, that's all for today's tutorial. As always, I'm open to suggestions or questions Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Arrays

Post  Brendan van Ryn on Wed Mar 07, 2012 9:39 pm

I was hoping to avoid this topic as it can get complicated, but it's unavoidable now, so I'll just cover the basics. This tutorial is on arrays, and in C, you have a LOT of flexibility with arrays. A tutorial Stuart showed me for C programming said it best: "C is a professional programmer's language". C is designed to be flexible and the let well-versed programmers do what they need to do, however they want to do it. As such, arrays are incredibly flexible in C, but this also makes them more difficult to understand--both because of complexity and because of the vague errors associated with arrays. As such, I was hoping to avoid arrays entirely as we rarely ever use them in robotics, but we used ONE this year, so I'll try to explain it.

Arrays are like declaring large numbers of variables with the same name. For example, I could have an array that is composed of five ints all called "x". You might remember from my variables tutorial that a variable name is used to distinguish one area of memory from another, so how does that work if we have five variables called "x"? Well, what we do is use a number to indicate which one we want. For reasons I'll show you in a bit, the first "x" is zero, the second is one, and the fifth is four.

Let me show you how an array is declared:

Code:

datatype arrayName[size];
int numbers[10]; // Ten ints named "numbers"
float speeds[60]; // Sixty floats called "speeds"
char string[100]; // One hundred chars called "string" (we didn't learn "char", but just go with it)

Like all variables, you place the datatype and the name. The only thing that we add is the square brackets and the number. The number in these brackets tells the compiler how many of that variable we want--the "size" of the array. This value cannot be negative (you can't have -2 variables) and cannot be fractional (you can't have a half of a variable). In the first example, we declare ten variables of type "int" and assign them the name "numbers". How we use these variables, then, is by placing a value in the brackets later on:

Code:

int numbers[10]; // An array of ten integers

numbers[0] = encoderName->Get(); // Store an encoder value in the FIRST variable
numbers[1] = numbers[0] * 3; // Store three times the value fo the first "element" in the second "element"
numbers[2]++; // Increment the third element
numbers[3] = 72; // Store 72 in the fourth element

Note that the line saying "numbers[2]++" is bad because we don't know what is in that spot yet (variables do NOT default to zero), but we'll talk about that later. I'm hoping that this all makes sense so far. To use one of the variables, you just put its number in the square brackets. Once you do that, it acts exactly like any other variable. "numbers[4]" is just a normal int like any other. There is no mysticism, it's just a normal variable.

So two questions arise: why is the first element "0", and why are arrays useful? To answer the first question, we must understand how arrays work within the compiler. When you declare an array called "numbers", that name really refers to the FIRST spot on the array (spots in an array are callled "elements"). You can actually access the value in the first spot using the asterisk, like this:

DISPLAYINTEGER(1, *numbers);

// Same as

DISPLAYINTEGER(2, numbers[0]);

"numbers" by itself would display the ADDRESS of the first element--something to do with pointers that we won't get into. Essentially, unless you know what you're doing, you NEVER use the array name by itself--always specify the number in the brackets (the "index"). What I'm trying to show here is that the value in the brackets indicates and OFFSET from the start--how many elements away from the start it is. Observe:

numbers[0] = 1; // Same as *numbers = 1
numbers[5] = 2; // Same as *(numbers+5) = 1;

It's not important to understand, but that's why the indices start at zero. This can be confusing for some as they are inclined to think that the first element is numbers[1], which is incorrect. Additionally, the LAST element in an array of ten is numbers[9], NOT numbers[10]--that would be the eleventh element, which doesn't exist. For reasons we're about to see, the computer can't tell when you're out of range, so no syntax error occurs. Instead, it is a logic error that is hard to detect and can cause the entire program to crash.

So here's the most important thing: why use arrays? For one thing, they're extremely convenient for storing large amounts of data. For example, the average MP3 file contains one MILLION ints. Declaring that many individual variables is implausible, so an array is used instead. Another, bigger advantage is that the number in the brackets can be an expression. For example:

Code:

numbers[2 * 7 - 10] = 45; // Indexes element 4 (the fifth one)
numbers[x] = 16; // Stores in the element at whatever position is stored in x. If x is 3, we go to the fourth element (element 3)
numbers[y / 2] = encoderName->Get();
numbers[array[x + 1] - 2] = 14; // Uses a variable to access a value in "array", and uses THAT value to index "numbers"

The examples where another variable is being used to index the array are the most important ones. This is the main feature of arrays: you can allow the value of a variable to determine which element (variable) to use. Of course, the same thing could be done with a series of if-statements, but when you have 100, or 1000, or more elements, it becomes impractical.

This indexing is the most common use of a "for" loop. We take the index variable that goes inside the loop, start it at zero (or wherever we want to start in the array), and increment it each time until the end of the array (or wherever we want to stop). For example, if we want to set every element in the "numbers" array from before to zero, we would write:

Code:

int numbers[10]; // Array to initialize
int i; // Index for loop below

for(i = 0; i < 10; i++) numbers[i] = 0; // Go to each index in numbers and set to zero

Now another cool thing is evident here: the "i < 10" matches with the size of the array (10) because the largest integer still less than ten is nine, the last element in our array. A common mistake by new programmers is that the same thing can be accomplished like this:

Code:

int numbers[10] = 0;

That will give an error, and attemping to to that elsewhere without the square brackets will also generate an error (for those experience folds, "numbers" is a constant integer containing an address, making it an immutable pointer). There IS a way to initialize an error when it's first declared though: using braces and commas to contain the values. A couple of examples are shown below to highlight that, like most things in C, you're free to arrange the text however you want:

Code:

int numbers[10] = {0, 5, 10, 15, 20, 25, 30, 35, 40, 45};
float table[15] =
{
   1.2f, 2.4f, 3.6f, 4.8f, 6.0f,
   7.2f, 8.4f, 9.6f, 10.8f, 12.0f,
   13.2f, 14.4f, 15.6f, 16.8f, 17.0f
};

double unknown[] = {3.14159265, 2.71828182, 1.414};

Note how the commas separate the values, and that--in this case--there IS a semicolon after the closing brace. This messes up our rule just a little bit, but we could add a stipulatino to that rule: "Always use a semicolon after a variable declaration". The second example just shows how you can break your initializers up into a grid if you'd like, to make it easier to read. In each case, the first value you give is assigned to element zero (the first), then the second to element one (the second), and so on. In either of the first two cases, the compiler will generate a warning if you provide fewer numbers than the array can hold, and an error (hopefully) if you provide more numbers than the array can hold. In the third case, we neglect to specify a size for the array. Normally, this would generate an error; however, since we provide THREE values to initialize the array, the compiler figures that you need THREE elements in that array, an allocates the memory accordingly. Generally, you CANNOT put variables in the braces of an array initialization, and you generally CANNOT put variables in the declaration to indicate the desired size of the array (to change the array size based on a variable requires the much more complicated dynamic allocation method).

Finally, I hope you saw a pattern in the declaration of "table". Each element is 1.2 greater than the one before it. This can be described mathematically, allowing use to use a loop to initialize it:

Code:

float table[15];
int i;

for(i = 0; i < 15; i++) table[i] = 1.2f * (float)(i + 1);

The reason we add one to "i" is because "i" starts at zero, but our first value needs to be 1.2, so we add one.

There is one final thing I should mention. I really want to stress how important it is to avoid exceeding the "boundaries" of the array. For the "table" array, with fifteen elements, the boundaries are from table[0] to table[14]. A negative index or an index greater than fourteen will not create a syntax error, but will likely make your program crash or otherwise act erratic. An example of such a simple mistake is:

Code:

float table[15];
int i;

for(i = 0; i <= 15; i++) table[i] = 1.2f * (float)(i + 1);

In this case, the programmer made a mistake and put <= when he or she meant <. Eventually, the loop would try to access table[15], which does not exist. This represents an area of memory that does not belong to the array, and trying to alter it can cause unpredictable behaviour. The following example shows a bit of a cheat to make sure you always know the size of an array (but since you declared it, you should know already). I don't encourage using this much. The sizeof() operator has a lot of other, more important uses, but this is technically correct, and should be used if you absolutely feel it is necessary (for anyone wondering, sizeof() is NOT guaranteed to work with dynamically allocated arrays, but it should. Just be careful that it's not returning the size of the POINTER you pass):

Code:

float table[] = {0.0f, 0.0f, 0.0f, 0.0f};
int i;

for(i = 0; i < sizeof(table) / sizeof(float); i++) table[i] = 1.2f * (float)(i + 1);

This is an example where sizeof() is handy. We didn't specify the size of the array when we declared it, but let the compiler calculate it based on the initialization. Since we don't know necessarily how large the array is, we use sizeof() to find out. This is helpful because we can add more values to the table[] initialization and the for loop will still work correctly. The reason we divided sizeof(table) by sizeof(float) is that sizeof() returns the number of BYTES of memory the array takes up, not how many elements it has. However, if we divide the size of the array by the size of ONE element (whatever the type of the array is), then we can calculate the number of elements, just like that.

So, those are array's at-a-glance. There is a lot more to them than I mentioned, but we don't often have need for them for our purposes in robotics. I do encourage you guys to look into arrays a bit more on your own if you're interested in more general-purpose programming, or just curious, but for robotics, this should do. Again, questions are welcome, a code update has been or will be posted, and I hope to see you all at the competition Wink

Below is the only function we have that uses an array:

Code:

/* Take the camera image and process it, returning the x-position of the largest particle. This should represent the
   horizontal centre of the most in-focus target. Comparing this value to the image width (320) should allow us to
   centre the image by rotating the robot. If the targets are out of view, -1 is returned. */
   int GetProcessedImage(AxisCamera &camera)
   {
      register int i, j, k;
      int numberOfParticles, highestParticle, indices[4];
      double maxHeight = -1, width, height, horizontalPosition, verticalPosition;
      double targetAreas[4];
      
      // Set the target areas to a sentinel value to detect empty positions
      for(i = 0; i < 4; i++) targetAreas[i] = -1.0f;
      
      // Save the image as a BinaryImage Object
      cameraImage = camera.GetImage();
      //binaryImage = cameraImage->ThresholdHSLHUEMIN, HUEMAX, SATURATIONMIN, SATURATIONMAX, LUMINANCEMIN, LUMINANCEMAX);
      binaryImage = cameraImage->ThresholdRGB(REDMIN, REDMAX, GREENMIN, GREENMAX, BLUEMIN, BLUEMAX);
      imaqImage = binaryImage->GetImaqImage();
      
      // Perform the Convex Hull operation
      imaqConvexHull(imaqImage, imaqImage, 1);
      
      // Count the number of distinct sections remaining
      imaqCountParticles(imaqImage, 2, &numberOfParticles);
            
       // Save image to the cRIO's memory (enable for debugging purposes only)
       //cameraImage->Write("camIm4.jpeg");
       //binaryImage->Write("binIm4.bmp");
      
       // For each particle found
       for(i = 0; i < numberOfParticles; i++)
       {
          // Get the width and height of the given particle
          imaqMeasureParticle(imaqImage, i, 0, IMAQ_MT_BOUNDING_RECT_WIDTH, &width);
          imaqMeasureParticle(imaqImage, i, 0, IMAQ_MT_BOUNDING_RECT_HEIGHT, &height);
          
          // Search through array of the four largest particles
          // Add the current one to the array if it overcomes any already there
          // Also store the index of each
          for(j = 0; j < 4; j++)
          {
             // If we find an empty spot, just fill it
             if(targetAreas[j] == -1.0f)
             {
                targetAreas[j] = width * height;
                indices[j] = i;
                break;
             }
             
             // If the current particle exceeds one already in the array
             if(width * height > targetAreas[j])
             {
                // Move all other entries back one spot
                for(k = 3; k > j; k--)
                {
                   targetAreas[k] = targetAreas[k-1];
                   indices[k] = indices[k-1];
                }
                
                // Store the current one
                targetAreas[j] = width * height;
                indices[j] = i;
                break;
             }
          }
       }
      
       // Search through the recorded target candidates
       for(i = 0; i < 4; i++)
       {
          // Skip any empty spots
          if(targetAreas[i] == -1) continue;
          
          // Find the specific target's vertical position
          imaqMeasureParticle(imaqImage, indices[i], 0, IMAQ_MT_CENTER_OF_MASS_Y, &verticalPosition);
          
          // If it's current position is greater than the largest recorded so far, store it as the highest
          if(verticalPosition > maxHeight)
          {
             maxHeight = verticalPosition;
             highestParticle = indices[i];
          }
       }
      
       // Find the x-position of the centre of the top target, with a few assumptions made
       imaqMeasureParticle(imaqImage, highestParticle, 0, IMAQ_MT_CENTER_OF_MASS_X, &horizontalPosition);
       imaqMeasureParticle(imaqImage, highestParticle, 0, IMAQ_MT_BOUNDING_RECT_WIDTH, &maxTargetWidth);
      
       // Free memory
       delete binaryImage;
       delete cameraImage;
      
       // If the largest particle is outside of the threshold, return -1 to indicate that the targets aren't visible
       if(targetAreas[highestParticle] < MINIMUMAREA) return -1;
      
       // Return this x-position
       return (int)horizontalPosition;
   }

Hopefully that all indented properly. This is a complicated function, so I'd like you to just focus on how the array is used. We have two arrays that work together: one stores the top four largest areas of the particles in the camera image, and the other stores the number-indices of each particle. The particle number in the indices[] array at one position matches with the area stored in the targetAreas[] array at that same position. We store negative one in the targetAreas[] array as a "sentinel" value--a value to represent that that spot is empty, because it is impossible to have a negative area. Using this, we store the indices of the four largest particles, find the one highest up (representing the top target, hopefully), and filter out any possible noise. That's all there is to it. "i", "j", and "k" are all index variables for "for" loops, which you should realize have been used a lot in this function--they aren't used anywhere else, I don't believe. If you're wondering why the word "register" is used before "int" when declaring those variables, it tells the compiler to give speed priority to those variables (it's a hint to place these variables in processor registers, or at least fast-access memory, at the compiler's discretion). It's a simple hint often used for variables in for loops to speed up the code a bit, but it doesn't change much (for the experienced programmers: it is also invalid to try to take the address of a "register" variable. "&i" would give an error).

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

At Home Programs

Post  Brendan van Ryn on Sat Mar 10, 2012 6:20 pm

So we've run out of tutorials and well into our competitiont and I'm sure you programmers are wondering what to do with your free time. Well, I've decided to start posting information on some programs you can write at home. Obviously, these will be a bit different from the kinds of programs you'll write for the robot itself, but it'll help you learn the language, its syntax, and how to deal with both kinds of errors I mentioned.

To do at-home programs, you'll first need a compiler. I provided a list of compilers sometime last year on this forum, so check that out. I, personally, prefer DevC++, but it's up to you. Learning how to use each interface might take a bit of time, but some common aspects are: you are writing a C program (.c extension), and you want an empty project with no other settings (just add a new .c file to it). Once you work out the mechanics of your compiler, things should be relatively simple from there.

As we know, the code we write for our robot goes in either SimpleTracker(void), OperatorControl(void), or Autonomous(void). We also have to include the "WPILib.h" file to ensure that we have access to all of the hardware objects. However, a program you write at home will be a bit different. You will be writing a "DOS" application--and application that runs on an old, but still supported, operating system that can run on top of windows. DOS is still widely used, and you can run DOS by clicking "Start", "Run", and entering "command.com". This little black window allows you to run operating system commands, like copying, moving, and editing files, and running programs. Of course, DOS is only available on Windows computers, but other computers should also have a command-line console that is similar in action (you shouldn't have to change anything about your code).

So, for our DOS program code, the basis looks like this:

Code:

// Entry point. Program starts here.
int main(void)
{
   // Our code goes here
   
   // Not necessary, but a function should technically return a value
   return 0;
}

As you can see, it is a much smaller base outline than for the robot (writing GUI applications is much longer, however). Like the "void OperatorControl(void)" function, "int main(void)" is the function that is automatically run when the program starts--no other function has to call it. Unlike the robot code, however, there is only one entry point function. When you create your program .exe file and run it, this is the function that is run, regardless of any factors. To get the program to operate differently depending on parameters is possible, but more advanced. The other main different for this layout is that main() returns a value. This value is only used by debugging programs, or other programs that start your program, so the value you return generally doesn't matter. However, the convention is to return false (zero) normally, and true (non-zero) if an error occurs in your program.

This outline alone is generally pretty useless. For a program to be useful, it needs to be able to take input and give output. In the robot, our input is through hardware objects, and so it our output. For programs you'll write on your home computer, input (for our purposes) will be from the keyboard, and output will be to the screen. To get access to our input and output functions, we include the "stdio.h" header file--"std" for "standard" and "io" for "input/output". First things first, let's run our base code with the header included:

Code:

#include <stdio.h> // Input and output functions

// Entry point. Program starts here.
int main(void)
{
   // Our code goes here
   
   // No error
   return 0;
}

If you compile this in Visual C++ and run it, you should see a black window appear with white text that says "Press any key to continue...". Pressing a key will close the window. If you compiled on DevC++, the window will likely flash up and disappear again. The message Visual C++ displays is actually a function call that it inserts automatically. In DevC++, you must insert it yourself. Adding this to a VisualC++ file will just make the message appear twice, so no harm done, but from no on, I'm going to assume that you are using DevC++:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions (for the system() function)

// Program starts here.
int main(void)
{
   // Code
   
   // Display "Press any key to continue..."
   system("pause");
   
   // No error
   return 0;
}

system() performs a DOS command--in this case, the "pause" command, which displays the "Press any key to continue..." message. Now when you compile and run your program (and remember to recompile each time you change something), it should work fine. So, let's start with output.

Output can be done a variety of ways. We're going to focus on one function in particular because it is flexible and, while less efficient, covers all of the output functionality of the other options: printf. You've seen something like this function in the robot code to display values on the DriverStation, but it has never been used to it's full extent. Here is the basic prototype:

int printf(const char *string, [, value [, value [, ...]]]);

It's a strange looking function, and there's a reason why: after the first argument ("string"), you can send anywhere from zero arguments to sixty-four or so. Exaclty how this works isn't important, but how we use it is. In its simplest form, you can simply put a sentence in quotation marks between the parentheses of the function, and that will appear on the screen, like in our first example:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

int main(void)
{
   // Display the message "Hello World" on the screen
   printf("Hello World");
   
   // Wait for key
   system("pause");
   
   // No error
   return 0;
}

When you run the program, the text that appears is "Hello WorldPress any key to continue...". This is because the "pause" command is printed right after the "Hello World" message. One solution is to put a space after "Hello World" to make the two messages separate. However, a nicer option is to use a special escapement sequence. These are backslash characters followed by a letter that indicate a special character. For example, "\n" moves to a new line; "\t" puts a tab; etc. For more, you can look up "C escapement sequences" or something to get a proper list, but those two are the important ones. With this in mind, we could rewrite the printf() line one of many ways:

Code:

printf("Hello World "); // Space added
printf("Hello World\t"); // Tab added
printf("Hello World\n"); // Move to a new line afterwards

The escapement sequences can appear anywhere in the string you want, and if you want to print an actual backslash, put two "\\".

String output is good, but what about numbers? Many programmers make the mistake of writing:

Code:

printf(45 + 1); // Wrong
printf(x * 2); // Wrong

Those will produce syntax errors. They are not correct. The first argument is supposed to be a string--a value between quotes (""). However, these won't work either:

Code:

printf("45 + 1"); // Literally prints 45 + 1
printf("x * 2"); // Literally prints x * 2

What is typed in the quotes literally appears on the screen EXACTLY as you type it. In order to display a value, we pass them as an additional argument. As we saw before, printf can have zero arguments, or one argument, or two, or three, or more. To display a value, we pass it as one of those optional arguments. However, printf() needs to know where to put the value, and how to format it. For this reason, "format specifiers" exist. If you look up the printf() function online, you'll be able to find information about all of the formatting options and features printf() provides (it's VERY flexible), but we'll just cover two: integers, and floats.

To display an integer, we call printf() just as we normally would. However, when we reach the point in the string where we want the value to show up, we type "%d". The percent sign tells printf() that this is a format specifier. The "d" tells printf() that we are printing an integer (I don't know why this letter was chosen. I think "%i" might also work, but I almost never see it). For those of you wondering how to print a percent sign, it's just like the backslash: put two "%%". Once we put the "%d" marking in the string, we can then pass the value we want displayed there. Be careful: forgetting to provide a value is a common mistake that the compiler WILL NOT catch! Failing to do so will cause random values to be displayed and could crash the program. That being said, here are some examples:

Code:

printf("Value of X: %d", x); // Prints the value of x, if it is an integer
printf("Twice X: %d", x * 2); // Prints the value of x doubled
printf("Loterry numbers: %d, %d, %d", x, y, z); // Prints the values of x, y, and z, in left-to-right order
printf("Percentage: %%%d", myPercentage); // Prints the value of myPercentage

That last example looks a bit weird. The first two percent signs tell printf() to display ONE percent sign. The second is part of the "%d" that tells printf() to display an integer. You can place the "%d" anywhere you would like in the string, and you can put practically as many as you want, as long as there is a matching argument for each "%d" present.

Floating-point values are a bit more involved, because there are a lot more options available in terms of how they are displayed. At it's base, the specifier for floating-point (float or double) values is "%f". This will simply display the number in standard form (no scientific notation) to a set number of decimal places (depending on the computer, it might be fixed to six decimal places or it'll vary depending on the number you display). While this is fine in most cases, you often want more flexibility. One option is to set the number of decimal places to show (fixed) by typing "%0.xf", where "x" is not a variable, but a number that indicates the number of decimal places to used. For example, "%0.2f" will display two decimal places. The zero isn't necessary. It could be written "%.2f". If I remember correctly, the zero just means that the unused spots will be set to zero. Not putting the zero means that unused spots will be blank. Another option is to use "%g" instead, which will either display the value in standard notation, or scientific notation--whichever is shorter. You can alter this by providing a number and a decimal point again, like "%0.5g". This will display the value in standard notation until the number of digits exceeds five--then it will display the value in scientific notation. It's generally best if you just play around with these specifiers to see what they can do, but here are a few examples:

Code:

printf("Change: %0.2f", moneyTendered - moneyOwed); // Monetary values are displayed with two decimal places, usually
printf("Mass of selected atom: %g grams", selectedMass); // Atomic masses are very small, so scientific notation makes sense
printf("Applied force: %0.3g", forceApplied); // In physics, numbers greater than or less than a certain range must be in scientific notation
printf("Answer: %f", answer); // Any vague, unrestricted floating-point value can be left as is

Now, it is extremely important that the type of variable you specify with the percent sign and the type of variable you pass. ints and floats are stored very differently by the computer, so consider some examples:

Code:

printf("%d", (float)2); // Wrong
printf("%f", (int)2); // Wrong
printf("%d", 2); // Will work
printf("%f", 2); // Might work. Might give a warning.
printf("%f", 2.0); // Will work

These are particularly important examples. Passing a float variable "x" when using "%d" is an easy mistake to make--especially if that float usually contains integer values. However, since we're using a constant that we actually entered directly into the code, it highlights the difference between "2" and "2.0". Test this out for yourself. Unless the compiler does a special check for this type of thing, only the third and fifth example should work properly. The fourth might, but I would be doubtful. Hopefully this exmplifies how important it is to specify the right type of variable in your string.

That's about all for output. You should try to test these things out just a bit--maybe perform some calculations and print the result, or do something cool with a loop. The internet is full of ideas. I'll be handing out assignments a bit later along in these lessons, but please do try this stuff out and post under the Questions board if you have difficulties. We're going to move on to input next, and from there, we can start doing examples because everything else is pretty much the same as you've seen in the other tutorials. Essentially, the input and output are all that have changed Wink

Edit: I forgot to add a bit about the return value of printf(). The prototype shows that printf() returns an int, so I'm surprised no one asked exactly what that int is. printf() returns the number of characters actually printed (which varies based on the numbers displayed, of coure). This has some applications, most of which are outside of DOS programs and involve printf()'s cousin sprintf(), but there you have it Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

At Home Input

Post  Brendan van Ryn on Sun Mar 11, 2012 1:01 pm

So, hopefully all of you downloaded a C compiler and test out printf() and the main() structure a bit for yourselves. We now have a basic outline for the programs we'll be writing, as well as a means of outputting results to the screen. Technically, this is enough for us to start making some example programs, but I think we should cover input as well before we move on.

As with output, there are a wide variety of options for taking input for a DOS program. The method we'll use here isn't exactly the most fool-proof, but it is simple and will accomplish what we need. Input is a harder beast to tame than output because it is impossible to know what the user will enter. For example, you might want to take an integer in from the keyboard, but the user types "hello". For this reason, you need to have facilities in place that can guard against invalid input. Thankfully, C has a variety of functions that will not only take input, but will also handle any errors and return a safe placeholder value in case of invalid values.

The function I'm going to introduce you to is scanf(). It is, in many ways, the opposite of printf(), and because of the sheer number of features it provides, it might be a good idea to do a quick internet search on it. Like with the output tutorial, we'll focus on just inputing integer and floating-point numbers, as we won't really ever have need of other datatypes in the robot code. scanf() is defined like this, a prototype that should look familiar:

int scanf(const char *string, [, value [, value [, etc...]]]);

Like printf(), scanf() receives a formatting string between double quotes (""), followed by zero or more arguments that correspond to format specifiers in the string. If successful, scanf() returns the number of items of input that were taken, or a negative error code on failure. To help explain scanf(), we'll look at the two datatypes of interest in particular.

Much like printf(), we tell scanf() to take in an integer by passing "%d" as our string. What's important to remember with scanf(), however, is that it does NOT printf anything to the screen. Typing "scanf("This is a test %d", &x);" does not do what you might think. scanf() will throw away characters matching the "This is a test" substring, then take in an integer. The exact operation of non-format specifiers in scanf() is a bit advanced, and I don't use the function enough to know them by heart. If you really want to know, you can check a library reference on the internet or a C programming book. My point is that, to accomplish the above, you must first enter "printf("This is a test: ");", followed by "scanf("%d", &x);". Now that I've clarified that common error, let's look at a quick example of taking integer input with scanf():

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   // Variable we'll take in
   int x;
   
   // Tell the user to enter a number
   printf("Please enter an integer: "); // Note how we added a space after the colon. This just looks neater.
   
   // Take input and store it in "x"
   scanf("%d", &x);
   
   // Show the value they entered (on a new line "\n")
   printf("\nYou entered: %d", x);
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

As always, commenting is a good habit to get into. A few important things need to be said now. If you run this program, you will see the printf() message displayed. Then, a blinking cursor will be displayed after the colon. If you type, you can enter anything you want in this space, and the backspace key works as well. Once you press enter, scanf() takes that value (specified as an integer by "%d") and stores it in the variable you passed to it. Then, on the next line, that value is printed again to prove that it really was stored in "x". You can take some time entering different things to see what happens when scanf() encounters input that is clearly not an integer--try entering "hello" or "3.14", for example. We'll talk about invalid input and handling it in a minute.

So, before we move on to floating-point values there is one other important difference between printf() and scanf() that I'm sure you have all noticed. With printf(), we just type the name of the variable we want to be displayed (or an expression, like "x * 2"). With scanf(), however, we put an ampersand before the variable ("&x"). The reason for this is that you could imagine the computer replacing the variable "x" with whatever value is in it. For example, if x was equal to five, you could imagine the line as turning into:

printf("\nYou entered: %d", 5);

This isn't what really happens, but if you imagine a variable as just a number, it'll help with this. In the case of scanf(), we aren't looking at the value stored in "x". The value stored there is irrelevant. What we really want to know is WHERE in the computer "x" is stored. That way, scanf() can place the new value entered by the user in that spot. This is what the ampersand is for. When the ampersand is there, instead of "replacing" x with its value, it's ADDRESS--or position in memory--is used. This allows scanf() to change the value of "x" without an equals sign. In reality, pointers and addresses can get quite complex, and no "replacing" actually happens, but I hope that makes sense. Also, beause of this, it does not make sense (and is an error) to type:

scanf("%d", &x * 2); // Wrong. Multiplying a position by two is meaningless.

Unfortunately, the compiler cannot catch these errors--using an ampersand where it isn't neccessary and not using it where it is necessary--even though it can in most other cases. This is because of the optional-parameter style of these functions, which makes it impossible for the compiler to enforce any type restrictions. Trying to type, however, "&x * 2" will give an error about invalid binary operands. To make it easy, just think of the ampersand as meaning "in". With scanf() we are storing a value IN x, so "&x" is appropriate. Meanwhile, with printf(), we are printing the value OF x, so no ampersand is necessary.

Next, floating-point values. Again, much like printf(), scanf() used "%f" to take floating-point numbers as input, and will support scientific notation where a lowercase or capital "e" inside the number indicates the "x10^"--three and a half thousand would be "3.5e3", meaning "3.5x10^3". Also similar to printf, it is possible to specify the number of decimal places to take an such, but the details of this escape me at the moment. In general, you won't need scanf()'s more advanced features, but you're free to research them if you'd like.

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

int main(void)
{
   // Variable we'll take in
   float x;
   
   // Prompt user
   printf("Enter a floating-point value: ");
   
   // Take input
   scanf("%f", &x); // Take in a float and store IN x
   
   // Printf value input on a new line
   printf("\n%f", x);
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

That's really all there is to it. Now, if you pass scanf() multiple format specifiers, it will generally take one value of input per line and store them in order. However, to avoid confusion, I suggest multiple calls to scanf() instead:

Code:

// Instead of...
scanf("%d %f", &x, &y);

// Do...
scanf("%d", &x);
scanf("%f", &y);

Also, just like with printf(), it is very important to pass the right type of variable to scanf(). Failing to do so will either produce garbage, meaningless values in the variables you DO send, or it will cause the program to crash. Again, the compiler can't check types, so it's very easy to do something like the following:

Code:

int x;
float y;

// Long section of code in between

scanf("%d", &y); // Incorrect
scanf("%f", &x); // Incorrect

I don't believe that the above code will cause any problems in terms of crashing, but regardless of what the user enters (apart from, perhaps, zero), neither x nor y will contain valid values that make sense in the context of the program.

Another serious problem with scanf() is the way it behaves when input does not fit the logical datatype you are trying to use. For example, expecting an int but typing a float or a string of characters may just zero the variable passed, but it may also place meaningless values in the variables. To avoid problems, it's best to play around with scanf() and different input patterns on your own. If you are having severe difficulties with scanf(), you can post a question and I'll try to help. Alternatively, you can take input the way I do it.

This method requires more steps, and I don't plan to explain the intricacies of the string/character array duality unique to C, assembly, and other mid-to-low level languages. The main advantage for this method is that it handles invalid input EXTREMELY well.

First, you MUST have <stdlib.h> included, even if you're using VisualC++ and don't need the "system("pause");" line. After that, input looks like this:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   int x; // Actual integer value we want to take in
   float y; // Actual floating-point value we want to take in
   char input[100]; // Temporary array of characters to hold input
   
   // Get the raw input string, limiting it to 98 characters
   fgets(input, 98, stdin);
   
   // Convert to an integer and store result
   x = atoi(input);
   
   // Get more raw input
   fgets(input, 98, stdin);
   
   // Convert to a float and store result
   y = atof(input);
   
   // Display results
   printf("%d %f", x, y);
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

As you should know, "input" is an array with 100 elements, from zero to ninety-nine. We pass this array to fgets(), which takes input, like scanf(), until enter is pressed. The characters entered are stored in "input". For example, if the user enters "3.14", the "3" is stored in input[0], the "." is stored in input[1], and so on. The "\n" character from when the user presses enter is also stored, along with a nul character to terminate ("\0"). However, these details are unneccessary because, for our purposes, we will not be using the data in this array directly. Instead, we pass the "input" array to a conversion function, like so:

int atoi(const char *string); // Converts a string to an integer
int atof(const char *string); // Converst a string to a float

So, when we call fgets(), it fills "input" with the characters the user entered. The second arugment, "98", means that it will only accept ninety-eight characters (any others are just ignored). We use 98 because the array holds 100, and we need room for both the "\n" and the "\0" added to the end. The final argument, "stdin" means "standard input device". This tells fgets() to take the input from the keyboard, rather than a parallel or serial port, or a file. When we pass "input", already filled with the user's data, to the conversion functions, they read through the string and pick out either an integer or floating-point value, respectively. So why go through all of this hassle of using an array, the more complicated fgets(), and the conversion functions to accomplish the same task scanf() is capable of? Well, there are a lot of technical reasons for it. For one thing, it's more efficient in terms of program size and speed. However, more importantly, if you try the above example, you will find that entering nonsense, like "soij##fec54.s5e64", will cause x and y t o be set to zero. This is much better than what scanf() does in terms of safety because we do not get meaningless values--we get a value that is easy to check for, or else use normally. As long as the variable you're passing is an array of type "char", and the size you send to fgets() is correct, there is no practical way for these functions to fail. The only thing I must stress is that you should not try to mix scanf() and fgets() in the same program, as they do not cooperate with each other. (For anyone wondering, fgets() returns a pointer to "input" on success, and NULL on failure; however, we aren't getting into pointers).

So, I believe that's everything. I suggest getting yourself acquainted with scanf(), and then trying out fgets() if you have time. Everyone has their preference, but I know scanf() can mess up sometimes. Nonetheless, I'm happy to answer questions if you have any problems. Tomorrow we'll start using this input/output knowledge and DOS base code for some example programs Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

An At-Home Example

Post  Brendan van Ryn on Mon Mar 12, 2012 4:18 pm

Today, we're going to do a simple example program together. The user is going to enter a whole number, and we are going to display that many terms in the Fibonacci sequence to them. As you should all know, the Fibonacci sequence works by adding the two last terms together to calculate the next term. Mathematically,

tn = tn-1 + tn-2

This produces a sequence that goes: 1 1 2 3 5 8 13 21 34 etc... For our purposes, we're going to say that the first "1" is the second term in the sequence, and the sequence starts at "0", which is not shown there. This is because it gives us a good starting point as the second "1" can be found by adding the first one and the initial zero together (0 + 1 = 1).

So, the way we ALWAYS approach a programming task is to break it into steps that we will code bit-by-bit. Once these pieces are put together, we'll have a working prototype that we can test and adjust as necessary. For this problem, the steps we need to fulfil are as follows:

1) Ask the user for the number of terms to display and store that number.
2) Calculate the Fibonacci sequence, displaying the terms, and stop after the number of terms the user entered in step one.
3) Exit the program.

If you read these steps, each can be broken down further, especially step two. Let's start with the first, however:

1a) Display a message prompting the user for input.
1b) Take the input and store.
1c) Check that the input is valid (in this case, greater than or equal to one) and display an error if necessary.

The third step in this example is very important and something programmers often overlook from time to time. In the algortihm we'll write for actually calculating the Fibonacci sequence, we'll be assuming that the count is greater than or equal to one. This is a problem if the user, for whatever reason, enters a value like -2.563. While I'm pretty certain that scanf() would turn this into -2 (as would the fgets() method), the negative part would lead to an infinite loop (or nearly so). Therefore, it is extremely important to check that valid input is received. In case of a problem while writing this program, I will also advise of you of this: if your program ever does get stuck in a loop and stops responding, pressing control and the pause/break key on your keyboard at the same time will terminate it prematurely.

The third item in our list is really simple (return no error, and display the pause message if necessary), so we don't need to expand on it. The second item certainly does though. I want you to really consider the second item for a moment. If you think about it, calculating the Fibonacci sequence is a distinct problem on its own. It is completely separate from and unrelated to the other two items. In fact, there might be other mathematical or programming problems in which we would also need to calculate a certain number of terms in the Fibonacci sequence. In a case like this, we would generally try to make a generic function that handles the task and gives us back as much information as possible. This way, the function can be copied and pasted (or compiled and linked) into another program we write, without us needing to write it again. However, writing a generic Fibonacci function is a bit more complex, and would require more advanced use of arrays and pointers. Therefore, we will keep it simple and write more of a "subroutine". We will still separate this Fibonacci code into its own function, but we won't worry about making it general.

So, what does the Fibonacci function do to accomplish its goal? Well, we must first decide what information this function will require from the main() function. If we are to calculate a specific number of terms, and the user entered this number in the main() function, then the main() function must pass this value to the Fibonacci function in order for it to know how many terms to calculate. No other arguments should be required. Next, we must decide if this function should return a value. Since this function will write each Fibonacci number directly to the screen itself, we do not technically need any return value. However, it is good practice to return anything you can that would be of some possible use, so we'll return the value of the last term we calculate. Again, while we might not specifically use this value in this example, it is still good practice. So, let's look at the tasks this function we are going to write must perform:

2a) Store two values to represent the values of the current and previous terms.
2b) Start these values at zero and one, repsectively.
2c) Print the current term.
2d) Temporarily store the value of the current term.
2e) Add the previous term to the current term to calculate the new term.
2f) Store the temporarily recorded value of the current term as the new previous term.
2g) Count one term calcaulted.
2h) Repeat "b" to "g" until the number of terms calculated equals the number of terms desired, as passed.
2i) Return the last term calculated.

I hope this process makes sense to you, but to prove that it works, let's run it through on our own. If we can get the correct results by following these steps (an "algorithm", as I mentioned at one of our lessons), then so can the computer:

The previous term is zero, and the current term is one. Displayed: 1 (First value in sequence)
We store the current term temporarily (1) and add the previous value (0) to the current value. 0 + 1 = 1
We move the temporarily-stored value into the previous value. Previous value: 1, Current value: 1
We repeat...
The previous term is one, and the current term is one. Displayed: 1 (Second value in sequence)
We store the current term temporarily (1) and add the previous value (1) to the current value. 1 + 1 = 2
We move the temporarily-stored value into the previous value. Previous value: 1, Current value: 2
We repeat...
The previous term is one, and the current term is two. Displayed: 2 (Third value in sequence)
We store the current term temporarily (2) and add the previous value (1) to the current value. 1 + 2 = 3
We move the temporarily-stored value into the previous value. Previous value: 2, Current value: 3
We repeat...

If you repeat this a given number of times, you would display all of the Fibonacci terms up until that value of n. So, we've confirmed that our algorithm does work. Hopefully, you also noticed something else about our algorithm: even though it stops printing values after the correct number of terms have been displayed, the loop does not exit until AFTER the next term is calculated. Therefore, if the user selects five terms, it calculates six, even if only five are displayed. The significance of this is that, to return the correct "last value", we should actually return the value we marked as the previous value, not the current value. Additionally, you could try to make an escape clause or re-write the loop to avoid wasting extra processing to calculate the extra term, but that's up to you--one added calculation will not make a noticeable speed difference. So, not only has testing this out logically ourselve allowed us to verify our steps, we've also discovered a possible problem with our algorithm and determined how to work around it.

So, it's time to get onto the actual code. We'll start by writing out our base code, as you should try to start memorizing this layout (just as you'll have know the robot code by heart, with time):

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   // Take input
   // Check for errors
   // Calculate sequence
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

I added some comments here that remind us what we need to do. This is a technique that I had seen before and used a bit, but was formally introduced to me by Matt Aceto. This is a very good idea, especially for complicated tasks, because it allows you to write out each step that needs to be done. This way, you never forget a step, and you never rush for fear that you WILL forget a step. You write the code to accomplish the commented task, then remove the comment (although, a better plan would be to simply alter the comment to make it more detailed). Since you should all have practice will prompting for and taking input by now, I'll quickly throw that in for you:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   int termCount; // The number of terms to display
   
   // Prompt user for input
   printf("Enter the number of terms of the Fibonacci Sequence to display: ");
   
   // Take input as an integer
   scanf("%d", &termCount); // Did you remember the ampersand?
   
   // Make sure the input is valid
   if(termCount < 1)
   {
      // Display error message on new line (leave another line for the "pause" command)
      printf("\nError: Input must be a whole number.\n"); // You could also add a "%d" specifier to show the user what they entered incorrectly
      
      // Wait
      system("pause");
      
      // An error occured, so quit early and return non-zero
      return 1;
   }
   
   // Call our function
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

We declared an int to store the number of terms we wanted, and just like in the robot code, we gave it a good name and commented its purpose. Then, we used printf() to ask the user for a value and used scanf() to take in the value itself. The only new part is the if-statement below it. Again, if-statements should not be new to you: if termCount is zero or negative, the condition is true, and the block of code is run; otherwise, the block is skipepd. Inside of the block, an error message is displayed to the user using printf(). A possible addition to this would be to add a bit to the printf() line that also displays the value the user entered, so they understand what was wrong with their input, but error messages are important. I've seen many programmers simply have their programs quit if an error was encountered, which can leave the user confused--this is not good practice. We put another call to system("pause"); in this block because of the line of code below it. In the first tutorial on at-home programs, I mentioned that the main() function should return a different, non-zero value if an error occurs. Well this is a good example of that. The return statement tells any other program that cares that an error occured, while also ending the execution of the main() function (remember how "return" makes a functino quit?). Since we are "quitting" the main() function, it is essentially equivalent to quitting the entire program. This is important because we do not want to run our Fibonacci function (which has a comment placeholder right now) or return zero if an error has occured, so returning early avoids those code lines. Of course, and "else" and another block of code for the lines we want skipped could accomplish the same, but we still need to return non-zero, and this is more efficient and less code to write.

So, you should be able to test this code as it is right now and see it work properly so far: it should take input and either go straight to the "Press any key to continue..." message, or display an error first, depending on what you enter. Now all we need to do is write our Fibonacci function. First, let's declare the function itself without writing any code for it:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Code goes here
}

We've given our function a descriptive name and indicated its arguments and return value. It's parameter, "numTerms", is also clearly named--the number of terms to display. Finally, a quick comment describes the function's purpose. No to write our code based off of our algorithm. First, we said we must store two values: one for the previous term, and one for the current term. This sound ring a bell to you that we need variables--areas of memory that hold data. We also know what to initialize them to: zero and one, respectively.

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
}

Next, we must print the current term:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   
   // Display current term
   printf("%d ", currentTerm); // See how we left a space so the numbers aren't jumbled together?
}

Then, our algorithm says to temporarily store the current value. Again, the word "store" should ring a bell. We will need a new variable to hold this value:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   
   // Display current term
   printf("%d ", currentTerm);
   
   // Store the current term elsewhere temporarily
   temporaryTerm = currentTerm;
}

Then, we calculate the new value of the current term by adding the previous term to it:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   
   // Display current term
   printf("%d ", currentTerm);
   
   // Store the current term elsewhere temporarily
   temporaryTerm = currentTerm;
   
   // Calculate the new current term
   currentTerm = currentTerm + previousTerm; // Could also be written: "currentTerm += previousTerm;"
}

Next, we copy the value we temporarily stored into the previous term variable:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   
   // Display current term
   printf("%d ", currentTerm);
   
   // Store the current term elsewhere temporarily
   temporaryTerm = currentTerm;
   
   // Calculate the new current term
   currentTerm = currentTerm + previousTerm;
   
   // Store the new previous term
   previousTerm = temporaryTerm;
}

Now we add one to a counter of the number of terms we have displayed so far. Since we need a counter now, that is another variable we must declare (since it's a counter, it should start at zero):

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   int termCount = 0; // The number of terms displayed so far
   
   // Display current term
   printf("%d ", currentTerm);
   
   // Store the current term elsewhere temporarily
   temporaryTerm = currentTerm;
   
   // Calculate the new current term
   currentTerm = currentTerm + previousTerm;
   
   // Store the new previous term
   previousTerm = temporaryTerm;
   
   // Incremenet the counter
   termCount = termCount + 1; // Or termCount++, or termCount += 1
}

Then we are supposed to repeat these steps. Repeat should signal the need of a loop to you. Now, normally the condition line for a loop is written first, then you write the code that goes inside of the loop. However, there's no reason we can't still do it the other way around. Simply indent all of the appropriate code lines one extra tab over (either manually, or your compiler will likely have something under an "edit" menu that will let you highlight the whole thing and automatically indent it), then black the braces at either end and put the loop condition. First, however, what is our loop condition? We want to continue running the loop UNTIL the counter is equal to the numebr of terms we passed to it. Therefore, we are looping while the counter is NOT equal to the target value. As we indicated in our algorithm, we're performing the check at the end of the loop, but we could easily check before (in fact, this would allow us to handle zero as an input value), but let's follow our algorithm an do a do-while loop:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   int termCount = 0; // The number of terms displayed so far
   
   do
   {
      // Display current term
      printf("%d ", currentTerm);
      
      // Store the current term elsewhere temporarily
      temporaryTerm = currentTerm;
      
      // Calculate the new current term
      currentTerm = currentTerm + previousTerm;
      
      // Store the new previous term
      previousTerm = temporaryTerm;
      
      // Increment the counter
      termCount = termCount + 1;
   }while(termCount != numTerms); // Did you remember the semicolon here, as per our rule?
}

Finally, we decided to return the last term we calculate, which we discovered will actually be the value of the previous term when we did our experiment ourselves. So, with one more line an a comment, we are done:

Code:

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   int termCount = 0; // The number of terms displayed so far
   
   do
   {
      // Display current term
      printf("%d ", currentTerm);
      
      // Store the current term elsewhere temporarily
      temporaryTerm = currentTerm;
      
      // Calculate the new current term
      currentTerm = currentTerm + previousTerm;
      
      // Store the new previous term
      previousTerm = temporaryTerm;
      
      // Increment the counter
      termCount = termCount + 1;
   }while(termCount != numTerms);
   
   // Return the last calculated term to the calling function
   return previousTerm;
}

That's all there is to it, and hopefully it wasn't too difficult. As you can see, it really does make things easier when you plan your solutions out step-by-step before actually coding them. We had almost one line of code to write for every step, which made our code practically write itself and gave us a rich basis for our documentation (comments). So now all that we need to do is call this function we wrote from the main function. The call itself looks like this:

// Display the Fibonacci sequence to the number of terms desired by the user
CalculateSequence(termCount);

However, this isn't quite enough. While I don't believe this particular example causes a problem (the compiler will make assumptions that end up being correct), there is a catch to writing functions outside of a "class" like we have in the robot code: the compiler has to know about the function BEFORE the function is called. This means that you either declare your functions in reverse order, which is a little less logical since the main() function should technically go first, or you make a prototype. We're going to do the latter. This might sound complicated, but it's just a single line of code. Between the #include directives and our main() function, we simply copy the declaration line of our function, but without the actual code and with a semi-colon after it. Then we're free to put the function itself at the end. So, our program looks like this:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// This is our function prototype, to tell the compiler about the function before we use it
int CalculateSequence(int numTerms); // Note the semicolon and similarity to the declaration below. The argument name isn't necessary, but is good practice.

// Program starts here
int main(void)
{
   int termCount; // The number of terms to display
   
   // Prompt user for input
   printf("Enter the number of terms of the Fibonacci Sequence to display: ");
   
   // Take input as an integer
   scanf("%d", &termCount); // Did you remember the ampersand?
   
   // Make sure the input is valid
   if(termCount < 1)
   {
      // Display error message on new line (leave another line for the "pause" command)
      printf("\nError: Input must be a whole number.\n"); // You could also add a "%d" specifier to show the user what they entered incorrectly
      
      // Wait
      system("pause");
      
      // An error occured, so quit early and return non-zero
      return 1;
   }
   
   // Calculate the desired number of terms of the Fibonacci sequence and display them
   CalculateSequence(termCount);
   
   // This just makes it look neater by putting the "Press any key to continue..." message on a new line
   printf("\n");
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

// Displays the first "numTerms" terms in the Fibonacci sequence
int CalculateSequence(int numTerms)
{
   // Local variables
   int previousTerm = 0; // The value of the previous term in the sequence
   int currentTerm = 1; // The value of the current term in the sequence
   int temporaryTerm; // A temporary holding spot for the current term
   int termCount = 0; // The number of terms displayed so far
   
   do
   {
      // Display current term
      printf("%d ", currentTerm);
      
      // Store the current term elsewhere temporarily
      temporaryTerm = currentTerm;
      
      // Calculate the new current term
      currentTerm = currentTerm + previousTerm;
      
      // Store the new previous term
      previousTerm = temporaryTerm;
      
      // Increment the counter
      termCount = termCount + 1;
   }while(termCount != numTerms);
   
   // Return the last calculated term to the calling function
   return previousTerm;
}

So that's our program. I may have made a mistake in typing this, so if you find an error please let me know, but otherwise, that should be it. Please try it out for yourself and consider adding some things to it on your own. Maybe have the error message tell the user exactly what number they entered that was wrong. Or, better yet, have the input loop until the user enters a value that is correct. Maybe you could make a function that recieves two arguments and takes input from the user until the value they enter is between these two numbers--performing the promting, input, and error-checking for you. This type of function might be useful later so that you can simply call one function and have it return an integer for you within the given range, all already written Wink Another thing you might notice is that the values in the sequence quickly get too large for the int to hold, and start turning into seemingly random values (they actually just loop around). Perhaps you could change some of the ints into floats or doubles to get a larger range, just make sure you change the right ones (the counters should still be integers, of course).

I plan to do another sample program tomorrow, then give you an assignment that I'll take up the next day. After that, I'll post some challenges for you guys in increasing difficulty and see how you fair. Once again, if you have any questions, feel free to post them Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Another At-Home Example

Post  Brendan van Ryn on Wed Mar 14, 2012 4:04 pm

So, I must apologize for not getting this tutorial up yesterday like I had promised. I completely forgot about the Waterloo open house and a friend of mine's birthday, so I wasn't home at all. Anyway, we're going to do another sample program today--one that exemplifies the importance of fully thinking a problem through, and considering different ways to solve that same problem. We'll look at an example question from this year's senior Canadian Computing Competition, which a few of our programmers wrote back in February. We'll take a simple appraoch to the problem, evaluate the efficiency of it, then you'll be given a separate assignment to do yourselves, which I will take up tomorrow.

The problem is simple. A soccer game is being played, and we are focussing on one team in particular. This team has players whose jerseys are numbered from one to ninety-nine, inclusively. This means that there are one-hundred possible jersey numbers. In the program we will write, the user will enter the jersey number of a player who has scored a goal. Again, this number is any integer between one and ninety-nine. In this very odd game of soccer, however, a goal only counts if the last four players that touched the ball have jersey numbers that are increasing in order. These four players include the goal-scoring playing. For example, if the user enters 45 (meaning player 45 scored the goal), then the numbers of the players that passed the ball to get it to 45 would be x, y, z, 45. This is read as player "x" passing to player "y" passing to player "z" passing to player 45, who scores. As per the aforementioned rule, in order for the goal to count, x < y < z < 45. Therefore, 5, 16, 22, 45 is a valid sequence, because each number is greater than the previous number. However, 2, 10, 8, 35, 45 is NOT a valid sequence. Going from 10 to 8 is a DECREASE instead of an increase, which means that this pass breaks the sequence. Since the last number is entered by the user (we'll call it "n"), we're just trying to find the following:

Find all values for x, y, z, and n
Where each is an integer greater than or equal to one, and less than or equal to ninety-nine
And where x < y < z < n

If you run a quick example through your head, you can see that, if the user enters 4, there is only one valid combination: 1, 2, 3, 4. If the user enters five, there are four: 1, 2, 3, 5; 1, 2, 4, 5; 1, 3, 4, 5; 2, 3, 4, 5. As you go up, the number of possible combinations starts to get very large. As a result, displaying all of these combinations is cumbersome. Instead, our program will just display the number of valid combinations available. Note that this can also be zero, as if the user enters three (there is no combination of strictly increasing integers greater than or equal to one, with three as the last term).

So, there are a lot of ways to appraoch this problem. In fact, especially if I've done a bad job of explaining it, it might be difficult to decide where to start. Often in computer programming, the best idea is to start with the asbolute simplest solution possible. In this case, the most obvious solution is to actually figure out every single possibility using a set of loops. This really isn't hard to do at all. Nonetheless, let's start by writing some algorithms. This first algorithm below, which is similar to our Fibonacci example, is called our "top-level" algorithm, as it doesn't go into much detail:

1) Take input and error-check
2) Calculate the number of possible combinations
3) Output result
4) Exit

So, once again, we can further refine each step, starting with the first one:

1a) Prompt the user for input
1b) Take input and store
1c) Check that value is between one and ninety-nine
1d) Show error message if necessary

This code is very similar to the Fibonacci input. We just change the acceptable range for our input, and the message to display, so maybe try writing out the code for yourself first, and then check it against the sample below:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   int goalScorer; // Jersey number of player that scored
   
   // Prompt user for input
   printf("Please enter the jersey number of the scoring player: "); // Remember the customary space
   
   // Take input
   scanf("%d", &goalScorer); // Remember the ampersand
   
   // Check input for errors (Must be between one and ninety-nine)
   if(goalScorer < 1 || goalScorer > 99)
   {
      // Display error message
      printf("Error: player number must be between one and ninety-nine.\n"); // Move to next line for "pause" command
      
      // Wait
      system("pause");
      
      // Return that an error has occured
      return 1;
   }
   
   // Calculate number of combinations
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

I put some reminders to help with the smaller things, but that is essentially the core outline for our program. We've handled the input, error-checking, and exiting. All we have to do now is write our code to calculate the combinations. This code could easily be written in the place of the comment, especially when we look at our second approach, but to make things easier to read, we'll make a function again. Additionally, this illustrates another helpful advantage to a function: when we go to change how we calculate the number of possible scoring combinations, all we have to do is change the code inside our function; no change to the main() function will be needed. This produces a minimal effect when changes are made, especially if a given function is used multiple times in a program.

Before we write our function, which will handle step 2 in our algorithm, we must divide that step into its smaller pieces. We can think about the problem as being the values of the three variables--x, y, and z. As we showed when we defined the problem, we must find every value where x < y < z < goalScorer. So, what we could do is start by considering all cases where z < goalScorer is true. To count this, we could start z at the first possible number (1) and increment it through all other values up until the value of goalScorer. This sounds like a basic for loop:

for(z = 1; z < goalScorer; z++) // ...

And this is definitely true. Then, when we consider the next part, "y < z", we needn't worry about the value of goalScorer anymore. We have already used our condition to ensure that z will always be less than goalScorer. Therefore, if y is indeed less than z, it is guaranteed that y will also be less than goalScorer. You can easily put real numbers in to test this too: if goalScorer is 7, the greatest z can be is 6, and the greatest y can be is 5 (5 < 7). So we've proven this to be true. Therefore, we could easily run a loop for y that looks like this:

for(y = 1; y < z; y++) // ...

The same can then be done for "x < y". In fact, we can make these loops even a bit more efficient, but we'll look at that later. So, even though we already have some code ideas floating around (which is good), let's look at an algorithm:

Code:

2a) For every value of z that is less than goalScorer
   2b) For every value of y that is less than z
      2c) For every value of x that is less than y
         2d) Increment a counter
      [Repeat]
   [Repeat]
[Repeat]
2e) Return the counted number of possibilities

I structured this algorithm differently than normal, but that's perfectly acceptable. The way you write an alorithm should be a way that helps you really think the problem through--you needn't worry about a stringent structure (some OOPs are probably spinning in their graves at that sentence, but it's true). In this algorithm, each level of indent (which I accentuated by putting [Repeat] at the end of) repeats once for every value of the given variable that fits a condition. At the very inner level, we incremement a counter. This nested technique is exactly what we need. You should also notice that the "for every value" statements bring "for" loops to mind. This is not an accident. These statements can easily be turned into for loops, and that is really what we were expecting from our planning above. So, let's try to make our function.

As always, a few things need to be decided before we actually write the code. First, we should give our function a descriptive name. Something like "FindPossibilities" is a good idea, as the name itself--even before you read the comment that you should ALWAYS put along with it--gives you an idea of what it does. It also follows the important "VerbNoun" style we programmers like to adopt. Next, we should decide what parameters our function needs. In our planning, our algorithm uses the value entered by the user to determine which values of z (and by extension, x and y) are valid, so our function needs to know what the user entered. Therefore, we should have one parameter that receives this value. Finally, the value to return to the main() function must be decided. We could easily print the result inside of this function and then return just an error code, or something meaningles; however, for the sake of keeping the function more generic, we'll just return the counter that stores the number of possibilities and worry about the output in the main() function. Again, it's hard to really illustrate the effectiveness of this compartmentalization when the problems are so specific, but it's a good habit to get into anyway. So, since counters are generally integers (there can't be half a possible combination), we'll return an int. Now for the code:

Code:

// Determine the number of valid combinations of jersey numbers
int FindPossibilities(int scoringPlayer)
{
   register int x, y, z; // The three jersey numbers leading up to the scoring player (remember what "register" does?)
   int count = 0; // The count of the number of possibilities (don't forget to start it at zero)
   
   // For every value of z that is valid (less than scoringPlayer)
   for(z = 1; z < scoringPlayer; z++)
   {
      // For every value of y that is valid (less than z)
      for(y = 1; y < z; y++)
      {
         // For every value of x that is valid (less than y)
         for(x = 1; x < y; x++)
         {
            // Add one to the counter
            count++; // Same as "count = count + 1" or "count += 1"
         }
      }
   }
   
   // Return the number of possiblities we found
   return count;
}

I hope you all remember the "register" keyword. It's not necessary, if you don't want to involve it, but it asks the compiler to store x, y, and z in fast-access memory to keep the program fast. It's a common convention that makes very little difference in a scenario like this, but every little bit counts. Another common mistake for new programmers is to assume that "count" automatically starts at zero. Until you give it a value, "count" can actually be any integer value possible, so always make sure you initialize it first. Additionally, some of you may realize that the braces used for the for loops are completely redundant, but I included them anyway. Some people like to keep them, and you should never omit braces unless you completely understand the syntax, or else you can get strange errors occuring. At any rate, this function should just about do it. Now we must call the function, and print the value. We can do this one of two ways. We can store the return value in a variable, then call printf() with that variable, or we can put the function directly in the call to printf():

Code:

// Store then print
x = FindPossibilities(goalScorer);
printf("%d\n", x);

// Just print
printf("%d\n", FindPossibilities(goalScorer));

Both are fine, and the second is actually more efficient because we don't need an extra variable, but the first is much easier to read an comment, so we'll use that one. There is also one more thing that I hope none of you forgot: we need our function prototype. Just the declaration of the function in the right spot so the compiler knows about it. Here we go now:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Function prototype
int FindPossibilities(int scoringPlayer);

// Program starts here
int main(void)
{
   int goalScorer; // Jersey number of player that scored
   int possibilities; // The number of possiblities calculated
   
   // Prompt user for input
   printf("Please enter the jersey number of the scoring player: ");
   
   // Take input
   scanf("%d", &goalScorer);
   
   // Check input for errors (Must be between one and ninety-nine)
   if(goalScorer < 1 || goalScorer > 99)
   {
      // Display error message
      printf("Error: player number must be between one and ninety-nine.\n");
            
      // Wait
      system("pause");
      
      // Return that an error has occured
      return 1;
   }
   
   // Calculate the number of possible, scoring combinations
   possibilities = FindPossibilities(goalScorer);
   
   // Display result
   printf("The number of possible combinations is: %d\n", possibilities); // Again, move to a new line for the "pause" command
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

// Determine the number of valid combinations of jersey numbers
int FindPossibilities(int scoringPlayer)
{
   register int x, y, z; // The three jersey numbers leading up to the scoring player
   int count = 0; // The count of the number of possibilities
   
   // For every value of z that is valid (less than scoringPlayer)
   for(z = 1; z < scoringPlayer; z++)
   {
      // For every value of y that is valid (less than z)
      for(y = 1; y < z; y++)
      {
         // For every value of x that is valid (less than y)
         for(x = 1; x < y; x++)
         {
            // Add one to the counter
            count++;         
         }
      }
   }
   
   // Return the number of possiblities we found
   return count;
}

So there it is. This method really isn't very efficient, but it does the job. Again, some small changes could be made. You could make a loop for the user when the number they enter is invalid. More on the efficiency side, you could change the FindPossibilities() function a bit. For example, there is not point in testing the values where z is equal to one or two, because in either case there would be no possible value for x. If z is one, neither x nor y can be less than that. If z is two, y can be one, but then x can't be. So we could rewrite the loops like this to be a bit more efficient, but only just:

Code:

// For every value of z that is valid (less than scoringPlayer)
for(z = 3; z < scoringPlayer; z++)
{
   // For every value of y that is valid (less than z)
   for(y = 2; y < z; y++)
   {
      // For every value of x that is valid (less than y)
      for(x = 1; x < y; x++)
      {
         // Add one to the counter
         count++;         
      }
   }
}

Hopefully this does exemplify to you how it can often be very easy to optimize a program a bit. I'd encourage any of you to look into a better, more mathematically sound method of solving this problem, but the above solution is quick to program and works for our purposes. As always, feel free to ask questions if you don't understand Wink

Now, a somewhat more complicated assignment. I would like you guys to write a program to find the sum and average of ten numbers entered by the user. When I take the assignment up tomorrow, we'll look at also finding the mode of this set of numbers. The numbers will be floating-point values, and all values are valid. If you have problems with the assignment, you can post questions, or else wait to see it taken up tomorrow Wink

As a quick hint, while you could definitely just declare and take in ten variables, this is very impractical--what if I asked to take in a hundred numbers? Therefore, use ten variables if you must, but an array would definitely be a better idea and better practice. At any rate, we will be using arrays when I take up the example, so make sure you're prepared Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Sum and Average

Post  Brendan van Ryn on Thu Mar 15, 2012 12:37 pm

Today, we will quickly take up the assignment I gave you yesterday, and then a new assignment will be handed out. This new assignment will probably be the last one for a while, unless there is a large demand for more or some other unusual circumstance.

So, yesterday's assignment was to write a program that could take in ten floating-point values from the user and calculate the sum and average of these values. The algorithm for this assignment is actually very simple. In fact, the main focus of this assignment is to get you programmers more comfortable with arrays. Nonetheless, let's look at the steps:

1) Take ten numbers as input
2) Calculate the sum
3) Calculate the average
4) Display the results
5) Exit

As always, we'll look at the very first step by itself to begin with. From experience, you know that this step usually involves prompting the user, taking input, and then error-checking. In this example, error-checking is not necessary as we will assume that all values are possible. However, we now need to take in ten values. There are several ways to do this, but like I said, we want to focus on arrays in this example. Therefore, you might think of something like this:

1a) Declare an array with ten elements
1b) Take input and store in first element
1c) Take input and store in second element
1d) Take input and store in third element
1e) Take input and store in ...

This is technically correct, but cumbersome. You should be able to easily notice that the only thing changing with each step is the position in which the new value is being stored. The elements are running through from the first (0) to the last (9). How do we make a counter run through the values zero to nine? Generally, we use a for loop. Therefore, our input could look something like this (including the base code):

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   // Our counter variable
   register int i;
   
   // An array to store the ten numbers entered by the user
   float input[10]; // We put "10" in the brackets because we need to hold ten numbers
   
   // Take input ten times and store in each position in the array
   for(i = 0; i < 10; i++) // Could also put "i <= 9", but remember that the elements are 0-9, not 0-10. Also, no semicolon here.
   {
      // Prompt user
      printf("Please enter a number: ");
      
      // Take input into current spot in the array
      scanf("%f", &input[i]); // Input[i] acts like any other float. Remember that "%f" is for floating-point values
   }
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

I hope this makes sense. The variable "i" starts at zero. We ask the user to enter a number, and store the value in "input[i]", which is "input[0]" the first time through the loop. Next, "i" is incremented because of the "i++" to become one. Now when the input is taken, it is stored in "input[1]", the next in the line. Eventually, "i" will reach nine, storing the new number in "input[9]", which is the tenth and last element in the array (you'll get used to zero-based indices soon enough). After this, "i++" is performed again. However, "i" is now ten, which is NOT less than ten, so the loop quits. So we've now very easily taken ten numbers as input and stored them. This is much easier than declaring ten variables and storing them individually, and the benefits will become more pronounced throughout this program. Note how, by chaning both of the "10"s in this program to "100", we could just as readily take in one hundred values without changing anything else.

Next, calculating the sum:

2a) Start a sum value at zero
2b) Add the first element's value to the sum
2c) Add the second element's value to the sum
2d) Add the third element's value to the sum
2e) Add the...

Again, a very similar situation to the input. The first step should definitely indicate the need of a variable to you, which you should also have been thinking about when you first read the program description and saw that you needed to find the sum. The sum is a value that needs to be stored. Other than that, the loop is basically the same (I'll only show the relevant code for this bit):

Code:

// Store the sum of the values the user enters
float sum = 0.0f; // Remember the ".0f" part for good form, even though it isn't necessary

// Omitted code...

// Calculate the sum of the values in the array
for(i = 0; i < 10; i++)
{
   // Add the current element to the running total
   sum = sum + input[i]; // Could also be "sum += input[i]"
}

Another reason for declaring "i" with the register keyword over any other variable is that is it used repeatedly and frequently, so it presents the greatest gain from optimization (also, the varaible used within the brackets of an array is called the "index" or "indexing variable"). For each element in the "input" array, input[0] through input[9], we add that value to "sum". Since sum is started at zero, this is the total of the value in the array, quick and easy.

Finally, we calculate the average. The average is just the sum of the values divided by the number of values. We took in ten values, so we just divide the sum by ten. Then, we display the results to the user, and we're done:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   // Variables
   register int i; // Indexing/counter variable for loops
   float sum = 0.0f; // Stores the sum of the values the user enters
   float input[10]; // An array to store the ten numbers entered by the user
   
   // Take input ten times and store in each position in the array
   for(i = 0; i < 10; i++)
   {
      // Prompt user
      printf("Please enter a number: ");
      
      // Take input into current spot in the array
      scanf("%f", &input[i]);
   }
   
   // Calculate the sum of the values in the array
   for(i = 0; i < 10; i++)
   {
      // Add the current element to the running total
      sum = sum + input[i];
   }
   
   // Display sum and average (separate by one line)
   printf("\nThe sum of the numbers is: %0.3f", sum); // I chose to display to three decimal places, but it's up to you
   printf("\nThe average of the numbers is: %0.3f\n", sum / 10.0f); // Remember to leave a line for the "pause" command
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

That should be all. Try it out and verify that it works. This code is actually pretty short compared to some of the others, and we didn't make a function this time, but I hope it's helped you guys get a bit more familiar with arrays. Additionally, it would be a good personal project for you to try to turn the sum calculation into a function on your own. Try making a function that receives an array, and then calculates and returns the sum. I'll take up the function with you tomorrow, but to get you started, here is the function prototype and the line in which it's called:

Code:

// Prototype for summing function you will write
float CalculateSum(float array[10]); // The size of the array in the declaration must be the same as the size of the array you pass to it (both ten)

// How you would use the function
sum = CalcaulteSum(input); // Note that you leave out the brackets when passing an array to a function

So that's one small assignment for you: make the CalculateSum() function and rewrite your program to use it. Also, I said we would look at calculate the mode of a set of numbers today, but that will be part of tomorrow's (final) tutorial, along with the aforementioned function. To calculate the mode, you will need another array to store counts of how many times a given value occurs. The value(s) that occur most often is/are the mode(s). Again, I'm open for questions, and will see you via another tutorial tomorrow Wink

As a little bonus, we could make this program even easier to change by using a macro for the size of the array. At the top of the code, between the #include lines and the main() function, we put a macro definition (remember how to do those?):

#define ARRAYSIZE 100 // The number of elements in the array we are using

Then, when we delcare the array, we use the macro instead of a number:

float input[ARRAYSIZE];

Finally, our loops are changed to check the array size instead of just a constant:

for(i = 0; i < ARRAYSIZE; i++) // etc...

So now, whatever value we set ARRAYSIZE to (as long as it's a positive integer), the code will automatically update to have that size of array. Note that this is a macro NOT a variable: the macro just gets replaced with the number you pass. You cannot put the name of a variable inside the brackets, unless you have a local C99 array, but even that is generally frowned upon. Dynamic allocation is not a topic we need to discuss, however, I'm just showing another way that macros can make your life much easier Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Mode and More Assignments

Post  Brendan van Ryn on Fri Mar 16, 2012 2:07 pm

Well, we're back for one last tutorial this break. In this post, we're going to look at creating that function I mentioned in the last tutorial, then write a program to find the "mode" of a set of values. Both will be done as completely separate, stand-alone projects, but could easily be integrated into other projects.

So, the function will be written first. From our last tutorial, we already know how to calculate the sum of a set of values stored in an array:

Code:

// For each element in the array
for(i = 0; i < ARRAYSIZE; i++)
{
   // Add the current array element's value to the running total
   sum = sum + array[i];
}

This is a common enough task that we could actually make a function to do it. In the last tutorial, I suggested a function with the prototype:

Code:

// In previous tutorial
float CalculateSum(float array[10]);

// Slightly more flexible declaration
float CalculateSum(float array[ARRAYSIZE]);

// New, better declaration
float CalculateSum(float array[], int size);

In the first example, the size of the array is indicate with a constant. In the second example, the array size is determined at compile-time based on the value of the ARRAYSIZE macro (not a variable, a macro). The third example is actually much better, however, as it receives an array of unknown size--which means that you can use it to calculate the sum of any array. In order to know how many elements are in the array, the size of the array is sent in the "size" argument. Now our function can be written pretty easily:

Code:

// Calculate and return the sum of the values in the array passed
float CalculateSum(float array[], int size) // Receives an array of any size, and the size of that array
{
   register int i; // Our counter variable for the loop
   float sum = 0.0f; // The variable to hold the sum of the array as we calculate it
   
   // For each element in the array passed
   for(i = 0; i < size; i++)
   {
      // Add the current element's value to the sum
      sum = sum + array[i];
   }
   
   // Return the sum to the calling procedure
   return sum;
}

It's as simple as that. The code is almost completely the same, but now it's in a function. Besides remembering the prototype, everything is done. This allows us to do the following without a problem:

Code:

int main(void)
{
   float arrayOne[100], arrayTwo[500];
   
   // Assume arrayOne is somehow filled with input from a file
   // Assume arrayTwo is somehow filled with input from a serial port
   
   // Calculate and display the sums
   printf("\nSum of file values: %0.2f", CalculateSum(arrayOne, 100));
   printf("\nSum of serial port values: %0.2f", CalculateSum(arrayTwo, 500));
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

This is a quick example of how making a generic function is helpful. No matter how many different float arrays you have, and no matter what size they are, you can calculate the sum of their values using a single line of code (the one calling the sum function). This is the kind of approach you should take to all of your code: if something can be used more than once, make it into a generic function. Flexibility is key. In fact, for those of you who might have been reading ahead on your own or are wondering, you can even allow the function to take any type of array (int, float, char, etc.) by using a pointer instead of the square brackets and declaring the pointer as type void. Then, the calling procedure would pass the size of the array and the size of an individual element in the array, so the function can determine the array type for indexing and the number of elements.

Next, let's write our program to calculate the mode of a set of numbers. Our algorithm should, as always, go first. We know that the "mode" of a set of data is the number that appears most often (and there may be multiple such numbers). Therefore, we need to know what values occur in our data set, and how often they occur. So:

1) Take fifteen numbers as input
2) Calculate and display the mode
3) Exit

We already know how to do steps 1 and 3, so let's write that out quickly:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Program starts here
int main(void)
{
   register int i; // Indexing/counting variable
   float data[15]; // Data for which we are calculating the mode (as entered by the user)
   
   // Take input fifteen times and store
   for(i = 0; i < 15; i++)
   {
      // Prompt user
      printf("Enter a value: ");
      
      // Take input and store in current element
      scanf("%f", &data[i]);
   }
   
   // Calculate the mode
   // Display result
   
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

This time, we're going to make the mode calculation a function right away. Additionally, because our mode function will require another array inside of it, we won't make it generic like we did with the sum function (dynamic allocation is not a good topic to get into right now). So, let's look at how the mode algorithm will work:

Code:

2a) Create a separate space to store each unique value and its corresponding frequency
2b) For each element in the array
   2c) Search for the value in the frequency list
   2d) If it already exists in the frequency list, increment the counter for that entry
   2e) Otherwise, add it as a new entry at the end of the list with a frequency of one
2f) Set a counter at zero to represent the maximum frequency
2g) For each entry in the list
   2h) If the frequency of that entry is greater than the current maximum, update the maximum
2i) For each entry in the list
   2j) If the frequency of that entry equals the maximum frequency value, display that value

This is a much more complicated algorithm than our previous ones, but it really isn't so bad. Basically, we run through the list of values and count how many times each value occurs. Then, we run through our counts and find the largest value--this is the maximum number of times any value appears. Then, we run through the list again and print any value that occurs this maximum number of times, as it is one of the data values that is the mode (occurs most often). Let's start by deciding our function declaration. Our function will handle printing the numbers itself, just for simplicity, so its return value isn't important. Normally, I suggest returning something useful, but in this case, we'll return void so we have one fewer thing to worry about. Meanwhile, the function should only need to receive the array. We arbitrarily decided above that we'll use fifteen numbers, so our array is of size fifteen:

Code:

void CalculateMode(float values[15])
{
   // Code goes here
}

Because the function is void, we do not need the return keyword, but you can put it at the very end if you wish. So the very first step may have you a bit confused. It asks us to create a space to store each unique value and its corresponding frequency. We are essentially creating a data table in which we can look up a data value and its frequency. This should definitely signal the need of a variable, but it should even more so signal the need of an array. What we will actually do is this:

float dataFrequencies[15][2];

This is a two-dimensional array. You can visualize it however you want, but it's really the same as a 30 (15*2) element normal array. It's first size is fifteen, because in a worst-case scenario, you would have fifteen unique values that all occur exactly once, in which case ALL of the values are the mode. The second size, 2, indicates that each entry in the first index (0-14) has two sub-entries (0-1). For any element in this array, the first sub-element is the data value, and the second sub-element is a count of the number of times that value appears. Better said:

dataFrequencies[x][0] == A data value
dataFrequencies[x][1] == The number of times that value appears

What we're trying to accomplish is this. If we have a set of data (1, 1, 2, 4, 4, 4, 9), the array would be filled like this:

Code:

[0][0] = 1 // The number from the data
[0][1] = 2 // The number of times it is seen

[1][0] = 2 // The number from the data
[1][1] = 1 // The number of times it is seen

[2][0] = 4
[2][1] = 3

[3][0] = 9
[3][1] = 1

Hopefully that makes sense. Now, this does create one problem: we need to know when a spot in the array is empty. For instance, in the above example, there are seven numbers, but only four DIFFERENT numbers, leaving three empty spots. The easiest way to check for this is to set [4][1], [5][1], and [6][1] all to zero, indicating that they don't occur at all. Therefore, the first step in our program is to empty our array (remember how we don't know what is inside the array until we explicitly put something in it?):

Code:

void CalculateMode(float values[15])
{
   register int i; // Our counter variable
   float dataFrequencies[15][2]; // Our frequency table
   
   // For each OUTER element in the array
   for(i = 0; i < 15; i++)
   {
      // Empty the INNER elements of the array
      dataFrequencies[i][0] = 0; // Just to be neat
      dataFrequencies[i][1] = 0; // Mark zero counts, so this spot is clearly empty
   }
}


Next, we start our loop to search through every element in the value list passed to our function. For each value, we use another for loop and counter to see if we find a match in the dataFrequencies[][] array (the first sub-element, which contains the data values themselves). If we find a match, the function stops and we use the value of the counter as the index. We also stop whenever dataFrequencies[j][1] is zero (where "j" is our counter) because this indicates an empty spot. For us to reach an empty spot, we know that our data value is not yet in that array, so we add it in. In either case, "j" now acts as our first index, and we incremenent the second sub-element, increasing the count of the number of times we have seen that value:

Code:

void CalculateMode(float values[15])
{
   register int i, j; // Our counter variables
   float dataFrequencies[15][2]; // Our frequency table
   
   // For each OUTER element in the array
   for(i = 0; i < 15; i++)
   {
      // Empty the INNER elements of the array
      dataFrequencies[i][0] = 0;
      dataFrequencies[i][1] = 0;
   }
   
   // For each element in the values passed, count the number of times it occurs
   for(i = 0; i < 15; i++)
   {
      // Search the dataFrequencies array for a match, or else the end of the array
      for(j = 0; dataFrequencies[j][0] != values[i] && dataFrequencies[j][1] != 0; j++); // Stop at a match or a blank
      
      // If we are at a blank, add the current value to the table
      if(dataFrequencies[j][1] == 0) dataFrequencies[j][0] = values[i];
      
      // Increment the count regardless
      dataFrequencies[j][1]++;
   }
}

So, for each value in the values[] array, we search through the dataFrequencies[][] array for a match. We put a semi-colon after the loop this time because there are no instructions inside of the loop--the condition and "j++" commands are the entirety of the code we need run. Therefore, nothing goes inside of the loop, and we signify this with the semi-colon. If a match is found, the loop will exit, and "j" will contain the position of the match. The if-statement is false, and we increment the count for that value. If no match is found and we reach the end of the data (a blank spot), "j" now contains the position of the first blank spot. The if-statement is true, and we add the current value to the table for future searches. We still increment the count, which will always just be increasing from zero to one in this case. I know this is a lot more complicated than previous examples--many nested loops and arrays--but all we're doing is counting how many times each value in the values[] array occurs.

Our next two steps call for another variable--a counter that tracks what the largest frequency count is. This is something we've done before in the robot code: we've used a variable to track the largest particle size from the camera. This is the same concept: we start a "max" variable at zero, then compare each value in the array to it. Whenever a value is larger than this value, we store the new value as the "max". By the end of the array, this variable holds the maximum value found in that array. In this case, we only care about the frequency counts:

Code:

void CalculateMode(float values[15])
{
   register int i, j; // Our counter variables
   float dataFrequencies[15][2]; // Our frequency table
   int maximum = 0; // Store the maximum frequency (maximum number of times a value appears)
   
   // For each OUTER element in the array
   for(i = 0; i < 15; i++)
   {
      // Empty the INNER elements of the array
      dataFrequencies[i][0] = 0;
      dataFrequencies[i][1] = 0;
   }
   
   // For each element in the values passed, count the number of times it occurs
   for(i = 0; i < 15; i++)
   {
      // Search the dataFrequencies array for a match, or else the end of the array
      for(j = 0; dataFrequencies[j][0] != values[i] && dataFrequencies[j][1] != 0; j++);
            
      // If we are at a blank, add the current value to the table
      if(dataFrequencies[j][1] == 0) dataFrequencies[j][0] = values[i];
      
      // Increment the count regardless
      dataFrequencies[j][1]++;
   }
   
   // For each of the counted frequencies, find the largest value
   for(i = 0; i < 15; i++)
   {
      // If the current count is greater than the current maximum, store the new maximum
      if(dataFrequencies[i][1] > maximum) maximum = dataFrequencies[i][1];
   }
}

We're almost done. Now, we just need to find every value whose frequency count matches this maximum. Remember, there can be more than one mode, like in the set {1, 1, 2, 2, 3, 3}. In this case, all three values are the mode as they all occur exactly twice. So, our last little bit consists of searching for values where the dataFrequency[x][1] is equal to the maximum we calculated, and printing it:

Code:

void CalculateMode(float values[15])
{
   register int i, j; // Our counter variables
   float dataFrequencies[15][2]; // Our frequency table
   int maximum = 0; // Store the maximum frequency (maximum number of times a value appears)
   
   // For each OUTER element in the array
   for(i = 0; i < 15; i++)
   {
      // Empty the INNER elements of the array
      dataFrequencies[i][0] = 0;
      dataFrequencies[i][1] = 0;
   }
   
   // For each element in the values passed, count the number of times it occurs
   for(i = 0; i < 15; i++)
   {
      // Search the dataFrequencies array for a match, or else the end of the array
      for(j = 0; dataFrequencies[j][0] != values[i] && dataFrequencies[j][1] != 0; j++);
            
      // If we are at a blank, add the current value to the table
      if(dataFrequencies[j][1] == 0) dataFrequencies[j][0] = values[i];
      
      // Increment the count regardless
      dataFrequencies[j][1]++;
   }
   
   // For each of the counted frequencies, find the largest value
   for(i = 0; i < 15; i++)
   {
      // If the current count is greater than the current maximum, store the new maximum
      if(dataFrequencies[i][1] > maximum) maximum = dataFrequencies[i][1];
   }
   
   // The heading for the values to be printed
   printf("Mode(s): ");
   
   // Find and print all values whose frequency match the maximum. These are the modes
   for(i = 0; i < 15; i++)
   {
      // If the current count matches the maximum frequency, print the value
      if(dataFrequencies[i][1] == maximum) printf("%0.2f ", dataFrequencies[i][0]); // Priting the value, not the count. Leave a space for next value.
   }
   
   // Leave a space for the "pause" command later
   printf("\n");
}

That should be all. The "printf()" before the loop just displays the "Mode(s):" message so the user understands the purpose of the numbers being disaplayed. A space is also left after each data value that is displayed so that the next value (if one exists) is not squished against the previous value. Finally, a blank line is printed so the "Press any key to continue..." message is on its own line. So, let's see the whole thing now, with the main() function and the prototype:

Code:

#include <stdio.h> // Input and output functions
#include <stdlib.h> // Utility functions

// Function prototypes
void CalculateMode(float values[15]);

// Program starts here
int main(void)
{
   register int i; // Indexing/counting variable
   float data[15]; // Data for which we are calculating the mode, as entered by the user
   
   // Take input fifteen times and store
   for(i = 0; i < 15; i++)
   {
      // Prompt user
      printf("Enter a value: ");
      
      // Take input and store in current element
      scanf("%f", &data[i]);
   }
   
   // Calculate and display the mode(s)
   CalculateMode(data);
      
   // Wait
   system("pause");
   
   // No errors
   return 0;
}

// Calculate and print all mode values in the data passed
void CalculateMode(float values[15])
{
   register int i, j; // Our counter variables
   float dataFrequencies[15][2]; // Our frequency table
   int maximum = 0; // Store the maximum frequency (maximum number of times a value appears)
   
   // For each OUTER element in the array
   for(i = 0; i < 15; i++)
   {
      // Empty the INNER elements of the array
      dataFrequencies[i][0] = 0;
      dataFrequencies[i][1] = 0;
   }
   
   // For each element in the values passed, count the number of times it occurs
   for(i = 0; i < 15; i++)
   {
      // Search the dataFrequencies array for a match, or else the end of the array
      for(j = 0; dataFrequencies[j][0] != values[i] && dataFrequencies[j][1] != 0; j++);
            
      // If we are at a blank, add the current value to the table
      if(dataFrequencies[j][1] == 0) dataFrequencies[j][0] = values[i];
      
      // Increment the count regardless
      dataFrequencies[j][1]++;
   }
   
   // For each of the counted frequencies, find the largest value
   for(i = 0; i < 15; i++)
   {
      // If the current count is greater than the current maximum, store the new maximum
      if(dataFrequencies[i][1] > maximum) maximum = dataFrequencies[i][1];
   }
   
   // The heading for the values to be printed
   printf("Mode(s): ");
   
   // Find and print all values whose frequency match the maximum. These are the modes
   for(i = 0; i < 15; i++)
   {
      // If the current count matches the maximum frequency, print the value
      if(dataFrequencies[i][1] == maximum) printf("%0.2f ", dataFrequencies[i][0]);   
   }
   
   // Leave a space for the "pause" command later
   printf("\n");
}

That example wasn't particularly long, and besides the two-dimensional array, nothing really new was used. It was a much more involved algorithm, and one that took me a while to develop on my own, but see to make sure that you understand the logic behind it and what the program is doing. This is the last tutorial I'll be writing for now, but I'll still answer any questions asked. I really hope some of you thought about this mode assignment before reading my solution, as there are definitely other ways of doing it. I'll write some short assignments below as possible points for you guys to continue practice until our lessons resume, so until then, post questions if you have them Wink

1) Write a program to take in ten integer numbers and print them back out in increasing order (least to greatest). [Sorting]
2) Write a program that acts like a calculator, allowing the user to enter two values and an operation, and displaying the result. [Parsing]
3) Write a program where the user enters the x and y position of a knight on a chess board and the x and y position of another square on the same chess board. The program will calculate the minium number of moves required for the knight to reach that spot (not the moves themselves, just how many moves). [Problem Solving]
4) Write a game where the user will try to guess a random number selected by the computer ("#include <math.h>" and use "rand() % 100" for a random number between zero and ninety-nine). If the user's guess is too low or too high, the computer will tell the user and let the user try again. When the user gueses the number correctly, display a congratulations message and the number of guesses they took.
5) Create the same program as above, but now the user thinks of a random number and the computer tries to guess it (the user will enter 1 for too high, -1 for too low, and 0 for correct. Display a message to inform the user of how to play the game).

I may or may not take up some of these assignments later, but for now, that's all. Have a good March Break Wink


Last edited by Brendan van Ryn on Fri Mar 16, 2012 3:24 pm; edited 1 time in total (Reason for editing : Fixed some typos. Sorry guys.)

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Characters

Post  Brendan van Ryn on Wed Mar 21, 2012 5:09 pm

I posted this in response to a question Will asked yesterday, so I figured I should add it to the tutorial list as it really is its own subject.

Without getting into strings, it is actually possible to compare keyboard characters directly. I think I mentioned in a few of the tutorials that other variable types exist besides the float and int types we mostly use. One of these is the char type. You declare a char the same way as an int, and it acts the same as an int for the most part. The difference is the range. While int can hold between (-2^31) and (+2^31 - 1), char can only hold from -128 to +127. This isn't very useful for numbers (though it is commonly used for flags), but it's perfect for characters.

See, characters on a computer screen are stored as numbers, just like everything else. The way these characters are encoded differ, but one common way is ASCII encoding. If you look up "ASCII chart" on google, a table will show up with 128 characters (including zero) and their ASCII values. For example, '0' is 48, and 'A' is 65. Therefore, to store a character 'B' in a char, you can set that char to 66 (the ASCII for B). Then, if you call printf() with the "%c" specifier, it will print the corresponding character.

Code:

char x = 66; // Character variable set to the ASCII value for 'B'

printf("%c -- %d", x, x);

This code prints the value of x twice--the first time, it prints the character using "%c", which causes the letter 'B' to be displayed. The second time, it displays the number 66, because the "%d" tell it to display a number, not a character. Of course, if printf() can do the "%c" specifier, so can scanf(). So you could take in a character like this:

Code:

char input; // Character input by user

printf("Enter an operation (+, -, x, /): "); // Prompt user to enter addition, subtraction, multiplication, or division
scanf("%c", &input); // Take in a character and store it's ASCII value in "input"

You might need to be careful with scanf() and characters. I think I've had problems with it before, but it should all be fine in theory. Now, all that needs to be done is to check the ASCII value to determine which operation the user wants:

Code:

if(input == 43)
{
    // Perform addition
}

This checks if the character entered was ASCII value 43, which corresponds to the addition sign. This could be repeated for the other three operations (or others if you want to support exponents or something). Now, memorizing a series of ASCII values is a bit tedious, and it makes the code harder to understand, so C has a feature that makes things easier. If you put a single character between SINGLE quotes (apostrophes), it will replace that "character constant" with the correct ASCII value. Therefore, the if-statement could be re-written as:

if(input == '+') // ...

Instead of putting 43, the ASCII value of the plus sign, we put the plus sign itself between single quotes (double-quotes are different and will turn red if you use them).

So that's that Will. Definitely a good question So yes, you can take in characters as wells as numbers, and check them quite easily. Also, as an added note, the single quotes aren't magical at all. They act like a number, or a macro. For example, this code would work fine:

int x;
x = 'A' * 'B' + 'C';

This sets x to (65) x (66) + (67). Again, the single-quoted characters just get replaced by their ASCII values, so they're the same as any other number. Finally, like your strings in printf() and scanf(), special escape-sequences still exist:

"\n" -- ASCII value of a newline (the character at an end of a line when you press "enter")
"\t" -- ASCII value of a tab
"\\" -- ASCII value of a backslash (putting "\" by itself confuses the compiler because the first backslash tells it to look at the next character)

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

More Assignments

Post  Brendan van Ryn on Fri Apr 13, 2012 7:36 pm

Well, it's been quite some time since I last posted here. No one has posted any questions for me, and I know that Will has finished all of the assignments I handed out to you guys, so to prevent us from becoming complacent, I've compiled a few more quick assignments for you guys.

As for my plans once we get up and running again, it varies a bit. For us, I'd really like to finish talking about these assignments, tutorials, and this year's code to make sure that everyone is on the same page. From there, we'll start working on the old robot--which the mechanical team will be working on to get back up to operation. First, we should construct a base file for next year as a starting point for basic driving capability. From there, we will test out some basic skills like toggle buttons, PID loops, servos, limit switches, relays, etc.

Additionally, I'm open to explain and DOS-program questions you may have as well. In fact, Seikun has started a Computer Science club that is having its first meeting at lunch this upcoming Tuesday (I believe). If you programmers are interested in getting ahead, this would be a prime place to test out your assignments, learn more about application programming--as well as programming in general--and receive feedback. Seikun has assured me that the meetings will be scheduled not to conflict with robotics (I think Thursdays after school are the plan), and the lessons will be taught by yours truly, so I definitely suggest checking it out Wink Some things I have planned are: Canadian Computing Competition preparation questions, expression parsing, GUI and Windows applications, Game Making, and some other fun stuff. Of course, it depends on skill level, but I think it's an excellent idea Wink

Assignments:
1) Create a program in which the user will enter ten numbers. Your program will output the median of these numbers (which is either the middle number, or the average of the two middle numbers).
2) Create a program in which the user will enter two strings (just an array of chars, remember?), one on each line (so you can use fgets()). Then, your program will output whether or not the two strings are anagrams of each other. An anagram means that the two strings have all of the same letters, but shifted around, like "TIME" and "ITEM". Whether or not the letter is uppercase or lowercase shouldn't matter, and spaces don't count.

If anyone asks, I can post or talk about the solution to the second one to help you along, as it's a bit complicated and you haven't worked with strings much before. Just to help, you can convert a letter to lowercase using the "tolower()" function (look it up Wink). I hope you guys handle these as well as you handled the other ones. Feel free to ask questions, but I won't get my hopes up Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Assignment

Post  Brendan van Ryn on Sun May 06, 2012 12:15 am

Okay, for the sake of keeping things interesting for you guys, I have a new assignment. It revolves around a real-world application that can be very simply achieved through many means.

As computers became more widespread, and especially as communication between those computers grew, the need for security increased with it. One important form of automated security is encryption. Encryption is the process of taking raw data (like text, financial information, or even player data from a computer game) and altering it using some algorithm so that the average person could no longer understand it--at least, not without knowing how to decode the data back into its original form.

As a simple example, consider the following text: THIS IS A TEST. I could encrypt this text to say, instead: UIJT JT UFTU. Of course, those letters in the second example could be completely random, but then how would I know how to turn it back into the original text? Therefore, a pattern must exist. In this case, I shifted each letter forward one in the alphabet: T became U, H became I, and so on. While this is a simple algorithm that works, it is too easy for someone who should not have access to the data to determine how we encoded it. For example, since THIS and IS both contain IS, the combination JT appears twice. Using this, someone could find a series of four-letter words containing a smaller, two-letter word inside of it and eventually figure out our shift-by-one algorithm (and getting a computer to do it automatically would take no time at all).

Now, while this algorithm isn't very good, I encourage you to start with it. Therefore, the algorithm for your assigned program is to do the following:

1) Take in a string from the user
2) Ask them whether they want to encrypt or decrypt it
3) Perform the correct operation (encrypt/decrypt)
4) Display the result

Hopefully, sometime in the future, I will post a better algorithm using XOR operations. Additionally, you could try to apply a password to your program. How this would work is not how you think though: instead of storing the password, asking the user to enter it, and checking it to see if it is right (which is bad because you have to store the password in memory), you use the letters in the password to determine how the data is encrypted (and, therefore, decrypted later). Encryption is a cool topic, and this is a program that would come in handy in real-life if you just added support for files, so hopefully you guys can see that you're learning applicable skills Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Encryption Update

Post  Brendan van Ryn on Sun May 13, 2012 4:33 pm

So, I'm back to continue my talk about encryption. As we saw in the previous tutorial, it is very easy to use a simple algorithm, such as "Shift each letter forward four in the alphabet" to encode a piece of text. To decode the text, we would simply shift the letters BACKWARD four positions in the alphabet. Additionally, you guys should have come across the problem of letters like Z, which cannot be moved forward anymore. Chances are, you chose to wrap the letter around to A and continue counting from there (Z->A->B->C->D).

This is good for simple applications, but cannot generally be used for actual sensitive data as it is far too easy to crack. So what do we do then? Well, the obvious choice is to use a password so that the data can only be decrypted by someone who knows that same password used during encryption. It is possible to simply store a list of passwords in a file somewhere and have the program check the password the user entered against the list. The list could then tell the program how to decrypt the file, but now you have the problem that the file containing the passwords must also be encrypted or anyone could simply open the file and steal the passwords from it.

Therefore, the approach is to use the password itself to encode the data. In this way, the correct password is required to decode the data, but that password is never stored anywhere. In encryption, this password is called a "key", and is usually a random string of numbers--binary 1s and 0s generated by a true random number generator or special operations relying on prime numbers. However, for our purposes, a simple character string is acceptable.

So, let's start by analyzing how our password system could work using our previous example. Let's say that the user's password is APPLE and the text is THIS IS A TEST. What we could do is use the letters in APPLE to determine how far to shift the letters in the text (and you will see that this produces a much more random-looking result). The first letter in the password, A, is the first letter in the alphabet, so we shift the first letter of the text, T, forward one to U. The next letter, P, is the fifteenth letter in the alphabet, so we shift the next letter, H, forward fifteen to W. This continues through the entirety of the text, wrapping around to A if necessary, to produce: UWXD NT Q IQXR (If you're wondering what happened when we ran out of letters in the password, we just jumped back to the beginning again at A).

Not only is this text much more difficult to recognize, you'll see that it no longer shares the same problem as before: The "is" in THIS became XD, but the "is" in IS became NT. This makes it harder for someone to crack the code. Additionally, you don't need to store the method of encryption; you can just reverse the operation using the password. The user would be asked for the password again before decrypting, and the same process would occur, but this time, you would shift the letters BACKWARDS instead of forwards. If you do it manually yourself, you'll find that it does work. Better yet, though, is what happens if the wrong password is entered. If the user enters BANANA as the password for decryption, and we proceed with shifting the letters, the text just becomes MORE scrambled. Watch: first we could take the U and shift it backwards two because B is the second letter of the alphabet, giving us S (which is definitely not T like we wanted). Then, we would shift the W back one for the A to get V. If we proceed in this manner, the "decrypted" text is: SVKC ZS O HDWE. Not only is this wrong, it's worse than before as entering the correct password of APPLE will no longer produce the correct result. Thus, this is a fairly secure way of handling encryption, and very easily implemented to. In fact, we could make things even more difficult for anyone trying to crack the code by encrypting spaces as well--after all, spaces have an ASCII value too (32 to be specific).

In reality, shiting letters is still a bit too simple and only really works for letters--other characters won't work so well, and it doesn't allow a password with numbers or symbols. Therefore, a much simpler and more elegant solution is required. The preffered method in use by encryption algorithms is the XOR operation, and it's chosen because it automatically toggles without needing to know whether we are encrypting or decrypting--the process is the same both times. To understand this process, we must first see how the XOR operation works.

In an XOR operation (meaning, "Exclusive OR"), an output is considered to be true if one of the inputs is true, but not both, and not neither. This is done bit-by-bit. As you know, data in a computer is stored in binary--bits that are all either 1 or 0. This is the same for characters in a string. The letter "A", for example, has an ASCII value of 65, and 65 in binary is 1000001. When we do an XOR operation between two values, say 1000001 and 10001011, each bit in one is compared to the other. From left to right, the 1 is compared to the 1, the 0 to the 0, the 0 to the 0, the 0 to the 0, the 0 to the 1, the 0 to the 0, the 0 to the 1, and the 1 to the 1. So when I describe the XOR operation, picture it happening to each bit, on at a time.

The XOR operation can be illustrated with a table displaying the two bits being compared, and the result bit being returned:
First BitSecond BitResult
000
011
101
110

So, we can see that the result is one if ONLY one of the other bits is one, but not both, and not neither. To see how this works with the two values above, watch:
Code:

   10000001
   10001011
   ________
   00001010

This represents an XOR operation between the capital letter "A" and the letter "i" with two dots over it (a special extended character). The result value is 10, which is actually the value of the newline character '\n'. The interesting part, however, is that--if we assume that the first number was the password and the second was the text--we can do the operation with the result to reverse the process:
Code:

   10000001
   00001010
   ________
   10001011

As you can see, we did the same operation and got the value we had in the original text. In this way, we can encrypt and decrypt text using a password with the exact same code.

I hope this hasn't become gradually overwhelming, as I'm told is often the case, but bear with me. I will be showing an example as the final bit of this tutorial--an entire program to illustrate the concept. It will take text input, ask for a password, and perform the operation. Like I said, the program doesn't need to know whether the text is being encrypted or decrypted. You can try this out by entering some text and a password, then entering the result and entering the same password (or a different password to see what happens).

Just a few notes about this program. First, the XOR operator is the caret symbol '^'. Because of it's order of operations, we need extra parentheses. Next, any time you XOR a value with itself (say an "A" in the password and an "A" in the text), you get zero. Since a zero in a string indicates the end of the string, we can't have this happen. Instead, we store the length of the string in a variable and use that to determine where the end is. Third, because the XOR operation can produce characters that can't be displayed (for example, the '\n' character is invisible), this program loads the text from a file and saves it to a file. I don't expect you to learn how to work with files, but I did comment it, and I'm just warning you that it's there. Now, on to the code:

Code:

/* Header files */
#include <stdio.h> // Standard input/output
#include <stdlib.h> // Utility
#include <malloc.h> // Memory allocation
#include <string.h> // String functions


// Macros
#define BLOCK_SIZE 256 // Size of a single block of memory


// Function prototypes
char *LoadFile(const char*, int*); // Load the text from a file
void EncryptFile(const char*, char*, int); // Encrypt the text and save to the file


// Program starts here
int main(void)
{
    // Local variables
    char *fileText = NULL; // Pointer to the text in the file
    char fileName[255]; // Name of the file (including the path, if necessary)
    int fileSize; // Size of the file in bytes

    // Prompt the user for the filename
    printf("Please enter the name of the file to encrypt: ");
    gets(fileName);
                 
    // Load the file, storing its size and contents
    fileText = LoadFile(fileName, &fileSize);
   
    // If the loading was successful
    if(fileText != NULL)
    {
        // Encrypt the file and release the memory we allocated
        EncryptFile(fileName, fileText, fileSize);
        free(fileText);
    }
   
    // Exit
    printf("\n"); // Leave a line for appearance
    system("pause"); // Wait
    return 0; // Return that no error occured
}

// Load the file "fileName" into memory and return a pointer to its contents. Store the size in "size".
char *LoadFile(const char *fileName, int *size)
{
    // Local variables
    FILE *fp; // File reference
    register int allocationCounts = 1; // Number of blocks of memory allocated (start with one)
    char *fileText = NULL; // Pointer to the file's contents
    int fileSize = 0; // Size of the file (start at zero)
   
    // Open the file for binary reading (rb)
    fp = fopen(fileName, "rb");
   
    // If an error occured
    if(fp == NULL)
    {
          // Display an error message and return NULL, which is an error-value for pointers
          printf("Failed to open the file. Check that the name is right.\n\n");
          return NULL;
    }
   
    // Loop
    do
    {
        // Allocate another block of memory (or the first one if this is the first run through)
        fileText = realloc(fileText, BLOCK_SIZE * allocationCounts);
       
        /* Read a block of text from the file into the allocated memory, and store the actual number of bytes read. This number
        will be less than the BLOCK_SIZE when we reach the end of the file, so we need to track the actual size. */
        fileSize += fread(fileText + fileSize, sizeof(char), BLOCK_SIZE, fp);
       
        // Increment the number of blocks we need
        allocationCounts++;
    }while(!feof(fp)); // Repeat until we reach the end of the file 
   
    // Close the file
    fclose(fp);
   
    // Display success message
    printf("File \"%s\" opened successfully.\n", fileName);
   
    // Store the calculated size for the calling function
    *size = fileSize;
   
    // Return the file's contents
    return fileText;
}

// Encrypt the file's contents and write them back to the file
void EncryptFile(const char *fileName, char *fileText, int fileSize)
{
    // Local variables
    FILE *fp; // File reference
    register int i, j; // Loop indices
    char password[255]; // The password entered by the user
    int passLength; // The number of characters in the password
   
    // Open the file for binary writing
    fp = fopen(fileName, "wb");
   
    // If an error occured
    if(fp == NULL)
    {
          // Display an error message
          printf("Error opening file. Please try again.\n\n");
          return;
    }
   
    // Prompt for the encryption key
    printf("\nEnter your encryption key: ");
    gets(password);
   
    // Store the length of the key
    passLength = strlen(password);
   
    // For each character in the file's text
    for(i = 0, j = 0; i < fileSize; i++)
    {
          // NewCharacter = OldCharacter XOR PasswordCharacter
          fileText[i] = (fileText[i] ^ password[j]);
          j++; // Move to the next character in the password
         
          // If we reach the end of the password, loop back
          if(j >= passLength) j = 0;
    }
   
    // Write the encrypted or decrypted contents back to the file
    fwrite(fileText, sizeof(char), fileSize, fp);
    fflush(fp); // Flush the file buffer (behind-the-scenes stuff)
   
    // Close the file
    fclose(fp);
   
    // Display a success message
    printf("File \"%s\" encrypted/decrypted successfully.\n\n", fileName);
}

That's it. That's the whole thing. I hope you enjoyed this tiered lesson, and maybe it's gotten you a bit more interested in the applications of what you're learning Wink

Brendan van Ryn

Posts : 95
Join date : 2010-09-30
Age : 24

Back to top Go down

Re: Programming Tutorials

Post  Sponsored content


Sponsored content


Back to top Go down

Page 1 of 2 1, 2  Next

Back to top

- Similar topics

 
Permissions in this forum:
You cannot reply to topics in this forum