u8glib: портирование на Blackfin |
![]() |
Добавил(а) microsin | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Библиотека 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, позволяющая печатать текст в текущей позиции экрана.
[Порт с передачей данных под управлением ядра] Реализация порта "в лоб" оказалась довольно простой. За основу был взят код микроконтроллера 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. |