本文主要是uboot——uboot下GPIO控制的相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
PC端开发环境 Windows Windows11 Ubuntu Ubuntu20.04.2的64位版本 VMware® Workstation 17 Pro 17.6.0 build-24238078 终端软件 MobaXterm(Professional Edition v23.0 Build 5042 (license)) Win32DiskImager Win32DiskImager v1.0 Linux开发板环境 Linux开发板 正点原子 i.MX6ULL Linux 阿尔法开发板 uboot NXP官方提供的uboot,使用的uboot版本为U-Boot 2019.04 linux内核 linux-4.19.71(NXP官方提供)
点击查看本文参考资料
点击查看相关文件下载
这一节,来学习一下,怎么在uboot下新增一个命令来控制gpio。
一、硬件原理图 我们先看底板原理图(”阿尔法Linux开发板(A盘)-基础资料/02、开发板原理图/IMX6ULL_ALPHA_V2.2(底板原理图).pdf”
可以看到LED0 接到了 GPIO_3 上, 这个GPIO_3是哪个引脚?感觉这里标的有问题,我们搜索一下,会发现这里接在一个BTB连接器母座上,所以我们再去搜一些核心板原理图 “阿尔法Linux开发板(A盘)-基础资料/02、开发板原理图/IMX6ULL_CORE_V1.6(核心板原理图).pdf”:
就会发现GPIO_3 就是 GPIO1_IO03,根据底板原理图,当 GPIO1_IO03输出低电平(0)的时候发光二极管 LED0 就会导通点亮,GPIO1_IO03 输出高电平(1)的时候发光二极管 LED0 不会导通,因此 LED0 也就不会点亮。所以 LED0 的亮灭取决于 GPIO1_IO03的输出电平,输出 0 就亮,输出 1 就灭。
二、uboot的gpio控制命令 1. 命令格式 我看在我使用的uboot 2019.04中是自带一个gpio命令的:
1 2 3 4 5 6 7 => gpio gpio - query and control gpio pins Usage: gpio <input|set|clear|toggle> <pin> - input/set/clear/toggle the specified pin gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs
这里可以控制某一个GPIO为输入模式、输出模式,为输出的时候可以使用set命令配置引脚输出高电平,使用clear命令配置引脚输出低电平,使用toggle命令来反转GPIO的输出电平。还可以通过status来查看GPIO的引脚状态。
2. 使用实例 2.1 配置GPIO输出高 1 2 => gpio set GPIO1_3 gpio: pin GPIO1_3 (gpio 3) value is 1
2.2 配置GPIO输出低 1 2 => gpio clear GPIO1_3 gpio: pin GPIO1_3 (gpio 3) value is 0
2.3 翻转GPIO电平 1 2 3 4 => gpio toggle GPIO1_3 gpio: pin GPIO1_3 (gpio 3) value is 0 => gpio toggle GPIO1_3 gpio: pin GPIO1_3 (gpio 3) value is 1
2.4 查看GPIO状态 1 2 3 4 5 6 7 8 9 10 11 12 13 => gpio status -a Bank GPIO1_: GPIO1_0: input: 1 [ ] # ...... GPIO1_3: output: 1 [ ] # ...... GPIO1_7: input: 0 [ ] GPIO1_8: output: 1 [x] backlight GPIO1_9: output: 1 [x] regulator@1.gpio GPIO1_10: input: 0 [ ] # ...... GPIO1_19: input: 0 [x] usdhc@02190000.cd-gpios
从这里可以看到这个GPIO是输入还是释出模式以及是否使用了复用功能。
三、gpio命令的定义 这里需要先回顾一下C语言宏定义中的#和##:
(1)#的作用是转换为字符串。
(2)##的作用是连接前后的内容。
1. gpio命令变量的定义 我们先看一下gpio命令的定义,定义在gpio.c - cmd/gpio.c 中:
1 2 3 4 5 U_BOOT_CMD(gpio, 4 , 0 , do_gpio, "query and control gpio pins" , "<input|set|clear|toggle> <pin>\n" " - input/set/clear/toggle the specified pin\n" "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs" );
毫无疑问,下一步就是把这个U_BOOT_CMD
宏展开,看看这段代码到底干了什么。
2. U_BOOT_CMD 2.1 相关的几个宏定义 2.1.1 U_BOOT_CMD 先来看一下U_BOOT_CMD这个宏,它定义在 command.h - include/command.h 中:
1 2 3 4 5 6 7 8 9 10 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
这里下面还嵌套了一个宏U_BOOT_CMD_COMPLETE。
2.1.2 U_BOOT_CMD_COMPLETE 我们再找一下这个宏的位置,在uboot 2019.14的版本中,这个宏有两个定义,都定义在command.h - include/command.h 文件中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 #ifdef CONFIG_CMDLINE #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp) \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) } #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(cmd_tbl_t, _name, cmd) = \ U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp); #else #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, \ _comp) \ _CMD_REMOVE(sub_ ## _name, _cmd) #endif
那么用的是哪一个?我们需要去搜一下CONFIG_CMDLINE这个宏定义,这个宏我们需要将uboot编译一遍,编译命令嘛,我们就用之前NXP的哪个评估板的配置文件的编译命令吧:
1 2 3 make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- distclean make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- mx6ull_14x14_evk_defconfig make V=0 ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -j16
编译之后在源码顶层目录下会生成.config文件,在这个文件中有它的定义:
所以U_BOOT_CMD_COMPLETE使用的是上面的定义,也就是:
1 2 3 4 #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(cmd_tbl_t, _name, cmd) = \ U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp);
可以看到里面还有一层U_BOOT_CMD_MKENT_COMPLETE。我们继续找。
2.1.3 U_BOOT_CMD_MKENT_COMPLETE 它也是根据CONFIG_CMDLINE有不同的定义,这里的定义是command.h - include/command.h :
1 2 3 4 5 #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp) \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) }
然后就发现这里还有一层_CMD_HELP和_CMD_COMPLETE。
2.1.4 _CMD_HELP 这个宏也定义在command.h - include/command.h :
1 2 3 4 5 #ifdef CONFIG_SYS_LONGHELP # define _CMD_HELP(x) x, #else # define _CMD_HELP(x) #endif
可以看到具体的定义是CONFIG_SYS_LONGHELP控制,还是去.config文件找,会发现这个配置项为y,所以这里对应的宏为:
1 # define _CMD_HELP(x) x,
2.1.5 _CMD_COMPLETE 这个同样是定义在command.h - include/command.h :
1 2 3 4 5 #ifdef CONFIG_AUTO_COMPLETE # define _CMD_COMPLETE(x) x, #else # define _CMD_COMPLETE(x) #endif
这里一样的方式,最后确定CONFIG_AUTO_COMPLETE=y,所以这里就是:
1 # define _CMD_COMPLETE(x) x,
2.1.6 ll_entry_declare 接下来看一下ll_entry_declare是什么,它也是一个宏,它定义在linker_lists.h - include/linker_lists.h :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 #define ll_entry_declare(_type, _name, _list) \ _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" #_list"_2_" #_name)))
这里是在做什么?我们可以看上面的注释,翻译一下就是:该宏声明了一个变量,该变量放置在链接器生成的数组中。这是更高级地使用链接器生成的数组的基本构建块。用户需要围绕这个宏包装器构建自己的宏包装器。使用此宏声明的变量必须在编译时初始化。好像没怎么看懂,其实就是声明一个_type类型的变量是这样的:
1 2 3 4 5 6 _type _u_boot_list_2_##_list##_2_##_name __aligned(4 ) __attribute__((unused, section(".u_boot_list_2_" #_list"_2_" #_name)))
使用此宏声明的变量必须在编译时初始化。使用此宏时必须特别注意:
(1)_type不能包含“static”关键字,否则生成的条目可以被迭代,但会列在map文件中,不能通过名称检索。
(2)如果声明一个section包含一些数组元素,并且声明该section的一个子节包含一些元素,则这些元素必须具有相同的类型。
(3)如果声明了包含一些数组元素的外部分段,并且声明了该分段的内部分段并包含一些元素,则遍历外部分段时,甚至内部分段的元素都存在于数组中。
2.2 展开这几个宏 我们一层一层向上展开。
2.2.1 U_BOOT_CMD_MKENT_COMPLETE 有这两个宏:
1 2 # define _CMD_HELP(x) x, # define _CMD_COMPLETE(x) x,
我们可以展开U_BOOT_CMD_MKENT_COMPLETE:
1 2 3 4 5 6 7 8 9 10 11 12 13 #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp) \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _CMD_HELP(_help) _CMD_COMPLETE(_comp) } #define U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp) \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _help, _comp,}
2.2.2 U_BOOT_CMD_COMPLETE 接下来是U_BOOT_CMD_COMPLETE,有上面的U_BOOT_CMD_MKENT_COMPLETE可以得到:
1 2 3 4 5 6 7 8 9 10 11 #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(cmd_tbl_t, _name, cmd) = \ U_BOOT_CMD_MKENT_COMPLETE(_name, _maxargs, _rep, _cmd, \ _usage, _help, _comp); #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ ll_entry_declare(cmd_tbl_t, _name, cmd) = \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _help, _comp,}
2.2.3 ll_entry_declare 我们吧这个ll_entry_declare也展开:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define ll_entry_declare(_type, _name, _list) \ _type _u_boot_list_2_##_list##_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" #_list"_2_" #_name))) #define ll_entry_declare(cmd_tbl_t, _name, cmd) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" "cmd" "_2_" #_name))) #define U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, _comp) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" "cmd" "_2_" #_name))) = \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _help, _comp,}
2.2.4 U_BOOT_CMD 最后一步就是把U_BOOT_CMD展开了,根据前面的U_BOOT_CMD_MKENT_COMPLETE展开形式,可以得到:
1 2 3 4 5 6 7 8 9 10 11 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL) #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" "cmd" "_2_" #_name))) = \ { #_name, _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _help, NULL,}
我们知道#就是转换为字符串,这个时候可以进一步把#替换掉:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, \ section(".u_boot_list_2_" "cmd" "_2_" "_name" ))) = \ { "_name" , _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _help, NULL,} #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, section(".u_boot_list_2_" "cmd" "_2_" "_name" ))) = \ { \ "_name" , \ _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, \ _usage, \ _help, \ NULL,}
2.3 gpio命令变量 2.3.1 变量定义展开 上面已经把U_BOOT_CMD展开了,我们现在根据gpio命令变量的定义,对变量进行替换,前面已经看到了gpio命令的定义,定义在gpio.c - cmd/gpio.c 中:
1 2 3 4 5 U_BOOT_CMD(gpio, 4 , 0 , do_gpio, "query and control gpio pins" , "<input|set|clear|toggle> <pin>\n" " - input/set/clear/toggle the specified pin\n" "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs" );
里面的各个变量对应关系是这样的:
然后我们完全展开,可以得到gpio命令变量的定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #define U_BOOT_CMD(gpio, 4, 0, do_gpio, "usage" , "help" ) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, section(".u_boot_list_2_" "cmd" "_2_" "_name" ))) = \ { \ "_name" , \ _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, _usage, _help, NULL,} #define U_BOOT_CMD(gpio, 4, 0, do_gpio, "usage" , "help" ) \ cmd_tbl_t _u_boot_list_2_cmd_2_gpio __aligned(4) \ __attribute__((unused, section(".u_boot_list_2_" "cmd" "_2_" "gpio" ))) = \ { \ "gpio" , \ 4, \ 0 ? cmd_always_repeatable : cmd_never_repeatable, \ do_gpio, \ "query and control gpio pins" , \ "<input|set|clear|toggle> <pin>\n" \ " - input/set/clear/toggle the specified pin\n" \ "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs" , \ NULL,}
把宏的标识去掉,就是
1 2 3 4 5 6 7 8 9 10 11 12 13 14 cmd_tbl_t _u_boot_list_2_cmd_2_gpio __aligned(4 ) __attribute__((unused, section(".u_boot_list_2_cmd_2_gpio" ))) = { "gpio" , 4 , cmd_never_repeatable, do_gpio, "query and control gpio pins" , "<input|set|clear|toggle> <pin>\n" " - input/set/clear/toggle the specified pin\n" "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs" , NULL ,};
我们可以去看一下map文件(01_uboot/01_gpio_cmd/u-boot.map · 苏木/imx6ull-driver-demo - 码云 - 开源中国 (gitee.com) ),搜索关键字就会发现里面有这个命令在:
这也可以证明前面宏展开是正确的。
2.3.2 cmd_tbl_t类型 可以看到上面的变量类型是cmd_tbl_t,它是一个结构体类型,定义在command.h - include/command.h :
1 typedef struct cmd_tbl_s cmd_tbl_t ;
最终的定义是在这里command.h - include/command.h :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 struct cmd_tbl_s { char *name; int maxargs; int (*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc, char * const argv[], int *repeatable); int (*cmd)(struct cmd_tbl_s *, int , int , char * const []); char *usage; #ifdef CONFIG_SYS_LONGHELP char *help; #endif #ifdef CONFIG_AUTO_COMPLETE int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]); #endif };
前面分析过,CONFIG_SYS_LONGHELP和CONFIG_AUTO_COMPLETE都有定义,所以这里就是:
1 2 3 4 5 6 7 8 9 struct cmd_tbl_s { char *name; int maxargs; int (*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc, char * const argv[], int *repeatable); int (*cmd)(struct cmd_tbl_s *, int , int , char * const []); char *usage; char *help; int (*complete)(int argc, char * const argv[], char last_char, int maxv, char *cmdv[]); };
可以看到这里有三个函数指针,其中complete指针由于U_BOOT_CMD_COMPLETE宏中固定为NULL,这里可以不用关心。还有两个函数指针cmd_rep和cmd,我们接着看一下。
2.4 两个函数指针 2.4.1 cmd_rep 2.4.1.1 函数指针定义 这个函数指针定义为:
1 int (*cmd_rep)(struct cmd_tbl_s *cmd, int flags, int argc, char * const argv[], int *repeatable);
这个函数指针含有5个形参,返回值为int类型。它主要控制是否自动重复(按Enter键是否会重复执行)命令。在命令定义的时候这里主要是根据_rep的值来决定调用哪个函数。
1 2 3 4 5 6 7 8 9 10 11 #define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help) \ cmd_tbl_t _u_boot_list_2_cmd_2_##_name __aligned(4) \ __attribute__((unused, section(".u_boot_list_2_" "cmd" "_2_" "_name" ))) = \ { \ "_name" , \ _maxargs, \ _rep ? cmd_always_repeatable : cmd_never_repeatable, \ _cmd, \ _usage, \ _help, \ NULL,}
2.4.1.2 cmd_always_repeatable 看一下cmd_always_repeatable,它定义在command.c - common/command.c
1 2 3 4 5 6 7 8 int cmd_always_repeatable (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], int *repeatable) { *repeatable = 1 ; return cmdtp->cmd(cmdtp, flag, argc, argv); }
2.4.1.3 cmd_never_repeatable cmd_never_repeatable定义在command.c - common/command.c :
1 2 3 4 5 6 7 int cmd_never_repeatable (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[], int *repeatable) { *repeatable = 0 ; return cmdtp->cmd(cmdtp, flag, argc, argv); }
从gpio的命令变量定义来看,这里调用的就是这个函数了。
2.4.2 cmd 2.4.2.1 函数指针定义 这个函数指针定义为:
1 int (*cmd)(struct cmd_tbl_s *, int , int , char * const []);
它有四个参数,返回值为int类型。这个函数指针就是命令的实现过程,我么敲了这个命令之后要执行什么,就在这个函数指针指向的函数中实现。
2.4.2.2 do_gpio 前面展开的时候可以看到这个cmd函数指针指向的是do_gpio函数,它定义在gpio.c - cmd/gpio.c
1 static int do_gpio (cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[]) ;
3. 总结一下吧 这里总结一下吧,总的来说上面那么多就是分析了一条语句:
1 2 3 4 5 U_BOOT_CMD(gpio, 4 , 0 , do_gpio, "query and control gpio pins" , "<input|set|clear|toggle> <pin>\n" " - input/set/clear/toggle the specified pin\n" "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs" );
这条语句经过层层展开之后,实际上是定义了一个结构体变量:
1 2 3 4 5 6 7 8 9 10 11 cmd_tbl_t _u_boot_list_2_cmd_2_gpio __aligned(4 ) __attribute__((unused, section(".u_boot_list_2_cmd_2_gpio" ))) = { "gpio" , 4 , cmd_never_repeatable, do_gpio, "query and control gpio pins" , "<input|set|clear|toggle> <pin>\n" " - input/set/clear/toggle the specified pin\n" "gpio status [-a] [<bank> | <pin>] - show [all/claimed] GPIOs" , NULL ,};
这个结构体变量名是_u_boot_list_2_cmd_2_gpio
,类型是cmd_tbl_t
,这个变量在内存中是四字节对齐的,通过__attribute__
给这个结构体变量设置了两个属性:
unused属性:这个变量如果没有被使用,编译器不会报“变量从未使用“的警告
section属性:".u_boot_list_2_cmd_2_gpio"
属性会将这个变量放置到可执行程序的".u_boot_list_2_cmd_2_gpio"
字段下,与.text段或.data等其他段独立出来
参考资料:
petalinux - u-boot中操作gpio_uboot gpio操作-CSDN博客
6. Uboot的GPIO控制 — [野火]嵌入式Linux镜像构建与部署——基于LubanCat-i.MX6ULL开发板 文档 (embedfire.com)
u-boot的命令实现(添加自定义命令)_uboot 自定义 函数-CSDN博客
Linux下uboot添加自定义命令(详细)实例及原理解析_怎么修改uboot指令-CSDN博客
链接脚本.lds(详细)总结附实例快速掌握-CSDN博客
9.uboot命令体系 源码解读并从中添加命令_uboot让参数等于命令返回值-CSDN博客
u-boot的linker list源码分析_uboot env callback-CSDN博客
uboot源码分析uboot启动流程,uboot-CMD命令调用关系_u-boot源码分析-CSDN博客