超级精简系列之:高效且运行时间固定的内存池实现

原创 嵌入式Lee 2024-06-20 08:31

一. 前言

这里简单提一下在RTOS环境开发中,比较容易出现的一个BUG为例,引出我们为什么要设计我们这个内存池的轮子。以FreeRTOS为例,堆管理mallocfree的实现是基于链表进行的,链表是一个全局的资源,且操作不是一个原子操作,涉及很多步骤,所以多处调用时就必须进行临界段保护,否则操作到一半被打断进行另外的操作就会导致资源被破坏。FreeRTOS的处理方式是关调度的方式来实现临界段保护,关调度只能保证任务不会抢占,无法保证中断抢占,所以中断中不能调用FreeRTOSmallocfree接口。而有时驱动设计又必须使用动态内存管理,驱动很多都是中断驱动的有时必须在中断中调用堆管理接口,此时直接调用FreeRTOS的堆管理接口就会导致问题,上述问题很可能难以发现,并且不一定能测试出来,可能在某种凑巧时机,刚好中断中打断任务中的堆接口执行,并调用堆管理接口此时就会出现问题。这种问题很可能就会出现很大的随机性导致了的调试的困难。知道了这个原因就可以避免类似的错误了,但是回到问题来,我们中断中还是有动态内存的使用需求那怎么办呢,最简单的方式就是还是使用FreeRTOS提供的堆接口,把关调度改为关中断,这样就可以保证临界段不被抢占,就不会有问题,但是这里会有一些副作用就是关中断时间的增加,并且这个时间还是不确定的,我们下一节就进行了实测。当然堆管理还有碎片化等问题,所以不适合在驱动层和中断时调用。所以我们还有一种方式就是使用内存池,这是很多中间件,驱动比较常见的方式,我们这一篇就是要来实现一个简单高效的内存池,但是我们不满足于此,我们还有更高的要求,毕竟是在驱动层甚至中断中使用,我们希望它执行非常快,并且执行时间基本无波动,因为这两个指标都是很重要的,很多系统甚至是必须要满足的。

二. 堆管理接口执行时间测试

执行时间测试有很多种方式,一般使用硬件定时器。我们这里使用另一种实践中也常用的方式,使用IO翻转,示波器查看波形的方式测试,进入接口执行时翻转IO,退出接口执行,再次翻转IO,IO波形得脉宽即执行时间,这样可以可视化观察,疏密变化可以看出执行时间的抖动,脉宽可以看出执行时间,malocfree使用不同IO对比,还可以看出申请和释放的时间分布等等,所以这是一种非常好的方式。

我这里实测某个平台,freertos得malloc和free执行时间如下(注意执行时间硬件平台不一样也不一样),可以看到时间抖动较大,时间也不短,4.6uS已经算很长了,比如中断中做时序解析这个时间很可能就难以满足需求。

后面我们对比修改为我们高效的内存池实现的对比测试,可以看到基本无抖动,且执行时间非常短(这里还未去掉翻转IO函数本身的执行和IO响应时间)。从汇编代码其实也可以看出指令很少了。

三. 内存池实现

常规设计

将一块空间划分为n个等大小的块,前面用一个字段标记是否使用的标记,剩余部分作为有效存储空间。初始化全部是空闲,标记都是0,需要申请时从头开始搜索,搜索到标记为0的块则可申请将标记改为1,释放则直接匹配地址将对应的标记设置为0.

上述标记至少需要占用一个字节,并且插入到有效块前会影响有效块的对齐属性,所以还可以优化,标记信息单独使用bitmap记录,如下所示,

有效块可以按照需要的原始数据结构连续对齐,bitmap一个bit对应一个块,为1表示该块已分配为0表示空闲,bitmap也减少了空间占用。申请释放算法和原来类似,申请只是变为了搜索bitmap的最低字节最低位开始的0的位置将其设置为1,获取对应的块地址,释放则是根据地址计算bitmapbit索引将对应的bit设置为0.

高效无时间抖动设计

上述bitmap实现在存储上已经优化了,但是在执行时间上因为要遍历搜寻所以是有抖动的,那么要满足我们的无时间抖动设计,目标就是要实现查找最低字节最低为开始的0的位置的算法时间固定。我们前面很多文章其实已经提到了矛盾的思想即,从矛盾的角度看问题,要解决一个问题可以看其对立面,因为往往矛盾是此消彼长的,算法的时间和空间就是矛盾体,要求时间那么我们就去看看是否能从空间的角度去考虑,一种常见的空间换时间的方法就是查表法,查表一方面时间固定一方面也高效。实际在uSOSII的实现中,查找最高就绪优先级就使用了该算法,我们直接拿过来使用并简单介绍下其原理。

首先我们将bitmap按照矩阵排列,自然而然地想到以字节为列,bit0~7对应0~7列,多个字节对应多行。每个bit对应一个块,bit1表示空闲,为0表示占用。

那么最低字节的最低位位1则是我们要找的空闲块。那么如何快速找到这个bit呢,常规思想是从字节0开始找,如果全为0则继续找字节1,找到不为0的字节,然后从bit0bit7找最开始出现的1的位置,那么这个bit在整个bit的索引就是(字节序号*8+位序号).

所以时间上可以分解为两步,第一步是找字节位置,第二步找bit位置。

bit位置实际我们可以很快想到通过查表法实现。

8为数据有256种可能,每一种可能我们直接将其最低出现1的位置写出来就行,实际就是构造一个256字节的数组表,其指定索引的值,就是这个索引对应的数字,其最先出现1bit位置。

比如

00000000 没有1则最低最先出现1的位置是0,所以数组索引1的位置值为0

00000001 最先出现1的位置是bit0,所以数组索引1的位置值为0

00000010 最先出现1的位置是bit1,所以数组索引2的位置值为1

00000011 最先出现1的位置是bit0,所以数组索引3的位置值为0

00000100 最先出现1的位置是bit2,所以数组索引4的位置值为2

所以最终构造出如下表格


static uint8_t  const  s_unmaptbl[256] = {    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F                             */    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF                             */};

现在查找字节最开始出现1的位置查表一步就得到了时间是固定的,那么查找字节按照前面介绍还是的从字节0开始查询最开始不是0的字节。我们仔细思考一下,前面一个字节8bit一行的角度去查找最开始的1是对应的列,现在查找字节不就是对应的行吗。

那么我们把每一行是否是0先记录下来,再用一个bitmap来记录这一行是否为0不就行了吗。如下所示我们原来的bitmap8个字节,8行,那么我们再使用一个字节8bit就可以记录这8个字节是否为0了,这个新增的bitmap我们叫做grp

如果原来bitmap的字节10grpbit00,否则为1表示这个字节中有1即有空闲块。那么问题简单了,查找这8个字节,最低字节不为0的字节,即查找这个grp的最低位最先出现的1,这样问题归一了,就是我们前面的查表。这样要找最低字节的最低先出现的1就是两步查表,先根据grp查找到字节位置,然后根据这个字节的值查找bit位置。最终的索引就是字节位置*8+bit位置

最终算法实现如下


y = s_unmaptbl[dev->grp];    x = s_unmaptbl[dev->tbl[y]];    index = (uint8_t)((y<<3) + x);

这里的dev->grp即我们上面的grp字节,

Dev->tblbitmap字节数组。

y即字节位置,xbit位置,indexbitmap的索引位置。

而找bitmap索引将对应的bit改为0即申请,操作如下,index>>3得到字节索引,~(1u<<(index & 0x07)是清除对应的bit。如果这个字节变为了0,则相应的grp字节的对应bit也要清零


      if(((dev->tbl[index>>3] &= (~(1u<<(index & 0x07)))) == 0))        {            dev->grp &= ~(1u<<(index>>3));        }

释放则是将bitmap对应索引位置的bit置为1

    dev->grp |= 1u<<(index>>3);    dev->tbl[index>>3] |= 1u<<(index & 0x07);

举例如下

Tbl2~7字节都非0所以,grpbit2~bit7都为1

tbl中最低字节最低位出现的1是字节2bit3.

计算过程如下,先grp=0xFC=252查表得到,索引252处的值为2,所以定位到字节2

Tbl[2]的值为0x08,查表得到该索引对应的值是3

所以最开始出现1的位置是字节2bit3,即2*8+3=19.

源码如下

Mem_pool.c

#include "mem_pool.h"
static uint8_t  const  s_unmaptbl[256] = {    0, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x00 to 0x0F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x10 to 0x1F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x20 to 0x2F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x30 to 0x3F                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x40 to 0x4F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x50 to 0x5F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x60 to 0x6F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x70 to 0x7F                             */    7, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x80 to 0x8F                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0x90 to 0x9F                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xA0 to 0xAF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xB0 to 0xBF                             */    6, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xC0 to 0xCF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xD0 to 0xDF                             */    5, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0,       /* 0xE0 to 0xEF                             */    4, 0, 1, 0, 2, 0, 1, 0, 3, 0, 1, 0, 2, 0, 1, 0        /* 0xF0 to 0xFF                             */};
int mem_pool_init(mem_pool_st* dev, void* buffer, void* tbl, uint32_t itemsize, uint32_t itemnum){    #if MEM_POOL_PARAM_CHECK        if(dev == (mem_pool_st*)0)        {            return -1;        }        if((buffer == (void*)0) || (tbl == (void*)0))        {            return -1;        }    #endif    dev->buffer = buffer;    dev->itemnum = itemnum;    dev->itemsize = itemsize;    dev->grp = (uint8_t)0xFF;    dev->tbl = tbl;    for(uint32_t i=0; i< (dev->itemnum+7)/8; i++)    {        dev->tbl[i] = (uint8_t)0xFF;    }    return 0;}
uint8_t* mem_pool_malloc(mem_pool_st* dev){    /* 查找最靠前的,空闲的块 */#if MEM_POOL_PARAM_CHECK    if(dev == (mem_pool_st*)0)    {        return (void*)0;    }    if((dev->buffer == (void*)0) || (dev->tbl == (uint8_t*)0))    {        return (void*)0;    }#endif
    uint8_t y;    uint8_t x;    uint8_t index;    y = s_unmaptbl[dev->grp];    x = s_unmaptbl[dev->tbl[y]];    index = (uint8_t)((y<<3) + x);    if((index == 0) && (dev->tbl[0]==0))    {        /* 找到的索引为0,且对应的tbl为0, 说明bitmap所有位都为0,即无空闲 */        return (void*)0;    }    else    {        /* 对应bit置0,表示已经占用 */        if(((dev->tbl[index>>3] &= (~(1u<<(index & 0x07)))) == 0))        {            dev->grp &= ~(1u<<(index>>3));        }        /* 返回对应的地址 */        return dev->buffer + dev->itemsize*index;    }}
int mem_pool_free(mem_pool_st* dev, uint8_t* buffer){#if MEM_POOL_PARAM_CHECK    if(dev == (mem_pool_st*)0)    {        return -1;    }    if((dev->buffer == (void*)0) || (dev->tbl == (uint8_t*)0))    {        return -1;    }#endif
#if MEM_POOL_ERR_CHECK    if(buffer == (void*)0)    {        return -2;    }#endif    uint8_t index;    index = (buffer - dev->buffer)/dev->itemsize;#if MEM_POOL_ERR_CHECK    if(((dev->grp & (1u<<(index>>3))) != 0) && ((dev->tbl[index>>3] & (1u<<(index & 0x07))) != 0))    {        /* 释放空闲的块 */        return -3;    }#endif    /* 设置对应bit位置为1表示空闲 */    dev->grp |= 1u<<(index>>3);    dev->tbl[index>>3] |= 1u<<(index & 0x07);    return 0;}

Mem_pool.h

#ifndef MEM_POOL_H#define MEM_POOL_H
#ifdef __cplusplusextern "C" {#endif  #include
#define MEM_POOL_PARAM_CHECK 1#define MEM_POOL_ERR_CHECK 1
typedef struct{    uint32_t   grp;    uint32_t   itemsize; /**< 区块大小                               */    uint32_t   itemnum;  /**< 总区块数,8的倍数,最多256                */    uint8_t*   buffer;   /**< 用户提供的缓存区,大小为itemsize*itemnum  */    uint8_t*   tbl;} mem_pool_st;
int mem_pool_init(mem_pool_st* dev, void* buffer, void* tbl, uint32_t itemsize, uint32_t itemnum);uint8_t* mem_pool_malloc(mem_pool_st* dev);int mem_pool_free(mem_pool_st* dev, uint8_t* buffer);
#ifdef __cplusplus}#endif
#endif

四. 实测

我们将其替换到我们某个驱动的实现中的mallocfree调用,测试功能OK,并按照二测试其时间抖动和执行时间,确认执行时间非常短,且基本无抖动。

初始化代码如下

  #define TRANS_POOL_NUM 64    #define GROUP_POOL_NUM 64    static dma_trans_t s_trans_buffer[TRANS_POOL_NUM];    static uint8_t s_trans_bitmap_buffer[(TRANS_POOL_NUM+7)/8];    static mem_group_info_t s_group_buffer[GROUP_POOL_NUM];    static uint8_t s_group_bitmap_buffer[(GROUP_POOL_NUM+7)/8];
    mem_pool_st s_trans_mem_pool_dev;    mem_pool_st s_group_mem_pool_dev;    mem_pool_init(&s_trans_mem_pool_dev, s_trans_buffer, s_trans_bitmap_buffer, sizeof(dma_trans_t), TRANS_POOL_NUM);    mem_pool_init(&s_group_mem_pool_dev, s_group_buffer, s_group_bitmap_buffer, sizeof(mem_group_info_t), GROUP_POOL_NUM);

申请

trans = (dma_trans_t *)mem_pool_malloc(&s_trans_mem_pool_dev);

释放

mem_pool_free(&s_trans_mem_pool_dev, trans);

五. 总结

以上我们借鉴uCOSII中的算法应用到我们自己设计的内存池轮子上,实现了高效且无时间抖动的内存池实现,是一个非常有用的轮子。这也提醒我们平常多注意借鉴参考别人的思想实现,并为己所用,不断积累。

以上实际使用的是bitmap查表实现固定时间,使用二维bitmap来扩充容量,上述例子是支持最大块数64个即8x8。如果要扩充更大的块数怎么办呢,自然想到得是增加二维的X/Y即可,即可将tblgrp8位改为16位,但是这存在一个问题,改为16位查表的表有2^16种情况,需要64k这在嵌入式中很可能是不可接受的。我们再来思考前面tbl扩展到grp,即X扩展到Y的过程,即一维扩展到二维的过程,我们是不是突然眼前一亮,拍案而起,甚至有点激动了,是的很自然的我们可以再增加一维变为三维,即对于体中点得位置,可以先找面,再找行,再找行中的bit点,就是这么简单自然,所以只需要将grp也改为数组即可,再增加一个vol字节对grp数组进行记录,vol一个bit 记录grp一个字节是否为0。所以我们要从本质去理解一项技术才可能是正真得理解,比好比这个查表从维度扩充角度去思考这才是本质,懂了这个本质自然会扩充,并为己所用应用到其他场景,而不是纠结于查表等技术细节,纠结于这些实际并没有理解本质,也仅仅会这个案例而已很难举一反三,这也是我们为什么思考问题一定要思考到本质,为什么我一直强调思考技术问题一定要思考到背后的本质甚至哲学意义,背后的原理甚至哲学意义才是普适得本质原理。

我们通过增加维度实现扩充,这就是降维打击,三阶搜寻变为了三次查表,有点类似对数的本质是把乘除法降维成加减法,也许四维度空间生物看我们三维空间的生物是多么的藐视了。

不由的要感叹世界的奇妙,也许只有造物主能做到,能够降维实际也是一种普遍的现象,可能也是造物主虽然没有让我们活在高维世界但是给我们开了个后门,让我们拥有了对高维进行降维分析的能力,对数降维,投影降维等都类似,可以让我们从侧面推测全貌,不知道能否从数学抽象角度来建立体系,证明具备何种属性的空间具备可降维性,那么这就建立了一套分析高维的数理体系了。


评论 (0)
  • 浪潮之上:智能时代的觉醒    近日参加了一场课题的答辩,这是医疗人工智能揭榜挂帅的国家项目的地区考场,参与者众多,围绕着医疗健康的主题,八仙过海各显神通,百花齐放。   中国大地正在发生着激动人心的场景:深圳前海深港人工智能算力中心高速运转的液冷服务器,武汉马路上自动驾驶出租车穿行的智慧道路,机器人参与北京的马拉松竞赛。从中央到地方,人工智能相关政策和消息如雨后春笋般不断出台,数字中国的建设图景正在智能浪潮中徐徐展开,战略布局如同围棋
    广州铁金刚 2025-04-30 15:24 297浏览
  • 随着电子元器件的快速发展,导致各种常见的贴片电阻元器件也越来越小,给我们分辨也就变得越来越难,下面就由smt贴片加工厂_安徽英特丽就来告诉大家如何分辨的SMT贴片元器件。先来看看贴片电感和贴片电容的区分:(1)看颜色(黑色)——一般黑色都是贴片电感。贴片电容只有勇于精密设备中的贴片钽电容才是黑色的,其他普通贴片电容基本都不是黑色的。(2)看型号标码——贴片电感以L开头,贴片电容以C开头。从外形是圆形初步判断应为电感,测量两端电阻为零点几欧,则为电感。(3)检测——贴片电感一般阻值小,更没有“充放
    贴片加工小安 2025-04-29 14:59 345浏览
  • 你是不是也有在公共场合被偷看手机或笔电的经验呢?科技时代下,不少现代人的各式机密数据都在手机、平板或是笔电等可携式的3C产品上处理,若是经常性地需要在公共场合使用,不管是工作上的机密文件,或是重要的个人信息等,民众都有防窃防盗意识,为了避免他人窥探内容,都会选择使用「防窥保护贴片」,以防止数据外泄。现今市面上「防窥保护贴」、「防窥片」、「屏幕防窥膜」等产品就是这种目的下产物 (以下简称防窥片)!防窥片功能与常见问题解析首先,防窥片最主要的功能就是用来防止他人窥视屏幕上的隐私信息,它是利用百叶窗的
    百佳泰测试实验室 2025-04-30 13:28 550浏览
  • 在CAN总线分析软件领域,当CANoe不再是唯一选择时,虹科PCAN-Explorer 6软件成为了一个有竞争力的解决方案。在现代工业控制和汽车领域,CAN总线分析软件的重要性不言而喻。随着技术的进步和市场需求的多样化,单一的解决方案已无法满足所有用户的需求。正是在这样的背景下,虹科PCAN-Explorer 6软件以其独特的模块化设计和灵活的功能扩展,为CAN总线分析领域带来了新的选择和可能性。本文将深入探讨虹科PCAN-Explorer 6软件如何以其创新的模块化插件策略,提供定制化的功能选
    虹科汽车智能互联 2025-04-28 16:00 247浏览
  • 在智能硬件设备趋向微型化的背景下,语音芯片方案厂商针对小体积设备开发了多款超小型语音芯片方案,其中WTV系列和WT2003H系列凭借其QFN封装设计、高性能与高集成度,成为微型设备语音方案的理想选择。以下从封装特性、功能优势及典型应用场景三个方面进行详细介绍。一、超小体积封装:QFN技术的核心优势WTV系列与WT2003H系列均提供QFN封装(如QFN32,尺寸为4×4mm),这种封装形式具有以下特点:体积紧凑:QFN封装通过减少引脚间距和优化内部结构,显著缩小芯片体积,适用于智能门铃、穿戴设备
    广州唯创电子 2025-04-30 09:02 341浏览
  • 想不到短短几年时间,华为就从“技术封锁”的持久战中突围,成功将“被卡脖子”困境扭转为科技主权的主动争夺战。众所周知,前几年技术霸权国家突然对华为发难,导致芯片供应链被强行掐断,海外市场阵地接连失守,恶意舆论如汹涌潮水,让其瞬间陷入了前所未有的困境。而最近财报显示,华为已经渡过危险期,甚至开始反击。2024年财报数据显示,华为实现全球销售收入8621亿元人民币,净利润626亿元人民币;经营活动现金流为884.17亿元,同比增长26.7%。对比来看,2024年营收同比增长22.42%,2023年为7
    用户1742991715177 2025-05-02 18:40 91浏览
  • 网约车,真的“饱和”了?近日,网约车市场的 “饱和” 话题再度引发热议。多地陆续发布网约车风险预警,提醒从业者谨慎入局,这背后究竟隐藏着怎样的市场现状呢?从数据来看,网约车市场的“过剩”现象已愈发明显。以东莞为例,截至2024年12月底,全市网约车数量超过5.77万辆,考取网约车驾驶员证的人数更是超过13.48万人。随着司机数量的不断攀升,订单量却未能同步增长,导致单车日均接单量和营收双双下降。2024年下半年,东莞网约出租车单车日均订单量约10.5单,而单车日均营收也不容乐
    用户1742991715177 2025-04-29 18:28 303浏览
  • 贞光科技代理品牌紫光国芯的车规级LPDDR4内存正成为智能驾驶舱的核心选择。在汽车电子国产化浪潮中,其产品以宽温域稳定工作能力、优异电磁兼容性和超长使用寿命赢得市场认可。紫光国芯不仅确保供应链安全可控,还提供专业本地技术支持。面向未来,紫光国芯正研发LPDDR5车规级产品,将以更高带宽、更低功耗支持汽车智能化发展。随着智能网联汽车的迅猛发展,智能驾驶舱作为人机交互的核心载体,对处理器和存储器的性能与可靠性提出了更高要求。在汽车电子国产化浪潮中,贞光科技代理品牌紫光国芯的车规级LPDDR4内存凭借
    贞光科技 2025-04-28 16:52 341浏览
  • 文/郭楚妤编辑/cc孙聪颖‍越来越多的企业开始蚕食动力电池市场,行业“去宁王化”态势逐渐明显。随着这种趋势的加强,打开新的市场对于宁德时代而言至关重要。“我们不希望被定义为电池的制造者,而是希望把自己称作新能源产业的开拓者。”4月21日,在宁德时代举行的“超级科技日”发布会上,宁德时代掌门人曾毓群如是说。随着宁德时代核心新品骁遥双核电池的发布,其搭载的“电电增程”技术也走进业界视野。除此之外,经过近3年试水,宁德时代在换电业务上重资加码。曾毓群认为换电是一个重资产、高投入、长周期的产业,涉及的利
    华尔街科技眼 2025-04-28 21:55 213浏览
  • 多功能电锅长什么样子,主视图如下图所示。侧视图如下图所示。型号JZ-18A,额定功率600W,额定电压220V,产自潮州市潮安区彩塘镇精致电子配件厂,铭牌如下图所示。有两颗螺丝固定底盖,找到合适的工具,拆开底盖如下图所示。可见和大部分市场的加热锅一样的工作原理,手绘原理图,根据原理图进一步理解和分析。F1为保险,250V/10A,185℃,CPGXLD 250V10A TF185℃ RY 是一款温度保险丝,额定电压是250V,额定电流是10A,动作温度是185℃。CPGXLD是温度保险丝电器元件
    liweicheng 2025-05-05 18:36 100浏览
  • ‌一、高斯计的正确选择‌1、‌明确测量需求‌‌磁场类型‌:区分直流或交流磁场,选择对应仪器(如交流高斯计需支持交变磁场测量)。‌量程范围‌:根据被测磁场强度选择覆盖范围,例如地球磁场(0.3–0.5 G)或工业磁体(数百至数千高斯)。‌精度与分辨率‌:高精度场景(如科研)需选择误差低于1%的仪器,分辨率需匹配微小磁场变化检测需求。2、‌仪器类型选择‌‌手持式‌:便携性强,适合现场快速检测;‌台式‌:精度更高,适用于实验室或工业环境。‌探头类型‌:‌横向/轴向探头‌:根据磁场方向选择,轴向探头适合
    锦正茂科技 2025-05-06 11:36 99浏览
  • 在全球制造业加速向数字化、智能化转型的浪潮中,健达智能作为固态照明市场的引领者和智能电子以及声学产品的创新先锋,健达智能敏锐捕捉到行业发展的新机遇与新挑战,传统制造模式已难以满足客户对品质追溯、定制化生产和全球化布局的需求。在此背景下, 健达智能科技股份有限公司(以下简称:健达智能)与盘古信息达成合作,正式启动IMS数字化智能制造工厂项目,标志着健达智能数字化转型升级迈入新阶段。此次项目旨在通过部署盘古信息IMS系统,助力健达实现生产全流程的智能化管控,打造照明行业数字化标杆。行业趋势与企业挑战
    盘古信息IMS 2025-04-30 10:13 53浏览
  • 文/Leon编辑/cc孙聪颖‍2023年,厨电行业在相对平稳的市场环境中迎来温和复苏,看似为行业增长积蓄势能。带着对市场向好的预期,2024 年初,老板电器副董事长兼总经理任富佳为企业定下双位数增长目标。然而现实与预期相悖,过去一年,这家老牌厨电企业不仅未能达成业绩目标,曾提出的“三年再造一个老板电器”愿景,也因市场下行压力面临落空风险。作为“企二代”管理者,任富佳在掌舵企业穿越市场周期的过程中,正面临着前所未有的挑战。4月29日,老板电器(002508.SZ)发布了2024年年度报告及2025
    华尔街科技眼 2025-04-30 12:40 312浏览
  • 一、gao效冷却与控温机制‌1、‌冷媒流动设计‌采用低压液氮(或液氦)通过毛细管路导入蒸发器,蒸汽喷射至样品腔实现快速冷却,冷却效率高(室温至80K约20分钟,至4.2K约30分钟)。通过控温仪动态调节蒸发器加热功率,结合温度传感器(如PT100铂电阻或Cernox磁场不敏感传感器),实现±0.01K的高精度温度稳定性。2、‌宽温区覆盖与扩展性‌标准温区为80K-325K,通过降压选件可将下限延伸至65K(液氮模式)或4K(液氦模式)。可选配475K高温模块,满足材料在ji端温度下的性能测试需求
    锦正茂科技 2025-04-30 13:08 454浏览
  •  一、‌核心降温原理‌1、‌液氮媒介作用‌液氮恒温器以液氮(沸点约77K/-196℃)为降温媒介,通过液氮蒸发吸收热量的特性实现快速降温。液氮在内部腔体蒸发时形成气-液界面,利用毛细管路将冷媒导入蒸发器,强化热交换效率。2、‌稳态气泡控温‌采用‌稳态气泡原理‌:调节锥形气塞与冷指间隙,控制气-液界面成核沸腾条件,使漏热稳定在设定值。通过控温仪调整加热功率,补偿漏热并维持温度平衡,实现80K-600K范围的快速变温。二、‌温度控制机制‌1、‌动态平衡调节‌控温仪内置模糊控制系统,通过温度
    锦正茂科技 2025-04-30 11:31 49浏览
我要评论
0
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦