Zephyr Uart console

本文说明如何使用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驱动模型

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函数自动补齐.

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. 为标准输出和内核打印注册输出
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作为输出的:

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才算真正的启动

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处理字符,主要流程如下:

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内

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();
}

参考 链接到标题

https://lgl88911.pages.dev/zephyr/zephyr%E9%A9%B1%E5%8A%A8%E6%A8%A1%E5%9E%8B/ https://lgl88911.pages.dev/zephyr/zephyr-shell%E5%88%86%E6%9E%90/