<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[飞凌OKT527开发板U-Boot添加自定义菜单]]></title><description><![CDATA[<p dir="auto">昨日，终于收到了心心念念的飞凌OK-T527开发板，板子很漂亮，外设丰富，性能强悍，T527创新性地使用了RISC-V架构的协处理器，后期值得研究一下异核的使用：</p>
<p dir="auto"><img src="/assets/uploads/files/1719493229545-f45322ef-1664-412b-b7a3-65855d79ebc2-8f951346f411a737513456c04543703-resized.jpg" alt="f45322ef-1664-412b-b7a3-65855d79ebc2-8f951346f411a737513456c04543703.jpg" class=" img-responsive img-markdown" /></p>
<p dir="auto">有趣的是，板子上电，按任意键进入U-Boot会自动列出一个功能菜单，有切换屏幕等功能：</p>
<p dir="auto"><img src="/assets/uploads/files/1719493617420-6b76d7f1-f33c-4d7c-8dc0-3d751d5fc0cd-image.png" alt="6b76d7f1-f33c-4d7c-8dc0-3d751d5fc0cd-image.png" class=" img-responsive img-markdown" width="563" height="214" /><br />
基于此，本文将分析如何在U-Boot添加自定义菜单。</p>
<p dir="auto"><strong>一、实验环境介绍</strong><br />
硬件：飞凌OK-T527开发板<br />
软件：全志Longan SDK（U-Boot版本2018）<br />
说明：本次实验不限制平台，请参考实际情况阅读。</p>
<p dir="auto"><strong>二、目标</strong><br />
本文主要分析U-Boot在程序中的执行顺序，又如何在U-Boot阶段调起菜单？相信大家都试过，在U-Boot倒数结束前按任意按键后，会进入U-Boot命令行模式。<br />
这里先留一个问题：如何做到按键按下后，调启的是自己的U-Boot菜单，而不再是进入命令行模式？</p>
<p dir="auto"><strong>三、U-Boot如何自动调起菜单</strong><br />
U-Boot的入口程序文件是 <strong>&lt;u-boot&gt;/common/main.c</strong>，入口函数<strong>main_loop()</strong>：</p>
<pre><code>/* &lt;u-boot&gt;/common/main.c */

...

/* We come here after U-Boot is initialised and ready to process commands */
/* 在U-Boot初始化并准备好处理命令之后，我们来到这里。 */
void main_loop(void)
{
    const char *s;

    bootstage_mark_name(BOOTSTAGE_ID_MAIN_LOOP, "main_loop");	

    #ifdef CONFIG_VERSION_VARIABLE
    env_set("ver", version_string);  /* set version variable */		
    #endif /* CONFIG_VERSION_VARIABLE */

    cli_init();							//命令初始化有关，初始化 hush shell 相关的变量

    run_preboot_environment_command();	//获取环境变量 perboot 的内容

    #if defined(CONFIG_UPDATE_TFTP)
    update_tftp(0UL, NULL, NULL);		
    #endif /* CONFIG_UPDATE_TFTP */

    s = bootdelay_process();	//此函数会读取环境变量 bootdelay 和 bootcmd 的内容
    if (cli_process_fdt(&amp;s))
        cli_secure_boot_cmd(s);

    autoboot_command(s);		//开启倒计时，并在倒计时结束前检测是否有按键按下

    cli_loop();					//命令行处理函数（即进入U-Boot命令行）
    panic("No CLI available");
}
</code></pre>
<p dir="auto">关键函数是<strong>autoboot_command()</strong>，该函数的实现在 <strong>&lt;u-boot&gt;/common/autoboot.c</strong>：</p>
<pre><code>/* &lt;u-boot&gt;/common/autoboot.c */

...

void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "&lt;UNDEFINED&gt;");

    if (stored_bootdelay != -1 &amp;&amp; s &amp;&amp; !abortboot(stored_bootdelay)) {	// 倒计时过程中，没有按键按下
#if defined(CONFIG_AUTOBOOT_KEYED) &amp;&amp; !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);		// 倒计时结束后，启动内核

#if defined(CONFIG_AUTOBOOT_KEYED) &amp;&amp; !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

#ifdef CONFIG_MENUKEY
    if (menukey == CONFIG_MENUKEY) {
        s = env_get("menucmd");
        if (s)
            run_command_list(s, -1, 0);
    }
#endif /* CONFIG_MENUKEY */
}
</code></pre>
<p dir="auto">进入 <strong>autoboot_command()</strong> 后，先看第一个if：</p>
<pre><code>void autoboot_command(const char *s)
{
    ...
        
    if (stored_bootdelay != -1 &amp;&amp; s &amp;&amp; !abortboot(stored_bootdelay))

    ...
}
</code></pre>
<p dir="auto">这里有三个条件：<br />
● <strong>stored_bootdelay != -1</strong>：stored_bootdelay是倒数的总时间，就是常见的3秒、5秒不等；<br />
● <strong>s</strong>：传进来的参数s不能为空；<br />
● <strong>!abortboot(stored_bootdelay)</strong>：该函数会从stored_bootdelay开始倒计时，期间判断是否有按键按下。函数实现如下，倒计时过程中若检测到按键按下，则令abort=1。无按键按下，则abort=0。最后返回abort。</p>
<pre><code>/* &lt;u-boot&gt;/common/autoboot.c */

...

static int __abortboot(int bootdelay)
{
        int abort = 0;
        unsigned long ts;

#ifdef CONFIG_MENUPROMPT
        printf(CONFIG_MENUPROMPT);
#else
        printf("Hit any key to stop autoboot: %2d ", bootdelay);
#endif

        /*
         * Check if key already pressed
         */
        if (tstc()) {   /* we got a key press   */
                (void) getc();  /* consume input        */
                puts("\b\b\b 0");
                abort = 1;      /* don't auto boot      */
        }

        while ((bootdelay &gt; 0) &amp;&amp; (!abort)) {
                --bootdelay;
                /* delay 1000 ms */
                ts = get_timer(0);
                do {
                        if (tstc()) {   /* we got a key press   */
                                abort  = 1;     /* don't auto boot      */
                                bootdelay = 0;  /* no more delay        */
# ifdef CONFIG_MENUKEY
                                menukey = getc();
# else
                                (void) getc();  /* consume input        */
# endif
                                break;
                        }
                        udelay(10000);
                } while (!abort &amp;&amp; get_timer(ts) &lt; 1000);

                printf("\b\b\b%2d ", bootdelay);
        }

        putc('\n');

        return abort;
}

...

</code></pre>
<p dir="auto">刚刚说了，<strong>abortboot()</strong> 函数执行期间有按键按下的话，<strong>abortboot()</strong> 会返回1，那就不会进入第一个if，程序会接着往下运行直至该函数运行结束。<strong>autoboot_command()</strong> 结束后继续返回到<strong>main_loop()</strong>，随后立刻执行<strong>cli_loop()</strong>，进入我们所熟悉的U-Boot命令行模式。<br />
<img src="/assets/uploads/files/1719495045144-7c739c2a-c80b-4925-b5fc-951173f5fb78-image.png" alt="7c739c2a-c80b-4925-b5fc-951173f5fb78-image.png" class=" img-responsive img-markdown" width="688" height="218" /><br />
至此，就实现了U-Boot倒数期间，有按键按下，则进入U-Boot的命令行模式。<br />
现在继续回到第一个if：</p>
<pre><code>/* &lt;u-boot&gt;/common/autoboot.c */

void autoboot_command(const char *s)
{
    ...
        
    if (stored_bootdelay != -1 &amp;&amp; s &amp;&amp; !abortboot(stored_bootdelay)) {	// 倒计时过程中，没有按键按下
#if defined(CONFIG_AUTOBOOT_KEYED) &amp;&amp; !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);		// 倒计时结束后，启动内核

#if defined(CONFIG_AUTOBOOT_KEYED) &amp;&amp; !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

    ...
}
</code></pre>
<p dir="auto">如果在autoboot倒计时结束前，一直没有按键按下呢？那 <strong>abortboot()</strong> 最后会返回0，第一个if的三个条件全部满足。进入if，<strong>run_command_list()</strong> 执行一系列命令后，启动内核。注意，这里的现象是直接启动内核，<strong>run_command_list()</strong> 后的程序不再执行。</p>
<p dir="auto">解析到这里，我们得出一个结论：在autoboot倒计时中，如果有按键按下的话，会进入U-Boot的命令行模式。无按键按下则在倒计时结束后直接启动内核。<br />
那现在可以回答第一个问题，如何做到按下按键后，是自启动U-Boot菜单，而不是进入U-Boot命令行呢？答案是在执行cli_loop()之前，我们可以在autoboot检测到按键按下后，调用run_command()函数执行menu命令，从而调起菜单。</p>
<pre><code>void autoboot_command(const char *s)
{
    debug("### main_loop: bootcmd=\"%s\"\n", s ? s : "&lt;UNDEFINED&gt;");

    if (stored_bootdelay != -1 &amp;&amp; s &amp;&amp; !abortboot(stored_bootdelay)) {
#if defined(CONFIG_AUTOBOOT_KEYED) &amp;&amp; !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    int prev = disable_ctrlc(1);    /* disable Control C checking */
#endif

    run_command_list(s, -1, 0);

#if defined(CONFIG_AUTOBOOT_KEYED) &amp;&amp; !defined(CONFIG_AUTOBOOT_KEYED_CTRLC)
    disable_ctrlc(prev);    /* restore Control C checking */
#endif
    }

    //在此处启动菜单
    run_command("menu", 0);
    
#ifdef CONFIG_MENUKEY
    if (menukey == CONFIG_MENUKEY) {
        s = env_get("menucmd");
        if (s)
            run_command_list(s, -1, 0);
    }
#endif /* CONFIG_MENUKEY */
}
</code></pre>
<p dir="auto"><strong>四、U-Boot添加自定义命令</strong><br />
难道通过 <strong>run_command()</strong> 执行menu命令后，菜单就自己出来了？这是一个理所当然的猜想。实际上U-Boot根本不认识menu命令：<br />
<img src="/assets/uploads/files/1719495382061-609ad0a8-dbdb-4e12-b3a0-7d5886c4c9f0-image.png" alt="609ad0a8-dbdb-4e12-b3a0-7d5886c4c9f0-image.png" class=" img-responsive img-markdown" width="460" height="94" /><br />
接下来看看如何添加U-Boot命令，参考一下别人的代码：</p>
<pre><code>/* &lt;u-boot&gt;/board/BuS/eb_cpu5282/eb_cpu5282.c */

int do_brightness(cmd_tbl_t *cmdtp, int flag, int argc, char * const argv[])
{
	int rcode = 0;
	ulong side;
	ulong bright;

	switch (argc) {
	case 3:
		side = simple_strtoul(argv[1], NULL, 10);
		bright = simple_strtoul(argv[2], NULL, 10);
		if ((side &gt;= 0) &amp;&amp; (side &lt;= 3) &amp;&amp;
			(bright &gt;= 0) &amp;&amp; (bright &lt;= 1000)) {
			vcxk_setbrightness(side, bright);
			rcode = 0;
		} else {
			printf("parameters out of range\n");
			printf("Usage:\n%s\n", cmdtp-&gt;usage);
			rcode = 1;
		}
		break;
	default:
		printf("Usage:\n%s\n", cmdtp-&gt;usage);
		rcode = 1;
		break;
	}
	return rcode;
}

U_BOOT_CMD(
	bright,	3,	0,	do_brightness,
	"here is uboot mymenu\n",
	"here is uboot mymenu, make in 2024-05-15\n"
);
</code></pre>
<p dir="auto">先看最底下的<strong>U_BOOT_CMD</strong>，这是一个宏，用来添加U-Boot命令：</p>
<pre><code>/* &lt;u-boot&gt;/include/command.h */

#define U_BOOT_CMD(_name, _maxargs, _rep, _cmd, _usage, _help)		\
	U_BOOT_CMD_COMPLETE(_name, _maxargs, _rep, _cmd, _usage, _help, NULL)
</code></pre>
<p dir="auto">● <strong>_name</strong>：命令的名字<br />
● <strong>_maxargs</strong>：添加的命令最多有几个参数<br />
● <strong>_rep</strong>：是否重复（1重复，0不重复），指在U-Boot命令行按下Enter键的时候，重复执行上次的命令<br />
● <strong>_cmd</strong>：执行函数（即执行该命令后，运行哪个函数）<br />
● <strong>_usage</strong>：短帮助信息<br />
● <strong>_help</strong>：长帮助信息</p>
<p dir="auto">再来看看执行函数<strong>do_brightness</strong>的声名：</p>
<pre><code>int (*cmd)(struct cmd_tbl_s *cmdtp, int flag, int argc, char *const argv[]);
</code></pre>
<p dir="auto">● <strong>cmdtp</strong>：Table entry describing the command (see above).<br />
● <strong>flag</strong>：A bitmap which may contain the following bit<br />
○ CMD_FLAG_REPEAT  - The last command is repeated.<br />
○ CMD_FLAG_BOOTD  - The command is called by the bootd command.<br />
○ CMD_FLAG_ENV       - The command is called by the run command.<br />
● <strong>argc</strong>：执行命令时，传入的参数数量<br />
● <strong>argv</strong>：传入的参数</p>
<p dir="auto"><strong>五、实践</strong><br />
下面，添加一个U-Boot菜单，不过只作打印，没有实际功能。<br />
在 <strong>&lt;u-boot&gt;/drivers</strong> 下创建一个名为<strong>mymenu</strong>的文件夹：<br />
<img src="/assets/uploads/files/1719495902868-36675d20-81f2-4cdd-bca7-b96f7060954d-image.png" alt="36675d20-81f2-4cdd-bca7-b96f7060954d-image.png" class=" img-responsive img-markdown" width="1179" height="193" /><br />
在<strong>mymenu</strong>文件夹下创建<strong>mymenu.c</strong>，内容如下：</p>
<pre><code>#include &lt;common.h&gt;
#include &lt;command.h&gt;
#include &lt;linux/ctype.h&gt;
#include &lt;cli.h&gt;
#include &lt;fs.h&gt;

static int do_mymenu(struct cmd_tbl_s *cmdtp, int flag, int argc, char *const argv[])
{

    printf("\n======== Title ========\n"); 
    printf("== [1] xxxxxx\n");
    printf("== [2] xxxxxx\n");
    printf("== [3] xxxxxx\n");
    printf("== [4] xxxxxx\n");
    printf("=========================\n\n");

    return 0;
}

U_BOOT_CMD(
	menu, 1, 1, do_mymenu,
	"here is uboot menu\n",
	"here is uboot menu, make in 2024-06-27\n"
);
</code></pre>
<p dir="auto">还需在<strong>mymenu</strong>文件夹下创建一个Makefile文件，内容如下：</p>
<pre><code>obj-y += mymenu.o
</code></pre>
<p dir="auto">最后修改 <strong>&lt;u-boot&gt;/drivers/</strong> 下的Makefile，在结尾加上如下内容，表示要编译mymenu路径下的文件：</p>
<pre><code>ifeq ($(CONFIG_SPL_BUILD)$(CONFIG_TPL_BUILD),)
obj-y += mymenu/
endif
</code></pre>
<p dir="auto"><img src="/assets/uploads/files/1719496211586-a316bdca-70e6-4150-83dc-2003e98addd2-image.png" alt="a316bdca-70e6-4150-83dc-2003e98addd2-image.png" class=" img-responsive img-markdown" width="683" height="180" /><br />
编译U-Boot，更新U-Boot，重启单板，在U-Boot倒计时结束前，按任意按键就进入我们自定义的菜单：<br />
<img src="/assets/uploads/files/1719497489598-b1c26ea8-400f-4a58-8108-5f4939100cda-image.png" alt="b1c26ea8-400f-4a58-8108-5f4939100cda-image.png" class=" img-responsive img-markdown" width="617" height="278" /><br />
剩下的菜单程序编写就是根据实际功能来开发了。</p>
<p dir="auto"><strong>六、总结</strong><br />
若有描述错误欢迎指出。</p>
]]></description><link>https://bbs.aw-ol.com/topic/5524/飞凌okt527开发板u-boot添加自定义菜单</link><generator>RSS for Node</generator><lastBuildDate>Sun, 19 Apr 2026 05:01:40 GMT</lastBuildDate><atom:link href="https://bbs.aw-ol.com/topic/5524.rss" rel="self" type="application/rss+xml"/><pubDate>Thu, 27 Jun 2024 14:13:45 GMT</pubDate><ttl>60</ttl></channel></rss>