Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Положение и скважность

Чтобы управлять сервоприводом с Raspberry Pi Pico, нужно задать частоту ШИМ 50 Гц. Насколько мне известно, ни в embassy, ни в rp-hal нет простого способа задать частоту напрямую.

В embassy-rp ШИМ настраивается через структуру Config с несколькими полями. Для нашей задачи нас в основном интересуют значения top и divider. То же относится и к rp-hal, где можно задавать top, div_int и div_frac отдельно.

Подходящее значение TOP можно найти ручным методом или с помощью формы, которая автоматически вычисляет и TOP, и делитель для целевой частоты 50 Гц.

Ручным методом я получил значение TOP 46 874 при делителе 64. С помощью формы я получил делитель 45,8125 и значение TOP 65 483. Можно использовать любую из этих конфигураций.

Note

В rp-hal целую и дробную части делителя нужно задавать отдельно. Так, делитель 64 становится div_int = 64 и div_frac = 0. Делитель 45,8125 становится div_int = 45 и div_frac = 13.

Расчёт положения на основе TOP

Когда известно значение TOP для ШИМ-сигнала 50 Гц, можно рассчитать значения скважности, необходимые для установки положения сервопривода.

Сервопривод определяет своё положение, измеряя ширину импульса, то есть время, в течение которого сигнал остаётся в высоком состоянии в каждом 20-миллисекундном цикле ШИМ. Точные ширины импульсов не одинаковы для всех сервоприводов и могут немного отличаться в зависимости от конкретной модели.

В моём случае значения были следующими:

  • 0° при примерно 0,5 мс, что соответствует скважности 2,5 %, поскольку 0,5 мс составляет 2,5 % периода 20 мс.

  • 90° при примерно 1,5 мс, что соответствует скважности 7,5 %, поскольку 1,5 мс составляет 7,5 % периода 20 мс.

  • 180° при примерно 2,4 мс, что соответствует скважности 12 %, поскольку 2,4 мс составляет 12 % периода 20 мс.

В главе о плавном изменении яркости светодиода изменять скважность было просто. Нас интересовала только яркость, а не частота, поэтому было достаточно использовать set_duty_cycle_percent. Эта функция принимает значение u8 от 0 до 100, что хорошо подходит для целых процентов.

Для управления сервоприводом такой подход не подходит, потому что нужные скважности включают дробные значения, например 2,5 %, 7,5 % и 12 %.

Поэтому у нас есть две альтернативы. Первый вариант — вычислить значение скважности напрямую из TOP и использовать set_duty_cycle, который принимает u16. Второй вариант — использовать set_duty_cycle_fraction, который позволяет задать скважность в виде числителя и знаменателя.

Вариант 1: ручной расчёт с set_duty_cycle

Сначала переводим ширину импульса в процент от периода. Затем этот процент умножаем на TOP + 1, чтобы получить значение скважности, которое настраивает выход ШИМ.

#![allow(unused)]
fn main() {
const PWM_DIV_INT: u8 = 64;
const PWM_TOP: u16 = 46_874;

const TOP: u16 = PWM_TOP + 1;
// 0,5 мс — это 2,5 % от 20 мс; 0 градусов для сервопривода
const MIN_DUTY: u16 = (TOP as f64 * (2.5 / 100.)) as u16;
// 1,5 мс — это 7,5 % от 20 мс; 90 градусов для сервопривода
const HALF_DUTY: u16 = (TOP as f64 * (7.5 / 100.)) as u16;
// 2,4 мс — это 12 % от 20 мс; 180 градусов для сервопривода
const MAX_DUTY: u16 = (TOP as f64 * (12. / 100.)) as u16;
}

Когда значение скважности вычислено, его можно применить так:

#![allow(unused)]
fn main() {
servo.set_duty_cycle(MIN_DUTY)
            .expect("invalid min duty cycle");
}

Вариант 2: использование set_duty_cycle_fraction

Другой вариант — использовать set_duty_cycle_fraction. Он поможет задавать проценты в виде дроби.

На самом деле set_duty_cycle_percent — это удобный метод из embedded-hal, который внутри вызывает set_duty_cycle_fraction. Он просто делит процент на 100 и передаёт результат как дробь.

Из embedded-hal:

#![allow(unused)]
fn main() {
/// Установить скважность в `percent / 100`
///
/// Вызывающий код отвечает за то, что `percent` не превышает 100.
#[inline]
fn set_duty_cycle_percent(&mut self, percent: u8) -> Result<(), Self::Error> {
    self.set_duty_cycle_fraction(u16::from(percent), 100)
}

/// Установить скважность в `num / denom`.
///
/// Вызывающий код отвечает за то, что `num` не превышает `denom`,
/// и что `denom` не равен нулю.
fn set_duty_cycle_fraction(&mut self, num: u16, denom: u16) -> Result<(), Self::Error> {
    debug_assert!(denom != 0);
    debug_assert!(num <= denom);
    let duty = u32::from(num) * u32::from(self.max_duty_cycle()) / u32::from(denom);

    // Это безопасно, потому что мы знаем, что `num <= denom`, поэтому `duty <= self.max_duty_cycle()` (u16)
    #[allow(clippy::cast_possible_truncation)]
    {
        self.set_duty_cycle(duty as u16)
    }
}
}

Эта функция не принимает значения с плавающей запятой. Вместо этого она принимает числитель и знаменатель, оба типа u16. Чтобы представить дробные проценты, мы просто масштабируем их до целых чисел.

Помните, что 2,5 % можно записать как дробь 2,5/100. Поскольку в числителе нельзя использовать десятичные дроби, умножим и числитель, и знаменатель на 10, чтобы получить эквивалентную дробь из целых чисел:

#![allow(unused)]
fn main() {
2.5/100 = (2.5 × 10)/(100 × 10) = 25/1000
}

Теперь у нас есть эквивалентная дробь, использующая только целые числа. Ту же операцию можно применить и к остальным процентам:

Например:

  • 2,5 % можно записать как 25 / 1000 (то есть 25 — это 2,5 % от 1000)
  • 7,5 % можно записать как 75 / 1000 (то есть 75 — это 7,5 % от 1000)
  • 12 % можно записать как 120 / 1000 (то есть 120 — это 12 % от 1000)

Поэтому в нашем коде можно применить это так:

#![allow(unused)]
fn main() {
// Переместить сервопривод в положение 0° (скважность 2,5 % = 25/1000)
servo.set_duty_cycle_fraction(25, 1000)
    .expect("invalid duty cycle");

// Положение 90° (скважность 7,5 %)
servo.set_duty_cycle_fraction(75, 1000)
    .expect("invalid duty cycle");

// Положение 180° (скважность 12 %)
servo.set_duty_cycle_fraction(120, 1000)
    .expect("invalid duty cycle");
}