【实验内容】
编写一个字符设备驱动程序,驱动FS_S5PC100开发板上的可调电阻,采集AD转换过来的电压值
可调电阻位于开发板第二个串口(COM2)的正下方
【实验目的】
了解AD的工作原理,掌握驱动里操作AD控制器的方法,巩固字符设备驱动程序编写方法
【实验平台】
主机:Ubuntu 10.10
目标板:FS_S5PC100
目标内核版本:2.6.29
交叉编译器版本:arm-unknown-linux-gnueabi-gcc-4.2.2
【实验步骤】
在主机ubuntu环境下:
1、将文件夹s5pc100_adc复制到linux环境中,如:/home/linux/test
$ cp s5pc100_adc /home/linux/test –a
2、$ cd /home/linux/test/s5pc100_adc
3、$ make //编译设备驱动,生成adc.ko模块
4、$ arm-linux-gcc adc_test.c -o adc_test //编译应用程序
5、$ sudo cp adc.ko adc_test /source/rootfs
在开发板的串口终端控制台下:
6、通过insmod命令将模块加入内核
# insmod adc.ko //由于驱动采用的是动态获取主设备号的方式,所以根据打印信息建立设备结点
major number of adc is 252
7、 创建设备结点
# mknod /dev/adc c 252 0
9、运行应用程序
# ./adc_test
调整可调电阻上的旋钮,发现输出值也在发生变化
val = 630
val = 623
val = 60b
val = 5fb
val = 913
val = 925
val = 916
val = 911
10、修改应用程序,将直接从驱动获得的数字量转换为实际的电压值(可调电阻可以承受的电压范围为: 0 —— 3.3V)
附:
1、 驱动程序代码:
#include <linux/input.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <asm/uaccess.h> // copy_to_user, copy_from_user
#include <asm/irq.h>
#include <asm/io.h>
#include <plat/irqs.h>
#include <plat/regs-adc.h>
MODULE_AUTHOR("farsight");
MODULE_LICENSE("Dual BSD/GPL");
#define ADC_BASE_ADDR 0xF3000000
#define MAP_SIZE 36
static int adc_major = 0;
static volatile void *adc_base;
#if 0
static irqreturn_t ad_interrupt(int irq, void *dummy)
{
//input_report_key(ad_dev, BTN_0, inb(BUTTON_PORT) & 1);
//input_sync(ad_dev);
return IRQ_HANDLED;
}
#endif
static int adc_open(struct inode *inode, struct file *filp)
{
//map physical address to virtual address
adc_base = ioremap(ADC_BASE_ADDR, MAP_SIZE);
//ADC output resolution seletion : 12-bit A/D conversion
writel(readl(adc_base+S3C_ADCCON) | S3C_ADCCON_RESSEL_12BIT, adc_base+S3C_ADCCON);
//enable A/D converter prescale and set prescaler as 0xFF
writel(readl(adc_base+S3C_ADCCON) | S3C2410_ADCCON_PRSCEN, adc_base+S3C_ADCCON);
//set prescaler as 0xFF
writel((readl(adc_base+S3C_ADCCON) & ~S3C2410_ADCCON_PRSCVLMASK) | S3C2410_ADCCON_PRSCVL(0xff), adc_base+S3C_ADCCON);
//select normal operation mode
writel(readl(adc_base+S3C_ADCCON) & ~S3C2410_ADCCON_STDBM, adc_base+S3C_ADCCON);
//select channel as AIN0
writel(readl(adc_base+S3C_ADCMUX) & ~S3C_ADCMUX_MASK, adc_base+S3C_ADCMUX);
#if 0
if (request_irq(IRQ_ADC, ad_interrupt, 0, "adc", NULL)) {
printk(KERN_ERR "%s: Can't allocate irq %d\n", __FUNCTION__, IRQ_ADC);
return -EBUSY;
}
#endif
//enable A/D conversion
//writel(readl(adc_base+S3C_ADCCON) | S3C2410_ADCCON_ENABLE_START, adc_base+S3C_ADCCON);
writel(readl(adc_base+S3C_ADCCON) | S3C_ADCCON_READ_START, adc_base+S3C_ADCCON);
return 0;
}
static int adc_close( struct inode *inode, struct file *filp )
{
//free_irq(IRQ_ADC, ad_interrupt);
return 0;
}
static ssize_t adc_read( struct file *filp, char __user *buffer, size_t count, loff_t *offset )
{
int val;
//read A/D conversion data from ADCDAT0
val = readl(adc_base+S3C_ADCDAT0) & 0xfff;
printk(KERN_INFO "val = %x\n", val);
if( copy_to_user(buffer, &val, sizeof(val)) != 0 ) {
printk( KERN_ERR "copy to user failed\n" );
return -1;
}
return sizeof(val);
}
static struct file_operations adc_fops = {
.owner = THIS_MODULE,
.open = adc_open,
.release = adc_close,
.read = adc_read,
};
static int __init adc_init( void )
{
int res;
res = register_chrdev( adc_major, "adc", &adc_fops );
if ( res < 0 ) {
printk( KERN_ERR "%s failed.\n", __FUNCTION__ );
return res;
}
else {
if ( adc_major )
printk ( KERN_INFO "major number of adc is %d\n", adc_major );
else {
adc_major = res;
printk ( KERN_INFO "major number of adc is %d\n", adc_major );
}
}
return 0;
}
static void __exit adc_cleanup( void )
{
unregister_chrdev( adc_major, "adc" );
}
module_init(adc_init);
module_exit(adc_cleanup);
2、 测试程序代码
#include <stdio.h>
#include <fcntl.h>
int
main( int argc, char *argv[] )
{
int fd = open( "/dev/adc", O_RDWR );
if( fd < 0 ) {
perror( "open /dev/adc error" );
return -1;
}
int nread = 0;
int adc_val = 0;
while( 1 ) {
nread = read( fd, (void *)&adc_val, sizeof(adc_val) );
if( nread < 0 ) {
perror( "read adc" );
return -1;
}
sleep( 1 );
}
close( fd );
return 0;
}
3、 AD转换接口原理图: