OpenHarmony(鸿蒙南向开发)——轻量系统STM32F407芯片移植案例
往期知识点记录:
- 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总
- 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~
- 持续更新中……
介绍基于STM32F407IGT6
芯片在拓维信息 Niobe407 开发板上移植OpenHarmony LiteOS-M轻量系统,提供交通、工业领域开发板解决方案。移植架构采用Board
与SoC
分离方案,使用arm gcc
工具链Newlib C
库,实现了lwip
、littlefs
、hdf
等子系统及组件的适配,开发了配套应用示例代码,支持通过Kconfig图形化配置编译选项。
适配准备
- 下载 stm32cubemx 图形工具。
- 准备ubuntu20.04系统环境,安装 arm-none-eabi-gcc 交叉编译工具链。
生成可用工程
通过stm32cubemx工具生成STM32F407IGT6
芯片的Makefile工程,在此给出如下配置建议:
- 系统相关配置采用默认配置。
- 时钟配置时将SYSCLK选项配置为168MHz,发挥芯片最强性能。
- 配置USART1用作调试串口,用来打印适配过程中的调试信息。
- 配置stm32cubemx工程选项时,将Toolchain/IDE选项选为Makefile。
生成的工程目录如下:
├── Core
│ ├── Inc
│ │ ├── main.h
│ │ ├── stm32f4xx_hal_conf.h
│ │ └── stm32f4xx_it.h
│ └── Src
│ ├── main.c --- 主函数
│ ├── stm32f4xx_hal_msp.c --- HAL库弱函数配置文件
│ ├── stm32f4xx_it.c --- 中断回调函数文件
│ └── system_stm32f4xx.c --- 系统
├── Drivers
│ ├── CMSIS --- CMSIS接口
│ └── STM32F4xx_HAL_Driver --- HAL库驱动
├── Makefile --- Makefile编译
├── STM32F407IGTx_FLASH.ld --- 链接文件
├── startup_stm32f407xx.s --- 启动文件
└── stm32f407_output.ioc --- stm32cubemx工程文件
验证生成的工程
将生成的工程拷贝至Ubuntu,进入工程目录下执行make命令编译,确定能够编译成功。
arm-none-eabi-gcc build/main.o build/stm32f4xx_it.o build/stm32f4xx_hal_msp.o build/stm32f4xx_hal_tim.o build/stm32f4xx_hal_tim_ex.o build/stm32f4xx_hal_uart.o build/stm32f4xx_hal_rcc.o build/stm32f4xx_hal_rcc_ex.o build/stm32f4xx_hal_flash.o build/stm32f4xx_hal_flash_ex.o build/stm32f4xx_hal_flash_ramfunc.o build/stm32f4xx_hal_gpio.o build/stm32f4xx_hal_dma_ex.o build/stm32f4xx_hal_dma.o build/stm32f4xx_hal_pwr.o build/stm32f4xx_hal_pwr_ex.o build/stm32f4xx_hal_cortex.o build/stm32f4xx_hal.o build/stm32f4xx_hal_exti.o build/system_stm32f4xx.o build/startup_stm32f407xx.o -mcpu=cortex-m4 -mthumb -mfpu=fpv4-sp-d16 -mfloat-abi=hard -specs=nano.specs -TSTM32F407IGTx_FLASH.ld -lc -lm -lnosys -Wl,-Map=build/stm32f407_output.map,--cref -Wl,--gc-sections -o build/stm32f407_output.elf
arm-none-eabi-size build/stm32f407_output.elftext data bss dec hex filename5000 20 1636 6656 1a00 build/stm32f407_output.elf
arm-none-eabi-objcopy -O ihex build/stm32f407_output.elf build/stm32f407_output.hex
arm-none-eabi-objcopy -O binary -S build/stm32f407_output.elf build/stm32f407_output.bin
编译完成会生成一个.bin文件,为了确认该程序能在开发板中成功运行,需要main函数中的串口初始化之后,通过串口输出一段字符串,运行时若收到打印信息,则开发板启动成功。
printf("hello world!!\r\n");
适配printf输出到串口,只需要重写_write函数即可,参考如下:
#include <stdio.h>int _write(int fd, char *ptr, int len)
{ return HAL_UART_Transmit(&huart1, (uint8_t *)ptr, len, 0xFFFF);
}
重新编译代码,将其烧录至开发板中验证。
编译构建
目录规划
芯片适配目录规划为:
device
├── board --- 单板厂商目录
│ └── talkweb --- 单板厂商名字:拓维信息
│ └── niobe407 --- 单板名:与产品名一致
└── soc --- SoC厂商目录└── st --- SoC厂商名称└── stm32f4xx --- SoC Series名:stm32f4xx是一个系列,包含该系列soc相关代码
产品样例目录规划为:
vendor
└── talkweb --- 开发产品样例厂商目录└── niobe407 --- 产品名字:niobe407
获取 OpenHarmony源码 ,根据上述目录规划,创建相应文件夹。
预编译适配
预编译适配内容就是围绕hb set命令的适配,使工程能够通过该命令设置根目录、单板目录、产品目录、单板公司名等环境变量,为后续适配编译做准备。
具体的预编译适配步骤如下:
1.在vendor/talkweb/niobe407目录下新增config.json文件,用于描述这个产品样例所使用的单板、内核等信息,描述信息可参考如下内容:
{"product_name": "niobe407", --- 用于hb set进行选择时,显示的产品名称"type": "mini", --- 构建系统的类型,mini/small/standard"version": "3.0", --- 构建系统的版本,1.0/2.0/3.0"device_company": "talkweb", --- 单板厂商名,用于编译时找到/device/board/talkweb目录"board": "niobe407", --- 单板名,用于编译时找到/device/board/talkweb/niobe407目录"kernel_type": "liteos_m", --- 内核类型,因为OpenHarmony支持多内核,一块单板可能适配了多个内核,所以需要指定某个内核进行编译"kernel_version": "3.0.0", --- 内核版本,一块单板可能适配了多个linux内核版本,所以需要指定某个具体的内核版本进行编译"subsystems": [ ] --- 选择所需要编译构建的子系统
}
2.在//device/board/talkweb/niobe407目录下创建board目录,在创建的目录下新增一个config.gni文件,用于描述该产品的编译配置信息:
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".
kernel_type = "liteos_m" --- 内核类型,跟config.json中kernel_type对应# Kernel version.
kernel_version = "3.0.0" --- 内核版本,跟config.json中kernel_version对应
3.验证hb set配置是否正确,输入hb set能够显示如下信息:
4.通过hb env可以查看选择出来的预编译环境变量:
5.hb介绍
hb是OpenHarmony为了方便开发者进行代码构建编译,提供的python脚本工具,其源码就在//build/lite仓库目录下。在执行hb set命令时,脚本会遍历//vendor/<product_company>/<product_name>目录下的config.json,给出可选产品编译选项。在config.json文件中,product_name表示产品名,device_company和board用于关联出//device/board/<device_company>/目录,匹配该目录下的<any_dir_name>/config.gni文件,其中<any_dir_name>目录名可以是任意名称,但建议将其命名为适配内核名称(如:liteos_m、liteos_a、linux)。hb命令如果匹配到了多个config.gni,会将其中的kernel_type和kernel_version字段与vendor/<device_company>下config.json文件中的字段进行匹配,从而确定参与编译的config.gni文件。
至此,预编译适配完成,但工程还不能执行hb build进行编译,还需要准备好后续的LiteOS-M内核移植。
内核移植
内核移植需要完成LiteOS-M Kconfig
适配、gn
的编译构建和内核启动最小适配。
Kconfig文件适配
1.在//vendor/talkweb/niobe407
目录下创建kernel_configs目录,并创建空文件,命名为debug.config。
2.打开//kernel/liteos_m/Kconfig
文件,可以看到在该文件通过orsource命令导入了//device/board
和//device/soc
下多个Kconfig文件,后续需要创建并修改这些文件:
orsource "../../device/board/*/Kconfig.liteos_m.shields"
orsource "../../device/board/$(BOARD_COMPANY)/Kconfig.liteos_m.defconfig.boards"
orsource "../../device/board/$(BOARD_COMPANY)/Kconfig.liteos_m.boards"
orsource "../../device/soc/*/Kconfig.liteos_m.defconfig"
orsource "../../device/soc/*/Kconfig.liteos_m.series"
orsource "../../device/soc/*/Kconfig.liteos_m.soc"
3.在//device/board/talkweb下参考如下目录结构创建相应的Kconfig文件:
.
├── Kconfig.liteos_m.boards
├── Kconfig.liteos_m.defconfig.boards
├── Kconfig.liteos_m.shields
└── niobe407├── Kconfig.liteos_m.board --- 开发板配置选项├── Kconfig.liteos_m.defconfig.board --- 开发板默认配置选项└── liteos_m└── config.gni
- 修改
Kconfig
文件内容:
- 在
//device/board/talkweb/Kconfig.liteos_m.boards
文件中添加:
if SOC_STM32F407orsource "niobe407/Kconfig.liteos_m.board" --- 可根据SOC定义,加载指定board目录定义endif
- 在
//device/board/talkweb/Kconfig.liteos_m.defconfig.boards
文件中添加:
orsource "*/Kconfig.liteos_m.defconfig.board"
- 在
//device/board/talkweb/Kconfig.liteos_m.defconfig.boards
文件中添加:
orsource "shields/Kconfig.liteos_m.shields"
- 在
//device/board/talkweb/niobe407/Kconfig.liteos_m.board
文件中添加:
menuconfig BOARD_NIOBE407bool "select board niobe407"depends on SOC_STM32F407 --- niobe407使用的是stm32f407的SoC,只有SoC被选择后,niobe407的配置选项才可见、可以被选择。
- 在
//device/board/talkweb/niobe407/Kconfig.liteos_m.defconfig.board
中添加:
if BOARD_NIOBE407--- 用于添加BOARD_NIOBE407默认配置endif #BOARD_NIOBE407
- 在
//device/soc/st
下参考如下目录结构创建相应的Kconfig文件,并将stm32cubemx
自动生成工程中的Drivers目录拷贝至stm32f4xx/sdk
目录下:
.├── Kconfig.liteos_m.defconfig├── Kconfig.liteos_m.series├── Kconfig.liteos_m.soc└── stm32f4xx├── Kconfig.liteos_m.defconfig.series├── Kconfig.liteos_m.defconfig.stm32f4xx├── Kconfig.liteos_m.series├── Kconfig.liteos_m.soc└── sdk└── Drivers├── CMSIS└── STM32F4xx_HAL_Driver
6.修改Kconfig文件内容:
- 在
//device/soc/st/Kconfig.liteos_m.defconfig
中添加:
rsource "*/Kconfig.liteos_m.defconfig.series"
- 在
//device/soc/st/Kconfig.liteos_m.series
中添加:
rsource "*/Kconfig.liteos_m.series"
- 在
//device/soc/st/Kconfig.liteos_m.soc
中添加:
config SOC_COMPANY_STMICROELECTRONICSboolif SOC_COMPANY_STMICROELECTRONICSconfig SOC_COMPANYdefault "st"rsource "*/Kconfig.liteos_m.soc"endif # SOC_COMPANY_STMICROELECTRONICS
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.defconfig.series
中添加:
if SOC_SERIES_STM32F4xxrsource "Kconfig.liteos_m.defconfig.stm32f4xx"config SOC_SERIESstringdefault "stm32f4xx"endif
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.defconfig.stm32f4xx
中添加:
config SOCstringdefault "stm32f4xx"depends on SOC_STM32F4xx
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.series
中添加:
config SOC_SERIES_STM32F4xxbool "STMicroelectronics STM32F4xx series"select ARCH_ARMselect SOC_COMPANY_STMICROELECTRONICSselect CPU_CORTEX_M4helpEnable support for STMicroelectronics STM32F4xx series
- 在
//device/soc/st/stm32f4xx/Kconfig.liteos_m.soc
中添加:
choiceprompt "STMicroelectronics STM32F4xx series SoC"depends on SOC_SERIES_STM32F4xxconfig SOC_STM32F407bool "SoC STM32F407"endchoice
- 在
kernel/liteos_m
目录下执行make menuconfig
,使得能够对SoC Series
进行选择:
结果将自动保存在$(PRODUCT_PATH)/kernel_configs/debug.config
,下次执行make menuconfig
时会导出保存的结果。
BUILD.gn文件适配
为了快速熟悉gn的编译和适配,建议先阅读 LiteOS-M内核BUILD.gn编写指南。
(注意,BUILD.gn文件中不要出现tab字符,所有tab用空格代替)
- 在
kernel/liteos_m/BUILD.gn
中,可以看到,通过deps
指定了Board
和SoC
的编译入口:
deps += [ "//device/board/$device_company" ] --- 对应//device/board/talkweb目录deps += [ "//device/soc/$LOSCFG_SOC_COMPANY" ] --- 对应//device/soc/st目录
- 在
//device/board/talkweb/BUILD.gn
中,新增内容如下:
if (ohos_kernel_type == "liteos_m") {import("//kernel/liteos_m/liteos.gni")module_name = get_path_info(rebase_path("."), "name")module_group(module_name) {modules = [ "niobe407" ]}}
3.在niobe407目录下创建BUILD.gn,为了方便管理,将目录名作为模块名:
import("//kernel/liteos_m/liteos.gni")module_name = get_path_info(rebase_path("."), "name")module_group(module_name) {modules = [ "liteos_m",]}
4.将stm32cubemx生成的示例工程Core目录下的文件、startup_stm32f407xx.s启动文件和STM32F407IGTx_FLASH.ld链接文件拷贝至//device/board/talkweb/niobe407/liteos_m/目录下,并在该目录下创建BUILD.gn,添加如下内容:
import("//kernel/liteos_m/liteos.gni")
module_name = get_path_info(rebase_path("."), "name")
kernel_module(module_name) {sources = ["startup_stm32f407xx.s","Src/main.c","Src/stm32f4xx_hal_msp.c","Src/stm32f4xx_it.c","Src/system_stm32f4xx.c",]include_dirs = [ "Inc",]
}config("public") {ldflags = ["-Wl,-T" + rebase_path("STM32F407IGTx_FLASH.ld"),"-Wl,-u_printf_float",]libs = ["c","m","nosys",]
}
5.在make menuconfig中配置(Top) → Compat → Choose libc implementation,选择newlibc。
6.由于_write函数会与kernel的文件操作函数重名,会导致编译失败。后续会换一种方法来适配printf函数,此处我们先将main.c文件中对_write函数的重写删除,将printf函数改用如下方式进行串口打印测试。
uint8_t test[]={"hello niobe407!!\r\n"};
int len = strlen(test);
HAL_UART_Transmit(&huart1, (uint8_t *)test, len, 0xFFFF);
7.同理//device/soc/st/BUILD.gn也是一样,按照目录结构层层依赖包含,最终在//device/soc/st/stm32f4xx/sdk/BUILD.gn中通过kernel_module模板中指定需要参与编译的文件及编译参数,参考如下:
import("//kernel/liteos_m/liteos.gni")
module_name = "stm32f4xx_sdk"
kernel_module(module_name) {asmflags = board_asmflagssources = ["Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_rcc_ex.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_gpio.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma_ex.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_dma.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_cortex.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_exti.c","Drivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_uart.c",]
}
#指定全局头文件搜索路径
config("public") {include_dirs = ["Drivers/STM32F4xx_HAL_Driver/Inc","Drivers/CMSIS/Device/ST/STM32F4xx/Include",]
}
config.gni文件适配
在预编译阶段,在//device/board/talkweb/niobe407/liteos_m目录下创建了一个config.gni文件,它其实就是gn脚本的头文件,可以理解为工程构建的全局配置文件。主要配置了CPU型号、交叉编译工具链及全局编译、链接参数等重要信息:
# Kernel type, e.g. "linux", "liteos_a", "liteos_m".
kernel_type = "liteos_m"# Kernel version.
kernel_version = "3.0.0"# Board CPU type, e.g. "cortex-a7", "riscv32".
board_cpu = "cortex-m4"# Board arch, e.g. "armv7-a", "rv32imac".
board_arch = ""# Toolchain name used for system compiling.
# E.g. gcc-arm-none-eabi, arm-linux-harmonyeabi-gcc, ohos-clang, riscv32-unknown-elf.
# Note: The default toolchain is "ohos-clang". It's not mandatory if you use the default toolchain.
board_toolchain = "arm-none-eabi-gcc"use_board_toolchain = true# The toolchain path installed, it's not mandatory if you have added toolchain path to your ~/.bashrc.
board_toolchain_path = ""# Compiler prefix.
board_toolchain_prefix = "arm-none-eabi-"# Compiler type, "gcc" or "clang".
board_toolchain_type = "gcc"#Debug compiler optimization level options
board_opt_flags = ["-mcpu=cortex-m4","-mthumb","-mfpu=fpv4-sp-d16","-mfloat-abi=hard",
]# Board related common compile flags.
board_cflags = ["-Og","-Wall","-fdata-sections","-ffunction-sections","-DSTM32F407xx",
]
board_cflags += board_opt_flagsboard_asmflags = ["-Og","-Wall","-fdata-sections","-ffunction-sections",
]
board_asmflags += board_opt_flagsboard_cxx_flags = board_cflagsboard_ld_flags = board_opt_flags# Board related headfiles search path.
board_include_dirs = [ "//utils/native/lite/include" ]# Board adapter dir for OHOS components.
board_adapter_dir = ""
如上所示,比较难理解的就是board_opt_flags、board_cflags、board_asmflags等几个参数配置。可以参考如下描述,从stm32cubemx生成的工程中的Makefile文件中提取出来:
board_opt_flags : 编译器相关选项,一般为芯片架构、浮点类型、编译调试优化等级等选项。
board_asmflags :汇编编译选项,与Makefile中的ASFLAGS变量对应。
board_cflags :C代码编译选项,与Makefile中的CFLAGS变量对应。
board_cxx_flags :C++代码编译选项,与Makefile中的CXXFLAGS变量对应。
board_ld_flags :链接选项,与Makefile中的LDFLAGS变量对应。
内核子系统适配
在//vendor/talkweb/niobe407/config.json文件中添加内核子系统及相关配置,如下所示:
{"product_name": "niobe407","type": "mini","version": "3.0","device_company": "talkweb","board": "niobe407","kernel_type": "liteos_m","kernel_version": "3.0.0","subsystems": [ {"subsystem": "kernel","components": [{"component": "liteos_m"}]}],"product_adapter_dir": "","third_party_dir": "//third_party"
}
target_config.h文件适配
在//kernel/liteos_m/kernel/include/los_config.h文件中,有包含一个名为target_config.h的头文件,如果没有这个头文件,则会编译出错。
该头文件的作用主要是定义一些与soc芯片相关的宏定义,可以创建一个空头文件,再配合编译报错提示信息来确定需要定义哪些宏。经验证,Cortex-M4的核适配只需定义LOSCFG_BASE_CORE_TICK_RESPONSE_MAX宏并包含stm32f4xx.h头文件即可将kernel编译通过。
若前期不知如何配置,可以参考虚拟机qemu示例中//device/qemu/arm_mps2_an386/liteos_m/board/target_config.h的配置。
#ifndef _TARGET_CONFIG_H
#define _TARGET_CONFIG_H#define LOSCFG_BASE_CORE_TICK_RESPONSE_MAX 0xFFFFFFUL
#include "stm32f4xx.h" //包含了stm32f4平台大量的宏定义#endif
其中宏定义LOSCFG_BASE_CORE_TICK_RESPONSE_MAX
是直接参考的//device/qemu/arm_mps2_an386/liteos_m/board/target_config.h
文件中的配置,//device/qemu/arm_mps2_an386
是cortex-m4
的虚拟机工程,后续适配可以直接参考,在此不做深入讲解。
内核启动适配
至此,已经可以成功将kernel子系统编译通过,并且在out目录下生成OHOS_Image.bin文件。将生成的OHOS_Image.bin文件烧录至开发板,验证板子能否正常启动运行,如果能成功打印出main函数中串口输出的正确的打印信息,则可以开始进行内核启动适配。
1.为liteos_m分配内存,适配内存分配函数
在文件//kernel/liteos_m/kernel/src/mm/los_memory.c中,OsMemSystemInit函数通过LOS_MemInit进行了内存初始化。可以看到几个比较关键的宏需要我们指定,我们将其添加到target_config.h中:
extern unsigned int __los_heap_addr_start__;
extern unsigned int __los_heap_addr_end__;
#define LOSCFG_SYS_EXTERNAL_HEAP 1
#define LOSCFG_SYS_HEAP_ADDR ((void *)&__los_heap_addr_start__)
#define LOSCFG_SYS_HEAP_SIZE (((unsigned long)&__los_heap_addr_end__) - ((unsigned long)&__los_heap_addr_start__))
其中,__los_heap_addr_start__与__los_heap_addr_end__变量在STM32F407IGTx_FLASH.ld链接文件中被定义, 将_user_heap_stack花括号内内容修改为:
._user_heap_stack :
{. = ALIGN(0x40);__los_heap_addr_start__ = .;__los_heap_addr_end__ = ORIGIN(RAM) + LENGTH(RAM);
} >RAM
除此之外,我们还需要适配内存分配函数,由于内核中已经对_malloc_r等内存分配函数进行了实现,在此我们采用包装函数的方式来适配,用内核中的内存分配函数替换标准库中的内存分配函数即可,在//device/board/talkweb/niobe407/liteos_m/config.gni中board_ld_flags链接参数变量修改为:
board_ld_flags = ["-Wl,--wrap=_calloc_r","-Wl,--wrap=_malloc_r","-Wl,--wrap=_realloc_r","-Wl,--wrap=_reallocf_r","-Wl,--wrap=_free_r","-Wl,--wrap=_memalign_r","-Wl,--wrap=_malloc_usable_size_r",
]
board_ld_flags += board_opt_flags
2.适配printf打印
为了方便后续调试,第一步需要先适配printf函数。而printf的函数适配可大可小,在此只做简单适配,具体实现可以参考其它各开发板源码。
在main.c同级目录下创建dprintf.c文件,文件内容如下:
#include <stdarg.h>
#include "los_interrupt.h"
#include <stdio.h>extern UART_HandleTypeDef huart1;INT32 UartPutc(INT32 ch, VOID *file)
{char RL = '\r';if (ch =='\n') {HAL_UART_Transmit(&huart1, &RL, 1, 0xFFFF);}return HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 0xFFFF);
}static void dputs(char const *s, int (*pFputc)(int n, FILE *cookie), void *cookie)
{unsigned int intSave;intSave = LOS_IntLock();while (*s) {pFputc(*s++, cookie);}LOS_IntRestore(intSave);
}int printf(char const *fmt, ...)
{char buf[1024] = { 0 };va_list ap;va_start(ap, fmt);int len = vsnprintf_s(buf, sizeof(buf), 1024 - 1, fmt, ap);va_end(ap);if (len > 0) {dputs(buf, UartPutc, 0);} else {dputs("printf error!\n", UartPutc, 0);}return len;
}
在串口初始化之后使用printf函数打印,测试是否适配成功。
3.调用LOS_KernelInit初始化内核,进入任务调度。
在main函数中串口初始化之后,调用LOS_KernelInit进行初始化,创建任务示例,进入任务调度。
#include "los_task.h"UINT32 ret;
ret = LOS_KernelInit(); //初始化内核
if (ret == LOS_OK) {TaskSample(); //示例任务函数,在此函数中创建线程任务LOS_Start(); //开始任务调度,程序执行将阻塞在此,由内核接管调度
}
其中TaskSample()函数内容如下:
VOID TaskSampleEntry2(VOID)
{while (1) {printf("TaskSampleEntry2 running...\n");(VOID)LOS_TaskDelay(2000); /* 2000 millisecond */}
}VOID TaskSampleEntry1(VOID)
{while (1) {printf("TaskSampleEntry1 running...\n");(VOID)LOS_TaskDelay(2000); /* 2000 millisecond */}
}
VOID TaskSample(VOID)
{UINT32 uwRet;UINT32 taskID1;UINT32 taskID2;TSK_INIT_PARAM_S stTask = {0};stTask.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry1;stTask.uwStackSize = 0x1000;stTask.pcName = "TaskSampleEntry1";stTask.usTaskPrio = 6; /* Os task priority is 6 */uwRet = LOS_TaskCreate(&taskID1, &stTask);if (uwRet != LOS_OK) {printf("Task1 create failed\n");}stTask.pfnTaskEntry = (TSK_ENTRY_FUNC)TaskSampleEntry2;stTask.uwStackSize = 0x1000;stTask.pcName = "TaskSampleEntry2";stTask.usTaskPrio = 7; /* Os task priority is 7 */uwRet = LOS_TaskCreate(&taskID2, &stTask);if (uwRet != LOS_OK) {printf("Task2 create failed\n");}
}
适配完内核启动后,可以通过调试串口看到如下打印信息:
后续还需要对整个基础内核进行详细适配验证。
内核基础功能适配
内核基础功能适配项包括:中断管理、任务管理、内存管理、内核通信机制、时间管理、软件定时器,可以参考对应链接中的编程实例进行内核基础功能验证。在验证的过程中发现问题,针对相应问题进行具体的适配。
从上一节中打印信息输出时间间隔可以看出,LOS_TaskDelay
函数的延时时间不准确,我们可以在target_config.h
中定义如下宏进行内核时钟适配:
#define OS_SYS_CLOCK 168000000
#define LOSCFG_BASE_CORE_TICK_PER_SECOND (1000UL)
其它内核基础功能的适配方法大多也是围绕于target_config.h
中的宏定义,需要大家配合//kernel/liteos_m
下源码,自行尝试摸索,在此不做进一步讲解。
littlefs文件系统移植适配
Niobe407开发板外挂了16MB的SPI-FLASH,Niobe407基于该Flash进行了littlefs适配。
内核已经对littlefs进行了适配,我们只需要开启Kconfig中的配置,然后适配Littlefs如下接口:
int32_t LittlefsRead(const struct lfs_config *cfg, lfs_block_t block,lfs_off_t off, void *buffer, lfs_size_t size){W25x_BufferRead(buffer, cfg->context + cfg->block_size * block + off, size);return LFS_ERR_OK;}int32_t LittlefsProg(const struct lfs_config *cfg, lfs_block_t block,lfs_off_t off, const void *buffer, lfs_size_t size){W25x_BufferWrite((uint8_t *)buffer,cfg->context + cfg->block_size * block + off,size);return LFS_ERR_OK;}int32_t LittlefsErase(const struct lfs_config *cfg, lfs_block_t block){W25x_SectorErase(cfg->context + cfg->block_size * block);return LFS_ERR_OK;}int32_t LittlefsSync(const struct lfs_config *cfg){return LFS_ERR_OK;}
W25x_BufferRead等函数是spi-flash读写操作的接口,不同型号的spi-flash其实现也不同,Niobe407的SPI-Flash操作具体实现可参考//device/board/talkweb/niobe407/liteos_m/drivers/spi_flash/src/w25qxx.c
由于SPI已经hdf化了,而littlefs依赖于spi驱动,为了方便对文件系统进行配置,可以将littlefs的配置加入至.hcs文件中,具体参考://device/board/talkweb/niobe407/liteos_m/hdf_config/hdf_littlefs.hcs文件
misc {littlefs_config {match_attr = "littlefs_config";mount_points = ["/talkweb"];partitions = [0x800000];block_size = [4096];block_count = [256];}
}
板级驱动移植
驱动适配相关文件放置在//drivers/adapter/platform
中,对应有gpio
,i2c
,pwm
,spi
,uart
,watchdog
,都是通过HDF
机制加载,本章节以pwm
为例进行说明。
PWM驱动适配
在HDF框架中,PWM的接口适配模式采用独立服务模式,在这种模式下,每一个设备对象会独立发布一个设备服务来处理外部访问,设备管理器收到API的访问请求之后,通过提取该请求的参数,达到调用实际设备对象的相应内部方法的目的。独立服务模式可以直接借助HDF DeviceManager的服务管理能力,但需要为每个设备单独配置设备节点。
- 接口说明
1. pwm open初始化函数:DevHandle PwmOpen(uint32_t num);参数说明: num: PWM设备编号。return: 获取成功返回PWM设备句柄,失败返回NULL。
2. pwm close去初始化函数:void PwmClose(DevHandle handle);参数说明:handle: pwm设备句柄。return: 无。
3. 设置PWM设备参数:int32_t PwmSetConfig(DevHandle handle, struct PwmConfig *config);参数说明:handle: pwm设备句柄。*config 参数指针。return: 返回0表示设置成功,返回负数表示失败。
- PWM HDF HCS配置文件解析
device_info.hcs文件位于//device/board/talkweb/niobe407/liteos_m/hdf_config/device_info.hcs,以下示例为使用TIM2、TIM3和TIM7定时器输出PWM信号:
device_pwm1 :: device {pwm1 :: deviceNode {policy = 2;priority = 100;moduleName = "ST_HDF_PLATFORM_PWM";serviceName = "HDF_PLATFORM_PWM_1";deviceMatchAttr = "config_pwm1";}
}
device_pwm2 :: device {pwm2 :: deviceNode {policy = 2;priority = 100;moduleName = "ST_HDF_PLATFORM_PWM";serviceName = "HDF_PLATFORM_PWM_2";deviceMatchAttr = "config_pwm2";}
}
device_pwm7 :: device {pwm7 :: deviceNode {policy = 2;priority = 100;moduleName = "ST_HDF_PLATFORM_PWM";serviceName = "HDF_PLATFORM_PWM_7";deviceMatchAttr = "config_pwm7";}
}
hdf.hcs文件位于//device/board/talkweb/niobe407/liteos_m/hdf_config/hdf.hcs,在此文件中配置TIM定时器具体信息:
--- 注意:tim2-tim7、tim12-tim14时钟频率为84M,TIM1、TIM8~TIM11为168M,tim6和tim7不能输出pwm。
--- tim1~tim5、tim8有4个channel,tim9、tim12有2个channel,tim10、tim11、tim13、tim14只有1个channel。pwm_config {pwm1_config {match_attr = "config_pwm1";pwmTim = 1; --- 定时器ID tim2(0:tim1,1:tim2,...,tim6和tim7不可用)pwmCh = 3; --- 对应channel数(0:ch1、1:ch2、2:ch3、3:ch4)prescaler = 4199; --- 预分频数,例如tim2时钟为84M,(84M/(4199+1))=20khz,则以20khz为基准。} pwm2_config {match_attr = "config_pwm2";pwmTim = 2;pwmCh = 0;prescaler = 8399;} pwm3_config {match_attr = "config_pwm7";pwmTim = 7;pwmCh = 0;prescaler = 8399;}
}
hdf pwm
适配代码请参考://drivers/adapter/platform/pwm/pwm_stm32f4xx.c
hdf pwm
使用示例可请参考://device/board/talkweb/niobe407/applications/206_hdf_pwm
子系统适配
OpenHarmony
子系统适配一般包含两部分:
- 在
config.json
中增加对应子系统和部件,这样编译系统会将该部件纳入编译目标中。 - 针对该部件的
HAL
层接口进行硬件适配,或者可选的软件功能适配。
LWIP部件适配
LiteOS-M kernel
通过Kconfig配置可以使lwip参与编译,并可以在kernel
组件中指定lwip
编译适配的目录。如下:
{"subsystem": "kernel","components": [{"component": "liteos_m","features": ["ohos_kernel_liteos_m_lwip_path = \"//device/board/talkweb/niobe407/liteos_m/lwip_adapter\"" --- 指定适配路径]}]
}
在指定的编译适配目录中,通过#include_next "lwip/lwipopts.h"
的方式入侵修改lwip三方库中头文件配置,关于有线以太网LWIP适配部分,后续会补充详细适配步骤,在此先不做深入讲解。
启动恢复子系统适配
启动恢复子系统适配bootstrap_lite
和syspara_lite
两个组件。请在//vendor/talkweb/niobe407/config.json
中新增对应的配置选项。
{"subsystem": "startup","components": [{"component": "bootstrap_lite","features": []},{"component": "syspara_lite","features": []}]
}
适配bootstrap_lite部件时,需要在链接文件//device/board/talkweb/niobe407/liteos_m/STM32F407IGTx_FLASH.ld中手动新增如下段:
__zinitcall_bsp_start = .;
KEEP (*(.zinitcall.bsp0.init))
KEEP (*(.zinitcall.bsp1.init))
KEEP (*(.zinitcall.bsp2.init))
KEEP (*(.zinitcall.bsp3.init))
KEEP (*(.zinitcall.bsp4.init))
__zinitcall_bsp_end = .;
__zinitcall_device_start = .;
KEEP (*(.zinitcall.device0.init))
KEEP (*(.zinitcall.device1.init))
KEEP (*(.zinitcall.device2.init))
KEEP (*(.zinitcall.device3.init))
KEEP (*(.zinitcall.device4.init))
__zinitcall_device_end = .;
__zinitcall_core_start = .;
KEEP (*(.zinitcall.core0.init))
KEEP (*(.zinitcall.core1.init))
KEEP (*(.zinitcall.core2.init))
KEEP (*(.zinitcall.core3.init))
KEEP (*(.zinitcall.core4.init))
__zinitcall_core_end = .;
__zinitcall_sys_service_start = .;
KEEP (*(.zinitcall.sys.service0.init))
KEEP (*(.zinitcall.sys.service1.init))
KEEP (*(.zinitcall.sys.service2.init))
KEEP (*(.zinitcall.sys.service3.init))
KEEP (*(.zinitcall.sys.service4.init))
__zinitcall_sys_service_end = .;
__zinitcall_sys_feature_start = .;
KEEP (*(.zinitcall.sys.feature0.init))
KEEP (*(.zinitcall.sys.feature1.init))
KEEP (*(.zinitcall.sys.feature2.init))
KEEP (*(.zinitcall.sys.feature3.init))
KEEP (*(.zinitcall.sys.feature4.init))
__zinitcall_sys_feature_end = .;
__zinitcall_run_start = .;
KEEP (*(.zinitcall.run0.init))
KEEP (*(.zinitcall.run1.init))
KEEP (*(.zinitcall.run2.init))
KEEP (*(.zinitcall.run3.init))
KEEP (*(.zinitcall.run4.init))
__zinitcall_run_end = .;
__zinitcall_app_service_start = .;
KEEP (*(.zinitcall.app.service0.init))
KEEP (*(.zinitcall.app.service1.init))
KEEP (*(.zinitcall.app.service2.init))
KEEP (*(.zinitcall.app.service3.init))
KEEP (*(.zinitcall.app.service4.init))
__zinitcall_app_service_end = .;
__zinitcall_app_feature_start = .;
KEEP (*(.zinitcall.app.feature0.init))
KEEP (*(.zinitcall.app.feature1.init))
KEEP (*(.zinitcall.app.feature2.init))
KEEP (*(.zinitcall.app.feature3.init))
KEEP (*(.zinitcall.app.feature4.init))
__zinitcall_app_feature_end = .;
__zinitcall_test_start = .;
KEEP (*(.zinitcall.test0.init))
KEEP (*(.zinitcall.test1.init))
KEEP (*(.zinitcall.test2.init))
KEEP (*(.zinitcall.test3.init))
KEEP (*(.zinitcall.test4.init))
__zinitcall_test_end = .;
__zinitcall_exit_start = .;
KEEP (*(.zinitcall.exit0.init))
KEEP (*(.zinitcall.exit1.init))
KEEP (*(.zinitcall.exit2.init))
KEEP (*(.zinitcall.exit3.init))
KEEP (*(.zinitcall.exit4.init))
__zinitcall_exit_end = .;
需要新增上述段是因为bootstrap_init提供的对外接口,见//utils/native/lite/include/ohos_init.h文件,采用的是灌段的形式,最终会保存到上述链接段中。主要的服务自动初始化宏如下表格所示:
接口名 | 描述 |
---|---|
SYS_SERVICE_INIT(func) | 标识核心系统服务的初始化启动入口 |
SYS_FEATURE_INIT(func) | 标识核心系统功能的初始化启动入口 |
APP_SERVICE_INIT(func) | 标识应用层服务的初始化启动入口 |
APP_FEATURE_INIT(func) | 标识应用层功能的初始化启动入口 |
通过上面加载的组件编译出来的lib文件需要手动加入强制链接。
如在 //vendor/talkweb/niobe407/config.json 中配置了bootstrap_lite 部件
{"subsystem": "startup","components": [{"component": "bootstrap_lite"},...]},
bootstrap_lite部件会编译//base/startup/bootstrap_lite/services/source/bootstrap_service.c,该文件中,通过SYS_SERVICE_INIT将Init函数符号灌段到__zinitcall_sys_service_start和__zinitcall_sys_service_end中,由于Init函数是没有显式调用它,所以需要将它强制链接到最终的镜像。如下:
static void Init(void)
{static Bootstrap bootstrap;bootstrap.GetName = GetName;bootstrap.Initialize = Initialize;bootstrap.MessageHandle = MessageHandle;bootstrap.GetTaskConfig = GetTaskConfig;bootstrap.flag = FALSE;SAMGR_GetInstance()->RegisterService((Service *)&bootstrap);
}
SYS_SERVICE_INIT(Init); --- 通过SYS启动即SYS_INIT启动就需要强制链接生成的lib
在//base/startup/bootstrap_lite/services/source/BUILD.gn文件中,描述了在//out/niobe407/niobe407/libs 生成 libbootstrap.a,如下:
static_library("bootstrap") {sources = ["bootstrap_service.c","system_init.c",]...
适配syspara_lite部件时,系统参数会最终写到文件中进行持久化保存。在轻量系统中,文件操作相关接口有POSIX接口与HalFiles接口这两套实现。
因为对接内核的文件系统,采用POSIX相关的接口,所以features字段中需要增加enable_ohos_startup_syspara_lite_use_posix_file_api = true。
如果对接HalFiles相关的接口实现的,则无须修改。
DFX子系统适配
进行DFX子系统适配需要添加hilog_lite和hievent_lite部件,直接在config.json文件配置即可。
{"subsystem": "hiviewdfx","components": [{"component": "hilog_lite","features": []},{"component": "hievent_lite","features": []}]
}
配置完成之后,需要注册日志输出实现函数,并加入编译。
bool HilogProc_Impl(const HiLogContent *hilogContent, uint32_t len)
{char tempOutStr[LOG_FMT_MAX_LEN];tempOutStr[0] = 0,tempOutStr[1] = 0;if (LogContentFmt(tempOutStr, sizeof(tempOutStr), hilogContent) > 0) {printf(tempOutStr);}return true;
}HiviewRegisterHilogProc(HilogProc_Impl);
系统服务管理子系统适配
进行系统服务管理子系统适配需要添加samgr_lite
部件,直接在config.json
配置即可。
{"subsystem": "systemabilitymgr","components": [{"component": "samgr_lite","features": []}]
}
在轻量系统中,samgr_lite
配置的共享任务栈大小默认为2048
。在适配时可以在features中,通过config_ohos_systemabilitymgr_samgr_lite_shared_task_size
重新设置共享任务栈大小。
"config_ohos_systemabilitymgr_samgr_lite_shared_task_size = 4096"
安全子系统适配
进行安全子系统适配需要添加huks组件,直接在config.json配置即可。
{"subsystem": "security","components": [{"component": "huks","features": ["huks_use_lite_storage = true","huks_use_hardware_root_key = true","huks_config_file = \"hks_config_lite.h\"","huks_key_store_path = \"storage\""]}]
}
huks
部件适配时,huks_key_store_path
配置选项用于指定存放秘钥路径,huks_config_file
为配置头文件名称。
公共基础库子系统适配
公共基础库子系统适配添加了kv_store
、file
、os_dump
组件,直接在config.json
配置即可。
{"subsystem": "utils","components": [{"component": "file","features": []},{"component": "kv_store","features": ["enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = false"]},{"component": "os_dump","features": []}]
},
与适配syspara_lite
部件类似,适配kv_store
部件时,键值对会写到文件中。在轻量系统中,文件操作相关接口有POSIX
接口与HalFiles
接口这两套实现。因为对接内核的文件系统,采用POSIX
相关的接口,所以features
需要增加enable_ohos_utils_native_lite_kv_store_use_posix_kv_api = true
。如果对接HalFiles
相关的接口实现的,则无须修改。
HDF子系统适配
与启动恢复子系统适配类似,我们需要在链接文件//device/board/talkweb/niobe407/liteos_m/STM32F407IGTx_FLASH.ld
中手动新增如下段:
_hdf_drivers_start = .;
KEEP(*(.hdf.driver))
_hdf_drivers_end = .;
然后,在kernel初始化完成后调用DeviceManagerStart函数,执行完成后,才能调用hdf接口控制外设。
#include "devmgr_service_start.h" --- 注意需要包含该头文件#ifdef LOSCFG_DRIVERS_HDFDeviceManagerStart();
#endif
devmgr_service_start.h头文件所在路径为: //drivers/framework/core/common/include/manager,为保证编译时能找到该头文件,需要将其加入到include_dirs中:
XTS兼容性测评子系统适配
产品兼容性规范
产品兼容性规范文档请参考 产品兼容性SIG介绍。
添加XTS子系统
XTS
测试参考资料见 xts参考资料,进行XTS
子系统适配需要添加xts_acts
与xts_tools
组件,直接在config.json
配置即可,配置如下:
{"subsystem": "xts","components": [{"component": "xts_acts","features": []},{"component": "xts_tools","features": []}]
}
我们可以在xts_acts组件的features数组中指定如下属性:
config_ohos_xts_acts_utils_lite_kv_store_data_path
配置挂载文件系统根目录的名字。enable_ohos_test_xts_acts_use_thirdparty_lwip
表示如果使用thirdparty/lwip
目录下的源码编译,则设置为true
,否则设置为false
。
编译XTS
在配置config.json后,使用hb build
是不会去编译xts的,只有在debug版本编译时才会参与编译,并且需要我们强制链接需要进行测试的套件静态库。
在我们//device/board/talkweb/liteos_m
下包含kernel_module
的BUILD.gn 脚本中添加如下内容:
config("public") {if (build_xts) {lib_dirs = [ "$root_out_dir/libs" ]ldflags += ["-Wl,--whole-archive", --- 开启whole-archive特性,可以把在其后面出现的静态库包含的函数和变量输出到动态库"-lbootstrap","-lbroadcast","-lhctest",#公共基础库# "-lmodule_ActsUtilsFileTest",# "-lmodule_ActsKvStoreTest",#DFX"-lmodule_ActsDfxFuncTest","-lmodule_ActsHieventLiteTest",#启动恢复# "-lmodule_ActsBootstrapTest",# "-lmodule_ActsParameterTest",#分布式任务调度# "-lmodule_ActsSamgrTest","-Wl,--no-whole-archive", --- 关掉whole-archive这个特性]}
}
由于Niobe407开发板内存有限,xts测试时需要分套件测试。执行如下编译命令,即可生成包含xts测试的固件。
hb build -f -b debug --gn-args build_xts=true
此外,我们还需要修改//vendor/talkweb/niobe407/hals/utils/sys_param/hal_sys_param.c文件,将这些字符串定义正确。
static const char OHOS_DEVICE_TYPE[] = {"Evaluation Board"};
static const char OHOS_DISPLAY_VERSION[] = {"OpenHarmony 3.1"};
static const char OHOS_MANUFACTURE[] = {"Talkweb"};
static const char OHOS_BRAND[] = {"Talkweb"};
static const char OHOS_MARKET_NAME[] = {"Niobe"};
static const char OHOS_PRODUCT_SERIES[] = {"Niobe"};
static const char OHOS_PRODUCT_MODEL[] = {"Niobe407"};
static const char OHOS_SOFTWARE_MODEL[] = {"1.0.0"};
static const char OHOS_HARDWARE_MODEL[] = {"2.0.0"};
static const char OHOS_HARDWARE_PROFILE[] = {"RAM:192K,ROM:1M,ETH:true"};
static const char OHOS_BOOTLOADER_VERSION[] = {"twboot-v2022.03"};
static const char OHOS_ABI_LIST[] = {"armm4_hard_fpv4-sp-d16-liteos"};
static const char OHOS_SERIAL[] = {"1234567890"}; // provided by OEM.
验证XTS
编译完成后,将固件烧录至开发板,xts全部跑完会有显示xx Tests xx Failures xx Ignored等信息,以下以公共基础库测试为例:
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:590:testKvStoreClearCache002:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:625:testKvStoreCacheSize001:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:653:testKvStoreCacheSize002:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:681:testKvStoreCacheSize003:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:709:testKvStoreMaxSize001:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:737:testKvStoreMaxSize002:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:765:testKvStoreMaxSize003:PASS
../../../test/xts/acts/utils_lite/kv_store_hal/src/kvstore_func_test.c:793:testKvStoreMaxSize004:PASS
+-------------------------------------------+-----------------------
32 Tests 0 Failures 0 Ignored
OK
All the test suites finished!
粉丝推荐
经常有很多小伙伴抱怨说:不知道学习鸿蒙开发哪些技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?
为了能够帮助到大家能够有规划的学习,这里特别整理了一套纯血版鸿蒙(HarmonyOS Next)全栈开发技术的学习路线,包含了鸿蒙开发必掌握的核心知识要点,内容有(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、WebGL、元服务、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、OpenHarmony驱动开发、系统定制移植等等)鸿蒙(HarmonyOS NEXT)技术知识点。
《鸿蒙 (Harmony OS)开发学习手册》(共计892页):https://gitcode.com/HarmonyOS_MN/733GH/overview
如何快速入门?
1.基本概念
2.构建第一个ArkTS应用
3.……
鸿蒙开发面试真题(含参考答案):
《OpenHarmony源码解析》:
- 搭建开发环境
- Windows 开发环境的搭建
- Ubuntu 开发环境搭建
- Linux 与 Windows 之间的文件共享
- ……
- 系统架构分析
- 构建子系统
- 启动流程
- 子系统
- 分布式任务调度子系统
- 分布式通信子系统
- 驱动子系统
- ……
OpenHarmony 设备开发学习手册:https://gitcode.com/HarmonyOS_MN/733GH/overview
相关文章:

OpenHarmony(鸿蒙南向开发)——轻量系统STM32F407芯片移植案例
往期知识点记录: 鸿蒙(HarmonyOS)应用层开发(北向)知识点汇总 鸿蒙(OpenHarmony)南向开发保姆级知识点汇总~ 持续更新中…… 介绍基于STM32F407IGT6芯片在拓维信息 Niobe407 开发板上移植OpenH…...

简单易懂的springboot整合Camunda 7工作流入门教程
简单易懂的Spring Boot整合Camunda7入门教程 因为关于Spring Boot结合Camunda7的教程在网上比较少,而且很多都写得有点乱,很多概念写得太散乱,讲解不清晰,导致看不懂,本人通过研究学习之后就写出了这篇教学文档。 介…...

LabVIEW提高开发效率技巧----点阵图(XY Graph)
在LabVIEW开发中,点阵图(XY Graph) 是一种强大的工具,尤其适用于需要实时展示大量数据的场景。通过使用点阵图,开发人员能够将实时数据可视化,帮助用户更直观地分析数据变化。 1. 点阵图的优势 点阵图&…...

C++-匿名空间
匿名命名空间(anonymous namespace)是 C 中的一种特性,用于将符号(如变量、函数或类)限制在定义它们的源文件的作用域内。这意味着在该源文件外部,这些符号不可见,从而避免了命名冲突。 1. 定义…...

jdk的安装和环境变量配置
1.将从官网下载好的jdk放在自己想要放的位置,这里的位置是:E:\develop 2.新建一个文件夹用来放安装的jdk,将jdk安装的此目录,这里的位置是:E:\develop\jdk17 3.jdk安装好之后,点击jdk17目录,点…...

继承、Lambda、Objective-C和Swift
继承 东风系列导弹是镇国神器。东风41不是突然就造出来的,之前有很多种东风xx导弹,每种导弹都有自己的独特之处,相同之处都具备导弹基本特点。很多工厂有量产磨具的生产线,盖房子就图纸,建筑设计建设都有参考ÿ…...

设置服务器走本地代理
勾选: 然后: git clone https://github.com/rofl0r/proxychains-ng.git./configure --prefix/home/wangguisen/usr --sysconfdir/home/wangguisen/etcmakemake install# 在最后配置成本地代理地址 vim /home/wangguisen/etc/proxychains.confsocks4 17…...

刷题 -哈希
面试面试经典 150 题 - 哈希 383. 赎金信 - 一个哈希表搞定 class Solution { public:bool canConstruct(string ransomNote, string magazine) {int hash[26] {0};for (auto& ch : magazine) {hash[ch - a];}for (auto& ch : ransomNote) {if (--hash[ch - a] < …...

React响应式修改数组和对象
在React中,响应式地修改数组数据是一个常见的需求,它涉及到状态(state)的管理和更新。React的状态是不可变的,这意味着你不能直接修改状态对象中的数组元素,而是需要创建一个新的数组来更新状态。下面将详细…...

cerbot https证书免费自动续期
1. 简介 cerbot是一个开源的数字证书生成平台,简单好用。下面我们来看下安装配置和使用。 cerbot有很多种安装方式,有源码安装、第三方发布版本、pip安装和snap安装,官方推荐使用snap安装。 2. snap安装 snap官方安装文档,Sna…...

嵌入式硬件设计
嵌入式硬件设计是指针对嵌入式系统(一种专用的计算机系统,通常嵌入到其他设备中)进行的硬件设计工作。嵌入式系统广泛应用于消费电子、工业控制、医疗设备、汽车电子、航空航天等领域。以下是嵌入式硬件设计的主要内容和步骤: 1.…...

2024.09.24 校招 实习 内推 面经
🛰️ :neituijunsir 交* 流*裙 ,内推/实习/校招汇总表格 1、校招 | 毫末智行2025届校园招聘正式启动 (内推) 校招 | 毫末智行2025届校园招聘正式启动 (内推) 2、校招 | 圭目机器人 2025 校…...

GIT安装及集成到IDEA中操作步骤
最近深感GIT使用技能太差,我只会些皮毛,还是得看官网,总结一下常用的操作方法吧。 GIT环境配置到IDEA中安装 一、GIt的基本的安装 这个不在这里赘述了,自己装一个git吧 二、给IDEA指定本地GIT的安装路径 1、下图这个是我本地的…...

Java使用线程池创建线程
一、线程前言 首先我们知道,线程的概念如果不知道可以去看这一篇Java中的线程,我们这篇主要讲述的是Java怎么使用线程池创建线程,首先我们要对线程池有点概念,其实顾名思义,线程池就是有喝多线程的一个池子类似于&…...

mysql UDF提权(实战案例)
作者:程序那点事儿 日期:2024/09/29 16:10 什么是UDF? 全称 User Define Function (用户自定义函数)UDF提权,就是通过自定义函数,实现执行系统的命令。 dll(windows,dll文件是c语…...

【瑞昱RTL8763E】刷屏
1 显示界面填充 用户创建的各个界面在 rtk_gui group 中。各界面中 icon[]表对界面进行描述,表中的每个元素代表一 个显示元素,可以是背景、小图标、字符等,UI_WidgetTypeDef 结构体含义如下: typedef struct _UI_WidgetTypeDef …...

【黑马点评】使用RabbitMQ实现消息队列——3.使用Jmeter压力测试,导入批量token,测试异步秒杀下单
3 批量获取用户token,使用jmeter压力测试 3 批量获取用户token,使用jmeter压力测试3.1 需求3.2 实现3.2.1 环境配置3.2.2 修改登录接口UserController和实现类3.2.3 测试类 3.3 使用jmeter进行测试3.4 测试结果3.5 将用户登录逻辑修改回去 3 批量获取用户…...

第 21 章 一条记录的多幅面孔——事务的隔离级别与 MVCC
21.1 事前准备 CREATE TABLE hero ( number INT, NAME VARCHAR ( 100 ), country VARCHAR ( 100 ), PRIMARY KEY ( number ) ) ENGINE INNODB CHARSET utf8;INSERT INTO hero VALUES ( 1, 刘备, 蜀 );21.2 事务隔离级别 在保证事务隔离性的前提下,使用不同的隔…...

javaScript操作dom的事件(3个案例+代码+效果图)
目录 1.焦点事件 案例:登录表单的验证 1.代码 2.效果 3.解释 2.鼠标事件 案例:单击鼠标使小球跳跃 1.代码 2.效果 3.解释 3.键盘事件 案例:使用左右键控制小球左右移动 1.代码 2.效果 编辑 3.解释 1.焦点事件 focus 当获得焦点时出发(不会冒泡)blur 当失去焦点时出发(不会…...

国庆期间的问题,如何在老家访问杭州办公室的网络呢
背景:国庆期间的问题,如何在老家访问杭州办公室的网络呢 实现方案:异地组网 实现语言:Java 环境:三个网络,一台拥有公网IP的服务器、一台杭州本地机房内服务器、你老家所在网络中的一台电脑(…...

动态规划算法——三步问题
1.题目解析 2.算法原理 本题可以近似看做泰波那契数列,即小孩到第一个台阶需要一步,到第二个台阶则是到第一个台阶的步数加上第一阶到第二阶的步数,同理第三阶就是第二阶的步数加上第二阶到第三阶的步数,由于小孩只能走三步&#…...

【鸿蒙学习】深入解析鸿蒙应用与元服务:含义、区别、应用场景及创建方法
文章目录 鸿蒙应用(HarmonyOS App)含义用于干什么优缺点 元服务(Atomic Service)含义用于干什么优缺点 鸿蒙应用与元服务的区别创建方法鸿蒙应用的创建元服务的创建 总结 随着科技的不断进步,操作系统也在不断迭代更新…...

React学习01 jsx、组件与组件的三大属性
文章目录 jsx的介绍与语法1.真实DOM和虚拟DOM2.jsx语法 模块与模块化,组件与组件化模块与模块化组件与组件化 React组件React事件绑定函数式组件类式组件组件属性state组件属性props组件属性ref 尚硅谷react教程官方文档学习记录笔记01 jsx的介绍与语法 1.真实DOM和…...

项目——超级马里奥——Day(3)
一、游戏开发思路: 1.Frame--->BackGround--->Obstacle---->BufferedImage,人物等 2.BackGround的构造函数: 只要记住窗口里边的每一个场景,只要游戏一开始就已经出现在屏幕里边的,都是在构造函数里边 3.绘…...

测试-BUG篇
文章目录 软件测试的生命周期BUGbug的概念描述bug的要素bug级别bug的生命周期 与开发产生争执怎么办(高频考题) 软件测试的生命周期 软件测试贯穿于软件的整个生命周期 BUG bug的概念 是指计算机程序中存在的一个错误(error)、缺陷(flaw)、疏忽(mista…...

vue2中 vue-count-to组件让数字从某个数字动态的显示到某个数字(后附vue3的用法)
1、首先安装 npm install vue-count-to2、使用 2.1、先导入组件 import countTo from ‘vue-count-to’2.2、注册组件 components: { countTo },2.3、使用组件 <countTo> <template><div class"home"><countTo class"count-to&qu…...

AI模型部署初认识
AI部署这个词儿大家肯定不陌生,可能有些小伙伴还不是很清楚这个是干嘛的,但总归是耳熟能详了。 近些年来,在深度学习算法已经足够卷卷卷之后,深度学习的另一个偏向于工程的方向–部署工业落地,才开始被谈论的多了起来…...

在线生成论文的网站有哪些?分享5款AI一键原创论文免费网站
一、千笔-AIPasspaper 千笔-AIPasspaper是一款备受推荐的AI写作助手,它集成了多种功能,包括论文大纲生成、内容填充、文献引用和查重修改等。这款工具基于最新的自然语言处理技术,能够帮助用户快速生成高质量的论文内容。 AI论文࿰…...

考研论坛平台|考研论坛小程序系统|基于java和微信小程序的考研论坛平台小程序设计与实现(源码+数据库+文档)
考研论坛平台小程序 目录 基于java和微信小程序的考研论坛平台小程序设计与实现 一、前言 二、系统功能设计 三、系统实现 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取: 博主介绍:✌️大厂…...

Pandas 时间序列处理
Pandas 时间序列处理 说明: 请回答以下问题,以展示您对 pandas 中时间序列处理的全面理解。请在适用时提供代码示例。 问题 1 如何将日期字符串列表 [2023-01-01, 2023-01-02, 2023-01-03] 转换为 pandas 的 DatetimeIndex? 问题 2 给定一…...