RP2040 has Dual Cortex M0+ processor cores which can run upto 133MHz independent of each other. However, the second core (core 1) is asleep on boot and needs to be waken up by a function call from the first core (core 0). This program compares the performance of the two cores by incrementing two different variables on each core. The resources for the project include the C SDK User Guide, the RP2040 Datasheet and Prof. Hunter's website.
/*
* Parth Sarthi Sharma (pss242@cornell.edu)
* Code based on examples from Raspberry Pi Foundation.
* The code wakes up core 1 from its slumber and increments two
* different variables on each core in order to compare the
* performance of the cores.
*/
#include <stdio.h> //The standard C library
#include "pico/stdlib.h" //Standard library for Pico
#include "pico/time.h" //The pico time library
#include "pico/multicore.h" //The pico multicore library
#include "hardware/gpio.h" //The hardware GPIO library
#define BUTTON 5 //The pushbutton
int i = 0, j = 0; //The two variables to be incremented
void core1_entry() { //The program running on core 1
uint64_t startTimeCore1 = get_absolute_time(); //Fetch the time at which core 1 starts executing the program
printf("Core 1 Start Time: %lld.\n", startTimeCore1); //Print out the time at which core 1 starts executing the program
while(1){ //While eternity
if(j < 100000000){ //As long as j is less than 100000000
j++; //Increment j
}
else if(j == 100000000){ //As soon as j hits 100000000
uint64_t timeTaken = get_absolute_time() - startTimeCore1; //Get the time it took for core 1 to finish the job
printf("Core 1 Time Taken: %lld.\n", timeTaken); //Print out the time it took for core 1 to finish the job
j++; //Increment j to stop printing
}
else if(gpio_get(BUTTON)){ //If the button has been pressed
j = 0; //Reset j to start the performance test again
startTimeCore1 = get_absolute_time(); //Reset the start time
printf("Core 1 Start Time: %lld.\n", startTimeCore1); //Print out start time
}
}
}
int main() { //The program running on core 0
stdio_init_all(); //Initialize all of the present standard stdio types that are linked into the binary
gpio_init(BUTTON);//Initialize the pushbutton pin
gpio_set_dir(BUTTON, GPIO_IN); //Initialize the pushbutton pin to be input
sleep_ms(10000); //Sleep for 10000 milliseconds
multicore_launch_core1(core1_entry); //Reset core1 and enter the core1_entry function on core 1 using the default core 1 stack
uint64_t startTimeCore0 = get_absolute_time(); //Fetch the time at which core 0 starts executing the program
printf("Core 0 Start Time: %lld.\n", startTimeCore0); //Print out the time at which core 0 starts executing the program
while(1){ //While eternity
if(i < 100000000){ //As long as i is less than 100000000
i++; //Increment i
}
else if(i == 100000000){ //As soon as i hits 100000000
uint64_t timeTaken = get_absolute_time() - startTimeCore0; //Get the time it took for core 0 to finish the job
printf("Core 0 Time Taken: %lld.\n", timeTaken); //Print out the time it took for core 0 to finish the job
i++; //Increment i to stop printing
}
else if(gpio_get(BUTTON)){ //If the button has been pressed
i = 0; //Reset i to start the performance test again
startTimeCore0 = get_absolute_time(); //Reset the start time
printf("Core 0 Start Time: %lld.\n", startTimeCore0); //Print out start time
}
}
}
The first lines of code in the C source file include some header files. One of these is standard C headers (stdio.h
) and the others are headers which come from the C SDK for the Raspberry Pi Pico. The first of these, pico/stdlib.h
is what the SDK calls a "High-Level API." These high-level API's "provide higher level functionality that isn’t hardware related or provides a richer set of functionality above the basic hardware interfaces." The architecture of this SDK is described at length in the SDK manual. All libraries within the SDK are INTERFACE libraries.
The next includes pull in hardware APIs which are not already brought in by pico/stdlib.h
. These include hardware/gpio.h
, pico/time.h
and pico/multicore.h
. As the names suggest, these interface libraries give us access to the API's associated with the hardware GPIO, pico time and pico multicore on the RP2040.
Don't forget to link these in the CMakeLists.txt file!
#include <stdio.h>
#include "pico/stdlib.h"
#include "pico/time.h"
#include "pico/multicore.h"
#include "hardware/gpio.h"
The next section of the code is the #define
's and the global variables which will be used throughout the code. The #define
is the pushbutton pin declaration on GPIO 5. The variables declared are i
and j
which are used as the incrementers to test the performance of the cores.
#define BUTTON 5
int i = 0, j = 0;
The core 1 function is the function which runs on the core 1 once it wakes up from its slumber. In other terms, this function is the main()
function for core 1 and runs independent of the actual main()
function running on core 0 (unless there is an intra-core communication). The core1_entry()
function follows the following algorithm:
j
is less than 100000000, increment j
.j
is equal to 100000000, calculate the time taken by core 1 to finish the job and print it out. Increment j
to avoid multiple prints.j
is greater than 100000000 and the button has been pushed, reset j
and the start time to start the job again.void core1_entry() {
uint64_t startTimeCore1 = get_absolute_time();
printf("Core 1 Start Time: %lld.\n", startTimeCore1);
while(1){
if(j < 100000000){
j++;
}
else if(j == 100000000){
uint64_t timeTaken = get_absolute_time() - startTimeCore1;
printf("Core 1 Time Taken: %lld.\n", timeTaken);
j++;
}
else if(gpio_get(BUTTON)){
j = 0;
startTimeCore1 = get_absolute_time();
printf("Core 1 Start Time: %lld.\n", startTimeCore1);
}
}
}
stdio_init_all();
In the next 2 lines of the code, I initialized the button pin and configured it to be the input pin. The gpio_init()
function is used to initialize the pin and the gpio_set_dir()
function us used to set the pin direction which can be GPIO_OUT (output) or GPIO_IN (input).
gpio_init(BUTTON);
gpio_set_dir(BUTTON, GPIO_IN);
In order to wake up the core 1 from sleep, I used the multicore_launch_core1()
function. This function resets core 1 and enters the given function on core 1 using the default core 1 stack (below core 0 stack).
multicore_launch_core1(core1_entry);
This part of the program is quite similar to the core1_entry()
function. It runs on the core 0 and follows the following algorithm:
i
is less than 100000000, increment i
.i
is equal to 100000000, calculate the time taken by core 0 to finish the job and print it out. Increment i
to avoid multiple prints.i
is greater than 100000000 and the button has been pushed, reset i
and the start time to start the job again.uint64_t startTimeCore0 = get_absolute_time();
printf("Core 0 Start Time: %lld.\n", startTimeCore0);
while(1){
if(i < 100000000){
i++;
}
else if(i == 100000000){
uint64_t timeTaken = get_absolute_time() - startTimeCore0;
printf("Core 0 Time Taken: %lld.\n", timeTaken);
i++;
}
else if(gpio_get(BUTTON)){
i = 0;
startTimeCore0 = get_absolute_time();
printf("Core 0 Start Time: %lld.\n", startTimeCore0);
}
}
In order to view the output, I used the serial monitor provided by the Arduino IDE. As it is quite evident from the provided data, core 0 takes an average of 90 milliseconds more than core 1 to finish the same job.
Iteration | Time taken by Core 0 | Time taken by Core 1 |
---|---|---|
1 | 7779846 | 7690696 |
2 | 7791554 | 7697507 |
3 | 7781144 | 7692173 |
4 | 7791770 | 7702660 |
Average | 7786078 | 7695759 |
cmake_minimum_required(VERSION 3.13)
include(pico_sdk_import.cmake)
project(MultiCoreMultiVarPerf)
pico_sdk_init()
add_executable(MultiCoreMultiVarPerf MultiCoreMultiVarPerf.c)
pico_enable_stdio_usb(MultiCoreMultiVarPerf 1)
pico_enable_stdio_uart(MultiCoreMultiVarPerf 1)
pico_add_extra_outputs(MultiCoreMultiVarPerf)
target_link_libraries(MultiCoreMultiVarPerf pico_stdlib pico_time pico_multicore hardware_gpio hardware_sync)