Дон Графон представляет: дисплей от Samsung SGH-E830 и векторная графика в примитиве.

Приключилась тут со мной очередная мобильно-дисплейная история. Уж и не знаю – радоваться, или плакать. Вроде и справился, да не совсем добился того, чего хотел. Ну да ладно, по порядку.

Один мой коллега сломал свой телефон. Причем сломал весьма распространенным образом: «лежал-лежал, работал, а потом – бах и дисплей перестал показывать!» Раздобыл он где-то новый дисп, воткнул в телефон – не робит! Изрыгая благой мат, дядька этот бегом в отдел разработчиков, причем к самому молодому (одногруппник мой, межи прочим), мол, молодежь все знает. А тот меня и сдал: иди – говорит – к Дэну, он умеет. Что умеет?
Чувак мозоли натер, прибег ко мне со своими дисплеями. Вот, мол, это было, это стало. Что за беда?
Я глянул – а он на SGH-J600 дисп от SGH-E830 поставил. Непорядок – говорю. – Так работать не будет. И объясняю ему доходчиво, что к чему, и в какое заднее отверстие второму дисплею он воткнул то, что торчало у первого дисплея в отверстии переднем. Тут у чувака мозг, видать, вскипел – говорит, мол, занимаешься? На, забирай это все хозяйство, занимайся. И насыпал мне дисплейчиков разных штук несколько. Видать, его деструктивный вклад в развитие мобильных технологий был весьма и весьма ощутимым. В качестве первого пациента я избрал именно этот дисплей, ибо остальные оказались китайскими, а там могли быть какие угодно сюрпризы, не считая абсолютного отсутствия информации.
Информации и на этот дисплей оказалось с гулькин нос. Распиновка разъема в сервисном мануале, и на том. Причем разъема не на плате дисплея, а на основной плате. Сам дисплей обладает расширением 176x220 точек, способен отображать 265 тыс. цветов. Матрица – TFT. В принципе, неплох даже для использования в каком-либо устройстве. Сработан интересно весьма: шлейф дисплея интегрирован в плату, то есть разведен где-то по промежуточным слоям. Отделить стекло от платы невозможно.

И вид сзади на плату:

Широкий разъем внизу – это разъем шлейфа на основную плату. Этот разъем я поначалу и пытался вызвонить в соответствии со схемой, и прямо диву давался – ничто схеме не соответствовало (обычно я прозваниваю все «земли»)! Только уж потом я догадался, что царь-то, говорят, не настоящий разъем не тот.
Шлейф мне, в общем-то, достался. Но выглядел печально, светил унылым рваным боком – примерно посередине до центра был напрочь разорван; прямая прозвонка отпадала. Ремонтники на туче такой шлейф в своих заначках не нашли, посему задачку пришлось решать непростую.
Наждачкой-нулевкой я снял верхний слой пленки шлейфа с двух сторон, как у одного, так и у другого разъемов, обнажив медь проводников. Фотографии не публикую, дабы не навредить психике детей и беременных женщин, которые, несомненно, могут прочесть этот материал.
Изготовил внушающую благоговейный ужас тулзу вот такого виду:

Эта «рыба-игла», как несложно догадаться, предназначена для использования в качестве щупа мультиметра – для прозвонки тонких проводников. Далее я размалевал шаблон таблицы соответствий выводов разъемов, перекрестился и принялся орудовать этим счупом как заправский херу хирург.
Описание часов этого адова труда я опущу. К концу всего этого процесса у меня болела спина, болел зад от сидения, и глаза – от напряжения. Но отступать было поздно – я уже вошел в раж.
В общем, разъем я вызвонил, таблицу составил. Главным было не это. Я с самого начала заприметил на плате аккуратные квадратные контрольные точки для сервисников, и сразу у меня промелькнула мысль их использовать – все сигнальные линии просто обязаны быть выведены на одну из контактных групп на плате. Я попробовал прозвонить – и мои надежды оправдались.
В итоге, результатом нескольких часов напряженной работы стала следующая распиновка (привожу в том виде, в котором эта группа располагается на плате).

Здесь у нас:
  • LD0..LD15 – 16-разрядная шина данных;
  • LCD_CS – Chipselect;
  • RS – выбор «команда/данные»;
  • L_WR – строб записи;
  • L_RES – сигнал сброса Reset;
  • VIO_2.8 – питание портов и логики (2,8 В);
  • VBAT – питание подсветки (3,7..4,2 В);
Линии «LCD_ESD_DETECT» и «SUB_LED_EN» — это управление включением / выключением подсветки; их мы трогать не будем, так же как и не будем подключать к питанию вывод «VBAT» ввиду специфичности величины напряжения.
Дисплей обладает двумя особенностями. Первая – отсутствие линии строба чтения. То есть ни о чем дисплей мы спрашивать не будем, мы будем просто его пинать командами и кормить данными. Вторая особенность – объединенное питание логики и портов ввода/вывода, в отличие от привычных уже раздельных 2,8 В -> 1,8 В. Это удобно, так как никаких преобразователей по питанию и логических уровней городить не придется. Ну и еще можно к удобной особенности отнести возможность питания подсветки от трех вольт.
Порывшись в своих хламовниках, обнаружил идеально подходящую переходную плату, которую когда-то лепил…не помню для чего. Плату посадил на термоклей, который в последнее время постоянно использую при изготовлении подобных поделок. После закрепления «переходника» и распайки контактов плата приобрела следующий вид.

Собственно группа контактных площадок с распаянными проводниками.

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

Весь этот дикий ужоснах был изрядно залит термоклеем. Сверху я закрепил защитный фрагмент текстолита, распаял свой разъем дисплея, и также приклеил его к плате.

Количество сигнальных линий и их предназначение недвузначно намекает на работу по интерфейсу i8080 (урезанный вариант – без сигнала «Read»). Все подобные мелкие проекты и проектики я прогоняю на плате STM32F4Discovery, и в данном случае решил вешать дисплей на аппаратный контроллер FSMC. Где-то здесь на сайте есть шикарный пример подключения дисплея на контроллере SSD1289 к FSMC микроконтроллера STM32.
Схема соответствия выводов дисплея и микроконтроллера приведена ниже.

Все прекрасно, но имеет место быть проблема – я не имею ни малейшего представления о том, какой контроллер установлен в имеемом девайсе. Путем изучения массы материалов на различных сайтах, устанавливаю – контроллер называется LTS200Q (кстати, это написано прямо на плате… но кто же знал?)
Производителем LTS200 является компания Genesis Tech, якобы являющаяся то ли партнером, то ли дочерним предприятием Samsung. Так или иначе, опубликовать подробную информацию – скажем, даташит, им невмоготу. Поэтому они опубликовали какой-то мелкий brief manual, и на том успокоились.
Первым делом я занялся анализом различных даташитов на различные дисплеи, которые способны работать по интерфейсу i8080, дабы по возможности вычислить общие черты.
Изначально я отталкивался от даташита на LCD-контроллер SSD1289, постепенно пробуя не сработавшие команды из различных прочих даташитов. Что-то срабатывало, что-то – нет. Например, установка адреса по горизонтали (адрес 0x44) работает так же, как и у SSD1289, один в один. А установка адреса по вертикали работает, но не так, как положено: устанавливается не адрес заполнения, а адрес неактивной области.

И самое печальное: данные на дисплей выводятся по вертикали через строку. Причем контроллер дисплея считает, что заполняет две строки, в то время как используется четыре строки дисплея: первая и третья заполняются данными, вторая и четвертая заливаются просто белым нейтральным (нормальным) цветом.
Тут имеет место быть такая вещь, как interlacing – чересстрочное отображение. Эта вещь отключается записью определенных данных в определенный регистр. Только вот в какой – главный вопрос. Ответа на него я так и не нашел.
Вывод мог быть только один: дисплей можно использовать только в составе каких-то отладочных средств; в качестве отладочного терминала. Об использовании в каком-либо изделии можно забыть.
Можно было бросить его в ящик (мусорный, к примеру), но я не мог себе позволить просто взять и похоронить такое количество времени, которое я уже потратил. Поэтому было решено идти до какого-то логического конца (и я об этом не пожалел в итоге, кстати).
Забегая вперед. Вот вам квадратик и кружочек. И корявый текст.

Ладно, эротические игрища с математикой – потом. А сейчас –

Достаем с полки губозакатывательную машинку.

Микроконтроллер с FSMC контроллером на борту – это шик и блеск, конечно. Но не следует забывать, что и у STM32 этот контроллер имеется не у всех представителей; а у кого и имеется, то начиная с корпусов LQFP100/LQFP144. Причины, думается, понятны. Вот у STM32F30x его и вообще нет – эта новость намедни повергла меня в грусть и сопливую тоску (демоборда уже где-то в пути просто). У меня, например, только у STM32F407 такой есть, а окромя 407-го у меня только пара STM32L151, пара STM32F103 да пара STM32F100, и все – в 64-ногих корпусах. То, что на работе на готовых платах распаяно – не в счет. А если с помощью «меги» какой хотим управлять дисплеем? Стало быть, будем организовывать управление дисплеем с помощью мною обожаемого ногодрыга. Хотя «организовывать» — слишком громко звучит, на деле все обстоит гораздо проще. Я не буду разглагольствовать о работе по интерфейсу i8080 (в принципе), в случае с дисплеями все немножко проще. Достаточно глянуть на диаграммы в любом даташите любого дисплея, поддерживающего любой интерфейс i8080.

Помня, что линии сигнала «RD» (Read) у нас нет, работаем только лишь на вывод информации. Запись в любой регистр производится в два этапа: запись адреса (индекса) и запись собственно данных в регистр. Эти последовательности при работе с дисплеем также используются раздельно (подготовка к записи в GRAM, к примеру), поэтому мы напишем отдельные функции для каждого из этапов, а при необходимости использовать обе будем вызывать их по очереди. В принципе, единственное, чем отличаются эти последовательности – это различный уровень линии RS.
По диаграмме прикидываем последовательность действий.

Для большего удобства мне придется перепаять шлейф от дисплея к F407Discovery. Под шину данных (LD0-LD15) я отведу GPIOE полностью, линию «CS» подключу к GPIOB_11, линию «RESET» — к GPIOB_12, «WR» — к GPIOB_13, и «RS» — к GPIOB_14.
Вообще, если писать на Си, есть как минимум два варианта реализации алгоритма. Можно написать так.

void LCD_WriteRegIndex (uint16_t RegIndex){
    GPIOB->ODR &= ~ GPIO_ODR_ODR_11;
    GPIOB->ODR &= ~ GPIO_ODR_ODR_14;
    GPIOE->ODR = RegIndex;
    GPIOB->ODR &= ~ GPIO_ODR_ODR_13;
    GPIOB->ODR |= GPIO_ODR_ODR_13;
    GPIOB->ODR |= GPIO_ODR_ODR_14;
    GPIOB->ODR |= GPIO_ODR_ODR_11;
  return;
}
/******************************************************************************/
void LCD_WriteRegValue (uint16_t RegValue){    
    GPIOB->ODR &= ~ GPIO_ODR_ODR_11;
    GPIOB->ODR |= GPIO_ODR_ODR_14;
    GPIOE->ODR = RegValue;
    GPIOB->ODR &= ~ GPIO_ODR_ODR_13;
    GPIOB->ODR |= GPIO_ODR_ODR_13;
    GPIOB->ODR |= GPIO_ODR_ODR_14;
    GPIOB->ODR |= GPIO_ODR_ODR_11;
  return;
}

А можно, не мудрствуя лукаво, воспользоваться стандартной библиотекой (эй, парень, спрячь свои гнилые помидоры) и написать так:

void LCD_WriteRegIndex (uint16_t RegIndex){
    
    GPIO_ResetBits(GPIOB, GPIO_Pin_11);
    GPIO_ResetBits(GPIOB, GPIO_Pin_14);
    GPIOE->ODR = RegIndex;
    GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    GPIO_SetBits(GPIOB, GPIO_Pin_13);
    GPIO_SetBits(GPIOB, GPIO_Pin_14);
    GPIO_SetBits(GPIOB, GPIO_Pin_11);
  return;
}
/******************************************************************************/
void LCD_WriteRegValue (uint16_t RegValue){
    
    GPIO_ResetBits(GPIOB, GPIO_Pin_11);
    GPIO_SetBits(GPIOB, GPIO_Pin_14);
    GPIOE->ODR = RegValue;
    GPIO_ResetBits(GPIOB, GPIO_Pin_13);
    GPIO_SetBits(GPIOB, GPIO_Pin_13);
    GPIO_SetBits(GPIOB, GPIO_Pin_14);
    GPIO_SetBits(GPIOB, GPIO_Pin_11);    
  return;
}

Но я забегу чуть вперед (дабы быть сейчас последовательным), и расскажу такую вещь. Когда я писал эдакую самоклепанную графическую библиотечку (о ней дальше), используя вышеозначенный дисплей, то использовал подключение к FSMC, а при переходе на «ручное управление» стал замечать более медленную отрисовку деталей, особенно в случае анимации. Связка функций записи в регистр дисплея является единицей передачи данных от микроконтроллера к LCD, и используется постоянно. Поэтому даже незначительная задержка, вносимая при каждом вызове, станет причиной существенного замедления вывода изображения на дисплей.
Вот тут меня посетила одна скабрезная мысль: написать данные функции на ассемблере, и использовать в проекте. В исходнике инициализации дисплея – прототипы функций «_wr_index(uint16_t RegIndex)» и «_wr_value(uint16_t RegValue)», а их реализация – в asm-исходнике «i8080.s». Это обстоятельство я подчеркнул спецификатором «extern» (дабы в будущем не создавать себе сложностей).
Естественно, меня терзали смутные сомнения, а не генерит ли IAR асм-код почище моего, и не обманул ли я сам себя эдаким хитрым способом через левое плечо? Поэтому я погонял все три реализации и сравнил код. При использовании StdPeriphLib каждая из функций представлена восемнадцатью командами на асме (!!); без использования – двадцатью четырьмя. Я написал функции, каждая из которых «весит» по 12 команд. Однако визуально разительного эффекта не вышло, отрисовка выровнялась без подрагиваний, да и все. И на том спасибо.
Мне не хотелось перекраивать исходник и удалять возможность работать с помощью FSMC. Посему я воспользовался директивами препроцессора «#ifdef», «#ifndef» и иже с ними, и в итоге получил возможность работать как через FSMC, так и без оного, всего лишь прописывая/удаляя строки:

#ifndef  USE_FSMC          //Erase or comment these three lines if you don't use FSMC
#define USE_FSMC
#endif

Ну, вот, к примеру, как преобразились вышеприведенные функции.

/******************************************************************************/
void LCD_WriteRegIndex (uint16_t RegIndex){

    #ifdef USE_FSMC
    /* Write 16-bit Index */
    LCD->LCD_REG = RegIndex;
    #else
    _wr_index(RegIndex);
    #endif
  return;
}
/******************************************************************************/
void LCD_WriteRegValue (uint16_t RegValue){
    #ifdef USE_FSMC
    /* Write 16-bit Reg */
    LCD->LCD_RAM = RegValue;
    #else
    _wr_value(RegValue); 
    #endif
  return;
}
/******************************************************************************/

Порисуем? ;)

«Дон Графон, выходи!» — Кричали дети,
пританцовывая возле туалета.

Вообще, изначально данный раздел не планировался. Я хотел описать запуск дисплея, привести красивую картинку, и закончить. Да только на этот дисплей какую картинку ни выведи – все говн не очень красиво получится. Не фотографировать же дисплей с заливкой одним цветом и радостной подписью: «представьте, камрады, что на дисплее изображены котики!».
Воспользовавшись случаем, я решил позаниматься выводом на дисплей текста и различных геометрических фигур.
Так в проект вошли четыре файла: по два заголовочных и по два исходника с соответствующими именами: «TextGenerator_MobLCD» и «Grafon_MobileLCD». Текст – это не очень интересно, а вот на втором остановлюсь.
Этот исходник представляет из себя нечто вроде полубиблиотеки. Являясь исходным файлом уже высокого уровня, который не взаимодействует с аппаратной частью напрямую, он вполне может быть использован в проекте на любом другом дисплее, что лично мне, думаю, пригодится – тем более, что я увлекся простейшей векторной графикой и связанной с нею математикой (преподавательница по Высшей математике из университета, узнав об этом, прослезилась бы), и намерен постепенно «библиотеку» насыщать. На входе, для нормального функционирования, «Grafon_MobileLCD.c» требует четыре функции взаимодействия с железом (но, по большому счету, достаточно двух – позже поясню): функция зарисовки пикселя («PutPixel (uint8_t pixelx,uint8_t pixely,uint16_t color)»), функция заливки области определенным цветом («FillArea (uint8_t startx, uint8_t finishx, uint8_t starty,uint8_t finishy, uint16_t color)»), и две необязательные: отрисовка горизонтальной линии («DrawHorizontalLine (uint8_t startx,uint8_t finishx,uint8_t line,uint16_t color)») и отрисовка вертикальной линии («DrawVerticalLine (uint8_t row, uint8_t starty, uint8_t finishy,uint16_t color)»). Две последние стали необязательным после того, как я написал функцию проведения рандомной линии в любом направлении. С нее и начнем.
Если вспомнить уравнение прямой, проходящей через две несовпадающие точки, то в общем виде его можно представить так:

Выразив y через x, получаем прекрасную во всех отношениях функцию.

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

/******************************************************************************/
/* input parameters: random line x start and x finish; y start and y finish
    coordinates, line color code. Works with both zero and non-zero x&y values,
    is a replacing complement function for functions DrawHorizontalLine and
    DrawVerticalLine. */
void DrawRandomLine (uint8_t startx, uint8_t finishx, uint8_t starty,
                     uint8_t finishy, uint16_t color){
    uint8_t     dl;
    uint8_t     xmax;
    float intmdtx, intmdty;

    if (startx==finishx) {                              //if a vertical line
        for (dl=starty; dl<(finishy+1); dl++) {
                PutPixel(startx,dl,color);}
        }
    else if (starty==finishy) {                         //if a horizontal line
        for (dl=startx; dl<(finishx+1); dl++) {
                PutPixel(dl,starty,color);}
        }
    else {
        if (startx>finishx) {xmax=startx; xmin=finishx;}//define x max/min for the following calculations
        else if (startx<finishx) {xmax=finishx; xmin=startx;}
        intmdtx=startx+0.1;                             //we need to use the next step after startx
            while (intmdtx<=xmax) {                     //equation of a line through two points
                    intmdty=((finishx*starty-startx*finishy)-((starty-finishy)*intmdtx))/(finishx-startx);
                    PutPixel((uint8_t)intmdtx,(uint8_t)intmdty,color);
                    intmdtx=intmdtx+0.1;}
        }
    return;
}
/******************************************************************************/

Изображение квадратов и прямоугольников, а также залитых прямоугольников, я описывать не хочу: это неинтересно. Никакого креатива и усилия мысли – сплошная рутина и рисование прямых линий.
Лучше придумаем, как изобразить окружность.
Я знаю два варианта навскидку: по уму и как сделал я. Вариант «по уму» мне вначале не подходил в силу ряда причин. Позже стало индифферентно, но переписывать ничего я не стал, хотя это и несложно. По уму – это значит с использованием параметрического уравнения окружности. Я же сделал несколько иначе.
Любую окружность можно разбить на четверти: I, II, III и IV.

Любая точка окружности вместе с точкой начала координат и точкой на одной из осей (на которую опущен перпендикуляр из точки на окружности) образует прямоугольный треугольник. Опуская перпендикуляр на ось абсцисс, с помощью величины радиуса окружности и координаты x, для каждого x мы можем получить значение y:

Таким образом, задавая значения x в определенном диапазоне, мы получим массив соответствующих значений y. Такие расчеты производятся для каждой четверти окружности по отдельности, параллельно производится построение.
То есть функция «DrawEmptyCircle (uint8_t centerx, uint8_t centery,uint8_t radius, uint16_t color)» в качестве входных параметров принимает координаты центра, величину радиуса, и цвет прорисовки.
Как это можно (и, пожалуй, нужно) было сделать. Существует параметрическое уравнение окружности:

Если мы зададим значение «фи» в пределах [0,2PI), то количество телодвижений уменьшится на порядок, а времени будет сэкономлено немало. Но мы ведь не ищем легких путей…
Моя девушка очень любит изображения сердечек. Поэтому следующая фигура, над изображением которой я задумался, была кардиоида.

Кардиоида – замечательная алгебраическая кривая четвертого порядка. Про улиток Паскаля, эпициклоиды, каспы и прочие лимаконы можно почитать в Википедии – это на самом деле очень интересно.
Параметрически (в прямоугольных координатах) кардиоида описывается следующим образом:

где «фи» принадлежит [0;2PI).
Получаем:

Отталкиваясь от данной системы, будем производить построение. Сначала введем понятие некоторого коэффициента плотности зарисовки фигуры на основании величины радиуса. Этот коэффициент вычислялся эмпирически и равен 2r^2. Собственно говоря, это делитель всего диапазона, в котором могут лежать значения «фи», и от него зависит величина приращения угла «фи». Чем меньше радиус, тем меньше делитель, следовательно, больше шаг и меньше точек, из которых состоит фигура. Чем же радиус больше, тем больше делитель и меньше шаг, а количество точек, из которого состоит фигура, увеличивается. Все логично.
Далее производится вычисление минимумов и максимумов координат, в пределах которых будет лежать фигура, параметры которой передает вызывающая функция. То есть мы определяем – а влезет ли фигура вообще в пределы дисплея, и не будет ли какая-либо ее часть «торчать» с краю. Если выходит, строить мы ее не будем – сразу возврат из функции.
И, наконец, на отрезке [0;2PI) производим вычисление и построение фигуры. В целом, это выглядит вот таким макаром.

/******************************************************************************/
/* input parameters: cardioid center x and y coordinates, radius value,
   cardioid color code */
void DrawEmptyCardioid (uint8_t centerx, uint8_t centery, uint8_t radi,
                        uint16_t color) {
    uint8_t xmax, fillfactor;
    int8_t xmin, ymin, ymax;
    float    angle=0;                                           //radians
    float32_t itrmedx, itrmedy;
                /* Calculation of the fill factor depending on radius */
    fillfactor=2*(radi*radi);
                /* Calculation of the minimum and maximum values of the x and y,
                    necessary for drawing of the figure in full */
        xmin=centerx-(4*radi);
            if (xmin<=0) return;
        angle=PI/4;
        /* x=2rcos(t)-rcos(2t) */
        itrmedx=(2*radi*(arm_cos_f32(angle)))-(radi*arm_cos_f32(2*(angle)));
        xmax=centerx+(int8_t)itrmedx;
            if (xmax>LCD_MAXWIDTH) return;
        angle=(3*PI)/2;
        /* y=2rsin(t)-rsin(2t) */
        itrmedy=(2*radi*(arm_sin_f32(angle)))-(radi*arm_sin_f32(2*(angle)));
        ymin=centery+(int8_t)itrmedy;
            if (ymin<=0) return;
        angle=PI/2;
        itrmedy=(2*radi*(arm_sin_f32(angle)))-(radi*arm_sin_f32(2*(angle)));
        ymax=centery+(int8_t)itrmedy;
            if (ymax>LCD_MAXHEIGHT/2) return;        //divided by 2 since interlacing
            angle=0;
                /* Draw a cardioid */
    while(angle<2*PI) {
        itrmedx=(2*radi*(arm_cos_f32(angle)))-(radi*arm_cos_f32(2*(angle)));
            xmin=centerx+(int8_t)itrmedx;
        itrmedy=(2*radi*(arm_sin_f32(angle)))-(radi*arm_sin_f32(2*(angle)));
            ymin=centery+((int8_t)itrmedy/2);
            PutPixel(xmin,ymin,color);
            angle=angle+(PI/fillfactor);
    }
  return;
}

Функции вычисления синуса и косинуса взяты мной из DSP библиотеки ST для STM32F4xx (не из IAR). Они включены в проект непосредственно, исходники присутствуют в архиве.

Кстати, девушка кардиоиду оценила. «Ой, классное сердечко!» — пропищала она. «Похоже на ж*пу.» — одобрительно кивнул мой коллега. В общем, всем понравилось.

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

Вращение производится в вертикальной плоскости, посему рассмотрим движение в разрезе плоскости горизонтальной. Параллелепипед имеет четыре крайние точки, движение которых нам предстоит рассчитывать и на основании которых будет производиться весь процесс отображения движения; это точки x1, x2, x3, x4. Все эти точки, как видно из рисунка, движутся по окружности, а значит, мы можем работать с движением этих точек, взяв на вооружение параметрическое уравнение окружности. Нужно заметить, что нас совершенно не интересует координата y, а значит, нам пригодится только уравнение для x:

Для того, чтобы успешно рассчитывать расстояние между точками и производить построение, мы изначально должны рассчитать величину угла «фи». Ведь аргумент функции, к примеру, x1(«кси») будет меняться в пределах [0;2PI), а положение x2 будет рассчитываться из соображений того, что аргументом будет являться сумма углов («кси»+«фи»).
Воспользуемся теоремой косинусов и выведем формулу вычисления «фи».

Здесь r – это радиус описанной окружности. Имея длины сторон a и c в качестве переданных параметров, мы легко вычислим r с помощью прямоугольного треугольника, образованного r, a/2 и высотой, опущенной из точки O на сторону a.

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

if (dir==CNTRCLOCKWISE) fract=(PI/divfactor);   //set the initial value of the fractional part
else if (dir==CLOCKWISE) fract=-(PI/divfactor); //of rhe increment

Итак, сначала мы вычисляем радиус описанной окружности и величину угла «фи». Затем рассчитываем величины минимального и максимального значений x, исходя из длин сторон. Потом в цикле рассчитываем значения всех четырех x, и производим построение закрашенных прямоугольников (которые являются сторонами параллелепипеда), исходя из условий, определяемых особенностями вращения нашей фигуры – например, направлением вращения.
Вот и вся функция целиком.

/******************************************************************************/
/* input parameters: rectangle start x and y coordinates, length of the a, b and
   c sides. Color codes of the three sides, rotating direction, frame delay.
*/
void RotatingParallelepiped (uint8_t startx, uint8_t starty, uint8_t sidea,
                             uint8_t sideb, uint8_t sidec, uint16_t colora,
                             uint16_t colorc, uint16_t bcolor, uint8_t dir,
                             uint16_t frame) {
    uint8_t rad,xminpos,xmaxpos,rp;
    uint8_t x1,x2,x3,x4,divfactor;
    uint8_t xmin,xmax,xintmi1,xintmi2,xintma1,xintma2;
    uint16_t tcolor;
    float32_t phi,inc,fract;

    if (sidea<sidec) {
        xminpos=sidea; xmaxpos=sidec;
        sidea=xmaxpos; sidec=xminpos;
        tcolor=colora; colora=colorc; colorc=tcolor;}
    divfactor=40;
    if (dir==CNTRCLOCKWISE) fract=(PI/divfactor);  //set the initial value of the fractional part
    else if (dir==CLOCKWISE) fract=-(PI/divfactor);//of rhe increment

    inc=fract;                                                                                             
    rad=(SquareRoot4((sidea*sidea)+(sidec*sidec))/2);    //calculation of the circumcircle

    phi=acosf((float)((2*rad*rad)-(sidec*sidec))/(2*rad*rad)); //calculation of the phi angle value
    xminpos=(uint8_t)(rad*(arm_cos_f32(PI+(phi/2)))+(startx+(sidea/2)));//minimum possible x position
    xmaxpos=(uint8_t)(rad*(arm_cos_f32((PI+PI)+(phi/2)))+(startx+(sidea/2)));//maximum possible x position

        for (rp=0; rp<(divfactor-1); rp++) {
            x1=(uint8_t)(rad*(arm_cos_f32((PI-(phi/2))+inc))+(startx+(sidea/2)));   //Calculation of the keypoints coordinates
            x2=(uint8_t)(rad*(arm_cos_f32((PI+(phi/2))+inc))+(startx+(sidea/2)));
            x3=(uint8_t)(rad*(arm_cos_f32((PI+(PI-phi/2))+inc))+(startx+(sidea/2)));
            x4=(uint8_t)(rad*(arm_cos_f32((PI+(PI+phi/2))+inc))+(startx+(sidea/2)));
            /*Calculation of the min and max points*/
              if (x1>x2)        {xintma1=x1; xintmi1=x2;}
              else {xintmi1=x1; xintma1=x2;}
              if (x3>x4)        {xintma2=x3; xintmi2=x4;}
              else {xintma2=x4; xintmi2=x3;}
              if (xintma1>xintma2) xmax=xintma1; else xmax=xintma2;
              if (xintmi1<xintmi2) xmin=xintmi1; else xmin=xintmi2;
                if (x2<x3) {
                DrawFilledRectangle (x2,x3,starty,starty+sideb,bcolor,colora);   //Drawing the polygones
                    if (dir==CLOCKWISE) DrawFilledRectangle (x3,x4,starty,starty+sideb,bcolor,colorc);
                    else if (dir==CNTRCLOCKWISE) DrawFilledRectangle (x1,x2,starty,starty+sideb,bcolor,colorc); }
                else if (x2==x3) {
                    if (dir==CLOCKWISE) DrawFilledRectangle (x3,x4,starty,starty+sideb,bcolor,colorc);
                    else if (dir==CNTRCLOCKWISE) DrawFilledRectangle (x1,x2,starty,starty+sideb,bcolor,colorc); }
                else if (x2>x3) {
                    if (dir==CLOCKWISE) DrawFilledRectangle (x3,x4,starty,starty+sideb,bcolor,colorc);
                    else if (dir==CNTRCLOCKWISE) DrawFilledRectangle (x1,x2,starty,starty+sideb,bcolor,colorc);
                DrawFilledRectangle (x4,x1,starty,starty+sideb,bcolor,colora); }
                Delay_ms(frame);
                if ((xminpos<=xmin)&&(xmaxpos>=xmax)){     //renewal the workspace with background
                FillArea (xminpos,xmin-1,starty,starty+sideb,bcolor);
                FillArea (xmax+1,xmaxpos,starty,starty+sideb,bcolor);}
                inc=inc+fract;
        }

  return;
}

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



Вот и все. Если что, простите за объемный текст. Увлекся-с…
  • +10
  • 04 декабря 2012, 05:38
  • SubDia
  • 1
Файлы в топике: STM32F407_&_Samsung SGH-E830_LCD.zip

Комментарии (34)

RSS свернуть / развернуть
Осилил только до губозакатывательной, на самое интересное не хватило. Для чего столько мата в тексте? И работа колосальная проделана, и ничего кроме негатива не вызывает.
0
А надо было начать с снобозакатывательной. Может тогда и негатива было бы меньше, и жизнь казалась приятнее?

Боже, это я сказал?!
+2
Как же проити мимо такой милой картинки? Вы что?
+1
Господь с Вами, где же здесь мат? Или Вы о слове «ж*па»? Возможно, усталость дала о себе знать, и силы кончились еще до оформления, не знаю…
0
В принципе, свежим взглядом пересмотрел, в некоторые моментах соглашусь.
Подредактировал. Негативное впечатление — не есть хорошо. =)
0
Благодарю. Продолжим чтиво вечерком.
0
ну зачем же вы так строго к нему :) (хотя может я какую-то версию статьи не увидел..) И код легко читается, и теория. Ну предыстория с юмором описана, не страшно. Интересная статья, такие проще читаются и с юмором, а главное он в этой теме затронул вопрос, что нужно быть настойчивее в желаниях и не выбрасывать а реанимировать. Скоро вся планета будет в электронном мусоре, 80% которого можно смело вторично использовать.
В любом случае, плюсану, ибо мне понравилась статейка. ;)
+1
Алгоритм Брезенхема? Не?
0
не. Как вариант, но я в тот момент не искал готовых решений.
0
А зря. Брезенхем рисует наклонные линии с той-же скоростью, что вы горизонтальные и вертикальные — всё в целых числах.
0
Спорить не стану. Тут дело такое: задумка начиналась изначально как «отбалдежная». А аппетит пришел во время еды. Но на это есть пересмотр существующих решений и оптимизация.)
0
void LCD_WriteRegIndex (uint16_t RegIndex){
ИМХО, здесь бы inline не помешало. Алсо, не будет ли оптимальней передавать сразу буфер?
Поэтому мы будем использовать дробное приращение к x, дабы не допустить изображения прерывистых линий.
Float на МК там, где без него можно обойтись — ИМХО, не лучшая идея. Почему бы не взять описанный в вики алгоритм Брезенхема в целочисленной имплементации?
Такие расчеты производятся для каждой четверти окружности по отдельности
Зачем? Достаточно получить координаты точки для одного квадранта, а для остальных получить их отражением относительно центра. Ну и в той же статье про алгоритм Брезенхема есть и про более оптимальное рисование окружностей.
0
  • avatar
  • Vga
  • 04 декабря 2012, 11:15
ИМХО, здесь бы inline не помешало.
Возможно, возможно. Даже, скорее всего, есть смысл над этим подумать.
Почему бы не взять описанный в вики алгоритм Брезенхема в целочисленной имплементации?
Вариант.
Достаточно получить координаты точки для одного квадранта, а для остальных получить их отражением относительно центра.
Была реализована первая пришедшая в голову идея. В принципе, как я выше написал, всегда можно поправиться.
0
Из текста не понял победили интерлейс или все таки нет?
0
К сожалению, нет. Пока нет. =(
interlacing – чересстрочное отображение. Эта вещь отключается записью определенных данных в определенный регистр. Только вот в какой – главный вопрос. Ответа на него я так и не нашел.
0
Такая «радость» как интерлейсинг, бывает, если что не так с интерфейсами, точнее, если один из проводников не подключен. Вы думаете, что софтово пишется?
0
Думаю, софтово. Например, у ILI9320 (я наобум открыл сейчас даташит) есть настройки чересстрочного отображения (регистр LCD Driving Waveform Control (R02h)). Думаю, здесь что-то аналогичное.
Что касается проводников — мне всю эту конструкцию в один момент пришлось переделывать (подсветку распаивать полностью), и перепаивать все, вплоть до «волосков». Результат не изменился. Кроме того, после монтажа я всегда тщательно всё прозваниваю. Думается, суть именно в программной конфигурации LCD.
0
отслеживая стиль написания, можно сделать вывод — в случае удачного запуска, мы бы увидели картинку с non-interlaced :)
Все равно, забавно! ТС молодца…
0
Может быть, если бы я проявил чуть больше упорства, я бы запустил в нормальном режиме, но я устал тыкаться по углам как котенок.
Благодарю за теплые слова.)
0
SubDia
Ага я тож не не понял. :) И еще про мужика бы того что же в итоге ему отдано было. :)
И вопросец маленький часто беретесь за такие сомнительные «удовольствия»
0
А мужику сразу было отдано «спасибо», которое не булькает. Ну да он и не просил.)
Бывает, что берусь и за такие удовольствия, особенно когда душа требует приключений на одно место.
0
Такому экрану место на свалке истории, я бы даже не стал заморачиваться.
А за вторую часть с графической математикой — респект, будет время, тоже побалуюсь.
0
Да пес с ним, с дисплеем. =)
Зато было интересно.
+1
Автор — счастливый человек. Есть время заниматься такой чепухой. Я б этот дисплей в мусор выбросил, а он столько труда приложил, чтоб поиграться, да еще и статейку написал… Молодец, канешна :)
0
Тут, понимаете ли, с какой стороны посмотреть. С точки зрения материально-потребительской — да, экран — фигня, отличного результата не вышло, время потрачено, и прочее, и прочее.
Но это как раз та ситуация, когда важен именно процесс. Поверьте, я получил бесценный опыт, я наконец-то вдумчиво и внимательно изучил thumb команды ассемблера для arm, немало повозился с математикой, освоил кое-какие приемы программирования на Си, и это еще не все. Да дисплей — ерунда, я могу хоть сейчас пойти и бросить его в мусор. А главное — что знания и приобретенный опыт останутся-то в голове, они в мусорку не улетят… И это самое важное во всем, что я сделал.
Время — да, я его потратил. Но потратил не напрасно, оно того стоило.
+4
У меня тоже иногда возникает желание поковыряться с железками, но времени к сожалению нет. Тут 2 больших проекта в разработке (как обычно, «нужно еще вчера») и так постоянно. Вот я и позавидовал чуток :) А больше всего я завидую ребятам, которые в лабораториях разрабатывают роботов для министерства обороны США. Посоны играются целый день в дорогущие игрушки, при этом получая немаленькую зарплату. Вот это — мечта!
+1
Посоны к успеху пришли. =)
По поводу времени — понимаю. Особенно когда этого всего на работе наешься, домой пришел — ничего не хочется, особенно колупать какие-то железяки.
+1
эх, ловил себя на этом кучу раз, с этим нужно бороться и дома продолжать воевать ))
0
Перенес в коллекитвный блог. Если вы будете править пост, то он опять улетит в ваш личный. Чтобы его не телепало, то тоже вступите в «Работа с дисплеями и графикой»
0
Что называется, не успел переставить винду, похоронив при этом линукс.)
Да, я в «дисплеях и графике» давно состою.
0
Ди, а может имеет смысл тебе как модератору сделать запрос на коллективный, нажав кнопочку? Автору темы пришло бы письмо и у него всегда бесило бы на любой страничку. И автор темы когда закончит подтвердит, о после этого улетает тема куда пожелаешь… Так вроде более удобно было бы всем и более человечнне. Если автор не захочет в коллекивный, просто отменит твой запрос.
0
Это имело бы смысл если бы движок это умел. Модератор не всесилен.
0
А почему бы автору не захотеть в коллективный, если оно того стоит? По-моему, и так вполне прекрасно.)
(Ну, это если не учитывать невозможность реализации, как выяснилось.)
0
Попался мне такой-же дисплей и еще дисплей от sgh-e250.
На дисплее от e250 написано lts200qq-f05.
На схеме телефона указан контроллер S6D0118, команды совпадают вроде с вашими.
Если еще есть необходимость попробуйте как S6D0118 запустить, хотя размеры кристаллов не совпадают
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.