硬件描述:
S3c2440有一个10-bit的CMOS ADC 模数转换器,支持8个模拟通道输入,10位的分辨率,最高速度可达500KSPS(500 千次/每秒)。
fl2440上的原理图:
从图中可知:模拟ADC,包含了2部分功能,一部分是触屏功能,另一部分就是普通ADC功能,分别可以产生INT_TC和INT_ADC 两个中断。8个AIN模拟输入(A[3:0],YM,YP,XM,XP)通过一个8路模拟开关MUX进行通道片选。 ADC模块共有20个寄存器。对于普通ADC转换,使用ADCCON 和 ADCDAT0即可完成控制。ADCCON用于控制设置,ADCDAT0保存了转换结果。
在fl2440上,开发板通过一个10K的电位器(可变电阻)来产生电压模拟信号,然后通过第一个通道(即:AIN0)将模拟信号输入ADC。
下面是ADC驱动程序的实现:
/*********************************************************************************
* Copyright: (C) 2017 TangBin<tangbinmvp@gmail.com>
* All rights reserved.
*
* Filename: adc.c
* Description: This file
*
* Version: 1.0.0(04/07/2017)
* Author: TangBin <tangbinmvp@gmail.com>
* ChangeLog: 1, Release initial version on "04/07/2017 12:48:02 PM"
*
********************************************************************************/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <plat/regs-adc.h>
#include <linux/errno.h>
#include <asm/uaccess.h> //copy_to_user、copy_from_user
#include <linux/input.h>
#include <linux/serio.h>
#include <linux/clk.h>
#include <linux/sched.h>
#include <linux/cdev.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <linux/ioport.h>
#define DEVICE_NAME "S3C_ADC"
#define S3C_ADC_MAJOR 0
#define ENABLE 1
#define DISABLE 0
#define CON_BASE 0x58000000
#define DATA_BASE 0x5800000c
#define CON_LEN 4
#define DATA_LEN 4
#define s3c_adc_read(reg) __raw_readl((reg))
#define s3c_adc_write(val,reg) __raw_writel((val),(reg))
static void __iomem *adc_con;
static void __iomem *adc_dat0;/* 保存经过虚拟映射后的内存地址 */
static struct clk *adc_clk; /* 保存从平台时钟队列中获取ADC的时钟 */
DEFINE_MUTEX(ADC_CLK);/* 定义互斥锁 */
int dev_cnt = 1;
int dev_major = S3C_ADC_MAJOR;
int dev_minor = 0;
int debug = DISABLE;
static struct cdev *adc_cdev;
static ssize_t adc_read(struct file *file, char *buffer, size_t count, loff_t *ppos)
{
unsigned int tmp;
unsigned long adc_data;
adc_con = ioremap(CON_BASE, CON_LEN);/* 将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址 */
adc_dat0 = ioremap(DATA_BASE, DATA_LEN);/* 将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址 */
tmp = s3c_adc_read(adc_con);
tmp = (1 << 14) | (255 << 6) | (0 << 3);// 0 1 00000011 000 0 0 0
s3c_adc_write(tmp, adc_con); //AD预分频器使能、模拟输入通道设为AIN0
tmp = s3c_adc_read(adc_con);
tmp = tmp | (1 << 0);// 0 1 00000011 000 0 0 1
s3c_adc_write(tmp, adc_con);//启动AD转换
while(s3c_adc_read(adc_con) &0x1);//启动转换后,等待启动位清零
while(!(s3c_adc_read(adc_con) &0x8000));//等待是否转换完毕
mutex_lock(&ADC_CLK);//获取信号量,加锁
adc_data = s3c_adc_read(adc_dat0) &0x3ff;//AD转换后的数据是保存在ADCDAT0的第0-9位,所以与上0x3ff(即:1111111111)后就得到第0-9位的数据,多余的位就都为0
//读取AD转换后的值保存到全局变量adc_data中
copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));//将读取到的AD转换后的值发往到上层应用程序
mutex_unlock(&ADC_CLK); //解锁
return sizeof(adc_data);
}
static int adc_open(struct inode *inode, struct file *file)
{
int minor = iminor(inode);
file->private_data = (void *)minor;
printk(KERN_DEBUG "dev/s3c_adc %d opened.\n", minor);
return 0;
}
static int adc_release(struct inode *inode, struct file *file)
{
return 0;
}
static struct file_operations adc_fops = {
.owner = THIS_MODULE,
.read = adc_read,
.open = adc_open,
.release = adc_release,
};
static int __init adc_init(void)
{
int result;
dev_t devno;
/* 分配主次设备号 */
if(0 != dev_major)
{
devno = MKDEV(dev_major, 0);
result = register_chrdev_region(devno, dev_cnt, DEVICE_NAME);
}
else
{
result = alloc_chrdev_region(&devno, dev_minor, dev_cnt, DEVICE_NAME);
dev_major = MAJOR(devno);
}
if(result < 0)
{
printk(KERN_ERR "%s driver can't use major %d\n", DEVICE_NAME, dev_major);
return -ENODEV;
}
printk(KERN_DEBUG "%s driver major %d\n", DEVICE_NAME, dev_major);
/* 从平台时钟队列中获取ADC的时钟,因为ADC的转换频率跟时钟有关 */
adc_clk = clk_get(NULL, "s3c_adc");
if(!adc_clk)
{
printk(KERN_ERR "failed to find adc clock source\n");
return -ENOENT;
}
clk_enable(adc_clk);//时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中
if(NULL == (adc_cdev=cdev_alloc()))
{
printk(KERN_ERR "%s driver can't alloc for the cdev.\n", DEVICE_NAME);
unregister_chrdev_region(devno, dev_cnt);
return -ENOMEM;
}
adc_cdev->owner = THIS_MODULE;
cdev_init(adc_cdev, &adc_fops);
result = cdev_add(adc_cdev, devno, dev_cnt);
if(0 != result)
{
printk(KERN_INFO "%s driver can't reigster cdev: result=%d\n", DEVICE_NAME, result);
goto ERROR;
}
printk(KERN_ERR "%s installed successfully.\n",DEVICE_NAME);
return 0;
ERROR:
printk(KERN_ERR "%s driver installed failure.\n", DEVICE_NAME);
cdev_del(adc_cdev);
unregister_chrdev_region(devno, dev_cnt);
return result;
}
void __exit adc_exit(void)
{
dev_t devno = MKDEV(dev_major, dev_minor);
cdev_del(adc_cdev);
unregister_chrdev_region(devno, dev_cnt);
iounmap(adc_con);
iounmap(adc_dat0);
if(adc_clk)
{
clk_disable(adc_clk);
clk_put(adc_clk);
adc_clk = NULL;
}
printk(KERN_ERR " %s driver removed!\n", DEVICE_NAME);
return ;
}
module_init(adc_init);
module_exit(adc_exit);
module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tang bin");
MODULE_DESCRIPTION("ADC Driver");
在本程序中,选择使用中断的方式来进行从ADC数据寄存器中读取AD转换后的值是比较好的。由于本人目前对中断的使用还较生疏,因此在本例中未使用。大致还是按照一个普通字符设备来编写,同样参考了之前学习的LED驱动里的一些方法。
另外,ADC可以注册为混杂设备,miscdevice.h中有相关的定义和函数。关于混杂设备的相关知识可以自行在网上搜索。
在开发板上通过调节电位器,可以读出并显示数值。测试程序:
/*********************************************************************************
* Copyright: (C) 2017 TangBin<tangbinmvp@gmail.com>
* All rights reserved.
*
* Filename: adc_test.c
* Description: This file
*
* Version: 1.0.0(04/07/2017)
* Author: TangBin <tangbinmvp@gmail.com>
* ChangeLog: 1, Release initial version on "04/07/2017 03:01:33 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main (int argc, char **argv)
{
int fd;
fd = open("/dev/S3C_ADC",0);
if(fd < 0)
{
printf("open ADC device failed.\n");
exit(1);
}
for(;;)
{
int ret;
int data;
ret = read(fd, &data, sizeof(data));
if(sizeof(data) != ret)
{
if(errno != EAGAIN)
{
printf("Read ADC Device Faild!\n");
}
continue;
}
else
{
printf("Read ADC value is: %d\n", data);
}
sleep(1);
}
close(fd);
return 0;
} /* ----- End of main() ----- */