当前位置: 首页 > news >正文

RK3568DAYU开发板-平台驱动开发:ADC驱动

1、概述

该程序是基于OpenHarmony标准系统编写的平台驱动:ADC驱动。

系统版本:openharmony5.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_MANAGERserviceName = "HDF_PLATFORM_ADC_MANAGER";       // 驱动对外发布服务的名称,ADC管理器服务名设置为HDF_PLATFORM_ADC_MANAGERdeviceMatchAttr = "hdf_platform_adc_manager";   // 驱动私有数据匹配的关键字,ADC管理器没有使用,可忽略}device1 :: deviceNode {policy = 0;                                     // 等于0,不对内核和应用发布服务priority = 55;                                  // 驱动驱动优先级permission = 0644;                              // 驱动创建设备节点权限moduleName = "linux_adc_adapter";               // 用于指定驱动名称,必须是linux_adc_adapterdeviceMatchAttr = "linux_adc_adapter_0";        // 用于配置控制器私有数据,必须与adc_config.hcs中对应控制器保持一致}}}
.....

注意:

  • device0ADC控制器,为了引入HDF_PLATFORM_ADC_MANAGER驱动,必须要。

  • device1ADC实际操作接口

  • 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驱动

相关文章:

  • vpt_denoise
  • Python实例题:使用Python定制词云
  • Linux系统入门篇三
  • 流量红利的破局之道—深度解析OPPO应用商店 CPD广告运营
  • Python安装、pycharm配置和添加库下载
  • 【Bluedroid】init_stack_internal 函数全流程源码解析
  • 【通关文件操作(上)】--文件的意义和概念,二进制文件和文本文件,文件的打开和关闭,文件的顺序读写
  • 智能体赋能效率,企业知识库沉淀价值:UMI企业智脑的双轮驱动!
  • Pydantic 学习与使用
  • TDengine 中的存储配置
  • 电商 API 开发指南:基于唯品会 API 实现商品详情页动态数据采集
  • 推荐GitHub项目:Pangolin开源Amazon关键词解析器(Python)与电商数据采集API技术剖析
  • 《仿盒马》app开发技术分享-- 确认订单页(数据展示)(端云一体)
  • [网页五子棋][用户模块]数据库设计和配置(MyBatis)、约定前后端交互接口、服务器开发
  • java中的定时期
  • TWTSolutions水厂污水厂设计计算软件:化学强化絮凝单元
  • MobaXterm连接Docker Desktop中的容器(shell)
  • oracle在线迁移数据文件
  • 系统设计——项目设计经验总结3
  • JDK21深度解密 Day 6:ZGC与内存管理进化
  • 武汉做网站建设/seo外链自动群发工具
  • 杭州网站建设多少钱/百度推广怎么登陆
  • 河北涿州建设局网站/查询网站流量的网址
  • wordpress微信登录申请/外贸推广优化公司
  • 济南建设厅官方网站/内存优化大师
  • 厦门做网站建设/网络公司关键词排名