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

news2024/12/27 11:27:46


四、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.coloradmin.cn/o/902614.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深入浅出解析Stable Diffusion中U-Net的核心知识与价值 | 【算法兵器谱】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【算法兵器谱】栏目专注分享AI行业中的前沿/经典/必备的模型&论文&#xff0c;并对具备划时代意义的模型&论文进行全方位系统的解析&#xff0c;比如Rocky之前出品的爆款文章Make YOLO Great Again系列。也欢迎大家提…

FFmpeg中avfilter模块简介及测试代码(overlay)

FFmpeg中的libavfilter模块(或库)用于filter(过滤器), filter可以有多个输入和多个输出。为了说明可能发生的事情&#xff0c;考虑以下filtergraph(过滤器图): 该filtergraph将输入流(stream)分成两个流&#xff0c;然后通过crop过滤器和vflip过滤器发送一个流&#xff0c;然后…

Android SDK 上手指南|| 第三章 IDE:Android Studio速览

第三章 IDE&#xff1a;Android Studio速览 Android Studio是Google官方提供的IDE&#xff0c;它是基于IntelliJ IDEA开发而来&#xff0c;用来替代Eclipse。不过目前它还属于早期版本&#xff0c;目前的版本是0.4.2&#xff0c;每个3个月发布一个版本&#xff0c;最近的版本…

非常适合大学附近的校园跑腿和自习室订座小程序

推荐两款非常适合在大学内和大学周边的项目 这两款小程序分别是校园跑腿系统和自习室在线订座系统 1、校园跑腿系统&#xff0c;第一张图所示&#xff0c;支持多校运营、快递代取、校园跑腿、租借服务、代理中心、跑腿中心、人员管理、订单抽成、数据统计、众包接单、消息通…

微信消息没通知iphone can‘t show notifications

小虎最近手机微信消息没通知&#xff0c;本来以为要卸载&#xff0c;但是发现原来是多客户端登录导致消息被其他平台截取&#xff0c;所有没有通知。 解决方法 小虎是在手机和电脑端同时登录的&#xff0c;所有退出电脑端后手机新消息就有提示了。可能是一个bug。

Docker版本号说明:安装不同版本看文档变化|遇错不求人

docker实战(一):centos7 yum安装docker docker实战(二):基础命令篇 docker实战(三):docker网络模式(超详细) docker实战(四):docker架构原理 docker实战(五):docker镜像及仓库配置 docker实战(六):docker 网络及数据卷设置 docker实战(七):docker 性质及版本选择 认知升…

Java:集合框架:Set集合、LinkedSet集合、TreeSet集合、哈希值、HashSet的底层原理

Set集合 创建一个Set集合对象&#xff0c;因为Set是一个接口不能直接new一个对象&#xff0c;所以要用一个实现类来接 HashSet来接 无序性只有一次&#xff0c;只要第一次运行出来后&#xff0c;之后再运行的顺序还是第一次的顺序。 用LinkedSet来接 有序 不重复 无索引 用Tree…

嵌入式入门教学——C51(下)

嵌入式入门教学汇总&#xff1a; 嵌入式入门教学——C51&#xff08;上&#xff09;嵌入式入门教学——C51&#xff08;中&#xff09;嵌入式入门教学——C51&#xff08;下&#xff09; 十三、AT24C02&#xff08;I2C总线&#xff09; 1、存储器 RAM、ROM各有优势&#xff…

pandas(pd)数据的一些操作( np数据转成pd数据、pd数据保存csv文件)

一. np数据转成pd数据 import pandas as pd import numpy as np# 第一种 data {Category: [A, B, C],Value: [10, 20, 15]}df pd.DataFrame(data) print(df)# 第二种 data np.array([[0, 1, 2, 5],[0, 3, 4, 5],[0, 5, 6, 5]]) df pd.DataFrame(data,columns[num1, num2, …

手撕vector容器

一、vector容器的介绍 vector是表示可变大小数组的序列容器。就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素&#xff0c;但是又不像数组&#xff0c;它的大小是可以动态改变的&#xff0c;而且它的大小会被容器自动处理。 总结&#xff1a;vector是一个动态…

OpenAI Function calling

开篇 原文出处 最近 OpenAI 在 6 月 13 号发布了新 feature&#xff0c;主要针对模型进行了优化&#xff0c;提供了 function calling 的功能&#xff0c;该 feature 对于很多集成 OpenAI 的应用来说绝对是一个“神器”。 Prompt 的演进 如果初看 OpenAI 官网对function ca…

计算机网络-物理层(三)编码与调制

计算机网络-物理层&#xff08;三&#xff09;编码与调制 在计算机网络中&#xff0c;计算机需要处理和传输用户的文字、图片、音频和视频&#xff0c;它们可以统称为消息 数据是运输信息的实体&#xff0c;计算机只能处理二进制数据&#xff0c;也就是比特0和比特1。计算机中…

不含数字的webshell绕过

异或操作原理 1.首先我们得了解一下异或操作的原理 在php中&#xff0c;异或操作是两个二进制数相同时&#xff0c;异或(相同)为0&#xff0c;不同为1 举个例子 A的ASCII值是65&#xff0c;对应的二进制值是0100 0001 的ASCII值是96&#xff0c;对应的二进制值是 0110 000…

CSS加载失败的6个原因

有很多刚刚接触 CSS 的新手有时会遇到 CSS 加载失败这个问题&#xff0c;但测试时&#xff0c;网页上没有显示该样式的问题&#xff0c;这就说明 CSS 加载失败了。出现这种状况一般是因为的 CSS 路径书写错&#xff0c;或者是在浏览器中禁止掉了 CSS 的加载&#xff0c;可以重新…

Linux/Ubuntu 的日常更新,如何操作?

我安装的是Ubuntu 20.04.6 LTS的Windows上Linux子系统版本&#xff0c;启动完成后显示&#xff1a; Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.90.4-microsoft-standard-WSL2 x86_64) * Documentation: https://help.ubuntu.com * Management: https://landscape.c…

使用mysql:5.6和owncloud镜像构建个人网盘

一、拉取镜像 使用docker拉取mysql:5.6和owncloud的镜像 [rootexam ~]# docker pull mysql:5.6 [rootexam ~]# docker pull owncloud 运行镜像生成容器实例 [rootexam ~]# docker run -d --name mydb1 --env MYSQL_ROOT_PASSWORD123456 mysql:5.6 a184c65b73ff993cc5cf86f…

保姆级教程:从0到1搭建Stable Diffusion XL完整工作流进行AI绘画 | 【人人都是算法专家】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【人人都是算法专家】栏目专注于分享Rocky在AI行业中对业务/竞赛/研究/产品维度的思考与感悟。欢迎大家一起交流学习&#x1f4aa; 大家好&#xff0c;我是Rocky。 之前Rocky详细介绍了Stable Diffusion&#xff08;SD&#…

Scratch 之 RPG 引擎制作教程(1) / 地图行走

大家好&#xff0c;又和大家见面了&#xff0c;那么我们这期讲的就是RPG游戏的地图以及人物的行走。 我发现大家并不是很懂RPG游戏引擎&#xff0c;也就是说这种引擎对于技术的要求还是比较高的。为了让更多人直接上手制作RPG游戏&#xff0c;我打算开启这一系列教程。 这个教程…

又一个 Python 图形界面库,简单好用

迷途小书童的Note 读完需要 10分钟 速读仅需 4 分钟 1 环境 python 3.9.16nicegui 1.3.9 2 前言 在现代计算机应用程序开发中&#xff0c;图形用户界面&#xff08;GUI&#xff09;是用户与程序交互的重要组成部分。然而&#xff0c;GUI 开发往往需要大量的代码和复杂的布局&am…

损失函数——感知损失

感知损失&#xff08;Perceptual Loss&#xff09;是一种基于深度学习的图像风格迁移方法中常用的损失函数。与传统的均方误差损失函数&#xff08;Mean Square Error&#xff0c;MSE&#xff09;相比&#xff0c;感知损失更注重图像的感知质量&#xff0c;更符合人眼对图像质量…