前言

在嵌入式开发中,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接口包括如下特性。

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

模块图

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

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

通道选择

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

A/D控制器寄存器

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

1、A/D控制寄存器ADCCON

  • RES         : 选择A/D转换精度,0:划分成1024份  1:划分成4096份

  • ECFLG   :转换是否结束  0:转换中  1:转换完毕;对于轮询模式需要根据该位判断数据是否转换完毕。

  • PRSCEN:A/D转换预分频是否使能

  • PRSCVL:预分频的值,转换公式见下面

  • STANDBY:待机模式  0:正常工作模式 1:待机模式。处于待机模式时要将PRSCEN设置为0

  • READ_START: A/D转换由读操作触发,设置为1后,每次读取A/D值的操作都会触发一次A/D转换。

  • 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 {
17 int i, j;
18
19 while(time--)
20 {
21 for (i = 0; i < 5; i++)
22 for (j = 0; j < 514; j++);
23 }
24 }
25
26 adc_init(int temp)
27 {
28 ADCCON = (1 << 16 | 1 << 14 | 99 <<6 | 1 << 1);
29 ADCMUX = 3;
30 temp = ADCDAT & 0xfff;
31 }
32
33 /*
34 * 裸机代码,不同于LINUX 应用层, 一定加循环控制
35 */
36
37 int main (void)
38 {
39 unsigned char bit4,bit3,bit2,bit1;
40 unsigned int temp = 0;
41
42 uart_init();
43 adc_init(temp);
44 puts("开始转换\n");
45
46 while(1)
47 {
48 while(!(ADCCON & 0x8000));
49 temp = ADCDAT & 0xfff;
50 printf("U = %d\n",temp);
51 temp = 1.8 * 1000 * temp/0xfff;
52 bit4 = temp /1000;
53 putc(table[bit4]);
54 bit3 = (temp % 1000)/100?;
55 putc(table[bit3]);
56 bit2 = ((temp % 1000)%100)/10;
57 putc(table[bit2]);
58 bit1 = ((temp % 1000)%100)%10;
59 putc(table[bit1]);
60 puts("mV");
61 putc('\n');
62 mydelay_ms(1000);
63 }
64 return 0;
65 }

中断模式

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

  • 1.要读取数据首先向ADC寄存器ADCCON的bit:0写1,发送转换命令;
  • 2.当ADC控制器转换完毕会通过中断线向CPU发送中断信号;
  • 3.在中断处理函数中,读走数据,并清中断.

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

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

基于Linux驱动编写

设备树

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

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

ADC寄存器信息填写

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

   
1 reg = <0x126C0000 0x20>;

ADC中断信息填写

描述中断连接需要四个属性:

父节点提供以下信息

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

子节点描述信息

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

父节点

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

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

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

grep combiner *.* -n

经过筛选得到以下信息:

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

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

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

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

interrupts属性填写

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

Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
69. b) two cells
 70.  ------------
 71.  The #interrupt-cells property is set to 2 and the first cell 72. defines the
 73.  index of the interrupt within the controller, while the second cell is used
 74.  to specify any of the following flags:
 75.    - bits[3:0] trigger type and level flags
 76.        1 = low-to-high edge triggered
 77.        2 = high-to-low edge triggered
 78.        4 = active high level-sensitive
 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。中断信息如下:

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

ADC外设设备树信息

fs4412-adc{
    compatible = "fs4412,adc";
    reg = <0x126C0000 0x20>;
    interrupt-parent = <&combiner>;
    interrupts = <10 3>;
};

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

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

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

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

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

arch\arm\mach-s5pc100\Mach-smdkc100.c
static struct platform_device *smdkc100_devices[] __initdata = {
  &s3c_device_adc,
  &s3c_device_cfcon,
  &s3c_device_i2c0,
  &s3c_device_i2c1,
  &s3c_device_fb,
  &s3c_device_hsmmc0,
  &s3c_device_hsmmc1,
  &s3c_device_hsmmc2,
  &samsung_device_pwm,
  &s3c_device_ts,
  &s3c_device_wdt,
  &smdkc100_lcd_powerdev,
  &s5pc100_device_iis0,
  &samsung_device_keypad,
  &s5pc100_device_ac97,
  &s3c_device_rtc,
  &s5p_device_fimc0,
  &s5p_device_fimc1,
  &s5p_device_fimc2,
  &s5pc100_device_spdif,
};

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

\arch\arm\plat-samsung\Devs.c
#ifdef CONFIG_PLAT_S3C24XX
static struct resource s3c_adc_resource[] = {
  [0] = DEFINE_RES_MEM(S3C24XX_PA_ADC, S3C24XX_SZ_ADC),
  [1] = DEFINE_RES_IRQ(IRQ_TC),
  [2] = DEFINE_RES_IRQ(IRQ_ADC),
};

struct platform_device s3c_device_adc = {
  .name    = "s3c24xx-adc",
  .id    = -1,
  .num_resources  = ARRAY_SIZE(s3c_adc_resource),
  .resource  = s3c_adc_resource,
};
#endif /* CONFIG_PLAT_S3C24XX */

#if defined(CONFIG_SAMSUNG_DEV_ADC)
static struct resource s3c_adc_resource[] = {
  [0] = DEFINE_RES_MEM(SAMSUNG_PA_ADC, SZ_256),
  [1] = DEFINE_RES_IRQ(IRQ_TC),
  [2] = DEFINE_RES_IRQ(IRQ_ADC),
};

struct platform_device s3c_device_adc = {
  .name    = "samsung-adc",
  .id    = -1,
  .num_resources  = ARRAY_SIZE(s3c_adc_resource),
  .resource  = s3c_adc_resource,
};
#endif /* CONFIG_SAMSUNG_DEV_ADC */

由代码可知,*台驱动对应的platform_device具体内容由宏CONFIG_PLAT_S3C24XX、CONFIG_SAMSUNG_DEV_ADC来控制。

驱动编写架构和流程如下

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

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

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

driver.c

#include <linux/module.h>
#include <linux/device.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <asm/io.h>
static int major = 250;
 
 
static wait_queue_head_t wq;
static int have_data = 0;
static int adc;
static struct resource *res1;
static struct resource *res2;
static void *adc_base;
 
#define ADCCON 0x0000
#define ADCDLY 0x0008
#define ADCDAT 0x000C
#define CLRINTADC 0x0018
#define ADCMUX 0x001C
 
 
static  irqreturn_t adc_handler(int irqno, void *dev)
{
  have_data = 1;
 
  printk("11111\n");
  /*清中断*/
  writel(0x12,adc_base + CLRINTADC);
  wake_up_interruptible(&wq);
  return IRQ_HANDLED;
}
static int adc_open (struct inode *inod, struct file *filep)
{
 
  return 0;
}
static ssize_t adc_read(struct file *filep, char __user *buf, size_t len, loff_t *pos)
{
    writel(0x3,adc_base + ADCMUX);
  writel(1<<0 | 1<<14 | 0X1<<16 | 0XFF<<6 ,adc_base +ADCCON );
 
  wait_event_interruptible(wq, have_data==1);
 
  /*read data*/
  adc = readl(adc_base+ADCDAT)&0xfff;
  
  if(copy_to_user(buf,&adc,sizeof(int)))
  {
    return -EFAULT;
  }
  have_data = 0;
  return len;
}
static  int adc_release(struct inode *inode, struct file *filep)
{
  return 0;
}
static struct file_operations  adc_ops =
{
  .open = adc_open,
  .release = adc_release,
  .read = adc_read,
};
 
 
static int hello_probe(struct platform_device *pdev)
{
  int ret;
  printk("match 0k \n");
 
  res1 = platform_get_resource(pdev,IORESOURCE_IRQ, 0);
    res2 = platform_get_resource(pdev,IORESOURCE_MEM, 0);
     
  ret = request_irq(res1->start,adc_handler,IRQF_DISABLED,"adc1",NULL);
      adc_base = ioremap(res2->start,res2->end-res2->start);
 
  register_chrdev( major, "adc", &adc_ops);
  init_waitqueue_head(&wq);
  
  return 0;
}
static int hello_remove(struct platform_device *pdev)
{
  free_irq(res1->start,NULL);
  free_irq(res2->start,NULL);  
  unregister_chrdev( major, "adc");
  return 0;
}
 
static struct of_device_id adc_id[]=
{
  {.compatible = "fs4412,adc" },
};
 
static struct platform_driver hello_driver=
{
  
  .probe = hello_probe,
  .remove = hello_remove,
  .driver ={
    .name = "bigbang",
    .of_match_table = adc_id,
  },
};
 
static int hello_init(void)
{
  printk("hello_init");
  return platform_driver_register(&hello_driver);
}
static void hello_exit(void)
{
  platform_driver_unregister(&hello_driver);
  printk("hello_exit \n");
  return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

测试程序

test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
 
main()
{
  int fd,len;
  int adc;
  fd = open("/dev/hello",O_RDWR);
  if(fd<0)
  {
    perror("open fail \n");
    return ;
  }
 
  while(1)
  {
    read(fd,&adc,4);
    printf("adc%0.2f V \n",(1.8*adc)/4096);
  }
 
  close(fd);
}

更多嵌入式资料,请关注公众号: 一口Linux。

一文搞懂Cortex-A9 ADC裸机和基于Linux驱动编写方法的更多相关文章

  1. 一文搞懂RAM、ROM、SDRAM、DRAM、DDR、flash等存储介质

    一文搞懂RAM.ROM.SDRAM.DRAM.DDR.flash等存储介质 存储介质基本分类:ROM和RAM RAM:随机访问存储器(Random Access Memory),易失性.是与CPU直接 ...

  2. 基础篇|一文搞懂RNN(循环神经网络)

    基础篇|一文搞懂RNN(循环神经网络) https://mp.weixin.qq.com/s/va1gmavl2ZESgnM7biORQg 神经网络基础 神经网络可以当做是能够拟合任意函数的黑盒子,只 ...

  3. 一文搞懂 Prometheus 的直方图

    原文链接:一文搞懂 Prometheus 的直方图 Prometheus 中提供了四种指标类型(参考:Prometheus 的指标类型),其中直方图(Histogram)和摘要(Summary)是最复 ...

  4. Web端即时通讯基础知识补课:一文搞懂跨域的所有问题!

    本文原作者: Wizey,作者博客:http://wenshixin.gitee.io,即时通讯网收录时有改动,感谢原作者的无私分享. 1.引言 典型的Web端即时通讯技术应用场景,主要有以下两种形式 ...

  5. 一文搞懂vim复制粘贴

    转载自本人独立博客https://liushiming.cn/2020/01/18/copy-and-paste-in-vim/ 概述 复制粘贴是文本编辑最常用的功能,但是在vim中复制粘贴还是有点麻 ...

  6. 三文搞懂学会Docker容器技术(中)

    接着上面一篇:三文搞懂学会Docker容器技术(上) 6,Docker容器 6.1 创建并启动容器 docker run [OPTIONS] IMAGE [COMMAND] [ARG...] --na ...

  7. 三文搞懂学会Docker容器技术(下)

    接着上面一篇:三文搞懂学会Docker容器技术(上) 三文搞懂学会Docker容器技术(中) 7,Docker容器目录挂载 7.1 简介 容器目录挂载: 我们可以在创建容器的时候,将宿主机的目录与容器 ...

  8. 一文搞懂所有Java集合面试题

    Java集合 刚刚经历过秋招,看了大量的面经,顺便将常见的Java集合常考知识点总结了一下,并根据被问到的频率大致做了一个标注.一颗星表示知识点需要了解,被问到的频率不高,面试时起码能说个差不多.两颗 ...

  9. 一文搞懂 js 中的各种 for 循环的不同之处

    一文搞懂 js 中的各种 for 循环的不同之处 See the Pen for...in vs for...of by xgqfrms (@xgqfrms) on CodePen. for &quo ...

  10. 一文搞懂如何使用Node.js进行TCP网络通信

    摘要: 网络是通信互联的基础,Node.js提供了net.http.dgram等模块,分别用来实现TCP.HTTP.UDP的通信,本文主要对使用Node.js的TCP通信部份进行实践记录. 本文分享自 ...

随机推荐

  1. SpringCloud开发之OpenFeign timeout和压缩等问题

    在某些时候,我们希望某个同步调用执行更长的时间(异步暂时不考虑),这个时候,首先就是要设置OpenFeign的timeout设定. 下面我们举例来说明,可以如何设定TimeOut参数. 一.环境 脱离 ...

  2. spring之NamedParameterJdbcTemplate返回自增列值

    以前使用JdbcTemplate来获取自增列的值,现在发现NamedParameterJdbcTemplate也可以,而且后者大部分情况下,其实更加方便. 这种方便主要是在于代码维护方面:我们更加习惯 ...

  3. PAT-甲级-1007

    一.看题,https://www.patest.cn/contests/pat-a-practise/1007 其实,也是一顿暴力,但是最后一个测试点会运行超时,最开始,计算一段区间的值的总和的时候, ...

  4. 基于MCU的SD卡fat文件系统读写移植

    背景 https://blog.csdn.net/huang20083200056/article/details/78508490 SD卡(Secure Digital Memory Card)具有 ...

  5. 背包DP——完全背包

    完全背包模型与 0-1 背包类似,与 0-1 背包的区别仅在于一个物品可以选取无限次,而非仅能选取一次. 而状态转移方程于01背包区别在于可以直接从[i][j-w[i]]转移 理由是当我们这样转移时, ...

  6. Spring中文官方文档

    Spring 中文文档 https://springdoc.cn/ Spring Boot 中文文档 https://www.docs4dev.com/docs/zh/spring-boot/1.5. ...

  7. 洛谷P1057

    #include<iostream> #include<utility> using namespace std; typedef long long ll; #define ...

  8. 解决php提示Maximum execution time of 30 seconds exceeded错误

    如何解决错误? 基本上,有3种方法可以处理此错误: 修改php配置文件php.ini文件 使用 ini_set() 函数 使用set_time_limit()函数 1)修改php配置文件php.ini ...

  9. [oeasy]python0013_ASCII码表_英文字符编码_键盘字符

    ​ ASCII 码表 回忆上次内容 ​ord(c)​​和​​chr(i)​ 这是俩函数 这俩函数是一对,相反相成的⚖️ ​​ord​​ 通过 ​​字符​​ 找到对应的 ​​数字​​ ​​chr​​ 通 ...

  10. 第七节 JMeter基础-高级登录【数据驱动-参数化】

    声明:本文所记录的仅本次操作学习到的知识点,其中商城IP错误,请自行更改. 背景:一个接口的不同情况,其实就是请求参数不一样.期望结果不一样.把这些不一样的东西都提取出来进行管理,下次可以直接使用.因 ...