502|1

274

帖子

2

TA的资源

纯净的硅(初级)

楼主
 

「2024 DigiKey 创意大赛 」4、儿童互动数学学习设计 [复制链接]

  本帖最后由 walker2048 于 2024-10-29 21:59 编辑

前言

幼小衔接阶段的数学计算通常包括基础的数数、认识数字、简单的加减法等。这个阶段的目标是让孩子对数学有一个初步的认识和兴趣,为小学阶段的学习打下基础。

例如:识别1到20的简单数字,能区分数字大小,例如4和7谁大。以及一些简单的加减法。

 

产品设计背景

在幼儿教育中,数学启蒙是关键一环,但传统教学方法往往难以激发孩子的兴趣。为了解决这一问题,开发一个专为幼小衔接儿童打造的互动式数学学习应用。该应用通过图形化界面和触摸屏操作,让孩子们在玩乐中学习加减法,提高他们的计算能力和逻辑思维。

 

功能设计:

设计一个适合小朋友的加减法学习应用,可以采用以下简化的步骤:

  1. 界面设计:使用明亮的颜色和卡通形象,以及大按钮和图标,确保界面友好且易于操作。

  2. 互动性:通过触摸屏操作,让孩子通过拖动或点击图形化的图示(如水果、动物等)来完成加减法题目。

  3. 倒计时:设置一个倒计时功能,增加游戏感,让孩子在有限时间内完成题目,提高专注力。

  4. 错题复习:自动记录孩子做错的题目,并提供复习模式,帮助孩子巩固易错点。

  5. 即时反馈:每当孩子答对题目,给予积极的反馈,如动画效果和表扬声音;答错时,提供正确答案和鼓励。

界面设计:

我们完全可以参考实体书的一些做法,例如使用一些常见的水果,通过添加删除线或者外框,组织一些简单的加减法算式。

  题目界面设计

在网上可以找到一些免费的扁平化水果图片和删除线条,组织成类似以下的一个界面设计。

从上到下分别是进度条提示、题目图形化提示,题目和答案选择按钮。

 

出题函数逻辑设计:

  1. 随机选择操作类型:决定是生成加法题目还是减法题目。

  2. 随机生成数字A:随机选择一个数字A,确保它在1到9之间。

  3. 根据数字A生成另一个随机数

    • 对于加法,随机生成一个数字B,使得A和B的和小于或等于10。
    • 对于减法,随机生成一个数字B,使得A和B的差大于0。
  4. 生成答案选项:为题目提供几个可能的答案选项,包括正确答案和几个错误的选项。

生成题目的具体代码

  • #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);
  • }
  • }

按钮使用统一的函数触发判断,根据传入的自定义数据判断按钮是否是正确的答案。

 

 

最终的效果如下图。

最新回复

最终的效果界面友好还是比较恰当的哈   详情 回复 发表于 2024-10-30 07:49
点赞 关注
 
 

回复
举报

7007

帖子

0

TA的资源

五彩晶圆(高级)

沙发
 

最终的效果界面友好还是比较恰当的哈

 
 
 

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

查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/10 下一条
有奖直播报名| TI 面向楼宇和工厂自动化行业的毫米波雷达解决方案
【内容简介】TI 60GHz IWRL6432和 IWRL1432毫米波雷达传感器如何帮助解决楼宇和工厂自动化应用中的感应难题
【直播时间】5月28日(周三)上午10:00
【直播礼品】小米双肩包、contigo水杯、胶囊伞、安克充电器

查看 »

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

 
机器人开发圈

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

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

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

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