您的位置:首页 > 运维架构 > Linux

Linux设备驱动开发基础---字符设备驱动程序开发之mini2440_ADC驱动

2013-07-23 19:51 507 查看
一、硬件原理分析

S3C2440内部ADC结构图



我们从上面的结构图和数据手册可以知道,该ADC模块总共有8个通道可以进行模拟信号的输入,分别是AIN0、AIN1、AIN2、AIN3、 YM、YP、XM、XP。那么ADC是怎么实现模拟信号到数字信号的转换呢?首先模拟信号从任一通道输入,然后设定寄存器中预分频器的值来确定AD转换器频率,最后ADC将模拟信号转换为数字信号保存到ADC数据寄存器0中(ADCDAT0),然后ADCDAT0中的数据可以通过中断或查询的方式来访问。 对于ADC的各寄存器的操作和注意事项请参阅数据手册。



上图是mini2440上的ADC应用实例,开发板通过一个10K的电位器(可变电阻)来产生电压模拟信号,然后通过第一个通道(即:AIN0)将模拟信号输入ADC。

二、实现方式

ADC设备在Linux中可以看做是简单的字符设备,也可以当做是一混杂设备(misc设备),这里我们就看做是misc设备来实现ADC的驱动。注意:这里我们获取AD转换后的数据将采用中断的方式,即当AD转换完成后产生AD中断,在中断服务程序中来读取ADCDAT0的第0-9位的值(即AD转换后的值)。

在 S3C2440 芯片中,AD 输入和触摸屏接口使用共同的A/D 转换器,见2440 芯片手册第16 章节,如图,其中通道7作为触摸屏接口的X坐标输入,通道5作为触摸屏接口的Y坐标输入。

首先drivers/misc目录下新建一个s3c24xx_adc.h文件,通过s3c24xx_adc.h文件中提供的宏修改通道获取采样数据,该头文件的代码如下:

#ifndef _S3C2410_ADC_H_

#define _S3C2410_ADC_H_

#define ADC_WRITE(ch, prescale) ((ch)<<16|(prescale))

#define ADC_WRITE_GETCH(data) (((data)>>16)&0x7)

#define ADC_WRITE_GETPRE(data) ((data)&0xff)

#endif /* _S3C2410_ADC_H_ */

然后在drivers/misc 目录下新建一个驱动程序的文件名为mini2440_adc.c的文件,由上述内容可知,ADC 驱动和触摸屏驱动若想共存,就必须解决共享“A/D 转换器”资源这个问题,因此在ADC 驱动程序中声明了一个全局的“ADC_LOCK”信号量,ADC 驱动程序的内容和注解如下:

#include <linux/errno.h>

#include <linux/kernel.h>

#include <linux/module.h>

#include <linux/slab.h>

#include <linux/input.h>

#include <linux/init.h>

#include <linux/serio.h>

#include <linux/delay.h>

#include <linux/clk.h>

#include <linux/wait.h>

#include <linux/sched.h>

#include <asm/io.h>

#include <asm/irq.h>

#include <asm/uaccess.h>

#include <mach/regs-clock.h>

#include <plat/regs-timer.h>

#include <plat/regs-adc.h>

#include <mach/regs-gpio.h>

#include <linux/cdev.h>

#include <linux/miscdevice.h>

//;自己定义的头文件

#include "s3c24xx_adc.h"

//;定义ADC 转换设备名称,将出现在/dev/adc

#define DEVICE_NAME "adc"

static void __iomem *adc_base; /*定义了一个用来保存经过虚拟映射后的内存地址*/

//;定义ADC 设备结构

typedef struct {

wait_queue_head_t wait;

int channel;

int prescale;

}ADC_DEV;

static ADC_DEV adcdev;

//定义一个信号量

DECLARE_MUTEX(ADC_LOCK);

/*用于标识AD转换后的数据是否可以读取,0表示不可读取*/

static volatile int ev_adc = 0;

/*用于保存读取的AD转换后的值,该值在ADC中断中读取*/

static int adc_data;

/*保存从平台时钟队列中获取ADC的时钟*/

static struct clk *adc_clk;

//;定义ADC 相关的寄存器

#define ADCCON (*(volatile unsigned long *)(adc_base + S3C2410_ADCCON)) //ADC control register

#define ADCTSC (*(volatile unsigned long *)(adc_base + S3C2410_ADCTSC)) //ADC TOUCH SCREEN CONTROL REGISTER

#define ADCDLY (*(volatile unsigned long *)(adc_base + S3C2410_ADCDLY)) //ADC start or IntervalDelay

#define ADCDAT0 (*(volatile unsigned long *)(adc_base + S3C2410_ADCDAT0)) //ADC CONVERSION DATA REGISTER 0

#define ADCDAT1 (*(volatile unsigned long *)(adc_base + S3C2410_ADCDAT1)) //ADC CONVERSION DATA REGISTER 1

#define ADCUPDN (*(volatile unsigned long *)(adc_base + 0x14)) //Stylus Up/Down interrupt status

/* 设置输入频道为ch,预分频系数为prescale,并使能ADC转换 */

#define start_adc(ch, prescale) \

do{ \

ADCCON = S3C2410_ADCCON_PRSCEN | S3C2410_ADCCON_PRSCVL(prescale) | S3C2410_ADCCON_SELMUX((ch)) ; \

ADCCON |= S3C2410_ADCCON_ENABLE_START; \

}while(0)

//ADC 中断处理函数

static irqreturn_t adc_irq(int irq, void *dev_id)

{

//如果ADC 驱动拥有"A/D 转换器"资源,则从ADC 寄存器读取转换结果

if (!ev_adc)

{

/*读取AD转换后的值保存到全局变量adc_data中*/

adc_data = ADCDAT0 & 0x3ff;

/*将可读标识为1,并唤醒等待队列*/

ev_adc = 1;

wake_up_interruptible(&adcdev.wait);

}

return IRQ_HANDLED;

}

//;ADC 读函数,一般对应于用户层/应用层的设备读函数(read)

static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)

{

/*试着获取信号量(即:加锁)*/

if (down_trylock(&ADC_LOCK)) {

return -EBUSY;

}

if(!ev_adc){ /*表示还没有AD转换后的数据,不可读取*/

if(filp->f_flags & O_NONBLOCK){

/*应用程序若采用非阻塞方式读取则返回错误*/

return -EAGAIN;

}

else {/*以阻塞方式进行读取*/

/*设置ADC控制寄存器,开启AD转换*/

start_adc(adcdev.channel, adcdev.prescale);

/*使等待队列进入睡眠*/

wait_event_interruptible(adcdev.wait, ev_adc);

}

}

/*能到这里就表示已有AD转换后的数据,则标识清0,给下一次读做判断用*/

ev_adc = 0;

/*将读取到的AD转换后的值发往到上层应用程序*/

copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

/*释放获取的信号量(即:解锁)*/

up(&ADC_LOCK);

return sizeof(adc_data);

}

static int adc_open(struct inode *inode, struct file *filp)

{

int ret;

/*Normal ADC conversion*/

ADCTSC = 0;

//初始化中断队列

init_waitqueue_head(&(adcdev.wait));

adcdev.channel=0;//;缺省通道为"0"

adcdev.prescale=0xff;

/* 申请ADC中断服务,这里使用的是共享中断:IRQF_SHARED,为什么要使用共享中断,因为在触摸屏驱动中

*也使用了这个中断号。中断服务程序为:adc_irq在下面实现,IRQ_ADC是ADC的中断号,这里注意:

*申请中断函数的最后一个参数一定不能为NULL,否则中断申请会失败,这里传入的是ADC_DEV类型的变量

*/

ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, &adcdev);

if (ret) {

/*错误处理*/

printk(KERN_ERR "IRQ%d error %d\n", IRQ_ADC, ret);

return -EINVAL;

}

return 0;

}

static int adc_release(struct inode *inode, struct file *filp)

{

return 0;

}

static struct file_operations dev_fops = {

.owner = THIS_MODULE,

.open = adc_open,

.read = adc_read,

.release = adc_release,

};

static struct miscdevice adc_miscdev = {

.minor = MISC_DYNAMIC_MINOR,

.name = DEVICE_NAME,

.fops = &dev_fops,

};

static int __init adc_init(void)

{

int ret;

/*开启对应外设的时钟*/

adc_clk = clk_get(NULL, "adc");

if (!adc_clk) {

printk(KERN_ERR "failed to get adc clock source\n");

return -ENOENT;

}

/*时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中*/

clk_enable(adc_clk);

/* 将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。

注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,

S3C2410_PA_ADC是ADC控制器的基地址,定义在mach-s3c2410/include/mach/map.h中,0x20是虚拟地址长度大小*/

adc_base=ioremap(S3C2410_PA_ADC,0x20);

if (adc_base == NULL) {

printk(KERN_ERR "Failed to remap register block\n");

ret = -EINVAL;

goto err_noclk;

}

ret = misc_register(&adc_miscdev);

if (ret) {

/*错误处理*/

printk(KERN_ERR "Cannot register miscdev on minor=%d (%d)\n", MISC_DYNAMIC_MINOR, ret);

goto err_nomap;

}

printk(DEVICE_NAME " initialized!\n");

return 0;

/*以下是上面错误处理的跳转点*/

err_noclk:

clk_disable(adc_clk);

clk_put(adc_clk);

err_nomap:

iounmap(adc_base);

return ret;

}

static void __exit adc_exit(void)

{

free_irq(IRQ_ADC, &adcdev); //释放中断

iounmap(adc_base); /*释放虚拟地址映射空间*/

if (adc_clk){ /*屏蔽和销毁时钟*/

clk_disable(adc_clk);

clk_put(adc_clk);

adc_clk = NULL;

}

misc_deregister(&adc_miscdev);

}

//;导出信号量"ADC_LOCK",以便触摸屏驱动使用

EXPORT_SYMBOL(ADC_LOCK);

module_init(adc_init);

module_exit(adc_exit);

MODULE_LICENSE("GPL");

MODULE_AUTHOR("DreamCatcher");

MODULE_DESCRIPTION("Mini2440 ADC Driver");

将此驱动程序编译入内核。

打开drivers/misc/Makefile 文件,在大概24 行加入ADC 驱动程序目标模块

obj-$(CONFIG_EP93XX_PWM) += ep93xx_pwm.o

obj-$(CONFIG_C2PORT) += c2port/

obj-$(CONFIG_MINI2440_ADC) += mini2440_adc.o

obj-y += eeprom/

obj-y += cb710/

再打开drivers/misc/Kconfig 文件,定位到16行附近,加入ADC 驱动配置选项:

menuconfig MISC_DEVICES

bool "Misc devices"

default y

---help---

Say Y here to get to see options for device drivers from various

different categories. This option alone does not add any kernel code.

If you say N, all options in this submenu will be skipped and disabled.

if MISC_DEVICES

config MINI2440_ADC

bool "ADC driver for FriendlyARM Mini2440 development boards"

depends on MACH_MINI2440

default y if MACH_MINI2440

help

this is ADC driver for FriendlyARM Mini2440 development boards

Notes: the touch-screen-driver required this option

config ATMEL_PWM

tristate "Atmel AT32/AT91 PWM support"

depends on AVR32 || ARCH_AT91SAM9263 || ARCH_AT91SAM9RL || ARCH_AT91CAP9

help

This option enables device driver support for the PWM channels

on certain Atmel processors. Pulse Width Modulation is used for

purposes including software controlled power-efficient backlights

on LCD displays, motor control, and waveform generation.

这样,我们就在内核中添加了ADC 驱动。

使用如下测试程序进行测试:

#include <stdio.h>

#include <stdlib.h>

#include <errno.h>

#include <sys/wait.h>

int main(int argc, char **argv)

{

int fd;

fprintf(stderr, "press Ctrl-C to stop\n");

//以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK

fd = open("/dev/adc", 0);

if(fd < 0)

{

printf("Open ADC Device Faild!\n");

exit(1);

}

while(1)

{

int ret;

int data;

//读设备

ret = read(fd, &data, sizeof(data));

if(ret != sizeof(data))

{

if(errno != EAGAIN)

{

printf("Read ADC Device Faild!\n");

}

continue;

}

else

{

printf("Read ADC value is: %d\n", data);

}

sleep(2);

}

close(fd);

return 0;

}

编译后进行测试,旋转电位器将看到如下结果:
root@MINI2440:/opt# ./adc_test&

root@MINI2440:/opt# press Ctrl-C to stop

Read ADC value is: 183

Read ADC value is: 198

Read ADC value is: 244

Read ADC value is: 312

Read ADC value is: 400

Read ADC value is: 507

Read ADC value is: 598

Read ADC value is: 728

Read ADC value is: 721

Read ADC value is: 753
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: 
相关文章推荐