[ARM入门]

基于ARM_contexA9 led驱动编程

[复制链接]
811|0
手机看帖
扫描二维码
随时随地手机跟帖
qingxiao|  楼主 | 2017-11-12 10:18 | 显示全部楼层 |阅读模式
基于ARM_contexA9 led驱动编程

关于友善之臂出的这款contexA9开发板,目前在网络上的资源较少,特别是内核的,非常之少,鉴于这种情况,我将会写一个系列的驱动来做关于tiny4412这款板子开发的总结。
     简单介绍一下:
Tiny4412是一款高性能的四核Cortex-A9核心板,由广州友善之臂设计、生产和发行销售。它采用三星Exynos4412作为主处理器,运行主频可高达1.5GHz,Exynos4412内部集成了Mali-400 MP高性能图形引擎,支持3D图形流畅运行,并可播放1080P大尺寸高清视频。三星旗舰智能手机Galaxy S3即是采用此CPU设计。
我用的是普通版.也就是只有一个串口的.但是核心板是一样的。

好了,介绍完毕,前面的**我们已经说过了如何编写一个字符设备的驱动程序,这里就不再继续扯字符驱动怎么写,非常简单了,看看就懂了。
我们进入整题,今天,我们需要实现一个LED的驱动程序。在友善之臂的核心板上,有4颗LED灯,如何编写一个驱动程序,让它亮起来,首先我们来看看核心板:


LED灯就位于右上角,第一个和第二个都是电源指示灯,我们不需要管它,我们只管后面那4个LED灯。
如何编写?
1、首先找到板子的原理图,找到对应的引脚。
2、接着打开数据手册,找到对应的寄存器。
3、开始编写LED驱动程序
4、编写makefile
5、插入模块insmod xxx.ko
6、查询主设备号 cat /proc/devices
7、创建设备节点 mknod /dev/xxx c x x
8、执行应用程序app
对应的原理图:


从这里我们可以得出一个结论,LED灯是低电平点亮的,也就是往对应的端口里写0,LED灯就亮了。从最下面一幅图可以知道,我们要找的寄存器是GPIO的GPM4开头的这个寄存器,现在我们进入查数据手册的阶段.
查手册:
我们找到手册的第288页GPIO章节的GPMCON这里:



这是我们要配置端口的模式的IO口,端口有以上的一些状态,在这里我们只考虑输出,也就是只要配置Output那一项就可以了。
我们要配的寄存器有GPM4CON[0],GPM4CON[1],GPM4CON[2],GPM4CON[3],这四位,分别配置成output输出模式.
接下来再看一个GPM4DAT,这个是端口的状态寄存器,对状态寄存器就是写0或者写1,那么LED就被驱动了,我们来看看:

好了,寄存器我们已经找到了,接下来,可以进入写代码的阶段了:
首先编写LED驱动程序:

[cpp] view plain copy print?
1.
#include <linux/init.h>  
2.
3.
#include <linux/module.h>  
4.
5.
#include <linux/kernel.h>  
6.
7.
#include <linux/fs.h>  
8.
9.
#include <linux/io.h>  
10.
11.
#include <asm/uaccess.h>  
12.
13.
#include <asm/irq.h>  
14.
15.
#include <asm/io.h>  
16.
17.
//这个是设备的名称,也就是对应在/dev/test-dev  
18.
19.
#define DEV_NAME    "test-dev"  
20.
21.
//LED灯IO口的地址,也就是刚刚我们在上面的芯片手册看到的Address  
22.
23.
#define GPM4COM     0x110002E0  
24.
25.
//定义配置模式的指针变量  
26.
27.
volatile unsigned long *led_config = NULL ;   
28.
29.
//定义配置状态的指针变量  
30.
31.
volatile unsigned long *led_dat = NULL ;   
32.
33.
//open方法,对LED灯进行初始化  
34.
35.
int led_open(struct inode *inode, struct file *filp)  
36.
37.
{  
38.
39.
    printk("led_open\n");//上层程序对LED进行Open操作的时候会执行这个函数  
40.
41.
    //先对LED的端口进行清0操作  
42.
43.
    *led_config &= ~(0xffff);  
44.
45.
    //将4个IO口16位都设置为Output输出状态  
46.
47.
    *led_config |= (0x1111);  
48.
49.
    return 0;  
50.
51.
}  
52.
53.
//write方法  
54.
55.
int led_write(struct file *filp , const char __user *buf , size_t count , loff_t *f_pos)  
56.
57.
{  
58.
59.
    int val ;   
60.
61.
    //注意,这里是在内核中进行操作,我们需要使用copy_from_user这个函数将用户态的内容拷贝到内核态  
62.
63.
    copy_from_user(&val , buf , count);   
64.
65.
    //以下就是当val是哪个值的时候,led就执行相应的操作,这里不多说  
66.
67.
    switch(val)  
68.
69.
    {  
70.
71.
        case 0 :   
72.
73.
                //对状态寄存器进行赋值,以下雷同  
74.
75.
                printk(KERN_EMERG"led1_on\n");  
76.
77.
                *led_dat &= ~0x1 ;  
78.
79.
                break ;  
80.
81.
        case 1 :  
82.
83.
                printk(KERN_EMERG"led2_on\n");  
84.
85.
                *led_dat &= ~0x2 ;  
86.
87.
                break ;  
88.
89.
        case 2 :  
90.
91.
                printk(KERN_EMERG"led3_on\n");  
92.
93.
                *led_dat &= ~0x4 ;  
94.
95.
                break ;  
96.
97.
        case 3 :  
98.
99.
                printk(KERN_EMERG"led4_on\n");  
100.
101.
                *led_dat &= ~0x8 ;   
102.
103.
                break ;  
104.
105.
        case 4 :  
106.
107.
                printk(KERN_EMERG"ledall_on\n");  
108.
109.
                *led_dat &= ~0xf ;  
110.
111.
                break ;  
112.
113.
        case 5 :   
114.
115.
                printk(KERN_EMERG"ledall_off\n");  
116.
117.
                *led_dat |= 0xf ;  
118.
119.
                break ;  
120.
121.
  
122.
123.
    }  
124.
125.
}  
126.
127.
//close方法  
128.
129.
int led_close(struct inode *inode, struct file *filp)  
130.
131.
{  
132.
133.
    printk("led_close\n");  
134.
135.
    *led_dat |= 0xf ;  //全灭,因为高电平是灭的,0xf ----> 1111  
136.
137.
    return 0;  
138.
139.
}  
140.
141.
//用ioctl这个方法也可以实现LED的操作的,自己去实现吧  
142.
143.
#if 0  
144.
145.
long led_ioctl(struct file *filp, unsigned int request, unsigned long arg)  
146.
147.
{  
148.
149.
    switch(request)  
150.
151.
    {  
152.
153.
        case 0:  
154.
155.
            printk(KERN_EMERG"led1 on\n");  
156.
157.
            *led_dat &=~0x1 ;  
158.
159.
            break;  
160.
161.
  
162.
163.
        case 1:  
164.
165.
            printk(KERN_EMERG"led2 on\n");  
166.
167.
            *led_dat &=~0x2 ;  
168.
169.
            break;  
170.
171.
  
172.
173.
        case 3:  
174.
175.
            printk(KERN_EMERG"led3 on\n");  
176.
177.
            *led_dat &=~0xf ;  
178.
179.
            break;  
180.
181.
  
182.
183.
        case 4:  
184.
185.
            printk(KERN_EMERG"led4 on\n");  
186.
187.
            *led_dat &=~0x8 ;  
188.
189.
            break ;  
190.
191.
        default :   
192.
193.
            *led_dat |= 0xf ;  
194.
195.
    }     
196.
197.
}  
198.
199.
#endif  
200.
201.
//对方法进行初始化  
202.
203.
struct file_operations fops = {  
204.
205.
    .owner = THIS_MODULE ,  
206.
207.
    .open = led_open,  
208.
209.
    .release = led_close,  
210.
211.
//  .unlocked_ioctl = led_ioctl,  
212.
213.
    .write = led_write,  
214.
215.
};  
216.
217.
//主设备号  
218.
219.
int major ;  
220.
221.
//启动函数  
222.
223.
static __init int test_init(void)  
224.
225.
{  
226.
227.
    printk("led_init\n");  
228.
229.
    major = register_chrdev(major, DEV_NAME, &fops);  
230.
231.
    led_config = (volatile unsigned long *)ioremap(GPM4COM , 16);  
232.
233.
    led_dat = led_config + 1 ;    
234.
235.
    return 0;  
236.
237.
}  
238.
239.
//注销函数  
240.
241.
static __exit void test_exit(void)  
242.
243.
{  
244.
245.
    printk("led_exit\n");  
246.
247.
    unregister_chrdev(major, DEV_NAME);  
248.
249.
    iounmap(led_config);  
250.
251.
}  
252.
253.
  
254.
255.
module_init(test_init);  
256.
257.
module_exit(test_exit);  
258.
259.
  
260.
261.
MODULE_LICENSE("GPL");  
262.
263.
MODULE_AUTHOR("Y.X.YANG");  
264.
265.
MODULE_VERSION("2016.1.15");</span>  
266.


以上就是led这个设备驱动的编写框架。看不懂的可以去学学linux内核设备驱动再来看就很简单了。其实跟单片机的编程差不了多少的,只不过内核驱动是按照框架来编写的,有所驱动就在这里。

驱动程序编写完了,接下来我们编写上层应用层的程序:

[cpp] view plain copy print?
1.
#include <stdio.h>  
2.
3.
#include <sys/types.h>  
4.
5.
#include <sys/stat.h>  
6.
7.
#include <fcntl.h>  
8.
9.
  
10.
11.
int main(int argc, char **argv)  
12.
13.
{  
14.
15.
    int fd;  
16.
17.
    int val = 0 ;  
18.
19.
    //打开对应的设备  
20.
21.
    fd = open("/dev/test-dev",O_RDWR) ;  
22.
23.
    if(-1 == fd)  
24.
25.
    {  
26.
27.
        printf("open fair!\n");  
28.
29.
        return -1 ;  
30.
31.
    }  
32.
33.
    while(1){  
34.
35.
        val = 0 ;  
36.
37.
        //写write方法就会调用到驱动程序的led_write  
38.
39.
        //最后我们能看到的结果是led灯做流水灯的实现,然后全灭,再周而复始  
40.
41.
        write(fd , &val , 4);  
42.
43.
        sleep(1);  
44.
45.
        val = 1 ;  
46.
47.
        write(fd , &val , 4);  
48.
49.
        sleep(1);  
50.
51.
        val = 2 ;  
52.
53.
        write(fd , &val , 4);  
54.
55.
        sleep(1);  
56.
57.
        val = 3 ;  
58.
59.
        write(fd , &val , 4);  
60.
61.
        sleep(1);  
62.
63.
        val = 5 ;  
64.
65.
        write(fd , &val , 4);  
66.
67.
        sleep(1);  
68.
69.
    }  
70.
71.
    return 0;  
72.
73.
}</span>  
74.


好了,程序已经写完了,我们来看看makefile怎么写.

[cpp] view plain copy print?
1.
#将你所写的驱动程序编译成模块形式  
2.
3.
obj-m   += leds.o  
4.
5.
#你需要的文件系统  
6.
7.
ROOTFS = /disk/A9/filesystem  
8.
9.
#你需要的内核  
10.
11.
KERNEL = /disk/A9/linux-3.5/  
12.
13.
#模块编译  
14.
15.
all:  
16.
17.
    make -C $(KERNEL) M=`pwd` modules  
18.
19.
#模块清除  
20.
21.
clean:  
22.
23.
    make -C $(KERNEL) M=`pwd` clean  
24.
25.
    rm -rf my_led  
26.
27.
#模块更新  
28.
29.
install:  
30.
31.
    make -C $(KERNEL) M=`pwd` modules_install INSTALL_MOD_PATH=$(ROOTFS)  
32.
33.
#编译上层app应用程序  
34.
35.
my_led:  
36.
37.
    arm-linux-gcc my_led.c -o my_led</span>  
38.

好了,所有的一切都编写完成,我们来看看接下来的操作:

2、编译app

3、启动minicom,打开开发板的电源,开发板bootload开始启动

4、开发板内核启动

5、进入文件系统,执行insmod插入模块和显示插入后的模块的操作

6、看看主设备号

从这里可以看到,我们的设备test-dev的主设备号是250
所以我们创建设备节点 : mknod /dev/test-dev c 250 0 ,创建完成后
7、执行app应用程序

在这里,我们可以看到程序开始跑起来了,我们来看看开发板上的led是怎么变化的:

1175589091

相关帖子

发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

104

主题

112

帖子

3

粉丝