Skip to content

Commit 78a6000

Browse files
sidartGithub Executorch
authored andcommitted
Summary: PoC to run/infer a simple mode on raspberry pi pico2
Test Plan: 1. Cross compile executorch libs for rp_pi pico2 board cmake -B cmake-out -DCMAKE_TOOLCHAIN_FILE=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake -DTARGET_CPU=cortex-m0plus -DEXECUTORCH_BUILD_ARM_BAREMETAL=ON -DEXECUTORCH_PAL_DEFAULT=minimal -DEXECUTORCH_DTYPE_SELECTIVE_BUILD=ON -DCMAKE_BUILD_TYPE=MinSizeRel -DEXECUTORCH_ENABLE_LOGGING=OFF -DEXECUTORCH_SELECT_ALL_OPS=OFF -DEXECUTORCH_BUILD_EXECUTOR_RUNNER=OFF -DCMAKE_INSTALL_PREFIX=cmake-out .; cmake --build cmake-out --target install -j$(nproc)1. Cross compile executorch libs for rp_pi pico2 board cmake -B cmake-out -DCMAKE_TOOLCHAIN_FILE=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake -DTARGET_CPU=cortex-m0plus -DEXECUTORCH_BUILD_ARM_BAREMETAL=ON -DEXECUTORCH_PAL_DEFAULT=minimal -DEXECUTORCH_DTYPE_SELECTIVE_BUILD=ON -DCMAKE_BUILD_TYPE=MinSizeRel -DEXECUTORCH_ENABLE_LOGGING=OFF -DEXECUTORCH_SELECT_ALL_OPS=OFF -DEXECUTORCH_BUILD_EXECUTOR_RUNNER=OFF -DCMAKE_INSTALL_PREFIX=cmake-out .; cmake --build cmake-out --target install -j$(npro1. Cross compile executorch libs for rp_pi pico2 board cmake -B cmake-out -DCMAKE_TOOLCHAIN_FILE=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake -DTARGET_CPU=cortex-m0plus -DEXECUTORCH_BUILD_ARM_BAREMETAL=ON -DEXECUTORCH_PAL_DEFAULT=minimal -DEXECUTORCH_DTYPE_SELECTIVE_BUILD=ON -DCMAKE_BUILD_TYPE=MinSizeRel -DEXECUTORCH_ENABLE_LOGGING=OFF -DEXECUTORCH_SELECT_ALL_OPS=OFF -DEXECUTORCH_BUILD_EXECUTOR_RUNNER=OFF -DCMAKE_INSTALL_PREFIX=cmake-out .; cmake --build cmake-out --target install -j$(nproc); 2. Setup the pico SDK path : export PICO_SDK_PATH=... 3. Build pico2 firmware cd executorch/examples/arm/raspberry_pi/pico2;rm -rf build; mkdir build; cd build; cmake .. -DPICO_BOARD=pico2 -DCMAKE_BUILD_TYPE=Release; cmake --build . -j$(nproc) 4. Flash the firmware : 'executorch_pico.uf2' 5. Verify that it successfully runs / infers the simple add module Reviewers: Subscribers: Tasks: Tags:
1 parent 85e5b6e commit 78a6000

File tree

5 files changed

+447
-0
lines changed

5 files changed

+447
-0
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Copyright (c) Meta Platforms, Inc. and affiliates.
2+
# All rights reserved.
3+
# Copyright 2023-2025 Arm Limited and/or its affiliates.
4+
5+
# This source code is licensed under the BSD-style license found in the LICENSE
6+
# file in the root directory of this source tree.
7+
8+
cmake_minimum_required(VERSION 3.13)
9+
include($ENV{PICO_SDK_PATH}/external/pico_sdk_import.cmake)
10+
project(executorch_pico C CXX ASM)
11+
pico_sdk_init()
12+
13+
set(EXECUTORCH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/../../../../..)
14+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
15+
16+
# Note: Replace simple_model_pte.c with your model's pte files
17+
add_executable(executorch_pico main.cpp simple_model_pte.c)
18+
pico_enable_stdio_usb(executorch_pico 1)
19+
pico_enable_stdio_uart(executorch_pico 0)
20+
21+
# Set correct flags for Pico (Cortex-M0+)
22+
target_compile_options(executorch_pico PRIVATE
23+
-mcpu=cortex-m0plus -mfloat-abi=soft -mthumb
24+
)
25+
26+
target_include_directories(
27+
executorch_pico
28+
PRIVATE ${EXECUTORCH_ROOT} ${EXECUTORCH_ROOT}/executorch/third-party/
29+
${EXECUTORCH_ROOT}/executorch/runtime/core/portable_type/c10
30+
)
31+
add_compile_definitions(C10_USING_CUSTOM_GENERATED_MACROS)
32+
33+
# Add these optimization flags to your CMakeLists.txt
34+
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Os -ffunction-sections -fdata-sections")
35+
set(CMAKE_CXX_FLAGS
36+
"${CMAKE_CXX_FLAGS} -Os -ffunction-sections -fdata-sections")
37+
38+
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections")
39+
# Also add these size optimization definitions
40+
add_compile_definitions(
41+
EXECUTORCH_ENABLE_LOGGING=OFF EXECUTORCH_PAL_DEFAULT=minimal
42+
)
43+
44+
set(BAREMETAL_BUILD_DIR ${EXECUTORCH_ROOT}/executorch/cmake-out/)
45+
# Link Executorch and Pico libraries
46+
target_link_libraries(
47+
executorch_pico
48+
PRIVATE ${BAREMETAL_BUILD_DIR}/lib/libexecutorch.a
49+
${BAREMETAL_BUILD_DIR}/lib/libexecutorch_core.a
50+
-Wl,--whole-archive
51+
${BAREMETAL_BUILD_DIR}/lib/libportable_ops_lib.a
52+
-Wl,--no-whole-archive
53+
${BAREMETAL_BUILD_DIR}/lib/libportable_kernels.a
54+
pico_stdlib
55+
pico_stdio_usb
56+
)
57+
58+
pico_add_extra_outputs(executorch_pico)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
## Overview
2+
This document outlines the steps required to run a simple Add Module on the Pico2 microcontroller using executorch.
3+
## Steps
4+
5+
### (Pre-requisistes) Prepare the Environment for Arm
6+
1. See <a href="https://docs.pytorch.org/executorch/main/tutorial-arm.html#set-up-the-developer-environment"/> for instructions on setting up the environment for Arm.
7+
2. Make sure you have the toolchain configured correctly.
8+
```bash
9+
which arm-none-eabi-gcc
10+
``` should return something like 'executorch/examples/arm/ethos-u-scratch/arm-gnu-toolchain-13.3.rel1-x86_64-arm-none-eabi/bin/arm-none-eabi-gcc'
11+
12+
### 1. Cross Compile Executorch for Arm Cortex M Target
13+
To begin, navigate to the executorch root directory and execute the following commands:
14+
```bash
15+
cmake -B cmake-out \
16+
-DCMAKE_TOOLCHAIN_FILE=examples/arm/ethos-u-setup/arm-none-eabi-gcc.cmake \
17+
-DTARGET_CPU=cortex-m0plus \
18+
-DEXECUTORCH_BUILD_ARM_BAREMETAL=ON \
19+
-DEXECUTORCH_PAL_DEFAULT=minimal \
20+
-DEXECUTORCH_DTYPE_SELECTIVE_BUILD=ON \
21+
-DCMAKE_BUILD_TYPE=MinSizeRel \
22+
-DEXECUTORCH_ENABLE_LOGGING=OFF \
23+
-DEXECUTORCH_SELECT_ALL_OPS=OFF \
24+
-DEXECUTORCH_BUILD_EXECUTOR_RUNNER=OFF \
25+
-DCMAKE_INSTALL_PREFIX=cmake-out .; \
26+
cmake --build cmake-out --target install -j$(nproc);
27+
```
28+
29+
### 2. Export PICO_SDK_PATH
30+
Download the Pico SDK from GitHub: https://github.com/raspberrypi/pico-sdk and set the PICO_SDK_PATH environment variable:
31+
```bash
32+
export PICO_SDK_PATH=<path_to_local_pico_sdk_folder>
33+
```
34+
35+
### 3. Build the example for Pico2
36+
Go to the example directory and initiate the build process:
37+
```bash
38+
cd examples/arm/raspberry_pi/pico2/
39+
rm -rf build
40+
mkdir build
41+
cd build
42+
cmake .. -DPICO_BOARD=pico2 -DCMAKE_BUILD_TYPE=Release
43+
cmake --build . -j$(nproc)
44+
```
45+
This step will generate the firmware file executorch_pico.uf2
46+
47+
### 4. Flash the Firmware
48+
Press and hold the BOOTSEL button on the Pico2.
49+
Connect the Pico2 to your computer; it should mount as RPI-RP2.
50+
Copy the executorch_pico.uf2 file to the mounted drive.
51+
52+
### 5. Verify the Firmware
53+
Check that the Pico2's LED blinks 10 times at 500 ms interval to confirm successful firmware execution.
54+
You should see the output (if the serial port is connected, see below for details) :
55+
````bash
56+
Method loaded [forward]
57+
Output: 13.000000, 136.000000, 24.000000, 131.000000
58+
```
59+
60+
### 6. Steps to debug / triage using a serial terminal
61+
62+
On macOS or Linux, run the following command to open a serial terminal for the Pico2:
63+
```bash
64+
screen /dev/tty.usbmodem1101 115200
65+
```
66+
67+
Make sure to replace /dev/tty.usbmodem1101 with the actual device path for your Pico if different.
68+
This will open a serial terminal at 115200 baud rate, where you should see the printf output from your program, including any logs or error messages printed during execution.
69+
If you see the LED blink 10 times at 100 ms interval, that indicates your program reached the error indicator code, so you should also see the corresponding logs in this terminal.
70+
71+
These steps complete the process required to run the simple Add Module on the Pico2 microcontroller using executorch.
72+
73+
### 6. Other Tips
74+
75+
a. The pte_to_header.py script converts binary PTE files into C++ header files containing byte arrays.
76+
```bash
77+
python ./examples/arm/executor_runner/pte_to_header.py -p model.pte
78+
```
79+
80+
b. The following command will generate a simple_ops.txt file with the list of operators used in any given model. This can be used to verify all the operators that should be included in the build.
81+
From the root executor dir,
82+
```bash
83+
python -m executorch.codegen.tools.gen_oplist --output_path simple_ops.txt --model_file_path ./model.pte
84+
```
85+
86+
c.
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
/* Copyright (c) Meta Platforms, Inc. and affiliates.
2+
* All rights reserved.
3+
* Copyright 2023-2025 Arm Limited and/or its affiliates.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree.
7+
*/
8+
9+
// Model data
10+
#include "simple_model_pte.h"
11+
12+
// Pico includes
13+
#include "pico/stdio_usb.h"
14+
#include "pico/stdlib.h"
15+
16+
// Executorch includes
17+
#include <executorch/extension/data_loader/buffer_data_loader.h>
18+
#include <executorch/runtime/core/portable_type/scalar_type.h>
19+
#include <executorch/runtime/executor/memory_manager.h>
20+
#include <executorch/runtime/executor/method.h>
21+
#include <executorch/runtime/executor/program.h>
22+
#include <executorch/runtime/platform/runtime.h>
23+
24+
// Std c++ includes
25+
#include <memory>
26+
27+
using namespace executorch::runtime;
28+
using executorch::aten::Tensor;
29+
using executorch::aten::TensorImpl;
30+
using ScalarType = executorch::runtime::etensor::ScalarType;
31+
using executorch::runtime::runtime_init;
32+
33+
// Define GPIO pins for indicators
34+
const uint INDICATOR_PIN_1 = 25; // Onboard LED
35+
const uint INDICATOR_PIN_2 = 22; // External LED
36+
const uint INDICATOR_PIN_3 = 23; // Onboard LED
37+
38+
void init_gpio_pins() {
39+
gpio_init(INDICATOR_PIN_1);
40+
gpio_set_dir(INDICATOR_PIN_1, GPIO_OUT);
41+
gpio_init(INDICATOR_PIN_2);
42+
gpio_set_dir(INDICATOR_PIN_2, GPIO_OUT);
43+
gpio_init(INDICATOR_PIN_3);
44+
gpio_set_dir(INDICATOR_PIN_3, GPIO_OUT);
45+
}
46+
47+
void wait_for_usb() {
48+
const int kMaxRetryCount = 10;
49+
int retry_usb_count = 0;
50+
while (!stdio_usb_connected() && retry_usb_count++ < kMaxRetryCount) {
51+
printf("Retry again! USB not connected \n");
52+
sleep_ms(1000);
53+
}
54+
}
55+
56+
// Helper function to blink an indicator pin on the pico board a given number of
57+
// times
58+
void blink_indicator(uint pin, int times, int delay_ms = 100) {
59+
for (int i = 0; i < times; ++i) {
60+
gpio_put(pin, 1);
61+
sleep_ms(delay_ms);
62+
gpio_put(pin, 0);
63+
sleep_ms(delay_ms);
64+
}
65+
}
66+
67+
bool load_and_prepare_model(
68+
std::unique_ptr<Program>& program_ptr,
69+
std::unique_ptr<Method>& method_ptr,
70+
MemoryManager& memory_manager) {
71+
executorch::extension::BufferDataLoader loader(model_pte, model_pte_len);
72+
auto program_result = Program::load(&loader);
73+
if (!program_result.ok()) {
74+
printf("Failed to load model: %d\n", (int)program_result.error());
75+
blink_indicator(INDICATOR_PIN_1, 10);
76+
return false;
77+
}
78+
program_ptr = std::make_unique<Program>(std::move(*program_result));
79+
auto method_name_result = program_ptr->get_method_name(0);
80+
if (!method_name_result.ok()) {
81+
printf("Failed to get method name: %d\n", (int)method_name_result.error());
82+
blink_indicator(INDICATOR_PIN_1, 10);
83+
return false;
84+
}
85+
auto method_result =
86+
program_ptr->load_method(*method_name_result, &memory_manager);
87+
if (!method_result.ok()) {
88+
printf("Failed to load method: %d\n", (int)method_result.error());
89+
blink_indicator(INDICATOR_PIN_1, 10);
90+
return false;
91+
}
92+
method_ptr = std::make_unique<Method>(std::move(*method_result));
93+
printf("Method loaded [%s]\n", *method_name_result);
94+
return true;
95+
}
96+
97+
bool run_inference(Method& method) {
98+
float input_data_0[4] = {4.0, 109.0, 13.0, 123.0};
99+
float input_data_1[4] = {9.0, 27.0, 11.0, 8.0};
100+
TensorImpl::SizesType sizes[1] = {4};
101+
TensorImpl::DimOrderType dim_order[] = {0};
102+
TensorImpl impl0(ScalarType::Float, 1, sizes, input_data_0, dim_order);
103+
TensorImpl impl1(ScalarType::Float, 1, sizes, input_data_1, dim_order);
104+
Tensor input0(&impl0);
105+
Tensor input1(&impl1);
106+
107+
if (method.set_input(input0, 0) != Error::Ok ||
108+
method.set_input(input1, 1) != Error::Ok) {
109+
printf("Failed to set input(s)\n");
110+
blink_indicator(INDICATOR_PIN_1, 10);
111+
return false;
112+
}
113+
if (method.execute() != Error::Ok) {
114+
printf("Failed to execute\n");
115+
blink_indicator(INDICATOR_PIN_1, 10);
116+
return false;
117+
}
118+
const EValue& output = method.get_output(0);
119+
if (output.isTensor()) {
120+
const float* out_data = output.toTensor().const_data_ptr<float>();
121+
printf(
122+
"Output: %f, %f, %f, %f\n",
123+
out_data[0],
124+
out_data[1],
125+
out_data[2],
126+
out_data[3]);
127+
} else {
128+
printf("Output is not a tensor!\n");
129+
blink_indicator(INDICATOR_PIN_1, 10);
130+
return false;
131+
}
132+
return true;
133+
}
134+
135+
int executor_runner() {
136+
init_gpio_pins();
137+
stdio_init_all();
138+
sleep_ms(1000);
139+
140+
wait_for_usb();
141+
runtime_init();
142+
143+
static uint8_t method_allocator_pool[32 * 1024]; // 32KB
144+
static uint8_t activation_pool[64 * 1024]; // 64KB
145+
MemoryAllocator method_allocator(
146+
sizeof(method_allocator_pool), method_allocator_pool);
147+
method_allocator.enable_profiling("method allocator");
148+
Span<uint8_t> memory_planned_buffers[1]{
149+
{activation_pool, sizeof(activation_pool)}};
150+
HierarchicalAllocator planned_memory({memory_planned_buffers, 1});
151+
MemoryManager memory_manager(&method_allocator, &planned_memory);
152+
153+
std::unique_ptr<Program> program_ptr;
154+
std::unique_ptr<Method> method_ptr;
155+
if (!load_and_prepare_model(program_ptr, method_ptr, memory_manager)) {
156+
printf("Failed to load and prepare model\n");
157+
return 1;
158+
}
159+
if (!run_inference(*method_ptr)) {
160+
printf("Failed to run inference\n");
161+
return 1;
162+
}
163+
164+
// If everything went well, it will blink the indicator pin
165+
blink_indicator(INDICATOR_PIN_1, 10, 500);
166+
return 0;
167+
}
168+
169+
int main() {
170+
return executor_runner();
171+
}

0 commit comments

Comments
 (0)