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

RTOS уже нами портирована , теперь подключим устойчивую к сбоям файловую систему exFAT, стек TCP/IP вместе с прикладными  протоколами HTTP, MQTT, SNTP, DNS, mDNS, FTP, Telnet , стек USB в режимах девайса и хоста с классами CDC ACM, CDC ECM, RNDIS, Storage, HID, движок логгера, движок параметризации на базе JSON кодировки, отладочные  мониторы, движок FreeMaster.    

Стек сетевых протоколов TCP/IP нам здесь нужен, потому что он, во-первых, значительно облегчает дистанционную отладку устройства. Во-вторых, сеть упрощает групповое управление и масштабирование количества устройств в одной системе управления. И в-третьих, таким образом упрощается интеграция в стороннее ПО для программирования и управления оборудованием  такое как: Eclipse 4diac, MATLAB, Node-RED, openHAB  и т.п. Еще одна неочевидная причина в том, что переходник USB-Ethernet обеспечивает гальваническую развязку. И эта развязка есть в любом устройстве с разъёмом Ethernet. Про интеграцию такого переходника в Azure RTOS написано здесь.

Начинается работа с того, что почти все нужные компоненты выбираются в окне конфигурации Synergy Software Package (SSP) в IDE e² studio

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

Окно конфигурации промежуточного ПО в SSP
Окно конфигурации промежуточного ПО в SSP

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

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

Итак, после авто генерации исходников из SSP получаем 2125 файлов с исходниками. Самое ценное в них - это некоторые особо сложные драйвера такие как:  SDIO, USB, DMA, аппаратная криптография, графический ускоритель. 

Большая часть исходников будет однако промежуточным софтом Azure RTOS. В SSP идёт своя версия Azure RTOS, она отличается от той что в репозитарии в основном структурой директорий и форматом хедеров с конфигурационными константами.  К ядру RTOS идёт свой оригинальный порт для семейства Synergy. На момент написания статьи релиз ядра RTOS в репозитарии Azure RTOS был 6.20, а релиз ядра в SSP 2.4.0 был версии 6.1.9. Отставание хоть и незначительное, но изменения в API не дадут так просто заменить одно другим.

Структура директорий после генерации проекта из SSP
Структура директорий после генерации проекта из SSP

А теперь уберём лишнее и добавим нужное.

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

Здесь потребуется тщательное изучение исходников и оценка своих возможностей. Полность избавиться от HAL очевидно невозможно в обозримой перспективе. Но сделать прямой доступ к  портам довольно тривиально. Для этого надо просто прямо обращаться к портам. Например, чтобы управлять светодиодом объявляем название логического сигнала светодиода

#define     RED_LED            R_PFS->P504PFS_b.PODR

А далее включаем RED_LED = 1 или выключаем RED_LED = 0 светодиод. Проще чем в Arduino. Как показал анализ, конфликтов с HAL тут не возникнет. Только дополнительно надо оставить пустой структуру 

const ioport_cfg_t g_bsp_pin_cfg =
{
  .number_of_pins = 0,
  .p_pin_cfg_data =0,
};

Эта та структура, которую HAL использует для начальной инициализации пинов. Полностью её удалять нельзя, поскольку тогда откажутся работать драйверы  SPI, I2C в составе HAL. Но и начальная инициализация от HAL нам тоже не нужна. У нас есть свой более удобный вариант. Поэтому выбран такой компромисс. 

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

#define IOPORT_CFG_PARAM_CHECKING_ENABLE (0)

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

Словом вот такие операторы можно встретить в моем коде для открытия доступа к регистрам 

   // Разрешаем запись в регистры пинов
    R_PMISC->PWPR_b.BOWI  = 0;
    R_PMISC->PWPR_b.PFSWE = 1;


   R_SYSTEM->PRCR = 0xA50B; // Открываем доступ на запись к регистрам

Далее очень важно правильно запустить движок отладочной трассировки в чипе. Это делается следующими командами:

trckcr = *((uint8_t*)(TRCKCR_REG));
trckcr = 0x80;
*((uint8_t*)(TRCKCR_REG)) = trckcr; // Разрешаем трассировщик с частотй 120 МГц

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

Самое сложное в HAL от  Synergy - это организовать прерывания.

HAL Synergy более сложный чем, например, HAL STM32. Разработчики  из Renesas решили сильнее абстрагироваться от железа. Ввели файловую метафору сродни Линуксу, и сопроводили организацию прерываний дополнительными информационно-управляющими структурами специальным образом размещаемыми линкером. 

В архитектуре Synergy нет жёстко закреплённых за периферией номеров прерываний. Любому сигналу прерывания от периферии можно задать любой номер в NVIC и соответственно вектор. Номера прерываниям задаются в периферийном блоке  под названием Interrupt Controller Unit (ICU) на этапе старта прошивки. 

Структура ICU
Структура ICU

Блок ICU, как видно из схемы, стоит между периферией и непосредственно контроллером прерываний NVIC ядра ARM. По своей сути гибкое и удобное  решение программисты SSP превратили в непроходимый квест. 

Первая проблема начинается с того что вектора объявляются в SSP тремя разными макросами  SSP_VECTOR_DEFINE, SSP_VECTOR_DEFINE_CHAN, SSP_VECTOR_DEFINE_UNIT. Какой макрос применить для конкретного сигнала прерывания зависит от типа периферии. Периферия может быть по логике программистов Renesas одиночная, мультиканальная и мульти юнитовая. Чем отличаются каналы и юниты периферии даже не спрашивайте. 

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

#define SSP_VECTOR_DEFINE_UNIT(isr,ip,unit_name,signal,channel) \
    void isr (void); \
    static void * gp_ctrl_##ip##_##unit_name##_##channel##_##signal; \
    const ssp_vector_t g_vector_##ip##_##unit_name##_##channel##_##signal \
        BSP_PLACE_IN_SECTION_V2(".vector."#ip"_"#unit_name"_"#channel"_"#signal )=isr; \
    const ssp_vector_info_t g_vector_info_##ip##_##unit_name##_##channel##_##signal  \
        BSP_PLACE_IN_SECTION_V2(".vector_info."#ip"_"#unit_name"_"#channel"_"#signal)= \
        {.event_number=ELC_EVENT_##ip##unit_name##_##signal, \
        .ip_id = SSP_IP_##ip, .ip_channel=(channel), .ip_unit=SSP_IP_UNIT_##ip##unit_name, \
        .ip_signal=SSP_SIGNAL_##ip##_##signal, .pp_ctrl = &gp_ctrl_##ip##_##unit_name##_##channel##_##signal};

Это превращается вот в такой код после обработки препроцессором

// Объявление функции обслуживания прерываний
void                      usbfs_int_isr (void);

// Переменная предназначенная для хранения указателя на структуру с управляющей информацией драйвера
static void              *gp_ctrl_USB_FS_0_INT;  

// Непосредственно вектор т.е. адрес перехода на процедуру обслуживания прерывания. Переменная будет размещена в секции .vector.* 
const ssp_vector_t        g_vector_USB_FS_0_INT      __attribute__ ((section(".vector.USB_FS_0_INT")))      __attribute__ ((__used__))=usbfs_int_isr;

// Структура с описанием типа прерывания. Переменная будет размещена в секции .vector_info.*
const ssp_vector_info_t   g_vector_info_USB_FS_0_INT __attribute__ ((section(".vector_info.USB_FS_0_INT"))) __attribute__ ((__used__))=
{
  . event_number = ELC_EVENT_USBFS_INT,
  . ip_id        =  SSP_IP_USB,           // Идентификатор из энумератора e_ssp_ip
  . ip_channel   = (0),
  . ip_unit      = SSP_IP_UNIT_USBFS,     // Идентификатор из энумератора e_ssp_ip_unit 
                                          // (там есть только USB)
  . ip_signal    = SSP_SIGNAL_USB_INT,    // Идентификатор из энумератора e_ssp_signal
  . pp_ctrl      = &gp_ctrl_USB_FS_0_INT  // Ссылка на объявленную ранее переменную содержащю
                                          //  указатель на управляющую информацию драйвера
};;

Далее начинается интересное. В командном файле линкера вида *.icf (в нашем случае r7fs5d97e3a01cfp.icf )  определяются такие именованные секции 

define block VECT_INFO         { ro section .vector_info.*  };
keep                           { section    .vector*        };
place at start of VECT_region  { ro section .vectors        };
place in VECT_region           { ro section .vector.*       };
place at start of FLASH_region { block VECT_INFO            };

Здесь секция .vectors предназначена для размещения стандартных векторов ядра ARM от Reset до SysTick. А в секции .vector.* будут размещены адреса переходов на обработчики остальных прерываний которые. Из примера выше туда попадёт переменная g_vector_USB_FS_0_INT  указывающая на обработчик usbfs_int_isr. 

В секцию .vector_info.* попадёт переменная  g_vector_info_USB_FS_0_INT , она содержит структуру с описанием типа прерывания и указателем на сопутствующие данные.

При выполнении программы происходит следующее:

  • На старте фирмваре процедуры HAL инициализируют ICU из массива переменных хранящихся в секции  .vector_info.*. Там будет именно массив, поскольку при сборке линкер аккуратно все переменные типа  g_vector_info_* будет складывать последовательно в порядке их обнаружения. В регистры каналов прерываний ICU Event Link Setting Register n (IELSRn) последовательно будут записываться идентификаторы периферии вызвавшей данное прерывание.

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

  • Чтобы получить информацию о том что вызвало прерывание процедура обслуживания сначала прочитает из NVIC номер прерывания , а потом по номеру прерывания как по индексу в массиве извлечёт из секции .vector_info.* информацию о периферии и указатель на управляющую структуру

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

Разрываем связи с SSP

В исходниках функций main.c после генерации есть функции tx_startup_common_init и tx_startup_err_callback_internal.  Их назначение придержать запуск основной задачи до тех пор пока не будут выполнены некоторые продолжительные процедуры инициализации. Но на самом деле никаких процедур не выполняется. Возможно они появятся при генерации более сложного фреймворка, но в любом случае такая услуга не стоит внимания и её удаляем из исходников.   

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

Также расчищаем от дебрей макросов и объявлений неиспользуемых переменных файлы common_data и hal_data

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

Корректируем заголовочный файл  S5D9.h

Заголовочный файл S5D9.h содержит точные адреса всей внутренней периферии чипа и описания битов. Надо сказать, что описания битов в виде структур очень удобно.  Потом это сильно упростит жизнь при прямом управлении периферии. Скажем для STM32 такого нет, и там это усложняет дела при отрыве от HAL.

Однако файл S5D9.h пришлось слегка подкорректировать поскольку в нем не было по какой-то причине объявления регистров PFS портов P208, P209, P210, P211, P214

Избавляемся от магических конфигурационных констант

Не все, но некоторые магические константы следует раскрыть и описать подробнее  чтобы не зависеть от генерации в SSP. В файле bsp_mcu_family_cfg.h есть константы записываемые во Flash и управляющие работой некоторых внутренних генераторов , вотчдогами , монитором напряжения. Файл был мной сильно модифицирован, чтобы этими настройками было удобно управлять прямо в этом файле. 

К сведению, сейчас вотчдог выключен! Это нужно для удобства отладки.

Добавляем расширения в RTOS

В Azure RTOS есть макросы, переопределить которые можно внести добавления в управляющие структуры задач. Мы добавляем следующие поля: 

#define TX_THREAD_EXTENSION_0                   ULONG       environment;
#define TX_THREAD_EXTENSION_1                   ULONG       driver;

Это нужно для реализации универсальных драйверов последовательного ввода-вывода. Об этом было написано подробнее здесь

Исправляем ошибки стека USB в Azure RTOS

При генерации исходников в SSP можно заметить, что там выбор классов USB значительно меньше чем есть в репозитарии UsbX Azure RTOS. Поэтому я отказался от исходников SSP и перешёл на UsbX из репозитария.  

Была обнаружена ошибка в файле ux_host_stack_class_interface_scan.c при назначении имён семафорам. Строкам имён семафоров присваивались ссылки на строковую переменную из локального стека функции. Впоследствии содержимое имён затиралось и вместо имён в отладчике выводились хаотические символы. Ошибка видна прямо сразу в отладчике IAR. Поскольку она существует уже пару лет, то можно предположить как мало разработчиков интересуется внутренностями стека USB.  

Чтобы USB композитный девайс корректно работал нужно разрешить макрос UX_DEVICE_COMPOSITE_ENABLE в файле sf_el_ux_dcd_common.h

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

static UINT _USB_initialize_transfer_support(ULONG dcd_io)
{
  UX_DCD_SYNERGY_TRANSFER dcd_transfer;
  dcd_transfer.ux_synergy_transfer_tx =(transfer_instance_t)&usb_transfer_tx; //  NULL; //
  dcd_transfer.ux_synergy_transfer_rx =(transfer_instance_t *)&usb_transfer_rx; //  NULL; //
  return (UINT)ux_dcd_synergy_initialize_transfer_support(dcd_io, (UX_DCD_SYNERGY_TRANSFER *)&dcd_transfer);
}

Скорость передачи файлов  по USB с использованием DMA может вырасти приблизительно на 40% по сравнению с обменом без DMA. 

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

Стоит отключать макрос TX_ENABLE_EVENT_TRACE , иначе все процедуры прерывания объявленные в исходниках SSP  будут содержать вызовы трассировщика. Программный трассировщик не так удобен как движок ITM поэтому его не используем.

Как выглядит настоящая заплата

Была обнаружена очень коварная ошибка в сетевом драйвере USB ECM . 

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

if (ed->ux_synergy_pipe_index == 2)
{
  hcd_synergy -> ux_hcd_synergy_ed_irq[ed -> ux_synergy_pipe_index] = hcd_synergy -> ux_hcd_synergy_ed_irq[ed -> ux_synergy_pipe_index] & (~(UX_SYNERGY_HC_ED_BRDY));
  return;
}

Иначе при работе Ethernet драйвера периодически пропадали отправляемые пакеты. Связь поддерживалась, но с постоянными пере повторами и сбоями.

Интегрируем fault tolerant exFAT

Да, файловая система в Azure RTOS сертифицирована и ужасно надёжная.

Но! Сервер FTP из репозитария Azure RTOS не работает с exFAT. Пришлось переделать сервер FTP. Реализация находится в файле NXD_exFAT_ftp_server.c

Для подключения к серверу FTP нужно использовать пассивный режим, иначе канал данных не устанавливается. Скорость передачи файлов на FTP в среднем составляет 200 кбайт в сек. Скорость скачивания 730 кбайт в сек. При максимальной оптимизации фирмваре по скорости и в режиме RNDIS. В режиме ECM скорость будет около 520 кбайт в сек. на скачивание. 

В файловой системе exFAT не все функции хорошо работают во всех случаях. Например удаление директории System Volume Information не удаётся, поскольку FileX обнаруживает там записи с  Entry Type = 0xE5 , которых не должно быть в exFAT. Чтобы исправить это поведение карту надо переформатировать на компьютере под exFAT. 

Установка опции FX_ENABLE_FAULT_TOLERANT снижает скорость работы с файловой системой exFAT на SD карте на 50% с  2400 до 1600 кбайт в сек. при тактовой частоте карты 30 МГц и размере файлов 8 кбайт. Файловая система exFAT на запись приблизительно в два раза медленнее работает чем FAT32. Скорость чтения не отличается.

Fault Tolerant файловая система FAT32 c тактовой частотой SD карты 30 МГц работает на запись со средней скоростью 2100 кбайт в сек., скорость чтения 9800 кбайт в сек. для файлов размеров 8 кбайт. Для файлов размером 32768 байт скорости будут соответственно  5400 и 12800 кбайт в сек. При максимальной оптимизации фирмваре по скорости. Без оптимизации для файлов размером 32768 байт скорости будут соответственно  5200 и 12700 кбайт в сек.

Тестирование скорости работы файловой системы

Приведённые выше цифры даны лишь для ориентировки. Для каждой SD карты следует выполнять отдельные тесты для большей уверенности. И для этого в представленном проекте есть отдельный пункт меню для тестирования файловой системы:

Скриншот тестирования скорости записи файловой системы
Скриншот тестирования скорости записи файловой системы

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

Исправления в работе сервиса mDNS Azure RTOS.

Сетевой сервис mDNS нужен для того чтобы в локальной сети не приходилось разыскивать IP адрес устройства, а обращаться к нему по имени записанному в само устройство. В локальных сетях чаще всего действует DHCP сервер и наше устройство при подключении в сеть должно использовать своего DHCP клиента чтобы получить IP адрес. Какой адрес оно получает мы обычно не знаем. Адрес выбирается случайно из пула адресов. Но если в устройстве есть сервис mDNS, то оно громогласно объявит в сеть какое у устройства имя и какой адрес оно получило от DHCP сервера. Это известие получают все локальные компьютеры в сети и могут уже обращаться к устройству по имени а не по IP адресу.

Так вот в стеке NetX Duo есть ужасная ошибка назначения интервала пробинга. Там происходит путаница со знаковыми и беззнаковыми операциями. Из-за этого интервал пробинга может оказаться почти бесконечным. Т.е. в результате сервис mDNS будет упорно молчать. В нашем проекте эта ошибка исправлена.

По ходу обнаружилась неточность в сетевом драйвере SSP работающем через класс USB ECM или USB RNDIS. Там команды NX_LINK_MULTICAST_JOIN и NX_LINK_MULTICAST_LEAVE возвращали статус ошибки и поэтому широкополосные посылки вообще не выходили из устройства.

Исправления в сервисе точного времени Azure RTOS.

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

Стандартный клиент в составе Azure RTOS использует множество конфигурационных констант. Я решил что время полинга лучше задавать пользователю и вывел его установку в параметры API клиента времени.

Настройки клиента точного времени
Настройки клиента точного времени

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

Немного тюнинга

Как всегда не обходится без тюнинга платы. Чтобы с платой работал адаптер USB-Ethernet на интерфейс надо подать +5 вольт из платы. Мы это делаем подпаяв перемычку на VD8

Просто изначально не предполагалось организовывать режим USB хоста в контроллере. Поскольку драйвер USB ECM не был реализован в SSP. Но опыт применения Azure RTOS показал очень хорошую адаптируемость её под различное железо. И хост решено было сделать.

С режима хоста на режим девайса USB можно с помощью настроек.

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

Может показаться, что с таким объёмом софта контроллер будет очень загруженным и медленно работать. Но это не так. Вот какую нагрузку показывает в реальном времени окно клиента протокола FreeMaster. Здесь зелёная линия - это усреднённая нагрузка процессора. Нагрузка в 100% буде на графике иметь значение 1000 (так нужно чтобы не использовать операции с плавающей точкой). Как видно нагрузка не превышает 14%. И даже это вызвано только лишь интенсивной работой клиента FreeMaster, который ведёт опрос устройства каждые 10 мсек. Без этого клиента загрузка процессора не будет превышать 6% при активном хосте USB и работе в сети.

По объёму занятой памяти статистика такая:

  672'258 bytes of readonly  code memory
   64'380 bytes of readonly  data memory
  396'572 bytes of readwrite data memory

Проект находится здесь

Развёртывание продолжается …

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


  1. progchip666
    10.04.2023 06:29

    Может показаться, что с таким объёмом софта контроллер будет очень загруженным и медленно работать.

    Бывает и такое, но в большинстве случаев при использовании RTOS раньше наступают проблемы с RAM, во всяком случае если не используется внешняя RAM