If you are a current student, please Log In for full access to the web site.
Note that this link will take you to an external site (https://shimmer.csail.mit.edu) to authenticate, and then you will be redirected back to this page.
1) Using Pointers with Functions
In our simplest way to create functions, we pass in some variables and then return zero or one variable. For example, the following function takes in two inputs and returns one output.
int multiply(int x, int y) {
return x*y;
}
Importantly in C/C++, the inputs to functions are by default by value, meaning when we call the function, the values we pass in get copied and used rather than the initial locations in memory. So even if we had code which utilized the multiply
function from above as follows:
int hey = 5;
int hi = 17;
int bye = multiply(hey,hi);
When multiply
is called, the values contained within hey
and hi
are copied into the scope of the function for use1. This should make sense.
What do you do if you want to return more than one output or possibly affect more than one variable? How do you return an array? In all of these cases using pointers as we discussed in Exercise 01 can be really useful. Because a pointer variable contains an address, when we pass that address into a function, we can have direct access to that variable's value from within the function and not just the value of the variable.
As a simple canonical example, imagine we want to swap two values, or in other words, we want a function that will take in two values and swap them, returning two values. Here's how to do that:
void setup(){
Serial.begin(115200);
}
void loop() {
int first = 47;
int second = 59;
char print_buffer[200] = {0}; //init to nulls;
sprintf(print_buffer,"First: %d, Second: %d", first,second);
Serial.println(print_buffer);
swap(&first, &second);
sprintf(print_buffer,"First: %d, Second: %d", first,second);
Serial.println(print_buffer);
}
int swap(int* x, int* y) {
int temp = *x;
*x = *y;
*y = temp;
}
Let's unpack the code. The swap
function takes in two arguments, but the arguments are actually pointers. So swap
is expecting some addresses to be passed in.
In the loop
we declare and initialize two int
s, first
and second
. So our memory map might look like this (again, realizing that nothing requires that first
and second
be adjacent to each other in memory):
5607be0b635ad175553d52e20eac081a
But here's the trick: when we call swap
in loop, we don't pass the values of first
and second
, we instead pass the addresses of those variables. When swap
gets called it will place its local variables somewhere else in memory, and its local pointer variable x
points to address 0x7530 (30000 in decimal, which we can also write as 0d30000) and y
points to address 0x7534 = 0d30004. So we might have the following memory map:
7e77441345c213ef88c94e22c8c23159
x
and y
are created in memory.In the next line, int temp = \*x;
, we dereference x
, which holds address 30000
, and reach out to grab the value that's being held at address 30000
, which is 0x2F=0d47
,2 which is then assigned to local variable temp
. Another way of saying this step is temp
= value pointed to by x
. The memory map now looks like:
5f4e8f1d529961fbbfa7313d8baa47ea
temp
is created within the scope of the function (and in memory). We initially give temp
the value pointed to by x
Next we dereference y
to grab the value stored at the address pointed to by y
. The address pointed to by y
is 0x7534 = 0d30004
, and so we grab the value at that location, which is 0x3B=0d59
. We then assign that value to the variable pointed at by x
. Since x
holds the address 0x7530=0d30000
, we place 0d59 into that address, which is first
's address. So now first
holds 0d59=0x3B
. Again, this is equivalent to "value pointed at by x
= value pointed at by y
. The memory map now looks like:
7608b3a739e9bcfba4d25712f38b7597
first
is now holding what is in second
. We now need to finish the job.Note that we are not changing the address held in x
; to do that, we would have written something like x = y;
.
Finally, we dereference y
to assign the value temp to the address held in y
, or "value pointed at by y
= temp
. Since y
points to 0x7534=0d30004, the value of temp
, which is 0x2F to that memory address, which is the address of second
. holds the address of the variable second
, now so does x
:
8746c47343abaa86bfae517cca46d815
And voila, we have swapped two variables without returning anything! Study what is going on here. It is confusing at first, especially coming from a Python background, but all we're doing is exchanging the values in two memory locations.
2) Writing a Function
You'll recall that previously we've been dealing with switches and been building up state machines around them so that we can respond appropriately to our interactions with them. We'd like to now write a generalized button finite state machine built around a reusable function and an arbitrary number of global variables that each hold a button state. Each button FSM will have its state encapsulated in a single integer variable provided by the user. The state can be one of two values. The values of the state are:
1
: Button is currently unpushed0
: Button is currently pushed
The system will obey the following state transition diagram and exhibit the following outputs:
2703842d7e43befc96e7212be2525c15
- If a button is in state
1
and it gets pushed, it should output a1
, and transition to state0
- If a button is in state
0
and it gets released (unpushed), it should output a-1
, and transition to state1
- In all other cases, the system should maintain its current state and output
0
In effect, we are detecting the "edges" of the button values with this state machine, so we're going to create a function responsible for handling the FSM transitions called edge_detect
. The function should be able to work on arbitrarily large numbers of button state machines (lots of buttons), provided the proper variables are used for it. The ability to work with an arbitrarily large number of state machines will come from its usage of pointers as inputs. It'll have three inputs:
input
which corresponds the current input to this button SM (fromdigitalRead
)button_state
an integer pointer corresponding to the current state of a particular button state machineoutput
an integer pointer corresponding to the current output of a particular button state machine.
In deployment this function would operate in the following manner:
const int PIN_1 = 16;
const int PIN_2 = 5;
int state_1;
int state_2;
const int LOOP_PERIOD = 30;
unsigned long timer;
void setup(){
Serial.begin(115200);
pinMode(PIN_1,INPUT_PULLUP);
pinMode(PIN_2,INPUT_PULLUP);
timer=millis();
state_1 = 1; //initialize
state_2 = 1; //initialize
}
void loop(){
int input_1 = digitalRead(PIN_1);
int input_2 = digitalRead(PIN_2);
int output_1;
int output_2;
edge_detect(input_1,&state_1,&output_1);
edge_detect(input_2,&state_2,&output_2);
char print_buffer[300]={0};
sprintf(print_buffer,"%d %d %d %d", output_1, output_2, state_1, state_2);
Serial.println(print_buffer);
//state logic below
while (millis()-timer<LOOP_PERIOD);
timer = millis();
}
Running this code for a little bit would result in the following annoted Serial monitor output. You should be able to see that even though we use one single function, we are able to properly isolate the transitions and outputs of the two separate state machines:
0 0 1 1
0 0 1 1
1 0 0 1 <---pushed button1 here
0 0 0 1
0 0 0 1
0 0 0 1
0 0 0 1
0 0 0 1
-1 0 1 1 <----released button1 here
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
1 0 0 1 <-----pushed button1 here
0 0 0 1
0 0 0 1
0 0 0 1
0 0 0 1
-1 0 1 1 <----released button 1 here
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
0 1 1 0 <----pushed button 2 here
0 0 1 0
0 0 1 0
0 0 1 0
0 0 1 0
0 -1 1 1 <---released button 2 here
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
0 1 1 0 <----pushed button 2 here
0 0 1 0
0 0 1 0
0 0 1 0
0 0 1 0
0 0 1 0
0 0 1 0
0 -1 1 1 <----released button 2 here
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
0 0 1 1
Implement a version of edge_detect
below that will behave as specified above. The outputs of the checker are similar to the transcript above in terms of what is printed.
Footnotes
1the notable exception to this are arrays which are passed in by reference, but this is also the result of an array in C++ being nothing more than a pointer