LittleVgl,一款开源嵌入式图形用户界面库。https://littlevgl.cn/
使用简单小巧,界面也漂亮。很适合用在嵌入式上。在Stm32单片机上都能跑,只要实现了基础的函数很好移植。
看下stm32单片机下的效果:
业余时间移植到了嵌入式linux上,使用帧缓冲Frambuffer,显示效果还不错。
计划选用LittleVgl作为嵌入式的漂亮的UI。
其他GUI如QT,miniGUI,周立功的AWTK也都有了解过。界面UI是强大,但是就是有点儿庞大,在简单的嵌入式界面上不想花太多精力和时间。毕竟不是手机,不需要太多人机交互,要的是简单小巧,够用即可。
之前本想在Ubuntu14上玩儿一下AWTK,但是编译依赖一大堆,最后放弃了。查看官网才知道需要使用Ubuntu16以上系统且安装好几个不知道什么用途的依赖,后续有机会再尝试吧。他们能不能出个纯c代码的精简版?可能太强大和支持N多平台的缘故吧,代码依赖很多三方库,这点儿有点儿不让人喜欢,不够小巧。
如图:
但是,还是不够好用。
计划使用go加上面向对象的思想,对其进行进一步的封装,让其更简单好用一点儿。
如果使用上类似于python上的绑定那样简单的话,该是多么美好的事情,如:
$ python3 Python 3.6.5 (default, May 9 2018, 10:02:20) [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)] on darwin Type "help", "copyright", "credits" or "license" for more information. >>> >>> from pylvgl import Demo, lvgl >>> d = Demo() >>> >>> b1 = lvgl.Btn(lvgl.scr_act()) >>> b1.set_size(200,50) >>> b1.align(b1.get_parent(), lvgl.ALIGN_IN_LEFT_MID, 0, 0) >>> >>> l1 = lvgl.Label(b1) >>> l1.set_text('LittlevGL') >>> >>> lm = lvgl.Lmeter(lvgl.scr_act()) >>> lm.align(cal.get_parent(), lvgl.ALIGN_IN_LEFT_MID, 300, 0) >>> lm.set_range(0,100) >>> lm.set_value(10) >>> >>> style_lm1.line_width = 4 >>> style_lm1.body_main_color = 0x2200 >>> style_lm1.body_grad_color = 0x0022 >>> lm.set_style(style_lm1) >>> >>> lb2 = lvgl.Label(lm) >>> lb2.set_text('10%') >>> lb2.align(lm, lvgl.ALIGN_CENTER, 0, 0)
已经初步验证了,go的绑定可行,使用也变得简单啦
如:
package main import ( "golvgl/lvgl" "log" "time" ) func main() { log.Println("Hello Go,LittlevGL") lb := lvgl.Label(lvgl.ScrAct(), nil) lb.SetText("hello world,go test 222") lb.Align(nil, lvgl.LV_ALIGN_CENTER, 0, 0) log.Println("Hello Go Over 222") for true { lvgl.TickInc(5) lvgl.TaskHandler() time.Sleep(5000) } }
随后代码会放在 github上,希望感兴趣的有志之士能一块儿参与完善下封装。
https://github.com/yangyongzhen/golvgl.git
package lvgl /* #cgo CFLAGS: -Iinclude/lvgl #include "lvgl.h" #cgo LDFLAGS: -Llibs -llvgl #include <stdlib.h> extern void disp_init(void); extern void fbdev_init(void); extern void fbdev_flush(void); void disp_init(){ lv_disp_drv_t disp_drv; lv_disp_drv_init(&disp_drv); disp_drv.disp_flush = fbdev_flush; lv_disp_drv_register(&disp_drv); } */ import "C" import "unsafe" // Lvobj ... type Lvobj C.struct__lv_obj_t const ( LV_ALIGN_CENTER C.uchar = C.LV_ALIGN_CENTER ) func init() { C.lv_init() C.fbdev_init() C.disp_init() } // ScrAct ... func ScrAct() *Lvobj { return (*Lvobj)(unsafe.Pointer(C.lv_scr_act())) } // Label ... func Label(par, copy *Lvobj) *Lvobj { var p1 *C.struct__lv_obj_t var p2 *C.struct__lv_obj_t p1 = (*C.struct__lv_obj_t)(unsafe.Pointer(par)) p2 = (*C.struct__lv_obj_t)(unsafe.Pointer(copy)) return (*Lvobj)(unsafe.Pointer(C.lv_label_create(p1, p2))) } // SetText ... func (obj *Lvobj) SetText(str string) { C.lv_label_set_text((*C.struct__lv_obj_t)(unsafe.Pointer(obj)), C.CString(str)) } // Align ... func (obj *Lvobj) Align(base *Lvobj, align C.uchar, x int, y int) { var ba *C.struct__lv_obj_t ba = (*C.struct__lv_obj_t)(unsafe.Pointer(base)) C.lv_obj_align((*C.struct__lv_obj_t)(unsafe.Pointer(obj)), ba, align, C.short(x), C.short(y)) } // TickInc ... func TickInc(tick int) { C.lv_tick_inc(C.uint(tick)) } // TaskHandler ... func TaskHandler() { C.lv_task_handler() }
其中遇到的问题总结:
void * 对应go中的unsafe.Pointer
c结构体和 go之间的转换为:
var obj *Lvobj var p *C.struct__lv_obj_t p = (*C.struct__lv_obj_t)(unsafe.Pointer(obj))
byte[]和 c的数组之间转换为:
如果是struct, union和enum的话,需要加上如下前缀,struct_、union_和enum_,比如 C.struct_MSG。
可以用注释符//和/**/包围C代码
import “C” 和包含C代码之间是没有空行的
动态库的导入和编译选项通过LDFLAGS、CFLAGS/CXXFLAGS来设置
还可以用pkg-config #cgo pkg-config : xxxxname
编译宏定义指定#cgo CFLAGS: -DNDEBUG -DXXXX=2
const ( LV_ALIGN_CENTER C.uchar = C.LV_ALIGN_CENTER // error:LV_PROTECT_POS C.enum_ = C.enum_LV_PROTECT_POS )
对于c中的enum,没有enum{} name,名字的,它的类型是什么呢?经过摸索发现,其实就是个int或byte
ok: LV_PROTECT_POS byte = C.LV_PROTECT_POS package main // #include <stdio.h> // #include <stdlib.h> /* void print(char *str) { printf("%s\n", str); } */ import "C" import "unsafe" func main() { s := "Hello Cgo" cs := C.CString(s) C.print(cs) C.free(unsafe.Pointer(cs)) }
c结构体的位域和go之间转换为:
var img_bubble_pattern C.lv_img_dsc_t var img_bubble_pattern_map = []byte{ 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53, 0x53} const ( LV_ALIGN_CENTER C.uchar = C.LV_ALIGN_CENTER ) func init() { fmt.Println(img_bubble_pattern) fmt.Println(reflect.TypeOf(img_bubble_pattern)) //fmt.Println(reflect.TypeOf(img_bubble_pattern.header.always_zero)) bt := [4]byte{0, 1, 2, 3} img_bubble_pattern = C.lv_img_dsc_t{ header: C.lv_img_header_t{bt}, data_size: 112320 * C.LV_COLOR_SIZE / 8, data: (*C.uchar)(unsafe.Pointer(&img_bubble_pattern_map[0])), } }
未完待续,敬请期待....
想完善其他的封装太难了,几度想放弃。上面的c结构体的位域和go之间转换问题解决了,又遇到其他问题,如:
panic: runtime error: cgo argument has Go pointer to Go pointer
大意是 Go调用C Code时,Go传递给C Code的Go指针所指的Go Memory中不能包含任何指向Go Memory的Pointer。
比如以下样例代码举例:我本意想在go层读取图片二进制内容,传给给c层,调用c的显示图片的接口。
package main /* #include <stdio.h> //模拟图片信息的定义 typedef struct { int a; int b; unsigned char* data; }st; //模拟 c层的获取图片内容并显示图片的接口 void testc(void *p){ char * p1; p1 = p; printf("a is:%d\n",p1[0]); } */ import "C" import ( "fmt" "unsafe" ) var img_bubble_pattern C.st //模拟go层二进制图片数据 var dat = []byte{ 1, 2, 3, } //模拟封装的调用c层的图片显示接口 func test(p *C.st) { //fmt.Printf("%#v\n", p) C.testc(unsafe.Pointer(p)) } func main() { fmt.Println("Hello, World!") fmt.Printf("%#v\n", img_bubble_pattern) //对结构体赋值 img_bubble_pattern = C.st{ a: 1, b: 2, data: (*C.uchar)(unsafe.Pointer(&dat[0])), } //调用 test(&img_bubble_pattern) }
var dat是从go应用层读取到的图片的二进制数据,调c的接口完成图片显示。c的显示图片的接口就是一个结构体指针。
但是如果调试这段代码就发现,报panic: runtime error: cgo argument has Go pointer to Go pointer,
这段代码有问题吗?肯定有问题的,但是实际的用途上没错,就想这么干,让cgo达到我这调用的目的,这问题该怎么破?
果然,还是有高人的,这个问题问到了go语言交流群里的群主苏州-狄,昵称曦晨。在这里表示感谢!
他的解决方案如下,把这个指针转换的问题换了一种方式,绕过去了,但也达到了目的。
package main /* #include <stdio.h> //模拟图片信息的定义 typedef struct { int a; int b; unsigned char* data; }st; //模拟 c层的获取图片内容并显示图片的接口 void testc(void *p){ st* p1 = p; printf("a is:%d\n",p1->data[0]); } */ import "C" import ( "fmt" "unsafe" ) var img_bubble_pattern C.st //模拟go层二进制图片数据 var dat = []byte{ 1, 2, 3, } //模拟封装的调用c层的图片显示接口 func test(p C.st) { //fmt.Printf("%#v\n", p) C.testc(unsafe.Pointer(&p)) } func main() { fmt.Println("Hello, World!") fmt.Printf("%#v\n", img_bubble_pattern) //对结构体赋值 img_bubble_pattern = C.st{ a: 1, b: 2, data: (*C.uchar)(unsafe.Pointer(&dat[0])), } //调用 // C.testc(unsafe.Pointer(&img_bubble_pattern)) test(img_bubble_pattern) }
对比两份代码发现,在go层不传指针了,改为结构体的值传递,解决了这个问题。
后续记,这个问题又有新解了。使用mattn大神封装的库,https://github.com/mattn/go-pointer。
可以实现CGO和C之间的指针传递,且不再报错 cgo argument has Go pointer to Go pointer
如下:
C.testc(pointer.Save(p))
package main /* #include <stdio.h> #include <stdlib.h> //模拟图片信息的定义 typedef struct { int a; int b; unsigned char* data; }st; //模拟 c层的获取图片内容并显示图片的接口 void testc(void *p){ st* p1 = p; printf("a is:%d\n",p1->data[0]); } */ import "C" import ( "fmt" "unsafe" "github.com/mattn/go-pointer" ) var img_bubble_pattern C.st //模拟go层二进制图片数据 var dat = []byte{ 1, 2, 3, } //模拟封装的调用c层的图片显示接口 // func test(p C.st) { // //fmt.Printf("%#v\n", p) // C.testc(unsafe.Pointer(&p)) // } //模拟封装的调用c层的图片显示接口 func test(p *C.st) { //fmt.Printf("%#v\n", p) //C.pass_pointer(pointer.Save(p)) //v := *(pointer.Restore(C.get_from_pointer()).(*C.st)) C.testc(pointer.Save(p)) } func main() { fmt.Println("Hello, World!") fmt.Printf("%#v\n", img_bubble_pattern) //对结构体赋值 img_bubble_pattern = C.st{ a: 1, b: 2, data: (*C.uchar)(unsafe.Pointer(&dat[0])), } //调用 // C.testc(unsafe.Pointer(&img_bubble_pattern)) test(&img_bubble_pattern) }