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 ints, 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):
5607be0b635ad175553d52e20eac081aBut 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:
7e77441345c213ef88c94e22c8c23159x 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:
5f4e8f1d529961fbbfa7313d8baa47eatemp is created within the scope of the function (and in memory). We initially give temp the value pointed to by xNext 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:
7608b3a739e9bcfba4d25712f38b7597first 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:
8746c47343abaa86bfae517cca46d815And 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
1and it gets pushed, it should output a1, and transition to state0 - If a button is in state
0and 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:
inputwhich corresponds the current input to this button SM (fromdigitalRead)button_statean integer pointer corresponding to the current state of a particular button state machineoutputan 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