玲珑GUI(LLGUI)是一套使用简单、低价的单片机GUI解决方案。可以用来代替串口屏、组态,降低产品成本,产品软硬件自主可控。 配套界面开发软件,图形化编辑界面,生成C代码。
如其名字玲珑小巧,代码量不大,纯c语言写的,适用各类资源受限的单片机mcu,且开源免费。(协议使用Apache License, Version 2.0,可以二次开发类GPL,非二次开发类使用Apache,多许可协议的方式。
教程地址:玲珑GUI教程 · 语雀
Gitee地址:LingLongGUI: 玲珑GUI是高效的界面开发解决方案。 代替串口屏、组态,降低产品成本,产品软硬件自主可控。 配套界面开发软件,图形化编辑界面,生成C代码。
简单体验了一把,感觉挺不错的,在这里推荐下。
尤其是它还提供了类似QT的可视化的GUI界面编辑器GUIBulider,可以可视化的编辑UI。同时还提供了类似于QT的信号和曹机制真心不错。虽然基础控件还不够多,比如常见的label控件没找到(作者说可以使用Text替代,我觉得label挺常用的,还是能有个单独的label组件比较好)。但是对于嵌入式应用差不多够用了,就像官方的介绍一样,可作为替代串口屏的一种低价的单片机GUI解决方案。
期待后续有更多好用的组件出来,期望llgui作为国产开源中的一员不断发展壮大。至少截止目前,配合这么好用的GUIBulider,可以和LittlevGL比个高下了。虽然组件丰富度不如LittlevGL,但是使用的易用性上还是这个小巧易用啊,类QT但比QT小巧太多,很有特色。
也希望作者开源出来一些自定义组件的方法和文档或教程,这样更利于生态扩建,丰富更多好用的组件。如一些仪表、曲线、table、chart等。
关于中文字库的支持方面(文档,示例等)期待再完善些。可能目前主要是瞄准嵌入式mcu上,在嵌入式linux上其实可以完善些常用字库。
GUIBulider长这样,有一种QT设计师的风格:
可以直接拖动编辑界面UI,最右侧可以更改属性。下方可以编辑发送者,信号和接收者。
点击工具栏上的绿色三角图标,自动生成对应的ui代码和对应的逻辑层处理代码文件。
在嵌入式linux上的移植(底层基于framebuffer的fb0):
新建一文件夹test,在里面新建llgui,ui和port文件夹。
其中llgui放置从gitee上下载到的最新llgui的源码。port文件夹放置跟移植相关的内容。ui文件夹里放置ui和ui的响应逻辑实现文件。
移植还是很简单的,实现LL_Config.c中的几个函数即可。主要的三个函数:画点和读点,填充矩形的函数。十分钟完整移植,此言不虚。
头文件LL_Config.h里做些配置:
...
颜色位数
#define CONFIG_COLOR_DEPTH 16 // 1 8 16 24 32
//屏幕宽度像素
#define LL_MONITOR_WIDTH 480
//屏幕高度像素
#define LL_MONITOR_HEIGHT 272
LL_Config.c文件
#include "LL_Config.h"
#include "string.h"
#include "freeRtosHeap4.h"
#include "io_fb.h"
uint8_t cfgColorDepth = CONFIG_COLOR_DEPTH;
uint16_t cfgMonitorWidth = 0;
uint16_t cfgMonitorHeight = 0;
#if USE_DOUBLE_BUFFERING == 1
uint32_t *lcdFrontBuf=LL_LCD_BUF1_POINTER;
uint32_t *lcdBackBuf=LL_LCD_BUF2_POINTER;
#endif
void llCfgSetLcdBufAddr(uint32_t *addr)
{
#if USE_DOUBLE_BUFFERING == 1
lcdSetBufferAddr(addr);
#endif
}
void llCfgSetLcdSrcAddr(uint32_t *addr)
{
#if USE_DOUBLE_BUFFERING == 1
lcdSetSrcAddr(addr);
#endif
}
void llCfgLcdCopyFront2Back(void)
{
#if USE_DOUBLE_BUFFERING == 1
// memcpy(lcdBackBuf,lcdFrontBuf,SDRAM_LCD_SIZE);
uint64_t i;
for(i=0;i<SDRAM_LCD_SIZE;i++)
{
lcdBackBuf[i]=lcdFrontBuf[i];
}
#endif
}
bool llCfgClickGetPoint(int16_t *x,int16_t *y)
{
bool ret;
return ret;
}
void llCfgSetPoint(int16_t x,int16_t y,llColor color)
{
fb_setpixel(LL_MONITOR_WIDTH,LL_MONITOR_HEIGHT,x,y,color);
}
llColor llCfgGetPoint(int16_t x,int16_t y)
{
llColor retColor;
retColor = fb_readpixel(LL_MONITOR_WIDTH,LL_MONITOR_HEIGHT,x,y);
return retColor;
}
void LCD_L0_SetPixelIndex(int x, int y, llColor color) {
fb_setpixel(LL_MONITOR_WIDTH, LL_MONITOR_HEIGHT, x, y, color);
}
void LCD_L0_DrawHLine (int x0, int y, int x1,llColor color) {
for (; x0 <= x1; x0++) {
LCD_L0_SetPixelIndex(x0, y, color);
}
}
void llCfgFillSingleColor(int16_t x0,int16_t y0,int16_t x1,int16_t y1,llColor color)
{
for (; y0 <= y1; y0++) {
LCD_L0_DrawHLine(x0, y0, x1,color);
}
}
void *llMalloc(uint32_t size)
{
return malloc(size);
}
void llFree(void *p)
{
free(p);
p=NULL;
}
void *llRealloc(void *ptr,uint32_t newSize)
{
return realloc(ptr,newSize);
}
void llExFlashInit(void)
{
}
void llReadExFlash(uint32_t addr,uint8_t* pBuffer,uint16_t length)
{
}
void llBuzzerBeep(void)
{
}
/***************************************************************************//**
* @fn void llGetRtc(uint8_t *readBuf)
* @brief 读取年月日时分秒周
* @param *readBuf yy yy mm dd hh mm ss ww
* @return void
* @version V0.1
* @date
* @details 数据用16进制储存,2021年 yyyy=0x07E5
******************************************************************************/
void llGetRtc(uint8_t *readBuf)
{
}
/***************************************************************************//**
* @fn void llSetRtc(uint8_t *writeBuf)
* @brief 写入年月日时分秒
* @param *writeBuf yy yy mm dd hh mm ss
* @return void
* @version V0.1
* @date
* @details 数据用16进制储存,2021年 yyyy=0x07E5
******************************************************************************/
void llSetRtc(uint8_t *writeBuf)
{
}
io_fb.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <sys/mman.h>
#include <linux/fb.h>
#define printV(v) printf(#v"=%d\n", v);
static unsigned char* npu8_fbmem;
static int ns32_fb;
static unsigned int nu32_screensize;
static unsigned char displaybuffer[480*272*4];
static unsigned char* pframebuffer;
static unsigned char pfbStat = 0; //0-framebuffer, 1-cache buffer
unsigned char getGUIcache(void);
int setGUIcache(unsigned char stat);
void GUIcache2fb(void);
static void* _fb_mmap(int fd, unsigned int screensize)
{
caddr_t fbmem;
if ((fbmem = mmap(0, screensize, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0)) == MAP_FAILED) {
perror(__func__);
return (void *) (-1);
}
return fbmem;
}
static int _fb_munmap(void *start, size_t length)
{
return (munmap(start, length));
}
static int _fb_stat(int fd, unsigned int *width, unsigned int *height, unsigned int *depth)
{
//struct fb_fix_screeninfo fb_finfo;
struct fb_var_screeninfo fb_vinfo;
//if (ioctl(fd, FBIOGET_FSCREENINFO, &fb_finfo)) {
// perror(__func__);
// return -1;
//}
if (ioctl(fd, FBIOGET_VSCREENINFO, &fb_vinfo)) {
perror(__func__);
return -1;
}
*width = fb_vinfo.xres;
*height = fb_vinfo.yres;
*depth = fb_vinfo.bits_per_pixel;
return 0;
}
int fb_init(void)
{
unsigned int fbw, fbh, fbd;
ns32_fb = open("/dev/fb0", O_RDWR);
if(ns32_fb<0){
printf("can not open fb0\n");
return -1;
}
if( _fb_stat(ns32_fb, &fbw, &fbh, &fbd) < 0 ) return -1;
printf("%d, %d, %d\n", fbw, fbh, fbd);
nu32_screensize = fbw * fbh * fbd / 8;
npu8_fbmem = _fb_mmap(ns32_fb, nu32_screensize);
setGUIcache(0);
return 0;
}
void fb_deinit(void)
{
close(ns32_fb);
_fb_munmap(npu8_fbmem, nu32_screensize);
}
int fb_setpixel(int width, int height, int x, int y, unsigned short color)
{
if ((x > width) || (y > height))
return -1;
//unsigned short *dst = ((unsigned short *)npu8_fbmem + y * width + x);
unsigned short *dst = ((unsigned short *)pframebuffer + y * width + x);
*dst = color;
return 0;
}
unsigned short fb_readpixel(int width, int height, int x, int y)
{
if ((x > width) || (y > height)) return -1;
//unsigned short *dst = ((unsigned short *)npu8_fbmem + y * width + x);
unsigned short *dst = ((unsigned short *)pframebuffer + y * width + x);
return *dst;
}
unsigned char getGUIcache(void)
{
//printf("%s\n", __FUNCTION__);
return pfbStat;
}
int setGUIcache(unsigned char stat)
{
//printf("%s\n", __FUNCTION__);
if( stat ) {
pframebuffer = displaybuffer;
pfbStat = 1;
} else {
pframebuffer = npu8_fbmem;
pfbStat = 0;
}
return 0;
}
void GUIcache2fb(void)
{
printf("%s\n", __FUNCTION__);
memcpy( npu8_fbmem, displaybuffer, nu32_screensize);
}
最后是makefile文件,更改下交叉编译工具链,直接执行make即可。
附:makefile文件:
########################################
##makefile template by yangyongzhen
########################################
#****************************************************************************
# Cross complie path
#****************************************************************************
# CHAIN_ROOT=/home/yang/imax283/ctools/gcc-4.4.4-glibc-2.11.1-multilib-1.0/arm-fsl-linux-gnueabi/bin
# CROSS_COMPILE=$(CHAIN_ROOT)/arm-none-linux-gnueabi-
CHAIN_ROOT= /opt/gcc-linaro-arm-linux-gnueabihf-4.9-2014.09_linux/bin
CROSS_COMPILE=$(CHAIN_ROOT)/arm-linux-gnueabihf-
#CROSS_COMPILE =
CC := $(CROSS_COMPILE)gcc
CXX := $(CROSS_COMPILE)g++
AS := $(CROSS_COMPILE)as
AR := $(CROSS_COMPILE)ar
LD := $(CROSS_COMPILE)ld
RANLIB := $(CROSS_COMPILE)ranlib
OBJDUMP:= $(CROSS_COMPILE)objdump
OBJCOPY:= $(CROSS_COMPILE)objcopy
STRIP := $(CROSS_COMPILE)strip
#****************************************************************************
# Source files
#****************************************************************************
SRC_C=$(shell find . -name "*.c")
OBJ_C=$(patsubst %.c, %.o, $(SRC_C))
SRCS := $(SRC_C) $(SRC_C)
OBJS := $(OBJ_C)
#****************************************************************************
# Flags
#****************************************************************************
LIBS := -LLIBS
INCS := -I./llgui/Gui -I./llgui/Misc -I./Fonts -I./port -I./ui -I./
CFLAGS= -std=gnu99 -fno-common -fsanitize=address -fno-stack-protector -fno-omit-frame-pointer -fno-var-tracking -g1
LDSCRIPT=
LDFLAGS= -lasan
#****************************************************************************
# Targets of the build
#****************************************************************************
TARGET := testllgui
TARGETLIB := libllgui
.PHONY: clean
all: prebuild $(TARGET)
lib: prebuild $(TARGETLIB).so
#****************************************************************************
# TARGET
#****************************************************************************
prebuild:
@echo Building...
$(TARGET): $(OBJS)
@echo Generating exe...
$(CC) -o $(TARGET) $(OBJS) $(LIBS) $(LDFLAGS)
@echo OK!
$(TARGETLIB).so : $(OBJS)
@echo Generating shared lib...
$(CC) -shared -fPIC -o $(TARGETLIB).so $(OBJS)
@echo OK!
%.o : %.c
$(CC) -c -fPIC $(CFLAGS) $(INCS) $< -o $@
clean:
@echo The following files:
rm -f $(TARGET) *.so
find . -name "*.[od]" |xargs rm
@echo Removed!
最后把编译生成的可执行文件,放在板子上,改下执行权限,直接运行即可。