В этой статье создадим простое приложение на Python для управления через UART цветом и яркостью светодиодной матрицы на WS2812B, подключенной к плате Ардуино.
Приложение для отправки цвета и яркости на Python
Для создания графического приложения будем использовать модуль Tkinter.
Создаем окно нашего приложения
from tkinter import * # Импортируем модуль Tkinter window = Tk() # Создаем главное окно window.title('Управление матрицей') # Указываем название главного окна window.geometry('400x200') # Задаем размер главного окна window.mainloop() # Запускаем главный цикл обработки событий
После запуска кода должно появится вот такое окно:
Выбор COM-порта
Добавим текстовую метку (виджет Label) и выпадающий список (виджет Combobox) для выбора COM-порта, к которому подключена плата Arduino. Для начала подключим пакет ttk
сверху в программе:
from tkinter import ttk # Подключаем пакет ttk
Теперь добавим код для создания текстовой метки, передав в качестве параметров наш объект главного окна window
и параметр text
со значением «COM-порт:».
lbl_port = Label(window, text='COM-порт:')
И разместим ее в нашем окне с помощью сетки grid
в нулевой строке row=0
и в нулевой колонке column=0
:
lbl_port.grid(row=0, column=0)
Выпадающий список создадим с помощью виджета Combobox, передав наше главное окно window
:
combo_port = ttk.Combobox(window)
И разметим его рядом с текстовой меткой в нулевой строке row=0
и в первой колонке column=1
:
combo_port.grid(row=0, column=1)
Получился вот такой вот код:
from tkinter import * # Импортируем модуль Tkinter from tkinter import ttk # Подключаем пакет ttk # Главное окно window = Tk() # Создаем главное окно window.title('Управление матрицей') # Указываем название главного окна window.geometry('400x200') # Задаем размер главного окна # Настройка соединения lbl_port = Label(window, text='COM-порт:') # Создаем элемент Label с текстом lbl_port.grid(row=0, column=0) # Размещаем в нашем окне с помощью сетки combo_port = ttk.Combobox(window) # Создаем выпадающий список combo_port.grid(row=0, column=1) # Размещаем в нулевой строке в первой колонке window.mainloop() # Запускаем главный цикл обработки событий
И вот такой вид:
Выбор скорости обмена данными
По аналогии с COM-портом, добавим текстовую метку и выпадающий список для выбора скорости обмена данными.
Создаем label:
lbl_speed = Label(window, text='Скорость:')
Размещаем в первой строке (нумерация строк и столбцов начинается с нуля). Параметр pady=10
задает отступ по вертикали (сверху и снизу) от границ ячейки до границы элемента (в пикселях).
Заметка
Если нужно добавить отступ по горизонтали в 10 пикселей, нужно добавить параметр padx=10
. Также доступны параметры ipadx
и ipady
, которые устанавливают отступы по горизонтали и вертикали соответственно между от границы элемента до его содержимого.
lbl_speed.grid(row=1, column=0, pady=10)
Теперь создаем комбобокс с выбором скорости по аналогии с выбором порта:
combo_speed = ttk.Combobox(window)
И размещаем на сетке:
combo_speed.grid(row=1, column=1)
Отделим настройки подключения с помощью горизонтального разделителя. Для этого используем виджет Separator
с параметром orien='horizontal'
. Параметр columnspan=3
указывает на то, что элемент занимает 3 столбца:
# Разделитель sep = ttk.Separator(window, orient='horizontal') sep.grid(row=2, column=0, columnspan=3, ipadx=190, padx=5, ipady=5, pady=5)
Теперь код имеет вид:
from tkinter import * # Импортируем модуль Tkinter from tkinter import ttk # Подключаем пакет ttk # Главное окно window = Tk() # Создаем главное окно window.title('Управление матрицей') # Указываем название главного окна window.geometry('400x200') # Задаем размер главного окна # Настройка соединения lbl_port = Label(window, text='COM-порт:') # Создаем элемент Label с текстом lbl_port.grid(row=0, column=0) # Размещаем в нашем окне с помощью сетки combo_port = ttk.Combobox(window) # Создаем выпадающий список combo_port.grid(row=0, column=1) # Размещаем в нулевой строке в первой колонке lbl_speed = Label(window, text='Скорость:') lbl_speed.grid(row=1, column=0, pady=10) combo_speed = ttk.Combobox(window) # Выпадающий список для выбора скорости combo_speed.grid(row=1, column=1) # Разделитель sep = ttk.Separator(window, orient='horizontal') sep.grid(row=2, column=0, columnspan=3, ipadx=190, padx=5, ipady=5, pady=5) window.mainloop() # Запускаем главный цикл обработки событий
А окно выглядит так:
Выбор цвета
Добавляем текстовую метку «Цвет» и размещаем ее в нашем окне:
# Цвет и яркость lbl_color = Label(window, text='Цвет:') lbl_color.grid(row=3, column=0, pady=10)
Теперь создадим кнопку, по нажатию которой будет открываться окно выбора цвета. Кнопка представлена виджетом Button
:
btn_color = ttk.Button(window, text='Выбрать цвет') btn_color.grid(row=3, column=1)
А также виджет Label
, который будет отображать выбранный цвет. При выборе цвета будем устанавливать фон для этого элемента.
lbl_val_color = Label(window, text='Не выбран') lbl_val_color.grid(row=3, column=2)
Выбор яркости
Добавим текст «Яркость» в четвертой строке:
lbl_brightness = Label(window, text='Яркость:') lbl_brightness.grid(row=4, column=0, pady=10)
Выбор яркости осуществим через ползунок, в tkinter это виджет Scale
. Значение яркости можно будет выбрать от 0 до 255. При 0 матрица светится не будет, 255 — максимальная яркость.
scale_brightness = ttk.Scale(orient=HORIZONTAL, length=100, from_=0, to=255) scale_brightness.grid(row=4, column=1)
Рядом добавим Label
, который будет отображаться значение ползунка:
lbl_val_brightness = Label(window, text='0', width=10) lbl_val_brightness.grid(row=4, column=2)
Кнопка отправки данных
Нам осталось поместить в наше окно кнопку, при нажатии на которую будет происходить отправка данных к ардуино.
btn_send = ttk.Button(window, text='Отправить', command=send) btn_send.grid(row=5, column=1)
В итоге получился вот такой код:
from tkinter import * # Импортируем модуль Tkinter from tkinter import ttk # Подключаем пакет ttk # Главное окно window = Tk() # Создаем главное окно window.title('Управление матрицей') # Указываем название главного окна window.geometry('400x200') # Задаем размер главного окна # Настройка соединения lbl_port = Label(window, text='COM-порт:') # Создаем элемент Label с текстом lbl_port.grid(row=0, column=0) # Размещаем в нашем окне с помощью сетки combo_port = ttk.Combobox(window) # Создаем выпадающий список combo_port.grid(row=0, column=1) # Размещаем в нулевой строке в первой колонке lbl_speed = Label(window, text='Скорость:') lbl_speed.grid(row=1, column=0, pady=10) combo_speed = ttk.Combobox(window) # Выпадающий список для выбора скорости combo_speed.grid(row=1, column=1) # Разделитель sep = ttk.Separator(window, orient='horizontal') sep.grid(row=2, column=0, columnspan=3, ipadx=190, padx=5, ipady=5, pady=5) # Цвет и яркость lbl_color = Label(window, text='Цвет:') lbl_color.grid(row=3, column=0, pady=10) btn_color = ttk.Button(window, text='Выбрать цвет') btn_color.grid(row=3, column=1) lbl_val_color = Label(window, text='Не выбран') lbl_val_color.grid(row=3, column=2) lbl_brightness = Label(window, text='Яркость:') lbl_brightness.grid(row=4, column=0, pady=10) scale_brightness = ttk.Scale(orient=HORIZONTAL, length=100, from_=0, to=255) scale_brightness.grid(row=4, column=1) lbl_val_brightness = Label(window, text='0', width=10) lbl_val_brightness.grid(row=4, column=2) btn_send = ttk.Button(window, text='Отправить') btn_send.grid(row=5, column=1) window.mainloop() # Запускаем главный цикл обработки событий
И вот такой внешний вид:
Получение доступных COM-портов
Для работы с COM-портами нам нужно установить модуль pyserial
. Чтобы установить в терминале вводим строку:
pip3 install pyserial
Теперь импортируем модуль и утилиту list_ports
из пакета serial.tools
для получения списка доступных ком-портов. Для этого в начале нашей программы добавим две строчки кода:
import serial from serial.tools import list_ports
Чтобы получить доступные ком-порты на ПК, необходимо вызвать функцию comports()
из list_ports
.
# Получаем список достпных COM-портов ports = list_ports.comports() # Создаем пустой список names_ports = list() # Добавляем все доступные порты в список names_ports for port in ports: names_ports.append(port.name)
Теперь добавим полученный список значений в созданный нами для этих целей Сombobox
:
# Добавляем в выпадающий список названия доступных портов combo_port['values'] = names_ports
И небольшая проверка. Если наш список с портами не пустой, то по умолчанию установим первый элемент списка:
if len(names_ports) != 0: combo_port.current(0)
Значения для скорости обмена данными
Тут все просто. Добавляем скорости, которые могут нам понадобиться, и устанавливаем 9600 по умолчанию.
combo_speed['values'] = (9600, 19200, 38400, 57600, 74880, 115200) combo_speed.current(0)
Код выглядит вот так:
from tkinter import * # Импортируем модуль Tkinter from tkinter import ttk # Подключаем пакет ttk import serial from serial.tools import list_ports # Главное окно window = Tk() # Создаем главное окно window.title('Управление матрицей') # Указываем название главного окна window.geometry('400x200') # Задаем размер главного окна # Настройка соединения lbl_port = Label(window, text='COM-порт:') # Создаем элемент Label с текстом lbl_port.grid(row=0, column=0) # Размещаем в нашем окне с помощью сетки combo_port = ttk.Combobox(window) # Создаем выпадающий список combo_port.grid(row=0, column=1) # Размещаем в нулевой строке в первой колонке lbl_speed = Label(window, text='Скорость:') lbl_speed.grid(row=1, column=0, pady=10) combo_speed = ttk.Combobox(window) # Выпадающий список для выбора скорости combo_speed.grid(row=1, column=1) # Разделитель sep = ttk.Separator(window, orient='horizontal') sep.grid(row=2, column=0, columnspan=3, ipadx=190, padx=5, ipady=5, pady=5) # Цвет и яркость lbl_color = Label(window, text='Цвет:') lbl_color.grid(row=3, column=0, pady=10) btn_color = ttk.Button(window, text='Выбрать цвет') btn_color.grid(row=3, column=1) lbl_val_color = Label(window, text='Не выбран') lbl_val_color.grid(row=3, column=2) lbl_brightness = Label(window, text='Яркость:') lbl_brightness.grid(row=4, column=0, pady=10) scale_brightness = ttk.Scale(orient=HORIZONTAL, length=100, from_=0, to=255) scale_brightness.grid(row=4, column=1) lbl_val_brightness = Label(window, text='0', width=10) lbl_val_brightness.grid(row=4, column=2) btn_send = ttk.Button(window, text='Отправить') btn_send.grid(row=5, column=1) # Получаем список достпных COM-портов ports = list_ports.comports() # Создаем пустой список names_ports = list() # Добавляем все доступные порты в список names_ports for port in ports: names_ports.append(port.name) # Добавляем в выпадающий список названия доступных портов combo_port['values'] = names_ports if len(names_ports) != 0: combo_port.current(0) # Скорости combo_speed['values'] = (9600, 19200, 38400, 57600, 74880, 115200) combo_speed.current(0) window.mainloop() # Запускаем главный цикл обработки событий
Функция вызова окна выбора цвета
Теперь нам нужно привязать нашу кнопку btn_color
к функции, которая будет нам отображать палитру цветов. Находим строчку создания нашей кнопки:
btn_color = ttk.Button(window, text='Выбрать цвет')
И добавляем параметр command=select_color
. Теперь при нажатии на кнопку будет вызываться функция select_color()
:
btn_color = ttk.Button(window, text='Выбрать цвет', command=select_color)
Подключаем функцию askcolor
из модуля tkinter:
from tkinter.colorchooser import askcolor
Собственно, теперь сама функция выбора цвета select_color()
:
def select_color(): colors = askcolor(title='Цвет') if colors[1] is None: return 0 lbl_val_color.configure(bg=colors[1], text=str(colors[1]))
В переменной colors
мы сохраняем кортеж со значениями, которое вернет функция askcolor()
. Вот пример данных, хранящихся в переменной colors
после выбора цвета:
((64.25, 0.0, 128.5), '#400080')
В элементе colors[0]
хранится еще один кортеж с тремя элементами с типом float
(число с плавающей запятой): colors[0][0]
— хранит значение для красного цвета (64.25), colors[0][1]
— значение для зеленного цвета (0.0), colors[0][2]
— значение для синего цвета (128.5). В элементе colors[1]
хранится строка, со значением цвета в шестнадцатеричном виде ('#800040'
). Вот это значение мы и будем отправлять, немного его преобразовав.
Инструкция if
проверяет выбрал ли пользователь цвет. Если нет, то выходит. Если выбрал, то устанавливает фон виджета lbl_val_color
в выбранный цвет и устанавливает текст, соответствующий RGB коду цвета.
Функция изменения яркости
Находим наш ползунок:
scale_brightness = ttk.Scale(orient=HORIZONTAL, length=100, from_=0, to=255)
И добавляем параметр command=change_brightness
.
scale_brightness = ttk.Scale(orient=HORIZONTAL, length=100, from_=0, to=255, command=change_brightness)
Функция change_brightness()
, при изменении положения ползунка, будет выводить значение в виджет lbl_val_brightness
.
def change_brightness(val): lbl_val_brightness.configure(text=int(float(val)))
Отправка значений плате Ардуино
При нажатии на кнопку отправить будем отправлять цвет и яркость плате. Добавим нашей кнопке Отправить параметр command=send
:
btn_send = Button(window, text='Отправить', command=send)
Сверху в коде подключим модуль time
, в котором реализована функция задержки:
import time
И напишем функцию для отправки:
def send(): # Получаем значение выбранного COM-порта port_select = combo_port.get() # Если порт не выбран, выходим из функции if port_select == 0: return 0 # Сохраняем установленное значение яркости в переменной brightness brightness = int(scale_brightness.get()) # Проверяем выбран ли цвет if lbl_val_color['text'] != 'Не выбран': # Если цвет выбран, сохраняем его в переменной color, # предварительно убрав знак "#" и преобразовав в тип int color = int(lbl_val_color['text'][1:], base=16) else: # Если цвет не выбран, выходим из функции и ничего не отправляем return 0 # Формируем сообщение из 4 байт. 3 байта будут хранить цвет, один младший байт - яркость message = (color << 8) | brightness # Создаем подключение, используя выбранный порт и скорость ser = serial.Serial(port=combo_port.get(), baudrate=combo_speed.get(), timeout=0.1) # Отправляем сообщение, преобразовав сообщение в байтстроку ser.write(str(message).encode()) # Ждем секунду time.sleep(1) # Закрываем порт ser.close()
Полный код для питона получился вот такой:
from tkinter import * # Импортируем модуль Tkinter from tkinter import ttk # Подключаем пакет ttk import serial from serial.tools import list_ports from tkinter.colorchooser import askcolor import time def select_color(): colors = askcolor(title='Цвет') if colors[1] is None: return 0 lbl_val_color.configure(bg=colors[1], text=str(colors[1])) def change_brightness(val): lbl_val_brightness.configure(text=int(float(val))) def send(): # Получаем значение выбранного COM-порта port_select = combo_port.get() # Если порт не выбран, выходим из функции if port_select == 0: return 0 # Сохраняем установленное значение яркости в переменной brightness brightness = int(scale_brightness.get()) # Проверяем выбран ли цвет if lbl_val_color['text'] != 'Не выбран': # Если цвет выбран, сохраняем его в переменной color, # предварительно убрав знак "#" и преобразовав число в тип int color = int(lbl_val_color['text'][1:], base=16) else: # Если цвет не выбран, выходим из функции и ничего не отправляем return 0 # Формируем сообщение. 3 байта будут хранить цвет, один младший байт - яркость message = (color << 8) | brightness # Создаем подключение, используя выбранный порт и скорость ser = serial.Serial(port=combo_port.get(), baudrate=combo_speed.get(), timeout=0.1) # Отправляем сообщение, преобразовав сообщение в байтстроку ser.write(str(message).encode()) # Ждем секунду time.sleep(1) # Закрываем порт ser.close() # Главное окно window = Tk() # Создаем главное окно window.title('Управление матрицей') # Указываем название главного окна window.geometry('400x200') # Задаем размер главного окна # Настройка соединения lbl_port = Label(window, text='COM-порт:') # Создаем элемент Label с текстом lbl_port.grid(row=0, column=0) # Размещаем в нашем окне с помощью сетки combo_port = ttk.Combobox(window) # Создаем выпадающий список combo_port.grid(row=0, column=1) # Размещаем в нулевой строке в первой колонке lbl_speed = Label(window, text='Скорость:') lbl_speed.grid(row=1, column=0, pady=10) combo_speed = ttk.Combobox(window) # Выпадающий список для выбора скорости combo_speed.grid(row=1, column=1) # Разделитель sep = ttk.Separator(window, orient='horizontal') sep.grid(row=2, column=0, columnspan=3, ipadx=190, padx=5, ipady=5, pady=5) # Цвет и яркость lbl_color = Label(window, text='Цвет:') lbl_color.grid(row=3, column=0, pady=10) btn_color = ttk.Button(window, text='Выбрать цвет', command=select_color) btn_color.grid(row=3, column=1) lbl_val_color = Label(window, text='Не выбран') lbl_val_color.grid(row=3, column=2) lbl_brightness = Label(window, text='Яркость:') lbl_brightness.grid(row=4, column=0, pady=10) scale_brightness = ttk.Scale(orient=HORIZONTAL, length=100, from_=0, to=255, command=change_brightness) scale_brightness.grid(row=4, column=1) lbl_val_brightness = Label(window, text='0', width=10) lbl_val_brightness.grid(row=4, column=2) btn_send = Button(window, text='Отправить', command=send) btn_send.grid(row=5, column=1) # Получаем список достпных COM-портов ports = list_ports.comports() # Создаем пустой список names_ports = list() # Добавляем все доступные порты в список names_ports for port in ports: names_ports.append(port.name) # Добавляем в выпадающий список названия доступных портов combo_port['values'] = names_ports if len(names_ports) != 0: combo_port.current(0) # Скорости combo_speed['values'] = (9600, 19200, 38400, 57600, 74880, 115200) combo_speed.current(0) window.mainloop() # Запускаем главный цикл обработки событий
Подключение матрицы к Ардуино
Подключение матрицы на WS2812B к Arduino можно увидеть на рисунке ниже:
Ардуино | Матрица | |
---|---|---|
Питание | +5V | VCC |
Земля | GND | GND |
Управляющий сигнал | D6 | DIN |
Внимание!
Матрица может потреблять достаточно большой ток, поэтому рекомендуется «запитывать» ее через внешний источник питания. У меня при максимальной яркости и при установленном белом цвете потребление подскочило до 3.74 ампера. Белый цвет самый прожорливый, при значении яркости 40 потребление у матрицы 10х10 уже составило 0.66 ампера.
Предупреждение!
Обратите внимание на конденсатор, который подключен между контактами RESET и GND ардуины. Его необходимо подключить после загрузки скетча в плату. Он необходим, чтобы ардуина не перезагружалась каждый раз, как получает данные через UART.
Скетч для ардуино
Вот сам скетч и немного пояснений к нему:
#include <FastLED.h> #define NUM_LEDS 64 #define DATA_PIN 6 CRGB leds[NUM_LEDS]; unsigned long data = 0; void setup() { Serial.begin(9600); FastLED.addLeds<NEOPIXEL, DATA_PIN>(leds, NUM_LEDS); FastLED.setBrightness(0); } void loop() { if(Serial.available()) { data = Serial.parseInt(); FastLED.setBrightness(0xFF&data); for(int i=0; i < NUM_LEDS; i++) { leds[i] = CRGB((data >> 8) & 0xFFFFFF); } FastLED.show(); } }
Для управления матрицей используем библиотеку «FastLED». Контакт для данных был выбран D6. NUM_LEDS
— количество светодиодов в нашей матрице (8х8).
В функции setup()
настраиваем последовательный порт на скорость 9600 бод, передаем количество светодиодов и сигнальный контакт классу FastLED, а также устанавливаем яркость на 0.
В функции loop()
проверяем пришли ли данные по UART, если пришли — то получаем их с помощью метода Serial.parseInt()
и сохраняем ее в переменной data
. Первый байт у нас яркость, поэтому это значение 0xFF&data
мы передаем методу setBrightness()
. В цикле for
мы устанавливаем принятый цвет (3 байта, цветовой RGB-код) для каждого светодиода (data >> 8) & 0xFFFFFF
(мы сдвинули число на 8 бит вправо, которые хранили значение яркости). FastLED.show()
— отображает внесенные изменения, если таковы были.