【RT-Thread 设备驱动二 -- console 注册】

1.1 console

consoletty 有很大区别:console是个只输出的设备,功能很简单,只能在内核中访问。console可以是串口,可以是vga,也可以是LCD等设备

1.2 控制台初始化

在系统启动的流程中会走入板级初始化的流程中,板级初始化中包含了所有外设的初始化,但是不包含串口 uartuart 的初始化需要放在班级初始化之前,这样在板级设备初始化的过程中才能看到相关的串口 log 打印,方便调试:

void rt_hw_board_init()
    console_uart_init();  //UART 初始化
    rt_console_set_device(RT_CONSOLE_DEVICE_NAME); //console 初始化
    rt_components_board_init();

从上面可以看到console的初始化是在板子初始化的流程中的,在 rt-thread 中对于 console 的设置是通过下面函数完成的:

 rt_device_t rt_console_set_device(const char *name)

需要注意该函数传入的参数 #define RT_CONSOLE_DEVICE_NAME "uart0",在 rt_console_set_device中会根据传入的参数名来找对应的 device

new_device = rt_device_find(name);

我们看下该函数 rt_console_set_device 的具体实现:

/**
 * This function will set a device as console device.
 * After set a device to console, all output of rt_kprintf will be
 * redirected to this new device.
 *
 * @param name the name of new console device
 *
 * @return the old console device handler on successful, or RT_NULL on failure.
 */
rt_device_t rt_console_set_device(const char *name)
{
    rt_device_t new_device, old_device;

    /* save old device */
    old_device = _console_device;

    /* find new console device */
    new_device = rt_device_find(name);  //根據傳入的參數找到將要配置console device 的 device。

    /* check whether it's a same device */
    if (new_device == old_device) return RT_NULL;

    if (new_device != RT_NULL) {
        if (_console_device != RT_NULL) {
            /* close old console device */
            rt_device_close(_console_device);
        }

        /* set new console device */
        rt_device_open(new_device, RT_DEVICE_OFLAG_RDWR | RT_DEVICE_FLAG_STREAM);
        _console_device = new_device;
    }

    return old_device;
}

该函数的作用是 使用一个新设备用来当做 console 设备,新设备设置好之后,所有的 rt_kprintf 的输出都会重定向到这个新device中, 一般 rt_kprintf 的输出都是通过先 open 一个 device 然后再 write 数据到这个 device 的流程。接下来看下 rt_kprintf 的实现:

rt-thread/rt-thread/src/kservice.c

/**
 * This function will print a formatted string on system console
 *
 * @param fmt the format
 */
RT_WEAK void rt_kprintf(const char *fmt, ...)
{
    va_list args;
    rt_size_t length;
    static char rt_log_buf[RT_CONSOLEBUF_SIZE];

    va_start(args, fmt);
    /* the return value of vsnprintf is the number of bytes that would be
     * written to buffer had if the size of the buffer been sufficiently
     * large excluding the terminating null byte. If the output string
     * would be larger than the rt_log_buf, we have to adjust the output
     * length. */
    length = rt_vsnprintf(rt_log_buf, sizeof(rt_log_buf) - 1, fmt, args);
    if (length > RT_CONSOLEBUF_SIZE - 1)
        length = RT_CONSOLEBUF_SIZE - 1;
    if (_console_device == RT_NULL) {
        rt_hw_console_output(rt_log_buf);
    } else {
        rt_device_write(_console_device, 0, rt_log_buf, length); //走这里
    }
    va_end(args);
}
RTM_EXPORT(rt_kprintf);

由于 _console_device 不为空所以走的是 rt_device_write函数。下面看下rt_device_write 的实现:

rt_size_t rt_device_write(rt_device_t dev, rt_off_t    pos, const void *buffer, rt_size_t   size)
{
    RT_ASSERT(dev != RT_NULL);
    RT_ASSERT(rt_object_get_type(&dev->parent) == RT_Object_Class_Device);

    if (dev->ref_count == 0) {
        rt_set_errno(-RT_ERROR);
        return 0;
    }

    /* call device_write interface */
    if (device_write != RT_NULL) {
        return device_write(dev, pos, buffer, size);
    }

    /* set error code */
    rt_set_errno(-RT_ENOSYS);

    return 0;
}
RTM_EXPORT(rt_device_write);

从上面 return device_write(dev, pos, buffer, size) 看到,通过调用 rt-thread 的标准接口 “device_write“ 来进行写操作,所以我们就需要知道device(_console_device)的 ops函数是在哪里实现的了?

1.2.1 uart 设备注册

uart 初始化的过程中,会对 uart 进行设备注册,

int dw_uart_hw_init(struct uart_config *config)
    rt_hw_serial_register(ser, config->name, RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_R
        rt_device_register(device, name, RT_DEVICE_FLAG_RDWR | flag);

后续通过已经注册过的 uart 设备的 name 可以拿到device句柄,此外还需要关注注册是填入的flag,当时在调试 ulog 异步输出模式的时候由于flag的问题导致 console 没能够接受输入。

1.3 shell 初始化

系统启动后,会进行shell的初始化:

INIT_APP_EXPORT(finsh_system_init)

shell 会创建一个线程,等待输入,目前在rtthread中使用的POSXI接口,finsh_getchar中调用getchar来获取字符的输入,getchar使用的arm libc,所以会通过先open 设备然后再读设备的流程:

const static struct dfs_file_ops _serial_fops =
{
    serial_fops_open,
    serial_fops_close,
    serial_fops_ioctl,
    serial_fops_read,
    serial_fops_write,
    RT_NULL, /* flush /RT_NULL, / lseek /RT_NULL, / getdents */
    serial_fops_poll,
};

其中_serial_fops是再注册uart设备的时候传入的:

rt_err_t rt_hw_serial_register(struct rt_serial_device *serial,
const char              *name,
rt_uint32_t              flag,
void                    *data)
{

	device->init        = rt_serial_init;
	device->open        = rt_serial_open;
	device->close       = rt_serial_close;
	device->read        = rt_serial_read;
	device->write       = rt_serial_write;
	device->control     = rt_serial_control;
#endif
	device->user_data   = data;
	/* register a character device */
	ret = rt_device_register(device, name, flag);

#if defined(RT_USING_POSIX)
	/* set fops */
	device->fops        = &_serial_fops;
#endif
	return ret;
}

推荐阅读

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
rt-thread是一个实时嵌入式操作系统,它提供了设备驱动开发的指南,帮助开发者更好地编写设备驱动程序。 首先,在rt-thread中进行设备驱动开发之前,需要了解rt-thread设备驱动框架,包括设备驱动的结构、设备驱动注册和使用方法等。同时,开发者也需要了解rt-thread设备模型,包括设备设备驱动设备节点的概念,以便更好地理解设备驱动的开发流程。 其次,在编写设备驱动程序时,需要遵循rt-thread设备驱动编程规范,包括设备驱动的命名规范、接口函数的实现规范等。同时,还需要了解不同类型设备的特点和使用方法,以便更好地编写对应的设备驱动程序。 另外,rt-thread提供了丰富的设备驱动接口和函数,开发者可以根据需求选择合适的接口和函数进行设备驱动的开发。在编写设备驱动程序时,还需要注意设备驱动的稳定性和可靠性,及时处理错误和异常情况,以提高设备驱动程序的质量和可靠性。 最后,在设备驱动程序开发完成后,还需要进行设备驱动的测试和调试工作,确保设备驱动程序能够正常工作并符合需求。同时,还需要关注设备驱动程序的性能和优化工作,以提高设备驱动程序的效率和性能。 总之,rt-thread设备驱动开发指南提供了设备驱动开发的规范和方法,帮助开发者更好地编写设备驱动程序,并最终实现嵌入式系统的稳定和可靠运行。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

主公CodingCos

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值