STM32F1хх — продолжаем лечение от ардуинозависимости при помощи LCD

    Добрый день, уважаемые хабровчане. В своей прошлой статье я рассмотрел применение замечательных микроконтроллеров STM32F1xx на примере управления сервоприводами. В этой статье мы обратимся к более интересному вопросу – управлению цветным графическим LCD-дисплеем. Помимо стандартной работы с дисплеем я постараюсь осветить вопросы использования особенностей микроконтроллеров STM32F для эффективного решения данного вопроса. Итак, начнем.

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

    Железо


    Прежде всего, о железе. Ядром системы, как и в прошлый раз, будет отладочная плата STM32VLDISCOVERY, которую можно приобрести за 300р. Собирать схему будем на макетной плате, чтобы поменьше паять. Однако, в отличие от прошлого раза, пайка нам таки предстоит, причем довольно специфическая – паять придется к гибко-жесткой печатной плате сзади дисплея (попросту говоря, к пленочке с контактами). Но об этом позже. Источник питания на 3.3В есть на отладочной плате, поэтому все что нам потребуется дополнительно – это собственно дисплей и источник питания на 10-20 вольт для его подсветки.
    Пришла пора рассмотреть ту часть, ради которой все, собственно, и затевалось – дисплей.
    На самом деле, вариантов, доступных разработчику в домашних условиях, довольно много.
    На момент написания статьи, самым доступным вариантом я считал дисплеи от мобильных телефонов, так как их относительно легко можно приобрести, многие управляются по SPI и стоят довольно дешево. Собственно, одному из таких дисплеев и посвящена эта статья.
    Однако, после того как я провел некоторое время на e-bay, я пришел к выводу, что самый оптимальный вариант, это, все-таки, покупка отдельного дисплея под свои нужды.
    Во-первых, можно найти довольно дешевые модели, такие, как, например, вот этот дисплей:
    image
    1-8-TFT-Color-LCD-Display-Module-SPI-interface

    Во-вторых, если заплатить чуть больше, можно получить законченный дисплейный модуль и не тратить время на пайку неудобных разъемов:
    image
    1-8-TFT-LCD-module-TF-Card-socket-break-out-arduino

    В-третьих, некоторые дисплейные модули и вовсе выше всяческих похвал –
    image
    2-4inch-TFT-LCD-module-touch-panel-SD-card-cage

    За 18 баксов мы получаем полностью законченный дисплейный модуль, включающий в себя дисплей 320х240 с контроллером, на который есть полная документация (обратите внимание, на контроллеры дисплея от многих мобильных телефонов документацию найти очень проблематично), резистивный тачскрин с контроллером (то есть он уже подключен, можно сразу запрашивать координаты по SPI), удобный разъем, и – как бонусный довесок – слот под микро-SD.
    Также на борту стоит повышающий преобразователь для питания подсветки.
    Поэтому в данный момент, я, конечно, считаю дисплеи с е-бей наиболее удобным и правильным вариантом.

    Наконец, последний вариант, который мне бы хотелось отметить, это вот такие занятные девайсы:
    image
    1-5-lcd-rechargeable-digital-usb-photo-frame-keychain
    Это чудо китайских технологий представляет собой миниатюрную цифровую фото-рамку. Как выяснилось, несколько зарубежных энтузиастов уже копались в ее недрах, и выяснили, что строятся они на 6502-совместимом микроконтроллере ST2203U, прошивка коего фиксируется при производстве, поэтому нам он не интересен. Зато, менее чем за 200р мы получим 128х128 STN ЖК-дисплей (большинство из них построены на контроллере PCF8833), микросхему флеш-памяти, литий-ионный аккумулятор со схемой контроллера заряда и удобный корпус для своего девайса.

    И все же, остановимся пока на первом варианте, то есть — дисплее от мобильного телефона.
    Согласно информации, взятой с просторов интернета, из небольших цветных ЖК-дисплеев популярностью пользуются дисплеи от Nokia ХЗ и Siemens S(K)65. По первому дисплею довольно много информации, но сам он уступает дисплеям от сименса в качестве изображения, разрешении, а главное – во времени отклика, которое делает его почти непригодным для воспроизведения анимации. А нам с вами не интересно выводить статические картинки, так ведь? Второй дисплей, судя по данным из интернета, предоставляет нам разрешение в 132 х 176 пикселя и время отклика, достаточное, для вывода анимации. Впрочем, возможности сравнить эти два дисплея у меня, к сожалению, не было – т.к. у меня завалялся старый Siemens SK65, выбор был однозначно сделан в пользу него.
    Первые грабли подстерегают нас при покупке самого дисплея. Дело в том, что сименс устанавливает в свои S65 дисплеи на контроллерах трех разных производителей, LS020xxx, LPH88xxxx, L2F50xxx.

    image

    image

    image

    Про третий я не слышал вообще ничего, на второй есть официальный даташит, но, похоже, этот контроллер намного менее распространен.
    А вот первый, LS020xxx — это тот контроллер, который чаще всего стоит в мобильниках, и мой SK65 не оказался исключением.
    Казалось бы – все замечательно, но тут кроется один неприятный факт: на контроллер даташита нет. Таким образом, разрабатывать придется без официального документа, в котором бы были описаны команды и регистры. Но не все так плохо – умельцы провели сеанс реверс-инжениринга и определили основы взаимодействия с дисплеем, и информации о нем в интернете очень немало.
    Рассмотрим дисплей подробнее.

    Дисплей LS020xxx



    image

    Первое, что бросается в глаза – весьма удобные контактные площадки, подпаяться к которым куда проще, чем к мелким разъемам на большинстве дисплеев. Правда, расположены они на гибко-жесткой печатной плате, которую, при неосторожном обращении, можно поплавить паяльником, так что при пайке ставим температуру паяльника поменьше.
    Всего дисплей имеет 10 контактов:

    1. RS – сигнал переключения команда / данные
    2. RESET – сигнал сброса дисплея
    3. CS – сигнал выбора устройства SPI
    4. CLK – тактовый сигнал SPI
    5. DAT – сигнал MOSI SPI
    6. 2.9V и 1.8V – питание дисплея
    7. LED+, LED_GND – питание и земля цепи подсветки
    8. GND – земля дисплея.


    На контакты LED+ и LED_GND подается питание подсветки дисплея, которое может составлять от 10 до 20 вольт. Это питание подается на 3 светодиода, ток через них регулируется схематикой контроллера, так что к ним безбоязненно можно подключить одну-две 9В батарейки без токоограничительных резисторов. Я подключил две последовательно включенные, слегка подсевшие, кроны, получив напряжение около 17В.
    Контакты 1V8, 2V9 и GND обеспечивают питание схем дисплея. Что интересно, во многих схемах подключения можно увидеть пин 1V8 вообще никуда не подключенным. Во всех остальных схемах пины 1V8 и 2V9 соединены вместе и подключены к источнику 3.3В. Это подключение оказалось вполне работоспособным, поэтому так и поступим.
    Пины CS, DAT, CLK являют нам тривиальный SPI интерфейс без ответного провода. Этот факт не может не радовать, т.к. даже на AVRках имеется в наличии аппаратный SPI-контроллер, а STMки и вовсе богаты на периферию подобного рода – в младших моделях этих контроллеров два, в старших – аж 4 штуки, поэтому один из SPI можно выделить всецело для общения с дисплеем.
    Пины RS и RESET – два дополнительных управляющих сигнала для дисплея. Первый, находясь в активном уровне (лог 0) индицирует о том, что мы передаем дисплею команды, а в неактивном (лог 1) – данные. RESET, как следует из названия, предназначен для сброса контроллера.
    Теперь нам требуется подключить дисплей к нашей отладочной плате при помощи макетки. Для этого мной был выбран шлейф с 10-контактным IDC-разъемом.



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

    Тем, кто будет повторять мои эксперименты все же советую взять однорядный 10-контактный разъем, проблем будет меньше. После того как шлейф обжат, его следует подпаять к контактам дисплея – напоминаю о том, что пленка, на которой расположены контакты, не очень любит длительное воздействие высокой температуры, так что стараемся паять быстро и четко.
    После пайки размещаем разъем на макетной плате и начинаем соединять перемычками с контроллером. Пины питания подсветки сразу же отводим куда подальше, втыкаем туда разъем от кроны и забываем о них. У кого есть источник питания на 10-20В – еще лучше, не придется постоянно отключать батарейки в целях экономия энергии.

    Далее соединям SPI-интерфейс. Отладочную плату в макетку удалось установить только свесив горизонтальные пины (по уже описанной причине перемыкания), которых, к счастью было немного. Однако среди них оказался интерфейс SPI2, так что в нашем распоряжении остался только первый. Впрочем, нас это не должно сильно расстроить, т.к. больше мы пока и не собираемся ничего к нему подключать. Поэтому соединяем CLK с пином PA7, DAT – с пином PA5. CS будем дергать ручками в соответствии с логикой работы, как и RS с RESETом – поэтому эти три сигнала выводим туда, куда нам удобно. Я выбрал пины PA2, PA3, PA4. В итоге у вас получится что-то вроде этого:



    Кстати, небольшой оффтоп: после того, как я все подключил, я выдал тестовый 10КГц меандр на один из пинов. И вот что показал осциллограф:

    image

    Канал 2, обозначенный синим цветом, подключен к самому пину. А вот канал 1 (желтый), подключен к соседнему с ним выводу. Видите эти красивые экспоненты с амплитудой почти 100мВ? Все эти перемычки на макетке дают неслабую емкостную связь между пинами. Что характерно, если отладочную плату вытащить из макетки, влияние пракически отсутствует:

    image

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

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

    Код


    Как я уже упоминал, я вернулся к написанию статьи спустя много месяцев после, собственно, экспериментов с дисплеем, поэтому часть кода была потеряна. Но я постарался восстановить важные моменты по памяти. Впрочем, взаимодействие с дисплеем весьма тривиально и не должно вызвать затруднения.
    Итак, на данный момент у нас должен быть полностью подключенный к макетной плате дисплей и запущенный Кейл с созданным проектом.

    Начнем мы с простого вывода на экран картинки. Так как наша задача сейчас – разобраться с дисплеем, то не будем мудрствовать лукаво и прикручивать загрузку с карточек, чтение файловых систем и т.д. – просто разместим картинку в виде сырых 16-битных RGB-значений во флеш-памяти контроллера. Для этого рекомендую набросать на удобном вам языке программирования программку, которая переведет стандартный bmp-файл в запись вида
    const uint16_t Picture[] = {0x0000, 0x0000, …, 0x0000};
    

    Так как разрешение нашего дисплея 132 х 176 пикселей, каждый из которых занимает 2 байта, полная картинка будет занимать в памяти 46464 байт, то есть чуть больше 45 Кб.
    К счастью, 500 КБ флеша СТМки позволяют нам сохранить картинку в сыром виде, и даже не одну.

    Теперь переходим к программированию дисплея. Больше всего это похоже на черную магию, потому что даташита на контроллер у нас нет, зато есть инструкции от тех, кто реверсил, в которых сразу дается «заклинание» в виде последовательности байт, без объяснения, почему она именно такая. Если вас не устраивает такой подход, то наилучшим выходом будет приобрести какой-нибудь из упомянутых мной дисплеев с е-бея, к которым идет полный комплект документации.

    Итак, заклинания.
    Дисплей инициализируется следующей последовательностью (очень чувствительной к паузам!):

    1. Подаем уровень лог 0 на линию RESET и удерживаем его около 5 мс
    2. Выдаем первое заклинание дисплею, представляющее собой вот такой блок байт:
      	uint16_t Init1[12]={0xFDFD,0xFDFD,0xEF00,0xEE04,
      0x1B04,0xFEFE,0xFEFE,0xEF90,
      0x4A04,0x7F3F,0xEE04,0x4306}

      Заменив Init1[9]=0x7F3F на 0x7F1F, мы можем инициализировать дисплей в режиме 256 цветов – но т.к. 16-битный цвет все таки поинтереснее, остановимся на исходном варианте.
      После этого блока байт необходимо выждать 7 мс.
    3. Выдаем блок байт
      	uint16_t Init2[20]={0xEF90,0x0983,0x0800,0x0BAF,0x0A00,
      0x0500,0x0600,0x0700,0xEF00,0xEE0C,
      0xEF90,0x0080,0xEFB0,0x4902,0xEF00,
      0x7F01,0xE181,0xE202,0xE276,0xE183} 

      И выжидаем 50 мс
    4. Кастуем последний спелл в виде двух байт
       uint16_t Init3[2]={0x8001}
      и выжидаем 5 мс.


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

    uint32_t *Reset	= (uint32_t*)0x42210184,
    			*CS	= (uint32_t*)0x42210188,
    			*RS	= (uint32_t*)0x4221018C;
    

    Для тех кто не помнит об этой замечательной технике – бит-бандинг это очень полезная фича, представляющая собой мапирование каждого бита ИО-регистров и RAM в дворд где-то в адресном пространстве СТМки. Так как адресное пространство у нас 32-битное, то бишь 4 гигабайта, а реально задействовано намного меньше, разработчики решили потратить несколько метров, на то чтобы связать биты из ИО или RAM (bit-band alias region) с двордами в bit-band region.
    Работает это просто – допустим, мы хотим управлять пином CS, который у нас соответствует PA2. Уровнем сигнала на нем управляет бит номер 2 регистра GPIOA->ODR.
    Воспользуемся формулой из даташита:

    bit_word_addr = bit_band_base + (byte_offset x 32) + (bit_number × 4)

    bit_word_addr – это искомый адрес бит-банда, который требуется вычислить.
    bit_band_base для нашего процессора, согласно даташиту, объявлен в хедере в виде

    #define PERIPH_BB_BASE        ((uint32_t)0x42000000)
    

    byte_offset – смещение до искомого регистра, в данном случае, до GPIOA->ODR, от начала bit-band alias region.
    Так как адрес GPIOA->ODR равен 0x4001080С, а начало периферийного bit-band alias региона объявлено как

    #define PERIPH_BASE           ((uint32_t)0x40000000)
    

    То смещение будет равно 0x4001080С-0x40000000 = 0x1080C
    И наконец, номер интересующего нас бита равен 2. Собирая все это вместе получаем
    *CS = 0x42000000 + (0x1080C)*32+2*4 = 0x42000000+210180+8=0x42210188

    Теперь, записав отличное от нуля значение по этому адресу, мы установим связанный с этой ячейкой памяти бит в 1, записав нулевое – в 0.

    Три блока байт у нас уже объявлены, проведем инициализацию периферии:

    RCC->APB2ENR	|= RCC_APB2ENR_IOPAEN;	
    RCC->APB2ENR	|= RCC_APB2ENR_SPI1EN;	
    
    GPIOA->CRL  |=GPIO_CRL_MODE1;
    GPIOA->CRL	&=~GPIO_CRL_CNF1;
    
    GPIOA->CRL  |=GPIO_CRL_MODE2;
    GPIOA->CRL	&=~GPIO_CRL_CNF2;
    
    GPIOA->CRL  |=GPIO_CRL_MODE3;
    GPIOA->CRL	&=~GPIO_CRL_CNF3;
    
    GPIOA->CRL  |=GPIO_CRL_MODE5;
    GPIOA->CRL	&=~GPIO_CRL_CNF5;
    GPIOA->CRL	|=GPIO_CRL_CNF5_1;
    
    *Reset=1;
    *CS=1;
    *RS=1;
    
    GPIOA->CRL  |=GPIO_CRL_MODE7;
    GPIOA->CRL	&=~GPIO_CRL_CNF7_0;
    GPIOA->CRL	|=GPIO_CRL_CNF7_1;
    	
    SPI1->CR1 &= ~SPI_CR1_BR_2;
    SPI1->CR1 |= SPI_CR1_DFF;
    SPI1->CR1 |= SPI_CR1_MSTR|SPI_CR1_SPE;	
    

    Тут все просто – Reset, CS, RS ставим на выход под управлением нашей программы,
    CLK и MOSI – выход под управлением периферии, то есть, в данном случае, контроллера SPI.
    SPI настраиваем на частоту Fclk/8 и посылку данных 16-битными вордами вместо байт.
    Также следует описать функции задержки и синхронной посылки по SPI, которые, в силу их тривиальности, приводить тут не буду, за исключением прототипов:

    
    __INLINE void Delay_ms(uint32_t us)
    __INLINE void SendSPI(uint16_t Data)
    

    Реализация их предельно проста – задержка реализована обычным циклом, синхронная посылка по SPI – выводом посылаемого байта в регистр данных SPI (SPI1->DR=Data;) и ожиданием в цикле до тех пор, пока не пропадет флаг SPI_SR_BSY из статуса SPI1->SR.

    Далее выполняем нашу магическую последовательность:

    
    *Reset=0;
    *CS=0;
     Delay_ms(5);
    *Reset=1;
     Delay_ms(50);
     for(i=0;i<12;i++)
    	SendSPI(Init1[i]);
    Delay_ms(7);
     for(i=0;i<20;i++)
     	SendSPI(Init2[i]);
     Delay_ms(50);
     for(i=0;i<2;i++)
     	SendSPI(Init3[i]);
     Delay_ms(5);
    

    Теперь пришла пора выучить новые заклинания. Итак:
    Команда {0xEF90, 0x05OR, 0x06XX, 0x07YY} – вывод графической информации на экран, где байт OR обозначает ориентацию экрана при выводе и может быть равен 0х04 в случае горизонтальной и 0х00 в случае вертикальной – именно в этом направлении будут заполняться пиксели. Байты ХХ и YY обозначают позицию нижнего левого угла прямоугольника. Верхний правый угол всегда будет равен пикселю (175, 131).
    После следует подавать байты графической информации в том формате, на который был инициализирован дисплей. В данном случае – 16-битные ворды вида 5-6-5 (R-G-B)

    Для вывода информации в ограниченную область, команда используется в несколько модифицированном виде:
    {0xEF90, 0x05OR,0x08X1,0x09X2,0x0AY1,0x0BY2}, где X1, Y1 – координаты нижнего левого угла области вывода, X2, Y2 – координаты верхнего правого угла.

    Таким образом, заливка всего экрана черным цветом будет выглядеть вот так:

    
    SendSPI(0xEF90);
    SendSPI(0x0504);
    SendSPI(0x0600);
    SendSPI(0x0700);
    *RS=0;
    for(i=0;i<132*176;i++)
    	SendSPI(0x0000);
    *RS=1;
    *CS=1;
    

    Ну а теперь выведем, наконец, нашу картинку!

    
    SendSPI(0xEF90);
    SendSPI(0x0504);
    SendSPI(0x0600);
    SendSPI(0x0700);
    *RS=0;
    for(i=0;i<132*176;i++)
    	SendSPI(Picture[i]);
    *RS=1;
    *CS=1;
    

    Если все сделано правильно, то мы должны получить вот такой результат:

    Зеркально отраженный морпех как бы говорит нам: помни о порядке вывода пикселей!

    Заканчивать работу с дисплеем рекомендуют не простым отключением питания, а следующей последовательностью команд:

    
    uint16_t Power[26]={0xEF00,0x7E04,0xEFB0,0x5A48,0xEF00,0x7F01,0xEFB0,0x64FF,0x6500,0xEF00,0x7F01,0xE262,0xE202,0xEFB0,0xBC02,0xEF00,0x7F01,0xE200,0x8000,0xE204,0xE200,0xE100,0xEFB0,0xBC00,0xEF00,0x7F01};
    
    *RS=1;
     for(i=0;i<26;i++)
    	SendSPI(Power[i]);		
    *CS=1;
    


    На этом, в принципе, можно было бы и закончить, но на самом деле, впереди нас ждет самое интересное!

    Использование особенностей архитектуры STM32F1xx для оптимизации вывода изображения на экран


    Итак, мы вывели на экран картинку из памяти. Это уже неплохое достижение и широкое поле для деятельности, но… То же самое мы могли легко проделать на пресловутой Ардуине. А наша цель – вылечиться от ардуинозависимости и понять, какие задачи лучше решать другими методами. Давайте подумаем: чем нас ограничивает текущая реализация?
    Ответ очевиден – если мы захотим выводить анимацию, то большую часть времени мы будем крутиться в цикле вывода графической информации. Так как разрешение экрана 132х176 пикселей, каждый из которых описывается двумя байтами, нам нужно вывести 371 712 бит по шине SPI. Каждый бит выводится с частотой в восемь раз меньше тактовой, то есть нам нужно потратить 2 973 696 тактов на вывод одного кадра. Имея под рукой 75 МГц СТМки это, может быть, и не столь критично – хотя и неприятно, а для 16 МГц АВРки и вовсе беда.
    Но к счастью, в архитектуре STM есть блок, который в подобных ситуациях делает нашу жизнь похожей на сказку. Это блок прямого доступа к памяти, то есть DMA.
    Он позволяет осуществлять передачу данных без использования ядра контроллера между следующими точками:
    • Память-память
    • Память-периферия
    • Периферия-Память.

    Довольно слов, давайте на деле посмотрим, что это нам дает!
    Для начала добавим к инициализации периферии необходимый код:

    	
    DMA1_Channel3->CPAR = (uint32_t)(&SPI1->DR);	
    DMA1_Channel3->CNDTR = 1560;
    DMA1_Channel3->CCR |= DMA_CCR3_PL_0;
    DMA1_Channel3->CCR |= DMA_CCR3_PSIZE_0;
    DMA1_Channel3->CCR |= DMA_CCR3_MSIZE_0;
    DMA1_Channel3->CCR |= DMA_CCR3_DIR;
    DMA1_Channel3->CCR |= DMA_CCR3_MINC;
    DMA1_Channel3->CCR |= DMA_CCR3_EN;
    

    Здесь мы настраиваем адрес, по которому будут записаны данные (он равен адресу регистра данных SPI), устанавливаем количество 16-битных слов для передачи, равное 1560 (мои кадры по размеру составляют 30х52 пикселя), средний приоритет передачи,
    размер приемника и источника – в данном случае, и тот и другой – 16 бит, направление передачи (из памяти в периферию), устанавливаем автоинкремент указателя на память, и включаем DMA.
    После нам следует подготовить несколько кадров в таком же формате, что и описанный раньше const uint16_t Picture[].
    Для этого я подготовил 12 хедеров frame01.h … frame12.h, и объединил данные из них в

    
    const uint16_t* Video[]={frame1,frame2,frame3,frame4,frame5,frame6,frame7,frame8,frame9,frame10,frame11,frame12};
    

    Также введем переменную-счетчик кадров

    
    uint8_t FrameCnt=0;
    

    Осталось совсем немного – в инициализации настраиваем системный таймер на нужную нам частоту смены кадров,

    
    SysTick_Config(SystemCoreClock/50000);
    

    Осталось описать обработчик прерывания таймера:

    
    void SysTick_Handler()
    {
    	*CS=0;
    	FrameCnt++;
    	if(FrameCnt>11)
    	FrameCnt=0;
    	SPI1->CR2 &= ~SPI_CR2_TXDMAEN;
    	DMA1_Channel3->CCR &=~DMA_CCR3_EN;
    	*RS=1;
    	*CS=0; 
    	SendSPI(0xEF90);
    	SendSPI(0x0504);
    	SendSPI(0x0A00+70); 
    	SendSPI(0x0800+50);
    	SendSPI(0x0B1D+70); 
    	SendSPI(0x0933+50); 
    	*RS=0;
    	DMA1_Channel3->CMAR = (uint32_t)Video[FrameCnt];
    	DMA1_Channel3->CNDTR = 1560;
    	DMA1_Channel3->CCR |= DMA_CCR3_EN;
    	SPI1->CR2 |= SPI_CR2_TXDMAEN;
    }
    

    Здесь мы вычисляем номер нашего следующего кадра, после чего отключаем DMA-передачу на время. Это необходимо, чтобы передать дисплею команды на вывод графики.
    В данном случае выводятся небольшие картинки, не во весь экран, т.к. попросту не хватает памяти контроллера. Если же прикрутить сюда чтение с какой-нибудь SD-карточки, то вполне можно крутить полноэкранное видео.

    После того, как команды переданы, мы настраиваем адрес источника графики – один из фреймов, указатели на который хранятся в Video[], устанавливаем размер передаваемого блока и включаем DMA.

    Все! Теперь наша программа тратит на вывод графики не более нескольких десятков тактов каждые 50 мс, те самые, что идут на обработку прерывания по таймеру – после того как прерывание будет обработано, DMA начинает передачу данных в регистр SPI без нашего участия! Таким образом мы можем совершенно спокойно заниматься своими делами в основном цикле, готовить следующий кадр, или обсчитывать какие-нибудь объекты, не заботясь о выводе графики.
    Если все сделано правильно, получаем следующее:


    Заключение


    Чего же мы достигли?
    Прежде всего, мы научились работать с такой интересной периферией, как цветной графический дисплей. Это уже само по себе неплохо и может пригодиться в проектах.
    Но главное – мы научились использовать блок DMA, который открывает широчайшие возможности, по сравнению с софтварной передачей данных на контроллерах, не имеющих такого блока.

    Что касается дисплеев, то я хотел бы добавить следующее: на e-bay я нашел отладочную плату, которую без вопросов назову отличным и необходимым инструментом разработчика на STM32. Продается она тут:Mini-STM32
    image

    За 45 баксов мы получаем невероятно удобный и мощный инструмент, в который входит микроконтроллер STM32F103VET, с аппаратным USB, контроллером статической памяти, в разы облегчающем работу с дисплеем и аппаратным контроллером SD-карт. Собственно дисплей, с разрешением 320х240 и установленным на нем резистивным тач-скрином, контроллером тач-скрина, буст-конвертером для питания подсветки. Вся необходимая обвязка для USB. RS-232 с конвертером. И батарейка, питающая бек-ап регистры контроллера.
    Также, по дополнительному запросу, за 28 долларов в заказ добавят удобный программатор, клон J-LINKа, который, по моим ощущениям, намного стабильнее чем тот, что встроен в отладочную за 300р.

    В общем, настоятельно рекомендую всем, кто собирается заниматься STMками приобрести комплект из этой отладочной платы и программатора. Последующие статьи я буду писать уже с использованием Mini-STM32 как основного инструмента.
    Так как в данный момент по работе я разбираюсь с весьма занятными радио-модулями, то следующая статья, вероятнее всего будет про них.

    Ссылки


    Разбираясь с дисплеем, я пользовался следующими материалами:
    http://www.juras-projects.org/eng/lcd.php
    http://avrhobby.ru/index.php?option=com_content&view=article&id=71:-rgb-1&catid=42:rgb-&Itemid=71
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама
    Комментарии 16
    • +1
      Кстати о птичках — я дисплеи обычно заказываю тут.
      Они почти все как ваш номер 3. Это не реклама, я с этим магазином никак не связан, но может быть кому-то пригодится.
      Месяц назад заказывал себе такой, поиграл с ним и сломал перепаивая его на 16 бит, позавчера заказал размером побольше.
      • 0
        Это он и есть, похоже.
        Хм, а зачем вы перепаивали, если это можно регулировать программно? Возможно, я ошибаюсь, но вроде бы пинами там задается только начальный режим. Обратившись по 8-битному интерфейсу к регистру 0x0C, RGB Display Interface Control, можно поменять значение нескольких бит, и выставить 16-бит шину.
        • 0
          На том который достался мне 8 или 16 бит устанавливаются (если верить документации) очень очень очень мелким smd резистором. Вроде нормально перепаял, но дисплей всё равно сдох. Вечерком обратно перепаяю, может я где то накосячил.
          • 0
            Вам достался точно такой же, они не просто на одном и том же контроллере, но и, похоже, вообще не различаются.
            Резистором задается начальный режим, насколько я понимаю. То есть интерфейс, по которому вы должны начать свое общение с контроллером дисплея. После, обратившись к регистру настройки интерфейса, вы можете его сменить программно.

            Во всяком случае, так я понял даташит.
            • 0
              Спасибо! Попробую реанимировать.
      • 0
        Спасибо, коллега, статья очень полезная — у меня как раз завалялся такой дисплей, а STM32 для меня сейчас — основное направление деятельности. Но у меня есть пара вопросов:

        1) Я правильно понимаю, что вы не используете прерывания по завершению передачи DMA и даже не проверяете флаг готовности? Если так, то код на малых частотах может работать некорректно.

        2) Вот этот вопрос не только к вам, но также ко многим STM32-падаванам: почему вы отказываетесь использовать STM32F* Standard Peripheral Library, а вместо этого работаете напрямую с регистрами периферии, при этом даже не приблизившись к пределу производительности камня? Читабельность кода падает, а выигрыш в скорости мизерный и не критичный для такого кода. Я замечал подобное уже не раз, но, согласно моим экспериментам, SPL «тормозит» только в отладочной сборке, при этом проверяя аргументы функций на корректность и упрощая отладку. В релизной сборке с оптимизациями SPL обычно максимум в 3 раза медленнее прямого доступа к периферии, но при этом код читать и править куда проще.

        Прошу всех STM32шников поучаствовать в обсуждении, но рассуждать объективно, без эмоций (некоторые принимают подобные вопросы как обвинение или вызов).
        • 0
          1) Да, не проверяю, и, разумеется, в случае «боевого» кода так делать нельзя, но этот код демонстрационный и частоты смены кадров заведомо позволяет DMA успеть завершить передачу)

          2) Этот вопрос я рассматривал в первой статье, чтобы понимать как работает SPL неплохо бы знать, как внутри устроены контроллеры периферии, поэтому я фокусирую внимание на работе непосредственно с регистрами в своих статьях.
          • 0
            Полностью поддерживаю позицию по SPL, использую её везде и работать с кодом намного легче. Не нужно всё время вспоминать регистры. + В исходниках библиотеки в заголовках расписана последовательность работы с библиотекой и собственно переферией — как инициализировать, как настраивать, и т. д. Не говоря уж о вложенных примерах.
            • 0
              А у меня обычно получается так, что процессор обрастает собственной библиотекой периферии, с одинаковым интерфейсом для разных платформ. При этом я активно использую средства С++.
            • 0
              Ожидается наплыв спроса на брелки-фоторамки )
              • 0
                Брелок я сам не прочь потестить. Даже не столько из-за дисплея (честно, после дисплеев с е-бей вообще не хочется возиться с выковырянными неизвестно откуда дисплейчиками без док на контроллеры и без нормальных разъемов), сколько из-за того что довеском будет Li-Ion, флешка и классный корпус)
                Есть большое желание развести плату под СТМку, по размерам соответствующую плате из брелока и сделать мобильный девайс.
                • 0
                  В брелках слишком уж мелко всё внутри, как в мобильниках, не подключиться. Можно плату с аккумулятором засунуть вот в такой относительно карманный корпус: iron.snop.ru/ (этот толстый за счет Ethernet'ного magjack'а, но такие же корпуса бывают более тонкие).
              • 0
                Поддерживаю Mini-STM32 — заказывал такой, работает сразу из коробки :-)

                Насчет высоких частот и макетки — подозреваю ошибку монтажа.
                Схемы на 1-10Мгц на макетке у людей работают, а тут частоты существенно скромнее.
                Паразитные емкости в 10pF, пусть даже 100pF — это не то, что может так меандр расколбасить.
                • 0
                  Монтажа там, как такового, не было. Просто отладочная воткнутая в макетку, осцил — на соседних пинах.
                  Это, конечно, было давно — надо бы перепроверить как-нибудь. Но тогда было именно такое впечатление.
                  К тому же — это не меандр расколбасило, графики в разных масштабах. Это с соседнего пина, который настроен на инпут (высокий импеданс то бишь, без подтяжки) снимался меандр в ~100 мВ, как при довольно неплохой емкостной связи.
                  Сам-то меандр вполне чистенький.
                  • 0
                    Ааа… Это болталась дорожка в воздухе… Так оно и на печатке так бы болталась )
                    Вот выдай на соседнюю линию 0, и тогда будем смотреть какие там помехи :-)
                • 0
                  Ну так если это будет какой-нибудь аналоговый вход, то разработчика ждет неприятный сюрприз.
                  Поэтому я и говорю — с этим нужно считаться. На печатке можно отвести дорожку куда следует)
                  Но вообще — да, вся эта помехозащищенность — та еще морока, при разводке можно затра замучаться весьма и весьма, пока помехи уберешь.

                  Я когда-то давно делал какой-то девайс, уже не помню особо какой — помню что там был усилок с выходом на динамик и мигающий диод. Так вот мигание диода отдавалось в динамике пока плату не переразвел, вот это было мерзко)

                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.