RK 利用SARADC 来做多个按键

本文详细介绍RK3399开发板上ADC的配置与驱动实现过程,包括温度传感器和逐次逼近ADC的特性,DTS配置方法,以及如何通过工业I/O子系统控制ADC。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

RK3399开发板上的 AD 接口有两种,分别为:温度传感器 (Temperature Sensor)、逐次逼近ADC (Successive Approximation Register)。

  • TS-ADC(Temperature Sensor):支持两通道,时钟频率必须低于800KHZ
  • AR-ADC(Successive Approximation Register):支持六通道单端10位的SAR-ADC,时钟频率必须小于13MHZ。
    内核采用工业 I/O 子系统来控制 ADC,该子系统主要为 AD 转换或者 DA 转换的传感器设计。 下面以SAR-ADC为例子,介绍 ADC 的基本配置方法。

DTS配置

配置DTS节点

SAR-ADC 的 DTS 节点在 kernel/arch/arm64/boot/dts/rockchip/rk3399.dtsi 文件中定义,如下所示:

saradc: saradc@ff100000 {
               compatible = "rockchip,rk3399-saradc";
               reg = <0x0 0xff100000 0x0 0x100>;
               interrupts = <GIC_SPI 62 IRQ_TYPE_LEVEL_HIGH 0>;
               #io-channel-cells = <1>;
               clocks = <&cru SCLK_SARADC>, <&cru PCLK_SARADC>;
               clock-names = "saradc", "apb_pclk";
               resets = <&cru SRST_P_SARADC>;
		       reset-names = "saradc-apb";
               status = "disabled";
       };

用户首先需在DTS文件中添加ADC的资源描述:

&rk_key {
    compatible = "rockchip,key";
    status = "okay";
  
    io-channels = <&saradc 1>;
  
    vol-up-key {
        linux,code = <114>;
        label = "volume up";
        rockchip,adc_value = <1>;
    };
  
    vol-down-key {
        linux,code = <115>;
        label = "volume down";
        rockchip,adc_value = <170>;
    };
  
    power-key {
        gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
        linux,code = <116>;
        label = "power";
        gpio-key,wakeup;
    };
  
    menu-key {
        linux,code = <59>;
        label = "menu";
        rockchip,adc_value = <746>;
    };
  
    home-key {
        linux,code = <102>;
        label = "home";
        rockchip,adc_value = <355>;
    };
  
    back-key {
        linux,code = <158>;
        label = "back";
        rockchip,adc_value = <560>;
    };
  
    camera-key {
        linux,code = <212>;
        label = "camera";
        rockchip,adc_value = <450>;
    };
};

这里申请的是SARADC通道1

驱动文件中匹配 DTS 节点

驱动key:drivers/input/keyboard/rk_keys.c
首先在驱动文件中定义 of_device_id 结构体数组:

static const struct of_device_id rk_key_match[] = { 
    { .compatible = "rockchip,key", .data = NULL},                                                                                                                                                                                        
    {},                  
}; 

然后将该结构体数组填充到 platform_driver 中:

static struct platform_driver keys_device_driver = {                                                                                                                                                                                      
    .probe      = keys_probe,
    .remove     = keys_remove,
    .driver     = {
        .name   = "rk-keypad",
        .owner  = THIS_MODULE,
        .of_match_table = rk_key_match,
#ifdef CONFIG_PM
        .pm = &keys_pm_ops,
#endif         
    }          
};  

接着在keys_probe中使用work 进行polling :

    /* adc polling work */
    if (ddata->chan) {
        INIT_DELAYED_WORK(&ddata->adc_poll_work, adc_key_poll);
        schedule_delayed_work(&ddata->adc_poll_work,
                ┊   ┊ ADC_SAMPLE_JIFFIES);
    }    

ADC key Poll DTS资源解析

static void adc_key_poll(struct work_struct *work)                                                                                                                                                                                        
{                                
    struct rk_keys_drvdata *ddata;
    int i, result = -1;          
                                 
    ddata = container_of(work, struct rk_keys_drvdata, adc_poll_work.work);
    if (!ddata->in_suspend) {    
        result = rk_key_adc_iio_read(ddata);/**读取SARADC值*/
        if (result > INVALID_ADVALUE &&
        ┊   result < (EMPTY_DEFAULT_ADVALUE - ddata->drift_advalue))
            ddata->result = result;
        for (i = 0; i < ddata->nbuttons; i++) {
            struct rk_keys_button *button = &ddata->button[i];
                                 
            if (!button->adc_value)
                continue;        
            if (result < button->adc_value + ddata->drift_advalue &&
            ┊   result > button->adc_value - ddata->drift_advalue)
                button->adc_state = 1;
            else                 
                button->adc_state = 0;
            if (button->state != button->adc_state)
                mod_timer(&button->timer,
                    ┊ jiffies + DEBOUNCE_JIFFIES);
        }                        
    }                            
                                 
    schedule_delayed_work(&ddata->adc_poll_work, ADC_SAMPLE_JIFFIES);
}  

驱动说明

获取ADC通道

在解析资源DTS时 获取对应的通道
struct iio_channel *chan; /定义 IIO 通道结构体
chan = iio_channel_get(&pdev->dev, NULL); /获取 IIO 通道结构体/

static int rk_keys_parse_dt(struct rk_keys_drvdata *pdata,struct platform_device *pdev)
{           
    struct device_node *node = pdev->dev.of_node;
    struct device_node *child_node;
    struct iio_channel *chan;
    int ret, gpio, i = 0;
    u32 code, adc_value, flags, drift;
            
    if (of_property_read_u32(node, "adc-drift", &drift))
        pdata->drift_advalue = DRIFT_DEFAULT_ADVALUE;
    else    
        pdata->drift_advalue = (int)drift;
            
    chan = iio_channel_get(&pdev->dev, NULL);
...............

获取ADC值

在adc_key polling中 调用 iio_read_channel_raw 函数读取 AD 采集的原始数据并存入 val 中

  ret = iio_read_channel_raw(channel, &val);  

计算采集到的电压

Vref / (2^n-1) = Vresult / raw

注:

Vref 为标准电压
n 为 AD 转换的位数
Vresult 为用户所需要的采集电压
raw 为 AD 采集的原始数据

接口说明

struct iio_channel *iio_channel_get(struct device *dev, const char *consumer_channel);
  • 功能:获取 iio 通道描述
  • 参数:
    • dev: 使用该通道的设备描述指针
    • consumer_channel: 该设备所使用的 IIO 通道描述指针
void iio_channel_release(struct iio_channel *chan);
  • 功能:释放 iio_channel_get 函数获取到的通道
  • 参数:
    • chan:要被释放的通道描述指针
int iio_read_channel_raw(struct iio_channel *chan, int *val);
  • 功能:读取 chan 通道 AD 采集的原始数据。
  • 参数:
    • chan:要读取的采集通道指针
    • val:存放读取结果的指针

调试方法

节点ADC值

cat /sys/bus/iio/devices/iio\:device1/in_voltage*_raw   
876
1021
164
512
513
349

在这里插入图片描述

### RK3ADC 实现按键按功能方法 #### 按检测逻辑设计 为了实现RK3ADC设备中的按键按功能,通常采用轮询机制定期读取AD转换后的数值并判断按键状态。具体来说,在`rk_key_adc_iio_read()`函数中通过调用`iio_read_channel_raw()`获取当前模拟输入端口上的电压水平,并将其存储于变量`val`之中[^1]。 当初始化过程中存在有效的ADC通道时,则会创建延迟工作队列用于周期性的执行ADC采样操作。此过程由`INIT_DELAYED_WORK()`宏完成,并设定初始延时时间间隔为`ADC_SAMPLE_JIFFIES`滴答数[^2]。 对于时间按下按钮的情况,可以在每次循环读取到有效键值之后启动定时器计时;如果连续多次(即超过一定阈值次数)都保持同一低电平信号不变,则认为发生了“按”。此时可触发特定事件响应,比如重启至恢复模式等特殊动作[^3]。 #### 示例代码展示如何集成按时限处理逻辑: ```c static void handle_long_press(struct rk_keys_drvdata *data){ // 增加一个成员记录上次的状态 static int last_val; // 获取最新的ADC值 int current_val = rk_key_adc_iio_read(data); // 如果是首次或者松开则重置计数器 if(current_val != last_val || !last_val){ reset_timer(); } // 当持续处于闭合状态下更新计时器 else{ update_timer(); // 达到预设时限后执行相应命令 if(timer_exceeds_threshold()){ trigger_recovery_mode(); // 如清除misc分区以激活恢复出厂设置流程 } } // 更新上一次的值 last_val = current_val; } ``` 上述伪代码片段展示了如何基于现有框架扩展支持按行为的功能模块。实际应用中可能还需要考虑更多细节问题,例如防止误触、优化性能等方面。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lin_AIOS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值