MultiClet: осваиваем SPI на примере работы с LCD

от автора

В начале мая я стал счастливым обладателем отладочного комплекта LDM-MCp. Пару месяцев он пылился на столе, было много работы, назревал отпуск. Вернувшись, с новыми силами, светлой головой и рвением что-то пощупать-поделать, но точно не работу, руки сами потянулись за новую игрушку. Поставил SDK под Linux, всё подключил.

Квест первый

Сразу после подключения система радостно обнаружила спаренное 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 более-менее разобрались. Перейдём к дисплейчику.
image

Распиновка достаточно простая. Нам понадобится подключить выводы 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-битными словами. Оригинальный код выдавал что-то «несъедобное».

Обложив всё немного функциями по рисованию секторов, окружностей, прямоугольников с прямыми, можно получить такой результат:

image

ссылка на оригинал статьи http://habrahabr.ru/post/191184/


Комментарии

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

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