linux内核串口控制器注册以及收发

我们看到Linux dev目录下面ttyS系列的串口设备的终端,现在用一个uart 控制器的程序来分析下怎么实现的


首先是定义了uart driver

static struct uart_driver XX_uart_driver = {
.owner = THIS_MODULE,
.driver_name = XX_UART_DEV_NAME, //名字,实际是uart
.dev_name = "ttyS", 
.nr = XX_UART_NUM,
.cons = XX_CONSOLE,
};

通过uart 核心层提供的接口先上层注册

uart_register_driver(&XX_uart_driver);

它原型是这样的

int uart_register_driver(struct uart_driver *drv)
{
struct tty_driver *normal;
int i, retval;


BUG_ON(drv->state);


/*
* Maybe we should be using a slab cache for this, especially if
* we have a large number of ports to handle.
*/
drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
if (!drv->state)
goto out;


normal = alloc_tty_driver(drv->nr); //由此可以看出来是通过tty设备来抽象的
if (!normal)
goto out_kfree;


drv->tty_driver = normal;


normal->driver_name= drv->driver_name;
normal->name = drv->dev_name;
normal->major = drv->major;
normal->minor_start= drv->minor;
normal->type = TTY_DRIVER_TYPE_SERIAL;
normal->subtype= SERIAL_TYPE_NORMAL;
normal->init_termios= tty_std_termios;
normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
normal->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
normal->driver_state    = drv;
tty_set_operations(normal, &uart_ops);


/*
* Initialise the UART state(s).
*/
for (i = 0; i < drv->nr; i++) { //cpu的串口多少组串口
struct uart_state *state = drv->state + i;
struct tty_port *port = &state->port;


tty_port_init(port);
port->ops = &uart_port_ops;
port->close_delay     = HZ / 2;/* .5 seconds */
port->closing_wait    = 30 * HZ;/* 30 seconds */
}


retval = tty_register_driver(normal); //注册tty设备驱动
if (retval >= 0)
return retval;


put_tty_driver(normal);
out_kfree:
kfree(drv->state);
out:
return -ENOMEM;
}

注册相应的devices,由于一个CPU有多组串口

for (i=0; i<XX_UART_NUM; i++) {
pdata = &XX_uport_pdata[i];
if (!pdata->used)
continue;
platform_device_register(&XX_uport_device[i]);
}

XX_uport_device里面包含了很多resource

struct XX_uart_pdata {
unsigned int used;
unsigned int base;
unsigned int irq; //中断号
unsigned int max_ios;
unsigned int io_num;//io内存
};

流程走到了注册

static struct platform_driver XX_uport_platform_driver = {
.probe  = XX_uart_probe,
.remove = XX_uart_remove,
.driver = {
.name  = XX_UART_DEV_NAME,
.pm    = SERIAL_XX_PM_OPS,
.owner = THIS_MODULE,
},
};

static int __devinit XX_uart_probe(struct platform_device *pdev)
{
struct uart_port *port;
struct XX_uart_port *XX_uport;
int ret = -1;


port = &XX_uart_ports[pdev->id].port;
port->dev = &pdev->dev;
sw_uport = UART_TO_SPORT(port);
sw_uport->id = pdev->id;
sw_uport->ier = 0;
sw_uport->lcr = 0;
sw_uport->mcr = 0;
sw_uport->fcr = 0;
sw_uport->dll = 0;
sw_uport->dlh = 0;
snprintf(XX_uport->name, 16, XX_UART_DEV_NAME"%d", pdev->id);
pdev->dev.init_name = XX_uport->name;


SERIAL_DBG("uart.%d probe ... \n", pdev->id);


/* request system resource and init them */
ret = XX_uart_request_resource(XX_uport);
if (unlikely(ret)) {
SERIAL_MSG("uart%d error to get resource\n", pdev->id);
return -ENXIO;
}


port->uartclk = clk_get_rate(sw_uport->mclk);


port->type = PORT_XX;
port->flags = UPF_BOOT_AUTOCONF;
port->mapbase = XX_uport->pdata->base;
port->irq = XX_uport->pdata->irq;
platform_set_drvdata(pdev, port);



SERIAL_DBG("add uart%d port, port_type %d, uartclk %d\n",
pdev->id, port->type, port->uartclk);
return uart_add_one_port(&XX_uart_driver, port);
}

 
我们来看一下linux内核是如何相应收数据,和发送数据的。
首先看驱动向内核注册的串口操作集合
static struct uart_ops _uart_ops = {
.tx_empty = _uart_tx_empty,
.set_mctrl = _uart_set_mctrl,
.get_mctrl = _uart_get_mctrl,
.stop_tx = _uart_stop_tx,//停止发送
.start_tx = _uart_start_tx,//应用层最终通过tty的驱动发送数据会掉到这个接口
.stop_rx = _uart_stop_rx,//停止接受
.enable_ms = _uart_enable_ms,
.break_ctl = _uart_break_ctl,
.startup = _uart_startup, //这个接口通常会做一些初始化一个串口 比如 注册串口中断,打开中断。
.shutdown = _uart_shutdown,
.set_termios = _uart_set_termios,//设置串口的属性
.type = _uart_type,//串口类型
.release_port = _uart_release_port,
.request_port = _uart_request_port,
.config_port = _uart_config_port,
.verify_port = _uart_verify_port,
.pm = _uart_pm,
};

首先我们看一下发送:
发送时cpu主动发起的数据,相对比较简单,由应用程序直接调用到_uart_start_tx,把相应的数据写到相应的寄存器,通常数据发送完以后会产生中断,这个中断对应的了startup函数里面注册中断。

接收数据过程:
接收数据cpu是被动接收的,只能听过中断来检测数据的到来,当中断到来时,产生中断。void uart_insert_char(struct uart_port *port, unsigned int status,unsigned int overrun, unsigned int ch, unsigned int flag)接口把接收到数据推送到上层去。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux 内核中,串口 485 驱动的实现主要是通过软件方式实现的。在使用 485 通信时,需要通过控制 RTS 和 DTR 两个信号线来控制 485 转换器的收发切换。由于这两个信号线在串口协议中已经有了固定的功能,因此需要通过软件方式来实现对它们的控制。 下面是一个简单的 485 驱动示例,实现了对 RTS 和 DTR 两个信号线的控制。该示例代码实现了一个基于 tty 驱动的 485 设备驱动程序,使用了 tty 驱动中的 `ioctl()` 函数来实现对 RTS 和 DTR 信号线的控制。 ```c #include <linux/module.h> #include <linux/serial.h> #include <linux/tty.h> #include <linux/tty_flip.h> #include <linux/tty_driver.h> #include <linux/tty_port.h> #define MY_DRIVER_NAME "my485" struct my485_port { struct tty_port port; spinlock_t lock; }; static struct my485_port myport; static int my485_open(struct tty_struct *tty, struct file *file) { return tty_port_open(&myport.port, tty, file); } static void my485_close(struct tty_struct *tty, struct file *file) { tty_port_close(&myport.port, tty, file); } static int my485_write(struct tty_struct *tty, const unsigned char *buf, int len) { int ret; spin_lock(&myport.lock); tty->driver->ioctl(tty, TIOCMGET, &ret); ret &= ~(TIOCM_DTR | TIOCM_RTS); tty->driver->ioctl(tty, TIOCMSET, &ret); ret |= TIOCM_DTR; tty->driver->ioctl(tty, TIOCMSET, &ret); usleep_range(1000, 2000); ret |= TIOCM_RTS; tty->driver->ioctl(tty, TIOCMSET, &ret); spin_unlock(&myport.lock); return tty_port_write(&myport.port, buf, len); } static const struct tty_operations my485_ops = { .open = my485_open, .close = my485_close, .write = my485_write, }; static struct tty_driver *my485_drv; static int __init my485_init(void) { int ret; memset(&myport, 0, sizeof(myport)); spin_lock_init(&myport.lock); tty_port_init(&myport.port); myport.port.ops = &my485_ops; my485_drv = alloc_tty_driver(1); if (!my485_drv) return -ENOMEM; my485_drv->owner = THIS_MODULE; my485_drv->driver_name = MY_DRIVER_NAME; my485_drv->name = "ttyMy485"; my485_drv->major = TTY_MAJOR; my485_drv->minor_start = 0; my485_drv->type = TTY_DRIVER_TYPE_SERIAL; my485_drv->subtype = SERIAL_TYPE_NORMAL; my485_drv->init_termios = tty_std_termios; tty_set_operations(my485_drv, &my485_ops); ret = tty_register_driver(my485_drv); if (ret) { printk(KERN_ERR "%s: failed to register driver\n", MY_DRIVER_NAME); put_tty_driver(my485_drv); return ret; } printk(KERN_INFO "%s: driver registered\n", MY_DRIVER_NAME); return 0; } static void __exit my485_exit(void) { tty_unregister_driver(my485_drv); put_tty_driver(my485_drv); printk(KERN_INFO "%s: driver unregistered\n", MY_DRIVER_NAME); } module_init(my485_init); module_exit(my485_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("485 driver for Linux tty"); ``` 在上述示例代码中,首先定义了一个 `struct my485_port` 结构体来表示 485 设备的端口信息。然后,实现了 `open()`、`close()` 和 `write()` 函数,分别用来处理设备文件的打开、关闭和写入操作。在 `write()` 函数中,首先使用 `ioctl()` 函数获取当前 DTR 和 RTS 信号线的状态,并将它们清零。然后,通过 `usleep_range()` 函数等待一段时间,最后将 RTS 信号线置为高电平,表示进入发送状态。 最后,在 `my485_init()` 函数中注册了一个 tty 驱动程序,并将其挂载到 tty 子系统中。在 `my485_exit()` 函数中,注销了该 tty 驱动程序。 请注意,上述示例代码仅作为演示用途,实际应用中还需要根据具体的硬件和通信协议进行相应的修改和优化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值