В этой статье будет краткий обзор возможностей библиотеки u8glib [1] и описание её использования совместно с процессором Blackfin ADSP-BF538 и индикатором OLED WEX025664 на контроллере SSD1322 [2].
Библиотека u8glib была выбрана потому, что она бесплатная, в ней было уже много готовых шрифтов и что самое главное - был готовый код для инициализации моего индикатора OLED. Это избавляет от необходимости изучать даташит на контроллер SSD1322 и упрощает программирование графики. Однако в библиотеке я не нашел готового кода, который позволил бы использовать аппаратуру Blackfin, поэтому низкоуровневый код пришлось добавлять самостоятельно.
[Схема подключения]
На рисунке показана схема подключения индикатора OLED WEX025664 к процессору Blackfin ADSP-BF538. Сигналы для индикатора MOSI и SCK формируются аппаратно под управлением периферийного порта SPI1, а сигналы D/C# (адрес/команда), CS# (выборка), RES# (сброс) формируются программно, переключением портов GPIO. При таком подключении данные передаются в индикатор только в одном направлении, прочитать данные из индикатора нельзя.
Сигнал RES# показан серым цветом потому, что он фактически является необязательным, потому что программа при старте его сразу устанавливает в высокий уровень и больше не трогает. Сигнал выборки CS# в общем-то также практически не нужен, потому что он при старте сразу переводится в лог. 0, и больше не переключается. Сигнал D/C# переключается при переустановки указателя RAM индикатора на начало экрана (перед выводом данных буфера экрана на индикатор), также он может переключаться при смене режима экрана или при вводе индикатора в режим пониженного энергопотребления, или при подаче других команд на индикатор.
[Краткое описание функций библиотеки u8glib]
Прототипы всех публичных функций приведены в заголовочном файле u8g.h библиотеки. Все функции, рисующие на экране, в качестве 1 параметра принимают указатель на глобальную переменную типа u8g_t, хранящую графический контекст. Перед вызовом любой функции библиотеки нужен вызов функции наподобие u8g_InitHWSPI, которая инициализирует эту переменную и настраивает индикатор в первоначальное состояние. Все координаты x, y отсчитываются от левого верхнего края экрана, и растут вправо и вниз. Графика и текст рисуются цветом пера, который можно переустановить вызовом функции u8g_SetColorIndex. Текст выводится текущим выбранным шрифтом, который можно поменять вызовом функции u8g_SetFont. Функции с суффиксом P (сравните u8g_DrawBitmap и u8g_DrawBitmapP) в качестве исходных данных принимают адрес не на ОЗУ, а на память программ (это актуально для AVR, но не актуально для Blackfin).
Функции для порта с передачей данных под управлением ядра должны вызываться из Picture Loop (т. е. они работают традиционно, через буфер страницы экрана). Функции для порта с передачей данных через DMA не нуждаются в Picture Loop и рисуют растр прямо в буфере экрана (подробнее см. проекты с исходным кодом в архиве [3]).
Примечание: мой индикатор OLED WEX025664 поддерживает 16 цветов (т. е. 16 градаций яркости). Однако сколько реально будет выводить цветов библиотека - зависит от низкоуровневой функции вывода на индикатор, указатель на которую передается в вызове u8g_InitHWSPI. Порт с передачей данных под управлением ядра может использовать варианты с 2 цветами (u8g_dev_ssd1322_nhd31oled_bw_hw_spi) и с 4 цветами (u8g_dev_ssd1322_nhd31oled_gr_hw_spi). Порт с выводом через DMA переделан таким образом, что на экран выводятся 16 цветов.
Ниже приведена таблица со списком основных функций библиотеки. Имена функций даны в упрощенном виде - без префиксов (имеются в виду u8g_ или U8GLIB::, которые зависят от варианта функции для языка C или C++) и без суффиксов, модифицирующих поведение функции (например, суффикс P, определяющий память программ для исходных данных функции, или суффиксы 90/180/270, задающие ориентацию вывода текста). Полное описание функций (на английском языке) см. в официальной документации на u8glib (строка для поиска u8glib userreference site:code.google.com).
Библиотека u8glib может быть в 2 версиях: для языка C и языка C++. Версия для C++ несколько более продвинутая, имеет больше возможностей - например, есть функция print, позволяющая печатать текст в текущей позиции экрана.
Общие функции управления дисплеем
|
GetWidth(*u8g) |
Макрос запрашивает ширину экрана дисплея в пикселах. |
GetHeight(*u8g) |
Макрос запрашивает высоту экрана дисплея в пикселах. |
Функции рисования графики
|
DrawPixel(*u8g, x, y) |
Закрашивает точку текущим цветом в позиции x, y экрана. |
DrawLine(*u8g, x1, y1, x2, y2) |
Рисует линию от точки x1y1 до точки x2y2. |
DrawHLine(*u8g, x, y, w) |
Рисует горизонтальную линию, начинающуюся в точке с координатами x, y и длиной w. |
DrawVLine(*u8g, x, y, w) |
Рисует вертикальную линию, начинающуюся в точке с координатами x, y и длиной w. |
DrawBox(*u8g, x, y, w, h) |
Рисует заполненный цветом пера прямоугольник, левый верхний угол имеет координаты x и y, ширина и высота соответственно w и h. Прямоугольник растет от начальной точки вправо и вниз. |
DrawFrame(*u8g, x, y, w, h) |
Рисует прямоугольную рамку (не заполненный прямоугольник). Параметры те же самые, что и у u8g_DrawBox. |
DrawRBox(*u8g, x, y, w, h, r) |
Рисует заполненный прямоугольник с закругленными краями. Параметры те же, что и у u8g_DrawBox, плюс добавился параметр r, задающий радиус скругления. |
DrawRFrame(*u8g, x, y, w, h, r) |
То же самое, что и u8g_DrawRBox, только прямоугольник не заполнен цветом пера. |
DrawCircle(*u8g, x0, y0, rad, option) |
Рисует окружность (всю целиком или любые из 4 секторов окружности, как задано в параметре option) с центром в точке x0y0 и радиусом rad. |
DrawDisc(*u8g, x0, y0, rad, option) |
То же самое, что и DrawCircle, только рисует закрашенный круг (или его сектора). |
DrawEllipse(*u8g, x0, y0, rx, ry, option) |
То же самое, что и DrawCircle, только рисует эллипс, поэтому указано 2 радиуса: rx и ry. |
DrawFilledEllipse(*u8g, x0, y0, rx, ry, option) |
То же самое, что и DrawEllipse, только рисует закрашенный эллипс или его сектора. |
DrawTriangle(*u8g, x0, y0, x1, y1, x2, y2) |
Рисует заполненный цветом треугольник с вершинами x0y0, x1y1, x2y2. |
DrawBitmap(*u8g, x, y, cnt, h, *bitmap) |
Рисует растровую монохромную картинку bitmap в указанной позиции (x, y задают левый верхний угол картинки). Параметр cnt задает количество байт в строке картинки (ширина картинки равна cnt*8), параметр h задает высоту картинки в пикселах. |
DrawXBM(*u8g, x, y, w, h, *bitmap) |
То же самое, что и u8g_DrawBitmap, разница только в том, что рисуются растровые данные картинки в формате XBM Bitmap. Вместо параметра cnt теперь стоит параметр w, задающий ширину картинки в пикселах. |
Функции рисования текста
|
SetFont(*u8g, *font) |
Задает текущий используемый шрифт для рисуемого текста. |
DrawStr(*u8g, x, y, *s) |
Рисует текст s в позиции x, y экрана. Точка x, y соответствует началу базовой линии текста, т. е. нижнему левому краю рисуемой строки. Текст может рисоваться в разной ориентации (для этого есть функции DrawStr с суффиксами 90/180/270). |
GetFontAscent(*u8g) |
Запрашивает текущую высоту шрифта вверх от базовой линии. |
GetFontDescent(*u8g) |
Запрашивает текущую высоту шрифта вниз от базовой линии. |
getFontLineSpacing(*u8g) |
Запрашивает текущее расстояние между строк текста по вертикали. Вычисляется от величин ascent и descent, и умножается на текущий коэффициент LineSpacingFactor. |
SetFontLineSpacingFactor(*u8g, factor) |
Устанавливает коэффициент factor, определяющий расстояние между строками текста по вертикали. |
Функции управления цветом
|
GetColorIndex(*u8g) |
Получает текущий цвет, которым рисуют функции. Например, для экрана OLED WEX025664 максимум может быть до 16 цветов (градации серого, значение цвета от 0 до 15, 4 бита на точку растра). На индикаторе WEX025664 библиотека u8glib может также рисовать в монохромном режиме (2 цвета, 1 бит на точку растра) и 4 цветами (4 градации яркости, 2 бита на точку растра). |
SetColorIndex(*u8g, color_index) |
Устанавливает текущий цвет, которым рисуют функции. |
SetContrast(*u8g, contast) |
Устанавливает контраст дисплея (0..255). Не все дисплеи поддерживают установку контраста. |
[Порт с передачей данных под управлением ядра]
Реализация порта "в лоб" оказалась довольно простой. За основу был взят код микроконтроллера AVR, и все низкоуровневые функции, работающие с портом SPI, были переписаны на порт SPI1 процессора Blackfin. Вот кусок кода из модуля u8g_com_blackfin_hw_spi.cpp, который инициализирует порт SPI и передает каждый байт данных на индикатор (весь проект целиком можно скачать по ссылке [3]):
static uint8_t u8g_blackfin_spi_out(uint8_t data)
{
/* отправка данных */
*pSPI1_TDBR = data;
/* ожидание окончания передачи */
while (*pSPI1_STAT & TXS)
;
while (0==(*pSPI1_STAT & SPIF))
;
return data;
}
uint8_t u8g_com_blackfin_hw_spi_fn(u8g_t *u8g, uint8_t msg, uint8_t arg_val, void *arg_ptr)
{
switch(msg)
{
...
case U8G_COM_MSG_INIT:
*pPORTDIO_FER |= portCSind;
*pPORTDIO_DIR |= portCSind;
*pPORTDIO_FER |= portDCind;
*pPORTDIO_DIR |= portDCind;
*pPORTDIO_FER |= portRSTind;
*pPORTDIO_SET = portRSTind;
*pPORTDIO_DIR |= portRSTind;
u8g_SetPILevel(u8g, U8G_PI_CS, 1);
//Настройка SPI1 на 1 МГц (1000 кГц)
*pSPI1_BAUD = get_sclk_hz()/(2*1000000);
*pSPI1_CTL = SPE|MSTR|CPOL|CPHA|TDBR_CORE;
break;
case U8G_COM_MSG_ADDRESS:
/* переключение в режим команды (arg_val = 0) или в режим данных (arg_val = 1) */
u8g_SetPILevel(u8g, U8G_PI_A0, arg_val);
break;
case U8G_COM_MSG_CHIP_SELECT:
if ( arg_val == 0 )
{
// запрет индикатора
u8g_SetPILevel(u8g, U8G_PI_CS, 1);
}
else
{
// разрешение индикатора
u8g_SetPILevel(u8g, U8G_PI_CS, 0); // CS = 0
}
break;
case U8G_COM_MSG_RESET:
u8g_SetPILevel(u8g, U8G_PI_RESET, arg_val);
break;
case U8G_COM_MSG_WRITE_BYTE:
u8g_blackfin_spi_out(arg_val);
break;
case U8G_COM_MSG_WRITE_SEQ:
{
register uint8_t *ptr = (uint8_t*)arg_ptr;
while( arg_val > 0 )
{
u8g_blackfin_spi_out(*ptr++);
arg_val--;
}
}
break;
case U8G_COM_MSG_WRITE_SEQ_P:
{
register uint8_t *ptr = (uint8_t *)arg_ptr;
while( arg_val > 0 )
{
u8g_blackfin_spi_out(u8g_pgm_read(ptr));
ptr++;
arg_val--;
}
}
break;
}
return 1;
}
В этом примере скорость SPI установлена на 1 МГц, хотя передача данных нормально работает на частотах вплоть до 8 МГц.
[Порт с передачей данных под управлением DMA]
Если в системе достаточно оперативной памяти и есть возможность организовать полный буфер экрана, то передача данных под управлением программы, по одному байту с ожиданием завершения передачи становится уже не эффективной. Программа могла бы работать намного быстрее, если организовать блочную передачу всего экрана под управлением DMA. Тогда ядро процессора разгрузится для полезных вычислений, и не будет тратить лишние такты на пустые циклы ожидания и постоянные запуски передачи каждого байта данных.
Для организации полного буфера экрана нужно было удалить из u8glib её основную фичу - страничную отрисовку экрана по частям с вызовами подпрограмм u8g_FirstPage и u8g_NextPage. Из-за того, что u8glib поддерживает множество индикаторов, заточена на младшие модели процессоров с акцентом на минимизации требований к памяти платформы, и еще находится в состоянии развития, то её код получился довольно объемным. Вот к примеру иерархия вызова функции рисования эллипса, пока программа наконец не доберется до вывода данных на индикатор:
u8g_DrawFilledEllipse -> u8g_draw_filled_ellipse -> u8g_draw_filled_ellipse_section -> u8g_DrawVLine -> u8g_draw_vline -> u8g_Draw8Pixel -> u8g_Draw8PixelLL -> dev->dev_fn (U8G_DEV_MSG_SET_8PIXEL), это то же самое что вызов u8g_dev_ssd1322_nhd31oled_gr_fn -> u8g_dev_pb8h2_base_fn -> u8g_pb8h2_Set8PixelStd -> u8g_pb8h2_SetPixel -> u8g_pb8h2_set_pixel
Эта иерархия вызовов сопровождается постоянными проверками на выход точки за пределы рисуемой страницы, так что код оказался сложным для анализа. К тому же исходный код задокументирован достаточно слабо, и документация по функциям практически отсутствует. Поэтому пришлось повозиться, пока удалось разобраться.
Под экран был выделен в SDRAM массив размером в 8192 байт. Такой размер потребовался из соображений организации адресного пространства индикатора WEX025664, когда на каждую точку выделяется по 4 бита (16 градаций яркости в каждой точке), 256 точек по горизонтали, 64 точки по вертикали.
[Рис. Как организован экран WEX025664 в режиме 16 градаций серого]
Всего получается 256 * 64 = 16384 точки, и так как одного байта хватает на 2 точки, то как раз и получается размер буфера экрана 8192 байта:
section ("sdram0") uint8_t Screen4bit[8192];
Процедура передачи данных экрана состоит из первоначальной настройки индикатора (передача ему команды 0x15, которая устанавливает текущий указатель RAM индикатора на начало столбца и строки экрана), это происходит традиционным способом, под управлением процессора. Передача команды небольшая по размеру, поэтому отнимает мало времени процессора. Затем в индикатор передается весь массив экрана, байт за байтом, под управлением DMA. Вот кусок кода из функции main, который выполняет запуск передачи данных через DMA:
int main( void )
{
...
while(1)
{
draw(); //код, который напрямую рисует в буфере экрана
//Функция blackfin_hw_spi_write_DMA_done проверяет завершение передачи DMA.
if (blackfin_hw_spi_write_DMA_done())
{
//переключение SPI1 в обычный режим:
*pSPI1_CTL = SPE|MSTR|CPOL|CPHA|TDBR_CORE;
//отправка команды 0x15 на установку OLED на начало экрана:
u8g_dev_ssd1322_2bit_prepare_screen(&u8gval, u8gval.dev);
//переключение SPI1 в режим DMA:
*pSPI1_CTL = SPE|MSTR|CPOL|CPHA|TDBR_DMA;
//запуск отправки данных через DMA:
blackfin_hw_spi_write_DMA(Screen4bit, sizeof(Screen4bit));
}
}
}
Все функции рисования были переделаны так, чтобы удалить страничную отрисовку экрана. Функция u8g_pb8h2_SetPixel теперь рисует в буфере экрана напрямую:
void u8g_pb8h2_SetPixel(u8g_pb_t *b, const u8g_dev_arg_pixel_t * const arg_pixel)
{
register uint8_t cleamask;
register uint16_t shift;
uint8_t *ptr;
uint8_t x, y;
if ((WIDTH>arg_pixel->x)&&(HEIGHT>arg_pixel->y))
{
x = arg_pixel->x;
y = arg_pixel->y;
ptr = Screen4bit + (WIDTH >> 1) * y + (x >> 1);
shift = (x & 0x01)?0:4;
cleamask = ~(0x0F << shift);
*ptr &= cleamask;
*ptr |= ((arg_pixel->color & 0x0F) << shift);
}
}
Проект полностью можно скачать по ссылке [3].
[Ссылки]
1. u8glib site:code.google.com. 2. SSD1322: контроллер/драйвер для дисплеев OLED/PLED. 3. 150924u8glib-blackfin.zip - проекты u8glib-test-generic и u8glib-test-DMA (VisualDSP++ 5.0), демонстрирующие использование библиотеки u8glib вместе с процессором Blackfin. 4. u8glib: что такое Picture Loop. |