s3c2410多通道adc驱动及测试程序 | 迟思堂工作室
A-A+

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

2014-08-30 15:52 嵌入式Linux 暂无评论 阅读 36,140 次

网上流行很多基于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");


如果本文对阁下有帮助,不妨赞助笔者以输出更多好文章,谢谢!
donate



标签:

给我留言