Хабрахабр

Применение Arm Mbed OS. Тонкая настройка

Далее рассказывается: LNDC1
После того как с помощью Arm Mbed OS удалось помигать светодиодом, настало время протестировать и настроить другие важные сервисы.

  • Технология конфигурирование Mbed
  • Почему сложно перейти на C++ в обычных RTOS
  • Как экономить память в RTOS
  • Как организуются прерывания в Mbed OS
  • Чем удобно отлаживать Mbed OS
  • Как избавиться от лишнего слоя абстракции SDK

Продолжаем знакомство с технологией программирования микроконтроллеров семейства MKE18F c использованием ARM Mbed OS

Технология конфигурирования Mbed

локально на компьютере пользователя. Важной составляющей проекта Mbed является система автоматического конфигурирования и сборки, как в online режиме так и в offline, т.е. Затем находящиеся в проекте Python скрипты преобразуют эти файлы в заголовочные файлы, файлы рабочих пространств выбранной пользователем IDE, командные файлы линкеров и прочие вспомогательные файлы.
Но проблема описанного метода в непрозрачности с точки зрения исходных текстов, так как нам очень трудно отследить где и что в исходниках меняет система конфигурирования. Конфигурировать предлагается путем редактирования файлов в формате .json со специальными именами. Как писалось в предыдущей статье, был просто сформирован в online проект для IDE IAR, получена неструктурированная куча файлов в рабочем пространстве IDE, затем выполнена их систематизация и отброшено ненужное. С другой стороны в нашем случае нет никаких мотивов поддерживать способность проекта автоматически переноситься на разные IDE.
Поэтому от такого подхода решено было отказаться. В результате больше не нужно делать конфигурирование через .json файлы и осталось только три конкретных места где находятся параметры влияющие на конфигурацию Mbed:

  • Опции компилятора в среде IDE
  • Командный файл MKE18F512xxx16_flash.icf линкера
  • Заголовочный файл mbed_config.h

Но к счастью большинство из них относится к стекам беспроводных протоколов, которые к данному моменту в проекте не используются. В файле mbed_config.h можно насчитать около 130 дефайнов, что поначалу очень напрягает. Исходно дефайны для разных модулей в файле mbed_config.h расположены хаотично, но после сортировки он выглядит так: Для удобства записи были отсортированы чтобы актуальные разместились вверху.

Открыть

#ifndef __MBED_CONFIG_DATA__
#define __MBED_CONFIG_DATA__ // Configuration parameters
#define MBED_CONF_RTOS_PRESENT 1 // set by library:rtos
#define MBED_ALL_STATS_ENABLED 1 //#define DEVICE_SLEEP=1 Снять комментарий если нужно переводить в SLEEP в случае отсутствия активных задач
#define MBED_CONF_APP_MAIN_STACK_SIZE 1024
#define MBED_CONF_APP_TIMER_THREAD_STACK_SIZE 512
#define MBED_CONF_APP_IDLE_THREAD_STACK_SIZE 512 #define MBED_CONF_PLATFORM_DEFAULT_SERIAL_BAUD_RATE 3000000 // set by library:platform
#define MBED_CONF_PLATFORM_ERROR_ALL_THREADS_INFO 0 // set by library:platform
#define MBED_CONF_PLATFORM_ERROR_FILENAME_CAPTURE_ENABLED 0 // set by library:platform
#define MBED_CONF_PLATFORM_ERROR_HIST_ENABLED 0 // set by library:platform
#define MBED_CONF_PLATFORM_ERROR_HIST_SIZE 4 // set by library:platform
#define MBED_CONF_PLATFORM_FORCE_NON_COPYABLE_ERROR 0 // set by library:platform
#define MBED_CONF_PLATFORM_MAX_ERROR_FILENAME_LEN 16 // set by library:platform
#define MBED_CONF_PLATFORM_POLL_USE_LOWPOWER_TIMER 0 // set by library:platform
#define MBED_CONF_PLATFORM_STDIO_BAUD_RATE 3000000 // set by library:platform
#define MBED_CONF_PLATFORM_STDIO_BUFFERED_SERIAL 0 // set by library:platform
#define MBED_CONF_PLATFORM_STDIO_CONVERT_NEWLINES 1 // set by library:platform
#define MBED_CONF_PLATFORM_STDIO_CONVERT_TTY_NEWLINES 0 // set by library:platform
#define MBED_CONF_PLATFORM_STDIO_FLUSH_AT_EXIT 1 // set by library:platform
#define MBED_CONF_DRIVERS_UART_SERIAL_RXBUF_SIZE 256 // set by library:drivers
#define MBED_CONF_DRIVERS_UART_SERIAL_TXBUF_SIZE 256 // set by library:drivers #define MBED_CONF_EVENTS_PRESENT 1 // set by library:events
#define MBED_CONF_EVENTS_SHARED_DISPATCH_FROM_APPLICATION 0 // set by library:events
#define MBED_CONF_EVENTS_SHARED_EVENTSIZE 256 // set by library:events
#define MBED_CONF_EVENTS_SHARED_HIGHPRIO_EVENTSIZE 256 // set by library:events
#define MBED_CONF_EVENTS_SHARED_HIGHPRIO_STACKSIZE 1024 // set by library:events
#define MBED_CONF_EVENTS_SHARED_STACKSIZE 1024 // set by library:events
#define MBED_CONF_EVENTS_USE_LOWPOWER_TIMER_TICKER 0 // set by library:events #define MBED_CONF_CELLULAR_DEBUG_AT 0 // set by library:cellular
#define MBED_CONF_CELLULAR_RANDOM_MAX_START_DELAY 0 // set by library:cellular
#define MBED_CONF_CELLULAR_USE_APN_LOOKUP 1 // set by library:cellular
#define MBED_CONF_FILESYSTEM_PRESENT 1 // set by library:filesystem
#define MBED_CONF_KINETIS_EMAC_RX_RING_LEN 16 // set by library:kinetis-emac
#define MBED_CONF_KINETIS_EMAC_TX_RING_LEN 8 // set by library:kinetis-emac #define MBED_CONF_LORA_ADR_ON 1 // set by library:lora
#define MBED_CONF_LORA_APP_PORT 15 // set by library:lora
#define MBED_CONF_LORA_APPLICATION_EUI // set by library:lora
#define MBED_CONF_LORA_APPLICATION_KEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora
#define MBED_CONF_LORA_APPSKEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora
#define MBED_CONF_LORA_AUTOMATIC_UPLINK_MESSAGE 1 // set by library:lora
#define MBED_CONF_LORA_DEVICE_ADDRESS 0x00000000 // set by library:lora
#define MBED_CONF_LORA_DEVICE_EUI {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora
#define MBED_CONF_LORA_DUTY_CYCLE_ON 1 // set by library:lora
#define MBED_CONF_LORA_LBT_ON 0 // set by library:lora
#define MBED_CONF_LORA_NB_TRIALS 12 // set by library:lora
#define MBED_CONF_LORA_NWKSKEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00} // set by library:lora
#define MBED_CONF_LORA_OVER_THE_AIR_ACTIVATION 1 // set by library:lora
#define MBED_CONF_LORA_PHY EU868 // set by library:lora
#define MBED_CONF_LORA_PUBLIC_NETWORK 1 // set by library:lora
#define MBED_CONF_LORA_TX_MAX_SIZE 64 // set by library:lora #define MBED_CONF_LWIP_ADDR_TIMEOUT 5 // set by library:lwip
#define MBED_CONF_LWIP_ADDR_TIMEOUT_MODE 1 // set by library:lwip
#define MBED_CONF_LWIP_DEBUG_ENABLED 0 // set by library:lwip
#define MBED_CONF_LWIP_DEFAULT_THREAD_STACKSIZE 512 // set by library:lwip
#define MBED_CONF_LWIP_ENABLE_PPP_TRACE 0 // set by library:lwip
#define MBED_CONF_LWIP_ETHERNET_ENABLED 1 // set by library:lwip
#define MBED_CONF_LWIP_IP_VER_PREF 4 // set by library:lwip
#define MBED_CONF_LWIP_IPV4_ENABLED 1 // set by library:lwip
#define MBED_CONF_LWIP_IPV6_ENABLED 0 // set by library:lwip
#define MBED_CONF_LWIP_MEM_SIZE 36560 // set by library:lwip[Freescale]
#define MBED_CONF_LWIP_PPP_THREAD_STACKSIZE 768 // set by library:lwip
#define MBED_CONF_LWIP_SOCKET_MAX 4 // set by library:lwip
#define MBED_CONF_LWIP_TCP_ENABLED 1 // set by library:lwip
#define MBED_CONF_LWIP_TCP_SERVER_MAX 4 // set by library:lwip
#define MBED_CONF_LWIP_TCP_SOCKET_MAX 4 // set by library:lwip
#define MBED_CONF_LWIP_TCPIP_THREAD_STACKSIZE 1200 // set by library:lwip
#define MBED_CONF_LWIP_UDP_SOCKET_MAX 4 // set by library:lwip
#define MBED_CONF_LWIP_USE_MBED_TRACE 0 // set by library:lwip #define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_CHANNEL 0 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_CHANNEL_MASK 0x7fff800 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_CHANNEL_PAGE 0 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_DEVICE_TYPE NET_6LOWPAN_ROUTER // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_PANID_FILTER 0xffff // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_PSK_KEY {0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf} // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_PSK_KEY_ID 1 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_SEC_LEVEL 5 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_6LOWPAN_ND_SECURITY_MODE NONE // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_HEAP_SIZE 32500 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_CHANNEL 22 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_CHANNEL_MASK 0x7fff800 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_CHANNEL_PAGE 0 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_COMMISSIONING_DATASET_TIMESTAMP 0x10000 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_EXTENDED_PANID {0xf1, 0xb5, 0xa1, 0xb2,0xc4, 0xd5, 0xa1, 0xbd } // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_ML_PREFIX {0xfd, 0x0, 0x0d, 0xb8, 0x0, 0x0, 0x0, 0x0} // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_NETWORK_NAME "Thread Network" // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_PANID 0x0700 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_CONFIG_PSKC {0xc8, 0xa6, 0x2e, 0xae, 0xf3, 0x68, 0xf3, 0x46, 0xa9, 0x9e, 0x57, 0x85, 0x98, 0x9d, 0x1c, 0xd0} // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_DEVICE_TYPE MESH_DEVICE_TYPE_THREAD_ROUTER // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_MASTER_KEY {0x10, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff} // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_PSKD "ABCDEFGH" // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_SECURITY_POLICY 255 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_THREAD_USE_STATIC_LINK_CONFIG 1 // set by library:mbed-mesh-api
#define MBED_CONF_MBED_MESH_API_USE_MALLOC_FOR_HEAP 0 // set by library:mbed-mesh-api #define MBED_CONF_NANOSTACK_CONFIGURATION nanostack_full // set by library:nanostack
#define MBED_CONF_NANOSTACK_HAL_CRITICAL_SECTION_USABLE_FROM_INTERRUPT 0 // set by library:nanostack-hal
#define MBED_CONF_NANOSTACK_HAL_EVENT_LOOP_DISPATCH_FROM_APPLICATION 0 // set by library:nanostack-hal
#define MBED_CONF_NANOSTACK_HAL_EVENT_LOOP_THREAD_STACK_SIZE 6144 // set by library:nanostack-hal
#define MBED_CONF_NANOSTACK_HAL_EVENT_LOOP_USE_MBED_EVENTS 0 // set by library:nanostack-hal
#define MBED_CONF_NANOSTACK_HAL_NVM_CFSTORE 0 // set by library:nanostack-hal #define MBED_CONF_NSAPI_DEFAULT_MESH_TYPE THREAD // set by library:nsapi
#define MBED_CONF_NSAPI_DEFAULT_STACK LWIP // set by library:nsapi
#define MBED_CONF_NSAPI_DEFAULT_WIFI_SECURITY NONE // set by library:nsapi
#define MBED_CONF_NSAPI_DNS_CACHE_SIZE 3 // set by library:nsapi
#define MBED_CONF_NSAPI_DNS_RESPONSE_WAIT_TIME 5000 // set by library:nsapi
#define MBED_CONF_NSAPI_DNS_RETRIES 0 // set by library:nsapi
#define MBED_CONF_NSAPI_DNS_TOTAL_ATTEMPTS 3 // set by library:nsapi
#define MBED_CONF_NSAPI_PRESENT 1 // set by library:nsapi #define MBED_CONF_PPP_CELL_IFACE_APN_LOOKUP 1 // set by library:ppp-cell-iface
#define MBED_CONF_PPP_CELL_IFACE_AT_PARSER_BUFFER_SIZE 256 // set by library:ppp-cell-iface
#define MBED_CONF_PPP_CELL_IFACE_AT_PARSER_TIMEOUT 8000 // set by library:ppp-cell-iface
#define MBED_CONF_PPP_CELL_IFACE_BAUD_RATE 115200 // set by library:ppp-cell-iface #define MBED_CONF_TARGET_NETWORK_DEFAULT_INTERFACE_TYPE ETHERNET // set by target:K66F #define MBED_LFS_BLOCK_SIZE 512 // set by library:littlefs
#define MBED_LFS_ENABLE_INFO 0 // set by library:littlefs
#define MBED_LFS_INTRINSICS 1 // set by library:littlefs
#define MBED_LFS_LOOKAHEAD 512 // set by library:littlefs
#define MBED_LFS_PROG_SIZE 64 // set by library:littlefs
#define MBED_LFS_READ_SIZE 64 // set by library:littlefs #define NSAPI_PPP_AVAILABLE 0 // set by library:lwip
#define NSAPI_PPP_IPV4_AVAILABLE 1 // set by library:lwip
#define NSAPI_PPP_IPV6_AVAILABLE 0 // set by library:lwip #define NVSTORE_ENABLED 1 // set by library:nvstore
#define NVSTORE_MAX_KEYS 16 // set by library:nvstore
// Macros
#define _RTE_ // defined by library:rtos
#define NS_USE_EXTERNAL_MBED_TLS // defined by library:nanostack
#define UNITY_INCLUDE_CONFIG_H // defined by library:utest #endif

Особенности перехода на C++ в применении к RTOS

Но тут есть нюансы о которых необходимо знать.
Использование C++ для RTOS в малых встраиваемых системах еще сравнительная редкость. API верхнего уровня в Mbed написано на C++, поэтому этот язык приходится использовать и в прикладном коде. Причина в стремлении скрыть от пользователя детали низкоуровневого управления ресурсами. Проблема здесь в том, что успешные проекты RTOS стремятся быть мультиплатформенными, а С++ предъявляет повышенные требования к менеджменту ресурсов платформы по сравнению с С. Конструкторы, деструкторы, потоки, исключения c автоматической деструкцией, шаблоны объектов структур данных и др. Речь прежде всего о ресурсах памяти. Но ресурс оперативной памяти RAM в малых системах очень ограничен. используют неявные операции с динамической памятью. В RTOS каждой задаче выделяется стек, его точный размер разработчик заранее спрогнозировать не может и поэтому выбирает с запасом. Оперативная память — самый дефицитный ресурс в таких системах и особенно в RTOS. Еще много памяти нужно для различных парсеров и протоколов (HTTP, HTML...) и файловых систем. Таким образом наличие RTOS с десятком задач сразу вызывает необходимость в RAM размером от 10 до 30 кБ. Чтобы они начали работать в RTOS нужно написать дополнительный код. Если применяется дисплей, то еще более увеличиваются требования к свободной RAM.
Библиотеки сред разработки типа IAR оснащаются неплохими менеджерами динамической памяти, но они рассчитаны на однопоточную среду исполнения. Поскольку там нет неявных операций с динамической памятью на уровне языка, то все операции выполняются явно вызовом собственных потокобезопасных вариантов функций malloc и free. Этот процесс называется retargeting.
В RTOS написанных на C ретаргетинг как правило не производится. Но retargeting в каждой среде разработки процесс сугубо индивидуальный. Программист имеет полный контроль над операциями с динамической памятью и легко может применять все возможные меры по ее экономии.
В случае C++ если мы хотим использовать все возможности этого языка нам придется делать retargeting. Функции __write, __lseek, __read могут пользователем и не реализовываться, но тогда их функциональность остается на усмотрение IDE. Это и усложняет жизнь разработчикам RTOS.
На рисунке ниже пример структуры вызовов с ретаргетингом. И уж точно printf и scanf не будут многопоточными.

Это работать может, но программисту тогда нужно помнить о разных неявных и недокументированных ограничениях при использовании конструкций C++ в IAR(только статические конструкторы, проверять все шаблоны на использование new, отказаться от исключений и т.д.). Mbed одна из немногих если не единственная RTOS, которая предоставляет исходники с уже проделанным ретаргетингом под триаду известных средств разработки: GCC, IAR, Keil
Несмотря на все сказанное выше можно встретить статьи о портировании RTOS на C++ без выполнения ретаргетинга, например решая проблему простой заменой некоторых распространненных стандартных функций на свои. Mbed как система дружелюбная к пользователю снимает многие такие ограничения приближаясь по простоте к Arduino. Это уже будет сложно назвать C++.

Помимо всего в свежих версиях IAR есть трудности перехода на C11 и C++14 о чем написано здесь — https://www.iar.com/support/resources/articles/exploring-c11-and-c14/

Как организуются прерывания в Mbed

Можно обнаружить только класс InterruptIn, который предначзначен только для внешних портов.
Ответы на такие вопросы надо искать в CMSIS-RTOS, а именно в хидерах CMSIS Cortex-M4 Core Peripheral Access Layer. Как ни странно, но найти класс или функцию или что-то подходящее для организации обслуживания прерываний в API Mbed не удастся. Там определены макросы:

  • NVIC_SetVector — устанавливает для заданного вектора процедуру обслуживания прерываний (interrupt service routine, ISR)
  • NVIC_SetPriority — устанавливает приоритет для заданного вектора прерываний.
  • NVIC_EnableIRQ — разрешает вызов прерываний по заданному вектору

Вот как инициализируется организация прерыванияй от таймера SysTick:

NVIC_SetVector(mbed_get_m0_tick_irqn(), (uint32_t)SysTick_Handler); // Здесь функция mbed_get_m0_tick_irqn возвращает номер вектора прерывания от таймера SysTick. // Тут можно было бы сразу вставить имя SysTick_IRQn их заголовочного файла MKE18F16.h // Функция SysTick_Handler является обработчиком прерывания. NVIC_SetPriority(mbed_get_m0_tick_irqn(), 0xFF); // 0xFF здесь назначает минимальный приоритет. Чем больше число тем меньше приоритет. В контроллере NVIC реализованном в семействые MKE18F16 возможны только 16 уровней приоритетов. Т.е. значащими будут 4-е младших бита. NVIC_EnableIRQ(mbed_get_m0_tick_irqn()); // Разрешает вызов обработчика прерывания.

Т.е. Нельзя путать приоритеты задач Mbed и приоритеты прерываний.
Если приоритет не назначается, то по умолчанию он устанавливается максимальным.
Из назначенных таким образом обработчиков прерываний можно вызывать любые сервисы RTOS, которые не вызывают ожиданий. Сервисы вызываются однако не из самого ISR, а путем вызова программного прерываний установкой бита PENDSVSET в регистре Interrupt Control and State Register (ICSR) блока System control block (SCB) ядра Cortex-M. передавать флаги, семафоры, сообщения, майлбоксы и проч. после завершения текущего обработчика прерываний если нет других приоритетных прерываний произойдет вызов системного обработчика по вектору PendSV где и будет проведено обслуживание. Т.е.

Как в Mbed происходит работа с динамической памятью

В нашем проекте Mbed под IAR определение размера этой области памяти производится в файле настроек линкера MKE18F512xxx16_flash.icf с помощью записи в переменную __size_heap__. Динамическая память или иначе куча или heap — обязательный компонент при программировании на C++. Сколько осталось свободной памяти узнаем из .map файла после компиляции, т.е. Размер ставим так чтобы он занял всю оставшуюся свободную память. определение размера heap — процесс итеративный.

Вызов статических конструкторов объектов C++

Даже в RTOS претендующих на серьезность, например МАКС, это обойдено вниманием, т.е. Важным вопросом при использовании C++ является где и как вызываются конструкторы глобальных объектов. Там конструкторами занимается стандартная однопоточная библиотека среды разработки с обычным однопоточным механизмом выделения памяти. пущено на самотек. Это дыра в наших усилиях по экономии памяти и контролю над всем.
В Mbed к данному вопросу подошли гораздо серьезней. Но после старта распространенные RTOS создают свой механизм управления динамической памятью, а память занятая глобальными объектами остается забытой. В IAR это делается так: Там для каждой среды разработки имеется собственный подход.

  • поток инициализации еще до вызова конструкторов перехватывается пользовательским кодом
  • в стандартную библиотеку подставляются методы блокировки потоков из API RTOS
  • перенаправляются стандартные функции new, delete, malloc, free… на обращения к API работы с динамической памятью RTOS.

Они реализованы в файле mbed_boot.c и используют мьютексы OS.
В функции __iar_program_start исполняемой в первых же строчках файла startup_MKE18F16.s происходит инициализация стека и динамической памяти OS с помощью вызова mbed_set_stack_heap Mbed использует адаптеры библиотек IAR для работы в много-поточном режиме
Об адаптации IAR под многопоточность можно почитать здесь — http://supp.iar.com/FilesPublic/UPDINFO/005691/arm/doc/infocenter/DLIBThreadSupport.html
В Mbed адаптированы как системные блокировки (System locks) так и поточные блокировки (File stream locks) библиотеки IAR.

Назначение размеров стеков задач

Например, на стек сильно влияют библиотечные функции для вывода и форматирования строк printf, sprintf, scanf и т.д. Урезание стеков задач до минимума — самый привлекательный вариант экономии RAM.
Чтобы задачи требовали меньше стека применяются разные техники. Если мы откажемся в задаче использовать эти функции, то сможем сократить стек задачи на каких-нибудь добрых пару сотен байт. Они имеют особенность выделять в стеке большие временные области для хранения данных.

Размер стека по умолчанию для них определялся макросами в заголовочном файле mbed_rtx_conf.h. Mbed OS при старте сразу создает три задачи с именами: "main_thread", "timer_thread", "idle_thread". Теперь определения выглядят так: Мы перенесли объявления этих стеков в файл конфигурации mbed_config.h и сократили размер стеков.

  • Стек задачи "main_thread" определяется макросом MBED_CONF_APP_MAIN_STACK_SIZE = 1024 байт
  • Стек задачи "timer_thread" определяется макросом MBED_CONF_APP_TIMER_THREAD_STACK_SIZE = 512 байт
  • Стек задачи "idle_thread" определяется макросом MBED_CONF_APP_IDLE_THREAD_STACK_SIZE = 512 байт

Средства контроля за использованием ресурсов памяти в Mbed

Чтобы видеть насколько использована динамическая память и какова интенсивность запросов к ней, сколько осталось стека у каждой задачи и какова была пиковая загрузка стеков в Mbed есть специальные счетчики. Динамическая память равно как и стек — это ресурсы требующие постоянного внимания. Когда дефайн объявлен, нужно написать свою процедуру для вывода информации пользователю. По умолчанию они отключены директивой условной компиляции, для их включения надо объявить дефайн MBED_ALL_STATS_ENABLED. Мы написали специальную процедуру для вывода статистики в эмулятор терминала VT100, о чем будет рассказано позже.

Прочитать о них можно здесь — https://www.iar.com/support/resources/articles/stack-protection-in-iar-embedded-workbench-for-arm/
Общие вопросы защиты стека от переполнения рассмотрены здесь — https://www.iar.com/support/resources/articles/detecting-and-avoiding-stack-overflow-in-embedded-systems/
и здесь — https://www.iar.com/support/resources/articles/mastering-stack-and-heap-for-system-reliability/ Помимо средств предоставляемых OS среда разработки IAR в последних версиях добавляет новую возможность — стековые канарейки.

Средства отладки и анализа кода Mbed

По настоящему глубоко изучить работу Mbed на новой платформе можно только с использованием JTAG/SWD отладчика.

Просто рассматривая исходники нельзя сказать какие функции работают, а какие нет, куда выполнение программы заходит, а куда не заходит. Исходники Mbed насыщены многоуровневыми макросами и инструкциями условной компиляции. Уже на этапе портирования практически невозможно обойтись без пошаговой отладки.
Я рекомендовал бы отладчики фирмы Segger версии J-Link Pro и J-Link Ultra. Единственным выходом остается подключить отладчик и по шагам анализировать путь выполнения программы. Для отладки систем с жестким реальным временем это немаловажно. Их отличает высокая пропускная способность, в несколько раз выше чем у распространенных дешевых моделей. Помимо пошаговой отладки они способны выполнять лог прерываний, вести статистику прерываний, включая их длительности выполнения, поддерживают технологии отладки RTT и ITM, перехватывают аппаратные исключения и другие функции. При трассировке быстрых событий такие отладчики меньше подвержены переполнениям и требуют меньше отладочных итераций.

Хотя более дорогие трассировщики J-Trace от Segger уже не дают большого преимущества, поскольку серия MKE18F не оснащена специальным трассировочным интерфейсом.
Вторым средством отладки, конечно, является ввод-вывод через UART. Ниже вид окна отладчика IAR при работе через адаптер J-Link.

Экономить не стоит, 90% времени разработки уходит на отладку. В Mbed реализовано как минимум четыре разных технологии доступа к последовательному каналу обмена данными через UART,
Это:

  • перенаправление через класс DirectSerial стандартных функций ввода-вывода языка C/C++
  • класс RawSerial
  • класс Serial
  • класс UARTSerial

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

Сколько мы теряем таймеров портировав Mbed

В прошлой статье было рассказано о проблемах ее портирования. Для мигания светодиодом использовалась функция wait из API Mbed OS. Эти сервисы используют низкоуровневые функции из файла lp_ticker.c. Но это не все, кроме этого Mbed ведет счетчик своей работы ( его можно прочитать функцией mbed_stats_cpu_get ) и есть API таймера. В процессе портирования этот файл был отредактирован для соответствия способам тактирования примененным в микроконтроллере MKE18F512VLL16.
Таким образом порт Mbed полностью захватывает два модуля счетчиков — PIT и LPTMR и таймер ядра SysTick. В этом файле для организации счета времени используется таймер LPTMR из набора периферии Kinetis. Об этом надо помнить планируя ресурсы для прикладной задачи.

Особенности начальной загрузки MKE18F

Но на нашей плате планируется использовать свой защищенный загрузчик, поэтому работа штатного загрузчика нежелательна.
В этом случае у чипов Kinetis надо обращать внимание на содержимое области Program Flash по адресу 0x040D. Чипы семейства MKE18F имеют встроенный ROM с начальным загрузчиком через последовательные интерфейсы: UART, CAN, SPI, I2C. В нашем случае там должна быть записана константа 0x7B, указывающая на старт из Flash памяти, а не из ROM и отключающая функцию NMI на внешнем выводе. Там хранится константа определяющая порядок начальной загрузки. в режиме HRUN запись во Flash невозможна. При ином значении этой константы программа может зависнуть в случайно вызванном из ROM встроенном загрузчике.
Еще важно помнить, что запись во Flash память контроллера возможна только на частоте ядра 120 МГц и не выше, т.е.

Активизируем Watchdog

Чтобы исправить ситуацию этот макрос был стерт и реализована своя инициализация WDOG.
Инициализация Watchdog-а производится в функции SystemInit. Плата нашего контроллера предназначена для промышленного применения и значит без Watchdog-а обойтись нельзя.
Изначально макрос DISABLE_WDOG в файле system_MKE18F16.h был установлен для отключения watchdog-а. Но это легко может произойти, например, при выводе на терминал больших дампов данных. Обновление счетчика watchdog-а сделано в задаче IDLE.
Такой подход требует чтобы более высокоприоритетные задачи не захватывали монопольно процессор более чем на 100 мс. Поэтому везде разбиваем длительные процедуры в задачах с приоритетом больше чем у IDLE на короткие фрагменты перемежаемые паузами c использованием функции wait.

Вопрос драйверов из поставки SDK

Они по идее, должны облегчать программирование сложной для изучения периферии, но к сожалению минимально документированы. Драйвера SDK имеют префикс fsl находятся в директории NXP_MKE18F_drivers и являются своеобразным слоем абстракции периферии. Возникает недоумении для кого тогда они написаны, и как они могут освободить нас от изучения мануалов на периферию микроконтроллере. Вернее их документирование ограничивается комментариями в заголовках функций. Драйвера всего лишь помогают легче переносить программы на разные микроконтроллеры в пределах одного семейства. Мой ответ — никак. Таким образом драйвера SDK решают довольно частную проблему разработчиков самих драйверов, далекую от нужд пользователей начинающих изучать Kinetis.
Драйвера также предназначены для работы на всех чипах семейства т.е. Но чтобы их эффективно применять нужно очень хорошо разобраться с документацией на периферию. Тут приходится исследовать исходный код драйверов. быть универсальными и поэтому насыщены условными макросами и проверками которые для каждого конкретного чипа не несут никакой полезной функции.
Драйвера еще некоторым образом могут помочь лучше понять как следует обращаться с периферией, но после прихода понимания драйвера можно смело игнорировать.
В связи со сказанным не должно удивлять, что в данном проекте обращение к периферии не затронутой портом Mbed идет напрямую минуя драйвера.
Могут однако возникнуть опасения по поводу зависимостей работы той или иной периферии от наличия драйверов SDK. Чтобы этого не случилось нужно отследить и запретить штатным сервисам Mbed использовать DMA при работе с периферией. Главная опасность зависимостей исходит от разделения функций DMA драйверами. Например надо знать какой приоритет назначен прерываниям SysTick, отладочного UART-а, и таймеров. Если DMA остается нетронутым драйверами SDK, то практически все, что не касается 2-х упомянутых модулей таймеров (PIT и LPTMR) и отладочного UART-а можно из директории NXP_MKE18F_drivers выкинуть или игнорировать.
Меньшая опасность, но тоже существенная может исходить от назначения приоритетов задействованной через драйвера SDK периферии. они по умолчанию получают максимальный уровень. В ситуации когда их приоритет равен или больше приоритету периферии используемой в realtime управлении это может привести к деградации качества управления.
Помним, что порт Mbed для MKE18F инициализирует прерывания UART и таймеров без назначения приоритета, т.е.

Теги
Показать больше

Похожие статьи

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

Ваш e-mail не будет опубликован. Обязательные поля помечены *

Кнопка «Наверх»
Закрыть