Периферия 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 градусов и обратно в цикле.