嵌入式Linux串口应用全解析:从初学者到专家 (嵌入式linux 串口)

嵌入式Linux串口应用全解析:从初学者到专家

嵌入式系统是近年来极受欢迎的技术,它的特点是体积小巧、功耗低、性能强劲,可以胜任很多嵌入式场景的应用。而Linux系统则是最为主流的嵌入式系统之一,因为它的开放性、灵活性和强大的社区支持,Linux系统在嵌入式领域被广泛应用,也是很多嵌入式开发者的首选。

在嵌入式Linux应用中,串口是一个非常关键的组成部分,几乎所有的嵌入式系统都需要通过串口与用户或者上层系统进行通信。串口的使用涉及底层硬件和驱动、用户层协议和应用等多个方面,难度较大,不同级别的开发者面对的问题也不同。本篇文章将为读者全面解析嵌入式Linux串口应用,从初学者到专家,为广大开发者提供参考和借鉴。

一、串口概览

串口通信是一种基于异步传输的通信方式,典型应用场景是通过串口连接计算机与外部设备,例如串口终端、单片机、传感器等。通常情况下,串口通信通过一根数线进行数据传输,可以实现两个设备之间的数据交换和控制信号的传输。串口通信涉及到多个方面的知识,例如时序、电气特性、通信协议等。

在嵌入式系统中,串口与外部设备的连接方式通常为TTL电平,信号包括TX、RX、GND等几条线。下图为串口电路的典型实现方式:

![串口电路](https://i.imgur.com/zbwOlq3.png)

从上图中可以看出,串口通信的最小单元是字节,包括起始位、数据位、校验位和停止位。在Linux系统中,串口通信可以通过底层驱动和用户层程序两种方式实现,具体实现方式会在后面的章节中详解。

二、串口驱动

Linux系统中的串口驱动一般是由CPU提供的内部UART芯片控制的,对于不同的CPU架构和不同的驱动实现方式,串口驱动的底层实现也不尽相同。本文将重点介绍在ARM架构下的嵌入式Linux串口驱动实现。

2.1、总线和设备

在Linux系统中,驱动程序通常被视为设备,设备又入到某些总线上,以便与其他设备或者操作系统内核进行通信。串口通信的底层驱动涉及到和总线的交互,特别是在多个设备共用同一总线时,对于驱动程序的管理和调度是非常重要的。

Linux系统中有很多种总线和设备,只需在内核中注册一个新设备,就可以支持这个设备作用于相应的总线上,然后操作系统就可以自动发现、管理和使用该设备。注册设备的过程是动态的,可以在操作系统运行时完成。

2.2、串口芯片与驱动

在ARM架构下,嵌入式系统中通常会用到一些内部UART串口控制器,这些控制器需要一个相应的驱动程序进行配置、控制和数据传输。串口驱动程序通常被编译为模块(又称驱动模块),在特定的时候需要被内核加载进来。

串口通信在硬件上的实现是非常复杂和多样化的,不同的芯片和控制器有不同的数据结构和寄存器配置,因此对应的驱动程序也有区别。通常情况下,驱动程序需要进行一些基本操作,例如设备注册、初始化、读写操作、中断处理等。

在Linux系统下,我们可以通过内核源码树的drivers/tty/serial/目录查看当前系统支持的串口驱动程序,这里列出一些常用的驱动程序名称:

“`

● 8250 – 最简单的串口驱动,支持大多数x86系统

● 8250_pci – 支持通过PCI总线连接的8250串口

● 8250_pnp – 支持ISA PnP总线的8250串口

● dz – DECstation串口驱动

● clps711x – 对于Cirrus Logic 711x处理器的串口驱动

● fsl-imx-uart – 用于Freescale i.MX系列Soc的驱动

● max3100 – 驱动Maxim的MAX3100多串口UART

“`

对于不同种类的驱动程序,需要传递相应的数据结构和参数,对应的信息一般写在一个包含驱动程序信息的数据结构中,例如tty_driver结构体。

2.3、串口驱动编写

编写嵌入式Linux串口驱动,需要按照特定的规范进行实现,这里介绍一下嵌入式系统中Linux串口驱动程序的编写流程:

1. 新建一个.c文件,作为驱动程序的源文件。

2. 在文件头部引入Linux内核头文件、驱动程序头文件等。

3. 在文件中定义驱动程序的关键结构体,例如tty_driver、tty_port等。

4. 声明一些必要的版本信息、完整与半口通信能力。

5. 定义驱动程序接口,例如模块加载和卸载、设备注册和卸载等函数。

6. 定义串口处理函数。

7. 注册串口驱动程序,将驱动程序添加到驱动程序链表中。

如下代码是一个简单的串口驱动示例,实现了从串口读取输入的字符,并将其打印输出:

“`

#include

#include

#include

#include

#include

#include

#include

#define DRIVER_NAME “my_serial”

static struct tty_driver *serial_driver;

static struct tty_port tty_port;

static dev_t dev_id;

static struct class *my_class;

static int my_serial_dev_init(struct tty_port *port)

{

return 0;

}

static int my_serial_dev_open(struct tty_struct *tty, struct file *file)

{

return tty_port_open(&tty_port, tty, file);

}

static ssize_t my_serial_dev_write(struct tty_struct *tty, const unsigned char *buf, size_t count)

{

return tty_write_room(&tty_port) – count;

}

static void my_serial_dev_close(struct tty_struct *tty, struct file *file)

{

tty_port_close(&tty_port, tty, file);

}

static inline void my_serial_output_byte(struct tty_struct *tty, unsigned char ch)

{

return;

}

static int my_serial_set_termios(struct tty_struct *tty, struct ktermios *old_termios)

{

return tty_standard_install(tty, old_termios);

}

static const struct tty_operations my_serial_ops = {

.open = my_serial_dev_open,

.close = my_serial_dev_close,

.write = my_serial_dev_write,

.set_termios = my_serial_set_termios,

.output = my_serial_output_byte,

};

static int my_serial_probe(struct platform_device *pdv)

{

int ret;

ret = alloc_chrdev_region(&dev_id, 0, 1, DRIVER_NAME);

if (ret) {

printk(“Can not get major number.\n”);

return ret;

}

my_class = class_create(THIS_MODULE, DRIVER_NAME);

if (IS_ERR(my_class)) {

printk(“Class create fled.”);

unregister_chrdev_region(dev_id, 1);

return PTR_ERR(my_class);

}

serial_driver = alloc_tty_driver(1);

if (!serial_driver) {

printk(“Can’t alloc driver.\n”);

class_destroy(my_class);

unregister_chrdev_region(dev_id, 1);

return -ENOMEM;

}

serial_driver->owner = THIS_MODULE;

serial_driver->driver_name = DRIVER_NAME;

serial_driver->name = “ttyMyS0”;

serial_driver->major = MAJOR(dev_id);

serial_driver->minor_start = 0;

serial_driver->type = TTY_DRIVER_TYPE_SERIAL;

serial_driver->subtype = SERIAL_TYPE_NORMAL;

serial_driver->flags = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;

tty_set_operations(serial_driver, &my_serial_ops);

ret = tty_register_driver(serial_driver);

if (ret) {

printk(“Can not register driver.\n”);

tty_unregister_driver(serial_driver);

class_destroy(my_class);

unregister_chrdev_region(dev_id, 1);

return ret;

}

ret = tty_port_init(&tty_port);

if (ret) {

printk(“Can not init port.\n”);

tty_unregister_driver(serial_driver);

class_destroy(my_class);

unregister_chrdev_region(dev_id, 1);

return ret;

}

ret = tty_port_register_device(&tty_port, serial_driver, 0, NULL);

if (ret) {

printk(“Can not register device.\n”);

tty_port_destroy(&tty_port);

tty_unregister_driver(serial_driver);

class_destroy(my_class);

unregister_chrdev_region(dev_id, 1);

return ret;

}

printk(“Serial device is attached successfully.\n”);

return 0;

}

static int my_serial_remove(struct platform_device *pdv)

{

tty_unregister_driver(serial_driver);

class_destroy(my_class);

unregister_chrdev_region(dev_id, 1);

return 0;

}

static struct platform_driver my_serial_platform_driver = {

.probe = my_serial_probe,

.remove = my_serial_remove,

.driver = {

.name = “my_serial”,

.owner = THIS_MODULE,

},

};

static int __init my_serial_init(void)

{

return platform_driver_register(&my_serial_platform_driver);

}

static void __exit my_serial_exit(void)

{

platform_driver_unregister(&my_serial_platform_driver);

}

module_init(my_serial_init);

module_exit(my_serial_exit);

MODULE_LICENSE(“GPL”);

MODULE_AUTHOR(“Your Name”);

MODULE_DESCRIPTION(“My Serial Driver”);

MODULE_ALIAS(“my_serial”);

“`

在上述驱动程序中,主要的结构体有:

● tty_driver:定义了串口驱动程序的一些属性,例如tty_ops等。

● tty_port:串口端口的结构体,包含了串口的一些控制信息。

● tty_struct:串口的数据结构,指向从用户空间调用驱动的附加参数。

驱动程序中的核心函数包括:

● my_serial_dev_init:初始化串口设备端口,设置相关中断服务程序。

● my_serial_dev_open:打开串口设备,这里使用了tty_port_open。

● my_serial_dev_close:关闭串口设备,这里使用了tty_port_close。

● my_serial_dev_write:向串口设备写数据,这里使用了tty_write_room。

● my_serial_output_byte:输出一个字节到串口,由tty_sub_internal()调用。

● my_serial_set_termios:设置terminal区设置的串口参数,这里使用tty_standard_install。

● my_serial_probe:驱动的探测函数,注册字符设备和tty结构,初始化和设置串口的参数和中断服务程序。

● my_serial_remove:驱动的销毁函数,注销字符设备等等。

本节介绍了嵌入式Linux系统中串口驱动的基本实现流程,涉及到很多领域的知识,包括设备驱动、总线结构、数据结构等等。对于初学者来说,这一部分的内容可能会比较晦涩,但是掌握这些基础知识对于理解上层串口应用和调试都有很大的帮助。

三、串口应用

在嵌入式Linux系统中,除了底层的串口硬件驱动外,还需要一些上层的应用程序来实现串口通信的功能。Linux系统中最常用的上层串口应用程序是minicom、picocom等,它们提供了完整的串口设置、字符输入和数据读取和显示等功能。在本节中,我们将主要介绍如何使用Linux系统中的串口应用程序。

3.1、串口环境配置

在使用上层串口应用程序之前,需要先进行一些配置工作。首先要确认系统中是否已经安装了相关的串口应用程序(例如minicom等),如果没有安装则需要先进行安装,配置好对应的串口驱动,可以在Linux系统下使用以下命令查看系统中当前支持的串口:

“`

$ dmesg|grep tty

[ 0.000000] Kernel command line: console=ttyS2,115200n8 root=/dev/mmcblk0p2 rootfstype=ext4 rootwt

[ 0.230496] ff180000.serial: ttyS0 at MMIO 0xff180000 (irq = 21, base_baud = 7500000) is a M

[ 0.231688] console [ttyS0] enabled

[ 0.238301] m_serial 78b6000.serial: m_serial: detected port #0

[ 0.238341] m_serial 78b6000.serial: uartclk = 19202300

[ 0.238389] 78b6000.serial: ttyS1 at MMIO 0x78b6000 (irq = 128, base_baud = 115200) is a M

[ 0.238425] m_serial: driver initialized

[ 0.245754] m_serial 7874000.serial: m_serial: detected port #1

[ 0.245790] m_serial 7874000.serial: uartclk = 19202300

[ 0.245834] 7874000.serial: ttyS2 at MMIO 0x7874000 (irq = 129, base_baud = 115200) is a M

“`

在本例中,系统中支持三个串口ttyS0、ttyS1和ttyS2,其中ttyS2口被配置为控制台。接下来,我们需要确认串口的一些基础参数,例如波特率、停止位、数据位和奇偶校验等。这些参数需要和通讯设备的参数相配合,正确设置后才能进行数据传输。

我们可以通过以下命令修改串口的默认设置:

“`

$sudo stty -F /dev/ttyS0 9600 -parenb cs8 -cstopb

“`

此命令表示将串口的波特率设置为9600,不使用奇偶校验位,数据位为8,停止位为1,可以根据需要进行修改。

3.2、使用串口应用

在配置好串口环境后,我们可以使用Linux系统中的minicom等串口应用来进行数据交互和调试。minicom是一个轻量级的串口通信程序,功能较为全面,可以很方便地进行数据读取和字符输入等操作,使用该程序时需要注意以下几个步骤:

1. 首先确认已正确配置串口相关参数,例如波特率、数据位、停止位等。

2. 打开终端,输入以下命令打开minicom程序:

“`

$ sudo minicom -s

“`

该命令表示使用minicom程序,并打开设置界面。

3. 在设置界面中选择Serial Port Configuration选项,配置好串口相关参数,例如波特率、数据位、停止位等。

4. 在前面的界面中选择Exit选项,此时将会自动连接串口设备,并打开一个终端窗口进行数据输入。

相关问题拓展阅读:

嵌入式linux怎样用串口传送文件

那个当然要看了。就看那个基础版的,基础的东西要学,如果你不看你就搞不定一些系统管理的东西:比如说:环境变量,系统故障,一些文件目录的权限啊,不同软件包的安装之类的……

这些最基础的东西没有学好,就别说你还嵌入式linux了,万一进不了系统,空贺慎你拍渣就干瞪眼了..

而且嵌入式linux当然是建立在linux之上,你连这个平台都没有玩转,还怎么谈开发。。

所以,静下心来,基斗敬础的东西要学好,嵌入式很难的,学的东西又多,路还长的。。。

嵌入式linux 串口的介绍就聊到这里吧,感谢你花时间阅读本站内容,更多关于嵌入式linux 串口,嵌入式Linux串口应用全解析:从初学者到专家,嵌入式linux怎样用串口传送文件的信息别忘了在本站进行查找喔。


数据运维技术 » 嵌入式Linux串口应用全解析:从初学者到专家 (嵌入式linux 串口)