嵌入式linux之go语言开发(十三)LittlevGL,漂亮的嵌入式GUI的go语言绑定

简介: 嵌入式linux之go语言开发(十三)LittlevGL,漂亮的嵌入式GUI的go语言绑定

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)
}


https://mp.weixin.qq.com/s/WUdaksFyjmT_c_jEx59VcQ

相关文章
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
6059 77
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
614 32
|
Ubuntu Linux 开发者
Ubuntu20.04搭建嵌入式linux网络加载内核、设备树和根文件系统
使用上述U-Boot命令配置并启动嵌入式设备。如果配置正确,设备将通过TFTP加载内核和设备树,并通过NFS挂载根文件系统。
982 15
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
844 13
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
1016 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
11月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
1058 1
二、Linux文本处理与文件操作核心命令
|
11月前
|
Linux
linux命令—stat
`stat` 是 Linux 系统中用于查看文件或文件系统详细状态信息的命令。相比 `ls -l`,它提供更全面的信息,包括文件大小、权限、所有者、时间戳(最后访问、修改、状态变更时间)、inode 号、设备信息等。其常用选项包括 `-f` 查看文件系统状态、`-t` 以简洁格式输出、`-L` 跟踪符号链接,以及 `-c` 或 `--format` 自定义输出格式。通过这些选项,用户可以灵活获取所需信息,适用于系统调试、权限检查、磁盘管理等场景。
731 137
|
11月前
|
安全 Ubuntu Unix
一、初识 Linux 与基本命令
玩转Linux命令行,就像探索一座新城市。首先要熟悉它的“地图”,也就是/根目录下/etc(放配置)、/home(住家)这些核心区域。然后掌握几个“生存口令”:用ls看周围,cd去别处,mkdir建新房,cp/mv搬东西,再用cat或tail看文件内容。最后,别忘了随时按Tab键,它能帮你自动补全命令和路径,是提高效率的第一神器。
1665 58
|
10月前
|
存储 安全 Linux
Linux卡在emergency mode怎么办?xfs_repair 命令轻松解决
Linux虚拟机遇紧急模式?别慌!多因磁盘挂载失败。本文教你通过日志定位问题,用`xfs_repair`等工具修复文件系统,三步快速恢复。掌握查日志、修磁盘、验重启,轻松应对紧急模式,保障系统稳定运行。
1585 2
|
11月前
|
Unix Linux 程序员
Linux文本搜索工具grep命令使用指南
以上就是对Linux环境下强大工具 `grep` 的基础到进阶功能介绍。它不仅能够执行简单文字查询任务还能够处理复杂文字处理任务,并且支持强大而灵活地正则表达规范来增加查询精度与效率。无论您是程序员、数据分析师还是系统管理员,在日常工作中熟练运用该命令都将极大提升您处理和分析数据效率。
866 16

热门文章

最新文章