如何编写基于ARM的裸机程序和基于Linux的驱动程序
前言
在嵌入式开发中,ADC应用比较频繁,本文主要讲解ADC的基本原理以及如何编写基于ARM的裸机程序和基于Linux的驱动程序 。
ARM架构:Cortex-A9 Linux内核:3.14
在讲述ADC之前,我们需要先了解什么是模拟信号和数字信号 。
模拟信号
主要是与离散的数字信号相对的连续的信号 。模拟信号分布于自然界的各个角落,如每天温度的变化,而数字信号是人为的抽象出来的在时间上不连续的信号 。电学上的模拟信号是主要是指幅度和相位都连续的电信号,此信号可以被模拟电路进行各种运算,如放大,相加,相乘等 。
模拟信号是指用连续变化的物理量表示的信息,其信号的幅度,或频率,或相位随时间作连续变化,如目前广播的声音信号,或图像信号等 。
如下图所示从上到下一次是正弦波、 调幅波、 阻尼震荡波、 指数衰减波。
中断模式
中断模式读取数据步骤如下:
1.要读取数据首先向ADC寄存器ADCCON的bit:0写1,发送转换命令;
2.当ADC控制器转换完毕会通过中断线向CPU发送中断信号;
3.在中断处理函数中,读走数据,并清中断.
注:中断对应寄存器的设置,后续会更新对应的文档 。
void do_irq(void) {int irq_num;irq_num = CPU0.ICCIAR &0x3ff;switch(irq_num){case 42:adc_num = ADCDAT&0xfff;printf("adc = %d ",adc_num);CLRINTADC = 0;//IECR2 = IECR2 | (1 << 19);打开的话只能读取一次,//42/32ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (1 << 10);【清GIC中断标志位类似于 ICDISER】break;}CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3ff) | irq_num; } void adc_init(void) {//12bit使能分频分频值手动ADCCON = (1 << 16) | (1 << 14) | (0xff << 6) | (1 << 0);ADCMUX = 3; } void adcint_init(void) {IESR2 = IESR2 | (1 << 19);ICDDCR = 1;//使能分配器//42/32ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 10);//使能相应中断到分配器ICDIPTR.ICDIPTR10 = ICDIPTR.ICDIPTR10 &(~(0xff << 16)) | (0x1 << 16);//发送到相应CPU接口CPU0.ICCPMR = 255;//设置中断屏蔽优先级CPU0.ICCICR = 1;//全局使能开关 } int main (void) {adc_init();adcint_init();while(1){ADCCON = ADCCON | 1;delay_ms(1000);}return 0; }
基于Linux驱动编写
设备树
编写基于Linux的ADC外设驱动,首先需要编写设备树节点信息,在裸机程序中,我们只用到了寄存器地址,而编写基于Linux的驱动,我们需要用到中断功能 。所以编写设备树节点需要知道ADC要用到的硬件资源主要包括:寄存器资源和中断资源 。
关于中断的使用我们在后续文章中会继续分析,现在我们只需要知道中断信息如何填写即可 。
ADC寄存器信息填写
文章插图
由上可知,寄存器基地址为0x126c0000,其他寄存器只需要根据基地址做偏移即可获取,所以设备树的reg属性信息如下:
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这个父节点的说明:
进入设备树文件所在目录:archarmootdts
grep combiner *.* -n
经过筛选得到以下信息:
文章插图
因为我们使用的板子是exynos4412,而exynos系列通用的平台设备树文件是exynos4.dtsi,查看该文件:
文章插图
上图列举了combiner控制器的详细信息:
interrupt-cells ;interrupt-cells =<2>;
所以ADC控制器中断控制器的interrupts属性应该有两个cell 。
interrupts属性填写
而设备的中断信息填写方式由内核的以下文档提供:
Documentationdevicetreeindingsinterrupt-controllerinterrupts.txt69. b) two cells70.------------71.The #interrupt-cells property is set to 2 and the first cell 72. defines the73.index of the interrupt within the controller, while the second cell is used74.to specify any of the following flags:75.- bits[3:0] trigger type and level flags76.1 = low-to-high edge triggered77.2 = high-to-low edge triggered78.4 = active high level-sensitive79.8 = active low level-sensitive
由以上信息可知,中断的第一个cell是该中断源所在中断控制器的index,第二个cell表示中断的触发方式
上升沿触发
下降沿触发
高电平触发
低电平触发
那么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 (archarmmach-s5pc100includemach) 外部中断在Irqs.h (archarmplat-s5pincludeplat)
ADC属于内部中断,位于archarmmach-s5pc100includemachIrqs.h中 。
寄存器信息填写在以下位置:
archarmmach-s5pc100Mach-smdkc100.cstatic struct platform_device *smdkc100_devices[] __initdata = http://www.dg8.com.cn/news/{&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定义在以下文件:
archarmplat-samsungDevs.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设备发送要读取的命令ADCCON1<<0 | 1<<14 | 0X1<<16 | 0XFF<<62、读取不到数据就休眠wait_event_interruptible();3、等待被唤醒读数据havedata = http://www.dg8.com.cn/news/0; } adc_handler() {1、清中断 ADC使用中断来通知转换数据完毕的2、状态位置位;havedata=1;3、唤醒阻塞进程wake_up() } probe() {1、读取中断号,注册中断处理函数2、读取寄存器的地址,ioremap3、字符设备的操作 }
驱动需要首先捕获中断信号后再去寄存器读取相应的数据,在ADC控制器没有准备好数据之前,应用层需要阻塞读取数据,所以在读取数据的函数中,需要借助等待队列来实现驱动对应用进程的阻塞 。驱动程序
驱动程序对寄存器的操作参考裸机程序,只是基地址需要通过ioremap()做映射,对寄存器的读写操作需要用readl、writel 。
driver.c
#include
测试程序
test.c
【如何编写基于ARM的裸机程序和基于Linux的驱动程序】 #include#include#include
推荐阅读
- 怎样保护髋关节和膝关节
- 液晶电视拆卸教程 如何将挂装的电视取下来
- 多乐士油漆如何选购 选购达人为你支招
- 空调扇如何选购 选购达人为你支招
- 教你如何快速清洁壁纸壁布方法
- 居家达人教你如何选择家居装修墙纸
- 怎么拯救爱情,处理堆集的对立
- 水星路由器如何恢复出厂设置 水星MW325R 路由器调试、设置方法
- 3亿罚款背后,公牛集团“牛市”如何继续?
- “花钱买罪受”的音乐节,如何就成了年轻人的必玩项?