|
现实生活中, 我们总是要与人打交道,互通有无。单片机也一样,需要跟各种设备交互。例如汽车的显示仪表需要知道汽车的转速及电动机的运行参数,那么显示仪表就需要从汽车的底层控制器取得数据。而这个数据的获得过程就是一个通信过程。类似的例子还有控制器通常是单片机或者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位的时间,即为停止位。 ! 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
1 W( k1 K9 Q7 X, W& @6 b
好了直接上代码吧。 - ( N" d! k5 N/ g) {- W( X
- [cpp] view plain copy9 _1 [3 E; K! Y2 {! w
- #include "reg51.h"
, m' {7 J# k7 ?( v/ C# N" S - /*
: H% @ t# M6 Z2 U6 x - 将P1.0虚拟成串口发送脚TX
- R2 }: p+ [ u& y - 以9600bit/s的比特率向外发送数据
3 z C: @4 h% [" L; r, H: M - 因为波特率是 9600bit/s + q) r/ y& S- e2 z, x! Q/ B
- 所以me发送一位的时间是 t=1000000us/9600=104us
% N. n) d( }9 J, f8 e( z8 y: Z: `+ q! ~ - */
3 j3 M" {% V F# [' t. d: F1 f2 S. P - 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
- #define u16 unsigned int //宏定义 O1 F* f, S( Q& V4 O Y/ m5 N# P5 {
- #define u8 unsigned char
- ?' I* a; X* B+ V7 N& Z - u8 sbuf; 6 J# w/ {) y! X* E$ _2 W
- bit ti=0; + G2 I& ?* Z9 }
- void delay(u16 x)
4 Q0 G) M) F( c/ m" P+ e- K' j - {
$ b& f7 N% m. B) I8 f - while(x--); 8 A) b5 K( X( S2 c2 d
- }
# y/ ]1 X- h' Z4 y5 j( V! J, T% \ - void Timer0_Init() * H9 Z& E7 D) y
- { " Q$ b1 Z7 `% f( g2 ^3 }( X$ A5 K |
- TMOD |= 0x01;
$ ]% G; R2 I4 e, k ^( n - TH0=65440/256;
- | p( }1 h( U5 r3 j- b# m - TH0=65440%256; : u" d' i1 F0 T! H) z' B+ |, e
- TR0=0; 3 f2 d+ ~7 z% n* f' {; h; L
- } & c2 ]/ k k2 a: ~( z
- void Isr_Init() 3 I6 T) [0 D" Z% E, Z
- { + ^# V% }% \5 v8 s) t7 ?
- EA=1; % k5 _, P# g: d2 H6 C4 d2 \
- ET0=1; : I3 g2 J' X N6 j0 M+ W9 Z
- } & A$ n1 c0 p- _0 E6 F* t" B
- void Send_Byte(u8 dat) $ t+ w, n- j: C" M
- {
0 C F$ P# O$ W - sbuf=dat;//通过引入全局变量sbuf,可以保存形参dat
3 E s1 j+ ~! q" _4 T - TX=0; //A 起始位
) F( l) ~2 E9 J. v - TR0=1; 9 D: r6 B9 v0 `# F( s" ]; W$ Q
- while(ti==0); //等待发送完成 6 m0 @* j4 h% s; O6 Z. \
- ti=0; //清除发送完成标志
4 v+ x9 _' ~: Q5 L2 f - }
! z- _* G* ^( X. M8 \/ S5 K1 k - void TF0_isr() interrupt 1 //每104us进入一次中断 + P& g* Z, S, S/ }) Q4 Q) ^
- { & N+ W3 J3 `! x" {4 X4 x
- static u8 i; //记录进入中断的次数
0 h3 }' v0 W8 Y7 w - TH0=65440/256;
9 F! o; @- N/ ^ r - TL0=65440%256; . a+ _, e. R3 x1 H' [! V# A
- i++;
6 s2 B K* D$ q" M - if(i>=1 && i<=8)
8 t$ o: g1 J H# R- L8 p - { if((sbuf&(1<<(i-1)))==0) // (sbuf&(1<<(i-1)))表示取出i-1位
6 S* U$ Y) _4 p( @ - " f8 \" D( V @. W7 I/ S8 [
- { ' w6 x3 N5 y5 ^9 j5 `6 n
- TX=0; 8 ~7 ]' O' s; }8 I1 b0 ~/ h
- } # R4 I2 K/ | f8 P0 ^5 b
- else 7 O1 R7 h, e; _" _9 m
- { 9 U. d9 U% L0 j" b) H p2 S
- TX=1; 6 A, C8 p9 j8 b1 V( ]1 ?
- }
" @1 e2 E" F" @: n! J9 z- E1 ]$ \. J3 A - }
! ^3 S6 Z% \/ A" K t g" ~ - if(i==9) //停止位
$ A2 u9 I7 f9 J8 A, _2 I$ ] - { TX=1;
9 c' U3 w2 X ]+ k7 M b - }
. @! \# v5 Q3 n$ X5 ^& V - if(i==10)
- @- m/ D% q/ g - {
4 r7 x- G+ a. \6 f3 v) n9 n - TR0=0;
0 [+ W: g! Q. S6 K; @% {3 H B - i=0; 4 R! n) ~; J$ b; E4 K; \: m
- ti=1; //发送完成 * G+ N4 Q8 A& Q+ }" k
- } 1 D& C5 J+ b3 ?- A
- }
0 I' |, X+ `+ q& p3 W - void main()
5 i7 w; v& b+ p* [7 x/ F. M - { : P4 g( `4 c$ E3 x
- TX=1; //使TX处于空闲状态
' } s" y, h! F+ D4 N/ e3 [ - Timer0_Init(); ) o. D: v) I; j% D
- Isr_Init();
e) i1 z! D. p L6 C6 j - while(1) 0 u. D0 N$ V6 g/ k! W$ L# O
- { 3 W& C [( a; U3 u. e8 G
- Send_Byte(65); //0x41
z- S, V# Y4 Q; G8 c7 | - delay(60000);
: F( @* _* i, ^. F4 p/ ^ - } 3 e( _! U) d- L
- }
复制代码! 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
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
好了现在开始我们的实验之旅。直接看代码吧。 - . p! J% C* l) p1 }( v
- [cpp] view plain copy
/ }- S4 [8 A9 t8 C -
1 M! G: m3 J0 q* P - # include "reg51.h". `5 {; h1 j+ i9 w+ Q: M2 i
-
5 }# a; X$ s" ~! j$ h$ y - #$ A; x( V+ k2 b/ _2 P
- define u16 unsigned int1 @8 v4 E5 G5 ?# M d' } W
-
; E3 `+ ?5 N5 q* I - # define u8 unsigned char
2 Z0 q9 a8 O8 U& H" a" \! g -
7 o# z r& L1 G - - p, E+ h) K* k4 i6 g
- 3 F$ b) I, V# g3 D
- void delay(u16 x)5 B' H6 c6 A# v* ?+ ~
- {6 A) B* |0 Q) f$ g+ K
-
! w) X( @% t& e; z - while (x--);
9 B4 _. f) @4 y+ a! O. ? - + m3 C6 s; J+ x* Q* }# @. P
- }
C2 ` {, h5 |' e& b+ U3 E5 H - / u4 ]5 B g/ s
-
% b1 O, P+ b' ~& S -
; a9 a; o7 N* a+ y& |2 X; S - void Uart_Init() //串口初始化
% q( r9 Z/ r& H, u - {. j4 S- x. \7 o+ y1 V
-
# T$ J* \9 v+ c" Q! o; m5 J3 v1 T/ _ - SCON = 0x50; //8位异步模式
! r$ B9 y+ g8 {. x8 F" x -
, E& \# E& M7 S - TMOD |= 0x20; //定时器1工作方式2 * |; I$ T+ V& h0 i; B
-
+ L- b4 P6 W+ @9 @9 L7 e - TH1 = 253; //9600bit/s 0 n/ J0 f' u9 b) e- u! `
-
$ n$ s8 R7 j' M; O( d - TR1 = 1;
3 O2 y& m2 M4 ]; j! {; q/ S -
: w$ ?' ]4 ^ Y2 [* b* E- D - }9 ?6 h6 S/ K( s; p' e, ^9 U
- 4 [, T& _" F/ d) R2 j5 z+ f, ^
- / m) m2 U; } O7 u+ l. I! s. n
- - b6 \) g8 j( F: A! A
- void Send_Byte(u8 dat)
1 K# `) x& L4 X v/ o - {
: [! s; K' M( o" i" u - ) Z7 m: ]1 g4 y9 F; L
- SBUF = dat; //启动发送,只需要把发送内容给SBUF这个寄存器 % ]% P" l; g' M
-
+ X+ C4 K6 {; b# `+ Y - while (TI == 0); //等待发送完成,因为TI为1时表示在发送停止位 i' m2 c2 O7 C- v# R
-
6 u) n) K/ K+ }6 p1 i - TI = 0;8 I* _6 g3 a7 Q8 V! n2 ~
-
1 \/ z1 B: V- r U$ F+ Q - }9 n7 |& x+ Y# x# f$ N
-
" w# V" q0 C- l. l5 Q& d - 2 w2 X8 S% B$ w* O
-
8 y9 J+ L( V# m ` g5 B2 b - void main()
2 L" }7 ~+ H3 y4 l - {
8 ?" c4 A+ }& X+ L* q -
0 i) ?& }8 j9 x - Uart_Init();
. F' c! f0 v7 [/ r - 2 ]( ]) @5 |* w. A( H
- while (1)/ P( n# i2 W+ c' b' N9 _
- 8 j* p; n8 l+ z+ M& E/ f
- {
# p5 f; U8 [9 H6 h; v- v - " `& k6 R! }3 O* |1 D0 @
- Send_Byte('m');" f$ G0 L7 l) f: m1 p/ z
-
# N2 V" U6 ?1 B T - delay(60000);2 o) w0 k5 i. C* y3 q, F1 {
-
. n" a$ y; p5 o6 t+ P, c5 x - }
$ M' s/ L4 b/ B -
; e2 Q6 t6 p' s4 \' T' p) `- _( B - }
复制代码" 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 ! 第一!”到打印机。直接上代码。
) V3 K% ?" k! O2 y% { j2 J- [cpp] view plain copy% U K/ y$ t+ H b" Y9 c
- #include "reg51.h"
/ f8 e% @7 } v: {( b- z" _/ H - " v- O' `' t6 R& l6 t
- #define u16 unsigned int
1 j7 V& X3 F$ j# y R* Q - #define u8 unsigned char 1 ^# G% k7 N/ n5 S0 E1 p
- void delay(u16 x)
0 h0 K8 d5 Q# b. v9 q; [" { - { 1 ]' _5 o9 `/ c1 Z
- while(x--); 0 j+ N) S) O# E/ d7 J) M( x4 K. R* N, ]
- }
5 E _1 ~3 D8 {5 Q' ` - void Uart_Init() //串口初始化
1 y0 n' X5 _ D4 V7 d8 |' L - { , M0 Z; x7 J/ v/ L# G, N. \% u
- SCON=0x50; //8位异步模式 4 }% m2 G% A/ W: I) e6 \
- TMOD|=0x20; //定时器1工作方式2 ( O. Q3 g0 R& j
- TH1=253;//9600bit/s
% W+ S2 ]- Y9 A: l; z: ?& U - TR1=1; ' }' ^1 {4 w; N# O9 ~- z
- } & q' D8 ^& n+ M0 ^
- void Send_Byte(u8 dat) //串口发送一个字节
0 z& I4 L3 e8 F2 k1 o m7 S+ u - {
: o- c: H# b: Y) P% _! E8 D - SBUF=dat; //启动发送,只需要把发送内容给SBUF这个寄存器
& {3 _: y8 p& K W3 c$ I- X( e - while(TI==0); //等待发送完成,因为TI为1时表示在发送停止位
5 M% \( J. w! E& C - TI=0;
9 s) ]1 T; G" c% F - }
- l2 ~ u/ j1 C - void Send_String(u8 *str) //发送一个字符串 *str为字符串第一个字符的地址
0 t" `3 P$ n6 q) C* k1 } - {
9 _( p* K5 m) V6 m+ t - abc: //标号 7 ^4 F% D: I' Y
- if(*str != 0) {
7 Y0 p% ~5 \, c6 ]5 R& R - & k% u/ R$ J$ _$ S( Q
- Send_Byte(*str); 3 X: @0 J" U0 f- p
' h' U [' z( H2 s- str++;
% ]3 W2 R- ]1 |+ X0 m+ @ - goto abc;
# D4 K/ x ? _6 L - } e' |0 ~2 F7 D* n0 }0 q* X' r
- }
6 Y- f/ N0 _8 Q+ s8 S5 x - ' N; w8 m0 ]8 U: B
- void main()
0 S: _' g) `* r3 n: y! n3 c - {
2 b; C0 F4 V9 ^9 B" D' v( R, E - Uart_Init(); 3 n# @% F7 D) X1 e
- while(1) 2 m! M+ i+ r/ X% m
- {
: ]8 ~* Z' y) W" L - : w5 J* \: o7 }* O" I P
- Send_String("Hello World! 第一!"); % s2 |5 ?! a4 f
5 K6 i. b# M5 Y- J- $ ~( p9 |2 L8 e, w# c# T7 B
- Send_Byte(10); 4 P" J# C1 g2 U0 c9 e. @
- delay(60000);
# i' l. x4 _1 a. `* Y& I8 W - delay(60000); ! ~( E4 {0 o; H/ k2 J: @( e* G. S
- }
# q L0 \. V2 r) W - }
复制代码
+ H/ h4 ]0 @4 X; c
实验效果 0 h# B! t9 Q t2 Q5 U6 h" J; M! a5 `
|