962|6

20

帖子

9

TA的资源

一粒金砂(中级)

【得捷Follow me第3期】+ 基于lvgl图形库驱动的多功能桌面摆件(完整项目) [复制链接]

本帖最后由 genvex 于 2023-12-15 20:02 编辑

 
 
本项目将包括以下内容:
任务一、安装及使用Micropython---(任务1)
   Micropython真香定律。
任务二、基于SSD1306移植lvgl ----(任务2)
   使用lvgl驱动oled_ssd1036,还很流畅。
任务三、音乐相册---(任务3)
    一边播放midi,一边翻相册。
 任务四、苹果风时钟---(任务4)
    网络同步时间,保证不迟到
任务五、天气预报小站----(任务4)
    天气预报站+ gif动画。
五、加速度数据采集---(任务5)
 
附加内容

一、温湿度监控器 
    高仿温湿度度传感器,数据还同步写入SD卡。

二、声音频谱分析
   
声音快速傅立叶分析,声音可视化

三、lvgl手表表盘 

   高端大气上档次的表盘。

 

211137hb0bpfh2ggh2z20o.jpg

 

一、安装及使用Micropython---(任务1)
   利用Arduino环境会自带esptool工具进行烧录。将bin文件放入到Arduino所在的esptool工具相同的目录下:
211137jzxr7wcn708nrkaz.png
   esptool.exe --chip esp32c3 --port COM71 erase_flash
   在esptool目录下运行cmd命令行
   并运行如下命令:
   esptool.exe --chip esp32c3 --port COM71 --baud 921600 --before default_reset --after hard_reset --no-stub write_flash --flash_mode dio --flash_freq 80m 0x0          ESP32_GENERIC_C3-20231005-v1.21.0.bin 
  其中,COM71需要替换为电脑实际使用的串口号,ESP32_GENERIC_C3-20231005-v1.21.0.bin替换为实际使用的MicroPython固件文件名。
211137kmpw1mjmj44z425m.png
    在Thonny打开 XiaoC3。
    下面直接使用Thonny连接ESP32C3运行 Hello World程序,看到了新鲜热辣的固件里面的内容,看到了Micorpython也一直在进步,我们祝贺它,赶紧亲上一口,然后88了。
 
211137lzsi3609qq3llxil.png

   


二、基于SSD1306移植lvgl ----(任务2)  

     Xiao-expansion-board拓展了多个grove接口,同时还配备了一个入门级别的ssd1306_oled,按照常规ssd1306就该使用u8g2这样的功耗上古神器来驱动,但随着很多高性能嵌入式开发板出现了,图形驱动更新迭代的速度也加快了,驱动ssd1306也有了更多选择。我们值得拥有最好的, 本项目中使用LovyanGFX图形驱动库来驱动ssd1306。LovyanGFX库受TFT_eSPI启发,深度改造而来,是很多大众创客的挚爱,它不单只可以驱动常见的LCD屏幕,同时还可以支持一些oled屏幕,使用这个库使得最简单入门级别的ssd1306 oled屏幕也可以共享LCD屏幕才有的作图函数。LVGL(Light and Versatile Graphics Library)的出现给嵌入式开发在最终产品呈现上提供了一种更优雅的解决方案。LVGL相对于传统图形库更适合用于嵌入式系统,它在性能、资源占用和灵活性方面都有一定的优势。本项目的最大的亮点就是使用lvgl驱动了ssd1306,又因lvgl具有非常优越的跨平台可移植性,ssd1306的应用会更加精彩。
 
    { //进行总线控制的设置。
      auto cfg = _bus_instance.config();  // 获取总线设置的结构。
      // I2C设置
      cfg.i2c_port    = 0;          //  (0 or 1)
      cfg.freq_write  = 400000;     // 写速
      cfg.freq_read   = 400000;     // 收速
      cfg.pin_sda     = 6;          // SDA引脚编号
      cfg.pin_scl     = 7;          // SCL引脚编号
      cfg.i2c_addr    = 0x3C;       // I2C地址

      _bus_instance.config(cfg);               // 将设定值从吃谧芟呱稀�
      _panel_instance.setBus(&_bus_instance);   // 把总线设置在面板上。
    }

       

211137waljvav9koorarva.png
                      Lvgl驱动的核心刷图函数
 
static void lvgl_begin(void)
{

    // #define DISP_BUF_SIZE (LVGL_HOR_RES * 100)
    lcd.init();
    lcd.setRotation(2);
    // lcd.setBrightness(100);
    lv_init();

    static lv_disp_draw_buf_t draw_buf;
    size_t DISP_BUF_SIZE = sizeof(lv_color_t) * (LVGL_HOR_RES * 20); // best operation.
    // static lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc(
    //     DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);
    // static lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(
    //     DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_SPIRAM);

    static lv_color_t *buf1 = (lv_color_t *)heap_caps_malloc( // most fast operation.
        DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);
    static lv_color_t *buf2 = (lv_color_t *)heap_caps_malloc(
        DISP_BUF_SIZE * sizeof(lv_color_t), MALLOC_CAP_DMA);

    lv_disp_draw_buf_init(&draw_buf, buf1, buf2, DISP_BUF_SIZE); /*Initialize the display buffer*/

    /*-----------------------------------
     * Register the display in LVGL
     *----------------------------------*/

    static lv_disp_drv_t disp_drv; /*Descriptor of a display driver*/
    lv_disp_drv_init(&disp_drv);   /*Basic initialization*/

    /*Set up the functions to access to your display*/
    /*Set the resolution of the display*/
    disp_drv.hor_res = LVGL_HOR_RES;
    disp_drv.ver_res = LVGL_VER_RES;
    /*Used to copy the buffer's content to the display*/
    disp_drv.flush_cb = my_disp_flush;
    /*Set a display buffer*/
    disp_drv.draw_buf = &draw_buf;
    /*Finally register the driver*/
    oled = lv_disp_drv_register(&disp_drv);
}

 

 
211137deb6wioiihzqdad4.jpg
成功移植后运行了benkmark 测试驱动的90fps的优越成绩。

 

三、音乐相册---(任务3)
     定制了一个MIDI音乐解码库,实现了midi音乐自由,实现了背景定时切换、长按按键切换音乐,极大的丰富了改设备的可玩性。 一曲“我和我的祖国”让享受沉浸式的爱国教育,洗涤了我狂躁的心灵。
2023-12-13_205437793.jpg

 

211137fpslxu53cq8qwq1s.png
    
 
void MonitorModel::play(int tempo, uint8_t looping, const char *song) // the key function here.
{
    static unsigned long music_tick = millis(); // caculate the time.
   
    if (song && this->song != song) // the most import change here. 
    {
        this->setSong(song); 

    }
    if (this->paused)
    { // check  if stop by the control
        return;
    }

    if (millis() - music_tick >= tempo) // 结束的时候 超过了 1节拍的时长。
    {                                   // where dose the tempo come from. how low will it play. the tempo is fixed, by the song.
        // if (this->p && *(this->p))
        if (this->p)
        {
            char note[4];
            sscanf(this->p, "%[^,]", note); // difficult point here.

            // Serial.println(note);
            this->playNote(note);
            this->p = strchr(this->p, ','); // the pointer arrives at the next","

            if (this->p)
            {                // not empty(still has "," in the sorn list) ,will keep going to next tone.
                ++(this->p); // next char of the ",",the real freq index string.
            }    
        }
        else // outer judgement.
        {
            if (looping)
            { // repeat the song or not !
                this->p = this->song;
            }
            else
            {
                this->stopped = 1; // move on till the end of the song
                                   // noTone(this->pinNumber);
                speaker.mute();
                Serial.println("End");
            }
        }
        music_tick = millis();
    }
}
   播放音乐关键函数实现了按照指定的节奏播放音乐,并支持暂停、切歌(长按用户按键)和循环播放的功能。为了实现非阻塞的播放功能(非阻塞的意思是可以一边上班一边摸鱼),函数会记录当前时间,用于检查是否需要切换到新的音调。最主要的功能是对midi音频数据序列进行解码。
    Midi 曲目的数据格式:
    const char *my_people_my_country = "#,#,A#5,A#5,A#5,A#5,C6,C6,C6,...#”

   每个音符使用逗号进行分隔,接下来,函数会根据设定的节拍时间(音调的播放持续时间)判断是否到达了一个节拍点。如果到达了节拍点,函数会解析字符串中的音符,并播放对应的音符。

    音符的识别使用了map容器来快速根据音符字符串查询到对应的音符频率数值,具体实现方式查看源码。
    std::map<String, int> tones = {
    {"C0", 16}, {"C#0", 17}, {"D0", 18},,,,{"A#9", 14917}, {"B9", 15804}};

     解析字符串中的音符的逻辑是函数会移动指针到下一个逗号的位置,以准备播放下一个音符。如果还有逗号存在,函数会继续播放下一个音符, 如再未检测到逗号,表明播放结束,歌曲播放结束后会检测是否循环播放。

这一套的播放逻辑非常巧妙有趣,用到容器来实现midi音符解码,是一套midi音乐播放的可行解决方案,根据esp32所具备的freeRTOS特性,还可以继续开发同时播放不同音频的功能。

 

四、苹果风时钟--- (任务4)
      开机后实现连接网络进行本地时间同步,苹果偏平风格的时钟图形界面通过lvgl的简单代码划线、圆角矩形、标签功能可以轻松实现,可谓lvgl真正的入门必学操作。时间的刷新和跳动的冒号作为秒针指示是通过两个定时器来完成的,这样时钟就不受其他程序的干扰可以自顾自的跑起来。
 
2023-12-13_205411555.jpg

 

void TimerController::onViewLoad() {
    // model.init();
    // view.create();
    view.load();                     // setup the lvgl display enviroment. 
    updateTimer = lv_timer_create(   // start the timers. 
        [](lv_timer_t* timer) {
            TimerController* controller = (TimerController *)timer->user_data;
            controller->update();    // get the time 
        },
        1000,
        this
    );

    tickTimer = lv_timer_create(
        [](lv_timer_t* timer) {
            TimerController* controller = (TimerController *)timer->user_data;
            controller->updateSegLabel();  // show the second flashing effect. 
        },
        1000,
        this
    );
}

   


 

五、天气预报小站----(任务4)
 
     不能实现时钟和天气预报的开发板不是一块好的开发板
                                                                                                                ------鲁迅 
 
2023-12-13_205648620.jpg

 

   通过高德地图提供的玩具天气信息服务站点获取到的json格式数据,然后用ArduinoJson库对其进行解析,提取关键信息。
{"status":"1","count":"1","info":"OK","infocode":"10000","lives":[{"province":"广东","city":"佛山市","adcode":"440600","weather":"阴","temperature":"20","winddirection":"北","windpower":"≤3","humidity":"75","reporttime":"2023-11-29 09:00:59","temperature_float":"20.0","humidity_float":"75.0"}]}

 

void WeatherModel::getWeather()
{
    if (WiFi.status() != WL_CONNECTED)
    {
        WiFi.begin(ssid, password);
        while (WiFi.status() != WL_CONNECTED)
        {
            delay(500);
            Serial.println("Connecting to WiFi...");
        }
    }
    // 定义一个全局变量作为连接成功的标志
    bool connection_success = false;

    Serial.println("Connected to WiFi");
    HTTPClient http;  
    http.begin("https://restapi.amap.com/v3/weather/weatherInfo?city=" + cityCode + "key=" + userKey);
    int httpCode = http.GET();
    if (httpCode == HTTP_CODE_OK)
    {
        String payload = http.getString();
        Serial.println(payload);

        DynamicJsonDocument doc(1024);
        deserializeJson(doc, payload);
        this->live = doc["lives"][0];
        this->temperature = live["temperature_float"].as<String>();
        this->humidity = live["humidity"].as<String>();
        this->city = live["city"].as<String>();
        this->weather = live["weather"].as<String>();
        this->winddirection = live["winddirection"].as<String>();
        this->windpower = live["windpower"].as<String>();
        // // Serial.print("城市:");
        // Serial.println(live["city"].as<String>());
        // Serial.print("天气:");
        // Serial.println(live["weather"].as<String>());
        // Serial.print("温度:");
        // Serial.println(live["temperature"].as<String>());
        // Serial.print("湿度:");
        // Serial.println(live["humidity"].as<String>());
    }
    else
    {
        Serial.printf("HTTP request failed with error %d\n", httpCode);
    }
    http.end();
    // WiFi.disconnect();
}

 

     天气数据的展示方式采用文字向上滚动播放形式,在文字旁边放一个gif小动画,为整个版面增添了不少活力(太空人大家也看腻),LVGL的使用gif的优点 在于动画上的实现不需要再逐帧提前页面再转换,直接一个gif转化就可以了,另外 本身自带很多动画过程,使得页面更加生动。

 

五、加速度数据采集---(任务5)
          实现一个简单的加速度数据采集界面,其中包含了一个图表和一个标签。首先创建了一个根对象,并对其进行设置,包括大小、位置和样式,创建了一个图表对象,用于显示加速度传感器的数据,并提供了图表和标签的布局和样式设置。
 
2023-12-14_223644449.jpg

 

 
void MpuView::create()
{
    /** screen */
    lv_obj_t *root = lv_obj_create(NULL);
    // lv_obj_add_style(ui.root, &style_scr, 0);
    // lv_obj_set_flex_flow(root, LV_FLEX_FLOW_ROW); // arrange as row.
    lv_obj_clear_flag(root, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
    lv_obj_set_size(root, LV_HOR_RES, LV_VER_RES);
    lv_obj_center(root);
    lv_obj_set_style_pad_all(root, 0, 0);
    lv_obj_set_style_bg_color(root, lv_color_black(), 0);
    lv_obj_set_style_bg_opa(root, LV_OPA_100, 0);
    ui.root = root;

    // lv_obj_set_flex_grow(ui.city, 1);

    static lv_style_t style;
    lv_style_init(&style);

    /*Make a gradient*/
    lv_style_set_bg_color(&style, lv_color_black());
    // lv_style_set_border_opa(&style, LV_OPA_100);
    lv_style_set_bg_opa(&style, LV_OPA_100);

    lv_obj_t *chart = lv_chart_create(root);
    // lv_obj_remove_style_all(game_station);
    lv_obj_set_size(chart, LV_PCT(100), LV_PCT(80));
    lv_obj_align(chart, LV_ALIGN_BOTTOM_MID, 0, 0);
    lv_obj_add_style(chart, &style, 0);

    // lv_obj_move_background(game_station);
    // lv_obj_clear_flag(chart, LV_OBJ_FLAG_SCROLLABLE | LV_OBJ_FLAG_CLICKABLE);
    // lv_obj_set_style_pad_all(chart, 0, 0);
    lv_obj_set_style_border_color(chart, lv_color_black(), 0);
    lv_obj_set_style_radius(chart, 0, 0);
 
 
    lv_chart_set_axis_tick(chart, LV_CHART_AXIS_PRIMARY_Y, 5, 3, 5, 2, true, 10);
    lv_chart_set_point_count(chart, 100);
    lv_chart_set_div_line_count(chart, 0, 0);
    ui.chart = chart;

    lv_chart_set_type(chart, LV_CHART_TYPE_LINE); /*Show lines and points too*/
    lv_obj_set_style_size(chart, 0, LV_PART_INDICATOR);
    /*Add two data series*/
    ser1 = lv_chart_add_series(chart, lv_color_white(), LV_CHART_AXIS_PRIMARY_Y);
    ser2 = lv_chart_add_series(chart, lv_color_white(), LV_CHART_AXIS_SECONDARY_Y);
    lv_chart_set_range(chart, LV_CHART_AXIS_PRIMARY_Y, -200,200);
    lv_chart_set_range(chart,LV_CHART_AXIS_SECONDARY_Y,-200,200);
    lv_obj_t *labelaccX = lv_label_create(root);
    lv_obj_set_size(labelaccX, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
    lv_label_set_text_fmt(labelaccX, "X:%.2f,Y:%.2f", 0.25, 0.25);
    
    lv_obj_align(labelaccX, LV_ALIGN_TOP_LEFT, 0, 0);
    lv_obj_set_style_text_color(labelaccX, lv_color_white(), 0);
    lv_obj_set_style_text_font(labelaccX, &lv_font_montserrat_16, 0);
    ui.labelaccX = labelaccX;

}

 

 
211137ph8pb6z97lhrqlcp.jpg
使用到的传感器是MPU6886 加速计

 

附加内容
 
一、温湿度监控器-- 
   在视图加载时初始化定时器,并设置定时器的回调函数,以便定期执行相关操作。其中包括刷新数据、更新环境信息和将数据写入 SD 卡等。updateTimer 定时器每隔 100 毫秒触发一次回调函数,用于刷新屏幕上显示数据。updateTH 定时器每隔 500 毫秒触发一次回调函数,用于更新环境数据。dataWriteTimer 定时器每隔 1000 毫秒触发一次回调函数,用于将数据写入 SD 卡。
2023-12-13_205855379.jpg

 

 
void EnviromentController::onViewLoad() {
    // model.init(); // will affect the program, if it load many times. 
    // view.create();
    view.load();    
    updateTimer = lv_timer_create(
        [](lv_timer_t* timer) {
            EnviromentController* controller = (EnviromentController *)timer->user_data;
            controller->flashData();
        },
        100,
        this
    );

        updateTH = lv_timer_create(
        [](lv_timer_t* timer) {
            EnviromentController* controller = (EnviromentController *)timer->user_data;
            controller->model.dataUpdate();
        },
        500,
        this
    );

}

 

 
     
211137kn1jln4lr8orxm4g.png
环境监测程序框架图
 
211137s6e6jjpzmzzlmjw9.png
成功将温湿度数据写入SD卡。

 

(2)声音可视化
     声音可视化的主要内容是将声音信号分解成各种频率的成分,通过傅里叶变换计算得到浮点型数组,然后根据条件判断更新柱状图的值和峰值,采用一阶滞后的平滑处理,使得柱状图的变化更加平稳。图形的绘制关键环节在于把频谱对象作为一个容器进行绘制,绘图没有采用 lvgl常规的使用预设的作图函数,而是采用底层的绘图方法。
gif.gif
 
static void spectrum_draw_event_cb(lv_event_t *e) {
    lv_event_code_t code = lv_event_get_code(e);

    if (code == LV_EVENT_DRAW_POST) {
        lv_obj_t *obj           = lv_event_get_target(e);
        lv_draw_ctx_t *draw_ctx = lv_event_get_draw_ctx(e);
        lv_draw_rect_dsc_t draw_rect_dsc;
        lv_draw_rect_dsc_init(&draw_rect_dsc);
        draw_rect_dsc.bg_opa   = LV_OPA_100;
        draw_rect_dsc.bg_color = lv_color_white();
        lv_draw_line_dsc_t draw_line_dsc;
        lv_draw_line_dsc_init(&draw_line_dsc);
        draw_line_dsc.width = 2;
        draw_line_dsc.color = lv_color_white();

        for (int i = 0; i < SAMPLE_SIZE; i++) {
            lv_area_t _rect;
            _rect.x1 = i * x_step;
            _rect.x2 = i * x_step + 3;
            _rect.y1 = CENTER_Y - int(bar_chart[i] / 2);
            _rect.y2 = CENTER_Y + int(bar_chart[i] / 2);
            lv_draw_rect(draw_ctx, &draw_rect_dsc, &_rect);            
    
            lv_point_t above_line[2];
            /* upside line always 2 px above the bar */
            above_line[0].x = i * x_step;
            above_line[0].y = CENTER_Y - int(bar_chart_peaks[i] / 2) - 2;
            above_line[1].x = i * x_step + 3;
            above_line[1].y = CENTER_Y - int(bar_chart_peaks[i] / 2) - 2;
            lv_draw_line(draw_ctx, &draw_line_dsc, &above_line[0],
                         &above_line[1]);

            lv_point_t blow_line[2];
            /* under line always 2 px below the bar */
            blow_line[0].x = i * x_step;
            blow_line[0].y = CENTER_Y + int(bar_chart_peaks[i] / 2) + 2;      
            blow_line[1].x = i * x_step + 3;
            blow_line[1].y = CENTER_Y + int(bar_chart_peaks[i] / 2) + 2;
            lv_draw_line(draw_ctx, &draw_line_dsc, &blow_line[0],
                         &blow_line[1]);
        }
    }
}

     我们采用一个振幅更新函数(bar_value_update)来处理频谱和屏幕大小适配的工作,它接受一个傅里叶变换计算得到的浮点型数组mag,循环遍历mag数组的每隔两个元素,计算它们的平均值ave,并将其与窗口高度进行比较得到柱状图的值。整体逻辑是计算加权平均值,然后根据条件判断,更新柱状图的值和峰值,并且采用了一阶滞后的平滑处理,使得柱状图的变化更加平稳。而图形的绘制关键环节在于把频谱对象作为一个容器进行绘制(spectrum_draw_event_cb),绘图没有采用lvgl常规的使用预设的作图函数,而是采用底层的绘图方法。绘图实际发生在LV_EVENT_DRAW_POST(绘图结束后)事件,会对频谱对象进行绘制操作。

整个过程中使用了绘制矩形(lv_draw_rect)、绘制线条(lv_draw_line)机制等。矩形图代表瞬时的频谱强度,线条用于代表频谱峰值的滞后响应,由于线条的宽度是2 像素,所以看起来也是一个小长方体。它通过循环遍历一个大小为频谱分析结果数据SAMPLE_SIZE的数组,绘制矩形和两条线条,其中bar_chart和bar_chart_peaks是用于确定柱状图高度的数据数组。快速地在spectrum_obj对象上绘制柱状图,并通过柱状图的高度数据进行实时更新。即使是在非黑即白的Oled屏上也能够响应及时,产生了良好的动态效果。

        经过 FFT解析的音乐就像是一幅绚丽多彩的音乐画卷,它将原本复杂的声音信号分解成各种频率的成分,音乐呈现出细腻而有序的频谱,其内在结构和情感深度被剖析得清晰可见,歌曲分解成了千百种细微的声音色彩,每一个频谱点都像是音乐的灵魂之窗,让音乐中各种音调、节奏和情感以细腻而深刻的方式展现,让人们仿佛能够聆听到音符之间跳动的光芒,感受到音乐灵魂的律动与魅力。我们可以感受到音符的跳跃、旋律的起伏、以及音乐整体的魅力,交织出令人陶醉的音乐画面,我们可以看到音符和音阶之间独特的关系,感受到音乐中隐藏的无限可能性,仿佛打开了音乐的魔法之匣,使我们得以窥见音乐中隐藏的美妙之处。

 

 

(3)圆屏功能展示
       完成了圆屏的lvgl驱动和手表功能,效果炸裂。
clockgif.gif
 
​
void showCurrentTime(lv_timer_t *timer)
{
    struct tm info;
    getLocalTime(&info);
    lv_img_set_angle(img_hour, (info.tm_hour * 300 + info.tm_min * 5) % 3600);
    lv_img_set_angle(img_minute, info.tm_min * 60 + info.tm_sec);
    lv_img_set_angle(img_second, info.tm_sec * 60);

#if smooth
    lv_anim_t a;
    lv_anim_init(&a);
    lv_anim_set_var(&a, img_second);
    lv_anim_set_exec_cb(&a, sec_img_cb);
    lv_anim_set_values(&a, (info.tm_sec * 60) % 3600,
                       (info.tm_sec + 1) * 60);
    lv_anim_set_time(&a, 1000);
    lv_anim_start(&a);
#endif
}

void setup()
{
    Serial.begin(115200); // prepare for possible serial debug
    Serial.println("XIAO round screen - LVGL_Arduino");
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(500);
        Serial.println("Connecting to WiFi...");
    }

    configTime(GMT_OFFSET_SEC, DAY_LIGHT_OFFSET_SEC, NTP_SERVER1, NTP_SERVER2);
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo))
    {
        Serial.println("No time available (yet)");
        return;
    }
    Serial.println(&timeinfo, "%A, %B %d %Y %H:%M:%S");

    lv_init();
    lv_xiao_disp_init();
    lv_xiao_touch_init();
    tf.init();
    // tf.readFile("/file.txt");  
   
    // lv_fs_if_init();
    lv_fs_test();
    lv_obj_t *gif = lv_gif_create(lv_scr_act());
    lv_gif_set_src(gif, &fighters);
    lv_obj_center(gif);
    lv_obj_t *connecting = lv_label_create(gif);
    lv_label_set_text(connecting, "Connecting to WiFi...");
    lv_obj_center(connecting);
    long n = 3000;
    while (n--)
    {
        lv_timer_handler();
    }
    WiFi.begin(ssid, password);
    while (WiFi.status() != WL_CONNECTED)
    {
        delay(10);
        lv_timer_handler();
        Serial.println("Connecting to WiFi...");
    }
    bg = lv_img_create(lv_scr_act());
    lv_img_set_src(bg, &watch_bg);
    lv_img_set_zoom(bg, 310);
    lv_obj_center(bg);

    img_hour = lv_img_create(bg);
    lv_img_set_src(img_hour, &hour);
    lv_obj_set_align(img_hour, LV_ALIGN_CENTER);
    lv_img_set_zoom(img_hour, 310);
    lv_img_set_antialias(img_hour, true);

    img_minute = lv_img_create(bg);
    lv_img_set_src(img_minute, &minute);
    lv_img_set_zoom(img_minute, 310);
    lv_obj_set_align(img_minute, LV_ALIGN_CENTER);
    // lv_img_set_antialias(img_minute,true);

    img_second = lv_img_create(bg);
    lv_img_set_src(img_second, &second);
    lv_img_set_zoom(img_second, 310);
    lv_obj_set_align(img_second, LV_ALIGN_CENTER);
    struct tm info;
    getLocalTime(&info);
    lv_img_set_angle(img_hour, (info.tm_hour * 300 + info.tm_min * 5) % 3600);
    lv_img_set_angle(img_minute, info.tm_min * 60 + info.tm_sec);
    lv_img_set_angle(img_second, info.tm_sec * 60);

    timer1 = lv_timer_create(showCurrentTime, 1000, NULL);
}

​

 

 

全文总结:
本文的创新点和亮点包括:

   (1)使用LovyanGFX图形驱动库驱动SSD1306,实现了非阻塞的音频播放,以及将温湿度数据写入SD卡。

   (2)声音可视化和光照亮度监测系统的实现,使得设备具有更高的实用性和趣味性。

   (3)音乐相册功能的实现,通过定制MIDI音乐解码库,实现了背景定时切换、长按按键切换音乐等功能,丰富了设备的可玩性。

   (4)天气预报小站的实现,通过高德地图提供的天气信息服务站点获取到的json格式数据,然后用ArduinoJson库对其进行解析,提取关键信息,实现了当天天气预报滚动播放。

    (同时引发的问题是 玩得太投入了,导致大卷赛 还没开始工作,可能导致翻车!!!)

 

flower.gif

(代码自取)

主任务源码

声音频谱分析

圆屏表盘

 


参考资料:
https://micropython.org/download/ESP32_GENERIC_C3/
https://wiki.seeedstudio.com/get_start_round_display/
https://www.eeworld.com.cn/huodong/digikey_follow_me/

最新回复

楼主提供的技术资料太详细了,感谢楼主的无私分享,最欣赏的是圆形图像展示技术,效果太炫了。   详情 回复 发表于 2024-1-1 11:19

回复
举报

5604

帖子

6

TA的资源

版主

LVGL的图形效果真不错,明年要接触一下  

点评

过几天就明年了,马上学起来。   详情 回复 发表于 2023-12-15 17:20
个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引


回复

20

帖子

9

TA的资源

一粒金砂(中级)

秦天qintian0303 发表于 2023-12-15 16:41 LVGL的图形效果真不错,明年要接触一下  

过几天就明年了,马上学起来。 

点评

学习的东西太多了,得一点一点来    详情 回复 发表于 2023-12-15 17:25

回复

5604

帖子

6

TA的资源

版主

genvex 发表于 2023-12-15 17:20 过几天就明年了,马上学起来。 

学习的东西太多了,得一点一点来  

个人签名

在爱好的道路上不断前进,在生活的迷雾中播撒光引


回复

239

帖子

3

TA的资源

纯净的硅(初级)

做的真不错,很有技术含量!

点评

谢谢大佬谬赞  详情 回复 发表于 2023-12-17 22:46

回复

20

帖子

9

TA的资源

一粒金砂(中级)

HonestQiao 发表于 2023-12-16 21:22 做的真不错,很有技术含量!

谢谢大佬谬赞


回复

683

帖子

5

TA的资源

纯净的硅(高级)

楼主提供的技术资料太详细了,感谢楼主的无私分享,最欣赏的是圆形图像展示技术,效果太炫了。


回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表