发布时间:2025-12-09 16:21:36 浏览次数:5
在Linux中,常碰到“控制台”、“终端”、“console”、“tty”等术语,也会经常使用一些设备文件:/dev/console、/dev/ttyS0、/dev/ttyUSB0等。tty是Teletype的缩写,Teletype是最早出现的一种终端设备,Linux通常使用tty来表示“终端”,终端设备的种类有很多,比如串行终端、键盘和显示器、通过网络实现的终端等。
UART与USART都是单片机上的串口通信, UART(universal asynchronous receiver and transmitter)通用异步收/发器,USART(universal synchronous asynchronous receiver and transmitter)通用同步/异步收/发器。从名字上可以看出,USART在UART基础上增加了同步功能,即USART是UART的增强型。当我们使用USART在异步通信的时候,它与UART没有什么区别,但是用在同步通信的时候,区别就很明显了:同步通信需要时钟来触发数据传输,也就是说USART相对UART的区别之一就是能提供主动时钟。
串口属于终端设备,它的驱动程序并不仅仅是简单的初始化硬件、接收/发送数据,在基本硬件操作的基础上,还增加了很多软件功能,是一个多层次的驱动程序。
路径: \linux-at91\drivers\tty\serial\atmel_serial.c。对于Atmel平台,是这样来注册串口驱动的,首先分配一个struct uart_driver 简单填充,并调用uart_register_driver 注册到内核中去。
struct uart_driver 中,只是填充了一些名字、设备号等信息,这些都是不涉及底层硬件访问的。
下面看一下完整的 uart_driver 结构。
在struct uart_driver atmel_uart结构体中,有两个成员未被赋值,分别是tty_driver和uart_state。对于tty_driver,代表的是上层,它会在 uart_ register_driver 的过程中赋值。而uart_state ,则代表下层,uart_state也会在uart_ register_driver 的过程中分配空间,但是它里面真正设置硬件相关的东西是 uart_state->uart_port ,这个uart_port 是需要从其它地方调用 uart_add_one_port 来添加的。
下层(串口驱动层)
首先,认识几个结构体。
1) 结构体:uart_state。
路径:\linux-at91\linux-at91\linux-at91\include\linux\serial_core.h
在注册 driver 时,会根据 uart_driver->nr 来申请 nr 个 uart_state 空间,用来存放驱动所支持的串口(端口)物理信息。
2) 结构体:uart_port
路径:\linux-at91\linux-at91\linux-at91\include\linux\serial_core.h
struct uart_port,这个结构体对应于一个串口设备,如果平台有3个串口那么就需要填充3个uart_port ,并且通过 uart_add_one_port 添加到 uart_driver->uart_state->uart_port 中去。当然 uart_driver 有多个 uart_state ,每个 uart_state 有一个 uart_port。在 uart_port 里还有一个非常重要的成员 struct uart_ops *ops ,一般芯片厂家都写好了,只需要稍作修改。
下面是struct uart_ops 结构体。
上层(tty核心层)
tty 核心层要从 uart_register_ driver 来看起,因为 tty_driver 是在注册过程中构建的,顺便了解注册过程。
uart_register_ driver 注册过程干了哪些事:
1)根据driver支持的最大设备数,申请n个 uart_state 空间,每一个 uart_state 都有一个 uart_port ;
2)分配一个 tty_driver ,并将drv->tty_driver 指向它;
3)对 tty_driver 进行设置,其中包括默认波特率、校验方式等,还有一个重要的ops ,uart_ops ,它是tty核心与串口驱动通信的接口;
4)初始化每一个 uart_state ;
5)注册 tty_driver 。
注册 uart_driver 实际上是注册 tty_driver,因此与用户空间打交道的工作完全交给了 tty_driver ,而且这一部分都是内核实现好的,基本不需要修改,了解一下工作原理即可。
这里介绍下uart_ops结构体。
static const struct tty_operations uart_ops = {.open = uart_open,.close = uart_close,.write = uart_write,.put_char = uart_put_char,.flush_chars = uart_flush_chars,.write_room = uart_write_room,.chars_in_buffer= uart_chars_in_buffer,.flush_buffer = uart_flush_buffer,.ioctl = uart_ioctl,.throttle = uart_throttle,.unthrottle = uart_unthrottle,.send_xchar = uart_send_xchar,.set_termios = uart_set_termios,.set_ldisc = uart_set_ldisc,.stop = uart_stop,.start = uart_start,.hangup = uart_hangup,.break_ctl = uart_break_ctl,.wait_until_sent= uart_wait_until_sent,#ifdef CONFIG_PROC_FS.proc_fops = &uart_proc_fops,#endif.tiocmget = uart_tiocmget,.tiocmset = uart_tiocmset,.get_icount = uart_get_icount,#ifdef CONFIG_CONSOLE_POLL.poll_init = uart_poll_init,.poll_get_char = uart_poll_get_char,.poll_put_char = uart_poll_put_char,#endif};这个是 tty 核心层的 ops ,简单一看,后面分析调用关系时,我们再来看具体的里边的函数。
下面来看 tty_driver 的注册,tty_register_driver。
tty_register_driver注册过程干了哪些事:
1)为线路规程和termios分配空间,并使 tty_driver 相应的成员指向它们;
2)注册字符设备,名字是 uart_driver->name 我们这里是“ttyS”,文件操作函数集是 tty_fops。可查看tty_cdev_add 函数的定义;
3)将该 uart_driver->tty_drivers 添加到全局链表 tty_drivers ;
4)向 proc 文件系统添加 driver ,这个暂时不了解。
下面是结构体 tty_fops。
至此,文章起初的结构图中的4个ops已经出现了3个,另一个关于线路规程的在哪?下面来看一下调用关系。
调用关系
tty_driver注册了一个字符设备(/dev/ttyS0),从它的 tty_fops 入手,可以得知用户空间是如何访问到最底层的硬件操作函数。以 open、read、write 为例,用户空间 open 时将调用到 uart_port.ops.startup ,在用户空间 write 则调用 uart_port.ops.start_tx。这些内核都已经实现好,在驱动开发过程中几乎不涉及这些代码的修改移植工作。图2.2为串口读写函数调用关系。
目前Linux2.6版本以后的ARM 结构使用设备树(DTS,Device Tree Source)来分管设备,DeviceTree是一种描述硬件的数据结构,为什么要引入DTS?这是因为在Linux2.6中,ARM架构的板极硬件细节过多地被硬编码在arch/arm/plat-xxx和arch/arm/mach-xxx,比如板上的platform设备、resource、i2c_board_info、spi_board_info以及各种硬件的platform_data,这些板级细节代码对内核来讲只不过是垃圾代码。而采用DeviceTree后,许多硬件的细节可以直接透过它传递给Linux,而不再需要在kernel中进行大量的冗余编码。
platform_driver的入口函数,仍采用platform_driver_register注册。platform_driver_register用来枚举名称为 “atmel_usart” 的平台设备,只要内核中有相同名称的平台设备,platform_driver_register函数就会调用atmel_serial_driver 中的atmel_serial_probe函数来枚举它。
函数:platform_driver_register
结构体:atmel_serial_driver
看结构体参数 atmel_serial_driver 的定义,该结构体一个很重要的成员是 .of_match_table = of_match_ptr(atmel_serial_dt_ids), of_match_ptr函数用于将atmel_serial_dt_ids 中.compatible 属性与.dts 文件中描述的设备节点.compatible 属性进行匹配,且不区分大小写,当完成配对之后,就调用atmel_serial_probe函数完成驱动注册的最后工作。
结构体:atmel_serial_dt_ids
设备树
Dtsi和dts同为设备树文件,dtsi为被包含文件,可被dts或者dtsi文件包含。
设备树文件sama5d4.dtsi中对串口设备节点的描述。路径:\linux-at91\arch\arm\boot\dts\sama5d4.dtsi。
sama5d4.dtsi中描述了7个串口设备节点,2个uart,5个usart。
(别名)
(节点描述)描述了节点的一些属性。
(引脚定义)
设备树文件at91-sama5d4_xplained.dts中对串口设备节点的描述。路径:\linux-at91\arch\arm\boot\dts\at91-sama5d4_xplained.dts。
阅读A5处理器资料,查看UART章节和USART章节对串口的相关描述,根据硬件原理图确定串口的引脚定义。
1) 设备树文件,到sama5d4.dtsi 文件下去搜索 USART ,对应找到相应的串口节点以及串口的引脚定义部分。sama5d4.dtsi 文件不需要进行修改。
节点描述
引脚定义
2) 设备树文件,查看 at91-sama5d4_xplained.dts 文件中的串口USART,在此文件下对节点进行相应参数修改。
—原来的串口节点信息如下:原始状态使能了uart0但我们没有接其引脚使用,使能了usart3的收发功能,使能了usart4但不具备收发功能,没有使能usart2。
—修改后的串口节点信息如下:
3) 驱动文件,在\linux-at91\drivers\tty\serial\atmel_serial.c文件中,查找static int __init atmel_serial_init(void)函数,找到uart_register_driver()函数,转到该函数定义,函数定义在 serial_core.c 文件中,文件中修改波特率,。原始波特率9600 ,可按自己的需求修改波特率,我们这里将其改为115200。
到此,串口驱动移植完成,重新编译驱动文件,包括设备树文件和驱动文件,烧录至开发板,测试串口是否可以正常使用。
将编译成功的内核镜像和设备树文件编译到开发板中,然后使用超级终端接开发板查看打印的日志。
串口收发功能测试步骤如下:
1) 查看tty设备。
(1)编译运行内核,如果UART驱动加载成功会在/dev目录下产生相应UART设备节点; 名称为:ttySx。这里会产生 ttyS0, ttyS1, ttyS2, ttyS5(没有使用)。
ttyS0对应USART3,为DEGB口;
ttyS1对应 USART4;
ttyS2对应 USART2;
ttyS5 对应UART0;
(2)运行命令: $ cat /proc/tty/driver/atmel_serial ,可以查看显示设备节点详细信息,其中通过地址可以对照设备节点;
root@sama5d4-xplained:/proc/tty/driver# cat atmel_serial
serinfo:1.0 driver revision:
0: uart:ATMEL_SERIAL mmio:0xFC00C000 irq:34 tx:2176533 rx:2142522 RTS|CTS|DTR|DSR|CD|RI
1: uart:ATMEL_SERIAL mmio:0xFC010000 irq:35 tx:1253 rx:68 brk:68 CTS|DSR|CD|RI
2: uart:ATMEL_SERIAL mmio:0xFC008000 irq:33 tx:6 rx:0 DSR|CD|RI
如果UART设备节点未产生,可在其相应驱动程序xx_probe函数中添加打印,查看xx_probe函数是否被调用,进一步查找原因。
2) 通过跳冒选择串口。
下图中,两个黑色串口共用一个芯片。左边USART3和USART4共用一个串口,通过跳冒来区分,右边USART2单独使用一个串口。
跳线冒J3用于设置选择DEBG- USART3和串口4,电路图如下图4.2。
具体的跳线设置如下表4.1和表4.2所示。
表4.1 DEGU模式跳线状态
3) 如果成功产生了UART设备节点,下面进行串口收发测试;
方法1:
可通过软件回环测试确认UART驱动程序功能是否正常。编写测试程序,将串口2、3引脚短接。读写数据。如果管脚信号测试通过,则串口功能基本调试成功。此方法的优点是无需上位机串口助手的配合,在串口模块到位之前提前完成接口调试工作。
方法2:
Echo 命令回显测试;
在超级终端下进入 /dev 目录下,以测试USART2为例,输入命令: echo test > ttyS2.在串口助手中查看收到的信息,这一步测试串口的发。在超级终端下输入命令: cat ttyS2, 在串口助手中发送clear,在超级终端下查看收到信息,这一步查看串口的收。由于USART2不是调试口,无法打印系统信息,需要使用SSH连接开发板。具体的测试如下图所示。
方法3: 使用系统集成的串口调试工具 busybox microcom
1) busybox microcom工具的使用;
命令(busybox microcom)使用方法很简单:
Usage: microcom [-d DELAY] [-t TIMEOUT] [-s SPEED] [-X] TTY
参数如下:
-d 表示延时时间,一般我都不设置。
-t 表示超时时间,超多少时间就自动退出。单位为ms
-s 表示传输速度,波特率的意思,这个根据自己的情况而定。
-X 不加
最后指定你的串口设备。如 /dev/ttyO0 , 这是TI的串口设备节点
2)测试方式如下:
将要测试串口与pc端连接,在pc端开启串口调试工具,波特率设定跟等下microcom设定一样。
在产品端运行如下命令:
microcom -s 115200 /dev/ttyS2
在超级终端命令行输出信息,在PC是否有正确显示。反之也测试一下。