从0学ARM,基于Cortex-A9 ADC裸机驱动详解

系统 Linux
在嵌入式开发中,ADC应用比较频繁,本文主要讲解ADC的基本原理以及如何编写基于ARM的裸机程序和基于Linux的驱动程序。

 前言

在嵌入式开发中,ADC应用比较频繁,本文主要讲解ADC的基本原理以及如何编写基于ARM的裸机程序和基于Linux的驱动程序。

ARM架构:Cortex-A9 Linux内核:3.14

在讲述ADC之前,我们需要先了解什么是模拟信号和数字信号。

模拟信号

主要是与离散的数字信号相对的连续的信号。模拟信号分布于自然界的各个角落,如每天温度的变化,而数字信号是人为的抽象出来的在时间上不连续的信号。电学上的模拟信号是主要是指幅度和相位都连续的电信号,此信号可以被模拟电路进行各种运算,如放大,相加,相乘等。

模拟信号是指用连续变化的物理量表示的信息,其信号的幅度,或频率,或相位随时间作连续变化,如目前广播的声音信号,或图像信号等。

如下图所示从上到下一次是正弦波、 调幅波、 阻尼震荡波、 指数衰减波 。


数字信号

数字信号指幅度的取值是离散的,幅值表示被限制在有限个数值之内。二进制码就是一种数字信号。二进制码受噪声的影响小,易于有数字电路进行处理,所以得到了广泛的应用。

数字信号:高清数字电视,MP3,JPG,PNG文件等等。


优点:

1. 抗干扰能力强、无噪声积累

在模拟通信中,为了提高信噪比,需要在信号传输过程中及时对衰减的传输信号进行放大,信号在传输过程中不可避免地叠加上的噪声也被同时放大。

随着传输距离的增加,噪声累积越来越多,以致使传输质量严重恶化。

对于数字通信,由于数字信号的幅值为有限个离散值(通常取两个幅值),在传输过程中虽然也受到噪声的干扰,但当信噪比恶化到一定程度时,

即在适当的距离采用判决再生的方法,再生成没有噪声干扰的和原发送端一样的数字信号,所以可实现长距离高质量的传输。

2. 便于加密处理

信息传输的安全性和保密性越来越重要,数字通信的加密处理的比模拟通信容易得多,以话音信号为例,经过数字变换后的信号可用简单的数字逻辑运算进行加密、解密处理。

3. 便于存储、处理和交换

数字通信的信号形式和计算机所用信号一致,都是二进制代码,因此便于与计算机联网,也便于用计算机对数字信号进行存储、处理和交换,

可使通信网的管理、维护实现自动化、智能化。

4. 设备便于集成化、微型

数字通信采用时分多路复用,不需要体积较大的滤波器。设备中大部分电路是数字电路,可用大规模和超大规模集成电路实现,因此体积小、功耗低。

5. 便于构成综合数字网和综合业务数字网

采用数字传输方式,可以通过程控数字交换设备进行数字交换,以实现传输和交换的综合。

另外,电话业务和各种非话业务都可以实现数字化,构成综合业务数字网。

6. 占用信道频带较宽

一路模拟电话的频带为4kHz带宽,一路数字电话约占64kHz,这是模拟通信目前仍有生命力的主要原因。随着宽频带信道(光缆、数字微波)的大量利用(一对光缆可开通几千路电话)以及数字信号处理技术的发展(可将一路数字电话的数码率由64kb/s压缩到32kb/s甚至更低的数码率),数字电话的带宽问题已不是主要问题了。

常用的数字信号编码有不归零(NRZ)编码、 曼彻斯特(Manchester)编码和差分曼彻斯特(Differential Manchester)编码。


数字信号与模拟信号的转化

模拟信号和数字信号之间可以相互转换:模拟信号一般通过PCM脉码调制(Pulse Code Modulation)方法量化为数字信号,

即让模拟信号的不同幅度分别对应不同的二进制值,例如采用8位编码可将模拟信号量化为2^8=256个量级,实用中常采取24位或30位编码;

数字信号一般通过对载波进行移相(Phase Shift)的方法转换为模拟信号。计算机、计算机局域网与城域网中均使用二进制数字信号,

目前在计算机广域网中实际传送的则既有二进制数字信号,也有由数字信号转换而得的模拟信号。但是更具应用发展前景的是数字信号。

PCM脉冲编码调制

脉冲编码调制就是把一个时间连续,取值连续的模拟信号变换成时间离散,取值离散的数字信号后在信道中传输。

脉冲编码调制就是对模拟信号先抽样,再对样值幅度量化, 编码的过程。


抽样:

就是对模拟信号进行周期性扫描,把时间上连续的信号变成时间上离散的信号。

该模拟信号经过抽样后还应当包含原信号中所有信息,也就是说能无失真的恢复原模拟信号。

量化:

就是把经过抽样得到的瞬时值将其幅度离散,即用一组规定的电平,把瞬时抽样值用最接近的电平值来表示,通常是用二进制表示。

编码:

就是用一组二进制码组来表示每一个有固定电平的量化值。然而,实际上量化是在编码过程中同时完成的,故编码过程也称为模/数变换,可记作A/D。

ADC

ADC,Analog-to-Digital Converter的缩写,指模/数转换器或者模数转换器。是指将连续变化的模拟信号转换为离散的数字信号的器件。真实世界的模拟信号,例如温度、压力、声音或者图像等,需要转换成更容易储存、处理和发射的数字形式。模/数转换器可以实现这个功能,在各种不同的产品中都可以找到它的身影。

ADC最早用于对无线信号向数字信号转换。如电视信号,长短播电台发接收等。

与之相对应的DAC,Digital-to-Analog Converter,它是ADC模数转换的逆向过程。

现在市场上的电子产品都集成了传感器,传感器要采集数据,他的内部结构里就一定要用到ADC,常见的传感器如下:

温湿度:温度传感器,DHT11声音:音频芯片进行录音,WM8906图像:索尼IMX386/IMX283传感器

Exynos4412 A/D转换器

三星的Exynos4412模块结构图如下所示:


Adc控制器集成在exynos4412 soc中,控制器内部有一根中断线连接到中断控制器combiner,然后路由到GIC(Generic Interrupt Controller),滑动变阻器连接到adc控制器的通道3。

ADC控制器

参考《Exynos 4412 SCP》 的datasheet。ADC控制器是10位或12位CMOS再循环式模拟数字转换器,它具有10个通道输入,并可将模拟量转换至10位或12位二进制数。5Mhz A/D 转换时钟,最大1Msps的转换速度。A/D转换具备片上采样保持功能,同时也支持待机工作模式。

ADC接口包括如下特性。

  • 10bit/12bit输出位可选。
  • 微分误差 1.0LSB。
  • 积分误差 2.0LSB。
  • 最大转换速率5Msps.
  • 功耗少,电压输入1.8V。
  • 电压输入范围 0~1.8V。
  • 支持偏上样本保持功能。
  • 通用转换模式。

模块图

4412 A/D转换器的控制器接口框图如下:


原理我们并不需要关注,知道即可。

通道选择


由上图可知,A/D控制器一共有4个通道,通用寄存器地址为0x126c0000。

A/D控制器寄存器

对ADC控制器的操作主要是通过配置寄存器来实现的,查看datasheet,必须掌握寄存器的使用。以下是A/D控制器寄存器汇总。


1、A/D控制寄存器ADCCON


  1. RES     : 选择A/D转换精度,0:划分成1024份  1:划分成4096份 
  2.   ECFLG   :转换是否结束  0:转换中  1:转换完毕;对于轮询模式需要根据该位判断数据是否转换完毕。 
  3.   PRSCEN:A/D转换预分频是否使能 
  4.   PRSCVL:预分频的值,转换公式见下面 
  5.   STANDBY:待机模式  0:正常工作模式 1:待机模式。处于待机模式时要将PRSCEN设置为0 
  6.   READ_START: A/D转换由读操作触发,设置为1后,每次读取A/D值的操作都会触发一次A/D转换。 
  7.   ENABLE_START: 单次开启A/D转换,转换完毕后该位自动清零,当READ_START设置为1的时候,该位无效。 

通常设置值为(1 << 16 | 1 << 14 | 99 <<6 | 1 << 1)。

2、A/D转换数据寄存器ADCDAT0


注意该寄存器的值只有低12位有效。

3、A/D清中断寄存器CLRINTADC


黄色部分可知,中断例程负责清中断,中断结束后写入任意值就可以清中断。

4、A/D通道选择寄存器ADCMUX


每次操作都要先设置通道,因为 4个通道是共用同一套寄存器,如果有其他任务也在使用A/D,就会产生混乱。在此我们选择通道3,置3即可。

5、ADC中断ID

参见9.2.2GIC Interrupt Table


由此可知,ADC中断号对应的SPI值是10,inturrupt ID 为42。对于终端查询方式和编写终端的驱动需要知道SPI id和inturrupt ID,后面讲解基于Linux驱动还会再分析设备树节点如何填写。

6、Combiner中断控制器


combiner的配置寄存器:IMSRn、IECRn、ISERn、ISTRn,类似于GPIO 对中断源分组。只有中断模式才需要考虑combiner中断控制器的操作。

7、Combiner分组

参考章节:10.2.1Interrupt Combiner Table 10-1Interrupt Groups of Interrupt Combiner


可见ADC在INTG10,即第10组。

8、Combiner IESR2

参考章节:10.4.2.9IESR2


如果要用中断模式设置为1即可。

9、Combiner IECR2

参考章节:10.4.2.10IECR2


此处用于关闭中断,采用默认值即可,注意,如果设置了1,那么中断功能就关闭了。

10、A/D转换的转换时间计算

例如:PCLK为100MHz,PRESCALER = 65 ;所有10位转换时间为

100MHz/(99+1) = 1MHz

转化时间为1/(1MHz/5 cycles) = 5us。

完成一次A/D转换需要5个时钟周期。A/D转换器的最大工作时钟为5MHz,所以最大采样率可以达到1Mit/s.

电路连接图


由该电路图可知,外设是一个滑动变阻器,根据接触点的不同,会导致输入电压的模拟值不同。连接的A/D控制器通道为3。该电路利用一个电位计输出电压到4412的AIN3管脚。输入的电压范围为0~1.8V。

ADC裸机开发程序实例

ADC数据的读取通常由2种方法:中断模式、轮训模式。

轮训模式

轮询模式读取数据步骤如下:

1.要读取数据首先向ADC寄存器ADCCON的bit:1写1,发送转换命令,采用读-启动模式来开启转换。

2.当ADC控制器转换完毕会将ADCCON的bit:15设置为1,

3.轮询检测ADCCON的bit:15是否设置为1,如果设置为1,就读走数据,否则继续等待。

这种方式比较占用CPU资源。

注:这里使用读-启动模式

  1. /***********************ADC ******************/ 
  2. #define   ADC_CFG  __REG(0x10010118) 
  3. #define  ADCCON  __REG(0x126C0000) 
  4. #define  ADCDLY  __REG(0x126C0008) 
  5. #define  ADCDAT  __REG(0x126C000C) 
  6. #define  CLRINTADC __REG(0x126C0018) 
  7. #define  ADCMUX  __REG(0x126C001C) 
  8.  
  9. #include "exynos_4412.h" 
  10. #include "pwm.h" 
  11. #include "uart.h" 
  12.  
  13. unsigned char table[10] = {'0','1','2','3','4','5','6','7','8','9'}; 
  14.  
  15. void mydelay_ms(int time
  16.   int i, j; 
  17.  
  18.   while(time--) 
  19.   { 
  20.     for (i = 0; i < 5; i++) 
  21.     for (j = 0; j < 514; j++); 
  22.   } 
  23.  
  24. adc_init(int temp
  25.   ADCCON = (1 << 16 | 1 << 14 | 99 <<6 | 1 << 1); 
  26.   ADCMUX = 3; 
  27.   temp = ADCDAT & 0xfff; 
  28.  
  29. /* 
  30.  *  裸机代码,不同于LINUX 应用层, 一定加循环控制 
  31.  */ 
  32.  
  33. int main (void) 
  34.   unsigned char bit4,bit3,bit2,bit1; 
  35.   unsigned int temp = 0; 
  36.    
  37.   uart_init(); 
  38.   adc_init(temp); 
  39.   puts("开始转换\n"); 
  40.  
  41.   while(1) 
  42.   { 
  43.     while(!(ADCCON & 0x8000)); 
  44.     temp = ADCDAT & 0xfff; 
  45.     printf("U = %d\n",temp); 
  46.     temp = 1.8 * 1000 * temp/0xfff; 
  47.     bit4 = temp /1000; 
  48.     putc(table[bit4]); 
  49.     bit3 = (temp % 1000)/100?; 
  50.     putc(table[bit3]); 
  51.     bit2 = ((temp % 1000)%100)/10; 
  52.     putc(table[bit2]); 
  53.     bit1 = ((temp % 1000)%100)%10; 
  54.     putc(table[bit1]); 
  55.     puts("mV"); 
  56.     putc('\n'); 
  57.     mydelay_ms(1000); 
  58.   } 
  59.   return 0; 

中断模式

中断模式读取数据步骤如下:

1.要读取数据首先向ADC寄存器ADCCON的bit:0写1,发送转换命令;

2.当ADC控制器转换完毕会通过中断线向CPU发送中断信号;

3.在中断处理函数中,读走数据,并清中断.

注:中断对应寄存器的设置,后续会更新对应的文档。

  1. void do_irq(void) 
  2.        int irq_num; 
  3.  
  4.        irq_num = CPU0.ICCIAR &0x3ff; 
  5.        switch(irq_num) 
  6.        { 
  7.          case 42: 
  8.               adc_num = ADCDAT&0xfff; 
  9.               printf("adc = %d\n",adc_num); 
  10.               CLRINTADC = 0; 
  11.        //    IECR2 = IECR2 | (1 << 19);               打开的话只能读取一次, 
  12.               //42/32 
  13.               ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (1 << 10);【清GIC中断标志位类似于 ICDISER】 
  14.               break; 
  15.        } 
  16.        CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3ff) | irq_num; 
  17. void adc_init(void) 
  18. {    //12bit   使能分频       分频值                 手动 
  19.        ADCCON = (1 << 16) | (1 << 14) | (0xff << 6) | (1 << 0); 
  20.        ADCMUX = 3; 
  21. void adcint_init(void) 
  22.        IESR2 = IESR2 | (1 << 19); 
  23.        ICDDCR = 1;    //使能分配器 
  24.        //42/32 
  25.        ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 10);//使能相应中断到分配器 
  26.        ICDIPTR.ICDIPTR10 = ICDIPTR.ICDIPTR10 &(~(0xff << 16)) | (0x1 << 16);//发送到相应CPU接口 
  27.        CPU0.ICCPMR = 255;//设置中断屏蔽优先级 
  28.        CPU0.ICCICR = 1;  //全局使能开关 
  29. int main (void) 
  30. {  
  31.   adc_init(); 
  32.        adcint_init(); 
  33.        while(1) 
  34.        { 
  35.               ADCCON = ADCCON | 1; 
  36.               delay_ms(1000); 
  37.        } 
  38.    return 0; 

基于Linux驱动编写

设备树

编写基于Linux的ADC外设驱动,首先需要编写设备树节点信息,在裸机程序中,我们只用到了寄存器地址,而编写基于Linux的驱动,我们需要用到中断功能。所以编写设备树节点需要知道ADC要用到的硬件资源主要包括:寄存器资源和中断资源。

关于中断的使用我们在后续文章中会继续分析,现在我们只需要知道中断信息如何填写即可。

ADC寄存器信息填写

在这里插入图片描述

由上可知,寄存器基地址为0x126c0000,其他寄存器只需要根据基地址做偏移即可获取,所以设备树的reg属性信息如下:

  1. reg = <0x126C0000 0x20>; 

ADC中断信息填写

描述中断连接需要四个属性:父节点提供以下信息

  1. interrupt-controller - 一个空的属性定义该节点作为一个接收中断信号的设备。 
  2. interrupt-cells      - 这是一个中断控制器节点的属性。它声明了该中断控制器的中断指示符中【interrupts】 cell 的个数(类似于 #address-cells 和 #size-cells)。 

子节点描述信息

  1. interrupt-parent - 这是一个设备节点的属性,包含一个指向该设备连接的中断控制器的 phandle。那些没有 interrupt-parent 的节点则从它们的父节点中继承该属性。 
  2. interrupts       - 一个设备节点属性,包含一个中断指示符的列表,对应于该设备上的每个中断输出信号。【设备的中断信息放在该属性中】 

父节点


首先我们必须知道ADC控制器的中断线的父节点:

由上图可知ADC控制器位于soc内,4个ADC通道公用一根中断线,该中断线连接在combiner上,所以我们需要查找到combiner这个父节点的说明:

进入设备树文件所在目录:arch\arm\boot\dts

  1. grep combiner *.* -n 

经过筛选得到以下信息,


因为我们使用的板子是exynos4412,而exynos系列通用的平台设备树文件是exynos4.dtsi,查看该文件:


上图列举了combiner控制器的详细信息:

  1. interrupt-cells ; 
  2. interrupt-cells =<2>; 

所以ADC控制器中断控制器的interrupts属性应该有两个cell。

interrupts属性填写

而设备的中断信息填写方式由内核的以下文档提供:

  1. Documentation\devicetree\bindings\interrupt-controller\interrupts.txt 

  1. 69. b) two cells 
  2.  70.  ------------ 
  3.  71.  The #interrupt-cells property is set to 2 and the first cell 72. defines the 
  4.  73.  index of the interrupt within the controller, while the second cell is used 
  5.  74.  to specify any of the following flags: 
  6.  75.    - bits[3:0] trigger type and level flags 
  7.  76.        1 = low-to-high edge triggered 
  8.  77.        2 = high-to-low edge triggered 
  9.  78.        4 = active high level-sensitive 
  10.  79.        8 = active low level-sensitive 

由以上信息可知,中断的第一个cell是该中断源所在中断控制器的index,第二个cell表示中断的触发方式

*. 1:上升沿触发 *. 2:下降沿触发 *. 3:高电平触发 *. 4:低电平触发

那么index应该是多少呢?

详见datasheet的9.2.2 GIC Interrupt Table 节:


此处我们应该是填写左侧的SPI ID:10 还是填写INTERRUPT ID:42呢?

此处我们可以参考LCD节点的interrupts填写方法:

通过查找父节点为combiner的设备信息。

继续grep combiner . -n


由此可见lcd这个设备的interrupts属性index值是11,所以可知ADC控制器中断线的index是10。中断信息如下:

  1. interrupt-parent = <&combiner>; 
  2. interrupts = <10 3>; 

ADC外设设备树信息

  1. fs4412-adc{ 
  2.     compatible = "fs4412,adc"
  3.     reg = <0x126C0000 0x20>; 
  4.     interrupt-parent = <&combiner>; 
  5.     interrupts = <10 3>; 
  6. }; 

本文默认大家会使用设备树,不知道如何使用设备树的朋友,后续会开一篇单独讲解设备树。

【注意】在不支持设备树内核中,以Cortex-A8为例,中断信息填写在以下文件中

  1. 内部中断,Irqs.h (arch\arm\mach-s5pc100\include\mach) 
  2. 外部中断在Irqs.h (arch\arm\plat-s5p\include\plat) 

ADC属于内部中断,位于arch\arm\mach-s5pc100\include\mach\Irqs.h中。

寄存器信息填写在以下位置:

  1. arch\arm\mach-s5pc100\Mach-smdkc100.c 

  1. static struct platform_device *smdkc100_devices[] __initdata = { 
  2.   &s3c_device_adc, 
  3.   &s3c_device_cfcon, 
  4.   &s3c_device_i2c0, 
  5.   &s3c_device_i2c1, 
  6.   &s3c_device_fb, 
  7.   &s3c_device_hsmmc0, 
  8.   &s3c_device_hsmmc1, 
  9.   &s3c_device_hsmmc2, 
  10.   &samsung_device_pwm, 
  11.   &s3c_device_ts, 
  12.   &s3c_device_wdt, 
  13.   &smdkc100_lcd_powerdev, 
  14.   &s5pc100_device_iis0, 
  15.   &samsung_device_keypad, 
  16.   &s5pc100_device_ac97, 
  17.   &s3c_device_rtc, 
  18.   &s5p_device_fimc0, 
  19.   &s5p_device_fimc1, 
  20.   &s5p_device_fimc2, 
  21.   &s5pc100_device_spdif, 
  22. }; 

结构体s3c_device_adc定义在以下文件:

  1. \arch\arm\plat-samsung\Devs.c 

  1. #ifdef CONFIG_PLAT_S3C24XX 
  2. static struct resource s3c_adc_resource[] = { 
  3.   [0] = DEFINE_RES_MEM(S3C24XX_PA_ADC, S3C24XX_SZ_ADC), 
  4.   [1] = DEFINE_RES_IRQ(IRQ_TC), 
  5.   [2] = DEFINE_RES_IRQ(IRQ_ADC), 
  6. }; 
  7.  
  8. struct platform_device s3c_device_adc = { 
  9.   .name    = "s3c24xx-adc"
  10.   .id    = -1, 
  11.   .num_resources  = ARRAY_SIZE(s3c_adc_resource), 
  12.   .resource  = s3c_adc_resource, 
  13. }; 
  14. #endif /* CONFIG_PLAT_S3C24XX */ 
  15.  
  16. #if defined(CONFIG_SAMSUNG_DEV_ADC) 
  17. static struct resource s3c_adc_resource[] = { 
  18.   [0] = DEFINE_RES_MEM(SAMSUNG_PA_ADC, SZ_256), 
  19.   [1] = DEFINE_RES_IRQ(IRQ_TC), 
  20.   [2] = DEFINE_RES_IRQ(IRQ_ADC), 
  21. }; 
  22.  
  23. struct platform_device s3c_device_adc = { 
  24.   .name    = "samsung-adc"
  25.   .id    = -1, 
  26.   .num_resources  = ARRAY_SIZE(s3c_adc_resource), 
  27.   .resource  = s3c_adc_resource, 
  28. }; 
  29. #endif /* CONFIG_SAMSUNG_DEV_ADC */ 

由代码可知,平台驱动对应的platform_device具体内容由宏CONFIG_PLAT_S3C24XX、CONFIG_SAMSUNG_DEV_ADC来控制。驱动编写架构和流程如下

  1. read() 
  2.        1、向adc设备发送要读取的命令 
  3.           ADCCON    1<<0 | 1<<14 | 0X1<<16 | 0XFF<<6 
  4.        2、读取不到数据就休眠 
  5.             wait_event_interruptible(); 
  6.        3、等待被唤醒读数据 
  7.           havedata = 0; 
  8. adc_handler() 
  9.        1、清中断 ADC使用中断来通知转换数据完毕的 
  10.        2、状态位置位; 
  11.             havedata=1; 
  12.        3、唤醒阻塞进程 
  13.             wake_up() 
  14. probe() 
  15.       1、读取中断号,注册中断处理函数 
  16.       2、读取寄存器的地址,ioremap 
  17.       3、字符设备的操作 

驱动需要首先捕获中断信号后再去寄存器读取相应的数据,在ADC控制器没有准备好数据之前,应用层需要阻塞读取数据,所以在读取数据的函数中,需要借助等待队列来实现驱动对应用进程的阻塞。驱动程序

驱动程序对寄存器的操作参考裸机程序,只是基地址需要通过ioremap()做映射,对寄存器的读写操作需要用readl、writel。

driver.c

  1. #include <linux/module.h> 
  2. #include <linux/device.h> 
  3. #include <linux/platform_device.h> 
  4. #include <linux/interrupt.h> 
  5. #include <linux/fs.h> 
  6. #include <linux/wait.h> 
  7. #include <linux/sched.h> 
  8. #include <asm/uaccess.h> 
  9. #include <asm/io.h> 
  10. static int major = 250; 
  11.   
  12.   
  13. static wait_queue_head_t wq; 
  14. static int have_data = 0; 
  15. static int adc; 
  16. static struct resource *res1; 
  17. static struct resource *res2; 
  18. static void *adc_base; 
  19.   
  20. #define ADCCON 0x0000 
  21. #define ADCDLY 0x0008 
  22. #define ADCDAT 0x000C 
  23. #define CLRINTADC 0x0018 
  24. #define ADCMUX 0x001C 
  25.   
  26.   
  27. static  irqreturn_t adc_handler(int irqno, void *dev) 
  28.   have_data = 1; 
  29.   
  30.   printk("11111\n"); 
  31.   /*清中断*/ 
  32.   writel(0x12,adc_base + CLRINTADC); 
  33.   wake_up_interruptible(&wq); 
  34.   return IRQ_HANDLED; 
  35. static int adc_open (struct inode *inod, struct file *filep) 
  36.   
  37.   return 0; 
  38. static ssize_t adc_read(struct file *filep, char __user *buf, size_t len, loff_t *pos) 
  39.     writel(0x3,adc_base + ADCMUX); 
  40.   writel(1<<0 | 1<<14 | 0X1<<16 | 0XFF<<6 ,adc_base +ADCCON ); 
  41.   
  42.   wait_event_interruptible(wq, have_data==1); 
  43.   
  44.   /*read data*/ 
  45.   adc = readl(adc_base+ADCDAT)&0xfff; 
  46.    
  47.   if(copy_to_user(buf,&adc,sizeof(int))) 
  48.   { 
  49.     return -EFAULT; 
  50.   } 
  51.   have_data = 0; 
  52.   return len; 
  53. static  int adc_release(struct inode *inode, struct file *filep) 
  54.   return 0; 
  55. static struct file_operations  adc_ops = 
  56.   .open = adc_open, 
  57.   .release = adc_release, 
  58.   .read = adc_read, 
  59. }; 
  60.   
  61.   
  62. static int hello_probe(struct platform_device *pdev) 
  63.   int ret; 
  64.   printk("match 0k \n"); 
  65.   
  66.   res1 = platform_get_resource(pdev,IORESOURCE_IRQ, 0); 
  67.     res2 = platform_get_resource(pdev,IORESOURCE_MEM, 0); 
  68.       
  69.   ret = request_irq(res1->start,adc_handler,IRQF_DISABLED,"adc1",NULL); 
  70.       adc_base = ioremap(res2->start,res2->end-res2->start); 
  71.   
  72.   register_chrdev( major, "adc", &adc_ops); 
  73.   init_waitqueue_head(&wq); 
  74.    
  75.   return 0; 
  76. static int hello_remove(struct platform_device *pdev) 
  77.   free_irq(res1->start,NULL); 
  78.   free_irq(res2->start,NULL);   
  79.   unregister_chrdev( major, "adc"); 
  80.   return 0; 
  81.   
  82. static struct of_device_id adc_id[]= 
  83.   {.compatible = "fs4412,adc" }, 
  84. }; 
  85.   
  86. static struct platform_driver hello_driver= 
  87.    
  88.   .probe = hello_probe, 
  89.   .remove = hello_remove, 
  90.   .driver ={ 
  91.     .name = "bigbang"
  92.     .of_match_table = adc_id, 
  93.   }, 
  94. }; 
  95.   
  96. static int hello_init(void) 
  97.   printk("hello_init"); 
  98.   return platform_driver_register(&hello_driver); 
  99. static void hello_exit(void) 
  100.   platform_driver_unregister(&hello_driver); 
  101.   printk("hello_exit \n"); 
  102.   return
  103. MODULE_LICENSE("GPL"); 
  104. module_init(hello_init); 
  105. module_exit(hello_exit); 

测试程序

test.c

  1. #include <sys/types.h> 
  2. #include <sys/stat.h> 
  3. #include <fcntl.h> 
  4. #include <stdio.h> 
  5.   
  6. main() 
  7.   int fd,len; 
  8.   int adc; 
  9.   fd = open("/dev/hello",O_RDWR); 
  10.   if(fd<0) 
  11.   { 
  12.     perror("open fail \n"); 
  13.     return ; 
  14.   } 
  15.   
  16.   while(1) 
  17.   { 
  18.     read(fd,&adc,4); 
  19.     printf("adc%0.2f V \n",(1.8*adc)/4096); 
  20.   } 
  21.   
  22.   close(fd); 

 

 

责任编辑:姜华 来源: 一口Linux
相关推荐

2021-01-08 12:06:59

WDT定时装置

2020-12-22 11:54:42

C语言Cortex-A9LED汇编

2021-01-19 19:32:01

Cortex-A9 R嵌入式系统i2c 外设

2021-01-26 06:15:42

Cortex-A9 R嵌入式系统启动代码

2020-12-30 15:17:25

Cortex-A9UARTprintf函数

2021-01-06 05:42:42

Cortex-A9 R嵌入式系统 RTC

2020-12-11 09:05:04

ARMMDKGNU

2021-01-13 11:51:25

ARM位置无关码

2020-12-10 08:13:15

ARM架构 嵌入式

2021-05-25 11:50:32

ARMuboot网络协议栈

2022-10-31 07:33:05

Javafor循环

2022-10-30 10:14:43

Java循环语句

2022-09-30 07:32:48

循环while循环体

2022-11-26 00:34:57

数组Java程序

2022-09-22 07:31:14

Java变量计算

2015-02-04 19:13:48

ARMCortex-A 72

2022-09-30 07:32:39

架构

2022-09-16 07:32:15

编程计算机命令

2022-10-28 07:38:06

Javawhile循环

2023-03-20 16:21:26

ADC数字转换器
点赞
收藏

51CTO技术栈公众号