Рассмотрев работу с портами ввода-вывода GPIO в качестве обычных входов и выходов, нельзя не затронуть и вопрос использования Raspberry Pi для генерации ШИМ (PWM).
ШИМ-сигналы могут использоваться, к примеру, для управления серво-приводами или двигателями постоянного тока, либо для контроля яркости светодиодов. И это только несколько самых основных, сразу возникающих в памяти, применений. Таким образом, необходимость в генерации ШИМ возникает довольно часто и в самых разных проектах.
Итак, ШИМ-сигнал представляет из себя последовательность импульсов с постоянной частотой следования, но разной длительностью. Давайте рассмотрим наглядный пример:
Поскольку частота импульсов постоянна, то значит и период имеет фиксированную величину. А вот длительность импульса может меняться, собственно так и происходит модуляция. Рассмотрим два разных сигнала, использующихся для включения/отключения светодиодов:
Пусть светодиоды загораются при высоком уровне сигнала. Период в обоих случаях идентичен, а вот длительность импульсов второго сигнала больше в 2 раза. Соответственно, светодиод во втором случае будет гореть ярче, чем в первом.
Строго говоря, светодиод будет мигать, то есть часть периода гореть, а часть периода - нет. Но при высокой частоте следования импульсов глазу будут не заметны эти мигания, поэтому на практике получим 2 светодиода с разной яркостью. То есть, чем большую часть периода диод горит - тем ярче будет в результате светить.
ШИМ-сигналы характеризуют параметром, который называется - коэффициент заполнения (duty cycle). Он вычисляется по формуле:
D = \frac{\tau}{T}\medspace * \medspace 100\%
Здесь \tau - длительность импульса, а T - период сигнала.
Вернемся к рассмотренным выше сигналам - для первого случая длительность импульса составляет \frac{4}{10} периода сигнала, а значит D = 40\%. Аналогично, для второго случая D = 80\%.
На этом заканчиваем лирическое отступление на тему ШИМ и переходим к практической реализации на Raspberry Pi.
Вспоминаем таблицу распределения функций портов GPIO. Я сейчас пользуюсь Raspberry Pi 4, для любой другой модификации можно найти аналогичную информацию в документации. Итак, для генерации ШИМ могут использоваться следующие выводы:
Задействуем два порта в нашем примере - GPIO12 и GPIO13. При этом пусть коэффициент заполнения будет одинаковым, а частота второго сигнала (GPIO13) - в 2 раза больше.
Для работы с PWM на Python используем все тот же модуль RPi.GPIO, установить который в случае отсутствия можно командой:
sudo apt-get install python-rpi.gpio
Создаем файл pwm_test.py:
import RPi.GPIO as GPIO import time GPIO_PWM_0 = 12 GPIO_PWM_1 = 13 WORK_TIME = 10 DUTY_CYCLE = 50 FREQUENCY = 100 GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_PWM_0, GPIO.OUT) GPIO.setup(GPIO_PWM_1, GPIO.OUT) pwmOutput_0 = GPIO.PWM(GPIO_PWM_0, FREQUENCY) pwmOutput_1 = GPIO.PWM(GPIO_PWM_1, 2 * FREQUENCY) pwmOutput_0.start(DUTY_CYCLE) pwmOutput_1.start(DUTY_CYCLE) time.sleep(WORK_TIME) pwmOutput_0.stop() pwmOutput_1.stop() GPIO.cleanup()
Разбираем написанное. Первым делом подключаем модули и объявляем переменные:
import RPi.GPIO as GPIO import time GPIO_PWM_0 = 12 GPIO_PWM_1 = 13 WORK_TIME = 10 DUTY_CYCLE = 50 FREQUENCY = 100
- GPIO_PWM_0 и GPIO_PWM_1 - номера выводов GPIO, которые мы используем для генерации ШИМ
- WORK_TIME - время выполнения программы. В этом примере не будем анализировать действия пользователя для определения момента выхода из программы. Просто включаем ШИМ и по истечении времени WORK_TIME (10 секунд) заканчиваем работу.
- DUTY_CYCLE - коэффициент заполнения, одинаковый для обоих каналов и составляющий 50%.
- FREQUENCY - частота сигнала (100 Гц) первого канала. На втором установим в 2 раза больше, как и планировали.
Далее настроим режим нумерации портов, чтобы номера соответствовали названию сигнала (например, GPIO12, GPIO13), а не порядковому номеру на разъеме, а также настроим нужные нам порты:
GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_PWM_0, GPIO.OUT) GPIO.setup(GPIO_PWM_1, GPIO.OUT)
Создаем объекты pwmOutput_0 и pwmOutput_1 для работы с каналами PWM. В качестве аргументов указываем последовательно - номер порта GPIO и частоту следования импульсов:
pwmOutput_0 = GPIO.PWM(GPIO_PWM_0, FREQUENCY) pwmOutput_1 = GPIO.PWM(GPIO_PWM_1, 2 * FREQUENCY)
Запускаем генерацию функцией start() - аргументом является требуемая величина коэффициента заполнения (duty cycle):
pwmOutput_0.start(DUTY_CYCLE) pwmOutput_1.start(DUTY_CYCLE)
Ожидаем 10 секунд и останавливаем процесс:
time.sleep(WORK_TIME) pwmOutput_0.stop() pwmOutput_1.stop() GPIO.cleanup()
На этом базовый пример закончен, запускаем его на выполнение:
python pwm_test.py
В результате получаем сигналы ШИМ:
Коэффициент заполнения одинаковый (50% - длительность импульса равна половине периода), а частота во втором случае в 2 раза выше, собственно, как и должно быть 👍
Давайте реализуем еще один небольшой пример - будем менять коэффициент заполнения, а, соответственно, и длительность импульса в цикле программы. При этом период импульсов будет оставаться постоянным. Создаем файл pwm_test_dynamic.py и пишем код:
import RPi.GPIO as GPIO import time GPIO_PWM_0 = 12 FREQUENCY = 100 DELAY_TIME = 0.02 GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_PWM_0, GPIO.OUT) pwmOutput_0 = GPIO.PWM(GPIO_PWM_0, FREQUENCY) pwmOutput_0.start(0) try: while True: for dutyCycle in range(0, 101, 1): pwmOutput_0.ChangeDutyCycle(dutyCycle) sleep(DELAY_TIME) except KeyboardInterrupt: pwmOutput_0.stop() GPIO.cleanup() print('exiting')
Аналогичным образом настраиваем GPIO12 и запускаем PWM с коэффициентом заполнения 0 и частотой 100 Гц:
GPIO_PWM_0 = 12 FREQUENCY = 100 DELAY_TIME = 0.02 GPIO.setmode(GPIO.BCM) GPIO.setup(GPIO_PWM_0, GPIO.OUT) pwmOutput_0 = GPIO.PWM(GPIO_PWM_0, FREQUENCY) pwmOutput_0.start(0)
В основном цикле программы while True каждые 0.02 с (DELAY_TIME) изменяем duty cycle в диапазоне от 0 до 100:
while True: for dutyCycle in range(0, 101, 1): pwmOutput_0.ChangeDutyCycle(dutyCycle) sleep(DELAY_TIME)
При нажатии пользователем Ctrl + C завершаем выполнение программы:
pwmOutput_0.stop() GPIO.cleanup() print('exiting')
Запускаем:
python pwm_test_dynamic.py
И получаем:
Генерация работает как положено, так что на этом заканчиваем разбор ШИМ на Raspberry Pi, до новых статей!
частоту так же можно менять в процессе выполнения программы?
Да, через ChangeFrequency(), аналогично по сути.
в строке "sleep(DELAY_TIME)" ошибка, там должно быть "time.sleep(DELAY_TIME)"
иначе будет вот это
File "pwm_test_dynamic.py", line 14, in <module>
sleep(DELAY_TIME)
NameError: name 'sleep' is not defined
Здравствуйте. Частота следования импульсов при проверке осциллографом не получается постоянной - при установке в программе 1000, она колеблется от 860 до 890 примерно. Читала что зависит это от частоты процессора самой Raspberry - она меняется в процессе работы. Подскажите, пожалуйста, возможно ли добиться постоянной частоты следования импульсов.
Добрый вечер!
Я насколько помню RPi.GPIO только программный ШИМ может генерировать, то есть тут на особую точность не приходится рассчитывать в принципе. Можно pigpio использовать как вариант (либо другие альтернативы), там точно hardware PWM поддерживается.
Спасибо, действительно дает ожидаемые 1000 импульсов в сек. или около того.
Отлично )