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

介绍51单片机的串口通信协议

[复制链接]
gaosmile 发布时间:2020-10-16 20:52
现实生活中, 我们总是要与人打交道,互通有无。单片机也一样,需要跟各种设备交互。例如汽车的显示仪表需要知道汽车的转速及电动机的运行参数,那么显示仪表就需要从汽车的底层控制器取得数据。而这个数据的获得过程就是一个通信过程。类似的例子还有控制器通常是单片机或者PLC与变频器的通信。通信的双方需要遵守一套既定的规则也称为协议,这就好比我们人之间的对话,需要在双方都遵守一套语言语法规则才有可能达成对话。
0 @( A# k' m3 j% G
% T5 O. X2 `3 i1 E3 s7 ?* _通信协议又分为硬件层协议和软件层协议。硬件层协议主要规范了物理上的连线,传输电平信号及传输的秩序等硬件性质的内容。常用的硬件协议有串口,IIC, SPI, RS485, CAN和 USB。软件层协议则更侧重上层应用的规范,比如modbus协议。% U$ c4 E) x5 {: W

$ P3 r9 W7 R0 T# \好了,那这里我们就着重介绍51单片机的串口通信协议,以下简称串口。串口的6个特征如下。/ C  c/ W1 l$ }$ }4 [

! Q( S/ A' T8 P# G  U(1)、物理上的连线至少3根,分别是Tx数据发送线,Rx数据接收线,GND共用地线。

: }# O1 U; Y& Y$ Y4 v(2)、0与1的约定。RS232电平,约定﹣5V至﹣25V之间的电压信号为1,﹢5V至﹢25V之间的电压信号为0 。TTL电平,约定5V的电压信号为1,0V电压信号为0 。CMOS电平,约定3.3V的电压信号为1,0V电压信号为0 。其中,CMOS电平一般用于ARM芯片中。

1 V; |9 {. B3 ?6 n; l
(3)、发送秩序。低位先发。

+ e" _1 L5 r/ q: Q: f4 b9 t(4)、波特率。收发双方共同约定的一个数据位(0或1)在数据传输线上维持的时间。也可理解为每秒可以传输的位数。常用的波特率有300bit/s, 600bit/s, 2400bit/s, 4800bit/s, 9600bit/s。
0 Q( E3 L/ T8 i) s8 H; n6 K
(5)、通信的起始信号。发送方在没有发送数据时,应该将Tx置1 。当需发送时,先将Tx置0,并且保持1位的时间。接受方不断地侦测Rx,如果发现Rx常时间变高后,突然被拉低(置为0),则视为发送方将要发送数据,迅速启动自己的定时器,从而保证了收发双方定时器同步定时。
% s9 R2 b, v! J$ _$ u" n2 l
3 _& J1 P" e+ {& }; m. _& T
(6)、停止信号。发送方发送完最后一个有效位时,必须再将Tx保持1位的时间,即为停止位。
微信图片_20201016205128.png
! P! p2 [7 d/ v) ?5 c* K
好了,理论暂时到这里,现在我们要做一个实验,将一个字节从51单片机发送到电脑串口调试助手上。这个实验的目的是为了掌握串口通信协议的收发过程。
$ s2 w9 q9 K) W2 X: |2 G% [
虚拟串口
实验一、虚拟串口实验
一般单片机都有专门的串口引脚,51里面分别是P3.0和P3.1,这些引脚拥有串口的硬件电路,因此使用它们并不需要设置信号的发送停止。为了掌握协议,我们使用其他的引脚来模拟串口,所以也叫虚拟串口。这里我们选用P1.0,然而注意到我们51单片机要发送数据给电脑,必须经过一个串口转USB设备(即TTL电平转换为RS232电平),而限于我们的开发板只有P3.0与P3.1连接到了串口转USB设备,所以我们可以将P1.0短接到P3.1 。下图是这个串口转USB的原理图。
* y9 x# [; w+ t( W
微信图片_20201016205132.jpg

1 W( k1 K9 Q7 X, W& @6 b
好了直接上代码吧。
  1. ( N" d! k5 N/ g) {- W( X
  2. [cpp] view plain copy9 _1 [3 E; K! Y2 {! w
  3. #include "reg51.h"  
    , m' {7 J# k7 ?( v/ C# N" S
  4. /*
    : H% @  t# M6 Z2 U6 x
  5.   将P1.0虚拟成串口发送脚TX
    - R2 }: p+ [  u& y
  6.   以9600bit/s的比特率向外发送数据
    3 z  C: @4 h% [" L; r, H: M
  7.   因为波特率是    9600bit/s + q) r/ y& S- e2 z, x! Q/ B
  8. 所以me发送一位的时间是 t=1000000us/9600=104us
    % N. n) d( }9 J, f8 e( z8 y: Z: `+ q! ~
  9. */         
    3 j3 M" {% V  F# [' t. d: F1 f2 S. P
  10. sbit TX=P3^1; //P1^0 output TTL signal, need to transferred to rs232 signal, can be connected to P3^1  2 y& L( a3 I0 B6 y" |: f
  11. #define u16 unsigned int //宏定义    O1 F* f, S( Q& V4 O  Y/ m5 N# P5 {
  12. #define u8 unsigned char  
    - ?' I* a; X* B+ V7 N& Z
  13. u8 sbuf;  6 J# w/ {) y! X* E$ _2 W
  14. bit ti=0;  + G2 I& ?* Z9 }
  15. void delay(u16 x)  
    4 Q0 G) M) F( c/ m" P+ e- K' j
  16. {  
    $ b& f7 N% m. B) I8 f
  17.     while(x--);  8 A) b5 K( X( S2 c2 d
  18. }  
    # y/ ]1 X- h' Z4 y5 j( V! J, T% \
  19. void Timer0_Init()  * H9 Z& E7 D) y
  20. {  " Q$ b1 Z7 `% f( g2 ^3 }( X$ A5 K  |
  21.     TMOD |= 0x01;  
    $ ]% G; R2 I4 e, k  ^( n
  22.     TH0=65440/256;  
    - |  p( }1 h( U5 r3 j- b# m
  23.     TH0=65440%256; : u" d' i1 F0 T! H) z' B+ |, e
  24.     TR0=0;  3 f2 d+ ~7 z% n* f' {; h; L
  25. }  & c2 ]/ k  k2 a: ~( z
  26. void Isr_Init()  3 I6 T) [0 D" Z% E, Z
  27. {  + ^# V% }% \5 v8 s) t7 ?
  28.     EA=1;  % k5 _, P# g: d2 H6 C4 d2 \
  29.     ET0=1;  : I3 g2 J' X  N6 j0 M+ W9 Z
  30. }  & A$ n1 c0 p- _0 E6 F* t" B
  31. void Send_Byte(u8 dat)  $ t+ w, n- j: C" M
  32. {  
    0 C  F$ P# O$ W
  33. sbuf=dat;//通过引入全局变量sbuf,可以保存形参dat  
    3 E  s1 j+ ~! q" _4 T
  34. TX=0; //A 起始位
    ) F( l) ~2 E9 J. v
  35. TR0=1;  9 D: r6 B9 v0 `# F( s" ]; W$ Q
  36. while(ti==0);    //等待发送完成 6 m0 @* j4 h% s; O6 Z. \
  37. ti=0; //清除发送完成标志  
    4 v+ x9 _' ~: Q5 L2 f
  38. }  
    ! z- _* G* ^( X. M8 \/ S5 K1 k
  39. void TF0_isr() interrupt 1     //每104us进入一次中断  + P& g* Z, S, S/ }) Q4 Q) ^
  40. {  & N+ W3 J3 `! x" {4 X4 x
  41.     static u8 i; //记录进入中断的次数  
    0 h3 }' v0 W8 Y7 w
  42.     TH0=65440/256;  
    9 F! o; @- N/ ^  r
  43.     TL0=65440%256;  . a+ _, e. R3 x1 H' [! V# A
  44.     i++;  
    6 s2 B  K* D$ q" M
  45.     if(i>=1 && i<=8)  
    8 t$ o: g1 J  H# R- L8 p
  46.     {          if((sbuf&(1<<(i-1)))==0)  // (sbuf&(1<<(i-1)))表示取出i-1位  
    6 S* U$ Y) _4 p( @
  47. " f8 \" D( V  @. W7 I/ S8 [
  48.         {  ' w6 x3 N5 y5 ^9 j5 `6 n
  49.             TX=0;  8 ~7 ]' O' s; }8 I1 b0 ~/ h
  50.         }  # R4 I2 K/ |  f8 P0 ^5 b
  51.         else  7 O1 R7 h, e; _" _9 m
  52.         {  9 U. d9 U% L0 j" b) H  p2 S
  53.             TX=1;  6 A, C8 p9 j8 b1 V( ]1 ?
  54.         }  
    " @1 e2 E" F" @: n! J9 z- E1 ]$ \. J3 A
  55.     }  
    ! ^3 S6 Z% \/ A" K  t  g" ~
  56.     if(i==9)  //停止位
    $ A2 u9 I7 f9 J8 A, _2 I$ ]
  57.     {          TX=1;  
    9 c' U3 w2 X  ]+ k7 M  b
  58.     }  
    . @! \# v5 Q3 n$ X5 ^& V
  59.     if(i==10)     
    - @- m/ D% q/ g
  60.     {  
    4 r7 x- G+ a. \6 f3 v) n9 n
  61.         TR0=0;  
    0 [+ W: g! Q. S6 K; @% {3 H  B
  62.         i=0;  4 R! n) ~; J$ b; E4 K; \: m
  63.         ti=1; //发送完成  * G+ N4 Q8 A& Q+ }" k
  64.     }  1 D& C5 J+ b3 ?- A
  65. }  
    0 I' |, X+ `+ q& p3 W
  66. void main()  
    5 i7 w; v& b+ p* [7 x/ F. M
  67. {  : P4 g( `4 c$ E3 x
  68.     TX=1; //使TX处于空闲状态  
    ' }  s" y, h! F+ D4 N/ e3 [
  69.     Timer0_Init();  ) o. D: v) I; j% D
  70.     Isr_Init();  
      e) i1 z! D. p  L6 C6 j
  71.     while(1)  0 u. D0 N$ V6 g/ k! W$ L# O
  72.     {  3 W& C  [( a; U3 u. e8 G
  73.         Send_Byte(65); //0x41  
      z- S, V# Y4 Q; G8 c7 |
  74.         delay(60000);  
    : F( @* _* i, ^. F4 p/ ^
  75.     }  3 e( _! U) d- L
  76. }
复制代码
! J: W0 U( }5 p+ [
实验引入了定时器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)。

0 v6 X% J: r/ k" r1 M& ?, P( C( a
片上串口
以上说的是虚拟串口,上文中谈到与串口相关的引脚P3.0与P3.1,事实上51单片机自带片上串口,那这个串口又该怎么使用呢?
片上串口支持同步模式与异步模式。简单来说同步模式就是指有时钟线,而异步模式无时钟线。这里的时钟线是指在同步通信时,用一根线专门传输时钟信号,这个信号用来与要发送的每一位保持同步,这样就避免了例如异步通信中因为采用定时器而引入的时间误差。

9 n. {  \% E# X, L" h9 V) Q
片上串口还支持8位模式和9位模式。如下图所示
& g: |/ z$ ?# J$ p7 J2 L; T9 j
微信图片_20201016205135.jpg

7 o! _/ v. u, o. S1 P  D
其中D0-D7是一个字节的8个位。9位模式只是多了一个位TB8,这个TB8的作用是奇偶校验或多机通信。奇偶校验原理这不加分析。多机通信时比如主机只发送数据给网络中的一台地址为0x02的设备,这时候先让TB8为1,前面的D0-D7则为地址即0x02,之后再让TB8为0,前面的D0-D7则为数据了。
: [  y+ \! k  I: F
上面设置了片上串口的模式,另外还要设置串口的波特率。

6 u* y4 c$ L; G
片上串口的波特率等于定时器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)

6 \) O4 \- h( a8 ~" z
其中f为晶振频率,b为希望的波特率,x为定时器的计数起点TH1的值。

, y" s# m( N: A, A. t
例如当晶振为11.0592M,希望波特率为9600bit/s,则TH1=253。题外话,我们同样可以演算出在其他常用波特率情况下,TH1始终为一个整数。这里也就解释了为什么51里面选用了11.0592M的晶振而不是12M,这样就保证了串口的时序更加准确,虽然牺牲了定时器的准确度。
& s" R: @1 o# o# {
实验二,片外串口发送一个字节。6 \9 r" C! k, ?/ f
好了现在开始我们的实验之旅。直接看代码吧。
  1. . p! J% C* l) p1 }( v
  2. [cpp] view plain copy
    / }- S4 [8 A9 t8 C

  3. 1 M! G: m3 J0 q* P
  4. # include "reg51.h". `5 {; h1 j+ i9 w+ Q: M2 i

  5. 5 }# a; X$ s" ~! j$ h$ y
  6. #$ A; x( V+ k2 b/ _2 P
  7. define u16 unsigned int1 @8 v4 E5 G5 ?# M  d' }  W

  8. ; E3 `+ ?5 N5 q* I
  9. # define u8 unsigned char
    2 Z0 q9 a8 O8 U& H" a" \! g

  10. 7 o# z  r& L1 G
  11. - p, E+ h) K* k4 i6 g
  12. 3 F$ b) I, V# g3 D
  13. void delay(u16 x)5 B' H6 c6 A# v* ?+ ~
  14. {6 A) B* |0 Q) f$ g+ K

  15. ! w) X( @% t& e; z
  16.     while (x--);
    9 B4 _. f) @4 y+ a! O. ?
  17. + m3 C6 s; J+ x* Q* }# @. P
  18. }
      C2 `  {, h5 |' e& b+ U3 E5 H
  19. / u4 ]5 B  g/ s

  20. % b1 O, P+ b' ~& S

  21. ; a9 a; o7 N* a+ y& |2 X; S
  22. void Uart_Init() //串口初始化
    % q( r9 Z/ r& H, u
  23. {. j4 S- x. \7 o+ y1 V

  24. # T$ J* \9 v+ c" Q! o; m5 J3 v1 T/ _
  25.     SCON = 0x50; //8位异步模式
    ! r$ B9 y+ g8 {. x8 F" x

  26. , E& \# E& M7 S
  27.     TMOD |= 0x20; //定时器1工作方式2 * |; I$ T+ V& h0 i; B

  28. + L- b4 P6 W+ @9 @9 L7 e
  29.     TH1 = 253; //9600bit/s 0 n/ J0 f' u9 b) e- u! `

  30. $ n$ s8 R7 j' M; O( d
  31.     TR1 = 1;
    3 O2 y& m2 M4 ]; j! {; q/ S

  32. : w$ ?' ]4 ^  Y2 [* b* E- D
  33. }9 ?6 h6 S/ K( s; p' e, ^9 U
  34. 4 [, T& _" F/ d) R2 j5 z+ f, ^
  35. / m) m2 U; }  O7 u+ l. I! s. n
  36. - b6 \) g8 j( F: A! A
  37. void Send_Byte(u8 dat)
    1 K# `) x& L4 X  v/ o
  38. {
    : [! s; K' M( o" i" u
  39. ) Z7 m: ]1 g4 y9 F; L
  40.     SBUF = dat; //启动发送,只需要把发送内容给SBUF这个寄存器 % ]% P" l; g' M

  41. + X+ C4 K6 {; b# `+ Y
  42.     while (TI == 0); //等待发送完成,因为TI为1时表示在发送停止位   i' m2 c2 O7 C- v# R

  43. 6 u) n) K/ K+ }6 p1 i
  44.     TI = 0;8 I* _6 g3 a7 Q8 V! n2 ~

  45. 1 \/ z1 B: V- r  U$ F+ Q
  46. }9 n7 |& x+ Y# x# f$ N

  47. " w# V" q0 C- l. l5 Q& d
  48. 2 w2 X8 S% B$ w* O

  49. 8 y9 J+ L( V# m  `  g5 B2 b
  50. void main()
    2 L" }7 ~+ H3 y4 l
  51. {
    8 ?" c4 A+ }& X+ L* q

  52. 0 i) ?& }8 j9 x
  53.     Uart_Init();
    . F' c! f0 v7 [/ r
  54. 2 ]( ]) @5 |* w. A( H
  55.     while (1)/ P( n# i2 W+ c' b' N9 _
  56. 8 j* p; n8 l+ z+ M& E/ f
  57.     {
    # p5 f; U8 [9 H6 h; v- v
  58. " `& k6 R! }3 O* |1 D0 @
  59.         Send_Byte('m');" f$ G0 L7 l) f: m1 p/ z

  60. # N2 V" U6 ?1 B  T
  61.         delay(60000);2 o) w0 k5 i. C* y3 q, F1 {

  62. . n" a$ y; p5 o6 t+ P, c5 x
  63.     }
    $ M' s/ L4 b/ B

  64. ; e2 Q6 t6 p' s4 \' T' p) `- _( B
  65. }
复制代码
" c6 ?  v' }$ Y: y6 ^) j; {
实验二较之实验一,代码减少了很多,而且不用考虑繁琐的位发送时序。只需要明白各个寄存器SCON,TMOD,TCON,SBUF的用法。TI是SCON中的第一位,为发送中断请求标志位。在本方式中,在停止位开始发送时由内部硬件置位,响应中断后TI必须又软件清零。

# a  M: ?9 ^5 K/ A% }; u( O. N
实验三、片上串口发送一个字符串
上面介绍了如何发送一个字节,那如何发送一个字符串甚至文本呢?这里我们首先介绍下字符串的概念。

9 `" }# H, o7 f+ |
字符串:从存储器的某个地址开始,连续存放多个字符的ASCII码,并且在最后一个字符的后面存放一个0,这段连续的内存空间就叫字符串,最后的0叫字符串的结束符。注意这里的0和加单引号的0不是一个概念,加单引号的0是指0的ASCII码。
" E9 H/ s0 w: J3 K* h
数组与字符串的关系:字符串是数组的一种特殊情况,数组在特定条件下可当做字符串用。C语言用双引号描述一个字符串,如“abcd”。
& f9 ^4 b$ b" f9 S  H2 O
下面我们通过一个实验来展示如何发送字符串。我们实验的目标是打印字符串“Hello World ! 第一!”到打印机。直接上代码。

  1. ) V3 K% ?" k! O2 y% {  j2 J
  2. [cpp] view plain copy% U  K/ y$ t+ H  b" Y9 c
  3. #include "reg51.h"  
    / f8 e% @7 }  v: {( b- z" _/ H
  4. " v- O' `' t6 R& l6 t
  5. #define u16 unsigned int  
    1 j7 V& X3 F$ j# y  R* Q
  6. #define u8 unsigned char  1 ^# G% k7 N/ n5 S0 E1 p
  7. void delay(u16 x)  
    0 h0 K8 d5 Q# b. v9 q; [" {
  8. {  1 ]' _5 o9 `/ c1 Z
  9.     while(x--);  0 j+ N) S) O# E/ d7 J) M( x4 K. R* N, ]
  10. }  
    5 E  _1 ~3 D8 {5 Q' `
  11. void Uart_Init() //串口初始化  
    1 y0 n' X5 _  D4 V7 d8 |' L
  12. {  , M0 Z; x7 J/ v/ L# G, N. \% u
  13.     SCON=0x50; //8位异步模式  4 }% m2 G% A/ W: I) e6 \
  14.     TMOD|=0x20; //定时器1工作方式2  ( O. Q3 g0 R& j
  15.     TH1=253;//9600bit/s  
    % W+ S2 ]- Y9 A: l; z: ?& U
  16.     TR1=1;  ' }' ^1 {4 w; N# O9 ~- z
  17. }  & q' D8 ^& n+ M0 ^
  18. void Send_Byte(u8 dat)    //串口发送一个字节  
    0 z& I4 L3 e8 F2 k1 o  m7 S+ u
  19. {  
    : o- c: H# b: Y) P% _! E8 D
  20.     SBUF=dat; //启动发送,只需要把发送内容给SBUF这个寄存器  
    & {3 _: y8 p& K  W3 c$ I- X( e
  21.     while(TI==0); //等待发送完成,因为TI为1时表示在发送停止位  
    5 M% \( J. w! E& C
  22.     TI=0;  
    9 s) ]1 T; G" c% F
  23. }
    - l2 ~  u/ j1 C
  24. void Send_String(u8 *str)   //发送一个字符串  *str为字符串第一个字符的地址  
    0 t" `3 P$ n6 q) C* k1 }
  25. {  
    9 _( p* K5 m) V6 m+ t
  26.     abc:      //标号  7 ^4 F% D: I' Y
  27.     if(*str != 0)      {  
    7 Y0 p% ~5 \, c6 ]5 R& R
  28. & k% u/ R$ J$ _$ S( Q
  29.         Send_Byte(*str);  3 X: @0 J" U0 f- p

  30. ' h' U  [' z( H2 s
  31.         str++;  
    % ]3 W2 R- ]1 |+ X0 m+ @
  32.         goto abc;     
    # D4 K/ x  ?  _6 L
  33.     }    e' |0 ~2 F7 D* n0 }0 q* X' r
  34. }   
    6 Y- f/ N0 _8 Q+ s8 S5 x
  35. ' N; w8 m0 ]8 U: B
  36. void main()  
    0 S: _' g) `* r3 n: y! n3 c
  37. {  
    2 b; C0 F4 V9 ^9 B" D' v( R, E
  38.     Uart_Init();  3 n# @% F7 D) X1 e
  39.     while(1)  2 m! M+ i+ r/ X% m
  40.     {  
    : ]8 ~* Z' y) W" L
  41. : w5 J* \: o7 }* O" I  P
  42.        Send_String("Hello World! 第一!");  % s2 |5 ?! a4 f

  43. 5 K6 i. b# M5 Y- J
  44. $ ~( p9 |2 L8 e, w# c# T7 B
  45.        Send_Byte(10);  4 P" J# C1 g2 U0 c9 e. @
  46.        delay(60000);  
    # i' l. x4 _1 a. `* Y& I8 W
  47.        delay(60000);  ! ~( E4 {0 o; H/ k2 J: @( e* G. S
  48.     }  
    # q  L0 \. V2 r) W
  49. }
复制代码

+ H/ h4 ]0 @4 X; c
实验效果
微信图片_20201016205138.jpg
0 h# B! t9 Q  t2 Q5 U6 h" J; M! a5 `
赞 收藏 1 评论0 发布时间:2020-10-16 20:52

举报

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