|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
RS485通信的特点5 P( c6 X) \* w' U
n1、采用差分信号。3 D5 B5 k+ Q$ f
n2、RS485通信速率快,最大传输速率可以达到10Mb/s以上。 I. v/ p* J* `3 ]- [" d+ `+ W" U
n3、RS485内部采用平衡驱动器和差分接收器的组合,抗干扰能力大大增加。
% r o; u* a" U3 jn4、传输距离最远可以达到1200米左右。
2 ]7 x* `- D7 b% { X ]% ^n5、可以在总线上进行联网多机通信。6 R& I% h. n7 f* A
n6、RS485接口非常简单。, s8 x: j2 e& o' u' @ D
Modbus通信协议介绍
# a2 j8 w' y1 H3 A1 O* b) Sn1、Modbus产生的背景。! Q# L; g& A" r) W
n2、Modbus协议特点。
8 Z8 `% T! o ?9 r8 H2 dn3、RTU协议帧数据
# U, a) r/ F2 x0 Z( w$ x0 nn4、Modbus功能码9 P2 c; v9 F! L" G% A; U+ i7 ?! B( n
/* 备 注:
7 L: Q0 t2 g8 m* 1、在lesson15_3的基础上去掉按键校时,添加lesson18_2中的Modbus协议支持! u O3 L/ R' T5 f! v" m
* 2、利用Modbus调试精灵的写寄存器功能,可修改日期时间的每一个字节
+ V. m9 T8 U8 h; G* 3、寄存器地址0x0000~0x0006分别对应“年/月/日/时/分/秒/星期”; `3 u* p# g' u5 h" V& m& f
* 4、RS485方向控制信号由原来的P1.7改为P2.0,因本例使用了DS1302而未使用按键
# x( }# X6 ~) r*******************************************************************************$ G3 j8 b. z9 w* A, e8 |
*/4 J( s& j/ z% F+ j) ]8 e0 n: h
! B; F3 y+ X5 p% a, z: W#include <reg52.h>
* e1 _2 | i) [% O2 [8 p& Q% f( e7 E$ l5 W( C6 t
struct sTime { //日期时间结构体定义% }& W* d+ Z8 R# f9 H
unsigned int year;0 A2 Y ~) N" f0 a v( h: o
unsigned char mon;
* d, j/ [7 u+ Z7 I1 y: D0 O1 l unsigned char day;
. t' r# h1 n5 Q unsigned char hour;: q6 {: c( b% g2 e
unsigned char min;
$ v4 o6 H5 B' @7 ?( f/ L unsigned char sec;) F: Q x) P5 a
unsigned char week;1 ?2 i+ A; ~" M$ a
};
8 ]1 e, |: K% x3 t, s# f3 R! S8 ^ ~: w* d
bit flag200ms = 1; //200ms定时标志
1 x. r9 s$ O S- tbit reqRefresh = 0; //时间刷新请求
5 O0 S, W/ z& q5 ^0 o# Fstruct sTime bufTime; //日期时间缓冲区
& C; h! L9 E6 e5 h! hunsigned char T0RH = 0; //T0重载值的高字节
+ h* \, o5 y4 y8 ~, @4 cunsigned char T0RL = 0; //T0重载值的低字节; g3 N' }1 N" ?: q0 I: R
( \2 N: ^0 R# ^) N5 @3 c
void ConfigTimer0(unsigned int ms);" u6 C4 q+ m6 ?: f. ]* f x2 A2 i D
void RefreshTimeShow();* T. c b) r1 l. K
extern void InitDS1302();
, V7 q& u* Q# ]/ Q m1 Y9 @extern void GetRealTime(struct sTime *time);
n5 |0 W. H4 _) hextern void SetRealTime(struct sTime *time);
7 F4 M. @1 K6 q/ |/ mextern void UartDriver();
1 r8 c0 g9 ^ t; U- H( L$ aextern void ConfigUART(unsigned int baud);
# J" H; [# f: c4 L; h5 i' bextern void UartRxMonitor(unsigned char ms);
6 c z% f4 H) d* ~extern void UartWrite(unsigned char *buf, unsigned char len);4 O% _* Q+ j2 c: b4 s* u$ S7 o
extern unsigned int GetCRC16(unsigned char *ptr, unsigned char len);" f4 h; s- I8 C1 O, {) @0 h
extern void InitLcd1602();
2 M2 U: [# r V, Qextern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
+ W$ o$ K& U4 K+ Y9 V& H* r# N& Q0 h: C
void main()" I. p" O- x7 u, c- {7 e; z/ h
{' d) @0 v; g# p {% z& p% c
unsigned char psec=0xAA; //秒备份,初值AA确保首次读取时间后会刷新显示
0 t Z! E4 j1 g$ M
J2 j# M. d( F9 l# S7 C' V$ ] EA = 1; //开总中断
v3 r7 A& j5 Y% S7 Y ConfigTimer0(1); //配置T0定时1ms0 G# o o0 a( s3 q i) r" @
ConfigUART(9600); //配置波特率为9600
, S3 e1 ~( d4 a/ P6 G InitDS1302(); //初始化实时时钟1 D; [: V" ]9 }$ w8 I
InitLcd1602(); //初始化液晶
* G% l( z" `2 w ^+ ~5 U, ?/ ?1 Z+ r! F3 \
//初始化屏幕上固定不变的内容
% Y: v+ F$ K' A H* _! \' X LcdShowStr(3, 0, "20 - - ");* x" ^# h, Q3 J' e. N' |& k
LcdShowStr(4, 1, " : : ");
" }- h+ T3 V+ t/ f) y5 \* N6 F! S8 K9 W% \6 B
while (1)
, f" ]: Q: k8 k7 L) c9 x- ? {' m" `) ~( D$ B Z$ n. A1 k l2 H
UartDriver(); //调用串口驱动* v- q0 ^1 i4 P9 j; R4 o% E
if (flag200ms)
! B. h) H$ ~; p; T1 Q {
& m6 w M& s; Z- Y0 s( g8 F: v flag200ms = 0; u/ w0 u/ i" n* W' b8 ^! Y
GetRealTime(&bufTime); //获取当前时间
. O! B5 W' Q$ N# x$ V if (reqRefresh || (psec!=bufTime.sec))3 j9 C- n! N" P0 y- ]
{ //检测到时间刷新请求或时间有变化时刷新显示
$ h6 ^ v5 Q4 G1 L1 Q RefreshTimeShow();3 j7 U4 r" x9 K! F
psec = bufTime.sec; //用当前值更新上次秒数
# h [$ } a* Z4 L3 w. g+ k7 |; U }
; ~9 A* E+ k9 w }2 P8 {) y8 v* j B' q! x
}$ i( c9 ?7 f$ S5 H# T6 f
}6 Z7 n+ e" F. {: ^. [
/* 将一个BCD码字节显示到屏幕上,(x,y)-屏幕起始坐标,bcd-待显示BCD码 */
2 Z1 r I; N8 A; }: n. ?6 jvoid ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd)
& g5 H3 \* S; `/ [4 v5 O q# _{
! N9 ]. y% X8 z, B unsigned char str[4];
0 r) }& R: C1 z5 }6 o1 e5 B- ?* S3 m$ i2 k! Z2 z3 f* ?
str[0] = (bcd >> 4) + '0';! }' b% P# i- E+ V8 ^
str[1] = (bcd&0x0F) + '0';; J% Y# X" }( C: x
str[2] = '\0';" Z0 p+ g5 S4 B) ?4 N" s
LcdShowStr(x, y, str);2 P9 Z) x+ O: u. [7 I
}& l8 a' }! k. R9 i
/* 刷新日期时间的显示 */
L. k0 i: s; C* C# G8 Ovoid RefreshTimeShow()2 m, C1 Z2 w7 P
{
/ h, d7 e. c! M/ Y7 j8 L) v# L ShowBcdByte(5, 0, bufTime.year);
, d' i' Z2 E5 }& E ShowBcdByte(8, 0, bufTime.mon);
" t3 C0 F6 ^; L {7 [ ShowBcdByte(11, 0, bufTime.day);
" F% T9 w8 \( q+ X2 s" d0 Q ShowBcdByte(4, 1, bufTime.hour);
( b/ D5 n& v" U1 e: J4 Y* v ShowBcdByte(7, 1, bufTime.min);5 R! I, k2 f, W, d
ShowBcdByte(10, 1, bufTime.sec);# y/ W/ H2 D4 t' T5 ~, k: c
}' D0 H7 ~( R' i: s9 e
/* 串口动作函数,根据接收到的命令帧执行响应的动作) f }, g& T3 A* t, Y8 P
buf-接收到的命令帧指针,len-命令帧长度 */5 N% ]! r5 P" \8 q% l2 U) v
void UartAction(unsigned char *buf, unsigned char len); }- l b! |1 F o+ ^
{
# s9 u8 A; p+ Z0 G unsigned int crc;& C( [/ b" ^' y, C7 Q
unsigned char crch, crcl;
0 s6 S6 E! k% Z' C! s/ J" w, _% v1 T& y+ h V
if (buf[0] != 0x01) //本例中的本机地址设定为0x01,1 `, r+ \+ {6 Y8 z' }, @
{ //如数据帧中的地址字节与本机地址不符,5 o) C2 a' `* g) a3 T5 d, x
return; //则直接退出,即丢弃本帧数据不做任何处理
' h* i5 D" i/ ^3 K* @( e( f N& _" F) O }
# _7 J0 q$ c0 A. u8 ?7 h //地址相符时,再对本帧数据进行校验
+ O$ {: k2 ?. T2 i2 b: } crc = GetCRC16(buf, len-2); //计算CRC校验值
0 Z6 w2 b6 `* Y. V* Z9 z, Q2 R crch = crc >> 8;
) q: K x# Y p$ T- \: ~0 a crcl = crc & 0xFF;& F( X& H5 E5 z: }4 D, W
if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))* U* M( `: O; W* c; ]. \5 w1 g9 g( Z
{. B9 J0 _* B/ Z! Q, j: a) E: E
return; //如CRC校验不符时直接退出8 T# @+ n0 @; v; N6 D
}
. a5 j, p) f8 U8 ? F2 C& P1 P/ k$ ? //地址和校验字均相符后,解析功能码,执行相关操作* v# a# h$ ?: S' K
switch (buf[1])
1 s' |* {( _& A: U! x' F. G {
9 I0 H2 X* ~6 ?* g4 O8 c case 0x06: //写入单个寄存器3 c" A; B, _8 l' E9 s% P
if ((buf[2]==0x00) && (buf[3]<=0x06)) //地址0x0000~0x0006分别对应
T+ c# \! p) B: O+ B { // “年/月/日/时/分/秒/星期”- j; v9 {1 i. C1 j, v* ^, W" v# V
GetRealTime(&bufTime); //获取当前时间) L& C# O6 D1 v3 c5 H
switch (buf[3]) //由寄存器地址决定要修改的时间位) ^1 g3 M: p7 S- F( K
{4 f0 G. V1 a( W
case 0: bufTime.year = 0x2000 + buf[5]; break;
4 f1 z+ ~. f7 w: S case 1: bufTime.mon = buf[5]; break;
6 h2 }6 S. n% f& p! i1 x3 k+ G: x! V case 2: bufTime.day = buf[5]; break;
3 p; f, j" I/ Y6 U case 3: bufTime.hour = buf[5]; break;
; B' ?9 o& Q* j- R B case 4: bufTime.min = buf[5]; break;, ?: g) z, ^9 t ^3 C) k
case 5: bufTime.sec = buf[5]; break;
5 f# z: B2 @& a3 }2 V5 ] case 6: bufTime.week = buf[5]; break;
| w* ^. s6 w7 y- \1 V# R. o/ Z default: break;
; D5 n6 D6 d. S' r# { }+ M% ?$ S0 t/ ?) ~
SetRealTime(&bufTime); //写入新修改后的时间
6 f- F, H; k) Q reqRefresh = 1; //设置显示刷新请求
/ S! E/ B! f* E/ y- O8 x% N len -= 2; //长度-2以重新计算CRC并返回原帧$ U2 q5 O, t3 ]7 U! o" b) O# l, |
break;% | g3 E" J4 `( i7 @
}7 _5 V/ I4 P! O, f, E1 B
else //寄存器地址不被支持时,返回错误码5 [; d) I5 `/ v" V+ j/ w6 g% x
{1 I; l7 l( b) D5 p
buf[1] = 0x86; //功能码最高位置1 V+ s* ^& B7 w5 `2 ^1 C n
buf[2] = 0x02; //设置异常码为02-无效地址
0 K. x7 T- k: k0 ]2 V len = 3;
) q/ A K. x' _* V break;
$ k: T Q& y# W4 Y$ O9 @ }% I+ G% N% E9 [' O) L
* d& F) \1 d$ P& e
default: //其它不支持的功能码3 ?/ w# {) r% V1 @% p6 \
buf[1] |= 0x80; //功能码最高位置1
- W9 t0 S/ g! r5 x) C buf[2] = 0x01; //设置异常码为01-无效功能
8 U. K$ Q. l1 t: g* n& i* `6 _, A! H3 v len = 3;# F$ @# G2 l8 S
break;( O; [* m, {. B
}
3 z: P6 m6 {2 X7 H; H crc = GetCRC16(buf, len); //计算返回帧的CRC校验值) f2 |, B1 J4 d# T- A
buf[len++] = crc >> 8; //CRC高字节
) V- U& R; N, J0 v% u' I5 ?2 m buf[len++] = crc & 0xFF; //CRC低字节
6 f. M0 Y. B2 i8 k) D. ^! S UartWrite(buf, len); //发送返回帧% Y# l" I \3 ~
}
8 ^; ^7 r" B8 P9 s4 b/* 配置并启动T0,ms-T0定时时间 */6 m# i7 C: u# Z; p/ Z
void ConfigTimer0(unsigned int ms)
& L' S3 J" O ^- L5 p, \7 ~{) w) @. E2 K u+ ~& u6 z' A
unsigned long tmp; //临时变量
0 v& E/ l0 y5 M/ U
6 h3 N5 D" v& s8 p0 m; Y/ H tmp = 11059200 / 12; //定时器计数频率
, B8 M/ A3 y0 {! F tmp = (tmp * ms) / 1000; //计算所需的计数值9 R7 P, F" u3 W: q3 p/ G' M9 p, x
tmp = 65536 - tmp; //计算定时器重载值
. ]: _! [. P$ { tmp = tmp + 33; //补偿中断响应延时造成的误差$ j0 I+ S' ]; S, X. i* t
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
+ N4 W9 N) e- j* C2 w$ d2 k! B; } T0RL = (unsigned char)tmp;
- i/ r1 p9 u: Y4 P: l5 \- A$ P) | TMOD &= 0xF0; //清零T0的控制位 R! \8 L' q! ^6 f7 h" {" x
TMOD |= 0x01; //配置T0为模式18 C9 s6 U0 K0 J9 R9 N" s
TH0 = T0RH; //加载T0重载值2 k5 y% C4 |! \) r2 e7 w
TL0 = T0RL;% V- Z6 s) |% Z u: B: D9 |8 v
ET0 = 1; //使能T0中断
: o5 G! f* I6 V- e TR0 = 1; //启动T0
! y: C u; {& a! h; d1 y) A}
+ [5 w( Q- A8 Y; a4 D, W( O/* T0中断服务函数,执行按键扫描和200ms定时 */
, w3 R( b" j" m6 {# y, wvoid InterruptTimer0() interrupt 1
, N6 q4 Y1 \$ e N( o{" h l( c& J: A1 x. l! A
static unsigned char tmr200ms = 0;! ?- V) P6 a0 x! a1 Y
6 F4 ?( ~+ {: c+ i
TH0 = T0RH; //重新加载重载值2 Z7 \# }5 i0 q; p v
TL0 = T0RL;
- x' z( L5 C- i# h( S; a7 k7 r UartRxMonitor(1); //串口接收监控3 c4 o5 Q: @- P' ]. r
tmr200ms++;% ~ L* i3 O1 p& z
if (tmr200ms >= 200) //定时200ms
) Y0 I2 k/ [4 Y% B {
# @9 ?& S* } q+ J4 Z4 K tmr200ms = 0;
; Z$ L9 n) n3 L6 f- c. X flag200ms = 1;
! F" h7 Y T+ C R; y% Y4 j }) z' }1 K! u: R( I" `) w4 H
}" X$ S5 D) a5 z: }- c
: f0 @9 x) S6 ~9 g- k4 w/ j
|
|