Android HIDL概述与绑定模式的实现

 一、前言

Android O(8.0) 版本之后,底层实现有了比较大的变化,最显著的一个方面就是 HIDL 机制的全面实施。本文对于理解系统源码中 GnssUsbCamera 等模块的工作原理有极大帮助。

二、HIDL 设计目的

Android O(8.0) 之前系统的升级牵扯多方协作,极为麻烦,HIDL机制的推出就是将 frameworkhal 层分开,使得框架部分可以直接被覆盖、更新,而不需要重新对 HAL 进行编译,这样在系统升级时,OEM 厂商 跳过 SoC 厂商,先对 framework 进行升级。

2.1、8.0 之前

frameworkhal 紧紧耦合存在于 system.img 中,因此在版本升级时需要: OEM 厂商适配 frameworkSoC厂商 适配 hal, 之后将修改打包到 system.img,生成 OTA 升级包,推送到手机进行 OTA 升级

2.2、8.0 之后

frameworkhal 进行了解耦, framework 存在于 system.imghal 存在于vendor.img,进行版本升级时,分为两次升级:

  • framework升级 : OEM 厂商适配 framework,将修改打包到 system.img, 生成OTA 升级包,推送到手机进行 OTA 升级(framework 发生改变,hal 层未变)。
  • hal升级 :SoC 厂商适配 hal, 将修改打包到 vendor.img, 生成OTA 升级包,推送到手机进行OTA升级(framework发生改变,hal 层发生改变)。

三、HIDL机制演进

3.1 老版本 Framework 与 HAL 的通信框架

正如上述所言,旧版的系统架构中, Android Framework 层与 Hal 层是打包成一个 system.img 的,且 Framework 与 hal 层之间是紧密耦合的,通过链接的方式使用相应的硬件 so 库。它们之间的架构一般有如下两种方式:

3.2 HIDL 类型介绍

为了解决两者之间这种紧耦合所带来的弊端,google 引入 HIDL 来定义 Framework 与 HAL 之间的接口,可以用下图来描述:

事实上虽然 google 推出了这种机制,但是很多厂商没有很快的跟上节奏,因此为了向前兼容, google 定义了三种类型:

  • ① 是 Treble Project 之前使用的实现架构,使用的是传统 HAL 和旧版 HAL
  • ② 直通模式,passthrough mode。如图所示,Framework 和 HAL 层工作在同一个进程当中,下面的 HAL 是使用 HIDL 封装后的库,是直通式 HAL。这些库文件也可用于 ③ 绑定模式
  • ③ 绑定模式,binderized mode。是直通式 HAL binder 化,变为绑定式 HAL。Framework 和 HAL 层工作在不同的进程,之间通过 Binder 进行 IPC
  • ④ 纯绑定式。相对于 ③ 来说,绑定式 HAL 中并不包含直通式 HAL,因此称为纯绑定式

上述可总结为

上述介绍参考此处

四、HIDL实现

绑定模式 是 google 为了向前兼容而定义的一种类型,且 Android 8.0 及后续版本的设备都必须只支持这种模式。这种模式下 Framework 与 Hal 分别位于不同的进程中,其实从具体实现来讲这种模式也更应该被称为 Binder 化的直通式。下面将通过这种方式实现一个具有加减乘除功能的 HIDL 服务,该服务的名称为 MyTest

4.1 创建IMyTest.hal文件

在系统源码中的 hardware/interfaces 目录下有很多的 HIDL,我们仿照其他 HIDL 来创建自己的目录,在源码根目录执行以下命令:

mkdir -p hardware/interfaces/my_test/1.0/default

之后创建 IMyTest.hal 文件:

touch hardware/interfaces/my_test/1.0/IMyTest.hal

这里定义了四种基本的运算:加、减、乘、除,这是上层调用 HAL 的入口,该文件内容如下:

package android.hardware.my_test@1.0;interface IMyTest{//加法add(uint32_t a,uint32_t b) generates (uint32_t result);//减法sub(uint32_t a,uint32_t b) generates (uint32_t result);//乘法mul(uint32_t a,uint32_t b) generates (uint32_t result);//除法div(uint32_t a,uint32_t b) generates (uint32_t result);};

4.2 使用hidl-gen生成HAL相关文件

Google帮我们提供了 hidl-gen 工具来生成 HAL 层相关的代码框架和代码实例,这样子我们只需要关心实现部分。在源码根目录执行以下命令:

source build/envsetup.sh
lunch xxx
PACKAGE=android.hardware.my_test@1.0
LOC=hardware/interfaces/my_test/1.0/default/
make hidl-gen
hidl-gen -o $LOC -Lc++-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE
hidl-gen -o $LOC -Landroidbp-impl -randroid.hardware:hardware/interfaces -randroid.hidl:system/libhidl/transport $PACKAGE

之后执行 update-makefiles.sh 脚本来为 HIDL 生成对应的 Android.bp 文件,源码根目录执行:

./hardware/interfaces/update-makefiles.sh

现在我们的工程目录结构如下:

接下来我们需要再创建两个文件:

touch hardware/interfaces/my_test/1.0/default/android.hardware.my_test@1.0-service.rc
touch hardware/interfaces/my_test/1.0/default/service.cpp

最终我们的工程目录结构如下:

4.3 调用流程分析

上述过程已经将 HIDL 服务所需要的文件创建完成,虽然其中很多文件还没有具体实现,我们先放在一边,先来对整体的调用流程及各个文件的作用略作说明:

Application上层应用
JNI指 framework 层,getService 获取 hal 层 service
android.hardware.my_test@1.0.so接口库,由hardware/interfaces/my_test/1.0/Android.bp 通过 IMyTest.hal 生成,这样只要这个接口库不变,那么 framework 的更新和 hal 层就隔绝开了
android.hardware.my_test@1.0-impl.so实现库,上层应用的最终调用
mytest-hal-serviceservice的名称
android.hardware.my_test@1.0-service.rc设备开机时通过解析 rc 文件启动此服务

 4.4 接口库生成

android.hardware.my_test@1.0.so 由hardware/interfaces/my_test/1.0/Android.bp 通过 IMyTest.hal 生成,该 Android.bp 文件是在上面一系列命令执行之后生成,而接口库是当我们最终执行编译模块时生成,可以说这个过程不需要我们手动参与。

4.5 实现库生成

由 hardware/interfaces/galaxy_one/1.0/default/Android.bp 在最后模块编译时通过 GalaxyOne.cpp 生成,

Android.bp 内容如下:

// FIXME: your file license if you have onecc_library_shared {// FIXME: this should only be -impl for a passthrough hal.// In most cases, to convert this to a binderized implementation, you should:// - change '-impl' to '-service' here and make it a cc_binary instead of a//   cc_library_shared.// - add a *.rc file for this module.// - delete HIDL_FETCH_I* functions.// - call configureRpcThreadpool and registerAsService on the instance.// You may also want to append '-impl/-service' with a specific identifier like// '-vendor' or '-<hardware identifier>' etc to distinguish it.name: "android.hardware.my_test@1.0-impl",relative_install_path: "hw",// FIXME: this should be 'vendor: true' for modules that will eventually be// on AOSP.proprietary: true,srcs: ["MyTest.cpp",],shared_libs: [    //可以添加需要的库"liblog","libhidlbase","libhidltransport","libhwbinder","libutils","android.hardware.my_test@1.0",],
}

4.6 MyTest.cpp的实现

实现库是通过 MyTest.cpp 编译生成的,现在完善 MyTest.h 和 MyTest.cpp

MyTest.h

Binder化直通式,同样需要将 HIDL_FETCH_XXX 打开

// FIXME: your file license if you have one#pragma once#include <android/hardware/my_test/1.0/IMyTest.h>
#include <hidl/MQDescriptor.h>
#include <hidl/Status.h>
#include <log/log.h>namespace android::hardware::my_test::V1_0::implementation {using ::android::hardware::hidl_array;
using ::android::hardware::hidl_memory;
using ::android::hardware::hidl_string;
using ::android::hardware::hidl_vec;
using ::android::hardware::Return;
using ::android::hardware::Void;
using ::android::sp;struct MyTest : public V1_0::IMyTest {// Methods from ::android::hardware::my_test::V1_0::IMyTest follow.Return<uint32_t> add(uint32_t a, uint32_t b) override;Return<uint32_t> sub(uint32_t a, uint32_t b) override;Return<uint32_t> mul(uint32_t a, uint32_t b) override;Return<uint32_t> div(uint32_t a, uint32_t b) override;// Methods from ::android::hidl::base::V1_0::IBase follow.};// FIXME: most likely delete, this is only for passthrough implementations
extern "C" IMyTest* HIDL_FETCH_IMyTest(const char* name);}  // namespace android::hardware::my_test::V1_0::implementation

注意,hidl-gen生成的 MyTest.h 的代码中,命名空间不对;将android::hardware::my_test::implementation 改成 android::hardware::my_test::V1_0::implementation

MyTest.cpp

// FIXME: your file license if you have one#include "MyTest.h"namespace android::hardware::my_test::V1_0::implementation {// Methods from ::android::hardware::my_test::V1_0::IMyTest follow.
Return<uint32_t> MyTest::add(uint32_t a, uint32_t b) {// TODO implementuint32_t result = a + b;ALOGE("MyTest::add  a = %d,b = %d,result = %d",a,b,result);return uint32_t {};
}Return<uint32_t> MyTest::sub(uint32_t a, uint32_t b) {// TODO implementuint32_t result = a - b;ALOGE("MyTest::sub  a = %d,b = %d,result = %d",a,b,result);return uint32_t {};
}Return<uint32_t> MyTest::mul(uint32_t a, uint32_t b) {// TODO implementuint32_t result = a * b;ALOGE("MyTest::mul  a = %d,b = %d,result = %d",a,b,result);return uint32_t {};
}Return<uint32_t> MyTest::div(uint32_t a, uint32_t b) {// TODO implementuint32_t result = a / b;ALOGE("MyTest::div  a = %d,b = %d,result = %d",a,b,result);return uint32_t {};
}// Methods from ::android::hidl::base::V1_0::IBase follow.IMyTest* HIDL_FETCH_IMyTest(const char* /* name */) {ALOGE("my_test service init success....");return new MyTest();
}
//
}  // namespace android::hardware::my_test::V1_0::implementation

同样需要更改命名空间

4.7 编译

现在除了需要的 rc 文件没有补充、mytest-hal-service 服务没有生成外其余均已配置好了,现在进行编译生成对应的库。进入根目录下执行如下命令:(注意是在刚刚执行过的 source build/envsetup.sh 和 lunch 的窗口下编译,若是新窗口则需要重新执行这两条命令)

mmm  hardware/interfaces/my_test/1.0

编译成功后会在 out\target\product\xxxxxxx\vendor\lib64\hw 下生成 android.hardware.my_test@1.0-impl.so;在 out\target\product\xxxxxxx\system\lib64 下生成 android.hardware.my_test@1.0.so;

4.8 生成Service

接下来我们需要生成对应的 service 可执行文件,这个过程一共分为三步:

1.在 /default 下的 Android.bp 文件中追加如下内容

cc_binary {name: "android.hardware.my_test@1.0-service",defaults: ["hidl_defaults"],relative_install_path: "hw",vendor: true,srcs: ["service.cpp"],init_rc: ["android.hardware.my_test@1.0-service.rc"],shared_libs: ["liblog","libhidlbase","libhidltransport","libhwbinder","libutils","android.hardware.my_test@1.0",],
}

2.补充 service.cpp 

defaultPassthroughServiceImplementation 会帮我们自动注册服务;

#define LOG_TAG "android.hardware.my_test@1.0-service"#include <android/hardware/my_test/1.0/IMyTest.h>
#include <hidl/LegacySupport.h>
#include "MyTest.h"// Generated HIDL files
using android::hardware::my_test::V1_0::IMyTest;
using android::hardware::my_test::V1_0::implementation::MyTest;using android::hardware::defaultPassthroughServiceImplementation;
using android::hardware::configureRpcThreadpool;
using android::hardware::joinRpcThreadpool;int main() {return defaultPassthroughServiceImplementation<IMyTest>();
} 

3.补充 rc 文件

系统开机会解析该 .rc 文件,启动该服务。这里的 mytest-hal-service 相当于这个服务的别名;如果需要开机自启动,需要配置 seLinux 权限,这里暂不介绍;因此下面我们测试验证的时候通过手动启动的方式启动服务。

service mytest-hal-service /vendor/bin/hw/android.hardware.my_test@1.0-serviceclass haluser systemgroup system

再次在根目录执行如下命令:

mmm  hardware/interfaces/my_test/1.0

完成之后就会得到如下二进制可执行文件:

out\target\product\xxxxxxx\vendor\bin\hw\android.hardware.my_test@1.0-service

4.9 编写测试Client 

经过一系列过程之后,我们得到了三个产物:

1、android.hardware.my_test@1.0.so
2、android.hardware.my_test@1.0-impl.so
3、android.hardware.my_test@1.0-service

接下来模拟一个客户端来测试调用;在 default 目录下创建 test 目录,并新建 client.cpp、Android.bp 文件;创建好后工程目录如下:

client.cpp内容:

#include <android/hardware/my_test/1.0/IMyTest.h>
#include <hidl/Status.h>
#include <log/log.h>using android::sp;
using android::hardware::my_test::V1_0::IMyTest;
using android::hardware::Return;int main(){android::sp<IMyTest> service = IMyTest::getService();if (service == nullptr) {ALOGD("faile to get my_test service......");return -1;}ALOGE("success to get my_test service.....");uint32_t addResult = service->add(3,4);ALOGE("my_test service add: result = %d",(int)addResult);uint32_t subResult = service->sub(8,3);ALOGE("my_test service sub: result = %d",(int)subResult);uint32_t mulResult = service->mul(3,8);ALOGE("my_test service mul: result = %d",(int)mulResult);uint32_t divResult = service->div(8,2);ALOGE("my_test service div: result = %d",(int)divResult);return 0;
}

Android.bp内容:

cc_binary {name: "my_hidl_test",    //表示生成的 client 名称srcs: ["client.cpp"],shared_libs: ["liblog","android.hardware.my_test@1.0","libhidlbase","libhidltransport","libhwbinder","libutils",],
}

根目录执行 mmm  hardware/interfaces/my_test/1.0 编译工程,成功后可在 out\target\product\xxxxxxx\system\bin 目录下找到 my_hidl_test

五、验证

5.1 将产物推入机器

现在我们一共得到 4 个产物,使用 adb 命令将其 push 到车机对应目录下:

android.hardware.my_test@1.0.so             ====》     /vendor/lib64
android.hardware.my_test@1.0-impl.so     ====》     /vendor/lib64/hw
android.hardware.my_test@1.0-service     ====》     /vendor/bin/hw
my_hidl_test                                                ====》     /system/bin

5.2 修改manifest.xml

HIDL 想要被 framework 获取使用还需要在 manifest.xml 中注册,该文件在车机 /vendor/etc/vintf/ 目录下(不同厂商可能不同,以实际情况为准),添加下面的内容:

<hal format="hidl"><name>android.hardware.my_test</name><transport>hwbinder</transport><version>1.0</version><interface><name>IMyTest</name><instance>default</instance></interface><fqname>@1.0::IMyTest/default</fqname></hal>
<hal format="hidl">

5.3 运行Service

手动后台运行

adb root
adb remount
./vendor/bin/hw/android.hardware.my_test@1.0-service &

5.4 运行client

./system/bin/my_hidl_test &

运行后查看系统日志,有如下内容则成功:

01-01 00:01:58.127 13377 13377 E my_hidl_test: success to get my_test service.....
01-01 00:01:58.127 11110 11110 E android.hardware.my_test@1.0-service: MyTest::add  a = 3,b = 4,result = 7
01-01 00:01:58.128 13377 13377 E my_hidl_test: my_test service add: result = 0
01-01 00:01:58.128 11110 11110 E android.hardware.my_test@1.0-service: MyTest::sub  a = 8,b = 3,result = 5
01-01 00:01:58.128 13377 13377 E my_hidl_test: my_test service sub: result = 0
01-01 00:01:58.128 11110 11110 E android.hardware.my_test@1.0-service: MyTest::mul  a = 3,b = 8,result = 24
01-01 00:01:58.128 13377 13377 E my_hidl_test: my_test service mul: result = 0
01-01 00:01:58.129 11110 11110 E android.hardware.my_test@1.0-service: MyTest::div  a = 8,b = 2,result = 4
01-01 00:01:58.129 13377 13377 E my_hidl_test: my_test service div: result = 0

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/247817.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

c语言实战之贪吃蛇

文章目录 前言效果展示游戏用到的图片游戏思路一览游戏前准备一、贪吃蛇、食物、障碍物节点坐标的结构体二、枚举游戏状态、和贪吃蛇的方向三、维护运行的结构体 游戏开始前的初始化一、学习图形库相关知识二、设置背景三、欢迎界面四、初始化贪吃蛇五、生成障碍物六、生成食物…

22.云原生之GitLab CICD实战及解析【干货】

云原生专栏大纲 文章目录 准备工作gitlab-ci.yml流水线mven打包项目制作并推送镜像kaniko方式docker方式 部署到k8s验证执行情况 GitLab Runner k8s执行器工作流程注册配置kubernetes runnerkubernetes runner配置通过修改 Pod 规范为每个构建作业创建一个 PVC自定义卷装载持久…

腾讯云Linux(OpenCloudOS)安装tomcat9(9.0.85)

腾讯云Linux(OpenCloudOS)安装tomcat9 下载并上传 tomcat官网 https://tomcat.apache.org/download-90.cgi 下载完成后上传至自己想要放置的目录下 解压文件 输入tar -xzvf apache-tomcat-9.0.85.tar.gz解压文件&#xff0c;建议将解压后的文件重新命名为tomcat,方便后期进…

如何编辑图片上的文字?分享5种可以编辑的工具!

在数字时代&#xff0c;图片已经成为信息传递的重要载体。有时候&#xff0c;我们需要在图片上添加文字&#xff0c;以增加信息的清晰度或创意性。那么&#xff0c;如何编辑图片上的文字呢&#xff1f;本文将为你揭秘编辑图片文字的必备工具&#xff0c;让你轻松实现创意表达。…

python小项目:口令保管箱

代码&#xff1a; #! python3 # python 编程-----口令保管箱passwords{emails: F7minlBDDuvMJuxESSKHFhTxFtjVB6,blog:VmALvQyKAxiVH5G8v01if1MLZF3sdt,luggage:12345,} import sys,pyperclip if len(sys.argv)<2:print(usage:python python3文件[accout]-copy accout pass…

【Linux网络编程】网络编程套接字(1)

【Linux网络编程】网络编程套接字(1) 目录 【Linux网络编程】网络编程套接字(1)源IP地址和目的IP地址端口号端口号和进程ID的关系 网络通信TCP协议UDP协议网络字节序socket编程接口简单的UDP网络程序 作者&#xff1a;爱写代码的刚子 时间&#xff1a;2024.1.29 前言&#xff1…

《HTML 简易速速上手小册》第9章:HTML5 新特性(2024 最新版)

文章目录 9.1 HTML5 新增标签和属性9.1.1 基础知识9.1.2 案例 1&#xff1a;创建一个结构化的博客页面9.1.3 案例 2&#xff1a;使用新的表单元素创建事件注册表单9.1.4 案例 3&#xff1a;创建一个具有高级搜索功能的搜索表单 9.2 HTML5 表单增强9.2.1 基础知识9.2.2 案例 1&a…

【Algorithms 4】算法(第4版)学习笔记 01 - 1.5 案例研究:union-find算法

文章目录 前言参考目录学习笔记1&#xff1a;动态连通性2&#xff1a;UF 实现 1&#xff1a;快速查找 quick-find2.1&#xff1a;demo 演示 12.2&#xff1a;demo 演示 22.3&#xff1a;quick-find 代码实现3&#xff1a;UF 实现 2&#xff1a;快速合并 quick-union3.1&#xf…

第 6 章:Linux中使用时钟、计时器和信号

在本章中&#xff0c;我们将开始探索Linux环境中可用的各种计时器。随后&#xff0c;我们将深入了解时钟的重要性&#xff0c;并探讨UNIX时间的概念。接下来&#xff0c;我们将揭示在Linux中使用POSIX准确测量时间间隔的方法。之后&#xff0c;我们将进入std::chrono的领域&…

第二篇:数据结构与算法-顺序表

顺序表 动态星空制作 #include <iostream> #include <graphics.h> #include <Windows.h> using namespace std;#define MAX_START 100 //星星数 #define MAX_MARGIN 80 //随机地 #define WIN_WIDTH 640 //窗口宽 #define WIN_HEIGHT 480 //窗口高 #define…

.ui文件相关

目录 ui类生成过程&#xff1a; 提问&#xff1a; 等以后自己熟练了用代码写这些样式内容&#xff0c;尽量用代码写&#xff0c;原因很简单&#xff1a; 用代码写的可以直接修改代码&#xff0c;但是在设计界面修改的东西&#xff0c;电脑没有QC这玩意&#xff0c;还真不好改…

计算机网络-数据交换方式(电路交换 报文交换 分组交换及其两种方式 )

文章目录 为什么要数据交换&#xff1f;总览电路交换电路交换的各个阶段建立连接数据传输释放连接 电路交换的特点电路交换的优缺点 报文交换报文交换流程报文交换的优缺点 分组交换分组交换流程分组交换的优缺点 数据交换方式的选择分组交换的两种方式数据报方式数据报方式的特…

深入浅出 diffusion(4):pytorch 实现简单 diffusion

1. 训练和采样流程 2. 无条件实现 import torch, time, os import numpy as np import torch.nn as nn import torch.optim as optim from torchvision.datasets import MNIST from torchvision import transforms from torch.utils.data import DataLoader from torchvision.…

基于Redis的高可用分布式锁——RedLock

目录 RedLock简介 RedLock工作流程 获取锁 释放锁 RedLock简介 Redis作者提出来的高可用分布式锁由多个完全独立的Redis节点组成&#xff0c;注意是完全独立&#xff0c;而不是主从关系或者集群关系&#xff0c;并且一般是要求分开机器部署的利用分布式高可以系统中大多数存…

基于ncurse的floppy_bird小游戏

1. 需求分析 将运动分解为鸟的垂直运动和杆的左右运动。 2. 概要设计 2.1 鸟运动部分 2.2 杆的运动 3. 代码实现 #include <stdio.h> #include <ncurses.h>#include <stdlib.h> #include <time.h>int vx 0; int vy 1;int bird_r; int bird_c;int…

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测(matlab)

2023年算法CDO-CNN-BiLSTM-ATTENTION回归预测&#xff08;matlab&#xff09; CDO-CNN-BiLSTM-Attention切诺贝利灾难优化器优化卷积-长短期记忆神经网络结合注意力机制的数据回归预测 Matlab语言。 切诺贝利灾难优化器Chernobyl Disaster Optimizer (CDO)是H. Shehadeh于202…

力扣题集(第一弹)

一日练,一日功;一日不练十日空。 学编程离不开刷题&#xff0c;接下来让我们来看几个力扣上的题目。 1. 242. 有效的字母异位词 题目描述 给定两个字符串 s 和 t &#xff0c;编写一个函数来判断 t 是否是 s 的字母异位词。 注意&#xff1a;若 s 和 t 中每个字符出现的次数…

详解OpenHarmony各部分文件在XR806上的编译顺序

大家好&#xff0c;今天我们来谈一谈编程时一个很有趣的话题——编译顺序。我知道&#xff0c;一提到编译可能大家会感到有点儿头疼&#xff0c;但请放心&#xff0c;我不会让大家头疼的。我们要明白&#xff0c;在开始写代码之前&#xff0c;了解整个程序的编译路径是十分有必…

[晓理紫]每日论文分享(有中文摘要,源码或项目地址)--大模型、扩散模型、视觉语言导航

专属领域论文订阅 VX 关注{晓理紫}&#xff0c;每日更新论文&#xff0c;如感兴趣&#xff0c;请转发给有需要的同学&#xff0c;谢谢支持 如果你感觉对你有所帮助&#xff0c;请关注我&#xff0c;每日准时为你推送最新论文。 为了答谢各位网友的支持&#xff0c;从今日起免费…

【国产MCU】-认识CH32V307及开发环境搭建

认识CH32V307及开发环境搭建 文章目录 认识CH32V307及开发环境搭建1、CH32V307介绍2、开发环境搭建3、程序固件下载1、CH32V307介绍 CH32V307是沁恒推出的一款基于32位RISC-V设计的互联型微控制器,配备了硬件堆栈区、快速中断入口,在标准RISC-V基础上大大提高了中断响应速度…