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

Итак, в одной разработке мне потребовалось сохранять значительные объемы информации с целью последующей передачи через сеть в обрабатывающий центр. Поскольку полученное устройство предполагало серийное производство, был выбран вариант с применением относительно недорогих компонентов, и, в частности, микроконтроллера как центрального элемента системы. Поскольку в тот момент (середина 2012 года) предложение микроконтроллеров с Ethernet PHY на борту не отличалось разнообразием (да и сейчас положение не намного лучше), был выбран МК фирмы TI семейства Stellaris, конкретно LM3S8962, тем более что отладочная плата для него у меня уже имелась. МК на тот момент относительно новый, активно продвигаемый фирмой TI (это в конце 2013 года она ВНЕЗАПНО перевела всю серию в разряд NRND), и обладающий вполне достаточными для решения данной задачи параметрами. Для хранения информациии был выбран вариант с SD карточкой, в первую очередь из за их доступности и дешевизны, а также потому, что на отладочной плате наличествовало контактное устройство для них, а на поставляемом с платой отладки CD имелись многочисленные примеры, в том числе и для SD карт. Интерфейс к карточке был реализован простейший — SPI, предложенные примеры сходу заработали, принятое решение позволяло обрабатывать полученные данные до написания интерфейса при помощи элементарного переноса карточки из устройства в кард-ридер ПК, так что первоначальная отладка алгоритмов взаимодействия с объектом управления проблем не вызвало, по крайней мере в этой части проекта. Как все понимают, проблемы возникли несколько позже…


Когда алторитмы были отлажены и устройство в целом заработало, начались тестовые прогоны. И тут выясняется, что SD карточка не способна записывать информацию в том темпе, в котором объект управления ее поставляет, причем разница скоростей составляет разы, а с учетом размеров единицы хранения (2.7 мегабайта) создать промежуточный буфер по приемлемой цене не удасться. Переходя к конкретным цифрам, требовалось файл размером 2.7 мегабайта записывать на SD карточку не более, чем за 1.6 секунды, а реально данные записывались 30 секунд, причем карточки были приобретены класса 10, то есть утверждали скорость записи 10 мбайт/сек. Борьба за скорость шла в несколько этапов и противниками оказывались то микроконтроллер, то стандартная библиотека (фирменная от TI между прочим), то, собственно, SD карточки.

Первый этап — исследую тайминги запи��и и сразу же выясняю, что запись различных участков информации идет разное время, причем время записи одинаковых блоков информации существенно (в разы) отличается. Путем экспериментов с различными размерами блоков записи устанавливаю простую закономерность — чем больше блоки информации для записи, тем меньше время записи, отнесенное к ее размеру. Псокольку модули библиотеки поддерживают FAT и записывают информацию посекторно, а переделывать их смысла не вижу, переформатирую карточку на размер сектора 32 кбайт и получаю время записи 14 секунд — 1 очко SD.

Второй этап — проверяю работы SPI интерфейса и обнаруживаю, что он работает на частоте 12.5 мгц, хотя описание позволяет установить частоту передачи до 25 мгц (половина от тактовой частоты процессора 50 мгц). Выясняется, что под��рограмма установки частоты SPI модуля из библиотеки ограничивает максимально возможную частоту значением 12.5 мгц, причем в документации на интерфейсный модуль микроконтроллера подобное ограничение отсутствует.
  i = ROM_SysCtlClockGet() / 2;    if(i > 12500000)    {        i = 12500000;    }

Изменяем код и получаем уменьшение времени записи в 2 раза до 7 секунд — 1 очко TI.

Третий этап — исследую модули обмена с SD карточкой и обнаруживаю весьма непроизводительное расходование времени в низкоуровневых процедурах, а именно: модуль SPI в микроконтроллере имеет в своем составе FIFO буфер на 8 байт, что позволяет ускорить работу с ним. Модуль вывода до передачи очередного байте проверяет флаг «буфер передачи не полон» для ожидания возможности переслать следующий байт, и вроде бы все нормально. Но вслед за передачей байта вызывается модуль приема байта (дело в том, что при передаче в интерфейсе SPI одновременно производится и прием), который должен выбрать из приемного буфера эти ненужные принятые байты. И вот эта процедура опрашивает флаг «буфер приема не пуст», то есть ожидает окончания сериализации последнего байта буфера. То есть ждет, пока не будет полностью передан текущий байт и лишь потом готовит следующий для передачи.
void xmit_spi(BYTE dat) {
   uint32_t ui32RcvDat;
   SSIDataPut(SDC_SSI_BASE, dat); /* Write */
   SSIDataGet(SDC_SSI_BASE, &ui32RcvDat); /* flush data */
}

Исправляю обнаруженую ошибку (а как это еще назвать ?) и получаю время передачи файла 3 секунды — 1 очко TI.
И вот что получилось в результате оптимизации, не учитывающей особенности задачи.
static void xmit_spi_my (BYTE const *dst, int length)
{  int i, *p, *d;
  d=(int*)(SDC_SSI_BASE+SSI_O_DR);
  p=(int*)(SDC_SSI_BASE+SSI_O_SR);
  do {
    while (!(*p & SSI_SR_TNF)) {}
    *d=*dst++;
  } while (--length);
  while (*p & SSI_SR_RNE) i=*d;
}

Четвертый этап — исследую модули более высокого уровня и выясняю что, поскольку передача данных в интерфейс предусмотрена только из памяти, мне приходится проводить двойную работу — сначала читать поток данных из объекта управления и пересылать в оперативную память микроконтроллера (а это, между прочим, 32 килобайта буфера), а потом из памяти в регистры интерфейса SPI. Пишу свой собственный модуль для передачи данных непосредственно из регистра в регистр, и получаю время записи 1.6 секунды. При этом обращение к своему модулю маскирую внутри стандартного вызова, чтобы файловую система понимала, что переданы 32 килобайта — 1 очко TI.

Пятый этап. Поставленная цель уже достигнута, но процесс оптимизации продолжается по инерции. Исследую еще раз сигналы на интерфейсе и обнаруживаю, что на самом деле передается не непрерывная последовательность тактовых импульсов, а 8 бит данных плюс пауза в 2 такта. Ну хорошо, девятый бит нужен для передачи сигнала синхронизации (не путать с тактовым сигналом), причем мне он совершенно не нужен, но десятый то зачем? Эксперименты с различными режимами SPI привели к получению передаваемого сигнала в реальные 8 бит без пропусков и, соответственно, к времени записи 1.3 секунды — 1 очко Stellaris.

Шестой этап. Вроде бы все хорошо, но совершенно неожидано возникает еще 1 проблема — при потоковой записи множества файлов первые 3 укладываются в требуемый интервал и даже с небольшим запасом, а вот четвертый файл показвает время записи намного большее — до 1.8-2.0 секунд и, соответственно, нарушает последовательность. Пробую очевидное решение, предположив что дело в переходах через страницы записи FLASH памяти, и исключаю эти места из обработки. Теперь начинают долго записываться те файлы, которые раньше записывались хорошо. Многочисленные эксперименты приводят к выводу, что поведение FLASH как то связано с ее особенностями внутренней организации. Я полагаю, что внутренний генератор высокого напряжения для записи ( его существование несомненно) не способен удержать требуемый уровень напряжения при длительных операциях и требует определенного времени на восстановление заряда. При этом общая средняя скорость выдерживается, но мне то нужна не средняя скорость, а мгновенная скорость записи каждого файла. Здесь могло бы выручить введение буфера данных для выравнивания нагрузки, но было найдено другое решение — приобретены SD карточки различных фирм и среди них нашлись те, которые давали постоянное время записи в 1.4 секунды без существенных разбросов. Конкретные названия фирм-производителей карточек называть не буду, чтобы не сочли статью рекламной — 1 очко SD.

Итог — задача решена, устройcтва отгружены потребителю и функционируют без сбоев, общий счет по количеству обнаруженных и исправленных проблем: SD карточки — 2, библиотека от TI — 3, особенности микроконтроллера -1. А из всего вышесказанного можно сделать следующий выводы:
1. С особым вниманием следует относится к имеющимся библиотекам стандартных программ с примерами применения. Они, как правило, функционируют и даже иногда без ошибок, но никоим образом НЕ оптимизированы по производительности. Так что смотрим исходные коды (благо они есть) и творчески модифицируем их. Более того, у меня сложилось мнение, что подобные свободно распространяемые бибилиотеки сознательно сделаны неоптимальными, чтобы стимулировать приобретение их платных аналогов.
2. С осторожностью относимся к спецификациям относительно производительности различных устройств, то есть внимательно читаем спецификации, в каких режимах и какие цифры достигнуты, а не просто смотрим 1-2 цифры параметров и решаем, что нас они устроят.
3. Внимательно читаем документацию на модули микроконтроллеров, пытаемся понять их внутреннее устройство, не забываем про осциллограф для изучения реальных процессов на реальной плате.

И в завершение статьи одно маленькое замечание — решил посмотреть, как обстоят дела в реализации аналогичных процедур в новом пакете поддержки микроконтроллеров типа TIVA-C (TivaWare_C_Series-2.0.1.11577). Ну что можно сказать — традиции не нарушены. Абсолютно все те же грабли лежат все в тех же местах, причем добавились еще одни — теперь функциии вызываются не непосредственно из FLASH памяти, а из так называемой ROM библиотеки с использованием двойного индексирования, что быстродействия не прибавляет. Как говорил Михаил Жванецкий «Или мы будет жить хорошо, или мои произведения всегда будут актуальны». Пока что верно второе.