本节将指导你如何在不同的平台上移植SGL图形库

SGL移植步骤

PC模拟器

  1. 安装make工具,确保make命令可用

  2. 安装MinGW工具链,确保gcc命令可用,这里推荐一个gcc工具链地址:MinGW 13.2.0 工具链

  3. 安装git工具,确保git命令可用

  4. 安装git工具后,打开git命令行,依次输入如下命令,即可完成SGL的移植:

        git clone https://github.com/sgl-org/sgl-port-windows.git
        cd sgl-port-windows
        git submodule init
        git submodule update --remote
        cd demo
        make -j8
        make run
    
  5. 执行完毕后,即可看到一个窗口,显示SGL的示例程序运行效果

小技巧

如果想修改模拟器的分辨率,可以在sgl_port_sdl2.c文件中修改CONFIG_SGL_PANEL_WIDTHCONFIG_SGL_PANEL_HEIGHT的值

MCU平台

  1. 下载SGL发布版本代码:SGL_20260201.zip

  2. 解压后如下文件结构:

source
├─sgl.h                 SGL头文件
├─sgl_config.h          SGL配置文件
├─core                  核心库文件
├─draw                  底层绘制库文件
├─fonts                 字体库文件
├─include               头文件
├─misc                  杂项文件
├─mm                    内存管理文件
│  ├─lwmem              lwmem内存管理库文件
│  ├─other              其他内存管理库文件
│  └─tlsf               tlsf内存管理库文件
└─widgets               控件库文件
    ├─2dball               2D小球
    ├─arc                  弧线
    ├─button               按钮
    ├─checkbox             复选框
    ├─circle               圆形
    ├─dropdown             下拉框
    ├─ext_img              扩展图片
    ├─icon                 图标
    ├─keyboard             键盘
    ├─label                标签
    ├─led                  LED
    ├─line                 线段
    ├─msgbox               消息框
    ├─numberkbd            数字键盘
    ├─polygon              多边形
    ├─progress             进度条
    ├─rectangle            矩形
    ├─ring                 圆形环
    ├─scope                示波器
    ├─scroll               滚动条
    ├─slider               滑块
    ├─switch               开关
    ├─textbox              文本框
    ├─textline             文本行
    └─unzip_image          解压图片

  1. 将SGL的所有代码拷贝到MCU的工程文件中,并添加所有文件,注意:对于mm目录下的文件,只需要添加lwmem目录下的文件,如果你的项目中已经有动态内存管理函数,则不需要添加mm目录下的文件,自己定义sgl_mm_initsgl_mallocsgl_free函数即可。

  2. 将sgl源码的sgl.h文件所在的目录添加到编译的头文件路径中

  3. 将sgl源码的include目录添加到编译的头文件中路径中

  4. 添加SGL的所有文件后,请修改sgl_config.h文件,用于适配你的MCU平台,下面是一个示例:

#ifndef  __CONFIG_H__
#define  __CONFIG_H__

#define    CONFIG_SGL_FBDEV_PIXEL_DEPTH       16          //颜色深度,这里是16位,即RGB565
#define    CONFIG_SGL_FBDEV_ROTATION          0           //屏幕旋转角度,软件旋转,这里设置为0度,即不做旋转
#define    CONFIG_SGL_FBDEV_RUNTIME_ROTATION  0           //屏幕实时旋转角度
#define    CONFIG_SGL_SYSTICK_MS              10          //SGL图形刷新事件间隔,这里设置为10ms
#define    CONFIG_SGL_EVENT_QUEUE_SIZE        16          //事件队列大小,这里设置为16
#define    CONFIG_SGL_DIRTY_AREA_NUM_MAX      16          //脏区域最大数量,这里设置为16
#define    CONFIG_SGL_ANIMATION               1           //是否启用动画,这里启用动画
#define    CONFIG_SGL_DEBUG                   1           //是否启用日志,这里启用日志,项目发布时,请关闭日志
#define    CONFIG_SGL_LOG_COLOR               1           //是否启用日志颜色,这里启用日志颜色
#define    CONFIG_SGL_LOG_LEVEL               0           //日志等级,0为全部输出,1为错误输出,2为警告输出,3为信息输出,4为调试输出
#define    CONFIG_SGL_OBJ_USE_NAME            0           //是否启用对象名称,这里不启用对象名称
#define    CONFIG_SGL_FONT_COMPRESSED         1           //是否启用字体压缩,这里启用字体压缩
#define    CONFIG_SGL_BOOT_LOGO               1           ///是否启用启动logo,这里启用开机logo
#define    CONFIG_SGL_HEAP_ALGO               lwmem       //内存管理算法,这里选择lwmem
#define    CONFIG_SGL_HEAP_MEMORY_SIZE        10240       //内存大小,这里设置为10KB
#define    CONFIG_SGL_FONT_SONG23             0           //是否启用宋体23号字体, 默认关闭
#define    CONFIG_SGL_FONT_CONSOLAS14         0           //是否启用Consolas14号字体, 默认关闭
#define    CONFIG_SGL_FONT_CONSOLAS23         0           //是否启用Consolas23号字体, 默认关闭
#define    CONFIG_SGL_FONT_CONSOLAS24         1           //是否启用Consolas24号字体, 默认开启
#define    CONFIG_SGL_FONT_CONSOLAS32         0           //是否启用Consolas32号字体, 默认关闭
#define    CONFIG_SGL_FONT_CONSOLAS24_COMPRESS     1      //是否启用Consolas24号字体压缩,这里启用字体压缩

#endif  //!__CONFIG_H__

小技巧

在刚开始移植SGL时,最好开启CONFIG_SGL_DEBUG这个值,这样可以使用串口输出调试日志,如果出现问题,可以快速定位原因。

  1. 对接屏幕底层驱动代码 在你的main函数中添加如下代码:

#include "sgl.h"

#define PANEL_WIDTH     320
#define PANEL_HEIGHT    240

static sgl_color_t panel_buffer[PANEL_WIDTH * 10];

void panel_flush_area(sgl_area_t *area, sgl_color_t *src)
{
    uint16_t w = area->x2 - area->x1 + 1;
    uint16_t h = area->y2 - area->y1 + 1;
    tft_set_win(area->x1, area->y1, area->x2, area->y2);
    GPIO_WriteBit(SPI_DC_PORT, SPI_DC_PIN, 1);
    SPI1_WriteMultByte((uint8_t*)src, w * h * sizeof(sgl_color_t));
    /* 调用sgl_fbdev_flush_ready()函数,告诉SGL框架,刷新完成,可以继续处理下一帧处理 */ 
    sgl_fbdev_flush_ready();
}

void uart_put_string(const char *str)
{
   /* 发送串口数据,将str中的数据发送出去 */
}

//你的SysTick中断处理函数,定时时间为1ms
void SysTick_Handler(void)
{
    sgl_tick_inc(1);
}

int main(void)
{
    sgl_fbinfo_t fbinfo = {
        .xres = PANEL_WIDTH,
        .yres = PANEL_HEIGHT,
        .flush_area = panel_flush_area,
        .buffer[0] = panel_buffer,
        .buffer_size = SGL_ARRAY_SIZE(panel_buffer), 
    };

    // 注册日志设备,可选
    sgl_logdev_register(uart_put_string);
    // 注册Framebuffer设备
    sgl_fbdev_register(&fbinfo);

    // 必须先初始化SysTick和屏幕设备,然后再初始化SGL框架
    systick_init();
    tft_init();

    sgl_init();

    //添加一个测试代码
    sgl_obj_t *label = sgl_label_create(NULL);
    sgl_obj_set_size(label, PANEL_WIDTH, 30);
    sgl_obj_set_pos_align(label, SGL_ALIGN_CENTER);
    sgl_label_set_font(label, &consolas24);
    sgl_label_set_text(label, "Hello SGL!");

    while (true) {
        sgl_task_handler();
    }
    return 0;
}

上面的过程中定义了一个sgl_device_fb_t结构体,并且初始化了一些主要的参数,参数的含义如下:

  • xres: 屏幕的宽度

  • yres: 屏幕的高度

  • flush_area:刷新区域函数,用于刷新指定区域

  • buffer[0]:帧缓冲区指针,指向帧缓冲区地址处,如何需要双帧缓冲区,则需要设置buffer[1]

  • buffer_size:帧缓冲区大小,单位:缓冲区中像素点的个数

panel_flush_area函数用于刷新指定区域,参数为:

  • area:区域结构体指针,包含区域左上角和右下角的坐标 x1:区域左上角X坐标 y1:区域左上角Y坐标 x2:区域右下角X坐标 y2:区域右下角Y坐标

  • src:区域数据指针 panel_flush_area函数必须调用sgl_fbdev_flush_ready()函数,用来告诉SGL框架,刷新完成,可以继续处理下一帧处理。

//DMA完成回调函数
void dma_complete_cb(void)
{
    sgl_fbdev_flush_ready();
}

void panel_flush_area(sgl_area_t *area, sgl_color_t *src)          
{
    // 非阻塞模式
    DMA_SendData_NoWait(src, (x2 - x1 + 1) * (y2 - y1 + 1)* sizeof(sgl_color_t));        
}

当然,对于使用DMA发送数据时,请使用双缓冲,即添加一个缓冲区,即buffer[1],大小和buffer[0]一样,即buffer_size

编译后,烧录到开发板上,即可看到屏幕显示“Hello SGL!”,整个移植主要只有四件事:

    1. 调用sgl_fbdev_register()函数注册FB设备

    1. 调用sgl_logdev_register()函数注册日志设备,可选

    1. 调用sgl_init()函数初始化SGL框架

    1. 在滴答中断中调用sgl_tick_inc()函数,定时为1ms

危险

sgl_tick_inc()所在的Systick或者定时器必须在SGL初始化之前就应该被初始化,否则会导致启动LOGO进入卡死状态,sgl_tick_inc()函数不是必须要在滴答中断中调用,你也可以在轮询或者线程中调用,每1ms调用一次即可。

KEIL IDE使用

1.创建工程

  1. 新建一个SGL_STM32F103目录,然后创建一个sgl目录,然后将sgl源码的source目录下的所有文件复制到SGL_STM32F103/sgl/目录下。
    alt text

  2. 打开MDK5软件,新建一个名为SGL_STM32F103的工程,保存到SGL_STM32F103目录下,点击【保存】。
    alt text

  3. 此时会进入芯片选择界面,然后选择STM32F103C8芯片,点击【OK】
    alt text

  4. 此时会进入Manage Run-Time Environment界面,勾选CMSISStartup,然后点击【OK】。
    alt text

  5. 点击文件扩展管理器:
    alt text

    然后新建sglexample目录结构,然后在sgl结构中,将sgl/core/目录下所有c文件添加,将sgl/draw/目录下所有c文件添加,将sgl/fonts/目录下所有c文件添加,将sgl/source/mm/lwmem/目录下的所有c文件添加,将sgl/source/widgets/目录下的所有文件添加,添加完毕后,目录结构如下:
    alt text

  6. 新建一个main.c文件,然后保存到example文件夹下:
    alt text

    然后输入如下代码:

    #include "stm32f10x.h"
    #include "sgl.h"
    
    #define  PANEL_WIDTH    240
    #define  PANEL_HEIGHT   240
    sgl_color_t panel_buffer[PANEL_WIDTH * 10];
    
    /* 系统时钟中断服务函数,设置为1ms中断一次 */
    void systick_handler(void)
    {
        sgl_tick_inc(1);
    }
    
    void demo_panel_flush_area(sgl_area_t *area, sgl_color_t *src)
    {
        /* set flush windows address */
        tft_set_win(area->x1, area->y1, area->x2, area->y2);    
        SPI1_WriteMultByte((uint8_t*)src, (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1) * sizeof(sgl_color_t));
        /* 调用sgl_fbdev_flush_ready()函数,告诉SGL框架,刷新完成,可以继续处理下一帧处理 */ 
        sgl_fbdev_flush_ready();
    }
    
    void UART1_SendString(const char *str)
        {
        while (*str != '\0') {
            while ((USART1->SR & USART_SR_TXE) == 0);
            USART1->DR = (uint8_t)(*str++);
        }
        while ((USART1->SR & USART_SR_TC) == 0);
    }
    
    int main(void)
    {
        sgl_fbinfo_t fbinfo = {
            .xres = PANEL_WIDTH,
            .yres = PANEL_HEIGHT,
            .flush_area = panel_flush_area,
            .buffer[0] = panel_buffer,
            .buffer_size = SGL_ARRAY_SIZE(panel_buffer), 
        };
        // 注册日志设备,可选
        sgl_logdev_register(UART1_SendString);
        // 注册Framebuffer设备
        sgl_fbdev_register(&fbinfo);
    
        // 必须先初始化SysTick和屏幕设备,然后再初始化SGL框架
        uart_init();
        systick_init();
        tft_init();
    
        /* init sgl */
        sgl_init();
    
        while(1) {
            sgl_task_handler();
        }
        
        return 0;
    }
    
  7. 编辑sgl_config.h文件,修改内容如下:

    #define    CONFIG_SGL_FBDEV_PIXEL_DEPTH       16          //颜色深度,这里是16位,即RGB565
    #define    CONFIG_SGL_FBDEV_ROTATION          0           //屏幕旋转角度,软件旋转,这里设置为0度,即不做旋转
    #define    CONFIG_SGL_FBDEV_RUNTIME_ROTATION  0           //屏幕实时旋转角度
    #define    CONFIG_SGL_SYSTICK_MS              10          //SGL图形刷新事件间隔,这里设置为10ms
    #define    CONFIG_SGL_EVENT_QUEUE_SIZE        16          //事件队列大小,这里设置为16
    #define    CONFIG_SGL_DIRTY_AREA_NUM_MAX      16          //脏区域最大数量,这里设置为16
    #define    CONFIG_SGL_ANIMATION               1           //是否启用动画,这里启用动画
    #define    CONFIG_SGL_DEBUG                   1           //是否启用日志,这里启用日志,项目发布时,请关闭日志
    #define    CONFIG_SGL_LOG_COLOR               1           //是否启用日志颜色,这里启用日志颜色
    #define    CONFIG_SGL_LOG_LEVEL               0           //日志等级,0为全部输出,1为错误输出,2为警告输出,3为信息输出,4为调试输出
    #define    CONFIG_SGL_OBJ_USE_NAME            0           //是否启用对象名称,这里不启用对象名称
    #define    CONFIG_SGL_FONT_COMPRESSED         1           //是否启用字体压缩,这里启用字体压缩
    #define    CONFIG_SGL_BOOT_LOGO               1           ///是否启用启动logo,这里不启用启动logo
    #define    CONFIG_SGL_HEAP_ALGO               lwmem       //内存管理算法,这里选择lwmem
    #define    CONFIG_SGL_HEAP_MEMORY_SIZE        10240       //内存大小,这里设置为10KB
    #define    CONFIG_SGL_FONT_SONG23             0           //是否启用宋体23号字体, 默认关闭
    #define    CONFIG_SGL_FONT_CONSOLAS14         0           //是否启用Consolas14号字体, 默认关闭
    #define    CONFIG_SGL_FONT_CONSOLAS23         0           //是否启用Consolas23号字体, 默认关闭
    #define    CONFIG_SGL_FONT_CONSOLAS24         1           //是否启用Consolas24号字体, 默认开启
    #define    CONFIG_SGL_FONT_CONSOLAS32         0           //是否启用Consolas32号字体, 默认关闭
    #define    CONFIG_SGL_FONT_CONSOLAS24_COMPRESS     1      //是否启用Consolas24号字体压缩,这里启用字体压缩
    

2.配置编译选项

  1. 打开Options for Target窗口,然后找到Target选项:
    alt text

    选择V6版本编译器

  2. 点击C/C++(AC6)选项`,然后选择如下配置:
    alt text

    然后添加头文件路径,将sgl/include添加到Include Path中,将sgl目录添加到Include Path中。
    alt text

3.创建一个简单的demo

main.c中添加如下代码:

int main(void)
{
    ...
    uart_init();
    systick_init();
    tft_init();
    // 必须先初始化SysTick和屏幕设备,然后再初始化SGL框架
    sgl_init();
    ...

    /* 添加一个按钮 */
    sgl_obj_t *label = sgl_label_create(NULL);
    sgl_obj_set_size(label, PANEL_WIDTH, 30);
    sgl_obj_set_pos_align(label, SGL_ALIGN_CENTER);
    sgl_label_set_font(label, &consolas24);
    sgl_label_set_text(label, "Hello SGL!");

    while(1) {
        sgl_task_handler();
    }

    return 0;
}

然后点击编译按钮,编译成功后,烧录到开发板中即可。
alt text

备注

如果发现颜色显示不正确,请查看屏幕驱动芯片手册,是否存在16位颜色交换模式,如果存在,可以使用下面两种方式任意一种解决:

  1. 修改sgl_config.h文件,将CONFIG_SGL_COLOR16_SWAP定义为1,使用软件交换颜色

  2. 查看屏幕驱动芯片手册,设置16位颜色交换模式。

触摸屏支持

SGL支持触摸屏,用户可以使用触摸屏来控制SGL的控件,例如点击按钮,滑动列表等等。触摸的底层对接有两种方式,一种是使用中断,一种是使用定时轮询,这里以定时轮询为例说明。 用户只需要在定时轮询函数中添加如下代码:

/* 定时轮询函数,一般设置10~30ms */
void touch_timer_handle(void)
{
    bool pressed;
    int16_t x, y;

    /* 获取触摸屏的坐标和是否按下 */
    pressed = touch_get_pressed();
    x = touch_get_x();
    y = touch_get_y();

    /* 调用SGL的触摸事件处理函数 */
    sgl_event_pos_input(x, y, pressed);
}

DMA双缓冲支持

SGL支持DMA双缓冲,用户可以使用DMA来提高图形刷新效率,用户需要使用双缓冲来避免屏幕闪烁,如下方式可使用双缓冲:

sgl_color_t panel_buffer1[PANEL_WIDTH * 10];
sgl_color_t panel_buffer2[PANEL_WIDTH * 10];

sgl_fbinfo_t fbinfo = {
    .xres = PANEL_WIDTH,
    .yres = PANEL_HEIGHT,
    .flush_area = panel_flush_area,
    .buffer[0] = panel_buffer1,
    .buffer[1] = panel_buffer2,
    .buffer_size = SGL_ARRAY_SIZE(panel_buffer1), 
};

上面的代码中,panel_buffer1和panel_buffer2是双缓冲,并且panel_buffer1和panel_buffer2的buffer_size必须一致,buffer_size为缓冲区大小,这里设置为10行,即10行数据。
并且sgl_fbdev_flush_ready()函数在DMA完成中断处理函数中调用,如下代码:

void dma_complete_cb(void)
{
    sgl_fbdev_flush_ready();
}

void panel_flush_area(sgl_area_t *area, sgl_color_t *src)          
{
    tft_set_win(area->x1, area->y1, area->x2, area->y2);
    GPIO_WriteBit(SPI_DC_PORT, SPI_DC_PIN, 1);
    //非阻塞函数
    DMA_SendData_NoWait(src, (area->x2 - area->x1 + 1) * (area->y2 - area->y1 + 1)* sizeof(sgl_color_t));        
}

LCD控制器方式

LCD支持直接向屏幕控制器显存直接写入数据,对于有LCD控制器的屏幕,可以直接向控制器的显存中写数据,这样不需要再进行DMA拷贝,从而提高效率,使用方法如下:

  1. 在配置文件中添加如下配置:

#define    CONFIG_SGL_USE_FBDEV_VRAM         1

这里的CONFIG_SGL_USE_FBDEV_VRAM为1表示使用LCD控制器方式

  1. 注册Framebuffer设备,代码如下:

void lcd_flush(sgl_area_t *area, sgl_color_t *src)          
{
    你需要刷新LCD这里省略代码
    // 通知SGL刷新完成
    sgl_fbdev_flush_ready();
}

sgl_fbinfo_t fbinfo = {
    .xres = PANEL_WIDTH,
    .yres = PANEL_HEIGHT,
    .flush_area = lcd_flush,
    .buffer[0] = lcd_addr, //这里指向LCD的显存地址
    .buffer_size = LCD_WIDTH * LCD_HEIGHT,
};

// 注册Framebuffer设备
sgl_fbdev_register(&fbinfo);