1、概述
该程序是基于OpenHarmony标准系统编写的平台驱动:ADC驱动。
系统版本 penharmony5.0.0
开发板:dayu200
编译情况:ubuntu22
摆设路径: //sample/02_platform_adc
2、基础知识
2.1、ADC简介
ADC(Analog to Digital Converter),即模拟-数字转换器,可将模拟信号转换成对应的数字信号,便于存储与计算等操作。除电源线和地线之外,ADC只需要1根线与被测量的设备举行连接。
2.2、ADC平台驱动
在HDF框架中,同范例设备对象较多时(大概同时存在十几个同范例配置器),若接纳独立服务模式,则需要配置更多的设备节点,且相干服务会占据更多的内存资源。相反,接纳同一服务模式可以利用一个设备服务作为管理器,同一处理全部同范例对象的外部访问(这会在配置文件中有所表现),实现便捷管理和节流资源的目的。ADC模块即接纳同一服务模式。如下图所示:
ADC模块各分层的作用为:
- 接口层:提供打开设备,写入数据,关闭设备的能力。
- 核心层:主要负责服务绑定、初始化以及释放管理器,并提供添加、删除以及获取控制器的能力。
- 适配层:由驱动适配者实现与硬件相干的具体功能,如控制器的初始化等。
在同一模式下,全部的控制器都被核心层同一管理,并由核心层同一发布一个服务供接口层,因此这种模式下驱动无需再为每个控制器发布服务。
具体资料请参考官网地点:ADC平台驱动
2.2.1、ADC平台驱动相干函数
为了保证上层在调用ADC接口时可以或许精确的操作硬件,核心层在//drivers/hdf_core/framework/support/platform/include/adc/adc_core.h中界说了以下钩子函数。驱动适配者需要在适配层实现这些函数的具体功能,并与这些钩子函数挂接,从而完成接口层与核心层的交互。
AdcMethod和AdcLockMethod界说:
- struct AdcMethod {
- int32_t (*read)(struct AdcDevice *device, uint32_t channel, uint32_t *value);
- int32_t (*start)(struct AdcDevice *device);
- int32_t (*stop)(struct AdcDevice *device);
- };
- struct AdcLockMethod {
- int32_t (*lock)(struct AdcDevice *device);
- void (*unlock)(struct AdcDevice *device);
- };
复制代码 在适配层中,AdcMethod必须被实现,AdcLockMethod可根据实际情况思量是否实现。核心层提供了默认的AdcLockMethod,其中利用Spinlock作为保护临界区的锁:
- static int32_t AdcDeviceLockDefault(struct AdcDevice *device)
- {
- if (device == NULL) {
- return HDF_ERR_INVALID_OBJECT;
- }
- return OsalSpinLock(&device->spin);
- }
- static void AdcDeviceUnlockDefault(struct AdcDevice *device)
- {
- if (device == NULL) {
- return;
- }
- (void)OsalSpinUnlock(&device->spin);
- }
- static const struct AdcLockMethod g_adcLockOpsDefault = {
- .lock = AdcDeviceLockDefault,
- .unlock = AdcDeviceUnlockDefault,
- };
复制代码 若实际情况不允许利用Spinlock,驱动适配者可以思量利用其他范例的锁来实现一个自界说的AdcLockMethod。一旦实现了自界说的AdcLockMethod,默认的AdcLockMethod将被覆盖。
(1)AdcMethod结构体成员的钩子函数功能说明
函数成员入参出参返回值功能startdevice:结构体指针,核心层ADC控制器无HDF_STATUS相干状态开启ADC设备stopdevice:结构体指针,核心层ADC控制器无HDF_STATUS相干状态关闭HDF_STATUS相干状态readdevice:结构体指针,核心层ADC控制器
channel:uint32_t范例,传入通道号value:uint32_t范例指针,传出的信号数据HDF_STATUS相干状态读取ADC采样信号的数据 (2)AdcLockMethod结构体成员函数功能说明
函数成员入参出参返回值功能lockdevice:结构体指针,核心层ADC控制器无HDF_STATUS相干状态获取临界区锁unlockdevice:结构体指针,核心层ADC控制器无HDF_STATUS相干状态释放临界区锁 2.2.2、ADC平台驱动开发步调
(1)实例化驱动入口
- 实例化HdfDriverEntry结构体成员。
- 调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
(2)配置属性文件
- 在device_info.hcs文件中添加deviceNode描述。
- 【可选】添加adc_config.hcs器件属性文件。
(3)实例化核心层接口函数
- 初始化AdcDevice成员。
- 实例化AdcDevice成员AdcMethod。
(4)驱动调试
- 【可选】针对新增驱动程序,建议验证驱动根本功能,例如挂载后的测试用例是否成功等。
2.3、ADC应用程序
ADC模块提供的主要接口如表1所示,具体API详见//drivers/hdf_core/framework/include/platform/adc_if.h。
ADC驱动API接口功能介绍如下所示:
接口名接口描述DevHandle AdcOpen(uint32_t number)打开ADC设备void AdcClose(DevHandle handle)关闭ADC设备int32_t AdcRead(DevHandle handle, uint32_t channel, uint32_t *value)读取AD转换结果值 利用ADC设备的一般流程如下所示:
具体资料请参考官网地点:ADC应用程序
3、程序解析
3.1、代码目录。
- zcc@ubuntu22:~/oh5.0.0/sample/02_platform_adc$ tree
- .
- ├── app
- │ └── adc_test.c
- ├── BUILD.gn
- ├── bundle.json
- 2 directories, 5 files
复制代码 在代码中依赖两个配置文件分别为device_info.hcs和adc_config_linux.hcs。
3.2、配置文件
3.2.1、device_info.hcs
device_info.hcs文件用于驱动设备描述,具体内容如下:
- ....
- platform :: host {
- device_adc :: device {
- device0 :: deviceNode { // ADC控制器信息描述
- policy = 2; // 对外发布服务,必须为2,用于定义ADC管理器的服务
- priority = 50;
- permission = 0644;
- moduleName = "HDF_PLATFORM_ADC_MANAGER"; // 这与drivers/hdf_core/framework/support/platform/src/adc/adc_core.c的g_adcManagerEntry.moduleName对应,它主要负责ADC的管理,必须是HDF_PLATFORM_ADC_MANAGER
- serviceName = "HDF_PLATFORM_ADC_MANAGER"; // 驱动对外发布服务的名称,ADC管理器服务名设置为HDF_PLATFORM_ADC_MANAGER
- deviceMatchAttr = "hdf_platform_adc_manager"; // 驱动私有数据匹配的关键字,ADC管理器没有使用,可忽略
- }
- device1 :: deviceNode {
- policy = 0; // 等于0,不对内核和应用发布服务
- priority = 55; // 驱动驱动优先级
- permission = 0644; // 驱动创建设备节点权限
- moduleName = "linux_adc_adapter"; // 用于指定驱动名称,必须是linux_adc_adapter
- deviceMatchAttr = "linux_adc_adapter_0"; // 用于配置控制器私有数据,必须与adc_config.hcs中对应控制器保持一致
- }
- }
- }
- .....
复制代码 注意:
- device0:ADC控制器,为了引入HDF_PLATFORM_ADC_MANAGER驱动,必须要。
- device1:ADC实际操作接口。
- moduleName:该驱动名称,必须是linux_adc_adapter,//drivers/hdf_core/adapter/khdf/linux/platform/adc/adc_iio_adapter.c已编写好。
- serviceName:对外发布服务的名称,必须是HDF_PLATFORM_ADC_MANAGER。
- deviceMatchAttr:关键字必须与config.hcs的match_attr匹配。
- 3.2.2、adc_config_linux.hcs

创建adc_config_linux.hcs,用于界说私有变量,具体内容如下:
- root {
- platform {
- adc_config {
- match_attr = "linux_adc_adapter_0"; // 与device_info.hcs的deviceMatchAttr的值一致
- template adc_device { // 必须与//drivers/hdf_core/adapter/khdf/linux/platform/adc/adc_iio_adapter.c的配置树定义保持一致
- serviceName = ""; // 服务名称
- deviceNum = 0; // 设备号标识
- channelNum = 8; // ADC通道数量
- driver_channel0_name = ""; // 通道0在linux文件系统路径
- driver_channel1_name = ""; // 通道1在linux文件系统路径
- driver_channel2_name = ""; // 通道2在linux文件系统路径
- driver_channel3_name = ""; // 通道3在linux文件系统路径
- driver_channel4_name = ""; // 通道4在linux文件系统路径
- driver_channel5_name = ""; // 通道5在linux文件系统路径
- driver_channel6_name = ""; // 通道6在linux文件系统路径
- driver_channel7_name = ""; // 通道7在linux文件系统路径
- scanMode = 0; // 扫描模式
- rate = 1000; // 转换速率
- }
- device_adc_0x0000 :: adc_device {
- deviceNum = 0;
- channelNum = 8;
- driver_channel0_name = "/sys/bus/iio/devices/iio:device0/in_voltage0_raw";
- driver_channel1_name = "/sys/bus/iio/devices/iio:device0/in_voltage1_raw";
- driver_channel2_name = "/sys/bus/iio/devices/iio:device0/in_voltage2_raw";
- driver_channel3_name = "/sys/bus/iio/devices/iio:device0/in_voltage3_raw";
- driver_channel4_name = "/sys/bus/iio/devices/iio:device0/in_voltage4_raw";
- driver_channel5_name = "/sys/bus/iio/devices/iio:device0/in_voltage5_raw";
- driver_channel6_name = "/sys/bus/iio/devices/iio:device0/in_voltage6_raw";
- driver_channel7_name = "/sys/bus/iio/devices/iio:device0/in_voltage7_raw";
- }
- }
- }
- }
复制代码 ADC实际驱动是//drivers/hdf_core/adapter/khdf/linux/platform/adc/adc_iio_adapter.c,template adc_device界说的各项关键变量是由adc_iio_adapter.c决定,不可修改。
adc_iio_adapter.c实际是对Linux IIO子系统举行操作来控制ADC。
注意:
- channelNum:表示通道数量
- driver_channelX_name:必须是从0开始
3.3、HDF驱动
ADC平台驱动是//drivers/hdf_core/adapter/khdf/linux/platform/adc/adc_iio_adapter.c,用户不必编写HDF驱动。
下面通太过析AdcOpen和AdcRead函数来看后续的执行过程,起首来看下gpio控制器(GpioCntlr)的类图,如下:
主要操作过程如下:
- drivers\hdf_core\framework\support\platform\src\adc\adc_if.c
- DevHandle AdcOpen(uint32_t number)//打开一个ADC(模数转换器)设备
- |-->struct AdcDevice *device = AdcDeviceGet(number);//获取指定编号的ADC设备
- |-->return AdcManagerFindDevice(number)
- |-->return device = g_adcManager->devices[number];//g_adcManager为AdcManager全局静态变量
- |-->ret = AdcDeviceStart(device);//启动该ADC设备
- |-->ret = device->ops->start(device);
- |-->return (DevHandle)device;//成功获取了设备指针并且启动设备也成功,函数最后会将 `device` 强制转换为 `DevHandle` 类型,并返回这个设备句柄。
复制代码 由以上可知打开adc设备主要过程是从g_adcManager中获取具体设备,并调用设备的启动(start)函数,那么下面的重点便是分析g_adcManager的赋值过程。
具体过程需要起首了解adc驱动的注册过程,主要是通过适配器(linux_adc_adapter)完成:
- struct HdfDriverEntry g_adcLinuxDriverEntry = {
- .moduleVersion = 1,
- .Bind = NULL,
- .Init = LinuxAdcInit,
- .Release = LinuxAdcRelease,
- .moduleName = "linux_adc_adapter",
- };
- HDF_INIT(g_adcLinuxDriverEntry);
复制代码 在驱动框架hdf初始化时会调用匹配函数
- static int32_t LinuxAdcInit(struct HdfDeviceObject *device)
- |--> DEV_RES_NODE_FOR_EACH_CHILD_NODE(device->property, childNode)//根据设备树文件遍历
- |--> ret = AdcIioParseAndDeviceAdd(device, childNode)//根据遍历的信息添加adc设备
- |-->struct AdcIioDevice *adcDevice = (struct AdcIioDevice *)OsalMemCalloc(sizeof(*adcDevice))//分配adc设备内存信息
- |-->ret = AdcIioReadDrs(adcDevice, node)//获取adc_config.hcs中的配置信息
- |-->adcDevice->device.priv = (void *)node;//将配置信息赋值到私有数据中
- |-->adcDevice->device.ops = &g_method;//赋值操作集,包含启、停、读,最终是通过file_open、file_close以及kernel_read函数实现的
- |-->ret = AdcDeviceAdd(&adcDevice->device);//添加adc设备
- |-->ret = AdcManagerAddDevice(device);//添加adc设备到全局静态变量g_adcManager
- |-->struct AdcManager *manager = g_adcManager;
- |-->manager->devices[device->devNum] = device//和前面打开adc设备的操作过程匹配上了。
复制代码 操作集中包含的AdcIioStart、AdcIioStop和AdcIioRead,终极是通过file_open、file_close以及kernel_read函数实现的,以AdcIioRead为例可见如下:
- //drivers\hdf_core\adapter\khdf\linux\platform\adc\adc_iio_adapter.c
- static int32_t AdcIioRead(struct AdcDevice *device, uint32_t channel, uint32_t *val)
- |-->ret = kernel_read(adcDevice->fp[channel], strValue, ADC_STRING_VALUE_LEN, &pos);//从指定通道的文件指针 `adcDevice->fp[channel]` 读取数据
- |-->*val = simple_strtoul(strValue, NULL, 0);//将 `strValue` 数组中的字符串转换为无符号长整型
复制代码 由上分析可大概理解ADC的AdcOpen过程,主要是通过适配器(linux_adc_adapter)根据配置文件(adc_config.hcs)完成驱动的初始化,给全局g_adcManager变量的赋值并设置操作集(adcDevice->device.ops = &g_method),当设置完成后便可支撑接口函数(AdcOpen、AdcClose、AdcRead),当调用接口函数时终极会调到操作集中,并终极由内核函数的file_open、file_close以及kernel_read函数实现。
以3.4、参与Linux内核编译
编辑//kernel/linux/config/linux-5.10/arch/arm64/configs/rk3568_standard_defconfig,启用CONFIG_DRIVERS_HDF_PLATFORM_ADC,具体内容如下:
- CONFIG_DRIVERS_HDF_PLATFORM_ADC=y
复制代码
3.5、应用程序
3.5.1、adc_test.c
添加平台驱动ADC的头文件,具体内容如下:
- #include "adc_if.h" // ADC标准接口头文件
复制代码 程序可通过,具体内容如下:
- int main(int argc, char* argv[])
- {
- DevHandle handle = NULL;
- int32_t ret;
- uint32_t value;
- // 解析参数
- parse_opt(argc, argv);
- printf("adc_device: %d\n", m_adc_device);
- printf("adc_channel: %d\n", m_adc_channel);
- // 打开ADC设备
- handle = AdcOpen(m_adc_device);
- if (handle == NULL) {
- PRINT_ERROR("AdcOpen failed\n");
- return -1;
- }
- // 进行AD转换并读取转换结果
- ret = AdcRead(handle, m_adc_channel, &value);
- if (ret != 0) {
- PRINT_ERROR("AdcRead failed and ret = %d\n", ret);
- AdcClose(handle);
- return -1;
- }
- printf("Adc Device(%d), Channel(%d) read successful and value = %d\n", m_adc_device, m_adc_channel, value);
- // 关闭ADC设备
- AdcClose(handle);
- return 0;
- }
复制代码 3.5.2、BUILD.gn
- import("//build/ohos.gni")
- import("//drivers/hdf_core/adapter/uhdf2/uhdf.gni")
- print("samples: compile rk3568_adc_test")
- ohos_executable("rk3568_adc_test") {
- sources = [ "adc_test.c" ]
- include_dirs = [
- "$hdf_framework_path/include",
- "$hdf_framework_path/include/core",
- "$hdf_framework_path/include/osal",
- "$hdf_framework_path/include/platform",
- "$hdf_framework_path/include/utils",
- "$hdf_uhdf_path/osal/include",
- "$hdf_uhdf_path/ipc/include",
- "//base/hiviewdfx/hilog/interfaces/native/kits/include",
- "//third_party/bounds_checking_function/include",
- ]
- deps = [
- "$hdf_uhdf_path/platform:libhdf_platform",
- "$hdf_uhdf_path/utils:libhdf_utils",
- "//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog",
- ]
- cflags = [
- "-Wall",
- "-Wextra",
- "-Werror",
- "-Wno-format",
- "-Wno-format-extra-args",
- ]
- subsystem_name = "applications"
- part_name = "rk3568_adc_test"
- install_enable = true
- }
复制代码 4、程序编译
5、运行结果
可以将ADC引脚通过引线接入排针线中的GPIO3_C2中,通过设置GPIO3_C2的高低电平可以查看ADC的变革。如下:
该程序运行结果如下所示:
6、参考资料
凌蒙派-RK3568开发板-基础外设类:ADC驱动
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |