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

51单片机的串口通信协议

[复制链接]
gaosmile 发布时间:2020-10-22 22:57
现实生活中, 我们总是要与人打交道,互通有无。单片机也一样,需要跟各种设备交互。例如汽车的显示仪表需要知道汽车的转速及电动机的运行参数,那么显示仪表就需要从汽车的底层控制器取得数据。而这个数据的获得过程就是一个通信过程。类似的例子还有控制器通常是单片机或者PLC与变频器的通信。通信的双方需要遵守一套既定的规则也称为协议,这就好比我们人之间的对话,需要在双方都遵守一套语言语法规则才有可能达成对话。& z' J* o. m3 L& Z; b/ p

) ?9 Z5 K' t8 M通信协议又分为硬件层协议和软件层协议。硬件层协议主要规范了物理上的连线,传输电平信号及传输的秩序等硬件性质的内容。常用的硬件协议有串口,IIC, SPI, RS485, CAN和 USB。软件层协议则更侧重上层应用的规范,比如modbus协议。
) V: I! m( J/ ?- f5 t, Q
7 \" {# N# }- j: b好了,那这里我们就着重介绍51单片机的串口通信协议,以下简称串口。串口的6个特征如下。7 u5 V  v2 I3 g  a" T2 Q. n7 {; Z

# [4 p! j% g* U8 Z; e4 L$ c7 J(1)、物理上的连线至少3根,分别是Tx数据发送线,Rx数据接收线,GND共用地线。

* k" D# `* p( B3 R3 m. R1 t(2)、0与1的约定。RS232电平,约定﹣5V至﹣25V之间的电压信号为1,﹢5V至﹢25V之间的电压信号为0 。TTL电平,约定5V的电压信号为1,0V电压信号为0 。CMOS电平,约定3.3V的电压信号为1,0V电压信号为0 。其中,CMOS电平一般用于ARM芯片中。
) b$ t; f* s! o3 u1 `. r( q
(3)、发送秩序。低位先发。
. b. X% Q) ?* X7 d
(4)、波特率。收发双方共同约定的一个数据位(0或1)在数据传输线上维持的时间。也可理解为每秒可以传输的位数。常用的波特率有300bit/s, 600bit/s, 2400bit/s, 4800bit/s, 9600bit/s。
3 e2 y3 b- B2 t9 m2 L5 h* Y
(5)、通信的起始信号。发送方在没有发送数据时,应该将Tx置1 。当需发送时,先将Tx置0,并且保持1位的时间。接受方不断地侦测Rx,如果发现Rx常时间变高后,突然被拉低(置为0),则视为发送方将要发送数据,迅速启动自己的定时器,从而保证了收发双方定时器同步定时。
. Z3 X& E$ u* A& U' S/ R3 o

. _, I; x+ G/ h4 g& G9 L
(6)、停止信号。发送方发送完最后一个有效位时,必须再将Tx保持1位的时间,即为停止位。
微信图片_20201022225650.png
# i7 s! s( M8 t$ m0 J) x7 x
好了,理论暂时到这里,现在我们要做一个实验,将一个字节从51单片机发送到电脑串口调试助手上。这个实验的目的是为了掌握串口通信协议的收发过程。
0 Z0 Q5 B% |: o% y$ l' O1 ^- w, X
虚拟串口
" N* ]) j0 w+ U/ F, R
实验一、虚拟串口实验
一般单片机都有专门的串口引脚,51里面分别是P3.0和P3.1,这些引脚拥有串口的硬件电路,因此使用它们并不需要设置信号的发送停止。为了掌握协议,我们使用其他的引脚来模拟串口,所以也叫虚拟串口。这里我们选用P1.0,然而注意到我们51单片机要发送数据给电脑,必须经过一个串口转USB设备(即TTL电平转换为RS232电平),而限于我们的开发板只有P3.0与P3.1连接到了串口转USB设备,所以我们可以将P1.0短接到P3.1 。下图是这个串口转USB的原理图。
2 ]7 j; _2 @: N* f4 Q
微信图片_20201022225653.jpg
- j  h/ [  t  \( G
好了直接上代码吧。

  1.   J' q+ D- R& k
  2. [cpp] view plain copy
    5 g8 ~6 K( z& a# J$ v
  3. #include "reg51.h"
    1 f# I& j- F0 Z5 s5 n  z' J
  4. /*
    . l9 p1 u$ B/ h7 I2 O0 l
  5.   将P1.0虚拟成串口发送脚TX
    7 a$ Z+ I1 L3 x' _
  6.   以9600bit/s的比特率向外发送数据
    1 [- q( q" o  K+ e0 h: V
  7.   因为波特率是    9600bit/s ( i: `- }6 ?6 e6 B4 i3 {
  8. 所以me发送一位的时间是 t=1000000us/9600=104us 2 `6 t1 ~6 y0 y' P! J
  9. */% v  v- `! o/ e4 {6 X
  10. sbit TX=P3^1; //P1^0 output TTL signal, need to transferred to rs232 signal, can be connected to P3^1  
    5 Y( N2 U, t8 M8 ^% n% G* {! k
  11. #define u16 unsigned int //宏定义  : d8 z/ u9 b( p
  12. #define u8 unsigned char  7 X0 q* u2 d, V* w8 L, i
  13. u8 sbuf;  2 o) \- M# T! }6 j$ e! p0 C
  14. bit ti=0;  
    1 h4 P" i% {) X" L
  15. void delay(u16 x)$ ]4 u+ o, E1 Y8 V% s
  16. {  
    # U3 h7 d; E- ^. m. A; |" }! O6 f
  17. while(x--);  # o6 c; S7 s5 }6 d4 N/ A: x$ g
  18. }  + c' n. Y  R7 _0 A6 Z& Y9 m% }
  19. void Timer0_Init()) X3 E1 [6 V, c
  20. {  , R9 j" L% `+ [1 \3 A
  21.     TMOD |= 0x01;  " m+ m3 `  @$ q! [1 R5 f
  22.     TH0=65440/256;  % [4 g  D7 c7 ~# l
  23.     TH0=65440%256;
    " s- W1 P# G* L6 r# _
  24.     TR0=0;  2 P, G* a  A$ Y4 p) L" Z
  25. }  3 Q+ |: X1 @) j, p7 I2 V
  26. void Isr_Init()3 v( Q8 v' W( g
  27. {  , C$ |! D6 i/ d/ ~3 C" z8 @
  28.     EA=1;  
    ! [: h/ X: ]! r5 B
  29.     ET0=1;    ~# L/ k& x9 j, \0 N; o
  30. }  % l- }5 d& x2 i4 D' r6 I0 R
  31. void Send_Byte(u8 dat)
    + n5 u1 v* z" O2 H% n7 _: g9 P' W
  32. {  4 I( }. m# u1 p" m+ g; K
  33. sbuf=dat;//通过引入全局变量sbuf,可以保存形参dat  
    % Q6 k/ _' {, o( z
  34. TX=0; //A 起始位
    / D. t$ ?+ n' E
  35. TR0=1;  
    9 _/ X0 @1 I6 ~+ z3 |2 R2 v: J8 h
  36. while(ti==0);    //等待发送完成
    ' U2 r8 _! a+ ?$ A
  37. ti=0; //清除发送完成标志  & g6 j+ K0 f& V2 d. U8 [: ^
  38. }  
    3 W' a+ v% z1 @9 K
  39. void TF0_isr() interrupt 1     //每104us进入一次中断  
    & G1 I% ~- T' A) L1 p+ _5 ~, M
  40. {  
    * Z- e) }  p- v! q
  41. static u8 i; //记录进入中断的次数  6 x" q/ I! l% C) f1 _; i5 x
  42.     TH0=65440/256;  / m1 Q4 q$ D5 H# K! i  U$ n$ Q
  43.     TL0=65440%256;  ! m' q. H# r( D1 k7 u
  44.     i++;  9 w$ Z' j$ Y- i# d9 Y2 T4 ~5 `1 Y
  45. if(i>=1 && i<=8)  
    2 P; b4 e- R5 L; K6 Q& P
  46.     {          if((sbuf&(1<<(i-1)))==0)  // (sbuf&(1<<(i-1)))表示取出i-1位  6 O) f8 H8 }# P7 E4 }9 j
  47. / J* E+ T' J; B0 s  C: f
  48.         {  # g4 }$ {  Y( G" {# X9 j$ f
  49.             TX=0;  7 |& `4 }2 t3 q( i/ i
  50.         }  
    - Z: E; ]1 X7 }) G% U, h
  51. else9 r: I. B) }, L( i& w6 `% m
  52.         {  
    8 z" N& e! w2 b8 G
  53.             TX=1;  6 F6 b1 \# i& U6 Q* h
  54.         }  : q1 T* t8 g* Y/ A2 g$ x
  55.     }  2 B: |2 ]% A+ i  Y/ ?3 m8 W+ p: L
  56. if(i==9)  //停止位
    5 W7 i. n* G/ v& O  O2 ~! V
  57.     {          TX=1;  - F& n4 s; c1 ^& D8 S
  58.     }  
    5 S) S' a! \( Q' a
  59. if(i==10)     
    4 M. X  t8 e  j& i' _% T) L( V" a% I
  60.     {  ; A, y* V, k7 g) p4 s) R( _& a
  61.         TR0=0;  
    0 O% L& m- Y* g9 E, v$ X1 p
  62.         i=0;  
    % }: l2 ^8 ?9 \0 ~( {% ]/ g# F
  63.         ti=1; //发送完成  , E5 Q7 b% D$ P
  64.     }  , H6 ?( g4 y( n( b  x5 U
  65. }  . f5 X! m, Q$ Q1 ], y/ d1 ?
  66. void main()
    " ]4 p3 M+ d. w4 E, i( I4 J
  67. {  3 ]# `' d$ O3 Y! @
  68.     TX=1; //使TX处于空闲状态  ! _0 e' q6 n5 b. x% V, ~7 k; d
  69.     Timer0_Init();  ' e: l" f' b  y; u& p
  70.     Isr_Init();  + P5 p9 f3 s, ^. ^; s: b
  71. while(1)  7 t( U, c! J" T* r  G$ w2 d
  72.     {  
    , |; |4 v8 m" S, f( C
  73.         Send_Byte(65); //0x41  % ~& l( J0 ?1 x% L: o# \. ^' I7 H
  74.         delay(60000);  
    4 v& K0 G' w8 P
  75.     }  3 C0 H" e4 a7 T5 M
  76. }
复制代码
  X, _3 R6 V/ ^* U% ]
实验引入了定时器0来控制发送线上的各个位的保持时间。首先main函数进入,TX置1则使发送线处于空闲,这时候发送方和接受方都处于空闲。接下来初始化定时器0,TR0置0表示还不要启动定时器0。接着中断系统初始化,此时中断系统已经开启。进入while循环,先进Send_Byte()函数,将65传给形参dat,dat再将65赋值给sbuf,到这里准备工作就做好了。接着TX置0,这个是起始位,要保持这个起始位104us。于是就启动定时器TR0置1,计时器开始计数。当第一次溢出的时候,也就是过了104us,进入中断,同时接收方也侦测到了这个突然被拉低的信号,于是迅速启动自己的定时器。进入中断子函数后,先是重装定时器初值,然后i加1,也就是当i=1时,就应该发送数据的最低位了,总共有8位数据,所以使用条件语句if(i>=1 && i<=8)来判断是否发送完数据位。然后再通过if(i==9) 来发送停止位,最后当i=10时,也就是发送完了,这时候要关闭定时器(那么程序也就),同时i置0,ti置1(才能跳出while(ti==0)循环),最后将ti置0,保证下次要发送字节时让程序停留在while(ti==0)。
. ~6 `, {: I; \& [' [  s1 o
片上串口
0 {* ?4 R% {4 g$ p
以上说的是虚拟串口,上文中谈到与串口相关的引脚P3.0与P3.1,事实上51单片机自带片上串口,那这个串口又该怎么使用呢?

1 s# \' O: E$ }8 v
片上串口支持同步模式与异步模式。简单来说同步模式就是指有时钟线,而异步模式无时钟线。这里的时钟线是指在同步通信时,用一根线专门传输时钟信号,这个信号用来与要发送的每一位保持同步,这样就避免了例如异步通信中因为采用定时器而引入的时间误差。

  [/ H9 n* x4 |2 r) ~1 O1 r$ l- Z
片上串口还支持8位模式和9位模式。如下图所示
; [8 O$ M$ |3 X2 U( Q, T
微信图片_20201022225657.jpg
; l5 ^/ y3 B5 F% X. ?! v! o( o: w& l
其中D0-D7是一个字节的8个位。9位模式只是多了一个位TB8,这个TB8的作用是奇偶校验或多机通信。奇偶校验原理这不加分析。多机通信时比如主机只发送数据给网络中的一台地址为0x02的设备,这时候先让TB8为1,前面的D0-D7则为地址即0x02,之后再让TB8为0,前面的D0-D7则为数据了。

' M! z; k  n2 U# K
上面设置了片上串口的模式,另外还要设置串口的波特率。

9 }2 }' ?' U/ V4 P: O
片上串口的波特率等于定时器1工作在方式2时溢出率的32分频。如果要定时器1工作在方式2,那么TMOD=0x20。另外要保证为32分频,我们还必须设置计数器初值。设晶振为11.0592Mhz,则定时器的计数脉冲为F=f/12,则定时器每计一个脉冲的时间为T=12/f。又令计数器的起点为x,则溢出一次要计的脉冲数为(256-x)。所以在计数起点为x时,溢出一次的时间为t=12/f*(256-x)。则对应的溢出率为1/t=f/(12*(256-x))。对应的波特率就为b=f/(384*(256-x))。
x=256-f/(384*b)
  {+ [9 b& p. [+ D
其中f为晶振频率,b为希望的波特率,x为定时器的计数起点TH1的值。
8 D8 N$ w5 m$ D, H  |9 O2 k
例如当晶振为11.0592M,希望波特率为9600bit/s,则TH1=253。题外话,我们同样可以演算出在其他常用波特率情况下,TH1始终为一个整数。这里也就解释了为什么51里面选用了11.0592M的晶振而不是12M,这样就保证了串口的时序更加准确,虽然牺牲了定时器的准确度。

$ u9 u& u2 p1 R' P
实验二,片外串口发送一个字节。
) c. F2 X7 g$ Y# Q3 m# Y
好了现在开始我们的实验之旅。直接看代码吧。
  1. 3 e- I3 f# |# K( y
  2. [cpp] view plain copy8 l; G( A" i. m! g  S

  3. # ^& D# U0 [7 F; Y! B1 _
  4. # include "reg51.h"! H; M) U/ a! m. z! f

  5. ' B1 y* c! W0 E& X' C
  6. #( G: y5 q9 z$ w3 c: y/ @
  7. define u16 unsigned int
    - w3 H: j4 Z1 L% G* \( y( [5 \% p

  8. / D% S9 s. C# h) M
  9. # define u8 unsigned char- T3 U. S6 V2 x+ _* W6 |. ~4 {) w: I
  10. # ]9 D/ _: Y' v+ c
  11. void delay(u16 x)
    4 W. g4 y, X7 h6 t; h3 _
  12. {4 X/ G3 j4 _, W1 B3 F8 P

  13. ' j, }3 W( ^2 L! g/ q4 c( [
  14. while (x--);
    . p* g$ A% @: a4 o, y: p9 F
  15. ( K7 s1 D* I: S
  16. }3 L5 [7 P5 v% ^

  17. 7 g4 Z5 ^6 m6 C) I  s
  18. void Uart_Init() //串口初始化
    # {; C; ^! [7 n7 {
  19. {( M6 [+ h" p! N7 |

  20. ' g+ Q* z$ k) A8 b3 l/ D) {
  21.     SCON = 0x50; //8位异步模式 3 }1 e! d# z2 @3 C- M) p$ P+ Q+ K
  22. ' N" F  ^% u& q- [
  23.     TMOD |= 0x20; //定时器1工作方式2 & w: P# q" K6 W2 y! H! G
  24. , T8 K. q! b  U4 o  w' D
  25.     TH1 = 253; //9600bit/s
    / B/ P' F# o! v% X5 M1 |
  26. / l- J5 \) x7 _8 b0 @" e7 E) u
  27.     TR1 = 1;( y6 T% F7 b/ a3 O% E. W  f& p

  28. 1 _; m9 h; i( K
  29. }$ t; m2 E& v# r' W/ k

  30. ! J( A0 c; Z* p. Z8 Q8 H$ w$ b
  31. void Send_Byte(u8 dat)
    - [. Y4 W" d2 q
  32. {
    8 y  `) J# |% I+ d: {. r( U  ^

  33. , v9 H, p# H% Z9 R1 y8 {1 P
  34.     SBUF = dat; //启动发送,只需要把发送内容给SBUF这个寄存器 2 ]  N- N& `% p/ k  V4 @" `, q4 ^, @) T

  35.   |$ A+ _, `$ `7 K" x
  36. while (TI == 0); //等待发送完成,因为TI为1时表示在发送停止位 % p- i+ Z7 K( t/ Q4 `

  37. - S! [1 j2 k, v% L( o
  38.     TI = 0;
    0 _. {* g1 C% x
  39. " `! Z2 y8 w  m& H# F3 }
  40. }8 k0 G/ w" L5 [1 J- q: m" O$ F7 m4 A

  41. + D* j# f& B: H5 l% W& L4 n
  42. void main()$ B- l/ x* p: A3 K
  43. {
    0 V, ~* T" [$ e" e% g# U

  44. ( ~3 n/ B2 ]7 |3 s8 Q1 n
  45.     Uart_Init();
    6 g2 V) b$ \. ]

  46. ! G. _' g) z, y1 l. W+ l) [$ [5 o
  47. while (1)6 D2 U* @: L. T0 [* |
  48. - E& Z% x( X- y0 J; v
  49.     {
    $ U' G9 L& {" @( y
  50. 0 H1 v7 f! V! m: \- ~
  51.         Send_Byte('m');
    - b/ K: ^  {9 @

  52. . g1 R6 I+ h& A- g/ }+ u
  53.         delay(60000);
    3 D* K  }7 U8 z% d

  54. 7 Y! |3 z) ~$ j! T! X7 F
  55.     }
    ! L7 }  P% Y! j6 M3 a5 J

  56. 2 A/ |3 `/ ^0 J9 \/ c9 @; w5 G% e
  57. }
复制代码

. u9 K, ]' N) {9 T! s" t
实验二较之实验一,代码减少了很多,而且不用考虑繁琐的位发送时序。只需要明白各个寄存器SCON,TMOD,TCON,SBUF的用法。TI是SCON中的第一位,为发送中断请求标志位。在本方式中,在停止位开始发送时由内部硬件置位,响应中断后TI必须又软件清零。

; ~8 |& b4 L5 {
实验三、片上串口发送一个字符串
上面介绍了如何发送一个字节,那如何发送一个字符串甚至文本呢?这里我们首先介绍下字符串的概念。

% t) P: E+ S, J7 _$ j# l
字符串:从存储器的某个地址开始,连续存放多个字符的ASCII码,并且在最后一个字符的后面存放一个0,这段连续的内存空间就叫字符串,最后的0叫字符串的结束符。注意这里的0和加单引号的0不是一个概念,加单引号的0是指0的ASCII码。

, L4 o  l( L# ~& ^+ o/ J+ n8 k
数组与字符串的关系:字符串是数组的一种特殊情况,数组在特定条件下可当做字符串用。C语言用双引号描述一个字符串,如“abcd”。
. R3 {7 U0 J. c2 c$ F% S& K7 Q# H( ~
下面我们通过一个实验来展示如何发送字符串。我们实验的目标是打印字符串“Hello World ! 第一!”到打印机。直接上代码。

  1. * A# F* i* y, i
  2. [cpp] view plain copy6 U' ]' f8 u2 z7 O6 r. ^/ A  [
  3. #include "reg51.h", v6 V- P# P( U( [

  4. 6 U" R1 [# k, G, q) E$ T/ h/ y. {
  5. #define u16 unsigned int  . [/ h( d5 R% P
  6. #define u8 unsigned char  0 E/ r& J) X. ]# T
  7. void delay(u16 x)
      ^# M8 M4 R  ~
  8. {  % f6 ]0 n  I9 D  L% D! I
  9. while(x--);  0 {: q- e* W; _+ r* |
  10. }  ; w9 x. e. T$ W
  11. void Uart_Init() //串口初始化  
    9 w  d# J2 P8 e4 Z: h
  12. {  + U' I6 X8 _! P, \8 W+ d
  13.     SCON=0x50; //8位异步模式  
    ! ^0 i+ L1 l# \
  14.     TMOD|=0x20; //定时器1工作方式2  
    2 H! u6 p7 D8 D6 L) }
  15.     TH1=253;//9600bit/s  
    # G6 `+ X- @1 N8 K1 h
  16.     TR1=1;  
    5 R4 {" F7 m% i! J7 V8 B
  17. }  9 y/ g5 _0 `7 B7 _+ k: g
  18. void Send_Byte(u8 dat)    //串口发送一个字节  # _7 a, r3 k! F+ W; [% I) |
  19. {  7 N. C0 ^* W5 o1 P$ ~
  20.     SBUF=dat; //启动发送,只需要把发送内容给SBUF这个寄存器  . i) N1 ^# u' r1 t
  21. while(TI==0); //等待发送完成,因为TI为1时表示在发送停止位  ! g, D$ [0 F: I( B' B
  22.     TI=0;  2 p' d0 F1 b3 q
  23. }
    + j* w" ]) p9 `3 D, J
  24. void Send_String(u8 *str)   //发送一个字符串  *str为字符串第一个字符的地址  
    9 j( X* G* _1 V2 X; Q
  25. {  
    , [9 W8 ?1 n. _  s
  26.     abc:      //标号  
    ! y! O3 b& ]/ a  G7 F
  27. if(*str != 0)      {  : P7 a0 n: i" J0 t4 x) [

  28. 1 K9 {8 A3 x/ j1 ^: t
  29.         Send_Byte(*str);  + d! u0 j; p1 Q2 V4 S1 g; v7 `& F
  30. & c$ \. e) V+ V6 J# ~: T  V
  31.         str++;  * u; X0 ]6 x( r1 D4 }0 h+ u, w
  32. goto abc;     7 p/ s+ `2 L. ?/ p# E# d
  33.     }  
    - g* m; L4 s. D- K. r7 P8 e
  34. }   
    ) F( l2 {( W) N
  35.   j7 p5 e- F; f
  36. void main()
    ) B2 W# Q0 O/ }" s: B
  37. {  7 S- _+ \' P+ O8 q0 G  w9 e
  38.     Uart_Init();  & M0 d. w) Y" `& p* L) ?
  39. while(1)  0 \2 k  \* _( e. J2 s3 Y
  40.     {  4 D4 ~& j3 g/ a& s) r# V$ L$ L. {2 q
  41. / t& v& @0 x$ M: N& e
  42.        Send_String("Hello World! 第一!");  
    & }( }& }; m8 R) P' s$ v! w2 P

  43. ; @2 ]! U9 b# x3 O4 s* x" x4 m

  44. , P# d$ q  L+ t; N2 v/ i
  45.        Send_Byte(10);  
    2 ?8 D# ^/ j  j  B* C
  46.        delay(60000);  
      Q7 f7 I( L' v; m
  47.        delay(60000);  8 k% P9 b/ ?6 i/ Z6 d
  48.     }  , k. ?$ X9 p4 Y
  49. }
复制代码

/ T5 [. ?# b/ |+ t# X3 K
4 z1 L! v$ D2 ~
实验效果
微信图片_20201022225700.jpg
( ]  C' q$ g; |0 E% H
赞 收藏 1 评论0 发布时间:2020-10-22 22:57

举报

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