MDK中的数据压缩算法之二-"手撸"反汇编分析解压算法

原创 嵌入式Lee 2023-11-30 20:28

一. 前言

前面我们对MDK中的数据压缩相关操作和配置进行了介绍,本文进一步分析其算法。

二. 算法分析

2.1代码结构梳理

随便创建一个工程这里创建的stm32的工程,代码如下(启动代码略),注意变量v要足够大,只有压缩省下的空间大于压缩代码本身大小链接器才会进行压缩。

#include 
#include
uint8_t v[]="1111122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222";
int main(void){
printf("%s",v);
}
void SystemInit (void){
}

我们可以通过map文件看到,实际是链接了cmprslib下的相关解压代码

代码具体位置如下,即0x08000269处的94字节。

配置软件仿真

调出汇编窗口,在对应的代码处0x08000268打断点(注上面看到的时0x08000269是因为bit0=1表示是thumb指令)

查看变量v的地址为0x20020004,大小是476字节

实际前面还有4字节的压缩数据是stdout用的。

加载地址为0x08000410对应的是压缩数据,解压后为4+476字节,其中4字节是stdout中的全局变量,476字节是我们定义的v变量。

 

对应的压缩后的数据,即该部分数据要解压成4+476字节的数据

对应的代码如下

2.2反汇编代码分析

2.2.1解压函数入口分析

前面可以看到压缩是链接器完成的,所以这里程序中只有解压的代码。我们通过解压代码也可以分析其算法。

现在开始就进行人工智能-手动反汇编。将上述汇编代码改写为C代码。

为了方便描述把对应的汇编代码复制出来,并添加行号。

(1):0x08000268 B570      PUSH          {r4-r6,lr}(2):0x0800026A 188D      ADDS          r5,r1,r2(3):0x0800026C F8104B01  LDRB          r4,[r0],#0x01(4):0x08000270 F0140203  ANDS          r2,r4,#0x03(5):0x08000274 D101      BNE           0x0800027A(6):0x08000276 F8102B01  LDRB          r2,[r0],#0x01(7):0x0800027A 1123      ASRS          r3,r4,#4(8):0x0800027C D106      BNE           0x0800028C(9):0x0800027E F8103B01  LDRB          r3,[r0],#0x01(10):0x08000282 E003      B             0x0800028C(11):0x08000284 F8106B01  LDRB          r6,[r0],#0x01(12):0x08000288 F8016B01  STRB          r6,[r1],#0x01(13):0x0800028C 1E52      SUBS          r2,r2,#1(14):0x0800028E D1F9      BNE           0x08000284(15):0x08000290 B1AB      CBZ           r3,0x080002BE(16):0x08000292 F8102B01  LDRB          r2,[r0],#0x01(17):0x08000296 F004040C  AND           r4,r4,#0x0C(18):0x0800029A 1A8A      SUBS          r2,r1,r2(19):0x0800029C 2C0C      CMP           r4,#0x0C(20):0x0800029E D003      BEQ           0x080002A8(21):0x080002A0 EBA21284  SUB           r2,r2,r4,LSL #6(22):0x080002A4 1C9B      ADDS          r3,r3,#2(23):0x080002A6 E008      B             0x080002BA(24):0x080002A8 F8104B01  LDRB          r4,[r0],#0x01(25):0x080002AC EBA22204  SUB           r2,r2,r4,LSL #8(26):0x080002B0 E7F8      B             0x080002A4(27):0x080002B2 F8124B01  LDRB          r4,[r2],#0x01(28):0x080002B6 F8014B01  STRB          r4,[r1],#0x01(29):0x080002BA 1E5B      SUBS          r3,r3,#1(30):0x080002BC D5F9      BPL           0x080002B2(31):0x080002BE 42A9      CMP           r1,r5(32):0x080002C0 D3D4      BCC           0x0800026C(33):0x080002C2 2000      MOVS          r0,#0x00(34):0x080002C4 BD70      POP           {r4-r6,pc}

首先,(1)r4~r6保存到栈中,因为后面要使用这几个寄存器,lr是函数返回地址也要保存。

(34)后面对应的POP即反过程,恢复这几个寄存器,并将lr寄存器恢复到PC即返回到调用该函数之前的位置。

(33)这里r0作为返回值,固定返回为0, 当然这里函数返回类型也可能是void,没有返回类型编译器只是固定将其置为0而已。

(1):0x08000268 B570      PUSH          {r4-r6,lr}(33):0x080002C2 2000      MOVS          r0,#0x00(34):0x080002C4 BD70      POP           {r4-r6,pc}

可以看到r0~r3没有保存,这是因为r0~r2他们用于传参了,r3未用。所以我们在(1)时查看r0~的值可以确认其传输入的参数值。

实际还需要更往前一步,先看解压之前做了什么准备工作,解压函数的参数是怎么准备的。

这里往前看前面一段代码

这个是_scatterload函数的入口,

其实这里定义了一个表格,表格的开始地址是后面的常数

0x08000260 03F0      DCW           0x03F00x08000262 0800      DCW           0x0800

结束地址是后面定义的常数

0x08000264 0410      DCW           0x04100x08000266 0800      DCW           0x0800

我们看到0x08000410DATA的加载地址的前面

0x08000240 01F9      DCW           0x01F90x08000242 0800      DCW           0x08000x08000244 4C06      LDR           r4,[pc,#24]  ; @0x080002600x08000246 4D07      LDR           r5,[pc,#28]  ; @0x080002640x08000248 E006      B             0x080002580x0800024A 68E0      LDR           r0,[r4,#0x0C]0x0800024C F0400301  ORR           r3,r0,#0x010x08000250 E8940007  LDM           r4,{r0-r2}0x08000254 4798      BLX           r30x08000256 3410      ADDS          r4,r4,#0x100x08000258 42AC      CMP           r4,r50x0800025A D3F6      BCC           0x0800024A0x0800025C F7FFFFD0  BL.W          0x08000200 __main_after_scatterload0x08000260 03F0      DCW           0x03F00x08000262 0800      DCW           0x08000x08000264 0410      DCW           0x04100x08000266 0800      DCW           0x0800


以下就是将上述定义的开始和结束常数加载到r4r5(通过pc偏移2428取得上述常数)

如果不相等,说明开始和结束之间有数据项。然后跳到中间的0x0800024A开始处理数据

0x08000244 4C06      LDR           r4,[pc,#24]  ; @0x080002600x08000246 4D07      LDR           r5,[pc,#28]  ; @0x080002640x08000248 E006      B             0x08000258......0x08000258 42AC      CMP           r4,r50x0800025A D3F6      BCC           0x0800024A

如下即处理过程,先从r40x080003F0处偏移12的地址,取值放到r0中,此时r0的值为0x08000268,即解压缩处理函数的地址。而后面ORR          r3,r0,#0x01是因为thunmb指令,地址bit0需要为1,后面的BLX           r3即跳到该函数处执行。

 

x0800024A 68E0      LDR           r0,[r4,#0x0C]0x0800024C F0400301  ORR           r3,r0,#0x010x08000250 E8940007  LDM           r4,{r0-r2}0x08000254 4798      BLX           r30x08000256 3410      ADDS          r4,r4,#0x100x08000258 42AC      CMP           r4,r50x0800025A D3F6      BCC           0x0800024A

前面的LDM          r4,{r0-r2}即从r4(0x080003F0)加载值到r0~r2

所以r0~r2的值即为解压函数的参数,值分别为r0=0x083000410对应加载地址压缩前数据地址,r1=0x20020000即运行地址即解压后存放的地址,r2=0x000001E0即解压后大小480=4+476.

 

后面处理完数组中的一个结构如下红色16个字节,r4就增加16,继续比较r4r5看是不是还有其他的.16是因为一个数据项是16字节(加载地址,运行地址,解压后大小,回调函数)

如果没有了就调用__main_after_scatterload

 

0x08000256 3410      ADDS          r4,r4,#0x100x08000258 42AC      CMP           r4,r50x0800025A D3F6      BCC           0x0800024A0x0800025C F7FFFFD0  BL.W          0x08000200 __main_after_scatterload

以上整个原理就完整的暴露出来了,为了方便理解,下面画出框图。

 

首先scatterload函数查询分散加载开始地址和结束地址之间是否有表项,

表项的结构是:

加载地址,即压缩前的数据地址

运行地址,即解压后数据的存放地址

加载后数据长度:解压后的数据长度

加载回调函数:解压回调函数。

注意上面实际是分散加载的处理过程,不一定是解压,也可以是任意的操作,由回调函数指定,所以理论上也可以插入自己的解压处理函数,甚至其他任意的自定义处理函数。

更进一步,实际上上面提供了一个用户钩子函数,可以有更多的玩法,比如解密,解混淆等等,可以发挥自己的想法玩出更多的花样,在具体产品开发时也可以考虑该特性去实现一些特殊的需求。

2.2.1 解压函数反汇编

接下来正是进入正题,分析解压函数

上面实际已经分析了解压函数的入口和出口

(1) (33) (34)已经分析了

(1):0x08000268 B570      PUSH          {r4-r6,lr}(2):0x0800026A 188D      ADDS          r5,r1,r2(3):0x0800026C F8104B01  LDRB          r4,[r0],#0x01(4):0x08000270 F0140203  ANDS          r2,r4,#0x03(5):0x08000274 D101      BNE           0x0800027A(6):0x08000276 F8102B01  LDRB          r2,[r0],#0x01(7):0x0800027A 1123      ASRS          r3,r4,#4(8):0x0800027C D106      BNE           0x0800028C(9):0x0800027E F8103B01  LDRB          r3,[r0],#0x01(10):0x08000282 E003      B             0x0800028C(11):0x08000284 F8106B01  LDRB          r6,[r0],#0x01(12):0x08000288 F8016B01  STRB          r6,[r1],#0x01(13):0x0800028C 1E52      SUBS          r2,r2,#1(14):0x0800028E D1F9      BNE           0x08000284(15):0x08000290 B1AB      CBZ           r3,0x080002BE(16):0x08000292 F8102B01  LDRB          r2,[r0],#0x01(17):0x08000296 F004040C  AND           r4,r4,#0x0C(18):0x0800029A 1A8A      SUBS          r2,r1,r2(19):0x0800029C 2C0C      CMP           r4,#0x0C(20):0x0800029E D003      BEQ           0x080002A8(21):0x080002A0 EBA21284  SUB           r2,r2,r4,LSL #6(22):0x080002A4 1C9B      ADDS          r3,r3,#2(23):0x080002A6 E008      B             0x080002BA(24):0x080002A8 F8104B01  LDRB          r4,[r0],#0x01(25):0x080002AC EBA22204  SUB           r2,r2,r4,LSL #8(26):0x080002B0 E7F8      B             0x080002A4(27):0x080002B2 F8124B01  LDRB          r4,[r2],#0x01(28):0x080002B6 F8014B01  STRB          r4,[r1],#0x01(29):0x080002BA 1E5B      SUBS          r3,r3,#1(30):0x080002BC D5F9      BPL           0x080002B2(31):0x080002BE 42A9      CMP           r1,r5(32):0x080002C0 D3D4      BCC           0x0800026C(33):0x080002C2 2000      MOVS          r0,#0x00(34):0x080002C4 BD70      POP           {r4-r6,pc}

我们先根据前面的分析写出函数的原型

void decompress(uint32_t len, uint8_t* vma, uint8_t* lma){  
}

其中lma通过r0传参,即加载地址(压缩数据地址)

vma通过r1为传参,运行地址(即解压后地址)

len通过r2传参即,解压后数据长度。

继续往下看

以下获取了vma的结束地址,即开始地址加上长度,用于判断结束条件。

(2)0x0800026A 188D      ADDS          r5,r1,r2

以下用于判断是否结束,即r1vma开始,表示写入vma地址,写完一个递增,直到递增到结束地址,表示写完。

比如如下指令用于将数据写入vma地址,每写入一个r1即递增

(28):0x080002B6 F8014B01  STRB          r4,[r1],#0x01

(31)(32)即比较当前写入vma的地址r1是否达到了vma的结束地址r5

(31):0x080002BE 42A9      CMP           r1,r5(32):0x080002C0 D3D4      BCC           0x0800026C

Cmp实际是进行r1-r5操作,bcc表示前面的操作结果有进位(c即进位),即r1即跳转到前面(3)继续执行。R1>=r5则结束。

所以可以写出如下代码,即最外面一层

void decompress(uint32_t len, uint8_t* vma, uint8_t* lma){    uint8_t* end = vma + len;      while(vma < end)    {
    }}

继续往下走

(3):0x0800026C F8104B01  LDRB          r4,[r0],#0x01(4):0x08000270 F0140203  ANDS          r2,r4,#0x03

(3)即从r0所在的地址处,即加载地址处读一个字节到r4,读完后r0递增,即指针递增。

于是写出如下c代码

void decompress(uint32_t len, uint8_t* vma, uint8_t* lma){    uint8_t* end = vma + len;      uint_t tmp4;    while(vma < end)    {        tmp4 = *lma++;    }}

(4)(5)将上述读出来的值取低2位,看是不是0.

不为0即跳到(7) 相同即执行后面的(6)

其中(6)即读出lma后面一个数据,

(6):0x08000276 F8102B01  LDRB          r2,[r0],#0x01

因为此时r0已经递增1了,并且读完后继续递增1.

(7)是将最开始读到到的值右移4位,赋值给r3,然后紧接着是(8)看结果是不是0

如果不是0则跳到(13)否则执行后面的(9)即读lma的后续一个数据。

上面可以看到是连续的if else判断。

先判断最低2位,表示是否需要读一个字节到r2

然后判断高4位,表示需要读一个字节到r3

此时代码可以写为如下

void decompress(uint32_t len, uint8_t* vma, uint8_t* lma){    uint8_t* end = vma + len;      uint8_t tmp4;    uint8_t tmp2;    uint8_t tmp3;        while(vma < end)    {        tmp4 = *lma++;
    tmp2 = tmp4 & 0x03;    if(tmp2 == 0)    {        tmp2 = *lma++;    }
    tmp3 = tmp4 >> 4;    if(tmp3 == 0)    {        tmp3 = *lma++;    }    }}

继续往下走来到,前面不管是if还是else都来到了0x0800028C

该处是一个循环,代码块如下

(11):0x08000284 F8106B01  LDRB          r6,[r0],#0x01(12):0x08000288 F8016B01  STRB          r6,[r1],#0x01(13):0x0800028C 1E52      SUBS          r2,r2,#1(14):0x0800028E D1F9      BNE           0x08000284

转化成c代码如下,这里是先到0x0800028C即先减1再比较所以是---tmp2

    while(--tmp2)    {        *vma++ = *lma++;    }

上述代码块执行完后,执行

(23):0x080002A6 E008      B             0x080002BA

都跳到了0x080002BA处执行,

这里也可看出是一个循环

接下来来到了如下代码块

(15):0x08000290 B1AB      CBZ           r3,0x080002BE

先判断r3是否位0,为0就直接跳到0x080002BE去,否则执行下面代码块

(16):0x08000292 F8102B01  LDRB          r2,[r0],#0x01(17):0x08000296 F004040C  AND           r4,r4,#0x0C(18):0x0800029A 1A8A      SUBS          r2,r1,r2

对应c

            tmp2 = *lma++;            tmp4 = tmp4 & 0x0C;            p = vma - tmp2

如下又是一个if else

(19):0x0800029C 2C0C      CMP           r4,#0x0C(20):0x0800029E D003      BEQ           0x080002A8(21):0x080002A0 EBA21284  SUB           r2,r2,r4,LSL #6(22):0x080002A4 1C9B      ADDS          r3,r3,#2(23):0x080002A6 E008      B             0x080002BA(24):0x080002A8 F8104B01  LDRB          r4,[r0],#0x01(25):0x080002AC EBA22204  SUB           r2,r2,r4,LSL #8(26):0x080002B0 E7F8      B             0x080002A4

改为c

      if(tmp4 != 0x0C)            {                p = p - (tmp4<<6);                tmp3 += 2;            }             else            {                tmp4 = *lma++;                p = p - (tmp4<<8);            }

最后又是一个循环赋值

前面都来到了(23):0x080002A6 E008     B             0x080002BA

(23):0x080002A6 E008      B             0x080002BA

先减少1再判断BPL,注意BPL是非负跳转,即r3每次减少1,大于等于0,都会跳到0x080002B2

(27):0x080002B2 F8124B01  LDRB          r4,[r2],#0x01(28):0x080002B6 F8014B01  STRB          r4,[r1],#0x01(29):0x080002BA 1E5B      SUBS          r3,r3,#1(30):0x080002BC D5F9      BPL           0x080002B2

改成c代码如下

            while(tmp3--)            {                                *vma++ = *p++;            }

以上就完成了整个代码

void decompress(uint32_t len, uint8_t* vma, uint8_t* lma){    uint8_t* end = vma + len;      uint8_t tmp4;    uint8_t tmp2;    uint16_t tmp3;        uint8_t* p;    while(vma < end)    {        tmp4 = *lma++;        tmp2 = tmp4 & 0x03;        if(tmp2 == 0)        {            tmp2 = *lma++;        }
        tmp3 = tmp4 >> 4;        if(tmp3 == 0)        {            tmp3 = *lma++;        }
        while(--tmp2)        {            *vma++ = *lma++;        }
        if(tmp3 != 0)        {            tmp2 = *lma++;            tmp4 = tmp4 & 0x0C;            p = vma - tmp2;
            if(tmp4 != 0x0C)            {                p = p - (tmp4<<6);                tmp3 += 2;            }             else            {                tmp4 = *lma++;                p = p - (tmp4<<8);            }
            while(tmp3--)            {                *vma++ = *p++;            }        }    }}

2.2.2 c代码测试

测试代码如下,将上面的压缩数据,再用我们反汇编后的代码进行解压,看是否正确

#include #include 
uint8_t out[480];uint8_t in[]={0x12,0x00,0x01,0x22,0x31,0x01,0x02,0xff,0x32,0x01,0x01,0xd2,0x01,0x02,0x00,0x00};uint8_t v[]="1111122222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222";
void decompress(uint32_t len, uint8_t* vma, uint8_t* lma){    uint8_t* end = vma + len;      uint8_t tmp4;    uint8_t tmp2;    uint16_t tmp3;        uint8_t* p;    while(vma < end)    {        tmp4 = *lma++;        tmp2 = tmp4 & 0x03;        if(tmp2 == 0)        {            tmp2 = *lma++;        }
        tmp3 = tmp4 >> 4;        if(tmp3 == 0)        {            tmp3 = *lma++;        }
        while(--tmp2)        {            *vma++ = *lma++;        }
        if(tmp3 != 0)        {            tmp2 = *lma++;            tmp4 = tmp4 & 0x0C;            p = vma - tmp2;
            if(tmp4 != 0x0C)            {                p = p - (tmp4<<6);                tmp3 += 2;            }             else            {                tmp4 = *lma++;                p = p - (tmp4<<8);            }
            while(tmp3--)            {                                *vma++ = *p++;            }        }    }}
int main(void){      decompress(480, out, in);        printf("%s",v);}

运行后查看输出,完全正确

 

2.3 算法分析

前面C代码出来之后,就好理解整个编码了。

通过前面的ifelse也可以看出,

首先编码数据的第一个字节不同字段决定了后续字节的含义,

位域

Bit[7:4]

Bit[3:2]

Bit[1:0]

含义

有压缩重复数据的个数,如果bit[3:2]!=11则这个+2代表真正的个数。

该值不为0才有重复的数据。

如果bit[7:4]不为0

则后续一个编码数据代表,重复数在当前已经解码数据往前的偏移位置。

 

如果bit[7:4]不为0

bit[3:2],表示偏移算法。

 

在后续一个编码偏移的基础上,如果bit[3:2]不为11则偏移bit[3:2]*64. 否则是在此基础上再偏移下一个编码数据的值*256.

 

最后根据上面偏移地址找到该重复数据,复制重复个数到当前解压区。

后续未压缩数据的个数-1

如果为0,则需要读后续一个字节的编码代表未压缩数据的个数

比如

0x12,0x00,0x01

0x12的最低两位为2,所以未压缩数据个数是2-1=1.

所以需要复制0x12后面的一个数据0x00到解压区。

此时解压后的数据是0x00.

0x12而高4位为1,所以有重估数据,而0x12bit[3:2]0,所以重复个数药+2,即1+2=3.

且偏移是后续一个编码数据0x01.

所以要找重复数据即要先偏移0x01,然后再偏移bit[3:2]*64,由于bit[3:2]=0,所以即偏移1

即解压位置往前偏移1就是已经解压的数据0x00的位置。

然后从这个位置复制这个重复的0,复制3次追加到原来的0x00后面,

最终得到0x00 0x00 0x00 0x00.

即上面的stdout4个数据。

其他解压过程类似。

以上是解压过程,比较简单,压缩的实现要复杂很多。 后面我们再继续分享。

三. 总结

以上通过手动反汇编,分析了解压算法的实现。我们可以借鉴其思想,实现我们自己一个超级精简的可移植的压缩,解压算法,作为我们的代码库积累。 之前也预告了,在我们的高效的文件传输协议中也将会引入压缩以提高传输效率。详见后面一期超级精简系列。


评论
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 482浏览
  • 数字隔离芯片是现代电气工程师在进行电路设计时所必须考虑的一种电子元件,主要用于保护低压控制电路中敏感电子设备的稳定运行与操作人员的人身安全。其不仅能隔离两个或多个高低压回路之间的电气联系,还能防止漏电流、共模噪声与浪涌等干扰信号的传播,有效增强电路间信号传输的抗干扰能力,同时提升电子系统的电磁兼容性与通信稳定性。容耦隔离芯片的典型应用原理图值得一提的是,在电子电路中引入隔离措施会带来传输延迟、功耗增加、成本增加与尺寸增加等问题,而数字隔离芯片的目标就是尽可能消除这些不利影响,同时满足安全法规的要
    华普微HOPERF 2025-01-15 09:48 78浏览
  • 在不断发展的电子元件领域,继电器——作为切换电路的关键设备,正在经历前所未有的技术变革。固态继电器(SSR)和机械继电器之间的争论由来已久。然而,从未来发展的角度来看,固态继电器正逐渐占据上风。本文将从耐用性、速度和能效三个方面,全面剖析固态继电器为何更具优势,并探讨其在行业中的应用与发展趋势。1. 耐用性:经久耐用的设计机械继电器:机械继电器依靠物理触点完成电路切换。然而,随着时间的推移,这些触点因电弧、氧化和材料老化而逐渐磨损,导致其使用寿命有限。因此,它们更适合低频或对切换耐久性要求不高的
    腾恩科技-彭工 2025-01-10 16:15 100浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 466浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 457浏览
  • 电动汽车(EV)正在改变交通运输,为传统内燃机提供更清洁、更高效的替代方案。这种转变的核心是电力电子和能源管理方面的创新,而光耦合器在其中发挥着关键作用。这些不起眼的组件可实现可靠的通信、增强安全性并优化电动汽车系统的性能,使其成为正在进行的革命中不可或缺的一部分。光耦合器,也称为光隔离器,是一种使用光传输电信号的设备。通过隔离高压和低压电路,光耦合器可确保安全性、减少干扰并保持信号完整性。这些特性对于电动汽车至关重要,因为精确控制和安全性至关重要。 光耦合器在电动汽车中的作用1.电池
    腾恩科技-彭工 2025-01-10 16:14 78浏览
  • Snyk 是一家为开发人员提供安全平台的公司,致力于协助他们构建安全的应用程序,并为安全团队提供应对数字世界挑战的工具。以下为 Snyk 如何通过 CircleCI 实现其“交付”使命的案例分析。一、Snyk 的挑战随着客户对安全工具需求的不断增长,Snyk 的开发团队面临多重挑战:加速交付的需求:Snyk 的核心目标是为开发者提供更快、更可靠的安全解决方案,但他们的现有 CI/CD 工具(TravisCI)运行缓慢,无法满足快速开发和部署的要求。扩展能力不足:随着团队规模和代码库的不断扩大,S
    艾体宝IT 2025-01-10 15:52 164浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 498浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 182浏览
  • 随着全球向绿色能源转型的加速,对高效、可靠和环保元件的需求从未如此强烈。在这种背景下,国产固态继电器(SSR)在实现太阳能逆变器、风力涡轮机和储能系统等关键技术方面发挥着关键作用。本文探讨了绿色能源系统背景下中国固态继电器行业的前景,并强调了2025年的前景。 1.对绿色能源解决方案日益增长的需求绿色能源系统依靠先进的电源管理技术来最大限度地提高效率并最大限度地减少损失。固态继电器以其耐用性、快速开关速度和抗机械磨损而闻名,正日益成为传统机电继电器的首选。可再生能源(尤其是太阳能和风能
    克里雅半导体科技 2025-01-10 16:18 325浏览
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 491浏览
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 522浏览
  • 随着通信技术的迅速发展,现代通信设备需要更高效、可靠且紧凑的解决方案来应对日益复杂的系统。中国自主研发和制造的国产接口芯片,正逐渐成为通信设备(从5G基站到工业通信模块)中的重要基石。这些芯片凭借卓越性能、成本效益及灵活性,满足了现代通信基础设施的多样化需求。 1. 接口芯片在通信设备中的关键作用接口芯片作为数据交互的桥梁,是通信设备中不可或缺的核心组件。它们在设备内的各种子系统之间实现无缝数据传输,支持高速数据交换、协议转换和信号调节等功能。无论是5G基站中的数据处理,还是物联网网关
    克里雅半导体科技 2025-01-10 16:20 444浏览
  • 食物浪费已成为全球亟待解决的严峻挑战,并对环境和经济造成了重大影响。最新统计数据显示,全球高达三分之一的粮食在生产过程中损失或被无谓浪费,这不仅导致了资源消耗,还加剧了温室气体排放,并带来了巨大经济损失。全球领先的光学解决方案供应商艾迈斯欧司朗(SIX:AMS)近日宣布,艾迈斯欧司朗基于AS7341多光谱传感器开发的创新应用来解决食物浪费这一全球性难题。其多光谱传感解决方案为农业与食品行业带来深远变革,该技术通过精确判定最佳收获时机,提升质量控制水平,并在整个供应链中有效减少浪费。 在2024
    艾迈斯欧司朗 2025-01-14 18:45 61浏览
  •   在信号处理过程中,由于信号的时域截断会导致频谱扩展泄露现象。那么导致频谱泄露发生的根本原因是什么?又该采取什么样的改善方法。本文以ADC性能指标的测试场景为例,探讨了对ADC的输出结果进行非周期截断所带来的影响及问题总结。 两个点   为了更好的分析或处理信号,实际应用时需要从频域而非时域的角度观察原信号。但物理意义上只能直接获取信号的时域信息,为了得到信号的频域信息需要利用傅里叶变换这个工具计算出原信号的频谱函数。但对于计算机来说实现这种计算需要面对两个问题: 1.
    TIAN301 2025-01-14 14:15 108浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦