|
一、模块硬件学习 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作为异步串口通信协议的一种,工作原理是将传输数据的每个字符一位接一位地传输。其中各位的意义如下:
由于数据是在传输线上定时的,并且每一个设备有其自己的时钟,很可能在通信中两台设备间出现了小小的不同步。因此停止位不仅仅是表示传输的结束,并且提供计算机校正时钟同步的机会。适用于停止位的位数越多,不同时钟同步的容忍程度越大,但是数据传输率同时也越慢。
Uart传输数据如图2-1所示:
波特率是衡量资料传送速率的指标。表示每秒钟传送的符号数(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与RS485UART:通常说的UART指的是一种串行通信协议,规定了数据帧格式,波特率等。RS232和RS485:是两种不同的电气协议,也就是说,是对电气特性以及物理特性的规定,作用于数据的传输通路上,它并不含对数据的处理方式。 对应的物理器件有RS232或者RS485驱动芯片,将CPU经过UART传送过来的电压信号驱动成RS232或者RS485电平逻辑。 RS232使用3-15V有效电平,而UART,因为对电气特性没有规定,所以直接使用CPU使用的电平,即TTL电平(在0-3.3V之间)。 更具体的,电气的特性也决定了线路的连接方式,比如RS232,规定用电平表示数据,因此线路就是单线路的,两根线能达到全双工的目的;RS485使用差分电平表示数据,因此必须用两根线才能达到传输数据的基本要求,要实现全双工,必须使用4根线。 RS232和RS485的区别 (1)抗干扰性
数据在两个串口传输时,常常会出现丢失数据的现象,或者两台计算机的处理速度不同,如台式机与单片机之间的通讯,接收端数据缓冲区以满,此时继续发送的数据就会丢失,流控制能解决这个问题,当接收端数据处理不过来时,就发出“不再接收”的信号,发送端就停止发送,直到收到“可以继续发送”的信号再发送数据。 因此流控制可以控制数据传输的进程,防止数据丢失。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(输入信号)。
由于电缆的限制,在普通的控制通讯中一般不采用硬件流控制,而是使用软件流控制。 一般通过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: I2.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 注册的字符设备节点来访问。
图3.1tty架构图 如图3.2所示,tty设备发送数据的流程为:tty核心从一个用户获取将要发送给一个tty设备的数据,tty核心将数据传递给tty线路规程驱动,接着数据被传到tty驱动,tty驱动将数据转换为可以发给硬件的格式。 接收数据的流程为:从tty硬件接收到的数据向上交给tty驱动,接着进入tty线路规程驱动,再进入tty核心,在这里它被一个用户获取。
图3.2 tty设备发送、接收数据流程* w) _5 Y. T |) a3 c ; `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 {struct module *owner; /*拥有该uart_driver的模块,一般为THIS_MODULE*/ const char *driver_name; /*驱动串口名,串口设备名以驱动名为基础*/: x5 q# V7 W) T. _. o" t9 | const char *dev_name; /*串口设备名*/1 Z7 m3 N" ~ }: F1 C$ M int major; /*主设备号*/ int minor; /*次设备号*/ 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 */ struct uart_state *state; /*下层,窗口驱动层*/ struct tty_driver *tty_driver; /*tty相关*/" b, }6 \0 V3 \( X& `# B" g 2.2.2. struct console 实现控制台打印功能必须要注册的结构体 struct console {/ T6 s, M" t6 x' D& k- Ochar name[16]; void(*write)(struct console *,const char *, unsigined);$ E2 z$ W% o" d3 B% L: Q; B int (*read)(struct console *, char *, unsigned); struct tty_driver *(struct console *,int*); void (*unblank)(void);. Q; Z; A A; _ int (*setup)(struct console *, char *); int (*early_setup)(void);- W! r* ^. \# Y( t short flags; short index; /*用来指定该console使用哪一个uart port (对应的uart_port中的line),如果为-1,kernel会自动选择第一个uart port*/ int cflag; void *data; struct console *next; };3 y1 [9 A1 h( Q. ^+ s 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;; W$ A/ \# H; H8 k/ R% G- _ , ^& B# n9 M. ]: L: u8 u enum uart_pm_state pm_state; struct circ_buf xmit;$ z( h/ n/ E- \% Y0 L struct uart_port *uart_port; /*对应于一个串口设备*/# I$ x8 n! x3 {. h% e, l0 v }; . } ?, 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); void (*serial_out)(struct uart_port *, int, int); void (*set_termios)(struct uart_port *,7 {% r: b/ v9 @" x. i4 p struct ktermios *new, struct ktermios *old); int (*handle_irq)(struct uart_port *); void (*pm)(struct uart_port *, unsigned int state, 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 */ 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 */ 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; : o9 s, k3 M ^/ A #define UPIO_PORT (0) #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) #define UPIO_AU (4) /* Au1x00 and RT288x type IO */ #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 */ unsigned int ignore_status_mask; /* driver specific */ 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) unsigned long sysrq; /* sysrq timeout */) X$ u) x: u& w8 x* l# H #endif # Z, R2 L1 j8 S1 ~0 B$ L upf_t flags; / E& |+ Y3 S* C! E #define UPF_FOURPORT ((__force upf_t) (1 << 1)) #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)) #define UPF_SPD_SHI ((__force upf_t) (0x1000))& g, W( t! A( c+ P' h #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))4 e. ?/ A+ e/ h. W: l, I# R5 O2 ^5 t3 | #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))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) */ #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))5 j" r+ g V- x4 a1 z( b5 K/ e #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))" V7 q4 A5 C" c /* 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)); 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)) #define UPF_IOREMAP ((__force upf_t) (1 << 31)) #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 unsigned int mctrl; /* current modem ctrl settings */ unsigned int timeout; /* character-based timeout */ unsigned int type; /* port type */# u# W/ g8 P4 p i ?9 ?# Y1 H, M- g const struct uart_ops *ops; unsigned int custom_divisor;: X; [2 D- _8 w& n. |; \ unsigned int line; /* port index */ 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 */ unsigned char suspended;. H9 g: @- c( K8 E unsigned char irq_wake; 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 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 *);& J; q7 |) S' } void (*stop_tx)(struct uart_port *); void (*start_tx)(struct uart_port *);8 R5 W/ N, H( t$ X% m p void (*throttle)(struct uart_port *); void (*unthrottle)(struct uart_port *); 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 *); void (*break_ctl)(struct uart_port *, int ctl);: u+ h4 ]) G4 N7 g7 A int (*startup)(struct uart_port *); void (*shutdown)(struct uart_port *);7 ~+ O3 ?% x8 v# d5 G& w void (*flush_buffer)(struct uart_port *); void (*set_termios)(struct uart_port *, struct ktermios *new, 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, 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 /* * 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 *);: 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. */, _$ 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); int (*verify_port)(struct uart_port *, struct serial_struct *); int (*ioctl)(struct uart_port *, unsigned int, unsigned long);0 j! M3 j* N5 M/ k* k" S #ifdef CONFIG_CONSOLE_POLL 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 2.3. 关键流程 2.3.1. 注册流程 2.3.1.1. 注册uart_driver 此接口在uart driver中调用,用来注册uart_driver到kernel中,调用阶段在uart driver的初始阶段,例如:module_init(), uart_driver的注册流程图
图3.3uart driver注册流程 注册过程主要做了以下操作:
此接口用于注册一个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所示:
- 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所示:
6 x$ c/ G/ U" W* W 3 o8 t4 E# I3 g9 \* b 2.4.2. 数据发送流程(write操作) 发送数据大体流程如图3-6所示:
9 u+ p, P! @ j( a, f 2.4.3. 数据接收流程(read操作) 接收数据的大体流程如图3-7所示:
+ A& R" E Q" S( _, p$ ^8 [ 2.4.4. 关闭设备(close操作) close设备的大体流程如图3-8所示:
; d1 }+ l: _4 ]3 ] : `3 \+ c- P- O' a 2.4.5. 注销流程 2.4.5.1. 移除uart_port 此接口用于从uart driver上注销一个uart port,该接口在uart driver中的remove函数中调用。uart移除port的流程如图3-9所示:
' 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所示
4 u2 a- N5 Q) I 2.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,电压转换芯片工作正常:
模式12-gpio-normal-uart-rs485-halfduplex (2个gpio独立控制de/re, enable就是将相关gpio设置到active电平;不用uart控制器的rs485模式;uart控制器处于normal模式)
模式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模式)
模式3rs485-software-halfduplex(de/re 独立输出) (使能uart控制器的rs485模式; 通过uart模块内部reg来控制 de/re 信号)
模式4rs485-hardware-halfduplex(de/re 独立输出) 基本配置同模式3,但是设置 rs485模式为 hardware-halfduplex模式
模式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. 关键函数接口2 n {! G7 p" G 3.1.1. uart_register_driver /*功能: 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 3.1.2. uart_unregister_driver /*功能: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 */ void uart_unregister_driver(struct uart_driver *drv) 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:要添加的串口端口 *返回值:成功,返回0;否则返回错误码 */ int uart_add_one_port(struct uart_driver *drv,struct uart_port *port) ! {( c C1 n5 \: g3 Q) y, F 3.1.4. uart_remove_one_port /*功能:uart_remove_one_port用于删除一个已经添加到串口驱动中的串口端口,通常在驱动卸载时调用该函数' g: n7 O8 H. d; i* S *参数: * drv:串口驱动 a4 W$ G! x8 @ * port:要删除的串口端口 *返回值:成功,返回0;否则返回错误码 */# b1 z2 w# o7 a. @ z int uart_remove_one_port(struct uart_driver *drv,struct uart_port *port) ' 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唤醒上层因串口端口写数据而堵塞的进程,通常在串口发送中断处理函数中调用该函数 *参数: * port: 需要唤醒写堵塞进程的串口端口* O; a# u( ]) e8 Q4 e2 Y. a */, Y& ]7 ^2 O4 Q7 p void uart_write_wakeup(struct uart_port *port) ! S! c7 Z1 _1 \: u# h 3.1.6. uart_suspend_port9 {! H! z3 z9 Y7 p t /*功能:uart_suspend_port用于挂起特定的串口端口 *参数:+ K! W) Z* m* v$ c * drv:要挂起的串口端口锁所属的串口驱动6 i h. h+ ^9 N5 b * port:要挂起的串口端口 *返回值:成功返回0;否则返回错误码' z1 y2 T4 `0 J" T' ^5 w$ K/ ?; B */ 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 /*功能:uart_resume_port用于恢复某一已挂起的串口 *参数:4 n2 } a8 |& k4 ~ * drv:要恢复的串口端口所属的串口驱动 p' |' B% _5 p& v * 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:要获取波特率的串口端口0 ]8 B% s# }# i: M& N * termios:当前期望的termios配置(包括串口波特率)2 ~7 ^7 C( a4 Y1 C/ p- e * old:以前的termios配置,可以为NULL * min:可以接受的最小波特率* ` R8 }, \" Y! X+ Y% Q% L% e * max:可以接受的最大波特率 * 返回值:串口波特率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) 3.1.9. uart_get_divisor) p/ \3 \& f& v4 v /*功能:uart_get_divisor 用于计算某一波特率的串口时钟分频数(串口波特率除数) *参数: * port:要计算分频数的串口端口 * baud:期望的波特率 *返回值:串口时钟分频数 */' T/ T7 q2 }3 C4 M9 H unsigned int uart_get_divisor(struct uart_port *port, unsigned int baund) 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值 * 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掩码! M$ b& L" i1 ^2 L% R * ch:需要插入的字符 * flag:插入字符的flag:TTY_BREAK,TTY_PSRIYY, TTY_FRAME0 w5 r' D/ q3 o j1 s0 n */ 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:要写的信息. N$ I2 }! i$ |5 m3 K" @2 t0 T * count:信息的大小. |) R( k- i% E% y ` * putchar:用于向串口端口写字符的函数,该函数有两个参数:串口端口和要写的字符! D2 P1 g4 V: q& b6 w */ Void 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]
tegetattr(fd, &oldtio);
Newtio.c_cflag |= CS8;7 k7 c- v' |: A6 Y9 Z
Newtio.c_cflag |= CSTOPB; /*停止位设置为2 */. H7 h. A1 {; N# |; F
newtio.c_cfag |= (IXON | IXOFF | IXANY); /*开启软件流控*/
newtio.c_cflag |= PARODD; newtio.c_iflag |= (INPCK | ISTRIP); 设置偶校验 newtio.c_iflag |= (INPCK | ISTRIP);5 o# G- r; d- K0 ^; [3 T6 Bnewtio.c_cflag |= PARENB; |2 M2 H S F( r( T/ Q newtio.c_cflag |= ~PARODD;
newtio.c_cc[VMIN] = 0;$ }- T) D; N! J6 k) C# g
quene数应当是下列三个常数之一: *TCIFLUSH 刷清输入队列 *TCOFLUSH 刷清输出队列/ b# c v6 r1 {$ m# y+ F5 _ *TCIOFLUSH 刷清输入、输出队列 例如:4 h( s7 r9 X2 J$ A; B; M$ D tcflush(fd, TCIFLUSH);$ L3 K1 P. v$ i1 v# P/ u
opt 指定在什么时候新的终端属性才起作用, *TCSANOW:更改立即发生 *TCSADRAIN:发送了所有输出后更改才发生。若更改输出参数则应使用此选项: f+ n& }* L5 ~' @ *TCSAFLUSH:发送了所有输出后更改才发生。更进一步,在更改发生时未读的 所有输入数据都被删除(刷清) 例如:tcsetatrr(fd, TCSANOW, &newtio); 4.1.3. 使用流程
O_NOCTTY:是为了告诉Linux这个程序不会成为这个端口上的“控制终端”。如果不这样做的话,所有的输入,比如键盘上过来的Ctrl+C中止信号等等,会影响到你的进程。 O_NDELAY:这个标志则是告诉Linux这个程序并不关心DCD信号线的状态,也就是不管串口是否有数据到来,都是非阻塞的,程序继续执行。
read(fd, buf ,8); write(fd,buff,8); 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> #include <termios.h> #include <stdio.h>% f& `# [! K4 j6 g+ q5 v. M: X, K8 O #include <string.h> #include <unistd.h> #include <log/log.h> #include <stdlib.h> #define UART_DEVICE "/dev/ttySLB1"& i# a& x6 ]; ^% j* U7 a struct temp { float temp_max1; float temp_max2;+ h3 W" b. p" `/ ~$ Z% y. f; m float temp_max3; float temp_min;; z, W8 p6 d1 _8 d float temp_mean; float temp_enviromem; char temp_col[1536];" F: ?* l, v, n8 m% ~3 l! n2 ^- V }; int main(void)3 r6 \: ]1 X6 f7 o# [! n { int count, i, fd;- C* w5 F& X0 c" _0 j struct termios oldtio, newtio;9 ~, [ `3 ~# n; l+ \9 ^ struct temp *temp; temp = (struct temp *)malloc(sizeof(struct temp)); if (!temp) {) r) R4 ]) k9 r, I- B5 d" K7 ^( h% B printf("malloc failed\n"); return -1;% s5 ^, g: j0 E1 J) X9 p } & 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}; 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, ? //-----------打开uart设备文件------------------ fd = open(UART_DEVICE, O_RDWR | O_NOCTTY); 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 //-----------设置操作参数----------------------- 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; newtio.c_iflag = IGNPAR; 8 p4 o- B% C2 D& H9 T tcflush(fd, TCIFLUSH);//清空输入缓冲区和输出缓冲区 tcsetattr(fd, TCSANOW, &newtio);//设置新的操作参数 //printf("input: %s, len = %d\n", cmd_buf, strlen(cmd_buf)); //------------向urat发送数据------------------- 7 \3 {6 w8 t9 U+ Y for (i = 0; i < 9; i++) 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; }; a- e/ H" n" U: K" `+ j$ \ usleep(500000); memset(read_buf, 0, sizeof(read_buf)); 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]; 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 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); printf("temp->temp_min = %f\n", temp->temp_min * 0.01); printf("temp->temp_mean = %f\n", temp->temp_mean * 0.01); & }" K9 R$ M: T2 z1 f9 U8 B) I } else {7 n7 t+ M* ?( X printf("read temp failed\n"); 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); if (count != 9) { printf("send failed\n"); return -1;2 O$ p4 g8 K, f } ( 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)); 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); } else { 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 } % 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) { printf("send failed\n"); return -1; }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)); 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 = 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); } } 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; } free(temp); close(fd);# c, \! X0 M6 F+ j tcsetattr(fd, TCSANOW, &oldtio); //恢复原先的设置: f) j9 x9 Y4 u E2 G $ f1 o V$ z I" u" i4 z return 0; } |
微信公众号
手机版