一、UART介绍

UART(Universal Asynchronous Receiver/Transmitter),中文全称为通用异步收发传输器,是一种异步收发传输器,它将要传输的数据通过并行到串行转换后再进行传输。该总线双向通信,可以实现全双工传输和接收。在嵌入式设备中,UART 用于主机与辅助设备通信。

1. 通信协议

UART通信协议的工作原理是将传输数据的每个比特位一位接一位地传输。其中各比特的意义如下:

  • 起始位:在时钟线为高电平时,数据线发出一个逻辑”0”的信号,表示传输字符的开始。

  • 数据位:紧接着起始位之后。数据位的个数可以是5、6、7、8等,构成一个字符。通常采用ASCII码。从最低位开始传送,靠时钟定位。

  • 奇偶校验位:数据位加上这一位后,使得“1”的位数应为偶数(偶校验)或奇数(奇校验),以此来校验数据传送的正确性 。

  • 停止位:在时钟线为高电平时,数据线发出一个逻辑”1”的信号。可以是1位、1.5位、2位的高电平。

由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。

  • 空闲位:当时钟线和数据新都处于逻辑“1”状态,表示当前线路上没有数据传送。

2. 波特率

波特率是衡量数据传送快慢的指标。表示每秒钟传送的符号数(symbol)。一个符号代表的信息量(比特数)与符号的阶数有关。例如传输使用256阶符号,每8bit代表一个符号,数据传送速率为120字符/秒,则波特率就是120 baud,比特率是120*8=960bit/s。这两者的概念很容易搞错。

UART 的接收和发送是按照相同的波特率进行收发的。波特率发生器产生的时钟频率不是波特率时钟频率,而是波特率时钟频率的16倍,目的是为在接收时进行精确的采样,以提取出异步的串行数据。根据给定的晶振时钟和要求的波特率,可以算出波特率分频计数值。

3. 工作原理

  • 发送数据过程:空闲状态,线路处于高电位;当收到发送数据指令后,拉低线路一个数据位的时间T,接着数据位按低位到高位依次发送,数据发送完毕后,接着发送奇偶检验位和停止位(停止位为高电位),一帧数据发送结束。

  • 接收数据过程: 空闲状态,线路处于高电位;当检测到线路的下降沿(线路电位由高电位变为低电位)时说明线路有数据传输,按照约定的波特率从低位到高位接收数据,数据接收完毕后,接着接收并比较奇偶检验位是否正确,如果正确则通知则通知后续设备准备接收数据或存入缓存。

  • 采用率:UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误码。一般UART一帧的数据位为8,这样即使每一个数据有一个时钟的误差,接收端也能正确地采样到数据。

  • 接收数据时序为:当检测到数据下降沿时,表明线路上有数据进行传输,这时计数器CNT开始计数,当计数器为8时,采样的值为“0”表示开始位;当计数器为24=16*1+8时,采样的值为bit0数据;当计数器的值为40=16*2+8时,采样的值为bit1数据;依次类推,进行后面6个数据的采样。如果需要进行奇偶校验位,则当计数器的值为152=16*9+8时,采样的值为奇偶位;当计数器的值为168=16*10+8时,采样的值为“1”表示停止位,一帧数据收发完成。

4. 流控

数据在两个串口传输时,常常会出现丢失数据的现象,或者两台计算机的处理速度不同,如台式机与单片机之间的通讯,接收端数据缓冲区以满,此时继续发送的数据就会丢失,流控制能解决这个问题,当接收端数据处理不过来时,就发出“不再接收”的信号,发送端就停止发送,直到收到“可以继续发送”的信号再发送数据。因此流控制可以控制数据传输的进程,防止数据丢失。

PC机中常用的两种流控为:硬件流控(包括RTS/CTS、DTR/CTS等)和软件流控制XON/XOFF(继续/停止)。

(1)硬件流控

  • 硬件流控制常用的有RTS/CTS流控制和DTR/DSR流控制两种。

    • **DTR–数据终端就绪(Data Terminal Ready)**低有效,当为低时,表示本设备自身准备就绪。此信号输出对端设备,使用对端设备决定能否与本设备通信。
    • **DSR-数据装置就绪(Data Set Ready)**低有效,此信号由本设备相连接的对端设备提供,当为低时,本设备才能与设备端进行通信。
    • **RTS - 请求发送(数据)(Request To Send)**低有效,此信号由本设备在需要发送数据给对端设备时设置。当为低时,表示本设备有数据需要向对端设备发送。对端设备能否接收到本方的发送数据,则通过CTS信号来应答。
    • **CTS - 接收发送(请求)(Clear To Send)**低有效,对端设备能否接收本方所发送的数据,由CTS决定。若CTS为低,则表示对端的以准备好,可以接收本端发送数据。
  • 以RTS/CTS流控制分析,分析主机发送/接收流程:

    • 物理连接

      • 主机的RTS(输出信号),连接到从机的CTS(输入信号)。主机是CTS(输入信号),连接到从机的RTS(输入信号)。
    • 主机的发送过程:主机查询主机的CTS脚信号,此信号连接到从机的RTS信号,受从机控制。如果主机CTS信号有效(为低),表示从机的接收FIFO未满,从机可以接收,此时主机可以向从机发送数据,并且在发送过程中要一直查询CTS信号是否为有效状态。主机查询到CTS无效时,则中止发送。主机的CTS信号什么时候会无效呢?从机在接收到主机发送的数据时,从机的接收模块的FIFO如果满了,则会使从机RTS无效,也即主机的CTS信号无效。主机查询到CTS无效时,主机发送中止。

    • 主机接收模式:如果主机接收FIFO未满,那么使主机RTS信号有效(为低),即从机的CTS信号有效。此时如果从机要发送,发送前会查询从机的CTS信号,如果有效,则开始发送。并且在发送过程中要一直查询从机CTS信号的有效状态,如果无效则终止发送。是否有效由主机的RTS信号决定。如果主机FIFO满了,则使主机的RTS信号无效,也即从机CTS信号无效,主机接收中止。

(2)软件流控

  • 由于电缆的限制,在普通的控制通讯中一般不采用硬件流控制,而是使用软件流控制。一般通过XON/XOFF来实现软件流控制。

  • 常用方法是:

    • 当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发送XOFF字符后就立即停止发送数据。
    • 当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发送XON字符(十进制的17或Control-Q),发送端收到XON字符后就立即开始发送数据。
  • 一般可从设备配套源程序中找到发送端收到XON字符后就立即发送数据。一般可以从设备配套源程序中找到发送的是什么字节。应注意,若传输的是二进制的数据,标志字符也可能在数据流中出现而引起误操作,这是软件流控的缺陷,而硬件流控不会出现这样的问题。

二、UART驱动编程

首先,带大家简单回顾一下tty架构情况,详情可参考一文彻底讲清Linux tty子系统架构及编程实例

  • 整个 tty架构大概的样子如上图所示,简单来分的话可以说成两层:

    • 一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体;
    • 另一层是 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问。
  • 发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传到tty驱动,tty驱动将数据转换为可以发给硬件的格式。
  • 接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线路规程驱动,再进入tty核心,在这里它被一个用户获取。

1. UART驱动编写

(1) 注册uart_driver

  • 在uart driver的初始阶段(module_init()),需要将我们的struct uart_driver结构变量注册到内核,其注册流程大致为:

    1. uart_register_driver
    2. 申请n个uart_state结构的空间根据driver支持的最大设备数,申请n个uart_state空间,每一个uart_state都有一个uart_port。
    3. 分配及初始化tty_driver结构分配一个tty_driver,设置默认波特率、检验方式等,并将uart_driver->tty_driver指向它
    4. 设置tty_driver的操作集——tty_operations(它是tty核心与串口驱动通信的接口
    5. 设置tty_port的操作集——tty_port_operations(初始化每一个uart_state的tty_port
    6. 注册tty_driver注册uart_driver实际上就是注册tty_driver,与用户空间打交道的工作完全交给tty_driver,这一部分是内核实现好的不需要修改
    7. 完毕

(2) 添加uart_port

  • uart_add_one_port接口用于注册一个uart port 到uart driver上。此后uart driver就可以访问对应的uart port进行数据收发。该接口在uart driver中的probe函数中调用,所以必须保证晚于uart_register_driver的注册过程。

  • uart driver在调用接口前,要手动设置uart_port的操作集uart_ops,使得通过调用uart_add_one_port接口后驱动完成硬件的操作接口注册。

  • uart添加port流程如下图所示:

2. 数据收发流程分析

(1)打开设备

(2)数据发送流程(write)

(3) 数据接收流程(read)

(4)关闭设备(close)

3. 注销流程

(1) 移除uart_port

此接口用于从uart driver上注销一个uart port,该接口在uart driver中的remove函数中调用。uart移除port的流程如图3-9所示:

(2) 注销uart_driver

此接口在uart driver中调用,用来从kernel中注销uart_driver,调用阶段在uart driver的退出阶段,例如:module_exit(),uart driver的注销流程如图3.10所示

三、UART驱动中的数据结构

  • 串口驱动数据结构总图

  • 以s3c2440开发板为例,讲述其中UART驱动的数据结构构成:

    • 因为我们和开发板的人机交互的接口是Windows下的串口控制台。这就是上面所说的控制台终端。但是我们用了console = ttySAC0。即把串口终端当做控制台终端。所以我们要研究具体的代码需要cd到serial子目录下。即串口终端目录。ls显示serial下的文件结点。如图所示:

  • 我们主要关心的是两类文件:

    • 一类是与体系结构和板载资源无关的通用串口操作文件(samsung.c)。
    • 一类是与体系结构相关的硬件操作文件(s3c2440.c s3c2410.c s5pv210.c等)。我们为了得到具体的调用链。在具体的发送函数中加入回溯。如图所示。

  • 我们得到的函数调用链是这样的(以发送函数。即文件的写操作为例)

write-> sys_write-> vfs_write-> redirected_tty_write-> tty_write-> n_tty_write-> uart_write-> uart_start-> s3c24xx_serial_start_tx

  • 从具体代码上来看。这些函数基本上都是通过结构体中的函数指针调用。我们可以把这个调用链分为三个部分。即tty子系统核心、tty链路规程、tty驱动:
  1. tty核心:是对整个tty设备的抽象,对用户提供统一的接口,包括sys_write->vfs_write

  2. tty线路规程:是对传输数据的格式化,在tty_ldisc_N_TTY变量中描述,包括redirected_tty_write-> tty_write->n_tty_write

  3. tty驱动:是面向tty设备的硬件驱动,这里面真正的对硬件进行操作,包括uart_write-> uart_start-> s3c24xx_serial_start_tx

这是从具体函数的角度来看的调用链。下面为了从数据结构的角度来分析调用链,介绍linux内核中针对于这一个串口硬件的主要数据结构。

(1) uart_driver

就是uart驱动程序结构,封装了tty_driver,使得底层的UART驱动无需关心tty_driver,具体定义如下:

struct uart_driver{struct module *owner;const char       *driver_name;const char     *dev_name;int               major;int               minor;int           nr;struct console  *cons;/* these are private;the low level driver should not* touch these; they should be initialised to NULL*/struct uart_state *state;...
}

(2) uart_port

uart_port用于描述一个UART端口(直接对应于一个串口)的I/O端口或者IO内存地址等信息。

typedef unsigned int __bitwise__ upf_t;
struct uart_port{spinlock_t     lock;unsigned long  iobase;unsigned char __iomem *membase;unsigned int  (*serial_in)(struct uart_port *, int);void  (*serial_out)(struct uart_port *, int);void     (*set_termios)(struct uart_port *, struct ktermios *new,struct ktermios *old);void    (*pm)(struct uart_port *, unsigned int state, unsigned int old);unsigned int  irq;unsigned long   irqflags;unsigned int    uartclk;...
}

(3) uart_ops

uart_ops定义了针对UART的一系列操作。注意这里不要把uart_ops结构和uart_ops变量混淆。uart_ops结构是我们这里的数据结构。而uart_ops变量则是一个tty_operations的变量。

在serial_core.c中定义了tty_operations的实例。即uart_ops变量,包含uart_open();uart_close();uart_send_xchar()等成员函数,这些函数借助uart_ops结构体中的成员函数来完成具体的操作:

struct uart_ops{unsigned int (*tx_empty)(struct uart_port *);void (*set_mctrl)(struct uart_port *, unsigned int mctrl);unsigned int (*get_mctrl)(struct uart_port *);void (*stop_tx)(struct uart_port *);void (*start_tx)(struct uart_port *);void (*send_xchar)(struct uart_port *, char ch);void (*stop_rx)(struct uart_port *);void (*enable_ms)(struct uart_port *);void (*break_ctl)(struct uart_port *, int ctl);void (*startup)(struct uart_port *);void (*shutdown)(struct uart_port *);
}

uart_ops变量是tty_operations型的一个变量。如下图所示:

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,
}

(4) uart_state

uart_state是uart的状态结构

struct uart_state{struct tty_port        port;int                    pm_state;struct circ_buf        xmit;struct tasklet_struct tlet;struct uart_port    *uart_port;
};
#define UART_XMIT_SIZE  PAGE_SIZE

(5) uart_info

uart_info是uart的信息结构,在这个体系结构下定义为s3c24xx_uart_info:

struct s3c24xx_uart_info{char        *name;unsigned int  type;unsigned int   fifosize;unsigned long  rx_fifomask;unsigned long   rx_fifoshift;unsigned long  rx_fifofull;
}
  • 所以很显然,用数据结构来描述函数调用链就是:

uart_driver -> uart_state-> uart_port-> uart_ops-> 特定的函数指针。

(6) 发送函数及中断服务例程

使能发送并没有真正的发送,而只是使能发送中断(enable_irq(ourport->tx_irq);)而已。

static void s3c24xx_serial_start_tx(struct uart_port *port)
{struct s3c24xx_uart_port *ourport = to_ourport(port);if(!tx_enabled(port)){if(port->flags & UPF_CONS_FLOW)s3c24xx_serial_rx_disable(port);enable_irq(ourport->tx_irq);tx_enabled(port) = 1;}
}

这是因为ARM9处理器上有一个循环缓冲。用户从write系统调用传下来的数据就会写入这个UTXH0寄存器。发送完事之后处理器会产生一个内部中断。我们通过这个内部中断就可以实现流控过程,我们打开芯片手册可以看到如下字样:

如下才是发送中断的ISR(Interrupt Service Routine)中断服务例程。一个irqreturn_t类型的handler。

static irqreturn_t s3c24xx_serial_tx_chars(int irq, void *id)
{struct s3c24xx_uart_port *ourport = id;struct uart_port *port = &ourport->port;struct cirt_buf *xmit = &port->state->xmit;int count = 256;if(port->x_char){wr_regb(port,S3C2410_UTXH, port->x_char);port->icount.tx++;port->x_char = 0;goto out;}...
}

这个wr_regb(port, S3C2410_UTXH, port->x_char);就是往特定寄存器写的过程。


至此我们的分析已经结束。相信读者对于Linux下的tty子系统已经有一个概观了。下面是这个uart驱动的总图。结合数据结构的调用链。Linux内核完成了驱动模型和特定硬件的分离:

在下一篇文章中,将继续讲解如何实际编写一个串口模块,欢迎点赞加关注!

【参考】

嵌入式大杂烩.原文

基于Linux的tty架构及UART驱动详解 (qq.com)

超详细Uart驱动框架及编程方法相关推荐

  1. uart驱动框架及编程方法

    一.UART介绍 UART(Universal Asynchronous Receiver/Transmitter),中文全称为通用异步收发传输器,是一种异步收发传输器,它将要传输的数据通过并行到串行 ...

  2. 超详细整合SSM框架--(Spring + Spring MVC + MyBatis)

    超详细整合SSM框架--(Spring + Spring MVC + MyBatis) SpringMVC框架--文章跳转 Spring框架--文章跳转 Mybatis框架--文章跳转 整合思路 设计 ...

  3. Linux uart驱动框架

    Linux uart驱动框架 串口驱动框架包括两部分 struct uart_driver int uart_register_driver(struct uart_driver *uart); vo ...

  4. 基于Linux的UART驱动框架源码分析笔记

    文章目录 前言 一.I.MX6ULL串口接收和发送方式 1.非DMA方式 1.1.接收方式 1.2 发送方式 2.DMA方式 2.1.接收方式 2.2 发送方式 二.UART驱动注册 1.uart_r ...

  5. i.MX6ULL驱动开发 | 15 - Linux UART 驱动框架

    Linux UART 驱动框架比较简单,不需要设备驱动,只需要UART设备驱动即可. 一.Linux UART驱动框架 1. uart_driver结构体 Linux将 UART 驱动抽象为 uart ...

  6. platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架

    platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...

  7. usb serial port 驱动_tty初探 — uart驱动框架分析

    写在前面: 我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(1700 ...

  8. tty初探 — uart驱动框架分析

    写在前面: 我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(1700 ...

  9. 【超详细】SSM框架项目实战|Spring+Mybatis+Springmvc框架项目实战整合-【CRM客户管理系统】——课程笔记

    相关资料网盘链接: CRM客户管理系统资料 提取码 :0u04 P1--CRM阶段简介: web项目开发:如何分析,设计,编码,测试.        形成编程思想和编程习惯. P2--CRM的技术架构 ...

最新文章

  1. s1考试 图书管理系统 结构体版
  2. 打算把我的视频工具整合一下
  3. 自学计算机科学CS总结-by 要有光LTBL
  4. Paxos算法(Basic Paxos 与 Multi-Paxos思想)
  5. 电容的q值计算公式_在设计电路中电容容量大小、耐压等级选取详解 (转)
  6. 面试官系统精讲Java源码及大厂真题 - 29 押宝线程源码面试题
  7. Android8.1 MTK平台 截屏功能分析
  8. c++ mysql 写库 乱码 ??_mysql c++ 乱码 解决方法
  9. Study From Work(2011-3-2)
  10. 拓端tecdat|R语言ARMA-EGARCH模型、集成预测算法对SPX实际波动率进行预测
  11. Exchange Server 2007邮箱存储服务器的容量规划和性能调优(下)
  12. oracle 排序性能优化,Oracle优化之: 利用索引的有序性减少排序
  13. 如何轻松入门西门子 SCL(结构化控制语言) | 附官方教程下载
  14. 计算机 小学数学应用题教学设计,小学数学教案相遇问题应用题
  15. Hbase------regionServer
  16. 粉色系列资源网emlog模板挺新鲜
  17. 核磁共振三维重建(视频)
  18. MPLAB 创建新项目
  19. Android 开发者,你真的会用textview(maxEms和maxLength)的属性吗?
  20. 【电源】之【常用稳压IC大全】

热门文章

  1. USB Mass Storage Class
  2. [转载] 杜拉拉升职记——41 SOP的多种功能
  3. Chrome OS与Windows
  4. 超强破解Word“取消文档保护”密码
  5. 计算机相关专业大学上海,上海哪几个大学计算机专业的系比较好
  6. 元胞自动机:森林火灾模拟(Matlab:heatmap、colormap)
  7. 【Verilog】跨时钟域设计Clock Domain Crossing Design(Multi cycle path formulation with feedback acknowledge)
  8. VBA中引用单元格与区域
  9. Spring Boot教程(二十五)返回JSON格式
  10. 利用28335的epwm产生spwm波的总结