网上流行很多基于S3C2410的ADC驱动及测试程序。本文所使用的是开发板光盘中自带的经过修改后的adc驱动。笔者在这个基础上再作一点修改。由于那个文件已经删除了版权信息(但还是能找到这些代码与网上流行的驱动的一些联系),这里也不知道如何添加了,可以肯定的是,它使用了GPL,这里公开源代码,也算是GPL了。
原来的代码默认使用ADC第0个通道,本文将添加ioctl接口,可以通过应用层的ioctl来选择多个通道。
与原来的代码相比,添加了如下几个方面:
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, "%d\n", 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: %d\n", 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 exit\n");
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] = '\0';
sscanf(buff, "%d", &val);
printf("read AIN[%d]: %d value:%d\n", i, len, val);
}
sleep(1); // 每隔1秒采1个通道
}
//sleep(1); // 每隔1秒采集4个通道
}
close(fd); /* just kidding */
return 0;
}
注:文中代码注释出现了中文,是为了方便文章的阅读(根据经验,有清楚注释的代码才算代码),在实际代码中是没有中文注释的。