使用weak(弱函数)构建跨平台的C语言代码

嵌入式电子 2023-05-23 22:14


弱符号

弱符号是指在定义或者声明一个对象(变量、结构体成员、函数)时,在对象的前面添加 __attribute__((weak)) 标志所得到的对象符号。如下所示函数即为一个弱对象符号 void test_weak_attr(void),或者称该函数是弱函数属性的、虚函数。

__attribute__((weak)) void test_weak_attr(void)// 或者使用如下样式的定义,两者等效void __attribute__((weak)) test_weak_attr(void){    printf("Weak Func!\r\n");}


弱符号的作用与示例

弱符号是相对于强符号而言的,在定义或者声明变量、函数时,未添加 __attribute__((weak)) 标识的就默认为强符号。如下,最普通的函数定义,就是定义了一个强符号 void test_strong_ref(void):

void test_weak_attr(void){    printf("this is a strong func\r\n");}

驱动程序往往需要考虑兼容性,因为要兼任很多厂商的不同型号的设备。若驱动程序中使用强符号定义一些与适配的设备的特性相关的功能,则下次适配其他设备时,该强符号函数可能需要被修改,以兼容新的设备。当适配的设备很多时,频繁地更改驱动代码将破坏驱动的可维护性。

弱符号的出现可以很好地解决该问题。弱符号的对象具有可以被重定义的功能(即可以被重载)。下面通过测试说明弱符号这种可被重载的特性。

在 test_weak_attr.c 程序中定义如下弱函数:

// test_weak_attr.c#include  __attribute__((weak)) void test_weak_attr(void){    printf("this is a weak func\r\n");}

在 main.c 中定义如下程序:

// main.cvoid test_weak_attr(void){    printf("this is a strong func\r\n");} void app_main(void){    printf("init done\r\n");         test_weak_attr();}

编译运行该 main.c 程序,得到的结果是什么样子的呢?

1

this is a strong func

将 main.c 中的 void test_weak_attr(void) 函数注释掉,再重新编译运行程序得到的结果是:

1

this is a weak func

小结:在使用弱符号函数时,我们可以重新定义一个同名的强符号函数来替代它;若没有重新定义一个强函数来替换它,就使用弱函数的实现。弱函数就好像是一个可以被替换的“默认函数”。

值得一提的是,旧版本的编译器还可以使用如下方式的定义(仅声明无效)将一个对象定义为一个弱对象:

1

2

3

4

__weak void f(void)

{

//code

}

在 linux 的一些代码中,__weak 其实就是通过 __attribute__((weak))的重命名,两者等效。

弱引用

弱引用是在声明一个对象时,通过__attribute__ ((weakref()) 定义一个符号的引用关系。如下所示即定义 test_weakref() 函数弱引用 test_weak_ref() 函数。

1

static void test_weakref(void) __attribute__ ((weakref("test_weak_ref")));

弱引用是相对于强引用而言的。未通过 __attribute__ ((weakref()) 的符号和实现代码之间的关系是强引用。如下即为一个强引用函数。它直接给出了 函数 test_strong_ref(void) 的实现。

1

2

3

4

static void test_strong_ref(void)

{

    printf("this is a strong ref\r\n");

}

在编译程序的时候,我们可以直接使用 test_strong_ref(void) 而不必担心编译不通过。如果,我没有时间去实现 test_strong_ref(void) ,还想在程序里先使用该函数那该如何呢?(是的,就是想白嫖,不想实现,还想先在程序里使用这个函数)。

这个时候弱引用就派上用场了。可以先将该函数定义为弱引用插入到代码中,待后期有时间再慢慢优化代码实现这个函数完整的功能。下面结合测试进行说明。

测试代码1:

static void test_weakref(void) __attribute__ ((weakref("test_weak_ref")));void app_main(void){    printf("init done\r\n");    if (test_weakref) {        test_weakref();    } else {        printf("There is no weakref\r\n");    }}

测试结果:

There is no weakref

测试代码2:

void test_weak_ref(void){    printf("this is a weak ref\n");}static void test_weakref(void) __attribute__ ((weakref("test_weak_ref")));void app_main(void){    printf("init done\r\n");    if (test_weakref) {        test_weakref();    } else {        printf("There is no weakref\r\n");    }}

测试结果:

this is a weak ref

小结:强引用,在未定义该强引用的实现时,编译会报错误:未定义的引用。弱引用允许定义一个未实现(未实例化)的对象,这在编译的时候会将该对象处理成 NULL,编译器并不会报错。通过使用弱引用可以实现后期优化代码的功能。而避免改动使用该函数的地方。使用弱函数可以实现类似“钩子(hook)"函数的功能。

实际上,包括C、python、go 编程语言在内的很多语言 都有类似用法,本篇文章叙述的方法同样适用于这些语言的相关开发。

注意:弱引用仅在静态编译中有效,动态链接中可能无效。

总结:

弱符号、弱引用都是增强程序的可维护性的方法。弱符号通过可以被重定义的特性,实现可以被替换实现。弱引用通过可以暂时使用一个未定义的函数的功能,实现允许后期再实现该函数具体功能,而不必担心编译不通过。

为了方便理解,我们先预设一个应用场景:

我们编写了一个模拟IIC的驱动,希望它能够在不同的的平台运行,目标的平台就设为 stm32 标准库,stm32 HAL 库,stm32 LL 库,和 RT-Thread Driver 驱动库。

或许读者有疑惑,为什么同样是 stm32 ,却分成三个平台呢?这是因为从跨平台软件编写者的角度看,只要调用的库的 API 不一致,就和换一个不同的平台没有什么本质的差别,如果在代码中写死了 API 的调用,即使是同一个平台,仍然像多平台一样不能运行。

由此可以看出,跨平台的困难所在,也不是由硬件平台所导致的,而是由代码所依赖的 API 的不同导致的。同一个平台,如果依赖的 API 不同,代码就不能跨平台,同样地,不同的平台,如果依赖的 API 相同,也可以跨平台。

所以归根结底,是代码所依赖的 API 出现了不同,所以下文中所说的“平台”,实际上对应的是一套 API 。

我们继续说这个模拟 IIC 的驱动,模拟 IIC 驱动是使用 GPIO 的反转来模拟 IIC 协议,所以依赖了平台的 GPIO API,如果把调用 GPIO 的部分写死,那么换一个平台,就肯定不能在多个平台上运行。

下面我们开始讨论在多平台运行的解决方案。

我们先从最朴素简单的解决方案开始,然后逐步迭代到弱函数的方案,这样有两方面好处:

一是从简单的方案开始,循序渐进地介绍,可以降低阅读门槛。
二是可以带读者亲历一遍方案的演进过程,以及演进动因。

和给直接给结果相比,注重过程和动因更能够还原技术决策的真实过程。因为任何技术都是从简单朴素逐步演进而来的,如果直接给出最后的结果,会产生理解的断层,即使记住了几种技术的优劣,在新的场景中,面对更加多样化的实际问题也会难免乏力。

先从最朴素的方案讲起。

方案一、手动控制添加编译的 .c 文件

朴素的方案有很多,比如就是多搞几个版本的 .c 文件,比如SIMU_IIC_STM32_HAL.c ,SIMU_IIC_STM32_LL.c, SIMU_IIC_RTT.c 需要哪个就添加哪个进去编译不就完了嘛!

这种朴素的方案虽然看起来简单,但是,这几个文件中包含有共用的逻辑,例如模拟 IIC 的协议的实现,如何将 8bit 的数据依次发送,等等。

这些共用的逻辑,相当于在每个文件中都复制了一份,一旦修改到共用的逻辑,就要手动同步每个文件,这会导致代码冗余和维护难度的急剧增加,很容易出现人为失误。如果需要添加更多的平台支持,就需要再次复制修改代码。

另一个问题是,使用不同的编译工具链,添加编译文件的方式并不一样,例如,Visual Studio 和 MDK keil 通常是手动添加,而 CMake 通常直接添加目录或者通过文件后缀进行搜索。用户在使用不同的编译工具时,需要针对编译工具来分别处理,这也增加了维护的成本。

因此,我们需要寻找更加优雅的解决方案。

方案一中最核心的问题,是没有分离共用的逻辑,和各个平台的适配接口。
在通常的命名惯例中,共用的逻辑称为 Common,而各个平台的适配接口称为 Port。

在接下来的方案中,我们就会引入 Common 和 Port 分离的设计思想,Common 和 Port 的分离,使得对共用逻辑的修改和增强直接 “分发” 到了各个的 Port 中,而增添新的平台,不需要对 Common 做任何修改。

方案二、条件编译

一种更加优雅的解决方案是使用条件编译。条件编译是一种编译时根据条件选择编译代码的技术,可以通过编译器提供的宏定义和预处理指令来实现。

在我们的模拟 IIC 驱动中,可以直接编写 Common 部分,然后 Common 部分通过条件编译,可以根据平台的不同选择不同的 GPIO Port API。

例如,在 STM32 标准库中,可以使用 GPIO_SetPinMode 和 GPIO_WritePin 接口来模拟 IIC 协议,而在 STM32 HAL 库中,可以使用 HAL_GPIO_WritePin 和 HAL_GPIO_ReadPin 接口来模拟 IIC 协议。因此,在代码中可以使用如下的条件编译方式:

#if defined (USE_STM32_STD_LIB)    /* STM32 Standard Peripheral Library */    GPIO_SetPinMode(SDA_PORT, SDA_PIN, GPIO_MODE_OUTPUT_PP);    GPIO_SetPinMode(SCL_PORT, SCL_PIN, GPIO_MODE_OUTPUT_PP);    ...#elif defined (USE_STM32_HAL_LIB)    /* STM32 HAL Library */    HAL_GPIO_WritePin(SDA_PORT, SDA_PIN, GPIO_PIN_SET);    HAL_GPIO_WritePin(SCL_PORT, SCL_PIN, GPIO_PIN_SET);    ...#elif defined (USE_STM32_LL_LIB)    /* STM32 LL Library */    LL_GPIO_SetOutputPin(SDA_PORT, SDA_PIN);    LL_GPIO_SetOutputPin(SCL_PORT, SCL_PIN);    ...#elif defined (USE_RTT_DRIVER)    /* RT-Thread Driver */    rt_pin_mode(SDA_PIN, PIN_MODE_OUTPUT);    rt_pin_mode(SCL_PIN, PIN_MODE_OUTPUT);    ...#endif

这样,在编译代码时,我们可以通过宏定义来选择编译使用哪个平台的代码,

从而实现跨平台运行。

然而,这种条件编译方式还是有一些问题,如果我们需要添加新的平台支持,就需要添加新的宏定义和条件编译,而且需要修改模块的源码。

方案三 函数指针

我们可以进一步分离 Common 和 Port,将其放在不同的 .c 文件中,Common 通过函数调用的方式来访问 Port 提供的接口,这样可以更加灵活和方便地添加新的平台支持。

具体实现可以通过在 Common 中定义一些函数指针类型来实现。

例如,我们可以定义一个名为 IICOps 的结构体,其中包含了一些指向函数的指针,这些函数实现了具体的 IIC 操作。

在 Port 中,我们实现这些函数,并将其指针传递给 Common 中的 IICOps 结构体。

这样,在 Common 中就可以通过调用这些函数指针来访问 Port 提供的接口了。

这种方案的好处是,添加新的平台支持时,只需要实现相应的 Port 函数,并将其指针传递给 Common 中的结构体即可,不需要修改 Common 的源码。

同时,由于 Common 和 Port 分离,不同平台的适配代码可以相互独立,修改一方不会影响到另一方,从而减少了代码冗余和维护难度。

但是,使用函数指针有两个主要的缺点,一是函数指针本身需要占据内存,增加了内存的开销,二是函数指针需要在初始化时进行加载。

具体来说,函数指针的内存开销相对于代码本身并不大,通常可以忽略不计,但在嵌入式系统中,资源有限,内存开销需要更加注意。

而函数指针的初始化需要在程序启动时进行,这也会对程序的启动时间产生一定的影响。此外,函数指针的使用可能会导致一定的运行时开销,需要在程序性能和资源利用方面做出权衡。

另外,函数指针的使用需要程序员有一定的技术水平和经验,需要合理地进行函数指针的定义、传递和调用等操作,避免出现潜在的错误和安全问题。

总的来说,函数指针是一种比较灵活和方便的方式,可以帮助我们实现代码的跨平台支持,但需要在实际应用中仔细考虑其使用场景和影响,做出合理的决策。

方案四、Common 中声明,Prot 中实现

在这种方案中,我们仍然将 Common 和 Port 分离,但是我们不再使用函数指针来访问 Port 中的接口,而是将其定义为 extern 声明,由 Port 来实现具体的函数。

具体实现可以通过在 Common 中定义一些 extern 声明的函数,这些函数实现了具体的 IIC 操作,但是并不在 Common 中实现具体的代码逻辑,而是在 Port 中实现。

在 Port 中,我们实现这些函数,并将其声明为 extern,然后在编译时链接到 Common 中。

这样,在 Common 中就可以通过调用这些函数来访问 Port 提供的接口了。

这种方案的好处是,添加新的平台支持时,只需要实现相应的 Port 函数,并在编译时链接到 Common 中即可,不需要修改 Common 的源码。

这样 Port 的实现函数的挂载就提前到了编译阶段,避免了运行时挂载函数指针的复杂性和容易出错问题。以及避免了函数指针的内存占用。

但是,和 IIC 的例子不同的是,在一些更实际的项目中,随着软件复杂度的提升, Common 中使用的 Port 函数数量会快速膨胀,这时,每个 Port 函数都必须要实现,即使这个功能非常的冷门,这样 Common 中每增加一个 Port 的依赖,都要求所有的 Port 进行及时的跟进,否则整个项目都无法编译通过。

接下来,就要有请 weak (弱函数)机制出马了

但方案四的缺点在于,在一些更实际的项目中,随着软件复杂度的提升,Common 中使用的 Port 函数数量会快速膨胀,这时,每个 Port 函数都必须要实现,即使这个功能非常的冷门,这样 Common 中每增加一个 Port 的依赖,都要求所有的 Port 进行及时的跟进,否则整个项目都无法编译通过。

方案五 弱函数

为了解决这个问题,可以使用 weak (弱函数)机制,将所有的 Port 函数都定义为 weak 函数。

weak 函数是一种特殊的函数类型,带 weak 的函数和不带 weak 的函数可以同时存在,如果有不带 weak 的函数,就会优先链接不带 weak 的实现,如果没有找到不带 weak 的实现函数,就会使用 weak 函数作为默认的实现。

即,在使用弱函数时,如果找到多个实现,链接器会选择优先级最高的实现。

在 C 语言中,可以使用 attribute((weak)) 来声明一个函数为弱函数。例如:

attribute((weak)) void port_func(){    // 默认实现}attribute((weak)) void port_func()

而在 Port 中,只需要实现需要的函数即可,如果某些函数不需要实现,可以不用管它,因为在链接时会使用 Common 中的默认实现。

这样在编译时如果没有找到相应的实现函数,就会使用默认的实现,而不会导致编译错误。

而在 Port 中,只需要实现需要的函数即可,如果某些函数不需要实现,可以不用管它,因为在编译时会使用 Common 中的默认实现。

这样,在添加新的平台支持时,只需要实现需要的函数,而不用实现所有的函数,大大简化了开发的难度和工作量。同时,也避免了函数指针的内存占用和运行时挂载函数指针的复杂性和容易出错问题。

弱函数的多编译器支持

在不同的平台上, weak 的声明方法也会有所不同,因此需要自己定义一个 weak 声明,例如 MY_WEAK,来支持不同的平台:

/* Compiler */#if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 5000000) /* ARM Compiler \                                                              */#define MY_WEAK __attribute__((weak))#elif defined(__IAR_SYSTEMS_ICC__) /* for IAR Compiler */#define MY_WEAK __weak#elif defined(__MINGW32__) /* MINGW32 Compiler */#define MY_WEAK#elif defined(__GNUC__) /* GNU GCC Compiler */#define MY_WEAK __attribute__((weak))#endif/* default MY_WEAK */#ifndef MY_WEAK#define MY_WEAK#endif

可以看到,在不同的编译器下,weak 有不同的写法,上面的这些定义包含了对 armcc5、 armclang、IAR、GCC 的支持。

然而,弱函数的方案在一些平台有一些明显的缺陷,例如,MSVC编译器是微软公司开发的C/C++编译器,在Windows操作系统下被广泛使用。与GCC和Clang等主流编译器相比,MSVC对于弱函数的支持不太完善。

MSVC 中,可以通过使用#pragma weak来声明弱函数,但是这个特性只能在 x86 和 x64 平台下使用,而在 ARM 平台下是不支持的。

此外,在一些版本的MSVC编译器中,#pragma weak 的功能也存在一些限制和bug,所以一般在 MSVC 中直接取消 weak 还会更实际一些。

用弱函数对 Port 进行分类

weak 的引入使得我们的 Port 可以根据实际的需求进行划分,而不是一股脑地必须实现所有的 Port 函数。

引入了 weak 之后,我们就有条件将 Port 划分为以下的几种:

1.核心且无默认实现的 Port

这属于必须实现的 Port,缺乏这个 Port,模块的核心功能就运行不起来。例如模拟 IIC中对 IO 的操作 Port 函数,这种 Port 用不用 weak 的区别不大,属于最硬的骨头,在设计软件时应当注意尽可能地减少这种 Port。

在设计软件时,可以直接取消这类 Port 的弱定义,让编译器在编译时就抛出错误。既然缺少了这类 Port 系统的核心功能就无法工作,那么编译通过了也没有什么意义。

2. 核心且有默认实现的 Port

这类 Port 可以直接定义一个默认实现,在大多数情况下,用户就可以不用管这个 Port 了,而在有定制需求的场合下,又可以灵活地定制。

例如弱定义一个 port_printf 用来支持跨平台软件的打印输出,默认是直接使用平台的 vprintf,对于大多数的用户来说,只需要打印到平台自带的 printf 即可,因此对于大多数用户来说,这个 Port 不用实现,就能正常使用系统了。

attribute((weak)) void port_printf(char* fmt, ...) {  va_list args;  va_start(args, fmt);  vprintf(fmt, args);  va_end(args);}

而有定制需求的用户可以通过自己重写 port_printf() 来打印到其他的地方(比如输出到 log,或者输出到其他串口)。

在 printf 的例子中,我们默认了 printf 是所有的平台都提供了的,对 printf 进行这种假设是合理的,因为它是 libc 的标准函数。

然而,有些 Port 虽然有默认实现,却不能支持所有的平台,例如线程操作的 Port,在常见的平台中,如 linux、RT-Thread、FreeRTOS ,我们知道如何写默认实现,但是这些实现又不通用,这时我们可以在默认实现中结合条件编译,为常见的平台提供默认实现,例如:

attribute((weak)) void port_thread_start(port_thread_t* thread) {#ifdef __linux    pthread_mutex_lock(&(thread->mutex));    pthread_cond_signal(&(thread->cond));    pthread_mutex_unlock(&(thread->mutex));#elif USE_FREERTOS    vTaskResume(thread->thread);#else    #error "port_thread_start() 需要用户实现"#endif}

这个例子中为 linux 和 FreeRTOS 提供了默认的线程启动 Port 的实现,使用 linux 或者 FreeRTOS 的用户可以通过条件编译来直接使用默认实现。

而既不用 linux 也不用 FreeRTOS 的用户,则会在编译时遇到 #error,这提示他们要自己实现 port_thread_start()。

这种写法还有一种好处,就是在不支持 weak 的平台,例如 MSVC,就可以通过 _WIN32 条件编译来进行跨平台支持。

3. 边缘但无默认实现的 Port

还有一类 Port,它们比较冷门,只有部分用户会使用到,但是又难以提供默认的实现。例如用 port_reboot() 来重启硬件,每个硬件平台重启硬件的 API 都是不同的,我们无法提供一个默认的实现。

但是,没有这个 Port,也不影响系统的核心功能,只是在某些时候(例如开启了超时自动重启功能),又有这个 Port才行,这样的 Port 就属于是边缘但无默认实现的 Port。

这时,就可以选择将 Port 缺失的错误延后到运行时,具体在操作时,就可以编写一个 weak 的实现,而这个实现中抛出一个运行时错误,例如:

attribute((weak)) void port_reboot(void){    printf("Error: port_reboot() 需要用户实现\r\n");    while(1);}

这样,只要不用到这个 Port,都可以编译通过且顺利运行,只有实际用到时,才会在运行时报错。

weak 在 GCC 链接静态库时的问题

这里我想特别提示一种常见的 weak 失效的问题,这种问题目前我只发现在 gcc 链接静态库时包含 weak 会出现。

gcc 在链接静态库时,默认的行为是只要找到第一个(不管是不是弱符号),就会将其链接,然后停止继续寻找,这样一来,如果你的 weak 是被第一个找到的,那么强定义的函数就失效了。

这个问题有多种解决方案,我这里只提示一种,有更好的方案可以进qq交流群:577623681 大家一起讨论。

解决方案:使用 "-Wl,--whole-archive" 选项来解决。当使用这个选项时,链接器将整个库文件都包含在链接输出文件中,而不考虑这些库文件是否实际上被使用了。这样就可以保证弱符号在整个库中得到了正确的链接,并且在可执行文件或其他库中保持有效。

需要注意的是,当使用 "-Wl,--whole-archive" 选项时,可能会将一些不必要的库文件链接到最终的可执行文件或库中,这可能会增加最终文件的大小。因此,应该仅在必要时使用这个选项。


定期以通俗易懂的方式分享嵌入式知识,关注公众号,加星标,每天进步一点点。


声明:

本号原创、转载的文章、图片等版权归原作者所有,如有侵权,请联系删除。

关注、点赞、在看、转发,支持优质内容! 

评论
  • 2024年是很平淡的一年,能保住饭碗就是万幸了,公司业绩不好,跳槽又不敢跳,还有一个原因就是老板对我们这些员工还是很好的,碍于人情也不能在公司困难时去雪上加霜。在工作其间遇到的大问题没有,小问题还是有不少,这里就举一两个来说一下。第一个就是,先看下下面的这个封装,你能猜出它的引脚间距是多少吗?这种排线座比较常规的是0.6mm间距(即排线是0.3mm间距)的,而这个规格也是我们用得最多的,所以我们按惯性思维来看的话,就会认为这个座子就是0.6mm间距的,这样往往就不会去细看规格书了,所以这次的运气
    wuliangu 2025-01-21 00:15 278浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 138浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 143浏览
  •  万万没想到!科幻电影中的人形机器人,正在一步步走进我们人类的日常生活中来了。1月17日,乐聚将第100台全尺寸人形机器人交付北汽越野车,再次吹响了人形机器人疯狂进厂打工的号角。无独有尔,银河通用机器人作为一家成立不到两年时间的创业公司,在短短一年多时间内推出革命性的第一代产品Galbot G1,这是一款轮式、双臂、身体可折叠的人形机器人,得到了美团战投、经纬创投、IDG资本等众多投资方的认可。作为一家成立仅仅只有两年多时间的企业,智元机器人也把机器人从梦想带进了现实。2024年8月1
    刘旷 2025-01-21 11:15 604浏览
  • 本文介绍瑞芯微开发板/主板Android配置APK默认开启性能模式方法,开启性能模式后,APK的CPU使用优先级会有所提高。触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。源码修改修改源码根目录下文件device/rockchip/rk3562/package_performance.xml并添加以下内容,注意"+"号为添加内容,"com.tencent.mm"为AP
    Industio_触觉智能 2025-01-17 14:09 180浏览
  • 数字隔离芯片是一种实现电气隔离功能的集成电路,在工业自动化、汽车电子、光伏储能与电力通信等领域的电气系统中发挥着至关重要的作用。其不仅可令高、低压系统之间相互独立,提高低压系统的抗干扰能力,同时还可确保高、低压系统之间的安全交互,使系统稳定工作,并避免操作者遭受来自高压系统的电击伤害。典型数字隔离芯片的简化原理图值得一提的是,数字隔离芯片历经多年发展,其应用范围已十分广泛,凡涉及到在高、低压系统之间进行信号传输的场景中基本都需要应用到此种芯片。那么,电气工程师在进行电路设计时到底该如何评估选择一
    华普微HOPERF 2025-01-20 16:50 109浏览
  • Ubuntu20.04默认情况下为root账号自动登录,本文介绍如何取消root账号自动登录,改为通过输入账号密码登录,使用触觉智能EVB3568鸿蒙开发板演示,搭载瑞芯微RK3568,四核A55处理器,主频2.0Ghz,1T算力NPU;支持OpenHarmony5.0及Linux、Android等操作系统,接口丰富,开发评估快人一步!添加新账号1、使用adduser命令来添加新用户,用户名以industio为例,系统会提示设置密码以及其他信息,您可以根据需要填写或跳过,命令如下:root@id
    Industio_触觉智能 2025-01-17 14:14 137浏览
  • 嘿,咱来聊聊RISC-V MCU技术哈。 这RISC-V MCU技术呢,简单来说就是基于一个叫RISC-V的指令集架构做出的微控制器技术。RISC-V这个啊,2010年的时候,是加州大学伯克利分校的研究团队弄出来的,目的就是想搞个新的、开放的指令集架构,能跟上现代计算的需要。到了2015年,专门成立了个RISC-V基金会,让这个架构更标准,也更好地推广开了。这几年啊,这个RISC-V的生态系统发展得可快了,好多公司和机构都加入了RISC-V International,还推出了不少RISC-V
    丙丁先生 2025-01-21 12:10 360浏览
  • 日前,商务部等部门办公厅印发《手机、平板、智能手表(手环)购新补贴实施方案》明确,个人消费者购买手机、平板、智能手表(手环)3类数码产品(单件销售价格不超过6000元),可享受购新补贴。每人每类可补贴1件,每件补贴比例为减去生产、流通环节及移动运营商所有优惠后最终销售价格的15%,每件最高不超过500元。目前,京东已经做好了承接手机、平板等数码产品国补优惠的落地准备工作,未来随着各省市关于手机、平板等品类的国补开启,京东将第一时间率先上线,满足消费者的换新升级需求。为保障国补的真实有效发放,基于
    华尔街科技眼 2025-01-17 10:44 230浏览
  •  光伏及击穿,都可视之为 复合的逆过程,但是,复合、光伏与击穿,不单是进程的方向相反,偏置状态也不一样,复合的工况,是正偏,光伏是零偏,击穿与漂移则是反偏,光伏的能源是外来的,而击穿消耗的是结区自身和电源的能量,漂移的载流子是 客席载流子,须借外延层才能引入,客席载流子 不受反偏PN结的空乏区阻碍,能漂不能漂,只取决于反偏PN结是否处于外延层的「射程」范围,而穿通的成因,则是因耗尽层的过度扩张,致使跟 端子、外延层或其他空乏区 碰触,当耗尽层融通,耐压 (反向阻断能力) 即告彻底丧失,
    MrCU204 2025-01-17 11:30 207浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 115浏览
  • 现在为止,我们已经完成了Purple Pi OH主板的串口调试和部分配件的连接,接下来,让我们趁热打铁,完成剩余配件的连接!注:配件连接前请断开主板所有供电,避免敏感电路损坏!1.1 耳机接口主板有一路OTMP 标准四节耳机座J6,具备进行音频输出及录音功能,接入耳机后声音将优先从耳机输出,如下图所示:1.21.2 相机接口MIPI CSI 接口如上图所示,支持OV5648 和OV8858 摄像头模组。接入摄像头模组后,使用系统相机软件打开相机拍照和录像,如下图所示:1.3 以太网接口主板有一路
    Industio_触觉智能 2025-01-20 11:04 184浏览
  • 故障现象 一辆2007款日产天籁车,搭载VQ23发动机(气缸编号如图1所示,点火顺序为1-2-3-4-5-6),累计行驶里程约为21万km。车主反映,该车起步加速时偶尔抖动,且行驶中加速无力。 图1 VQ23发动机的气缸编号 故障诊断接车后试车,发动机怠速运转平稳,但只要换挡起步,稍微踩下一点加速踏板,就能感觉到车身明显抖动。用故障检测仪检测,发动机控制模块(ECM)无故障代码存储,且无失火数据流。用虹科Pico汽车示波器测量气缸1点火信号(COP点火信号)和曲轴位置传感器信
    虹科Pico汽车示波器 2025-01-23 10:46 42浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦