更多嵌入式Linux原创,请关注公众号:一口Linux

一、模块硬件学习

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位的高电平。

由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。

因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。

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

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

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电路连接。

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

物理连接



主机的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信号无效,主机接收中止。

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驱动程序框架

2.1.1. TTY概念

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 注册的字符设备节点来访问。

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

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

2.2. 关键数据结构

2.2.1. Struct uart_driver

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

(底层串口驱动无需关心tty_driver)

struct uart_driver {
struct module *owner; /*拥有该uart_driver的模块,一般为THIS_MODULE*/
const char *driver_name; /*驱动串口名,串口设备名以驱动名为基础*/
const char *dev_name; /*串口设备名*/
int major; /*主设备号*/
int minor; /*次设备号*/
int nr; /*该uart_driver支持的串口数*/
struct console *cons; /*其对应的console,若该uart_driver支持serial console,
*否则为NULL*/
/*
* these are private; the low level driver should not
* touch these; they should be initialised to NULL
*/
struct uart_state *state; /*下层,窗口驱动层*/
struct tty_driver *tty_driver; /*tty相关*/

2.2.2. struct console

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

struct console {
char name[16];
void(*write)(struct console *,const char *, unsigined);
int (*read)(struct console *, char *, unsigned);
struct tty_driver *(struct console *,int*);
void (*unblank)(void);
int (*setup)(struct console *, char *);
int (*early_setup)(void);
short flags;
short index; /*用来指定该console使用哪一个uart port (对应的uart_port中的line),如果为-1,kernel会自动选择第一个uart port*/
int cflag;
void *data;
struct console *next;
};

2.2.3. struct uart_state

每一个uart端口对应着一个uart_state,该结构体将uart_port与对应的circ_buf联系起来。uart_state有两个成员在底层串口驱动会用到:xmit和port。用户空间程序通过串口发送数据时,上层驱动将用户数据保存在xmit;而串口发送中断处理函数就是通过xmit获取到用户数据并将它们发送出去。串口接收中断处理函数需要通过port将接收到的数据传递给线路规程层。

struct uart_state {
struct tty_port port; enum uart_pm_state pm_state;
struct circ_buf xmit; struct uart_port *uart_port; /*对应于一个串口设备*/
};

2.2.4. struct uart_port

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

struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* in/out[bwl] */
unsigned char __iomem *membase; /* read/write[bwl] */
unsigned int (*serial_in)(struct uart_port *, int);
void (*serial_out)(struct uart_port *, int, int);
void (*set_termios)(struct uart_port *,
struct ktermios *new,
struct ktermios *old);
int (*handle_irq)(struct uart_port *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int old);
void (*handle_break)(struct uart_port *);
unsigned int irq; /* irq number */
unsigned long irqflags; /* irq flags */
unsigned int uartclk; /* base uart clock */
unsigned int fifosize; /* tx fifo size */
unsigned char x_char; /* xon/xoff char */
unsigned char regshift; /* reg offset shift */
unsigned char iotype; /* io access style */
unsigned char unused1; #define UPIO_PORT (0)
#define UPIO_HUB6 (1)
#define UPIO_MEM (2)
#define UPIO_MEM32 (3)
#define UPIO_AU (4) /* Au1x00 and RT288x type IO */
#define UPIO_TSI (5) /* Tsi108/109 type IO */ unsigned int read_status_mask; /* driver specific */
unsigned int ignore_status_mask; /* driver specific */
struct uart_state *state; /* pointer to parent state */
struct uart_icount icount; /* statistics */ struct console *cons; /* struct console, if any */
#if defined(CONFIG_SERIAL_CORE_CONSOLE) || defined(SUPPORT_SYSRQ)
unsigned long sysrq; /* sysrq timeout */
#endif upf_t flags; #define UPF_FOURPORT ((__force upf_t) (1 << 1))
#define UPF_SAK ((__force upf_t) (1 << 2))
#define UPF_SPD_MASK ((__force upf_t) (0x1030))
#define UPF_SPD_HI ((__force upf_t) (0x0010))
#define UPF_SPD_VHI ((__force upf_t) (0x0020))
#define UPF_SPD_CUST ((__force upf_t) (0x0030))
#define UPF_SPD_SHI ((__force upf_t) (0x1000))
#define UPF_SPD_WARP ((__force upf_t) (0x1010))
#define UPF_SKIP_TEST ((__force upf_t) (1 << 6))
#define UPF_AUTO_IRQ ((__force upf_t) (1 << 7))
#define UPF_HARDPPS_CD ((__force upf_t) (1 << 11))
#define UPF_LOW_LATENCY ((__force upf_t) (1 << 13))
#define UPF_BUGGY_UART ((__force upf_t) (1 << 14))
#define UPF_NO_TXEN_TEST ((__force upf_t) (1 << 15))
#define UPF_MAGIC_MULTIPLIER ((__force upf_t) (1 << 16))
/* Port has hardware-assisted h/w flow control (iow, auto-RTS *not* auto-CTS) */
#define UPF_HARD_FLOW ((__force upf_t) (1 << 21))
/* Port has hardware-assisted s/w flow control */
#define UPF_SOFT_FLOW ((__force upf_t) (1 << 22))
#define UPF_CONS_FLOW ((__force upf_t) (1 << 23))
#define UPF_SHARE_IRQ ((__force upf_t) (1 << 24))
#define UPF_EXAR_EFR ((__force upf_t) (1 << 25))
#define UPF_BUG_THRE ((__force upf_t) (1 << 26))
/* The exact UART type is known and should not be probed. */
#define UPF_FIXED_TYPE ((__force upf_t) (1 << 27))
#define UPF_BOOT_AUTOCONF ((__force upf_t) (1 << 28))
#define UPF_FIXED_PORT ((__force upf_t) (1 << 29))
#define UPF_DEAD ((__force upf_t) (1 << 30))
#define UPF_IOREMAP ((__force upf_t) (1 << 31)) #define UPF_CHANGE_MASK ((__force upf_t) (0x17fff))
#define UPF_USR_MASK ((__force upf_t) (UPF_SPD_MASK|UPF_LOW_LATENCY)) unsigned int mctrl; /* current modem ctrl settings */
unsigned int timeout; /* character-based timeout */
unsigned int type; /* port type */
const struct uart_ops *ops;
unsigned int custom_divisor;
unsigned int line; /* port index */
resource_size_t mapbase; /* for ioremap */
struct device *dev; /* parent device */
unsigned char hub6; /* this should be in the 8250 driver */
unsigned char suspended;
unsigned char irq_wake;
unsigned char unused[2];
void *private_data; /* generic platform data pointer */
};

2.2.5. struct uart_ops

struct 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 (*throttle)(struct uart_port *);
void (*unthrottle)(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);
int (*startup)(struct uart_port *);
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *, int new);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
int (*set_wake)(struct uart_port *, unsigned int state); /*
* Return a string describing the type of the port
*/
const char *(*type)(struct uart_port *); /*
* Release IO and memory resources used by the port.
* This includes iounmap if necessary.
*/
void (*release_port)(struct uart_port *); /*
* Request IO and memory resources used by the port.
* This includes iomapping the port if necessary.
*/
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};

2.3. 关键流程

2.3.1. 注册流程

2.3.1.1. 注册uart_driver

此接口在uart driver中调用,用来注册uart_driver到kernel中,调用阶段在uart driver的初始阶段,例如:module_init(), uart_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,这一部分是内核实现好的不需要修改

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所示:

2.4. 数据收发流程

2.4.1. 打开设备(open操作)

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

2.4.2. 数据发送流程(write操作)

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

2.4.3. 数据接收流程(read操作)

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

2.4.4. 关闭设备(close操作)

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

2.4.5. 注销流程

2.4.5.1. 移除uart_port

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

2.4.5.2. 注销uart_driver

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

2.5. 使用rs485通信

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.

模式1

2-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模式。

模式2

1-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为低电平)

模式3

rs485-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

模式4

rs485-hardware-halfduplex(de/re 独立输出)

基本配置同模式3,但是设置 rs485模式为 hardware-halfduplex模式

  • a, 只要设置 de-en/rx-en 都为1,然后就不用管了,硬件实现半双工切换。

模式5:

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



接收:

默认没有数据时,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. 模块详细设计

3.1. 关键函数接口

3.1.1. uart_register_driver

/*功能:  uart_register_driver用于串口驱动uart_driver注册到内核(串口核心层)中,通常在模块初始化函数调用该函数。
*参数:drv:要注册的uart_driver
*返回值:成功,返回0;否则返回错误码
*/
int uart_register_driver(struct uart_driver *drv)

3.1.2. uart_unregister_driver

/*功能:uart_unregister 用于注销我们已注册的uart_driver,通常在模块卸载函数调用该函数,
*参数 : drv:要注销的uart_driver
*返回值:成功返回0,否则返回错误码
*/
void uart_unregister_driver(struct uart_driver *drv)

3.1.3. uart_add_one_port

/*功能:uart_add_one_port用于为串口驱动添加一个串口端口,通常在探测到设备后(驱动的设备probe方法)调用该函数
*参数:
* drv:串口驱动
* port:要添加的串口端口
*返回值:成功,返回0;否则返回错误码
*/
int uart_add_one_port(struct uart_driver *drv,struct uart_port *port)

3.1.4. uart_remove_one_port

/*功能:uart_remove_one_port用于删除一个已经添加到串口驱动中的串口端口,通常在驱动卸载时调用该函数
*参数:
* drv:串口驱动
* port:要删除的串口端口
*返回值:成功,返回0;否则返回错误码
*/
int uart_remove_one_port(struct uart_driver *drv,struct uart_port *port)

3.1.5. uart_write_wakeup

/*功能:uart_write_wakeup唤醒上层因串口端口写数据而堵塞的进程,通常在串口发送中断处理函数中调用该函数
*参数:
* port: 需要唤醒写堵塞进程的串口端口
*/
void uart_write_wakeup(struct uart_port *port)

3.1.6. uart_suspend_port

/*功能:uart_suspend_port用于挂起特定的串口端口
*参数:
* drv:要挂起的串口端口锁所属的串口驱动
* port:要挂起的串口端口
*返回值:成功返回0;否则返回错误码
*/
int uart_suspend_port(struct uart_driver *drv, struct uart_port *port)

3.1.7. uart_resume_port

/*功能:uart_resume_port用于恢复某一已挂起的串口
*参数:
* drv:要恢复的串口端口所属的串口驱动
* port:要恢复的串口端口
*返回值:成功返回0;否则返回错误码
*/
int uart_resume_port(struct uart_driver *drv, struct uart_port *port)

3.1.8. uart_get_baud_rate

/*功能:uart_get_baud_rate通过解码termios结构体来获取指定串口的波特率
*参数:
* port:要获取波特率的串口端口
* termios:当前期望的termios配置(包括串口波特率)
* old:以前的termios配置,可以为NULL
* min:可以接受的最小波特率
* max:可以接受的最大波特率
* 返回值:串口波特率
*/
unsigned int uart_get_baund_rate(struct uart_port *port, struct ktermios *termios, struct ktermios *old,unsigned int min, unsigned int max)

3.1.9. uart_get_divisor

/*功能:uart_get_divisor 用于计算某一波特率的串口时钟分频数(串口波特率除数)
*参数:
* port:要计算分频数的串口端口
* baud:期望的波特率
*返回值:串口时钟分频数
*/
unsigned int uart_get_divisor(struct uart_port *port, unsigned int baund)

3.1.10. uart_update_timeout

/*功能:uart_update_timeout用于更新(设置)串口FIFO超出时间
*参数:
* port:要更新超时间的串口端口
* cfalg:termios结构体的cflag值
* baud:串口的波特率
*/
void uart_update_timeout(struct uart_port *port,unsigned int cflag, unsigned int baud)

3.1.11. uart_insert_char

/*功能:uart_insert_char用于向uart层插入一个字符
*参数:
* port:要写信息的串口端口
* status:RX buffer状态
* overrun:在status中的overrun bit掩码
* ch:需要插入的字符
* flag:插入字符的flag:TTY_BREAK,TTY_PSRIYY, TTY_FRAME
*/
void uart_insert_char(struct uart_port *port, unsigned int status, unsigned int overrun,unsigned int ch, unsigned int flag)

3.1.12. uart_console_write

/*功能:uart_console_write用于向串口端口写一控制台信息
*参数:
* port:要写信息的串口端口
* s:要写的信息
* count:信息的大小
* putchar:用于向串口端口写字符的函数,该函数有两个参数:串口端口和要写的字符
*/
Void uart_console_write(struct uart_port *port,const char *s, unsigned int count,viod(*putchar)(struct uart_port*, int))

4. 模块使用说明

4.1. 串口编程

4.1.1. 串口控制函数

属性 说明
tcgetatrr 取属性(termios结构)
tcsetarr 设置属性(termios结构)
cfgetispeed 得到输入速度
cfsetispeed 得到输出速度
cfstospeed 设置输出速度
tcdrain 等待所有输出都被传输
tcflow 挂起传输或接收
tcflush 刷请未决输出和/或输入
tcsendbreak 送BREAK字符
tcgetpgrp 得到前台进程组ID
Tcsetpgrp 设置前台进程组ID

4.1.2. 串口配置流程

  • (1) 保持原先串口配置,使用tegetatrr(fd, &oldtio);
struct termious newtio, oldtio;
tegetattr(fd, &oldtio);
  • (2) 激活选项有CLOCAL和CREAD,用于本地连接和接收使用
newtio.cflag |= CLOCAL|CREAD;
  • (3) 设置波特率
newtio.c_cflag = B115200;
  • (4) 设置数据位,需使用掩码设置
newtio.c_cflag &= ~CSIZE;
Newtio.c_cflag |= CS8;
  • (5) 设置停止位,通过激活c_cflag中的CSTOP实现。若停止位为1,则清除CSTOPB,若停止位为2,则激活CSTOP
newtio.c_cflag &= ~CSTOPB; /*停止位设置为1*/
Newtio.c_cflag |= CSTOPB; /*停止位设置为2 */
  • (6) 设置流控
newtio.c_cfag |= CRTSCTS /*开启硬件流控 */
newtio.c_cfag |= (IXON | IXOFF | IXANY); /*开启软件流控*/
  • (7) 奇偶检验位设置,使用c_cflag和c_ifag.

    设置奇校验
newtio.c_cflag |= PARENB;
newtio.c_cflag |= PARODD;
newtio.c_iflag |= (INPCK | ISTRIP);

设置偶校验

newtio.c_iflag |= (INPCK | ISTRIP);
newtio.c_cflag |= PARENB;
newtio.c_cflag |= ~PARODD;
  • (8) 设置最少字符和等待时间,对于接收字符和等待时间没有什么特别的要求,可设置为0:
newtio.c_cc[VTIME] = 0;
newtio.c_cc[VMIN] = 0;
  • (9) 处理要写入的引用对象

    tcflush函数刷清(抛弃)输入缓冲(终端程序已经接收到,但用户程序尚未读)或输出缓冲(用户程序已经写,但未发送)。
int tcflash(int filedes, int quene)
quene数应当是下列三个常数之一:
*TCIFLUSH 刷清输入队列
*TCOFLUSH 刷清输出队列
*TCIOFLUSH 刷清输入、输出队列
例如:
tcflush(fd, TCIFLUSH);
  • (10) 激活配置,在完成配置后,需要激活配置使其生效。使用tcsetattr()函数:
int tcsetarr(int filedes, const struct termios *termptr);
opt 指定在什么时候新的终端属性才起作用,
*TCSANOW:更改立即发生
*TCSADRAIN:发送了所有输出后更改才发生。若更改输出参数则应使用此选项
*TCSAFLUSH:发送了所有输出后更改才发生。更进一步,在更改发生时未读的
所有输入数据都被删除(刷清)
例如:tcsetatrr(fd, TCSANOW, &newtio);

4.1.3. 使用流程

  • (1)打开串口,例如"/dev/ttySLB0"
fd = open("/dev/ttySLB0",O_RDWR | O_NOCTTY | O_NDELAY);
O_NOCTTY:是为了告诉Linux这个程序不会成为这个端口上的“控制终端”。如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。
O_NDELAY:这个标志则是告诉Linux这个程序并不关心DCD信号线的状态,也就是不管串口是否有数据到来,都是非阻塞的,程序继续执行。
  • (2)恢复串口状态为阻塞状态,用于等待串口数据的读入,用fcntl函数:
fcntl(fd,F_SETFL,0);  //F_SETFL:设置文件flag为0,即默认,即阻塞状态
  • (3)接着测试打开的文件描述符是否应用一个终端设备,以进一步确认串口是否正确打开。
isatty(STDIN_FILENO);
  • (4)读写串口
串口的读写与普通文件一样,使用read,write函数
read(fd, buf ,8);
write(fd,buff,8);

4.1.4. Demo

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

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <termios.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <log/log.h>
#include <stdlib.h> #define UART_DEVICE "/dev/ttySLB1" struct temp {
float temp_max1;
float temp_max2;
float temp_max3;
float temp_min;
float temp_mean;
float temp_enviromem;
char temp_col[1536];
}; int main(void)
{
int count, i, fd;
struct termios oldtio, newtio;
struct temp *temp;
temp = (struct temp *)malloc(sizeof(struct temp));
if (!temp) {
printf("malloc failed\n");
return -1;
} char cmd_buf1[] = { 0xAA, 0x01, 0x04, 0x00, 0x06, 0x10, 0x05, 0x00, 0xBB};
char cmd_buf2[] = { 0xAA, 0x01, 0x04, 0x00, 0x00, 0xA0, 0x00, 0x03, 0xBB};
char cmd_buf3[] = { 0xAA, 0x01, 0x04, 0x00, 0x03, 0x10, 0x01, 0x00, 0xBB};
char read_buf[2000]; //-----------打开uart设备文件------------------
fd = open(UART_DEVICE, O_RDWR | O_NOCTTY);
if (fd < 0) {
printf("Open %s failed\n", UART_DEVICE);
return -1;
} else {
printf("Open %s successfully\n", UART_DEVICE);
} //-----------设置操作参数-----------------------
tcgetattr(fd, &oldtio);//获取当前操作模式参数
memset(&newtio, 0, sizeof(newtio)); //波特率=230400 数据位=8 使能数据接收
newtio.c_cflag = B230400 | CS8 | CLOCAL | CREAD | CSTOPB;
newtio.c_iflag = IGNPAR; tcflush(fd, TCIFLUSH);//清空输入缓冲区和输出缓冲区
tcsetattr(fd, TCSANOW, &newtio);//设置新的操作参数 //printf("input: %s, len = %d\n", cmd_buf, strlen(cmd_buf));
//------------向urat发送数据------------------- for (i = 0; i < 9; i++)
printf("%#X ", cmd_buf1[i]); count = write(fd, cmd_buf1, 9);
if (count != 9) {
printf("send failed\n");
return -1;
} usleep(500000); memset(read_buf, 0, sizeof(read_buf));
count = read(fd, read_buf, sizeof(read_buf));
if (count > 0) {
for (i = 0; i < count; i++);
temp->temp_max1 = read_buf[7] << 8 | read_buf[6];
temp->temp_max2 = read_buf[9] << 8 | read_buf[8];
temp->temp_max3 = read_buf[11] << 8 | read_buf[10];
temp->temp_min = read_buf[13] << 8 | read_buf[12];
temp->temp_mean = read_buf[15] << 8 | read_buf[14]; printf("temp->temp_max1 = %f\n", temp->temp_max1 * 0.01);
printf("temp->temp_max2 = %f\n", temp->temp_max2 * 0.01);
printf("temp->temp_max3 = %f\n", temp->temp_max3 * 0.01);
printf("temp->temp_min = %f\n", temp->temp_min * 0.01);
printf("temp->temp_mean = %f\n", temp->temp_mean * 0.01); } else {
printf("read temp failed\n");
return -1;
} count = write(fd, cmd_buf3, 9);
if (count != 9) {
printf("send failed\n");
return -1;
} usleep(365);
memset(read_buf, 0, sizeof(read_buf));
count = read(fd, read_buf, sizeof(read_buf));
if (count > 0) {
for (i = 0; i < count; i++);
temp->temp_enviromem = read_buf[7] << 8 | read_buf[6]; printf("temp->temp_enviromem = %f\n", temp->temp_enviromem * 0.01);
} else {
printf("read enviromem failed\n");
return -1;
} count = write(fd, cmd_buf2, 9);
if (count != 9) {
printf("send failed\n");
return -1;
} usleep(70000);
memset(read_buf, 0, sizeof(read_buf));
memset(temp->temp_col, 0, sizeof(temp->temp_col));
count = read(fd, read_buf, sizeof(read_buf));
printf("count = %d\n", count);
if (count > 0) {
for (i = 0; i < count - 7; i++)
temp->temp_col[i] = read_buf[i+6];
for (i = 0; i < 1536; i++)
{
if (!(i%10))
printf("\n");
printf("%#X ", temp->temp_col[i]);
}
} else {
printf("read temp colour failed\n");
return -1;
}
free(temp); close(fd); tcsetattr(fd, TCSANOW, &oldtio); //恢复原先的设置 return 0;
}

基于Linux的tty架构及UART驱动详解的更多相关文章

  1. linux usb 驱动详解

    linux usb 驱动详解 USB 设备驱动代码通过urb和所有的 USB 设备通讯.urb用 struct urb 结构描述(include/linux/usb.h ). urb 以一种异步的方式 ...

  2. Linux(centos)系统各个目录的作用详解

    Linux(centos)系统各个目录的作用详解 文件系统的类型 LINUX有四种基本文件系统类型:普通文件.目录文件.连接文件和特殊文件,可用file命令来识别. 普通文件:如文本文件.C语言元代码 ...

  3. 25.Linux-Nor Flash驱动(详解)

    1.nor硬件介绍: 从原理图中我们能看到NOR FLASH有地址线,有数据线,它和我们的SDRAM接口相似,能直接读取数据,但是不能像SDRAM直接写入数据,需要有命令才行 1.1其中我们2440的 ...

  4. 16.Linux-LCD驱动(详解)

    在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构体: framebuffer_alloc(); 2) 设置fb_info 3) 设置硬件相关的操作 ...

  5. Linux DTS(Device Tree Source)设备树详解之二(dts匹配及发挥作用的流程篇)【转】

    转自:https://blog.csdn.net/radianceblau/article/details/74722395 版权声明:本文为博主原创文章,未经博主允许不得转载.如本文对您有帮助,欢迎 ...

  6. Linux中redis安装配置及使用详解

    Linux中redis安装配置及使用详解 一. Redis基本知识 1.Redis 的数据类型 字符串 , 列表 (lists) , 集合 (sets) , 有序集合 (sorts sets) , 哈 ...

  7. Linux 网络流量实时监控工具之ntopng详解

    大纲一.前言二.ntopng 简介三.ntopng 功能说明 四.ntopng 安装详解五.ntopng 配置详解 六.ntopng 使用详解注,操作系统 CentOS 5.5 X86_64,软件版本 ...

  8. Linux NFS服务器的安装与配置详解

    一.NFS服务简介 NFS是Network File System(网络文件系统).主要功能是通过网络让不同的服务器之间可以共享文件或者目录.NFS客户端一般是应用服务器(比如web,负载均衡等),可 ...

  9. 16.Linux-LCD驱动(详解)【转】

    转自:https://www.cnblogs.com/lifexy/p/7604011.html 在上一节LCD层次分析中,得出写个LCD驱动入口函数,需要以下4步: 1) 分配一个fb_info结构 ...

随机推荐

  1. anaconda python3.7 安装 tensorflow-gpu 2.0.0 beta1 配置PyCharm

    参考tensorflow 公众号<tensorflow2.0 安装指南> https://mp.weixin.qq.com/s/7rNXFEC5HYe91RJ0-9CKdQ # 1. NV ...

  2. hash table

    Hash Table,叫做哈希表,也叫做散列表.概念:通过某种对应关系h,使得每一个元素和储存位置一一对应.这种对应关系称为哈希函数.它最大的优点就是插入.搜索和删除得很快(O(1)).碰撞(Coll ...

  3. np.random.randint()的返回值

    返回的是数组而非int 比如返回x,y 为[1][2] 而非1,2 容易在只有一维一列时没有意识到 其他函数的返回值也要注意

  4. nyoj-1236 挑战密室

    挑战密室 时间限制:1 s | 内存限制:128 M 提交 状态 排名 题目描述 R组织的特工Dr. Kong 为了寻找丢失的超体元素,不幸陷入WTO密室.Dr. Kong必须尽快找到解锁密码逃离,否 ...

  5. R语言学习2:绘图

    本系列是一个新的系列,在此系列中,我将和大家共同学习R语言.由于我对R语言的了解也甚少,所以本系列更多以一个学习者的视角来完成. 参考教材:<R语言实战>第二版(Robert I.Kaba ...

  6. Python+OpenCV+图片旋转并用原底色填充新四角

    import cv2 from math import fabs, sin, cos, radians import numpy as np from scipy.stats import mode ...

  7. Front End Frameworks Trending 2021

    Front End Frameworks Trending 2021 Front End Frameworks https://2019.stateofjs.com/front-end-framewo ...

  8. js console 性能测试 & don't-use-array-foreach-use-for-instead

    don't-use-array-foreach-use-for-instead slower https://coderwall.com/p/kvzbpa/don-t-use-array-foreac ...

  9. CSS3 & Flex Layout All In One

    CSS3 & Flex Layout All In One demos https://www.cnblogs.com/xgqfrms/p/10769302.html .flex-contai ...

  10. background & background-image & border-image

    background & background-image & border-image https://developer.mozilla.org/en-US/docs/Web/CS ...