将uboot2010.03移植到mini2440上,选择LCD作为输出设备,串口作为输入设备。LCD分辨率240*320。在lcd的命令行界面出现了一个问题:当命令长度超过一行转到下一行时,输入回车,执行结果会将命令本身的最后一行覆盖。比如:命令本身一共2行,则执行后第2行,被覆盖;命令本身3行,则前2行没问题,第3行被覆盖。这个问题困扰了很久,但是今天终于找到原因了。
首先分析一下管理命令行界面的部分。uboot进入命令行界面的过程为:reset->start_armboot->main_loop->readline。界面的交互处理就是在readline()函数中实现的。然后readline函数本身很简单,就是调用readlinr_into_buffer(),在readlinr_into_buffer()中有一段处理特殊字符的代码:
c = getc();
/*
* Special character handling
*/
switch (c) {
case '\r':
/* Enter */
case '\n':
*p = '\0';
puts ("\r\n");
return (p - p_buf);
case '\0':
/* nul */
continue;
case 0x03:
/* ^C - break */
p_buf[0] = '\0';
/* discard input */
return (-1);
case 0x15:
/* ^U - erase line */
while (col > plen) {
puts (erase_seq);
--col;
}
p = p_buf;
n = 0;
continue;
case 0x17:
/* ^W - erase word */
p=delete_char(p_buf, p, &col, &n, plen);
while ((n > 0) && (*p != ' ')) {
p=delete_char(p_buf, p, &col, &n, plen);
}
continue;
case 0x08:
/* ^H - backspace */
case 0x7F:
/* DEL - backspace */
p=delete_char(p_buf, p, &col, &n, plen);
continue;
default:
/*
* Must be a normal character then
*/
if (n < CONFIG_SYS_CBSIZE-2) {
if (c == '\t') {
/* expand TABs */
#ifdef CONFIG_AUTO_COMPLETE
/* if auto completion triggered just continue */
/* *p = '\0';
if (cmd_auto_complete(prompt, console_buffer, &n, &col)) {
p = p_buf + n;
//* reset /
continue;
} */
#endif
puts (tab_seq+(col&07));
col += 8 - (col&07);
} else {
++col;
/* echo input */
putc (c);
}
*p++ = c;
++n;
} else {
/* Buffer full */
putc ('\a');
}
}
其中#ifdef CONFIG_AUTO_COMPLETE是预编译,附加其他功能模块,这里可以忽略不看,对我们今天的话题没有影响。
case '\r':
/* Enter */
case '\n':
*p = '\0';
puts ("\r\n");
从上面这一小段代码,可以看到,对于回车键(输入为'\n')的处理是输出puts ("\r\n");即执行两个控制符,先是回车,然后是换行,一定要注意输出的顺序,这个对我们要分析的问题很重要。
然后我们再来分析一下puts()函数。因为我们使用LCD作为输出设备,实际上puts()函数调用的就是LCD的输出函数:video_puts()->video_putc()这两个函数都在文件drivers/video/cfb_console.c中。
void video_putc (const char c)
{
static int nl = 1;
switch (c) {
case 13:
/* back to first column */
console_cr ();
break;
case '\n':
/* next line */
if (console_col || (!console_col && nl))
console_newline ();
nl = 1;
break;
case 9:
/* tab 8 */
CURSOR_OFF console_col |= 0x0008;
console_col &= ~0x0007;
if (console_col >= CONSOLE_COLS)
console_newline ();
break;
case 8:
/* backspace */
console_back ();
break;
default:
/* draw the char */
video_putchar (console_col * VIDEO_FONT_WIDTH,
console_row * VIDEO_FONT_HEIGHT,
c);
console_col++;
/* check for newline */
if (console_col >= CONSOLE_COLS) {
console_newline ();
nl = 0;
}
}
CURSOR_SET
}
我们来分析一下该函数对于'\n'的处理:
case '\n':
/* next line */
if (console_col || (!console_col && nl))
console_newline ();
nl = 1;
break;
该代码段的关键在于if语句的条件
其中console_col下一个字符的横坐标,nl为一个标志为,当换行是因为控制字符'\n'时,将nl置1;当换行是因为显示的内容超过一行,自动换行转到下一行输出时将nl置0。if (console_col || (!console_col && nl))中的一种情况可以对应为,横坐标在最左端且上一个换行是因为超出一行范围时,不执行console_newline ();即不换行。
现在我们再来看一下前面界面交互部分关于'\n'的处理:
case '\r':
/* Enter */
case '\n':
*p = '\0';
puts ("\r\n");
先回车然后换行(回车是光标回到本行行首,不是下一行;换行是光标到下一行,但是并不回到行首,横坐标不变,平常使用时,一般这两个功能一起使用,但是本质是两个功能的组合)这个顺序很重要,前面已经强调。按照代码的运行我们可以看到,先输出回车,横坐标回到最左边,然后在进行'\n'的处理,这里nl的值为0,所以if (console_col || (!console_col && nl))的值不成立,不执行换行的操作。为什么nl的值为0?我们在回到问题的描述环节:“在lcd的命令行界面出现了一个问题:当命令长度超过一行转到下一行时,输入回车,执行结果会将命令本身的最后一行覆盖。比如:命令本身一共2行,则执行后第2行,被覆盖;命令本身3行,则前2行没问题,第3行被覆盖。”这里说的是命令本身超过一行,进到下一行,而这里进到下一行并不是我们输入'\n'导致的,而是内容超出一行自动换行的,nl就是存储这种状态的标志位,当自动换行时,nl=0;
综上我们可以了解到,就是这两个地方存在相互冲突,导致了这种现象的出现。
怎么解决这个问题呢?其实很简单将交互界面程序中关于'\n'的处理部分的puts ("\r\n");改为puts ("\n");就可以了。
可能有的小伙伴就说这不是少输出了一个'\r'吗?不符合原作者的本意,是不是就不能回车了?我们分析一下'\n'的处理函数:console_newline()
static void console_newline (void)
{
/* Check if last character in the line was just drawn. If so, cursor was
overwriten and need not to be cleared. Cursor clearing without this
check causes overwriting the 1st character of the line if line lenght
is >= CONSOLE_COLS
*/
if (console_col < CONSOLE_COLS)
CURSOR_OFF
console_row++;
console_col = 0;
/* Check if we need to scroll the terminal */
if (console_row >= CONSOLE_ROWS) {
/* Scroll everything up */
console_scrollup ();
/* Decrement row number */
console_row--;
}
}
可以看到,实际上进行的操作就是:
console_row++;
console_col = 0;
console_row++;实际上就是换行,纵坐标加1;console_col = 0;实际上就是回车,横坐标置0,回到行首。------------’\n'完成的更能已经包含该了\r\n两者的控制效果,所以可以直接puts ("\r\n");改为puts ("\n");没有什么影响。
如果还是不放心,我们也可以这样改:puts ("\r\n")--------->puts ("\n\r"),同样可以解决问题,同时最大程度忠于源程序作者思想。至于为什么这样也可以,原因其实很简单,如果前面的分析认真读过了,这样修改也可以的原因就知道了。
最后在说一句:这个问题的本质就是,输入的内容在回显到LCD之前要进行一点中间处理,中间处理的过程出了一点bug。