В этой статье создадим простое приложение на 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() – отображает внесенные изменения, если таковы были.













