基于RP2040带屏12指神探的LVGL图形化控制终端
该项目使用了RP2040,实现了电子时钟的设计,它的主要功能为:电子时钟。
标签
嵌入式系统
RP2040
2024寒假一起练
genvex
更新2024-03-29
467


本项目基于RP2040带屏12指神探在vscode_pio编程环境移植了lvgl,利用外部rtc作为时钟源,实现了手表功能和一个泡泡屏保程序,程序可通过按键切换。




项目代码思路流程图

一、编程环境搭建

   项目是基于Arduino在vscode的PlatformIO开发平台完成的。Arduio用来管理小型单功能项目还是可以的,主要是它编程环境准备相对简单,大众创客的挚爱。进阶的使用可以将战场转移到vscodeIDE,利用vscodeIDE提供的自带技能及其他优秀插件带来的便利,来提升编程效果。


二、屏幕驱动及LVGL移植

     成功的嵌入式设备需要一个极具吸引力的用户界面,才能给用户留下良好的第一印象。在Arduino的范畴里,传统的图形绘制库(例如经典TFT_eSPI,Adafruit_GFX,Lovyan_GFX等)可以帮助用户快速的入门,LVGL的出现给嵌入式开发在最终产品呈现提供了一种解决方案。Arduino编程环境的LVGL运行效果比在micropython环境更为流畅,可以实现多任务的切换,而且编程的难度可深可浅,根据不同的编程能力,实现同一功能有很多种解决办法。但是LVGL的版本一直在演替,带来的问题是版本间的差异会给刚入门的小伙伴造成很大的困扰,即便是想运行一个helloworld测试,就要折腾好久,甚至弃坑,都是很正常的现象,所以要多加练习。 

(1)屏幕驱动

因为早前已经实现了TFT_eSPI库来驱动ST7789这个屏幕,如果不想玩LVGL,直接使用TFT_eSPI这个经典的图形库也可以玩出花来,毕竟过去这么多年,国内外的大神就是使用这些纯朴的工具开发出很多优秀的作品,但是社会变了,使用LVGL来制作用户界面成为了一种时尚,假如你在嵌入式开发领域活动,你不掌握一点LVGL相关知识,在大家津津有味地讨论起技术来,你只能在风中凌乱了。

/* Display flushing */

void my_disp_flush(lv_disp_drv_t *disp_drv, const lv_area_t *area, lv_color_t *color_p)

{

  uint32_t w = (area->x2 - area->x1 + 1);

  uint32_t h = (area->y2 - area->y1 + 1);

  tft.startWrite();

  tft.setAddrWindow(area->x1, area->y1, w, h);

  tft.pushColors((uint16_t *)&color_p->full, w * h, true);

  tft.endWrite();

  lv_disp_flush_ready(disp_drv);

}

 

static void lv_encoder_read(lv_indev_drv_t *indev_drv, lv_indev_data_t *data)

{

  data->state = LV_INDEV_STATE_RELEASED;

  if (ENTER_BUTTON)

  {

    data->state = LV_INDEV_STATE_PR;

    ENTER_BUTTON = false;

  }

  else if (PREV_BUTTON)

  {

    data->enc_diff = -1;

    PREV_BUTTON = false;

  }

  else if (NEXT_BUTTON)

  {

    data->enc_diff = 1;

    NEXT_BUTTON = false;

  }

}

lv_timer_t *button_read_timer;



三、时钟设计

(1)RTC8563驱动

rp2040默认I2C引脚是4-5,没有被引出,要使用扩展口的引脚必须修改才能使用I2C的外置设备。在Arduino官方板卡支持库没有I2C驱动指定引脚的功能函数。所以,折中的办法是手动修改系统内部的引脚默认设置。

修改I2C默认引脚的方法 :pins_arduino.h ~/.platformio/packages/framework-arduino-mbed/variants/RASPBERRY_PI_PICO/pins_arduino.h,具体路径参见下图。

如果忘记修改了,就会花屏,不能正常启动(大家应该都知道,我为什么知道的吧--把一个主机整成砖头了)。


由于Pico没有提供网络支持,所以要实现时钟功能需要外部提供一个时钟源,这里用到的是RTC8563.这个设备在esp32上使用得挺好的,在pico上的使用却还不多。


(2)时钟设计

使用了面向对象思路来构造程序,把LVGL时钟用类来封装,这样有利于程序的进入和退出的管理。相较于面向过程的编程,代码的思路更为清晰,但工作量会增加,处理变量、函数、定时器会需要一些特殊处理。

时间指针旋转角度的更新函数:


void WatchController::update()
{
    rtc.getTime(&timeStruct);
    lv_img_set_angle(view.ui.img_hour, (timeStruct.hours * 300 + timeStruct.minutes * 5) % 3600);
    lv_img_set_angle(view.ui.img_minute, timeStruct.minutes * 60 + timeStruct.seconds);
    lv_img_set_angle(view.ui.img_second, timeStruct.seconds * 60);
}

其对应的计算公式如下

LVGL 系统里1圈是3600度

  1. 时针移动角度计算公式
    • 时针每小时300度,即每小时移动(3600/12)= 300度。
    • 分针每分钟移动(300/60)= 5度。
    • 计算公式:hours * 300 + minutes * 5
  2. 分钟针移动角度计算公式
    • 分针每分钟60度,即每秒移动(3600/60)= 60度。
    • 计算公式:minutes * 60 + seconds * 1
  3. 秒针移动角度计算公式
    • 秒针每秒钟60度,即每秒移动(3600/60)= 60度。
    • 计算公式:seconds * 60


四、泡泡程序

    完成了两个程序的切换,按拨轮的中间键可以切换两个程序。


 {

  // monitorController.model.speaker.beep();

  switch ((++currentScreen) % 2)

  // switch ((currentScreen) % 2)

  {

  case 0:

    bubblecontroller.onViewDisappear();   

    watchcontroller.onViewLoad();

    break;

  case 1:

    watchcontroller.onViewDisappear();

    bubblecontroller.onViewLoad();

    break;

  default:

    break;

  }

}




void BubbleView::game_draw_event_cb(lv_event_t *e)
{
    // changing the style of the rectangle.
    lv_obj_t *obj = lv_event_get_target(e);
    lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e); // get the draw context
    lv_draw_rect_dsc_t draw_rect_dsc; // there is many draw dsc
    lv_draw_rect_dsc_init(&draw_rect_dsc);

    draw_rect_dsc.radius = LV_RADIUS_CIRCLE; // change the rectangle into a circle
    draw_rect_dsc.border_width = 2;
    draw_rect_dsc.bg_opa = LV_OPA_0;               // transparent circle
    draw_rect_dsc.border_color = lv_color_white(); //,LV_COLOR_MAKE(100, 150, 226);
    // draw_rect_dsc.border_color = lv_color_hex(rand()*512+rand()*125);
    if (vectb.size() < 50)
    {
        bubble_t b = {
            .r = lv_rand(5, 50),
            .x = lv_rand(b.r, LV_HOR_RES - b.r),
            .y = lv_rand(b.r, LV_VER_RES - b.r),
            .dx = (((random(2) == 1) ? -1 : 1) * lv_rand(1, 2)),
            .dy = (((random(2) == 1) ? -1 : 1) * lv_rand(1, 2)),
            // .color = lv_color_white(),
        };
        vectb.push_back(b);
    }

    for (auto &b : vectb)
    {
        // draw_rect_dsc.border_color = lv_color_white();
        lv_area_t gen_rect;      // the area_t is import for the drawing.
        gen_rect.x1 = b.x;       // giving the x1,y1,x2,y2 site for every rectangle
        gen_rect.x2 = b.x + b.r; // but now it is circle here.
        gen_rect.y1 = b.y;
        gen_rect.y2 = b.y + b.r;
        lv_draw_rect(draw_ctx, &draw_rect_dsc, &gen_rect); // the main function works here
        b.move();
    }


}


  1. 更改矩形的样式
    • 通过lv_event_get_target(e)获取目标对象obj和lv_event_get_draw_ctx(e)获取绘图上下文draw_ctx。
    • 初始化绘制矩形描述符draw_rect_dsc,并设置属性,如圆角半径、边框宽度、背景透明度和边框颜色。
  2. 绘制气泡
    • 如果vectb(气泡向量)的大小小于50,随机生成气泡的半径、位置、速度等属性,并将气泡添加到vectb中。
    • 遍历vectb中的每个气泡b,根据其属性绘制圆形(实际上是用矩形模拟圆形),并调用b.move()移动气泡。
  3. 绘制函数
    • lv_draw_rect函数用于绘制矩形或圆形,根据给定的绘图上下文、绘制描述符和区域进行绘制。

本项目的一些心得体会:

   以往参加活动,针对主办方提出的要求逐一完成任务,把任务分解开来,这样比较轻松的完成单个内容。这次尝试把所有的任务整合在一起,在一次启动就可以展示所有(必做任务)内容,这是一次积极的挑战,通过面向对象的编程思想,把功能用类(class)进行统一的封装,使得不同的功能可以自由切换,而且还能保持流畅运行。理论上只要Flash够大,可以按照类的模板不断的扩展应用程序,相当做个了一个项目的架构,体现了做应用项目的思维,这种思维应该继续使用下去,因为经过简单功能程序学习,最终是需要把这些简单的功能综合起来),这样才能成为一个有用的工具,一个有趣的产品。

   感谢主办方给予我极大的创作空间,祝愿该系列活动完满成功并持续举办下去!


核心代码:


https://gitee.com/genvex/rp2040_watch_with_rtc




团队介绍
评论
0 / 100
查看更多
硬禾服务号
关注最新动态
0512-67862536
info@eetree.cn
江苏省苏州市苏州工业园区新平街388号腾飞创新园A2幢815室
苏州硬禾信息科技有限公司
Copyright © 2024 苏州硬禾信息科技有限公司 All Rights Reserved 苏ICP备19040198号