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定义:

c 复制代码
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作为保护临界区的锁:

c 复制代码
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结构体成员的钩子函数功能说明

函数成员 入参 出参 返回值 功能
start device:结构体指针,核心层ADC控制器 HDF_STATUS相关状态 开启ADC设备
stop device:结构体指针,核心层ADC控制器 HDF_STATUS相关状态 关闭HDF_STATUS相关状态
read device:结构体指针,核心层ADC控制器 channel:uint32_t类型,传入通道号 value:uint32_t类型指针,传出的信号数据 HDF_STATUS相关状态 读取ADC采样信号的数据

(2)AdcLockMethod结构体成员函数功能说明

函数成员 入参 出参 返回值 功能
lock device:结构体指针,核心层ADC控制器 HDF_STATUS相关状态 获取临界区锁
unlock device:结构体指针,核心层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、代码目录。

shell 复制代码
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文件用于驱动设备描述,具体内容如下:

json 复制代码
....
        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中对应控制器保持一致
                }
            }
        }
.....

注意:

  • 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,用于定义私有变量,具体内容如下:

json 复制代码
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)的类图,如下:

主要操作过程如下:

c 复制代码
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)完成:

c 复制代码
struct HdfDriverEntry g_adcLinuxDriverEntry = {
    .moduleVersion = 1,
    .Bind = NULL,
    .Init = LinuxAdcInit,
    .Release = LinuxAdcRelease,
    .moduleName = "linux_adc_adapter",
};
HDF_INIT(g_adcLinuxDriverEntry);

在驱动框架hdf初始化时会调用匹配函数

c 复制代码
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为例可见如下:

c 复制代码
//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的头文件,具体内容如下:

c 复制代码
#include "adc_if.h"                 // ADC标准接口头文件

程序可通过,具体内容如下:

c 复制代码
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
shell 复制代码
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驱动

相关推荐
小龙报1 小时前
【51单片机】串口通讯从入门到精通:原理拆解 + 参数详解 + 51 单片机实战指南
c语言·驱动开发·stm32·单片机·嵌入式硬件·物联网·51单片机
dlz083618 小时前
POE驱动开发流程
驱动开发
嵌入式-老费20 小时前
Linux camera驱动开发(DVP接口的camera sensor)
驱动开发
VernonJsn1 天前
visual studio 2022的windows驱动开发
ide·驱动开发·visual studio
嵌入式郑工2 天前
RK3566 LubanCat 开发板 USB Gadget 配置完整复盘
linux·驱动开发·ubuntu
雾削木3 天前
树莓派 ESPHome 固件编译与烧录全攻略(解决超时与串口识别问题)
驱动开发
春日见4 天前
win11 分屏设置
java·开发语言·驱动开发·docker·单例模式·计算机外设
DarkAthena4 天前
【GaussDB】手动编译不同python版本的psycopg2驱动以适配airflow
驱动开发·python·gaussdb
松涛和鸣4 天前
DAY66 SPI Driver for ADXL345 Accelerometer
linux·网络·arm开发·数据库·驱动开发
嵌入式郑工5 天前
# RK3576 平台 RTC 时钟调试全过程
linux·驱动开发·ubuntu