Raspberry Pi - PWM і Сервопривод


10.10.2014

Raspberry Pi має декілька шляхів для реалізації PWM (Широтно-імпульсної модуляції). Ми розглянемо як реалізувати, PWM програмно, та задіємо для генерації PWM апаратні ресурси Raspberry Pi. Спочатку будемо змінювати яскравість світлодіода, а потім навчимося керувати сервоприводом.

Що таке PWM (ШІМ) ?

Широтно-імпульсна модуляція (ШІМ) - це імпульсний сигнал постійної частоти і змінної шпаруватості, тобто відношення тривалості імпульсу до періоду його проходження. За допомогою завдання шпаруватості (тривалості імпульсів) можна міняти середнє значення напруги на виході ШІМ.

PWM

Програмна реалізація PWM

Підключимо світлодіода до GPIO23 як вказано на схемі:

Raspberry Pi & LED

Напишемо скрипт pwm_soft.py:

nano ./pwm_soft.py

Текст скрипта:

import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setup(23, GPIO.OUT)
p = GPIO.PWM(23, 50)  # channel=23 frequency=50Hz
p.start(0)
try:
    while 1:
        for dc in range(0, 101, 5):
            p.ChangeDutyCycle(dc)
            time.sleep(0.1)
        for dc in range(100, -1, -5):
            p.ChangeDutyCycle(dc)
            time.sleep(0.1)
except KeyboardInterrupt:
    pass
p.stop()
GPIO.cleanup()

Запустимо його:

python ./pwm_soft.py

Світлодіод буде плавно загоратися і плавно гаснути.

Програмна реалізація PWM дозволяє сформувати PWM сигнал на будь-кому виводі. У цьому прикладі ми використовуємо RPi.GPIO для програмної генерації PWM сигналу. А це означає, що витрачаються обчислювальні ресурси мікрокомп’ютера. Якщо мікрокомп’ютер буде відволікатися на інші задачі, PWM сигнал буде викривлятися і не буде стабільним. Це не принципово, якщо PWM застосовується для керування яскравістю світлодіода. Але може стати неприйнятним, коли PWM застосовується для формування керуючого сигналу. Наприклад, при керуванні сервоприводами програмна реалізація PWM не може стабільно утримувати сервоприводи у заданому положенні.

Raspberry Pi має технічну можливість використовувати апаратний ресурс для генерації PWM.

Генерація PWM сигналу завдяки апаратним ресурсам Raspberry Pi

Проект WiringPi - це бібліотека, яка містить утиліти для простого доступу до GPIO. Вона дозволяє налаштувати апаратні модулі для спеціальних виходів PWM. Встановлюємо wiringPi:

sudo apt-get install git-core
git clone git://git.drogon.net/wiringPi
cd wiringPi
./build
cd ..

Підключимо світлодіод до GPIO18 як вказано на схемі:

Raspberry Pi & PWM

Перший вихід PWM заведений на GPIO18, інші канали PWM задіяні на аудіо-виході. Виконаємо наступні команди для формування на GPIO18 PWM сигналу. Налаштовуємо перший канал PWM (GPIO18):

gpio mode 1 pwm

Задаємо шпаруватість від 0 до 1024:

gpio pwm 1 500

Світлодіод має світитися напівсили. Проекспериментуйте з PWM. Спробуйте задати наступні значення:

gpio pwm 1 10
gpio pwm 1 1023

Відключаємо PWM:

gpio unexport 1

або

gpio unexportall

Генерація апаратного PWM сигналу на Python

Щоб використовувати PWM у Python треба встановили WiringPi-Python:

sudo apt-get install python-dev python-setuptools
git clone https://github.com/WiringPi/WiringPi-Python
cd WiringPi-Python
git submodule update --init
python setup.py install
cd ..

Створимо срипт pwm.py:

nano pwm.py

Текст скрипта:

import time
import wiringpi
# GPIO pin 12 = BCM pin 18 = wiringpi pin 1
led_pin  = 1
wiringpi.wiringPiSetup()
wiringpi.pinMode(led_pin, 2)
wiringpi.pwmWrite(led_pin, 0)
def led(led_value):
    wiringpi.pwmWrite(led_pin, led_value)
led(0)
while 1:
        for dc in range(0, 1023, 5):
                led(dc)
                time.sleep(0.01)
        for dc in range(1024, 0, -5):
                led(dc)
                time.sleep(0.01)

Запустимо його:

python ./pwm.py

Світлодіод буде плавно загоратися і плавно гаснути. Апаратна реалізація PWM забезпечує більш стабільний результат. Нажаль апаратний вихід у Raspberry Pi тільки один. Але існує ще пара методів генерування PWM. Через DMA, та використання зовнішнього PWM контролера. Ці методи розглянемо нижче для керування сервоприводами, оскільки на світлодіодах різниця не буде помітна.

Керування Сервоприводом

Про сервоприводи та характеристики сигналу управління я писав раніше у статті Управление сервоприводом (сервомашинкой) с помощью микроконтроллера ATMega.

Зазвичай сервоприводи використовують живлення 5В. Малопотужний сервопривід можна живити від Raspberry Pi. Але якщо привід споживає досить великий струм, або Вам потрібно підключити декілька сервомашинок, краще не навантажувати Raspberry Pi і використовувати окреме джерело живлення. Схема під’єднання сервопривода:

Raspberry Pi & Servo Textronik HXT900

Керування сервоприводом за допомогою програмно сформованого PWM

Спочатку спробуємо формувати PWM для керування сервоприводом програмно. Cтворимо скрипт servo.py:

nano servo.py

Текст скрипта:

import RPi.GPIO as GPIO
import time
GPIO.setmode(GPIO.BCM)
GPIO.setup(17,GPIO.OUT)
p=GPIO.PWM(17,50)
p.start(7.5)
try:
        while True:
                p.ChangeDutyCycle(7.5)
                print "Left"
                time.sleep(1)
                p.ChangeDutyCycle(12.5)
                print "Center"
                time.sleep(1)
                p.ChangeDutyCycle(2.5)
                print "Right"
                time.sleep(1)
except KeyboardInterrupt:
        p.stop()
        GPIO.cleanup()

Запустимо servo.py:

python ./servo.py

Я навмисно вставив функції print у код. Наявність цих функцій виявляє описану раніше проблему нестабільності програмно сформованого PWM. Сервомашинка не фіксується у заданому положенні і смикається. Якщо видалити інструкції print, проблема зменшується або взагалі зникає.

Керування сервоприводом за допомогою PWM, сформованого через DMA

Встановлюємо RPIO:

apt-get install python-setuptools
easy_install -U RPIO

Створимо срипт servo_dma.py:

nano ./servo_dma.py

Текст скрипта:

import time
from RPIO import PWM
servo = PWM.Servo()
# Set servo on GPIO17 to 900.s (0.9ms)
servo.set_servo(17, 900)
# Set servo on GPIO17 to 2000.s (2.0ms)
#servo.set_servo(17, 2000)
try:
        while True:
                servo.set_servo(17, 750)
                print "Left"
                time.sleep(1)
                servo.set_servo(17, 1500)
                print "Center"
                time.sleep(1)
                print "Right"
                servo.set_servo(17, 2500)
                time.sleep(1)
except KeyboardInterrupt:
        # Clear servo on GPIO17
        servo.stop_servo(17)

Запускаємо його:

python ./servo_dma.py

Тепер сервопривід працює стабільно. Тобто, для керування сервоприводами про програмний PWM бажано взагалі забути.

Подробиці використання RPIO читайте тут: http://pythonhosted.org/RPIO/pwm_py.html#examples

На цьому відео видно різницю між різними способами генерації PWM сигналу:

Servoblaster

Існує проект Servoblaster, який теж працює через DMA. За допомогою нього можна керувати до 8 сервоприводами. Servoblaster встановлюється як демон і дозволяє керувати сервоприводами через файли пристроїв. Тобто, керувати сервами можна через файлову систему. Це дозволяє керувати сервами використовуючи будь-яку мову програмування або з командного рядка і не потребує встановлення додаткових модулів таких як RPIO, котрий ми встановлювали раніше для Python.

Встановлюємо Servoblaster:

git clone http://github.com/richardghirst/PiBits.git
cd PiBits/ServoBlaster/user
make
sudo make install

Перевіряємо чи Servoblaster встановлено:

ls /dev | grep servoblaster

Маємо побачити:

servoblaster
servoblaster-cfg

Можна переглянути конфіг Servoblaster-а:

cat /dev/servoblaster-cfg

Виводи на які можна підключати сервоприводи наведені у наступній таблиці:

Servo number GPIO number Pin in P1 header
0 4 P1-7
1 17 P1-11
2 18 P1-12
3 21/27 * P1-13
4 22 P1-15
5 23 P1-16
6 24 P1-18
7 25 P1-22
*- RaspberryPi B і RaspberryPi B Revision 2 мають різницю у розпіновках. Детальніше тут: Raspberry Pi – що це таке?

Допустимі значення положення залежать від вашого сервоприводу. У більшості вони лежать у діапазоні від 80 до 249. Напишемо наступний срипт для керування сервою:

nano ./servo_blaster.py

Текст скрипта:

import time
# Servo Channel 1 => GPIO 17
servoChannel = 1
def setServo(servoChannel, position):
    servoStr ="%u=%u" % (servoChannel, position)
    with open("/dev/servoblaster", "wb") as f:
        f.write(servoStr)
if __name__ == `__main__`:
    val = 50
    direction = 1
    while True:
        #print val
        setServo(servoChannel, val)
        time.sleep(.01)
        if val == 249:
            direction -= 1
        elif val == 50:
            direction = 1
        val += direction

Запустимо його:

python ./servo_blaster.py

Є одна особливість у Servoblaster. Поки він запущений, він займає 8 вказаних в таблиці виходів і під інші цілі ви їх вже не зможете задіяти. Спробуйте запустити раніше написані скрипти:

python ./servo.py
python ./servo_dma.py

Сервомашинка не працює як слід.

Спробуємо зупинити демон Servoblaster і повторити спробу. Зупиняємо Servoblaster за допомогою команди:

sudo killall servod

Перевіряємо чи нема тепер servod у запущених процесах:

ps ax | grep servod

Повторюємо запуск скриптів:

python ./servo.py
python ./servo_dma.py

Все працює як слід. Запускаємо servod командою:

/usr/local/sbin/servod --idle-timeout=2000

Виникає питання: а що робити, якщо треба задіяти лише декілька каналів, а не всі 8? Servoblaster можна конфігурувати завдяки наступним опціям:

--pcm tells servod to use PCM rather than PWM hardware to implement delays --idle-timeout=Nms tells servod to stop sending servo pulses for a given output N milliseconds after the last update --cycle-time=Nus Control pulse cycle time in microseconds, default 20000us --step-size=Nus Pulse width increment step size in microseconds, default 10us --min={N|Nus|N%} specifies the minimum allowed pulse width, default 50 steps or 500us --max={N|Nus|N%} specifies the maximum allowed pulse width, default 250 steps or 2500us --invert Inverts outputs --dma-chan=N tells servod which dma channel to use, default 14 --p1pins=<list> tells servod which pins on the P1 header to use --p5pins=<list> tells servod which pins on the P5 header to use

Зараз нас цікавить опція --p1pins. Зупинимо Servoblaster:

sudo killall servod

Та запустимо з новими опціями:

/usr/local/sbin/servod --idle-timeout=2000 --p1pins=11

Тепер Servoblaster займатиме одну ногу P1-11 (GPIO17). Перевірити це можна переглянувши конфіг:

cat /dev/servoblaster-cfg

Якщо перезавантажити Raspbery Pi, Servoblaster знову буде працювати з початковими опціями. Для того, щоб при старті системи Servoblaster запускався з Вашими налаштуваннями, вкажіть їх у файлі /etc/init.d/servoblaster

Ви можете прочитати детальніше про Servoblaster тут: https://github.com/richardghirst/PiBits/tree/master/ServoBlaster

Деінсталяція Servoblaster:

sudo killall servod
cd PiBits/ServoBlaster/user
make uninstall

Adafruit 16-channel servo driver

Якщо Вам потрібно керувати надзвичайною кількістю сервоприводів, доречно використовувати багатоканальний зовнішній PWM контролер. Наприклад Adafruit 16-channel servo driver, або його аналоги. Ця плата підключається до Raspberry Pi по інтерфейсу I2C і може одночасно керувати 16-ма сервоприводами. Детально про цей пристрій: PWM контролер на базе микросхемы PCA9685

Успіхів!

Дивись також:

Raspberry Pi
Коментарі:
barteroff говорить:
13.10.2014 21:02
Було б непогано, якщо у статті було відео роботи.

andre говорить:
13.10.2014 21:11
Дякую за зауваження. Відео обов`язково додам.

andre говорить:
15.10.2014 10:03
Добавив відео у статтю.

barteroff говорить:
15.10.2014 17:37
Доречі, підкажіть, будь-ласка, чим обумовлено вибір мови Python для реалізації керування ШІМ?
Цікавлюсь, оскільки стою перед вибором на чому писати програми під CubieBoard (аналог Raspberry, але не таке велике комюніті).

andre говорить:
16.10.2014 07:46
Python дуже потужний і популярний. На ньому можна писати скрипти з графічним інтерфейсом. До тогож у Respbian він вже встановлений. Якщо не потрібна супершвидкість Python - саме те. Для іншого краще C. Доречі, треба буде добавити приклади на С.

Oleh говорить:
14.06.2023 18:15
Саме цікаве про pca9685 Ви і не розкрили

Додати коментар
Code
* - обов'язкові поля

Архіви