element14 Community
element14 Community
    Register Log In
  • Site
  • Search
  • Log In Register
  • Community Hub
    Community Hub
    • What's New on element14
    • Feedback and Support
    • Benefits of Membership
    • Personal Blogs
    • Members Area
    • Achievement Levels
  • Learn
    Learn
    • Ask an Expert
    • eBooks
    • element14 presents
    • Learning Center
    • Tech Spotlight
    • STEM Academy
    • Webinars, Training and Events
    • Learning Groups
  • Technologies
    Technologies
    • 3D Printing
    • FPGA
    • Industrial Automation
    • Internet of Things
    • Power & Energy
    • Sensors
    • Technology Groups
  • Challenges & Projects
    Challenges & Projects
    • Design Challenges
    • element14 presents Projects
    • Project14
    • Arduino Projects
    • Raspberry Pi Projects
    • Project Groups
  • Products & Partners
    Products & Partners
    • Arduino
    • Avnet & Tria Boards Community
    • Dev Tools
    • Manufacturers
    • Multicomp Pro
    • Product Groups
    • Raspberry Pi
    • RoadTests & Reviews
  • About Us
    About the element14 Community
  • Store
    Store
    • Visit Your Store
    • Choose another store...
      • Europe
      •  Austria (German)
      •  Belgium (Dutch, French)
      •  Bulgaria (Bulgarian)
      •  Czech Republic (Czech)
      •  Denmark (Danish)
      •  Estonia (Estonian)
      •  Finland (Finnish)
      •  France (French)
      •  Germany (German)
      •  Hungary (Hungarian)
      •  Ireland
      •  Israel
      •  Italy (Italian)
      •  Latvia (Latvian)
      •  
      •  Lithuania (Lithuanian)
      •  Netherlands (Dutch)
      •  Norway (Norwegian)
      •  Poland (Polish)
      •  Portugal (Portuguese)
      •  Romania (Romanian)
      •  Russia (Russian)
      •  Slovakia (Slovak)
      •  Slovenia (Slovenian)
      •  Spain (Spanish)
      •  Sweden (Swedish)
      •  Switzerland(German, French)
      •  Turkey (Turkish)
      •  United Kingdom
      • Asia Pacific
      •  Australia
      •  China
      •  Hong Kong
      •  India
      •  Japan
      •  Korea (Korean)
      •  Malaysia
      •  New Zealand
      •  Philippines
      •  Singapore
      •  Taiwan
      •  Thailand (Thai)
      •  Vietnam
      • Americas
      •  Brazil (Portuguese)
      •  Canada
      •  Mexico (Spanish)
      •  United States
      Can't find the country/region you're looking for? Visit our export site or find a local distributor.
  • Translate
  • Profile
  • Settings
Raspberry Pi
  • Products & Partners
  • More
Raspberry Pi
Blog Pico PID controller without floating point using DSP C++ fixed point API
  • Blog
  • Forum
  • Documents
  • Quiz
  • Events
  • Polls
  • Files
  • Members
  • Mentions
  • Sub-Groups
  • Tags
  • More
  • Cancel
  • New
Join Raspberry Pi to participate - click to join for free!
Featured Articles
Announcing Pi
Technical Specifications
Raspberry Pi FAQs
Win a Pi
GPIO Pinout
Raspberry Pi Wishlist
Comparison Chart
Quiz
  • Share
  • More
  • Cancel
Group Actions
  • Group RSS
  • More
  • Cancel
Engagement
  • Author Author: Jan Cumps
  • Date Created: 23 Oct 2026 9:36 AM Date Created
  • Views 21 views
  • Likes 2 likes
  • Comments 0 comments
Related
Recommended
  • pico2
  • c++26
  • pico_eurocard
  • pico c sdk
  • pid_controller
  • c++

Pico PID controller without floating point using DSP C++ fixed point API

Jan Cumps
Jan Cumps
23 Oct 2026

This post is a proof of concept. A test to show that the generic PID class I wrote with a TI MSPM0, is actually generic.
In, this post, I make it run on a Raspberry Pico2.

The design is a stabile DC voltage supply.

  • Pico PWM generates a DC voltage. The level is set by changing the duty cycle between 0% (0 V) and 100% (3.3 V)
  • a low-pass RC filter turns the PWM signal into DC.
  • Pico ADC monitors that DC value (feedback).
  • my PID class controls the PWM, based on desired output, and the ADC feedback.

For a full design explanation, see  PID controller without floating point for ARM controllers using DSP C++ fixed point API . This post uses the exact same design. Only the peripherals are ported from the MSPM0 SDK to Pico SDK: GPIO, ADC, PWM and timer tick.

image

Differences compared to MSPM0 design:

  • the Pico can run the PWM at higher frequency, with the same duty cycle resolution (0-4095): MSPM0: 488 Hz, Pico 36 kHz.
  • It executes the PID loop faster. With compiler optimisation to max: MSPM0: 140 µs, Pico RP2350: 2.8 µs

Same as MSPM0 design:

  • identical code, except the SDK calls to GPIO, PWM, ADC and timer
  • same resolution of ADC and PWM duty cycle
  • same GCC ARM compiler version and optimisation settings

Pico port of the PID example

source

#include "fixed_point.hpp"


#include "hardware/adc.h"
#include "hardware/gpio.h"
#include "hardware/pwm.h"
#include "pico/time.h"


import pid;

using namespace arm_cmsis_dsp;

using fp_t = Q15;
constexpr Q15 operator""_fp(long double val) {
        return operator""_q15(val);
}

using pid_controller_t = pid::pid<fp_t>;

// PID loop timer
volatile bool perform = false;
struct repeating_timer timer;
bool repeating_timer_callback(struct repeating_timer *t) {
    perform = true;   
    return true; 
}

// ADC and PWM declarations
// Pico SDK types are used here:
const uint ADC_PIN = 26;
const uint PWM_PIN = 0;
const uint PWM_CHANNEL = 0;
// frequency of PWM will be 125 MHz / (PWM_MAX * CKDIV)
// i.e. about 30.5 kHz for PWM_MAX = 4095
const uint PWM_MAX = 4095;
const float CKDIV = 1; // set CKDIV to 1 for approx 30.5 kHz PWM frequency if PWM_MAX is 4095
uint slice_num;

// PID variables and objects
fp_t feedback;
constinit fp_t target_command = 0.66_fp; // Example target command in fixed point per-unit (0.0 to 1.0)
constinit fp_t output_pid = 0.0_fp; // Initialize output_pid to 0.0 in fixed point per-unit
pid_controller_t controller(0.1_fp, 0.01_fp, 0.05_fp); // Example PID gains for voltage control

// GPIO pin, to measure duration of a PID loop
const uint GPIO_PIN = 22; // happens to be the Pico Eurocard LED

void init_adc() {
    adc_init();
    adc_gpio_init(ADC_PIN);
    adc_select_input(0);
}

void perform_adc() {
    uint16_t adc = adc_read();
    feedback.v = static_cast<int16_t>((adc) << 3); // Direct to 0.0 - 1.0 per-unit in Q15!
}

void init_pwm() {
    gpio_set_function(PWM_PIN, GPIO_FUNC_PWM);
    slice_num = pwm_gpio_to_slice_num(PWM_PIN);
    pwm_set_clkdiv(slice_num, CKDIV);
    pwm_set_wrap(slice_num, PWM_MAX-1);
    pwm_set_chan_level(slice_num, PWM_CHANNEL, 0);
    pwm_set_enabled(slice_num, true);
}

void perform_pwm() {
    // Convert Q15 per-unit (0.0 to 1.0 excluded) to raw timer counts (0 to 65535)
    // the level will be 0 - 4095, 12 bits
    pwm_set_chan_level(slice_num, PWM_CHANNEL, static_cast<std::uint16_t>(output_pid.v >> 3)); // set PWM value
}

void init_gpio() {
    gpio_init(GPIO_PIN);
    gpio_put(GPIO_PIN, 0);
    gpio_set_dir(GPIO_PIN, true);
}

void perform_pid() {         
    output_pid = controller.compute(target_command, feedback);
}

int main() {
    init_adc();
    init_pwm();

    init_gpio();

    // PWM is +- 30 (36.6) kHz, set PID loop to 1/10: 3.66 kHz
    add_repeating_timer_us(-274, repeating_timer_callback, NULL, &timer); // call PID every 274 µs, Start-tot-Start timing
    
    while (1) {
        if (perform) {
            perform = false;

            gpio_xor_mask(1u << GPIO_PIN); // toggle before

            perform_adc();
            perform_pid();
            perform_pwm();

            gpio_xor_mask(1u << GPIO_PIN); // toggle after
        }
    }
}

CMake

cmake_minimum_required(VERSION 3.28)

set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 26)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fmodules-ts -fcommon -fno-rtti -fno-exceptions")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mthumb -mfpu=fpv5-sp-d16")
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# set(PICO_DEOPTIMIZED_DEBUG 1)

# This block automatically detects the active selection in VS Code
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
    
    # 1. If you select "Debug" in VS Code:
    set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -gstrict-dwarf -gdwarf-3" CACHE STRING "Debug flags" FORCE)
    
    # 2. If you select "Release" in VS Code:
    set(CMAKE_CXX_FLAGS_RELEASE "-O3" CACHE STRING "Release flags" FORCE)

endif()

# path to PICO utilities (if needed)
if (NOT DEFINED ENV{PIOASM_DIR})
        message("PIOASM: set PIOASM_DIR env variable if you use PIO")
else()
        message("PIOASM: PIOASM_DIR env variable is set to '$ENV{PIOASM_DIR}'")
        set(pioasm_DIR $ENV{PIOASM_DIR})
endif()
if (NOT DEFINED ENV{PICOTOOL_DIR})
        message("PICOTOOL: set PICOTOOL_DIR env variable if you create uf2 output with pico_add_extra_outputs()")
else()
        message("PICOTOOL: PICOTOOL_DIR env variable is set to '$ENV{PICOTOOL_DIR}'")
        set(picotool_DIR $ENV{PICOTOOL_DIR})
endif()

# set(PICO_PLATFORM rp2350-riscv CACHE STRING "Pico Platform")
set(PICO_PLATFORM rp2350-arm-s CACHE STRING "Pico Platform")
set(PICO_BOARD pico2 CACHE STRING "Board type")


include(pico_sdk_import.cmake)

project(pico_pid_template C CXX ASM)

pico_sdk_init()

include(FetchContent)

# CMSIS DSP fixed point c++ library dsppp
FetchContent_Declare(dsp_fixed_point
  GIT_REPOSITORY "https://github.com/ARM-software/CMSIS-DSP.git"
  GIT_TAG HEAD
  GIT_SHALLOW OFF # needed to find the commit HEAD
  # prevent makefile execution (if any)
  SOURCE_SUBDIR =
)
FetchContent_MakeAvailable(dsp_fixed_point)

# PID embedded C++ library
FetchContent_Declare(pid_embedded
  GIT_REPOSITORY "https://github.com/jancumps/pid_embedded_cpp.git"
  GIT_TAG origin/main
  # prevent makefile execution (if any)
  SOURCE_SUBDIR =
)
FetchContent_MakeAvailable(pid_embedded)

include_directories( # this project uses CMSIS DSP fixed point library, so include the path to the CMSIS DSP headers
    ${PICO_SDK_PATH}/src/rp2_common/cmsis/stub/CMSIS/Core/Include
    ${dsp_fixed_point_SOURCE_DIR}/dsppp/Include/dsppp
)


add_executable(${CMAKE_PROJECT_NAME})

# 4. Voeg ook de CMSIS definitie toe voor ARM-modus
target_compile_definitions(${CMAKE_PROJECT_NAME} PRIVATE 
    ARM_MATH_CM33=1
)

target_sources(${CMAKE_PROJECT_NAME}
        PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/main.cpp
)
target_sources(${CMAKE_PROJECT_NAME}
        PUBLIC
        FILE_SET cxx_modules TYPE CXX_MODULES FILES
        ${pid_embedded_SOURCE_DIR}/source/pid_template.cpp
)

target_link_libraries( ${CMAKE_PROJECT_NAME}
        pico_stdlib
        hardware_adc
        hardware_gpio
        hardware_pwm
)

pico_add_extra_outputs(${CMAKE_PROJECT_NAME} )

references:

Development kit:  Pi Pico Eurocard Development Board 

MSPM0 blog:  PID controller without floating point for ARM controllers using DSP C++ fixed point API 

PID class GitHub repository

Thank you for reading

  • Sign in to reply
element14 Community

element14 is the first online community specifically for engineers. Connect with your peers and get expert answers to your questions.

  • Members
  • Learn
  • Technologies
  • Challenges & Projects
  • Products
  • Store
  • About Us
  • Feedback & Support
  • FAQs
  • Terms of Use
  • Privacy Policy
  • Legal and Copyright Notices
  • Sitemap
  • Cookies

An Avnet Company © 2026 Premier Farnell Limited. All Rights Reserved.

Premier Farnell Ltd, registered in England and Wales (no 00876412), registered office: Farnell House, Forge Lane, Leeds LS12 2NE.

Follow element14

  • X
  • Facebook
  • linkedin
  • YouTube