網上流行很多基於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;
}
注:文中代碼注釋出現了中文,是為了方便文章的閱讀(根據經驗,有清楚注釋的代碼才算代碼),在實際代碼中是沒有中文注釋的。