在嵌入式系统中大量用到了串口。
但是很多时候,在使用中串口是复用的,例如和MCU通讯,传的有触摸、按键,控制数据等等。
或者在通讯前要拉一些IO口进行握手之类的。
对于这些要求,单纯用串口就无法满足,那么我们就需要用到线路规程。
下面详细介绍下线路规程
1 线路规程的数据结构
struct tty_ldisc_ops {
int magic;
char *name;
int num;
int flags;
/*
* The following routines are called from above.
*/
int (*open)(struct tty_struct *);
void (*close)(struct tty_struct *);
void (*flush_buffer)(struct tty_struct *tty);
ssize_t (*chars_in_buffer)(struct tty_struct *tty);
ssize_t (*read)(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr);
ssize_t (*write)(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr);
int (*ioctl)(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios *old);
unsigned int (*poll)(struct tty_struct *, struct file *,
struct poll_table_struct *);
int (*hangup)(struct tty_struct *tty);
/*
* The following routines are called from below.
*/
void (*receive_buf)(struct tty_struct *, const unsigned char *cp,
char *fp, int count);
void (*write_wakeup)(struct tty_struct *);
void (*dcd_change)(struct tty_struct *, unsigned int);
void (*fasync)(struct tty_struct *tty, int on);
int (*receive_buf2)(struct tty_struct *, const unsigned char *cp,
char *fp, int count);
struct module *owner;
int refcount;
};
常用的成员变量说明:
.owner :通常设置为THIS_MODULE
.name: 线路规程的名称,可以任何有意义的名称
.magic:通常设置为TTY_LDISC_MAGIC
.open :当线路规程附加到串口终端是会被调用。
.close:当线路规程从串口终端移除的时候被调用,关闭串口时,也会被调用
.read:应用程序读串口的时候被调用
.write:应用程序写串口的时候被调用
.ioctl:当ioctl交给tty层时被调用
.poll: 当调用poll\select 的时候检查资源的状态
.receive_buf:当串口接收缓冲区有数据的时候被调用,调用这个函数前会检查线路规程缓冲区的可用长度,如果长度为0,将不调用这个函数
.receive_buf2:当串口接收缓冲区有数据的时候被调用,这个函数调用前不检测线路规程缓冲区的可用长度。
.flush_buffer:清空线路规程的接收缓冲区
.chars_in_buffer:报告输入缓冲区中的字节数
2函数说明
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc);
函数功能:注册一个线路规程
入口参数:disc 唯一标识一个线路规程,这个整数在系统中是唯一的,不能和其他的线路规程相同
new_ldisc 上面介绍的tty_ldisc_ops结构的指针
返回值: 0 成功
非0 失败
int tty_unregister_ldisc(int disc);
函数功能:注销一个线路规程
入口参数:disc tty_register_ldisc注册时传的disc的值
返回值: 0 成功
非0 失败
3 示例代码
#define DEV_NAME "ldisc-test"
#define MAX_BUF_LEN 32
#define N_LDISC_TEST 25
typedef struct
{
u8 bReceivebuf[MAX_BUF_LEN];
int iLen;
}stCtrl;
stCtrl MyData;
wait_queue_head_t wqReadOK;
static int ldisc_test_open (struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
/* There should not be an existing table for this slot. */
if (data) {
printk (KERN_ERR"n_tty_open:tty already associated!\n" );
return -EEXIST;
}
data = &MyData;
data->iLen= 0;
tty->disc_data = data;//这里可以将自己的私有数据保存在这里
tty->receive_room = MAX_BUF_LEN; //注意这里要指定receive_room,如果没有指定,默认为0 ,那么ldisc_test_receive不会被调用了
/* flush receive data from driver */
tty_driver_flush_buffer(tty);
return 0;
}
static void ldisc_test_close(struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
data=NULL;
printk("ldisc_close ok\n");
}
static ssize_t ldisc_test_read(struct tty_struct *tty, struct file *file,
__u8 __user *buf, size_t nr)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
int ret = 0;
if(data->iLen==0)
{
if (file->f_flags & O_NONBLOCK)
{
return ret;
}
else
{
wait_event_interruptible_timeout(wqReadOK, (data->iLen>0) ,10);
}
}
//这里是示例代码,没有做判断是否会溢出,实际处理要判断nr的长度
if(copy_to_user(buf,data->bReceivebuf,data->iLen)==0)
{
ret= data->iLen;
}
tty->receive_room = MAX_BUF_LEN;
data->iLen = 0;
return ret;
}
static ssize_t ldisc_test_write(struct tty_struct *tty, struct file *file,
const unsigned char *data, size_t count)
{
int error;
......这里可以做自己的一些相应的处理,例如拉IO口,进行握手等等
error = tty->ops->write(tty, data, count);
......这里可以做自己的一些相应的处理
return error;
}
static int ldisc_test_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
int error = 0;
int count;
switch (cmd) {
case FIONREAD: //获取缓冲区的数据长度
error = put_user(data->iLen, (int __user *)arg);
break;
case TIOCOUTQ://返回输出队列中还未送出的字符数。
count = tty_chars_in_buffer(tty);
error = put_user(count, (int __user *)arg);
break;
/* fall through to default */
default:
error = n_tty_ioctl_helper(tty, file, cmd, arg);
break;
}
return error;
}
static unsigned int ldisc_test_poll(struct tty_struct *tty, struct file *filp,
poll_table *wait)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
unsigned int mask = 0;
poll_wait(filp, &wqReadOK, wait);
if(data->iLen >0)
{
mask |= POLLIN | POLLRDNORM; /* 缓冲区有数据 */
}
return mask;
}
static void ldisc_test_receive(struct tty_struct *tty, const __u8 *data,
char *flags, int count)
{
stCtrl *Mydata = ((stCtrl *) ((tty)->disc_data));
int i;
Mydata->iLen = 0;
//这里只是示例代码,没有判断是否会数组越界,也没有考虑是否缓冲区有数据
for(i=0;i<count;i++)
{
if(flags[i]==TTY_NORMAL)//flags 是标志位,判断是否是有效数据
{
Mydata->bReceivebuf[Mydata->iLen++]=data[i];
}
}
tty->receive_room = 0; //这里没有判断实际缓冲区已经满了,直接认为满了,实际可以根据我们缓冲区的剩余空间,修改 receive_room的值。
.......这里可以根据自己的规则处理数据,或者对IO的操作,处理接收到的按键数据或者触摸数据等等
wake_up_interruptible(&wqReadOK);
}
static void ldisc_test_wakeup(struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
}
static void flush_rx_queue(struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
data->iLen = 0; //缓冲区清零
}
static struct tty_ldisc_ops ldisc_test_ldisc = {
.owner = THIS_MODULE,
.magic = TTY_LDISC_MAGIC,
.name = DEV_NAME,
.open = ldisc_test_open,
.close = ldisc_test_close,
.read = ldisc_test_read,
.write = ldisc_test_write,
.ioctl = ldisc_test_ioctl,
.poll = ldisc_test_poll,
.receive_buf = ldisc_test_receive,
.write_wakeup = ldisc_test_wakeup,
.flush_buffer = flush_rx_queue,
};
static int __init ldisc_test_init(void)
{
int status;
init_waitqueue_head(&wqReadOK);
//注册线路规程
status = tty_register_ldisc(N_LDISC_TEST, &ldisc_test_ldisc);
if (!status)
printk("ldisc register ok\n");
else
printk("ldisc register fail, status=%d\n",status);
return status;
}
static void __exit ldisc_test_exit(void)
{
int status = tty_unregister_ldisc(N_LDISC_TEST);
if (status)
printk(KERN_ALERT"ldisc unregister fail, status=%d\n",status);
}
module_init(ldisc_test_init);
module_exit(ldisc_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TEST@qq.com");
MODULE_ALIAS_LDISC(N_LDISC_TEST);
4 应用程序调用示例
int OpenLinBus( )
{
struct termios Opt;
int iLdisc = N_ITAS_LIN;
int i;
int fd = open(DEV, O_RDWR|O_NOCTTY);
if(fd<0)
{
return -1 ;
}
ioctl(fd , TIOCSETD, &iLdisc); //这里注册线路规程
//下面都是通用的串口设置了
tcgetattr(fd, &Opt) ;
cfsetispeed(&Opt, Bautrate);
cfsetospeed(&Opt, Bautrate);
tcsetattr(fd, TCSANOW, &Opt);
tcflush(fd, TCIOFLUSH);
tcgetattr(fd, &Opt) ;
Opt.c_cflag &= ~CSTOPB;
Opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
Opt.c_oflag &= ~OPOST;
Opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
Opt.c_cflag &= ~(CSIZE | PARENB);
Opt.c_cflag |= CS8;
Opt.c_cflag &= ~CRTSCTS;
tcsetattr(fd, TCSANOW, &Opt);
tcflush(fd, TCIOFLUSH);
return 0;
}
之后应用就可以像操作普通串口一样调用read write等接口了。最后就会调到前面线路规程驱动中的read write了
但是很多时候,在使用中串口是复用的,例如和MCU通讯,传的有触摸、按键,控制数据等等。
或者在通讯前要拉一些IO口进行握手之类的。
对于这些要求,单纯用串口就无法满足,那么我们就需要用到线路规程。
下面详细介绍下线路规程
1 线路规程的数据结构
struct tty_ldisc_ops {
int magic;
char *name;
int num;
int flags;
/*
* The following routines are called from above.
*/
int (*open)(struct tty_struct *);
void (*close)(struct tty_struct *);
void (*flush_buffer)(struct tty_struct *tty);
ssize_t (*chars_in_buffer)(struct tty_struct *tty);
ssize_t (*read)(struct tty_struct *tty, struct file *file,
unsigned char __user *buf, size_t nr);
ssize_t (*write)(struct tty_struct *tty, struct file *file,
const unsigned char *buf, size_t nr);
int (*ioctl)(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg);
long (*compat_ioctl)(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg);
void (*set_termios)(struct tty_struct *tty, struct ktermios *old);
unsigned int (*poll)(struct tty_struct *, struct file *,
struct poll_table_struct *);
int (*hangup)(struct tty_struct *tty);
/*
* The following routines are called from below.
*/
void (*receive_buf)(struct tty_struct *, const unsigned char *cp,
char *fp, int count);
void (*write_wakeup)(struct tty_struct *);
void (*dcd_change)(struct tty_struct *, unsigned int);
void (*fasync)(struct tty_struct *tty, int on);
int (*receive_buf2)(struct tty_struct *, const unsigned char *cp,
char *fp, int count);
struct module *owner;
int refcount;
};
常用的成员变量说明:
.owner :通常设置为THIS_MODULE
.name: 线路规程的名称,可以任何有意义的名称
.magic:通常设置为TTY_LDISC_MAGIC
.open :当线路规程附加到串口终端是会被调用。
.close:当线路规程从串口终端移除的时候被调用,关闭串口时,也会被调用
.read:应用程序读串口的时候被调用
.write:应用程序写串口的时候被调用
.ioctl:当ioctl交给tty层时被调用
.poll: 当调用poll\select 的时候检查资源的状态
.receive_buf:当串口接收缓冲区有数据的时候被调用,调用这个函数前会检查线路规程缓冲区的可用长度,如果长度为0,将不调用这个函数
.receive_buf2:当串口接收缓冲区有数据的时候被调用,这个函数调用前不检测线路规程缓冲区的可用长度。
.flush_buffer:清空线路规程的接收缓冲区
.chars_in_buffer:报告输入缓冲区中的字节数
2函数说明
int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc);
函数功能:注册一个线路规程
入口参数:disc 唯一标识一个线路规程,这个整数在系统中是唯一的,不能和其他的线路规程相同
new_ldisc 上面介绍的tty_ldisc_ops结构的指针
返回值: 0 成功
非0 失败
int tty_unregister_ldisc(int disc);
函数功能:注销一个线路规程
入口参数:disc tty_register_ldisc注册时传的disc的值
返回值: 0 成功
非0 失败
3 示例代码
#define DEV_NAME "ldisc-test"
#define MAX_BUF_LEN 32
#define N_LDISC_TEST 25
typedef struct
{
u8 bReceivebuf[MAX_BUF_LEN];
int iLen;
}stCtrl;
stCtrl MyData;
wait_queue_head_t wqReadOK;
static int ldisc_test_open (struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
/* There should not be an existing table for this slot. */
if (data) {
printk (KERN_ERR"n_tty_open:tty already associated!\n" );
return -EEXIST;
}
data = &MyData;
data->iLen= 0;
tty->disc_data = data;//这里可以将自己的私有数据保存在这里
tty->receive_room = MAX_BUF_LEN; //注意这里要指定receive_room,如果没有指定,默认为0 ,那么ldisc_test_receive不会被调用了
/* flush receive data from driver */
tty_driver_flush_buffer(tty);
return 0;
}
static void ldisc_test_close(struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
data=NULL;
printk("ldisc_close ok\n");
}
static ssize_t ldisc_test_read(struct tty_struct *tty, struct file *file,
__u8 __user *buf, size_t nr)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
int ret = 0;
if(data->iLen==0)
{
if (file->f_flags & O_NONBLOCK)
{
return ret;
}
else
{
wait_event_interruptible_timeout(wqReadOK, (data->iLen>0) ,10);
}
}
//这里是示例代码,没有做判断是否会溢出,实际处理要判断nr的长度
if(copy_to_user(buf,data->bReceivebuf,data->iLen)==0)
{
ret= data->iLen;
}
tty->receive_room = MAX_BUF_LEN;
data->iLen = 0;
return ret;
}
static ssize_t ldisc_test_write(struct tty_struct *tty, struct file *file,
const unsigned char *data, size_t count)
{
int error;
......这里可以做自己的一些相应的处理,例如拉IO口,进行握手等等
error = tty->ops->write(tty, data, count);
......这里可以做自己的一些相应的处理
return error;
}
static int ldisc_test_ioctl(struct tty_struct *tty, struct file *file,
unsigned int cmd, unsigned long arg)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
int error = 0;
int count;
switch (cmd) {
case FIONREAD: //获取缓冲区的数据长度
error = put_user(data->iLen, (int __user *)arg);
break;
case TIOCOUTQ://返回输出队列中还未送出的字符数。
count = tty_chars_in_buffer(tty);
error = put_user(count, (int __user *)arg);
break;
/* fall through to default */
default:
error = n_tty_ioctl_helper(tty, file, cmd, arg);
break;
}
return error;
}
static unsigned int ldisc_test_poll(struct tty_struct *tty, struct file *filp,
poll_table *wait)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
unsigned int mask = 0;
poll_wait(filp, &wqReadOK, wait);
if(data->iLen >0)
{
mask |= POLLIN | POLLRDNORM; /* 缓冲区有数据 */
}
return mask;
}
static void ldisc_test_receive(struct tty_struct *tty, const __u8 *data,
char *flags, int count)
{
stCtrl *Mydata = ((stCtrl *) ((tty)->disc_data));
int i;
Mydata->iLen = 0;
//这里只是示例代码,没有判断是否会数组越界,也没有考虑是否缓冲区有数据
for(i=0;i<count;i++)
{
if(flags[i]==TTY_NORMAL)//flags 是标志位,判断是否是有效数据
{
Mydata->bReceivebuf[Mydata->iLen++]=data[i];
}
}
tty->receive_room = 0; //这里没有判断实际缓冲区已经满了,直接认为满了,实际可以根据我们缓冲区的剩余空间,修改 receive_room的值。
.......这里可以根据自己的规则处理数据,或者对IO的操作,处理接收到的按键数据或者触摸数据等等
wake_up_interruptible(&wqReadOK);
}
static void ldisc_test_wakeup(struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
}
static void flush_rx_queue(struct tty_struct *tty)
{
stCtrl *data = ((stCtrl *) ((tty)->disc_data));
data->iLen = 0; //缓冲区清零
}
static struct tty_ldisc_ops ldisc_test_ldisc = {
.owner = THIS_MODULE,
.magic = TTY_LDISC_MAGIC,
.name = DEV_NAME,
.open = ldisc_test_open,
.close = ldisc_test_close,
.read = ldisc_test_read,
.write = ldisc_test_write,
.ioctl = ldisc_test_ioctl,
.poll = ldisc_test_poll,
.receive_buf = ldisc_test_receive,
.write_wakeup = ldisc_test_wakeup,
.flush_buffer = flush_rx_queue,
};
static int __init ldisc_test_init(void)
{
int status;
init_waitqueue_head(&wqReadOK);
//注册线路规程
status = tty_register_ldisc(N_LDISC_TEST, &ldisc_test_ldisc);
if (!status)
printk("ldisc register ok\n");
else
printk("ldisc register fail, status=%d\n",status);
return status;
}
static void __exit ldisc_test_exit(void)
{
int status = tty_unregister_ldisc(N_LDISC_TEST);
if (status)
printk(KERN_ALERT"ldisc unregister fail, status=%d\n",status);
}
module_init(ldisc_test_init);
module_exit(ldisc_test_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("TEST@qq.com");
MODULE_ALIAS_LDISC(N_LDISC_TEST);
4 应用程序调用示例
int OpenLinBus( )
{
struct termios Opt;
int iLdisc = N_ITAS_LIN;
int i;
int fd = open(DEV, O_RDWR|O_NOCTTY);
if(fd<0)
{
return -1 ;
}
ioctl(fd , TIOCSETD, &iLdisc); //这里注册线路规程
//下面都是通用的串口设置了
tcgetattr(fd, &Opt) ;
cfsetispeed(&Opt, Bautrate);
cfsetospeed(&Opt, Bautrate);
tcsetattr(fd, TCSANOW, &Opt);
tcflush(fd, TCIOFLUSH);
tcgetattr(fd, &Opt) ;
Opt.c_cflag &= ~CSTOPB;
Opt.c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP
| INLCR | IGNCR | ICRNL | IXON);
Opt.c_oflag &= ~OPOST;
Opt.c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
Opt.c_cflag &= ~(CSIZE | PARENB);
Opt.c_cflag |= CS8;
Opt.c_cflag &= ~CRTSCTS;
tcsetattr(fd, TCSANOW, &Opt);
tcflush(fd, TCIOFLUSH);
return 0;
}
之后应用就可以像操作普通串口一样调用read write等接口了。最后就会调到前面线路规程驱动中的read write了