mirror of
https://bitbucket.org/librepilot/librepilot.git
synced 2024-11-29 07:24:13 +01:00
587 lines
17 KiB
C
587 lines
17 KiB
C
/**
|
|
******************************************************************************
|
|
* @addtogroup PIOS PIOS Core hardware abstraction layer
|
|
* @{
|
|
* @addtogroup PIOS_SERVO RC Servo Functions
|
|
* @brief Code to do set RC servo output
|
|
* @{
|
|
*
|
|
* @file pios_servo.c
|
|
* @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
|
|
* The OpenPilot Team, http://www.openpilot.org Copyright (C) 2012.
|
|
* @brief RC Servo routines (STM32 dependent)
|
|
* @see The GNU Public License (GPL) Version 3
|
|
*
|
|
*****************************************************************************/
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful, but
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
* for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
*/
|
|
/*
|
|
* DShot: Tribute belongs to dRonin, http://dRonin.org/ for sparking the idea of
|
|
* using gpio bitbang as more general solution over using timer dma.
|
|
*/
|
|
|
|
#include "pios.h"
|
|
|
|
#ifdef PIOS_INCLUDE_SERVO
|
|
|
|
#include "pios_servo_priv.h"
|
|
#include "pios_tim_priv.h"
|
|
|
|
/* Private Function Prototypes */
|
|
|
|
#define PIOS_SERVO_GPIO_BANKS 3
|
|
|
|
static const struct pios_servo_cfg *servo_cfg;
|
|
|
|
static volatile uint32_t *pios_servo_bsrr[PIOS_SERVO_GPIO_BANKS]; // GPIO banks
|
|
|
|
struct pios_servo_bank {
|
|
enum pios_servo_bank_mode mode;
|
|
uint16_t next_update;
|
|
uint16_t max_pulse;
|
|
TIM_TypeDef *timer;
|
|
};
|
|
|
|
struct pios_servo_pin {
|
|
struct pios_servo_bank *bank;
|
|
uint8_t bank_nr;
|
|
uint8_t gpio_bank;
|
|
uint16_t value;
|
|
};
|
|
|
|
|
|
static struct pios_servo_bank pios_servo_banks[PIOS_SERVO_BANKS];
|
|
static struct pios_servo_pin *pios_servo_pins;
|
|
|
|
|
|
// Dshot timing
|
|
static uint32_t pios_dshot_t0h_raw;
|
|
static uint32_t pios_dshot_t1h_raw;
|
|
static uint32_t pios_dshot_t_raw;
|
|
|
|
static bool pios_servo_enabled = true;
|
|
|
|
#define PIOS_SERVO_TIMER_CLOCK 1000000
|
|
#define PIOS_SERVO_SAFE_MARGIN 50
|
|
|
|
|
|
#define DSHOT_TIMING_ADJUST 8
|
|
#define DSHOT_T0H_DIV 2666
|
|
#define DSHOT_T1H_DIV 1333
|
|
#define DSHOT_NUM_BITS 16
|
|
|
|
|
|
extern void PIOS_Servo_Disable()
|
|
{
|
|
if (!servo_cfg) {
|
|
return;
|
|
}
|
|
pios_servo_enabled = false;
|
|
|
|
/* NOTE: Following will stop pulses and force low level on output pins.
|
|
* this is ok with ESC and servos, but brushed motors could be in trouble
|
|
* if using inverted setup */
|
|
|
|
for (uint8_t i = 0; (i < servo_cfg->num_channels); i++) {
|
|
const struct pios_tim_channel *chan = &servo_cfg->channels[i];
|
|
|
|
GPIO_InitTypeDef init = chan->pin.init;
|
|
|
|
#if defined(STM32F40_41xxx) || defined(STM32F446xx) || defined(STM32F411xE) || defined(STM32F3)
|
|
init.GPIO_Mode = GPIO_Mode_OUT;
|
|
#elif defined(STM32F10X_MD)
|
|
init.GPIO_Mode = GPIO_Mode_Out_PP;
|
|
#else
|
|
#error Unsupported MCU
|
|
#endif
|
|
GPIO_ResetBits(chan->pin.gpio, chan->pin.init.GPIO_Pin);
|
|
|
|
GPIO_Init(chan->pin.gpio, &init);
|
|
}
|
|
}
|
|
|
|
static void PIOS_Servo_SetupBank(uint8_t bank_nr)
|
|
{
|
|
struct pios_servo_bank *bank = &pios_servo_banks[bank_nr];
|
|
|
|
if (!bank->timer) {
|
|
return;
|
|
}
|
|
|
|
// Setup the timer accordingly
|
|
switch (bank->mode) {
|
|
case PIOS_SERVO_BANK_MODE_PWM:
|
|
case PIOS_SERVO_BANK_MODE_SINGLE_PULSE:
|
|
TIM_ARRPreloadConfig(bank->timer, ENABLE);
|
|
TIM_CtrlPWMOutputs(bank->timer, ENABLE);
|
|
TIM_SelectOnePulseMode(bank->timer, TIM_OPMode_Repetitive);
|
|
TIM_Cmd(bank->timer, ENABLE);
|
|
break;
|
|
default:;
|
|
// do not manage timers otherwise
|
|
}
|
|
|
|
// Setup GPIO/AF
|
|
for (uint8_t i = 0; (i < servo_cfg->num_channels); i++) {
|
|
const struct pios_tim_channel *chan = &servo_cfg->channels[i];
|
|
|
|
if (chan->timer != bank->timer) { // Not interested in this bank
|
|
continue;
|
|
}
|
|
|
|
GPIO_InitTypeDef init = chan->pin.init;
|
|
|
|
switch (bank->mode) {
|
|
case PIOS_SERVO_BANK_MODE_PWM:
|
|
case PIOS_SERVO_BANK_MODE_SINGLE_PULSE:
|
|
#if defined(STM32F40_41xxx) || defined(STM32F446xx) || defined(STM32F411xE) || defined(STM32F3)
|
|
GPIO_PinAFConfig(chan->pin.gpio, chan->pin.pin_source, chan->remap);
|
|
#elif defined(STM32F10X_MD)
|
|
init.GPIO_Mode = GPIO_Mode_AF_PP;
|
|
if (chan->remap) {
|
|
GPIO_PinRemapConfig(chan->remap, ENABLE);
|
|
}
|
|
#else
|
|
#error Unsupported MCU
|
|
#endif
|
|
GPIO_Init(chan->pin.gpio, &init);
|
|
|
|
/* Set up for output compare function */
|
|
switch (chan->timer_chan) {
|
|
case TIM_Channel_1:
|
|
TIM_OC1Init(chan->timer, (TIM_OCInitTypeDef *)&servo_cfg->tim_oc_init);
|
|
TIM_OC1PreloadConfig(chan->timer, TIM_OCPreload_Enable);
|
|
break;
|
|
case TIM_Channel_2:
|
|
TIM_OC2Init(chan->timer, (TIM_OCInitTypeDef *)&servo_cfg->tim_oc_init);
|
|
TIM_OC2PreloadConfig(chan->timer, TIM_OCPreload_Enable);
|
|
break;
|
|
case TIM_Channel_3:
|
|
TIM_OC3Init(chan->timer, (TIM_OCInitTypeDef *)&servo_cfg->tim_oc_init);
|
|
TIM_OC3PreloadConfig(chan->timer, TIM_OCPreload_Enable);
|
|
break;
|
|
case TIM_Channel_4:
|
|
TIM_OC4Init(chan->timer, (TIM_OCInitTypeDef *)&servo_cfg->tim_oc_init);
|
|
TIM_OC4PreloadConfig(chan->timer, TIM_OCPreload_Enable);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case PIOS_SERVO_BANK_MODE_DSHOT:
|
|
{
|
|
#if defined(STM32F40_41xxx) || defined(STM32F446xx) || defined(STM32F411xE) || defined(STM32F3)
|
|
init.GPIO_Mode = GPIO_Mode_OUT;
|
|
#elif defined(STM32F10X_MD)
|
|
init.GPIO_Mode = GPIO_Mode_Out_PP;
|
|
#else
|
|
#error Unsupported MCU
|
|
#endif
|
|
GPIO_ResetBits(chan->pin.gpio, chan->pin.init.GPIO_Pin);
|
|
|
|
GPIO_Init(chan->pin.gpio, &init);
|
|
}
|
|
break;
|
|
|
|
default:;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void PIOS_Servo_Enable()
|
|
{
|
|
if (!servo_cfg) {
|
|
return;
|
|
}
|
|
|
|
for (uint8_t i = 0; (i < PIOS_SERVO_BANKS); i++) {
|
|
PIOS_Servo_SetupBank(i);
|
|
}
|
|
|
|
pios_servo_enabled = true;
|
|
}
|
|
|
|
void PIOS_Servo_DSHot_Rate(uint32_t rate_in_khz)
|
|
{
|
|
if (rate_in_khz < 150) {
|
|
rate_in_khz = 150;
|
|
}
|
|
|
|
uint32_t raw_hz = PIOS_DELAY_GetRawHz();
|
|
|
|
uint32_t tmp = raw_hz / rate_in_khz;
|
|
|
|
pios_dshot_t0h_raw = (tmp / DSHOT_T0H_DIV) - DSHOT_TIMING_ADJUST;
|
|
pios_dshot_t1h_raw = (tmp / DSHOT_T1H_DIV) - DSHOT_TIMING_ADJUST;
|
|
pios_dshot_t_raw = (tmp / 1000) - DSHOT_TIMING_ADJUST;
|
|
}
|
|
|
|
/**
|
|
* Initialise Servos
|
|
*/
|
|
int32_t PIOS_Servo_Init(const struct pios_servo_cfg *cfg)
|
|
{
|
|
/* Store away the requested configuration */
|
|
servo_cfg = cfg;
|
|
|
|
pios_servo_pins = pios_malloc(sizeof(*pios_servo_pins) * cfg->num_channels);
|
|
PIOS_Assert(pios_servo_pins);
|
|
|
|
memset(pios_servo_pins, 0, sizeof(*pios_servo_pins) * cfg->num_channels);
|
|
|
|
/* set default dshot timing */
|
|
PIOS_Servo_DSHot_Rate(300);
|
|
|
|
|
|
uint8_t timer_bank = 0;
|
|
uint8_t gpio_bank = 0;
|
|
|
|
for (uint8_t i = 0; (i < servo_cfg->num_channels); i++) {
|
|
const struct pios_tim_channel *chan = &servo_cfg->channels[i];
|
|
bool new = true;
|
|
/* See if any previous channels use that same timer */
|
|
for (uint8_t j = 0; (j < i) && new; j++) {
|
|
new &= chan->timer != servo_cfg->channels[j].timer;
|
|
}
|
|
|
|
if (new) {
|
|
PIOS_Assert(timer_bank < PIOS_SERVO_BANKS);
|
|
struct pios_servo_bank *bank = &pios_servo_banks[timer_bank];
|
|
|
|
|
|
for (uint8_t j = i; j < servo_cfg->num_channels; j++) {
|
|
if (servo_cfg->channels[j].timer == chan->timer) {
|
|
pios_servo_pins[j].bank = bank;
|
|
pios_servo_pins[j].bank_nr = timer_bank;
|
|
}
|
|
}
|
|
|
|
bank->timer = chan->timer;
|
|
bank->mode = PIOS_SERVO_BANK_MODE_NONE;
|
|
|
|
TIM_Cmd(chan->timer, DISABLE);
|
|
|
|
timer_bank++;
|
|
}
|
|
|
|
// now map gpio banks
|
|
new = true;
|
|
for (uint8_t j = 0; (j < i) && new; j++) {
|
|
new &= chan->pin.gpio != servo_cfg->channels[j].pin.gpio;
|
|
}
|
|
|
|
if (new) {
|
|
PIOS_Assert(gpio_bank < PIOS_SERVO_GPIO_BANKS);
|
|
|
|
for (uint8_t j = i; j < servo_cfg->num_channels; j++) {
|
|
if (servo_cfg->channels[j].pin.gpio == chan->pin.gpio) {
|
|
pios_servo_pins[j].gpio_bank = gpio_bank;
|
|
}
|
|
}
|
|
#if defined(STM32F40_41xxx) || defined(STM32F446xx) || defined(STM32F411xE)
|
|
pios_servo_bsrr[gpio_bank] = (uint32_t *)&chan->pin.gpio->BSRRL;
|
|
#else
|
|
pios_servo_bsrr[gpio_bank] = &chan->pin.gpio->BSRR;
|
|
#endif
|
|
|
|
++gpio_bank;
|
|
}
|
|
}
|
|
|
|
static uint32_t dummy_bsrr;
|
|
|
|
for (int i = gpio_bank; i < PIOS_SERVO_GPIO_BANKS; ++i) {
|
|
pios_servo_bsrr[gpio_bank] = &dummy_bsrr;
|
|
}
|
|
|
|
PIOS_Servo_Enable();
|
|
|
|
return 0;
|
|
}
|
|
|
|
void PIOS_Servo_SetBankMode(uint8_t bank, uint8_t mode)
|
|
{
|
|
PIOS_Assert(bank < PIOS_SERVO_BANKS);
|
|
pios_servo_banks[bank].mode = mode;
|
|
|
|
if (!pios_servo_enabled) {
|
|
return;
|
|
}
|
|
|
|
PIOS_Servo_SetupBank(bank);
|
|
}
|
|
|
|
static void PIOS_Servo_DShot_Update()
|
|
{
|
|
uint32_t next;
|
|
uint32_t data[PIOS_SERVO_GPIO_BANKS];
|
|
uint16_t pins[PIOS_SERVO_GPIO_BANKS];
|
|
uint16_t buffer[DSHOT_NUM_BITS][PIOS_SERVO_GPIO_BANKS];
|
|
|
|
for (uint8_t i = 0; i < PIOS_SERVO_GPIO_BANKS; ++i) {
|
|
pins[i] = 0;
|
|
}
|
|
|
|
bool has_dshot = false;
|
|
|
|
memset(buffer, 0, sizeof(buffer));
|
|
|
|
for (uint8_t i = 0; (i < servo_cfg->num_channels); i++) {
|
|
struct pios_servo_pin *pin = &pios_servo_pins[i];
|
|
|
|
if (pin->bank->mode != PIOS_SERVO_BANK_MODE_DSHOT) {
|
|
continue;
|
|
}
|
|
|
|
has_dshot = true;
|
|
|
|
uint16_t payload = pin->value;
|
|
if (payload > 2047) {
|
|
payload = 2047;
|
|
}
|
|
|
|
payload <<= 5;
|
|
|
|
payload |= ((payload >> 4) & 0xf) ^
|
|
((payload >> 8) & 0xf) ^
|
|
((payload >> 12) & 0xf);
|
|
|
|
uint16_t gpio_pin = servo_cfg->channels[i].pin.init.GPIO_Pin;
|
|
|
|
for (int j = 0; j < DSHOT_NUM_BITS; ++j) {
|
|
if (!(payload & 0x8000)) {
|
|
buffer[j][pin->gpio_bank] |= gpio_pin;
|
|
}
|
|
payload <<= 1;
|
|
}
|
|
|
|
pins[pin->gpio_bank] |= gpio_pin;
|
|
|
|
pin->value = 0;
|
|
}
|
|
|
|
if (!has_dshot) {
|
|
return;
|
|
}
|
|
|
|
PIOS_IRQ_Disable();
|
|
|
|
uint32_t start = PIOS_DELAY_GetRaw();
|
|
|
|
for (int i = 0; i < DSHOT_NUM_BITS; ++i) {
|
|
// single bit:
|
|
|
|
COMPILER_BARRIER();
|
|
// 1. write 3x BSRR to set gpio high
|
|
for (int j = 0; j < PIOS_SERVO_GPIO_BANKS; ++j) {
|
|
*(pios_servo_bsrr[j]) = (uint32_t)pins[j];
|
|
}
|
|
|
|
// Prep data
|
|
for (int j = 0; j < PIOS_SERVO_GPIO_BANKS; ++j) {
|
|
data[j] = buffer[i][j] << 16;
|
|
}
|
|
|
|
// 2. wait until T0H, write 3x BSRR to clear whatever bits are set to 0
|
|
next = start + pios_dshot_t0h_raw;
|
|
while ((next - PIOS_DELAY_GetRaw()) < pios_dshot_t0h_raw) {
|
|
;
|
|
}
|
|
|
|
COMPILER_BARRIER();
|
|
for (int j = 0; j < PIOS_SERVO_GPIO_BANKS; ++j) {
|
|
*(pios_servo_bsrr[j]) = data[j];
|
|
}
|
|
|
|
// Prep data
|
|
for (int j = 0; j < PIOS_SERVO_GPIO_BANKS; ++j) {
|
|
data[j] = (uint32_t)pins[j] << 16;
|
|
}
|
|
|
|
// 3. wait until T1H, then write 3x BSRR to set all to low
|
|
next = start + pios_dshot_t1h_raw;
|
|
while ((next - PIOS_DELAY_GetRaw()) < pios_dshot_t1h_raw) {
|
|
;
|
|
}
|
|
|
|
COMPILER_BARRIER();
|
|
for (int j = 0; j < PIOS_SERVO_GPIO_BANKS; ++j) {
|
|
*(pios_servo_bsrr[j]) = data[j];
|
|
}
|
|
|
|
// 4. wait until Tend
|
|
start += pios_dshot_t_raw;
|
|
while ((start - PIOS_DELAY_GetRaw()) < pios_dshot_t_raw) {
|
|
;
|
|
}
|
|
}
|
|
|
|
PIOS_IRQ_Enable();
|
|
}
|
|
|
|
|
|
void PIOS_Servo_Update()
|
|
{
|
|
if (!pios_servo_enabled) {
|
|
return;
|
|
}
|
|
|
|
for (uint8_t i = 0; (i < PIOS_SERVO_BANKS); i++) {
|
|
struct pios_servo_bank *bank = &pios_servo_banks[i];
|
|
if (bank->timer && (bank->mode == PIOS_SERVO_BANK_MODE_SINGLE_PULSE)) {
|
|
// a pulse to be generated is longer than cycle period. skip this update.
|
|
if (TIM_GetCounter((TIM_TypeDef *)bank->timer) > (uint32_t)(bank->next_update + PIOS_SERVO_SAFE_MARGIN)) {
|
|
TIM_GenerateEvent((TIM_TypeDef *)bank->timer, TIM_EventSource_Update);
|
|
bank->next_update = bank->max_pulse;
|
|
}
|
|
}
|
|
bank->max_pulse = 0;
|
|
}
|
|
|
|
for (uint8_t i = 0; (i < servo_cfg->num_channels); i++) {
|
|
if (pios_servo_pins[i].bank->mode == PIOS_SERVO_BANK_MODE_SINGLE_PULSE) {
|
|
/* Update the position */
|
|
const struct pios_tim_channel *chan = &servo_cfg->channels[i];
|
|
|
|
switch (chan->timer_chan) {
|
|
case TIM_Channel_1:
|
|
TIM_SetCompare1(chan->timer, 0);
|
|
break;
|
|
case TIM_Channel_2:
|
|
TIM_SetCompare2(chan->timer, 0);
|
|
break;
|
|
case TIM_Channel_3:
|
|
TIM_SetCompare3(chan->timer, 0);
|
|
break;
|
|
case TIM_Channel_4:
|
|
TIM_SetCompare4(chan->timer, 0);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
PIOS_Servo_DShot_Update();
|
|
}
|
|
/**
|
|
* Set the servo update rate (Max 500Hz)
|
|
* \param[in] array of rates in Hz
|
|
* \param[in] array of timer clocks in Hz
|
|
* \param[in] maximum number of banks
|
|
*/
|
|
void PIOS_Servo_SetHz(const uint16_t *speeds, const uint32_t *clock, uint8_t banks)
|
|
{
|
|
PIOS_Assert(banks <= PIOS_SERVO_BANKS);
|
|
if (!servo_cfg) {
|
|
return;
|
|
}
|
|
|
|
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure = servo_cfg->tim_base_init;
|
|
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
|
|
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
|
|
|
|
for (uint8_t i = 0; i < banks && i < PIOS_SERVO_BANKS; i++) {
|
|
const TIM_TypeDef *timer = pios_servo_banks[i].timer;
|
|
if (timer) {
|
|
uint32_t new_clock = PIOS_SERVO_TIMER_CLOCK;
|
|
if (clock[i]) {
|
|
new_clock = clock[i];
|
|
}
|
|
|
|
uint32_t timer_clock;
|
|
|
|
// Choose the correct prescaler value for the APB the timer is attached
|
|
|
|
#if defined(STM32F10X_MD) || defined(STM32F3)
|
|
// F1 & F3 have both timer clock domains running at master clock speed
|
|
timer_clock = PIOS_MASTER_CLOCK;
|
|
#elif defined(STM32F40_41xxx) || defined(STM32F446xx) || defined(STM32F411xE)
|
|
if (timer == TIM1 || timer == TIM8 || timer == TIM9 || timer == TIM10 || timer == TIM11) {
|
|
timer_clock = PIOS_PERIPHERAL_APB2_CLOCK;
|
|
} else {
|
|
timer_clock = PIOS_PERIPHERAL_APB1_CLOCK;
|
|
}
|
|
#else
|
|
#error Unsupported MCU
|
|
#endif
|
|
TIM_TimeBaseStructure.TIM_Prescaler = (timer_clock / new_clock) - 1;
|
|
TIM_TimeBaseStructure.TIM_Period = ((new_clock / speeds[i]) - 1);
|
|
TIM_TimeBaseInit((TIM_TypeDef *)timer, &TIM_TimeBaseStructure);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set servo position
|
|
* \param[in] Servo Servo number (0-7)
|
|
* \param[in] Position Servo position in microseconds
|
|
*/
|
|
void PIOS_Servo_Set(uint8_t servo, uint16_t position)
|
|
{
|
|
/* Make sure servo exists */
|
|
if (!pios_servo_enabled || !servo_cfg || servo >= servo_cfg->num_channels) {
|
|
return;
|
|
}
|
|
|
|
|
|
/* Update the position */
|
|
|
|
pios_servo_pins[servo].value = position;
|
|
|
|
const struct pios_tim_channel *chan = &servo_cfg->channels[servo];
|
|
struct pios_servo_bank *bank = pios_servo_pins[servo].bank;
|
|
|
|
if ((bank->mode == PIOS_SERVO_BANK_MODE_SINGLE_PULSE) || (bank->mode == PIOS_SERVO_BANK_MODE_PWM)) {
|
|
uint16_t val = position;
|
|
uint16_t margin = chan->timer->ARR / 50; // Leave 2% of period as margin to prevent overlaps
|
|
if (val > (chan->timer->ARR - margin)) {
|
|
val = chan->timer->ARR - margin;
|
|
}
|
|
|
|
if (bank->max_pulse < val) {
|
|
bank->max_pulse = val;
|
|
}
|
|
switch (chan->timer_chan) {
|
|
case TIM_Channel_1:
|
|
TIM_SetCompare1(chan->timer, val);
|
|
break;
|
|
case TIM_Channel_2:
|
|
TIM_SetCompare2(chan->timer, val);
|
|
break;
|
|
case TIM_Channel_3:
|
|
TIM_SetCompare3(chan->timer, val);
|
|
break;
|
|
case TIM_Channel_4:
|
|
TIM_SetCompare4(chan->timer, val);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
uint8_t PIOS_Servo_GetPinBank(uint8_t pin)
|
|
{
|
|
if (pin < servo_cfg->num_channels) {
|
|
return pios_servo_pins[pin].bank_nr;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
const struct pios_servo_cfg *PIOS_Servo_GetConfig()
|
|
{
|
|
return servo_cfg;
|
|
}
|
|
|
|
#endif /* PIOS_INCLUDE_SERVO */
|