[OK210開發板體驗]功能篇(7) Linux字符驅動之ADC模數轉換驅動
[OK210開發板體驗]入門篇(2):板載資源
[OK210開發板體驗]入門篇(3):開發環境(軟件安裝,開發環境,燒寫系統)
[OK210開發板體驗]入門篇(4):編程入門(NFS登錄,驅動入門)
[OK210開發板體驗]功能篇(1):Linux字符驅動之Led
[OK210開發板體驗]功能篇(2):Linux字符驅動之Key按鍵
[OK210開發板體驗]功能篇(3):Linux Input子系統之Key按鍵
[OK210開發板體驗]功能篇(4):Linux字符驅動之DS18B20
[OK210開發板體驗]功能篇(5):Linux字符驅動之PWM蜂鳴器
[OK210開發板體驗]功能篇(6):Linux字符驅動之紅外遙控
今天是功能篇的第七篇:Linux字符驅動之ADC模數轉換,本節主要分3部分:硬件分析,軟件基礎,驅動編程。
一、硬件分析
在【OK210試用體驗】的第二篇:板載資源中,簡單分析了ADC的功能和作用。其實對ADC的操作,可以看作是對一些模擬量的采集操作,如溫濕度,光度。
首先從OK210的底板原理圖中可知,通過一個可調電位器RP1,連接到S5PV210的模擬輸入0通道,通過對該可調電位器的調節可以改變模擬通道的輸入電壓值,電壓的調節范圍為0V-3V,可以利用這個可調電位器來熟悉S5PV210的ADC控制器的使用,如下圖所示
模擬輸入0通首家引腳為XADCAIN0,由S5PV210用戶手冊,可知,該引腳是一個模塊引腳,如下圖所示。
所以,我們要對ADC進行操作,就是通過對配置ADC的各寄存器進行實現。
二、軟件基礎
1、S5PV210的ADC的主要特征:
(1)分辨率(輸出離散值的個數)可以是10位或者12位(可以通過TSADCCON0/TSADCCON1的第16位RES進行設定,將RES設為0表示10位,設為1表示12位)。
(2)10通道的模擬輸入(AIN[9]---AIN[0])。
(3)輸入電壓為0--3.3V。
(4)最大轉換速率:1MSPS。
對于轉換速率的計算,S5PV210_UM手冊上是這樣介紹的:當PCLK=66MHz時,預分頻比P=65時,12位分辨率的轉換時間如下:
A/D轉換頻率=66MHz/(65+1)=1MHz
A/D轉換時間=1/(1MHz/5cycles)=1/200kHz=5us
PS
1)A/D的轉換頻率最大可達5MHz,所以A/D轉換時間最大可達1MSPS
2)對于官方手冊上,所說的A/D轉換時間的計算,可能我們第一次看的時候會不太理解,會什么要1MHz/5cycles?原因是這樣的,因為A/D轉換時間包括A/D建立時間,1位1位轉換時間,保存數據時間等等,加起來一共是5個時鐘周期,所以會是1MHz/5cycles。其實對于A/D轉換時間=1/(1MHz/5cycles) 可以換種寫法:A/D轉換時間=1/1MHz *5=5us 也是一樣的,所以當最大轉換頻率達到5MHz時,A/D轉換時間可達1MSPS(SPS為每秒的采樣率)。
(5)具有采樣保持功能。
(6)ADC有10通道的模擬輸入,但是只有AIN[0],AIN[1]沒有復用,而AIN[2]--AIN[9]是復用為觸摸屏的兩路控制信號(XM,XP)。
2、S5PV210的ADC的寄存器
操作AIN[0],在編寫ADC的驅動代碼的時候,一般情況下只需考慮三個寄存器,分別是
寄存器 | 映射地址 | 讀/寫 | 描述 | 復位值 |
TSADCCON0 | 0xE170_0000 | R/W | TS0/ADC控制寄存器 | 0x0000_3FC4 |
TSDLY0 | 0xE170_0008 | R/W | TS0/ADC延時寄存器 | 0x0000_00FF |
TSDATX0 | 0xE170_000C | R | TS0/ADC轉換數據X寄存器 | --- |
3、S5PV210的ADC操作流程
主要分三步:
(1)使用clk_get獲取adc時鐘,接著使用clk_enable使能adc時鐘;
(2)設置ADCCON的工作方式,預分頻比之類的;
(3)當開始讀取數據值時,開啟ADC轉換,判斷是否開始轉換,判斷是否轉換完成,最終讀取數據,返回給用戶空間。值得注意的是,S5PV210的控制寄存器,是沒有通道選擇的,因為在使用之前,直接用ioremap進行地址的映射時,就相當于選擇了不同的通道,所以無需通道選擇了。
三、驅動編程
有圖有真相,
1 驅動代碼
-
#include <linux/kernel.h>
-
#include <linux/module.h>
-
#include <linux/slab.h>
-
#include <linux/input.h>
-
#include <linux/init.h>
-
#include <linux/errno.h>
-
#include <linux/serio.h>
-
#include <linux/delay.h>
-
#include <linux/clk.h>
-
#include <linux/wait.h>
-
#include <linux/sched.h>
-
#include <linux/cdev.h>
-
#include <linux/miscdevice.h>
-
-
#include <asm/io.h>
-
#include <asm/irq.h>
-
#include <asm/uaccess.h>
-
-
#include <mach/map.h>
-
#include <mach/regs-clock.h>
-
#include <mach/regs-adc.h>
-
#include <mach/regs-gpio.h>
-
#include <plat/regs-timer.h>
-
-
#undef DEBUG
-
//#define DEBUG
-
#ifdef DEBUG
-
#define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);}
-
#else
-
#define DPRINTK(x...) (void)(0)
-
#endif
-
-
#define DEVICE_NAME "adc"
-
static void __iomem *base_addr;
-
-
typedef struct {
-
wait_queue_head_t wait;
-
int channel;
-
int prescale;
-
} ADC_DEV;
-
-
static int __ADC_locked = 0;
-
static ADC_DEV adcdev;
-
static volatile int ev_adc = 0;
-
static int adc_data;
-
static struct clk *adc_clock;
-
-
#define __ADCREG(name) (*(volatile unsigned long *)(base_addr + name))
-
#define ADCCON __ADCREG(S3C_ADCCON) // ADC control
-
#define ADCTSC __ADCREG(S3C_ADCTSC) // ADC touch screen control
-
#define ADCDLY __ADCREG(S3C_ADCDLY) // ADC start or Interval Delay
-
#define ADCDAT0 __ADCREG(S3C_ADCDAT0) // ADC conversion data 0
-
#define ADCDAT1 __ADCREG(S3C_ADCDAT1) // ADC conversion data 1
-
#define ADCUPDN __ADCREG(S3C_ADCUPDN) // Stylus Up/Down interrupt status
-
-
#define PRESCALE_DIS (0 << 14)
-
#define PRESCALE_EN (1 << 14)
-
#define PRSCVL(x) ((x) << 6)
-
#define ADC_INPUT(x) ((x) << 3)
-
#define ADC_START (1 << 0)
-
#define ADC_ENDCVT (1 << 15)
-
-
#define START_ADC_AIN(ch, prescale) \
-
do { \
-
ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
-
ADCCON |= ADC_START; \
-
} while (0)
-
-
-
static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
-
{
-
#if 1
-
if (__ADC_locked) {
-
adc_data = ADCDAT0 & 0x3ff;
-
-
ev_adc = 1;
-
wake_up_interruptible(&adcdev.wait);
-
-
/* clear interrupt */
-
__raw_writel(0x0, base_addr + S3C_ADCCLRINT);
-
}
-
#endif
-
-
return IRQ_HANDLED;
-
}
-
-
static ssize_t ok210_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
-
{
-
char str[20];
-
int value;
-
size_t len;
-
-
__ADC_locked = 1;
-
START_ADC_AIN(adcdev.channel, adcdev.prescale);
-
wait_event_interruptible(adcdev.wait, ev_adc);
-
ev_adc = 0;
-
DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ADCCON & 0x80 ? 1:0);
-
value = adc_data;
-
__ADC_locked = 0;
-
len = sprintf(str, "%d\n", value);
-
if (count >= len) {
-
int r = copy_to_user(buffer, str, len);
-
return r ? r : len;
-
} else {
-
return -EINVAL;
-
}
-
}
-
-
static int ok210_adc_open(struct inode *inode, struct file *filp)
-
{
-
init_waitqueue_head(&(adcdev.wait));
-
-
adcdev.channel=0;
-
adcdev.prescale=0xff;
-
-
DPRINTK("adc opened\n");
-
return 0;
-
}
-
-
static int ok210_adc_release(struct inode *inode, struct file *filp)
-
{
-
DPRINTK("adc closed\n");
-
return 0;
-
}
-
-
-
static struct file_operations dev_fops = {
-
owner: THIS_MODULE,
-
open: ok210_adc_open,
-
read: ok210_adc_read,
-
release: ok210_adc_release,
-
};
-
-
static struct miscdevice misc = {
-
.minor = MISC_DYNAMIC_MINOR,
-
.name = "adc", //DEVICE_NAME,
-
.fops = &dev_fops,
-
};
-
-
static int __init dev_init(void)
-
{
-
int ret;
-
-
base_addr = ioremap(SAMSUNG_PA_ADC, 0x20);
-
if (base_addr == NULL) {
-
printk(KERN_ERR "Failed to remap register block\n");
-
return -ENOMEM;
-
}
-
-
adc_clock = clk_get(NULL, "adc");
-
if (!adc_clock) {
-
printk(KERN_ERR "failed to get adc clock source\n");
-
return -ENOENT;
-
}
-
clk_enable(adc_clock);
-
-
/* normal ADC */
-
ADCTSC = 0;
-
-
#if 1
-
ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
-
if (ret) {
-
printk("request IRQ %d failed for adc, %d\n", IRQ_ADC, ret);
-
iounmap(base_addr);
-
return ret;
-
}
-
#endif
-
-
ret = misc_register(&misc);
-
-
printk (DEVICE_NAME"\tinitialized\n");
-
return ret;
-
}
-
-
static void __exit dev_exit(void)
-
{
-
free_irq(IRQ_ADC, &adcdev);
-
iounmap(base_addr);
-
-
if (adc_clock) {
-
clk_disable(adc_clock);
-
clk_put(adc_clock);
-
adc_clock = NULL;
-
}
-
-
misc_deregister(&misc);
-
}
-
-
module_init(dev_init);
-
module_exit(dev_exit);
-
-
MODULE_LICENSE("GPL");
-
MODULE_AUTHOR("gjianw217@163.com");
- MODULE_DESCRIPTION("ADC driver");
-
#include <stdio.h>
-
#include <unistd.h>
-
#include <stdlib.h>
-
#include <sys/types.h>
-
#include <sys/stat.h>
-
#include <sys/ioctl.h>
-
#include <fcntl.h>
-
#include <linux/fs.h>
-
#include <errno.h>
-
#include <string.h>
-
-
int main(void)
-
{
-
fprintf(stderr, "press Ctrl-C to stop\n");
-
int fd = open("/dev/adc", "r");
-
if (fd < 0) {
-
perror("open ADC device:");
-
return 1;
-
}
-
ioctl(fd,'s',0);
-
int ADCValue=0;
-
double voltage=0;
-
for(;;) {
-
ADCValue=0;
-
voltage=0;
-
read(fd, &ADCValue, sizeof(int));
-
if(ADCValue==-1) continue;
-
//channel 0 12bit ADC max voltage = 3.3v /10K+1k *10K =3v
-
voltage=(float)3.3*ADCValue/4096;
-
printf("adc = %d voltage=%f V \n",ADCValue,voltage);
-
usleep(500* 1000);
-
}
-
-
close(fd);
- }
-
#adc Makefile
-
ARCH=arm
-
CROSS_COMPILE=/home/ok210/arm-2009q3/bin/arm-none-linux-gnueabi-
-
APP_COMPILE=/home/ok210/arm-2009q3/bin/arm-none-linux-gnueabi-
-
#obj-m := app-drv.o
-
obj-m := pwm-drv.o
-
#KDIR := /path/to/kernel/linux/
-
KDIR := /home/ok210/android-kernel-samsung-dev/
-
PWD := $(shell pwd)
-
default:
-
make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
-
app:pwm-app.c
-
$(APP_COMPILE)gcc -o app pwm-app.c
-
clean:
- $(MAKE) -C $(KDIR) M=$(PWD) clean