На графике D0 видно, что каждое 16 число находится на шине на 70% дольше остальных, то есть, в это время происходит инкремент (в реальной программе, где это возможно, естественно, декремент) счетчика и сравнение с условием завершения цикла. В нашем случае ни того, ни другого нет, но это в листинге программы, внутренние структуры все равно инициализируются и проверяются условия. Поэтому чем больше разница этих длительностей, тем больший выигрыш можно получить.
Все контролируемые линии группируются, каждые восемь линий позволяют выводить состояние 8-битной шины в шестнадцатеричном коде (строка B0 для первых восьми входов), при желании можно вывести графическое представление, что удобно, например, если генерируется пилообразное изменение сигнала или синусоида, но пока графическая интерпретация нас не интересует.
Число записываемых отсчетов можно регулировать или поставить на авто (максимально возможное число), оно зависит от выбранной частоты сэмплироания, запись идет во внутреннюю память FPGA.
Поскольку 8-битный параллельный доступ к LCD не имеет практического смысла на данном МК, а старшие биты порта А пока не используются, уберем из цикла операцию чтения
Посмотрим, что получается, заодно поставим самое лучшее разрешение по времени - на верхней шкале цена деления 5 наносекунд:
Временной шаг - 5нс/дел Время вывода в порт сократилось более, чем в три раза. Таким образом, восьмибитная шина обеспечивает пропускную способность более 14 мегабайт в секунду, 16-битная шина — более 28 мегабайт в секунду против максимальной теоретической производительности SPI на STM32 18 мегабит/c. Более, чем десятикратный рост, но об этом уже было сказано в предыдущей заметке.
Обратите внимание, что, по данным B0, на шине после значения единицы появляется не двойка, а кратковременная тройка, и только уже после нее правильное значение. Но эта тройка существует только по данным сниффера, успевающего уловить неодновременность смены состояния порта, и сохраняется на шине менее 10нс, а в данном контексте все, что происходит в пределах такта МК, трактуется им как одновременные события. Поэтому с его точки зрения никакой тройки на шине не было. Поскольку управляющие команды (например, запись в LCD) формирует также МК, то даже и более быстрая периферия никакой тройки не увидит.
Но пропускная способность шины вовсе не означает, что по ней удастся передать именно максимальный поток данных. Мы пока не рассматривали управляющие сигналы, а МК их выдает (или получает) , затрачивая дополнительные такты перед и после появления данных на шине.В то же время в SPI уже «все включено». Но об этом позже.
Чуть быстрее может быть операция вывода на дисплей константы — заполнение одим цветом, в этом случае отпадает необходимость получения данных. Но язык С не позволяет увидеть ни этого, ни узких мест самой реализации конструкции
GPIOA->ODR = х
для потокового вывода.
Без знания ассемблера дальше не обойтись. Вернее, можно обойтись, поставив на этом точку. Характерны при этом «советы» одного участника форума (в самом начале моей ветке про МК), проповедующего исключительность применение языка С для МК. Для многих задач, безусловно, можно обойтись и без этого и вообще не знать системы команд МК. Только при этом можно будет писать только заурядные программы школьного уровня. Знание ассемблера требуется для качественного программирования на языке высокого уровня, а как иначе понять механизм «atomic» операций, формат стековых кадров и многие другие нюансы, влияющие на архитектуру программы? Люди, понимающие в программировании и создающие нормальные программы на «чистом» С, с такими тезисами не выступают.
Специально для таких товарищей приведу фрагмент кода, который генерирует компилятор для вышеприведенной программы (с оптимизацией по ввыводу в порт). К слову будет сказано, что часто на ассемблерный код компилятора без включенной оптимизации без слез смотреть нельзя.
...
TRIG1H;
0x08000280 <main+64>: movw r0, #3072; 0xc00
0x08000284 <main+68>: movt r0, #16385; 0x4001
0x08000288 <main+72>: mov.w r1, #256; 0x100
0x0800028c <main+76>: mov.w r2, #1
0x08000290 <main+80>: bl 0x800087c <GPIO_WriteBit>
TRIG1L;
0x08000294 <main+84>: movw r0, #3072; 0xc00
0x08000298 <main+88>: movt r0, #16385; 0x4001
0x0800029c <main+92>: mov.w r1, #256; 0x100
0x080002a0 <main+96>: mov.w r2, #0
0x080002a4 <main+100>: bl 0x800087c <GPIO_WriteBit>
GPIOA->ODR = buf[0];
0x080002a8 <main+104>: movw r3, #2048; 0x800
0x080002ac <main+108>: movt r3, #16385; 0x4001
0x080002b0 <main+112>: ldrb.w r2, [r7, #624]; 0x270
0x080002b4 <main+116>: str r2, [r3, #12]
GPIOA->ODR = buf[1];
0x080002b6 <main+118>: movw r3, #2048; 0x800
0x080002ba <main+122>: movt r3, #16385; 0x4001
0x080002be <main+126>: ldrb.w r2, [r7, #625]; 0x271
0x080002c2 <main+130>: str r2, [r3, #12]
...
МК серии Cortex поддерживают управление битовыми полями для набортной статической памяти (суммарно до 1МБ) и столько же для периферии, включая и порты ввода-вывода. Это делается путем добавления союзной (соответствующей, привязанной, связанной, комплементарной — я русскоязычную литературу не читаю и не знаю, какой термин общепринят) группы адресов, в 32 раза (по числу разрядов МК, чтобы не прибегать ко всякого рода кастингам, смещениям и логическим преобразованиям) превышающем область тех адресов памяти, для которой предусмотрены прямые операции с битовыми полями. Благо что адресное пространство МК 4ГБ. То есть, блоку адресов периферии размером в 1МБ соответствует союзный блок адресов в 32МБ. Запись 0 или 1 по созному адресу приводит к изменению соответствующего битового поля конкретного адреса периферии.
Фрагмент дизассемблированной программы начинается со взвода триггеров (для запуска OLS) и показан для вывода в порт двух первых чисел из буфера. Триггеры для изменения логического состояния используют стандартную периферийную библиотеку STM и, после инициализации базового адреса порта в регистре r0 и данных для вычисления адреса изменяемого бита, вызвают функцию GPIO_WriteBit. На это потребуется 560нс.
Операция записи байта в порт,
GPIOA->ODR = buf[j];,
на ассемблере по логике построения будет аналогична записи в союзную область и состоит из 4 ассемблерных команды, общей длительностью (1/72Mhz)*5 c (так как инструкция ldrb.w требует двух тактов процессора, а str, в данном случае, — один такт), т.е. чуть больше 70нс. Таким образом, все протсходит в восемь и более раз быстрее.
Но и это не предел.
Пара команд
movw r0, #3072; 0xc00
movt r0, #16385; 0x4001,
формирующая стандартный для Cortex-M3 базовый адрес порта А 0х40010с00, одинаковая для каждой команды записи в порт, нужна только один раз. Это дополнительно сократит время записи в порт почти вдвое. При этом нужно будет использовать инлайновый ассемблер. Можно это сделать и вместе с самим циклом.
К слову сказать, более изящные по синтаксису С конструкции с указателем и смещением дадут пять ассемблерных команд, указателя с постинкрементом — восемь.
Управляющие команды - запись, чтение и признак команда/данные, итоговый результат которых — изменение битового поля, на ассемблере вообще могут представлять из себя одну команду, длительностью даже меньше разрешенных для дисплея MI0283QT-2 15нс.
Но, поскольку параллельное подключение к «обычному» порту МК не будет являться конечным вариантом подключения LCD, описанной выше ассемблерной оптимизации проводить не будем. На новой плате будет RGB-подключение через контроллер FSMC. Хотя там планируется «мультимониторная» конфигурация. И даже мультипроцессорная. Но об этом в другой раз.
Для иллюстрации посмотрим, как OLS визуализует код инициализации LCD MI0283QT-2.
Вот реальный код из программы:
void LCD_Init()
{
LCD_RSTL;
delay(50);
LCD_RSTL;
delay(50);
LCD_WriteReg (OSC_CTRL1, 1<<OSC_EN);
LCD_WriteReg (POWER_CTRL3, (1<<AP1)|(1<<AP0));
LCD_WriteReg (POWER_CTRL6, (1<<GASEN) | (1<<DK));
LCD_WriteReg (POWER_CTRL6, 0);
LCD_WriteReg (POWER_CTRL6, 1<<PON);
LCD_WriteReg (POWER_CTRL6, (1<<PON)|(1<<VCOMG));
LCD_WriteReg (DISPLAY_CTRL3, (1<<GON)|(1<<DTE)|(1<<D1)|(1<<D0));
LCD_WriteReg (COLMOD, MODE_16bpp);
LCD_WriteReg (MEMORY_CTRL, (1<<MV)|(1<<BGR));
}
Не могу сказать еще раз о стиле. Если посмотреть, как оформлены коды инициализации различных дисплеев от STM, Atmel и прочих вендоров, то это вряд ли похоже на язык высокого уровня — все параметры записываются только в виде чисел, то есть просто номера регистров и данные, без единого намека на функционал той или иной команды. На мой взгляд, это не правильный подход, тем более для обучающего материала. Заголовочный файл, который я написал для примененного дисплея MI0283QT-2 можно скачать
отсюда. Он приведен не как образец исполения, а для того, чтобы сократить кому-то время написания своего драйвера под контроллер HX8347D.
Файл содержит описания регистров,и битовых полей, которые хоть и оставлены без расшифровки (RTFM!), но смысловая нагрузка для кода становится очевидна.
Например, первая строчка записи в регистр LCD:
LCD_WriteReg (OSC_CTRL1, 1<<OSC_EN);
сразу понятно, что речь идет о запуске осциллятора контроллера дисплея. То же самое, но как принято у производителей микропроцессоров:
LCD_WriteReg (0х19, 0х01);
Конечно, и мой заголовочный файл можно покритиковать, можно было ввести более внятные дефиниции MEMORY_CTRL,определяющей геометрический свойства экрана и порядок следования цветовых составляющих. Но мне никогда не поздно и добавить.
Перед строкой запуска осциллятора ставим триггерное условие запуска по высокому уровню TRIG1 и TRIG3 и низкому TRIG2, после обновления прошивки и сброса OLS выдает следующую картинку:
Инициализация дисплея MI0283QT-2 Цена деления времени — 25нс. Прекрасно видна работа функции записи по стробу LCD_WR, сначала индекса регистра (0х19) при низком уровне LCD_RS, затем значения этого регистра (0х01). При высоком уровне на линии LCD_RS записываются данные.
16 битная шина Хоть сначала и не планировал, но не удержался и пересобрал стенд на 16-битное подключение к LCD:
Подключаем к OLS 16-битную шинуНа ZP-STM32 высвободить 16 линий от одного порта (чтобы не заниматься ненужной программной группировкой) можно только для порта В, так как два вывода порта А используются под отладочный порт SWD, а еще два не имеют контактов для подсоединения, так как отданы исключительно под USB.
Для высвобождения порта В в начале программы следует разместить строку
GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);,
которая оставит только отладочный порт SWD, а выводы порта 3 и 4, ранее служившие TDO JRST для JTAG-интерфейса, освободятся как GPIO. Также при этом высвободится 15-й вывод порта А, бывший ранее TDI.
При этом нужно учесть, что в технической документации STM (Doc ID 13587 Rev 12, June2010), на 30 странице в таблице 5 содержится ошибка, так как показано, что GPIO функции вторичны по отношению к JTAG и работа этих выводов как обычных портов — это альтернативная функция (AF). Но, если высвобождаемые выводы при запрете JTAG конфигурировать как GPIO_Mode_AF_PP, то работать они не будут. Надо было прямо указать в документации, что при этом их нужно конфигурировать как обычные GPIO. Как говорится, доверяй, но проверяй.
На фото видно, как с колодки JTAG-разъема выходят красный и синий провода, которые и являются этими выводами 3 и 4 порта В. Коричневый провод — это вывод 2 порта В, он на отдельной колодке, так как служит еще и как boot1. Серый провод идет на внешнюю кнопку сброса.
К сожалению, при таком подключении мы лишаемся обоих портов I2C, остается только первый SPI (4 вывода), выводы PA0..PA7, PA15 и обрезок из трех линий порта C. Три линии необходимы для управления LCD, еще три заберем на триггеры запуска, а два вывода порта С - на RTC.
Итого на все остальное — три вывода. Но это у нас кратковременная учебная конфигурация.
Зато есть повод припаять на OLS колодку к небуферизованной 16-битной шине и посмотреть одновременно на более, чем 16 цифровых сигналов. При этом максимальная частота сэмплирования составит 100MHz (количество используемых входов можно выбрать программно, при этом выбирается и широкий ряд значений до максимально возможного для данной конфигурации).
Запустим ту же программу инициализации дисплея, но уже на 16-битной шине:
Инициализация LCD на 16-битной шине Операции по записи в регистры LCD при его настройке по-прежнему выполняются в восьмибитном формате, 16 бит используются только при записи в графическую память, что видно в правой части экрана, после появления на шие команды начала записи в память (0х22). Это уже идет выполнение программы LCD_clear(uint16_t color), котрая заполняет весь экран, в данном случае, серым цветом (0х7BEF).
Ну и, напоследок, максимальное быстрое заполнение серым цветом экрана, которое у меня получилось на «чистом С»:
Запись константы в графическую память Четыре с половиной миллиона 16-цветных точек в секунду, при размерах дисплея 320х240, дают более 59 кадров в секунду. Как показано выше, ассемблер даст возможность серьезно улучшить и этот результат.
Разумеется, был запущен тест из предыдущей заметки, с мультиком и Штирлицем. 8-битный режим уехал за 20 фпс и уперся с производительность дисковой подсистемы. Суммарное время «рваного» чтения кадра, не влезающего в приемный буфер 12 килобайт - более 40мс, что ограничивает итоговый результат (без использования DMA), размер же 24-битного исходного кадра в три раза больше. Чтение 8-битных четвертинок со внешних флэшек, не обремененных файловой системой — 22мс. Но это тоже тема не сегодняшнего материала.
Кстати, может возникнуть вопрос, а можно ли подобными маневрами поднять фпс на обычном PC, работающем под DirectX? Можно, но на это уйдет вся жизнь. Ввиду сложности самой компоненты, необходимости пересоздания инклюдов, да и вся программа на ассемблере сведется к многочисленным invoke сишных функций API DirectX. При этом от классического ассемблера ничего не остается. Другое дело, когда ты с железом один на один без запутанной (часто намеренно) программной прослойки. Тут уже есть возможность выцарапать лишние миллисекунды.
Итог Как в свое время и веллемановский осциллограф-генератор PCSGU-250, OLS кажется тем прибором, который, надеюсь, на достаточно долгое время, закроет мои текущие нужды по контролю за тем, что творится на цифровых линиях моих игрушек. Конечно, это не DSO-модули к многокилобаксовым осциллоскопам, но и стоит он всего менее 50 долларов ( с доставкой!).
Девайс из серии мust have. А когда еще и несколько проколов из клиента уберется, то все будет в полном ажуре.
Оформленные словами мысли по поводу вышеизложенного —
сюда. На сегодня все,
Zauropod,
5 декабря 2010 года.