【得捷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手表表盘
高端大气上档次的表盘。
一、安装及使用Micropython---(任务1)
利用Arduino环境会自带esptool工具进行烧录。将bin文件放入到Arduino所在的esptool工具相同的目录下:
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固件文件名。
在Thonny打开 XiaoC3。
下面直接使用Thonny连接ESP32C3运行 Hello World程序,看到了新鲜热辣的固件里面的内容,看到了Micorpython也一直在进步,我们祝贺它,赶紧亲上一口,然后88了。
二、基于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); // 把总线设置在面板上。
}
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);
}
成功移植后运行了benkmark 测试驱动的90fps的优越成绩。
三、音乐相册---(任务3)
定制了一个MIDI音乐解码库,实现了midi音乐自由,实现了背景定时切换、长按按键切换音乐,极大的丰富了改设备的可玩性。 一曲“我和我的祖国”让享受沉浸式的爱国教育,洗涤了我狂躁的心灵。
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真正的入门必学操作。时间的刷新和跳动的冒号作为秒针指示是通过两个定时器来完成的,这样时钟就不受其他程序的干扰可以自顾自的跑起来。
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)
不能实现时钟和天气预报的开发板不是一块好的开发板
------鲁迅
通过高德地图提供的玩具天气信息服务站点获取到的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)
实现一个简单的加速度数据采集界面,其中包含了一个图表和一个标签。首先创建了一个根对象,并对其进行设置,包括大小、位置和样式,创建了一个图表对象,用于显示加速度传感器的数据,并提供了图表和标签的布局和样式设置。
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;
}
使用到的传感器是MPU6886 加速计
附加内容
一、温湿度监控器--
在视图加载时初始化定时器,并设置定时器的回调函数,以便定期执行相关操作。其中包括刷新数据、更新环境信息和将数据写入 SD 卡等。updateTimer 定时器每隔 100 毫秒触发一次回调函数,用于刷新屏幕上显示数据。updateTH 定时器每隔 500 毫秒触发一次回调函数,用于更新环境数据。dataWriteTimer 定时器每隔 1000 毫秒触发一次回调函数,用于将数据写入 SD 卡。
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
);
}
环境监测程序框架图
成功将温湿度数据写入SD卡。
(2)声音可视化
声音可视化的主要内容是将声音信号分解成各种频率的成分,通过傅里叶变换计算得到浮点型数组,然后根据条件判断更新柱状图的值和峰值,采用一阶滞后的平滑处理,使得柱状图的变化更加平稳。图形的绘制关键环节在于把频谱对象作为一个容器进行绘制,绘图没有采用 lvgl常规的使用预设的作图函数,而是采用底层的绘图方法。
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驱动和手表功能,效果炸裂。
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库对其进行解析,提取关键信息,实现了当天天气预报滚动播放。
(同时引发的问题是 玩得太投入了,导致大卷赛 还没开始工作,可能导致翻车!!!)
(代码自取)
主任务源码
声音频谱分析
圆屏表盘
参考资料:
https://micropython.org/download/ESP32_GENERIC_C3/
https://wiki.seeedstudio.com/get_start_round_display/
https://www.eeworld.com.cn/huodong/digikey_follow_me/
|