STM32 + монохромный LCD. Хозяйке на заметку.

Делаю пару устройств на базе STM32 и монохромного дисплея от Нокии. Ну, рассказывать про работу с экранами от нокиевских мобил я не буду, всё уже много раз обжёвано. Но, хочу поделиться парой идей по работе с ними на stm32 (хотя для других cortex-m3 думаю тоже подойдёт). Авось кому пригодится.

Я использую дисплей от nokia 1202, благо он дешевый, с подсветкой, неплохим разрешением и паяемым шлейфом. Но это не принципиально, к другим монохромным экранчикам тоже применимо.

Поворот

В одном девайсе мне оказалось очень удобным разместить экран не горизонтально, как в мобиле, а вертикально. Казалось бы, какая проблема. А проблема оказалась в организации дисплея. Графика на нём отображается построчно, каждый байт — 8 вертикальных точек. Если экран повернуть, получим столбцы с горизонтальными байтами. Выводить часто используемый шрифт 5х8 становится проблематично. Удобно использовать символы шириной 8 пикселей, 8х12, к примеру. Но это надо шрифт искать, библиотеку переделывать. Я подумал что проще создать виртуальный фрейм-буфер, с расположением байт как удобнее, а при отправке на дисплей — переворачивать его. Согласен, решение спорное, имеющее минусы (нужен фрейм-буфер, нельзя просто обновить часть экрана, лишняя работа по переворачиванию и т.д.), но почему бы и нет? Зато можно использовать готовую библиотеку и шрифт. А ещё можно сделать как в смартфонах, поворот информации на экране по датчику с акселерометра :)
Вопрос, как повернуть данные? Задача сводится к повороту блока 8х8 пикселей. Нужно из 8 байт, представляющих 8 вертикальных пикселей каждый, получить 8 байт с горизонтальными пикселями. И вот тут подумалось, если делать это на Си, алгоритм получается относительно сложный, с двоичной магией, сдвигами и в циклах. Нет, я конечно справился бы :) Но ведь в ассемблере (по крайней мере в тех, с которыми я сталкивался ранее) есть классные команды сдвига через флаг переноса (да-да, как правильно сказал angel5a, специальная ассемблерная магия)! Сдвинул один байт, один бит «выпал» в флаг, оттуда его задвинул во временный байт. Потом вытаскиваем бит из следующего байта, вставляем в тот же временный байт. И так 8 раз. И один байт результата готов. Имхо, просто и красиво. Что ж, хороший повод попробовать arm-ассемблер, а точнее набор инструкций Thumb-2.
Кстати, ассемблер оказался весьма интересным. Например тем, что команды с двумя операндами могут заносить результат в третий (а не в первый из них, как обычно). Но можно указывать и два операнда, как привычно. Или тем, что команды могут выполняться по условию, уменьшает число ветвлений. Есть даже «if then else» блок до 4 команд (например, ITEEE EQ означает: из следующих 4 команд первую выполнить, если условие (EQ) выполняется, а другие три — если нет. И задаётся это просто буквами TEEE (от Then и Else), в любых комбинациях. Красота. Ещё прикольная фишка, добавление восклицательного знака в команду LDR R0, [R1, #1]! превращает её из R0 = [R1+1] в R0 = [R1=R1+1], т.е. R1 ещё и инкрементируется в той же команде.
Итак, получилась вот такая функция поворота блока, в синтаксисе Keil'а:
__asm Mirror(uint8_t *vb, uint8_t *res)
{
	push {r2-r6}
	ldmia r0, {r2, r3}

	mov r5, #1    // константы для операций сдвига
	mov r6, #24

	ror r3, #24
	ror r2, #24

	mov r0, #8    // 8 байт

m1

	rors r3, r5
	adc r4, r4
	rors r3, r6
	adc r4, r4
	rors r3, r6
	adc r4, r4
	rors r3, r6
	adc r4, r4
	ror r3, r6

	rors r2, r5
	adc r4, r4
	rors r2, r6
	adc r4, r4
	rors r2, r6
	adc r4, r4
	rors r2, r6
	adc r4, r4
	ror r2, r6

	cmp r0, #5 // если дошли до 5го байта, сохраняем обработанные байты 8-5 (32 бита)
	streq r4, [r1, #4]	// выполнение по совпадению условия

	subs r0, r0, #1
	bne m1

	str r4, [r1]

	pop {r2-r6}

	bx lr
}

Тут ничего сложного. Переменные (адреса блоков по 8 байт) попадают в регистры R0 и R1. «ldmia r0, {r2, r3}» загружает сразу все 8 исходных байт в регистры r2 и r3. К сожалению не оказалось команды циклического сдвига через флаг переноса влево, есть только вправо (RRX). Можно было бы сдвинуть 32 раза вправо, но RRX, в отличии от других команд сдвига, двигает только на один бит. Проще оказалось использовать команду сложения с переносом, adc r4, r4.
Работать должно шустро, если совместить с аппаратным SPI, можно готовить следующий блок, пока отправляется предыдущий. Для 9-bit SPI можно попробовать на лету ещё и перепаковывать в 9 байт. Но я уже не стал заморачиваться, у меня это отдельной функцией выполняется.
Использовать просто:

__align(4) uint8_t vbuf[12][72];
void LcdFlush()
{
	int i, j, k;
	__align(4) uint8_t res[8];

	LcdGotoXY(0, 0);
	for (j=0; j<9; j++)
	{
		for (i=11; i>=0; i--)
		{
			Mirror(&vbuf[i][j*8], res);
			for (k=0; k<8; k++)
			    LcdSend(res[k], 1);   // 1 - признак данных
		}
	}
	SpiFlush();
}

Использование побитового доступа

Для другого проекта захотелось использовать шрифт покрупнее. Например, 8х12. Такой, какой удобно использовать, если дисплей повернуть. Но вот засада, тут как раз хотелось горизонтальной ориентации. Опять же, можно поизвращаться с булевой магией, выводить символ в полторы строки высотой, из 12 точек 8 прямым копированием, а 4 — добавляя с маской. Но тут пришла идея получше. STM32 позволяет обращаться к индивидуальным битам памяти напрямую. Называется это bit banding. Работает это так. Есть область виртуальной памяти, в которой каждое двойное слово соответствует биту оперативной памяти. Пишем туда что-то, отличное от нуля — устанавливаем бит, пишем 0 — сбрасываем. Также можно читать биты. Идеально для виртуального фреймбуфера. Получаем 32 бита на точку, если правильно расположить байты, то вообще получаем линейную видеопамять. Великолепно!
Делается элементарно.

#define RAM_BASE 0x20000000
#define RAM_BB_BASE 0x22000000
#define VBITS ((uint32_t *)(RAM_BB_BASE+((uint32_t)&vbuf[0][0]-RAM_BASE)*32))

__align(4) uint8_t vbuf[72][96/8];

VBITS[y*96 + x]=1; // устанавливаем точку в координаты x, y

Адреса памяти указаны для STM32F100, для других камней лучше уточнить.
Используя такой виртуальный фреймбуфер, легко реализовать вывод любых графических примитивов и вывод текста, причём с попиксельными координатами и переменной шириной символов.
После отрисовки кадра, останется только правильно отправить его на дисплей. У меня биты идут по строкам, так что пришлось совместить с поворотом. Можно было бы обойтись без этого, сделав буфер vbuf[96][72/8] и считая координаты как VBITS[x*72 + y]. Но это уже кому как удобнее.

SPI 9-bit на STM32
Ну и ещё добавлю к теме.
Как известно, в STM32 SPI в режиме 9-bit не работает. А дисплеи этот режим очень любят. В таком случае можно использовать софтовую реализацию. Можно, как это часто делают, использовать синхронный режим работы USART'а для эмуляции SPI. Мне лично эти варианты не нравятся. Софтовый по понятным причинам, а USART’ов мне обычно и так не хватает, их удобно использовать для отладки, связи с UART-USB чипами, шиной 1-wire и т.д. Остаётся вариант с перепаковкой данных в 8 бит перед отправкой. Идею я почерпнул у angel5a, за что ему спасибо.

uint8_t buf_in[9]; // буфер, принимающий данные
uint8_t inbuf=0; // принято байт

void SpiSend(uint16_t bits9)
{
	buf_in[inbuf] |= bits9 >> (1+inbuf);
	buf_in[inbuf+1] = bits9 << (7-inbuf);
	inbuf++;
	if (inbuf==8) SendIt();
}

void SendIt()
{
	... // тут отправляем буфер из 9 байт в SPI, хоть ручками, хоть в DMA.
	memset(buf_in, 0, 9);
	inbuf=0;
}

В случае, если байты надо отправить немедленно, буфер можно добить NOP'ами. Для нокиевских дисплеев и просто нулей хватает. Способ довольно простой, весьма эффективный (особенно в сочетании с DMA), не занимает USART.

Надеюсь, кому-нибудь пригодится.

Bonus

Нарисовал шрифт 8х12. Русские и английские буквы, цифры, символы. Windows-1251. Начинается с символа пробела (0х20). Два массива, самих символов и их ширина. Кому нужен — пользуйтесь без ограничений. В аттаче исходник в bmp png и фонт в Си.
  • +10
  • 24 августа 2012, 01:16
  • ACE
  • 2
Файлы в топике: font.txt, font.png

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

RSS свернуть / развернуть
Помнится мне, что Nokia жки можно было менять способ адресации. Точно уже не помню, но могу посмотреть если интересно… Ну а так не плохо, про bit banding тоже понравилось.
+1
В графических дисплеях вроде было. А в монохромных не встречал. В частности, в n1202 можно только отзеркалить по горизонтали.
0
От Nokia 3310 точно способ адресации меняется — лично проверял. Можно по горизонтали, а можно по вертикали. И дисплей монохромный.
0
Удивительно, только вчера думал про поворот, и тоже пришел к выводу, что легче на ходу поворачивать. Только у меня I2C дисплей, сейчас думаю, как лучше строки выводить… и битовый доступ тоже. В ближайшие дни отпишусь.
0
И вот тут подумалось, если делать это на Си, алгоритм получается относительно сложный, с двоичной магией, ...cut… Но ведь в ассемблере
Надо продолжить так: тоже есть своя магия!

И тоже понравилось про битбенг.
0
Битбэнд. Битбэнг из другой оперы.
0
выложи, плиз, шрифт 8х12. А если есть — и 8х8.
0
  • avatar
  • Flash
  • 24 августа 2012, 11:46
Я его ещё только рисую :) Цифры и символы пока только готовы. Буквы только те, что на скриншоте. Доделаю — тогда поделюсь.
0
чем рисуешь? пробовал вскольз несколько редакторов, во всех нужно долго и нудно ручками…
0
В фотошопе :) Долго и нудно ручками. Не знаю, а мне нравится, сидишь, слушаешь музычку, точечки ставишь себе. Умиротворяет :)
0
это жестолько времени и усидчивости, что б весь шрифт нарисовать.
мне больше всего понравилась FontEdit(or), наша, и забугорная TheDotFactory. Последняя, хоть и серьезный тул, но я не осилил. Кстати, там, помоему, можно вращать символы как угодно.
0
Добавил что получилось к статье.
0
За шрифт спасибо!
Такой вопрос — как применять таблицу uint8_t font8x12_w?
Я так понял, с ее помощью получается использовать не моноширинный шрифт?
0
Ага. Можно тупо выводить символ полностью, а потом сдвигаться на нужное число пикселей вправо. А можно сразу выводить ровно столько пикселей от символа, сколько нужно. Я второй вариант использую
0
У меня есть идея сделать вывод в каком то универсальном виде, протоколе, причём вывод делается просто за пределы контроллера. А за ним стоит преобразователь в интерфейс имеющегося дисплея. Что то вроде мини видеокарты получается. Можно даже со своей памятью. Из плюсов — универсальность и контроллер не заморачивается обсчётом вывода, из минусов — дополнительный, пусть и небольшой контроллер, который занимается только выводом. По идее на это хватит контроллера порядка тиньки. Как идея?
0
Делайте VGA :)
Второй вариант — терминал.
0
Я себе так и сделал — дисплей 2.8 320х240 от китайцев 10$ + контроллер STM32F100C4. Наружу последовательный интерфейс. По сбросу как терминал работает, но можно и графику рисовать. Код взял взял здесь с заменой для контроллера.

0
  • avatar
  • x893
  • 27 августа 2012, 13:41
Хорошая у вас навигация, надо будет и мне так сделать на своём сайте: Портал стройматериалов потолок строительство коттеджей.
-2
А можно рабочий пример посмотреть? Сейчас через USART1 сделано, но хочется эти ноги освободить
0
Ммм, пример чего? SPI9 бит? Так в статье всё есть, кроме настройки самого SPI. А оно от камня зависит и от того, использовать DMA или нет и т.д, но там нет ничего необычного.
0
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.