首頁 運維 linux運維 linux adc是什麼設備

linux adc是什麼設備

Apr 17, 2023 am 09:33 AM
linux adc

linux adc是混雜設備驅動;在linux2.6.30.4中,系統已經自帶了ADC通用驅動檔“arch/arm/plat-s3c24xx/adc.c”,它是以平台驅動設備模型的架構來寫的,裡面是一些比較通用穩定的程式碼。

linux adc是什麼設備

本教學操作環境:linux2.6.30.4系統、Dell G3電腦。

linux adc是什麼裝置?

linux 混雜裝置驅動程式之adc驅動程式

linux2.6.30.4中,系統已經自有了ADC通用驅動檔---arch/arm/plat-s3c24xx/adc.c,它是以平台驅動裝置模型的架構來寫的,裡面是一些比較通用穩定的程式碼,但是linux2.6.30.4版本的ADC通用驅動檔並不完善,居然沒有讀函數。後來我去看了linux3.8版本的ADC通用檔案----arch/arm/plat-samsung/adc.c才是比較完善的。

但本節並不是分析這個文件,而是以另一個架構來寫ADC驅動,因為ADC驅動實在比較簡單,就沒有使用平台驅動設備模型為架構來編寫了,這次我們使用的是混雜(misc)裝置驅動。

問:什麼是misc裝置驅動程式?

答案:miscdevice共用一個主裝置號碼MISC_MAJOR(10),但次裝置號碼不同。所有的miscdevice設備形成一條鍊錶,對設備存取時核心會根據設備號碼來尋找對應的miscdevice設備,然後呼叫其file_operations結構體中註冊的檔案操作介面進行操作。

struct miscdevice  {
	int minor;				//次设备号,如果设置为MISC_DYNAMIC_MINOR则系统自动分配
	const char *name;		//设备名
	const struct file_operations *fops;		//操作函数
	struct list_head list;
	struct device *parent;
	struct device *this_device;
};
登入後複製
dev_init入口函數分析:

#
static int __init dev_init(void)
{
	int ret;

	base_addr=ioremap(S3C2410_PA_ADC,0x20);
	if (base_addr == NULL)
	{
		printk(KERN_ERR "failed to remap register block\n");
		return -ENOMEM;
	}

	adc_clock = clk_get(NULL, "adc");
	if (!adc_clock)
	{
		printk(KERN_ERR "failed to get adc clock source\n");
		return -ENOENT;
	}
	clk_enable(adc_clock);
	
	ADCTSC = 0;

	ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
	if (ret)
	{
		iounmap(base_addr);
		return ret;
	}

	ret = misc_register(&misc);

	printk (DEVICE_NAME" initialized\n");
	return ret;
}
登入後複製
首先是映射ADC暫存器位址將其轉換為虛擬位址,接著取得ADC時鐘並使能ADC時鐘,接著申請ADC中斷,其中斷處理函數為

adcdone_int_handler,而flags為IRQF_SHARED,即共享中斷,因為觸控螢幕裡也要申請ADC中斷,最後註冊一個混雜裝置.

當應用程式open ("/dev/adc",...)時,就會呼叫到驅動裡面的open函數,那麼我們來看看open函數做了什麼?

static int tq2440_adc_open(struct inode *inode, struct file *filp)
{
	/* 初始化等待队列头 */
	init_waitqueue_head(&(adcdev.wait));

	/* 开发板上ADC的通道2连接着一个电位器 */
	adcdev.channel=2;	//设置ADC的通道
	adcdev.prescale=0xff;

	DPRINTK( "ADC opened\n");
	return 0;
}
登入後複製
很簡單,先初始化一個等待佇列頭,因為入口函數裡既然有申請ADC中斷,那麼肯定要使用等待隊列,接著設定ADC通道,因為TQ2440的ADC輸入通道預設是2,設定預分頻值為0xff。

當應用程式read時,就會呼叫到驅動裡面的read函數,那麼我們來看看read函數做了什麼?

static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
	char str[20];
	int value;
	size_t len;

	/* 尝试获得ADC_LOCK信号量,如果能够立刻获得,它就获得信号量并返回0 
	 * 否则,返回非零,它不会导致调用者睡眠,可以在中断上下文使用
	 */
	if (down_trylock(&ADC_LOCK) == 0)
	{
		/* 表示A/D转换器资源可用 */
		ADC_enable = 1;

		/* 使能预分频,选择ADC通道,最后启动ADC转换*/
		START_ADC_AIN(adcdev.channel, adcdev.prescale);

		/* 等待事件,当ev_adc = 0时,进程被阻塞,直到ev_adc>0 */
		wait_event_interruptible(adcdev.wait, ev_adc);

		ev_adc = 0;

		DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ((ADCCON & 0x80) ? 1:0));

		/* 将在ADC中断处理函数读取的ADC转换结果赋值给value */
		value = adc_data;
		sprintf(str,"%5d", adc_data);
		copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

		ADC_enable = 0;
		up(&ADC_LOCK);
	}
	else
	{
		/* 如果A/D转换器资源不可用,将value赋值为-1 */
		value = -1;
	}

	/* 将ADC转换结果输出到str数组里,以便传给应用空间 */
	len = sprintf(str, "%d\n", value);
	if (count >= len)
	{
		/* 从str数组里拷贝len字节的数据到buffer,即将ADC转换数据传给应用空间 */
		int r = copy_to_user(buffer, str, len);
		return r ? r : len;
	}
	else
	{
		return -EINVAL;
	}
}
登入後複製
tq2440_adc_read函數首先嘗試獲得ADC_LOCK訊號量,因為觸控螢幕驅動也有使用ADC資源,兩者互有競爭關係,獲得ADC資源後,啟用預分頻,選擇ADC通道,最後啟動ADC轉換,接著就呼叫wait_event_interruptible 函式進行等待,直到ev_adc>0行程才會繼續往下跑,往下跑就會adc_data資料讀出來,呼叫copy_to_user函式將ADC資料傳給應用空間,最後釋放ADC_LOCK訊號量。

問:什麼時候ev_adc>0?預設ev_adc = 0

答案:在adcdone_int_handler中斷處理函數裡,等資料讀出後,ev_adc被設定為1。

ADC中斷處理函數adcdone_int_handler

/* ADC中断处理函数 */
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
	/* A/D转换器资源可用 */
	if (ADC_enable)
	{
		/* 读ADC转换结果数据 */
		adc_data = ADCDAT0 & 0x3ff;

		/* 唤醒标志位,作为wait_event_interruptible的唤醒条件 */
		ev_adc = 1;
		wake_up_interruptible(&adcdev.wait);
	}
	return IRQ_HANDLED;
}
登入後複製
當AD轉換完成後就會觸發ADC中斷,就會進入adcdone_int_handler,這個函數就會講AD轉換資料讀出adc_data,接著將喚醒標誌位ev_adc置1,最後呼叫wake_up_interruptible函式喚醒adcdev.wait等待隊列。
總結一下ADC的工作流程:

一、open函數裡,設定類比輸入通道,設定預分頻值

二、read函數裡,啟動AD轉換,進程休眠

三、adc_irq函数里,AD转换结束后触发ADC中断,在ADC中断处理函数将数据读出,唤醒进程

四、read函数里,进程被唤醒后,将adc转换数据传给应用程序

ADC驱动参考源码:

/*************************************

NAME:EmbedSky_adc.c
COPYRIGHT:www.embedsky.net

*************************************/

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
	 
#include 
#include 
#include 
#include 

#include "tq2440_adc.h"

#undef DEBUG
//#define DEBUG
#ifdef DEBUG
#define DPRINTK(x...) {printk(KERN_DEBUG "EmbedSky_adc: " x);}
#else
#define DPRINTK(x...) (void)(0)
#endif

#define DEVICE_NAME	"adc"		/* 设备节点: /dev/adc */

static void __iomem *base_addr;

typedef struct
{
	wait_queue_head_t wait;		/* 定义等待队列头 */
	int channel;
	int prescale;
}ADC_DEV;

DECLARE_MUTEX(ADC_LOCK);	/* 定义并初始化信号量,并初始化为1 */
static int ADC_enable = 0;			/* A/D转换器资是否可用标志位 */

static ADC_DEV adcdev;				/* 用于表示ADC设备 */
static volatile int ev_adc = 0;		/* 作为wait_event_interruptible的唤醒条件 */
static int adc_data;

static struct clk	*adc_clock;

#define ADCCON		(*(volatile unsigned long *)(base_addr + S3C2410_ADCCON))	//ADC control
#define ADCTSC		(*(volatile unsigned long *)(base_addr + S3C2410_ADCTSC))	//ADC touch screen control
#define ADCDLY		(*(volatile unsigned long *)(base_addr + S3C2410_ADCDLY))	//ADC start or Interval Delay
#define ADCDAT0		(*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT0))	//ADC conversion data 0
#define ADCDAT1		(*(volatile unsigned long *)(base_addr + S3C2410_ADCDAT1))	//ADC conversion data 1
#define ADCUPDN		(*(volatile unsigned long *)(base_addr + 0x14))			//Stylus Up/Down interrupt status

#define PRESCALE_DIS	(0 << 14)
#define PRESCALE_EN		(1 << 14)
#define PRSCVL(x)		((x) << 6)
#define ADC_INPUT(x)	((x) << 3)
#define ADC_START		(1 << 0)
#define ADC_ENDCVT		(1 << 15)


/* 使能预分频,选择ADC通道,最后启动ADC转换*/
#define START_ADC_AIN(ch, prescale) \
	do{ 	ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
		ADCCON |= ADC_START; \
	}while(0)


/* ADC中断处理函数 */
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
{
	/* A/D转换器资源可用 */
	if (ADC_enable)
	{
		/* 读ADC转换结果数据 */
		adc_data = ADCDAT0 & 0x3ff;

		/* 唤醒标志位,作为wait_event_interruptible的唤醒条件 */
		ev_adc = 1;
		wake_up_interruptible(&adcdev.wait);
	}
	return IRQ_HANDLED;
}

static ssize_t tq2440_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
{
	char str[20];
	int value;
	size_t len;

	/* 尝试获得ADC_LOCK信号量,如果能够立刻获得,它就获得信号量并返回0 
	 * 否则,返回非零,它不会导致调用者睡眠,可以在中断上下文使用
	 */
	if (down_trylock(&ADC_LOCK) == 0)
	{
		/* 表示A/D转换器资源可用 */
		ADC_enable = 1;

		/* 使能预分频,选择ADC通道,最后启动ADC转换*/
		START_ADC_AIN(adcdev.channel, adcdev.prescale);

		/* 等待事件,当ev_adc = 0时,进程被阻塞,直到ev_adc>0 */
		wait_event_interruptible(adcdev.wait, ev_adc);

		ev_adc = 0;

		DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ((ADCCON & 0x80) ? 1:0));

		/* 将在ADC中断处理函数读取的ADC转换结果赋值给value */
		value = adc_data;
		sprintf(str,"%5d", adc_data);
		copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

		ADC_enable = 0;
		up(&ADC_LOCK);
	}
	else
	{
		/* 如果A/D转换器资源不可用,将value赋值为-1 */
		value = -1;
	}

	/* 将ADC转换结果输出到str数组里,以便传给应用空间 */
	len = sprintf(str, "%d\n", value);
	if (count >= len)
	{
		/* 从str数组里拷贝len字节的数据到buffer,即将ADC转换数据传给应用空间 */
		int r = copy_to_user(buffer, str, len);
		return r ? r : len;
	}
	else
	{
		return -EINVAL;
	}
}

static int tq2440_adc_open(struct inode *inode, struct file *filp)
{
	/* 初始化等待队列头 */
	init_waitqueue_head(&(adcdev.wait));

	/* 开发板上ADC的通道2连接着一个电位器 */
	adcdev.channel=2;	//设置ADC的通道
	adcdev.prescale=0xff;

	DPRINTK( "ADC opened\n");
	return 0;
}

static int tq2440_adc_release(struct inode *inode, struct file *filp)
{
	DPRINTK( "ADC closed\n");
	return 0;
}


static struct file_operations dev_fops = {
	owner:	THIS_MODULE,
	open:	tq2440_adc_open,
	read:	tq2440_adc_read,	
	release:	tq2440_adc_release,
};

static struct miscdevice misc = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEVICE_NAME,
	.fops = &dev_fops,
};

static int __init dev_init(void)
{
	int ret;

	base_addr=ioremap(S3C2410_PA_ADC,0x20);
	if (base_addr == NULL)
	{
		printk(KERN_ERR "failed to remap register block\n");
		return -ENOMEM;
	}

	adc_clock = clk_get(NULL, "adc");
	if (!adc_clock)
	{
		printk(KERN_ERR "failed to get adc clock source\n");
		return -ENOENT;
	}
	clk_enable(adc_clock);
	
	ADCTSC = 0;

	ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
	if (ret)
	{
		iounmap(base_addr);
		return ret;
	}

	ret = misc_register(&misc);

	printk (DEVICE_NAME" initialized\n");
	return ret;
}

static void __exit dev_exit(void)
{
	free_irq(IRQ_ADC, &adcdev);
	iounmap(base_addr);

	if (adc_clock)
	{
		clk_disable(adc_clock);
		clk_put(adc_clock);
		adc_clock = NULL;
	}

	misc_deregister(&misc);
}

EXPORT_SYMBOL(ADC_LOCK);
module_init(dev_init);
module_exit(dev_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("www.embedsky.net");
MODULE_DESCRIPTION("ADC Drivers for EmbedSky SKY2440/TQ2440 Board and support touch");
登入後複製
ADC应用测试参考源码:

/*************************************

NAME:EmbedSky_adc.c
COPYRIGHT:www.embedsky.net

*************************************/

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <linux/fs.h>
#include <errno.h>
#include <string.h>

int main(void)
{
	int fd ;
	char temp = 1;

	fd = open("/dev/adc", 0);
	if (fd < 0)
	{
		perror("open ADC device !");
		exit(1);
	}
	
	for( ; ; )
	{
		char buffer[30];
		int len ;

		len = read(fd, buffer, sizeof buffer -1);
		if (len > 0)
		{
			buffer[len] = '\0';
			int value;
			sscanf(buffer, "%d", &value);
			printf("ADC Value: %d\n", value);
		}
		else
		{
			perror("read ADC device !");
			exit(1);
		}
		sleep(1);
	}
adcstop:	
	close(fd);
}
登入後複製
测试结果:

[WJ2440]# ./adc_test 
ADC Value: 693
ADC Value: 695
ADC Value: 694
ADC Value: 695
ADC Value: 702
ADC Value: 740
ADC Value: 768
ADC Value: 775
ADC Value: 820
ADC Value: 844
ADC Value: 887
ADC Value: 937
ADC Value: 978
ADC Value: 1000
ADC Value: 1023
ADC Value: 1023
ADC Value: 1023
登入後複製

相关推荐:《Linux视频教程

以上是linux adc是什麼設備的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
1 個月前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

centos和ubuntu的區別 centos和ubuntu的區別 Apr 14, 2025 pm 09:09 PM

CentOS 和 Ubuntu 的關鍵差異在於:起源(CentOS 源自 Red Hat,面向企業;Ubuntu 源自 Debian,面向個人)、包管理(CentOS 使用 yum,注重穩定;Ubuntu 使用 apt,更新頻率高)、支持週期(CentOS 提供 10 年支持,Ubuntu 提供 5 年 LTS 支持)、社區支持(CentOS 側重穩定,Ubuntu 提供廣泛教程和文檔)、用途(CentOS 偏向服務器,Ubuntu 適用於服務器和桌面),其他差異包括安裝精簡度(CentOS 精

centos如何安裝 centos如何安裝 Apr 14, 2025 pm 09:03 PM

CentOS 安裝步驟:下載 ISO 映像並刻錄可引導媒體;啟動並選擇安裝源;選擇語言和鍵盤佈局;配置網絡;分區硬盤;設置系統時鐘;創建 root 用戶;選擇軟件包;開始安裝;安裝完成後重啟並從硬盤啟動。

Centos停止維護2024 Centos停止維護2024 Apr 14, 2025 pm 08:39 PM

CentOS將於2024年停止維護,原因是其上游發行版RHEL 8已停止維護。該停更將影響CentOS 8系統,使其無法繼續接收更新。用戶應規劃遷移,建議選項包括CentOS Stream、AlmaLinux和Rocky Linux,以保持系統安全和穩定。

docker原理詳解 docker原理詳解 Apr 14, 2025 pm 11:57 PM

Docker利用Linux內核特性,提供高效、隔離的應用運行環境。其工作原理如下:1. 鏡像作為只讀模板,包含運行應用所需的一切;2. 聯合文件系統(UnionFS)層疊多個文件系統,只存儲差異部分,節省空間並加快速度;3. 守護進程管理鏡像和容器,客戶端用於交互;4. Namespaces和cgroups實現容器隔離和資源限制;5. 多種網絡模式支持容器互聯。理解這些核心概念,才能更好地利用Docker。

Centos停止維護後的選擇 Centos停止維護後的選擇 Apr 14, 2025 pm 08:51 PM

CentOS 已停止維護,替代選擇包括:1. Rocky Linux(兼容性最佳);2. AlmaLinux(與 CentOS 兼容);3. Ubuntu Server(需要配置);4. Red Hat Enterprise Linux(商業版,付費許可);5. Oracle Linux(與 CentOS 和 RHEL 兼容)。在遷移時,考慮因素有:兼容性、可用性、支持、成本和社區支持。

centos停止維護後怎麼辦 centos停止維護後怎麼辦 Apr 14, 2025 pm 08:48 PM

CentOS 停止維護後,用戶可以採取以下措施應對:選擇兼容髮行版:如 AlmaLinux、Rocky Linux、CentOS Stream。遷移到商業發行版:如 Red Hat Enterprise Linux、Oracle Linux。升級到 CentOS 9 Stream:滾動發行版,提供最新技術。選擇其他 Linux 發行版:如 Ubuntu、Debian。評估容器、虛擬機或云平台等其他選項。

docker desktop怎麼用 docker desktop怎麼用 Apr 15, 2025 am 11:45 AM

如何使用 Docker Desktop? Docker Desktop 是一款工具,用於在本地機器上運行 Docker 容器。其使用步驟包括:1. 安裝 Docker Desktop;2. 啟動 Docker Desktop;3. 創建 Docker 鏡像(使用 Dockerfile);4. 構建 Docker 鏡像(使用 docker build);5. 運行 Docker 容器(使用 docker run)。

vscode需要什麼電腦配置 vscode需要什麼電腦配置 Apr 15, 2025 pm 09:48 PM

VS Code 系統要求:操作系統:Windows 10 及以上、macOS 10.12 及以上、Linux 發行版處理器:最低 1.6 GHz,推薦 2.0 GHz 及以上內存:最低 512 MB,推薦 4 GB 及以上存儲空間:最低 250 MB,推薦 1 GB 及以上其他要求:穩定網絡連接,Xorg/Wayland(Linux)

See all articles