Программирование DSP u8glib: портирование на Blackfin Thu, November 21 2024  

Поделиться

Нашли опечатку?

Пожалуйста, сообщите об этом - просто выделите ошибочное слово или фразу и нажмите Shift Enter.


u8glib: портирование на Blackfin Печать
Добавил(а) microsin   

u8glib and BlackfinВ этой статье будет краткий обзор возможностей библиотеки u8glib [1] и описание её использования совместно с процессором Blackfin ADSP-BF538 и индикатором OLED WEX025664 на контроллере SSD1322 [2].

Библиотека u8glib была выбрана потому, что она бесплатная, в ней было уже много готовых шрифтов и что самое главное - был готовый код для инициализации моего индикатора OLED. Это избавляет от необходимости изучать даташит на контроллер SSD1322 и упрощает программирование графики. Однако в библиотеке я не нашел готового кода, который позволил бы использовать аппаратуру Blackfin, поэтому низкоуровневый код пришлось добавлять самостоятельно. 

[Схема подключения]

WEX025664 Blackfin connect

На рисунке показана схема подключения индикатора 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.

 

Добавить комментарий


Защитный код
Обновить

Top of Page