Как стать автором
Обновить

Комментарии 19

Отличная статья! Очень наглядно и доступно.
А можно ли применять STM32CubeMX для генерации кода для камней STM32 F0 и F1?
Можно.
Да, смотри, на офф-сайте STM заявляют, что CubeMX работает с линейками от F0 до L4.
Но почему-то не каждый опубликовал. Не смог найти реализацию под HAL. А при портировании выяснил, что чистый HAL не может того, что нужно.
как по мне хуарт так называется неспроста. его использование во многих случаях чрезмерно. единственное что есть переносимость с f0 на другие кристаллы вплоть до л4. хотя я не вижу ничего страшного в правке имен пары регистров для настройки, а вот висеть столько в прерывание — не камильфо. имхо далеко не лушая реализация поставленной перед ТС задачи.
А как поступили бы Вы?
Благодарю за статью. Мне как раз предстоит в ближайшем будущем реализовывать поддержку Modbus/RTU.

Только я не вижу обработки интервалов. Их нет вообще или просто Вы не включили их в статью? Я имею в виду межсимвольный интервал t1.5 и межпакетный интервал t3.5 (по оф. терминологии).

Я хочу сделать следующим образом. Согласно документации, интервалы таковы:

19200 бод и ниже: t1.5 = 1.5 char = 3 x 0.5 char; t.3.5 = 3.5 char = 7 x 0.5 char;
19200 бод и выше: t1.5 = 750 мкс = 3 x 250 мкс; t3.5 = 1750 мкс = 7 x 250 мкс.

Т.о. таймер программируется на t0.5 (по моей терминологии). Прикинул, что значение Prescaler = 125(-1) будет оптимальным. Оно позволяет получить целочисленное значение Period, а также единую формулу вычисления для различных камней/частот и скоростей обмена.

Понадобятся 2 счётчика:

счётчик повторений, c_rep — количество срабатываний таймера;
счётчик «ошибок», c_err — количество «косых» фреймов.

При получении байта счётчик c_rep сбрасывается. Когда c_rep == 3, счётчик c_err увеличивается. Т.о. если c_rep == 7, то пакет получен. При этом, если c_err == 1, то пакет целый, в противном случае — битый.

Поскольку я далеко не профи в данной области, прошу прокомментировать мой [пока] чисто умозрительный подход.
Я не включал их в статью, потому что она (реализация интервалов) спрятана под капотом.
Сразу стоит уточнить, что это не те интервалы, которые надо выдерживать, а те, которые нельзя превышать. То есть, если Вы превысите межпакетный интервал, то система считает, что на линии проблемы. Проверкой межпакетного интервала занимается таймер, который мы портировани в porttimer.c.
_1. Похоже, я чего-то недопонимаю.

В документации на modbus написано:

In RTU mode, message frames are separated by a silent interval of at least 3.5 character times

и

If a silent interval of more than 1.5 character times occurs between two characters, the message frame is declared incomplete and
should be discarded by the receiver.

Т.е. слэйв должен следить как за межпакетным, так и межсимвольным интервалами. Я этого не увидел в статье (может, проглядел), поэтому и спросил. Мне интересно, как именно Вы это реализовали. Например, я слышал, что в MSP есть возможность сделать "двойной" таймер, т.е. в одном таймере задать два времени срабатывания.

_2. Если не ошибаюсь, имеется возможность отправить целиком буфер с помощью HAL_UART_Transmit_DMA. Что можете сказать по этому поводу?
  1. Вероятно должен, но в freeModbus контролируют только 3.5t интервал. В porttimer.c мы как раз инициализируем таймер, который занимается контролем этого интервала. FreeModbus перезапускает таймер каждый раз, когда приходит новый байт, и начинает обрабатывать сообщение, когда таймер обнуляется.
    По поводу "двойного" таймера: знаю, что есть многоканальные, не расскажу, потому что не работал с ними.

  2. Официальная документация на HAL описывает 3 способа взаимодействия с периферией:
    1. Polling
    2. Interrupt
    3. DMA

    И во всех случаях можно и нужно передавать буферы данных.
    Если интересует конкретно DMA, то я, пока, его не использовал, но там идея в том, что мы пишем данные в память, а потом DMA-контроллер сам скармливает его периферии. DMA надо отдельно инициализировать (CubeMX справится).
Вот теперь Вы поняли, почему многие так любят Ардуино?
Если я правильно понимаю, Ардуино — это отладочные платы, правильно?
Их сделали, чтобы студенты могли практиковаться, или для создания прототипов.
По поводу костыля. Вы обрабатываете событие TXE передатчика, поэтому линия DE деактивируется в момент начала передачи последнего байта, а не тогда, когда этот байт уже целиком «выдвинется». Поэтому либо Ваш костыль, либо после записи последнего байта нужно ждать события TC (transmission complete).
Я не совсем понял. Я же сам управляю направлением передачи, нет?
По поводу TC: я смотрел исходники HAL, и там TC вызывается программно. Или есть возможность получить это прерывание аппаратно?
Но идея мне понравилась, можно перенести переключение направления передачи прямо в обработчики прерываний и посмотреть что получится.
Бит TC в регистре статуса, прерывание от него маскируется битом TCIE в USART_CR1.
Вызов pxMBFrameCBTransmitterEmpty() в UART_IRQ_Handler() при передаче последнего байта вызывает vMBPortSerialEnable( TRUE, FALSE ), что выключает DE в то время как этот байт только начал передаваться по линии.
Далее нас интересуют макросы ENTER_CRITICAL_SECTION( ) и EXIT_CRITICAL_SECTION( ). Заменяем их на __disable_irq() и __enable_irq() соответственно.

С таким можно отгрести, если произойдёт вложенный вызов. У меня (Bare-metal ARM926E-JS, правда ещё ThreadX есть, но временами и прерывания гасить нужно) сделано с сохранением прерывания и при выходе из критической секции восстанавливается исходное. Т.е. если оно было и так выключено — оно выключенным и останется:

// Declare interrupt save area
#define INTR_SAVE_AREA unsigned int _interrupt_save, _interrupt_temp

// Disable iterrupts and save old status register
#define INTR_DISABLE __asm volatile (\
    "mrs %0, CPSR\n\t" \
    "orr %1, %0, #128\n\t" \
    "msr CPSR_c, %1" \
    : "=r"(_interrupt_save), "=r"(_interrupt_temp) /* output  */ \
    : /* input   */ \
    : /* clobber */ \
    )

// Restore status register
#define INTR_RESTORE __asm volatile (\
    "msr CPSR_c, %0" \
    : /* output  */ \
    : "r"(_interrupt_save)/* input   */ \
    : /* clobber */ \
    )

В вашем случае нужно совместить INTR_SAVE_AREA и INTR_DISABLE. Правда как оно будет выглядеть без C99 (при объявлении переменной не в начале функции) и при наличии goto — нужно думать.

Кстати, попытался поискать как используются эти вызовы и наткнулся: http://we.easyelectronics.ru/Yanichar/portiruem-freemodbus-rtu-na-primere-stm8l.html, в прочем вышеописанная проблема там тоже присутствует, на что так же указали в комментарии.

Вот ещё описание портирования: http://ctrl-v.biz/blog/6 — на STM32F4 (плата STM32F4Discovery). Похоже, что всё изобретено до нас :)
По поводу вложенных прерываний: да, я знаю, читал про это, но в этой библиотеке нет вложенных вызовов, поэтому простительно. Они могли бы быть, если бы мы вызывали функцию отключения прерываний внутри файлов портирования библиотеки, но там я не нашел мест, где это нужно делать. Думаете стоит включить пояснения по этому вопросу в статью?

По поводу "изобретено до нас": я не зря указал в статье, что мы работаем с HAL. HAL снижает порог вхождения в разработку под STM, но на нем можно сделать не все, это же все-таки абстракция. Пришлось немного запарится, и я решил поделиться своим видением: можно использовать HAL, но нужно использовать немного хаков, думаю это будет полезно тем, кому надо "очень срочно" портировать библиотеку и у них нет опыта работы с StdPeriph, который, как заявляют STM, устарел.
По поводу вложенных прерываний

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

я не зря указал в статье, что мы работаем с HAL

А, этот нюанс не уловил (не работал с STM). Тогда просто пожелание — опубликовать статью на easyelectronics.ru — там и людей со знанием дело больше может откликнуться, может что-то ещё вылезет. Я бегло — только вышеизложенное заметил.
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.