Не работает аппаратный RTS RS485 на SC16IS752, когда он включён в оверлей дерева устройств.

Вопрос или проблема

Я пытаюсь заставить SC16IS752 (преобразователь SPI в UART) работать в режиме RS485, используя оверлей дерева устройств (без запуска C приложения при загрузке).

Я взял исходный код оригинального оверлея для SC16IS752 из официальных источников на GitHub:

sc16is752-spi1-overlay.dts

Без добавления строки linux,rs485-enabled-at-boot-time; он работает так:
(RTS всегда высокий)

нет флага дерева устройств

Я изменил часть fragment@1 следующим образом и добавил RS485
(Я также изменил частоту тактового сигнала, потому что использую кристалл 1.843200 МГц в своем оборудовании, а не 14.xxxx МГц):

fragment@1 {
    target = <&spi1>;
    frag1: __overlay__ {
        #address-cells = <1>;
        #size-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&spi1_pins &spi1_cs_pins>;
        cs-gpios = <&gpio 18 1>;
        status = "okay";

        /* ПОДДЕРЖКА RS485 */
        linux,rs485-enabled-at-boot-time;
        rs485-rts-delay = <0 0>;
        /* КОНЕЦ ПОДДЕРЖКИ RS485 */

        sc16is752: sc16is752@0 {
            compatible = "nxp,sc16is752";
            reg = <0>; /* CE0 */
            clocks = <&sc16is752_clk>;
            interrupt-parent = <&gpio>;
            interrupts = <24 2>; /* IRQ_TYPE_EDGE_FALLING */
            gpio-controller;
            gpio-cells = <2>;
            spi-max-frequency = <4000000>;

            /* Я также пробовал указать это здесь */
            /* но RTS всегда высокий */
            /* ПОДДЕРЖКА RS485 */
            /* linux,rs485-enabled-at-boot-time; */
            /* rs485-rts-delay = <0 0>; */
            /* КОНЕЦ ПОДДЕРЖКИ RS485 */

            sc16is752_clk: sc16is752_clk {
                compatible = "fixed-clock";
                #clock-cells = <0>;
                clock-frequency = <1843200>;
            };
        };
    };
};

Я скомпилировал файл dts, положил его в /boot/overlays и добавил соответствующую строку в /boot/config.txt, чтобы использовать этот оверлей.

Линия RTS теперь всегда низкая (без флага она была высокой – см. изображение выше):

флаг дерева устройств установлен

Таким образом, этот флаг был прочитан ядром/драйвером, но RTS не делает ничего, когда я отправляю данные. Он должен работать вот так:

вставьте описание изображения здесь

Последний скриншот с работающим режимом RS485 был сделан после того, как я активировал режим RS485 в C программе вот так:

#include <fcntl.h>
#include <unistd.h>
#include <linux/serial.h>

/* Включите определение для RS485 ioctls: TIOCGRS485 и TIOCSRS485 */
#include <sys/ioctl.h>

int main(int argc, char *artv[]){

        /* Откройте ваше конкретное устройство (например, /dev/mydevice): */
        int fd = open ("/dev/ttySC0", O_RDWR);
        if (fd < 0) {
                /* Обработка ошибок. См. errno. */
                return -1;
        }

        struct serial_rs485 rs485conf;

        /* Включите режим RS485: */
        rs485conf.flags |= SER_RS485_ENABLED;

        /* Установите логический уровень для RTS пина равным 1 при отправке: */
        rs485conf.flags |= SER_RS485_RTS_ON_SEND;
        /* Установите логический уровень для RTS пина равным 0 после отправки: */
        rs485conf.flags &= ~(SER_RS485_RTS_AFTER_SEND);

        rs485conf.delay_rts_before_send = 0;
        rs485conf.delay_rts_after_send = 0; // ноль! не поддерживается SC16IS752

        if (ioctl (fd, TIOCSRS485, &rs485conf) < 0) {
                /* Обработка ошибок. См. errno. */
                return -2;
        }

        /* Используйте syscalls read() и write() здесь... */

        /* Закройте устройство, когда закончите: */
        if (close (fd) < 0) {
                /* Обработка ошибок. См. errno. */
                return -3;
        }

        return 0;
}

Итак, вопрос в том, как сделать это без C программы?

Почему установка режима rs485 в дереве устройств недостаточно?

Я подозреваю, что у драйвера нет конфигурации по умолчанию, и в флагах SER_RS485_RTS_ON_SEND и SER_RS485_RTS_AFTER_SEND только нули. Если это так – (дополнительный вопрос) должен ли я подать отчет о проблеме с этим драйвером в этой ситуации?

Полезные ресурсы:


Редактировано/добавлено позже:

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

    /* Чтение структуры rs485conf с порта */
    if (ioctl (fd, TIOCGRS485, &rs485conf) < 0) {
            /* Обработка ошибок. См. errno. */
            return -2;
    }

    printf("Перед: ");
    binprintf(rs485conf.flags); // функция, которая печатает int в двоичном виде
    printf("\n");

все флаги пустые. Так что… оверлей изменил состояние RTS после загрузки, но флаги на порту нули? Я не понимаю.

Если кто-то найдет этот вопрос, как я, пытаясь заставить SC16IS752 работать по I2C на RPi, ответ кратко:

  • Драйвер sc16is7xx.c не имеет никакого кода, вызывающего uart_get_rs485_mode(), как описано в serial_core.c и который появляется в нескольких других серийных драйверах;
  • свойство дерева устройств rs485-rts-active-low похоже, было добавлено только в ядра RPi 5.3 и позже (см. этот коммит).

Чтобы решить первую проблему, я внес несколько небольших изменений в драйвер sc16is7xx.c, см. этот коммит. Функция uart_get_rs485_mode() извлекает соответствующие свойства из дерева устройств и записывает struct serial_rs485, тот же самый, который используется для ioctl TIOCSRS485. Изменения не были проверены на регрессию, но на данный момент они работают надежно для меня на моем макетном интерфейсе SC16IS752.

Этот коммит основан на ядре 5.4, поэтому поддерживает свойство rs485-rts-active-low и, следовательно, хорошо работает с моими драйверами линии MAX3072.

Вот часть моего файла оверлея:

fragment@1 {
    target = <&i2c_arm>;
    __overlay__ {
        #address-cells = <1>;
        #size-cells = <0>;
        status = "okay";

        sc16is752: sc16is752@48 {
            compatible = "nxp,sc16is752";
            reg = <0x48>; /* I2C адрес */
            clocks = <&sc16is752_clk>;
            interrupt-parent = <&gpio>;
            interrupts = <18 2>; /* IRQ_TYPE_EDGE_FALLING */
            gpio-controller;
            #gpio-cells = <2>;
            i2c-max-frequency = <400000>;
            linux,rs485-enabled-at-boot-time;
            rs485-rts-active-low;
        };
    };
};

И вот изображение с осциллографа, показывающее, что это работает:

I2C и RS485 сигналы

Для справки: D8=SCL, D9=SDA, D10=_IRQ, D11=TXD, D12=_RTS, D13=RXD, Analog1=одна линия RS485, Analog2=RXD (та же, что и D13). Ответ на команду поступает от удаленного устройства ModBus. Обратите внимание, что _RTS высокий (3.3 В) во время передачи, так как там двойное отрицание.

Ответ или решение

К сожалению, возникшая у вас проблема с управлением сигналом RTS на SC16IS752 в режиме RS485 и использованием устройства дерева (device tree) для настройки имеет несколько аспектов, которые стоит подробно рассмотреть.

Основные проблемы

  1. Недостаточная поддержка в драйверах: судя по вашему описанию, драйвер sc16is7xx.c не реализует функционал, который бы вызывал uart_get_rs485_mode(), как это сделано в других драйверах для UART. Это может быть причиной того, что настройки RS485, заданные через device tree, не применяются корректно и не инициализируют управление RTS.

  2. Совместимость с версиями ядра: если вы используете версию ядра, более раннюю чем 5.3, некоторые свойства, такие как rs485-rts-active-low, могут быть недоступны. Это следует учесть, так как ваша текущая версия драйвера может не поддерживать все необходимые для RS485 настройки.

Решения

Рассмотрим, что можно сделать для решения проблемы или обхода её:

  1. Изменение Драйвера: Поскольку вы уже заметили, что драйвер не инициализирует флаги SER_RS485_RTS_ON_SEND и SER_RS485_RTS_AFTER_SEND, вы можете самостоятельно модифицировать драйвер. Например, добавление вызова uart_get_rs485_mode() поможет считывать настройки из device tree и применять их для управления RTS непосредственно при инициализации.

    Вот пример того, как может выглядеть такая модификация:

    struct serial_rs485 rs485conf;
    if (uart_get_rs485_mode(uart, &rs485conf)) {
        // Применяем настройки рус485 из rs485conf
    }
  2. Обновление ядра: Рекомендуется обновить до более поздней версии ядра (как минимум 5.3 или выше), если у вас версия ниже, чтобы воспользоваться новыми возможностями и исправлениями драйвера. Это может значительно улучшить функционал управления RS485.

  3. Проверка свойств device tree: Убедитесь, что свойства, такие как linux,rs485-enabled-at-boot-time и rs485-rts-active-low, добавлены в правильном формате, и не забывайте перезагружать систему после изменений.

  4. Тестирование и диагностика: Используйте средства диагностики, такие как dmesg, чтобы проверить, есть ли ошибки или предупреждения, связанные с инициализацией устройства после применения overlay. Это может дать понимание о том, применяются ли настройки и корректно ли работает драйвер.

  5. Создание отчета об ошибке: Если вы уверены, что текущая реализация драйвера требует улучшений, вы можете создать отчет об ошибках (issue) в репозитории драйвера на GitHub. Опишите ваш случай, и приложите конкретные изменения, которые вы реализовали для улучшения работы.

Заключение

Проблема с управлением RTS на SC16IS752 в режиме RS485 требует внимания как к устройству, так и к драйверу. Модификация драйвера и обновление ядра, при необходимости, являются основными шагами для достижения правильной работы. Надеюсь, что эти рекомендации помогут вам в решении вашей задачи.

Оцените материал
Добавить комментарий

Капча загружается...