День 4: Удивительная аналоговая электроника!
Добро пожаловать на четвёртый день вашего Адвент-календаря «12 проектов Кодмас». Сегодня мы будем работать с аналоговыми входами с помощью потенциометра, а также совместим это с некоторыми компонентами, с которыми мы уже познакомились.
До сих пор наш код работал в режиме ВКЛ/ВЫКЛ или HIGH/LOW, используя цифровые входы и выходы. С сегодняшним компонентом мы изучим аналоговые входы, которые работают иначе.
Подсказка: в мире мейкеров одни используют «analog», другие предпочитают «analogue» — это одно и то же.
Содержимое коробки №4
В этой коробке вы найдёте:
1x потенциометр на 10 кОм
1x ручка потенциометра
1x шайба (может быть уже установлена на потенциометр)
1x гайка (может быть уже установлена на потенциометр)
3x провода папа-папа
Сегодняшний проект
Сегодня мы исследуем замечательный мир аналоговых сигналов.
До сих пор мы использовали цифровые сигналы с кнопками и светодиодами из нашего набора. Цифровые сигналы — это строго 1 или 0, HIGH или LOW, ВКЛ или ВЫКЛ. Это одно или другое, и ничего между ними.
Аналоговые сигналы предлагают широкий диапазон чисел для представления значений, что гораздо лучше подходит для датчиков, регуляторов и подобных компонентов.
Думайте о цифровом как о выключателе света — он либо включён, либо выключен. Думайте об аналоговом как о регуляторе громкости на стереосистеме — вы можете поворачивать ручку, чтобы постепенно увеличивать и уменьшать громкость на небольшие величины.
Наш потенциометр может передавать эти аналоговые сигналы (в виде напряжения) на Raspberry Pi Pico, давая нам плавный вход с широким диапазоном значений для использования в нашем коде.
Что такое потенциометр?
Потенциометр — это переменный резистор.
В нашем наборе уже были обычные резисторы для светодиодов, но они имели фиксированное значение, обеспечивая определённое сопротивление. Потенциометр — это резистор, значение которого можно изменять (поворачивая ручку).
Потенциометр в вашей коробке рассчитан на 10 кОм, то есть его можно установить в диапазоне от 0 Ом до 10 000 Ом (сопротивление измеряется в омах).
Способ подключения нашего потенциометра позволяет использовать его для подачи переменного напряжения на Raspberry Pi Pico, которое специальные пины умеют считывать, преобразовывать и выдавать значение, которое можно использовать в проектах.
Мы объясним эти пины чуть позже, а пока давайте соберём схему.
Сборка схемы
Убедитесь, что USB-кабель Pico отключён перед изменением схемы.
Возьмите потенциометр, наденьте плоскую шайбу на ось и затем закрутите включённую гайку (они могут быть уже установлены).
Затем, расположив ножки потенциометра вниз, поверните ручку полностью влево и установите колпачок так, чтобы цветная линия указывала в нижний левый угол (как у радио, повёрнутого до минимума).
Оставьте светодиоды и провод GND (в синей полосе) на месте от вчерашней коробки, но уберите кнопки и все другие детали. Это наша отправная точка:
Теперь возьмите мини-макетную плату и установите потенциометр на переднем крае (обращённом к вам). На нашей схеме используется изображение стандартного потенциометра, однако поскольку у вас версия с панельным креплением, между ножками будет пустое отверстие:
Далее нам нужно подключить потенциометр к схеме.
Правая ножка должна подключаться к пину 3V3(OUT) (физический пин 36), средняя ножка — к GPIO 27 (физический пин 32), а левая ножка — к GND (используйте синюю полосу, так как она уже подключена к GND для светодиодов).
Задание 1: Вывод аналоговых значений
Начнём с простой программы, которая непрерывно выводит аналоговые показания с нашего потенциометра.
Мы используем именно GPIO27, так как это один из АЦП-пинов Pico. Но что такое АЦП?
Пины АЦП
Если снова посмотреть на карту пинов Pico, вы увидите три тёмно-зелёных АЦП-пина справа — GPIO26, 27 и 28 (физические пины 31, 32 и 34).
АЦП — это Аналого-Цифровой Преобразователь (A nalogue to D igital C onverter). АЦП-пины нашего Pico обладают особой способностью преобразовывать аналоговый входной сигнал в цифровую форму, которую мы можем использовать. Для использования этой функции нам нужно импортировать ADC в наш код.
Примечание: вы также заметите пины ADC_VREF и AGND, но в этом наборе мы не будем углубляться в их изучение, так как они нам строго не нужны — не хочется усложнять, пока мы только учимся.
Код
Код ниже импортирует ADC и задаёт GPIO27 как пин АЦП, затем запускает цикл while, который выводит значение потенциометра каждую секунду.
Функция read_u16 делает следующее: она берёт переменное напряжение, поданное на пин от потенциометра, и масштабирует его до диапазона используемых значений от 0 до 65535.
(Для тех, кто хочет разобраться подробнее: АЦП-пины Pico 12-битные, однако MicroPython масштабирует это до 16-битного диапазона, который составляет 0–65535).
Запустите приведённый ниже код, а затем попробуйте повернуть ручку — наблюдайте, как значения растут и падают при повороте ручки справа налево (значения не всегда опускаются до нуля — это нормально). Именно здесь всё вышесказанное должно обрести смысл:
# Импорты
from machine import ADC, Pin
import time
# Настройка потенциометра на АЦП-пине 27
potentiometer = ADC(Pin(27))
while True: # Зацикливаемся навсегда
print(potentiometer.read_u16()) # Считываем значение потенциометра
time.sleep(1) # Ждём секунду
Задание 2: Управление миганием светодиодов аналоговыми значениями
Итак, что мы можем делать с этим широким диапазоном входных показаний? Одно из применений — использовать его в коде для управления другими компонентами, например светодиодами.
В отличие от предыдущих случаев, когда мы включали и выключали светодиоды кнопками, широкий диапазон выходных показаний позволяет нам указать коду делать разные вещи в зависимости от того, в какой части диапазона находится значение.
Код ниже использует показания для управления скоростью мигания светодиодов. Мы делаем это, превращая временную задержку в переменную.
Переменные для задержки
Во втором дне мы использовали переменную для счётчика, который постоянно обновлялся. На этот раз мы используем переменную, чтобы задавать значение в одном месте, а не искать все временные задержки по всему коду.
Например, представьте, что в нашем коде 5 светодиодов и мы используем задержку в 1 секунду после каждой вспышки. Из того, что мы изучили до сих пор, мы бы использовали time.sleep(1) после каждой вспышки.
Это работает нормально… но обновлять все эти строки каждый раз, когда мы хотим изменить скорость мигания, довольно неудобно! Вместо этого мы можем создать переменную и присвоить ей имя, например mydelay, и задать ей значение, скажем 1.
Затем мы можем изменить строки с временной задержкой так, чтобы они ссылались на эту переменную, а не вводили число напрямую, используя time.sleep(mydelay). Теперь, чтобы обновить скорость каждой вспышки во всей программе, нам нужно изменить только переменную mydelay!
Хотя в примере ниже мы используем задержку только один раз, мы всё же решили показать ещё один способ использования переменных, чтобы сэкономить ваше время и улучшить код.
Код
Пример кода ниже:
Считывает показания с потенциометра
Делит показание, чтобы получить число, более удобное в качестве задержки
Присваивает разделённое показание переменной mydelay
Использует эту переменную задержки для управления тем, как долго светодиоды остаются включёнными и выключенными, создавая последовательность мигания.
Для начала поверните ручку примерно до половины, запустите приведённый ниже код, затем попробуйте добавить другие светодиоды и заставить их мигать тоже, или добавьте строку вывода в конце цикла while, чтобы увидеть, какое значение использует mydelay:
# Импорты
from machine import ADC, Pin
import time
# Настройка потенциометра на АЦП-пине 27
potentiometer = ADC(Pin(27))
# Настройка пина красного светодиода
red = Pin(18, Pin.OUT)
# Создаём переменную mydelay и начинаем с 0
mydelay = 0
while True: # Зацикливаемся навсегда
# Обновляем переменную значением показания, делённым на 65000
mydelay = potentiometer.read_u16() / 65000
# Красный светодиод включён на период задержки
red.value(1)
time.sleep(mydelay)
# Красный светодиод выключен на период задержки
red.value(0)
time.sleep(mydelay)
Задание 3: Зажигание светодиодов в зависимости от значения потенциометра
Мы также можем использовать широкий диапазон аналоговых значений для срабатывания действия в зависимости от того, в какой части общего диапазона мы находимся. Это можно сделать с помощью операторов if, на этот раз с условиями, которые проверяют, является ли значение выше, ниже или находится в заданном диапазоне!
В нашем примере ниже мы используем несколько операторов if, чтобы проверить, меньше ли показание 20000, находится ли оно между 20000 и 40000, или больше 40000:
Чтобы оператор if искал значение «меньше или равно», мы используем оператор <=, вот так: if reading <= 20000
Чтобы оператор if искал значение «между» двумя числами, мы используем немного менее очевидную комбинацию двух операторов <, вот так: if 20000 < reading < 40000
Чтобы оператор if искал значение «больше или равно», мы используем оператор >=, вот так: if reading >= 40000
Попробуйте, скопировав приведённый ниже код в Thonny, запустив его на Pico и *медленно* двигая ручку слева направо. Помните, комментарии в коде помогают понять каждую строку:
# Импорты
from machine import ADC, Pin
import time
# Настройка потенциометра на АЦП-пине 27
potentiometer = ADC(Pin(27))
# Настройка пинов светодиодов
red = Pin(18, Pin.OUT)
amber = Pin(19, Pin.OUT)
green = Pin(20, Pin.OUT)
# Создаём переменную для хранения показания
reading = 0
while True: # Зацикливаемся навсегда
reading = potentiometer.read_u16() # Считываем значение потенциометра и записываем в переменную
print(reading) # Выводим показание
time.sleep(0.1) # Короткая задержка
if reading <= 20000: # Если показание меньше или равно 20000
red.value(1) # Красный ВКЛ
amber.value(0)
green.value(0)
elif 20000 < reading < 40000: # Если показание между 20000 и 40000
red.value(0)
amber.value(1) # Жёлтый ВКЛ
green.value(0)
elif reading >= 40000: # Если показание больше или равно 40000
red.value(0)
amber.value(0)
green.value(1) # Зелёный ВКЛ
Задание 4: Плавное изменение яркости светодиода с помощью ШИМ
А теперь кое-что чуть более сложное, но очень крутое — мы собираемся плавно изменять яркость светодиода с помощью нашего потенциометра!
Для этого нам нужно использовать ШИМ.
Что такое ШИМ?
ШИМ расшифровывается как Ш иротно- И мпульсная М одуляция (PWM — P ulse W idth M odulation). Это тип цифрового сигнала, при котором мы за заданный период времени решаем, как долго сигнал будет ВКЛЮЧЁН (HIGH) и как долго ВЫКЛЮЧЕН (LOW).
Этот очень быстрый сигнал ВКЛ/ВЫКЛ может создавать эффект плавного изменения яркости светодиодов, а также использоваться для управления робототехническими компонентами, такими как сервоприводы.
Нашему коду нужны некоторые значения для работы ШИМ — Коэффициент заполнения и Частота.
Что такое коэффициент заполнения ШИМ?
Мы можем управлять тем, как долго наши светодиоды будут ВКЛЮЧЕНЫ/HIGH, изменяя Коэффициент заполнения (Duty Cycle). Коэффициент заполнения — это процент времени, в течение которого наш светодиод будет включён. Чем выше коэффициент заполнения, тем дольше светодиод будет включён и тем ярче он будет казаться.
Коэффициент заполнения для Pico в MicroPython может варьироваться от 0 до 65535, что удобно, поскольку это совпадает с выходом нашего потенциометра (0–65535), и мы можем использовать это значение напрямую, не манипулируя им.
Что такое частота ШИМ?
Наш код ШИМ также требует значения частоты — количества раз в секунду, которое мы будем повторять цикл ВКЛ/ВЫКЛ. В примере ниже мы используем 1000.
Код
Теперь, когда мы знаем, что такое ШИМ, Коэффициент заполнения и Частота ШИМ, давайте попробуем!
Пример кода ниже вводит несколько новых элементов:
Во-первых, нам нужно импортировать PWM
Мы настраиваем пин светодиода немного по-другому, чтобы задать его как выход ШИМ, используя led = PWM(Pin(18))
Мы задаём частоту ШИМ с помощью pwm.freq(1000)
Мы создаём переменную для хранения показания потенциометра и начинаем её с нуля, используя reading = 0
Цикл while True затем считывает значение потенциометра и сохраняет его в переменной reading, и выводит это для нас в Thonny
Затем светодиоду передаётся это показание в качестве коэффициента заполнения с помощью led.duty_u16(reading)
После этого следует очень короткая задержка, и цикл продолжается
Скопируйте приведённый ниже код в Thonny, запустите его, а затем поверните ручку слева направо:
# Импорты (включая PWM и ADC)
from machine import ADC, Pin, PWM
import time
# Настройка потенциометра на АЦП-пине 27
potentiometer = ADC(Pin(27))
# Настройка пина светодиода с ШИМ
led = PWM(Pin(18))
# Задаём частоту ШИМ
# Определяет, как часто переключать питание между включением и выключением для светодиода
led.freq(1000)
# Создаём переменную для показания
reading = 0
while True: # Зацикливаемся навсегда
reading = potentiometer.read_u16() # Считываем значение потенциометра и записываем в переменную
print(reading) # Выводим показание
# Задаём коэффициент заполнения ШИМ светодиода равным значению показания потенциометра
# Коэффициент заполнения указывает светодиоду, как долго он должен быть включён каждый раз
led.duty_u16(reading)
time.sleep(0.001) # Короткая задержка
День 4 завершён!
Отличная работа, мейкеры! Мы понимаем, что сегодня было немного тяжелее, так как мы представили более сложные темы — аналоговые сигналы и ШИМ. Не торопитесь вернуться к сегодняшним примерам и поиграть с кодом, если хотите больше практики — сделай, сломай, почини, учись!
Сегодня вы:
Узнали об аналоговых сигналах и разнице между аналоговыми и цифровыми сигналами
Собрали схему с потенциометром
Научились использовать АЦП, встроенный в Raspberry Pi Pico
Управляли светодиодами с помощью аналогового сигнала
Узнали о ШИМ, включая коэффициент заполнения и частоту
Сохраните схему в безопасном месте до завтра (ничего не разбирайте пока). До встречи завтра!
Для создания изображений схем на макетных платах использовался Fritzing.