应用实战精解系列(二):平头哥RVB2601测评:OLED与LVGL移植介绍

简介: 芯片开放社区(OCC)面向广大开发者推出应用实战系列内容,通过分享开发者实战开发案例,总结应用开发经验,梳理开发中的常见问题及解决方案,为后续参与的开发者提供更多参考与借鉴。

编辑语:

芯片开放社区(OCC)面向广大开发者推出应用实战系列内容,通过分享开发者实战开发案例,总结应用开发经验,梳理开发中的常见问题及解决方案,为后续参与的开发者提供更多参考与借鉴。


上期内容中,我们介绍了本文作者溪悦哦通过OCC开发板试用活动申请了RVB2601开发板,并对RVB2601开发板进行了开箱评测和硬件分析,最后搭建了开发环境,为后续开发做好了准备。本期内容将基于前文介绍,为大家带来OLED介绍,并演示LVGL移植的具体步骤。


RVB2601开发板试用活动仍在进行中,想要体验平头哥丰富的RISC-V生态资源和玄铁开源带来的便捷性吗?点击文末【阅读原文】即可参与,赶快前往申请吧~


申请链接:

https://yida.alibaba-inc.com/o/occ#/


01 OLED介绍

我们的屏幕采用的是128*64的分辨率的,驱动ic应该是SSD1306,这个刚好支持129*64的,但是就是单色屏,用的是spi接口。

image.png

首先初始化io口:
 csi_gpio_pin_t pin_clk;
 csi_gpio_pin_t pin_mosi;
 csi_gpio_pin_t pin_cs;
 csi_gpio_pin_t pin_miso;
 static void oled_pinmux_init()
 {
     csi_pin_set_mux(PA28, PIN_FUNC_GPIO); //clk
     csi_pin_set_mux(PA29, PIN_FUNC_GPIO); //mosi
     csi_pin_set_mux(PA27, PIN_FUNC_GPIO); //cs
     csi_pin_set_mux(PA30, PIN_FUNC_GPIO); //miso
 }
 static void oled_gpio_init()
 {
     csi_gpio_pin_init(&pin_clk, PA28);
     csi_gpio_pin_dir(&pin_clk, GPIO_DIRECTION_OUTPUT);     csi_gpio_pin_init(&pin_mosi, PA29);
     csi_gpio_pin_dir(&pin_mosi, GPIO_DIRECTION_OUTPUT);
     csi_gpio_pin_init(&pin_cs, PA27);
     csi_gpio_pin_dir(&pin_cs, GPIO_DIRECTION_OUTPUT);
     csi_gpio_pin_init(&pin_miso, PA30); //dc
     csi_gpio_pin_dir(&pin_miso, GPIO_DIRECTION_OUTPUT);
 }
 然后写命令、数据函数
 void Write_Command(unsigned char Data)
 {
     unsigned char i;
     lcd_cs(0);
     lcd_dc(0);
     for (i = 0; i < 8; i++) {
         lcd_sclk(0);
         lcd_sdin((Data & 0x80) >> 7);
         Data = Data << 1;
         lcd_sclk(1);
     }
     lcd_dc(1);
     lcd_cs(1);
 }
 void Write_Data(unsigned char Data)
 {
     unsigned char i;
     lcd_cs(0);
     lcd_dc(1);
     for (i = 0; i < 8; i++) {
         lcd_sclk(0);
         lcd_sdin((Data & 0x80) >> 7);
         Data = Data << 1;
         lcd_sclk(1);
     }
     lcd_dc(1);
     lcd_cs(1);
 }
 对于这种单色屏,我们直接开一个缓冲区就行:
 uint8_t g_oled_ram[8][128];
 画点就是修改缓存区的内容:
 void oled_draw_point(uint8_t r, uint8_t c, uint8_t t)
 {
     if (t) {
         SET_BIT(g_oled_ram[r / 8][c], ((r % 8)));
     } else {
         CLR_BIT(g_oled_ram[r / 8][c], (r % 8));
     }
 }
 最后要调用刷新函数来修改一整个屏幕:
 void oled_reflesh()
 {
     unsigned char i, j;
     for (i = 0; i < 8; i++) {
         Set_Start_Page(i);
         Set_Start_Column(0x00);
         for (j = 0; j < 128; j++) {
             Write_Data(g_oled_ram[i][j]);
         }
     }
 }
 屏幕初始化:
 static void oled_initialize()
 {
     Set_Command_Lock(0x12);           // Unlock Driver IC (0x12/0x16)
     Set_Display_On_Off(0xAE);         // Display Off (0xAE/0xAF)
     Set_Display_Clock(0xA0);          // Set Clock as 116 Frames/Sec
     Set_Multiplex_Ratio(0x3F);        // 1/64 Duty (0x0F~0x3F)
     Set_Display_Offset(0x00);         // Shift Mapping RAM Counter (0x00~0x3F)
     Set_Start_Line(0x00);             // Set Mapping RAM Display Start Line (0x00~0x3F)
     Set_Low_Power(0x04);              // Set Normal Power Mode (0x04/0x05)
     Set_Addressing_Mode(0x02);        // Set Page Addressing Mode (0x00/0x01/0x02)
     Set_Segment_Remap(0xA1);          // Set SEG/Column Mapping (0xA0/0xA1)
     Set_Common_Remap(0xC8);           // Set COM/Row Scan Direction (0xC0/0xC8)
     Set_Common_Config(0x12);          // Set Alternative Configuration (0x02/0x12)
     Set_Contrast_Control(Brightness); // Set SEG Output Current
     Set_Precharge_Period(0x82);       // Set Pre-Charge as 8 Clocks & Discharge as 2 Clocks
     Set_VCOMH(0x34);                  // Set VCOM Deselect Level
     Set_Entire_Display(0xA4);         // Disable Entire Display On (0xA4/0xA5)
     Set_Inverse_Display(0xA6);        // Disable Inverse Display On (0xA6/0xA7)
     Fill_RAM(0x00); // Clear Screen
     Set_Display_On_Off(0xAF); // Display On (0xAE/0xAF)
 }


如果要显示图片看借助取模软件来更改缓冲区:

image.png


02 LVGL的移植

littlevgl是一个小型开源嵌入式 GUI 库(简称LVGL),界面精美,消耗资源小,可移植度高,支持响应式布局,全库采用纯 c 语言开发,移植上手简单。


  • 具有非常丰富的内置控件,像 buttons, charts, lists, sliders, images 等
  • 高级图形效果:动画,反锯齿,透明度,平滑滚动
  • 支持多种输入设备,像 touchpad, mouse, keyboard, encoder 等
  • 支持多语言的 UTF-8 编码
  • 支持多个和多种显示设备,例如同步显示在多个彩色屏或单色屏上
  • 完全自定制的图形元素
  • 硬件独立于任何微控制器或显示器
  • 可以缩小到最小内存 (64 kB Flash, 16 kB RAM)
  • 支持操作系统、外部储存和 GPU(非必须)
  • 仅仅单个帧缓冲设备就可以呈现高级视觉特效
  • 使用 C 编写以获得最大兼容性(兼容 C++)
  • 支持 PC 模拟器
  • 为加速 GUI 设计,提供教程,案例和主题,支持响应式布局


这是我以前写的基于LVGL的温度测试显示界面,有点丑。

截屏2021-12-17 上午11.11.50.png截屏2021-12-17 上午11.11.59.png截屏2021-12-17 上午11.12.09.png截屏2021-12-17 上午11.12.20.png


Src就是一些源文件:

image.png


两个重要的API:

  • 一个是事务处理函数:

       lv_task_handler();


  • 一个是LVGL心跳:

       lv_tick_inc(1);


两个都要周期调用。


比较重要移植相关的就是porting这个文件了:

image.png


第一个就是显示接口:

//lvgl显示接口初始化
 void lv_port_disp_init(void)
 {
     static lv_disp_buf_t disp_buf;
     //显示缓冲区初始化
     lv_disp_buf_init(&disp_buf, color_buf, NULL, COLOR_BUF_SIZE); 
     //显示驱动默认值初始化   
     lv_disp_drv_t disp_drv;                         
     lv_disp_drv_init(&disp_drv);                 
     //设置屏幕的显示大小,我这里是为了支持正点原子的多个屏幕,采用动态获取的方式
     //如果你是用于实际项目的话,可以不用设置,那么其默认值就是lv_conf.h中LV_HOR_RES_MAX和LV_VER_RES_MAX宏定义的值
     disp_drv.hor_res = lcddev.width;
     disp_drv.ver_res = lcddev.height;
     //注册显示驱动回调
     disp_drv.flush_cb = disp_flush;
     //注册显示缓冲区
     disp_drv.buffer = &disp_buf;
 #if LV_USE_GPU
     //可选的,只要当使用到GPU时,才需要实现gpu_blend和gpu_fill接口
     //使用透明度混合俩个颜色数组时需要用到gpu_blend接口
     disp_drv.gpu_blend = gpu_blend;
     //用一个颜色填充一个内存数组时需要用到gpu_fill接口
     disp_drv.gpu_fill = gpu_fill;
 #endif
     //注册显示驱动到lvgl中
     lv_disp_drv_register(&disp_drv);
 }
 //把指定区域的显示缓冲区内容写入到屏幕上,你可以使用DMA或者其他的硬件加速器在后台去完成这个操作
 //但是在完成之后,你必须得调用lv_disp_flush_ready()
 static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
 {
     //把指定区域的显示缓冲区内容写入到屏幕
     LCD_Color_Fill(area->x1,area->y1,area->x2,area->y2,(u16*)color_p);
     //最后必须得调用,通知lvgl库你已经flushing拷贝完成了
     lv_disp_flush_ready(disp_drv);
 }


主要是显示缓冲区,还有打点函数的适配。


2和3就是触摸和文件相关的操作:

//lvgl的输入设备初始化
 void lv_port_indev_init(void)
 {
     lv_indev_drv_t indev_drv;
     //lvgl支持很多种输入设备,但是我们一般常用的就是触摸屏,也就是Touchpad
     lv_indev_drv_init(&indev_drv);
     indev_drv.type = LV_INDEV_TYPE_POINTER;
     indev_drv.read_cb = touchpad_read;
     lv_indev_drv_register(&indev_drv);
 }
 //将会被lvgl周期性调用,周期值是通过lv_conf.h中的LV_INDEV_DEF_READ_PERIOD宏来定义的
 //此值不要设置的太大,否则会感觉触摸不灵敏,默认值为30ms
 static bool touchpad_read(lv_indev_drv_t * indev_drv, lv_indev_data_t * data)
 {
     static uint16_t last_x = 0;
     static uint16_t last_y = 0;
     if(tp_dev.sta&TP_PRES_DOWN)//触摸按下了
     {
     last_x = tp_dev.x[0];
     last_y = tp_dev.y[0];
     data->point.x = last_x;
     data->point.y = last_y;
     data->state = LV_INDEV_STATE_PR;
     }else{//触摸松开了
     data->point.x = last_x;
     data->point.y = last_y;
     data->state = LV_INDEV_STATE_REL;
     }
     //返回false代表没有缓冲的数据
     return false;
 }


其实也就是把坐标值赋值给他内部的数据结构,然后修改状态就行了,但是我们的板子没有触摸,可惜了很多功能都用不了。


最后就要跟我们刚才实际OLED接口函数对上,这里我们用的是双缓存速度快:

static void oled_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)
 {
     oled_draw_frame((uint8_t(*)[Max_Column])color_p);
     oled_reflesh();
     lv_disp_flush_ready(disp_drv);
 }
 static lv_disp_buf_t disp_buf1;
 static lv_color_t    buf1[64 * 128];
 static lv_color_t    buf2[64 * 128];
 void oled_init()
 {
     oled_pinmux_init();
     oled_gpio_init();
     oled_initialize();
     lv_disp_buf_init(&disp_buf1, buf1, buf2, 64 * 128);
     lv_disp_drv_t disp_drv;
 lv_disp_drv_init(&disp_drv); 
 disp_drv.buffer   = &disp_buf1;
     disp_drv.flush_cb = oled_flush;
     disp_drv.rotated  = 0;
     lv_disp_drv_register(&disp_drv);
 }
 最后按例创建一个GUI任务:
 static void gui_label_create(void)
 {
     lv_obj_t *p = lv_label_create(lv_scr_act(), NULL);
     lv_label_set_long_mode(p, LV_LABEL_LONG_BREAK);
     lv_label_set_align(p, LV_LABEL_ALIGN_CENTER);
     lv_obj_set_pos(p, 0, 4);
     lv_obj_set_size(p, 128, 60);
     lv_label_set_text(p, "THEAD RISC-V\nGUI TEST\nEEWORLD NB!!");
 }
 static void gui_lvgl_task(void *arg)
 {
     lv_init();
     oled_init();
     gui_label_create();
     while (1) {
         /* Periodically call the lv_task handler.
          * It could be done in a timer interrupt or an OS task too.*/
         lv_task_handler();
         aos_msleep(5);
         lv_tick_inc(1);
     }
 }


这样就可以了:

image.png


03 下期预告

以上即为OLED与LVGL移植的全部介绍,欢迎登录OCC网站查看原文和该系列其他内容。


RVB2601开发板试用仍在进行中,点击【阅读原文】即可参与申请,欢迎广大技术开发者免费申请试用。


下期内容,继续为大家带来溪悦哦关于的RVB2601测评:ADC轮询单通道与DMA多通道采集。请大家持续关注应用实战系列内容。

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
网络协议 数据安全/隐私保护 芯片
|
存储 API 开发工具
【平头哥RVB2601创意应用开发】使用体验02 -- KV存储
使用KV组件在RVB2601进行项目参数的本地持久化。
583 0
【平头哥RVB2601创意应用开发】使用体验02 -- KV存储
|
网络协议 物联网 数据处理
【平头哥RVB2601开发板试用体验】AT 解析器和通过w800 AT命令接入阿里云生活物联网平台
RISC-V RVB2601 Yoc 实现和测试验证阿里云平台接入的等相关功能,同时去了解YoC中网络设备和AT解析器框架。
701 0
【平头哥RVB2601开发板试用体验】AT 解析器和通过w800 AT命令接入阿里云生活物联网平台
|
Web App开发 人工智能 Rust
RISC-V移植安卓12.0再进一步:阿里平头哥首次实现AI支持
RISC-V移植安卓12.0再进一步:阿里平头哥首次实现AI支持
467 0
RISC-V移植安卓12.0再进一步:阿里平头哥首次实现AI支持
|
存储 JSON 数据格式
【平头哥RVB2601创意应用开发】疫情播报系统应用
实现通过网络读取json数据,进行分解,针对数据字符,播放相应的mp3音频文件,并在OLED屏幕上显示实际对应数据的疫情播报系统。
229 0
【平头哥RVB2601创意应用开发】疫情播报系统应用
|
机器学习/深度学习 人工智能 算法
|
存储 边缘计算 固态存储
玄铁RISC-V处理器入门与实战-平头哥玄铁CPU IP-面向高性能领域CPU
玄铁RISC-V处理器入门与实战-平头哥玄铁CPU IP-
1489 0
|
供应链 芯片
平头哥芯片采用的RISC-V架构
平头哥芯片采用的RISC-V架构
357 1

热门文章

最新文章

下一篇
无影云桌面