深入解析RK3576平台U-Boot下ADC驱动实现|从架构到实战 电子说
在嵌入式开发中,ADC(模拟数字转换器)堪称 “模拟世界与数字世界的桥梁”—— 电池电压检测、温度采集、按键识别,几乎所有需要感知物理量的场景都离不开它。
今天我们就聚焦 RK3576 平台,从驱动架构、核心代码、实战使用三个维度,彻底讲透 U-Boot 下 ADC 驱动的实现逻辑,无论是调试 ADC 功能,还是定制化开发,看完这篇都能落地!
一、先搞懂:RK3576 ADC 驱动的整体架构
RK3576 的 ADC 驱动基于 U-Boot 的 Driver Model(DM)框架设计,核心是 “通用框架 + 芯片专属驱动” 的分层思路,既保证了接口统一,又适配了硬件特性。
1.1 核心文件结构
整个驱动的代码都集中在u-boot/drivers/adc/目录下,关键文件就这几个:
u-boot/drivers/adc/├── adc-uclass.c # ADC通用框架(提供统一API,上层调用不用管底层差异)├── rockchip-saradc-v2.c # RK3576专属驱动(新版SARADC v2,重点分析这个)├── rockchip-saradc.c # 旧版驱动(适配老芯片,RK3576不用)├── Kconfig # 驱动编译配置└── Makefile # 编译规则
1.2 核心分层逻辑
•上层:ADC 通用框架(adc-uclass.c)提供标准化 API(比如启动转换、读取数据),开发者只用调用这些接口,不用关心底层硬件;
•下层:rockchip-saradc-v2.c处理 RK3576 ADC 的硬件细节(寄存器操作、时钟 / 复位配置、时序控制)。
二、核心代码拆解:读懂驱动的“底层逻辑”
想要定制 ADC 驱动,核心是搞懂 3 个关键部分:寄存器定义、驱动初始化、转换与数据读取。
2.1 先认识:新版 SARADC v2 的寄存器
RK3576 的 SARADC v2 寄存器结构更完善,覆盖了转换控制、时序配置、中断、数据存储等全流程,核心寄存器如下(挑关键的讲):
struct rockchip_saradc_regs { u32 conv_con; // 转换控制(启动转换、选择通道、模式) u32 t_pd_soc; // 上电延时(配置ADC上电后的稳定时间) u32 t_das_soc; // 数据采集时间(决定采样精度) u32 end_int_en; // 转换结束中断使能(判断转换是否完成) u32 status; // ADC状态寄存器 u32 data0-15; // 16通道数据寄存器(转换结果存在这里)};
2.2 驱动初始化:probe 函数
驱动加载时首先执行probe函数,核心是完成“时钟 + 复位” 的初始化,为 ADC 工作做准备:
static int rockchip_saradc_probe(struct udevice *dev){ struct rockchip_saradc_priv *priv = dev_get_priv(dev); struct clk clk; int ret; // 1. 获取复位控制(后续复位ADC用) ret = reset_get_by_name(dev, "saradc-apb", &priv->rst); // 2. 获取并配置时钟(设置ADC工作时钟频率) ret = clk_get_by_index(dev, 0, &clk); ret = clk_set_rate(&clk, priv->data->clk_rate); // 3. 等待PLL稳定(硬件要求,避免时钟不稳导致采样错误) mdelay(5); priv->active_channel = -1; // 初始化当前激活通道为-1(无激活) return 0;}
核心逻辑:ADC 工作前必须先配置时钟(保证时序稳定),获取复位控制(后续启动转换前复位 ADC)。
2.3 启动通道转换:开始采样
想要读取某个通道的 ADC 值,第一步是启动该通道的转换,核心函数rockchip_saradc_start_channel:
static int rockchip_saradc_start_channel(struct udevice *dev, int channel){ struct rockchip_saradc_priv *priv = dev_get_priv(dev); int val; // 1. 检查通道是否有效(RK3576最多支持8个通道) if (channel < 0 || channel >= priv->data->num_channels) { pr_err("Requested channel is invalid!"); return -EINVAL; } // 2. 复位ADC(保证每次转换的初始状态一致) reset_assert(&priv->rst); udelay(10); reset_deassert(&priv->rst); // 3. 配置时序参数(上电延时、数据采集时间) writel(0x20, &priv->regs->t_pd_soc); writel(0xc, &priv->regs->t_das_soc); // 4. 使能转换结束中断(方便判断转换完成) val = SARADC2_EN_END_INT << 16 | SARADC2_EN_END_INT; writel(val, &priv->regs->end_int_en); // 5. 启动单次转换(选择通道+单次模式+启动) val = SARADC2_START | SARADC2_SINGLE_MODE | channel; writel(val << 16 | val, &priv->regs->conv_con); udelay(100); priv->active_channel = channel; // 标记当前激活通道 return 0;}
核心逻辑:先复位清状态→配置时序(保证采样精度)→使能中断→启动单次转换,每一步都是为了让 ADC 稳定输出数据。
2.4 读取转换数据:拿到最终值
启动转换后,通过rockchip_saradc_channel_data读取数据,核心是“等中断 + 读寄存器 + 清标志”:
static int rockchip_saradc_channel_data(struct udevice *dev, int channel, unsigned int *data){ struct rockchip_saradc_priv *priv = dev_get_priv(dev); u32 status; // 1. 检查通道是否匹配(只能读当前激活的通道) if (channel != priv->active_channel) { pr_err("Requested channel is not active!"); return -EINVAL; } // 2. 等待转换完成(超时则返回错误) if (readl_poll_timeout(&priv->regs->end_int_st, status, status & SARADC2_EN_END_INT, SARADC_TIMEOUT)) { pr_err("Wait for end conversion interrupt status timeout!n"); return -ETIMEDOUT; } // 3. 清除中断标志(避免影响下一次转换) writel(0x1, &priv->regs->end_int_st); // 4. 读取数据并应用掩码(RK3576是12位精度,掩码过滤无效位) *data = readl(&priv->regs->data0 + priv->active_channel); *data &= uc_pdata->data_mask; return 0;}
三、一张图看懂:ADC 完整工作流程
把上面的代码逻辑串起来,RK3576 ADC 的工作流程可以总结为这张图,一目了然:

四、设备树配置:硬件参数的“入口”
RK3576 的 ADC 硬件参数都定义在设备树arch/arm/dts/rk3576.dtsi里,核心配置如下,改参数不用动驱动,改设备树即可:
saradc: adc@2ae00000 { compatible = "rockchip,rk3576-saradc", "rockchip,rk3588-saradc"; reg = <0x0 0x2ae00000 0x0 0x10000>; // 寄存器基地址 interrupts = ; // 中断号 #io-channel-cells = <1>; clocks = <&cru CLK_SARADC>, <&cru PCLK_SARADC>; // 时钟源 clock-names = "saradc", "apb_pclk"; resets = <&cru SRST_P_SARADC>; // 复位控制 reset-names = "saradc-apb"; status = "disabled"; // 默认禁用,启用改"okay"};
关键参数解读:
•compatible:驱动匹配标识(RK3576 复用 RK3588 的驱动);
•reg:ADC 寄存器的物理基地址,驱动通过这个地址操作硬件;
•clocks:ADC 的工作时钟和 APB 时钟,决定采样速率;
•resets:复位控制器,驱动通过它复位 ADC 模块。
五、实战:U-Boot 中如何读取 ADC 值
驱动最终是为了用,U-Boot 提供了通用 API,几行代码就能读取 ADC 值,还能转换成实际电压:
unsigned int adc_value;int ret;// 读取通道0的ADC值(设备名对应设备树里的saradc@2ae00000)ret = adc_channel_single_shot("saradc@2ae00000", 0, &adc_value);if (ret) { printf("ADC read failed: %dn", ret);} else { printf("ADC value: %dn", adc_value); // 转换为电压(12位精度,参考电压1.8V → 1LSB = 1.8V/4095 ≈ 0.439mV) int voltage = (adc_value * 1800) / 4095; printf("Voltage: %d mVn", voltage);}
六、新旧版本对比:为什么 RK3576 用 v2 版驱动?
RK3576 弃用旧版rockchip-saradc.c,改用rockchip-saradc-v2.c,核心是 v2 版功能更强大:
| 特性 | 旧版 (rockchip-saradc.c) | 新版 (rockchip-saradc-v2.c) |
| 通道数 | 最多 6 个 | 最多 8 个 |
| 精度 | 10/12 位 | 12 位(固定,精度更高) |
| 中断支持 | 基本(仅转换结束) | 完整(高低阈值、中阈值等) |
| 复位控制 | 无 | 有(每次转换复位,稳定性更高) |
| 支持芯片 | RK3066/RK3399 等老芯片 | RK3562/RK3576/RK3588 等新芯片 |
七、总结
RK3576 的 ADC 驱动设计,完美体现了 U-Boot DM 框架的核心思想:分层解耦。
•通用框架层:屏蔽硬件差异,提供统一 API,上层应用不用改;
•硬件驱动层:专注处理 RK3576 的硬件细节(寄存器、时钟、复位),适配新芯片只需改这层;
这种设计不仅让代码易维护、易扩展,也给我们开发带来启示:嵌入式驱动开发,优先复用框架,聚焦硬件差异即可。
如果你的项目中需要调试 RK3576 的 ADC 功能,比如电池检测、温度采集,不妨从这篇文章的思路入手 —— 先看架构,再拆代码,最后结合设备树和实战示例,问题往往能迎刃而解。
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !