你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

基于Linux的tty架构

[复制链接]
gaosmile 发布时间:2021-2-23 11:23
一、模块硬件学习
3 `" x% s1 p# @. i, \: k
7 n+ G- b# J" E1 D  Q; Z
1.1.        Uart介绍

通用异步收发传输器(Universal Asynchronous Receiver/Transmitter),通常称为UART,是一种异步收发传输器,是电脑硬件的一部分。它将要传输的资料在串行通信与并行通信之间加以转换。

作为把并行输入信号转成串行输出信号的芯片,UART 通常被集成于其他通讯接口的连上。

UART 是一种通用串行数据总线,用于异步通信。该总线双向通信,可以实现全双工传输和接收。在嵌入式设备中,UART 用于主机与辅助设备通信,如汽车音与外接AP 之间的通信,与PC 机通信包括与监控调试器和其它器件,如EEPOM通信。

1.1.1.        通信协议

UART作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。其中各位的意义如下:

  • 起始位:先发出一个逻辑”0”的信号,表示传输字符的开始。

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

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

  • 停止位:它是一个字符数据的结束标志。可以是1位、1.5位、2位的高电平。

    ' F: t" N% @/ C1 ?9 u0 T+ C

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

  • 空闲位:处于逻辑“1”状态,表示当前线路上没有数据传送。
    0 q$ w' U# E* ?6 a6 R% w# Q% [

Uart传输数据如图2-1所示:

微信图片_20210223111907.png

1.1.2.        波特率

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

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

1.1.3.        工作原理

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

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

由于UART是异步传输,没有传输同步时钟。为了能保证数据传输的正确性,UART采用16倍数据波特率的时钟进行采样。每个数据有16个时钟采样,取中间的采样值,以保证采样不会滑码或误码。

一般UART一帧的数据位为8,这样即使每一个数据有一个时钟的误差,接收端也能正确地采样到数据。

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

1.1.4.        RS232与RS485

UART:通常说的UART指的是一种串行通信协议,规定了数据帧格式,波特率等。RS232和RS485:是两种不同的电气协议,也就是说,是对电气特性以及物理特性的规定,作用于数据的传输通路上,它并不含对数据的处理方式。

对应的物理器件有RS232或者RS485驱动芯片,将CPU经过UART传送过来的电压信号驱动成RS232或者RS485电平逻辑。

RS232使用3-15V有效电平,而UART,因为对电气特性没有规定,所以直接使用CPU使用的电平,即TTL电平(在0-3.3V之间)。

更具体的,电气的特性也决定了线路的连接方式,比如RS232,规定用电平表示数据,因此线路就是单线路的,两根线能达到全双工的目的;RS485使用差分电平表示数据,因此必须用两根线才能达到传输数据的基本要求,要实现全双工,必须使用4根线。

RS232和RS485的区别

(1)抗干扰性

  • RS485 接口是采用平衡驱动器和差分接收器的组合,具有抑制共模干扰的能力,抗噪声干扰性强。
  • RS232接口使用一根信号线和一根信号返回线而构成供地的传输形式,这种共地传输容易产生共模干扰,所以抗噪声干扰性弱。(2)传输距离
  • RS485 接口的最大传输距离标准值为1200 米(9600bps 时),实际上可达3000米。
  • RS232 传输距离有限,最大传输距离标准值为50米,实际上也只能用15米左右。
  • (3)通信能力
  • RS485接口在总线上最多可以连接128个收发器,即具有多站能力,而这样的用户可以利用单一的RS485接口方便的建立起设备网络。
  • RS232只允许一对一通信。
  • (4)传输速率
  • RS232传输速率较低,在异步传输时,波特率为20Kbps.
  • RS485的数据最高传输速率为10Mbps.
  • (5)        信号线
  • RS485全双工:uart-tx 1根线,变成 RS485- A/B 2根线;uart-rx 1根线,变成 RS485- x/y 2根线,
  • RS485半双工: 将全双工的 A/B; X/Y 合并起来,分时复用。
  • RS232只允许一对一通信
  • (6)电气电平值
  • 逻辑“1”以两线间的电压差为+(2-6)V表示;逻辑“0”以两线间的电压差为-(2-6)V表示。
  • 在RS232中任何一条信号的电压均为负逻辑关系。即:逻辑“1”-5-15V;逻辑“0”,+5~+15V,噪声容限为2V。即要求接收器能识别低至+3V的信号作为逻辑“0”,高到-3V的信号的信号作为逻辑“1”。
  • RS232接口的信号电平值较高,易损坏接口电路的芯片,又因为与TTL电平不兼容故使用电平转换电路方能与TTL电路连接。
  • RS485接口信号电平比RS232降低了,就不易损坏接口电路的芯片,且该电平与TTL电平兼容,方便与TTL电路连接。
    . A  U" i, C: F2 e
1.1.5.        流控

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

因此流控制可以控制数据传输的进程,防止数据丢失。PC机中常用的两种流控为:硬件流控(包括RTS/CTS、DTR/CTS等)和软件流控制XON/XOFF(继续/停止)。

1.1.5.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流控制分析,分析主机发送/接收流程:

物理连接

微信图片_20210223111911.png

主机的RTS(输出信号),连接到从机的CTS(输入信号)。主机是CTS(输入信号),连接到从机的RTS(输入信号)。

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

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

    7 ^1 r- R6 U: N6 W8 j
1.1.5.2.        软件流控

由于电缆的限制,在普通的控制通讯中一般不采用硬件流控制,而是使用软件流控制。

一般通过XON/XOFF来实现软件流控制。常用方法是:当接收端的输入缓冲区内数据量超过设定的高位时,就向数据发送端发送XOFF字符后就立即停止发送数据。

当接收端的输入缓冲区内数据量低于设定的低位时,就向数据发送端发送XON字符(十进制的17或Control-Q),发送端收到XON字符后就立即开始发送数据。

一般可从设备配套源程序中找到发送端收到XON字符后就立即发送数据。一般可以从设备配套源程序中找到发送的是什么字节。

应注意,若传输的是二进制的数据,标志字符也可能在数据流中出现而引起误操作,这是软件流控的缺陷,而硬件流控不会出现这样的问题。

二、Linux serial框架

在Linux系统中,终端是一种字符型设备,它有多种类型,通常使用tty(Teletype)来简称各种类型的终端设备。

对于嵌入式系统而言,最普遍采用的是Uart(Universal Asynchronous Receiver/Transmitter),串行端口,日常生活中简称端口

2.1.        TTY驱动程序框架5 c% A4 B" ^% M- |: A: I
2.1.1.        TTY概念: ^) R% e. N- m( x, T
2.1.1.1.        串口终端(/dev/ttyS*)

串口终端是使用计算机串口连接的终端设备。Linux把每个串行端口都看做是一个字符设备。这些串行端口所对应的设备名称是/dev/ttySAC*;

2.1.1.2.        控制台终端(/dev/console)

在Linux系统中,计算机的输出设备通常被称为控制台终端,这里特指printk信息输出到设备。/dev/console是一个虚拟的设备,它需要映射到真正的tty上,比如通过内核启动参数“console=ttySCA0”就把console映射到了串口0

2.1.1.3.        虚拟终端(/dev/tty*)

当用户登录时,使用的是虚拟终端。使用Ctcl+Alt[F1 - F6]组合键时,我们就可以切换到tty1、tty2、tty3等上面去。tty*就称为虚拟终端,而tty0则是当前所使用虚拟终端的一个别名。

2.1.2.        TTY架构分析

整个 tty架构大概的样子如图3.1所示,简单来分的话可以说成两层,一层是下层我们的串口驱动层,它直接与硬件相接触,我们需要填充一个 struct uart_ops 的结构体,另一层是上层 tty 层,包括 tty 核心以及线路规程,它们各自都有一个 Ops 结构,用户空通过间是 tty 注册的字符设备节点来访问。

微信图片_20210223111915.png
) h( C9 b6 P2 s  D, j: \/ |图3.1tty架构图

如图3.2所示,tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传到tty驱动,tty驱动将数据转换为可以发给硬件的格式。

接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线路规程驱动,再进入tty核心,在这里它被一个用户获取。

微信图片_20210223111918.png
* m" s9 f; b& o6 m; }, s# l图3.2 tty设备发送、接收数据流程* w) _5 Y. T  |) a3 c

; J- A% P6 [# @- I$ S; a
; `7 }! w4 `* N4 D
2.2.        关键数据结构# s$ U# [3 B& u3 h
2.2.1.        Struct uart_driver

uart_driver 包含了串口设备名,串口驱动名,主次设备号,串口控制台(可选))等信息,还封装了tty_driver (底层串口驱动无需关心tty_driver)

struct uart_driver {
( s# Q0 g- t" S/ a8 o) N" e6 u        struct module    *owner; /*拥有该uart_driver的模块,一般为THIS_MODULE*/
0 V0 I2 w, z$ c$ Q$ D        const char        *driver_name; /*驱动串口名,串口设备名以驱动名为基础*/: x5 q# V7 W) T. _. o" t9 |
        const char        *dev_name; /*串口设备名*/1 Z7 m3 N" ~  }: F1 C$ M
        int                 major; /*主设备号*/
5 {; P: v, H7 N+ l" b! [& e        int                 minor; /*次设备号*/
+ P+ I! \- b2 G: `* }2 l" P9 f        int                 nr; /*该uart_driver支持的串口数*/0 C* E# g7 A% K5 K8 V9 m
        struct console    *cons; /*其对应的console,若该uart_driver支持serial console,1 d. L/ L9 s7 U) W3 B
*否则为NULL*/
( o, e/ }1 D! Z0 _
/*' R% q& q( l; R; ^( T- ?7 V+ I
* these are private; the low level driver should not5 ~+ i, @; {$ Z3 C
* touch these; they should be initialised to NULL/ c2 O% j0 e! s) I
*/

7 k! i! k$ H6 a3 \) ystruct uart_state *state; /*下层,窗口驱动层*/
1 W/ c5 c! I; k, l+ F# b6 `& {struct tty_driver  *tty_driver; /*tty相关*/" b, }6 \0 V3 \( X& `# B" g

$ y1 c( q6 p: r; O/ I. K
2.2.2.        struct console

实现控制台打印功能必须要注册的结构体

struct console {/ T6 s, M" t6 x' D& k- O
      char name[16];
* V: n3 @( {3 c. E2 `- {      void(*write)(struct console *,const char *, unsigined);$ E2 z$ W% o" d3 B% L: Q; B
      int (*read)(struct console *, char *, unsigned);
9 v, S# l8 c! p" n* P      struct tty_driver *(struct console *,int*);
6 W9 x; H7 H& D" m7 T8 ~# s      void (*unblank)(void);. Q; Z; A  A; _
      int  (*setup)(struct console *, char *);
! T& f" h' l# Q1 y8 O7 G' _' L      int  (*early_setup)(void);- W! r* ^. \# Y( t
      short  flags;
) ]3 E) b* H* f! B7 q' H3 U0 ?      short  index; /*用来指定该console使用哪一个uart port (对应的uart_port中的line),如果为-1,kernel会自动选择第一个uart port*/
4 \: e% M0 s8 C) B      int   cflag;
& K/ b. {6 @4 k9 t) N+ t      void  *data;
4 L6 r" c5 H# |! p6 d$ c      struct   console *next;
* ?% U7 m5 m/ i9 b0 J0 ~* P};3 y1 [9 A1 h( Q. ^+ s

- F0 [8 P( [8 B5 c: l: I  p
2.2.3.        struct uart_state

每一个uart端口对应着一个uart_state,该结构体将uart_port与对应的circ_buf联系起来。uart_state有两个成员在底层串口驱动会用到:xmit和port。

用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。串口接收中断处理函数需要通过port将接收到的数据传递给线路规程层。

struct uart_state {
2 v& |& r/ M* X7 c& g2 Q9 t$ w, s       struct  tty_port  port;; W$ A/ \# H; H8 k/ R% G- _
       , ^& B# n9 M. ]: L: u8 u
       enum uart_pm_state   pm_state;
3 @" g6 i# C. M       struct circ_buf     xmit;$ z( h/ n/ E- \% Y0 L
      
0 _. A, f9 a2 `# r# O; n       struct uart_port     *uart_port; /*对应于一个串口设备*/# I$ x8 n! x3 {. h% e, l0 v
};
- M- F: p& l9 R/ n3 m. }  ?, q6 `/ k" I+ s
2.2.4.        struct uart_port

uart_port用于描述串口端口的I/O端口或I/O内存地址、FIFO大小、端口类型、串口时钟等信息。实际上,一个uart_port实现对应一个串口设备。

struct uart_port {/ q; r4 P) T+ B  w# _0 B. |
        spinlock_t              lock;                   /* port lock */  s& M, X* u1 J" q
        unsigned long           iobase;                 /* in/out[bwl] */7 d* h/ U" Q- s8 c
        unsigned char __iomem   *membase;               /* read/write[bwl] */+ P& F$ O# b5 i4 h
        unsigned int            (*serial_in)(struct uart_port *, int);
! P. L  R$ G( l; R2 x        void                    (*serial_out)(struct uart_port *, int, int);
. _1 W$ O0 A8 m; ?  x3 h& T        void                    (*set_termios)(struct uart_port *,7 {% r: b/ v9 @" x. i4 p
                                               struct ktermios *new,
$ H3 i' {: ]5 {9 D: @' F                                               struct ktermios *old);
, u0 f* o9 p) M) S2 ~        int                     (*handle_irq)(struct uart_port *);
) G0 W! D! C* h% [        void                    (*pm)(struct uart_port *, unsigned int state,
0 Y6 O# Q& ]5 {6 z" n) v                                      unsigned int old);5 o/ O  n( |& G6 Z6 Y
        void                    (*handle_break)(struct uart_port *);5 ]! T: _6 J3 ?* a) v$ y2 r
        unsigned int            irq;                    /* irq number */
; d- w# ]+ r! n        unsigned long           irqflags;               /* irq flags  *// }' f) I" _! W
        unsigned int            uartclk;                /* base uart clock */; I$ p2 X4 Q, [* s: m7 h& }  s
        unsigned int            fifosize;               /* tx fifo size */4 p; o" n( i! M. ]2 ]" c4 M
        unsigned char           x_char;                 /* xon/xoff char */
6 Q! y2 l: Z7 Z; O- r        unsigned char           regshift;               /* reg offset shift */& u  q( X6 E1 Q5 ]2 H
        unsigned char           iotype;                 /* io access style */! p+ l$ x; V0 ^! I- G$ y% ?
        unsigned char           unused1;
( Q- ?* L! o; o! B3 t9 L: o9 s, k3 M  ^/ A
#define UPIO_PORT               (0)
9 _0 m$ H7 W" }# @* O#define UPIO_HUB6               (1)+ p  }7 o) W% y0 K) x8 X  A0 J4 z
#define UPIO_MEM                (2)) C. L& v- @2 F& `0 d0 o
#define UPIO_MEM32              (3)
1 k+ m8 a1 r6 e/ @) G#define UPIO_AU                 (4)                     /* Au1x00 and RT288x type IO */
( u/ Z, u0 a  M# U2 i0 g% `#define UPIO_TSI                (5)                     /* Tsi108/109 type IO */! u- Z0 c/ V5 p% _& Q; w! s/ x: H
9 M2 [5 I- ^! m
        unsigned int            read_status_mask;       /* driver specific */
; o5 b- u9 T4 A9 Q! Y; S# v  y0 q        unsigned int            ignore_status_mask;     /* driver specific */
6 Y- b" J  I0 n! Z/ p+ _/ T        struct uart_state       *state;                 /* pointer to parent state */4 q* r1 N% l3 o& [4 i" i) r
        struct uart_icount      icount;                 /* statistics */. N/ |6 N& b" Y2 g/ s
8 J4 x6 q9 X5 ^6 @. b5 Y. Y0 {* w
        struct console          *cons;                  /* struct console, if any */- O$ ~/ S' B+ J5 l* i- O9 R3 e
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
: B8 m3 `! _$ {- M. {$ z4 A        unsigned long           sysrq;                  /* sysrq timeout */) X$ u) x: u& w8 x* l# H
#endif
  q% Y- x' ]% [& A, v; \# Z, R2 L1 j8 S1 ~0 B$ L
        upf_t                   flags;
7 U! K( }* ~, \, X  q# V/ E& |+ Y3 S* C! E
#define UPF_FOURPORT            ((__force upf_t) (1 << 1))
& ]% O: `$ `0 c- |. n/ r4 W#define UPF_SAK                 ((__force upf_t) (1 << 2))& u' ~  A$ J% A9 }3 k
#define UPF_SPD_MASK            ((__force upf_t) (0x1030))/ W) h/ D" ]. L8 e
#define UPF_SPD_HI              ((__force upf_t) (0x0010))- w, o$ @' a+ I8 `2 K3 {; y# V+ O* }
#define UPF_SPD_VHI             ((__force upf_t) (0x0020))+ I4 d. o7 [' z! c4 c% z5 B
#define UPF_SPD_CUST            ((__force upf_t) (0x0030))
" ]: z2 [- O& v  q$ d! T#define UPF_SPD_SHI             ((__force upf_t) (0x1000))& g, W( t! A( c+ P' h
#define UPF_SPD_WARP            ((__force upf_t) (0x1010))
5 _! V! h) q/ t5 i8 o1 q#define UPF_SKIP_TEST           ((__force upf_t) (1 << 6))
; A4 S3 A$ o2 i# f; H* J% k#define UPF_AUTO_IRQ            ((__force upf_t) (1 << 7))4 e. ?/ A+ e/ h. W: l, I# R5 O2 ^5 t3 |
#define UPF_HARDPPS_CD          ((__force upf_t) (1 << 11))
! q$ P# b* E4 {- ^" q: }- |#define UPF_LOW_LATENCY         ((__force upf_t) (1 << 13))
# }3 x7 X2 P3 @& b7 f- n( h#define UPF_BUGGY_UART          ((__force upf_t) (1 << 14))3 u6 [9 _8 u% x. ^6 X' q
#define UPF_NO_TXEN_TEST        ((__force upf_t) (1 << 15))9 ~: b+ Y# a2 S% }
#define UPF_MAGIC_MULTIPLIER    ((__force upf_t) (1 << 16)); m9 G5 y. ~; M. X& m4 s
/* Port has hardware-assisted h/w flow control (iow, auto-RTS *not* auto-CTS) */
4 ^, y5 V7 [4 G- c  i+ A#define UPF_HARD_FLOW           ((__force upf_t) (1 << 21))
& w9 q0 P0 d5 j( `/* Port has hardware-assisted s/w flow control */
7 e# v, q5 C$ P8 P5 g8 k8 K: e( S! B) m#define UPF_SOFT_FLOW           ((__force upf_t) (1 << 22))5 j" r+ g  V- x4 a1 z( b5 K/ e
#define UPF_CONS_FLOW           ((__force upf_t) (1 << 23))
5 O  h, e# }/ y6 i#define UPF_SHARE_IRQ           ((__force upf_t) (1 << 24))
; ^) `4 d8 x( U3 x: o- j) T+ S6 |#define UPF_EXAR_EFR            ((__force upf_t) (1 << 25))
0 x8 D- I$ g9 J9 N. @/ j1 l+ m$ f#define UPF_BUG_THRE            ((__force upf_t) (1 << 26))" V7 q4 A5 C" c
/* The exact UART type is known and should not be probed.  */
) u) p, ?0 f5 v#define UPF_FIXED_TYPE          ((__force upf_t) (1 << 27))
& G: H" E  T' ^#define UPF_BOOT_AUTOCONF       ((__force upf_t) (1 << 28)); I$ S  Z+ e# L: w3 q, G, _& Z$ o
#define UPF_FIXED_PORT          ((__force upf_t) (1 << 29))7 Y) U, E; \2 I: @; C8 z! g1 E
#define UPF_DEAD                ((__force upf_t) (1 << 30))
) Z' Y  @6 J6 ^#define UPF_IOREMAP             ((__force upf_t) (1 << 31))
) Y2 g* M0 V" v' Q" t$ B; K
: ~" l* _6 @: U#define UPF_CHANGE_MASK         ((__force upf_t) (0x17fff))& ^! q9 H6 M$ j' K- s, a/ t
#define UPF_USR_MASK            ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY))/ X$ Q- c' @% k

3 y% ]3 r4 r' y9 }, A1 L7 u: j$ _- L        unsigned int            mctrl;                  /* current modem ctrl settings */
- D5 ]9 S: }: [- R# r( z        unsigned int            timeout;                /* character-based timeout */
. Z2 [/ L  S7 W( h( H        unsigned int            type;                   /* port type */# u# W/ g8 P4 p  i  ?9 ?# Y1 H, M- g
        const struct uart_ops   *ops;
" Q1 e: [6 F8 J% r        unsigned int            custom_divisor;: X; [2 D- _8 w& n. |; \
        unsigned int            line;                   /* port index */
7 Z* h: b- w' A3 F, A' l        resource_size_t         mapbase;                /* for ioremap */( L0 M- k* \$ k, K+ y. @
        struct device           *dev;                   /* parent device */8 U6 e4 l2 J. i, a8 X& ?1 x5 a
        unsigned char           hub6;                   /* this should be in the 8250 driver */
( c( E- D( I! X! A! |- t( ?* g3 S        unsigned char           suspended;. H9 g: @- c( K8 E
        unsigned char           irq_wake;
+ k: Q9 e8 V2 _, Q        unsigned char           unused[2];/ ?" A1 j0 Q! {. k5 U5 F7 S: s
        void                    *private_data;          /* generic platform data pointer */1 Y% j5 Y% ?$ ?: X3 N: `3 T
};! s: K, H; y7 k! P5 d

8 |6 T: g9 `4 l+ z
2.2.5.        struct uart_ops

struct uart_ops涵盖了驱动可对串口的所有操作

struct uart_ops {
- c. f  f! y8 s/ r) |/ @" v$ f        unsigned int    (*tx_empty)(struct uart_port *);
5 y" B5 _4 d7 d' G9 ^6 v        void            (*set_mctrl)(struct uart_port *, unsigned int mctrl);
8 ?; ~! H3 e+ A7 Z' s        unsigned int    (*get_mctrl)(struct uart_port *);& J; q7 |) S' }
        void            (*stop_tx)(struct uart_port *);
. h# T8 a# w3 A        void            (*start_tx)(struct uart_port *);8 R5 W/ N, H( t$ X% m  p
        void            (*throttle)(struct uart_port *);
0 _+ W/ Y) v5 m* C0 d        void            (*unthrottle)(struct uart_port *);
2 l( J3 S3 n! `8 c( L- }; n        void            (*send_xchar)(struct uart_port *, char ch);$ P1 W7 H3 `! v' H* M
        void            (*stop_rx)(struct uart_port *);6 {# D5 h  t0 T" T* V* N
        void            (*enable_ms)(struct uart_port *);
1 m; K+ c, |1 p5 J4 ~1 a/ f        void            (*break_ctl)(struct uart_port *, int ctl);: u+ h4 ]) G4 N7 g7 A
        int             (*startup)(struct uart_port *);
3 g" M" P9 n3 P& t4 y5 T" a  M' W" G, M        void            (*shutdown)(struct uart_port *);7 ~+ O3 ?% x8 v# d5 G& w
        void            (*flush_buffer)(struct uart_port *);
; Z3 E- H/ v2 ?% x+ U        void            (*set_termios)(struct uart_port *, struct ktermios *new,
" S1 p. s4 c8 |4 @                                       struct ktermios *old);  z4 f, c; ~" u1 r1 ^
        void            (*set_ldisc)(struct uart_port *, int new);* Y5 e% M: t) g) y
        void            (*pm)(struct uart_port *, unsigned int state,
" p+ ^. {9 i2 Q* N' A                              unsigned int oldstate);! u. i* ?7 `8 y% t1 \
        int             (*set_wake)(struct uart_port *, unsigned int state);/ @! n" f7 ^1 Q
& \9 ]" {9 @, ^  y, ^: d
        /*
+ }4 u9 L1 _; J; D+ r; O+ f2 G         * Return a string describing the type of the port
# W2 L  b2 G! J6 J4 N         */

9 ?" r! d; ~: g9 \* c        const char      *(*type)(struct uart_port *);
/ J! A# a( U/ A1 D
% {2 q) z; m6 {3 o/ U3 t7 k. A) N        /*
8 H. b8 F: m% S& c  L! S+ X& ~2 k         * Release IO and memory resources used by the port.
2 _' T# X1 d( A. J& U         * This includes iounmap if necessary.
5 }; r8 o$ U% r4 z         */

/ R% Y  }; F& N( Y; w! n# r        void            (*release_port)(struct uart_port *);: K8 ~0 j+ a! K+ Q
9 b6 X3 q% L5 L+ t; c  w6 s
        /*" |2 M! L! ^* I( x# g
         * Request IO and memory resources used by the port.* e8 h& T& ?/ ?
         * This includes iomapping the port if necessary.
% L& p, F3 T/ I# u$ S0 W  ?0 Q         */
, _$ G8 O) g4 j: Z' F. z* Y
        int             (*request_port)(struct uart_port *);8 b+ `% m" P1 ~% z" I* Q6 ]6 M4 m: a
        void            (*config_port)(struct uart_port *, int);
8 Z6 k5 P  V9 D  Z' `1 E        int             (*verify_port)(struct uart_port *, struct serial_struct *);
* K* y  O9 F6 R8 A        int             (*ioctl)(struct uart_port *, unsigned int, unsigned long);0 j! M3 j* N5 M/ k* k" S
#ifdef CONFIG_CONSOLE_POLL
* j7 w9 ?5 `# S) P        int             (*poll_init)(struct uart_port *);, |- K* j( F6 k$ T" C. W/ l
        void            (*poll_put_char)(struct uart_port *, unsigned char);# `" g& V) j6 _, T, S% V# s  W
        int             (*poll_get_char)(struct uart_port *);+ L$ E- ]+ b2 R# n/ @' t
#endif" v+ M% P1 i# e1 [! }9 P- i
};+ Q. M3 [' a; L' u4 K  H$ V5 g

3 z3 F2 z2 H. Y/ r5 \$ e
2.3.        关键流程
1 H) K6 n$ u. d" Y  j2.3.1.        注册流程
' O) G1 ~( N2 t% U8 R: Q2.3.1.1.        注册uart_driver

此接口在uart driver中调用,用来注册uart_driver到kernel中,调用阶段在uart driver的初始阶段,例如:module_init(), uart_driver的注册流程图

微信图片_20210223111921.png
9 F0 W+ C# v4 d% [4 Y. a图3.3uart driver注册流程

注册过程主要做了以下操作:

  • 1、根据driver支持的最大设备数,申请n个uart_state空间,每一个uart_state都有一个uart_port。
  • 2、分配一个tty_driver,并将uart_driver->tty_driver指向它。
  • 3、对tty_driver进行设置,其中包括默认波特率、检验方式等,还有一个重要的ops,结构体tty_operation的注册,它是tty核心与串口驱动通信的接口。
  • 4、初始化每一个uart_state的tty_port;
  • 5、注册tty_driver。注册uart_driver实际上是注册tty_driver,与用户空间打交道的工作完全交给tty_driver,这一部分是内核实现好的不需要修改
    5 ?+ U& `. f$ {. E$ ]! H2 ^
2.3.1.2.        添加uart_port

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

uart driver在调用接口前,要手动设置uart_port的操作uart_ops,使得通过调用uart_add_one_port接口后驱动完成硬件的操作接口注册。uart添加port流程如图3-4所示:

微信图片_20210223111925.png 图3-4 uart添加port流程图* k. K3 Z8 C+ [% J9 }

" k" o4 @) E2 {
- x: |& R1 g- P& h  G* R$ s. X0 v4 U& z
2.4.        数据收发流程6 k4 }6 M* Q8 [0 P3 y: w' x
2.4.1.        打开设备(open操作)

open设备的大体流程如图3-5所示:

微信图片_20210223111928.jpg 图3-5 open设备流程/ I, d) s4 c% t" q
6 x$ c/ G/ U" W* W
3 o8 t4 E# I3 g9 \* b
2.4.2.        数据发送流程(write操作)

发送数据大体流程如图3-6所示:

微信图片_20210223111931.png 图3-6 发送数据流程
+ J! H  P+ C) T/ h( h; c9 u+ p, P! @  j( a, f

# b6 Q5 ]) J; \& O2 w9 {* T2.4.3.        数据接收流程(read操作)

接收数据的大体流程如图3-7所示:

微信图片_20210223111935.png 图3-7数据接收流程
8 y# m" E1 l+ ^- b+ L; T7 J
; D0 a% v0 [! ^; x2 A6 [! X" a
+ A& R" E  Q" S( _, p$ ^8 [
2.4.4.        关闭设备(close操作)

close设备的大体流程如图3-8所示:

微信图片_20210223111938.png 图3-8 close设备流程! z2 ]- P$ v* V6 I5 x  {* L
; d1 }+ l: _4 ]3 ]
: `3 \+ c- P- O' a
2.4.5.        注销流程
/ i0 u) X% v1 w: z/ ?2.4.5.1.        移除uart_port

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

微信图片_20210223111943.png 图3.9 uart移除port流程图5 ~  w4 s1 S2 g- h
' D, y/ r5 U3 ^' H, _# Z7 f" z
' M& p7 F! P$ r: P8 v4 H: H" M
2.4.5.2.        注销uart_driver

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

微信图片_20210223111946.png 2 M( n' {/ R; Z' \
4 u2 a- N5 Q) I

% P; o+ V) i" Z; [( t. W2.5.        使用rs485通信5 G0 {0 `& {+ Z3 I  m/ F
2.5.1.        rs485和rs232的区别

uart(TTL-3.3V)/rs232(工业级 +-12V)是电压驱动,rs485是电流驱动(能传输更远的距离) rS232用电平表示数据,使用2根线可实现全双工,rs485用差分电平表示数据,因此必须用4根线实现全双工rs485;

全双工:uart-tx 1根线变成rs485-A/B 2根线;uart-rx 1根线变成rs485- X/Y两根线;

rs485半双工: 将全双工的A/B和X/Y合并起来分时复用;rs485-de/re是给转换器的一个控制信号,对我们芯片来说,都是输出;

2.5.2.        rs485调试方法:

首先保证uart模块和相关gpio,电压转换芯片工作正常:

  • a,保证uart tx/rx功能正常。
  • b,用gpio-output来控制 de/re 相关的2个gpio,观察 de/re的gpio输出low/high是否正常
  • c,在b的基础上,单独调试 rs485-tx/rs485-rx,单端调试是否pass.
    + W' Y4 b: p1 ?6 o9 L$ N

模式12-gpio-normal-uart-rs485-halfduplex (2个gpio独立控制de/re, enable就是将相关gpio设置到active电平;不用uart控制器的rs485模式;uart控制器处于normal模式)

  • a, 默认re-en, de-dis,默认rs485-rx
  • b, 当要发送的时候,re-dis, de-enable, 然后uart-tx.
  • c, tx完成之后,de-dis; re-en,进入默认的rs485-rx模式。5 ^3 t+ r& W7 e8 e: l' P8 F  z4 Y' X; F

模式21-gpio-normal-uart-rs485-halfduplex 这个模式的前提条件,外设器件的 de/re必须是相反极性的,比如de是高电平有效,re是低电平有效,则可以用一个gpio,来控制 de/re,此时de/re一定是互斥的。(1个gpio控制de/re, enable就是将相关gpio设置到active电平;不用uart控制器的rs485模式;uart控制器处于normal模式)

  • a, re-en,进入rs485-rx模式 (re 通常是低电平有效,这一步就是 设置 re对应的gpio为低电平)
  • b, 当要发送的时候,设置gpio:re-disable, de-enable, 然后uart-tx.(re 通常是低电平有效,这一步就是 设置 re对应的gpio为高电平)
  • c, tx完成之后,de-disable; re-enable,进入默认的rs485-rx模式。(re 通常是低电平有效,这一步就是 设置 re对应的gpio为低电平)
    6 m; I7 p( ~' ^) ~- ?& h$ C8 L. ~  N5 T; F, a

模式3rs485-software-halfduplex(de/re 独立输出) (使能uart控制器的rs485模式; 通过uart模块内部reg来控制 de/re 信号)

  • a,使能uart控制器的 rs485模式,并按照电压转换芯片的特性,设置de/re polarity
  • b, 设置rs485的模式为 sw-half-duplex, 设置 de-timing寄存器; 设置 de/re turnaround 寄存器。
  • c, 默认为rs485-rx模式,设置 de-dis/re-en
  • d, 当要tx的时候,设置 de-en/re-dis
  • e, 发送完成,设置 de-dis/re-en! Z7 N3 r; A6 w  Q

模式4rs485-hardware-halfduplex(de/re 独立输出) 基本配置同模式3,但是设置 rs485模式为 hardware-halfduplex模式

  • a, 只要设置 de-en/rx-en 都为1,然后就不用管了,硬件实现半双工切换。
    ' u6 o/ W( t* [; w

模式5:使用纯硬件的办法实现RS485半双工功能,电路如图所示:

微信图片_20210223111949.png

接收:默认没有数据时,UART_TX为高电平,三极管导通,485芯片RE低电平使能,RO接收数据使能,此时从485AB口收到什么数据就会通过RO通道传到MCU,完成数据接收过程。发送:当发送数据时,UART_TX会有一个下拉的电平,表示开始发送数据,此时三极管截止,DE为高电平发送使能。当发送数据‘0’时,由于DI口连接地,此时数据‘0’就会传输到AB口 A-B<0,传输‘0’,完成了低电平的传输。当发送‘1’时,此时三极管导通,按理说RO使能,此时由于还处在发送数据中,这种状态下485处于高阻态,此时的状态通过A上拉B下拉电阻决定,此时A-B>0传输‘1’,完成高电平的传输。

3.        模块详细设计
/ M9 D; k: ^9 u2 t& p9 u6 t5 F3.1.        关键函数接口2 n  {! G7 p" G
3.1.1.        uart_register_driver
( I8 T1 \/ o. Q. r/*功能:  uart_register_driver用于串口驱动uart_driver注册到内核(串口核心层)中,通常在模块初始化函数调用该函数。( ]9 q. f% }7 s: E) a/ J: V4 d: ~
*参数:drv:要注册的uart_driver" L1 [  n' P  F! x. [* I3 I& K
*返回值:成功,返回0;否则返回错误码! x  S; u0 k' K- A
*/
8 T, i; R+ e3 ^/ C2 ]& ~
int uart_register_driver(struct uart_driver *drv)1 R, q" w- a! S5 i

5 n& P. r0 ?# M$ @3 ~
3.1.2.        uart_unregister_driver
+ I* f5 c* i: |8 R/*功能:uart_unregister 用于注销我们已注册的uart_driver,通常在模块卸载函数调用该函数,2 u* F% H1 j1 j: f
*参数 : drv:要注销的uart_driver  [  u: J- Z8 U: r+ A) s" _
*返回值:成功返回0,否则返回错误码# W8 i, Y! `6 Y# s6 R
*/

  w# c7 |# x; i* c1 |7 q5 _. dvoid uart_unregister_driver(struct uart_driver *drv)
2 R2 R  E6 `1 @
2 Y1 t  h% a+ `1 Y
3.1.3.        uart_add_one_port! w3 V. [# p" l/ F' W. q. M4 S
/*功能:uart_add_one_port用于为串口驱动添加一个串口端口,通常在探测到设备后(驱动的设备probe方法)调用该函数& x2 V( ]6 E1 s1 E  c  p+ l" m
*参数:4 ]3 b, X" T4 P! A' U( H
*     drv:串口驱动9 U, K2 W  Q% Y9 O& N
*     port:要添加的串口端口
( p7 y$ T4 H9 Y6 Y: n6 p6 A- H+ O *返回值:成功,返回0;否则返回错误码
; O% `7 p0 ^  q- r; Z1 E3 I. \ */

# m6 V: x; o3 }' J# [int uart_add_one_port(struct uart_driver *drv,struct uart_port *port)
' s5 N* h- S% }) Q/ u! {( c  C1 n5 \: g3 Q) y, F
3.1.4.        uart_remove_one_port
6 K. l. V1 x: T% j( n2 g/*功能:uart_remove_one_port用于删除一个已经添加到串口驱动中的串口端口,通常在驱动卸载时调用该函数' g: n7 O8 H. d; i* S
*参数:
& h+ t. I7 n7 W* ~: } *     drv:串口驱动  a4 W$ G! x8 @
*     port:要删除的串口端口
/ y; f  j0 A/ d8 W *返回值:成功,返回0;否则返回错误码
3 A# D3 R1 _4 c/ u& X */
# b1 z2 w# o7 a. @  z
int uart_remove_one_port(struct uart_driver *drv,struct uart_port *port)
9 W1 v) W) p/ ?- v0 [3 s- N+ |5 p' g! a$ U" k+ O* p1 Y+ j8 G
3.1.5.        uart_write_wakeup5 l! r& W2 e5 o+ P* m
/*功能:uart_write_wakeup唤醒上层因串口端口写数据而堵塞的进程,通常在串口发送中断处理函数中调用该函数
. v7 K7 v# |) V9 d* u1 Q *参数:
3 a+ N2 W. E4 ?8 h7 T/ I *     port: 需要唤醒写堵塞进程的串口端口* O; a# u( ]) e8 Q4 e2 Y. a
*/
, Y& ]7 ^2 O4 Q7 p
void uart_write_wakeup(struct uart_port *port)
8 z) I: _+ |; X' F! a5 M! S! c7 Z1 _1 \: u# h
3.1.6.        uart_suspend_port9 {! H! z3 z9 Y7 p  t
/*功能:uart_suspend_port用于挂起特定的串口端口
% u# L. g4 H5 O" U, N7 h8 B *参数:+ K! W) Z* m* v$ c
*     drv:要挂起的串口端口锁所属的串口驱动6 i  h. h+ ^9 N5 b
*     port:要挂起的串口端口
) d4 E  R8 p7 `) d+ w) x3 n. u *返回值:成功返回0;否则返回错误码' z1 y2 T4 `0 J" T' ^5 w$ K/ ?; B
*/

- F: {- s7 W. ?int uart_suspend_port(struct uart_driver *drv, struct uart_port *port)' ?( I' i. w3 X( O8 U& ]5 K. [* \  p
% W4 Q7 u5 ?" r" F- e7 X
3.1.7.        uart_resume_port
: _& l' u$ b6 p( T. K9 R/*功能:uart_resume_port用于恢复某一已挂起的串口
) h8 S' s5 ?! B% W4 U *参数:4 n2 }  a8 |& k4 ~
*     drv:要恢复的串口端口所属的串口驱动  p' |' B% _5 p& v
*     port:要恢复的串口端口
8 W( `% ~8 v2 L# i8 x7 X *返回值:成功返回0;否则返回错误码
3 d! X; ?+ f3 i5 P/ {' x7 b */

7 ^1 b$ Q3 L. yint uart_resume_port(struct uart_driver *drv, struct uart_port *port)
* B4 l3 E2 Z; ]9 W
3.1.8.        uart_get_baud_rate
" ?* _% k& V$ C/ c/*功能:uart_get_baud_rate通过解码termios结构体来获取指定串口的波特率
2 D1 ^7 d+ q, [6 t; `5 T *参数:
0 L) V3 m+ g* w6 d6 W7 N *     port:要获取波特率的串口端口0 ]8 B% s# }# i: M& N
*     termios:当前期望的termios配置(包括串口波特率)2 ~7 ^7 C( a4 Y1 C/ p- e
*     old:以前的termios配置,可以为NULL
. z$ w9 S1 N2 k *     min:可以接受的最小波特率* `  R8 }, \" Y! X+ Y% Q% L% e
*     max:可以接受的最大波特率
' L# B$ V8 w4 L. d *     返回值:串口波特率3 n/ A( ]1 S- n$ e! ~
*/
: ?3 t% B: g8 U8 K; c; Q' z( |
unsigned int uart_get_baund_rate(struct uart_port *port, struct ktermios *termios, struct ktermios *old,unsigned int min, unsigned int max)
+ Q$ S7 k- p2 K1 u3 [
3.1.9.        uart_get_divisor) p/ \3 \& f& v4 v
/*功能:uart_get_divisor 用于计算某一波特率的串口时钟分频数(串口波特率除数)
4 O0 F3 k, s# W# r0 _/ R3 t; w& ]. h *参数:
: O! a8 ^8 a! q; |$ D: ^: P *     port:要计算分频数的串口端口
/ |: K7 ~6 x6 F! V+ Q7 V *     baud:期望的波特率
+ \& x% p" y# C' {& W *返回值:串口时钟分频数
* g: |) _% L/ N# k0 f0 x */
' T/ T7 q2 }3 C4 M9 H
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baund)
7 T- i) E! I3 x. f3 n( [
3.1.10.        uart_update_timeout, j7 M1 S0 W- `0 J
/*功能:uart_update_timeout用于更新(设置)串口FIFO超出时间2 q$ l% A/ z3 b8 e, m9 D: u6 ?
*参数:9 j0 C, V" o1 J
*     port:要更新超时间的串口端口3 v/ D6 I7 M. f. U( P
*     cfalg:termios结构体的cflag值
; Q) ]# V% U4 b1 M *     baud:串口的波特率
. z: k: D# E- {) A; Y$ A( ^: v( M# n */

9 c. D+ z! j! tvoid uart_update_timeout(struct uart_port *port,unsigned int cflag, unsigned int baud)
% n% l  p8 w/ R9 Q. a0 S/ w4 \
3.1.11.        uart_insert_char
5 \+ n; x' J) {- B+ u/*功能:uart_insert_char用于向uart层插入一个字符
' N, e5 M* ?! J- _7 C *参数:
% g5 H3 f- \; I$ C( ~& \6 B *     port:要写信息的串口端口
; }' `, B* F% @ *     status:RX buffer状态
& f* a" ~: w7 ]3 P2 j0 J *     overrun:在status中的overrun bit掩码! M$ b& L" i1 ^2 L% R
*     ch:需要插入的字符
1 G! ~/ c. a! I *     flag:插入字符的flag:TTY_BREAK,TTY_PSRIYY, TTY_FRAME0 w5 r' D/ q3 o  j1 s0 n
*/

1 V, M3 \  e5 zvoid uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun,unsigned int ch, unsigned int flag)
* G) |3 `. Q) R) Q! G& Z5 ?4 b
3.1.12.        uart_console_write
+ m' b* Z, F1 Z/*功能:uart_console_write用于向串口端口写一控制台信息
: p: b- x4 ?6 E" O. _ *参数:
9 G$ R7 A+ |" D2 b& i. x/ i *     port:要写信息的串口端口
. H1 Z* S; q+ {0 G# E *     s:要写的信息. N$ I2 }! i$ |5 m3 K" @2 t0 T
*     count:信息的大小. |) R( k- i% E% y  `
*     putchar:用于向串口端口写字符的函数,该函数有两个参数:串口端口和要写的字符! D2 P1 g4 V: q& b6 w
*/

  ~; i) m, ?1 ?4 h0 ]  ^6 L4 ZVoid uart_console_write(struct uart_port *port,const char *s, unsigned int count,viod(*putchar)(struct uart_port*, int))8 Y% ]6 \5 j+ G& i7 j4 S) k
4.        模块使用说明8 l0 R3 D' L9 {3 f
4.1.        串口编程% R& r! I6 ?! a! j9 S  c+ Y
4.1.1.        串口控制函数[td]
属性
说明
tcgetatrr取属性(termios结构)
tcsetarr设置属性(termios结构)
cfgetispeed得到输入速度
cfsetispeed得到输出速度
cfstospeed设置输出速度
tcdrain等待所有输出都被传输
tcflow挂起传输或接收
tcflush刷请未决输出和/或输入
tcsendbreak送BREAK字符
tcgetpgrp得到前台进程组ID
Tcsetpgrp设置前台进程组ID
4.1.2.        串口配置流程
  • (1)        保持原先串口配置,使用tegetatrr(fd, &oldtio);
    + k2 T; y7 y1 I7 w3 O) q" l
struct termious newtio, oldtio;$ F. V$ w( I4 Z( j. c  H+ }
tegetattr(fd, &oldtio);
8 @$ z# k* a( {0 y8 e
  • (2)        激活选项有CLOCAL和CREAD,用于本地连接和接收使用
    % V- v6 M9 ?/ N
newtio.cflag |= CLOCAL|CREAD;- u5 O6 c- F! _  M) f! I
  • (3)        设置波特率) l) l. a+ ^7 e
newtio.c_cflag = B115200;+ K3 H: E1 P& A9 N
  • (4)        设置数据位,需使用掩码设置1 w# ^8 I" }% r3 J. m: ]
newtio.c_cflag &= ~CSIZE;
+ J% g! y/ @0 \) o, u8 NNewtio.c_cflag |= CS8;7 k7 c- v' |: A6 Y9 Z
  • (5)        设置停止位,通过激活c_cflag中的CSTOP实现。若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOP
    8 q  a( O0 I# B9 T, E+ z6 p$ P
newtio.c_cflag &= ~CSTOPB; /*停止位设置为1*/8 K" T( M0 g+ }0 i, v
Newtio.c_cflag |= CSTOPB; /*停止位设置为2 */. H7 h. A1 {; N# |; F
  • (6)        设置流控! \, ]: u" P& M, ^# x
newtio.c_cfag |= CRTSCTS /*开启硬件流控 */
' W; L, y2 x8 c  ]% c! ~newtio.c_cfag |= (IXON | IXOFF | IXANY); /*开启软件流控*/
; \! h  y  u! }- e# F; ^
  • (7)        奇偶检验位设置,使用c_cflag和c_ifag. 设置奇校验. J1 W6 N9 U- ~$ K
newtio.c_cflag |= PARENB;8 F' {( X  L6 A) W! h! u7 g
newtio.c_cflag |= PARODD;
, s- y9 e3 @; B- g9 a" B/ I6 Hnewtio.c_iflag |= (INPCK | ISTRIP);
6 T1 |" f; k2 V2 K1 ^

设置偶校验

newtio.c_iflag |= (INPCK | ISTRIP);5 o# G- r; d- K0 ^; [3 T6 B
newtio.c_cflag |= PARENB;  |2 M2 H  S  F( r( T/ Q
newtio.c_cflag |= ~PARODD;
+ j. b; n" t7 g8 e
  • (8)        设置最少字符和等待时间,对于接收字符和等待时间没有什么特别的要求,可设置为0:
    5 U1 v5 |! A% _) k3 F! [
newtio.c_cc[VTIME] = 0;
5 w2 g3 B+ F. Z& b  Bnewtio.c_cc[VMIN]  = 0;$ }- T) D; N! J6 k) C# g
  • (9)        处理要写入的引用对象 tcflush函数刷清(抛弃)输入缓冲(终端程序已经接收到,但用户程序尚未读)或输出缓冲(用户程序已经写,但未发送)。) Q6 ?+ h! P( E# A
int tcflash(int filedes, int quene)9 l+ c/ U0 E  C9 K- Z8 V
quene数应当是下列三个常数之一:
) W, z7 ]6 ^* P  *TCIFLUSH 刷清输入队列
2 Y) h3 u  O9 z8 b  *TCOFLUSH 刷清输出队列/ b# c  v6 r1 {$ m# y+ F5 _
  *TCIOFLUSH 刷清输入、输出队列
& E  T' |! ~: \8 E例如:4 h( s7 r9 X2 J$ A; B; M$ D
tcflush(fd, TCIFLUSH);$ L3 K1 P. v$ i1 v# P/ u
  • (10)        激活配置,在完成配置后,需要激活配置使其生效。使用tcsetattr()函数:
    ' c# h# o0 Y  J. A/ h% p: C6 s
int tcsetarr(int filedes, const struct termios *termptr);
, R. \1 c' K+ T$ Y4 aopt 指定在什么时候新的终端属性才起作用,
2 {, U4 c1 k2 v# i) U8 P   *TCSANOW:更改立即发生
- B7 r0 i+ ~% h! t& p   *TCSADRAIN:发送了所有输出后更改才发生。若更改输出参数则应使用此选项: f+ n& }* L5 ~' @
   *TCSAFLUSH:发送了所有输出后更改才发生。更进一步,在更改发生时未读的
4 \8 K( w* ^; i8 m                 所有输入数据都被删除(刷清)
+ L: O* _% W5 Q, R9 h. j例如:tcsetatrr(fd, TCSANOW, &newtio);
3 o: M2 i9 g0 Q2 w! Q1 @7 L
4.1.3.        使用流程
  • (1)打开串口,例如"/dev/ttySLB0"
    : e. _' k4 S( ^. D3 o) ?, A( i
fd = open("/dev/ttySLB0",O_RDWR | O_NOCTTY | O_NDELAY);
) o; }# a2 a& i2 K8 N1 @( r" gO_NOCTTY:是为了告诉Linux这个程序不会成为这个端口上的“控制终端”。如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。
  a- L! R0 Y" w$ S* T: H  r( M9 hO_NDELAY:这个标志则是告诉Linux这个程序并不关心DCD信号线的状态,也就是不管串口是否有数据到来,都是非阻塞的,程序继续执行。
& Y( p$ u4 u) e; W; d0 }
  • (2)恢复串口状态为阻塞状态,用于等待串口数据的读入,用fcntl函数:
    - Q1 |. X  K3 t7 b; G! W: X' m- D
fcntl(fd,F_SETFL,0);  //F_SETFL:设置文件flag为0,即默认,即阻塞状态3 `9 v; m* n9 q6 _+ {, d: j
  • (3)接着测试打开的文件描述符是否应用一个终端设备,以进一步确认串口是否正确打开。
    9 m7 l. O6 O6 [: j0 {7 S
isatty(STDIN_FILENO);
1 d1 O+ O8 u) X" a0 D% `
  • (4)读写串口3 j: T* K. i" u8 C. L% H
串口的读写与普通文件一样,使用read,write函数
! N- F! A! B$ g7 f- B# a8 Bread(fd, buf ,8);
$ g6 P+ o4 W9 l) qwrite(fd,buff,8);
: b, d% }6 F0 \. k
4.1.4.        Demo

以下给出一个测温模块收取数据的例子

#include <sys/types.h>6 S  }4 J; F' h* x
#include <sys/stat.h>! ~5 `3 j4 i( h0 o0 h0 \9 D
#include <fcntl.h>
' y9 f! D9 \' L9 R- B0 O3 s% x! i#include <termios.h>
  N& G; d) b( |#include <stdio.h>% f& `# [! K4 j6 g+ q5 v. M: X, K8 O
#include <string.h>
1 W: U2 i; A7 s: j, v& y0 T#include <unistd.h>
9 }% e1 p& G1 B/ e$ ]#include <log/log.h>
+ r! o- B! q8 D# D# I#include <stdlib.h>
( v7 F* u2 a0 x; R6 L
; O2 ^0 I8 A! i#define UART_DEVICE     "/dev/ttySLB1"& i# a& x6 ]; ^% j* U7 a

# ]$ z. g# f( ustruct temp {
5 u7 U" t; b: c- J float temp_max1;
0 Y( f3 ]# T6 T( e( f float temp_max2;+ h3 W" b. p" `/ ~$ Z% y. f; m
float temp_max3;
5 l* I$ d& A8 }$ F: U4 B float temp_min;; z, W8 p6 d1 _8 d
float temp_mean;
3 a( m* l; Z% s8 ^ float temp_enviromem;
; R% x. J: ?8 F6 U, M char temp_col[1536];" F: ?* l, v, n8 m% ~3 l! n2 ^- V
};
( u( |& m- x$ _8 q! N
* d$ ]+ q6 W; Jint main(void)3 r6 \: ]1 X6 f7 o# [! n
{
: V: n; h4 q' t0 P0 a# k- p int count, i, fd;- C* w5 F& X0 c" _0 j
struct termios oldtio, newtio;9 ~, [  `3 ~# n; l+ \9 ^
struct temp *temp;
7 N1 y, d' `- ]0 V5 B2 x9 } temp = (struct temp *)malloc(sizeof(struct temp));
: p2 f5 u+ Q- s, h if (!temp) {) r) R4 ]) k9 r, I- B5 d" K7 ^( h% B
  printf("malloc failed\n");
/ ]# L" u( e9 I! Q8 }$ c  `  return -1;% s5 ^, g: j0 E1 J) X9 p
}
7 \) @- |3 L9 \% q& r8 T$ Z. P+ b2 P* R
char cmd_buf1[] = { 0xAA, 0x01, 0x04, 0x00, 0x06, 0x10, 0x05, 0x00, 0xBB};" ^# Z7 s: `5 X/ B! q1 ]4 |
char cmd_buf2[] = { 0xAA, 0x01, 0x04, 0x00, 0x00, 0xA0, 0x00, 0x03, 0xBB};
$ y! j3 J: b- }* V char cmd_buf3[] = { 0xAA, 0x01, 0x04, 0x00, 0x03, 0x10, 0x01, 0x00, 0xBB};6 c+ v6 T0 P% t8 C0 Y/ f  ~$ ]
char read_buf[2000];) P# ]4 G1 m# B: K- C, ?

0 X4 z  o& ]' ` //-----------打开uart设备文件------------------
( c) k; m% |. J: f5 i( ]& Z# o fd = open(UART_DEVICE, O_RDWR | O_NOCTTY);
+ M# T; W2 o) h7 s if (fd < 0) {9 G0 I+ w# e3 f+ x) Y+ I- x
  printf("Open %s failed\n", UART_DEVICE);& p* W' w) T9 H1 d5 t
  return -1;1 p; e1 U& ]) R6 R3 ?& c
} else {4 {* K% e. @) @0 s* Z/ P- v
  printf("Open %s successfully\n", UART_DEVICE);) |5 K+ a" O! i! q6 {; \. U) m
}0 v1 a8 U1 ~* ?& |! C

- F& E6 ?$ a  W7 {/ @, U! ]- _2 F, D! l //-----------设置操作参数-----------------------
8 V) c' x. X8 C* W tcgetattr(fd, &oldtio);//获取当前操作模式参数  k& Z, W2 l( R* e9 T) ^8 D! e5 w& |
memset(&newtio, 0, sizeof(newtio));4 h! f. ^/ E% F
) u/ R7 D/ H( G
//波特率=230400 数据位=8 使能数据接收, e7 O' r% i* ?/ d# @/ D
newtio.c_cflag = B230400 | CS8 | CLOCAL | CREAD | CSTOPB;
0 r1 W: W- A! F9 k3 o3 q newtio.c_iflag = IGNPAR;
4 y7 t( D2 h: T2 u- {8 p4 o- B% C2 D& H9 T
tcflush(fd, TCIFLUSH);//清空输入缓冲区和输出缓冲区
% M* N! I- M& F( ? tcsetattr(fd, TCSANOW, &newtio);//设置新的操作参数
1 s  u4 n; ?/ N; S
. W$ l6 _9 H# e4 R  F //printf("input: %s, len = %d\n", cmd_buf, strlen(cmd_buf));
( s8 h. r& U. r4 ?: _" U //------------向urat发送数据-------------------
+ F8 L* |6 C% z2 T. Z7 \3 {6 w8 t9 U+ Y
for (i = 0; i < 9; i++)
% Z9 U- f& x: |4 k  printf("%#X ", cmd_buf1);$ {9 }" Q9 B0 a& \% ?
2 _! z% b4 o+ B! t+ H- e  }- M+ i  s
count = write(fd, cmd_buf1, 9);( d; U: x4 n' t
if (count != 9) {# m. X5 v% B, u
  printf("send failed\n");9 b3 F  q! q  v# u1 c1 p
  return -1;
3 i# A' K" C  `- C! `- e9 p }; a- e/ H" n" U: K" `+ j$ \

- X- p- Y8 t) ^. x7 Z( ~; @ usleep(500000);
- d# ]8 s6 y, D2 e! \6 h8 ?) O5 E
3 j, ]" C1 j! x" @+ Q: D! I memset(read_buf, 0, sizeof(read_buf));
" E7 y) K, e0 Q( j; l count = read(fd, read_buf, sizeof(read_buf));5 m0 ]' j0 H  O& H$ ?0 ~
if (count > 0) {, A6 B" o5 l  `- h
  for (i = 0; i < count; i++);% N0 i% `* V: i/ Z2 C
  temp->temp_max1 = read_buf[7] << 8 | read_buf[6];% s) f; d* r7 U/ [
  temp->temp_max2 = read_buf[9] << 8 | read_buf[8];# U! g) E/ Q1 M
  temp->temp_max3 = read_buf[11] << 8 | read_buf[10];
* ]3 M# n9 E" F2 |  temp->temp_min  = read_buf[13] << 8 | read_buf[12];9 j0 |- A; a2 \) [9 M
  temp->temp_mean = read_buf[15] << 8 | read_buf[14];0 b% |) b. E% M% B1 T5 q1 o6 Z3 k

% `1 W. m/ Q! r8 Z5 ~( R  printf("temp->temp_max1 = %f\n", temp->temp_max1 * 0.01);; f$ Y8 L* d: G5 P$ Z0 }) c, s. Q( ?
  printf("temp->temp_max2 = %f\n", temp->temp_max2 * 0.01);7 W. X( @; h& z
  printf("temp->temp_max3 = %f\n", temp->temp_max3 * 0.01);
: z/ P3 A" L- U$ K! ?" i  printf("temp->temp_min  = %f\n", temp->temp_min  * 0.01);
& c$ E( s! [; o  B  printf("temp->temp_mean = %f\n", temp->temp_mean * 0.01);
! U) p- G4 b* D% g) d  G  
' a" {2 }5 J4 K! p( N- |# i4 [$ j% c& }" K9 R$ M: T2 z1 f9 U8 B) I
} else {7 n7 t+ M* ?( X
  printf("read temp failed\n");
8 A3 f' Z' X! w2 E' u  return -1;# g$ Q4 g( E* D* H2 q/ a1 E8 r
}+ `' C# D3 i! @/ u1 ~. B
# Q) t$ K! P4 J/ N. A: k$ U
count = write(fd, cmd_buf3, 9);
8 h# \1 m! C/ F. ^ if (count != 9) {
2 j+ u& h$ q. m  printf("send failed\n");
7 u: A8 Q5 e# x# j) d1 I  return -1;2 O$ p4 g8 K, f
}
9 H* z' |3 [5 V' b- j" j( q6 O! a+ w6 C5 p7 V0 I
usleep(365);5 N% m: T1 k4 E: {2 Q- n. T# |
memset(read_buf, 0, sizeof(read_buf));9 X3 {2 F) X3 i5 G( x
count = read(fd, read_buf, sizeof(read_buf));
, T4 I% S" \8 u, C if (count > 0) {; y1 J5 z% q2 R4 M! L% f
  for (i = 0; i < count; i++);+ Y2 `' L4 t4 Y9 k  m
  temp->temp_enviromem = read_buf[7] << 8 | read_buf[6];& [" y+ Y$ ~' L. ?! H( J" N& ?1 i9 Z; w
2 D; z7 _; _1 Y$ m/ Z
  printf("temp->temp_enviromem = %f\n", temp->temp_enviromem * 0.01);
4 L* D& e' U3 l9 \ } else {
. w' X6 T* [! X2 M# H- Q  printf("read enviromem failed\n");5 `5 b) {* ]+ ]* [0 u7 W6 w% a7 J9 p* u
  return -1;) j6 T0 D; J) p5 @% T; G5 }6 A0 V
}
. }" w; V  N) d  % E1 T+ F! Q6 N
1 F9 z  |8 ^# G9 f0 p3 T! t
count = write(fd, cmd_buf2, 9);2 j. y2 ~" @& g8 t
if (count != 9) {
% F5 [" K! Q, P' A- Y3 S  printf("send failed\n");
2 v* s" I0 h4 p* X' }. g; U4 \4 Z  return -1;
# j, \0 c$ y2 Y$ w/ ?8 b  J- M }6 @+ p! ]0 m6 C
& |' Y8 F8 X/ B( c0 b
usleep(70000);, d3 O& A( I3 m8 \+ x( {/ Q
memset(read_buf, 0, sizeof(read_buf));
* B( h7 J# s3 J memset(temp->temp_col, 0, sizeof(temp->temp_col));
3 c7 G2 Z& O7 s0 `0 T, S count = read(fd, read_buf, sizeof(read_buf));
! p7 A4 N* C) D9 u) h9 Q- R3 D printf("count = %d\n", count);
4 M- o# w5 }+ { if (count > 0) {
/ C5 J! O4 j8 r, [9 O  for (i = 0; i < count - 7; i++)
5 U$ D/ K/ i2 G  ^& f: m  temp->temp_col = read_buf[i+6];( ?' Q% w2 B* Q- Z9 @$ t
  for (i = 0; i < 1536; i++)7 I* V' W$ b$ k" i* G" T" G3 {! {
  {7 P2 h. t+ v7 P9 e
   if (!(i%10))) J1 j. Z1 p3 i. g. k. X% _$ q
    printf("\n");- E% e3 M1 v: z3 N& X* e2 H/ ?/ i
   printf("%#X ", temp->temp_col);
) L" Y+ u5 j% j! ~  _$ q8 m. X  }
; n5 b5 h9 m0 w+ a } else {) `% E7 X' N' A% @# y) W* G
  printf("read temp colour failed\n");* |/ E; I! l$ B7 D) O, H4 f; n- R. `4 d# T! T
  return -1;
( e) j8 y2 k6 O  S" M6 N }
! _. ]; J% J: Z+ ?* n1 `9 p. [' e free(temp);
# g4 n7 h% W3 ]9 ]) f2 G1 f
% O0 K3 N* L/ S0 x9 l close(fd);# c, \! X0 M6 F+ j

; u( j8 c+ _. F+ w% ^8 M: T tcsetattr(fd, TCSANOW, &oldtio); //恢复原先的设置: f) j9 x9 Y4 u  E2 G
$ f1 o  V$ z  I" u" i4 z
return 0;
2 z! r1 o9 i! C}

' N3 l  S' L3 c0 t
赞 收藏 评论0 发布时间:2021-2-23 11:23

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
人形机器人运动控制、感知与智能配电
半导体创新技术与应用方向
EE架构与软件定义汽车
12V/48V 汽车智能配电(SPD)
区域控制单元(ZCU)与分区架构
关注我们
st-img 微信公众号
st-img 手机版