基于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是怎么变化的: