В первой статье серии про сервоприводы с названием "Сервоприводы: устройство и управление" мы рассказывали о том, для чего нужны сервоприводы, как они устроены, и как ими управлять с помощью ШИМ, приведены характеристики некоторых популярных сервоприводов. Мы написали программу на Python для Repka Pi, позволяющую выполнять необходимые операции над сервоприводами с удержанием угла, а также с сервоприводами постоянного вращения.

С помощью этой статьи (второй статьи цикла статей про управление сервоприводами), Вы научитесь пользоваться 16-канальным ШИМ-контроллером Robointellect Controller 001. Данный контроллер создан на базе микросхемы PCA9685 и предназначен для управления различными исполнительными устройствами, использующими ШИМ-модуляцию:

  • светодиоды;

  • сервоприводы;

  • шаговые приводы;

  • реле

Вы узнаете, что этим новым устройством можно работать через порт USB ноутбука, настольного компьютера или даже одноплатного компьютера, и вам не потребуются никакие дополнительные микроконтроллеры или наборы устройств. Так же рассмотрим подключение серво-контроллера к микрокомпьютеру Repka Pi для управления сервоприводами и светодиодом RGB.

В практической части о программном управлении рассмотрим библиотеку RI SDK для управления сервоприводами и светодиодами RGB, вызывая ее функции из программ, составленных на языке Python.

Контроллер Robointellect Controller 001 разработан и производится в России. Описание и даташит на устройство доступны тут.

Если Вам интересна тема управления сервоприводами, то все статьи данного цикла статей Вы можете найти здесь:

Содержание данной статьи:

Как устроен Robointellect Controller 001

Контроллер Robointellect Controller 001 представляет собой очень полезное устройство при создании робототехнических систем и систем автоматизации (рис. 1.1).

Рис. 1.1. Контроллер Robointellect Controller 001.
Рис. 1.1. Контроллер Robointellect Controller 001.

Плата контроллера Robointellect Controller 001, разработана и производится проектом РобоИнтеллект, что, собственно, очевидно из надписи, логотипа и названия контроллера (видимо изначально разработали для управления учебными моделями роботов этого проекта и из-за удачности этого контроллера вообще для всякой робототехники с сервоприводами его предлагают теперь и отдельно) и по сути объединяет сразу четыре устройства:

  • 16-канальный 12-битный контроллер PWM на базе микросхемы PCA9685;

  • SB-I2C конвертер (USB на I2C) на базе микросхемы CH341T;

  • RGB-светодиод (подключен на контроллере к 16-ому ШИМ порту и может включаться и отключаться отдельной перемычкой);

  • I2C Hub для подключения дополнительных устройств I2C.

Схемы подключения, коммутации, полярности и т.д. - представлены на рис. 1.2.

Рис. 1.2. Контроллер Robointellect Controller 001 - распиновка портов и полярность подключения.
Рис. 1.2. Контроллер Robointellect Controller 001 - распиновка портов и полярность подключения.

Расскажем о возможностях этих устройств в составе Robointellect Controller 001.

Контроллер PWM

На плате Robointellect Controller 001 имеется контроллер ШИМ, созданный на базе микросхемы PCA9685. Если поискать контроллеры на Алиэкспресс по этому названию, то  в результатах поиска вам попадется контроллер, показанный на рис. 2 или его разношёрстные аналоги.

Рис. 2. Контроллер ШИМ на базе PCA9685.
Рис. 2. Контроллер ШИМ на базе PCA9685.

Как видите, здесь есть 16 портов для подключения сервоприводов, отдельные контакты для электропитания, а также контакты для подключения интерфейса I2C.

По сравнению с подобными контроллерами в Robointellect Controller 001 есть такие дополнения:

  • около каждого порта сервопривода находится светодиод, по свечению которого можно судить о наличии управляющих импульсов. Эти светодиоды помогают при отладке программного обеспечения (ПО), это оказалось крайне удобно и наглядно и даже может превращать контроллер сам по себе в удобство для обучения программированию без дополнительного подключения индикаторов или приборов;

  • есть контакты (пины) для подключения датчика тока, такого как INA219. Этот датчик позволяет контролировать потребление тока сервоприводами, это тоже очень удобно для компактной реализации всяких роботов и устройств с обратной связью или просто с обработкой данных по потребляемым току и напряжению;

  • в комплекте с Robointellect Controller 001 можно заказать сетевой блок для питания сервоприводов, который можно включить в специально предназначенный для этого разъем, который оказывается куда удобнее и надёжнее и, что важно, может выдерживать большие токи. Также имеется разъем для самостоятельного подключения питания с помощью проводов с винтовыми зажимами;

  • заявлены максимальные и рабочие токи в несколько раз превышающие токи на других подобных контроллерах, что косвенно можно подтвердить качеством платы, её чуть большим размером и толщиной текстолита - это важное преимущество для тех, кто собирается в своих проектах применять более мощные сервоприводы;

  • и вот возможно одна из главных фич этого контроллера - для Robointellect Controller 001 создана и свободно доступна специальная библиотека RI SDK, с помощью которой можно управлять оборудованием, подключенным к этому контроллеру, из программ на языках C, C++, Golang, PHP и Python, в т.ч. большое количество высокоуровневых методов для работы с сервоприводами, rgb-светодиодами, датчиками тока и т.п., что позволяет программировать логику работы без глубокого погружения в особенности работы протокола I2C, управления ШИМ сигналами и принципов управления устройствами и их характеристик - это делаем возможным быстрое начало работы с робототехникой даже при минимальных знаниях в данной области и позволяет сосредоточить внимание на изучении программирования на примере управления различными устройствами и роботами.

Одним словом видно, что в части работы с ШИМ (PWM) разработчики хорошо заморочились, видимо учтя свой опыт работы с подобными контроллерами, и представили реально удобный для различных проектов с сервоприводами вариант устройства. Лично мне было даже просто приятно с ним поработать.

Кроме этого, сам контроллер Robointellect Controller 001 может управляться через I2C, при этом с помощь набора переключателей можно устанавливать адреса контроллеров на шине I2C. И это даже не "джамперы" и тем более не контакты, которые нужно соединять и разъединять паяльником - это просто переключатель на котором достаточно просто пальцами задать положение соответствующие нужному нам адресу I2C шины - и вот это уже пример настоящей заботы о пользователях и это делает контроллер особенно подходящим для обучения, когда учащиеся получают простой, быстрый и удобный доступ ко всем функциям важным и нужным без лишних телодвижений.

Но двинемся дальше в рассмотрении того, какие ещё устройства агрегирует в себе данный контроллер.

Конвертер SB-I2C на базе микросхемы CH341T

В то время как управление контроллером, показанным на рис. 2, возможно только с помощью интерфейса I2C, контроллер Robointellect Controller 001 дает вам возможность подключения к управляющему компьютеру через USB. Причем это может быть компьютер под управлением Microsoft Windows или Linux, например, микрокомпьютер Repka Pi.

Управление через USB выполняет микросхема конвертера (моста) SB-I2C, созданная на базе микросхемы CH341T.

Не исключено что вы уже использовали подобные конвертеры, они есть в продаже на Алиэкспресс и не только (рис. 3).

Рис. 3. Конвертер USB для I2C и UART.
Рис. 3. Конвертер USB для I2C и UART.

Наличие подобного конвертера в составе контроллера Robointellect Controller 001 упрощает работу с сервоприводами и другими устройствами - можно разрабатывать ПО для управления сервоприводами с помощью обычного ноутбука или настольного компьютера, просто подключая контроллер по USB к своему компьютеру. И тут сразу опять хочется упомянуть доступную библиотеку RI SDK - так как она позволяет сразу же после подключения и установки начать программировать и управлять сервоприводами без затрат кучи времени на изучение низкоуровневого сопряжения и т.п.

Модуль RGB-светодиода

На плате контроллера Robointellect Controller 001 установлен RGB-светодиод, для управления которым используются порты ШИМ с номерами 14, 15 и 16, если считать все порты от 1 до 16.

Светодиод можно использовать и для отладки, а также для индикации состояния каких-либо процессов при работе вашего робота или другого устройства - например если у Вас робот управляется 5-10 сервоприводами и Вы хотите сделать так, чтобы когда он двигается, то моргал красный, а когда робот ждёт команд на исполнение, то загорался зелёный свет или что-то подобное - то это очень удобно.

I2C Hub для подключения дополнительных устройств I2C

Контроллер Robointellect Controller 001 может быть использован как I2C Hub (расширитель I2C) для подключения дополнительных устройств, в том числе и дополнительных контроллеров Robointellect Controller 001.

Последнее обстоятельство дает возможность управления сотнями сервоприводов при каскадировании контроллеров Robointellect Controller 001, так как шина I2C позволяет последовательно подключать порядка сотни устройств с разными адресами шин, даже если считать шины, зарезервированные для служебного использования по стандарту I2C.

Т.е. как уже и отмечалось - разработчики сильно постарались сделать реально удобное во всех смыслах устройство для робототехники.

Как мы видим, объединяя всё вышесказанное - контроллер очень удобен и компактно объединяет много функций и обладает расширенными сервисными возможностями - что оказывается в сумме и по стоимости и по количеству затрачиваемых усилий и по удобству для коммутации и по занимаемому месту для подключения - очень выгодным и, вместе с этим, эстетичным вариантом.

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


Сборка макета

Для подключения контроллера Robointellect Controller 001 к порту USB компьютера вам потребуется кабель USB типа «папа-папа» (рис. 14).

Рис. 14. Кабель USB типа «папа-папа».
Рис. 14. Кабель USB типа «папа-папа».

Соедините таким кабелем порт USB контроллера с одним из портов вашего ноутбука или настольного компьютера.

Подключите сервоприводы к соответствующим портам контроллера. Обратите внимание, что контакты портов раскрашены в черный, красный и желтый цвета.

Вам нужно подключить сервоприводы аналогично тому, как это показано на рис. 15.

Рис. 15. Подключение сервоприводов к контроллеру Robointellect Controller 001.
Рис. 15. Подключение сервоприводов к контроллеру Robointellect Controller 001.

Здесь коричневый провод подключается к черному контакту, это земля. Для сервопривода постоянного вращения DS4-NFC провод земли черный.

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

Обратите внимание, что управляющий провод сервопривода постоянного DS4-NFC вращения белый.

Также подключите блок питания, который идет в комплекте с Robointellect Controller 001, к соответствующему разъему на плате контроллера.

Установка RoboIntellect SDK для управления сервоприводами

Для управления устройствами, подключенными к Robointellect Controller 001, доступна библиотека RI SDK. В этом разделе мы установим ее на компьютер, работающий под управлением ОС Microsoft Windows.

Инсталляторы RI SDK для Windows и для Linux доступны на странице библиотеки в разделе "Скачать Robointellect SDK", там же есть подробное описание API с примерами кода. Важно так же отметить, что биндинг для библиотеки возможен на C, C++, Go, Python и даже PHP.

Сначала я устанавливал библиотеку в "винде" и потому далее подробно опишу именно этот процесс. В конце этой статьи есть так же описание установки библиотеки на одноплатном компьютере с описанием установки под Linux, вот быстрая ссылка на этот раздел данной статьи.

Но в принципе вы можете пропустить эту часть статьи и сразу перейти далее к части статьи с примерами управления исполнительными устройствами (светодиодами и сервоприводами), так как в документации к библиотеке есть подробное описание установки, настройки и начала работы с библиотекой - доступно тут.

Установка RISDK для Windows

Скачав нужный дистрибутив этой страницы (RI SDK для Windows, файл robointellect_sdk_windows_amd64.exe - номер версии в имени файла может отличаться), запускаем его с правами администратора. Для этого щелкните имя файла в папке правой клавишей мыши и выберите в контекстном меню строку Запуск от имени администратора.

Если после запуска появится предупреждение, показанное на рис. 4, щелкните кнопку Подробнее.

Рис. 4. Предупреждение о запуске неопознанного приложения.
Рис. 4. Предупреждение о запуске неопознанного приложения.

Вы увидите информацию о запуске программы неизвестного издателя (рис. 5).

Рис. 5. Информация о запускаемой программе.
Рис. 5. Информация о запускаемой программе.

Щелкните в этом окне кнопку Выполнить в любом случае, а затем согласитесь с внесением изменений на вашем устройстве от имени неизвестного приложения, щелкнув кнопку Да.

Вы увидите первое окно мастера установки RoboIntellect (рис. 6).

Рис. 6. Мастер установки RoboIntellect.
Рис. 6. Мастер установки RoboIntellect.

Продолжите работу мастера при помощи кнопки Далее. Вам будет предложено согласиться с условиями лицензионного соглашения (рис. 7).

Рис. 7. Просмотр условий лицензионного соглашения.
Рис. 7. Просмотр условий лицензионного соглашения.

Установите отметку для флажка Я принимаю условия соглашения и затем щелкните кнопку Далее.

Вам будет предложено выбрать папку для установки (рис. 8).

Рис. 8. Выбор папки для установки RoboIntellect.
Рис. 8. Выбор папки для установки RoboIntellect.

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

На следующем этапе нужно выбрать компоненты для установки (рис. 9).

Рис. 9. Выбор компонентов для установки
Рис. 9. Выбор компонентов для установки

Согласитесь с предложенным выбором, а затем снова щелкните кнопку Далее.

Теперь все готово для установки (рис. 10).

Рис. 10. Подготовка к установке завершена.
Рис. 10. Подготовка к установке завершена.

Чтобы запустить установку, щелкните кнопку Установить и наблюдайте за ходом установки (рис. 11).

Рис. 11. Ход процесса установки RoboIntellect SDK.
Рис. 11. Ход процесса установки RoboIntellect SDK.

После завершения установки вам будет предложено открыть руководство пользователя (рис. 12).

Рис. 12. Установка RoboIntellect SDK завершена.
Рис. 12. Установка RoboIntellect SDK завершена.

Установка пульта управления роботом-манипулятором

Есть второй способ установки библиотеки RI SDK - она также поставляется в составе ПО пульта управления роботом-манипулятором RoboIntellect - и это любопытное программное обеспечение, которое так же можно использовать для управления сервоприводами но уже в ручном режиме через Web-интерфейс и приложение и в т.ч. подключая джойстик - это удобно для проверки работы сервоприводов. Для установки этого ПО нужно открыть страницу и загрузить с нее Пульт управления роботом-манипулятором для Windows.

В этом случае в составе ПО так же будет установлена библиотека RI SDK, а вот установить интерпретатор питона и настроить пути к нужным файлам библиотеки при её использовании в своём проекте придётся самостоятельно.

Настройка путей к файлам библиотеки

Для этого нужно или вручную скопировать файлы из каталога C:\Program Files (x86)\RoboIntellect\ri_sdk\ri_sdk_x64\drivers\x64\ в каталог C:\Windows\System32:

  • CH341DLLA64.dll

  • librisdk.dll

  • libusb-1.0.dll

  • SLABHIDDevice.dll

  • SLABHIDtoSMBus.dll

  • vcruntime140.dll

для копирования нужны права администратора или чтобы не копировать эти файлы, можно добавить путь к каталогу C:\Program Files (x86)\RoboIntellect\ri_sdk\ri_sdk_x64\drivers\x64\ в переменную среды "PATH".

Чтобы это сделать в ОС Microsoft Windows 11, откройте окно Параметры. Далее выберите О системе и Дополнительные параметры системы.

На вкладке Дополнительно щелкните кнопку Переменные среды. В разделе Переменные среды для пользователя найдите переменную PATH и выделите её.

Затем щелкните кнопку Изменить. В открывшемся окне нажмите кнопку Новый и добавьте путь к каталогу C:\Program Files (x86)\RoboIntellect\ri_sdk\ri_sdk_x64\drivers\x64.

И, наконец, щелкните кнопку ОК во всех открытых окнах, чтобы сохранить изменения.

Установка Python

Установите Python 3.11 или более новой версии с помощью приложения Microsoft Store (рис. 13), если он еще не установлен.

Рис. 13. Установка Python.
Рис. 13. Установка Python.

Далее откройте командное приглашение Microsoft Windows и введите там команду python -V:

C:\Users\1>python -V
Python 3.11.4

На консоль будет выведена версия Python.


Коротко о библиотеке RI SDK

Библиотека RI SDK была создана для управления роботом-манипулятором, а также всеми устройствами, подключенными к контроллеру Robointellect Controller 001.

Эта весьма обширная библиотека работает на платформах Microsoft Windows и Linux. Она насчитывает десятки функций, предназначенных для работы с сервоприводами, RGB-светодиодами, адаптером I2C, а также цифровым датчиком тока, напряжения и мощности INA219.

В документации к библиотеке приведены многочисленные примеры программ для следующих языков программирования:

  • Python

  • C

  • C++

  • Golang

  • Фреймворк Golang gRPC

  • PHP

В этой статье мы сосредоточимся на использовании RI SDK в программах Python.


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

Перед тем как мы с Вами попробуем управлять сервоприводами давайте начнем с самого простого — научимся управлять светодиодом RGB, установленным на плате контроллера Robointellect Controller 001, из программы, написанной на языке Python.

Управление светодиодом с помощью функций RI SDK

Пример программы risdk_led_sample.py, включающей RGB светодиод на 10 секунд, приведен в листинге 1. Вы также можете найти этот пример в описании функции RI_SDK_exec_RGB_LED_SinglePulse.

Листинг 1. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_led_sample.py

import sys
from ctypes import *

# Подключаем внешнюю библиотеку для работы с SDK
lib = cdll.LoadLibrary("C:\Windows\system32\librisdk.dll")

# Указываем типы аргументов для функций библиотеки RI_SDK
lib.RI_SDK_InitSDK.argtypes = [c_int, c_char_p]
lib.RI_SDK_CreateModelComponent.argtypes = [c_char_p, c_char_p, c_char_p, POINTER(c_int), c_char_p]
lib.RI_SDK_LinkPWMToController.argtypes = [c_int, c_int, c_uint8, c_char_p]
lib.RI_SDK_LinkLedToController.argtypes = [c_int, c_int, c_int, c_int, c_int, c_char_p]
lib.RI_SDK_DestroySDK.argtypes = [c_bool, c_char_p]
lib.RI_SDK_exec_RGB_LED_SinglePulse.argtypes = [c_int, c_int, c_int, c_int, c_int, c_bool, c_char_p]

def main():
    errTextC = create_string_buffer(1000)  # Текст ошибки. C type: char*
    i2c = c_int()
    pwm = c_int()
    led = c_int()

    # Инициализация библиотеки RI SDK с уровнем логирования 3
    errCode = lib.RI_SDK_InitSDK(3, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)

    # Создание компонента i2c адаптера модели ch341
    errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "i2c_adapter".encode(), "ch341".encode(), i2c, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)

    print("i2c: ", i2c.value)

    # Создание компонента ШИМ модели pca9685
    errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "pwm".encode(), "pca9685".encode(), pwm, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)

    print("pwm: ", pwm.value)

     # Создание компонента светодиода модели ky016
    errCode = lib.RI_SDK_CreateModelComponent("executor".encode(), "led".encode(), "ky016".encode(), led, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)

    print("led: ", led.value)

    # Связывание i2c с ШИМ
    errCode = lib.RI_SDK_LinkPWMToController(pwm, i2c, 0x40, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)
    
    # Связывание ШИМ со светодиодом
    errCode = lib.RI_SDK_LinkLedToController(led, pwm, 15, 14, 13, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)

    # Непрерывное свечение светодиода красным цветом в течение 10 секунд.
    # Яркость красного цвета - 150. Яркость остальных цветов - 0.
    errCode = lib.RI_SDK_exec_RGB_LED_SinglePulse(led, 150, 0, 0, 10000, False, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2) 

    # Удаление библиотеки со всеми компонентами
    errCode = lib.RI_SDK_DestroySDK(True, errTextC)
    if errCode != 0:
        print(errCode, errTextC.raw.decode())
        sys.exit(2)

    print("Success")

main()

Прежде всего необходимо подключить файл библиотеки librisdk.dll с помощью функции cdll.LoadLibrary:

lib = cdll.LoadLibrary("C:\Windows\system32\librisdk.dll")

Здесь предполагается, что в процессе установки пульта управления роботом-манипулятором, описанным ранее в этой главе, были скопированы в системный каталог  C:\Windows\system32\ все необходимые файлы DLL библиотек.

После загрузки библиотеки с помощью функции RI_SDK_CreateModelComponent программа создает компонент i2c для адаптера ch341, установленного на плате контроллера Robointellect Controller 001:

i2c = c_int()
…
errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "i2c_adapter".encode(), "ch341".encode(), i2c, errTextC)
if errCode != 0:
    print(errCode, errTextC.raw.decode())
    sys.exit(2)

print("i2c: ", i2c.value)

На следующем шаге наша программа создает компонент ШИМ модели pca9685, вызывая ту же функцию RI_SDK_CreateModelComponent:

pwm = c_int()
…
errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "pwm".encode(), "pca9685".encode(), pwm, errTextC)

Также требуется создать компонент для GRB светодиода модели ky016, установленного на плате контроллера:

led = c_int()
…
errCode = lib.RI_SDK_CreateModelComponent("executor".encode(), "led".encode(), "ky016".encode(), led, errTextC)

После создания компонент для i2c, ШИМ и светодиода нужно связать i2c и ШИМ, а ШИМ — со светодиодом:

errCode = lib.RI_SDK_LinkPWMToController(pwm, i2c, 0x40, errTextC)
…
errCode = lib.RI_SDK_LinkLedToController(led, pwm, 15, 14, 13, errTextC)

Для привязки i2c к ШИМ используется функция RI_SDK_LinkPWMToController. Обратите внимание, что в качестве третьего параметра передается адрес ШИМ контроллера на шине i2c, равный 40.

Привязка светодиода выполняется функцией RI_SDK_LinkLedToController. Через третий, четвертый и пятый параметры этой функции нужно передать номера каналов ШИМ на плате контроллера Robointellect Controller 001, к которым подключены, соответственно, красный (R), зеленый (G) и голубой (B) контакт RGB светодиода, соответственно. По умолчанию это каналы 15, 14 и 13.

Обращаем ваше внимание, что на плате Robointellect Controller 001 имеется перемычка ON/OFF RGB LED. Если ее удалить, перечисленные выше каналы будут отключены от встроенного светодиода, и их можно будет использовать для подключения других устройств.

Теперь программа готова для работы со светодиодом. В примере программы с помощью функции RI_SDK_exec_RGB_LED_SinglePulse задается непрерывное свечение светодиода красным цветом в течение 10 секунд:

errCode = lib.RI_SDK_exec_RGB_LED_SinglePulse(led, 150, 0, 0, 10000, False, errTextC)

Второй, третий и четвертый параметры задают яркости для красного, зеленого и голубого цвета в диапазоне от 0 до 255. В приведенном примере горит только красный свет с яркостью 155.

Пятый параметр задает длительность свечения в мс. В данном случае светодиод будет гореть 10 секунд.

Шестой параметр позволяет запустить выполнение команды в асинхронном режиме. При этом функция сразу вернет управление.

Перед завершением программы нужно удалить библиотеку со всеми компонентами. Это действие выполняется функцией RI_SDK_DestroySDK:

errCode = lib.RI_SDK_DestroySDK(True, errTextC)

Программа для управления светодиодом RGB в синхронном режиме

Программа risk_led_demo.py, представленная в листинге 2, демонстрирует все функции управления RGB светодиодом, реализованные в RISDK, и работающие в синхронном режиме.

Листинг 2. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_led_demo.py

import sys
from ctypes import *
import platform
import traceback

def err_msg(errTextC):
    return(errTextC.raw.decode())

def init(i2c, pwm):
    platform_os = platform.system()
    try:
        if platform_os == "Windows":
            lib = cdll.LoadLibrary("C:\Windows\system32\librisdk.dll")
        if platform_os == "Linux":
            lib = cdll.LoadLibrary("/usr/local/robohand_remote_control/librisdk.so")
    except OSError as e:
        raise Exception("Failed to load: " + str(e))

    lib.RI_SDK_InitSDK.argtypes = [c_int, c_char_p]
    lib.RI_SDK_CreateModelComponent.argtypes = [c_char_p, c_char_p, c_char_p, POINTER(c_int), c_char_p]
    lib.RI_SDK_LinkPWMToController.argtypes = [c_int, c_int, c_uint8, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_InitSDK(3, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_InitSDK failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "i2c_adapter".encode(), "ch341".encode(), i2c, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "pwm".encode(), "pca9685".encode(), pwm, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_LinkPWMToController(pwm, i2c, 0x40, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_LinkPWMToController failed with error code {errCode}: {err_msg(errTextC)}")        

    return lib
    
def add_led(lib, led, pwm, r, g, b):
    lib.RI_SDK_CreateModelComponent.argtypes = [c_char_p, c_char_p, c_char_p, POINTER(c_int), c_char_p]
    lib.RI_SDK_LinkLedToController.argtypes = [c_int, c_int, c_int, c_int, c_int, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_CreateModelComponent("executor".encode(), "led".encode(), "ky016".encode(), led, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_LinkLedToController(led, pwm, 15, 14, 13, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_LinkLedToController failed with error code {errCode}: {err_msg(errTextC)}")        

def cleanup(lib):
    lib.RI_SDK_DestroySDK.argtypes = [c_bool, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_DestroySDK(True, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_DestroySDK failed with error code {errCode}: {err_msg(errTextC)}")        

def led_flicker(lib, led, r, g, b, duration, qty, async_mode):
    lib.RI_SDK_exec_RGB_LED_Flicker.argtypes = [c_int, c_int, c_int, c_int, c_int, c_int, c_bool, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_exec_RGB_LED_Flicker(led, r, g, b, duration, qty, async_mode, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_exec_RGB_LED_Flicker failed with error code {errCode}: {err_msg(errTextC)}")        

def led_pulse(lib, led, r, g, b, duration, async_mode):
    lib.RI_SDK_exec_RGB_LED_SinglePulse.argtypes = [c_int, c_int, c_int, c_int, c_int, c_bool, c_char_p]
    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_exec_RGB_LED_SinglePulse(led, r, g, b, duration, async_mode, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_exec_RGB_LED_SinglePulse failed with error code {errCode}: {err_msg(errTextC)}")        

def led_pulse_pause(lib, led, r, g, b, duration, pause, limit, async_mode):
    lib.RI_SDK_exec_RGB_LED_FlashingWithPause.argtypes = [c_int, c_int, c_int, c_int, c_int, c_int, c_int, c_bool, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_exec_RGB_LED_FlashingWithPause(led, r, g, b, duration, pause, limit, async_mode, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_exec_RGB_LED_FlashingWithPause failed with error code {errCode}: {err_msg(errTextC)}")        

def led_pulse_frequency(lib, led, r, g, b, frequency, limit, async_mode):
    lib.RI_SDK_exec_RGB_LED_FlashingWithFrequency.argtypes = [c_int, c_int, c_int, c_int, c_int, c_int, c_bool, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_exec_RGB_LED_FlashingWithFrequency(led, r, g, b, frequency, limit, async_mode, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_exec_RGB_LED_FlashingWithFrequency failed with error code {errCode}: {err_msg(errTextC)}")        

def led_cleanup(lib, led):
    lib.RI_SDK_DestroyComponent.argtypes = [c_int, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_DestroyComponent(led, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_DestroyComponent failed with error code {errCode}: {err_msg(errTextC)}")        


if __name__ == "__main__":
    try:
        i2c = c_int()
        pwm = c_int()
        led = c_int()
        
        lib = init(i2c, pwm)

#        add_led(lib, led, pwm, 15, 14, 13)
        add_led(lib, led, pwm, 14, 15, 13)

        print("Start pulse...")

        led_pulse(lib, led, 255, 0, 0, 1500, False)
        led_pulse(lib, led, 0, 255, 0, 1500, False)
        led_pulse(lib, led, 0, 0, 255, 1500, False)

        print("Start flicker...")

        led_flicker(lib, led, 255, 0, 0, 500, 5, False)
        led_flicker(lib, led, 0, 255, 0, 500, 5, False)
        led_flicker(lib, led, 0, 0, 255, 500, 5, False)

        print("Start pulse_pause...")

        led_pulse_pause(lib, led, 255, 0, 0, 1000, 200, 3, False)
        led_pulse_pause(lib, led, 0, 255, 0, 1000, 200, 3, False)
        led_pulse_pause(lib, led, 0, 0, 255, 1000, 200, 3, False)

        print("Start pulse_frequency...")

        led_pulse_frequency(lib, led, 255, 0, 0, 10, 10, False)
        led_pulse_frequency(lib, led, 0, 255, 0, 20, 10, False)
        led_pulse_frequency(lib, led, 0, 0, 255, 30, 10, False)

        led_cleanup(lib, led)
        cleanup(lib)
    except Exception as e:
        print(traceback.format_exc() + "===> ", str(e))
        sys.exit(2)

Инициализация

В самом начале этой программы определены переменные i2c и pwm, необходимые для инициализации контроллера Robointellect Controller 001, а также переменная led для работы со светодиодом:

i2c = c_int()
pwm = c_int()
led = c_int()
lib = init(i2c, pwm)

Инициализация выполняется функцией init, которой передаются переменные i2c и pwm:

def init(i2c, pwm):
    platform_os = platform.system()
    try:
        if platform_os == "Windows":
            lib = cdll.LoadLibrary("C:\Windows\system32\librisdk.dll")
        if platform_os == "Linux":
            lib = cdll.LoadLibrary("/usr/local/robohand_remote_control/librisdk.so")
    except OSError as e:
        raise Exception("Failed to load: " + str(e))

    lib.RI_SDK_InitSDK.argtypes = [c_int, c_char_p]
    lib.RI_SDK_CreateModelComponent.argtypes = [c_char_p, c_char_p, c_char_p, POINTER(c_int), c_char_p]
    lib.RI_SDK_LinkPWMToController.argtypes = [c_int, c_int, c_uint8, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_InitSDK(3, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_InitSDK failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "i2c_adapter".encode(), "ch341".encode(), i2c, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_CreateModelComponent("connector".encode(), "pwm".encode(), "pca9685".encode(), pwm, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")        

    errCode = lib.RI_SDK_LinkPWMToController(pwm, i2c, 0x40, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_LinkPWMToController failed with error code {errCode}: {err_msg(errTextC)}")        

    return lib

Для инициализации RISDK вызывается функция RI_SDK_InitSDK. В качестве первого параметра ей передается уровень глубины логирования (0 — только верхний уровень сообщений, 1, 2 и 3 — более подробная трассировка).

В качестве второго параметра функции необходимо передать ссылку на строку errTextC, определенную как буфер размером 1000 байт:

errTextC = create_string_buffer(1000)

Заметим, что такая строка используется для записи текста сообщения об ошибке во всех функциях RISDK.

На следующем этапе инициализации вызывается функция RI_SDK_CreateModelComponent, создающие компоненты для моста USB-I2C (микросхема ch341) и PWM-модуляция (микросхема pca9685).

В качестве первого параметра функции RI_SDK_CreateModelComponent нужно передать тип компонента — «executor», «connector» или «sensor». В нашем примере мы указываем тип «connector».

Второй параметр позволяет указать устройство компонента. Это могут быть такие устройства: «i2c», «pwm», «servodrive», «servodrive_rotate», «led» или, «voltage_sensor».

Третий параметр задает модель компонента. В библиотеке RISDK определены компоненты следующих типов:

  • контроллеры ch341, cp2112, pca9685;

  • сервоприводы mg90s, a0090, mg996, corona_ds929mg, corona_sb9039, corona_ds843mg, corona_ds238mg, mg996r;

  • светодиод ky016;

  • измеритель тока ina219

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

Наша функция init вызывает функцию RI_SDK_CreateModelComponent три раза, создавая компоненты для моста ch341, а также контроллеров ch341 и pca9685.

И, наконец, через последний параметр нужно передать ссылку на буфер для записи сообщения об ошибке.

Последний этап инициализации заключается в присоединении ШИМ-модулятора к адаптеру i2c с помощью функции RI_SDK_LinkPWMToController библиотеки RISDK.

Через первый и второй параметры этой функции нужно передать дескрипторы ШИМ и адаптера i2c, участвующие в присоединении.

Третий параметр задает адрес адаптера на шине i2c.

Последний параметр предназначен для получения текста ошибки (если она возникнет).

Добавление светодиода

Прежде чем программа сможет работать со светодиодом RGB, она должна добавить его к контроллеру, указав тип светодиода и номера каналов ШИМ на плате контроллера Robointellect Controller 001, к которому подключен светодиод.

Ниже приведен код функции add_led, выполняющий операцию добавления:

def add_led(lib, led, pwm, r, g, b):
    lib.RI_SDK_CreateModelComponent.argtypes = [c_char_p, c_char_p, c_char_p, POINTER(c_int), c_char_p]
    lib.RI_SDK_LinkLedToController.argtypes = [c_int, c_int, c_int, c_int, c_int, c_char_p]
errTextC = create_string_buffer(1000)
errCode = lib.RI_SDK_CreateModelComponent("executor".encode(), "led".encode(), "ky016".encode(), led, errTextC)
if errCode != 0:
    raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")        

errCode = lib.RI_SDK_LinkLedToController(led, pwm, r, g, b, errTextC)
if errCode != 0:
    raise Exception(f"RI_SDK_LinkLedToController failed with error code {errCode}: {err_msg(errTextC)}")</code></pre><p>Эта функция создает компонент светодиода функцией <a target="_blank" rel="noopener noreferrer nofollow" title="" href="https://docs.robointellect.ru/docs/risdk/api-basic/RI_SDK_CreateModelComponent">RI_SDK_CreateModelComponent</a>, а затем подключает этот компонент к контроллеру, вызывая <a target="_blank" rel="noopener noreferrer nofollow" title="" href="https://docs.robointellect.ru/docs/risdk/api-basic/RI_SDK_LinkLedToController">RI_SDK_LinkLedToController</a>.</p><p>Через первый и второй параметры функции передаются соответственно дескриптор светодиода и контроллера ШИМ, созданные на этапе инициализации.</p><p>Следующие параметры задают порты подключения на шине ШИМ контроллера красного, зеленого и синего светодиодов (установленных внутри светодиода RGB), соответственно.</p><p>Последний параметр нужен для передачи текста ошибки.</p><p>Наша программа добавляет светодиод следующим образом:</p><pre><code class="language-python"># add_led(lib, led, pwm, 15, 14, 13)

add_led(lib, led, pwm, 14, 15, 13)

Обратите внимание, что вместо последовательности (15,14, 13) здесь указано (14, 15, 13). В экземпляре контроллера, который был у автора этой статьи, в светодиоде RGB оказались перепутаны контакты. Поэтому пришлось при добавлении светодиода сделать коррекцию.

Операции со светодиодом

Когда светодиод добавлен, можно с ним работать. Сначала программа вызывает функцию led_pulse, включающую светодиод с помощью функции RI_SDK_exec_RGB_LED_SinglePulse библиотеки RISDK:

led_pulse(lib, led, 255, 0, 0, 1500, False)
led_pulse(lib, led, 0, 255, 0, 1500, False)
led_pulse(lib, led, 0, 0, 255, 1500, False)

Здесь программа последовательно зажигает светодиод красным, зеленым и голубым цветом на полторы секунды.

Код функции led_pulse приведен ниже:

def led_pulse(lib, led, r, g, b, duration, async_mode):
    lib.RI_SDK_exec_RGB_LED_SinglePulse.argtypes = [c_int, c_int, c_int, c_int, c_int, c_bool, c_char_p]
    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_exec_RGB_LED_SinglePulse(led, r, g, b, duration, async_mode, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_exec_RGB_LED_SinglePulse failed with error code {errCode}: {err_msg(errTextC)}")

Через первый параметр функции RI_SDK_exec_RGB_LED_SinglePulse передается дескриптор светодиода, полученный от функции add_led.

Второй, третий и четвертый параметр задают яркость для красного, зеленого и голубого каналов светодиода, соответственно. Можно указывать значения в диапазоне от 0 до 255.

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

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

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

После вызова led_pulse программа вызывает еще три функции:

led_flicker(lib, led, 255, 0, 0, 500, 5, False)
led_flicker(lib, led, 0, 255, 0, 500, 5, False)
led_flicker(lib, led, 0, 0, 255, 500, 5, False)

led_pulse_pause(lib, led, 255, 0, 0, 1000, 200, 3, False)
led_pulse_pause(lib, led, 0, 255, 0, 1000, 200, 3, False)
led_pulse_pause(lib, led, 0, 0, 255, 1000, 200, 3, False)

led_pulse_frequency(lib, led, 255, 0, 0, 10, 10, False)
led_pulse_frequency(lib, led, 0, 255, 0, 20, 10, False)
led_pulse_frequency(lib, led, 0, 0, 255, 30, 10, False)

Функция led_flicker реализована с помощью функции RI_SDK_exec_RGB_LED_Flicker и вызывает мерцание светодиода и постепенным изменением яркости от нулевой до значения, указанного третьим, четвертым и пятым параметрами.

Шестой параметр задает длительность мерцания в мс, а седьмой — количество мерцаний.

Таким образом, приведенный выше фрагмент кода с функцией led_flicker вызывает последовательное мерцание светодиода красным, зеленым и голубым цветом.

Функция led_pulse_pause реализована при помощи функции RI_SDK_exec_RGB_LED_FlashingWithPause.

Здесь светодиод мигает цветом, заданным при помощи третьего, четвертого и пятого параметров. Шестой параметр задает длительность одного импульса в мс, седьмой — паузу между импульсами в мс, а восьмой — количество миганий.

Если нужно мигать светодиодом с заданной частотой, используйте функцию led_pulse_frequency, реализованную с помощью RI_SDK_exec_RGB_LED_FlashingWithFrequency.

Третий, четвертый и пятый параметры задают цвет мигания, шестой — частоту мигания в Гц, а седьмой — количество миганий.

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

Освобождение ресурсов

Перед завершением работы программа должна освободить ресурсы, занятые светодиодом и библиотекой RISDK. Для этого она вызывает функции  led_cleanup и cleanup:

led_cleanup(lib, led)
cleanup(lib

Функция led_cleanup вызывает функцию RI_SDK_DestroyComponent библиотеки RISDK. Она удаляет компонент, созданный для светодиода RGB.

Что касается функции cleanup, то она вызывает функции RI_SDK_sigmod_PWM_ResetAll, RI_SDK_DestroyComponent и RI_SDK_DestroySDK.

Функция RI_SDK_sigmod_PWM_ResetAll устанавливает скважность, равную нулю на всех портах  ШИМ модулятора. С помощью функции RI_SDK_DestroyComponent удаляется компонент i2c. И, наконец, функция RI_SDK_DestroySDK освобождает память, выделенную RISDK.


Асинхронное управление светодиодом

Библиотека RISDK может работать в асинхронном режиме. При этом асинхронные функции возвращают управление в вызывающую программу немедленно, а вызванный ими процесс будет продолжен до завершения или отмены.

В листинге 3 мы привели в сокращенном виде пример программы risdk_led_demo_async.py асинхронного управления светодиодом RGB.

Листинг 3. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_led_demo_async.py

…
if __name__ == "__main__":
    try:
        i2c = c_int()
        pwm = c_int()
        led = c_int()
        
        lib = init(i2c, pwm)

#        add_led(lib, led, pwm, 15, 14, 13)
        add_led(lib, led, pwm, 14, 15, 13)
        print_led_state(lib, led)


        print("Start pulse...")

        led_pulse(lib, led, 255, 0, 0, 1500, True)
        time.sleep(0.3) 
        print_led_state(lib, led)
        time.sleep(2) 
        led_pulse(lib, led, 0, 255, 0, 1500, True)
        time.sleep(0.3) 
        print_led_state(lib, led)
        time.sleep(2) 
        led_pulse(lib, led, 0, 0, 255, 1500, True)
        time.sleep(0.3) 
        print_led_state(lib, led)
        time.sleep(2) 

        print("Start flicker...")

        led_flicker(lib, led, 255, 0, 0, 500, 5, True)
        time.sleep(2) 
        led_flicker(lib, led, 0, 255, 0, 500, 5, True)
        time.sleep(2) 
        led_flicker(lib, led, 0, 0, 255, 500, 5, True)
        time.sleep(2) 

        print("Start pulse_pause...")

        led_pulse_pause(lib, led, 255, 0, 0, 1000, 200, 3, True)
        time.sleep(2) 
        led_pulse_pause(lib, led, 0, 255, 0, 1000, 200, 3, True)
        time.sleep(2) 
        led_pulse_pause(lib, led, 0, 0, 255, 1000, 200, 3, True)
        time.sleep(2) 

        print("Start pulse_frequency...")

        led_pulse_frequency(lib, led, 255, 0, 0, 10, 10, True)
        time.sleep(2) 
        led_pulse_frequency(lib, led, 0, 255, 0, 10, 10, True)
        time.sleep(2) 
        led_pulse_frequency(lib, led, 0, 0, 255, 10, 10, True)
        time.sleep(2) 

        led_stop(lib)
        led_cleanup(lib, led)
        cleanup(lib)
    except Exception as e:
        print(traceback.format_exc() + "===> ", str(e))
        sys.exit(2)

Обратите внимание, что в качестве последнего параметра мы передаем функциям, управляющим светодиодом, значение True, а не False, как это было в предыдущем примере (листинг 2). В результате все функции RISDK вызываются в асинхронном режиме.

После вызова метода led_pulse добавлены две задержки. Первая вызывается перед функцией print_led_state, которая выводит на консоль состояние светодиода, а вторая — после вывода этого состояния.

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

Для получения состояния и текущих компонентов цвета светодиода RGB здесь вызывается функция led_get_state, основанная на вызове функции RI_SDK_exec_RGB_LED_GetState библиотеки RISDK.

Функция RI_SDK_exec_RGB_LED_GetState записывает в переменную, адрес которой указан во втором параметре, код состояния для светодиода, дескриптор которого указан в первом параметре.

Возможны такие значения состояния:

  • 0 — компонент ожидает вызова действий, светодиод не выполняет никакую команду;

  • 1 — компонент выполняет действие;

  • 2 — простое свечение, светодиод выполняет команду простого свечения;

  • 3 — мигание, светодиод выполняет одну из 2-х команд мигания;

  • 4 — мерцание, светодиод выполняет команду мерцания

Текущий цвет светодиода возвращается функцией led_get_color, которая, в свою очередь, вызывает функцию RI_SDK_exec_RGB_LED_GetColor библиотеки RISDK.

Также обратите внимание на функцию led_stop, полезную в асинхронном режиме  и останавливающую выполнение текущей операции для светодиода. Она вызывает функцию RI_SDK_exec_RGB_LED_Stop.


Управление сервоприводами с удержанием угла

Для управления сервоприводами с удержанием угла был создана программа risdk_servo_mg90s.py. Исходный текст этой программы приведен в листинге 4 (в сокращенном виде).

Листинг 4. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_servo_mg90s.py

…
i2c = c_int()
pwm = c_int()
mg90s = c_int()

lib = init(i2c, pwm)

servo_add(lib, pwm, mg90s, "mg90s", 0)
print_servo_state(lib, mg90s)

print("\nMG90S поворот в крайние положения")

servo_rotate(lib, mg90s, 0, 200, False)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))
print_servo_state(lib, mg90s)

servo_rotate(lib, mg90s, 1, 200, False)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_set_middle(lib, mg90s)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))


print("\nMG90S управление через длительность импульсов")

servo_turn_by_pulse(lib, mg90s, 2650)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_turn_by_pulse(lib, mg90s, 365)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))
        
servo_turn_by_pulse(lib, mg90s,1500)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))
        

print("\nMG90S Минимальный шаг")

servo_set_middle(lib, mg90s)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_rotate_min_step(lib, mg90s, 1, 100, False)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_rotate_min_step(lib, mg90s, 0, 100, False)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

print("\nMG90S управление через Duty")

servo_turn_by_duty(lib, mg90s, 75)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_turn_by_duty(lib, mg90s, 300)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_turn_by_duty(lib, mg90s, 540)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))
     
print("\nMG90S поворот на заданный угол")

servo_set_middle(lib, mg90s)
time.sleep(2) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_turn_by_angle(lib, mg90s, 90, 200, False)
time.sleep(1) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

servo_turn_by_angle(lib, mg90s, -90, 300, False)
time.sleep(1) 
print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

cleanup_servo(lib, mg90s)
cleanup(lib)
…

Инициализация

На этапе инициализации с помощью функции init программа risdk_servo_mg90s.py создает переменные i2c, pwm и mg90s:

i2c = c_int()
pwm = c_int()
mg90s = c_int()
lib = init(i2c, pwm)

Работа этой функции уже была описана ранее в предыдущем разделе (листинге 2).

Добавление сервопривода

Для работы с сервоприводом его нужно добавить функцией servo_add:

servo_add(lib, pwm, mg90s, "mg90s", 0)

В качестве третьего параметра функции servo_add нужно передать переменную для сохранения дескриптора сервопривода, в качестве четвертого — имя модели сервопривода, а в качестве пятого — номер канала ШИМ контроллера Robointellect Controller 001, к которому подключен сервопривод.

Функция servo_add вначале вызывает функцию RI_SDK_CreateModelComponent для создания компонента уровня устройства, а затем — функцию RI_SDK_LinkServodriveToController для привязки контроллера к ШИМ модулятору:

def servo_add(lib, pwm, servo, servo_type, channel):
    lib.RI_SDK_CreateModelComponent.argtypes = [c_char_p, c_char_p, c_char_p, POINTER(c_int), c_char_p]
    lib.RI_SDK_LinkPWMToController.argtypes = [c_int, c_int, c_uint8, c_char_p]

    errTextC = create_string_buffer(1000)
    errCode = lib.RI_SDK_CreateModelComponent("executor".encode(), "servodrive".encode(), servo_type.encode(), servo, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_CreateModelComponent failed with error code {errCode}: {err_msg(errTextC)}")

    errCode = lib.RI_SDK_LinkServodriveToController(servo, pwm, channel, errTextC)
    if errCode != 0:
        raise Exception(f"RI_SDK_LinkServodriveToController failed with error code {errCode}: {err_msg(errTextC)}")

Определение текущего состояния сервопривода

После добавления сервопривода программа вызывает функцию print_servo_state чтобы вывести на консоль состояние сервопривода:

print_servo_state(lib, mg90s)

Функция print_servo_state определена так:

def print_servo_state(lib, servo):
    print(f"Servo state: : {str(servo_get_state(lib, servo).value)}")

Она получает состояние с помощью функции servo_get_state. Эта функция, в свою очередь, вызывает функцию RI_SDK_exec_ServoDrive_GetState.

Функция  RI_SDK_exec_ServoDrive_GetState может вернуть одно из двух значений:

  • 0 — сервопривод ожидает вызова действий, ничего не делает и готов к работе;

  • 1 — компонент выполняет действие. Сервопривод в данный момент осуществляет движение

Таким образом, если функция servo_get_state возвращает нулевое значение, сервопривод находится в состоянии ожидания, если же значение единицы — вал сервопривода находится в движении.

Поворот в крайние положения

В программе risdk_servo_mg90s.py есть функция servo_rotate для поворота вала сервопривода в крайние положения, а также функция servo_set_middle для установки вала в среднее положение:

servo_rotate(lib, mg90s, 0, 200, False)
servo_rotate(lib, mg90s, 1, 200, False)
servo_set_middle(lib, mg90s)

Функции servo_rotate, реализованная с помощью функции RI_SDK_exec_ServoDrive_Rotate, в качестве первого параметра нужно передать направление вращения. Значение 0 соответствует вращению по часовой стрелке, а значение 1 — против часовой стрелки.

Второй параметр функции servo_rotate задает угловую скорость поворота (градусы в секунду).

Функция servo_set_middle  работает с помощью функции RI_SDK_exec_ServoDrive_SetPositionToMidWorkingRange. Она устанавливает вал сервопривода в среднее положение (в середину рабочего диапазона углов поворота).

Определение текущего угла поворота

При выполнении операций с сервоприводом программа risdk_servo_mg90s.py после выполнения тех или иных операций выводит на консоль текущий угол поворота вала сервопривода:

print("mg90s angle: " + str(servo_get_angle(lib, mg90s)))

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

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

Управление через длительность импульсов

Функция servo_turn_by_pulse позволяет управлять вращением вала сервопривода, задавая длительность управляющих импульсов в мс:

servo_turn_by_pulse(lib, mg90s, 2650)
servo_turn_by_pulse(lib, mg90s, 365)
servo_turn_by_pulse(lib, mg90s, 1500)

Длительность передается этим функциям в качестве последнего параметра.

Функция servo_turn_by_pulse вызывает функцию RI_SDK_exec_ServoDrive_TurnByPulse библиотеки RISDK. В качестве первого параметра этой функции передается дескриптор сервопривода, в качестве второго — необходимая длительность импульсов, а в качестве третьего — ссылка на буфер для записи строки сообщения об ошибке.

Управление через минимальный шаг

Если нужно перемещать вал сервопривода минимальными шагами, по одному градусу, пригодится функция servo_rotate_min_step, созданная на базе функции RI_SDK_exec_ServoDrive_MinStepRotate.

Фрагмент программы, приведенный ниже, вначале устанавливает вал сервопривода в среднее положение, а затем поворачивает его на один градус сначала по часовой стрелке, а затем — против часовой стрелке:

servo_set_middle(lib, mg90s)
servo_rotate_min_step(lib, mg90s, 1, 100, False)
servo_rotate_min_step(lib, mg90s, 0, 100, False)

Угловая скорость поворота задана как 100⁰ в секунду.

В качестве первого параметра функции RI_SDK_exec_ServoDrive_MinStepRotate нужно передать дескриптор сервопривода.

Второй и третий параметры задают, соответственно, направление и угловую скорость поворота (градусы в секунду).

Направление движения задается так:

  • 0 — по часовой стрелке;

  • 1 — против часовой стрелки

Управление через скважность импульсов Duty

Функция servo_turn_by_duty, реализованная с помощью функции библиотеки RISDK RI_SDK_exec_ServoDrive_TurnByDutyCycle, позволяет задавать угол поворота вала сервопривода через скважность управляющих импульсов:

servo_turn_by_duty(lib, mg90s, 75)
servo_turn_by_duty(lib, mg90s, 300)
servo_turn_by_duty(lib, mg90s, 540)

В качестве третьего параметра этой функции нужно передать количество шагов для ШИМ преобразователя.

Поворот на заданный угол

Функция servo_turn_by_angle позволяет поворачивать вал сервопривода на заданный угол по часовой стрелке или против часовой стрелке. Она вызывает функцию RI_SDK_exec_ServoDrive_Turn библиотеки RISDK.

Ниже приведен фрагмент программы, который вначале устанавливает вал сервопривода в среднее положение, а затем поворачивает его почасовой стрелке на 90⁰ и вслед за этим против часовой стрелки на -90⁰:

servo_set_middle(lib, mg90s)
servo_turn_by_angle(lib, mg90s, 90, 200, False)
servo_turn_by_angle(lib, mg90s, -90, 300, False)

Освобождение ресурсов

Перед завершением своей работы программа освобождает ресурсы, выделенные для сервопривода и для библиотеки RISDK:

cleanup_servo(lib, mg90s)
cleanup(lib)

Функция cleanup_servo вызывает функцию RI_SDK_DestroyComponent библиотеки RISDK для освобождения ресурсов, выделенных сервоприводу. Дескриптор сервопривода передается этой функции в качестве первого параметра.

Функция cleanup обращается к функции RI_SDK_DestroySDK, передавая ей в качестве первого параметра значение True для полного очищения реестра.


Подключение нестандартного сервопривода

Библиотека RISDK позволяет вам работать с нестандартными сервоприводами, модели, названия и параметры работы которых ещё не определены по умолчанию в этой библиотеке. По сути это кастомизация, когда можно использовать данную библиотеку для управления любым ШИМ управляемым сервоприводом в своём проекте.

В листинге 5 показана в сокращенном виде программа risdk_servo_sg90.py, которая демонстрирует такую поддержку для широко распространенного и недорогого сервопривода SG90.

Листинг 5. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_servo_sg90.py

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

Инициализация выполняется функцией add_custom_servo:

sg90 = add_custom_servo(lib, pwm, sg90, 2350, 365, 200, 180, 0)

Она задает параметры нашего сервопривода SG90 и вызывает функции RI_SDK_CreateDeviceComponent, RI_SDK_exec_ServoDrive_CustomDeviceInit и RI_SDK_LinkServodriveToController.

Параметры нестандартного сервопривода передаются функции RI_SDK_exec_ServoDrive_CustomDeviceInit.

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

В качестве второго и третьего параметров передаются максимальная и минимальная длительности управляющих импульсов в мс, соответственно.

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

Четвертый параметр задает максимально допустимую угловую скорость вращения сервопривода (градусы в секунду).

Пятый параметр определяет максимальный угол поворота вала сервопривода в градусах.

И, наконец, в шестом параметре необходимо указать номер канала ШИМ контроллера Robointellect Controller 001, к которому подключен сервопривод.

Ниже мы привели в сокращенном виде фрагмент программы, выполняющий повороты вала сервопривода в крайние положения, и управляющие сервоприводом через длительность импульсов:

servo_rotate(lib, sg90, 0, 200, False)
servo_rotate(lib, sg90, 1, 200, False)
servo_set_middle(lib, sg90)

servo_turn_by_pulse(lib, sg90, 2350)
servo_turn_by_pulse(lib, sg90, 365)
servo_turn_by_pulse(lib, sg90,1500)

Управление сервоприводами постоянного вращения

В библиотеке RISDK имеются функции, предназначенные для управления стандартными и нестандартными сервоприводами постоянного вращения.

Стандартные сервоприводы

В листинге 6 вы найдете в сокращенном виде код программы управления сервоприводом постоянного вращения risdk_servo_mg996r.py.

Листинг 6. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_servo_mg996r.py

…
i2c = c_int()
pwm = c_int()
mg996r = c_int()

lib = init(i2c, pwm)

rservo_add(lib, pwm, mg996r,"mg996r", 0)
print_rservo_state(lib, mg996r)

rservo_rotate_by_pulse(lib, mg996r, 1050, True)
print_rservo_state(lib, mg996r)
time.sleep(3) 

rservo_rotate_by_pulse(lib, mg996r, 2100, True)
print_rservo_state(lib, mg996r)
time.sleep(3) 

rservo_rotate_by_pulse(lib, mg996r, 1570, True)
print_rservo_state(lib, mg996r)
time.sleep(3) 

rservo_rotate_at_speed(lib, mg996r, 0, 50, True)
time.sleep(3) 

rservo_rotate_at_speed(lib, mg996r, 1, 100, True)
time.sleep(3) 

stop_rservo(lib, mg996r)
print_rservo_state(lib, mg996r)

cleanup_servo(lib, mg996r)
cleanup(lib)

После инициализации функцией init программа добавляет сервопривод постоянного вращения с помощью функции rservo_add . Она вызывает функции RI_SDK_CreateModelComponent и RI_SDK_LinkRServodriveToController.

Чтобы добавить сервопривод вращения типа mg996r программа вызывает функцию rservo_add:

rservo_add(lib, pwm, mg996r,"mg996r", 0)

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

В качестве четвертого параметра функции передается тип сервопривода, а в качестве пятого — номер порта ШИМ контроллера Robointellect Controller 001, к которому подключен этот сервопривод.

Для управления скоростью и направлением вращения вала сервопривода используется функция rservo_rotate_by_pulse, вызывающий функцию RI_SDK_exec_RServoDrive_RotateByPulse библиотеки RISDK:

rservo_rotate_by_pulse(lib, mg996r, 1050, True)
rservo_rotate_by_pulse(lib, mg996r, 2100, True)
rservo_rotate_by_pulse(lib, mg996r, 1570, True)

В качестве третьего параметра методу передается длительность управляющего импульса в мс.

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

Чтобы включить асинхронные режим, программа risdk_servo_mg996r.py передает значение True функции rservo_rotate_by_pulse.

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

  • 0 — сервопривод ожидает вызова действий, ничего не делает и готов к работе;

  • 1 — компонент выполняет действие. Сервопривод в данный момент осуществляет движение

Функция rservo_rotate_at_speed, основанная на вызове функции библиотеки RISDK с именем RI_SDK_exec_RServoDrive_RotateWithRelativeSpeed, позволяет запустить вращение вала сервопривода в заданном направлении и с заданной скоростью:

rservo_rotate_at_speed(lib, mg996r, 0, 50, True)
rservo_rotate_at_speed(lib, mg996r, 1, 100, True)

Третий параметр функции rservo_rotate_at_speed задает направление вращения. Нулевое значение соответствует вращению по часовой стрелке, значение единицы — вращению против часовой стрелки.

Четвертый параметр задает скорость вращения в процентах от максимальной.

Для остановки вала сервопривода вызывается функция stop_rservo:

stop_rservo(lib, mg996r)

Она обращается к функции RI_SDK_exec_RServoDrive_Stop библиотеки RI SDK.

Синхронные функции для сервоприводов вращения

В листинге 7 приведен код программы risdk_servo_mg996r_sync.py (в сокращенном виде), управляющая сервоприводами вращения в синхронном режиме.

Листинг 7. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_servo_mg996r_sync.py

…
rservo_rotate_by_pulse_over_time(lib, mg996r, 1050, 2000, False)
rservo_rotate_by_pulse_over_time(lib, mg996r, 2100, 2000, False)
rservo_rotate_by_pulse_over_time(lib, mg996r, 1570, 2000, False)

rservo_rotate_at_speed_over_time(lib, mg996r, 0, 50, 2000, False)
rservo_rotate_at_speed_over_time(lib, mg996r, 1, 100, 2000, False)
…

Эта программа вызывает функции rservo_rotate_by_pulse_over_time и rservo_rotate_at_speed_over_time, аналогичные по назначению только что рассмотренным функциям rservo_rotate_by_pulse и rservo_rotate_at_speed.

Отличие в том, что они вызывают функции RI_SDK_exec_RServoDrive_RotateByPulseOverTime и RI_SDK_exec_RServoDrive_RotateWithRelativeSpeedOverTime. Обе эти функции позволяют указать длительность вращения в мс. Вал сервопривода будет остановлен после истечения указанного времени, что можно использовать в синхронных программах.

Нестандартные сервоприводы

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

Пример программы risdk_servo_ds04-nfs.py, подключающей сервопривод DS04-NFS, показан в листинге 8 (в сокращенном виде).

Листинг 8. https://raw.githubusercontent.com/AlexandreFrolov/ri-controller-001-risdk/main/risdk_servo_ds04-nfs.py

…
i2c = c_int()
pwm = c_int()
ds04_fns = c_int()

lib = init(i2c, pwm)

ds04_fns = add_custom_rservo(lib, pwm, 1050, 2100, 1050, 2100, 0)        
print_rservo_state(lib, ds04_fns)

rservo_rotate_by_pulse(lib, ds04_fns, 1050, True)
print_rservo_state(lib, ds04_fns)
time.sleep(3) 

rservo_rotate_by_pulse(lib, ds04_fns, 2100, True)
print_rservo_state(lib, ds04_fns)
time.sleep(3) 

rservo_rotate_by_pulse(lib, ds04_fns, 1570, True)
print_rservo_state(lib, ds04_fns)
time.sleep(3) 

rservo_rotate_at_speed(lib, ds04_fns, 0, 50, True)
time.sleep(3) 
rservo_rotate_at_speed(lib, ds04_fns, 1, 100, True)
time.sleep(3) 

stop_rservo(lib, ds04_fns)
print_rservo_state(lib, ds04_fns)

cleanup_servo(lib, ds04_fns)
cleanup(lib)

Добавление сервопривода осуществляется функцией add_custom_rservo:

ds04_fns = add_custom_rservo(lib, pwm, 1050, 2100, 1050, 2100, 0)

Эта функция вызывает функции RI_SDK_CreateDeviceComponent, RI_SDK_exec_RServoDrive_CustomDeviceInit и RI_SDK_LinkRServodriveToController.

Здесь в качестве третьего и четвертого параметров функции add_custom_rservo передаются минимальная и максимальная длительность управляющих импульсов для поворота вала сервопривода по часовой стрелке.

Пятый и шестой параметры определяет минимальную и максимальную длительность импульсов для поворота вала против часовой стрелке.

И, наконец, седьмой параметр задает номер канала ШИМ контроллера Robointellect Controller 001, к которому подключен сервопривод.

Напомним, что, если подать управляющие импульсы длительностью 1 мс, вал сервопривода DS04-NFS будет вращаться с полной скоростью в направлении против часовой стрелки.

Импульсы длительностью 2 мс вызовут вращение вала сервопривода с полной скоростью в направлении по часовой стрелке.

И, наконец, для того чтобы остановить вращение вала, нужно подать на его управляющий контакт импульсы длительностью 1.5 мс.

Промежуточные значения длительности импульсов от 1 мс до 1.5 мс и от 1.5 мс до 2 мс можно использовать для управления скоростью вращения вала.


Установка RI SDK для Repka OS на микрокомпьютере Repka Pi

До сих пор мы работали с контроллером Robointellect Controller 001, подключив его через USB к ноутбуку или настольному компьютеру с ОС Microsoft Windows. Теперь попробуем подключить его к одноплатному компьютеру и сделаем это на примере Российского одноплатника Repka Pi, на которой установлен Linux дистрибутив Repka OS (рис. 16).

Рис. 16. Подключение контроллера Robointellect Controller 001 к Repka Pi

Также подключите к контроллеру сервоприводы.

Загрузите Repka OS и с помощью команды lsusb проверьте, что в системе определяется контроллер QinHeng Electronics CH341, установленный на плате Robointellect Controller 001:

# lsusb
Bus 008 Device 002: ID 1a86:5512 QinHeng Electronics CH341 in EPP/MEM/I2C mode, EPP/I2C adapter
Bus 008 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 005 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 007 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 004 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 006 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 003 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 002 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

Если подключение через USB выполнено правильно и контроллер CH341 виден, продолжайте установку.

Вам нужно открыть страницу и скачать файл установки robointellect_sdk_linux_arm64.deb (рис. 17).

Рис. 17. Скачивание файла установки пульта управления

Для этого скопируйте ссылку Скачать для архитектура arm64.

Скачайте файл robointellect_sdk_linux_arm64.deb:

# wget --no-check-certificate https://download.robointellect.ru/robointellect_sdk_linux_arm64.deb

Далее установите необходимые пакеты:

# apt install gcc libgtk-3-0 libayatana-appindicator3-1 make i2c-tools dkms python3 libusb-1.0-0-dev

После установки сделайте текущим каталог, в который был загружен файл robointellect_sdk_linux_arm64.deb и установите его следующим образом:

# dpkg -i robointellect_sdk_linux_arm64.deb

Через некоторое время на консоли появится запрос подтверждения установки драйвера:

…
/lib/systemd/system/ri_translator.service.
Хотите ли установить драйвер для CH341 y/n

Подтвердите установку, для чего введите символ «y» с клавиатуры.

Проверьте, что после установки появился файл библиотеки RI SDK /usr/local/robointellect_sdk/ri_sdk/librisdk.so .

Далее установите репозиторий с классами RiController, RiServo, RiRotateServo, RiLed и примеров программ на Python:

# git clone https://github.com/AlexandreFrolov/ri-controller-001-risdk

После установки библиотеки RISDK в Repka OS испытайте работу всех приведенных в этом репозитории программ.


Итоги

Из этой статьи Вы узнали о том, как устроен контроллер Robointellect Controller 001, плата которого объединяет сразу четыре устройства и предоставляет расширенные возможности для управления сервоприводами. Вы научились управлять серводвигателями с удержанием угла, серводвигателями постоянного вращения, подключенными к контроллеру, а также светодиодом RGB, уже установленном на плате контроллера, изучили библиотеку RI SDK и научились работать с ней из программ, написанных на языке Python.

Примеры программ запускались на компьютере под управлением Microsoft Windows, а также на ARM микрокомпьютере на примере Repka Pi с установленным Libux дистрибутивом Repka OS. В обоих случаях контроллер Robointellect Controller 001 был подключен через USB. В одной из следующих статей рассмотрим подробно управление PWM сервоприводами с использованием таких же аппаратных драйверов-контроллеров но уже через I2C шину напрямую с портов одноплатных компьютеров, т.е. без применения USB, а так же возможность управления сервоприводами через аппаратные PWM порты одноплатных компьютеров.

Комментарии (7)


  1. IronHead
    07.09.2023 13:54
    +1

    Может быть не внимательно читал текст, а почему кабель для подключения типа папа-папа?

    Есть же Type-C, USB-B и прочие кабели для подключения периферии. А ваша плата - как раз периферия, не хост.


    1. AlexandreFrolov
      07.09.2023 13:54
      +1

      Все правильно, для этого контроллера нужен кабель USB типа папа-папа. Так сделали разработчики контроллера, почему - это скорее вопрос к ним. Возможно, для компактности.


      1. Dark_Purple
        07.09.2023 13:54

        За такое надо на костре сжигать!


      1. NutsUnderline
        07.09.2023 13:54

        Сразу напоминает дешеман китайского производства


  1. miksoft
    07.09.2023 13:54

    Подскажите, пожалуйста, подобное устройство с GPIO и USB.


    1. pvvv
      07.09.2023 13:54
      +1

      ft232h


    1. NutsUnderline
      07.09.2023 13:54

      сколь я помню CH341 тоже можно было ногами подрыгать.

      А так любой контроллер, имеющий USB, и даже НЕ умеющий USB может такое, при наличии нужной прошивки, а она может быть в 5 строчек в Arduino

      Тут более специфично со стороны USB хоста софт писать, но готовых библиотек очень много