14. Linux内核ADC驱动¶
在嵌入式系统中,模拟信号与数字信号的转换是连接物理世界与计算系统的核心环节。 模数转换器(Analog-to-Digital Converter,简称ADC)作为实现这一转换的关键器件,负责将温度、电压、电流等连续变化的模拟量,转换为计算机可识别、处理的离散数字量。
事实上,上一章节介绍的IIO子系统在嵌入式ADC驱动开发中应用广泛,例如TI(德州仪器)的ADS1115、NXP(恩智浦)IMX系列SoC内置ADC、Analog Devices(ADI)的AD7606高速ADC,以及瑞芯微SoC内置SARADC驱动,均采用IIO驱动框架开发,实现标准化的模拟信号采集接口。
14.1. ADC核心概念¶
14.1.1. ADC定义¶
ADC即模数转换器,是一种将连续变化的模拟电压(或电流)信号,转换为离散的数字信号的电子器件。 其核心作用是搭建模拟信号与数字系统之间的桥梁,使得嵌入式处理器能够读取、分析和处理外部物理世界的模拟信息。
例如,温度传感器输出的模拟电压信号,经过ADC转换后,变成数字值,处理器才能根据该数字值计算出对应的温度。
14.1.2. ADC分类¶
根据转换原理的不同,ADC主要分为以下几类,各类ADC的性能特点不同,适用于不同的应用场景:
逐次逼近型ADC(SAR ADC):本章重点讲解的类型,采用逐次比较逻辑,从最高位到最低位一位一位逼近真实电压,最终得到数字量,其特点是转换速度中等(通常为us级)、分辨率较高(8~16位)、功耗低、成本适中,是嵌入式系统中最常用的ADC类型。
积分型ADC:分为双积分、单积分等类型,转换精度高、抗干扰能力强,但转换速度慢(通常为ms级),适用于对精度要求高、对速度要求低的场景,如数字万用表。
流水线型ADC:采用分级转换的方式,转换速度极快(通常为ns级)、分辨率高,但功耗和成本较高,适用于高速数据采集场景,如示波器、雷达。
Flash型ADC:转换速度最快(ps~ns级),但分辨率低(通常≤8位)、功耗和成本极高,适用于超高速、低分辨率的特殊场景。
14.1.3. ADC性能参数¶
ADC性能参数直接决定了信号采集的质量,也是驱动开发中需要重点关注的指标:
分辨率:指ADC将模拟信号量化为数字信号时的最小间隔,通常以“位(bit)”为单位,分为8位、10位、12位等。分辨率越高,量化精度越高,能识别的模拟信号变化越小。例如,10位ADC的量化范围为0~2¹⁰-1(0~1023),若参考电压为3.3V,则最小量化电压为3.3V/1024≈3.22mV。
采样率:指ADC每秒能完成的转换次数,单位为Hz(赫兹)。采样率越高,能捕捉到的模拟信号变化越迅速,适用于高速变化的信号采集(如音频信号);采样率越低,功耗越低,适用于缓慢变化的信号(如温度、湿度)。
参考电压(Vref):ADC转换的基准电压,决定了模拟信号的输入范围(通常为0~Vref或-Vref~+Vref),参考电压的稳定性直接影响ADC的转换精度。
转换精度:指ADC转换结果与真实模拟信号值的偏差,通常用误差(如LSB,最低有效位)表示。误差越小,转换精度越高,受参考电压、时钟稳定性、噪声等因素影响。
输入范围:ADC能正常转换的模拟信号电压范围,超出该范围的信号会被截断,导致转换结果失真。
14.2. SAR型ADC工作原理¶
SAR型ADC的核心是逐次逼近寄存器(SAR)和比较器,其转换过程类似“猜数字”,通过逐次比较、逼近,最终得到与模拟信号对应的数字值,具体工作流程如下:
初始化:ADC上电后,SAR寄存器清零,准备开始转换;同时,参考电压Vref被送入数模转换器(DAC)。
逐次逼近:从SAR寄存器的最高位开始,依次将每一位设为1,通过DAC将该数字值转换为对应的模拟电压,与输入的模拟信号进行比较:
若DAC输出的模拟电压 ≥ 输入模拟电压,则该位保持为1;
若DAC输出的模拟电压 < 输入模拟电压,则该位清零。
完成转换:当所有位都比较完成后,SAR寄存器中的数字值即为输入模拟信号对应的数字转换结果,处理器读取该数字值。
14.3. Rockchip ADC驱动详解¶
14.3.1. Rockchip ADC接口¶
Rockchip瑞芯微系列SoC内部集成两款独立高性能ADC模块,分别为专用温度采集TSADC、通用模拟量采集SARADC,覆盖芯片温控、外设模拟信号采集的场景需求。
专用温度采集TSADC(Temperature Sensor):主打温感信号采集,专为芯片内核测温、外部温控传感器适配设计,如RK3568芯片的TSADC最高50KS/s采样速率,支持双路温度传感器接入、双通道独立采集,工作温度检测范围-20℃~120℃,温度分辨率5℃;
通用模拟量采集SARADC:主打常规模拟电压信号采集,适配各类模拟电平采集场景,如RK3568芯片的SARADC有10bit硬件采样分辨率,最高可达1MS/s高速采样速率,支持8路单端模拟信号输入通道,输入电压仅支持0~1.8V。
注解
由于TSADC驱动使用的是Thermal子系统并不是IIO子系统,对于我们目前学习情况来说存在过多知识盲区,因此,仅分析使用IIO子系统的SARADC驱动。
14.3.2. Rockchip SARADC驱动详解¶
Rockchip SARADC驱动基于Linux内核的Platform子系统和IIO子系统开发,核心功能是初始化Rockchip SoC内置的SARADC硬件、注册IIO设备、实现多通道模拟电压原始数据读取和缩放系数提供,适配不同型号的SARADC(v1/v2版本),支持中断触发采样、电源管理和调试测试功能。
驱动源码位于: 内核源码/drivers/iio/adc/rockchip_saradc.c
以下以瑞芯微6.1.99内核版本的SARADC驱动进行分析:
核心定义与数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /* SARADC寄存器定义:v1版本核心寄存器 */
#define SARADC_DATA 0x00 /* 采样数据寄存器 */
#define SARADC_CTRL 0x08 /* 控制寄存器 */
#define SARADC_DLY_PU_SOC 0x0c /* 上电到启动转换的延时配置 */
#define SARADC_TIMEOUT msecs_to_jiffies(100) /* 采样超时时间 */
#define SARADC_MAX_CHANNELS 8 /* 最大支持通道数 */
/* v2版本寄存器定义(适配RK3528/RK3588等芯片) */
#define SARADC2_CONV_CON 0x0 /* v2版本转换控制寄存器 */
#define SARADC2_DATA_BASE 0x120 /* v2版本采样数据寄存器基地址 */
#define SARADC2_EN_END_INT BIT(0) /* v2版本中断使能位 */
#define SARADC2_START BIT(4) /* v2版本采样启动位 */
/* 芯片专属配置结构体 */
struct rockchip_saradc_data {
const struct iio_chan_spec *channels; /* IIO通道描述数组 */
int num_channels; /* 通道数量 */
unsigned long clk_rate; /* SARADC工作时钟频率 */
void (*start)(struct rockchip_saradc *info, int chn); /* 采样启动回调 */
int (*read)(struct rockchip_saradc *info); /* 采样读取回调 */
void (*power_down)(struct rockchip_saradc *info); /* 断电回调 */
};
/* 驱动私有数据结构体 */
struct rockchip_saradc {
void __iomem *regs; /* SARADC寄存器基地址映射 */
struct clk *pclk; /* APB总线时钟句柄 */
struct clk *clk; /* SARADC工作时钟句柄 */
struct completion completion; /* 采样完成同步信号量 */
struct regulator *vref; /* 参考电压电源句柄 */
struct mutex lock; /* 访问互斥锁,防止多线程冲突 */
int uv_vref; /* 参考电压值 */
struct reset_control *reset; /* 复位控制器句柄 */
const struct rockchip_saradc_data *data; /* 芯片专属配置指针 */
u16 ast_val; /* 上一次采样值 */
const struct iio_chan_spec *last_chan; /* 上一次采样通道 */
struct notifier_block nb; /* 参考电压变化通知块 */
bool suspended; /* 挂起状态标记 */
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
bool test; /* 测试模式标记 */
u32 chn; /* 测试通道号 */
spinlock_t lock; /* 测试模式自旋锁 */
struct workqueue_struct *wq; /* 测试工作队列 */
struct delayed_work work; /* 测试延迟工作 */
#endif
};
/* IIO操作函数集:绑定读取回调,供IIO子系统调用 */
static const struct iio_info rockchip_saradc_iio_info = {
.read_raw = rockchip_saradc_read_raw,
};
|
关键解析:
寄存器定义:区分v1和v2两个版本的SARADC寄存器,v1为传统版本(如RK3399/RK3568),v2为升级版本(如RK3528/RK3588),寄存器地址和功能存在差异,通过回调函数适配。
struct rockchip_saradc_data:为不同芯片(如RK3066/RK3399/RK3588)定义专属配置,包括通道数量、时钟频率、采样启动/读取/断电的差异化回调,实现多芯片兼容。
struct rockchip_saradc:驱动私有数据核心,封装寄存器映射、时钟、电源、同步信号量等运行时资源,同时记录采样状态和参考电压信息。
struct iio_info:IIO子系统的核心操作集,仅绑定read_raw回调,实现IIO标准的原始数据和缩放系数读取。
IIO通道配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 | /* IIO通道宏定义 */
#define SARADC_CHANNEL(_index, _id, _res) { \
.type = IIO_VOLTAGE, \ /* 通道类型:电压采集通道 */\
.indexed = 1, \ /* 通道索引使能 */\
.channel = _index, \ /* 通道索引值 */\
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \ /* 支持读取原始采样值(_raw节点) */\
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \/* 支持读取缩放系数(_scale节点) */\
.datasheet_name = _id, \ /* 通道名称 */\
.scan_index = _index, \ /* 扫描索引 */\
.scan_type = { \ /* 采样数据格式配置,定义采样值的存储和解析规则 */\
.sign = 'u', \ /* 数据符号:'u'表示无符号数据,SARADC采样值为非负 */\
.realbits = _res, \ /* 有效位数:ADC实际采样分辨率10/12位,由_res参数传入 */\
.storagebits = 16, \ /* 存储位数:采样值在内存中存储的位数,固定16位,兼容不同分辨率 */\
.endianness = IIO_CPU, \ /* 字节序:采样数据字节序与CPU一致,无需额外字节序转换 */\
}, \
}
/* 通用SARADC通道配置:3通道,10位分辨率 */
static const struct iio_chan_spec rockchip_saradc_iio_channels[] = {
SARADC_CHANNEL(0, "adc0", 10),
SARADC_CHANNEL(1, "adc1", 10),
SARADC_CHANNEL(2, "adc2", 10),
};
/* 芯片专属配置:绑定通道数组和回调函数 */
static const struct rockchip_saradc_data saradc_data = {
.channels = rockchip_saradc_iio_channels,
.num_channels = ARRAY_SIZE(rockchip_saradc_iio_channels),
.clk_rate = 1000000,
.start = rockchip_saradc_start_v1,
.read = rockchip_saradc_read_v1,
.power_down = rockchip_saradc_power_down_v1,
};
/* RK3528 SARADC通道配置:4通道,10位分辨率 */
static const struct iio_chan_spec rockchip_rk3528_saradc_iio_channels[] = {
SARADC_CHANNEL(0, "adc0", 10),
SARADC_CHANNEL(1, "adc1", 10),
SARADC_CHANNEL(2, "adc2", 10),
SARADC_CHANNEL(3, "adc3", 10),
};
static const struct rockchip_saradc_data rk3528_saradc_data = {
.channels = rockchip_rk3528_saradc_iio_channels,
.num_channels = ARRAY_SIZE(rockchip_rk3528_saradc_iio_channels),
.clk_rate = 1000000,
.start = rockchip_saradc_start_v2,
.read = rockchip_saradc_read_v2,
};
/* RK3588 SARADC通道配置:8通道,12位分辨率 */
static const struct iio_chan_spec rockchip_rk3588_saradc_iio_channels[] = {
SARADC_CHANNEL(0, "adc0", 12),
SARADC_CHANNEL(1, "adc1", 12),
SARADC_CHANNEL(2, "adc2", 12),
SARADC_CHANNEL(3, "adc3", 12),
SARADC_CHANNEL(4, "adc4", 12),
SARADC_CHANNEL(5, "adc5", 12),
SARADC_CHANNEL(6, "adc6", 12),
SARADC_CHANNEL(7, "adc7", 12),
};
static const struct rockchip_saradc_data rk3588_saradc_data = {
.channels = rockchip_rk3588_saradc_iio_channels,
.num_channels = ARRAY_SIZE(rockchip_rk3588_saradc_iio_channels),
.clk_rate = 1000000,
.start = rockchip_saradc_start_v2,
.read = rockchip_saradc_read_v2,
};
|
关键解析:
SARADC_CHANNEL宏:标准化IIO通道配置,指定通道类型为IIO_VOLTAGE(电压),indexed=1表示通道按索引区分,同时配置原始数据(RAW)和缩放系数(SCALE)的sysfs节点。
通道属性:scan_type中realbits指定ADC分辨率(10/12位),sign=’u’表示无符号数据,storagebits=16表示数据存储为16位,endianness=IIO_CPU表示数据字节序与CPU一致。
多芯片适配:不同芯片的通道数量、分辨率不同(如RK3528为4通道,RK3588为8通道),通过rockchip_saradc_data结构体绑定对应的通道数组和回调函数,实现一套驱动适配多芯片。
sysfs节点生成:IIO子系统根据通道配置,自动生成/sys/bus/iio/devices/下的节点,如in_voltage0_raw(通道0原始值)、in_voltage_scale(缩放系数),供应用层读取。
probe函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | static int rockchip_saradc_probe(struct platform_device *pdev)
{
struct rockchip_saradc *info = NULL;
struct device_node *np = pdev->dev.of_node;
struct iio_dev *indio_dev = NULL;
const struct of_device_id *match;
int ret;
int irq;
if (!np)
return -ENODEV;
/* 分配IIO设备内存 + 私有数据内存 */
indio_dev = devm_iio_device_alloc(&pdev->dev, sizeof(*info));
if (!indio_dev) {
dev_err(&pdev->dev, "failed allocating iio device\n");
return -ENOMEM;
}
/* 获取私有数据结构体地址 */
info = iio_priv(indio_dev);
/* 设备树匹配,根据compatible属性选择芯片专属配置 */
match = of_match_device(rockchip_saradc_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "failed to match device\n");
return -ENODEV;
}
info->data = match->data;
/* 映射SARADC寄存器地址 */
info->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(info->regs))
return PTR_ERR(info->regs);
/* 获取复位控制器 */
info->reset = devm_reset_control_get_exclusive(&pdev->dev, "saradc-apb");
if (IS_ERR(info->reset)) {
ret = PTR_ERR(info->reset);
if (ret != -ENOENT)
return dev_err_probe(&pdev->dev, ret, "failed to get saradc-apb\n");
dev_dbg(&pdev->dev, "no reset control found\n");
info->reset = NULL;
}
/* 初始化采样完成同步信号量 */
init_completion(&info->completion);
/* 获取中断资源,用于采样完成中断 */
irq = platform_get_irq(pdev, 0);
if (irq < 0)
return dev_err_probe(&pdev->dev, irq, "failed to get irq\n");
/* 申请中断 */
ret = devm_request_irq(&pdev->dev, irq, rockchip_saradc_isr,
0, dev_name(&pdev->dev), info);
if (ret < 0) {
dev_err(&pdev->dev, "failed requesting irq %d\n", irq);
return ret;
}
/* 获取并配置时钟:APB总线时钟 + SARADC工作时钟 */
info->pclk = devm_clk_get(&pdev->dev, "apb_pclk");
if (IS_ERR(info->pclk))
return dev_err_probe(&pdev->dev, PTR_ERR(info->pclk), "failed to get pclk\n");
info->clk = devm_clk_get(&pdev->dev, "saradc");
if (IS_ERR(info->clk))
return dev_err_probe(&pdev->dev, PTR_ERR(info->clk), "failed to get adc clock\n");
/* 获取并使能参考电压 */
info->vref = devm_regulator_get(&pdev->dev, "vref");
if (IS_ERR(info->vref))
return dev_err_probe(&pdev->dev, PTR_ERR(info->vref), "failed to get regulator\n");
/* 复位SARADC控制器 */
if (info->reset)
rockchip_saradc_reset_controller(info->reset);
/* 配置SARADC工作时钟频率 */
ret = clk_set_rate(info->clk, info->data->clk_rate);
if (ret < 0) {
dev_err(&pdev->dev, "failed to set adc clk rate, %d\n", ret);
return ret;
}
/* 使能参考电压、时钟,并注册自动释放动作 */
ret = regulator_enable(info->vref);
if (ret < 0) {
dev_err(&pdev->dev, "failed to enable vref regulator\n");
return ret;
}
ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_regulator_disable, info);
if (ret) return ret;
ret = clk_prepare_enable(info->pclk);
if (ret < 0) return ret;
ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_pclk_disable, info);
if (ret) return ret;
ret = clk_prepare_enable(info->clk);
if (ret < 0) return ret;
ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_clk_disable, info);
if (ret) return ret;
/* 配置IIO设备参数 */
platform_set_drvdata(pdev, indio_dev);
indio_dev->name = dev_name(&pdev->dev);
indio_dev->info = &rockchip_saradc_iio_info;
indio_dev->modes = INDIO_DIRECT_MODE;
indio_dev->channels = info->data->channels;
indio_dev->num_channels = info->data->num_channels;
/* 注册IIO触发式缓冲区 */
ret = devm_iio_triggered_buffer_setup(&indio_dev->dev, indio_dev, NULL,
rockchip_saradc_trigger_handler, NULL);
if (ret) return ret;
/* 注册参考电压变化通知,动态更新参考电压值 */
info->nb.notifier_call = rockchip_saradc_volt_notify;
ret = regulator_register_notifier(info->vref, &info->nb);
if (ret) return ret;
ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_regulator_unreg_notifier, info);
if (ret) return ret;
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
/* 初始化测试模式资源:工作队列、延迟工作、自旋锁 */
info->wq = create_singlethread_workqueue("adc_wq");
INIT_DELAYED_WORK(&info->work, rockchip_saradc_test_work);
spin_lock_init(&info->lock);
/* 创建sysfs测试节点 */
ret = sysfs_create_group(&pdev->dev.kobj, &rockchip_saradc_attr_group);
if (ret) return ret;
ret = devm_add_action_or_reset(&pdev->dev, rockchip_saradc_remove_sysgroup, pdev);
if (ret) return ret;
#endif
/* 初始化互斥锁,注册IIO设备 */
mutex_init(&info->lock);
return devm_iio_device_register(&pdev->dev, indio_dev);
}
|
关键解析:
第13行:devm_iio_device_alloc:使用devres机制分配IIO设备和私有数据内存,无需手动释放,简化资源管理。
第19行:iio_priv:从IIO设备结构体中获取私有数据指针,用于后续存储硬件资源句柄和运行状态。
第21行:of_match_device:根据设备树compatible属性,匹配对应的芯片专属配置(rockchip_saradc_data),实现多芯片兼容。
第29行:devm_platform_ioremap_resource:将SARADC寄存器物理地址映射为虚拟地址,供驱动直接操作。
第50行:中断相关操作:获取中断资源,申请采样完成中断,绑定中断处理函数rockchip_saradc_isr,确保采样完成后及时通知驱动读取数据。
第58、61行:时钟管理:获取APB总线时钟(pclk)和SARADC工作时钟(clk),后续配置时钟频率并使能,时钟是SARADC正常工作的基础。
第66行:参考电压管理:获取参考电压(vref)并使能,参考电压的稳定性直接影响ADC采样精度,同时注册电压变化通知,动态更新电压值。
第101-106行:IIO设备配置:绑定IIO操作函数集、通道配置、工作模式,为IIO设备注册做准备。
第109行:IIO触发式缓冲区:注册触发式缓冲区,支持批量采样和数据缓存,提升采样效率。
第134行:devm_iio_device_register:将IIO设备注册到内核,IIO子系统自动生成sysfs节点,完成驱动加载的最后一步。
复位函数
1 2 3 4 5 6 | static void rockchip_saradc_reset_controller(struct reset_control *reset)
{
reset_control_assert(reset); /* 拉低复位信号 */
usleep_range(10, 20); /* 延时10~20us,确保复位稳定 */
reset_control_deassert(reset); /* 拉高复位信号 */
}
|
复位控制器用于初始化SARADC到默认状态,避免上电后状态异常,延时操作确保复位信号稳定生效,兼容不同芯片的复位时序要求。
采样启动函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | /* v1版本采样启动(适配RK3399/RK3568等) */
static void rockchip_saradc_start_v1(struct rockchip_saradc *info, int chn)
{
/* 配置上电到启动转换的延时:8个时钟周期 */
writel_relaxed(8, info->regs + SARADC_DLY_PU_SOC);
/* 选择通道、使能电源、使能中断,触发采样 */
writel(SARADC_CTRL_POWER_CTRL | (chn & SARADC_CTRL_CHN_MASK) |
SARADC_CTRL_IRQ_ENABLE, info->regs + SARADC_CTRL);
}
/* v2版本采样启动(适配RK3528/RK3588等) */
static void rockchip_saradc_start_v2(struct rockchip_saradc *info, int chn)
{
int val;
/* 切换通道时复位控制器,避免通道读取错误 */
if (info->reset)
rockchip_saradc_reset_controller(info->reset);
/* 配置延时参数 */
writel_relaxed(0xc, info->regs + SARADC_T_DAS_SOC);
writel_relaxed(0x20, info->regs + SARADC_T_PD_SOC);
/* 使能采样完成中断 */
val = SARADC2_EN_END_INT << 16 | SARADC2_EN_END_INT;
writel_relaxed(val, info->regs + SARADC2_END_INT_EN);
/* 选择通道、设置单模式、启动采样 */
val = SARADC2_START | SARADC2_SINGLE_MODE | chn;
writel(val << 16 | val, info->regs + SARADC2_CONV_CON);
}
/* 统一采样启动接口,调用对应版本的回调函数 */
static void rockchip_saradc_start(struct rockchip_saradc *info, int chn)
{
info->data->start(info, chn);
}
|
关键解析:
v1版本:通过配置SARADC_CTRL寄存器,实现通道选择、电源使能、中断使能,同时设置上电延时,确保ADC稳定启动后再开始采样。
v2版本:相比v1版本,增加了通道切换时的复位操作,避免多通道切换时出现读取错误,同时使用v2版本专属寄存器配置中断和采样模式。
统一接口:rockchip_saradc_start函数通过芯片专属配置的start回调,自动适配v1/v2版本,实现代码复用。
采样读取函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | /* v1版本读取采样数据 */
static int rockchip_saradc_read_v1(struct rockchip_saradc *info)
{
/* 读取SARADC_DATA寄存器,获取原始采样值 */
return readl_relaxed(info->regs + SARADC_DATA);
}
/* v2版本读取采样数据 */
static int rockchip_saradc_read_v2(struct rockchip_saradc *info)
{
int offset;
int channel;
/* 清除采样完成中断标志 */
writel_relaxed(0x1, info->regs + SARADC2_END_INT_ST);
/* 确定当前采样通道 */
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
channel = info->chn;
#else
channel = info->last_chan->channel;
#endif
/* 计算当前通道的数据寄存器地址 */
offset = SARADC2_DATA_BASE + channel * 0x4;
/* 读取对应通道的采样数据 */
return readl_relaxed(info->regs + offset);
}
/* 统一读取接口 */
static int rockchip_saradc_read(struct rockchip_saradc *info)
{
return info->data->read(info);
}
|
关键解析:
v1版本:直接读取SARADC_DATA寄存器,该寄存器存储当前采样通道的原始数据,操作简单。
v2版本:先清除中断标志,再根据当前采样通道计算数据寄存器地址(v2版本每个通道有独立的数据寄存器,基地址为SARADC2_DATA_BASE,通道间偏移4字节),最后读取对应地址的数据。
统一接口:rockchip_saradc_read函数适配不同版本的读取逻辑,确保驱动上层无需关心硬件版本差异。
中断处理函数
采样完成后触发中断,中断处理函数读取采样数据、关闭ADC电源、唤醒阻塞的采样线程,是采样同步的关键。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | static irqreturn_t rockchip_saradc_isr(int irq, void *dev_id)
{
struct rockchip_saradc *info = dev_id;
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
unsigned long flags;
#endif
/* 读取采样数据,存储到last_val */
info->last_val = rockchip_saradc_read(info);
/* 屏蔽无效位 */
#ifndef CONFIG_ROCKCHIP_SARADC_TEST_CHN
info->last_val &= GENMASK(info->last_chan->scan_type.realbits - 1, 0);
#endif
/* 关闭ADC电源,降低功耗 */
rockchip_saradc_power_down(info);
/* 唤醒等待采样完成的线程(completion同步) */
complete(&info->completion);
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
/* 测试模式:打印采样值,重新触发延迟采样 */
spin_lock_irqsave(&info->lock, flags);
if (info->test) {
pr_info("chn[%d] val = %d\n", info->chn, info->last_val);
mod_delayed_work(info->wq, &info->work, msecs_to_jiffies(100));
}
spin_unlock_irqrestore(&info->lock, flags);
#endif
return IRQ_HANDLED;
}
|
关键解析:
第8行:读取采样数据:调用统一读取接口rockchip_saradc_read,获取当前采样值并存储到last_val。
第11行:屏蔽无效位:根据通道分辨率(realbits)屏蔽无效的高位数据,确保采样值的准确性(如10位分辨率屏蔽高6位)。
第14行:断电操作:采样完成后关闭ADC电源,降低系统功耗,符合低功耗设计要求。
第16行:唤醒线程:通过complete函数唤醒等待采样完成的线程(如read_raw回调中的wait_for_completion_timeout),实现同步采样。
测试模式:在测试模式下,打印采样值并重新触发延迟采样,便于硬件调试和通道验证。
IIO原始数据读取回调函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | static int rockchip_saradc_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct rockchip_saradc *info = iio_priv(indio_dev);
int ret;
#ifdef CONFIG_ROCKCHIP_SARADC_TEST_CHN
if (info->test)
return 0;
#endif
switch (mask) {
/* 读取原始采样值,对应sysfs的in_voltageX_raw节点 */
case IIO_CHAN_INFO_RAW:
mutex_lock(&info->lock); /* 加锁,防止多线程并发访问 */
if (info->suspended) { /* 挂起状态,拒绝读取 */
mutex_unlock(&info->lock);
return -EBUSY;
}
/* 触发采样并等待完成,超时100ms */
ret = rockchip_saradc_conversion(info, chan);
if (ret) {
rockchip_saradc_power_down(info);
mutex_unlock(&info->lock);
return ret;
}
*val = info->last_val; /* 读取采样值,存入val */
mutex_unlock(&info->lock);/* 解锁*/
return IIO_VAL_INT; /* 返回值类型:纯整数 */
/* 读取缩放系数,对应sysfs的in_voltage_scale节点 */
case IIO_CHAN_INFO_SCALE:
if (info->uv_vref < 0)
return info->uv_vref;
/* 缩放系数 = 参考电压 / 2^分辨率,val为毫伏,val2为分辨率 */
*val = info->uv_vref / 1000;
*val2 = chan->scan_type.realbits;
return IIO_VAL_FRACTIONAL_LOG2; /* 返回值类型:分数对数格式,即val/(2^val2) */
default:
return -EINVAL; /* 无效请求 */
}
}
|
关键解析:
第13行:case IIO_CHAN_INFO_RAW:匹配应用层读取原始采样值的请求,对应sysfs中所有以“in_voltageX_raw”结尾的节点。
第20行:rockchip_saradc_conversion:触发采样并等待完成,内部调用rockchip_saradc_start启动采样,通过completion同步等待中断完成,超时时间为100ms。
第26、28行:返回原始值:将存储在last_val的采样值存入val,返回IIO_VAL_INT,告知IIO内核返回值为纯整数。
第30、36行:case IIO_CHAN_INFO_SCALE:匹配应用层读取缩放系数的请求,对应sysfs中“in_voltage_scale”节点。缩放系数计算公式为“参考电压(毫伏) / 2^分辨率”,返回IIO_VAL_FRACTIONAL_LOG2格式,内核自动计算最终缩放系数。
测试功能
驱动支持测试模式(需开启CONFIG_ROCKCHIP_SARADC_TEST_CHN配置),通过sysfs节点控制,实现指定通道的循环采样和数据打印,便于硬件调试。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | /* 测试通道控制:通过sysfs节点写入通道号,启动/停止测试 */
static ssize_t saradc_test_chn_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t size)
{
u32 val = 0;
int err;
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct rockchip_saradc *info = iio_priv(indio_dev);
unsigned long flags;
err = kstrtou32(buf, 10, &val);
if (err)
return err;
spin_lock_irqsave(&info->lock, flags);
/* 写入非法通道号,停止测试 */
if (val > SARADC_CTRL_CHN_MASK && info->test) {
info->test = false;
spin_unlock_irqrestore(&info->lock, flags);
cancel_delayed_work_sync(&info->work);
return size;
}
/* 写入有效通道号,启动测试模式 */
if (!info->test && val <= SARADC_CTRL_CHN_MASK) {
info->test = true;
info->chn = val;
mod_delayed_work(info->wq, &info->work, msecs_to_jiffies(100));
}
spin_unlock_irqrestore(&info->lock, flags);
return size;
}
/* 定义sysfs写节点:saradc_test_chn */
static DEVICE_ATTR_WO(saradc_test_chn);
static struct attribute *saradc_attrs[] = {
&dev_attr_saradc_test_chn.attr,
NULL
};
static const struct attribute_group rockchip_saradc_attr_group = {
.attrs = saradc_attrs,
};
/* 测试工作函数:循环触发指定通道采样 */
static void rockchip_saradc_test_work(struct work_struct *work)
{
struct rockchip_saradc *info = container_of(work,
struct rockchip_saradc, work.work);
/* 启动采样 */
rockchip_saradc_start(info, info->chn);
}
|
关键解析:
sysfs节点:通过DEVICE_ATTR_WO定义可写节点“saradc_test_chn”,应用层可通过echo命令写入通道号(0~7)启动测试,写入大于7的数停止测试。
测试流程:启动测试后,通过延迟工作(delayed_work)每隔100ms触发一次采样,采样完成后在中断处理函数中打印通道号和采样值。
自旋锁保护:测试模式中使用自旋锁保护test、chn等变量,避免中断上下文与进程上下文并发访问冲突。
电源管理
驱动实现了电源管理的suspend(挂起)和resume(恢复)回调,适配系统休眠/唤醒流程,在挂起时关闭资源,恢复时重新初始化,降低休眠时的功耗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | #ifdef CONFIG_PM_SLEEP
static int rockchip_saradc_suspend(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct rockchip_saradc *info = iio_priv(indio_dev);
/* 加锁,避免挂起时进行采样操作 */
mutex_lock(&info->lock);
/* 关闭SARADC时钟和APB总线时钟 */
clk_disable_unprepare(info->clk);
clk_disable_unprepare(info->pclk);
/* 关闭参考电压 */
regulator_disable(info->vref);
/* 标记为挂起状态,拒绝采样请求 */
info->suspended = true;
mutex_unlock(&info->lock);
return 0;
}
static int rockchip_saradc_resume(struct device *dev)
{
struct iio_dev *indio_dev = dev_get_drvdata(dev);
struct rockchip_saradc *info = iio_priv(indio_dev);
int ret;
/* 重新使能参考电压 */
ret = regulator_enable(info->vref);
if (ret)
return ret;
/* 重新使能时钟 */
ret = clk_prepare_enable(info->pclk);
if (ret)
return ret;
ret = clk_prepare_enable(info->clk);
if (ret)
clk_disable_unprepare(info->pclk);
/* 标记为非挂起状态,允许采样 */
info->suspended = false;
return ret;
}
#endif
/* 注册电源管理操作 */
static DEFINE_SIMPLE_DEV_PM_OPS(rockchip_saradc_pm_ops,
rockchip_saradc_suspend,
rockchip_saradc_resume);
|
关键解析:
suspend函数:系统休眠时,关闭SARADC时钟、APB总线时钟和参考电压,标记挂起状态,拒绝任何采样请求,最大限度降低功耗。
resume函数:系统唤醒时,重新使能参考电压和时钟,恢复SARADC工作状态,标记为非挂起状态,允许正常采样。
DEFINE_SIMPLE_DEV_PM_OPS:将suspend和resume回调注册到电源管理操作集,供内核在休眠/唤醒时调用。
驱动注册
驱动基于Platform子系统注册,通过设备树匹配表适配多芯片,简化驱动的加载和卸载流程。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /* 设备树匹配表:匹配不同芯片的compatible属性 */
static const struct of_device_id rockchip_saradc_match[] = {
{ .compatible = "rockchip,saradc", .data = &saradc_data },
{ .compatible = "rockchip,rk3066-tsadc", .data = &rk3066_tsadc_data },
{ .compatible = "rockchip,rk3399-saradc", .data = &rk3399_saradc_data },
{ .compatible = "rockchip,rk3528-saradc", .data = &rk3528_saradc_data },
{ .compatible = "rockchip,rk3562-saradc", .data = &rk3562_saradc_data },
{ .compatible = "rockchip,rk3568-saradc", .data = &rk3568_saradc_data },
{ .compatible = "rockchip,rk3588-saradc", .data = &rk3588_saradc_data },
{ .compatible = "rockchip,rv1106-saradc", .data = &rv1106_saradc_data },
{},
};
/* 声明设备树匹配表,供内核识别 */
MODULE_DEVICE_TABLE(of, rockchip_saradc_match);
/* Platform驱动结构体 */
static struct platform_driver rockchip_saradc_driver = {
.probe = rockchip_saradc_probe,
.driver = {
.name = "rockchip-saradc",
.of_match_table = rockchip_saradc_match, /* 设备树匹配表 */
.pm = pm_sleep_ptr(&rockchip_saradc_pm_ops), /* 电源管理操作 */
},
};
/* 注册Platform驱动,简化模块入口/出口 */
module_platform_driver(rockchip_saradc_driver);
/* 模块信息声明 */
MODULE_AUTHOR("Heiko Stuebner <heiko@sntech.de>");
MODULE_DESCRIPTION("Rockchip SARADC driver");
MODULE_LICENSE("GPL v2");
|
关键解析:
设备树匹配表:rockchip_saradc_match数组定义了不同芯片的compatible属性,内核通过该属性匹配对应的芯片专属配置(rockchip_saradc_data),实现多芯片兼容。
platform_driver结构体:绑定probe函数、驱动名称、设备树匹配表和电源管理操作集。
module_platform_driver:替代传统的module_init和module_exit函数,自动实现驱动的注册和注销,简化驱动代码。
14.3.3. Rockchip ADC测试¶
板卡默认使能了ADC接口,可以直接进行测试,其中ADC通道2、通道3用于板卡的Board ID确定,当系统第一次启动的时候就会调用初始化脚本读取ADC通道2、通道3的电压值从而确定板卡型号, 将设备树和uEnv配置文件软连接到对应的板级文件。
以下使用LubanCat2-V2板卡进行测试,其余板卡操作一致,需注意不同板卡的Board ID不同,对应的ADC值不同:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | #进入SARADC的sysfs目录
cd /sys/bus/iio/devices/iio:device0
#查看设备名字
cat name
#信息输出如下,名字由saradc的地址和.saradc后缀组成
fe720000.saradc
#读取通道2的原始值
cat in_voltage2_raw
#信息输出如下
685
#读取通道3的原始值
cat in_voltage3_raw
#信息输出如下
522
#读取缩放系数
cat in_voltage_scale
#信息输出如下
1.757812500
#运行系统初始化脚本
sudo /etc/init.d/boot_init.sh
#信息打印如下
SOC_type:rk3568
ADC_CH2_RAW:685
ADC_CH3_RAW:525
BOARD_ID:0403
BOARD_NAME:LubanCat-2 v2
BOARD_DTB:rk3568-lubancat-2-v2.dtb
BOARD_uEnv:uEnvLubanCat2-V2.txt
|
实际电压(mV)=缩放系数*通道原始值,即通道2的实际电压是685 * 1.757812500 = 1204mV,通道3的实际电压是522 * 1.757812500 = 917mV。
查看板卡对应原理图的Board ID部分,LubanCat2-V2板卡的如下图:
可以看到,读取到实际值和理论值十分接近,说明ADC功能正常。
14.4. ADC驱动实验¶
由于大部分板卡都没有引出ADC接口,并且内部SARADC仅支持0~1.8V,因此,本实验选择使用外接ADC模块进行实验操作。 本实验基于“IIO子系统 + I2C子系统 + Regmap API + ADS1115芯片”实现4通道电压采集, 实验整体逻辑为“设备树配置->驱动加载->IIO节点生成->应用程序读取->数据转换”。
本章的示例代码目录为: linux_driver/32_adc_driver
14.4.1. ADS1115模块简介¶
ADS1115带内部参考的超小型、低功耗、16 位分辨率的模数转换器(ADC),使用I2C通信接口与各微控制器通讯,可以选择四个不同的从机地址,高达每秒860个样本(SPS)的速率执行转换, 具有内部低漂移电压基准、内部振荡器、可编程增益放大器(PGA)和可编程比较器,PGA提供从电源电压到低至±256mV的输入范围,还具有一个输入多路复用器 (MUX),可实现四个单端输入或两个差分输入。
参数特性:
分辨率:16位
通信接口:I2C总线接口
I2C 地址:四个可选从机地址
宽工作电压范围:2.0V~5.5V
工作温度范围:-40℃~+125℃
可编程数据转换速率:8SPS ~860SPS
输入通道:四个单端输入或两个差分输入
低功耗:连续模式:150μA;单次模式:自动关机
总线速度:标准模式100KHz;快速模式400KHz;高速模式3.4MHz
模块参考链接: 野火小智【ADS1115】模块
重要
ADS1115模块的寄存器说明在以上模块配套资料中有十分详细的说明,本教程不作详细说明,仅从资料中截取关键说明。
野火小智ADS1115模块规格手册:
截取手册关键说明并结合本实验情况整理得:
ADDR引脚接GND时,I2C通信地址为0x48;
ADS1115具有四个寄存器,转换寄存器(0x0)、配置寄存器(0x1)、比较器低阈值寄存器(0x2)和比较器高阈值寄存器(0x3);
分辨率 = 测量电压范围 / (2^(AD位数-1));
电压值 = 采集到的 ADC 值 * 分辨率。
配置寄存器
16位配置寄存器可用于控制工作模式、输入选择、数据速率、满量程设置和比较器模式。重置或通电后配置寄存器默认值为8583h。
配置寄存器bit15:单次转换启动位,写1启动ADC转换;
配置寄存器bit14-12:通道选择掩码,用于配置单端/差分通道,各值对应通道如下:
000:AINP=AIN0 和 AINN=AIN1(默认)
001:AINP=AIN0 和 AINN=AIN3
010:AINP=AIN1 和 AINN=AIN3
011:AINP=AIN2 和 AINN=AIN3
100:AINP=AIN0 和 AINN=GND
101:AINP=AIN1 和 AINN=GND
110:AINP=AIN2 和 AINN=GND
111:AINP=AIN3 和 AINN=GND
配置寄存器bit11-9:可编程增益PGA掩码,设置电压量程,各值对应电压量程如下:
000:FS = ±6.144V
001:FS = ±4.096V
010:FS = ±2.048V(默认)
011:FS = ±1.024V
100:FS = ±0.512V
101:FS = ±0.256V
110:FS = ±0.256V
111:FS = ±0.256V
配置寄存器bit8:工作模式位,1=单次转换模式(默认),0=连续转换模式;
配置寄存器bit7-5:采样率掩码,设置ADC转换速率,各值对应转换速率如下:
000:8SPS
001:16SPS
010:32SPS
011:64SPS
100:128SPS(默认)
101:250SPS
110:475SPS
111:860SPS
配置寄存器bit4:比较器模式位,1=窗口比较器,0=具有滞后的传统比较器(默认);
配置寄存器bit3:比较器极性,1=高电平有效,0=低电平有效(默认);
配置寄存器bit2:锁存比较器,1=非锁存比较器,置位后ALERT/RDY引脚不锁存(默认),0=锁存比较器,置为有效后ALERT/RDY引脚保持锁存状态,直到主设备读取转换数据或发送适当的SMBus警报响;
配置寄存器bit1-0:比较器队列和禁用当设置为11时,比较器被禁用并将ALERT/RDY引脚置于高阻抗状态。当设置为任何其他值时,启用ALERT/RDY引脚和比较器功能,它们控制在断言ALERT/RDY引脚之前超过所需上限或下限阈值的连续转换次数,各值对应功能如下:
00:一次转换后断言
01:两次转换后断言
10:四次转换后断言
11:禁用比较器(默认)
转换寄存器
配置寄存器选择对应通道并完成配置后,16位转换寄存器输出对应通道包含二进制补码格式的转换结果。重置或通电后,转换寄存器被清除为0,并保持0直到第一次转换完成。
14.4.2. 设备树插件详解¶
本实验设备树插件(lubancat-adc-ads1115-overlay.dts)用于配置I2C适配器和ads1115从设备,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | /dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/pinctrl/rockchip.h>
/ {
fragment@0 {
target = <&i2c3>;
__overlay__ {
clock-frequency = <400000>;
pinctrl-names = "default";
pinctrl-0 = <&i2c3m1_xfer>;
#address-cells = <1>;
#size-cells = <0>;
status = "okay";
ads1115: ads1115@48 {
compatible = "fire,ads1115";
reg = <0x48>;
gain = <0>; /* 增益:0 -> ±6.144V */
data-rate = <4>; /* 采样率:4 -> 128SPS */
};
};
};
};
|
关键配置说明:
target = &i2c3:指定要修改的I2C适配器为I2C3,需与实际要使用的硬件接口一致;
clock-frequency = <400000>:配置I2C通信速率为400KHz,ADS1115支持标准模式100KHz、快速模式400KHz、高速模式3.4MHz;
pinctrl-0 = <&i2c3m1_xfer>:此处使用的具体I2C引脚为i2c3m1;
ads1115@48:从设备节点名称,@后的48是ADS1115的ADDR引脚接GND时,I2C通信地址为0x48;
compatible = “fire,ads1115”:驱动匹配的核心标识,需与I2C驱动中of_match_table的属性完全一致,否则驱动无法匹配设备;
reg = <0x48>:明确ADS1115的I2C从设备地址,内核通过该属性识别I2C总线上的具体设备。
gain = <0>:PGA增益配置,对应后续驱动中ads1115_pga_mv_table[0],即±6.144V满量程,可根据需求修改为0~7之间的值。
data-rate = <4>:采样率配置,对应后续驱动中ads1115_dr_sps_table[4],即128SPS,可根据需求修改为0~7之间的值。
注意
以上设备树插件是以I2C3_M1为例,需根据板卡实际接口进行修改!
如果不清楚自己板卡有哪些可用的I2C接口,可在板卡执行以下命令确认:
1 2 3 4 5 6 7 8 9 10 | #查看配置文件可用i2c插件
cat /boot/uEnv/uEnv.txt | grep i2c
#以LubanCat2-V2板卡为例,信息打印如下
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m0-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m1-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c5-m0-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m0-rtc-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c3-m1-rtc-overlay.dtbo
#dtoverlay=/dtb/overlay/rk356x-lubancat-i2c5-m0-rtc-overlay.dtbo
|
从可用的设备树插件可以确认,LubanCat2-V2支持i2c3-m0、i2c3-m1、i2c5-m0,从内核源码中找到对应的设备树插件源码如下,以i2c3-m1为例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | #查看i2c3-m1设备树插件源码内容
cat kernel/arch/arm64/boot/dts/rockchip/overlay/rk356x-lubancat-i2c3-m1-overlay.dts
#信息打印如下
/dts-v1/;
/plugin/;
/ {
compatible = "rockchip,rk3568";
fragment@0 {
target = <&i2c3>;
__overlay__ {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&i2c3m1_xfer>;
};
};
};
|
可知,如果LubanCat2-V2使用i2c3-m1接口,lubancat-i2c-mpu6050-overlay.dts配置的target和pinctrl-0就是 target = <&i2c3>; 和 pinctrl-0 = <&i2c3m1_xfer>; 。
结合板卡配套的快速使用手册的40pin引脚对照图章节,可确认i2c3-m1接口对应的物理引脚,如下图:
14.4.3. 驱动代码详解¶
驱动基于I2C子系统和IIO子系统开发,核心功能是初始化ADS1115硬件、注册IIO设备、实现原始数据读取和缩放系数提供,通过Regmap API简化寄存器操作。
核心定义与数据结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 | #define ADS1115_REG_CONVERSION 0x00 /* 定义ADS1115转换寄存器地址,存储ADC采样原始值 */
#define ADS1115_REG_CONFIG 0x01 /* 定义ADS1115配置寄存器地址,设置ADC工作模式/通道/增益/采样率 */
#define ADS1115_CFG_OS BIT(15) /* 配置寄存器bit15:单次转换启动位,写1启动ADC转换 */
#define ADS1115_CFG_MODE BIT(8) /* 配置寄存器bit8:工作模式位,1=单次转换模式,0=连续转换模式 */
/* 定义PGA增益对应表:寄存器配置值 -> 满量程电压,单位mV */
static const unsigned int ads1115_pga_mv_table[] = {
6144, /* 0: 寄存器值0 -> ±6.144V满量程 */
4096, /* 1: 寄存器值1 -> ±4.096V满量程 */
2048, /* 2: 寄存器值2 -> ±2.048V满量程 */
1024, /* 3: 寄存器值3 -> ±1.024V满量程 */
512, /* 4: 寄存器值4 -> ±0.512V满量程 */
256, /* 5: 寄存器值5 -> ±0.256V满量程 */
256, /* 6: 寄存器值6 -> ±0.256V满量程 */
256, /* 7: 寄存器值7 -> ±0.256V满量程 */
};
/* 定义默认PGA增益:0 -> ±6.144V满量程 */
#define ADS1115_DEFAULT_PGA 0
/* 定义采样率对应表:寄存器配置值 -> 采样速率,单位SPS */
static const unsigned int ads1115_dr_sps_table[] = {
8, /* 0: 寄存器值0 -> 8采样点/秒 */
16, /* 1: 寄存器值1 -> 16采样点/秒 */
32, /* 2: 寄存器值2 -> 32采样点/秒 */
64, /* 3: 寄存器值3 -> 64采样点/秒 */
128, /* 4: 寄存器值4 -> 128采样点/秒 */
250, /* 5: 寄存器值5 -> 250采样点/秒 */
475, /* 6: 寄存器值6 -> 475采样点/秒 */
860, /* 7: 寄存器值7 -> 860采样点/秒 */
};
/* 定义默认采样率:4 -> 128采样点/秒 */
#define ADS1115_DEFAULT_DR 4
/* 定义ADS1115驱动私有数据结构体 */
struct ads1115_data {
struct i2c_client *client; /* I2C客户端句柄,关联I2C从设备 */
struct regmap *regmap; /* Regmap句柄,用于寄存器统一读写 */
unsigned int pga; /* 保存当前PGA增益配置值 */
unsigned int dr; /* 保存当前采样率配置值 */
};
/* 配置Regmap参数 */
static const struct regmap_config ads1115_regmap_config = {
.reg_bits = 8, /* 寄存器地址位宽:8位 */
.val_bits = 16, /* 寄存器值位宽:16位 */
.val_format_endian = REGMAP_ENDIAN_BIG, /* 寄存器值为大端字节序 */
.max_register = ADS1115_REG_CONFIG, /* 最大可访问寄存器地址 */
.cache_type = REGCACHE_NONE, /* 关闭寄存器缓存,保证实时性 */
};
|
关键解析:
定义寄存器地址:ADS1115_REG_CONVERSION转换结果寄存器,存储ADC采样原始值、ADS1115_REG_CONFIG配置寄存器,设置工作参数。
PGA增益表:ads1115_pga_mv_table[],存储增益对应的满量程电压(mV),与设备树的gain属性对应。
采样率表:ads1115_dr_sps_table[],存储采样率对应的采样速度(SPS),与设备树的data-rate属性对应。
私有数据结构体:避免使用全局变量,整合了I2C客户端、Regmap句柄及当前配置参数,提高代码可维护性。
Regmap配置:指定了寄存器的位宽、字节序等参数,与ADS1115的寄存器特性匹配,后续通过regmap_read/regmap_write函数即可实现寄存器操作,无需手动拼接I2C消息。
IIO通道配置
通过宏定义和通道描述数组,定义ADS1115的4路电压采集通道,用于IIO子系统生成sysfs节点。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* 定义IIO通道宏 */
#define ADS1115_CHANNEL(_chan) { \
.type = IIO_VOLTAGE, /* 通道类型:电压采集 */ \
.indexed = 1, /* 启用通道索引标识 */ \
.channel = _chan, /* 通道编号:0/1/2/3 */ \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW) | BIT(IIO_CHAN_INFO_SCALE), /* 生成sysfs节点:原始值(raw) + 缩放系数(scale) */ \
.scan_type = { \
.sign = 's', /* 数据类型:有符号数 */ \
.realbits = 16, /* ADC有效分辨率:16位 */ \
.storagebits = 16, /* 数据存储位宽:16位 */ \
.endianness = IIO_CPU, /* 数据字节序:CPU本地序 */ \
}, \
}
/* 定义ADS1115的4路电压采集通道 */
static const struct iio_chan_spec ads1115_channels[] = {
ADS1115_CHANNEL(0), /* 通道0:AIN0 */
ADS1115_CHANNEL(1), /* 通道1:AIN1 */
ADS1115_CHANNEL(2), /* 通道2:AIN2 */
ADS1115_CHANNEL(3), /* 通道3:AIN3 */
};
|
关键解析:
每个通道指定类型为电压采集,启用索引标识,通道编号对应ADS1115的AIN0~AIN3;
info_mask_separate属性指定每个通道生成两个sysfs节点(in_voltageX_raw、in_voltageX_scale),供用户态读取原始数据和缩放系数;
scan_type属性定义了数据的类型、分辨率和字节序,与ADS1115的采样数据特性匹配。
IIO原始数据读取回调函数
回调函数由IIO子系统调用,用于读取ADC的原始数据和缩放系数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | /* IIO子系统读取回调函数,处理RAW原始值和SCALE缩放系数读取 */
static int ads1115_read_raw(struct iio_dev *indio_dev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
/* 获取驱动私有数据结构体指针 */
struct ads1115_data *data = iio_priv(indio_dev);
/* 定义寄存器值临时变量 */
unsigned int reg_val;
/* 定义16位有符号ADC原始值变量 */
s16 raw_adc;
/* 定义配置寄存器值变量 */
u16 config;
/* 定义函数返回值变量 */
int ret;
/* 判断读取类型:原始值/缩放系数 */
switch (mask) {
case IIO_CHAN_INFO_RAW: /* 读取ADC原始采样值 */
/* 拼接配置寄存器值:启动转换+通道选择+PGA+单次模式+采样率 */
config = ADS1115_CFG_OS | /* 启动单次转换 */
((chan->channel + 4) << 12) | /* 设置通道chan->channel,如ADS1115_CHANNEL(0) + 4 得到bit值为100,左移12位到bit 14:12位,即可选择AINP=AIN0和AINN=GND */
(data->pga << 9) | /* 设置PGA增益data->pga */
ADS1115_CFG_MODE | /* 设置单次模式 */
(data->dr << 5); /* 设置采样率data->dr */
/* 通过Regmap写入配置寄存器,启动ADC转换 */
ret = regmap_write(data->regmap, ADS1115_REG_CONFIG, config);
if (ret)
return ret;
/* 根据采样率计算转换延时,等待ADC采样完成 */
msleep(DIV_ROUND_UP(1000, ads1115_dr_sps_table[data->dr]));
/* 通过Regmap读取转换结果寄存器值 */
ret = regmap_read(data->regmap, ADS1115_REG_CONVERSION, ®_val);
if (ret)
return ret;
/* 将16位无符号寄存器值强转为有符号数 */
raw_adc = (s16)reg_val;
/* 将转换后的原始值存入输出参数 */
*val = raw_adc;
/* 返回标识:读取的数据为纯整数类型 */
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE: /* 读取电压缩放系数 */
/* 缩放系数=满量程mV值,配合2^15计算实际电压 */
*val = ads1115_pga_mv_table[data->pga];
/* 位移值:15位,2^15=32768(16位有符号数最大值) */
*val2 = 15;
/* 返回标识:2的指数分数类型 val/(2^val2) */
return IIO_VAL_FRACTIONAL_LOG2;
default: /* 未知读取类型,返回参数错误 */
return -EINVAL;
}
}
/* 定义IIO操作函数集,绑定read_raw回调给内核IIO子系统 */
static const struct iio_info ads1115_iio_info = {
.read_raw = ads1115_read_raw,
};
|
关键解析:
第19-47行:读取原始数据(IIO_CHAN_INFO_RAW):
第21-25行:拼接配置寄存器值:包含启动单次转换(ADS1115_CFG_OS)、通道选择(根据当前通道编号)、PGA增益(data->pga)、单次模式(ADS1115_CFG_MODE)、采样率(data->dr)。
第28行:写入配置寄存器:通过regmap_write函数将配置值写入ADS1115_REG_CONFIG,启动ADC转换。
第33行:等待采样完成:根据当前采样率计算转换延时(1000ms / 采样率),通过msleep函数等待采样完成。
第36行:读取转换结果:通过regmap_read函数读取转换结果寄存器的值。
第41、44行:强转为16位有符号数(raw_adc),存入val参数返回给IIO子系统。
第47行:告诉IIO子系统返回的数据为纯整数类型。
第49-57行:读取缩放系数(IIO_CHAN_INFO_SCALE),缩放系数也即分辨率 = 测量电压范围 / (2^(AD位数-1));:
第51行:根据当前PGA增益(data->pga),从ads1115_pga_mv_table[]中获取满量程电压(mV),存入val参数。
第54行:val2参数设为15,对应2^15。
第57行:返回IIO_VAL_FRACTIONAL_LOG2标识,告知IIO子系统缩放系数格式为val/(2^val2)。
设备树配置读取函数
设备树配置读取函数用于从设备树中读取gain和data-rate属性,配置PGA增益和采样率。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | /* 从设备树中读取PGA增益和采样率配置参数 */
static void ads1115_of_get_config(struct ads1115_data *data)
{
/* 获取设备指针,关联I2C客户端设备 */
struct device *dev = &data->client->dev;
/* 定义设备树属性读取临时变量 */
unsigned int val;
/* 读取设备树gain属性,获取PGA增益配置 */
if (!of_property_read_u32(dev->of_node, "gain", &val)) {
/* 校验增益值是否在合法范围内 */
if (val < ARRAY_SIZE(ads1115_pga_mv_table))
data->pga = val; /* 合法则使用设备树配置 */
else
data->pga = ADS1115_DEFAULT_PGA; /* 非法则使用默认增益 */
} else {
data->pga = ADS1115_DEFAULT_PGA; /* 无配置则使用默认增益 */
}
/* 读取设备树data-rate属性,获取采样率配置 */
if (!of_property_read_u32(dev->of_node, "data-rate", &val)) {
/* 校验采样率值是否在合法范围内 */
if (val < ARRAY_SIZE(ads1115_dr_sps_table))
data->dr = val; /* 合法则使用设备树配置 */
else
data->dr = ADS1115_DEFAULT_DR; /* 非法则使用默认采样率 */
} else {
data->dr = ADS1115_DEFAULT_DR; /* 无配置则使用默认采样率 */
}
/* 内核日志打印当前PGA和采样率配置 */
dev_info(dev, "ADS1115: PGA=%dmV, DataRate=%uSPS\n",
ads1115_pga_mv_table[data->pga],
ads1115_dr_sps_table[data->dr]);
}
|
关键解析:
第10行:读取设备树gain属性,校验值是否在合法范围,合法则使用该值,否则使用默认增益(ADS1115_DEFAULT_PGA=0)。
第21行:读取设备树data-rate属性,校验值是否在合法范围,合法则使用该值,否则使用默认采样率(ADS1115_DEFAULT_DR=4)。
probe函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | /* I2C设备探测函数,设备匹配成功后自动调用 */
static int ads1115_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
/* 定义IIO设备结构体指针 */
struct iio_dev *indio_dev;
/* 定义驱动私有数据结构体指针 */
struct ads1115_data *data;
/* 定义函数返回值变量 */
int ret;
/* 动态分配IIO设备内存 + 私有数据内存 */
indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*data));
if (!indio_dev)
return -ENOMEM;
/* 获取私有数据结构体地址 */
data = iio_priv(indio_dev);
/* 绑定I2C客户端 */
data->client = client;
/* 初始化I2C regmap映射 */
data->regmap = devm_regmap_init_i2c(client, &ads1115_regmap_config);
if (IS_ERR(data->regmap)) {
dev_err(&client->dev, "regmap init failed\n");
return PTR_ERR(data->regmap);
}
/* 从设备树加载ADC配置参数 */
ads1115_of_get_config(data);
/* 设置IIO设备名称 */
indio_dev->name = "ads1115";
/* 绑定IIO操作函数集 */
indio_dev->info = &ads1115_iio_info;
/* 绑定IIO通道配置数组 */
indio_dev->channels = ads1115_channels;
/* 设置通道数量为4路 */
indio_dev->num_channels = ARRAY_SIZE(ads1115_channels);
/* 设置IIO工作模式:直接访问模式 */
indio_dev->modes = INDIO_DIRECT_MODE;
/* 注册IIO设备到内核,自动生成sysfs节点 */
ret = devm_iio_device_register(&client->dev, indio_dev);
/* 注册失败则打印错误并返回 */
if (ret) {
dev_err(&client->dev, "iio device register failed\n");
return ret;
}
dev_info(&client->dev, "ADS1115 IIO driver probe successful\n");
return 0;
}
|
关键解析:
第13行:动态分配IIO设备和私有数据内存。
第24行:通过devm_regmap_init_i2c函数,绑定I2C客户端和Regmap配置。
第31行:调用ads1115_of_get_config函数,从设备树读取PGA增益和采样率配置。
第33-42行:配置IIO设备参数:设置设备名称、绑定IIO操作函数集(ads1115_iio_info)、绑定IIO通道配置、设置工作模式为直接访问模式。
第45行:注册IIO设备到内核,自动生成sysfs节点。
驱动注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | /* 定义设备树匹配表 */
static const struct of_device_id ads1115_of_match[] = {
{ .compatible = "fire,ads1115" },
{ }
};
/* 声明设备树匹配表,供内核识别 */
MODULE_DEVICE_TABLE(of, ads1115_of_match);
/* 定义i2c总线设备结构体 */
static struct i2c_driver ads1115_driver = {
.probe = ads1115_probe,
.remove = ads1115_remove,
.driver = {
.name = "adc-ads1115",
.of_match_table = ads1115_of_match,
},
};
/* 注册/注销I2C驱动,简化模块入口/出口函数 */
module_i2c_driver(ads1115_driver);
|
关键解析:
通过of_device_id匹配表(ads1115_of_match[])定义设备兼容属性(fire,ads1115),与设备树中的compatible属性对应,实现驱动与设备的自动匹配;
通过i2c_driver结构体绑定probe、remove函数;
使用module_i2c_driver宏简化驱动注册流程,无需手动编写init和exit函数。
14.4.4. 应用代码详解¶
应用程序的功能是读取IIO sysfs节点的原始数据和缩放系数,完成电压转换并循环打印,完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 | #include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <limits.h>
#include <errno.h>
/*
* ======================== 计算说明 ========================
* 1. ADS1115电压计算公式:
* 电压(V) = 原始值(raw) × 缩放系数(scale) / 1000
* (scale单位:mV/LSB,除以1000转为V)
* 2. 支持4通道电压采集:AIN0 ~ AIN3
*/
/* 全局退出标志 */
static int exit_flag = 0;
/* Ctrl+C 信号处理 */
static void sigint_handler(int sig)
{
if (sig == SIGINT) {
exit_flag = 1;
printf("\n程序退出中...\n");
}
}
/* 读取通道原始值 in_voltageX_raw */
static int read_ads_raw(const char *iio_dev, int ch)
{
char path[PATH_MAX];
char buf[16];
int fd, ret;
// 拼接完整的 sysfs 文件路径
snprintf(path, sizeof(path), "%s/in_voltage%d_raw", iio_dev, ch);
// 以只读方式打开节点文件
fd = open(path, O_RDONLY);
if (fd < 0) return INT_MIN;
ret = read(fd, buf, sizeof(buf)-1);
close(fd);
return (ret < 0) ? INT_MIN : atoi(buf);
}
/* 读取通道独立缩放系数 in_voltageX_scale */
static float read_ads_scale(const char *iio_dev, int ch)
{
char path[PATH_MAX];
char buf[32];
int fd, ret;
// 拼接完整的 sysfs 文件路径
snprintf(path, sizeof(path), "%s/in_voltage%d_scale", iio_dev, ch);
// 以只读方式打开节点文件
fd = open(path, O_RDONLY);
if (fd < 0) return -1.0f;
ret = read(fd, buf, sizeof(buf)-1);
close(fd);
return (ret < 0) ? -1.0f : atof(buf);
}
int main(int argc, char *argv[])
{
int raw[4];
float scale[4];
float volt[4];
int i;
signal(SIGINT, sigint_handler);
if (argc != 2) {
printf("使用方法: ./adc_ads1115_app /sys/bus/iio/devices/iio:device1\n");
return -1;
}
printf("===== ADS1115 4通道独立读取程序 =====\n");
printf("设备路径: %s\n", argv[1]);
printf("按 Ctrl+C 退出\n");
printf("-----------------------------------------\n");
printf("CH0(V)\tCH1(V)\tCH2(V)\tCH3(V)\n");
printf("-----------------------------------------\n");
while (!exit_flag)
{
// 逐通道读取 raw 和 scale
for (i = 0; i < 4; i++) {
raw[i] = read_ads_raw(argv[1], i);
scale[i] = read_ads_scale(argv[1], i);
}
// 数据校验
int valid = 1;
for (i = 0; i < 4; i++) {
if (raw[i] == INT_MIN || scale[i] <= 0.0f)
valid = 0;
}
if (!valid) {
printf("读取节点失败,重试...\n");
sleep(1);
continue;
}
// 逐通道计算电压
for (i = 0; i < 4; i++) {
volt[i] = (float)raw[i] * scale[i] / 1000.0f;
}
// 打印结果
printf("%.2f\t%.2f\t%.2f\t%.2f\n",
volt[0], volt[1], volt[2], volt[3]);
sleep(1);
}
return 0;
}
|
关键解析:
read_ads_raw函数:拼接sysfs节点路径,以只读方式打开节点,读取字符串格式的原始数据,转换为整型后返回;读取失败则返回INT_MIN。
read_ads_scale函数:拼接sysfs节点路径,以只读方式打开节点,读取字符串格式的缩放系数,转换为浮点型后返回;读取失败则返回-1.0f。
main主函数:
注册信号处理函数,校验命令行参数,若参数错误则打印使用方法并退出。
循环读取4通道的raw原始数据和scale缩放系数,直至收到退出信号。
数据校验:判断读取的原始数据和缩放系数是否有效,若无效则提示读取失败,等待1秒后重新读取。
数据转换:按照电压转换公式,将每个通道的raw数据转换为实际电压值,单位V。
循环打印4通道的电压数据,保留2位小数,每秒打印一次,直至收到退出信号。
14.4.5. Makefile说明¶
本节实验使用的Makefile如下所示,编写该Makefile时,只需要根据实际情况修改变量KERNEL_DIR、obj-m和test_app即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #指定内核路径,可以是相对路径或绝对路径
KERNEL_DIR=../../kernel/
#KERNEL_DIR=/home/guest/LubanCat_Linux_rk356x_SDK/kernel/
#指定目标架构为arm64
ARCH=arm64
#指定交叉编译工具链的前缀
CROSS_COMPILE=aarch64-linux-gnu-
#导出为环境变量
export ARCH CROSS_COMPILE
#指定要编译的内核模块目标文件
obj-m := adc_ads1115.o
test_app = adc_ads1115_app
#all :默认目标,执行时会编译驱动模块
#$(MAKE) :调用make工具
#-C $(KERNEL_DIR) :指定的内核源码目录
#M=$(CURDIR) :模块的源码位于当前目录
#modules :编译模块
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) modules
$(CROSS_COMPILE)gcc -o $(test_app) $(test_app).c
.PHONE:clean
#清理编译生成的文件
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURDIR) clean
rm $(test_app)
|
14.4.6. 编译设备树和驱动¶
14.4.6.1. 编译设备树¶
修改内核目录/arch/arm64/boot/dts/rockchip/overlays下的Makefile文件,添加我们编辑好的设备树插件,并把设备树插件文件放在和Makefile文件同级目录下,以进行设备树插件的编译。
然后在内核源码顶层目录执行以下命令编译设备树插件:
1 2 3 | #这里以rk356x系列4.19.232内核配置文件为例
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu- lubancat2_defconfig
make ARCH=arm64 -j4 CROSS_COMPILE=aarch64-linux-gnu- dtbs
|
提示
其余系列板卡参考 使用内核的构建脚本编译设备树插件 章节进行编译。
14.4.6.2. 加载设备树¶
编译出来的设备树插件位于 内核源码/arch/arm64/boot/dts/rockchip/overlay/lubancat-adc-ads1115-overlay.dtbo,
将设备树插件先传到板卡,再拷贝到板卡的 /boot/dtb/overlay/ 目录下。
1 2 3 4 | #先传输到板卡
#再拷贝到板卡的/boot/dtb/overlay/目录下
sudo cp -f lubancat-adc-ads1115-overlay.dtbo /boot/dtb/overlay/
|
然后在 /boot/uEnv/uEnv.txt 按照格式添加我们的设备树插件,需要在#overlay_start和#overlay_end之间添加,然后重启开发板,那么系统就会加载我们编译的设备树插件。
14.4.6.3. 编译驱动¶
在实验目录下输入 make 即可编译驱动和应用程序,编译得到内核模块adc_ads1115.ko和应用程序adc_ads1115_app。
14.4.7. 模块接线说明¶
通过杜邦线连接ADS1115模块和板卡,接线如下,其中ADDR引脚确定I2C通信地址,A0~A3为ADC采样通道,分别接到3.3V、5V、GND进行测试:
1 2 3 4 5 6 7 8 9 10 11 12 | ADS1115模块 板卡
VDD -------- 5V
GND -------- GND
SCL -------- SCL
SDA -------- SDA
ADDR -------- GND
A0 -------- 3.3V
A1 -------- 5V
A2 -------- GND
// 其余引脚不接
|
注意
VDD引脚需要接5V,若接3.3V,即使PGA增益配置为±6.144V满量程,测量超过3.8V的电压会因绝对电压超限,被芯片内部钳位,只能输出3.8V电压对应数据。
14.4.8. 程序运行结果¶
如出现 Permission denied 或类似字样,请注意用户权限,大部分操作硬件外设的功能,几乎都需要root用户权限,简单的解决方案是在执行语句前加入sudo或以root用户运行程序。
14.4.8.1. 实验操作¶
使用以下命令加载驱动和运行测试程序,加载驱动前需确保ADS1115模块已经连接到板卡:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #先确认设备地址存在,使用i2cdetect命令,其中3表示是i2c3,需要根据实际使用的i2c修改
sudo i2cdetect -a -y 3
#信息打印如下,可以看到48地址
0 1 2 3 4 5 6 7 8 9 a b c d e f
00: 00 -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
#加载驱动
sudo insmod adc_ads1115.ko
#信息输出如下
[ 8820.282036] adc-ads1115 3-0048: ADS1115: PGA=6144mV, DataRate=128SPS
[ 8820.282693] adc-ads1115 3-0048: ADS1115 IIO driver probe successful
#查看iio sysfs节点
ls /sys/bus/iio/devices/
#信息打印如下
iio:device0 iio:device1 iio_sysfs_trigger
#查看设备名称
cat /sys/bus/iio/devices/iio:device*/name
#信息打印如下
fe720000.saradc
ads1115
|
可以看到当前系统有俩个iio:device,分别对应iio:device0、iio:device1,其中iio:device1就是我们注册生成的ADS1115对应的sysfs节点。
可以通过cat命令直接读取数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | #进入mpu6050对应的sysfs节点目录
cd /sys/bus/iio/devices/iio:device1
#查看目录文件
ls -l
#信息打印如下
total 0
-r--r--r-- 1 root root 4096 Apr 24 11:55 dev
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage0_raw
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage0_scale
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage1_raw
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage1_scale
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage2_raw
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage2_scale
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage3_raw
-rw-r--r-- 1 root root 4096 Apr 24 11:55 in_voltage3_scale
-r--r--r-- 1 root root 4096 Apr 24 11:54 name
drwxr-xr-x 2 root root 0 Apr 24 11:55 power
lrwxrwxrwx 1 root root 0 Apr 24 11:55 subsystem -> ../../bus/iio
-rw-r--r-- 1 root root 4096 Apr 24 11:54 uevent
#查看通道0原始数据
cat in_voltage0_raw
#信息打印如下
17602
#查看通道0缩放系数
cat in_voltage0_scale
#信息打印如下
0.187500000
|
可计算通道0实际电压值(mV) = 采集到的原始数据 * 缩放系数 = 17602 * 0.187500000 = 3300.375 mV,因为A0通道接到了板卡的3.3V,因此采集到的电压值正常。
运行应用程序可以同时读取4通道原始数据和缩放系数,完成电压转换并循环打印:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | #运行测试程序
sudo ./adc_ads1115_app /sys/bus/iio/devices/iio:device1
#信息打印如下
===== ADS1115 4通道独立读取程序 =====
设备路径: /sys/bus/iio/devices/iio:device1
按 Ctrl+C 退出
-----------------------------------------
CH0(V) CH1(V) CH2(V) CH3(V)
-----------------------------------------
3.30 5.10 -0.00 0.54
3.30 5.09 -0.00 0.54
3.30 5.10 -0.00 0.54
3.30 5.09 -0.00 0.54
3.30 5.09 -0.00 0.54
3.30 5.10 -0.00 0.54
|
可以看到打印了4通道当前读取到的电压,其中通道0~2通道分别接到了板卡的3.3V、5V、0V,与实际值一致,通道3悬空,通道电压会显示为随机值或接近0V。
注意
板卡5V电压实际可能会比5V稍微高一点,因此数据是正常的,可自行用万用表进行确认。
14.4.9. 实验注意事项¶
硬件连接:ADS1115的VDD须接5V,否则采集超过3.8V的电压会因绝对电压超限,被芯片内部钳位,数据异常;SDA、SCL引脚需对应开发板的I2C引脚,避免接反,否则I2C通信失败,ADS1115无法被识别。
修改PGA增益或采样率:只需修改设备树中的gain和data-rate属性,重新编译并加载设备树即可,无需修改驱动代码。
应用程序读取:检查IIO设备路径,确认传入应用程序的路径正确,实际iio:device名字为ads1115的设备为ads1115驱动注册的设备。
采集电压:信号电压需在当前PGA增益对应的量程范围内,避免超出量程导致数据失真或损坏芯片。
若电压数据异常:检查硬件连接:确保ADS1115的VDD接5V,GND共地,模拟信号输入稳定,避免干扰;检查PGA增益配置:确认设备树的gain属性与实际需求一致,若增益配置错误,会导致量程不匹配,数据偏差过大。