Софт и утилиты
Конструктор сайтов - uCoz
Приветствую Вас, Пробегающий мимо · RSS 20.09.2017, 11:28
Главная » Статьи » Компиляторы и программирование » ARM

Embedded.NGL();

     Задумал я реализовать что то подобное уже давно и даже что то давно сделал, но во первых та библиотека была фактически заточена на применение только совместно с HX8352, а во вторых недостаточно развита в сторону GUI. Поэтому я задумался над дальнейшем развитием этой библиотеки и добавлением элементов GUI для нее, таких как кнопки, меню, события нажатий и т.д., основываясь на опыте построения меню в NeilScope 3 и других проектах, которыми за это время я занимался, одновременно с этим пытался сохранить максимально возможную скорость работы, учитывая что МК все же обладает ограниченным быстродействием, и думаю мне это вполне удалось.

     Конечно же, как всегда, на многое не хватало времени и  все откладывалось, копясь в виде набора идей, концепций, возможных решений. Но дело все же понемногу двигалось и доросло до бета версии, которой и хочу поделиться. В ходе работы над библиотекой возникали довольно интересные идеи и часть самых интересных, на мой взгляд, я оставил, очень надеюсь у меня со временем получится воплотить их в библиотеке. Итак, библиотека написана на чистом СИ с использованием gcc 4.8, пожалуй начнем со структуры:

      Как видим структура проекта библиотеки довольно проста (не считая кучи линий :) ), но так же достаточно гибка и расширяема, в том числе, и новыми типами контроллеров ЖК, для этого необходимо написать лишь спецефичный к добавляемому контроллеру драйвер. Собственно сам драйвер представляет из себя набор базовых низкоуровневых команд спецефичных конкретному контроллеру ЖК. Программный интерфейс приложения уровня драйверов дисплея используется для сброса дисплея, определения размеров экрана, выделения области, установки текущего цвета и реализации аппаратно-зависимых функций для работы с дисплеем. Для примера рассмотри драйвер под SSD1289, который уже есть в библиотеке. Драйвер состоит из функций реализующих старт записи в память ЖК, установку курсора, очистку экрана и т.д., и переменной типа LCD_Typedef, в которой описывается тип подключения ЖК к микроконтроллеру - FMSC, GPIO(8bit/16bit), количество точек дисплея, адреса регистров для записи/чтения, и указателей на низкоуровневые функции:

/* LCD driver low level functions */
void SSD1289_Init(void);
void SSD1289_WriteRAM_Prepare(void);
void SSD1289_SetCursor(uint16_t X, uint16_t Y);
void SSD1289_SetArea(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1);
void SSD1289_ClearArea(uint32_t PointCounter);
void SSD1289_SetFullScreen(void);
void SSD1289_SetRotation(LCD_RotationState NewState);
void SSD1289_GetPixels(uint8_t *ReadData, uint16_t NumPixels);

/* SSD1289 LCD controller struct */
LCD_Typedef SSD1289 = {

        LCD_GPIO_Connect,                  // GPIO connection mode with LCD
        16,                                             // Data bus bits
        16,                                             // LCD color bits
        239,                                           // X Max
        319,                                           // Y Max
        76800,                                       // All points counter (X_Max + 1) * (Y_Max + 1) = 240 * 320 points
        0x004e,                                      // X register
        0x004f,                                       // Y register
        _0_degree,                                 // Default rotation

        SSD1289_WriteRAM_Prepare,    // Write RAM preapare function
        (void*)0,                                     // Write command function pointer
        (void*)0,                                     // Write data function pointer
        (void*)0,                                     // Read data function pointer
        (void*)0,                                     // Write pixel function pointer
        SSD1289_Init,                            // Initialization LCD controller
        SSD1289_SetRotation,               // Set view rotation
        SSD1289_SetArea,                     // Set out area for draw

        SSD1289_SetCursor,                  // Set position for write cursor
        SSD1289_GetPixels,                   // Read pixels
};

     Обратите внимание на то что некоторые указатели по умолчанию равны (void*)0, данные указатели инициализируются при установке типа подключения/разрядности шины функциями HAL перед началом работы с ЖК.

 

 

     HAL уровень библиотеки содержит набор тех же низкоуровневых функций записи чтения дисплея и состоит из LCD_HAL.c, LCD_HAL.h, LCD_GPIO.c, LCD_GPIO.h. Он тесно связан с аппаратными особенностями МК и драйверами дисплеев, но выделен в отдельный модуль, для удобства добавления новых драйверов, без необходимости внесения изменений в файлах HAL уровня. В одной из последующей частей статьи я покажу, на примере HX8352, как реализуется драйвер "с нуля", и как его добавить в библиотеку.

 

function name description
void LCD_Set_RESET_Pin(GPIO_TypeDef* GPIOx, uint16_t Pin); Init RESET pin for LCD
void LCD_Set_RD_Pin(GPIO_TypeDef* GPIOx, uint16_t Pin); Init RD(read) pin
void LCD_Set_RS_Pin(GPIO_TypeDef* GPIOx, uint16_t Pin); Init RS pin
void LCD_Set_WR_Pin(GPIO_TypeDef* GPIOx, uint16_t Pin); Init WR(write) pin
void LCD_Set_CS_Pin(GPIO_TypeDef* GPIOx, uint16_t Pin); Init CS(chip select) pin
void LCD_SetDataPort(GPIO_TypeDef* GPIOx, uint16_t GPIO_Pins); Init MCU IO pins for LCD data bus
   
LCD_Typedef* NGL_LCD_GetController_FromIndex(uint8_t index); Get controller struct var from index, for support LCDs indexes see NGL_types.h
int8_t NGL_LCD_ControllerInit(LCD_Typedef* LCD_init, void *pDelayFunc);

Init sequence LCD controller,

LCD_init - pointer to initialization structure

pDelayFunc - pointer to delay function for internal use of library

return 0 if OK, -1 if ERROR
void NGL_LCD_SetRotation(NGL_RotationLCD NewState); Set rotation - 0,90,180,270 degree
void NGL_LCD_WriteRegister(uint16_t RegisterAddress, uint16_t Data);

Write LCD controller register

 

 

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

function name description
void NGL_LCD_SetFullScreen(void); Set fullscreen out draw area
void NGL_LCD_ClearArea(X0, Y0, X1, Y1, uint16_t Color); Clear area rectangle P0(X0, Y0), P1(X1, Y1)
void NGL_LCD_Clear_AllScreen(uint16_t Color); Clear all screen
void NGL_LCD_SetCursor(uint16_t X, uint16_t Y); Set cursor to point position P(X, Y)
void NGL_LCD_PutPixel(uint16_t Color); Draw pixel for current cursor position
void NGL_LCD_GetPixels(uint8_t *ReadData, uint16_t NumPixels); Read several pixels from current cursor position

 

 

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

function name description
void NGL_GP_SetAntialiasingLine_State(uint8_t NewState); ON/OFF antialiase for lines (NOT WORK NOW)
void NGL_GP_DrawPixel(uint16_t X, uint16_t Y, uint16_t Color); Draw singe pixel
void void NGL_GP_DrawLine(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Color); Draw line
void NGL_GP_DrawRect(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Color); Draw rectangle
void NGL_GP_DrawFillRect(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Color, NGL_DrawState border, uint16_t borderColor); Draw fill rectangle with optional border
void NGL_GP_DrawCircle(uint16_t X, uint16_t Y, uint16_t Radius, uint16_t Color); Draw circle
void NGL_GP_DrawBeveledRect(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Radius, uint16_t Color); Draw rectangle with rounded corners
void NGL_GP_DrawFillBeveledRect(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1, uint16_t Radius, uint16_t Color, uint8_t border, uint16_t borderColor); Draw filled rectangle with rounded corners and optional border

 

 

     Picture & Fonts модуль отвечает за работу с выводом текста и изображений, вывод текста возможен посимвольно и строками, есть также возможность измерить в пикселях ширину символа/строки. Библиотека поддерживает разные шрифты, не обязательно моношринные. В плане отрисовки изображений библиотека позволяет использовать форматы 1/8/16 бит на точку, поддерживает сжатие RLE и PicoJPG. Для конвертации шрифтов и изображений под библиотеку можно использовать софт NGL GUI Creator, собственно с его помощью можно делать не только это а и значительно больше, во второй части статьи я расскажу про создание готовых интерфейсов в этой программе.

 

function name description
void NGL_Font_SetFont(const NGL_Font *selectFont); Select active font
uint8_t NGL_Font_DrawChar(uint16_t x, uint16_t y, NGL_TransparentState trspr, char c); Draw one char with optional transparent
uint16_t NGL_Font_DrawString(uint16_t XPos, uint16_t YPos, uint8_t trspr, char *str); Draw string with active NGL_TextColor color, optional transparent
uint16_t NGL_Font_DrawColorString(uint16_t X, uint16_t Y, uint16_t Color, NGL_TransparentState trspr, char *str);

Draw string with specified color.

Somewhat slower function then NGL_Font_DrawString when a large number of output strings

uint16_t NGL_Font_DrawCropString(uint16_t X, uint16_t Y, NGL_TransparentState trspr, char *str, uint8_t Pos, uint8_t Lenght, uint16_t Color);

Draw cropped string from Pos in input sting to Pos+Lenght

Example:

NGL_Font_DrawCropString(5, 5, Transparent, "TestCropString", 4, 4, Red);

It will display or update only "Crop" on the LCD

uint16_t NGL_Font_MeasureStringWidth(char *str); Measure width of string in pixel.
uint16_t NGL_Font_MeasureCropStringWidth(char *str, uint8_t NumSymbols); Measure width part of string in pixel.
void NGL_uintToString(uint32_t Num, char* Str, uint8_t NumSymbol); Convert uint32_t to string.
void NGL_DrawImage(uint16_t X, uint16_t Y, const NGL_Image *Image); Draw image

 

 

     GUI уровень позволяет создавать страницы(меню), добавлять или удалять элементы и обрабатывать события нажатий этих элементов, также предоставляет функции отрисовки и очистки, с возможностью callback функций. Для многих интерфейсов этого будет более чем достаточно, а что то более или менее спецефичное можно реализовать на уровне пользовательского приложения. Собственно страницы и реализация функций событий не включается в саму библиотеку, это сделано для большей гибкости, а что бы облегчить создание страниц и рутины создания функций событий, а также для более удобного создания интерфейса(расстановка кнопок и т.д.) и была создана NGL GUI Creator. Функций внутри библиотеки немногоб вот они:

 

function name description
void NGL_DrawGrid(NGL_Grid *Grid, NGL_DrawState state); Draw simple grid
void NGL_DrawButton(const NGL_Button *Button, NGL_ButtonState pushState); Draw button
void NGL_DrawLabel(const NGL_Label *Label); Draw label
void NGL_UpdateLabel(const NGL_Label *Label, char *NewText); Update label, redrawed only changes chars, fastest method for update.

 

 

     API библиотеки это фактически файл с расширением .h, который необходимо добавить к пользовательскому проекту вместе с файлом самой библиотеки - NGL_Lib.a. В файле NGL.h собраны все доступные пользователю функции, а в NGL_types.h собраны все спецефичные типы данных используемые в библиотеке.

 

 

Добавляем контроллер HX8352 на примере NeilScope3

    Итак нужно добавить HX8352 в библиотеку, откроем проект в Em::Blocks или Coocox (оба в одном репозитории на github). Что имеем? Имеем 8-ми битную шину данных и 16-ти битный цвет на точку, ага, еще соединение по GPIO, а не по FSMC. Хорошо, значит нужно создать HAL уровень для этого контроллера, создаем новый файл и называем его HX8352.c, это и будет наш HAL. Смотрим как устроен HAL для уже существующих контроллеров в библиотеке, сперва нам нужно обьявить и заполнить переменную:

 

LCD_Typedef HX8352 = {

 

        LCD_GPIO_Connect,  // помним что у нас GPIO
        8,                             // 8-ми битная шина данных
        16,                           // цвет 16 бит на точку
        239,                         // максимум по Х для "родной" ореинтации ЖК
        399,                         // максимум по Y для "родной" ореинтации ЖК
        96000,                      // количество точек (X_Max + 1) * (Y_Max + 1) = 240 * 400 = 96000
        0x004e,                    // регистр установки курсора по X
        0x004f,                     // регистр установки курсора по Y
        _0_degree,               // поворот ЖК 0 гр.

       HX8352_WriteRAM_Prepare,    // указатель функции внутренней подготовки ЖК к записи в буферную память
        (void*)0,                    // указатель на функцию записи команды в ЖК
        (void*)0,                    // указатель на функцию записи данных в ЖК
        (void*)0,                    // указатель на функцию чтения данных из ЖК
        (void*)0,                    // указатель на функцию записи точки
        HX8352_Init,                // указатель на функцию инициализации контроллера ЖК
        HX8352_SetRotation,   // указатель на функцию поворота ЖК
        HX8352_SetArea,           // указатель на функцию установки области для вывода на ЖК

        HX8352_SetCursor,           // указатель на функцию установки курсора ЖК
        HX8352_GetPixels,           // указатель на функцию чтения из ЖК
};

    Вот в принципе и весь HAL собранный в одной переменной структуре, конечно же нужно реализовать функции указатели на которые я проставил, для начала обьявим их:

static void HX8352_WriteRAM_Prepare(void);
static void HX8352_Init(void);
static void HX8352_SetRotation(NGL_RotationLCD NewState);
static void HX8352_SetArea(uint16_t X0, uint16_t Y0, uint16_t X1, uint16_t Y1);
static void HX8352_SetCursor(uint16_t X, uint16_t Y);
static void HX8352_GetPixels(uint8_t *ReadData, uint16_t NumPixels);

    Эти функции единственное что нужно обьявлять и реализовывать сразу, функции же записи/чтения ЖК устанавливаются при инициализации контроллера, конкретно к нашему примеру подключения оно инициализируется так:

LCD->WriteCommand = NGL_LCD_GPIO_WriteCommand;    // указатель на функцию записи команды в ЖК
LCD->WriteData = NGL_LCD_GPIO_WriteData;                    // указатель на функцию записи данных в ЖК
LCD->ReadData = (void*)0;                                                  // указатель на функцию чтения данных из ЖК, чтение не используем
LCD->WritePixel = NGL_LCD_GPIO_WriteData_16bpp_8b;    // указатель на функцию записи точки

Параметры шины и прочего также можно сразу не проставлять а указать их при инициализации в пользовательском коде, то есть их можно изменять "на лету", специально к этому не стримился так вышло само, но возможно кому то будет полезна данная возможность.

 

  Итак далее реализуем небходимые функции, читая даташит на контроллер конечно :) , выкидывать сюда это полотно функций считаю ненужным, вот можно посмотреть результат - HX8352.c, на этом все, контроллер добавлен и больше нам не требуется внесения изменений в библиотеку, скомпилируем ее, и будем переходить к пользовательскому приложению. Единственное что это я не реализовал установку поворота ЖК и поэтому поворот не будет использоваться в примере далее.

    Создадим проект под STM32F103RB который установлен в NS3, скопируем в папку проекта файлы - NGL.a, NGL.h и NGL_types.h, и добавим их в проект, далее пишем в main.c:

#include "stm32f10x.h"
#include "NGL.h"
    
LCD_Typedef *LCD_Struct;


int main(void)
{

    /* Конфигурируем выводы МК под ЖК */
    LCD_CtrlLinesConfig();

    /* Сброс ЖК -----------------------------------------------------------------*/
    GPIO_ResetBits(GPIOD, GPIO_Pin_12);
    delay_ms(50);
    GPIO_SetBits(GPIOD, GPIO_Pin_12);
    delay_ms(50);

    /* Инициализация параметров и контроллера ЖК ---------------------------------------------*/
    LCD_Struct = NGL_LCD_GetController_FromIndex(__HX8352__);   // получаем указать на структуру которую мы создали ранее в библиотеке
    LCD_Struct->DataBusBits = 8;  // устанавливаем разрядность шины
    LCD_Struct->ColorBits = 16;     // устанавливаем бит на точку
    LCD_Struct->ConnectionType = LCD_GPIO_Connect;  // тип подключения GPIO

    LCD_Set_RS_Pin(GPIOD, GPIO_Pin_2);  // вывод - RS
    LCD_Set_WR_Pin(GPIOB, GPIO_Pin_4);  // вывод записи - WR
    LCD_Set_CS_Pin(GPIOB, GPIO_Pin_3);   // вывод выбора чипа - CS
    LCD_SetDataPort(GPIOC, GPIO_Pin_7_0); // установить порт данных, используем младшие 8 бит, старшие свободны и могут использоватся как угодно
        
    /* Инициализация контроллера ЖК и установка функции задержки(используется библиотекой для реализации своих задержек) */
    int8_t LCD_Init_Status =  NGL_LCD_ControllerInit(LCD_Struct, delay_ms);

    /* Проверяем статус */
    if(LCD_Init_Status != 0)
    {
        while(1)
        {
            // Ошибка :( , что то пошло не так, висим сдесь навсегда
        }
    }
    else
    {
        /**< Все ОК, если бы реализовал то устанавливали бы поворот ЖК, очищаем экран черным*/
//        NGL_LCD_SetRotation(_0_degree);
        NGL_LCD_Clear_AllScreen(0x0000);        
    }

// Добавим еще отрисовку например прямоугольника

NGL_GP_DrawFillRect(
            50,
            100,
            100,
            150,
            0xF800,
            DRAW,
            0xFFFF);

  while(1)
  {
  }

}

Все, можем тестировать! Компилируем, прошиваем и вуаля :), извиняюсь правда за качество фото.

 

Продолжение скоро будет....

 

 

 

Ссылки:

Репозиторий проектов библиотеки NGL под CoocoxIDE / Em:Bmlocks на github - https://github.com/LeftRadio/ngl

Репозиторий тестового проекта HX8352 на github -  https://github.com/LeftRadio/hx8352-test

Репозиторий NGL GUI Creator ( C# ) на github - https://github.com/LeftRadio/ngl-gui-creator

Репозиторий проекта HS USB Audio использующий библиотеку NGL на github - https://github.com/LeftRadio/neil-T11

 

 

 

 

 

Категория: ARM | Добавил: LeftRadio (17.04.2014)
Просмотров: 792 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]