stm32开发之threadx之modulex模块文件的生成脚本项目

news2024/10/6 18:22:24

前言

  1. 为了保证在window上运行,且体积小的问题,所以采用c语言编写生成脚本,
  2. 将相关路径由json文件进行配置,
  3. 使用了一个cjson库进行解析
  4. 项目构建使用的是cmake

项目代码

CMakeLists文件

cmake_minimum_required(VERSION 3.27)
project(txm_bat_script C)

set(CMAKE_C_STANDARD  11)
find_package(cJSON CONFIG REQUIRED)
configure_file(config.json ${CMAKE_CURRENT_BINARY_DIR}/config.json COPYONLY)
add_executable(txm_bat_script main.c
        tx_app_module_build.c
        tx_txm_lib_build.c)



target_link_libraries(txm_bat_script PRIVATE cjson)


main函数

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-14     shchl   first version
 */
#include "main.h"
#include "cjson/cJSON.h"

FILE *configFile; /*配置文件*/
cJSON *rootJson;

char conf_data[4096];

int main(void) {
    configFile = fopen("config.json", "r");
    if (configFile) {
        fseek(configFile, 0, SEEK_SET);
        size_t readCnt = fread(conf_data, 1, 4096, configFile);
        printf("read cnt:%zu\r\n", readCnt);
        rootJson = cJSON_Parse(conf_data);
        if (!rootJson) goto config_error;
        build_txm_lib_cjson(rootJson);
        build_app_module_bat_json(rootJson);


        goto _exit;
    }


    config_error:
    printf("json config error\r\n");

    _exit:
    if(configFile) fclose(configFile);
    if (rootJson) cJSON_Delete(rootJson);
    return 0;
}

构建txm静态库源文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-14     shchl          thread txm lib 脚本构建
 */
#include "main.h"
#include <dirent.h>
#include <sys/stat.h>


/**
 * @brief txm 源文件构建命令
 */
const char *tx_txm_lib_source_build_cmd =
        "arm-none-eabi-gcc -c -g "
        "-mcpu=cortex-m4 "
        "-mfloat-abi=hard "
        "-mfpu=vfpv4 "
        "-mthumb "
        "-fpic -fno-plt "
        "-mno-pic-data-is-text-relative "
        "-msingle-pic-base ";

static void build_txm_lib(char *txm_inc, char *bat_name, char **txm_src_lst, uint32_t cnt) {
    FILE *pSaveFile = fopen(bat_name, "w");
    DIR *pDir;
    char *file_name;
    if (pSaveFile) {
        /*删除之前的静态库文件*/
        fputs("del txm.a\n", pSaveFile);
        for (int i = 0; i < cnt; ++i) {
            file_name = txm_src_lst[i];
            if (strstr(file_name, ".c")) {
                /*单独添加接口编译源文件*/
                fprintf(pSaveFile, "%s %s %s\n",
                        tx_txm_lib_source_build_cmd,
                        txm_inc,
                        file_name);
            } else { /*认为就是目录*/
                pDir = opendir(file_name);
                if (pDir) {
                    struct dirent *pDirent = readdir(pDir);
                    while (pDirent) {

                        /*判断是否是.c文件*/
                        if (strstr(pDirent->d_name, ".c")) {
                            fprintf(pSaveFile, "%s %s %s\\%s\n",
                                    tx_txm_lib_source_build_cmd,
                                    txm_inc,
                                    file_name,
                                    pDirent->d_name);


                        } else if (strstr(pDirent->d_name, ".") && pDirent->d_namlen == 1) {
                            // 当前目录
                        } else if (strstr(pDirent->d_name, "..") && pDirent->d_namlen == 2) {
                            // 上一级目录
                        } else {
                            // 其他判断
                        }
                        pDirent = readdir(pDir);
                    }
                    closedir(pDir);
                }
            }
        }
        /*构建静态库*/
        fputs("arm-none-eabi-ar -r  txm.a *.o\n", pSaveFile);
        /*删除.o文件*/
        fputs("del *.o\n", pSaveFile);
        fclose(pSaveFile);
    }
}

cJSON *itm;
char txm_lib_include[4096] = {0}; /*头文件保存*/
char txm_bat_name[128] = {0};
char **txm_lib_src_dir;
uint32_t txm_lib_src_cnt = 0;


static int parse_txm_lib_include_array(cJSON *txm_itm) {

    cJSON *inc_arr = cJSON_GetObjectItem(txm_itm, "lib_inc_arr");
    if (!inc_arr) return -1;
    int cnt = cJSON_GetArraySize(inc_arr);
    int len = 0;
    for (int i = 0; i < cnt; ++i) {
        itm = cJSON_GetArrayItem(inc_arr, i);
        len += sprintf(txm_lib_include + len, "\t-I%s", cJSON_GetStringValue(itm));
    }
    return 1;
}

static int parse_txm_lib_bat_name(cJSON *txm_itm) {
    itm = cJSON_GetObjectItem(txm_itm, "bat_file_name");
    if (!itm) return -1;
    char *value = cJSON_GetStringValue(itm);
    if (!value) return -1;
    memcpy(txm_bat_name, value, strlen(value));
    return 1;
}

static int parse_txm_lib_src_list(cJSON *txm_itm) {
    cJSON *inc_arr = cJSON_GetObjectItem(txm_itm, "lib_src_dir_arr");
    if (!inc_arr) return -1;
    txm_lib_src_cnt = cJSON_GetArraySize(inc_arr);
    txm_lib_src_dir = malloc(sizeof(char *) * txm_lib_src_cnt);
    for (int i = 0; i < txm_lib_src_cnt; ++i) {
        itm = cJSON_GetArrayItem(inc_arr, i);
        txm_lib_src_dir[i] = strdup(cJSON_GetStringValue(itm));
    }
    return 1;
}

int build_txm_lib_cjson(cJSON *root) {
    cJSON *txm_itm = cJSON_GetObjectItem(root, "txm_lib_config");
    if (!txm_itm) return -1;
    /*解析头文件*/
    if (parse_txm_lib_include_array(txm_itm) == -1) return -1;
    if (parse_txm_lib_bat_name(txm_itm) == -1)return -1;
    if (parse_txm_lib_src_list(txm_itm) == -1)return -1;
    build_txm_lib(txm_lib_include, txm_bat_name, txm_lib_src_dir, txm_lib_src_cnt);
    /*释放内存*/
    if (txm_lib_src_dir) {
        for (int i = 0; i < txm_lib_src_cnt; ++i) {
            free(txm_lib_src_dir[i]);
        }
        free(txm_lib_src_dir);
    }
    return 1;
}




构建app模块源文件

/*
 * Copyright (c) 2024-2024,shchl
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-4-14     shchl          生成模块应用脚本代码
 */
#include "main.h"
/*指令格式*/
const char *common_del_cmd = "del *.o *.axf *.map"; /*通用删除指令*/
/**
 * @brief  编译指令 c文件编译成 目标文件
 */
const char *module_compilar_to_obj_cmd =
        "arm-none-eabi-gcc -c -g "
        " -mcpu=cortex-m4  "
        " -mfloat-abi=hard  "
        " -mfpu=vfpv4 -fpie -fno-plt  "
        " -mno-pic-data-is-text-relative  "
        " -msingle-pic-base ";
/**
 * @brief 链接文件+目标文件
 */
const char *module_compilar_to_axf_cmd =
        "arm-none-eabi-ld -A cortex-m4"
        " -T %s %s "
        " -e _txm_module_thread_shell_entry txm.a "
        " -o   app_module.axf"
        " -M > app_module.map ";
const char *module_compilar_to_bin_cmd =
        "\narm-none-eabi-objcopy  -O binary  app_module.axf  \"app_module.bin\" "
        "\narm-none-eabi-objcopy  -O ihex  app_module.axf  \"app_module.hex\" ";

/*模块使用的头文件目录*/
//const char *module_inc_dir =
//        "^\n\t\t-IE:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common\\inc "
//        "^\n\t\t-IE:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common_modules\\inc "
//        "^\n\t\t-IE:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\ports_module\\cortex_m4\\gnu\\inc ";

void build_app_module_bat(char *inc_list, char *asm_list, char *src_list, char *obj_list,char *link,char *bat) {
    const char *cmd_asm_fmt = "%s %s\n"; /*汇编指令格式*/
    const char *cmd_source_fmt = "%s %s %s\n"; /*汇编指令格式*/
    FILE *pFile = fopen(bat, "w");
    fprintf(pFile, "del  *.bin  *.hex\n");/*清除*/
    /*汇编文件构建目标文件*/
    fprintf(pFile, cmd_asm_fmt, module_compilar_to_obj_cmd, asm_list);
    /*源文件构建目标文件*/
    fprintf(pFile, cmd_source_fmt, module_compilar_to_obj_cmd, inc_list, src_list);
    /*目标文件构建axf 文件*/
    fprintf(pFile, module_compilar_to_axf_cmd, link, obj_list);
    /*生成bin文件和map文件*/
    fprintf(pFile, "%s", module_compilar_to_bin_cmd);

    /*删除 构建中间文件*/
    fprintf(pFile, "\n%s\n", common_del_cmd);/*清除*/
    fclose(pFile);
}
static char app_module_inc_str[4096];
static char app_module_src_str[2048];
static char app_module_asm_str[1024];
static char app_module_obj_list[4096];
static char app_module_link[64];
static char app_module_bat[64];
static int app_module_obj_cur_len = 0;

char *strReplace(const char *original, const char *substr, const char *replace) {
    char *tok = NULL;
    char *newstr = NULL;
    char *oldstr = NULL;
    char *head = NULL;

    if (original == NULL || substr == NULL || replace == NULL) {
        return NULL;
    }

    newstr = strdup(original);
    head = newstr;
    while ((tok = strstr(head, substr))) {
        oldstr = newstr;
        newstr = (char *) malloc(strlen(oldstr) - strlen(substr) + strlen(replace) + 1);
        /*failed to alloc mem, free old string and return NULL */
        if (newstr == NULL) {
            free(oldstr);
            return NULL;
        }
        memcpy(newstr, oldstr, tok - oldstr);
        memcpy(newstr + (tok - oldstr), replace, strlen(replace));
        memcpy(newstr + (tok - oldstr) + strlen(replace), tok + strlen(substr),
               strlen(oldstr) - strlen(substr) - (tok - oldstr));
        memset(newstr + strlen(oldstr) - strlen(substr) + strlen(replace), 0, 1);
        head = newstr + (tok - oldstr) + strlen(replace);
        free(oldstr);
    }
    return newstr;
}


static int parse_app_module_inc_list(cJSON *module_itm) {
    cJSON *inc_dir_arr_itm = cJSON_GetObjectItem(module_itm, "module_inc_dir");
    if (!inc_dir_arr_itm) return -1;
    int size = cJSON_GetArraySize(inc_dir_arr_itm);
    int idx = 0;
    for (int i = 0; i < size; ++i) {
        cJSON *itm = cJSON_GetArrayItem(inc_dir_arr_itm, i);
        if (!itm) return -1;
        char *value = cJSON_GetStringValue(itm);
        idx += sprintf(app_module_inc_str + idx, " -I%s ", value);

    }

    return 1;
}

static void add_file_name_to_obj_list(char *file_name) {
    /*去掉头*/
    char *post = strrchr(file_name, '\\') + 1;
    /*去掉尾部*/
    char *src = strtok(post, ".");
    app_module_obj_cur_len += sprintf(app_module_obj_list + app_module_obj_cur_len, " %s.o", src);
}

static int parse_app_module_asm_list(cJSON *module_itm) {
    cJSON *inc_dir_arr_itm = cJSON_GetObjectItem(module_itm, "module_asm_file_list");
    if (!inc_dir_arr_itm) return -1;
    int size = cJSON_GetArraySize(inc_dir_arr_itm);
    int idx = 0;
    for (int i = 0; i < size; ++i) {
        cJSON *itm = cJSON_GetArrayItem(inc_dir_arr_itm, i);
        if (!itm) return -1;
        char *value = cJSON_GetStringValue(itm);
        idx += sprintf(app_module_asm_str + idx, " %s ", value);

        add_file_name_to_obj_list(value);
    }

    return 1;
}

static int parse_app_module_src_list(cJSON *module_itm) {
    cJSON *inc_dir_arr_itm = cJSON_GetObjectItem(module_itm, "module_c_source_file_list");
    if (!inc_dir_arr_itm) return -1;
    int size = cJSON_GetArraySize(inc_dir_arr_itm);
    int idx = 0;
    for (int i = 0; i < size; ++i) {
        cJSON *itm = cJSON_GetArrayItem(inc_dir_arr_itm, i);
        if (!itm) return -1;
        char *value = cJSON_GetStringValue(itm);
        idx += sprintf(app_module_src_str + idx, " %s ", value);
        add_file_name_to_obj_list(value);
    }
    return 1;
}

static int parse_app_module_link_file(cJSON *module_itm) {
    cJSON *link_itm = cJSON_GetObjectItem(module_itm, "link_file");
    if (!link_itm) return -1;
    char *value = cJSON_GetStringValue(link_itm);
    sprintf(app_module_link, "%s", value);
    return 1;
}

static int parse_app_module_bat_file(cJSON *module_itm) {
    cJSON *bat_itm = cJSON_GetObjectItem(module_itm, "bat_file_name");
    if (!bat_itm) return -1;
    char *value = cJSON_GetStringValue(bat_itm);
    sprintf(app_module_bat, "%s", value);
    return 1;
}

int build_app_module_bat_json(cJSON *root) {
    cJSON *module_itm = cJSON_GetObjectItem(root, "txm_module_config");
    if (parse_app_module_inc_list(module_itm) == -1)return -1;
    if (parse_app_module_asm_list(module_itm) == -1)return -1;
    if (parse_app_module_src_list(module_itm) == -1)return -1;
    if (parse_app_module_link_file(module_itm) == -1)return -1;
    if (parse_app_module_bat_file(module_itm) == -1)return -1;

    printf("%s\n", app_module_obj_list);
    printf("%s\n", app_module_link);
    printf("%s\n", app_module_bat);
    build_app_module_bat(app_module_inc_str,
                         app_module_asm_str,
                         app_module_src_str,
                         app_module_obj_list,
                         app_module_link,
                         app_module_bat);

    return 1;
}



json配置文件(里面的文件可以配置成相对路径)

{
  "txm_lib_config": {
    "bat_file_name": "txm_lib_build.bat",
    "lib_inc_arr": [
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common\\inc",
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common_modules\\inc",
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\ports_module\\cortex_m4\\gnu\\inc"
    ],
    "lib_src_dir_arr": [
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common_modules\\module_lib\\src",
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\ports_module\\cortex_m4\\gnu\\module_lib\\src\\txm_module_thread_shell_entry.c"
    ]
  },
  "txm_module_config": {
    "bat_file_name": "txm_module_build.bat",
    "link_file": "..\\app_module.ld",
    "module_inc_dir":[
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common\\inc",
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\common_modules\\inc",
      "E:\\code\\clion\\STM32_Project\\Middleware\\threadx_6_4\\ports_module\\cortex_m4\\gnu\\inc"
    ],
    "module_asm_file_list": [
      "..\\app_module_preamble.S ",
      "..\\app_module_setup.s "
    ],
    "module_c_source_file_list": [
      "..\\app_module.c"
    ]

  }
}

在这里插入图片描述

测试(适用于cortex-m4,如果想使用其他内核,修改源码中的编译指令)

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1607605.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MindOpt APL向量化建模语法的介绍与应用(2)

前言 在数据科学、工程优化和其他科学计算领域中&#xff0c;向量和矩阵的运算是核心组成部分。MAPL作为一种数学规划语言&#xff0c;为这些领域的专业人员提供了强大的工具&#xff0c;通过向量式和矩阵式变量声明以及丰富的内置数学运算支持&#xff0c;大大简化了数学建模…

通过实例学C#之ArrayList

介绍 ArrayList对象可以容纳若干个具有相同类型的对象&#xff0c;那有人说&#xff0c;这和数组有什么区别呢。其区别大概可以分为以下几点&#xff1a; 1.数组效率较高&#xff0c;但其容量固定&#xff0c;而且没办法动态改变。 2.ArrayList容量可以动态增长&#xff0c;但…

Go栈内存管理源码解读

基本介绍 栈内存一般是由Go编译器自动分配和释放&#xff0c;其中存储着函数的入参和局部变量&#xff0c;这些参数和变量随着函数调用而创建&#xff0c;当调用结束后也会随之被回收。通常开发者不需要关注内存是分配在堆上还是栈上&#xff0c;这部分由编译器在编译阶段通过…

Day92:系统攻防-WindowsLinux远程探针本地自检任意执行权限提升入口点

目录 操作系统-远程漏扫-Nessus&Nexpose&Goby Nessus Nexpose 知识点&#xff1a; 1、远程漏扫-Nessus&Nexpose&Goby 2、本地漏扫-Wesng&Tiquan&Suggester 3、利用场景-远程利用&本地利用&利用条件 操作系统-远程漏扫-Nessus&Nexpose&a…

C语言(一维数组)

Hi~&#xff01;这里是奋斗的小羊&#xff0c;很荣幸各位能阅读我的文章&#xff0c;诚请评论指点&#xff0c;关注收藏&#xff0c;欢迎欢迎~~ &#x1f4a5;个人主页&#xff1a;小羊在奋斗 &#x1f4a5;所属专栏&#xff1a;C语言 本系列文章为个人学习笔记&#x…

鸿蒙TypeScript学习21天:【声明文件】

TypeScript 作为 JavaScript 的超集&#xff0c;在开发过程中不可避免要引用其他第三方的 JavaScript 的库。虽然通过直接引用可以调用库的类和方法&#xff0c;但是却无法使用TypeScript 诸如类型检查等特性功能。为了解决这个问题&#xff0c;需要将这些库里的函数和方法体去…

将本地项目上传到Github

首先安装git、创建github账号 1、创建一个新的仓库 2、创建SSH KEY。先看一下你C盘用户目录下有没有.ssh目录&#xff0c;有的话看下里面有没有id_rsa和id_rsa.pub这两个文件&#xff0c;有就跳到下一步&#xff0c;没有就通过下面命令创建。 ssh-keygen -t rsa -C "you…

微信小程序echart图片不显示 问题解决

目录 1.问题描述&#xff1a;2.解决方法&#xff1a;2.1第一步2.2第二步2.2效果 小结&#xff1a; 1.问题描述&#xff1a; echart图片不显示 图片&#xff1a; 2.解决方法&#xff1a; 2.1第一步 给wxml中的ec-canvas组件添加宽高样式&#xff1a;style"width: 100%…

Docker容器tomcat中文名文件404错误不一定是URIEncoding,有可能是LANG=zh_CN.UTF-8引起

使用Docker部署tomcat&#xff0c;出现中文名文件无法读取&#xff0c;访问就是404错误。在网上搜索一通&#xff0c;都说是在tomcat的配置文件server.xml中修改一下URIEncoding为utf-8就行&#xff0c;但是我怎么测试都不行。最终发现&#xff0c;是Docker启动时&#xff0c;传…

【经典算法】LeetCode 64. 最小路径和(Java/C/Python3/Golang实现含注释说明,Easy)

作者主页&#xff1a; &#x1f517;进朱者赤的博客 精选专栏&#xff1a;&#x1f517;经典算法 作者简介&#xff1a;阿里非典型程序员一枚 &#xff0c;记录在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法&#xff08;公众号同名&#xff09; ❤️觉得文章还…

java文件夹文件比较工具

import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.util.HashSet; import java.util.Set;public class FolderFileNames {public static void main(String[] args) {// 假设您要读取的文件夹路径是 &q…

代码随想录-算法训练营day12【休息,复习与总结】

代码随想录-035期-算法训练营【博客笔记汇总表】-CSDN博客 ● day 12 周日休息&#xff08;4.14&#xff09; 目录 复习与总结 0417_图论-太平洋大西洋水流问题 0827_图论-最大人工岛 复习与总结 二刷做题速度提升了一大截&#xff0c;ヾ(◍∇◍)&#xff89;&#xff9e;加…

【IDEA】JRebel LS client not configured

主要原因就是因为 jrebel 的版本跟 idea的版本对不上&#xff0c;或者说jrebel的版本比idea的版本还高&#xff0c;导致出现该错误 查看idea版本 ![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/a7ba43e6822947318cdb0d0e9d8d65e9.png 获取jrebel 版本 如何处理 …

设计模式:简单工厂模式(Simple Factory)

设计模式&#xff1a;简单工厂模式&#xff08;Simple Factory&#xff09; 设计模式&#xff1a;简单工厂模式&#xff08;Simple Factory&#xff09;模式动机模式定义模式结构时序图模式实现测试模式分析实例&#xff1a;Qt 控件类优缺点适用环境模式应用 设计模式&#xff…

李沐-26 网络中的网络 NiN【动手学深度学习v2】

主要记载关于全局平均池化层&#xff08;Global Average Pooling, GAP&#xff09;中如下两点的理解&#xff1a; 1. GAP的原理 2. 相对于全连接层&#xff0c;GAP具有更少的参数 为了直观地说明全局平均池化层相对于全连接层具有更少的参数&#xff0c;我们可以构造一个简…

博客文章:AWS re:Invent 2023 新产品深度解析 - 第四部分

TOC &#x1f308;你好呀&#xff01;我是 是Yu欸 &#x1f30c; 2024每日百字篆刻时光&#xff0c;感谢你的陪伴与支持 ~ &#x1f680; 欢迎一起踏上探险之旅&#xff0c;挖掘无限可能&#xff0c;共同成长&#xff01; 写在最前面 去年发布文章的一部分&#xff0c;由于内…

bugku-web-login2

这里提示是命令执行 抓包发现有五个报文 其中login.php中有base64加密语句 $sql"SELECT username,password FROM admin WHERE username".$username.""; if (!empty($row) && $row[password]md5($password)){ } 这里得到SQL语句的组成&#xff0c;…

SOLIDWORKS批量改名工具个人版 慧德敏学

每个文件都会有自己的名字&#xff0c;SOLIDWOKRKS模型也不例外。但是如果从资源管理器直接修改模型的文件名&#xff0c;就会导致模型关联的丢失&#xff0c;导致装配体打开之后找不到模型&#xff0c;因此就需要使用SOLIDWORKS的重命名功能。 SOLIDWORKS批量改名插件- Solid…

Blazor 下的 Json 编辑器

最近恰好碰到个比较冷门的需求&#xff0c;就是在线编码 Json&#xff0c;这其中有Json的语法着色&#xff0c;有Json对象属性数据类型的限制&#xff0c;其实要是单纯改一下Json字符串也不是难事&#xff0c;就是没法控制让用户只能给属性值&#xff0c;而不是属性名称&#x…

【随笔】Git 高级篇 -- 远程服务器拒绝 git push reset(三十二)

&#x1f48c; 所属专栏&#xff1a;【Git】 &#x1f600; 作  者&#xff1a;我是夜阑的狗&#x1f436; &#x1f680; 个人简介&#xff1a;一个正在努力学技术的CV工程师&#xff0c;专注基础和实战分享 &#xff0c;欢迎咨询&#xff01; &#x1f496; 欢迎大…