Найти в Дзене
Stepan.Burmistrov

Двусторонний обмен данными между компьютером и Arduino по UART

Оглавление

Сегодня я хочу поделиться с вами достаточно важным проектом, несмотря на то, что это опять «Мигание светодиодами».
Мы все знаем, что объединение программирования, электроники и механики открывает бесконечные возможности, и именно поэтому мы занимаемся робототехникой! Сегодня мы разработаем двунаправленный обмен данными между Python скриптом и Arduino!

Проект предполагает создание двух частей: программы на Python, которая обеспечивает графический интерфейс для управления светодиодами, и скетча для Arduino, который реагирует на команды от Python и управляет светодиодами. Взаимодействие между этими частями осуществляется через Serial порт по протоколу UART.

-2

Важно, что нажатия физических и виртуальных кнопок синхронизируют события, происходящие в двух программах!
Синхронизация данных - ключевой аспект в системах, где программное обеспечение взаимодействует с аппаратным. Это процесс, при котором данные передаются между программой и физическим устройством (например, между компьютером и микроконтроллером) таким образом, чтобы обеспечивать актуальность и точность информации с обеих сторон.

Эффективная синхронизация данных важна не только в учебных проектах, но и в реальных задачах, таких как робототехника, автоматизированные производственные линии, медицинское оборудование и многих других областях, где программное обеспечение взаимодействует с физическим миром.

Подключения светодиодов и кнопок к Arduino

-3
  1. Подключение светодиодов

Светодиоды подключаются к цифровым пинам Arduino. В нашем проекте используются пины 5, 6 и 7. Каждый светодиод подключается одним выводом к пину Arduino, а другим - к земле (GND) через резистор для ограничения тока. Это предотвращает повреждение светодиодов из-за чрезмерного тока.

Важно учитывать полярность светодиода: длинная ножка (анод) подключается к пину Arduino, а короткая (катод) - к земле через резистор.

  1. Подключение кнопок

Кнопки подключаются к цифровым пинам Arduino (в проекте используются пины 2, 3 и 4). Один контакт кнопки подключается к пину, а другой - к земле.

В коде Arduino используется режим INPUT_PULLUP, который активирует внутренний подтягивающий резистор к питанию. Это обеспечивает стабильное высокое (HIGH) состояние при отпущенной кнопке и низкое (LOW) при нажатии.

  1. Управление светодиодами

Состояние всех трех светодиодов хранится в одной переменной `ledStates`, что оптимизирует использование памяти. Каждый бит этой переменной соответствует состоянию одного светодиода.

Битовые операции используются для изменения и проверки состояния каждого светодиода. Например, операция `(ledStates >> i) & 1` возвращает состояние i-го светодиода.

  1. Переключение светодиодов

При изменении состояния светодиода используется операция XOR (`^=`), которая переключает соответствующий бит в `ledStates`.
Например, `ledStates ^= (1 << i)` переключит i-й светодиод.

  1. Чтение состояния кнопок

В основном цикле `loop()` Arduino постоянно проверяет состояние кнопок. Когда кнопка нажата, её пин переходит в низкое состояние (LOW).

Для каждой кнопки используются переменные, которые хранят время последнего изменения состояния и последнее зафиксированное состояние.

  1. Антидребезг

Антидребезг необходим, чтобы избежать ложных срабатываний из-за естественного "дребезга" контактов при нажатии кнопки. Это состояние, когда кнопка еще нажата не до конца и нет точного режима (нажата или отпущена).
Если текущее состояние кнопки отличается от последнего зафиксированного и прошло достаточно времени (задержка антидребезга), состояние кнопки обновляется, и происходит изменение состояния соответствующего светодиода.

Подробнее об обработке нажатия кнопки можно прочитать в этом материале:

https://t.me/burmistrov_robotics/28

https://dzen.ru/a/ZQ1mTsQZ6GkuxrH4

https://vk.com/@stepan.burmistrov-upravlenie-sostoyaniem-svetodioda-s-pomoschu-knopki-na-ardui

Программирование на Python

  1. Обнаружение Arduino

Скрипт начинается с поиска доступных COM-портов, чтобы найти подключенную Arduino. Это достигается с помощью библиотеки `serial.tools.list_ports`.

Функция `find_arduino_port()` перебирает все доступные порты, пытаясь открыть их с помощью `serial.Serial`. Успешное открытие порта со скоростью 9600 бод говорит о том, что Arduino найдена.

# Импортируем необходимые библиотеки
import serial
import serial.tools.list_ports
import cv2
import numpy as np
import time

# Функция для автоматического поиска и подключения к Arduino
def find_arduino_port():
# Получаем список всех доступных COM-портов
ports = serial.tools.list_ports.comports()
for port in ports:
print(port, end=" ")
try:
# Пытаемся открыть каждый порт
ser = serial.Serial(port.device, 9600, timeout=1)
print("opened")
return ser # Возвращаем объект порта, если успешно
except serial.SerialException:
# Продолжаем поиск, если порт не подходит
continue

# Выводим сообщение, если Arduino не найдена
print("Arduino не найдена.")
return None

# Ищем Arduino и подключаемся
ser = find_arduino_port()
if ser is None:
exit() # Завершаем программу, если подключение не удалось
time.sleep(2) #Ждем открытия порта
  1. Установление соединения

После обнаружения Arduino скрипт устанавливает соединение. Это обеспечивает двустороннюю связь между Python-скриптом и Arduino.

После подключения программа ждет две секунды, чтобы убедиться, что соединение стабильно и готово к передаче данных.

  1. Создание графического интерфейса с помощью OpenCV для управления светодиодами

С помощью библиотеки OpenCV создается простой графический интерфейс, который включает в себя визуальное представление светодиодов и кнопок управления.

Для каждого светодиода на интерфейсе отображается его текущее состояние (включен или выключен), а также кнопки для управления ими.

  1. Отрисовка элементов управления
-4

Используя функции OpenCV, такие как `cv2.ellipse` и `cv2.rectangle`, рисуются элементы управления: полукруги для светодиодов и прямоугольники для кнопок.

Цвета и размеры элементов выбираются так, чтобы четко отображать состояние каждого светодиода и обеспечивать понятный интерфейс.

# Функция для отрисовки интерфейса управления светодиодами
def draw_interface():
# Создаем черное изображение
img = np.zeros((220, 310, 3), dtype=np.uint8)
for i in range(3):
# Определяем центральные точки светодиодов и кнопок
center = (50 + i*100, 40)
bottom_center = (50 + i*100, 60)

# Выбираем цвет светодиода в зависимости от его состояния
led_color = (0, 255, 0) if ledStates & (1 << i) else (0, 50, 0)

# Рисуем полукруг и прямоугольник, имитируя форму светодиода
cv2.ellipse(img, center, (20, 20), 0, 180, 360, led_color, -1)
cv2.rectangle(img, (center[0] - 20, center[1]), (center[0] + 20, bottom_center[1]), led_color, -1)

# Рисуем "ножки" светодиода
cv2.line(img, (center[0] - 10, bottom_center[1]), (center[0] - 10, bottom_center[1] + 30), (200, 200, 200), 2)
cv2.line(img, (center[0] + 10, bottom_center[1]), (center[0] + 10, bottom_center[1] + 40), (200, 200, 200), 2)

# Рисуем кнопки
button_center = (50 + i*100, 170)
button_color = (200, 200, 200)
cv2.rectangle(img, (30 + i*100, 150), (70 + i*100, 190), button_color, -1)
cv2.circle(img, button_center, 10, (0, 0, 0), -1)

# Добавляем подписи к светодиодам
cv2.putText(img, f'LED {i+1}', (30 + i*100, 189), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 1)

# Отображаем созданное изображение
cv2.imshow('LED Control', img)
  1. Обработка событий мыши и синхронизация состояний светодиодов

Для взаимодействия пользователя с интерфейсом применяется обработчик событий мыши `mouse_callback`.

При клике на кнопку интерфейса скрипт определяет, какой светодиод должен быть переключен, изменяя переменную `ledStates`.

# Функция для обработки событий мыши
def mouse_callback(event, x, y, flags, param):
global ledStates
# Проверяем нажатие левой кнопки мыши
if event == cv2.EVENT_LBUTTONDOWN:
# Перебираем координаты кнопок
for i in range(3):
if 30 + i*100 < x < 70 + i*100 and 150 < y < 190:
# Изменяем состояние светодиода и отправляем новое состояние в Arduino
ledStates ^= (1 << i)
ser.write(bytearray([ledStates]))
return
  1. Синхронизация состояний

После изменения состояния светодиода, обновленное состояние отправляется в Arduino через последовательный порт, используя функцию `ser.write`.

Arduino, получив данные, обновляет состояние светодиодов соответственно. Таким образом, достигается синхронизация состояний между графическим интерфейсом на компьютере и физическими светодиодами.

Правило действительно и в обратную сторону, когда пользователь нажимает кнопку, подключенную к Arduino, светодиод переключает состояние, и с помощью команды Serial.write(ledStates) передает новые состояния в Python-скрипт.

Конечно же, весь материал доступен для подробного изучения и самостоятельного применения!

https://github.com/stepanburmistrov/arduino_python_sync

Код python:

# Импортируем необходимые библиотеки
import serial
import serial.tools.list_ports
import cv2
import numpy as np
import time

# Функция для автоматического поиска и подключения к Arduino
def find_arduino_port():
# Получаем список всех доступных COM-портов
ports = serial.tools.list_ports.comports()
for port in ports:
print(port, end=" ")
try:
# Пытаемся открыть каждый порт
ser = serial.Serial(port.device, 9600, timeout=1)
print("opened")
return ser # Возвращаем объект порта, если успешно
except serial.SerialException:
# Продолжаем поиск, если порт не подходит
continue

# Выводим сообщение, если Arduino не найдена
print("Arduino не найдена.")
return None

# Ищем Arduino и подключаемся
ser = find_arduino_port()
if ser is None:
exit() # Завершаем программу, если подключение не удалось
time.sleep(2) #Ждем открытия порта

# Переменная для хранения состояний светодиодов
ledStates = 0

# Функция для отрисовки интерфейса управления светодиодами
def draw_interface():
# Создаем черное изображение
img = np.zeros((220, 310, 3), dtype=np.uint8)
for i in range(3):
# Определяем центральные точки светодиодов и кнопок
center = (50 + i*100, 40)
bottom_center = (50 + i*100, 60)

# Выбираем цвет светодиода в зависимости от его состояния
led_color = (0, 255, 0) if ledStates & (1 << i) else (0, 50, 0)

# Рисуем полукруг и прямоугольник, имитируя форму светодиода
cv2.ellipse(img, center, (20, 20), 0, 180, 360, led_color, -1)
cv2.rectangle(img, (center[0] - 20, center[1]), (center[0] + 20, bottom_center[1]), led_color, -1)

# Рисуем "ножки" светодиода
cv2.line(img, (center[0] - 10, bottom_center[1]), (center[0] - 10, bottom_center[1] + 30), (200, 200, 200), 2)
cv2.line(img, (center[0] + 10, bottom_center[1]), (center[0] + 10, bottom_center[1] + 40), (200, 200, 200), 2)

# Рисуем кнопки
button_center = (50 + i*100, 170)
button_color = (200, 200, 200)
cv2.rectangle(img, (30 + i*100, 150), (70 + i*100, 190), button_color, -1)
cv2.circle(img, button_center, 10, (0, 0, 0), -1)

# Добавляем подписи к светодиодам
cv2.putText(img, f'LED {i+1}', (30 + i*100, 189), cv2.FONT_HERSHEY_SIMPLEX, 0.4, (0, 0, 0), 1)

# Отображаем созданное изображение
cv2.imshow('LED Control', img)

# Функция для обработки событий мыши
def mouse_callback(event, x, y, flags, param):
global ledStates
# Проверяем нажатие левой кнопки мыши
if event == cv2.EVENT_LBUTTONDOWN:
# Перебираем координаты кнопок
for i in range(3):
if 30 + i*100 < x < 70 + i*100 and 150 < y < 190:
# Изменяем состояние светодиода и отправляем новое состояние в Arduino
ledStates ^= (1 << i)
ser.write(bytearray([ledStates]))
return

# Настройка окна OpenCV и привязка функции обработки событий мыши
cv2.namedWindow('LED Control')
cv2.setMouseCallback('LED Control', mouse_callback)

# Основной цикл программы
while True:
# Чтение данных с Arduino
if ser.in_waiting > 0:
ledStates = ord(ser.read(1)) # Обновляем состояния светодиодов
draw_interface() # Рисуем интерфейс
if cv2.waitKey(1) & 0xFF == ord('q'):
break

# Закрытие окна и освобождение ресурсов
cv2.destroyAllWindows()
ser.close()

Код Arduino:

// Определение пинов, к которым подключены светодиоды и кнопки
const int ledPins[] = { 5, 6, 7 }; // Пины светодиодов
const int buttonPins[] = { 2, 3, 4 }; // Пины кнопок
int ledStates = 0; // Состояния светодиодов хранятся в одном байте

// Настройка начальных параметров
void setup() {
Serial.begin(9600); // Инициализация порта
// Настройка пинов светодиодов на вывод и пинов кнопок на ввод с подтяжкой к питанию
for (int i = 0; i < 3; i++) {
pinMode(ledPins[i], OUTPUT);
pinMode(buttonPins[i], INPUT_PULLUP);
}
}

// Основной цикл выполнения
void loop() {
// Проверка на наличие данных в порту
if (Serial.available() > 0) {
ledStates =
Serial.read(); // Чтение полученных данных
// Перебор светодиодов и обновление их состояния
for (int i = 0; i < 3; i++) {
digitalWrite(ledPins[i], (ledStates >> i) & 1); // Установка состояния светодиода
}
}

// Перебор кнопок для обработки их состояний
for (int i = 0; i < 3; i++) {
static unsigned long lastDebounceTime[3] = { 0, 0, 0 }; // Таймеры для антидребезга
static bool lastButtonState[3] = { HIGH, HIGH, HIGH }; // Последние состояния кнопок

bool currentButtonState = !digitalRead(buttonPins[i]); // Чтение текущего состояния кнопки
// Проверка на изменение состояния кнопки и антидребезг
if (currentButtonState != lastButtonState[i] && (millis() - lastDebounceTime[i] > 50)) {
lastDebounceTime[i] = millis(); // Обновление таймера антидребезга
lastButtonState[i] = currentButtonState; // Обновление состояния кнопки

// Обработка нажатия кнопки
if (currentButtonState == HIGH) { // Если кнопка отпущена
ledStates ^= (1 << i); // Переключение бита состояния светодиода
digitalWrite(ledPins[i], (ledStates >> i) & 1); // Обновление состояния светодиода
// Отправка обновленных состояний светодиодов через порт
Serial.write(ledStates);
}
}
}
}

Это лишь один из способов создавать проекты, объединяющие Python, Arduino, электронику, разработку интерфейсов, работу с библиотеками для компьютерного зрения и много другое!

Удачи и успехов в освоении!