Что такое Сервопривод?
Сервопривод — это тип привода в робототехнике, механике и элементах автоматизации, предназначенный для точного управления положением поворота какой-либо детали. Он состоит из мотора, системы редукторов и управляющей электроники с потенциометром для обратной связи о положении.
Как работает сервопривод
Сервоприводы работают, преобразуя электрическую энергию, под управлением ШИМ-сигнала в механическое движение. Серво содержит небольшой электродвигатель, который через систему редукторов соединен с выходным валом. Внутри сервопривода находится потенциометр, который связан с выходным валом. Он измеряет угол поворота вала и отправляет эту информацию обратно на управляющую электронику, обеспечивая точное позиционирование.
Управление сервоприводом происходит через ШИМ (широтно-импульсную модуляцию). ШИМ-сигнал определяет угол поворота вала сервопривода.
Что такое ШИМ?
ШИМ (широтно-импульсная модуляция) — это метод управления мощностью, подаваемой на электронное устройство, путем изменения длительности импульсов электрического сигнала.
Как работает ШИМ
- Импульсы постоянной частоты: В ШИМ используются импульсы постоянной частоты, но изменяется их длительность (ширина). Частота ШИМ-сигнала обычно измеряется в герцах (Гц).
- Скважность: Основным параметром ШИМ является скважность, которая определяется как отношение времени включения сигнала к общему периоду цикла. Скважность может варьироваться от 0% (сигнал всегда выключен) до 100% (сигнал всегда включен).
Использование ШИМ для управления сервоприводом
При управлении сервоприводами ШИМ-сигнал используется для задания угла поворота вала.
- Стандартные ШИМ-параметры для сервоприводов: Для большинства моделей сервоприводов стандартный ШИМ-сигнал имеет частоту 50 Гц (период около 20 мс). Изменение ширины импульса в этом сигнале приводит к изменению угла поворота сервопривода.
- Длительность импульса: для управления сервоприводом обычно находится в диапазоне от 1 мс до 2 мс. Импульс длительностью 1 мс обычно заставляет сервопривод повернуться на 0°, 1.5 мс соответствует примерно 90°, а 2 мс — 180°. Эти значения могут варьироваться в зависимости от конкретной модели сервопривода.
- Точность управления: зависит от стабильности и точности ШИМ-сигнала. Микроконтроллеры, такие как Arduino, могут генерировать достаточно точные ШИМ-сигналы для большинства приложений.
Для начала, ознакомимся со схемой, которая генерирует ШИМ сигнал без использования микроконтроллера.
https://www.tinkercad.com/things/4wUYzuSpZ9T-ne555-servotester2
Arduino и библиотека Servo.h
В Arduino для работы с сервоприводами используется библиотека Servo.h. Она позволяет легко управлять сервоприводами, отправляя на них ШИМ-сигналы.
Простой подход
Пример простого кода управления сервоприводом в Arduino:
#include <Servo.h>
Servo motor; // создаем объект серво
int button1 = 3; // кнопка 1 подключена к пину 3
int button2 = 4; // кнопка 2 подключена к пину 4
int servoPin = 2; // серво подключено к пину 2
void setup() {
motor.attach(servoPin); // подключаем серво к соответствующему пину
pinMode(button1, INPUT_PULLUP); // настраиваем пин кнопки 1 как вход
pinMode(button2, INPUT_PULLUP); // настраиваем пин кнопки 2 как вход
motor.write(0); // начальное положение серво - 0 градусов
}
void loop() {
if (digitalRead(button1) == LOW) { // если нажата кнопка 1
motor.write(180); // поворачиваем серво на 180 градусов
delay(1000); // задержка для устойчивости
}
if (digitalRead(button2) == LOW) { // если нажата кнопка 2
motor.write(0); // поворачиваем серво в исходное положение 0 градусов
delay(1000); // задержка для устойчивости
}
}
Этот код представляет собой простой пример управления сервоприводом с помощью Arduino и двух кнопок. Он идеально подходит для новичков, которые только начинают изучать робототехнику и программирование на Arduino. Давайте разберем каждую часть этого кода пошагово.
Подключение библиотеки и создание сбъекта
#include <Servo.h>
Servo motor;
- #include <Servo.h>: Эта строка подключает библиотеку Servo.h, которая необходима для управления сервоприводами с помощью Arduino.
- Servo motor;: Здесь мы создаем объект motor из класса Servo. Этот объект будет использоваться для управления сервоприводом.
Настройка портов (пинов)
int button1 = 3; // кнопка 1 подключена к пину 3
int button2 = 4; // кнопка 2 подключена к пину 4
int servoPin = 2; // серво подключено к пину 2
- В этой части кода мы определяем пины, к которым подключены кнопки и сервопривод. button1 и button2 это пины для кнопок, а servoPin это пин для сервопривода.
Начальная настройка
void setup() {
motor.attach(servoPin); // подключаем серво к соответствующему пину
pinMode(button1, INPUT_PULLUP); // настраиваем пин кнопки 1 как вход
pinMode(button2, INPUT_PULLUP); // настраиваем пин кнопки 2 как вход
motor.write(0); // начальное положение серво - 0 градусов
}
- Функция setup() вызывается один раз при запуске или перезагрузке Arduino. Здесь мы настраиваем пины и сервопривод.
- motor.attach(servoPin);: Присоединяем сервопривод к указанному пину.
- pinMode(button1, INPUT_PULLUP); и pinMode(button2, INPUT_PULLUP);: Настраиваем пины кнопок как входные, используя внутреннюю подтяжку к питанию.
- motor.write(0);: Устанавливаем начальное положение сервопривода на 0 градусов.
Почему используется подтяжка к питанию INPUT_PULLUP?
Использование внутренней подтяжки к питанию упрощает подключение и программирование, так как:
- Избавляет от необходимости внешних резисторов: Без подтяжки к питанию, вам бы потребовалось добавить внешний резистор к каждой кнопке для обеспечения стабильного состояния входа, когда кнопка не нажата.
- Предотвращает "плавающие" значения: Без подтяжки, входной пин может случайно переключаться между HIGH и LOW из-за электрических помех, что приводит к непредсказуемому поведению.
Основной цикл
void loop() {
if (digitalRead(button1) == LOW) { // если нажата кнопка 1
motor.write(180); // поворачиваем серво на 180 градусов
delay(1000); // задержка для устойчивости
}
if (digitalRead(button2) == LOW) { // если нажата кнопка 2
motor.write(0); // поворачиваем серво в исходное положение 0 градусов
delay(1000); // задержка для устойчивости
}
}
- void loop() {}: Эта функция выполняется постоянно после setup().
- if (digitalRead(button1) == LOW) {}: Проверяем, нажата ли кнопка 1. Если да, то выполняется код внутри фигурных скобок.
- motor.write(180);: Поворачиваем сервопривод на 180 градусов.
- delay(1000);: Делаем задержку в 1 секунду, чтобы сервопривод успел повернуться и стабилизироваться
Сложности «Простого подхода»
Этот подход прост, но имеет недостатки, такие как блокировка основного цикла loop во время задержки delay, что делает невозможным одновременное управление несколькими сервоприводами и приводит к резким движениям.
Выход с использованием millis()
Для улучшения работы кода можно использовать функцию millis(), которая позволяет выполнять управление сервоприводом без блокировки loop.
Для начала пример всего кода, а дальше разберем его детально:
#include <Servo.h>
Servo motor; // Объект сервопривода
int button1 = 3; // Пин подключения кнопки 1
int button2 = 4; // Пин подключения кнопки 2
int servoPin = 2; // Пин подключения сервопривода
int servoCurrentPos = 0; // Текущая позиция сервопривода
int servoTargetPos = 0; // Целевая позиция сервопривода
uint32_t servoTimer = 0; // Таймер для контроля скорости перемещения сервопривода
int servoDelay = 5; // Задержка между шагами перемещения сервопривода
// Функция для плавного управления положением сервопривода
void servoPosControl() {
// Проверяем, прошло ли достаточно времени с последнего изменения положения
if (millis() - servoTimer > servoDelay) {
int delta = 0; // Разница между текущим и целевым положением
// Определяем направление движения сервопривода
if (servoCurrentPos > servoTargetPos){
delta = -1;
} else if (servoCurrentPos < servoTargetPos){
delta = 1;
}
// Обновляем текущую позицию и время
servoCurrentPos += delta;
servoTimer = millis();
// Устанавливаем новую позицию сервопривода
motor.write(servoCurrentPos);
}
}
void setup() {
motor.attach(servoPin); // Подключаем сервопривод
pinMode(button1, INPUT_PULLUP); // Настраиваем пин кнопки 1 как вход
pinMode(button2, INPUT_PULLUP); // Настраиваем пин кнопки 2 как вход
motor.write(0); // Устанавливаем начальное положение сервопривода
}
void loop() {
servoPosControl(); // Вызываем функцию управления положением сервопривода
// Проверяем состояние кнопок и обновляем целевую позицию сервопривода
if (digitalRead(button1) == LOW) {
servoTargetPos = 180;
}
if (digitalRead(button2) == LOW) {
servoTargetPos = 0;
}
}
Обзор функции servoPosControl()
Функция servoPosControl() в коде Arduino представляет собой эффективный способ управления сервоприводом для достижения плавного движения. Эта функция использует неблокирующий подход с таймерами, что позволяет Arduino выполнять другие задачи во время управления сервоприводом. Давайте подробно разберем, как работает эта функция.
Работа функции
// Функция для плавного управления положением сервопривода
void servoPosControl() {
// Проверяем, прошло ли достаточно времени с последнего изменения положения
if (millis() - servoTimer > servoDelay) {
int delta = 0; // Разница между текущим и целевым положением
// Определяем направление движения сервопривода
if (servoCurrentPos > servoTargetPos){
delta = -1;
} else if (servoCurrentPos < servoTargetPos){
delta = 1;
}
// Обновляем текущую позицию и время
servoCurrentPos += delta;
servoTimer = millis();
// Устанавливаем новую позицию сервопривода
motor.write(servoCurrentPos);
}
}
- Проверка времени: Если разница между текущим временем и servoTimer больше servoDelay, значит пришло время обновить положение сервопривода.millis() возвращает количество миллисекунд, прошедших с момента запуска программы.
servoTimer это переменная, которая хранит время последнего изменения положения сервопривода.
servoDelay это задержка (в миллисекундах) между последовательными изменениями положения сервопривода. - Определение направления движения:servoCurrentPos это текущее положение сервопривода.
servoTargetPos это целевое положение сервопривода.
delta определяет направление движения: уменьшение (-1) или увеличение (+1) текущего положения. - Обновление положения сервопривода:servoCurrentPos += delta;: Обновляем текущее положение сервопривода, прибавляя или вычитая delta.
servoTimer = millis();: Обновляем время последнего изменения положения.
motor.write(servoCurrentPos);: Передаем новое положение в сервопривод.
Преимущества использования servoPosControl()
- Плавность движения: Постепенное изменение положения сервопривода делает его движение более плавным.
- Неблокирующий код: Использование millis() вместо delay() позволяет избежать блокировки выполнения других задач во время управления сервоприводом.
- Гибкость: Функция позволяет легко настраивать скорость движения сервопривода, изменяя servoDelay.
Теперь разберем оставшуюся часть кода:
#include <Servo.h>
Servo motor; // Объект сервопривода
int button1 = 3; // Пин подключения кнопки 1
int button2 = 4; // Пин подключения кнопки 2
int servoPin = 2; // Пин подключения сервопривода
int servoCurrentPos = 0; // Текущая позиция сервопривода
int servoTargetPos = 0; // Целевая позиция сервопривода
uint32_t servoTimer = 0; // Таймер для контроля скорости перемещения сервопривода
int servoDelay = 5; // Задержка между шагами перемещения сервопривода
Объявление Переменных:
- #include <Servo.h>;: Подключаем библиотеку.
- Servo motor;: Объект класса Servo для управления сервоприводом.
- int button1 = 3; и int button2 = 4;: Пины, к которым подключены кнопки.
- int servoPin = 2;: Пин, к которому подключен сервопривод.
- int servoCurrentPos = 0;: Текущая позиция сервопривода.
- int servoTargetPos = 0;: Целевая позиция сервопривода.
- uint32_t servoTimer = 0;: Таймер для контроля времени между обновлениями положения сервопривода.
- int servoDelay = 5;: Задержка между шагами перемещения сервопривода в миллисекундах.
void setup() {
motor.attach(servoPin); // Подключаем сервопривод
pinMode(button1, INPUT_PULLUP); // Настраиваем пин кнопки 1 как вход
pinMode(button2, INPUT_PULLUP); // Настраиваем пин кнопки 2 как вход
motor.write(0); // Устанавливаем начальное положение сервопривода
}
Действия вsetup():
- Присоединяет сервопривод к пину и устанавливает пины кнопок как входные с включенной внутренней подтяжкой к питанию.
- Устанавливает начальное положение сервопривода.
void loop() {
servoPosControl(); // Вызываем функцию управления положением сервопривода
// Проверяем состояние кнопок и обновляем целевую позицию сервопривода
if (digitalRead(button1) == LOW) {
servoTargetPos = 180;
}
if (digitalRead(button2) == LOW) {
servoTargetPos = 0;
}
}
Основной Цикл loop():
- Непрерывно вызывает servoPosControl(), обеспечивая плавное изменение положения сервопривода.
- Проверяет состояние кнопок и обновляет servoTargetPos.
Важные замечания!
- С этим подходом использование delay() в loop() или других функциях может привести к нестабильной работе, так как оно блокирует выполнение программы, включая обновление положения сервопривода.
- Неблокирующий подход: Использование millis() позволяет избежать блокировки, что критически важно для многозадачных решений в роботах!
Посмотреть проект в TinkerCad и испытать его в деле можно по ссылке:
https://www.tinkercad.com/things/d0hzbpr34DJ-servo-2-metoda
Завершающая часть статьи: преимущества и использование кода для управления массивом сервоприводов
#include <Servo.h> // Подключаем библиотеку Servo для управления сервоприводами
Servo part0; // Создаем объекты сервоприводов
Servo part1;
Servo part2;
Servo servos[] = { part0, part1, part2}; // Массив объектов сервоприводов для удобства управления
int servosCurrentPos[] = {90, 90, 90}; // Массив текущих позиций сервоприводов
int servosTargetPos[] = {90, 90, 90}; // Массив целевых позиций сервоприводов
uint32_t servosTimer[] = {0, 0, 0}; // Таймеры для контроля времени обновления положения каждого сервопривода
int sDelay = 30; // Задержка между обновлениями положения
uint32_t servosDelay[] = {sDelay, sDelay, sDelay}; // Массив задержек для каждого сервопривода
void parseSerialInput() {
// Функция обработки данных, получаемых через Serial Monitor
String inputString = ""; // Строка для хранения входных данных
int inputArray[3]; // Массив для хранения распарсенных значений углов
int inputIndex = 0; // Индекс для перебора элементов массива
// Читаем данные из Serial пока они доступны
while (Serial.available() > 0) {
delay(1); // Небольшая задержка для стабилизации данных
char inChar = Serial.read(); // Читаем символ из Serial
// Проверяем на символ конца строки
if (inChar == '|') {
// Печатаем полученную строку для отладки
Serial.print("Received String: ");
Serial.println(inputString);
// Преобразуем String в массив char для разбора
char tempStr[inputString.length() + 1];
inputString.toCharArray(tempStr, sizeof(tempStr));
// Разбираем строку на отдельные числа
char* ptr = strtok(tempStr, ";");
while (ptr != NULL && inputIndex < 3) {
inputArray[inputIndex] = atoi(ptr);
// Выводим целевое положение для каждого сервопривода
Serial.print("Servo ");
Serial.print(inputIndex);
Serial.print(" Target: ");
Serial.println(inputArray[inputIndex]);
inputIndex++;
ptr = strtok(NULL, ";");
}
// Обновляем целевые позиции сервоприводов
for (int i = 0; i <= 2; i++) {
servosTargetPos[i] = inputArray[i];
}
// Сброс переменных для следующего чтения
inputString = "";
inputIndex = 0;
} else if (isdigit(inChar) || inChar == ';') {
// Если символ является числом или разделителем, добавляем его к строке
inputString += inChar;
}
}
}
void servoPosControl() {
// Функция для плавного управления положением сервоприводов
for (int i = 0; i <= 2; i++) {
// Проверяем, прошло ли достаточно времени с последнего обновления положения
if (millis() - servosTimer[i] > servosDelay[i]) {
// Вычисляем разницу между текущим и целевым положением
int delta = servosCurrentPos[i] == servosTargetPos[i] ? 0 : (servosCurrentPos[i] < servosTargetPos[i] ? 1 : -1);
// Обновляем текущее положение
servosCurrentPos[i] += delta;
// Обновляем таймер
servosTimer[i] = millis();
// Устанавливаем новое положение сервопривода
servos[i].write(servosCurrentPos[i]);
}
}
}
void setup() {
Serial.begin(9600);
part0.attach(2);
part1.attach(3);
part2.attach(4);
}
void loop() {
parseSerialInput(); // Обрабатываем входные данные из Serial Monitor
servoPosControl(); // Управляем положением сервоприводов
}
//Откройте Serial Monitor
//Отправьте строку в формате "90;120;45;|" для установки углов поворота сервоприводов.
Этот код является отличным инструментом для управления несколькими сервоприводами в проектах на Arduino. Для его использования необходимо загрузить код на плату Arduino и подключить сервоприводы к соответствующим пинам. Управление положением сервоприводов осуществляется через Serial Monitor путем отправки команд в формате, например, "90;120;45;|".
Преимущества обработки массива сервоприводов
- Плавное управление: функция servoPosControl() обеспечивает плавное изменение положения каждого сервопривода, что предотвращает резкие движения и улучшает точность позиционирования.
- Неблокирующая работа: использование millis() вместо delay() позволяет избежать блокировки основного цикла loop, что важно для поддержания отзывчивости системы и возможности параллельного выполнения других задач.
- Гибкая настройка: код легко адаптируется под различное количество сервоприводов, достаточно изменить массивы и добавить необходимые объекты Servo.
- Удобство управления: функция parseSerialInput() позволяет легко управлять сервоприводами через Serial, что делает процесс интуитивно понятным и удобным для тестирования и прототипирования.
- Отладка и мониторинг: встроенные сообщения Serial.print() предоставляют важную информацию для отладки, позволяя пользователю отслеживать процесс обработки команд и движения сервоприводов.
Заключение
Применение данного кода открывает широкие возможности для разработчиков и энтузиастов Arduino в сферах робототехники, автоматизации и интерактивных проектов. Он обеспечивает точное управление несколькими сервоприводами одновременно, что делает его ценным инструментом для множества механизмов!
Конечно же, для вас подготовлен проект в TinkerCad, где можно все испытать!
https://www.tinkercad.com/things/aBrLwNGxIF1-servo-massiv
Удачи и успехов в ваших разработках!