字符设备驱动实例(ADC驱动)

news/2025/1/15 20:04:16/


四、ADC驱动


        ADC是将模拟信号转换为数字信号的转换器,在 Exynos4412 上有一个ADC,其主要的特性如下。
(1)量程为0~1.8V。
(2)精度有 10bit 和 12bit 可选。
(3)采样时钟最高为5MHz,转换速率最高为1MSPS
(4)具有四路模拟输入,同一时刻只有一路进行转换
(5) 转换完成后可以产生中断。

下面是ADC的控制寄存器

下面是延时和数据寄存器 

 下面是中断清除和通道多路复用寄存器

 根据上面的寄存器描述,我们大致可以设计出这个ADC 驱动实现的主要步骤。

(1)初始化 ADC,包括选择精度、设置分频值、设置 ADC 为正常工作模式、设置转换启动方式。
(2)注册ADC中断处理函数。
(3)在上层需要ADC数据时,选择好ADC 通道,启动转换,然后等待一个完成量

(4)转换结束后产生中断,在中断处理函数中获取转换结果值,向 CLRINTADC 寄存器写任意值清除中断,然后唤醒等待完成量的进程。
(5)进程被唤醒,返回转换结果值给上层。
接下来就是要编写ADC的设备树节点,代码如下。

adc@126C0000 {compatible = "fs4412,fsadc";reg = <0x126C0000 32>;interrupt-parent = <&combiner>;interrupts = <10 3>;
};


        reg 属性可以查阅芯片手册得到其寄存器地址。在这里比较麻烦的是中断属性的设置。在 Exynos4412中有的中断是直接接入 GIC 中断控制器的,有的中断则是先通过中断组合器(Combiner)将多个中断复合后再接入 GIC 中断控制器的,连接图如图所示。

而ADC中断属于INTG10这一组中断,手册上的描述如图所示


这一组共用一根中断线,中断号如图 所示。

        查阅Documentation/devicetree/bindings/arm/samsung/interrupt-combiner.txt内核文档可知:对于这种中断的描述,combiner 是其父中断控制器,所以,interrupt-parent 的值为<&combiner>。interrupts 属性的第一个 cell是中断在组合器中的组号,第二个cell是中断在该组中的序号。

        在FS4412日标板上有一个电位器的抽头接在了AIN3通道上原理图如图所示
图ADC电路图


        接下来就是驱动的实现,主要代码如下
 

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/cdev.h>#include <linux/slab.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>#include <linux/io.h>
#include <linux/ioport.h>
#include <linux/platform_device.h>#include <linux/of.h>
#include <linux/interrupt.h>#include "fsadc.h"#define FSADC_MAJOR	256
#define FSADC_MINOR	6
#define FSADC_DEV_NAME	"fsadc"struct fsadc_dev {unsigned int __iomem *adccon;unsigned int __iomem *adcdat;unsigned int __iomem *clrint;unsigned int __iomem *adcmux;unsigned int adcval;struct completion completion;atomic_t available;unsigned int irq;struct cdev cdev;
};static int fsadc_open(struct inode *inode, struct file *filp)
{struct fsadc_dev *fsadc = container_of(inode->i_cdev, struct fsadc_dev, cdev);filp->private_data = fsadc;if (atomic_dec_and_test(&fsadc->available))return 0;else {atomic_inc(&fsadc->available);return -EBUSY;}
}static int fsadc_release(struct inode *inode, struct file *filp)
{struct fsadc_dev *fsadc = filp->private_data;atomic_inc(&fsadc->available);return 0;
}static long fsadc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{struct fsadc_dev *fsadc = filp->private_data;union chan_val cv;if (_IOC_TYPE(cmd) != FSADC_MAGIC)return -ENOTTY;switch (cmd) {case FSADC_GET_VAL:if (copy_from_user(&cv, (union chan_val __user *)arg, sizeof(union chan_val)))return -EFAULT;if (cv.chan > AIN3)return -ENOTTY;writel(cv.chan, fsadc->adcmux);writel(readl(fsadc->adccon) | 1, fsadc->adccon);if (wait_for_completion_interruptible(&fsadc->completion))return -ERESTARTSYS;cv.val = fsadc->adcval & 0xFFF;if (copy_to_user( (union chan_val __user *)arg, &cv, sizeof(union chan_val)))return -EFAULT;break;default:return -ENOTTY;}return 0;
}static irqreturn_t fsadc_isr(int irq, void *dev_id)
{struct fsadc_dev *fsadc = dev_id;fsadc->adcval = readl(fsadc->adcdat);writel(1, fsadc->clrint);complete(&fsadc->completion);return IRQ_HANDLED;
}static struct file_operations fsadc_ops = {.owner = THIS_MODULE,.open = fsadc_open,.release = fsadc_release,.unlocked_ioctl = fsadc_ioctl,
};static int fsadc_probe(struct platform_device *pdev)
{int ret;dev_t dev;struct fsadc_dev *fsadc;struct resource *res;dev = MKDEV(FSADC_MAJOR, FSADC_MINOR);ret = register_chrdev_region(dev, 1, FSADC_DEV_NAME);if (ret)goto reg_err;fsadc = kzalloc(sizeof(struct fsadc_dev), GFP_KERNEL);if (!fsadc) {ret = -ENOMEM;goto mem_err;}platform_set_drvdata(pdev, fsadc);cdev_init(&fsadc->cdev, &fsadc_ops);fsadc->cdev.owner = THIS_MODULE;ret = cdev_add(&fsadc->cdev, dev, 1);if (ret)goto add_err;res = platform_get_resource(pdev, IORESOURCE_MEM, 0);if (!res) {ret = -ENOENT;goto res_err;}fsadc->adccon = ioremap(res->start, resource_size(res));if (!fsadc->adccon) {ret = -EBUSY;goto map_err;}fsadc->adcdat = fsadc->adccon + 3;fsadc->clrint = fsadc->adccon + 6;fsadc->adcmux = fsadc->adccon + 7;fsadc->irq = platform_get_irq(pdev, 0);if (fsadc->irq < 0) {ret = fsadc->irq;goto irq_err;}ret = request_irq(fsadc->irq, fsadc_isr, 0, "adc", fsadc);if (ret)goto irq_err;writel((1 << 16) | (1 << 14) | (19 << 6), fsadc->adccon);init_completion(&fsadc->completion);atomic_set(&fsadc->available, 1);return 0;
irq_err:iounmap(fsadc->adccon);
map_err:
res_err:cdev_del(&fsadc->cdev);
add_err:kfree(fsadc);
mem_err:unregister_chrdev_region(dev, 1);
reg_err:return ret;
}static int fsadc_remove(struct platform_device *pdev)
{dev_t dev;struct fsadc_dev *fsadc = platform_get_drvdata(pdev);dev = MKDEV(FSADC_MAJOR, FSADC_MINOR);writel((readl(fsadc->adccon) & ~(1 << 16)) | (1 << 2), fsadc->adccon);free_irq(fsadc->irq, fsadc);iounmap(fsadc->adccon);cdev_del(&fsadc->cdev);kfree(fsadc);unregister_chrdev_region(dev, 1);return 0;
}static const struct of_device_id fsadc_of_matches[] = {{ .compatible = "fs4412,fsadc", },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, fsadc_of_matches);struct platform_driver fsadc_drv = { .driver = { .name    = "fsadc",.owner   = THIS_MODULE,.of_match_table = of_match_ptr(fsadc_of_matches),},  .probe   = fsadc_probe,.remove  = fsadc_remove,
};module_platform_driver(fsadc_drv);MODULE_LICENSE("GPL");
MODULE_AUTHOR("name <e-mail>");
MODULE_DESCRIPTION("ADC driver");

 

#ifndef _FSADC_H
#define _FSADC_H#define FSADC_MAGIC	'f'union chan_val {unsigned int chan;unsigned int val;
};#define FSADC_GET_VAL	_IOWR(FSADC_MAGIC, 0, union chan_val)#define AIN0	0
#define AIN1	1
#define AIN2	2
#define AIN3	3#endif


        代码第 26 行至第 29 行是各寄存器的指针。代码第 32 行是用于保存采样(转换)结果的变量。代码第32 行是用于同步采结束的完成量。
        在fsadc_probe 函数中,与字符设备相关的代码和前面基本一样,接下来就是资源的获取、IO 内存的映射、中断的获取和中断处理函数的注册。然后就是初始化 ADC,将ADCCON 寄存器的第 16 位和第 14 位置 1,表示采样精度为 12位,使能预分频。并且预分配值设为19,即为20分频,这是因为在 U-Boot 中,我们将 APB 的时钟设为了100MHz。最后没有设置读启动位,也就意味着,采样的启动是靠 ADCCON 寄存器的比特0来控制的。
        fsadc_ioctl 函数是处理采样的关键函数,这里用到了自定义的一个联合体 unionchan_val,它的定义如下。

union chan_val {unsigned int chan;unsigned int val;
};


        有两个成员分别是 chan 和 val。命令 FSADC_GET_VAL 是读写类型的,在应用层往驱动层的传递过程中传递的是要使用的ADC通道chan,在驱动层往应用层传递的过程中传递的是得到的采样值 val。代码第 69 行至第72行先从应用层得到通道号,然后代码第73行将通道号写入到ADCMUX 寄存器中,接下来将ADCCON 寄存器的比特0置1启动采样,最后就调用wait_for_completion_interruptible 来等待完成量。当采样完成时,中断处理函数自动被调用,代码第 92 行先读取了转换结果值寄存器 ADCDAT,然后将 1写入 CLRINT 寄存器中,清除中断,最后唤醒等待完成量的进程。进程被唤醒后,代码第77行和第78 行得到采样值,并返回给上层。
        测试代码如下。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <errno.h>#include "fsadc.h"int main(int argc, char *argv[])
{int fd;int ret;union chan_val cv;fd = open("/dev/adc", O_RDWR);if (fd == -1)goto fail;while (1) {cv.chan = 3;ret = ioctl(fd, FSADC_GET_VAL, &cv);if (ret == -1)goto fail;printf("current volatage is: %.2fV\n", 1.8 * cv.val / 4095.0);sleep(1);}
fail:perror("adc test");exit(EXIT_FAILURE);
}


将数字值转换为对应的模拟值使用了下面的表达式

1.8 * cv.val  /  4095.0


        1.8 是满量程的电压,4095.0 是满量程时对应的数字值(因为是 12位),cv.val 是采样得到的数字,用1.8 乘以采样值和满量程数字值之比就能得到当前的电压编译和测试的命令如下,旋转电位器旋钮可以看到电压变化。

 

新网线就是丝滑,之前还会突然断开。 


http://www.ppmy.cn/news/1054984.html

相关文章

了解单例模式,工厂模式(简单易懂)

文章目录 单例模式饿汉模式懒汉模式对比 工厂模式简单工厂模式&#xff08;Simple Factory Pattern&#xff09;工厂方法模式&#xff08;Factory Method Pattern&#xff09;抽象工厂模式&#xff08;Abstract Factory Pattern&#xff09;对比 单例模式 什么是单例&#xff…

Spring缓存深入解析:@Cacheable的使用详解

摘要&#xff1a;在本文中&#xff0c;我们将深入研究Spring框架中的Cacheable注解。我们会通过详细的Java示例&#xff0c;探讨如何使用这个功能强大的注解来提升应用程序性能。 一、什么是缓存&#xff1f; 在计算机科学中&#xff0c;缓存是一种存储技术&#xff0c;用于保…

Sui第四轮资助:16个团队瓜分

近日&#xff0c;Sui基金会公布了第四轮开发者资助名单&#xff0c;受助项目均是集中在DeFi、支付、基础设施、游戏、预言机等领域的Sui生态项目&#xff0c;他们是从2023年7月1日之前提交的申请中选出的。在此时间之后提交的任何项目目前正在审查中。 在前三轮资助中累积发放…

SAP‘s ECC6 EoL(End of Life) 支持服务声明 2027?

前言 一、EoL公告信息&#xff0c;2027&#xff1f; 二、继续使用ECC6.0的选项 1.引入第三方支持 2.S/4 HANA 3.SAP Business ByDesign 4.SAP Business One 总结 最新的公告是&#xff1a;2027年&#xff0c;SAP ECC 6.0将停止得到支持&#xff0c;并退出主流SAP支持&am…

【C++入门到精通】C++入门 —— 模版(template)

阅读导航 前言一、模版的概念二、函数模版1. 函数模板概念2. 函数模板定义格式3. 函数模板的原理4. 函数模版的实例化&#x1f6a9;隐式实例化&#x1f6a9;显式实例化 5. 函数模板的匹配原则 三、类模板1. 类模板的定义格式2. 类模板的实例化 四、非类型模板参数1. 概念2. 定义…

MySQL数据库软件

MySQL数据库软件的详细知识介绍: 1. 存储引擎 MySQL支持多种存储引擎,如InnoDB、MyISAM等。不同引擎有各自的特点,InnoDB支持事务、行锁,MyISAM支持全文索引等。 2. 索引结构 MySQL索引主要有B树索引、哈希索引、全文索引等。这些索引通过不同的数据结构加速查找效率。 3. …

备份服务器搭建

备份服务器搭建 1、背景2、作用3、选型4、环境5、部署5.1、服务端部署5.1.1、安装5.1.2、配置 5.2、客户端部署5.3、备份策略5.3.1、定时备份策略5.3.2、文件变动备份 6、参考 1、背景 随着项目的推进&#xff0c;备份服务器被提上了工作日程&#xff0c;等保、密评和接入测评…

vue 简单实验 v-for 循环

1.代码 <script src"https://unpkg.com/vuenext" rel"external nofollow" ></script> <div id"list-rendering"><ol><li v-for"todo in todos">{{ todo.text }}</li></ol> </div> &…