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

单片机开发串口——应用最为广泛的通信接口

[复制链接]
gaosmile 发布时间:2020-7-10 13:34

[导读] 单片机开发串口是应用最为广泛的通信接口,也是最为简单的通信接口之一,但是其中的一些要点你是否明了呢?来看看本人对串口的一些总结,当然这个总结并不能面面俱到,只是将个人认为具有共性以及相对比较重要的点做了些梳理。


1 o' ^4 Z  h1 ]" j

啥是串口?首先这玩意儿分两种:


* I' n5 c) @4 }" f  V0 @

通用异步收发器UART)是用于异步串行通信的一种物理层标准,其中数据格式和传输速度是可配置的。


$ p1 U( F7 z' S7 b- F1 Q

通用同步收发器(USART)是一种串行接口设备,可以对其进行编程以进行异步或同步通信。

* O/ z9 x2 ]+ V" l

数据格式


. G: l3 P/ a) P5 Y7 D2 j' u

        


% p& _. a4 P( X' c0 N

线上空闲、无数据状态为常高电平,故逻辑低定义为起始位。


$ n. i, `% G: X# c" b

起始位:总是 1 位


* M6 U; {  Q3 A2 |% w+ A

数据位:常见的有 8 位或 9 位。


" z) o0 z0 E( B, K' X0 O5 Y8 b

校验位

% A; R; H; {- I5 Q

奇校验

/ X( F1 W' O# d# E- y" Q0 N

偶校验

& m5 i, G* ^+ C$ Y" B9 W

无校验


8 h* }( T6 h2 h7 m6 ]

停止位:

9 I/ T9 l+ h1 o4 [3 M1 |& Q

1 位


0 d( f# Y& o0 P" z

2 位

, y2 X3 b# p& ^4 X9 w0 z9 t1 l1 g/ \

波特率:bit rate 就是位/秒的概念,就是 1 秒传送多少位的概念。常见的波特率有哪些呢?


4 B7 V6 @% P$ g

        


1 @, q" [; a/ l- x

这里须注意的要点:


6 C7 F+ N9 ]/ p# B% |7 v

一个有效字节的传输时间怎么算?


: T" E, Y% w. ~% S+ E

比如 9600 下,1 位起始位,8 位数据位,奇校验,1 位停止位,则

7 a9 {& G9 F  j( c8 h

为什么要理解清楚这个概念呢,因为在应用中需要计算数据吞吐率问题,就比如一个应用是数据采集串口传输问题,需要计算采集的位速率需要小于或等于传输波特率,否则数据就来不及传。当然如果说你有足够大的缓冲区可以临时存储,但是如果进来太快,而传出速度跟不上,多大的缓冲都会满!


# D) \5 u/ ]* l% T# m1 F' ^

校验位有用吗?当你的传输介质处于一个有干扰的场景下,校验位就可以从物理层检测出错误。

8 E7 S! m% F) H; w

理解数据编码方式有啥意义呢?比如在调试中你可以利用逻辑分析直接去解析收发线上的数据报文。


* x; x9 `7 ^5 y) a8 U

应用电路设计的时候 RX-TX 相连,很多初学者容易在这里踩坑!


6 |! m, ^. {+ c0 N7 u2 U( y+ p

常见的传输位序为低有效位在前。


* k( W2 U5 P! s# _2 B

对于波特率而言需要注意波特率发生器有可能带来误码问题


9 K6 p3 x8 O/ R& y) d' @* N' o

啥是 UART?

2 i4 w0 L! q  f7 f0 E

        


2 R" a8 T( b$ s- Y" b# W

两边分别代表两个通信的设备,单从 UART 编程的角度讲收发不需要物理同步握手,想发就发。图中箭头代表数据信息流向。RX 表示接收数据,TX 表示发送数据。数据总是从发送端传递到接收端,这就是为啥 RX 连接 TX,TX 连 RX 的原因。

, J  \! J  d* Z) ^0 g

啥是 USART?

$ Z: |+ u: O% F# W* B- ?

        


* Y' g$ o# z+ T; w& ?2 x

同步简单说,收发不可自如,不可以想发就发,收发需要利用硬件 IO 口进行握手,RTS/CTS 就是用于同步的握手信号:

  p. p! M5 U. h6 M/ G

RTS:Ready to send,请求发送,用于在当前传输结束时阻止数据发送。


* Z8 T' m% ]/ l6 K* V+ [3 o

CTS:clear to send,清除发送,用于指示 USART 已准备好接收数据。


8 P# }! H5 _! ]

这个对于普通应用而言并不常见,这里不做详细展开,需要用到的时候只需要对应收发时控制握手信号即可。

8 N' p6 V9 w" {: f9 Y, u

编程策略对于不同的单片机,其硬件体系各异,寄存器也差异很大,但是从收发编程策略角度而言,常见有下面三种方式:

- A3 d" [( V4 M* T7 Z6 Y( B& Q9 R

查询发送/中断接收模式

& ^$ Q1 [  |$ N, X0 p8 h( {

收发中断模式


9 t" s7 H1 J* q$ f, n" G( E

DMA 模式

1 C& j% a1 H, x0 O. k

查询发送/中断接收模式这里以伪代码方式描述一下:

- D- n6 `( m+ L' z

        /*查询发送字节*/


0 a. E: L! f6 d% ]

        void uart_send_byte( uint8 ch )

2 B( A$ u% C5 p0 Z1 B/ P

        {

' K/ [! u" \$ y# y) ]7 G

        /*如果当前串口状态寄存器非空闲,则一直等待*/

  ]3 c  G& I; S/ e5 K

        /*注意while循环后的分号,表示循环体为空操作*/

. n9 ]& I9 n) `$ A6 [4 c! p- p

        while( !UART_IS_IDLE() );


- r8 R0 B8 V, N

        /*此时将发送字节写入发送寄存器*/


. p2 _* B: b/ H: @

        UART_TX_REG = ch;

. ~$ A5 D8 ]7 H2 d8 K/ a

        }


  k5 c  U4 o. t1 l4 a' V

        /*发送一个缓冲区*/

7 P. i4 {7 w' C: m; w0 Y

        void uart_send_buffer( uint8 *pBuf,uint8 size )


2 K0 L9 V; }; ^/ x/ P( Z

        {


: d9 S: }& O& R

        uint8 i = 0;

$ [' {) h! }( i* ^5 F3 d) Y

        /* 异常参数处理*/


. P: m7 S+ }  \- a# G7 }3 f

        if( pBuf == NULL )

! E+ s1 r% Q4 U" H

        return;


. G- _/ V, k; v0 R9 i. ?& N

        for( i=0; i《size;i++ )


4 o4 f& X9 k7 r1 M5 H

        {


8 l( L8 Q7 }8 z# U0 d/ |

        send_byte( pBuf[i] );

) K+ i4 j% y7 c- M

        }

- l. f$ [% `7 W( U4 O6 ?1 o

        }

" i3 N) }& q* n- S* a5 V9 J

对于接收而言,如采用查询模式则几乎是没有任何应用价值,因为外部数据不知道什么时候会到来,所以查询接受就不描述了,这里描述一下中断接收。

2 L, i( p1 o+ h  z; s! g; q5 W: m

        static uint8 rx_index = 0;


, k9 k! D$ }" z* `1 Z; j4 n" ^

        void uart_rx_isr( void )


4 T, B. V0 i! \1 N* g8 z5 i7 G% y+ M; F

        {

3 X% b0 E8 N& c) m+ f9 v7 \2 p

        /* 接收报文处理 */


6 _) b8 @& C+ T, Z/ ~, D) B/ u1 `4 i

        rx_buffer[rx_index++] = UART_RX_REG;

5 i- p3 R$ l9 v6 p4 K

        }

5 n1 N2 s/ N( c' Y. J- n# N

中断接收需要考虑的几个要点:


# ]5 W8 D) Y, r: @  Z6 Q) e

断帧:这就取决于协议怎么制定了,比如应用协议定义的是 ASCII 码方式,就可以定义同步头、同步尾,比如 AT 指令的解析,做逻辑判断帧头、帧尾即可。但是如果传输的是 16 进制数据,比如 MODBUS-RTU 其断帧采用的是 3.5 个字节时间没有新的字节接收到,则认为收到完整的帧了。

0 C; J+ q8 H$ ^2 T: s

如何保证帧的完整性,一般会在报文尾部加校验,比较常用的校验模式有 CRC 校验算法。

$ \: x1 q9 U. \: l

不同的单片机开发环境对于中断向量的处理方式略有不同,需要根据各自芯片的特点进行处理。比如 51 单片机,其发送/接收都共享一个中断向量号。


9 F" i* E% G) z0 a6 H6 S- K( e- R

        收发中断模式#define FRAME_SIZE (128u)

' A* o9 F8 [( B( N! G

        staTIc uint8 tx_buffer[FRAME_SIZE];


5 n5 \" x& R1 B& J

        staTIc uint8 tx_index = 0;

: [  h6 R$ C" ]1 s

        staTIc uint8 tx_length = 0;


. z! X) V/ }7 A1 x+ a7 D% {5 G% _

        staTIc uint8 rx_buffer[FRAME_SIZE];

. _8 g2 |8 |: A0 |2 u

        static uint8 rx_index = 0;

$ T# j, ^5 v; v, Q( I/ @

        static bool rx_frame_done = false;


& C- m3 a- b' G- e

        void prepare_frame( uint8 * pBuf, uint8 size )

9 k% N0 c( W8 U& G5 g

        {


! H' a! I# H0 N; G) _, c

        /*将待传的报文按照协议封装*/


+ H6 x* _4 w2 M

        /*可能需要处理的事情,比如帧头、帧尾、校验等*/

- W4 c0 l, h  `- x, H1 {

        }

# N# m5 J3 U. X, \0 s5 b  l3 N) H

        bool uart_start_sending( uint8 * pBuf, uint8 size )


  q" N( \! D+ M0 O0 Y% n

        {


- k/ g4 @5 }* e$ e

        if( pBuf == NULL )


* j! ]3 ]* d# C+ |

        return false;

8 @( u: E) a) q

        memcpy( tx_buffer,pBuf,size );


% w8 }* s& L1 C8 ~2 H

        tx_index = 0;

$ h& G6 o6 e2 t  I8 T3 u

        tx_length = size;

/ ?4 r; R1 L3 d, i0 A) o, F9 P$ ]/ J

        /*使能发送中断,向发送寄存器写入一个字节,进入连续发送模式*/


2 F2 G; O) |( t

        ENABLE_TX_INT = 1;

9 I! c' W; J; `7 U8 T

        UART_TX_REG = tx_buffer[tx_index++];


; a/ {( r% o* T% ?; B

        }


3 Y; T6 c" [% w* W/ N

        void uart_tx_isr( void )


  _8 H$ H" H1 O- j0 }

        {

+ G% J( }2 b- w2 d5 p: ^& [: f. F

        if( tx_index《tx_length )


& P" S# Q+ ^7 R; A

        {


" Q# F( h' b" @1 K

        UART_TX_REG = tx_buffer[tx_index++];

- L% E3 d9 }& H; W8 f/ x7 g' X% x

        }


! i* c; r1 N1 g! z& V0 p$ n

        else


" p1 P4 n& P- z. f1 U

        {

+ |$ p0 D; M, D* z

        /*发送完毕,关闭发送中断*/


2 z! j; G# D6 y6 A! h% c7 V  b

        DISABLE_TX_INT = 1;

5 I. D3 ]$ y' f/ S  b+ [: w+ _

        }

+ O" v$ c, Z9 M. |# S7 t

        }

- M% }" X$ h: }- F* m

        void uart_rx_isr( void )


. f( }$ ^$ a! X: ~) K

        {

( G4 ~1 S2 s$ Y9 O: L9 Z) Y

        /*处理接收,待接收到完整的帧就设置帧完成标记*/

$ L- F: f- x  M! `5 u, ]! W

        /*由于应用各有不同,这里就无法描述实现了*/

3 B: b6 N9 I: c% {2 q" p

        }


7 ~$ v  b; K" p7 i! e

还需要考虑的是,对于 UART 硬件层面的出错处置,以 STM32 为例,就可能有下面的错误可能发生:

% z, s3 }% c) k/ \* o) \

溢出错误


  p, E6 a# }  r* k% t4 I4 f  d

噪声检测


& i; B5 K; t- d4 k8 l

帧错误

; Y1 d; _5 @% u" A

奇偶校验错误


8 q( T- b" Z7 _% H

另外不同的单片机其底层硬件实现差异也不较大,比如有的硬件发送缓冲是单字节的缓冲,有的则具有 FIFO,这些在选型编程时都需要综合考虑。

7 c% ^' z$ i) Z9 J5 ]  I

DMA 模式DMA 发送模式而言,大致分这样几步:


$ `& Z/ ^3 z4 n( |- q

初始化 UART 为 DMA 发送模式,开启 DMA 结束中断,并写好 DMA 传输结束中断处理函数


0 a. b) d4 E, Q7 V

准备待发送报文,帧头、帧尾、校验处理

, |- `8 m& _+ |7 I

将待发送报文缓冲区首地址赋值给 DMA 源地址,DMA 目标地址设置为 UART 发送寄存器,设置好发送长度。

! Q. g/ p# H5 _) F9 h

启动 DMA 传输,剩下传输完成就会进入传输结束中断处理函数。


1 A; Y) C: V0 t( Y

DMA 接收模式而言,大致分这样几步:


$ [2 c0 e1 n" k  H

初始化 UART 为 DMA 接收模式,开启 DMA 结束中断,并写好 DMA 传输结束中断处理函数


* K' B+ l& R4 u; T. Y6 g" `2 j

中断处理函数中标记接收到帧,对于使用 RTOS 而言,还可以使用的机制是利用 RTOS 的事件机制、消息机制进行通知有新的帧接收到了。

: z: B4 a/ h" }

对于 DMA 接收模式而言,对于变长帧的处理较为不利,所以如果想使用 DMA 接收,制定协议时尽量考虑将帧长度固定,这样处理会方便些。


& Y2 \* _# }/ L5 G( U

总结一下单片机串口是一个需要好好掌握的内容,这里总结了一些个人经验,尽量将一些个人共性的东西总结出来。至于实际实现而言,由于芯片体系差异较多,具体代码各异。但个人认为处置的思路方法却是基本一致。所以本文除了描述串口本身的细节而言,想表达的一个额外的观点是:

% ]! w$ y6 I  T" r

对于一些技术点尽量学会将其共性的东西剥离总结出来。

  G4 ]! Q. m8 L9 p- w

总结、概括、剥离抽象是一个比较好的学习思路,不用对具体的硬件死记,万变不离其宗。


3 S  }, C  b2 J/ u3 E! t' d

如果本文有喜欢的朋友,后面陆续可以总结一下I2C/SPI等常用接口。

收藏 2 评论0 发布时间:2020-7-10 13:34

举报

0个回答
关于意法半导体
我们是谁
投资者关系
意法半导体可持续发展举措
创新和工艺
招聘信息
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
关注我们
st-img 微信公众号
st-img 手机版