转自
http://www.rosoo.net/a/linux/201006/9648.html
TAG: 驱动开发 S3C2440 ADC驱动
嵌入式Linux之我行,主要讲述和总结了本人在学习
嵌入式linux中的每个步骤。一为总结经验,二希望能给想入门
嵌入式Linux的朋友提
供方便。如有错误之处,谢请指正。
一、开发环境
- 主 机:VMWare--Fedora 9
- 开发板:Mini2440--64MB Nand, Kernel:2.6.30.4
- 编译器:arm-linux-gcc-4.3.2
二、硬件原理分析
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 转换后的值)。
1、建立驱动程序文件my2440_adc.c,实现驱动的初始化和退出,代码如下:
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
-
-
- static void __iomem *adc_base;
-
-
- static struct clk *adc_clk;
-
-
- DECLARE_MUTEX(ADC_LOCK);
-
- static int __init adc_init(void)
- {
- int ret;
-
-
-
- adc_clk = clk_get(NULL, "adc");
- if (!adc_clk)
- {
-
- printk(KERN_ERR "failed to find adc clock source/n");
- return -ENOENT;
- }
-
-
- clk_enable(adc_clk);
-
-
-
-
- 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, 1);
- iounmap(adc_base);
-
- if (adc_clk)
- {
- clk_disable(adc_clk);
- clk_put(adc_clk);
- adc_clk = NULL;
- }
-
- misc_deregister(&adc_miscdev);
- }
-
-
-
- EXPORT_SYMBOL(ADC_LOCK);
-
- module_init(adc_init);
- module_exit(adc_exit);
-
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("Huang Gang");
- MODULE_DESCRIPTION("My2440 ADC Driver");
2、adc_miscdev结构体定义及内部各接口函数的实现,代码如下:
- #include
-
-
- #define DEVICE_NAME "my2440_adc"
-
-
- static DECLARE_WAIT_QUEUE_HEAD(adc_waitq);
-
-
- static volatile int ev_adc = 0;
-
-
- static int adc_data;
-
-
- static struct miscdevice adc_miscdev =
- {
- .minor = MISC_DYNAMIC_MINOR,
- .name = DEVICE_NAME,
- .fops = &adc_fops,
- };
-
-
- static struct file_operations adc_fops =
- {
- .owner = THIS_MODULE,
- .open = adc_open,
- .read = adc_read,
- .release = adc_release,
- };
-
-
- static int adc_open(struct inode *inode, struct file *file)
- {
- int ret;
-
-
-
-
-
- ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, 1);
- if (ret)
- {
-
- printk(KERN_ERR "IRQ%d error %d/n", IRQ_ADC, ret);
- return -EINVAL;
- }
-
- return 0;
- }
-
-
- static irqreturn_t adc_irq(int irq, void *dev_id)
- {
-
-
- if(!ev_adc)
- {
-
-
-
- adc_data = readl(adc_base + S3C2410_ADCDAT0) & 0x3ff;
-
-
- ev_adc = 1;
- wake_up_interruptible(&adc_waitq);
- }
-
- return IRQ_HANDLED;
- }
-
-
- 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)
- {
- if(filp->f_flags & O_NONBLOCK)
- {
-
- return -EAGAIN;
- }
- else
- {
-
- start_adc();
-
-
- wait_event_interruptible(adc_waitq, ev_adc);
- }
- }
-
-
- ev_adc = 0;
-
-
- copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));
-
-
- up(&ADC_LOCK);
-
- return sizeof(adc_data);
- }
-
-
- static void start_adc(void)
- {
- unsigned int tmp;
-
- tmp = (1 << 14) | (255 << 6) | (0 << 3);
- writel(tmp, adc_base + S3C2410_ADCCON);
-
- tmp = readl(adc_base + S3C2410_ADCCON);
- tmp = tmp | (1 << 0);
- writel(tmp, adc_base + S3C2410_ADCCON);
- }
-
-
- static int adc_release(struct inode *inode, struct file *filp)
- {
- return 0;
- }
注意:在上面实现的每步中,为了让代码逻辑更加有条理和容易理解,就没有考虑代码的顺序,比如函数要先定义后调用。如果要编译此代码,请严格按照C 语言的规范来调整代码的顺序。
3、编写用户应用程序测试my2440_adc驱动。建立应用程序adc_test.c,代码如下:
- #include
- #include
- #include
-
- int main(int argc, char **argv)
- {
- int fd;
-
-
- fd = open("/dev/my2440_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);
- }
- }
- close(fd);
- return 0;
- }
4、将驱动程序下载挂载到内核,下载应用程序到开发板上后,运行应用程序,扭动mini2440开发板上的定位器,可以观察到ADC转换值的变化, 证明驱动程序工作正常。效果图如下: