在嵌入式开发中,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寄存器结构更完善,覆盖了转换控制、时序配置、中断、数据存储等全流程,核心寄存器如下(挑关键的讲):
structrockchip_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工作做准备:
staticintrockchip_saradc_probe(structudevice *dev){ structrockchip_saradc_priv *priv = dev_get_priv(dev); structclk clk; intret; // 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(无激活) return0;}
核心逻辑: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); intval; // 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;// 标记当前激活通道 return0;}
核心逻辑:先复位清状态→配置时序(保证采样精度)→使能中断→启动单次转换,每一步都是为了让ADC稳定输出数据。
2.4读取转换数据:拿到最终值
启动转换后,通过rockchip_saradc_channel_data读取数据,核心是“等中断+读寄存器+清标志”:
staticintrockchip_saradc_channel_data(structudevice *dev,intchannel, unsignedint*data){ structrockchip_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; return0;}
三、一张图看懂: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值,还能转换成实际电压:
unsignedintadc_value;intret;//读取通道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) intvoltage = (adc_value *1800) /4095; printf("Voltage:%dmVn", 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功能,比如电池检测、温度采集,不妨从这篇文章的思路入手——先看架构,再拆代码,最后结合设备树和实战示例,问题往往能迎刃而解。
审核编辑 黄宇
-
adc
+关注
关注
100文章
7950浏览量
556931 -
u-boot
+关注
关注
0文章
140浏览量
39955 -
rk3576
+关注
关注
1文章
304浏览量
1678
发布评论请先 登录
吃透RK3576 U-Boot.map文件!嵌入式开发调试、性能优化、代码裁剪全攻略
深度解析 RK 平台 U-Boot 环境变量(env):原理、配置与实战
深度剖析U-Boot ADC Uclass:从架构到实战的全维度解析
深入解析U-Boot image.c:RK平台镜像处理核心逻辑
深入理解 RK3506 U-Boot 重定位:从代码到原理
RK3576驱动高端显控系统升级:多屏拼控与AI视觉融合解决方案
360环视硬件平台为什么推荐使用米尔RK3576开发板?
【作品合集】米尔RK3576开发板测评
瑞芯微RK3576平台FFmpeg硬件编解码移植及性能测试实战攻略 触觉智能RK3576开发板演示
瑞芯微RK3576与RK3576S有什么区别,性能参数配置与型号差异解析
深入解析RK3576平台U-Boot下ADC驱动实现|从架构到实战
评论