This is a C-language tutorial for the 8-core Propeller microcontroller. It features the Propeller Activity Board (original [1] or WX [2] version) but other Propeller development boards will work.
A function is a little piece of reusable code designed to do a specific task. This tutorial will show you how to use functions, pass information to them, and get information back from them. It shows you how C functions work with the Propeller's memory and multiple cores.
Once you are done here, you will be able to understand how functions are used in these tutorials' example code. And, you will be ready to write functions to use in your own projects. You will be well-prepared for:
Just follow the links below to get started!
Throughout the Propeller C - Start Simple [5] lessons, we put our code in the main function. You can also add other functions to your program and call them from the main function, or even make code in one of those functions call another function.
This first example has a function named hello with a print command in its code block. When the code running in the main method sees hello(); it jumps down to the function named hello, executes the statements in its code block, then returns to the main function. There, the program moves on to the next thing, which is a print statement that displays “Hello again!”
/* Function Call.c Send a simple hello message to the console, but use a function to display one of the messages. */ #include "simpletools.h" // Include simpletools void hello(void); // Function prototype int main() // main function { hello(); // Call hello function print("Hello again from main!\n"); // Display message } void hello(void) // Hello function { print("Hello from function!\n"); // Display hello message pause(500); // Pause 1/2 second }
The void hello(void); line above the main function is called a forward declaration. It tells the PropGCC compiler to expect to see a function named hello later in the program.
Function call.c starts in the main function with pause(500). After that, it sees hello(), which tells it to go find the function named hello, do whatever is in its code block, and come back when done. The hello(); in the main function is called a function call.
The actual hello function is the void hello(void) block of code down below the main function. That’s where the program goes when it sees the hello() call, and it executes that one print statement that displays the “Hello from function!” message. After it runs out of statements to execute, the program returns to the hello call in the main function. There, it moves on to the next statement, which is the print command that displays “Hello again from main!”
In the hello function definition, the void to the left of the name means that the function does not send a value back to the function call. The (void) to the right of the function’s name means that the function does not need to receive any values, called parameters, to do its job. We'll look at parameters and return values in the next few lessons.
A function has a code block contained by curly braces { }. A code block can include one or more statements that will execute when the function is called.
You can also get rid of the forward declaration by moving the entire hello function above the main function. That way, the compiler will have seen the hello function itself before it sees the hello() function call inside main.
The great thing about functions are that they are reusable — they can be called over and over again.
Many functions are designed to receive values that they need to do their jobs. These values are called parameters.
/* Function with Parameter.c Call a function that displays a value passed to it with a parameter. */ #include "simpletools.h" // Include simpletools void value(int a); // Function prototype int main() // main function { value(6); // Call value function } void value(int a) // value function { print("The value is: a = %d\n", a); // Display a single value }
First we see the function definition void value(int a). Remember, the void on the left means this function does not send back a return value to function calls. The (int a) on the right means that it expects to receive an int value from function calls.
In main, the function call value(6) passes the value 6 to the function. The value function receives the value 6, stores it in the int variable named a. Then, the value function's code block displays that value with print("The value is: %d\n", a).
Let's define a function named values that has two parameters.
Did you notice that both function definitions include a parameter named a, and that they do not interfere with each other?
Some functions are designed to do a job and send a return value back to the function call. For example, a function might check a pushbutton and return its state. Or, a function might receive some values through its parameters, use them in a calculation, and return the answer — we will try this.
We will also try a neat coding technique: setting a variable as equal to a function call. This is a quick and efficient way to call a function and get its return value into a variable.
/* Function with Parameters and Return Value.c Pass parameters to a function, let it do its job, and display the result it returns. */ #include "simpletools.h" // Include simpletools int adder(int a, int b); // Function prototype int main() // main function { int n = adder(25, 17); // Call adder function print("adder's result is = %d", n); // Display adder function result } int adder(int a, int b) // adder function { int c = a + b; // Add two values return c; // Return the result }
The adder function’s definition is int adder(int a, int b). The (int a, int b) part means that a function call to adder must pass two int values. The int at the beginning means adder will send back, or return, an int value to the function call.
Take a close look at the line inside main that sets int n equal to an adder function call. adder(25, 17) passes those two values to the adder function's a and b parameters. There, a + b adds the values together, and the result is assigned to a local variable with int c =. At that point, c = 42.
return c sends the value of c back to take the place of the original function call in main. So, int n = adder(25, 17) evaluates to n = 42.
The next line in main, a print statement, displays the value of n. Your SimpleIDE Terminal will display "adder's result is 42"
Local Options — The function’s parameter list is not your only chance to declare local variables. For example, int n = adder... and also (int a, int b) in the adder function are local variables.
Type Less — The adder function could have been even shorter. You could replace the two lines in its code block with return a + b; no need for int c at all.
Return value from main? — Notice all of our programs begin with int main(), by C convention. In computer system programming, a main routine might return a 0 tell a higher-level program that it executed. Since we are programming a stand-alone microcontroller we don't need to have main return anything. In fact, void main() and just main() will also work, though the C compiler might give you a warning message in the Status window.
Here are some variations on the main function that use the result adder returns in different ways.
Try including a subtracter function.
So far, this Propeller C - Functions tutorial has used local variables declared inside of functions. There are advantages to using local variables, since they only use memory temporarily while the function using the variable is executing. This can be useful with microcontrollers, where memory is limited as compared to computers.
Sometimes it is necessary to have variables that more than one function can access and modify. These shared variables are called global variables. The multicore Propeller can execute different functions with different cores (also called cogs) at the same time. These functions operating in parallel can use global variables to exchange information (which we will do in the next lesson).
The example program below uses a modified version of the adder function from the previous lesson, but with all local variables replaced with global variables throughout the code.
/* Global Exchange.c Functions exchange information with global variables. */ #include "simpletools.h" // Include simpletools void adder(void); // Forward declaration int a, b, n; // Global variables int main() // main function { a = 96; // Set values of a & b b = 32; adder(); // Call adder function print("n = %d\n", n); // Display result } void adder(void) // adder, no parameters or return { n = a + b; // Use values of a & b to set n }
The adder function definition void adder(void) ; does not declare parameters or a return value.
Next, a, b, and c are declared as global variables. Since they are all the same type, int variables, they can be declared in a single statement separated by commas.
Instead, a and b are assigned values in separate statements inside the main function, just before the adder function call. The code jumps to the adder function, where the result of a + b is assigned to the global variable n.
Code execution then resumes on the next line in the main function, a print statement that displays the value of n.
Since a, b, and n were all declared before the first function, they were accessible to all the functions in the program. So, there was no need to pass parameters or return values between functions.
Declare & Initialize — In this example, a and b were first assigned values from within the main function. However, that could have been done right in the original declaration, like this: int a = 96, b = 32, n;
Space vs. Speed — Global variables take more memory, but using them can make code execute faster in some cases. Setting up and releasing memory for local variables takes a little bit of time. If your project uses lots and lots of local variables, these bits of time can add up.
Scope — The terms local and global describe a variable's scope, the context in which the variable can be used validly in a program.
BUG ALERT!
Do not put int (or any other variable type) in front of a global variable while using it inside a function! That would create a local variable with the same name, but only that function would use it. It might not have the same value as the same-named global function, and changes made to the local variable's value would not be stored in the global same-name function.
The previous lesson showed you how to make functions share global variables. Now, let's try making two functions run in different Propeller cores, also called cogs, and exchange information through those global variables.
This next example program launches the adder function into another cog. The adder function has been modified so that it keeps on adding 1 to the global n variable every 20th of a second.
The main function monitors the adder activity in that other cog by repeatedly displaying the value of the global n variable. The adder function changes n 20 times-per-second, so the main function will display a new value each time through its loop even though it doesn't actually do anything to n.
/* Multicore Example.c Launch a function into another cog (processor) and display what it does to the global n variable over time. */ #include "simpletools.h" // Include simpletools void adder(void *par); // Forward declaration static volatile int t, n; // Global vars for cogs to share unsigned int stack[40 + 25]; // Stack vars for other cog int main() // main function { t = 50; // Set values of t & n n = 5000; // Launch adder function into another cog (processor). cogstart(adder, NULL, stack, sizeof(stack)); // Watch what the other cog is doing to the value of n. while(1) { print("n = %d\n", n); // Display result pause(100); // Wait 1/10 of a second } } // Function that can continue on its // own if launched into another cog. void adder(void *par) // adder keeps going on its own { while(1) // Endless loop { n = n + 1; // n + 1 each time loop repeats pause(t); // Wait for t ms between updates } }
Cog 0 starts at the beginning of main, setting the values of t and n.
Next, cog 0 passes the memory address of the adder function along with some other details, to a function called cogstart. (It's part of the propeller library, which simpletools includes.) cogstart uses that information to make the next available cog (cog 1) start executing code in the adder function.
At this point, two cogs are executing different parts of the program. Cog 0 moves on to start repeating the while(1) loop in the main function and cog 1 starts executing the while loop in the adder function. Cog 1 updates the value of n every 50 ms because t = 50 and adder's while loop has pause(t). Meanwhile, cog 0 checks the value of n every 100 ms and displays the value with print. Since cog 0 is checking more slowly than cog 1 is updating, the value of n it displays increases by about 2 every time.
After the cogstart call, the adder function starts running in another cog. Meanwhile, the code in the first cog continues onward into the main function's while(1) loop.
From this example, we can see the general steps for setting up multicore code, for large or compact memory models (LMM or CMM):
NOTE: You can get the address of either a function or an array by just using its name. So, adder( ) calls the function, but adder returns the function's address in memory. Likewise, stack[0] returns the value of the stack array's zeroth element, but stack returns the array's address in memory.
LMM & CMM — This is an example of launching either large memory model (LMM) or compact memory model (CMM) code into another cog. These memory models fetch machine codes (numbers that represent your program) from the Propeller chip's 32 KB main RAM, and code running in the cog processes them.
COGC & PASM — (memory models) There are other, higher performance options such as COGC (C that fits entirely inside a cog) and PASM (Propeller Assembly Language). Code for both of these types of programs get loaded into a cog's 2 KB of memory, which increases execution speed because the cog doesn't have to wait for its turn to access main RAM and grab machine codes.
More on Memory Models — Additional examples and more memory model options can be found in the Propeller GCC Demos folder.
Let's find out which cog the cogstart function launches the adder function into.
Add a subtracter function to the project and launch it into a third cog. Make subtracter decrement a new global variable with the same initial value as n, repeating every t ms. Display the changing values of both variables.
Links
[1] https://learn.parallax.com/propellerab
[2] https://learn.parallax.com/propellerab-wx
[3] https://learn.parallax.com/propeller-c-set-simpleide
[4] https://learn.parallax.com/propeller-c-set-simpleide/update-your-learn-folder
[5] https://learn.parallax.com/propeller-c-start-simple
[6] https://learn.parallax.com/propeller-c-simple-circuits
[7] https://learn.parallax.com/propeller-c-simple-devices