超详细Uart驱动框架及编程方法
一、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
结构变量注册到内核,其注册流程大致为:- uart_register_driver
- 申请n个uart_state结构的空间(根据driver支持的最大设备数,申请n个uart_state空间,每一个uart_state都有一个uart_port。)
- 分配及初始化tty_driver结构(分配一个tty_driver,设置默认波特率、检验方式等,并将uart_driver->tty_driver指向它)
- 设置tty_driver的操作集——tty_operations(它是tty核心与串口驱动通信的接口)
- 设置tty_port的操作集——tty_port_operations(初始化每一个uart_state的tty_port)
- 注册tty_driver(注册uart_driver实际上就是注册tty_driver,与用户空间打交道的工作完全交给tty_driver,这一部分是内核实现好的不需要修改)
- 完毕
(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驱动:
tty核心:是对整个tty设备的抽象,对用户提供统一的接口,包括sys_write->vfs_write
tty线路规程:是对传输数据的格式化,在tty_ldisc_N_TTY变量中描述,包括redirected_tty_write-> tty_write->n_tty_write
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驱动框架及编程方法相关推荐
- uart驱动框架及编程方法
一.UART介绍 UART(Universal Asynchronous Receiver/Transmitter),中文全称为通用异步收发传输器,是一种异步收发传输器,它将要传输的数据通过并行到串行 ...
- 超详细整合SSM框架--(Spring + Spring MVC + MyBatis)
超详细整合SSM框架--(Spring + Spring MVC + MyBatis) SpringMVC框架--文章跳转 Spring框架--文章跳转 Mybatis框架--文章跳转 整合思路 设计 ...
- Linux uart驱动框架
Linux uart驱动框架 串口驱动框架包括两部分 struct uart_driver int uart_register_driver(struct uart_driver *uart); vo ...
- 基于Linux的UART驱动框架源码分析笔记
文章目录 前言 一.I.MX6ULL串口接收和发送方式 1.非DMA方式 1.1.接收方式 1.2 发送方式 2.DMA方式 2.1.接收方式 2.2 发送方式 二.UART驱动注册 1.uart_r ...
- i.MX6ULL驱动开发 | 15 - Linux UART 驱动框架
Linux UART 驱动框架比较简单,不需要设备驱动,只需要UART设备驱动即可. 一.Linux UART驱动框架 1. uart_driver结构体 Linux将 UART 驱动抽象为 uart ...
- platform框架--Linux MISC杂项框架--Linux INPUT子系统框架--串行集成电路总线I2C设备驱动框架--串行外设接口SPI 设备驱动框架---通用异步收发器UART驱动框架
platform框架 input. pinctrl. gpio 子系统都是 Linux 内核针对某一类设备而创建的框架, input子系统是管理输入的子系统 pinctrl 子系统重点是设置 PIN( ...
- usb serial port 驱动_tty初探 — uart驱动框架分析
写在前面: 我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(1700 ...
- tty初探 — uart驱动框架分析
写在前面: 我们没有讲UART驱动,不过我们认为,只要系统学习了第2期,应该具备分析UART驱动的能力,小编做答疑几年以来,陆陆续续有不少人问到UART驱动怎么写,所以今天就分享一篇深度长文(1700 ...
- 【超详细】SSM框架项目实战|Spring+Mybatis+Springmvc框架项目实战整合-【CRM客户管理系统】——课程笔记
相关资料网盘链接: CRM客户管理系统资料 提取码 :0u04 P1--CRM阶段简介: web项目开发:如何分析,设计,编码,测试. 形成编程思想和编程习惯. P2--CRM的技术架构 ...
最新文章
- s1考试 图书管理系统 结构体版
- 打算把我的视频工具整合一下
- 自学计算机科学CS总结-by 要有光LTBL
- Paxos算法(Basic Paxos 与 Multi-Paxos思想)
- 电容的q值计算公式_在设计电路中电容容量大小、耐压等级选取详解 (转)
- 面试官系统精讲Java源码及大厂真题 - 29 押宝线程源码面试题
- Android8.1 MTK平台 截屏功能分析
- c++ mysql 写库 乱码 ??_mysql c++ 乱码 解决方法
- Study From Work(2011-3-2)
- 拓端tecdat|R语言ARMA-EGARCH模型、集成预测算法对SPX实际波动率进行预测
- Exchange Server 2007邮箱存储服务器的容量规划和性能调优(下)
- oracle 排序性能优化,Oracle优化之: 利用索引的有序性减少排序
- 如何轻松入门西门子 SCL(结构化控制语言) | 附官方教程下载
- 计算机 小学数学应用题教学设计,小学数学教案相遇问题应用题
- Hbase------regionServer
- 粉色系列资源网emlog模板挺新鲜
- 核磁共振三维重建(视频)
- MPLAB 创建新项目
- Android 开发者,你真的会用textview(maxEms和maxLength)的属性吗?
- 【电源】之【常用稳压IC大全】
热门文章
- USB Mass Storage Class
- [转载] 杜拉拉升职记——41 SOP的多种功能
- Chrome OS与Windows
- 超强破解Word“取消文档保护”密码
- 计算机相关专业大学上海,上海哪几个大学计算机专业的系比较好
- 元胞自动机:森林火灾模拟(Matlab:heatmap、colormap)
- 【Verilog】跨时钟域设计Clock Domain Crossing Design(Multi cycle path formulation with feedback acknowledge)
- VBA中引用单元格与区域
- Spring Boot教程(二十五)返回JSON格式
- 利用28335的epwm产生spwm波的总结