当前位置: 首页 > 嵌入式Linux > 正文

s3c2410多通道adc驱动及测试程序

网上流行很多基于2410的ADC驱动及测试程序。本文所使用的是开发板光盘中自带的经过修改后的adc驱动。笔者在这个基础上再作一点修改。由于那个文件已经删除了版权信息(但还是能找到这些代码与网上流行的驱动的一些联系),这里也不知道如何添加了,可以肯定的是,它使用了GPL,这里公开源代码,也算是GPL了。

原来的代码默认使用ADC第0个通道,本文将添加ioctl接口,可以通过应用层的ioctl来选择多个通道。{jcomments on}

与原来的代码相比,添加了如下几个方面:

1、添加头文件<linux/ioctl.h>,不过经测试,没有也可以通过编译。

2、修改原来的调试信息为:

#define DEBUG

#ifdef DEBUG /* define it in Makefile of somewhere */

/* KERN_INFO */

#define DPRINTK(fmt, …) printk(KERN_DEBUG fmt, ##__VA_ARGS__)

#else

#define DPRINTK(fmt, …)

#endif

这个便于查看调试信息。

3、添加ioctl相关宏定义:

/* ioctl */#ifndef u16

#define u16 unsigned short

#endif#define ADC_IOC_MAGIC 0xd2

#define ADC_SET_CHANNEL _IOW(ADC_IOC_MAGIC, 0, u16)

#define ADC_SET_CLKDIV _IOW(ADC_IOC_MAGIC, 1, u16)

#define ADC_MAX_IOC 2 /* we only have 2 ioctl commands */

#define MAX_ADC  4 /* we have 4 adc chnnels */

/* end of ioctl */

 

4、添加ioctl接口:

 /* 在应用层调用系统调用ioctl发生错误时,会返回-1,并设置errno为相应的错误号,而这个错误号便是驱动中ioctl中的那个。网上有资料说要返回-ENOTTY,不过个人认为这里返回-EINVAL更恰当一些。

*/

static int s3c2410_adc_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

if ((_IOC_TYPE(cmd) != ADC_IOC_MAGIC) || (_IOC_NR(cmd) > ADC_MAX_IOC))

return -EINVAL;        switch (cmd) {

/* set channel */

case ADC_SET_CHANNEL:

arg &=3;  //多此一举??

if (arg > 3)

arg = 3;

adcdev.channel=arg;

break;

case ADC_SET_CLKDIV:

arg &= 0xff; // ??

if (arg > 0xff)

arg = 0xff;

adcdev.prescale=arg;

break;

default:

return -EINVAL;

break;

}

return 0;

}

当然,也要在这个文件的file_operations结构体添加这个接口:

        .ioctl   = s3c2410_adc_ioctl,

5、copy_to_user

原来的代码使用了sprintf将ADC转换的结果转换为字符串类型,不过却在后面添加一个“n”,不知是何意。

 //len = sprintf(str, “%dn”, value); // why ‘n’?len = sprintf(str, “%d”, value);

……

copy_to_user(buffer, str, len);

也正是这个原因,在测试程序中要使用sscanf将字符串转换成整数类型才能得到正常的结果。

其它的修改不影响使用。

测试代码也简单,如下:

#include <unistd.h>#include <stdio.h>

#include <stdlib.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <sys/ioctl.h>

#include <signal.h>

#include <linux/ioctl.h>#define ADC “/dev/adc”

#ifdef DEBUG

#define debug(fmt, …) printf(fmt, ##__VA_ARGS__)

#else

#define debug(fmt, …)

#endif

#ifndef u16

#define u16        unsigned short

#endif

// 控制字,与驱动一致

#define ADC_IOC_MAGIC        0xd2

#define ADC_SET_CHANNEL        _IOW(ADC_IOC_MAGIC, 0, u16)

#define ADC_SET_CLKDIV        _IOW(ADC_IOC_MAGIC, 1, u16)

// 特意的错误控制字

// test

#define AAA 333

// test end

int fd;

// 由于是死循环,需要捕获SIGINT信号

void sig_handle(int sig)

{

//debug(“you press ^C: %dn”, sig);

close(fd);        /* we colse it here */

exit(0);

}

int main(void)

{

char buff[10];

int val;

int len;

int i;

signal(SIGINT, sig_handle);

debug(“Test of ADC. ^C to exitn”);

fd = open(ADC, O_RDWR);

if (fd < 0){

perror(“Open /dev/adc failed”);

exit(1);

}

// test

/* it will return -EINVAL(which specified in ADC driver) */

if (ioctl(fd, AAA,0) < 0)

perror(“ioctl”);

while (1) {

/* we have 4 channels ADC*/

for (i=0; i<4; i++) {

ioctl(fd, ADC_SET_CHANNEL, i);

len = read(fd, buff,sizeof(buff));

if (len < 0) {

perror(“read adc device failed”);

exit(0);

} else {

buff[len] = ”;

sscanf(buff, “%d”, &val);

printf(“read AIN[%d]: %d value:%dn”, i, len, val);

}

sleep(1); // 每隔1秒采1个通道

}

//sleep(1); // 每隔1秒采集4个通道

}

close(fd); /* just kidding */

return 0;

}

 

注:文中代码注释出现了中文,是为了方便文章的阅读(根据经验,有清楚注释的代码才算代码),在实际代码中是没有中文注释的。

后续之事:修改网上流行的adc驱动,使用write或ioctl从应用层传递数据到驱动。

在开发板上运行结果如下:

./myadcread AIN[0]: 3 value:629

read AIN[1]: 3 value:316

read AIN[2]: 3 value:257

read AIN[3]: 3 value:303

read AIN[0]: 1 value:0

read AIN[1]: 1 value:0

read AIN[2]: 3 value:259

read AIN[3]: 3 value:297

……

read AIN[1]: 3 value:468

read AIN[2]: 3 value:299

read AIN[3]: 3 value:327

read AIN[0]: 3 value:629

read AIN[1]: 1 value:0

……

read AIN[0]: 3 value:629

read AIN[1]: 3 value:287

read AIN[2]: 3 value:273

read AIN[3]: 1 value:0

read AIN[0]: 3 value:629

read AIN[1]: 3 value:296

read AIN[2]: 3 value:271

read AIN[3]: 3 value:306

read AIN[0]: 3 value:629

read AIN[1]: 3 value:281

read AIN[2]: 1 value:0

read AIN[3]: 3 value:269

 

上面显示0的就是用导线将开发板上几个ADC通道分别接触板子上GND出现的。如果接VCC,值应该是1023。就是说,ADC的值为0~1023,ADC驱动将转换结果与0x3ff相与,这可以看datasheet。

 

至于测试代码中使用sscanf,是因为驱动中使用了sprintf。

驱动的debug信息如下(举例,与前面显示无直接关系):

# dmesg | tailAIN[3] = 0x012c, 1

AIN[0] = 0x0275, 1

AIN[1] = 0x0146, 1

AIN[2] = 0x0126, 1

AIN[3] = 0x0150, 1

AIN[0] = 0x0275, 1

AIN[1] = 0x012d, 1

AIN[2] = 0x0109, 1

AIN[3] = 0x0126, 1

adc closed

 

本人虽然学过一段时间的单片机,但是对于电机控制、PWM、ADC等等兴趣不大,了解也不多。其实我也不知这个多通道的ADC能做什么。

文中ioctl控制字参考《ARM徽处理器与应用开发》一书,电子工业出版社出版。

完整的驱动程序如下:

#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/ioctl.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>

#define DEBUG

#ifdef DEBUG /* define it in Makefile of somewhere */

/* KERN_INFO */

#define DPRINTK(fmt, …) printk(KERN_DEBUG fmt, ##__VA_ARGS__)

#else

#define DPRINTK(fmt, …)

#endif

#define DEVICE_NAME        “adc”

static void __iomem *base_addr;

typedef struct {

wait_queue_head_t wait;

int channel;

int prescale;

}ADC_DEV;

DECLARE_MUTEX(ADC_LOCK);

/* you may need to change is to the following */

//DEFINE_SEMAPHORE(ADC_LOCK);

static int OwnADC = 0;

static ADC_DEV adcdev;

static volatile int ev_adc = 0;

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)

#define START_ADC_AIN(ch, prescale)

do{

ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ;

ADCCON |= ADC_START;

}while(0)

/* ioctl */

#ifndef u16

#define u16        unsigned short

#endif

#define ADC_IOC_MAGIC        0xd2

#define ADC_SET_CHANNEL        _IOW(ADC_IOC_MAGIC, 0, u16)

#define ADC_SET_CLKDIV        _IOW(ADC_IOC_MAGIC, 1, u16)

#define ADC_MAX_IOC        2 /* we only have 2 ioctl commands */

#define MAX_ADC                4 /* we have 4 adc chnnels */

/* end of ioctl */

static irqreturn_t adcdone_int_handler(int irq, void *dev_id)

{

if (OwnADC) {

adc_data = ADCDAT0 & 0x3ff;

ev_adc = 1;

wake_up_interruptible(&adcdev.wait);

}

return IRQ_HANDLED;

}

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

{

char str[20];

int value;

size_t len;

if (down_trylock(&ADC_LOCK) == 0) {

OwnADC = 1;

START_ADC_AIN(adcdev.channel, adcdev.prescale);

wait_event_interruptible(adcdev.wait, ev_adc);

ev_adc = 0;

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

value = adc_data;

#if 0

sprintf(str,”%5d”, adc_data);

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

#endif

OwnADC = 0;

up(&ADC_LOCK);

} else {

value = -1;

//len = sprintf(str, “%dn”, value); // why ‘n’?

len = sprintf(str, “%d”, value);

if (count >= len) {

int r = copy_to_user(buffer, str, len);

return r ? r : len;

} else {

return -EINVAL;

}

}

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

{

init_waitqueue_head(&(adcdev.wait));

adcdev.channel=0;

adcdev.prescale=0xff;

DPRINTK( “adc openedn”);

return 0;

}

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

{

DPRINTK( “adc closedn”);

return 0;

}

static int s3c2410_adc_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)

{

if ((_IOC_TYPE(cmd) != ADC_IOC_MAGIC) || (_IOC_NR(cmd) > ADC_MAX_IOC))

return -EINVAL;

switch (cmd) {

/* set channel */

case ADC_SET_CHANNEL:

arg &=3; //??

if (arg > 3)

arg = 3;

adcdev.channel=arg;

break;

case ADC_SET_CLKDIV:

arg &= 0xff; // ??

if (arg > 0xff)

arg = 0xff;

adcdev.prescale=arg;

break;

default:

return -EINVAL;

break;

}

return 0;

}

static struct file_operations dev_fops = {

.owner   = THIS_MODULE,

.open    = s3c2410_adc_open,

.read    = s3c2410_adc_read,

.release = s3c2410_adc_release,

.ioctl   = s3c2410_adc_ioctl,

};

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 blockn”);

return -ENOMEM;

}

/* test: ioremap: c4876000(kernel space) ADCCON: 3fc4 */

DPRINTK(“ioremap: %x ADCCON: %xn”, base_addr, ADCCON);

adc_clock = clk_get(NULL, “adc”);

if (!adc_clock) {

printk(KERN_ERR “failed to get adc clock sourcen”);

return -ENOENT;

}

clk_enable(adc_clock);

/* normal ADC */

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”tinitializedn”);

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”);

本文固定链接: http://www.latelee.org/embedded-linux/mutil-channel-adc-of-s3c2410.html

如无特别说明,迟思堂工作室文章均为原创,转载请注明: s3c2410多通道adc驱动及测试程序 | 迟思堂工作室

目前暂无评论

发表评论

*

快捷键:Ctrl+Enter