嵌入式Linux应用开发-基础知识-第七章 具体单板的 LED驱动程序
- 第七章 具体单板的 LED 驱动程序
- 7.1 怎么写 LED 驱动程序?
- 7.2 AM335X的 LED驱动程序
- 7.2.1 原理图 XXXXXX_AM335X开发板结构为:
- 7.2.2 所涉及的寄存器操作
- 7.2.3 写程序
- 7.2.4 配置内核去掉原有 LED驱动
- 7.2.5 课后作业
第七章 具体单板的 LED 驱动程序
我们选用的内核都是 4.x 版本,操作都是类似的:
rk3399 linux 4.4.154
rk3288 linux 4.4.154
imx6ul linux 4.9.88
am3358 linux 4.9.168
录制视频时,我的 source insight 里总是使用某个版本的内核。这没有关系,驱动程序中调用的内核
函数,在这些 4.x 版本的内核里都是一样的。
7.1 怎么写 LED 驱动程序?
详细步骤如下:
① 看原理图确定引脚,确定引脚输出什么电平才能点亮/熄灭 LED
② 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是?
③ 编写驱动:先写框架,再写硬件操作的代码
注意:在芯片手册中确定的寄存器地址被称为物理地址,在 Linux 内核中无法直接使用。
需要使用内核提供的 ioremap 把物理地址映射为虚拟地址,使用虚拟地址。
ioremap 函数的使用:
① 函数原型:
使用时,要包含头文件:
#include <asm/io.h>
② 它的作用: 把物理地址 phys_addr开始的一段空间(大小为 size),映射为虚拟地址;返回值是该段虚拟地址的首地址。
virt_addr = ioremap(phys_addr, size);
实际上,它是按页(4096字节)进行映射的,是整页整页地映射的。 假设 phys_addr = 0x10002,size=4,ioremap的内部实现是: a. phys_addr按页取整,得到地址 0x10000 b. size按页取整,得到 4096 c. 把起始地址 0x10000,大小为 4096的这一块物理地址空间,映射到虚拟地址空间, 假设得到的虚拟空间起始地址为 0xf0010000 d. 那么 phys_addr = 0x10002对应的 virt_addr = 0xf0010002
③ 不再使用该段虚拟地址时,要 iounmap(virt_addr):
void iounmap(volatile void __iomem *cookie)
volatile的使用:
① 编译器很聪明,会帮我们做些优化,比如:
int a;
a = 0; // 这句话可以优化掉,不影响 a的结果
a = 1;
② 有时候编译器会自作聪明,比如:
int *p = ioremap(xxxx, 4); // GPIO寄存器的地址
*p = 0; // 点灯,但是这句话被优化掉了
*p = 1; // 灭灯
③ 对于上面的情况,为了避免编译器自动优化,需要加上 volatile,告诉它“这是容易出错的,别乱优化”:
volatile int *p = ioremap(xxxx, 4); // GPIO寄存器的地址
*p = 0; // 点灯,这句话不会被优化掉
*p = 1; // 灭灯
7.2 AM335X的 LED驱动程序
7.2.1 原理图 XXXXXX_AM335X开发板结构为:
底板+核心板,其中一个 LED原理图如下:
它使用 GPIO1_16这个引脚,当它输出低电平时,LED被点亮;当它输出高电平时,LED被熄灭。
7.2.2 所涉及的寄存器操作
a. 使能 GPIO1
/* set PRCM to enalbe GPIO1
* set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)
* * val: (1<<18) | 0x2
* */
b. 设置 GPIO1_16的功能,
让它工作于 GPIO模式 根据原理图可以找到 GPIO1_16这个引脚接到 AM3358的 R13引脚,根据下图知道 pin name为 GPMC_A0,并且知道要设置这个引脚为 Mode 7。
在芯片手册中搜“conf_gpmc_a0”,可得:
/* set Control Module to set GPIO1_16 (R13) used as GPIO
* conf_gpmc_a0 as mode 7
* * addr : 0x44E10000 + 0x840
* * val : 7
* */
c. 设置 GPIO1_16的方向,让它作为输出引脚
/* set GPIO1's registers , to set GPIO1_16'S dir (output)
* GPIO_OE
* * addr : 0x4804C000 + 0x134
* * clear bit 16
* */
d. 设置 GPIO1_16的数据,让它输出高电平 AM335X芯片支持 set-and-clear protocol,设置 GPIO_SETDATAOUT的 bit 16为 1即可让引脚输出 1:
/* set GPIO1_16's registers , to output 1
* GPIO_SETDATAOUT
* addr : 0x4804C000 + 0x194
*/
e. 清除 GPIO1_16的数据,让它输出低电平 AM335X芯片支持 set-and-clear protocol,设置 GPIO_CLEARDATAOUT的 bit 16为 1即可让引脚输出0:
/* set GPIO1_16's registers , to output 0
* GPIO_CLEARDATAOUT
* addr : 0x4804C000 + 0x190
*/
7.2.3 写程序
使用 GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\
05_嵌入式 Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\am335x_src_bin
硬件相关的文件是 board_am335x.c,其他文件跟 LED框架驱动程序完全一样。 它首先构造了一个 led_operations结构体,用来表示 LED的硬件操作:
100 static struct led_operations board_demo_led_opr = {
101 .num = 1,
102 .init = board_demo_led_init,
103 .ctl = board_demo_led_ctl,
104 };
led_operations结构体中有 init函数指针,它指向 board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为 GPIO模式、设置为输出引脚。 值得关注的是第 33~37行,对于寄存器要先使用 ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器。
19 #include "led_opr.h"
20
21 static volatile unsigned int *CM_PER_GPIO1_CLKCTRL;
22 static volatile unsigned int *conf_gpmc_a0;
23 static volatile unsigned int *GPIO1_OE;
24 static volatile unsigned int *GPIO1_CLEARDATAOUT;
25 static volatile unsigned int *GPIO1_SETDATAOUT;
26
27 static int board_demo_led_init (int which) /* 初始化 LED, which-哪个 LED */
28 {
29 if (which == 0)
30 {
31 if (!CM_PER_GPIO1_CLKCTRL)
32 {
33 CM_PER_GPIO1_CLKCTRL = ioremap(0x44E00000 + 0xAC, 4);
34 conf_gpmc_a0 = ioremap(0x44E10000 + 0x840, 4);
35 GPIO1_OE = ioremap(0x4804C000 + 0x134, 4);
36 GPIO1_CLEARDATAOUT = ioremap(0x4804C000 + 0x190, 4);
37 GPIO1_SETDATAOUT = ioremap(0x4804C000 + 0x194, 4);
38 }
39
40 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
41 /* a. 使能 GPIO1
42 * set PRCM to enalbe GPIO1
43 * set CM_PER_GPIO1_CLKCTRL (0x44E00000 + 0xAC)
44 * val: (1<<18) | 0x2
45 */
46 *CM_PER_GPIO1_CLKCTRL = (1<<18) | 0x2;
47
48 /* b. 设置 GPIO1_16的功能,让它工作于 GPIO模式
49 * set Control Module to set GPIO1_16 (R13) used as GPIO
50 * conf_gpmc_ad0 as mode 7
51 * addr : 0x44E10000 + 0x800
52 * val : 7
53 */
54 *conf_gpmc_a0 = 7;
55
56 /* c. 设置 GPIO1_16的方向,让它作为输出引脚
57 * set GPIO1's registers , to set GPIO1_16'S dir (output)
58 * GPIO_OE
59 * addr : 0x4804C000 + 0x134
60 * clear bit 16 61 */
62
63 *GPIO1_OE &= ~(1<<16);
64 }
65
66 return 0;
67 }
68
led_operations结构体中有 ctl函数指针,它指向 board_demo_led_ctl函数,在里面将会根据参数设置 LED引脚的输出电平:
69 static int board_demo_led_ctl (int which, char status) /* 控制 LED, which-哪个 LED, status:1-亮,0-灭 */
70 {
71 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
72
73 if (which == 0)
74 {
75 if (status) /* on: output 0 */
76 {
77 /* e. 清除 GPIO1_16的数据,让它输出低电平
78 * AM335X芯片支持 set-and-clear protocol,设置 GPIO_CLEARDATAOUT的 bit 16为 1即可让引脚输出 0:
79 * set GPIO1_16's registers , to output 0
80 * GPIO_CLEARDATAOUT
81 * addr : 0x4804C000 + 0x190
82 */
83 *GPIO1_CLEARDATAOUT = (1<<16);
84 }
85 else
86 {
87 /* d. 设置 GPIO1_16的数据,让它输出高电平
88 * AM335X芯片支持 set-and-clear protocol,设置 GPIO_SETDATAOUT的 bit 16为1即可让引脚输出 1:
89 * set GPIO1_16's registers , to output 1
90 * GPIO_SETDATAOUT
91 * addr : 0x4804C000 + 0x194
92 */
93 *GPIO1_SETDATAOUT = (1<<16);
94 }
95 }
96
97 return 0;
98 }
99
下面的 get_board_led_opr函数供上层调用,给上层提供 led_operations结构体:
106 struct led_operations *get_board_led_opr(void)
107 {
108 return &board_demo_led_opr;
109 }
110
7.2.4 配置内核去掉原有 LED驱动
不需要重新配置内核,只需要在开发板上执行以下 3条命令关闭内核对 LED的使用即可:
# echo none > /sys/class/leds/am335x:green:cpu0/trigger
# echo none > /sys/class/leds/am335x:green:mmc0/trigger
# echo none > /sys/class/leds/am335x:green:nand/trigger
然后就可以去安装驱动程序,执行测试程序了,操作过程跟 LED框架驱动程序的测试是一样的。
7.2.5 课后作业
a. 在 board_am335x.c里有 ioremap,什么时候执行 iounmap?请完善程序
b. 视频里我们只实现了点一个 LED,请修改代码实现操作 4个 LED