Квест первый
Сразу после подключения система радостно обнаружила спаренное FTDI-устройство, создав сразу два ttyUSBx-девайса. И тут дилемма — либо использовать Serial-консоль, либо иметь возможность заливать прошивки, — идущий в комплекте загрузчик работает напрямую с FTDI-устройством. Пришлось на коленке рисовать скрипты для «правильной» загрузки модуля ftdi_sio. Наколенность проявила себя в использовании питоновских биндингов к библиотеке ftd2xx. Общая суть сводится к выгрузке модуля, блокированию FTDI, используемого для прошивки, и одновременной с этим загрузке модуля обратно. Тогда ядерный модуль может заблокировать оставшийся FTDI для UART.
Hello, world! — слишком банально
Простенький «Hello, world!» с мигающими LED заработал сразу, только обнаружилось, что после прошивки линуксовым mc-ploader'ом необходимо дополнительо сбросить плату или подожать, пока сработает WDT.
Когда-то зимой заказывал себе пару SPI-экранчиков HY28A, но с нашей почтой пришли они только в мае. Тут и решение само пришло — начать с экранчика. Вооружившись USB'ым логическим анализатором SYSCLK DX, полез штудировать спеки на регистры GPIO и SPIx в процессоре MCp и ковыряться в примерах использования SPI.
Настройка звука
На плате LDM уже были задействованы два SPI из трёх. Один под АЦП, второй под слот microSD. Вся периферия MCp состоит из GPIO-регистров, имеющих также альтернативные функции, будь то сетевой интерфейс (MAC/MDIO), UART, USB или I2C.
Первым делом, необходимо сконфигурировать через битовые поля альтернативную функцию для оставшегося незадействованным SPI0. Для работы нам надо подключить пару заголовочных файлов:
#include <HDL51001_ccf.h>
#include <spi.h>
…
GPIOB->BPS = 0x07F;
Здесь мы задали альтернативные функции для пинов 0-9 GPIOB, соответствующие SCK, MOSI, MISO, SS0, SS1 и SS2. Ещё пара SEL_IN/SCK_IN используется только в режиме ведомого.
Теперь нам необходимо задать параметры самой шины SPI:
SPI0->SS = 0x07; // выставляем сигнал CS в логическую единицу
SPI0->CR = 0x37710000;
Итак, что из себя представляет регистр SPIxCR. Отмечу наиболее интересные для нас поля:
Бит | Значение | Описание |
---|---|---|
29 | 1 | Поляризация. Состояние SCK в режиме ожидания. Мы используем сигнал HIGH. |
28 | 1 | Фаза синхросиганал. Мы читаем данные от ведомого на спаде сигнала. |
27 | 0 | Разрешение делителя на 16. Мы хотим максимальной скорости. При частоте процессора 80МГц, частота шины SPI будет 20MHz |
26 | 1 | Последовательность бит при передаче — LSB или MSB. Мы используем MSB. |
25 | 1 | Режим работы нашего SPI: ведомый или ведущий. |
24 | 1 | Данный бит включает блок SPI |
23-20 | 0111 | Длина слова данных. Мы хотим 8 бит (0x3 — 0xf — 4-16 бит соответственно). Для 32 бит значение поля должо быть нулевым. |
19-16 | 0001 | Задаёт режим предделителя. При нулевом значении были проблемы с синхросигналом. Т.ч. я выбрал «1». |
Для выбора ведомого используются линии SS0-SS2. Активным считается состояние LOW. Выборка осуществляется через регистр SPIxSS: «1» выставляет значение HIGH, а «0» — значение LOW.
С конфигурированием SPI более-менее разобрались. Перейдём к дисплейчику.
Распиновка достаточно простая. Нам понадобится подключить выводы 3v3_IN, GND, SCK, CS, SDO (он же MISO), SDI (он же MOSI) и nRESET с BL_CTRL. С помощью линии BL_CTRL и ШИМ можно управлять яркостью LED-подсветки. Подсветка и так слабовата, т.ч. просто запитываем её от 3v3. Сигнальная линия nRESET используется во время процедуры сброса/инициализации экрана. Для этого нам понадобится сконфигурировать ещё один GPIO-выход:
GPIOB->DIR |= 0x100; // Задаём 8-ой вывод GPIOB как OUTPUT
GPIOB->OUT &= ~0x100; // Переводим вывод в состояние LOW
Теперь мы готовы немного поработать с SPI.
SPI: первые шаги
Чтобы что-то передавать, надо объявить о наших намерениях. Для этого, перед каждой посылкой команд, нам необходимо произвести выборку ведомого устройства. У нас оно одно — LCD. Команда записи в SPI-порт для наших 8-ми битных посылок будет выглядеть так:
#define SPI_CS_LOW SPI0->SS = 0x06 /* объявляем макросы по выбору активной линии */
#define SPI_CS_HI SPI0->SS = 0x07 /* … и сбросу выбора */
int SPI_WRITE(int data)
{
/* Не забываем, что наш байт надо положить в старший байт регистра TX, т.к. у нас MSB */
SPI0->TX = ((uint32_t)data) << 24;
/* Ждём пока данные передаются */
while(SPI_ST_TIP(SPI0) == 1);
/* Ждём наличие данных */
/* Возможно, ещё стоит проверить на заполненность буфера (SPI_ST_NF) */
while(SPI_ST_NE(SPI0) == 0);
/*
* Читаем значение регистра RX.
* Стоим напомнить, что одновременно с передачей битов происходит чтение,
* чем мы и будем пользоваться далее.
*/
return SPI0->RX;
}
Вдаваться в подробности инициализации экрана я не буду — это и выставление/снятие активного сиганала nRESET, это и множественные команды записи/чтения из SPI. Всё это можно посмотреть в исходнике lcd-ili9320.c в функции LCD_init.
Работа с LCD сводится к выдаче команд на задание позиции (X, Y) или же ограничиванию области сканирования, и тогда последовательно записываемые данные будут заполнять заданную область. Например, рисование прямоугольников осуществляется последовательной записью WxH слов цветового значения, оптимизируя тем самым число выданных команд. SPI — шина не быстрая, ведомые устройства способны работать на частоте до 25 МГц. Поэтому показывать видео не получится, — для этой цели надо использовать параллельную шину. Двойного буфера тоже нет. Всё, что выдаётся по SPI, записывается во внутреннюю память драйвера дисплея и при последующем сканировании, отображается на экране. Добро пожаловать во времена Турбо Паскаля с модулем graph! Т.ч. экран в режиме SPI хорош для отображения статики или редко меняющейся картинки.
В своей тестовой программе я генерировал шрифты и картинку в последовательность RLE с помощью программы lcd-image-converter. Правда, пришлось модифицировать алгоритм генерирования RLE-последовательностей, наиболее оптимальных при работе с 32-битными словами. Оригинальный код выдавал что-то «несъедобное».
Обложив всё немного функциями по рисованию секторов, окружностей, прямоугольников с прямыми, можно получить такой результат: