RISCVLinuxkernel启动代码分析之七:早期使用opensbi的串口打印

原创 嵌入式Lee 2024-11-20 08:38

一. 前言

适配内核到新的平台,基本环境搭建好之后,首要的就是要调通调试串口,方便后面的信息打印。正常流程init/main.cstart_kernel入口,要到console_init之后才能真正打印,前面的打印都是缓存在printkringbuffer中的。如果在console_init前就异常了,此时就看不到打印信息,为了调试console_init前的状态,需要能更早的打印。内核提供了一种early打印的方式,尤其是riscv平台我们可以直接ecall调用opensbi的打印,这样opensbi适配好之后这里就可以直接使用。这一篇就来分析下kernelearly打印数据流。

二. 配置

使能early打印需要做一些配置

menuconfig

Device Drivers  --->  

Character devices --->

Serial drivers  --->    

[*] Early console using RISC-V SBI  

对应配置项drivers/tty/serial/Kconfig

SERIAL_EARLYCON_RISCV_SBI

config SERIAL_EARLYCON_RISCV_SBI    bool "Early console using RISC-V SBI"    depends on RISCV_SBI_V01    select SERIAL_CORE    select SERIAL_CORE_CONSOLE    select SERIAL_EARLYCON    help      Support for early debug console using RISC-V SBI. This enables      the console before standard serial driver is probed. This is enabled      with "earlycon=sbi" on the kernel command line. The console is      enabled when early_param is processed.

依赖RISCV_SBI_V01,该选项配置之后默认也会配置

SERIAL_CORE

SERIAL_CORE_CONSOLE

SERIAL_EARLYCON

其中RISCV_SBI_V01是默认使能的,依赖于RISCV_SBI

config RISCV_SBI_V01    bool "SBI v0.1 support"    default y    depends on RISCV_SBI    help      This config allows kernel to use SBI v0.1 APIs. This will be      deprecated in future once legacy M-mode software are no longer in use.

RISCV_SBI默认也是y,依赖!RISCV_M_MODE

# set if we are running in S-mode and can use SBI callsconfig RISCV_SBI    bool    depends on !RISCV_M_MODE    default y

RISC_M_MODE又依赖!MMU,即如果不适用MMU则内核跑M模式

# set if we run in machine mode, cleared if we run in supervisor modeconfig RISCV_M_MODE    bool    default !MMU

MMU默认是y

config MMU    bool "MMU-based Paged Memory Management Support"    default y    help      Select if you want MMU-based virtualised addressing space      support by paged memory management. If unsure, say 'Y'.

配置后output/.config

CONFIG_SERIAL_EARLYCON_RISCV_SBI=y

output/include/config/auto.conf

CONFIG_SERIAL_EARLYCON_RISCV_SBI=y

output/include/generated/autoconf.h

#define CONFIG_SERIAL_EARLYCON_RISCV_SBI 1

drivers/tty/serial/Makefile中编译对应的文件 earlycon-riscv-sbi.c

obj-$(CONFIG_SERIAL_EARLYCON_RISCV_SBI) += earlycon-riscv-sbi.o

三. 相关代码

3.1 Kernelecall调用opensbi的打印

drivers/tty/serial/earlycon-riscv-sbi.c

其中early_sbi_setup设置对应的写接口

static int __init early_sbi_setup(struct earlycon_device *device,                  const char *opt){    device->con->write = sbi_console_write;    return 0;}

实现如下

static void sbi_console_write(struct console *con,                  const char *s, unsigned n){    struct earlycon_device *dev = con->data;    uart_console_write(&dev->port, s, n, sbi_putc);}

最终实际是调用sbi_putc接口写

static void sbi_putc(struct uart_port *port, int c){    sbi_console_putchar(c);}

sbi_console_putchararch/riscv/kernel/sbi.c中实现

通过ecall调用opensbi中的实现,

前面我们看到CONFIG_RISCV_SBI_V01是使能的

#ifdef CONFIG_RISCV_SBI_V01/** * sbi_console_putchar() - Writes given character to the console device. * @ch: The data to be written to the console. * * Return: None */void sbi_console_putchar(int ch){    sbi_ecall(SBI_EXT_0_1_CONSOLE_PUTCHAR, 0, ch, 0, 0, 0, 0, 0);}EXPORT_SYMBOL(sbi_console_putchar);

sbi_ecall

arch/riscv/kernel/sbi.c中实现

struct sbiret sbi_ecall(int ext, int fid, unsigned long arg0,            unsigned long arg1, unsigned long arg2,            unsigned long arg3, unsigned long arg4,            unsigned long arg5){    struct sbiret ret;
    register uintptr_t a0 asm ("a0") = (uintptr_t)(arg0);    register uintptr_t a1 asm ("a1") = (uintptr_t)(arg1);    register uintptr_t a2 asm ("a2") = (uintptr_t)(arg2);    register uintptr_t a3 asm ("a3") = (uintptr_t)(arg3);    register uintptr_t a4 asm ("a4") = (uintptr_t)(arg4);    register uintptr_t a5 asm ("a5") = (uintptr_t)(arg5);    register uintptr_t a6 asm ("a6") = (uintptr_t)(fid);    register uintptr_t a7 asm ("a7") = (uintptr_t)(ext);    asm volatile ("ecall"              : "+r" (a0), "+r" (a1)              : "r" (a2), "r" (a3), "r" (a4), "r" (a5), "r" (a6), "r" (a7)              : "memory");    ret.error = a0;    ret.value = a1;    return ret;}

其中第一个参数extSBI_EXT_0_1_CONSOLE_PUTCHAR=1放在寄存器a7中。

3.2 Kernel中初始化路径

3.2.1命令行参数param_setup_earlycon(line)

配置参数

要使能earlycon功能,需要给内核传入参数earlycon=xxx,

xxx表示对应的驱动和参数,我们这里是sbi使用sbi的串口输出,如果没有指定xxx则从设备树的chosen节点解析串口。

参考《https://www.kernel.org/doc/html/v4.14/admin-guide/kernel-parameters.html》下搜索earlycon

对应流程如下,如下位置对early后面是否有参数进行不同的处理

可以手动添加参数:

arch/riscv/Kconfig中如下配置CMDLINE

config CMDLINE    string "Built-in kernel command line"    help      For most platforms, the arguments for the kernel's command line      are provided at run-time, during boot. However, there are cases      where either no arguments are being provided or the provided      arguments are insufficient or even invalid.
      When that occurs, it is possible to define a built-in command      line here and choose how the kernel should use it later on.

menuconfig配置

Boot options --->   

(earlycon=sbi) Built-in kernel command line

配置完对应output/.config

CONFIG_CMDLINE="earlycon=sbi"

include/generated/autoconf.h

#define CONFIG_CMDLINE "earlycon=sbi"

参数解析过程

我们就来分析下earlycon=sbi时的路径。

drivers/tty/serial/earlycon.c

early_param("earlycon", param_setup_earlycon);

其中include/linux/init.h

#define early_param(str, fn)                        \    __setup_param(str, fn, fn, 1)
#define __setup_param(str, unique_id, fn, early)            \    static const char __setup_str_##unique_id[] __initconst     \        __aligned(1) = str;                     \    static struct obs_kernel_param __setup_##unique_id      \        __used __section(".init.setup")             \        __attribute__((aligned((sizeof(long)))))        \        = { __setup_str_##unique_id, fn, early }

展开为

__setup_param("earlycon", param_setup_earlycon, param_setup_earlycon,1)

继续展开

static const char __setup_str_ param_setup_earlycon[] __initconst

__aligned(1) = “earlycon”;

static struct obs_kernel_param __setup_param_setup_earlycon

__used __section(".init.setup")  

__attribute__((aligned((sizeof(long)))))

={__setup_str_param_setup_earlycon, param_setup_earlycon, 1}

即定义了一个static struct obs_kernel_param类型结构体变量__setup_param_setup_earlycon

放置在段.init.setup中按照sizeof(long)对齐,结构体内容是

{.str=__setup_str_param_setup_earlycon,

.setup_func=param_setup_earlycon,

.early= 1}

__setup_str_param_setup_earlycon即前面的字符数组内容是earlycon

param_setup_earlycon是回调函数

其中结构体 struct obs_kernel_paraminclude/linux/init.h


struct obs_kernel_param {    const char *str;    int (*setup_func)(char *);    int early;};

include/asm-generic/vmlinux.lds.h

#define INIT_SETUP(initsetup_align)                 \        . = ALIGN(initsetup_align);             \        __setup_start = .;                  \        KEEP(*(.init.setup))                    \        __setup_end = .;
#define INIT_DATA_SECTION(initsetup_align)              \    .init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {       \        INIT_DATA                       \        INIT_SETUP(initsetup_align)             \        INIT_CALLS                      \        CON_INITCALL                        \        INIT_RAM_FS                     \    }

arch/riscv/kernel/vmlinux.lds.S

INIT_DATA_SECTION(16)

所以上述变量放在了.init.setup

开始位置是__setup_start

init/main.c中申明变量以便访问

extern const struct obs_kernel_param __setup_start[], __setup_end[];

如下函数遍历上述.init.setup段,遍历每个结构体,回调对应的setup_func,这里即param_setup_earlycon

static bool __init obsolete_checksetup(char *line){    const struct obs_kernel_param *p;    bool had_early_param = false;
    p = __setup_start;    do {        int n = strlen(p->str);        if (parameqn(line, p->str, n)) {            if (p->early) {                /* Already done in parse_early_param?                 * (Needs exact match on param part).                 * Keep iterating, as we can have early                 * params and __setups of same names 8( */                if (line[n] == '\0' || line[n] == '=')                    had_early_param = true;            } else if (!p->setup_func) {                pr_warn("Parameter %s is obsolete, ignored\n",                    p->str);                return true;            } else if (p->setup_func(line + n))                return true;        }        p++;    } while (p < __setup_end);    return had_early_param;}

obsolete_checksetup这里打断点,我们来跟踪分析函数的处理过程

此时我们看到

unknown_bootoption传过来的参数值为param=earlyconval=sbi

正是我们配置的参数。

至于参数是怎么解析的参考parse_args

调用路径是start_kernel->pares_args->unknown_bootoption

执行完repair_env_string

参数param变为了earlycon=sbi

继续单步进入obsolete_checksetup

然后从__setup_start开始遍历段,需要满足参数即linep->str参数匹配这里line就是earlycon=sbi,通过函数parameqn匹配,然后要满足p->early=1

再来看我们定义的结构体

early_param("earlycon", param_setup_earlycon);

会满足这两个条件

static const char __setup_str_ param_setup_earlycon[] = earlycon;

static struct obs_kernel_param __setup_param_setup_earlycon

{.str=__setup_str_param_setup_earlycon,

.setup_func=param_setup_earlycon,

.early= 1}

接下来

line[n] == '='满足

这里直接返回true,所以如下路径是不通的,这里路只能使early不带参数时,走early_init_dt_scan_chosen_stdout查找/chosen的节点。

此时就不会调用param_setup_earlycon(line)

而只有此时不带参数即只有early时才会调用setup_funcparam_setup_earlycon,传入的参数line=

实际early=sbi这里走到是另外一条路,如下图所示

对应do_early_param时会回调setup_func,但是此时传递的参数param_setup_earlycon(line)中的linevalsbi了。和上面一条路传递的参数early不一样了。

do_early_paramparam=earlyval=sbi

param_setup_earlycon(line)line=sbi

而如果走obsolete_checksetup这边的路径,line传入的参数为空

此时回调

param_setup_earlycon(line)

时走early_init_dt_scan_chosen_stdout

注册处理

EARLYCON_DECLARE(sbi, early_sbi_setup);在指定段中放置结构体指针,

初始化时遍历该结构体指针,找到结构体,调用其setup函数进行初始化。

include/linux/serial_core.h

#define _OF_EARLYCON_DECLARE(_name, compat, fn, unique_id)      \    static const struct earlycon_id unique_id           \         EARLYCON_USED_OR_UNUSED __initconst            \        = { .name = __stringify(_name),             \            .compatible = compat,               \            .setup = fn  };                 \    static const struct earlycon_id EARLYCON_USED_OR_UNUSED     \        __section("__earlycon_table")               \        * const __PASTE(__p, unique_id) = &unique_id
#define OF_EARLYCON_DECLARE(_name, compat, fn)              \    _OF_EARLYCON_DECLARE(_name, compat, fn,             \                 __UNIQUE_ID(__earlycon_##_name))#define EARLYCON_DECLARE(_name, fn) OF_EARLYCON_DECLARE(_name, "", fn)

就是定义了一个结构体

struct earlycon_id

include/linux/serial_core.h

struct earlycon_id {    char    name[15];    char    name_term;  /* In case compiler didn't '\0' term name */    char    compatible[128];    int (*setup)(struct earlycon_device *, const char *options);};

static const struct earlycon_id __earlycon_sbi

其成员

.setup = early_sbi_setup

然后定义了一个指针变量__p__earlycon_sbi指向了这个结构体

static const struct earlycon_id * const __p__earlycon_sbi = & __earlycon_sbi

该指针放在了段__earlycon_table

source/include/asm-generic/vmlinux.lds.h

#ifdef CONFIG_SERIAL_EARLYCON#define EARLYCON_TABLE() . = ALIGN(8);              \             __earlycon_table = .;          \             KEEP(*(__earlycon_table))      \             __earlycon_table_end = .;#else#define EARLYCON_TABLE()#endif

符号__earlycon_table表示该段的开始

drivers/tty/serial/earlycon.c

setup_earlycon

如果是走右边这条路

drivers/of/fdt.c

early_init_dt_scan_chosen_stdout

最终都是注册控制台register_console

而走左边的sbi路径会回调early_sbi_setup将写接口改为

device->con->write = sbi_console_write;调用sbi打印。

3.3. Opensbi代码中实现

lib/sbi/sbi_ecall.c

ecall处理入口如下

int sbi_ecall_handler(struct sbi_trap_regs *regs){    int ret = 0;    struct sbi_ecall_extension *ext;    unsigned long extension_id = regs->a7;    unsigned long func_id = regs->a6;    struct sbi_trap_info trap = {0};    unsigned long out_val = 0;    bool is_0_1_spec = 0;
    ext = sbi_ecall_find_extension(extension_id);    if (ext && ext->handle) {        ret = ext->handle(extension_id, func_id,                  regs, &out_val, &trap);        if (extension_id >= SBI_EXT_0_1_SET_TIMER &&            extension_id <= SBI_EXT_0_1_SHUTDOWN)            is_0_1_spec = 1;    } else {        ret = SBI_ENOTSUPP;    }

ext即传过来的a7,待打印字符通过a0传递。以下即获取该ext

  ext = sbi_ecall_find_extension(extension_id);

include/sbi/sbi_ecall_interface.h中定义对应的ext宏。

#define SBI_EXT_0_1_CONSOLE_PUTCHAR     0x1

#define SBI_EXT_0_1_CONSOLE_GETCHAR     0x2

然后回调对应的处理接口

      ret = ext->handle(extension_id, func_id,

                regs, &out_val, &trap);

Handle回调在如下地方注册

sbi_ecall_init->

ret = sbi_ecall_register_extension(&ecall_legacy);

sbi_ecall_register_extensionlib/sbi/sbi_ecall.c中实现

int sbi_ecall_register_extension(struct sbi_ecall_extension *ext){    struct sbi_ecall_extension *t;
    if (!ext || (ext->extid_end < ext->extid_start) || !ext->handle)        return SBI_EINVAL;    sbi_list_for_each_entry(t, &ecall_exts_list, head) {        unsigned long start = t->extid_start;        unsigned long end = t->extid_end;        if (end < ext->extid_start || ext->extid_end < start)            /* no overlap */;        else            return SBI_EINVAL;    }    SBI_INIT_LIST_HEAD(&ext->head);    sbi_list_add_tail(&ext->head, &ecall_exts_list);    return 0;}

ecall_legacy

lib/sbi/sbi_ecall_legacy.c中定义

struct sbi_ecall_extension ecall_legacy = {    .extid_start = SBI_EXT_0_1_SET_TIMER,    .extid_end = SBI_EXT_0_1_SHUTDOWN,    .handle = sbi_ecall_legacy_handler,};

所以最终回调其handle

sbi_ecall_legacy_handlerlib/sbi/sbi_ecall_legacy.c中实现

该函数就根据ext=SBI_EXT_0_1_CONSOLE_PUTCHAR,最终调用sbi_putc发送一个字符

    case SBI_EXT_0_1_CONSOLE_PUTCHAR:        sbi_putc(regs->a0);        break;    case SBI_EXT_0_1_CONSOLE_GETCHAR:        ret = sbi_getc();        break;

四. 仿真调试

查看early_sbi_setup的调用路径

参数为sarlycon=sbi

setup_earlycon

在如下位置打断点运行到到断点处,查看调用栈

hb setup_earlycon

c

hb early_sbi_setup

c

bt

对应如下路径

参数为earlycon

此时走设备树/chosen的串口

early_init_dt_scan_chosen_stdout

hb early_init_dt_scan_chosen_stdout

c

查看sbi_console_write调用路径

hb sbi_console_write

c

bt

看到在console_init();之前此时就可以打印了,之前是必须要在console_init();之后才真正打印,之前是打印在ringbuffer中的缓存的。

最终是调用ecall完成打印

五. 总结

如果要使能early通过opensbi打印,要使配置参数early=sbi,且使能early打印,此时在console_init之前就可以进行直接打印。























评论
  • 在智能家居领域中,Wi-Fi、蓝牙、Zigbee、Thread与Z-Wave等无线通信协议是构建短距物联局域网的关键手段,它们常在实际应用中交叉运用,以满足智能家居生态系统多样化的功能需求。然而,这些协议之间并未遵循统一的互通标准,缺乏直接的互操作性,在进行组网时需要引入额外的网关作为“翻译桥梁”,极大地增加了系统的复杂性。 同时,Apple HomeKit、SamSung SmartThings、Amazon Alexa、Google Home等主流智能家居平台为了提升市占率与消费者
    华普微HOPERF 2025-01-06 17:23 97浏览
  • 本文介绍Linux系统更换开机logo方法教程,通用RK3566、RK3568、RK3588、RK3576等开发板,触觉智能RK3562开发板演示,搭载4核A53处理器,主频高达2.0GHz;内置独立1Tops算力NPU,可应用于物联网网关、平板电脑、智能家居、教育电子、工业显示与控制等行业。制作图片开机logo图片制作注意事项(1)图片必须为bmp格式;(2)图片大小不能大于4MB;(3)BMP位深最大是32,建议设置为8;(4)图片名称为logo.bmp和logo_kernel.bmp;开机
    Industio_触觉智能 2025-01-06 10:43 72浏览
  • 这篇内容主要讨论三个基本问题,硅电容是什么,为什么要使用硅电容,如何正确使用硅电容?1.  硅电容是什么首先我们需要了解电容是什么?物理学上电容的概念指的是给定电位差下自由电荷的储藏量,记为C,单位是F,指的是容纳电荷的能力,C=εS/d=ε0εrS/4πkd(真空)=Q/U。百度百科上电容器的概念指的是两个相互靠近的导体,中间夹一层不导电的绝缘介质。通过观察电容本身的定义公式中可以看到,在各个变量中比较能够改变的就是εr,S和d,也就是介质的介电常数,金属板有效相对面积以及距离。当前
    知白 2025-01-06 12:04 110浏览
  • 车身域是指负责管理和控制汽车车身相关功能的一个功能域,在汽车域控系统中起着至关重要的作用。它涵盖了车门、车窗、车灯、雨刮器等各种与车身相关的功能模块。与汽车电子电气架构升级相一致,车身域发展亦可以划分为三个阶段,功能集成愈加丰富:第一阶段为分布式架构:对应BCM车身控制模块,包含灯光、雨刮、门窗等传统车身控制功能。第二阶段为域集中架构:对应BDC/CEM域控制器,在BCM基础上集成网关、PEPS等。第三阶段为SOA理念下的中央集中架构:VIU/ZCU区域控制器,在BDC/CEM基础上集成VCU、
    北汇信息 2025-01-03 16:01 193浏览
  • 光耦合器,也称为光隔离器,是一种利用光在两个隔离电路之间传输电信号的组件。在医疗领域,确保患者安全和设备可靠性至关重要。在众多有助于医疗设备安全性和效率的组件中,光耦合器起着至关重要的作用。这些紧凑型设备经常被忽视,但对于隔离高压和防止敏感医疗设备中的电气危害却是必不可少的。本文深入探讨了光耦合器的功能、其在医疗应用中的重要性以及其实际使用示例。什么是光耦合器?它通常由以下部分组成:LED(发光二极管):将电信号转换为光。光电探测器(例如光电晶体管):检测光并将其转换回电信号。这种布置确保输入和
    腾恩科技-彭工 2025-01-03 16:27 171浏览
  • 彼得·德鲁克被誉为“现代管理学之父”,他的管理思想影响了无数企业和管理者。然而,关于他的书籍分类,一种流行的说法令人感到困惑:德鲁克一生写了39本书,其中15本是关于管理的,而其中“专门写工商企业或为企业管理者写的”只有两本——《为成果而管理》和《创新与企业家精神》。这样的表述广为流传,但深入探讨后却发现并不完全准确。让我们一起重新审视这一说法,解析其中的矛盾与根源,进而重新认识德鲁克的管理思想及其著作的真正价值。从《创新与企业家精神》看德鲁克的视角《创新与企业家精神》通常被认为是一本专为企业管
    优思学院 2025-01-06 12:03 76浏览
  • 根据Global Info Research项目团队最新调研,预计2030年全球封闭式电机产值达到1425百万美元,2024-2030年期间年复合增长率CAGR为3.4%。 封闭式电机是一种电动机,其外壳设计为密闭结构,通常用于要求较高的防护等级的应用场合。封闭式电机可以有效防止外部灰尘、水分和其他污染物进入内部,从而保护电机的内部组件,延长其使用寿命。 环洋市场咨询机构出版的调研分析报告【全球封闭式电机行业总体规模、主要厂商及IPO上市调研报告,2025-2031】研究全球封闭式电机总体规
    GIRtina 2025-01-06 11:10 89浏览
  • 自动化已成为现代制造业的基石,而驱动隔离器作为关键组件,在提升效率、精度和可靠性方面起到了不可或缺的作用。随着工业技术不断革新,驱动隔离器正助力自动化生产设备适应新兴趋势,并推动行业未来的发展。本文将探讨自动化的核心趋势及驱动隔离器在其中的重要角色。自动化领域的新兴趋势智能工厂的崛起智能工厂已成为自动化生产的新标杆。通过结合物联网(IoT)、人工智能(AI)和机器学习(ML),智能工厂实现了实时监控和动态决策。驱动隔离器在其中至关重要,它确保了传感器、执行器和控制单元之间的信号完整性,同时提供高
    腾恩科技-彭工 2025-01-03 16:28 166浏览
  •     为控制片内设备并且查询其工作状态,MCU内部总是有一组特殊功能寄存器(SFR,Special Function Register)。    使用Eclipse环境调试MCU程序时,可以利用 Peripheral Registers Viewer来查看SFR。这个小工具是怎样知道某个型号的MCU有怎样的寄存器定义呢?它使用一种描述性的文本文件——SVD文件。这个文件存储在下面红色字体的路径下。    例:南京沁恒  &n
    电子知识打边炉 2025-01-04 20:04 82浏览
  • 随着市场需求不断的变化,各行各业对CPU的要求越来越高,特别是近几年流行的 AIOT,为了有更好的用户体验,CPU的算力就要求更高了。今天为大家推荐由米尔基于瑞芯微RK3576处理器推出的MYC-LR3576核心板及开发板。关于RK3576处理器国产CPU,是这些年的骄傲,华为手机全国产化,国人一片呼声,再也不用卡脖子了。RK3576处理器,就是一款由国产是厂商瑞芯微,今年第二季推出的全新通用型的高性能SOC芯片,这款CPU到底有多么的高性能,下面看看它的几个特性:8核心6 TOPS超强算力双千
    米尔电子嵌入式 2025-01-03 17:04 48浏览
  • 物联网(IoT)的快速发展彻底改变了从智能家居到工业自动化等各个行业。由于物联网系统需要高效、可靠且紧凑的组件来处理众多传感器、执行器和通信设备,国产固态继电器(SSR)已成为满足中国这些需求的关键解决方案。本文探讨了国产SSR如何满足物联网应用的需求,重点介绍了它们的优势、技术能力以及在现实场景中的应用。了解物联网中的固态继电器固态继电器是一种电子开关设备,它使用半导体而不是机械触点来控制负载。与传统的机械继电器不同,固态继电器具有以下优势:快速切换:确保精确快速的响应,这对于实时物联网系统至
    克里雅半导体科技 2025-01-03 16:11 178浏览
  • 在测试XTS时会遇到修改产品属性、SElinux权限、等一些内容,修改源码再编译很费时。今天为大家介绍一个便捷的方法,让OpenHarmony通过挂载镜像来修改镜像内容!触觉智能Purple Pi OH鸿蒙开发板演示。搭载了瑞芯微RK3566四核处理器,树莓派卡片电脑设计,支持开源鸿蒙OpenHarmony3.2-5.0系统,适合鸿蒙开发入门学习。挂载镜像首先,将要修改内容的镜像传入虚拟机当中,并创建一个要挂载镜像的文件夹,如下图:之后通过挂载命令将system.img镜像挂载到sys
    Industio_触觉智能 2025-01-03 11:39 115浏览
  • 每日可见的315MHz和433MHz遥控模块,你能分清楚吗?众所周知,一套遥控设备主要由发射部分和接收部分组成,发射器可以将控制者的控制按键经过编码,调制到射频信号上面,然后经天线发射出无线信号。而接收器是将天线接收到的无线信号进行解码,从而得到与控制按键相对应的信号,然后再去控制相应的设备工作。当前,常见的遥控设备主要分为红外遥控与无线电遥控两大类,其主要区别为所采用的载波频率及其应用场景不一致。红外遥控设备所采用的射频信号频率一般为38kHz,通常应用在电视、投影仪等设备中;而无线电遥控设备
    华普微HOPERF 2025-01-06 15:29 93浏览
  • PLC组态方式主要有三种,每种都有其独特的特点和适用场景。下面来简单说说: 1. 硬件组态   定义:硬件组态指的是选择适合的PLC型号、I/O模块、通信模块等硬件组件,并按照实际需求进行连接和配置。    灵活性:这种方式允许用户根据项目需求自由搭配硬件组件,具有较高的灵活性。    成本:可能需要额外的硬件购买成本,适用于对系统性能和扩展性有较高要求的场合。 2. 软件组态   定义:软件组态主要是通过PLC
    丙丁先生 2025-01-06 09:23 71浏览
  • 在快速发展的能源领域,发电厂是发电的支柱,效率和安全性至关重要。在这种背景下,国产数字隔离器已成为现代化和优化发电厂运营的重要组成部分。本文探讨了这些设备在提高性能方面的重要性,同时展示了中国在生产可靠且具有成本效益的数字隔离器方面的进步。什么是数字隔离器?数字隔离器充当屏障,在电气上将系统的不同部分隔离开来,同时允许无缝数据传输。在发电厂中,它们保护敏感的控制电路免受高压尖峰的影响,确保准确的信号处理,并在恶劣条件下保持系统完整性。中国国产数字隔离器经历了重大创新,在许多方面达到甚至超过了全球
    克里雅半导体科技 2025-01-03 16:10 122浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦