Arduino: расширитель портов ШИМ PCA9685
Мы уже говорили о том, как решить проблему нехватки цифровых портов ввода-вывода. В этой статье вы узнаете, как увеличить количество портов с аппаратной поддержкой ШИМ.
Широтно-импульсная модуляция (ШИМ, PWM) – это управление соотношением длительности высокого и низкого уровня импульсного сигнала. Чаще всего такой сигнал используют для управления углом поворота сервомашинок и яркостью свечения ламп или светодиодов.
В микроконтроллере Atmega328 доступно шесть универсальных портов с функцией ШИМ. Не так уж мало, но эти порты могут быть нужны для других целей. Представьте, что вам надо собрать робота-паука:
В этой конструкции задействовано 18 сервомашинок. Выводов Arduino явно не хватает.
Проблема нехватки портов ШИМ легко решается при помощи модулей на основе микросхемы PCA9685:
Эту микросхему разработали для управления яркостью светодиодов, но она идеально подходит для управления сервомашинками и сервоприводами.
Основные характеристики PCA9685
- Количество каналов: 16
- Разрешение канала: 12-bit, 4096 шагов (в Arduino 8-bit, 256 шагов)
- Частота ШИМ: от 24 Гц до 1526 Гц (в Arduino по умолчанию 488 Гц)
- Напряжение питания: от 2.3 до 5.5 вольт
- Частота шины I2C: до 1000 кГц
Библиотеки Arduino PCA9685
Наиболее популярны библиотеки от Adafruit и NachtRaveVL. Вторая библиотека пригодится для продвинутых пользователей, а для простых проектов и примеров достаточно библиотеки Adafruit PCA9685 PWM Library. Для установки библиотеки запустите Arduino IDE, перейдите в меню Скетч → подключить библиотеку → Управлять библиотеками → Все. Введите в строку поиска “adafruit pwm”, щелкните на строке с названием найденной библиотеки и нажмите кнопку “Установить”.
Подключение модуля PCA9685 к Arduino
Соберите простую схему, в которой светодиод подключен к порту P0, а сервомашинка подключена к порту P3:
На плате модуля уже установлены последовательные гасящие резисторы, поэтому светодиод можно подключить непосредственно к выводу платы:
Микросхема оснащена инверсным входом управления EN (enable). Если на вывод EN подан высокий уровень, все выходы принудительно переключаются в низкий уровень. В схеме модуля есть резистор, который подтягивает EN в низкий уровень, но для надежной работы следует соединить его с общим проводом. Этот вывод при необходимости можно соединить со свободным портом платы Arduino и программно отключать порты микросхемы PCA9685 подачей высокого уровня.
Выходные каскады PCA9685 питаются от независимого источника по линии V+, которая также выведена на отдельный зажимной разъем. Если вы используете модуль для управления несколькими сервомашинками или светодиодами, то его можно питать от платы Arduino и не подключать линию V+ или соединить ее с линией питания +5V платы Arduino. Но если вы используете мощные сервомашинки или сервоприводы, они могут создать импульсные помехи по питанию, вплоть до зависания или перезагрузки платы Arduino. В таком случае для питания управляемых устройств надо подключить к линии V+ отдельный источник питания.
Пример управления светодиодом и сервомашинкой
Скопируйте и загрузите в плату Arduino скетч примера:
#include <Wire.h> #include <Adafruit_PWMServoDriver.h> // По умолчанию модуль доступен по адресу I2C 0x40 Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(); // При необходимости вы можете задать другой адрес, например 0x41 //Adafruit_PWMServoDriver pwm = Adafruit_PWMServoDriver(0x41); #define SERVOMIN 150 // минимальная длительность импульса для сервомашинки #define SERVOMAX 600 // максимальная длина импульса для сервомашинки #define SERVOPORT 3 // Номер порта для подключения сервомашинки #define LEDPORT 0 // Номер порта для подключения светодиода void setup() { pwm.begin(); pwm.setPWMFreq(60); // Частота следования импульсов 60 Гц delay(10); } void loop() { // Вращение сервомашинки из минимума в максимум for (uint16_t pulselen = SERVOMIN; pulselen < SERVOMAX; pulselen++) { pwm.setPWM(SERVOPORT, 0, pulselen); } delay(250); // Вращение сервомашинки из максимума в минимум for (uint16_t pulselen = SERVOMAX; pulselen > SERVOMIN; pulselen--) { pwm.setPWM(SERVOPORT, 0, pulselen); } delay(250); // Яркость светодиода из минимума в максимум for (uint16_t pulselen = 0; pulselen < 4096; pulselen++) { pwm.setPWM(LEDPORT, 0, pulselen); } delay(250); // Яркость светодиода из максимума в минимум for (uint16_t pulselen = 4096; pulselen > 0; pulselen--) { pwm.setPWM(LEDPORT, 0, pulselen); } delay(250); }
Сначала вал сервомашинки вращается от минимального положения до максимального и обратно. Затем яркость светодиода плавно нарастает от нуля до максимума и вновь снижается до нуля. Частота повторения импульсов ШИМ установлена в 60 Гц, потому что это значение подходит для обычной сервомашинки и действует на все выводы. При управлении только светодиодами частоту желательно увеличить минимум до 100 Гц чтобы избежать неприятное мерцание.
Настройка значений SERVOMAX и SERVOMIN
Сервомашинка работает только в пределах своего механического диапазона вращения, поэтому мы вынуждены ограничить диапазон изменения длительности импульса с двух сторон. При попытке выйти за пределы диапазона могут сломаться зубцы пластиковых шестеренок или ограничители вращения сервомашинки. Точные значения пределов зависят от марки машинки. Желательно начинать отладку с узкого диапазона значений SERVOMIN – SERVOMAX и постепенно расширить его, но не доводя вращение вала машинки до ограничителей.
Перекодировка угла поворота в длительность импульса
Управлять сервомашинкой удобнее, если указывать в скетче физический угол поворота вала. У стандартной машинки угол поворота от минимума до максимума обычно составляет 180 градусов. Используя стандартную функцию Arduino map() вы можете конвертировать угол поворота в значение длительности импульса:
pulselength = map(degrees, 0, 180, SERVOMIN, SERVOMAX);
Статические уровни порта – эмуляция цифровых выходов GPIO
Порты микросхемы PCA9685 можно использовать для эмуляции обычных цифровых выходов.
Команда pwm.setPWM(pin, 4096, 0); устанавливает на выводе pin логическую единицу.
Команда pwm.setPWM(pin, 0, 4096); устанавливает на выводе pin логический ноль.
Здравствуйте.
Есть задача управлять светодиодами (15) с компьютера через ардуино + pca9685 изменяя их яркость. Каждый светодиод управляется обособленно, можно управлять сразу всеми. Задача передачи значений по порту решена. Они парсятся из строки и записываются в переменные(r1, g1, b1, r2, g2, b2 …). Но нет понимания, как должен выглядеть код для работы с pca9685. В задаче нет таймаута когда светодиод загорается из одного значения в другое. Нужно в любой момент времени иметь возможность изменить его яркость фейдером подключенного к компьютеру миди-контроллера.
Разобрался
pwm.setPWM(0, 0, r1 * 16);
pwm.setPWM(1, 0, g1 * 16);
pwm.setPWM(2, 0, b1 * 16);
Совершенно верно. Кстати, для сокращения кода программы можно парсить входную строку в массив leds[ ], а потом обновлять значения яркости светодиодов в цикле:
for (int i=0; i<=15; i++) { pwm.setPWM(i, 0, leds[i]*16); }
Подскажите, как добавить в программу управление кнопками, на каждую серву по 2 кнопки (вперёд, назад)? Серва должна крутиться, пока кнопка нажата (до крайнего значения)
Как вариант:
1. Объявляете переменные длительности импульса по числу сервомашинок. 2. В главном цикле loop() делаете проверку состояния кнопок. Если кнопка нажата, то сперва проверяете, не достигнуто ли уже крайнее значение SERVOMAX (или SERVOMIN). Если да, то ничего не делаете.
3. Если крайнее значение не достигнуто, то увеличиваете или уменьшаете длительность импульса в зависимости от назначения кнопки.
4. Записываете новое значение длительности в порт ШИМ.
5. Надо опытным путем подобрать задержку в цикле loop(), чтобы обработка нажатия не проскакивала слишком быстро весь диапазон значений.
Если еще актуально, подскажите как переписать скетч на угол поворота
Везде пишут pulselength = map(degrees, 0, 180, SERVOMIN, SERVOMAX);
А на самом деле все друг у друга копируют одну строчку бездумно.
На самом деле эта строчка взята из gist.github.com, нагло выдрана из скетча.
Вот ссылка https://gist.github.com/Injac/ddf7459038bd2e4a6bd5
//Полностью она выглядит так:
for(uint8_t degrees = 1; degrees <=180;degrees++)
{
pulsedegree = map(degrees, 0, 180, SERVOMIN, SERVOMAX);
pwm.setPWM(0, 0, pulsedegree);
delay(1);
Serial.println(degrees);
}
боже, спасибо вам