Zephyr Uart console

Creative Commons
本作品采用知识共享署名

本文说明如何使用uart console,并分析uart console的工作原理。

概述

uart console的代码在driver/console/uart_console.c内,uart console使用控制uart驱动为用户提供一个交流终端。uart console也提供ASCI转义系列,但本文不做该部分的分析。

使用

uart console通过uart_console_init初始化后,由使用者通过uart_register_input注册fifo到console, console将从uart接受到的字符串通过fifo送给使用者

初始化

uart console通过uart_console_init进行初始化,初始化函数通过SYS_INIT注册,并在上电时自动调用.SYS_INIT最后使用的是DEVICE_INIT,以后会有其他文章介绍SYS_INIT这里不展开介绍。DEVICE_INIT可以参看文章zephyr驱动模型

1
2
3
4
5
6
7
8
9
SYS_INIT(uart_console_init,
#if defined(CONFIG_USB_UART_CONSOLE)
APPLICATION,
#elif defined(CONFIG_EARLY_CONSOLE)
PRE_KERNEL_1,
#else
POST_KERNEL,
#endif
CONFIG_UART_CONSOLE_INIT_PRIORITY);

注册输入

uart console的使用者通过uart_register_input注册输出的fifo,该API也是uart_console对使用者提供的唯一API.console的使用者分配两个fifo注册到console,一个avail携带空闲的buffer送到console内,当console从uart收满一行时通过另一个lines送给使用者,此外console也检测tab按键,并通过使用者注册的completion函数自动补齐.

1
2
3
void uart_register_input(struct k_fifo *avail, 
struct k_fifo *lines,
u8_t (*completion)(char *str, u8_t len));

分析

初始化

初始做了两件事情,只是为console的运转做准备,console并未真正的驱动起来

  1. 根据配置的驱动名获取uart console使用的uart驱动
  2. 为标准输出和内核打印注册输出
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    static int uart_console_init(struct device *arg)
    {
    uart_console_dev = device_get_binding(CONFIG_UART_CONSOLE_ON_DEV_NAME);
    uart_console_hook_install();

    return 0;
    }

    void uart_console_hook_install(void)
    {
    __stdout_hook_install(console_out);
    __printk_hook_install(console_out);
    }

输出

console在初始化时会将console_out注册到标准输出和内核打印内作为这两个模块的输出函数。而console_out本身是使用uart driver作为输出的:

1
2
3
4
5
6
7
8
9
10
static int console_out(int c)
{
//收到回车时补一个\r
if ('\n' == c) {
uart_poll_out(uart_console_dev, '\r');
}
uart_poll_out(uart_console_dev, c);

return c;
}

输入

注册输入后,uart console才算真正的启动

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
void uart_register_input(struct k_fifo *avail, struct k_fifo *lines,
u8_t (*completion)(char *str, u8_t len))
{
//保存fifo handle和completion函数指针备用
avail_queue = avail;
lines_queue = lines;
completion_cb = completion;

//启动console
console_input_init();
}

static void console_input_init(void)
{
u8_t c;
//关闭中断
uart_irq_rx_disable(uart_console_dev);
uart_irq_tx_disable(uart_console_dev);

//注册isr服务,uart driver收到字符后会送到注册的uart_console_isr处理
uart_irq_callback_set(uart_console_dev, uart_console_isr);

//清空接收fifo
/* Drain the fifo */
while (uart_irq_rx_ready(uart_console_dev)) {
uart_fifo_read(uart_console_dev, &c, 1);
}

//开启接收中断
uart_irq_rx_enable(uart_console_dev);
}

console isr处理

uart console最终是被uart driver所驱动,当收到uart收到字符时产生中断,调用uart_console_isr处理字符,主要流程如下:

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
43
44
45
void uart_console_isr(struct device *unused)
{
//从uart读取一个ASIIC到变量byte
rx = read_uart(uart_console_dev, &byte, 1);

//从空闲fifo获取一个cmd buffer
cmd = k_fifo_get(avail_queue, K_NO_WAIT);

//如果读取的ASIIC不是字符,进行控制字符处理
if (!isprint(byte)) {
switch (byte) {
case DEL: //收到DEL按键,删除一个字符
if (cur > 0) {
del_char(&cmd->line[--cur], end);
}
break;
case ESC:
atomic_set_bit(&esc_state, ESC_ESC);
break;
case '\r': //收到回车符,将cmd buffer送到fifo lines_queue中,console的使用者会读取这个fifo并处理cmd
cmd->line[cur + end] = '\0';
uart_poll_out(uart_console_dev, '\r');
uart_poll_out(uart_console_dev, '\n');
cur = 0;
end = 0;
k_fifo_put(lines_queue, cmd);
cmd = NULL;
break;
case '\t': //收到table按键,如果注册了completion,则调用急性自动补全
if (completion_cb && !end) {
cur += completion_cb(cmd->line, cur);
}
break;
default:
break;
}

continue;
}

//如果读到的是字符,保存在cmd line中
if (cur + end < sizeof(cmd->line) - 1) {
insert_char(&cmd->line[cur++], byte, end);
}
}

关于输入回显:
console支持输入回显,就是说在键盘上敲入一个字符后,console会调用uart驱动将这个字符送到串口上,让终端可以显示,主要体现在insert_char和del_char内

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
43
44
45
46
47
48
49
50
static void insert_char(char *pos, char c, u8_t end)
{
char tmp;

/* Echo back to console */
uart_poll_out(uart_console_dev, c); //回显输入的字符

if (end == 0) {
*pos = c;
return;
}

tmp = *pos;
*(pos++) = c;

cursor_save();

while (end-- > 0) {
uart_poll_out(uart_console_dev, tmp);
c = *pos;
*(pos++) = tmp;
tmp = c;
}

/* Move cursor back to right place */
cursor_restore();
}

static void del_char(char *pos, u8_t end)
{
uart_poll_out(uart_console_dev, '\b'); //删除上一个字符

if (end == 0) {
uart_poll_out(uart_console_dev, ' ');
uart_poll_out(uart_console_dev, '\b');
return;
}

cursor_save();

while (end-- > 0) {
*pos = *(pos + 1);
uart_poll_out(uart_console_dev, *(pos++));
}

uart_poll_out(uart_console_dev, ' ');

/* Move cursor back to right place */
cursor_restore();
}

参考

http://lgl88911.gitee.io/2018/04/10/zephyr%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B/
http://lgl88911.gitee.io/2018/04/04/Zephyr-Shell%E5%88%86%E6%9E%90/