LVGL图像导入和解码

LVGL版本:8.1

概述

       在LVGL中,可以导入多种不同类型的图像:

  • 经转换器生成的C语言数组,适用于页面中不常改变的固定图像。
  • 存储系统中的外部图像,比较灵活,可以通过插卡或从网络中获取,但需要配置相应的驱动和解码器,解码过程也需要占用时间和内存。
  • SYMBOL类型的文本,和label组件类似。

       其中第三种比较容易实现,第一种仅需经过官方工具生成二进制数据,再在代码中引用即可。本文重点说明第二种情况即外部图像的使用,并以PNG类型图片为例介绍LVGL图像的解码和显示。

原理框图

       图像的解析和使用可以分以下几层:

        图中的虚线框可有可无,有的仅代表多一层函数,为了更好的理解,在图上加入这些虚线框。

       对于一个通过lv_img_create()函数创建的lv_img_t图像,可以通过设置图像源进行图像导入,LVGL会先查看缓存,如果有命中则直接使用,否则进入解码环节。对于内置图像如SYMBOL和VARIABLE(即C数组)类型,可以直接使用内置的解码器,在编译时会直接合并入程序。对于外部图像格式如jpg和png,则需要添加额外的解码器,并且,由于这些外部图像通常是以文件的形式存放在文件系统中,还需要提供基本的文件操作来读取文件,如Linux可以简单使用POSIX标准的系统调用函数(open、read、write等),事实上,如果没有提供文件系统,用户也可以自定义图像的打开和读取方式,只要符合接口标准即可。

    开启配置

           以PNG类型的图像为例,需要在lv_conf.h配置文件中,打开LV_USE_PNG开关,同时要开启文件系统(根据实际所在系统进行选择),比如在这里我选择的是LV_USE_FS_POSIX,即使用POSIX标准的文件操作函数(read、write等),也是Linux的原生操作。注意这里要求填入一个前缀来区分不同系统,通常使用类似“盘符”的字母(如这里使用了'S'),因为LVGL底层打开源路径时也是比对的路径首字母,配对成功后才会使用对应的驱动来打开。

    //lv_conf.h
    /* 文件系统,如开启,请设置相应的驱动字母(或前缀) */
    #define LV_USE_FS_STDIO '\0'        /*Uses fopen, fread, etc*/
    //#define LV_FS_STDIO_PATH "/home/john/"    /*Set the working directory. If commented it will be "./" */#define LV_USE_FS_POSIX 'S'        /*Uses open, read, etc*/
    //#define LV_FS_POSIX_PATH "/home/john/"    /*Set the working directory. If commented it will be "./" */#define LV_USE_FS_WIN32 '\0'        /*Uses CreateFile, ReadFile, etc*/
    //#define LV_FS_WIN32_PATH "C:\\Users\\john\\"    /*Set the working directory. If commented it will be ".\\" */#define LV_USE_FS_FATFS '\0'        /*Uses f_open, f_read, etc*//*PNG decoder library*/
    #define LV_USE_PNG 1

           此外,还可以增加系统的图像缓存数量LV_IMG_CACHE_DEF_SIZE,来减少不必要的解码,提高渲染效率。

    //lv_conf.h
    #define LV_IMG_CACHE_DEF_SIZE 1   //图像缓存数量,只有使用外部解码器的图像,该配置才有意义

     

    图像解码

           通过lv_img_set_src()函数设置图像源来开启LVGL解码和显示的过程,其中图像源src可以是以下类型:

    • 图像路径,通常以一个盘符为首(如'S'或'S:'),后面跟绝对路径,比如是'S/img/abc.png',注意图像要预先导入到设备对应路径上才能读取
    • LVGL规定的SYMBOL类型,也可以通过官方工具生成自定义SYMBOL;
    • C语言数组,包含图像的位置和色彩信息,可以通过官方工具将其它类型的图像转换为LVGL可识别的数据文件,并在使用处进行声明和导入。

           在这里,该函数仅用于设置图像源,并获取图像基本信息,还没有到解码部分。

    //lv_img.c
    void lv_img_set_src(lv_obj_t * obj, const void * src)
    {LV_ASSERT_OBJ(obj, MY_CLASS);lv_obj_invalidate(obj);lv_img_src_t src_type = lv_img_src_get_type(src);  //获取来源类型lv_img_t * img = (lv_img_t *)obj;/*If the new source type is unknown free the memories of the old source*/if(src_type == LV_IMG_SRC_UNKNOWN) {LV_LOG_WARN("lv_img_set_src: unknown image type");if(img->src_type == LV_IMG_SRC_SYMBOL || img->src_type == LV_IMG_SRC_FILE) {lv_mem_free((void *)img->src);}img->src      = NULL;img->src_type = LV_IMG_SRC_UNKNOWN;return;}lv_img_header_t header;lv_img_decoder_get_info(src, &header);  //通过图片来源获取对应的解码器/*Save the source*/if(src_type == LV_IMG_SRC_VARIABLE) {/*If memory was allocated because of the previous `src_type` then free it*/if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {lv_mem_free((void *)img->src);}img->src = src;}else if(src_type == LV_IMG_SRC_FILE || src_type == LV_IMG_SRC_SYMBOL) {/*If the new and the old src are the same then it was only a refresh.*/if(img->src != src) {const void * old_src = NULL;/*If memory was allocated because of the previous `src_type` then save its pointer and free after allocation.*It's important to allocate first to be sure the new data will be on a new address.*Else `img_cache` wouldn't see the change in source.*/if(img->src_type == LV_IMG_SRC_FILE || img->src_type == LV_IMG_SRC_SYMBOL) {old_src = img->src;}char * new_str = lv_mem_alloc(strlen(src) + 1);LV_ASSERT_MALLOC(new_str);if(new_str == NULL) return;strcpy(new_str, src);img->src = new_str;if(old_src) lv_mem_free((void *)old_src);}}if(src_type == LV_IMG_SRC_SYMBOL) {/*`lv_img_dsc_get_info` couldn't set the with and height of a font so set it here*/const lv_font_t * font = lv_obj_get_style_text_font(obj, LV_PART_MAIN);lv_coord_t letter_space = lv_obj_get_style_text_letter_space(obj, LV_PART_MAIN);lv_coord_t line_space = lv_obj_get_style_text_line_space(obj, LV_PART_MAIN);lv_point_t size;lv_txt_get_size(&size, src, font, letter_space, line_space, LV_COORD_MAX, LV_TEXT_FLAG_NONE);header.w = size.x;header.h = size.y;}img->src_type = src_type;   //图像类型(变量/符号/文件路径)img->w        = header.w;   //图像宽度img->h        = header.h;   //图像高度img->cf       = header.cf;   //图像色彩信息img->pivot.x = header.w / 2;img->pivot.y = header.h / 2;lv_obj_refresh_self_size(obj);/*Provide enough room for the rotated corners*/if(img->angle || img->zoom != LV_IMG_ZOOM_NONE) lv_obj_refresh_ext_draw_size(obj);lv_obj_invalidate(obj);   //标记无效区域,下次更新才会正式解码图片
    }

           其中lv_img_decoder_get_info()用于获取图像源的基本信息,该函数会首先遍历解码器链表,匹配合适的解码器,然后将图像的宽、高、色彩信息记录下来。

    //lv_img_decoder.c
    lv_res_t lv_img_decoder_get_info(const void * src, lv_img_header_t * header)
    {lv_memset_00(header, sizeof(lv_img_header_t));if(src == NULL) return LV_RES_INV;lv_img_src_t src_type = lv_img_src_get_type(src);if(src_type == LV_IMG_SRC_VARIABLE) {const lv_img_dsc_t * img_dsc = src;if(img_dsc->data == NULL) return LV_RES_INV;}lv_res_t res = LV_RES_INV;lv_img_decoder_t * d;_LV_LL_READ(&LV_GC_ROOT(_lv_img_decoder_ll), d) {  //遍历解码器链表,为图像源寻找合适的解码器if(d->info_cb) {res = d->info_cb(d, src, header);  //调用info回调,获取图像信息,header用于保存这些信息if(res == LV_RES_OK) break;   //返回OK,说明解码器配对成功,退出并返回}}return res;
    }

           如果定义了LV_USE_PNG,则默认注册LVGL自带的PNG解码器,其中包含获取基本信息的回调函数decoder_info()

    //lv_png.c
    static lv_res_t decoder_info(struct _lv_img_decoder_t * decoder, const void * src, lv_img_header_t * header)
    {(void) decoder; /*Unused*/lv_img_src_t src_type = lv_img_src_get_type(src);     /* 获取来源类型 *//* 检查是否是文件路径类型 */if(src_type == LV_IMG_SRC_FILE) {const char * fn = src;if(!strcmp(&fn[strlen(fn) - 3], "png")) {     /* 检查后缀是否为png *//* Read the width and height from the file. They have a constant location:* [16..23]: width* [24..27]: height*/uint32_t size[2];lv_fs_file_t f;lv_fs_res_t res = lv_fs_open(&f, fn, LV_FS_MODE_RD);  //使用对应的驱动打开文件if(res != LV_FS_RES_OK) return LV_RES_INV;lv_fs_seek(&f, 16, LV_FS_SEEK_SET);uint32_t rn;lv_fs_read(&f, &size, 8, &rn);if(rn != 8) return LV_RES_INV;lv_fs_close(&f);/*Save the data in the header*/header->always_zero = 0;header->cf = LV_IMG_CF_RAW_ALPHA;/*The width and height are stored in Big endian format so convert them to little endian*/header->w = (lv_coord_t) ((size[0] & 0xff000000) >> 24) +  ((size[0] & 0x00ff0000) >> 8);header->h = (lv_coord_t) ((size[1] & 0xff000000) >> 24) +  ((size[1] & 0x00ff0000) >> 8);return LV_RES_OK;}}/*If it's a PNG file in a  C array...*/else if(src_type == LV_IMG_SRC_VARIABLE) {const lv_img_dsc_t * img_dsc = src;header->always_zero = 0;header->cf = img_dsc->header.cf;       /*Save the color format*/header->w = img_dsc->header.w;         /*Save the color width*/header->h = img_dsc->header.h;         /*Save the color height*/return LV_RES_OK;}return LV_RES_INV;         /*If didn't succeeded earlier then it's an error*/
    }

           其中lv_fs_open()函数是寻找合适的驱动来打开这个文件,我们在前面定义了LV_USE_FS_POSIX,LVGL在初始化阶段就会自动注册POSIX标准驱动,使用open、read、write等函数打开对应文件。

    //lv_fs_posix.c
    void lv_fs_posix_init(void)
    {static lv_fs_drv_t fs_drv; /* 驱动描述子 */lv_fs_drv_init(&fs_drv);fs_drv.letter = LV_USE_FS_POSIX;  //文件系统前缀,可以简单设置为'S'fs_drv.open_cb = fs_open;fs_drv.close_cb = fs_close;fs_drv.read_cb = fs_read;fs_drv.write_cb = fs_write;fs_drv.seek_cb = fs_seek;fs_drv.tell_cb = fs_tell;fs_drv.dir_close_cb = fs_dir_close;fs_drv.dir_open_cb = fs_dir_open;fs_drv.dir_read_cb = fs_dir_read;lv_fs_drv_register(&fs_drv);
    }

           到这里为止仅仅只是把图像源记录下来,直到下次更新周期的绘制阶段,才会正式解析图像。下面直接跳转到图像的绘制函数lv_draw_img_core(),这里仅截取解码图像的代码片段。

    //lv_draw_img.c
    LV_ATTRIBUTE_FAST_MEM static lv_res_t lv_img_draw_core(const lv_area_t * coords, const lv_area_t * clip_area,const void * src,const lv_draw_img_dsc_t * draw_dsc)
    {if(draw_dsc->opa <= LV_OPA_MIN) return LV_RES_OK;_lv_img_cache_entry_t * cdsc = _lv_img_cache_open(src, draw_dsc->recolor, draw_dsc->frame_id);if(cdsc == NULL) return LV_RES_INV;/*......*/
    }

           可以看到,LVGL在绘制图像前先询问缓存,即_lv_img_cache_open()函数,该函数会先遍历缓存链表,查找是否存在匹配的缓存,如匹配直接返回该缓存,否则从链表中找一个存活时间最短的缓存,用新的图像替换掉该旧缓存。

    //lv_img_cache.c
    _lv_img_cache_entry_t * _lv_img_cache_open(const void * src, lv_color_t color, int32_t frame_id)
    {_lv_img_cache_entry_t * cached_src = NULL;#if LV_IMG_CACHE_DEF_SIZEif(entry_cnt == 0) {LV_LOG_WARN("lv_img_cache_open: the cache size is 0");return NULL;}_lv_img_cache_entry_t * cache = LV_GC_ROOT(_lv_img_cache_array);/*Decrement all lifes. Make the entries older*/uint16_t i;for(i = 0; i < entry_cnt; i++) {if(cache[i].life > INT32_MIN + LV_IMG_CACHE_AGING) {cache[i].life -= LV_IMG_CACHE_AGING;}}for(i = 0; i < entry_cnt; i++) {if(color.full == cache[i].dec_dsc.color.full &&frame_id == cache[i].dec_dsc.frame_id &&lv_img_cache_match(src, cache[i].dec_dsc.src)) {  //是否命中缓存/* 打开一个图像也会增加图像的存活时间,因此在这里对缓存增加一个time_to_open的时间 */cached_src = &cache[i];cached_src->life += cached_src->dec_dsc.time_to_open * LV_IMG_CACHE_LIFE_GAIN;if(cached_src->life > LV_IMG_CACHE_LIFE_LIMIT) cached_src->life = LV_IMG_CACHE_LIFE_LIMIT;LV_LOG_TRACE("image source found in the cache");break;}}/* 命中缓存后直接返回该缓存 */if(cached_src) return cached_src;/* 如果没有命中,则在缓存中寻找一个合适的位置存放新图像 */cached_src = &cache[0];for(i = 1; i < entry_cnt; i++) {if(cache[i].life < cached_src->life) {   //寻找生存时间最短的图像cached_src = &cache[i];}}/* 找到被替代的缓存后,要先关闭它的解码器 */if(cached_src->dec_dsc.src) {lv_img_decoder_close(&cached_src->dec_dsc);LV_LOG_INFO("image draw: cache miss, close and reuse an entry");}else {LV_LOG_INFO("image draw: cache miss, cached to an empty entry");}
    #elsecached_src = &LV_GC_ROOT(_lv_img_cache_single);
    #endif/* 打开一个新的解码器 */uint32_t t_start  = lv_tick_get();lv_res_t open_res = lv_img_decoder_open(&cached_src->dec_dsc, src, color, frame_id);if(open_res == LV_RES_INV) {LV_LOG_WARN("Image draw cannot open the image resource");lv_memset_00(cached_src, sizeof(_lv_img_cache_entry_t));cached_src->life = INT32_MIN; /*Make the empty entry very "weak" to force its us*/return NULL;}cached_src->life = 0;   //记录缓存后,设置存活时间从0开始/* 记录解码它的时间 */if(cached_src->dec_dsc.time_to_open == 0) {cached_src->dec_dsc.time_to_open = lv_tick_elaps(t_start);}if(cached_src->dec_dsc.time_to_open == 0) cached_src->dec_dsc.time_to_open = 1;return cached_src;
    }

           当没有命中缓存时,会调用lv_img_decoder_open()函数尝试使用解码器打开该图像,打开成功后,会将图像数据提取出来。

    //lv_img_decoder.c
    lv_res_t lv_img_decoder_open(lv_img_decoder_dsc_t * dsc, const void * src, lv_color_t color, int32_t frame_id)
    {lv_memset_00(dsc, sizeof(lv_img_decoder_dsc_t));if(src == NULL) return LV_RES_INV;lv_img_src_t src_type = lv_img_src_get_type(src);if(src_type == LV_IMG_SRC_VARIABLE) {const lv_img_dsc_t * img_dsc = src;if(img_dsc->data == NULL) return LV_RES_INV;}dsc->color    = color;dsc->src_type = src_type;dsc->frame_id = frame_id;if(dsc->src_type == LV_IMG_SRC_FILE) {size_t fnlen = strlen(src);dsc->src = lv_mem_alloc(fnlen + 1);LV_ASSERT_MALLOC(dsc->src);if(dsc->src == NULL) {LV_LOG_WARN("lv_img_decoder_open: out of memory");return LV_RES_INV;}strcpy((char *)dsc->src, src);}else {dsc->src = src;}lv_res_t res = LV_RES_INV;lv_img_decoder_t * decoder;_LV_LL_READ(&LV_GC_ROOT(_lv_img_decoder_ll), decoder) {   //遍历解码器链表/* info和open函数是必要的 */if(decoder->info_cb == NULL || decoder->open_cb == NULL) continue;res = decoder->info_cb(decoder, src, &dsc->header);if(res != LV_RES_OK) continue;  //返回OK,说明命中解码器dsc->decoder = decoder;res = decoder->open_cb(decoder, dsc);  //使用解码器打开图像文件/* 如果返回OK,表示成功打开图像文件,则返回 */if(res == LV_RES_OK) return res;/* 准备遍历下一个解码器节点 */lv_memset_00(&dsc->header, sizeof(lv_img_header_t));dsc->error_msg = NULL;dsc->img_data  = NULL;dsc->user_data = NULL;dsc->time_to_open = 0;}if(dsc->src_type == LV_IMG_SRC_FILE)lv_mem_free((void *)dsc->src);return res;
    }

           接下来就是使用对应解码器的open函数来获取图像,这里就不进一步说明了。可以参考官方对于不同格式图像的解码器例程代码。

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

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

    相关文章

    【Web前端开发】HTML基础

    Web前端开发是用来直接给用户呈现一个一个的网页&#xff0c;主要包含实现用户的结构&#xff08;HTML&#xff09;、样式&#xff08;CSS&#xff09;、交互&#xff08;JavaScript&#xff09;。然而一个软件通常是由后端和前端完成的。可以查阅文档&#xff1a;HTML 教程 (w…

    MySQL 8.0 单节点部署与一主两从架构搭建实战

    前言&#xff1a;在数据驱动的时代&#xff0c;数据库作为数据存储与管理的核心组件&#xff0c;其架构的选择与配置对系统的性能、可用性和扩展性至关重要。MySQL 作为一款广泛应用的开源关系型数据库&#xff0c;凭借其稳定的性能和丰富的功能&#xff0c;深受开发者和企业的…

    数据库故障排查全攻略:从实战案例到体系化解决方案

    一、引言&#xff1a;数据库故障为何是技术人必须攻克的 "心腹大患" 在数字化时代&#xff0c;数据库作为企业核心数据资产的载体&#xff0c;其稳定性直接决定业务连续性。据 Gartner 统计&#xff0c;企业每小时数据库 downtime 平均损失高达 56 万美元&#xff0…

    牛客周赛round91

    C 若序列为1 4 5 7 9 1 2 3&#xff0c;1 9一定大于1 1或1 4...所以只需要记录当前数之前数字的最大值&#xff0c;然后遍历取max即可&#xff0c;所以对于上面的序列有效的比较为1 9&#xff0c;2 9&#xff0c;3 9取max 代码 //求大于当前数的最大值&#xff0c;然后…

    【MCAL】TC397+EB-tresos之I2c配置实战(同步、异步)

    I2C总线是Philips公司在八十年代初推出的一种串行、半双工的总线&#xff0c;主要用于近距离、低速的芯片之间的通信。本篇文章首先从理论讲起&#xff0c;介绍了英飞凌TC3x系列芯片对应MCAL中对I2C驱动的定义与介绍&#xff0c;建议读者在阅读本篇文章之前对I2C有个简单的认识…

    深拷贝与浅拷贝:理解 Python 中的对象复制机制

    深拷贝与浅拷贝&#xff1a;理解 Python 中的对象复制机制 在 Python 编程中&#xff0c;对象的复制是一个常见的操作。然而&#xff0c;很多初学者在处理对象复制时会遇到困惑&#xff0c;尤其是在涉及到复杂数据结构&#xff08;如列表、字典、自定义对象等&#xff09;时。…

    BeanPostProcessor和AOP

    BeanPostProcessor Spring中有一个接口Oredr的getOrder()方法&#xff0c;这个方法返回值是一个int类型&#xff0c;Spring容器会根据这个方法的返回值 对容器的多个Processor对象从小到大排序&#xff0c;创建Bean时候依次执行他们的方法&#xff0c;也就是说getOrder()方法的…

    拒绝服务攻击(DoS/DDoS/DRDoS)详解:洪水猛兽的防御之道

    在数字时代&#xff0c;服务的可用性是衡量一个在线系统成功与否的关键指标之一。然而&#xff0c;存在一类被称为"拒绝服务攻击" (Denial of Service, DoS) 的网络攻击&#xff0c;其主要目的就是通过各种手段耗尽目标服务器或网络的资源&#xff0c;使其无法响应正…

    小刚说C语言刷题—1078求恰好使s=1+1/2+1/3+…+1/n的值大于X时n的值

    1.题目描述 求恰好使 s11/21/3⋯1/n 的值大于 X 时 n 的值。( 2≤x≤10 ) 输入 输入只有一行&#xff0c;包括 1个整数 X 。 输出 输出只有一行&#xff08;这意味着末尾有一个回车符号&#xff09;&#xff0c;包括 1 个整数。 样例 输入 2 输出 4 2.参考代码(C语言…

    深度学习中的目标检测:从 PR 曲线到 AP

    深度学习中的目标检测&#xff1a;从 PR 曲线到 AP 在目标检测任务中&#xff0c;评估模型的性能是非常重要的。通过使用不同的评估指标和标准&#xff0c;我们可以量化模型的准确性与效果。今天我们将重点讨论 PR 曲线&#xff08;Precision-Recall Curve&#xff09;、平均精…

    MySQL 1366 - Incorrect string value:错误

    MySQL 1366 - Incorrect string value:错误 错误如何发生发生原因&#xff1a; 解决方法第一种尝试第二种尝试 错误 如何发生 在给MySQL添加数据的时候发生了下面的错误 insert into sys_dept values(100, 0, 0, 若依科技, 0, 若依, 15888888888, ryqq.com, 0,…

    [ctfshow web入门] web70

    信息收集 使用cinclude("php://filter/convert.base64-encode/resourceindex.php");读取的index.php error_reporting和ini_set被禁用了&#xff0c;不必管他 error_reporting(0); ini_set(display_errors, 0); // 你们在炫技吗&#xff1f; if(isset($_POST[c])){…

    Linux在web下http加密和配置虚拟主机及动态页面发布

    web服务器的数据加密 1.简介&#xff1a;由于http协议以明文方式发送&#xff0c;不提供任何方式的数据加密&#xff0c;也不适合传输一些重要的信息&#xff0c;如银行卡号、密码等&#xff0c;解决该缺陷设计了安全套接字层超文本传输协议https&#xff1b; 2.https的握手流…

    uni-app,小程序中的addPhoneContact,保存联系人到手机通讯录

    文章目录 方法详解简介 基本语法参数说明基础用法使用示例平台差异说明注意事项最佳实践 方法详解 简介 addPhoneContact是uni-app框架提供的一个实用API&#xff0c;用于向系统通讯录添加联系人信息。这个方法在需要将应用内的联系人信息快速保存到用户设备通讯录的场景下非…

    NHANES稀有指标推荐:HALP score

    文章题目&#xff1a;Associations of HALP score with serum prostate-specific antigen and mortality in middle-aged and elderly individuals without prostate cancer DOI&#xff1a;10.3389/fonc.2024.1419310 中文标题&#xff1a;HALP 评分与无前列腺癌的中老年人血清…

    【django.db.utils.OperationalError: unable to open database file】

    解决platform.sh 环境下&#xff0c;无法打开数据库问题 场景 在platform.sh 执行python manage.py createsuperuser是提示 django.db.utils.OperationalError: unable to open database file 错误 原因 由于settings.py文件中 本地数据库配置在线上配置后&#xff0c;导致…

    【前端分享】CSS实现3种翻页效果类型,附源码!

    使用 css 可以实现多种翻页效果&#xff0c;比如书本翻页、卡片翻转等。以下是两种常见的翻页效果实现&#xff1a; 效果 1&#xff1a;书本翻页效果 通过 transform 和 rotateY 实现 3D 翻页效果。 html 结构 <divclass"book"> <divclass"page pa…

    【部署满血Deepseek-R1/V3】大型语言模型部署实战:多机多卡DeepSeek-R1配置指南

    大家好&#xff01;这里是迪小莫学AI&#xff0c;今天的文章是“”大型语言模型部署实战&#xff1a;多机多卡DeepSeek-R1配置指南“” 前言 随着大型语言模型的快速发展&#xff0c;如何高效部署这些模型成为技术团队面临的重要挑战。本文将分享基于DeepSeek-R1模型的多机多…

    IPM IMI111T-026H 高效风扇控制板

    概述&#xff1a; REF-MHA50WIMI111T 是一款专为风扇驱动设计的参考开发板&#xff0c;搭载了英飞凌的IMI111T-026H iMOTION™智能功率模块(IPM)。这个模块集成了运动控制引擎(MCE)、三相栅极驱动器和基于IGBT的功率级&#xff0c;全部封装在一个紧凑的DSO22封装中。REF-MHA50…

    Linux 阻塞和非阻塞 I/O 简明指南

    目录 声明 1. 阻塞和非阻塞简介 2. 等待队列 2.1 等待队列头 2.2 等待队列项 2.3 将队列项添加/移除等待队列头 2.4 等待唤醒 2.5 等待事件 3. 轮询 3.1 select函数 3.2 poll函数 3.3 epoll函数 4. Linux 驱动下的 poll 操作函数 声明 本博客所记录的关于正点原子…