Python и Arduino. Управление цветом светодиодной матрицы

В этой статье создадим простое приложение на Python для управления через UART цветом и яркостью светодиодной матрицы на WS2812B, подключенной к плате Ардуино.

Приложение для отправки цвета и яркости на Python

Для создания графического приложения будем использовать модуль Tkinter.

Создаем окно нашего приложения

from tkinter import *                   # Импортируем модуль Tkinter

window = Tk()                           # Создаем главное окно
window.title('Управление матрицей')     # Указываем название главного окна
window.geometry('400x200')              # Задаем размер главного окна

window.mainloop()                       # Запускаем главный цикл обработки событий

После запуска кода должно появится вот такое окно:

Tkinter - главное окно
Рисунок 1. Главное окно для приложения

Выбор 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 можно увидеть на рисунке ниже:

АрдуиноМатрица
Питание +5VVCC
Земля GNDGND
Управляющий сигнал D6DIN
Подключение контактов матрицы к ардуино

Внимание!


Матрица может потреблять достаточно большой ток, поэтому рекомендуется «запитывать» ее через внешний источник питания. У меня при максимальной яркости и при установленном белом цвете потребление подскочило до 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() — отображает внесенные изменения, если таковы были.


0 0 голоса
Рейтинг статьи
Подписаться
Уведомить о
guest
0 комментариев
Межтекстовые Отзывы
Посмотреть все комментарии