В предыдущих статьях нашей серии про сервоприводы мы рассказывали, как они устроены, как можно управлять сервоприводами с помощью широтно-импульсной модуляции ШИМ (Pulse Width Modulation,  PWM) с помощью контроллера Robointellect Controller 001, а также напрямую через GPIO через генератор ШИМ на плате микрокомпьютера Repka Pi.

В четвертой статье серии статей про сервоприводы мы расскажем, как управлять сервоприводами с помощью серво-драйвера (контроллера) Robointellect Controller 001 или PCA9685 (он же драйвер серводвигателей или сервомоторов с ШИМ управлением), подключенного к Repka Pi через шину I2C.

Другие статьи серии про управление сервоприводами вы найдете здесь:

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

Краткое введение в I2C

Интерфейс I2C (Inter-Integrated Circuit), или как его обычно обозначают, I2C, был разработан в начале 80-х годов компанией Philips. Однако он широко используется до сих пор для соединения различных электронных устройств.

Для передачи данных по шине I2C требуется всего две линии — SLC и SDA. По линии SLC передаются сигналы синхронизации, а линия SDA служит для передачи данных.

К шине I2C может быть подключено одно или несколько ведущих устройств, и до 127 ведомых. Ведущие устройства управляют процессом передачи данных ведомых устройств. В этой статье мы будем использовать в качестве ведущего устройства микрокомпьютер Repka Pi, а в качестве ведомого — контроллер Robointellect Controller 001.

В исходном состоянии на линиях I2C поддерживается напряжение, равное напряжению питания контроллера, — примерно 3 В. Линии подключены к источнику питания через резисторы таким образом, что все устройства могут замыкать их на землю в процессе передачи данных (рис. 1). Еще говорят, что эти линии «подтянуты» к шине 3 В.

Рис. 1. Шина данных I2C.
Рис. 1. Шина данных I2C.

На этом рисунке мы показали пример подключения двух устройств к шине I2C.

В GPIO микрокомпьютера Repka Pi для распиновок от второй до пятой физический контакт 5 играет роль линии SCL, а контакт 3 — линии SDA (рис. 2).

Рис. 2. Распиновки GPIO микрокомпьютера Repka Pi.
Рис. 2. Распиновки GPIO микрокомпьютера Repka Pi.

Все устройства на шине I2C имеют собственный 8-битный адрес. Этот адрес можно узнать из документации (data sheet, «даташит») на подключаемое устройство. Некоторые устройства (в том числе контроллер Robointellect Controller 001) позволяют изменять адрес I2C при помощи перемычек или переключателей.

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

Достаточно подробное описание интерфейса I2C можно найти на этом сайте (на английском языке). Также будет полезен перевод спецификации на русский язык.

Еще рекомендуем две статьи, которые будут вам полезны при изучении I2C:

Собираем макет с Robointellect Controller 001

Подключите контроллер Robointellect Controller 001 к интерфейсу I2C микрокомпьютера Repka Pi, как это показано на рис. 3.

Рис. 3. Подключение контроллера Robointellect Controller 001 к интерфейсу I2C микрокомпьютера Repka Pi.
Рис. 3. Подключение контроллера Robointellect Controller 001 к интерфейсу I2C микрокомпьютера Repka Pi.

Выводы GND и VCC 5V контроллера Robointellect Controller 001 нужно подключить, соответственно, к земле и контакту +5 В микрокомпьютера Repka Pi.

Вы можете найти эти контакты на рис. 2, при этом установите пятую распиновку.

Как видно из этого рисунка, напряжение +5 В выведено на физические контакты 2 и 4, а земля — на физические контакты 9, 14, 20, 25, 30, 34 и 39.

Выводы SDA и SCL контроллера Robointellect Controller 001 нужно подключить, соответственно, к физическим контактам 3 и 5 микрокомпьютера Repka Pi.

Также подключите к нулевому и третьему каналам контроллера два сервопривода sg90 (рис. 4).

Рис. 4. Подключение сервоприводов.
Рис. 4. Подключение сервоприводов.

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

Программа pca9685_test.py

Итак, мы подключили контроллер Robointellect Controller 001 к шине I2C микрокомпьютера Repka Pi, а к контроллеру — два сервопривода.

Теперь давайте напишем программу для Repka Pi, которая будет управлять подключенными сервоприводами.

Подготовка к запуску программы

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

Прежде всего, обновите ОС и установите пакет i2c-tools:

# apt update
# apt upgrade
# apt install i2c-tools

Затем посмотрите список шин I2C:

# i2cdetect -l
i2c-1   i2c     mv64xxx_i2c adapter         I2C adapter
i2c-2   i2c     mv64xxx_i2c adapter         I2C adapter
i2c-0   i2c     DesignWare HDMI             I2C adapter

# i2cdetect -l

Здесь обнаружены шины с номерами 0, 1 и 2.

Выведите на консоль адреса устройств, подключенных к шине с номером 1:

# i2cdetect -y 1
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: 40 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

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

Теперь установите библиотеку smbus, с помощью которой наша программа будет работать с шиной I2C:

# apt-get install python3-smbus

Исходный код программы

Скачайте программу pca9685_test.py, исходный код которой представлен в листинге 1 (в сокращенном виде).

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

…
bus = smbus.SMBus(1)
…
try:
initialize_pca9685()
set_pwm_freq(50)
<span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
    set_servo_angle(CHANNEL00, <span class="hljs-number">0</span>)
    time.sleep(<span class="hljs-number">2</span>)
    set_servo_angle(CHANNEL00, <span class="hljs-number">90</span>)
    time.sleep(<span class="hljs-number">2</span>)
    set_servo_angle(CHANNEL00, <span class="hljs-number">180</span>)
    time.sleep(<span class="hljs-number">2</span>)

    set_servo_angle(CHANNEL03, <span class="hljs-number">0</span>)
    time.sleep(<span class="hljs-number">2</span>)
    set_servo_angle(CHANNEL03, <span class="hljs-number">90</span>)
    time.sleep(<span class="hljs-number">2</span>)
    set_servo_angle(CHANNEL03, <span class="hljs-number">180</span>)
    time.sleep(<span class="hljs-number">2</span>)

except KeyboardInterrupt:
pass
finally:
set_all_pwm(0, 0)
time.sleep(0.05)
reset()

Инициализация чипа pca9685

После создания объекта bus, с помощью которого программа будет выполнять обмен данными по шине I2C с номером 1, идет вызов функции initialize_pca9685:

def initialize_pca9685():
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE2, OUTDRV)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, ALLCALL)
    time.sleep(0.005)
mode1 = bus.read_byte_data(PCA9685_ADDRESS, PCA9685_MODE1)
mode1 = mode1 &amp; ~SLEEP 
bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, mode1)
time.sleep(<span class="hljs-number">0.005</span>)

В чипе pca9685 есть два регистра, управляющих режимом работы — регистры PCA9685_MODE1 и PCA9685_MODE2.

Функция инициализации чипа pca9685 записывает в регистр PCA9685_MODE2 значение OUTDRV, в результате чего выходы для подключения управляемых устройств (таких как сервоприводы) подключаются по схеме с каскадным выходом. Так же есть возможность подключения чипов по схеме с открытым стоком.

Запись в регистр PCA9685_MODE1 значения ALLCALL разрешает чипу реагировать на адрес общего вызова шины I2C. Наша программа работает только по адресу 40, поэтому эту операцию выполнять не обязательно.

На следующем шаге программа выводит чип pca9685 из состояния «спячки», когда он находится в энергосберегающем режиме. Для этого программа читает содержимое регистра PCA9685_MODE1, инвертирует бит SLEEP, а затем записывает полученное значение в этот же регистр.

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

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

Функция установки частоты ШИМ

Внутри чипа pca9685 имеется кварцевый тактовый генератор частотой 25 МГц, с помощью которого формируются импульсы ШИМ. Используя этот генератор, чип может формировать импульсы ШИМ с частотой от 24 до 1526 Гц.

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

Для настройки предварительного делителя предназначена функция set_pwm_freq, которой в качестве параметра передается нужное значение частоты ШИМ:

def set_pwm_freq(freq_hz):
    prescaleval = 25000000.0 
    prescaleval /= 4096.0 
    prescaleval /= float(freq_hz)
    prescaleval -= 1.0
    prescale = int(math.floor(prescaleval + 0.5))
    oldmode = bus.read_byte_data(PCA9685_ADDRESS, PCA9685_MODE1)
    newmode = (oldmode & 0x7F) | 0x10  
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, newmode)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_PRESCALE, prescale)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, oldmode)
    time.sleep(0.005)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, oldmode | 0x80)

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

Как она это делает?

Вначале функция set_pwm_freq записывает значение частоты тактового генератора в герцах 25000000.0 в переменную prescaleval.

Далее это значение делится на 4096 — это максимальная разрядность. При делении на 4096 получаем минимальный шаг изменения скважности импульса, и это значение можно считать точностью управления. По этой причине для увеличения точности управления можно повышать частоту.

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

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

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

oldmode = bus.read_byte_data(PCA9685_ADDRESS, PCA9685_MODE1)
newmode = (oldmode & 0x7F) | 0x10  
bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, newmode)

Далее функция программирует предварительный делитель, записывая вычисленное значение в регистр PCA9685_PRESCALE:

bus.write_byte_data(PCA9685_ADDRESS, PCA9685_PRESCALE, prescale)

И, наконец, восстанавливается старое значение регистра PCA9685_MODE1:

bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, oldmode)
time.sleep(0.005)
bus.write_byte_data(PCA9685_ADDRESS, PCA9685_MODE1, oldmode | 0x80)

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

После инициализации чипа pca9685 можно вызывать функцию set_servo_angle, предназначенную для установки вала сервопривода sg90 в нужное положение:

def set_servo_angle(channel, angle):
    if angle < 0:
        angle = 0
    elif angle > 180:
        angle = 180
sg90_servo_min_pulse = <span class="hljs-number">500</span>
sg90_servo_maxn_pulse = <span class="hljs-number">2400</span>
servo_min = pulse_to_pwm(<span class="hljs-number">500</span>, <span class="hljs-number">50</span>)
servo_max = pulse_to_pwm(<span class="hljs-number">2400</span>, <span class="hljs-number">50</span>)

pulse_width = <span class="hljs-built_in">int</span>(angle * (servo_max - servo_min) / <span class="hljs-number">180</span> + servo_min)
set_pwm(channel, <span class="hljs-number">0</span>, pulse_width)

Вначале функция set_servo_angle проверяет значение угла, который передается ей в качестве параметра angle. Угол должен находиться в интервале [0, 180⁰]. Если у вас сервопривод с другим максимальным углом поворота, код проверки необходимо изменить соответствующим образом.

Далее функция вычисляет минимальное и максимальное значение для установки ширины управляющих импульсов:

sg90_servo_min_pulse = 500
sg90_servo_maxn_pulse = 2400
servo_min = pulse_to_pwm(500, 50)
servo_max = pulse_to_pwm(2400, 50)

Здесь предполагается, что для сервопривода sg90 длительность управляющих импульсов должна находиться в интервале [500, 2400] микросекунд.

Чтобы пойти дальше, нам нужно разобраться, как чип pca9685 формирует импульсы ШИМ на выходах своих 16 каналов.

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

Например, для первого канала (с номером 0) это такие регистры:

PCA9685_LED0_ON_L = 0x06
PCA9685_LED0_ON_H = 0x07
PCA9685_LED0_OFF_L = 0x08
PCA9685_LED0_OFF_H = 0x09

Регистры PCA9685_LED0_ON_L и PCA9685_LED0_ON_H задают время, на которое сигнал управления канала с номером 0 будет включен, а регистры PCA9685_LED0_OFF_L и PCA9685_LED0_OFF_H — на которое сигнал управления будет выключен.

Время задается в виде периодов в интервале от 0 до 4095. При этом упомянутые выше регистры содержат 12-разрядные значения, соответствующие разрешению ШИМ чипа pca9685.

Функция set_pwm устанавливает для заданного канала время, в течение которого сигнал будет включен, а также время, в течение которого сигнал будет выключен:

def set_pwm(channel, on, off):
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_LED0_ON_L + 4 * channel, on & 0xFF)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_LED0_ON_H + 4 * channel, on >> 8)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_LED0_OFF_L + 4 * channel, off & 0xFF)
    bus.write_byte_data(PCA9685_ADDRESS, PCA9685_LED0_OFF_H + 4 * channel, off >> 8)

Обратите внимание, что здесь функции bus.write_byte_data записи данных в шину I2C передается адреса регистров, вычисленные относительно адресов регистров нулевого канала (адреса регистров нумеруются последовательно).

Таким образом, функция set_pwm задает интервал on в течение которого сигнал на канале channel будет включен, а также интервал off, в течение которого этот сигнал будет выключен.

Для примера на рис. 5 показана осциллограмма сигнала ШИМ, когда длина импульса составляет 2350 мкс, что соответствует интервалу 491 из диапазона [0, 4095].

Рис. 5. Осциллограмма сигнала ШИМ.
Рис. 5. Осциллограмма сигнала ШИМ.

На рис. 5 также показано, что период импульсов ШИМ соответствует частоте 50 Гц и равен 20000 мкс. Это соответствует полному диапазону значений, которые можно записывать в регистры каналов.

При этом во время интервала, равного 491, сигнал ШИМ включен, а во время интервала, равного 3605 — выключен.

Теперь мы знаем, как программировать длительность импульсов ШИМ исходя из значений в диапазоне [0, 4095].

Но у нас есть другая задача — сформировать управляющие импульсы, длина которых задана в микросекундах. Эту задачу решает функция pulse_to_pwm:

def pulse_to_pwm(pulse, frequency):
    period_duration = (1 / frequency) * 1000000
    relative_pulse_duration = pulse / period_duration
    return(int(relative_pulse_duration * 4096))

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

Далее функция вычисляет относительное значение длительности импульса в единицах периода, и сохраняет это значение в переменной relative_pulse_duration.

И, наконец, функция pulse_to_pwm возвращает значение длительности, пересчитанное в диапазон [0, 4096].

Однако при работе с сервоприводами программы часто оперируют не с длительностями управляющих импульсов, а с углами поворота вала, заданного в градусах. Например, вал сервопривода sg90 может поворачиваться в диапазоне [0⁰, 180⁰].

Функция set_servo_angle поворачивает вал сервопривода, подключенного к каналу channel, на угол angle, который должен находиться в указанном выше диапазоне:

def set_servo_angle(channel, angle):
    if angle < 0:
        angle = 0
    elif angle > 180:
        angle = 180
sg90_servo_min_pulse = <span class="hljs-number">500</span>
sg90_servo_maxn_pulse = <span class="hljs-number">2400</span>
servo_min = pulse_to_pwm(<span class="hljs-number">500</span>, <span class="hljs-number">50</span>)
servo_max = pulse_to_pwm(<span class="hljs-number">2400</span>, <span class="hljs-number">50</span>)

pulse_width = <span class="hljs-built_in">int</span>(angle * (servo_max - servo_min) / <span class="hljs-number">180</span> + servo_min)

<span class="hljs-built_in">print</span>(<span class="hljs-string">"pulse_width: "</span> + <span class="hljs-built_in">str</span>(pulse_width))
set_pwm(channel, <span class="hljs-number">0</span>, pulse_width)

После получения управления функция set_servo_angle проверяет, что угол поворота angle находится в интервале [0⁰, 180⁰] и, если это не так, приводит к этому интервалу.

Далее исходя из минимальной sg90_servo_min_pulse и максимальной sg90_servo_maxn_pulse длины управляющего импульса функция определяет, какие граничные значения из диапазона [0, 4095] можно использовать для управления каналами. Преобразования выполняет рассмотренная ранее функция pulse_to_pwm.

Перед вызовом функции set_pwm, управляющей длительностью импульса заданного канала, функция set_servo_angle приводит значение угла в градусах к нужному значению длительности управляющего импульса в интервале [servo_max, servo_min].

Запуск программы pca9685_test.py

Если вы уже выполнили все необходимые подготовительные действия и скачали программу, представленную в листинге 1, запустите ее из командной строки Repka OS с правами привилегированного пользователя root:

# python3 pca9685_test.py
pulse_width: 102
pulse_width: 296
pulse_width: 491
pulse_width: 102
pulse_width: 296
pulse_width: 491

Программа будет по очереди поворачивать валы первого и второго сервоприводов в положения 0⁰, 90⁰ и 180⁰. При этом на консоль будут выводиться соответствующие длительности импульсов в диапазоне [0, 4095].

Вы также можете посмотреть видео, где показано как все это работает.

Установка RISDK в Repka OS

Для контроллера Robointellect Controller 001 доступна библиотека RISDK, значительно облегчающая управление сервоприводами, светодиодами и другими подобными устройствами, подключенными к каналам этого контроллера.

Библиотека RISDK может работать при подключении контроллера через USB, о чем мы рассказывали ранее в статье Repka Pi и управление сервоприводами, ЧАСТЬ 2. Управляем сервоприводами с помощью Robointellect Controller 001 и RISDK, а также при подключении через I2C.

Чтобы установить RISDK в Repka OS, откройте страницу и скачайте файл установки robointellect_sdk_linux_arm64.deb (рис. 6).

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

Для скачивания скопируйте ссылку Скачать для архитектура arm64. Используйте эту ссылку следующим образом:

# wget 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 --install robointellect_sdk_linux_arm64.deb

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

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

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

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

Далее установите репозиторий с примерами программ на Python:

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

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

В предыдущей статье серии Repka Pi и управление сервоприводами, ЧАСТЬ 2. Управляем сервоприводами с помощью Robointellect Controller 001 и RISDK мы рассказывали о том, как управлять светодиодами и сервоприводами, подключенными к контроллеру Robointellect Controller 001 через USB.

Все приведенные там программы после небольшой модификации могут работать и в ОС Репка, когда контроллер Robointellect Controller 001 подключен к Repka Pi через шину I2C.

Попробуйте запустить программы из репозитория второй статьи, где уже внесены необходимые изменения.

В листинге 2 приведена в сокращенном виде программа risdk_led_i2c_demo.py, в которой сделаны необходимые изменения.

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

…
def init(i2c, pwm):
errTextC = create_string_buffer(<span class="hljs-number">1000</span>)
platform_os = platform.system()
<span class="hljs-keyword">try</span>:
    <span class="hljs-keyword">if</span> platform_os == <span class="hljs-string">"Windows"</span>:
        lib = cdll.LoadLibrary(<span class="hljs-string">"C:\Windows\system32\librisdk.dll"</span>)
    <span class="hljs-keyword">if</span> platform_os == <span class="hljs-string">"Linux"</span>:
        lib = cdll.LoadLibrary(<span class="hljs-string">"/usr/local/robointellect_sdk/ri_sdk/librisdk.so"</span>)
<span class="hljs-keyword">except</span> OSError <span class="hljs-keyword">as</span> e:
    <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">"Failed to load: "</span> + <span class="hljs-built_in">str</span>(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]
lib.RI_SDK_connector_i2c_SetBus.argtypes = [c_int, c_int, POINTER(c_int), POINTER(c_int), c_char_p]

errTextC = create_string_buffer(<span class="hljs-number">1000</span>)
errCode = lib.RI_SDK_InitSDK(<span class="hljs-number">3</span>, errTextC)
<span class="hljs-keyword">if</span> errCode != <span class="hljs-number">0</span>:
    <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">f"RI_SDK_InitSDK failed with error code <span class="hljs-subst">{errCode}</span>: <span class="hljs-subst">{err_msg(errTextC)}</span>"</span>)        

errCode = lib.RI_SDK_CreateModelComponent(<span class="hljs-string">"connector"</span>.encode(), <span class="hljs-string">"i2c_adapter"</span>.encode(), <span class="hljs-string">"ch341"</span>.encode(), i2c, errTextC)
<span class="hljs-keyword">if</span> errCode != <span class="hljs-number">0</span>:
    <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">f"RI_SDK_CreateModelComponent failed with error code <span class="hljs-subst">{errCode}</span>: <span class="hljs-subst">{err_msg(errTextC)}</span>"</span>)        

errCode = lib.RI_SDK_CreateModelComponent(<span class="hljs-string">"connector"</span>.encode(), <span class="hljs-string">"pwm"</span>.encode(), <span class="hljs-string">"pca9685"</span>.encode(), pwm, errTextC)
<span class="hljs-keyword">if</span> errCode != <span class="hljs-number">0</span>:
    <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">f"RI_SDK_CreateModelComponent failed with error code <span class="hljs-subst">{errCode}</span>: <span class="hljs-subst">{err_msg(errTextC)}</span>"</span>)        

nextBus = c_int()
prevBus = c_int()
errCode = lib.RI_SDK_connector_i2c_SetBus(i2c, <span class="hljs-number">1</span>, nextBus, prevBus, errTextC)
            
<span class="hljs-keyword">if</span> errCode != <span class="hljs-number">0</span>:
    <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">f"init failed with error code <span class="hljs-subst">{errCode}</span>: <span class="hljs-subst">{err_msg(errTextC)}</span>"</span>)        

errCode = lib.RI_SDK_LinkPWMToController(pwm, i2c, <span class="hljs-number">0x40</span>, errTextC)
<span class="hljs-keyword">if</span> errCode != <span class="hljs-number">0</span>:
    <span class="hljs-keyword">raise</span> Exception(<span class="hljs-string">f"RI_SDK_LinkPWMToController failed with error code <span class="hljs-subst">{errCode}</span>: <span class="hljs-subst">{err_msg(errTextC)}</span>"</span>)        

<span class="hljs-keyword">return</span> lib

…
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)
    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Start pulse..."</span>)

    led_pulse(lib, led, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1500</span>, <span class="hljs-literal">False</span>)
    led_pulse(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1500</span>, <span class="hljs-literal">False</span>)
    led_pulse(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">1500</span>, <span class="hljs-literal">False</span>)

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Start flicker..."</span>)

    led_flicker(lib, led, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">500</span>, <span class="hljs-number">5</span>, <span class="hljs-literal">False</span>)
    led_flicker(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">500</span>, <span class="hljs-number">5</span>, <span class="hljs-literal">False</span>)
    led_flicker(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">500</span>, <span class="hljs-number">5</span>, <span class="hljs-literal">False</span>)

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Start pulse_pause..."</span>)

    led_pulse_pause(lib, led, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1000</span>, <span class="hljs-number">200</span>, <span class="hljs-number">3</span>, <span class="hljs-literal">False</span>)
    led_pulse_pause(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">1000</span>, <span class="hljs-number">200</span>, <span class="hljs-number">3</span>, <span class="hljs-literal">False</span>)
    led_pulse_pause(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">1000</span>, <span class="hljs-number">200</span>, <span class="hljs-number">3</span>, <span class="hljs-literal">False</span>)

    <span class="hljs-built_in">print</span>(<span class="hljs-string">"Start pulse_frequency..."</span>)

    led_pulse_frequency(lib, led, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">10</span>, <span class="hljs-number">10</span>, <span class="hljs-literal">False</span>)
    led_pulse_frequency(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">0</span>, <span class="hljs-number">20</span>, <span class="hljs-number">10</span>, <span class="hljs-literal">False</span>)
    led_pulse_frequency(lib, led, <span class="hljs-number">0</span>, <span class="hljs-number">0</span>, <span class="hljs-number">255</span>, <span class="hljs-number">30</span>, <span class="hljs-number">10</span>, <span class="hljs-literal">False</span>)

    led_cleanup(lib, led)
    cleanup(lib)
<span class="hljs-keyword">except</span> Exception <span class="hljs-keyword">as</span> e:
    <span class="hljs-built_in">print</span>(traceback.format_exc() + <span class="hljs-string">"===&gt; "</span>, <span class="hljs-built_in">str</span>(e))
    sys.exit(<span class="hljs-number">2</span>)

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

cdll.LoadLibrary("/usr/local/robointellect_sdk/ri_sdk/librisdk.so")
…
lib.RI_SDK_connector_i2c_SetBus.argtypes = [c_int, c_int, POINTER(c_int), POINTER(c_int), c_char_p]
…
nextBus = c_int()
prevBus = c_int()
errCode = lib.RI_SDK_connector_i2c_SetBus(i2c, 1, nextBus, prevBus, errTextC)
if errCode != 0:
raise Exception(f"init failed with error code {errCode}: {err_msg(errTextC)}")

Функция RI_SDK_connector_i2c_SetBus позволяет задать номер шины I2C, к которой подключен чип pca9685 контроллера Robointellect Controller 001.

После завершения своей работы функция запишет в переменные nextBus, prevBus установленный и предыдущий номер шины, соответственно.

Если не вызывать эту функцию, то в Linux по умолчанию будет использована шина с номером 1. Поэтому вы можете и не добавлять вызов функции RI_SDK_connector_i2c_SetBus в init, если уверены, что у вас используется шина 1.

Итоги

Из предыдущих статей серии вы узнали, как устроены сервоприводы, и как управлять ими с помощью генераторов ШИМ, как отдельными, так и встроенными в микрокомпьютер Repka Pi. Вы также научились управлять светодиодами и сервоприводами с помощью контроллера Robointellect Controller 001, подключенного к Repka Pi с помощью USB (в Repka OS).

В четвертой статье вы познакомились с шиной I2C и научились подключать драйвер серводвигателей RoboIntellect Controller 001 к Repka Pi через эту шину. Вы написали программу, которая управляет сервоприводом, обращаясь непосредственно к регистрам чипа PCA9685, установленного в Robointellect Controller 001.

Кроме этого, Вы научились устанавливать RI SDK в Repka OS и вызывать функции этого SDK для управления устройствами, подключенными к Robointellect Controller 001. При этом саму драйвер сервомоторов вы подключили к Repka Pi через шину I2C.

Вы убедились, что все примеры, описанные в статье Repka Pi и управление сервоприводами, ЧАСТЬ 2. Управляем сервоприводами с помощью Robointellect Controller 001 и RISDK, работают при подключении контроллера Robointellect Controller 001 к Repka Pi не только через USB, но и через шину I2C.

Сам серво-драйвер RoboIntellect можно найти тут, чтобы долго не искать его в поисковике и на плейс-маркетах.

Всем успехов в Ваших DIY проектах по автоматике, робототехнике и IoT.

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


  1. Dimon41
    19.12.2023 05:51

    Как понять какая версия исполнения платы? По картинке с распиновкой кажется, что в исполнении 1 это работать не будет.