Pull to refresh

К вопросу о TI

Reading time19 min
Views6K
«Сейчас я покажу вам портрет… Хм… Я предупреждаю вас, что это именно портрет… Во всяком случае, прошу отнестись к нему, как к портрету...

В данном посте пойдет речь о разработке и отладке программ для МК СС1350 в рекомендованной производителем среде разработки CCS. Будут затронуты достоинства (а они есть) и недостатки (а как же без них) вышеупомянутых продуктов. В тексте не будет скриншотов, призванных показать (обведенное кружочком) расположение иконки компиляции в интегрированной среде программирования или выбора файла в директории. Признавая принципиальную возможность статей в подобном стиле, я постараюсь сосредоточиться на концептуальных моментах в надежде, что мой читатель сам сможет разобраться в деталях.

Целью данного опуса, помимо передачи полученного опыта, является попытка возбудить здоровую зависть у отечественных производителей МК, являющихся прямыми конкурентами TI (»на территории страны, где мы с Вами и процветаем") — задача откровенно неблагодарная, но говорят, что капля камень точит.

Сразу подчеркну — речь будет идти только о Windows (более того, только) 7 версии, хотя на сайте TI есть вариант и под Мак и под Линух, я их не пробовал, вполне готов поверить, что там все не так здорово, но зачем думать о плохом (или наоборот, там все великолепно, но тогда зачем завидовать).

Итак, чему нас учит сайт TI — для начала работы с оценочными модулями следует выполнить три необходимых шага:

  1. Купить оценочные модули — выполнено.

    Примечание на полях(Пнп): Вам тоже придется это сделать, поскольку в рассматриваемой среде программирования лично мне (к глубокому сожалению) не удалось найти возможность эмуляции аппаратных средств для отладки, по крайней мере там, где я искал.
  2. Установить среду разработки — скачиваем, запускаем инсталлятор, все получилось. Подключаем оценочный модуль к USB — дрова поднимаются сами и все опять получилось — выполнено. При попытке запрограммировать устройство получаем сообщение о необходимости обновить прошивку, соглашаемся и опять все получилось. В общем писать-то и не о чем, если бы так было всегда и везде ...
  3. Пойти и изучить курс TI SimpleLink Academy 3.10.01 for SimpleLink CC13x0 SDK 3.10 — странное предложение, вроде как меня учить — только портить, но так уж и быть, открываю соответствующую ссылку и тихо обалдеваю — сколько тут всего понапихано.

Здесь мы видим обучающие материалы по работе с драйверами аппаратуры SYS/BIOS и с операционной системой TI-RTOS и по использованию сетевого стека NDK, включая USB, и по использованию беспроводных протоколов и по еще множеству аспектов работы с представителями разнообразных семейств МК, выпускаемых фирмой. И сопровождается все это богатство готовыми к применению примерами, а если еще учесть наличие руководств пользователя и описания модулей, то, пожалуй, больше и пожелать нечего. А ведь еще есть и утилиты, облегчающие работу по подготовке и конфигурированию программного кода, прошивке и отладке различными способами, и это богатство тоже достаточно документировано.

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

Ладно, пока отложим изучение материалов на потом, будем все постигать «по ходу дела нутром самородка» и смело открываем CCS. Здесь реализована концепция рабочих пространств, полученная от родителя — Eclipse. Лично мне ближе концепция проекта, но нам никто не мешает держать в пространстве ровно один проект, так что идем дальше.

А вот дальше дела становятся несколько хуже — открываем рабочее пространство (РП) для нашей отладочной платы и видим множество проектов (как правило, в двух вариантах — под РТОС и для «голого железа»). Как я сказал ранее, это не криминал, но вот то, что многие проекты содержат одинаковые файлы с идентичными программными модулями — это совсем не здорово. Код многократно дублируется и поддержка изменений становится весьма нетривиальной задачей. Да, при таком решении намного проще переносить проект, просто скопировав директорию, но для подобных вещей есть экспорт проекта, и он вполне себе неплохо реализован. Ссылки на файлы в дереве проекта поддерживаются адекватно, так что решение с включением самих файлов в предоставляемых примерах нельзя считать удовлетворительным.

Продолжаем исследования — начнем работу с готовым проектом, но не мигание светодиодом, хотя их на отладочной плате целых два, а работу с последовательным портом, готовый пример uartecho. Создаем новое РП, включаем в него интересующий нас проект и… ничего не получается, из сообщения ясно, что требуется включить в РП связанный проект. Не очень понятно, зачем это делать, но выполнить требования среды нетрудно, после чего проект начинает собираться.

Пнп: на домашней машине я воспользовался командой «Импорт проекта» и все необходимые включения произошли сами по себе. Где именно указываются связанные проекты, я не знаю, оставим разбор данного аспекта на будущее.

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

Пнп: зато я нашел опции включения проверки на соответствие MISRA-C.

Пнп: другой способ — воспользоваться командой «Clean...» с последующей сборкой, команда «Build All» почему-то на связанный проект не влияет.

Далее обнаруживаем, что не всегда и не все отлаживается нормально, иногда мы оказываемся в таких областях машинного кода, для которых компилятор не находит исходников. Поскольку среда программирования предоставляет нам все необходимые для работы файлы — результат работы препроцессора, ассемблерный код и карту линкера (надо только не забыть включить соответствующие опции), обращаемся к последней. Обнаруживаем две области кода программы — начиная с 0х0000. и начиная с 0х1000. (всем хороши 32- разрядные архитектуры, но запись адресов — не их сильное место). Обращаемся к документации на микросхему и выясняем, что внутри имеется область ПЗУ, картированная именно на 0х1000., и в ней расположена встроенная часть библиотек. Утверждается, что использование подпрограмм из нее повышает быстродействие и снижает потребление по сравнению с адресным пространством 0х000. Пока мы осваиваем МК, нас не столь интересуют последние параметры, а вот удобство отладки является определяющим. Отключить использование ПЗУ можно (а для наших целей нужно), задав компилятору опцию NO_ROM, что мы делаем и пересобираем проект.

Пнп: весьма забавно выглядит переход на подпрограмму в ПЗУ — в системе команд нет длинного перехода, поэтому сначала выполняется переход с возвратом на промежуточную точку в области малых адресов (0х0000), а уже там лежит команда загрузки PC, параметры которой дизассемблером не распознаются. Что-то мне не верится, будто бы при таких накладных расходах можно выиграть в быстродействии, хотя для длинных подпрограмм — почему бы и нет.

Кстати, интересный вопрос — а чем вообще гарантируется, что содержимое ПЗУ соответствует исходным кодам, любезно представленным фирмой. Я сразу могу предложить механизм встраивания в ПЗУ дополнительных (конечно же, отладочных и сервисных) функций, которые для пользователя — программиста МК будут совершенно не заметны. И лично я не сомневаюсь, что разработчики микросхемы знают и множество других механизмов, реализующих подобный функционал, но закончим приступ паранойи.

С другой стороны, я могу только приветствовать появление подобного аналога BIOS, ведь в перспективе это позволит сделать реальностью мечту разработчиков о настоящей переносимости кода между разными семейства МК с одним ядром. Отметим также особенность реализации взаимодействия со «вшитыми» программными модулями. Если в ранних попытках создать подобный механизм, реализованных в моделях TivaC, имел место супервизор вызовов, к которому обращались с номером группы и номером точки входа в подпрограмму, что вызывало значительный оверхед, то здесь разрешение связей идет на уровне линкера за счет двойных наименований функций и вставлены прямые длинные переходы на подпрограммы в ПЗУ. Это намного быстрее в исполнении, но требует перекомпиляции проекта при изменении модели использования.

Теперь, когда мы полностью готовы к удобной отладке, возвращаемся к нашему проекту и начинаем спокойно отлаживать программу с доступом к исходным кодам модулей (ну это я так думал ...), что позволит нам составить свое мнение о качестве этих текстов. Исследуемый проект осуществляет зеркало последовательного канала связи и исключительно удобен для целей обучения. Разумеется, мы взяли вариант с применением РТОС, я не вижу ни малейших оснований не использовать ее в нашей конфигурации (много памяти и памяти программ).

Сразу отметим, что исходные коды представлены на языке С, часто это не слишком удобно, многие языковые конструкции выглядят громоздко, по сравнению с аналогами на плюсах, но создателей больше волновала совместимость кода, нежели синтаксический сахар. Хотя, можно было бы создать и С++ версию библиотек, условная компиляция известна давно и используется повсеместно, но это влечет за собой дополнительные материальные издержки. Наверняка, руководство фирмы знает, что делает, а мои замечания являются некоей «диванной аналитикой», но мне кажется, что я тоже имею право на свое мнение.

Мне известен и обратный подход, когда библиотека проектируется с использованием новейших средств языка С++, а на вопрос, что делать тем разработчикам, у кого в инструментах используются компиляторы, не отвечающие новейшим спецификациям, следует прекрасный ответ — переходить на новые версии либо не юзать данную библиотеку (я настоятельно рекомендую в таких случаях второй вариант). Мое личное мнение — если мы действительно хотим, чтобы наш продукт использовали (а фирма TI этого явно желает, а не делает библиотеку по принципу «от… валите на фиг от меня, вот вам новый барабан»), то ее подход является безусловно верным.

Исходный текст программы выглядит классически — инициализация железа и программного окружения, создание задач и запуск шедулера в основном модуле, текст задачи в отдельном модуле компиляции. В рассматриваемом примере задача ровно одна — mainThread, назначение не вполне понятно из названия, и еще, что меня несколько смущает — имя файла, содержащего исходный текст, не совпадает с именем функции (uartecho.c — хотя как раз тут имя говорящее) ну да поиск в среде программирования реализован стандартным образом (контекстное меню либо F3 на имени сущности) и с этим проблем нет.

Процесс настройки параметров задачи перед запуском довольно-таки ожидаем:

  1. создаем структуру параметров (локальную, конечно),
  2. присваиваем ей значения по умолчанию,
  3. задаем параметры, отличные от стандартных, и
  4. используем структуру при создании задачи.

Несмотря на вроде как естественность этих операций, не для всех авторов библиотек она очевидна, и я видел разные реализации, в которых не было, например, стадии 2, что приводило к забавному (для постороннего наблюдателя, не для программиста) поведению программы. В рассматриваемом случае все хорошо, единственный возникший вопрос — почему значения по умолчанию не константны, наверное, это наследие проклятого прошлого.

Пнп: в широко известной FREE-RTOS принят несколько иной подход с указанием параметров задачи непосредственно в теле вызова АПИ функции создании задачи. Плюсы и минусы данных подходов следующие:

  1. +позволяет не указывать явно параметры, совпадающие со значениями по умолчанию, +не требует запоминания порядка параметров, -более многословен, -бОльшие затраты памяти, -надо знать параметры по умолчанию, -создает именованный промежуточный объект
  2. — требует указания всех параметров, -требует запоминания порядка параметров, +более компактен, +требует меньше памяти, +не требует именованных промежуточных объектов.

    Есть третий способ, пропагандируемый автором данного поста (в стиле ТУРБО), у которого свой набор
  3. +позволяет не указывать явно параметры, совпадающие со стандартным, +не требует запоминания порядка параметров, -многословен, -бОльшие затраты памяти, -надо знать параметры по умолчанию, +работает в стиля «лямбда», +делает труднореализуемыми стандартные ошибки, -выглядит несколько странновато из за множества правых скобок.

Ну и есть еще четвертый вариант, лишенный каких либо недостатков, но требующий С++ не ниже 14 – облизываемся и проходим мимо.

Начинаем отладку, запускаем программу и открываем один из двух последовательных портов, предоставляемых отладочной платой, в терминальном окне, предоставляемой средой программирования. Какой именно из двух портов (один — отладочный, наверное, второй — пользовательский, номера их можно посмотреть в системе) заранее сказать трудно, иногда младший, иногда старший, хорошо хоть, он не меняется при переподключении платы, поэтому его можно на плате написать. Ну и еще одно неудобство — открытые терминалы не сохраняются вместе с проектом и не восстанавливаются при открытии отладочной сессии, хотя и не закрываются при выходе из нее. Проверяем работу программы и сразу обнаруживаем еще один недостаток — терминал невозможно настроить, он, например, принципиально работает в Unix стиле с закрывающим /r, отвык я от подобного минимализма, хотя никто не мешает нам пользоваться внешней терминальной программой.

Пнп: Отметим еще одну особенность отладки, ну это верно для любой среды разработки — при переключении задачи шедулером мы теряем фокус трассировки, точки останова нам помогут решить эту проблему.

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

Внимательное изучение исходников показывает, что не все уж настолько хорошо. В чем проблема — функция инициализации копирует в нашу управляющую структуру значения по умолчанию из объекта, который лежит в области констант, и это замечательно, но почему-то:

  1. объект глобальный, хотя используется единственной функцией инициализации параметров (в свое время похожая практика обошлась Тойоте в приличную сумму) — ладно, добавить директиву static несложно;
  2. управляющий объект именован, в С красивого решения этой проблемы нет, вернее, решение с анонимным экземпляром есть и я его давал в давнем посте, но множество правых скобок не дает назвать этот вариант по-настоящему красивым, в плюсах есть решение обалденной красоты, но что мечтать о несбыточном;
  3. все поля объекта явно избыточны по разрядности, даже битовые поля (перечисления из двух возможных значений) хранятся в 32-разрядных словах;
  4. перечислимые константы режимов заданы в виде дефайнов, что делает невозможной проверку на этапе компиляции и необходимой — в ран-тайме;
  5. повтор секции бесконечного цикла в разных местах возможных отказов, намного правильнее было бы сделать один (в данном случае пустой) обработчик;
  6. ну и все операции по настройке и запуску задачи можно (и нужно) спрятать в одну функцию или даже макрос.

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

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

Ну и еще одно несколько неожиданное решение — задание возможного количества объектов в перечислении, для последовательных портов и для данной отладочной платы равного 1, в стиле

typedef enum CC1310_LAUNCHXL_UARTName {
    CC1310_LAUNCHXL_UART0 = 0,
    CC1310_LAUNCHXL_UARTCOUNT
} CC1310_LAUNCHXL_UARTName;

Такие решения стандартны для настоящих перечислений, но для описания аппаратных объектов — а я и не знал, что так можно, хотя вполне себе работает. С инициализацией железа покончили, идем дальше.

Мы наблюдаем в запущенной задаче классический бесконечный цикл, в котором читаются данные из последовательного порта функцией
UART_read(uart, &input, 1);
и тут же отправляются обратно функцией
UART_write(uart, &input, 1);
. Войдем в первую из них и видим попытку чтения символов из приемного буфера
return (handle->fxnTablePtr->readPollingFxn(handle, buffer, size))
(как же я ненавижу такие вещи, но в С иначе просто нельзя), проходим глубже и оказываемся в UARTCC26XX_read, а из нее попадаем в реализации кольцевого буфера — функция
RingBuf_get(&object->ringBuffer, &readIn)
. Здесь обычная жизнь переходит в острую фазу.

Сказать, что данный конкретный модуль (файл ringbuf.c) мне не понравился — это не сказать ничего, написано просто ужасно и лично я бы на месте такой уважаемой компании авторов данной части выгнал с позором (можно еще взять меня на их место, но боюсь что уровень зарплат наших индийских коллег меня не устроит), но, наверное, я чего то не знаю. Следите за руками:

1) ре-ролл указателей чтения/записи реализован через остаток от деления

object->tail = (object->tail + 1) % object->length;

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

Пнп: недавно видел описание новой архитектуры М7 в реализации уж и не помню кого, так там почему-то деление 32 на 32 стало выполняться за 2-12 тактов вместо 2-7. Или это ошибка перевода, или… даже и не знаю, что придумать.

2) мало того, этот фрагмент кода повторен более чем в одном месте — макросы и инлайны для слабаков, ctrl+C и ctrl+V рулят, принцип DRY идет лесом,

3) реализован совершенно излишний счетчик заполненных мест в буфере, что повлекло за собой следующий недостаток,

4) критические секции как в чтении, так и в записи. Ну ладно, я еще могу поверить, что авторы данного модуля не читают мои посты на Хабре (хотя для профессионалов в области прошивки такое поведение недопустимо), но с «Книгой мустанга» они должны быть знакомы, там этот вопрос рассмотрен подробно,

5) как вишенка на торте, введен еще индикатор максимального размера буфера, причем с весьма невнятным именем и полностью отсутствующим описанием (последнее относится вообще ко всему модулю). Я не исключаю, что данный параметр может быть полезен при отладке, но зачем тащить его в релиз — нам что, такты процессора вообще девать некуда вместе с оперативкой?

6) при этом полностью отсутствует обработка переполнения буфера (есть возврат -1, сигнализирующей о данной ситуации)- даже в Ардуино она есть, оставим в стороне качество этой обработки, но ее отсутствие еще хуже. Или же авторов вдохновлял известный факт о том, что относительно пустого множества справедливы любые предположения, в том числе и то, что оно не пусто?

В общем, мои замечания полностью соответствуют первой строчке демотиватора на тему ревью кода «10 строк кода — 10 замечаний».

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

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

Обратим теперь внимание на передачу принятых данных в последовательный канал, поскольку при создании объекта ему предоставили только один кольцевой буфер — приемный. И действительно, для передачи символов используется внутренний буфер аппаратной части и при его заполнении вводится ожидание готовности (по крайней мере в блокирующем режиме работы). Не могу не удержаться, чтобы не покритиковать стиль соответствующих функций: 1) в объекте почему-то имеется обобщенный указатель, который внутри функции постоянно превращается в указатель на символы
*(unsigned char *)object->writeBuf);
2) логика работы совершенно непрозрачна и слегка запутана. Но все это не столь важно, поскольку остается спрятанным от пользователя и «на максимальную скорость не влияет».

В процессе исследований наталкиваемся еще на одну особенность — мы не видим исходного кода некоторых внутренних функций в режиме отладки — это связано с изменением имен для разных вариантов компиляции (ROM/NO_ROM). Подменить требуемый файл исходного текста (C:\Jenkins\jobs\FWGroup-DriverLib\workspace\modules\output\cc13xx_cha_2_0_ext\driverlib\bin\ccs/./../../../driverlib/uart.c--) мне не удалось (но я не очень то и старался), хотя исходник я нашел (естественно, в файле в файле uart.c, спасибо, капитан), по счастью, этот фрагмент несложен и однозначно отождествить ассемблерный код с исходным текстом на С несложно (особенно если знать особенности команды ITxxx). Как решать эту проблему для библиотек со сложными функциями пока не знаю, будем думать, когда возникнет необходимость.

Ну и напоследок небольшое замечание — я готов поверить, что аппаратная часть реализации последовательного канала для моделей МК СС13х0 совпадает с таковой для СС26х0, и дублирование содержимого файла с названием UARTCC26XX.c--- нельзя назвать правильным решением, но создание промежуточного файла определений с включением исходного файла, переопределением функций и соответствующим комментарием приветствовал бы, поскольку это сделало бы программу более понятной, а это всегда должно приветствоваться, воут.

Итак, тестовый пример работает, мы узнали многое о внутреннем устройстве стандартных библиотек, отметили их сильные и не очень стороны, в заключение обзора попробуем найти ответ на вопрос, который обычно волнует программиста в дилемме «ОС или не ОС» — время переключения контекста. Здесь возможны два пути: 1) рассмотрение исходного кода — это скорее теоретический путь, он требует такого уровня погружения в предмет, который я продемонстрировать не готов, и 2) практический эксперимент. Конечно, второй метод, в отличие от первого, не дает абсолютно верных результатов, но «истина всегда конкретна» и полученные данные вполне можно рассматривать как адекватные, если измерения организованы правильно.

Для начала, чтобы оценить время переключения, нам необходимо научиться оценивать вообще время исполнения различных фрагментов программы. В рассматриваемой архитектуре имеется модуль отладки, частью которого является системный счетчик. Информация о данном модуле вполне доступна, но дьявол, как всегда, прячется в деталях. Для начала попробуем настроить необходимый режим ручками напрямую через доступ к регистрам. Быстро находим блок регистров CPU_DWT и в нем обнаруживаем как собственно счетчик CYCCNT, так и управляющий регистр к нему CTRL с битом CYCCNTENA. Естественно, или, как еще говорят, разумеется, ничего не получилось и на сайте ARM есть ответ на вопрос, почему — необходимо разрешить работу модуля отладки битом TRCENA в регистре DEMCR. А вот с последним регистром не все так просто — в блоке DWT его нет, в других блоках искать лениво — они достаточно длинные, а никакого поиска по имени в окне регистров я не нашел (а было бы неплохо его иметь). Идем в окно памяти, вводим адрес регистра (он нам известен из дата) (кстати, почему-то шестнадцатеричный формат адреса не является дефолтным, надо ручками добавлять префикс 0х) и, внезапно, видим именованную ячейку памяти с именем CPU_CSC_DEMCR. Забавно, если не сказать больше, зачем фирма переименовала регистры по сравнению с предлагаемой лицензиатором архитектуры названиями, наверное, так надо было. И точно, в блоке регистров CPU_CSC находим наш регистр, ставим в нем нужный бит, возвращаемся к счетчику, разрешаем его и все заработало.

Пнп: поиск по имени все-таки есть, вызывается (естественно) комбинацией Ctrl-F, просто он есть только в контекстном меню, а в обычном погашен, приношу извинения разработчикам.

Сразу отмечу еще недостаток окна памяти — печать содержимого прерывается указанием именованных ячеек, что делает выдачу рваной и ни фига не сегментированной по 16 (8,32,64, нужное подставить) слов. Более того, формат выдачи меняется при изменении размера окна. Может быть, все это можно настроить так, как пользователю нужно, но, исходя из собственного опыта (а из чего еще мне исходить) заявляю, что к интуитивно-очевидным решениям настройка формата выдачи окна просмотра памяти не относится. Я полностью за то, чтобы такая удобная фича, как показ именованных областей памяти в окне просмотра, была включена по умолчанию, иначе о ней многие пользователи и не узнали бы никогда, но надо позаботиться и о тех, кто сознательно ее хочет отключить.

Кстати, я бы совсем не отказался от возможности создания макросов (или скриптов) по работе со средой, ведь такую настройку регистров (для включения измерения времени) приходилось делать всякий раз после сброса МК, поскольку корректировать код путем вставления манипуляций с регистрами для целей отладки считаю не очень правильным. Но, хотя макросов я так и не нашел, работу с регистрами можно сильно упростить за счет того, что отдельные (необходимые) регистры можно включить в окно выражений, и тем самым существенно облегчить и ускорить работу с ними.

Чтобы подчеркнуть, что чувство инженера к семейству МК не охладело (а то я все ругаю разные аспекты среды разработки), отмечу, что счетчик работает прекрасно — никаких лишних тактов ни в одном из режимов отладки я обнаружить не смог, а раньше такое явление имело место быть, по крайней мере в серии МК разработки LuminaryMicro.

Итак, намечаем план эксперимента по определению времени переключения контекста — создаем вторую задачу, которая будет инкрементировать некий внутренний счетчик (в бесконечном цикле), запускаем МК на определенное время, находим соотношение между системным счетчиком и счетчиком задачи. Далее запускаем МК на аналогичное время (не обязательно точно такое же) и вводим 10 символов в темпе приблизительно раз в секунду. Можно ожидать, что при этом произойдет 10 переключений на задачу эха и 10 переключений обратно на задачу счетчика. Да, эти переключения контекста будут производится не по таймеру шедулера, а по событию, но на общее время исполнения исследуемой функции это не должно повлиять, так что начинаем претворять план в жизнь, создаем задачу счетчика и запускаем ее.

Тут же обнаруживаем одну особенность РТОС, по крайней мере в стандартной конфигурации — она не является вытесняющей «по настоящему»: если более приоритетная задача постоянно готова к выполнению (а задача счетчика такова) и не отдает управление шедулеру (не ожидает сигналов, не засыпает, не блокируется по флагам и т.д.) то ни одна задача более низкого приоритета не будет исполняться от слова совсем. Это не Линукс, в котором применяются различные методы для гарантии получения кванта всеми, «и чтобы никто не ушел обиженным». Данное поведение вполне ожидаемо, многие легкие ОСРВ ведут себя так, но проблема оказывается глубже, поскольку управление не получают и задачи равного с постоянно готовой приоритета. Именно поэтому в рассматриваемом примере я ставлю задаче эха, которая входит в ожидание, приоритет выше, чем постоянно готовой задаче счетчика, иначе последняя захватит все ресурсы процессора по времени.

Начинаем эксперимент, первая часть (просто ожидание времени исполнения) дала данные отношения счетчиков 406181к/58015к = 7 — вполне ожидаемо. Вторая часть (с 10 последовательными символами в течении ~10 секунд) дает результаты 351234к-50167к*7 = 63к/20=3160 тактов, последняя цифра и есть время, связанное с процедурой переключения контекста в тактах МК. Лично мне данная величина кажется несколько большей, чем ожидалось, продолжаем исследования, похоже, что есть еще какие-то действия, которые портят статистику.

Пнп: частая ошибка экспериментатора — не оценить предварительно ожидаемые результаты и поверить в полученный мусор (привет разработчикам 737).

Очевидно («ну да, совершенно очевидно»), что в полученном результате, помимо времени собственно переключения контекста, содержится еще и время, необходимое для выполнения операций чтения символа из буфера и вывода его в последовательный порт. Менее очевидно, что в нем есть еще и время, необходимое для обработки прерывания при приеме и помещения символа в приемный буфер). Как нам отделить кошку от мяса — для этого у нас есть хитрый трюк — останавливаем программу, вводим 10 символов и запускаем ее. Можно ожидать (надо бы посмотреть исходники), что прерывание по приему произойдет только 1 раз и сразу все символы будут пересланы из приемного буфера в кольцевой, значит и мы увидим меньшие накладные расходы. Определить время выдачи в последовательный порт также несложно — будем выводить каждый второй символ и решим получившиеся 2 линейных уравнения с 2 неизвестны. А можно и еще проще – ничего не выводить, что я и сделал.

И вот результаты подобных хитрых манипуляций: делаем ввод пакетом и пропавших тактов становится меньше – 2282, отключаем вывод и затраты падают до 1222 тактов – уже лучше, хотя я надеялся тактов на 300.

А вот со временем выполнения считывания ничего подобного придумать не удастся, оно масштабируется одновременно с искомым временем переключения контекста. Единственное, что я могу предложить — это отключать внутренний таймер при начале ввода принятого символа и снова включать его перед входом в ожидание очередного. Тогда два счетчика будут работать синхронно (за исключением переключения) и его легко можно определить. Но такой подход требует глубокого внедрения в тексты системных программ и все равно составляющая обработки прерываний останется. Поэтому я предлагаю ограничиться уже полученными данными, которые позволяют твердо утверждать, что время переключения задач в рассматриваемой TI-RTOS не превосходит 1222 тактов, что при заданной тактовой частоте составляет 30 мксек.

Пнп: все равно очень много – я рассчитывал тактов на 100: 30 на сохранение контекста, 40 на определение готовой задачи и 30 на восстановление контекста, а у нас получается на порядок больше. Хотя сейчас отключена оптимизация, включим –o2 и посмотрим результат: почти не изменился – стало 2894 вместо 3160.

Есть еще одна идея — если бы в ОС поддерживалось переключение равноранговых задач, то можно было бы запустить две задачи со счетчиками, некоторым волшебным образом получить данные о количестве переключений за некое время и рассчитать убыль системного счетчика, но в силу особенностей шедулера, о которой я уже говорил, данный подход не приведет к успеху. Хотя возможен другой вариант — делать пинг-понг между двумя одноранговыми (или даже разноранговыми) задачами через семафор, здесь легко посчитать число переключений контекста — надо будет попробовать, но это уже завтра.

Традиционный опрос в конце поста на этот раз будет посвящен не уровню изложения (любому непредвзятому читателю ясно, что он вне всяких похвал и превосходит любые ожидания), а теме очередного поста.
Only registered users can participate in poll. Log in, please.
Вы предпочтете либо:
54.17% продолжить знакомиться с средой от TI и TI-RTOS,13
37.5% описание отладочных плат от TI для данного семейства9
45.83% увидеть обзор отечественной РТОС МАКС (если Вы думаете, что ТИ я ругал, то поймете, что ошибались, я и беличьим хвостиком гладил)11
24 users voted. 12 users abstained.
Only registered users can participate in poll. Log in, please.
Вы предпочтете либо:
80% продолжить знакомиться с средой от TI и TI-RTOS,4
60% описание отладочных плат от TI для данного семейства3
0% увидеть обзор отечественной РТОС МАКС (если Вы думаете, что ТИ я ругал, то поймете, что ошибались, я и беличьим хвостиком гладил)0
5 users voted. 4 users abstained.
Tags:
Hubs:
+14
Comments11

Articles

Change theme settings