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

Периферия LEDC ESP32 для управления сервоприводом

ESP32 имеет периферию LEDC и Motor Control Pulse Width Modulator (MCPWM) для управления ШИМ. Сначала мы используем LEDC для генерации ШИМ, которую уже применяли в примерах со светодиодом, пищалкой и другими.

В предыдущих упражнениях мы использовали функцию set_duty, которая принимает процент коэффициента заполнения как u8. Но для сервопривода нужны дробные проценты (например, 2,5%, 7,5%, 12,5%), которые нельзя представить в виде u8.

Трейт SetDutyCycle

Что же делать? К счастью, каналы LEDC в esp-hal реализуют трейт SetDutyCycle из embedded-hal, который даёт больше контроля над ШИМ.

Мы будем использовать две функции трейта SetDutyCycle: max_duty_cycle и set_duty_cycle.

max_duty_cycle:

Эта функция возвращает максимальный коэффициент заполнения в зависимости от разрешения в битах, которое мы задаём для таймера. Например, если установить разрешение 8 бит, максимальный коэффициент заполнения будет 256 (то есть 28). Если установить 12 бит, максимум будет 4096 (то есть 212). Мы будем использовать 12-битное разрешение, поэтому максимальный коэффициент заполнения будет 4096.

#![allow(unused)]
fn main() {
// Приводим к u32 (из u16), потому что для последующего умножения нужен u32.
let max_duty_cycle = channel0.max_duty_cycle() as u32;
}

set_duty_cycle:

Эта функция задаёт коэффициент заполнения в диапазоне от 0 до 4096 при 12-битном разрешении.

#![allow(unused)]
fn main() {
let duty = 512;
channel0.set_duty_cycle(duty).unwrap();
}

Но как?

Эти функции принимают или возвращают значения u16. Но как использовать проценты, которые являются дробными? Вместо того чтобы использовать процент напрямую, мы вычисляем соответствующее значение. Например, 2,5% от 4096 примерно равны 102. Этого достаточно, чтобы повернуть сервопривод в нужное положение; в данном случае — на 0 градусов.

Для вычисления значения по проценту мы не будем использовать float. Вместо этого приведём максимальное значение коэффициента заполнения к u32.

Проценты вроде 2,5% нельзя напрямую представить в u32, поэтому умножаем процент на 10, чтобы он вписался. Например, 2,5 становится 25. Затем делим итоговое значение на 1000 (100 × 10), а не на 100.

\[ \text{min_duty} = \frac{Percent_{u32} \times \text{max_duty_cycle}}{1000} \]

Например:

#![allow(unused)]
fn main() {
// Минимальный коэффициент заполнения (2,5%) для положения сервопривода
// Для 12 бит -> 25 * 4096 /1000 => ~ 102
// то же самое, что 2.5 * 4096 / 100 => ~102
let min_duty = (25 * max_duty_cycle) / 1000;

// Максимальный коэффициент заполнения (12,5%) для положения сервопривода
// Для 12 бит -> 125 * 4096 /1000 => 512
let max_duty = (125 * max_duty_cycle) / 1000;

}

Вычисление коэффициента заполнения по углу

У нас есть простая вспомогательная функция, которая переводит угол в значение коэффициента заполнения. Нужно передать градусы, min_duty и разницу между min_duty и max_duty для диапазона положений сервопривода. Затем приводим результат к u16, потому что функция set_duty_cycle принимает только u16.

#![allow(unused)]
fn main() {
let duty_gap = max_duty - min_duty; // 512 - 102 => 410
fn duty_from_angle(deg: u32, min_duty: u32, duty_gap: u32) -> u16 {
    let duty = min_duty + ((deg * duty_gap) / 180);
    duty as u16
}
}

Например, возьмём угол 180 градусов. Мы уже вычислили диапазон min_duty и max_duty, то есть 102 и 512, а разница между ними 410. Подставим в формулу.

duty = 102 + ((180 * 410) / 180) = 512

Для угла 90 градусов:

duty = 102 + ((90 * 410) / 180) = 307

307 примерно равно 7,5% от 4096, что нам и нужно для положения 90 градусов.

Поворот

Будем поворачивать качалку сервопривода с 0 градусов на 180, а затем обратно на 0 в цикле. Сначала вычисляем коэффициент заполнения по углу и задаём его. Ждём 1500 миллисекунд, чтобы сервопривод успел дойти до положения. Попробуйте уменьшить задержку до 50 мс, и вы заметите, что сервопривод начнёт двигаться рывками и вообще не дойдёт до нужного положения.

#![allow(unused)]
fn main() {
loop{
    let duty = duty_from_angle(0, min_duty, duty_gap);
    channel0.set_duty_cycle(duty).unwrap();

    delay.delay_millis(1500); // даём время дойти до положения

    let duty = duty_from_angle(180, min_duty, duty_gap);
    channel0.set_duty_cycle(duty).unwrap();

    delay.delay_millis(1500); // даём время дойти до положения
}
}

Не переживайте, как запустить этот код. В следующей главе мы рассмотрим код, который плавно перемещает сервопривод от 0 до 180 градусов и обратно в цикле.