野火、正点原子用的内核版本是 4.1.15,
我们用的内核版本是 linux 4.9.88, 都是 4.x版本,在学习上没有任何差别。 你拿到板子后,可以使用他们出厂的系统, 也可以根据我们提供的高级用户手册更改为我们的系统。
LED原理图如下,它使用 GPIO5_IO03,引脚输出低电平时 LED被点亮,输出高电平时 LED被熄灭:
LED原理图如下,它使用 GPIO1_IO03,引脚输出低电平时 LED被点亮,输出高电平时 LED被熄灭:
GPIO模块图如下:
代码中对硬件的操作截图如下,截图便于对比,后面有文字便于复制:
步骤 1:使能 GPIO5
设置 b[31:30]就可以使能 GPIO5,设置为什么值呢?
注意:
在 imx6ullrm.pdf中,CCM_CCGR1的 b[31:30]是保留位;我以前写程序时错用了 imx6ul(不是imx6ull)的手册,导致程序中额外操作了这些保留位。不去设置 b[31:30],GPIO5也是默认使能的。 看下图,设置为 0b11:
① 00:该 GPIO模块全程被关闭
② 01:该 GPIO模块在 CPU run mode情况下是使能的;在 WAIT或 STOP模式下,关闭
③ 10:保留
④ 11:该 GPIO模块全程使能
/* GPIO5_IO03 */
/* a. 使能 GPIO5
* set CCM to enable GPIO5
* CCM_CCGR1[CG15] 0x20C406C
* bit[31:30] = 0b11
* /
步骤 2:设置 GPIO5_IO03为 GPIO模式
设置如下寄存器:
/* b. 设置 GPIO5_IO03用于 GPIO
* set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
* to configure GPIO5_IO03 as GPIO
* IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014
* bit[3:0] = 0b0101 alt5
* /
步骤 3:设置 GPIO5_IO03为输出引脚,
设置其输出电平 寄存器地址为:
设置数据寄存器,设置引脚的输出电平:
/* c. 设置 GPIO5_IO03作为 output引脚
* set GPIO5_GDIR to configure GPIO5_IO03 as output
* GPIO5_GDIR 0x020AC000 + 0x4
* bit[3] = 0b1
*/
/* d. 设置 GPIO5_DR输出低电平
* set GPIO5_DR to configure GPIO5_IO03 output 0
* GPIO5_DR 0x020AC000 + 0
* bit[3] = 0b0
* /
/* e. 设置 GPIO5_IO3输出高电平
* set GPIO5_DR to configure GPIO5_IO03 output 1
* GPIO5_DR 0x020AC000 + 0
* bit[3] = 0b1
* /
**步骤 1:**使能 GPIO1
设置 b[27:26]就可以使能 GPIO1,设置为什么值呢? 看下图,设置为 0b11:
① 00:该 GPIO模块全程被关闭
② 01:该 GPIO模块在 CPU run mode情况下是使能的;在 WAIT或 STOP模式下,关闭
③ 10:保留
④ 11:该 GPIO模块全程使能
/* GPIO1_IO03 */
/* a. 使能 GPIO1
* set CCM to enable GPIO1
* * CCM_CCGR1[CG13] 0x20C406C
* * bit[27:26] = 0b11
* */
步骤 2:
设置 GPIO1_IO03为 GPIO模式 设置如下寄存器:
/* b. 设置 GPIO1_IO03用于 GPIO
* set IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
* to configure GPIO1_IO03 as GPIO
* IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 0x20E0068
* bit[3:0] = 0b0101 alt5
*/
步骤 3:
设置 GPIO1_IO03为输出引脚,设置其输出电平 寄存器地址为:
设置方向寄存器,把引脚设置为输出引脚:
/* c. 设置 GPIO1_IO03作为 output引脚
* set GPIO1_GDIR to configure GPIO1_IO03 as output
* GPIO1_GDIR 0x0209C000 + 0x4
* bit[3] = 0b1
* /
/* d. 设置 GPIO1_DR输出低电平
* set GPIO1_DR to configure GPIO1_IO03 output 0
* GPIO1_DR 0x0209C000 + 0
* bit[3] = 0b0
* /
/* e. 设置 GPIO1_IO03输出高电平
* set GPIO1_DR to configure GPIO1_IO03 output 1
* GPIO1_DR 0x0209C000 + 0
* bit[3] = 0b1
* /
使用 GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\
05_嵌入式 Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\fire_imx6ull-pro_src_bin
硬件相关的文件是 board_fire_imx6ull-pro.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 };
105
led_operations结构体中有 init函数指针,它指向 board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为 GPIO模式、设置为输出引脚。
值得关注的是第 35~38行,对于寄存器要先使用 ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
21 static volatile unsigned int *CCM_CCGR1 ;
22 static volatile unsigned int *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
23 static volatile unsigned int *GPIO5_GDIR ;
24 static volatile unsigned int *GPIO5_DR ;
25
26 static int board_demo_led_init (int which) /* 初始化 LED, which-哪个 LED */
27 {
28 unsigned int val;
29
30 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
31 if (which == 0)
32 {
33 if (!CCM_CCGR1)
34 {
35 CCM_CCGR1 = ioremap(0x20C406C, 4);
36 IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = ioremap(0x2290014, 4);
37 GPIO5_GDIR = ioremap(0x020AC000 + 0x4, 4);
38 GPIO5_DR = ioremap(0x020AC000 + 0, 4);
39 }
40
41 /* GPIO5_IO03 */
42 /* a. 使能 GPIO5
43 * set CCM to enable GPIO5
44 * CCM_CCGR1[CG15] 0x20C406C
45 * bit[31:30] = 0b11
46 */
47 *CCM_CCGR1 |= (3<<30);
48
49 /* b. 设置 GPIO5_IO03用于 GPIO
50 * set IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3
51 * to configure GPIO5_IO03 as GPIO
52 * IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 0x2290014
53 * bit[3:0] = 0b0101 alt5
54 */
55 val = *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3;
56 val &= ~(0xf);
57 val |= (5);
58 *IOMUXC_SNVS_SW_MUX_CTL_PAD_SNVS_TAMPER3 = val;
59
60
61 /* b. 设置 GPIO5_IO03作为 output引脚
62 * set GPIO5_GDIR to configure GPIO5_IO03 as output
63 * GPIO5_GDIR 0x020AC000 + 0x4
64 * bit[3] = 0b1
65 */
66 *GPIO5_GDIR |= (1<<3);
67 }
68
69 return 0;
70 }
71
led_operations结构体中有 ctl函数指针,它指向 board_demo_led_ctl函数,在里面将会根据参数设置 LED引脚的输出电平:
72 static int board_demo_led_ctl (int which, char status) /* 控制 LED, which-哪个 LED, status:1-亮,0-灭 */
73 {
74 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
75 if (which == 0)
76 {
77 if (status) /* on: output 0*/
78 {
79 /* d. 设置 GPIO5_DR输出低电平
80 * set GPIO5_DR to configure GPIO5_IO03 output 0
81 * GPIO5_DR 0x020AC000 + 0
82 * bit[3] = 0b0
83 */
84 *GPIO5_DR &= ~(1<<3);
85 }
86 else /* off: output 1*/
87 {
88 /* e. 设置 GPIO5_IO3输出高电平
89 * set GPIO5_DR to configure GPIO5_IO03 output 1
90 * GPIO5_DR 0x020AC000 + 0
91 * bit[3] = 0b1
92 */
93 *GPIO5_DR |= (1<<3);
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.4.3.2 正点原子 Atk_imx6ull-alpha开发板
使用 GIT下载所有源码后,本节源码位于如下目录:
01_all_series_quickstart\
05_嵌入式 Linux驱动开发基础知识\source\02_led_drv\
02_led_drv_for_boards\atk_imx6ull-alpha_src_bin
硬件相关的文件是 board_atk_imx6ull-alpha.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 };
105
led_operations结构体中有 init函数指针,它指向 board_demo_led_init函数,在里面将会初始化LED引脚:使能、设置为 GPIO模式、设置为输出引脚。
值得关注的是第 35~38行,对于寄存器要先使用 ioremap得到它的虚拟地址,以后使用虚拟地址访问寄存器:
26 static int board_demo_led_init (int which) /* 初始化 LED, which-哪个 LED */
27 {
28 unsigned int val;
29
30 //printk("%s %s line %d, led %d\n", __FILE__, __FUNCTION__, __LINE__, which);
31 if (which == 0)
32 {
33 if (!CCM_CCGR1)
34 {
35 CCM_CCGR1 = ioremap(0x20C406C, 4);
36 IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = ioremap(0x20E0068, 4);
37 GPIO1_GDIR = ioremap(0x0209C000 + 0x4, 4);
38 GPIO1_DR = ioremap(0x0209C000 + 0, 4);
39 }
40
41 /* GPIO1_IO03 */
42 /* a. 使能 GPIO1
43 * set CCM to enable GPIO1
44 * CCM_CCGR1[CG13] 0x20C406C
45 * bit[27:26] = 0b11
46 */
47 *CCM_CCGR1 |= (3<<26);
48
49 /* b. 设置 GPIO1_IO03用于 GPIO
50 * set IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03
51 * to configure GPIO1_IO03 as GPIO
52 * IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 0x20E0068
53 * bit[3:0] = 0b0101 alt5
54 */
55 val = *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03;
56 val &= ~(0xf);
57 val |= (5);
58 *IOMUXC_SW_MUX_CTL_PAD_GPIO1_IO03 = val;
59
60
61 /* c. 设置 GPIO1_IO03作为 output引脚
62 * set GPIO1_GDIR to configure GPIO1_IO03 as output
63 * GPIO1_GDIR 0x0209C000 + 0x4
64 * bit[3] = 0b1 65 */
66 *GPIO1_GDIR |= (1<<3);
67 }
68
69 return 0;
70 }
71
led_operations结构体中有 ctl函数指针,它指向 board_demo_led_ctl函数,在里面将会根据参数设置 LED引脚的输出电平:
72 static int board_demo_led_ctl (int which, char status) /* 控制 LED, which-哪个 LED, status:1-亮,0-灭 */
73 {
74 //printk("%s %s line %d, led %d, %s\n", __FILE__, __FUNCTION__, __LINE__, which, status ? "on" : "off");
75 if (which == 0)
76 {
77 if (status) /* on: output 0*/
78 {
79 /* d. 设置 GPIO1_DR输出低电平
80 * set GPIO1_DR to configure GPIO1_IO03 output 0
81 * GPIO1_DR 0x0209C000 + 0
82 * bit[3] = 0b0
83 */
84 *GPIO1_DR &= ~(1<<3);
85 }
86 else /* off: output 1*/
87 {
88 /* e. 设置 GPIO1_IO03输出高电平
89 * set GPIO1_DR to configure GPIO1_IO03 output 1
90 * GPIO1_DR 0x0209C000 + 0
91 * bit[3] = 0b1
92 */
93 *GPIO1_DR |= (1<<3);
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
首先设置工具链,然后修改驱动程序 Makefile指定内核源码路径,就可以编译驱动程序和测试程序了。 启动开发板,挂载 NFS文件系统,这样就可以访问到 Ubuntu中的文件。 最后,就可以在开发板上进行下列测试。
注意:如果要使用板子自带的系统,关闭原有 LED驱动的方法是类似的,也是进入开发板/sys/class/leds/目录,对于每一个 LED在该目录下都有一个子目录,假设某个子目录名为 XXX,则执行如下命令:
# echo none > /sys/class/leds/XXX/trigger
使用我们的系统时,按如下操作。
要先禁止内核中原来的 LED驱动,把“heatbeat”功能关闭,执行以下命令即可:
# echo none > /sys/class/leds/cpu/trigger
这样就可以使用我们的驱动程序做实验了:
# insmod xxxxxx_led.ko
#./ledtest /dev/xxxxxx_led0 on
#./ledtest /dev/xxxxxx_led0 off
如果想恢复原来的心跳功能,可以执行:
# echo heartbeat > /sys/class/leds/cpu/trigger
注意:如果要使用板子自带的系统,关闭原有 LED驱动的方法是类似的,也是进入开发板/sys/class/leds/目录,对于每一个 LED在该目录下都有一个子目录,假设某个子目录名为 XXX,则执行如下命令:
# echo none > /sys/class/leds/XXX/trigger
使用我们的系统时,按如下操作。 要先禁止内核中原来的 LED驱动,把“heatbeat”功能关闭,执行以下命令即可:
# echo none > /sys/class/leds/sys-led/trigger
这样就可以使用我们的驱动程序做实验了:
# insmod xxxxxx_led.ko
# ./ledtest /dev/xxxxxx_led0 on
# ./ledtest /dev/xxxxxx_led0 off
如果想恢复原来的心跳功能,可以执行:
# echo heartbeat > /sys/class/leds/sys-led/trigger
a. 在驱动里有 ioremap,什么时候执行 iounmap?请完善程序
b. 视频里我们只实现了点一个 LED,开发板上也只有一个 LED, 所以,请修改代码操作蜂鸣器。