walker2048

  • 2024-11-03
  • 发表了主题帖: ESP32软件发生致命错误时的处理办法--Coredump

    本帖最后由 walker2048 于 2024-11-3 23:52 编辑 概述 核心转储是软件发生致命错误时,由紧急处理程序自动保存的一组软件状态信息。核心转储有助于对故障进行事后分析,了解软件状态。ESP-IDF 支持生成核心转储。 核心转储包含了系统中所有任务在发生故障时的快照,每个快照都包括任务的控制块 (TCB) 和栈信息。通过分析任务快照,可以确定是哪个任务、在哪个指令(代码行),以及该任务的哪个调用栈导致了系统崩溃。如果将某些变量赋予特殊的核心转储属性,还可以转储这些变量的内容。   一、修改menuconfig配置 我们通过修改默认的致命错误处理方式,从打印寄存器和重启,修改成打印寄存器然后停机   启用coredump功能   修改程序,我们可以在可以执行到的任意程序位置添加assert(0); 让程序直接进入断言错误这种致命错误,查看是否生效。   二、运行程序 编译程序后运行,可以看到程序符合预期,在main函数直接发生错误了,并成功将转储文件纯粹到flash里   三、通过命令直接查看flash里的coredump关键信息 我们可以通过以下命令,查看一些关键的Coredump信息,例如CPU的寄存器情况,FreeRTOS的线程信息(例如优先级,栈空间使用情况,线程栈帧等等内容) idf.py coredump-info -p /dev/ttyACM0   四、GDB调试 我们还可以进入GDB debug模式进行调试,在命令输入以下内容。通过该命令可以直接加载Flash内存储的Coredump数据,并恢复现场。  idf.py coredump-debug -p /dev/ttyACM0 加载后的输出如下图,我们还可以输入bt命令来显示栈情况   切换线程和栈帧   查看局部变量   总结 使用esp32的coredump功能,可以将致命错误存储到flash里,这样我们在做产品测试的时候,可以不用长期挂着调试器或者串口,一些偶尔出现的致命错误情况,也可以有效的捕获到,并进行现场恢复和调试。

  • 2024-11-02
  • 加入了学习《【2024DigiKey创意大赛】基于AIOT的智能家居设备开发演示视频》,观看 【2024DigiKey创意大赛】基于AIOT的智能家居设备开发

  • 2024-10-30
  • 发表了主题帖: 【2024 DigiKey 创意大赛】 基于ESP32的儿童互动数学学习仪 作品总结与提交

    基于ESP32的儿童互动数学学习仪 作者:忙碌的死龙 一、作品简介 前言 幼小衔接阶段的数学计算通常包括基础的数数、认识数字、简单的加减法等。这个阶段的目标是让孩子对数学有一个初步的认识和兴趣,为小学阶段的学习打下基础。 例如:识别1到20的简单数字,能区分数字大小,例如4和7谁大。以及一些简单的加减法。   产品设计背景 在幼儿教育中,数学启蒙是关键一环,但传统教学方法往往难以激发孩子的兴趣。为了解决这一问题,开发一个专为幼小衔接儿童打造的互动式数学学习应用。该应用通过图形化界面和触摸屏操作,让孩子们在玩乐中学习加减法,提高他们的计算能力和逻辑思维。   功能设计: 设计一个适合小朋友的加减法学习应用,可以采用以下简化的步骤: 界面设计:使用明亮的颜色和卡通形象,以及大按钮和图标,确保界面友好且易于操作。 互动性:通过触摸屏操作,让孩子通过拖动或点击图形化的图示(如水果、动物等)来完成加减法题目。 倒计时:设置一个倒计时功能,增加游戏感,让孩子在有限时间内完成题目,提高专注力。 错题复习:自动记录孩子做错的题目,并提供复习模式,帮助孩子巩固易错点。 即时反馈:每当孩子答对题目,给予积极的反馈,如动画效果和表扬声音;答错时,提供正确答案和鼓励。 界面设计: 我们完全可以参考实体书的一些做法,例如使用一些常见的水果,通过添加删除线或者外框,组织一些简单的加减法算式。 在网上可以找到一些免费的扁平化水果图片和删除线条,组织成类似以下的一个界面设计。 从上到下分别是进度条提示、题目图形化提示,题目和答案选择按钮。 出题函数逻辑设计: 随机选择操作类型:决定是生成加法题目还是减法题目。 随机生成数字A:随机选择一个数字A,确保它在1到9之间。 根据数字A生成另一个随机数: 对于加法,随机生成一个数字B,使得A和B的和小于或等于10。 对于减法,随机生成一个数字B,使得A和B的差大于0。 生成答案选项:为题目提供几个可能的答案选项,包括正确答案和几个错误的选项。 测试做好的出题函数   二、系统框图   三、各部分功能说明 核心部分,esp32-s3-LCD开发板,负责显示数学题目和成绩,通过触屏实现答案选择和输入功能 ,若答题错误则将错误题目存储到 flash里。   颜色传感器,通过该颜色传感器实现钞票面额识别,并通过图形化显示大约价值(例如100元可以买多少个芒果,多少个桃子),让小朋友可以直观了解到钞票的购买力。   四、作品源码 /* * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: CC0-1.0 */ #include "esp_log.h" #include "freertos/FreeRTOS.h" #include "freertos/semphr.h" #include "freertos/task.h" #include "esp_random.h" #include "lvgl.h" #include "bsp/esp-bsp.h" #include <stdint.h> #include <stdio.h> #include <string.h> static char *TAG = "app_main"; #define MAX_QUESTION_NUM 10 #define OPERATION_ADD 1 #define OPERATION_SUB 0 #define FRUIT_OFFSET 72 #define QUESTION_ERR_KEY "err_ques" extern esp_err_t esp_storage_init(); extern esp_err_t esp_storage_set(const char *key, const void *value, size_t length); extern esp_err_t esp_storage_get(const char *key, void *value, size_t length); /** * [url=home.php?mod=space&uid=159083]@brief[/url] 定义一个数学题目结构体 * 该结构体用于存储一个数学题目的相关信息,包括两个数字、操作类型和答案。 */ typedef struct { uint8_t numbers[2]; // 存储两个操作数,例如加法或减法中的两个数字 uint16_t operation : 2; // 存储操作类型,使用2位来表示不同的操作 uint16_t solution : 14; // 存储答案,使用14位来表示答案的值 } math_question_t; typedef struct { math_question_t question[MAX_QUESTION_NUM * 10]; uint8_t error_count[MAX_QUESTION_NUM * 10]; uint32_t question_num; } err_question_t; /** * 全局数组,用于存储生成的数学题目的数组。 */ math_question_t math_questions[MAX_QUESTION_NUM]; err_question_t err_questions = {0}; static char question_str[512]; // 创建二进制信号量 static SemaphoreHandle_t xquestionSemaphore = NULL; static lv_style_t f_style; // 创建一个样式结构体 lv_obj_t *answer_btn[3] = {0}; uint8_t score = 0; uint8_t curren_que = 0; void set_textlable(const char *str, lv_style_t *style, int16_t y_offset) { /*Create a white label, set its text and align it to the center*/ lv_obj_t *label = lv_label_create(lv_scr_act()); lv_label_set_text(label, str); if (style != NULL) { lv_obj_add_style(label, style, LV_STATE_DEFAULT); } lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333), LV_PART_MAIN); lv_obj_align(label, LV_ALIGN_CENTER, 0, y_offset); } /** * 按钮事件回调函数,处理按钮点击事件。 * @param e 事件对象指针。 */ static void btn_event_cb(lv_event_t *e) { // 获取事件代码,判断事件类型。 lv_event_code_t code = lv_event_get_code(e); // 获取触发事件的对象的用户数据,这里存储的是按钮对应的答案值。 intptr_t value = (intptr_t)lv_obj_get_user_data(lv_event_get_target(e)); // 如果事件类型是点击事件(LV_EVENT_CLICKED)。 if (code == LV_EVENT_CLICKED) { // 检查当前问题索引是否小于最大问题数,并且用户点击的答案是否正确。 if (curren_que < MAX_QUESTION_NUM && value == math_questions[curren_que].solution) { // 如果答案正确,更新得分并设置提示文本。 strcpy(question_str, "答对了!"); score += 10; } else { // 如果答案错误,设置提示文本。 strcpy(question_str, "答错了。"); uint8_t index = err_questions.question_num; // 初始化index为当前错误题目的数量 for (int i = 0; i < err_questions.question_num; i++) { if (memcmp(&math_questions[curren_que], &err_questions.question[i], sizeof(math_question_t)) == 0) { // 检查是否找到相同的题目 index = i; err_questions.error_count[i]++; break; } } if (index == err_questions.question_num) { // 如果没有找到相同的题目,添加新的错题 memcpy(&err_questions.question[index], &math_questions[curren_que], sizeof(math_question_t)); err_questions.error_count[index] = 1; // 初始化错误次数为1 err_questions.question_num++; // 增加错误题目的数量 } esp_storage_set(QUESTION_ERR_KEY, &err_questions, sizeof(err_questions)); } // 清除当前活动屏幕上的所有对象。 lv_obj_clean(lv_scr_act()); set_textlable(question_str, &f_style, 40); // 释放信号量,允许下一个问题的处理。 xSemaphoreGive(xquestionSemaphore); } } // 生成指定范围内的随机数 // 参数: // min: 随机数的最小值(包含) // max: 随机数的最大值(包含) // 返回值: // 返回一个在[min, max]范围内的随机数 uint32_t get_random(uint32_t min, uint32_t max) { // 确保随机数生成器只被种子化一次 static bool seeded = false; if (!seeded) { // 使用硬件随机数作为种子,提高随机性 srand(esp_random()); seeded = true; // 标记为已种子化,避免重复种子化 } uint32_t value = (rand()) % (max - min + 1) + min; if (value == 0) { value = (rand()) % (max - min + 1) + min; } return value; // 返回生成的随机数 } /** * 生成一系列数学题目的函数。 * 该函数生成MAX_QUESTION_NUM个数学题目,并存储在全局数组ques中。 * 每个题目随机选择加法或减法操作,并确保操作数不重复。 */ void gen_question() { int8_t last_value = 0; // 用于存储上一个生成的数字,以避免重复 for (int i = 0; i < MAX_QUESTION_NUM; i++) { int8_t operation = get_random(0, 1); // 随机选择1(加法)或0(减法) math_questions[i].numbers[0] = get_random(0, 10); // 随机生成第一个操作数 // 确保第一个操作数不与上一个题目的第一个操作数相同 while (last_value == math_questions[i].numbers[0]) { math_questions[i].numbers[0] = get_random(0, 10); } last_value = math_questions[i].numbers[0]; // 更新上一个操作数的值 // 根据随机选择的操作类型生成题目 if (operation == OPERATION_ADD) { math_questions[i].operation = OPERATION_ADD; // 设置操作类型为加法 // 生成第二个操作数,确保和不超过10 math_questions[i].numbers[1] = get_random(0, 10 - math_questions[i].numbers[0]); math_questions[i].solution = math_questions[i].numbers[0] + math_questions[i].numbers[1]; // 计算答案 } else { math_questions[i].operation = OPERATION_SUB; // 设置操作类型为减法 // 生成第二个操作数,确保差非负 math_questions[i].numbers[1] = get_random(0, math_questions[i].numbers[0]); math_questions[i].solution = math_questions[i].numbers[0] - math_questions[i].numbers[1]; // 计算答案 } } } LV_IMG_DECLARE(chengzi); LV_IMG_DECLARE(taozi); LV_IMG_DECLARE(mangguo); LV_IMG_DECLARE(lizi); LV_IMG_DECLARE(yangtao); const lv_img_dsc_t *src_array[5] = {&chengzi, &taozi, &mangguo, &lizi, &yangtao}; LV_IMG_DECLARE(_fork); LV_IMG_DECLARE(quan); /** * 创建图片对象并设置其位置。 * @param img_ptr 指向图片对象指针的指针。 * @param src 图片源。 * @param x_offset 水平偏移量。 * @param y_offset 垂直偏移量。 * @param index 图片索引。 */ void create_and_position_image(lv_obj_t *img_ptr, const lv_img_dsc_t *src, int16_t x_offset, int16_t y_offset, int index) { img_ptr = lv_img_create(lv_scr_act()); // 创建图片对象 lv_img_set_src(img_ptr, src); // 设置图片源 lv_obj_align(img_ptr, LV_ALIGN_LEFT_MID, x_offset, y_offset + (index % 2 == 0 ? 0 : FRUIT_OFFSET)); // 设置图片对象的对齐方式和位置 } /** * 创建一个按钮,并为其设置样式和事件回调。 * @param btn 指向按钮对象的指针。 * @param value 按钮显示的值。 * @param index 按钮的索引,用于计算位置。 */ void create_ans_button(lv_obj_t *btn, uint8_t value, uint8_t index) { // 设置按钮的背景颜色为蓝色调色板中的主要颜色。 lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), 0); // 设置按钮的圆角半径为10,应用于默认状态和所有部分。 lv_obj_set_style_radius(btn, 10, LV_PART_MAIN | LV_STATE_DEFAULT); // 根据按钮索引计算其在水平方向上的位置,并设置垂直位置为350。 lv_obj_set_pos(btn, 20 + 160 * index, 350); // 设置按钮的大小为120x100像素。 lv_obj_set_size(btn, 120, 100); // 为按钮添加事件回调函数,处理所有事件。 lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); // 将自定义样式应用到按钮的默认状态。 lv_obj_add_style(btn, &f_style, 0); // 在按钮上创建一个标签对象,用于显示文本。 lv_obj_t *label = lv_label_create(btn); // 准备一个字符缓冲区,用于存储按钮的值,并将其格式化为字符串。 char buff[10] = ""; sprintf(buff, "%d", value); // 设置标签的文本为按钮的值。 lv_label_set_text(label, buff); // 将标签居中对齐。 lv_obj_center(label); } /** * 创建一系列图片对象来显示数学题目的选项。 * @param que 指向数学题目结构体的指针,包含题目信息和选项数量。 */ void create_images(math_question_t *que) { const lv_img_dsc_t *src = src_array[get_random(0, 4)]; uint8_t total_images = que->solution; // 总图片数量 if (total_images > 10) return; // 如果总图片数量超过10,则不创建图片 lv_obj_t *img_array[total_images]; // 存储图片对象的数组 lv_obj_t *opt_array[que->numbers[1]]; // 存储图片对象的数组 // 初始化x和y偏移量 int16_t y_offset = -140; // 垂直偏移量,用于将图片向下移动 int16_t x_offset = 60; // 水平偏移量,用于将图片向右移动 uint8_t count = que->numbers[0]; if (que->operation != OPERATION_ADD) { count = que->numbers[0] - que->numbers[1]; } // 创建并设置每个图片对象,用于显示题目的数字 for (int i = 0; i < count; i++) { create_and_position_image(img_array[i], src, x_offset, y_offset, i); x_offset += (i % 2 == 1) ? FRUIT_OFFSET : 0; // 每两个图片水平间隔80 } // 创建并设置每个图片对象,用于显示选项 for (int i = 0; i < que->numbers[1]; i++) { uint8_t img_index = que->numbers[0] + i; // 如果是加法,添加圈背景 if (que->operation == OPERATION_ADD) { create_and_position_image(opt_array[i], &quan, x_offset, y_offset, i + count); } create_and_position_image(img_array[img_index], src, x_offset, y_offset, i + count); // 如果是减法,添加打叉图案 if (que->operation != OPERATION_ADD) { create_and_position_image(opt_array[i], &_fork, x_offset, y_offset, i + count); } x_offset += ((i + count) % 2 == 1) ? FRUIT_OFFSET : 0; // 每两个图片水平间隔80 } // 格式化问题字符串,包含两个数字和一个操作符。 sprintf(question_str, "%d %s %d = ( )", que->numbers[0], que->operation ? "+" : "-", que->numbers[1]); set_textlable(question_str, &f_style, 40); // 生成一个0到2之间的随机索引,用于确定正确答案按钮的位置。 uint8_t right_index = get_random(0, 2); uint8_t v = -1; // 用于生成错误答案的变量。 // 循环创建三个答案按钮。 for (int i = 0; i < 3; i++) { answer_btn[i] = lv_btn_create(lv_scr_act()); // 创建按钮并添加到当前活动屏幕上。 // 如果当前索引是正确答案的索引,设置按钮的用户数据为正确答案。 if (right_index == i) { lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)que->solution); create_ans_button(answer_btn[i], que->solution, right_index); // 创建正确答案按钮。 } else { // 如果当前索引不是正确答案的索引,生成错误答案。 if (que->solution == 0) { int32_t wrong_solution = 0; wrong_solution = que->solution + i + 1; // 计算错误答案。 // 设置按钮的用户数据为错误答案,并创建错误答案按钮。 lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)wrong_solution); create_ans_button(answer_btn[i], wrong_solution, i); } else { int32_t wrong_solution = 0; wrong_solution = que->solution + v; // 计算错误答案。 // 设置按钮的用户数据为错误答案,并创建错误答案按钮。 lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)wrong_solution); create_ans_button(answer_btn[i], wrong_solution, i); } v = 1; // 更新v的值,以便下次循环生成不同的错误答案。 } } } void app_main(void) { /* Initialize display and LVGL */ bsp_display_start(); /* Set display brightness to 100% */ bsp_display_backlight_on(); // 初始化nvs esp_storage_init(); esp_storage_get(QUESTION_ERR_KEY, &err_questions, sizeof(err_questions)); LV_FONT_DECLARE(OPPOSans); lv_style_set_text_font(&f_style, &OPPOSans); // 创建二进制信号量 xquestionSemaphore = xSemaphoreCreateBinary(); ESP_LOGI(TAG, "err questions num is %lu", err_questions.question_num); gen_question(); for (int i = 0; i < MAX_QUESTION_NUM; i++) { curren_que = i; create_images(&math_questions[i]); xSemaphoreTake(xquestionSemaphore, portMAX_DELAY); vTaskDelay(pdMS_TO_TICKS(300)); lv_obj_clean(lv_scr_act()); } sprintf(question_str, "太棒了\n%d 分", score); set_textlable(question_str, &f_style, -50); vTaskDelay(pdMS_TO_TICKS(3000)); lv_obj_clean(lv_scr_act()); char buff[100] = ""; memset(question_str, 0, sizeof(question_str)); for (int i = 0; i < err_questions.question_num; i++) { sprintf(buff, "%d %s %d = ? [e] %d\n", err_questions.question[i].numbers[0], err_questions.question[i].operation ? "+" : "-", err_questions.question[i].numbers[1], err_questions.error_count[i]); strncat(question_str, buff, sizeof(question_str) - strlen(question_str) - 1); } set_textlable(question_str, NULL, 0); } 五、作品功能演示视频 [localvideo]7ee91f0fd3b65e0c9fa0ab39dafebb79[/localvideo]   六、链接汇总 「2024 DigiKey 创意大赛 」1、ESP32 S3 LCD DEV开箱 「2024 DigiKey 创意大赛 」2、运行LVGL demo [经验分享] 「2024 DigiKey 创意大赛 」3、ESPNOW发送单包时间测试 「2024 DigiKey 创意大赛 」4、儿童互动数学学习设计  「2024 DigiKey 创意大赛 」5、补充功能_错题表和钞票面额识别功能 七、项目总结 本项目开发了一款专为儿童设计的互动数学学习仪,通过集成触摸屏和简约界面,实现了直观的数学互动教学。通过图形化形象化处理10以内的加减法,让小朋友更直观的学习到数字和图形的关联,提高小朋友的学习兴趣,并通过反复做题增强记忆。通过本次活动的项目,我学习到了lvgl的图形组件和自定义字体的使用方式,感受到esp32的强大魅力。

  • 2024-10-29
  • 发表了主题帖: 「2024 DigiKey 创意大赛 」4、儿童互动数学学习设计

    本帖最后由 walker2048 于 2024-10-29 21:59 编辑 前言 幼小衔接阶段的数学计算通常包括基础的数数、认识数字、简单的加减法等。这个阶段的目标是让孩子对数学有一个初步的认识和兴趣,为小学阶段的学习打下基础。 例如:识别1到20的简单数字,能区分数字大小,例如4和7谁大。以及一些简单的加减法。   产品设计背景 在幼儿教育中,数学启蒙是关键一环,但传统教学方法往往难以激发孩子的兴趣。为了解决这一问题,开发一个专为幼小衔接儿童打造的互动式数学学习应用。该应用通过图形化界面和触摸屏操作,让孩子们在玩乐中学习加减法,提高他们的计算能力和逻辑思维。   功能设计: 设计一个适合小朋友的加减法学习应用,可以采用以下简化的步骤: 界面设计:使用明亮的颜色和卡通形象,以及大按钮和图标,确保界面友好且易于操作。 互动性:通过触摸屏操作,让孩子通过拖动或点击图形化的图示(如水果、动物等)来完成加减法题目。 倒计时:设置一个倒计时功能,增加游戏感,让孩子在有限时间内完成题目,提高专注力。 错题复习:自动记录孩子做错的题目,并提供复习模式,帮助孩子巩固易错点。 即时反馈:每当孩子答对题目,给予积极的反馈,如动画效果和表扬声音;答错时,提供正确答案和鼓励。 界面设计: 我们完全可以参考实体书的一些做法,例如使用一些常见的水果,通过添加删除线或者外框,组织一些简单的加减法算式。   题目界面设计 在网上可以找到一些免费的扁平化水果图片和删除线条,组织成类似以下的一个界面设计。 从上到下分别是进度条提示、题目图形化提示,题目和答案选择按钮。   出题函数逻辑设计: 随机选择操作类型:决定是生成加法题目还是减法题目。 随机生成数字A:随机选择一个数字A,确保它在1到9之间。 根据数字A生成另一个随机数: 对于加法,随机生成一个数字B,使得A和B的和小于或等于10。 对于减法,随机生成一个数字B,使得A和B的差大于0。 生成答案选项:为题目提供几个可能的答案选项,包括正确答案和几个错误的选项。 生成题目的具体代码 #define MAX_QUESTION_NUM 10 #define OPERATION_ADD 1 #define OPERATION_SUB 0 /** * @brief 定义一个数学题目结构体 * 该结构体用于存储一个数学题目的相关信息,包括两个数字、操作类型和答案。 */ typedef struct { uint8_t numbers[2]; // 存储两个操作数,例如加法或减法中的两个数字 uint16_t operation : 2; // 存储操作类型,使用2位来表示不同的操作 uint16_t solution : 14; // 存储答案,使用14位来表示答案的值 } math_question_t; /** * 全局数组,用于存储生成的数学题目的数组。 */ math_question_t math_questions[MAX_QUESTION_NUM]; uint8_t score = 0; // 生成指定范围内的随机数 // 参数: // min: 随机数的最小值(包含) // max: 随机数的最大值(包含) // 返回值: // 返回一个在[min, max]范围内的随机数 uint32_t get_random(uint32_t min, uint32_t max) { // 确保随机数生成器只被种子化一次 static bool seeded = false; if (!seeded) { // 使用硬件随机数作为种子,提高随机性 srand(esp_random()); seeded = true; // 标记为已种子化,避免重复种子化 } return (rand()) % (max - min + 1) + min; // 返回生成的随机数 } /** * 生成一系列数学题目的函数。 * 该函数生成MAX_QUESTION_NUM个数学题目,并存储在全局数组ques中。 * 每个题目随机选择加法或减法操作,并确保操作数不重复。 */ void gen_question() { int8_t last_value = 0; // 用于存储上一个生成的数字,以避免重复 for (int i = 0; i < MAX_QUESTION_NUM; i++) { int8_t operation = get_random(0, 1); // 随机选择1(加法)或0(减法) math_questions[i].numbers[0] = get_random(0, 10); // 随机生成第一个操作数 // 确保第一个操作数不与上一个题目的第一个操作数相同 while (last_value == math_questions[i].numbers[0]) { math_questions[i].numbers[0] = get_random(0, 10); } last_value = math_questions[i].numbers[0]; // 更新上一个操作数的值 // 根据随机选择的操作类型生成题目 if (operation == OPERATION_ADD) { math_questions[i].operation = OPERATION_ADD; // 设置操作类型为加法 // 生成第二个操作数,确保和不超过10 math_questions[i].numbers[1] = get_random(0, 10 - math_questions[i].numbers[0]); math_questions[i].solution = math_questions[i].numbers[0] + math_questions[i].numbers[1]; // 计算答案 } else { math_questions[i].operation = OPERATION_SUB; // 设置操作类型为减法 // 生成第二个操作数,确保差非负 math_questions[i].numbers[1] = get_random(0, math_questions[i].numbers[0]); math_questions[i].solution = math_questions[i].numbers[0] - math_questions[i].numbers[1]; // 计算答案 } } } 编译程序验证代码     按原有设计编写lvgl的界面部分: 过程就不浪费时间说了,代码如下: LV_IMG_DECLARE(chengzi); LV_IMG_DECLARE(taozi); LV_IMG_DECLARE(mangguo); LV_IMG_DECLARE(lizi); LV_IMG_DECLARE(yangtao); const lv_img_dsc_t *src_array[5] = {&chengzi, &taozi, &mangguo, &lizi, &yangtao}; LV_IMG_DECLARE(_fork); LV_IMG_DECLARE(quan); /** * 创建图片对象并设置其位置。 * @param img_ptr 指向图片对象指针的指针。 * @param src 图片源。 * @param x_offset 水平偏移量。 * @param y_offset 垂直偏移量。 * @param index 图片索引。 */ void create_and_position_image(lv_obj_t *img_ptr, const lv_img_dsc_t *src, int16_t x_offset, int16_t y_offset, int index) { img_ptr = lv_img_create(lv_scr_act()); // 创建图片对象 lv_img_set_src(img_ptr, src); // 设置图片源 lv_obj_align(img_ptr, LV_ALIGN_LEFT_MID, x_offset, y_offset + (index % 2 == 0 ? 0 : FRUIT_OFFSET)); // 设置图片对象的对齐方式和位置 } /** * 创建一个按钮,并为其设置样式和事件回调。 * @param btn 指向按钮对象的指针。 * @param value 按钮显示的值。 * @param index 按钮的索引,用于计算位置。 */ void create_ans_button(lv_obj_t *btn, uint8_t value, uint8_t index) { // 设置按钮的背景颜色为蓝色调色板中的主要颜色。 lv_obj_set_style_bg_color(btn, lv_palette_main(LV_PALETTE_BLUE), 0); // 设置按钮的圆角半径为10,应用于默认状态和所有部分。 lv_obj_set_style_radius(btn, 10, LV_PART_MAIN | LV_STATE_DEFAULT); // 根据按钮索引计算其在水平方向上的位置,并设置垂直位置为350。 lv_obj_set_pos(btn, 20 + 160 * index, 350); // 设置按钮的大小为120x100像素。 lv_obj_set_size(btn, 120, 100); // 为按钮添加事件回调函数,处理所有事件。 lv_obj_add_event_cb(btn, btn_event_cb, LV_EVENT_ALL, NULL); // 将自定义样式应用到按钮的默认状态。 lv_obj_add_style(btn, &f_style, 0); // 在按钮上创建一个标签对象,用于显示文本。 lv_obj_t *label = lv_label_create(btn); // 准备一个字符缓冲区,用于存储按钮的值,并将其格式化为字符串。 char buff[10] = ""; sprintf(buff, "%d", value); // 设置标签的文本为按钮的值。 lv_label_set_text(label, buff); // 将标签居中对齐。 lv_obj_center(label); } /** * 创建一系列图片对象来显示数学题目的选项。 * @param que 指向数学题目结构体的指针,包含题目信息和选项数量。 */ void create_images(math_question_t *que) { const lv_img_dsc_t *src = src_array[get_random(0, 4)]; uint8_t total_images = que->solution; // 总图片数量 if (total_images > 10) return; // 如果总图片数量超过10,则不创建图片 lv_obj_t *img_array[total_images]; // 存储图片对象的数组 lv_obj_t *opt_array[que->numbers[1]]; // 存储图片对象的数组 // 初始化x和y偏移量 int16_t y_offset = -140; // 垂直偏移量,用于将图片向下移动 int16_t x_offset = 60; // 水平偏移量,用于将图片向右移动 uint8_t count = que->numbers[0]; if (que->operation != OPERATION_ADD) { count = que->numbers[0] - que->numbers[1]; } // 创建并设置每个图片对象,用于显示题目的数字 for (int i = 0; i < count; i++) { create_and_position_image(img_array[i], src, x_offset, y_offset, i); x_offset += (i % 2 == 1) ? FRUIT_OFFSET : 0; // 每两个图片水平间隔80 } // 创建并设置每个图片对象,用于显示选项 for (int i = 0; i < que->numbers[1]; i++) { uint8_t img_index = que->numbers[0] + i; // 如果是加法,添加圈背景 if (que->operation == OPERATION_ADD) { create_and_position_image(opt_array[i], &quan, x_offset, y_offset, i + count); } create_and_position_image(img_array[img_index], src, x_offset, y_offset, i + count); // 如果是减法,添加打叉图案 if (que->operation != OPERATION_ADD) { create_and_position_image(opt_array[i], &_fork, x_offset, y_offset, i + count); } x_offset += ((i + count) % 2 == 1) ? FRUIT_OFFSET : 0; // 每两个图片水平间隔80 } // 初始化文本标签指针为NULL。 lv_obj_t *textlabel = NULL; // 创建一个新的标签对象,用于显示问题。 textlabel = lv_label_create(lv_scr_act()); // 格式化问题字符串,包含两个数字和一个操作符。 sprintf(question_str, "%d %s %d = ( )", que->numbers[0], que->operation ? "+" : "-", que->numbers[1]); // 设置标签的文本为格式化后的问题字符串。 lv_label_set_text(textlabel, question_str); // 将自定义样式应用到标签的默认状态。 lv_obj_add_style(textlabel, &f_style, LV_STATE_DEFAULT); // 设置整个屏幕的文本颜色为深灰色。 lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333), LV_PART_MAIN); // 将标签居中对齐,并向下偏移40个单位。 lv_obj_align(textlabel, LV_ALIGN_CENTER, 0, 40); // 生成一个0到2之间的随机索引,用于确定正确答案按钮的位置。 uint8_t right_index = get_random(0, 2); uint8_t v = -1; // 用于生成错误答案的变量。 // 循环创建三个答案按钮。 for (int i = 0; i < 3; i++) { answer_btn[i] = lv_btn_create(lv_scr_act()); // 创建按钮并添加到当前活动屏幕上。 // 如果当前索引是正确答案的索引,设置按钮的用户数据为正确答案。 if (right_index == i) { lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)que->solution); create_ans_button(answer_btn[i], que->solution, right_index); // 创建正确答案按钮。 } else { // 如果当前索引不是正确答案的索引,生成错误答案。 if (que->solution == 0) { v = 2; // 如果正确答案是0,错误答案应该增加2。 } int32_t wrong_solution = 0; wrong_solution = que->solution + v; // 计算错误答案。 // 设置按钮的用户数据为错误答案,并创建错误答案按钮。 lv_obj_set_user_data(answer_btn[i], (void *)(intptr_t)wrong_solution); create_ans_button(answer_btn[i], wrong_solution, i); v = 1; // 更新v的值,以便下次循环生成不同的错误答案。 } } } void app_main(void) { /* Initialize display and LVGL */ bsp_display_start(); /* Set display brightness to 100% */ bsp_display_backlight_on(); LV_FONT_DECLARE(OPPOSans); lv_style_set_text_font(&f_style, &OPPOSans); // 创建二进制信号量 xquestionSemaphore = xSemaphoreCreateBinary(); gen_question(); for (int i = 0; i < MAX_QUESTION_NUM; i++) { curren_que = i; create_images(&math_questions[i]); xSemaphoreTake(xquestionSemaphore, portMAX_DELAY); vTaskDelay(pdMS_TO_TICKS(300)); lv_obj_clean(lv_scr_act()); } lv_obj_t *textlabel = NULL; /*Create a white label, set its text and align it to the center*/ textlabel = lv_label_create(lv_scr_act()); sprintf(question_str, "太棒了\n%d 分", score); lv_label_set_text(textlabel, question_str); lv_obj_add_style(textlabel, &f_style, LV_STATE_DEFAULT); lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333), LV_PART_MAIN); lv_obj_align(textlabel, LV_ALIGN_CENTER, 0, -50); } 这样就把出题和答题部分的内容做好了。   // 创建二进制信号量 static SemaphoreHandle_t xquestionSemaphore = NULL; static lv_style_t f_style; // 创建一个样式结构体 lv_obj_t *answer_btn[3] = {0}; uint8_t score = 0; uint8_t curren_que = 0; /** * 按钮事件回调函数,处理按钮点击事件。 * @param e 事件对象指针。 */ static void btn_event_cb(lv_event_t *e) { // 获取事件代码,判断事件类型。 lv_event_code_t code = lv_event_get_code(e); // 获取触发事件的对象的用户数据,这里存储的是按钮对应的答案值。 intptr_t value = (intptr_t)lv_obj_get_user_data(lv_event_get_target(e)); // 如果事件类型是点击事件(LV_EVENT_CLICKED)。 if (code == LV_EVENT_CLICKED) { // 检查当前问题索引是否小于最大问题数,并且用户点击的答案是否正确。 if (curren_que < MAX_QUESTION_NUM && value == math_questions[curren_que].solution) { // 如果答案正确,更新得分并设置提示文本。 strcpy(question_str, "答对了!"); score += 10; } else { // 如果答案错误,设置提示文本。 strcpy(question_str, "答错了。"); } // 清除当前活动屏幕上的所有对象。 lv_obj_clean(lv_scr_act()); // 创建一个新的标签对象,用于显示反馈信息。 lv_obj_t *textlabel = lv_label_create(lv_scr_act()); lv_label_set_text(textlabel, question_str); // 设置标签的文本为反馈信息。 // 将自定义样式应用到标签的默认状态。 lv_obj_add_style(textlabel, &f_style, LV_STATE_DEFAULT); // 设置整个屏幕的文本颜色为深灰色。 lv_obj_set_style_text_color(lv_scr_act(), lv_color_hex(0x333333), LV_PART_MAIN); // 将标签居中对齐,并向下偏移40个单位。 lv_obj_align(textlabel, LV_ALIGN_CENTER, 0, 40); // 释放信号量,允许下一个问题的处理。 xSemaphoreGive(xquestionSemaphore); } } 按钮使用统一的函数触发判断,根据传入的自定义数据判断按钮是否是正确的答案。     最终的效果如下图。

  • 回复了主题帖: 如何在低成本ARM平台部署LVGL免费图形库,基于全志T113-i

    蓝雨夜 发表于 2024-10-29 16:05 ST的 G071驱动LCD成本咋样? 自己评估下flash和RAM占用吧

  • 2024-10-18
  • 回复了主题帖: 有没有DC-DC降压,输出可以延时变化的电路

    这样做的目的是什么?

  • 2024-10-02
  • 回复了主题帖: 三菱plc知识

    没啥卵用,就是各种PLC相关产品型号和大致参数

  • 2024-09-30
  • 回复了主题帖: ESP32 EThernet_kit_a_v12。求购一些

    淘宝有替代的,虽然不能直接替换,但是带eth和poe的还是有的

  • 2024-09-25
  • 回复了主题帖: 【2024 DigiKey 创意大赛】开箱帖:树莓派5、ESP32C6和nRF52840 Dongle

    JectXie 发表于 2024-9-25 12:25 是因为不好用吗?又贵 又贵又没啥用,淘宝有30元不到的替代产品

  • 2024-09-24
  • 回复了主题帖: 关于自恢复保险使用问题

    maychang 发表于 2024-9-24 10:14 【有些LDO或者DCDC没有短路保护,只有过流保护,这种情况下用什么器件能增强保护能力呢?】 有过流保 ... 也是,我理解错误了

  • 回复了主题帖: 关于自恢复保险使用问题

    maychang 发表于 2024-9-23 18:52 【其它品牌、保持电流、最大工作电压不一样的自恢复保险,也存在焊接前后阻值完全不一样问题,是不是并不 ... 您好,想问一下,有些LDO或者DCDC没有短路保护,只有过流保护,这种情况下用什么器件能增强保护能力呢?就是担心有些客户不小心短接了3.3V和GND,烧坏DCDC

  • 2024-09-23
  • 回复了主题帖: 【2024 DigiKey 创意大赛】开箱帖:树莓派5、ESP32C6和nRF52840 Dongle

    nRF52840 Dongle其实不大建议买

  • 2024-09-19
  • 回复了主题帖: TMS320F28P65外部晶振负阻测试

    但为君故chris 发表于 2024-9-19 09:15 您好,请问一下可变电阻两端是接在 芯片与晶振连接的 X1和X2口吗 是的,我理解是这样,你可以试试看

  • 2024-09-18
  • 回复了主题帖: TMS320F28P65外部晶振负阻测试

    接在两个晶振线路之间,XI和XO

  • 2024-09-16
  • 回复了主题帖: 立创·ESP32S3R8N8开发板

    其实esp idf真的很好用,用习惯了,比python更顺手,生态也更完善

  • 2024-09-15
  • 回复了主题帖: 幼儿园要求带月饼上学

    我家娃儿没到中秋就惦记上吃月饼了

  • 2024-09-13
  • 加入了学习《Follow me第二季第2期视频演示》,观看 演示视频

  • 2024-09-12
  • 回复了主题帖: 编译错误的问题

    再往前找第一个错误

  • 回复了主题帖: [BearPi-Pico H2821]测评 ⑧拉距测试

    目前hi2821的最远拉距测试是409米,在B站可以查到。 一般拉距测试建议去河边,或者田野这种宽阔,同时周边没有干扰信号的场所进行测试。

  • 发表了日志: 国产以太网控制器CH390h试用体验----替代W5500

统计信息

已有346人来访过

  • 芯积分:648
  • 好友:3
  • 主题:63
  • 回复:158

留言

你需要登录后才可以留言 登录 | 注册


现在还没有留言