Moved common PWM and MultiPWM out of servo driver, and renamed to cluster

servo-pio
ZodiusInfuser 2022-02-20 11:00:36 +00:00
rodzic 06c272916c
commit aeb9705d15
20 zmienionych plików z 150 dodań i 106 usunięć

Wyświetl plik

@ -26,4 +26,5 @@ add_subdirectory(icp10125)
add_subdirectory(scd4x)
add_subdirectory(hub75)
add_subdirectory(uc8151)
add_subdirectory(pwm)
add_subdirectory(servo)

Wyświetl plik

@ -0,0 +1,2 @@
include(pwm.cmake)
include(pwm_cluster.cmake)

Wyświetl plik

@ -0,0 +1,12 @@
set(DRIVER_NAME pwm)
add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/pwm.cpp
)
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(${DRIVER_NAME} INTERFACE
pico_stdlib
)

Wyświetl plik

@ -0,0 +1,47 @@
#include "pwm.hpp"
#include "hardware/clocks.h"
namespace pimoroni {
static const uint32_t MAX_PWM_WRAP = UINT16_MAX;
// Derived from the rp2 Micropython implementation: https://github.com/micropython/micropython/blob/master/ports/rp2/machine_pwm.c
bool calculate_pwm_factors(float freq, uint16_t& top_out, uint16_t& div16_out) {
bool success = false;
uint32_t source_hz = clock_get_hz(clk_sys);
// Check the provided frequency is valid
if((freq >= 1.0f) && (freq <= (float)(source_hz >> 1))) {
uint32_t div16_top = (uint32_t)((float)(source_hz << 4) / freq);
uint32_t top = 1;
while(true) {
// Try a few small prime factors to get close to the desired frequency.
if((div16_top >= (5 << 4)) && (div16_top % 5 == 0) && (top * 5 <= MAX_PWM_WRAP)) {
div16_top /= 5;
top *= 5;
}
else if((div16_top >= (3 << 4)) && (div16_top % 3 == 0) && (top * 3 <= MAX_PWM_WRAP)) {
div16_top /= 3;
top *= 3;
}
else if((div16_top >= (2 << 4)) && (top * 2 <= MAX_PWM_WRAP)) {
div16_top /= 2;
top *= 2;
}
else {
break;
}
}
// Only return valid factors if the divisor is actually achievable
if(div16_top >= 16 && div16_top <= (UINT8_MAX << 4)) {
top_out = top;
div16_out = div16_top;
success = true;
}
}
return success;
}
};

Wyświetl plik

@ -0,0 +1,7 @@
#pragma once
#include "pico/stdlib.h"
namespace pimoroni {
bool calculate_pwm_factors(float freq, uint16_t& top_out, uint16_t& div16_out);
}

Wyświetl plik

@ -1,8 +1,8 @@
set(DRIVER_NAME multi_pwm)
set(DRIVER_NAME pwm_cluster)
add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/multi_pwm.cpp
${CMAKE_CURRENT_LIST_DIR}/pwm_cluster.cpp
)
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
@ -13,4 +13,4 @@ target_link_libraries(${DRIVER_NAME} INTERFACE
hardware_dma
)
pico_generate_pio_header(${DRIVER_NAME} ${CMAKE_CURRENT_LIST_DIR}/multi_pwm.pio)
pico_generate_pio_header(${DRIVER_NAME} ${CMAKE_CURRENT_LIST_DIR}/pwm_cluster.pio)

Wyświetl plik

@ -1,12 +1,13 @@
#include "multi_pwm.hpp"
#include "pwm_cluster.hpp"
#include <cstdio> // TOREMOVE once done debugging
#include "hardware/gpio.h" // TOREMOVE once done debugging
#include "hardware/clocks.h"
#include "pwm_cluster.pio.h"
// Uncomment the below line to enable debugging
#define DEBUG_MULTI_PWM
namespace servo {
namespace pimoroni {
#ifdef DEBUG_MULTI_PWM
static const uint DEBUG_SIDESET = 17;
@ -76,11 +77,11 @@ interrupt is fired, and the handler reconfigures channel A so that it is ready f
* */
MultiPWM::MultiPWM(PIO pio, uint sm, uint channel_mask) : pio(pio), sm(sm), channel_mask(channel_mask) {
PWMCluster::PWMCluster(PIO pio, uint sm, uint channel_mask) : pio(pio), sm(sm), channel_mask(channel_mask) {
#ifdef DEBUG_MULTI_PWM
pio_program_offset = pio_add_program(pio, &debug_multi_pwm_program);
pio_program_offset = pio_add_program(pio, &debug_pwm_cluster_program);
#else
pio_program_offset = pio_add_program(pio, &multi_pwm_program);
pio_program_offset = pio_add_program(pio, &pwm_cluster_program);
#endif
channel_polarities = 0x00000000;
wrap_level = 0;
@ -109,9 +110,9 @@ MultiPWM::MultiPWM(PIO pio, uint sm, uint channel_mask) : pio(pio), sm(sm), chan
#endif
#ifdef DEBUG_MULTI_PWM
pio_sm_config c = debug_multi_pwm_program_get_default_config(pio_program_offset);
pio_sm_config c = debug_pwm_cluster_program_get_default_config(pio_program_offset);
#else
pio_sm_config c = multi_pwm_program_get_default_config(pio_program_offset);
pio_sm_config c = pwm_cluster_program_get_default_config(pio_program_offset);
#endif
sm_config_set_out_pins(&c, 0, irq_gpio); //TODO change this to be 32
#ifdef DEBUG_MULTI_PWM
@ -189,14 +190,14 @@ MultiPWM::MultiPWM(PIO pio, uint sm, uint channel_mask) : pio(pio), sm(sm), chan
//dma_start_channel_mask(1u << ctrl_dma_channel);
}
MultiPWM::~MultiPWM() {
PWMCluster::~PWMCluster() {
dma_channel_unclaim(data_dma_channel);
dma_channel_unclaim(ctrl_dma_channel);
pio_sm_set_enabled(pio, sm, false);
#ifdef DEBUG_MULTI_PWM
pio_remove_program(pio, &debug_multi_pwm_program, pio_program_offset);
pio_remove_program(pio, &debug_pwm_cluster_program, pio_program_offset);
#else
pio_remove_program(pio, &multi_pwm_program, pio_program_offset);
pio_remove_program(pio, &pwm_cluster_program, pio_program_offset);
#endif
#ifndef MICROPY_BUILD_TYPE
// pio_sm_unclaim seems to hardfault in MicroPython
@ -210,17 +211,17 @@ MultiPWM::~MultiPWM() {
}
}
uint MultiPWM::get_chan_mask() const {
uint PWMCluster::get_chan_mask() const {
return channel_mask;
}
void MultiPWM::set_wrap(uint32_t wrap, bool load) {
void PWMCluster::set_wrap(uint32_t wrap, bool load) {
wrap_level = MAX(wrap, 1); // Cannot have a wrap of zero!
if(load)
load_pwm();
}
void MultiPWM::set_chan_level(uint8_t channel, uint32_t level, bool load) {
void PWMCluster::set_chan_level(uint8_t channel, uint32_t level, bool load) {
if((channel < NUM_BANK0_GPIOS) && bit_in_mask(channel, channel_mask)) {
channel_levels[channel] = level;
if(load)
@ -228,7 +229,7 @@ void MultiPWM::set_chan_level(uint8_t channel, uint32_t level, bool load) {
}
}
void MultiPWM::set_chan_offset(uint8_t channel, uint32_t offset, bool load) {
void PWMCluster::set_chan_offset(uint8_t channel, uint32_t offset, bool load) {
if((channel < NUM_BANK0_GPIOS) && bit_in_mask(channel, channel_mask)) {
channel_offsets[channel] = offset;
if(load)
@ -236,7 +237,7 @@ void MultiPWM::set_chan_offset(uint8_t channel, uint32_t offset, bool load) {
}
}
void MultiPWM::set_chan_polarity(uint8_t channel, bool polarity, bool load) {
void PWMCluster::set_chan_polarity(uint8_t channel, bool polarity, bool load) {
if((channel < NUM_BANK0_GPIOS) && bit_in_mask(channel, channel_mask)) {
if(polarity)
channel_polarities |= (1u << channel);
@ -248,21 +249,21 @@ void MultiPWM::set_chan_polarity(uint8_t channel, bool polarity, bool load) {
}
// These apply immediately, so do not obey the PWM update trigger
void MultiPWM::set_clkdiv(float divider) {
void PWMCluster::set_clkdiv(float divider) {
pio_sm_set_clkdiv(pio, sm, divider);
}
// These apply immediately, so do not obey the PWM update trigger
void MultiPWM::set_clkdiv_int_frac(uint16_t integer, uint8_t fract) {
void PWMCluster::set_clkdiv_int_frac(uint16_t integer, uint8_t fract) {
pio_sm_set_clkdiv_int_frac(pio, sm, integer, fract);
}
/*
void MultiPWM::set_phase_correct(bool phase_correct);
void PWMCluster::set_phase_correct(bool phase_correct);
void MultiPWM::set_enabled(bool enabled);*/
void PWMCluster::set_enabled(bool enabled);*/
void MultiPWM::load_pwm() {
void PWMCluster::load_pwm() {
gpio_put(write_gpio, 1);
TransitionData transitions[64];
@ -276,14 +277,14 @@ void MultiPWM::load_pwm() {
// If the level is greater than zero, add a transition to high
if(channel_levels[channel] > 0) {
MultiPWM::sorted_insert(transitions, data_size, TransitionData(channel, channel_offsets[channel], !polarity));
PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, channel_offsets[channel], !polarity));
//if((channel_offsets[channel] < wrap_level) && (channel_offsets[channel] + channel_levels[channel] >= wrap_level)) {
// MultiPWM::sorted_insert(transitions, data_size, TransitionData(channel, 0, !polarity)) // Adds an initial state for PWMs that have their end offset beyond the transition line
// PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, 0, !polarity)) // Adds an initial state for PWMs that have their end offset beyond the transition line
//}
}
// If the level is less than the wrap, add a transition to low
if(channel_levels[channel] < wrap_level) {
MultiPWM::sorted_insert(transitions, data_size, TransitionData(channel, channel_offsets[channel] + channel_levels[channel], polarity));
PWMCluster::sorted_insert(transitions, data_size, TransitionData(channel, channel_offsets[channel] + channel_levels[channel], polarity));
}
}
}
@ -322,9 +323,9 @@ void MultiPWM::load_pwm() {
if(transitions[data_index].level <= current_level) {
// Yes, so add the transition state to the pin states mask
if(transitions[data_index].state)
pin_states |= (1u << transitions[data_index].servo);
pin_states |= (1u << transitions[data_index].channel);
else
pin_states &= ~(1u << transitions[data_index].servo);
pin_states &= ~(1u << transitions[data_index].channel);
data_index++; // Move on to the next transition
}
@ -368,17 +369,17 @@ void MultiPWM::load_pwm() {
gpio_put(write_gpio, 0); //TOREMOVE
}
bool MultiPWM::bit_in_mask(uint bit, uint mask) {
bool PWMCluster::bit_in_mask(uint bit, uint mask) {
return ((1u << bit) & mask) != 0;
}
void MultiPWM::sorted_insert(TransitionData array[], uint &size, const TransitionData &data) {
void PWMCluster::sorted_insert(TransitionData array[], uint &size, const TransitionData &data) {
uint i;
for(i = size; (i > 0 && !array[i - 1].compare(data)); i--) {
array[i] = array[i - 1];
}
array[i] = data;
//printf("Added %d, %ld, %d\n", data.servo, data.level, data.state);
//printf("Added %d, %ld, %d\n", data.channel, data.level, data.state);
size++;
}
}

Wyświetl plik

@ -4,27 +4,26 @@
#include "hardware/pio.h"
#include "hardware/dma.h"
#include "hardware/irq.h"
#include "multi_pwm.pio.h"
namespace servo {
namespace pimoroni {
struct TransitionData {
uint8_t servo;
uint8_t channel;
uint32_t level;
bool state;
TransitionData() : servo(0), level(0), state(false) {};
TransitionData(uint8_t servo, uint32_t level, bool new_state) : servo(servo), level(level), state(new_state) {};
TransitionData() : channel(0), level(0), state(false) {};
TransitionData(uint8_t channel, uint32_t level, bool new_state) : channel(channel), level(level), state(new_state) {};
bool compare(const TransitionData& other) const {
return level <= other.level;
}
};
class MultiPWM {
class PWMCluster {
public:
MultiPWM(PIO pio, uint sm, uint channel_mask);
~MultiPWM();
PWMCluster(PIO pio, uint sm, uint channel_mask);
~PWMCluster();
uint get_chan_mask() const;
void set_wrap(uint32_t wrap, bool load = true);
void set_chan_level(uint8_t channel, uint32_t level, bool load = true);

Wyświetl plik

@ -37,7 +37,7 @@
; PWM Program
; --------------------------------------------------
.program multi_pwm
.program pwm_cluster
.wrap_target
pull ; Pull in the new pin states
@ -55,7 +55,7 @@ delay:
; Debug PWM Program
; --------------------------------------------------
.program debug_multi_pwm
.program debug_pwm_cluster
.side_set 1
.wrap_target

Wyświetl plik

@ -1,2 +1,2 @@
include(servo.cmake)
include(multi_pwm.cmake)
include(servo_cluster.cmake)

Wyświetl plik

@ -3,7 +3,6 @@ add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/servo.cpp
${CMAKE_CURRENT_LIST_DIR}/servo_cluster.cpp
${CMAKE_CURRENT_LIST_DIR}/calibration.cpp
${CMAKE_CURRENT_LIST_DIR}/servo_state.cpp
)
@ -13,5 +12,5 @@ target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(${DRIVER_NAME} INTERFACE
pico_stdlib
hardware_pwm
multi_pwm
pwm
)

Wyświetl plik

@ -1,5 +1,6 @@
#include "servo.hpp"
#include "hardware/clocks.h"
#include "pwm.hpp"
namespace servo {
Servo::Servo(uint pin, CalibrationType type)
@ -14,7 +15,7 @@ namespace servo {
bool success = false;
uint16_t period; uint16_t div16;
if(Servo::calculate_pwm_factors(pwm_frequency, period, div16)) {
if(pimoroni::calculate_pwm_factors(pwm_frequency, period, div16)) {
pwm_period = period;
pwm_cfg = pwm_get_default_config();
@ -81,7 +82,7 @@ namespace servo {
if((freq >= MIN_FREQUENCY) && (freq <= MAX_FREQUENCY)) {
// Calculate a suitable pwm wrap period for this frequency
uint16_t period; uint16_t div16;
if(Servo::calculate_pwm_factors(freq, period, div16)) {
if(pimoroni::calculate_pwm_factors(freq, period, div16)) {
// Record if the new period will be larger or smaller.
// This is used to apply new pwm values either before or after the wrap is applied,
@ -164,42 +165,4 @@ namespace servo {
return state.calibration();
}
// Derived from the rp2 Micropython implementation: https://github.com/micropython/micropython/blob/master/ports/rp2/machine_pwm.c
bool Servo::calculate_pwm_factors(float freq, uint16_t& top_out, uint16_t& div16_out) {
bool success = false;
uint32_t source_hz = clock_get_hz(clk_sys);
// Check the provided frequency is valid
if((freq >= 1.0f) && (freq <= (float)(source_hz >> 1))) {
uint32_t div16_top = (uint32_t)((float)(source_hz << 4) / freq);
uint32_t top = 1;
while(true) {
// Try a few small prime factors to get close to the desired frequency.
if((div16_top >= (5 << 4)) && (div16_top % 5 == 0) && (top * 5 <= MAX_PWM_WRAP)) {
div16_top /= 5;
top *= 5;
}
else if((div16_top >= (3 << 4)) && (div16_top % 3 == 0) && (top * 3 <= MAX_PWM_WRAP)) {
div16_top /= 3;
top *= 3;
}
else if((div16_top >= (2 << 4)) && (top * 2 <= MAX_PWM_WRAP)) {
div16_top /= 2;
top *= 2;
}
else {
break;
}
}
if(div16_top >= 16 && div16_top <= (UINT8_MAX << 4)) {
top_out = top;
div16_out = div16_top;
success = true;
}
}
return success;
}
};

Wyświetl plik

@ -17,7 +17,6 @@ namespace servo {
static constexpr float MIN_FREQUENCY = 10.0f; // Lowest achievable with hardware PWM with good resolution
static constexpr float MAX_FREQUENCY = 350.0f; // Highest nice value that still allows the full uS pulse range
// Some servos are rated for 333Hz for instance
static const uint32_t MAX_PWM_WRAP = UINT16_MAX;
//--------------------------------------------------
@ -72,8 +71,6 @@ namespace servo {
Calibration& calibration();
const Calibration& calibration() const;
private:
static bool calculate_pwm_factors(float freq, uint16_t& top_out, uint16_t& div16_out);
};
}

Wyświetl plik

@ -0,0 +1,15 @@
set(DRIVER_NAME servo_cluster)
add_library(${DRIVER_NAME} INTERFACE)
target_sources(${DRIVER_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/servo_cluster.cpp
${CMAKE_CURRENT_LIST_DIR}/calibration.cpp
${CMAKE_CURRENT_LIST_DIR}/servo_state.cpp
)
target_include_directories(${DRIVER_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR})
target_link_libraries(${DRIVER_NAME} INTERFACE
pico_stdlib
pwm_cluster
)

Wyświetl plik

@ -2,8 +2,8 @@
namespace servo {
ServoCluster::ServoCluster(PIO pio, uint sm, uint channel_mask)
: multi_pwm(pio, sm, channel_mask) {
multi_pwm.set_wrap(20000);
: pwms(pio, sm, channel_mask) {
pwms.set_wrap(20000);
}
ServoCluster::~ServoCluster() {
@ -25,20 +25,20 @@ namespace servo {
}
uint ServoCluster::get_pin_mask() const {
return multi_pwm.get_chan_mask();
return pwms.get_chan_mask();
}
void ServoCluster::enable(uint servo, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].enable();
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
void ServoCluster::disable(uint servo, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].disable();
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
@ -59,7 +59,7 @@ namespace servo {
void ServoCluster::set_value(uint servo, float value, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].set_value(value);
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
@ -73,7 +73,7 @@ namespace servo {
void ServoCluster::set_pulse(uint servo, float pulse, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].set_pulse(pulse);
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
@ -101,35 +101,35 @@ namespace servo {
void ServoCluster::to_min(uint servo, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].to_min();
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
void ServoCluster::to_mid(uint servo, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].to_mid();
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
void ServoCluster::to_max(uint servo, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].to_max();
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
void ServoCluster::to_percent(uint servo, float in, float in_min, float in_max, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].to_percent(in, in_min, in_max);
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}
void ServoCluster::to_percent(uint servo, float in, float in_min, float in_max, float value_min, float value_max, bool load) {
if(servo < NUM_BANK0_GPIOS) {
float new_pulse = servos[servo].to_percent(in, in_min, in_max, value_min, value_max);
multi_pwm.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
pwms.set_chan_level(servo, ServoState::pulse_to_level(new_pulse, 20000, 50), load);
}
}

Wyświetl plik

@ -1,7 +1,7 @@
#pragma once
#include "pico/stdlib.h"
#include "multi_pwm.hpp"
#include "pwm_cluster.hpp"
#include "servo_state.hpp"
namespace servo {
@ -22,7 +22,7 @@ namespace servo {
// Variables
//--------------------------------------------------
private:
MultiPWM multi_pwm;
pimoroni::PWMCluster pwms;
ServoState servos[NUM_BANK0_GPIOS]; // TODO change this to array of pointers
// so that only the servos actually assigned
// to this cluster have states

Wyświetl plik

@ -56,7 +56,7 @@ int main() {
simple_servo.init();
//MultiPWM pwms(pio1, 0, 0b111111111111111);
//PWMCluster pwms(pio1, 0, 0b111111111111111);
//pwms.set_wrap(20000);
ServoCluster cluster(pio1, 0, 0b111100);

Wyświetl plik

@ -3,4 +3,4 @@ add_library(servo2040 INTERFACE)
target_include_directories(servo2040 INTERFACE ${CMAKE_CURRENT_LIST_DIR})
# Pull in pico libraries that we need
target_link_libraries(servo2040 INTERFACE pico_stdlib plasma multi_pwm servo)
target_link_libraries(servo2040 INTERFACE pico_stdlib plasma servo servo_cluster)

Wyświetl plik

@ -2,7 +2,6 @@
#include "pico/stdlib.h"
#include "ws2812.hpp"
#include "multi_pwm.hpp"
#include "servo.hpp"
#include "servo_cluster.hpp"

Wyświetl plik

@ -5,16 +5,18 @@ add_library(usermod_${MOD_NAME} INTERFACE)
target_sources(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.c
${CMAKE_CURRENT_LIST_DIR}/${MOD_NAME}.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/pwm/pwm.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/pwm/pwm_cluster.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/servo.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/servo_cluster.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/servo_state.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/calibration.cpp
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/multi_pwm.cpp
)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/multi_pwm.pio)
pico_generate_pio_header(usermod_${MOD_NAME} ${CMAKE_CURRENT_LIST_DIR}/../../../drivers/pwm/pwm_cluster.pio)
target_include_directories(usermod_${MOD_NAME} INTERFACE
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/pwm/
${CMAKE_CURRENT_LIST_DIR}/../../../drivers/servo/
)