xenomai内核解析--双核系统调用(二)--应用如何区分xenomai/linux系统调用或服务

原创 Linux阅码场 2022-05-10 08:00

阅码场Ftrace公开课火热报名中:Ftrace公开课:学优化,学内核(限50人)。课程报名累计30+,课程报名即将截止,报名咨询客服(小月微信:linuxer2016)。



作者简介

顺刚(网名:沐多),一线码农,从事工控行业,目前在一家工业自动化公司从事工业实时现场总线开发工作,喜欢钻研Linux内核及xenomai,个人博客 wsg1100,欢迎大家关注!

一、如何区分xenomai、linux系统调用/服务

1. 引出问题

上一篇文章xenomai内核解析--双核系统调用(一)以X86处理器为例,分析了xenomai内核系统调用的流程,读了以后可能会觉得缺了点什么,你可能会有以下疑问:

  1. 系统中的两个内核都是POSIX接口实现系统调用,那么我们用POSIX接口写了一个应用程序,怎样知道它调用的内核,或者如何区分这个应用是cobalt内核的应用,而不是普通linux应用?

  2. 对于同一个POSIX接口应用程序,可能既需要xenomai内核提供服务(xenomai 系统调用),又需要调用linux内核提供服务(linux内核系统调用),或者既有libcobalt,又有glibc库,他们是如何实现和区分的?



2. 编译链接

对于问题1,答案是:由编译的链接过程决定,链接的库不同当然执行的也就不同,如果普通编译,则该应用编译后是一个普通linux运用。如果要编译为xenomai应用,则需要链接到xenomai库。但是我们应用程序调用的代码函数symbol完全一样,我们会有疑惑xenomai是如何狸猫换太子的?首先链接是通过符号表(symbol)来链接的,当然也就要从代码符号(symbol)入手,首先来看一个常用的编译xenomai 应用的makefile:

XENO_CONFIG := /usr/xenomai/bin/xeno-config
PROJPATH = .
CFLAGS := $(shell $(XENO_CONFIG) --posix --alchemy --cflags)LDFLAGS := $(shell $(XENO_CONFIG) --posix --alchemy --ldflags)INCFLAGS= -I$(PROJPATH)/include/

EXECUTABLE := rt-app
src = $(wildcard ./*.c)obj = $(patsubst %.c, %.o, $(src))
all: $(EXECUTABLE)
$(EXECUTABLE): $(obj) $(CC) -g -o $@ $^ $(INCFLAGS) $(CFLAGS) $(LDFLAGS)
%.o:%.c $(CC) -g -o $@ -c $< $(INCFLAGS) $(CFLAGS) $(LDFLAGS)
.PHONY: cleanclean: rm -f $(EXECUTABLE) $(obj)

其中最重要的就是编译时需要 xeno-config来生成gcc参数。xeno-config在我们编译安装xenomai库后,默认放在 /usr/bin/xeno-config

$ /usr/bin/xeno-config --helpxeno-config --verbose        --core=cobalt        --version="3.1"        --cc="gcc"        --ccld="/usr/bin/wrap-link.sh gcc"        --arch="x86"        --prefix="/usr"        --library-dir="/usr/lib"Usage xeno-config OPTIONSOptions :        --help        --v,--verbose        --version        --cc        --ccld        --arch        --prefix        --[skin=]posix|vxworks|psos|alchemy|rtdm|smokey|cobalt        --auto-init|auto-init-solib|no-auto-init        --mode-check|no-mode-check        --cflags        --ldflags        --lib*-dir|libdir|user-libdir        --core        --info        --compat

例如编译一个POSIX接口的实时应用,参数 --cflags表示编译,指定接口(skin) --posix,就能得到编译该程序的gcc参数了,看着没什么特别的:

$ /usr/bin/xeno-config --posix --cflags-I/usr/include/xenomai/cobalt -I/usr/include/xenomai -D_GNU_SOURCE -D_REENTRANT -fasynchronous-unwind-tables -D__COBALT__ -D__COBALT_WRAP__

再看链接--ldflags表示链接,如下得到链接参数:

$ /usr/bin/xeno-config --ldflags --posix-Wl,--no-as-needed -Wl,@/usr/lib/cobalt.wrappers -Wl,@/usr/lib/modechk.wrappers  /usr/lib/xenomai/bootstrap.o -Wl,--wrap=main -Wl,--dynamic-list=/usr/lib/dynlist.ld -L/usr/lib -lcobalt -lmodechk -lpthread -lrt

这一看就多出不少东西,重点就在这 cobalt.wrappersmodechk.wrappers,两个文件的内容如下:

...--wrap open--wrap open64--wrap socket--wrap close--wrap ioctl--wrap read....--wrap recv--wrap send--wrap getsockopt--wrap setsockop...

里面是一些posix系统调用,前面的 --wrap是什么作用?这是执行链接过程的程序 ld的一个参数,通过 man ld可以找到该参数的说明:

--wrap symbol           Use a wrapper function for symbol.  Any undefined reference to symbol will be resolved to "__wrap_symbol".  Any undefined reference to "__real_symbol" will be resolved to symbol.
This can be used to provide a wrapper for a system function. The wrapper function should be called "__wrap_symbol". If it wishes to call the system function, it should call "__real_symbol".
Here is a trivial example:
void * __wrap_malloc (size_t c) { printf ("malloc called with %zu\n", c); return __real_malloc (c); }
If you link other code with this file using --wrap malloc, then all calls to "malloc" will call the function "__wrap_malloc" instead. The call to "__real_malloc" in "__wrap_malloc" will call the real "malloc" function.
You may wish to provide a "__real_malloc" function as well, so that links without the --wrap option will succeed. If you do this, you should not put the definition of "__real_malloc" in the same file as "__wrap_malloc"; if you do, the assembler may resolve the call before the linker has a chance to wrap it to "malloc".

简单来说就是:任何 对 symbol未定义 的 引用 (undefined reference) 将 解析为 __wrap_symbol. 任何 对 __real_symbol未定义 的 引用 将 解析为 symbol。意思就是我们代码里使用到且是参数 --wrap指定的符号 symbol(即文件里的内容),链接的时候就认为它是 __wrap_symbol,比如我们代码用到了 open()在链接的时候是与库中的 __wrap_open()链接的, __wrap_open就是在xenomai 实时库libcobalt中实现,现在我们明白我们的posix函数如何和xenomai对接上了。

对于 xeno-config的其他更多参数可通过xenomai Manual Page了解。

这样就将POSIX接口源码编译成一个xenomai可执行程序了。

我们还有另一个问题,既然链接到了libcobalt,但我们是要Linux来提供服务,又是怎么让Linux来提供服务的呢?看libcobalt具体实现可以知道答案。

3. libcobalt中的实现

下面来看问题2,既然我们已将一个接口链接到实时内核库libcobalt,当然由实时内核库libcobalt来区分该发起linux内核调用还是xenomai内核系统。与上一篇文章一样,以一个POSIX接口 pthread_cretate()来解析libcobalt中的实现。

xenomai线程的创建流程比较复杂,需要先让linux创建普通线程,然后再由xenomai创建该线程的shadow 线程,即xenomai调度的实时线程,很符合我们上面的提出的问题2。说到这先简答介绍一下xenomai实时线程的创建,详细的创建流程后面会写专门写一篇文章解析,敬请期待。

pthread_cretate()不是一个系统调用,由NPTL(Native POSIX Threads Library)实现(NPTL是Linux 线程实现的现代版,由UlrichDrepper 和Ingo Molnar 开发,以取代LinuxThreads),NPTL负责一个用户线程的用户空间栈创建、内存分配、初始化等工作,与linux内核配合完成线程的创建。每一线程映射一个单独的内核调度实体(KSE,Kernel Scheduling Entity)。内核分别对每个线程做调度处理。线程同步操作通过内核系统调用实现。

xenomai coblat作为实时任务的调度器,每个实时线程需要对应到 coblat调度实体,如果要创建实时线程就需要像linux那样NPTL与linux 内核深度结合,那么coblat与libcoblat实现将会变得很复杂。在这里,xenomai使用了一种方式,由NPTL方式去完成实时线程实体的创建(linux部分),在普通线程的基础上附加一些属性,对应到xenomai cobalt内核实体时能被实时内核cobalt调度。

所以libcoblat库中的实时线程创建函数 pthread_cretate最后还是需要使用 glibc的 pthread_cretate函数,xenomai只是去扩展glibc pthread_cretate创建的线程,使这个线程可以在实时内核cobalt调度。

pthread_cretate()在libcobalt中pthread.h文件中定义如下:

COBALT_DECL(int, pthread_create(pthread_t *ptid_r,        const pthread_attr_t *attr,        void *(*start) (void *),        void *arg));

COBALT_DECL宏在 wrappers.h中如下,展开上面宏,会为 pthread_create()生成三个类型函数:

#define __WRAP(call)    __wrap_ ## call#define __STD(call)    __real_ ## call#define __COBALT(call)    __cobalt_ ## call#define __RT(call)    __COBALT(call)#define COBALT_DECL(T, P)  \  __typeof__(T) __RT(P);  \  __typeof__(T) __STD(P); \  __typeof__(T) __WRAP(P)  int __cobalt_pthread_create(pthread_t *ptid_r,        const pthread_attr_t *attr,        void *(*start) (void *),        void *arg);int __wrap_pthread_create(pthread_t *ptid_r,        const pthread_attr_t *attr,        void *(*start) (void *),        void *arg);int __real_pthread_create(pthread_t *ptid_r,        const pthread_attr_t *attr,        void *(*start) (void *),        void *arg);

声明 pthread_create()函数的这三个宏意思为:

__RT(P)__cobalt_pthread_create 明确表示Cobalt实现的POSIX函数

__COBALT(P):与 __RT()等效。

__STD(P)__real_pthread_create表示这是原始的POSIX函数(Linux glibc实现),cobalt库内部通过它来表示调用原始的POSIX函数(glibc NPTL).

__WRAP(P): __wrap_pthread_create是 __cobalt_pthread_create 的弱别名,如果编译器编译时知道有该函数其它的实现,该函数就会被覆盖。


主要关注前面两个,对于最后一个宏,如果外部库想覆盖已有的函数,应提供其自己的 __wrap_pthread_create()实现,来覆盖Cobalt实现的 pthread_create()版本。原始的Cobalt实现仍可以引用为 __COBALT(pthread_create)。由宏COBALT_IMPL来定义:

    #define COBALT_IMPL(T, I, A)                \__typeof__(T) __wrap_ ## I A __attribute__((alias("__cobalt_" __stringify(I)), weak));  \__typeof__(T) __cobalt_ ## I A


最后cobalt库函数 pthread_create实现主体为

COBALT_IMPL(int, pthread_create, (pthread_t *ptid_r,          const pthread_attr_t *attr,          void *(*start) (void *), void *arg)){  pthread_attr_ex_t attr_ex;  ......  return pthread_create_ex(ptid_r, &attr_ex, start, arg);}

COBALT_IMPL定义了 __cobalt_pthread_create函数及该函数的一个弱别名 __wrap_pthread_create,调用这两个函数执行的是同一个函数体。

对于 NPTL函数 pthread_create,在Cobalt库里使用 __STD()修饰,展开后即 __real_pthread_create(),其实只是NPTL pthread_create()的封装, __real_pthread_create()会直接调用 NPTL pthread_create,在lib\cobalt\wrappers.c实现如下:

/* pthread */__weakint __real_pthread_create(pthread_t *ptid_r,        const pthread_attr_t * attr,        void *(*start) (void *), void *arg){  return pthread_create(ptid_r, attr, start, arg);}

它调用的就是glibc中的 pthread_create函数.同样我们接着 __cobalt_pthread_create()看哪里调用的.

int pthread_create_ex(pthread_t *ptid_r,          const pthread_attr_ex_t *attr_ex,          void *(*start) (void *), void *arg){  ......  __STD(sem_init(&iargs.sync, 0, 0));
ret = __STD(pthread_create(&lptid, &attr, cobalt_thread_trampoline, &iargs));/*__STD 调用标准库的函数*/ if (ret) { __STD(sem_destroy(&iargs.sync)); return ret; }
__STD(clock_gettime(CLOCK_REALTIME, &timeout)); .....}

下面再看另一个例子,实时任务在代码中使用了linux的网络套接字(xenomai任务也是一个linux任务,也可以使用linux来提供服务,只不过会影响实时性),有以下代码,:

....    int sockfd,ret;                                                struct sockaddr_in addr;                               sockfd = socket(PF_INET, SOCK_STREAM, 0);            .....    bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in));        ....

该代码编译时链接到了libcobalt,socket()函数即libcobalt中的 __cobalt_socket(),其定义在xenomai- 3.x.x\lib\cobalt\rtdm.c,如下:

COBALT_IMPL(int, socket, (int protocol_family, int socket_type, int protocol)){  int s;  s = XENOMAI_SYSCALL3(sc_cobalt_socket, protocol_family,           socket_type, protocol);  if (s < 0) {    s = __STD(socket(protocol_family, socket_type, protocol));  }  return s;}

可以看到,libcobalt中的函数会先尝试调用实时内核cobalt的系统调用, 当cobalt系统调用不成功的时候才继续尝试通过 __STD()宏来调用linux系统调用(cobalt内核根据socket协议类型参数 PF_INET, SOCK_STREAM判断),这样就有效的分清了是linux系统调用还是xenomai系统调用,这也是所有libcobalt实现的posix都链接到libobalt库的原因。

一般情况下,可以直接在代码中使用 __STD()宏指明我们调用的linux内核的服务,修改如下:

....    int sockfd,ret;                                                struct sockaddr_in addr;                               sockfd = __STD(socket(PF_INET, SOCK_STREAM, 0));            .....     __STD(bind(sockfd, (struct sockaddr *)&my_addr,sizeof(struct sockaddr_in)));....

现在一切都明了了,一个函数编译时通过参数链接到xenomai库后,通过 __STD()宏来表示使用linux接口。

4. 总结

  • 在实时程序或实时库libcobalt中,通过 __STD()宏来表示使用linux接口。

  • 对于一个未指明的接口,libcobalt会先尝试发起xenomai系统调用,不成功会接着尝试linux内核系统调用或者调用glibc函数。

  • 如果我们向libcobalt库中新添加一个libcobalt库中没有的自定义POSIX函数/系统调用时,一定要在内部先尝试发起xenomai系统调用,不成功时接着尝试linux内核系统调用,此外还必须将该接口添加到文件 xenomai\lib\cobalt\cobalt.wrappers中,这样才能正确链接,否则编译后的应用还是原来的。

二、 如何为xenomai添加一个系统调用

1. 添加系统调用

有的时候我们需要给系统添加一个特殊功能的系统调用,比如我们在对xenomai做benchmark测试的时候,需要测试每个系统服务操作系统需要消耗多长时间,比如测量获取信号量操作系统的耗时。我们知道中断优先级最高,会强占前台的操作系统和应用,为了更准确的测量获取信号量时操作系统的耗时,这里需要将这种情况下的结果丢弃,我们就需要一个获取xenomai中断次数的系统调用。

假设该系统没有任何实时驱动运行,且设置了xenomai.supportedcpus和linux irqaffinity,supportedcpus启用tickless,下面给xenomai添加一个系统调用 get_timer_hits(),用于获取应用程序运行CPU的定时器中断产生的次数(类似于VxWorks里的tickGet(),VxWorks是采用周期tick的方式来驱动系统运作,tickGet()获取的也就是tick定时器中断的次数)。以该系统调用来举例如何为xenomai添加一个实时系统调用。

在前两篇文中说到,xenomai每个系统的系统系统调用号在 \cobalt\uapi\syscall.h中:

#define sc_cobalt_bind         0#define sc_cobalt_thread_create      1#define sc_cobalt_thread_getpid          2......#define sc_cobalt_extend           96

在此添加 sc_cobalt_get_timer_hits的系统,为了避免与xenomai系统调用冲突(xenomai官方添加的系统调用号从小到大),那我们就从最后一个系统调用添加,即127号系统调用,如下。

#define sc_cobalt_bind             0#define sc_cobalt_thread_create            1#define sc_cobalt_thread_getpid            2......#define sc_cobalt_extend            96#define sc_cobalt_ftrace_puts                   97#define sc_cobalt_recvmmsg                      98#define sc_cobalt_sendmmsg                      99#define sc_cobalt_clock_adjtime                 100#define sc_cobalt_thread_setschedprio           101#define sc_cobalt_get_timer_hits            127#define __NR_COBALT_SYSCALLS                    128 /* Power of 2 */

先确定一下我们这个函数的API形式,由于是一个非标准的形式,这里表示如下:

int get_timer_hits(unsigned long *u_tick);

参数为保存hits的变量地址;

返回值:成功0;出错 <0;

系统调用的头文件,然后添加一个系统调用的声明,觉得它和clock相关,那就放在 kernel\xenomai\posix\clock.h中吧。

#includeCOBALT_SYSCALL_DECL(get_timer_hits,  (unsigned long __user *u_tick));

然后是该函数的内核实现,放在 /kernel\xenomai\posix\clock.c,如下:

COBALT_SYSCALL(get_timer_hits, primary,          (unsigned long __user *u_tick)){        struct xnthread *thread;    unsigned long tick;    int cpu;    int ret = 0;       unsigned int irq;    thread = xnthread_current();    if (thread == NULL)                return -EPERM;    /*得到当前任务CPU号*/    cpu = xnsched_cpu(thread->sched);    irq = per_cpu(ipipe_percpu.hrtimer_irq, cpu);    /*读取该CPU中断计数*/    tick = __ipipe_cpudata_irq_hits(&xnsched_realtime_domain, cpu,                         irq);    if (cobalt_copy_to_user(u_tick, &tick, sizeof(tick)))                return -EFAULT;    return ret;}

需要注意的是该系统调用的权限,这里使用 primary,表示只有cobalt上下文(实时线程)才能调用。

修改完成后重新编译内核并安装。

2.Cobalt库添加接口

在前两篇文中说到,xenomai系统调用由libcobalt发起,所以修改应用库来添加该函数接口,添加声明 include\cobalt\time.h

COBALT_DECL(int,get_timer_hits(unsigned long tick));

xenomai3.x.x\lib\cobalt\clock.c添加该接口定义:

COBALT_IMPL(int,get_timer_hits, (unsigned long * tick)){        int ret;        ret = -XENOMAI_SYSCALL1(sc_cobalt_get_tick,                                tick);        return ret;}

因为该系统调用和posix接口没有符号重名,不需要修改wrappers文件,完成上述步骤后重新编译并安装xenomai库

3. 应用使用

由于我们添加 get_timer_hits()系统调用时,指定了系统调用的权限为primary,这里创建一个实时任务,使用宏 __RT()指定链接到libcobalt库。

#include #include #include #include #include #include #include #include #include #include #include #include #include 
#define PRIO 50
void test(void *cookie){ unsigned long tick; int ret; ret = __RT(get_timer_hits(&tick)); if (ret){ fprintf(stderr, "%s: failed to get_tick,%s\n", __func__,strerror(-ret)); return ret; } fprintf(stdout,"timer_hits:%ld\n",tick); /*....*/ return 0;}
int main(int argc, char *const *argv){ struct sigaction sa __attribute__((unused)); int sig, cpu = 0; char sem_name[16]; sigset_t mask; RT_TASK task; int ret; sigemptyset(&mask); sigaddset(&mask, SIGINT); sigaddset(&mask, SIGTERM); sigaddset(&mask, SIGHUP); sigaddset(&mask, SIGALRM); pthread_sigmask(SIG_BLOCK, &mask, NULL); setlinebuf(stdout); ret = rt_task_spawn(&task, "test_task", 0, PRIO, T_JOINABLE, test, NULL); if (ret){ fprintf(stderr, "%s: failed to create task,%s\n", __func__,strerror(-ret)); return ret; } __STD(sigwait(&mask, &sig)); rt_task_join(&task); rt_task_delete(&task); return 0;}

编译Makefile:

XENO_CONFIG := /usr/xenomai/bin/xeno-config
PROJPATH = .
CFLAGS := $(shell $(XENO_CONFIG) --posix --alchemy --cflags)LDFLAGS := $(shell $(XENO_CONFIG) --posix --alchemy --ldflags)INCFLAGS= -I$(PROJPATH)/include/

EXECUTABLE := get-timer-hits
src = $(wildcard ./*.c)obj = $(patsubst %.c, %.o, $(src))
all: $(EXECUTABLE)
$(EXECUTABLE): $(obj) $(CC) -g -o $@ $^ $(INCFLAGS) $(CFLAGS) $(LDFLAGS)
%.o:%.c $(CC) -g -o $@ -c $< $(INCFLAGS) $(CFLAGS) $(LDFLAGS)
.PHONY: cleanclean: rm -f $(EXECUTABLE) $(obj)

运行结果:

$./get-timer-hitstimer_hits:3

可以看到,虽然系统已经启动十几分钟了,但一直没有运行xenomai应用,xenomai tick相关中断才产生了3次,这就是tickless,后面会出xenomai调度及时间子系统和xenomai benchmark相关文章,敬请关注。


作者往期文章:
第一篇: xenomai内核解析--双核系统调用(一)
第二篇: xenomai3.1+linux构建linux实时操作系统-基于X86_64和arm
第三篇: xenomai内核解析之嵌入式实时linux概述

阅码场往期精华文章:【精华】Linux阅码场原创精华文章汇总


阅码场付费会员专业交流群

会员招募:各专业群会员费为88元/季度,权益包含群内提问,线下活动8折,全年不定期群技术分享(普通用户直播免费,分享后每次点播价为19元/次),有意加入请私信客服小月(小月微信号:linuxer2016)


专业群介绍:

彭伟林-阅码场内核性能与稳定性
本群定位内核性能与稳定性技术交流,覆盖云/网/车/机/芯领域资深内核专家,由阅码场资深讲师彭伟林主持。


甄建勇-性能优化与体系结构

本群定位Perf、cache和CPU架构技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师甄建勇主持。


邓世强-Xenomai与实时优化

本群定位Xenomai与实时优化技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师邓世强和彭伟林共同主持。


周贺贺-Tee和ARM架构

本群定位Tee和ARM架构技术交流,覆盖云/网/车/机/芯领域资深用户,由阅码场资深讲师周贺贺主持。

Linux阅码场 专业的Linux技术社区和Linux操作系统学习平台,内容涉及Linux内核,Linux内存管理,Linux进程管理,Linux文件系统和IO,Linux性能调优,Linux设备驱动以及Linux虚拟化和云计算等各方各面.
评论
  • 故障现象 一辆2007款日产天籁车,搭载VQ23发动机(气缸编号如图1所示,点火顺序为1-2-3-4-5-6),累计行驶里程约为21万km。车主反映,该车起步加速时偶尔抖动,且行驶中加速无力。 图1 VQ23发动机的气缸编号 故障诊断接车后试车,发动机怠速运转平稳,但只要换挡起步,稍微踩下一点加速踏板,就能感觉到车身明显抖动。用故障检测仪检测,发动机控制模块(ECM)无故障代码存储,且无失火数据流。用虹科Pico汽车示波器测量气缸1点火信号(COP点火信号)和曲轴位置传感器信
    虹科Pico汽车示波器 2025-01-23 10:46 163浏览
  •     IPC-2581是基于ODB++标准、结合PCB行业特点而指定的PCB加工文件规范。    IPC-2581旨在替代CAM350格式,成为PCB加工行业的新的工业规范。    有一些免费软件,可以查看(不可修改)IPC-2581数据文件。这些软件典型用途是工艺校核。    1. Vu2581        出品:Downstream     
    电子知识打边炉 2025-01-22 11:12 225浏览
  • 前篇文章中『服务器散热效能不佳有解吗?』提到气冷式的服务器其散热效能对于系统稳定度是非常重要的关键因素,同时也说明了百佳泰对于散热效能能提供的协助与服务。本篇将为您延伸说明我们如何进行评估,同时也会举例在测试过程中发现的问题及改善后的数据。AI服务器的散热架构三大重点:GPU导风罩:尝试不同的GPU导风罩架构,用以集中服务器进风量,加强对GPU的降温效果。GPU托盘:改动GPU托盘架构,验证出风面积大小对GPU散热的影想程度。CPU导风罩:尝试封闭CPU导风罩间隙,集中风流,验证CPU降温效果。
    百佳泰测试实验室 2025-01-24 16:58 66浏览
  • 项目展示①正面、反面②左侧、右侧项目源码:https://mbb.eet-china.com/download/316656.html前言为什么想到要做这个小玩意呢,作为一个死宅,懒得看手机,但又想要抬头就能看见时间和天气信息,于是就做个这么个小东西,放在示波器上面正好(示波器外壳有个小槽,刚好可以卡住)功能主要有,获取国家气象局的天气信息,还有实时的温湿度,主控采用ESP32,所以后续还可以开放更多奇奇怪怪的功能,比如油价信息、股票信息之类的,反正能联网可操作性就大多了原理图、PCB、面板设计
    小恶魔owo 2025-01-25 22:09 163浏览
  • 飞凌嵌入式基于瑞芯微RK3562系列处理器打造的FET3562J-C全国产核心板,是一款专为工业自动化及消费类电子设备设计的产品,凭借其强大的功能和灵活性,自上市以来得到了各行业客户的广泛关注。本文将详细介绍如何启动并测试RK3562J处理器的MCU,通过实际操作步骤,帮助各位工程师朋友更好地了解这款芯片。1、RK3562J处理器概述RK3562J处理器采用了4*Cortex-A53@1.8GHz+Cortex-M0@200MHz架构。其中,4个Cortex-A53核心作为主要核心,负责处理复杂
    飞凌嵌入式 2025-01-24 11:21 167浏览
  • 临近春节,各方社交及应酬也变得多起来了,甚至一月份就排满了各式约见。有的是关系好的专业朋友的周末“恳谈会”,基本是关于2025年经济预判的话题,以及如何稳定工作等话题;但更多的预约是来自几个客户老板及副总裁们的见面,他们为今年的经济预判与企业发展焦虑而来。在聊天过程中,我发现今年的聊天有个很有意思的“点”,挺多人尤其关心我到底是怎么成长成现在的多领域风格的,还能掌握一些经济趋势的分析能力,到底学过哪些专业、在企业管过哪些具体事情?单单就这个一个月内,我就重复了数次“为什么”,再辅以我上次写的:《
    牛言喵语 2025-01-22 17:10 297浏览
  • 书接上回:【2022年终总结】阳光总在风雨后,启航2023-面包板社区  https://mbb.eet-china.com/blog/468701-438244.html 总结2019,松山湖有个欧洲小镇-面包板社区  https://mbb.eet-china.com/blog/468701-413397.html        2025年该是总结下2024年的喜怒哀乐,有个好的开始,才能更好的面对2025年即将
    liweicheng 2025-01-24 23:18 132浏览
  • 随着AI大模型训练和推理对计算能力的需求呈指数级增长,AI数据中心的网络带宽需求大幅提升,推动了高速光模块的发展。光模块作为数据中心和高性能计算系统中的关键器件,主要用于提供高速和大容量的数据传输服务。 光模块提升带宽的方法有两种:1)提高每个通道的比特速率,如直接提升波特率,或者保持波特率不变,使用复杂的调制解调方式(如PAM4);2)增加通道数,如提升并行光纤数量,或采用波分复用(CWDM、LWDM)。按照传输模式,光模块可分为并行和波分两种类型,其中并行方案主要应用在中短距传输场景中成本
    hycsystembella 2025-01-25 17:24 97浏览
  • 不让汽车专美于前,近年来哈雷(Harley-Davidson)和本田(Honda)等大型重型机车大厂的旗下车款皆已陆续配备车载娱乐系统与语音助理,在路上也有越来越多的普通机车车主开始使用安全帽麦克风,在骑车时透过蓝牙连线执行语音搜寻地点导航、音乐播放控制或免持拨打接听电话等各种「机车语音助理」功能。客户背景与面临的挑战以本次分享的客户个案为例,该客户是一个跨国车用语音软件供货商,过往是与车厂合作开发前装车机为主,且有着多年的「汽车语音助理」产品经验。由于客户这次是首度跨足「机车语音助理」产品,因
    百佳泰测试实验室 2025-01-24 17:00 79浏览
  • 高速先生成员--黄刚这不马上就要过年了嘛,高速先生就不打算给大家上难度了,整一篇简单但很实用的文章给大伙瞧瞧好了。相信这个标题一出来,尤其对于PCB设计工程师来说,心就立马凉了半截。他们辛辛苦苦进行PCB的过孔设计,高速先生居然说设计多大的过孔他们不关心!另外估计这时候就跳出很多“挑刺”的粉丝了哈,因为翻看很多以往的文章,高速先生都表达了过孔孔径对高速性能的影响是很大的哦!咋滴,今天居然说孔径不关心了?别,别急哈,听高速先生在这篇文章中娓娓道来。首先还是要对各位设计工程师的设计表示肯定,毕竟像我
    一博科技 2025-01-21 16:17 197浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦