|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
RS485通信的特点
& f0 ^6 U" O3 O j$ I$ j. n2 }n1、采用差分信号。
" n) P- c/ t; o3 f( Q9 rn2、RS485通信速率快,最大传输速率可以达到10Mb/s以上。
9 k' w4 o8 l3 R" V6 \5 }n3、RS485内部采用平衡驱动器和差分接收器的组合,抗干扰能力大大增加。3 {' Q- k* I- ~3 @8 a4 B' g% q
n4、传输距离最远可以达到1200米左右。
& [1 ^: B9 g/ w! X% Mn5、可以在总线上进行联网多机通信。! d" M' \! }! t8 ]+ Y0 L2 ~
n6、RS485接口非常简单。6 h6 w# [' w6 L( H) Z' l, h) P
Modbus通信协议介绍
4 x. A8 X0 R' |' S4 j# `n1、Modbus产生的背景。0 ^' d8 ^! m: B4 j; J$ \) e+ B% f
n2、Modbus协议特点。3 V5 k9 h- c' ]! `
n3、RTU协议帧数据
9 l0 s4 v' g5 a8 On4、Modbus功能码
. K; c, q8 L! ]. z/* 备 注:
, W' l. Q8 {3 S* 1、在lesson15_3的基础上去掉按键校时,添加lesson18_2中的Modbus协议支持0 Q+ a" R3 |5 N4 |/ T) ?
* 2、利用Modbus调试精灵的写寄存器功能,可修改日期时间的每一个字节, v- y) a% g: g
* 3、寄存器地址0x0000~0x0006分别对应“年/月/日/时/分/秒/星期”
O7 c0 a- {6 h5 Q& T* 4、RS485方向控制信号由原来的P1.7改为P2.0,因本例使用了DS1302而未使用按键; Z0 {1 `; j# y# e
*******************************************************************************
: U# v& j$ w0 [$ Q*/8 o; s F8 _% e, T! l9 u
# Z1 ~2 W n8 s( _ _$ o ]8 t
#include <reg52.h>
$ X( v+ }8 f1 D9 \( A- G g
- I1 X4 m, n1 B/ p; \% J* C( Tstruct sTime { //日期时间结构体定义3 d) g2 {% N$ A9 i* l, f O0 B# _
unsigned int year;
/ `9 T5 X: F0 \5 n unsigned char mon;
) n! I# D- a k& c% G2 ]: \! d unsigned char day;
0 o; \/ H1 t2 O. F7 x9 C! C) n8 s unsigned char hour;) [' j' w! V M6 t, J, [# v$ l
unsigned char min;
! w; |( w) q' h; l unsigned char sec;# f( |! r" r' X+ S( Z9 ~* _ u
unsigned char week;& H" ]8 ~: g7 R2 l
};& e# Z ~9 R8 z' `1 F
3 l4 ]/ Y& W1 m$ o3 \$ \1 ?4 p4 x( Zbit flag200ms = 1; //200ms定时标志
E+ Y6 I5 o: {, b, _( ?bit reqRefresh = 0; //时间刷新请求: a) ~% k7 ]# p2 i0 P0 R, r
struct sTime bufTime; //日期时间缓冲区
4 p* d3 h+ F5 V% G7 Runsigned char T0RH = 0; //T0重载值的高字节8 ^% ]( y5 z. L6 d/ w4 q7 R+ X
unsigned char T0RL = 0; //T0重载值的低字节
' I4 N$ s9 \5 { E! q" h- a
6 p( R, r% a, S9 Y8 Ovoid ConfigTimer0(unsigned int ms);& g$ V( c E7 B) b. w; l5 L3 q
void RefreshTimeShow();
! W$ f1 x( U# l# j- Q; W$ d. Qextern void InitDS1302();
6 A" b" z; h9 D# C5 `7 e2 }' J3 Lextern void GetRealTime(struct sTime *time);
: J) J1 o0 B- `" o: m' j& uextern void SetRealTime(struct sTime *time);
9 ~9 i. M# y1 r6 kextern void UartDriver();
' y# F2 h) J j$ z( c' Q# {# Cextern void ConfigUART(unsigned int baud);
3 F# D2 r" O2 G0 wextern void UartRxMonitor(unsigned char ms);
. G; T5 c3 ?$ _ \" Y/ @extern void UartWrite(unsigned char *buf, unsigned char len);
$ H/ h: ~- F4 }: x+ H! ~/ Wextern unsigned int GetCRC16(unsigned char *ptr, unsigned char len);
. [, | l0 D( R& P5 ^extern void InitLcd1602();: \0 J: }, r# ?6 `% A3 v' H
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);" A+ M+ K* v& S) N7 \
- o R2 a: f* ]2 n$ X
void main()8 i( @( `+ F, B& i+ v K
{2 R6 E1 r+ g2 t& d
unsigned char psec=0xAA; //秒备份,初值AA确保首次读取时间后会刷新显示
1 f' ~) {! l: k$ i
$ J$ c' X( T. { G1 }) [- B5 c EA = 1; //开总中断7 |1 W& c3 a$ K8 S# p0 B
ConfigTimer0(1); //配置T0定时1ms [. v- z" G& `
ConfigUART(9600); //配置波特率为9600
! `3 S- Q* P! L! z; D2 M5 r) B" @ InitDS1302(); //初始化实时时钟! C/ O" j, d/ r7 M6 F( a7 w
InitLcd1602(); //初始化液晶
1 ^3 A6 M5 R8 }- K/ q* C$ R- x; w# A
//初始化屏幕上固定不变的内容) U2 O$ Y; Z4 t
LcdShowStr(3, 0, "20 - - ");
7 D% E* t% }+ W& H" w2 R LcdShowStr(4, 1, " : : ");( D' N2 a/ j9 A, l* L) v$ `
! D. J0 _6 ?0 S while (1)7 | l3 @1 F6 E2 p1 w( h
{
8 |7 J* P9 u, N" i2 \ UartDriver(); //调用串口驱动1 Y# H# z0 P" m. {
if (flag200ms)$ j4 ~0 A* x8 G4 g' V+ o
{3 a8 _: d. O" O+ K1 j
flag200ms = 0;3 Q, Z o1 C0 _7 }
GetRealTime(&bufTime); //获取当前时间
d) Z3 Z* r" P r+ x" S- Z if (reqRefresh || (psec!=bufTime.sec))
# U# ^, } O/ g8 ^9 q { //检测到时间刷新请求或时间有变化时刷新显示# p0 E1 y% g+ L# F( U5 f. E9 G% G
RefreshTimeShow();* u+ G. l- }9 d7 ?
psec = bufTime.sec; //用当前值更新上次秒数) K9 ]% k5 I9 ?4 I# v& u! [2 i# g8 l
}4 Z# n; n. E7 b( u2 K% }- ]& g8 b
}
# L- {9 F% C- L8 q }; L0 r( w4 Q% I9 @. I, L/ m
}) C. O1 f1 C \0 l+ F0 u) }
/* 将一个BCD码字节显示到屏幕上,(x,y)-屏幕起始坐标,bcd-待显示BCD码 */" b) y+ e) A2 ~: A; U
void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd)
" U3 W5 q% |& g{
; ?& r3 i3 z3 J& r unsigned char str[4];
B% C9 m. U/ m* @4 G j2 P4 o& C$ i& Z: u
str[0] = (bcd >> 4) + '0';7 A6 R. b# K% L- i2 _
str[1] = (bcd&0x0F) + '0';% ]; G' G/ q! h% O' O" \2 ~+ O
str[2] = '\0';
9 C* p' }+ F8 R+ ] LcdShowStr(x, y, str);
9 v, ^' [7 r4 o% f# @0 e}% Y% Y3 Z4 R5 k0 R+ Y' y
/* 刷新日期时间的显示 */
" ^; X/ f2 J; B2 b$ {% Lvoid RefreshTimeShow()
! y. H- l6 f9 Z5 u4 B+ ^{# C5 K- K6 z8 \5 X9 d. e7 r
ShowBcdByte(5, 0, bufTime.year);
% ^% o c! R2 w5 ?- {' z* ?9 R ShowBcdByte(8, 0, bufTime.mon);
0 q( O- v% w; `$ F) ?# @% ~ ShowBcdByte(11, 0, bufTime.day);" g: P* ^3 ^! ?' L7 a8 Z1 F; p
ShowBcdByte(4, 1, bufTime.hour);
+ @! C; f* o8 C- w, @9 }, U+ u/ F ShowBcdByte(7, 1, bufTime.min);
5 |, W7 l/ G( T) _% a" Y! s" C& N+ s8 D ShowBcdByte(10, 1, bufTime.sec);
3 w' ~* }; r( T" x& B% }7 {" I) ]}4 p" \4 a6 ^4 h+ f& i6 y
/* 串口动作函数,根据接收到的命令帧执行响应的动作
' e, n+ c2 d( o" y3 k1 M buf-接收到的命令帧指针,len-命令帧长度 */0 `; c4 w0 b- R& c1 U8 R0 Q
void UartAction(unsigned char *buf, unsigned char len)
" C( g5 x/ ?% E{
! B4 S8 x0 c' W) k/ U6 L& N unsigned int crc;
) ~. c' j; m- m: L4 V4 z unsigned char crch, crcl;" l0 i5 k8 M4 _4 X& W
T- F; x+ T/ R. w7 G8 g
if (buf[0] != 0x01) //本例中的本机地址设定为0x01,: t' u4 X9 }7 J) G
{ //如数据帧中的地址字节与本机地址不符,
6 [* }8 U8 z$ e! g! i% Z- i, o0 X return; //则直接退出,即丢弃本帧数据不做任何处理. X* C1 Z0 p2 R8 q% M9 n
}7 O9 [/ Y4 m B) r5 z; c' r
//地址相符时,再对本帧数据进行校验3 Z) e8 c7 X- x; g; G, B
crc = GetCRC16(buf, len-2); //计算CRC校验值
6 e0 ^; n. V/ E crch = crc >> 8;
' i, A- K1 [. Q' r: ^4 z( E) j* m crcl = crc & 0xFF;
! }. s/ `/ O9 e if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))1 F M/ B2 k& W; E! j1 x, s. V
{, Y4 S" i* U4 C
return; //如CRC校验不符时直接退出9 q5 |4 R" q6 P4 J( p) z$ _) s
}
' Q6 r8 G1 A1 k' h# h //地址和校验字均相符后,解析功能码,执行相关操作
) d/ L- c( [) J1 ^+ g switch (buf[1])& p @9 r7 U, N4 Y, [8 {
{8 V) `) \6 L+ n( R. f) W) m
case 0x06: //写入单个寄存器3 u) l( G1 L' H `/ _7 J
if ((buf[2]==0x00) && (buf[3]<=0x06)) //地址0x0000~0x0006分别对应
8 K2 z! w& E# g! S" s& x' X5 g2 d { // “年/月/日/时/分/秒/星期”
; p4 D* P) L% b4 R6 s$ | GetRealTime(&bufTime); //获取当前时间0 E$ T7 Q$ ^" Y9 h4 ~
switch (buf[3]) //由寄存器地址决定要修改的时间位. ?. {. \1 }* @2 H, L5 M1 v2 x0 a0 I' i
{
' z& M" m ~: w9 S" X; G case 0: bufTime.year = 0x2000 + buf[5]; break;
' E1 f, h. N% r8 u1 y4 v case 1: bufTime.mon = buf[5]; break;
. s7 K+ \3 k* Z! P7 W3 v case 2: bufTime.day = buf[5]; break;5 [+ w7 O$ W- {5 O
case 3: bufTime.hour = buf[5]; break;
. q0 |& T7 N; ]) `/ { case 4: bufTime.min = buf[5]; break;- \* ]! O U+ o
case 5: bufTime.sec = buf[5]; break;! l- `, Q& r' d
case 6: bufTime.week = buf[5]; break;9 F: t% q5 P/ c) e% n5 N! N
default: break;% ^3 U }5 Z, X* u$ G4 L% A
}
2 I/ E: p9 s; v6 z4 E1 u SetRealTime(&bufTime); //写入新修改后的时间
* n) ^/ V. x- J$ D2 x5 b1 @ reqRefresh = 1; //设置显示刷新请求0 V9 K# e( j5 H2 ]
len -= 2; //长度-2以重新计算CRC并返回原帧1 ]+ ?) s% c) @4 [# ~( f& H
break;
I3 c; j3 Q# u- p) l4 \1 t }) m; D. A7 e7 x% T8 U
else //寄存器地址不被支持时,返回错误码
@- P4 ]' C( m6 h" F0 a$ N {8 C. t, g7 [- V+ L/ M
buf[1] = 0x86; //功能码最高位置16 h5 m- V& _+ L( h# i/ I$ M
buf[2] = 0x02; //设置异常码为02-无效地址
2 X5 b. A; H: X/ E8 ^) U len = 3;
0 P# @2 Z" U, i/ P9 t break;
2 b' r1 b3 ]" v. c* @- U; W- } }
' L/ y4 [. D" l, B7 X1 M$ T4 P1 S+ p; A" s2 e; { ^
default: //其它不支持的功能码
5 ~9 M' k; B7 J/ H: j buf[1] |= 0x80; //功能码最高位置1, v0 G; J. ^3 D
buf[2] = 0x01; //设置异常码为01-无效功能: M4 N- @1 u) O% x# Q
len = 3;
% S# \" E `" Q break; y2 J; ^8 C* }$ Z
}+ `% s7 N5 e8 ^% ~! U
crc = GetCRC16(buf, len); //计算返回帧的CRC校验值7 ]: n E2 |+ G$ }5 R' _6 ] ]
buf[len++] = crc >> 8; //CRC高字节
' d& n4 t* T7 a7 k, B$ Q& T- X buf[len++] = crc & 0xFF; //CRC低字节
- Q" Y r3 F2 ], a( U: I& R' y+ H6 S UartWrite(buf, len); //发送返回帧
, L# F4 F7 r' f! l8 m+ l}
, r4 `- w. V4 P& G1 u9 C/* 配置并启动T0,ms-T0定时时间 */
' p9 e1 B+ R2 Jvoid ConfigTimer0(unsigned int ms)
9 |% ]2 F6 O3 M! b, M{
! H; @% M0 w+ h$ h4 Z/ W unsigned long tmp; //临时变量
4 M( n/ c$ F* O
+ J9 @$ r! @- C7 b! V' x tmp = 11059200 / 12; //定时器计数频率
. R5 o4 S/ C9 U1 C6 _( k! F tmp = (tmp * ms) / 1000; //计算所需的计数值
8 t0 ~0 j0 l3 C. g tmp = 65536 - tmp; //计算定时器重载值
/ y" |2 M3 j& p R. D+ y6 h) V tmp = tmp + 33; //补偿中断响应延时造成的误差6 f, m) K0 H- v$ B- K
T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节, R2 g/ \: G6 Y( D# g+ S
T0RL = (unsigned char)tmp;
. q4 J: A `% w/ O5 \0 A/ L TMOD &= 0xF0; //清零T0的控制位
0 \) R& \6 p5 ^) u TMOD |= 0x01; //配置T0为模式1
! B3 g A$ k/ w0 W9 u TH0 = T0RH; //加载T0重载值/ i$ {% A) z7 W- N4 O7 y7 v
TL0 = T0RL;
' ]! Q$ @1 P* V) Z) z ET0 = 1; //使能T0中断
- t) s* T w+ ? TR0 = 1; //启动T0
0 `. w- N# |. @) C}
% P+ g3 z% J" n/* T0中断服务函数,执行按键扫描和200ms定时 */
+ e e, Q5 J7 E9 w3 l5 u7 d' S bvoid InterruptTimer0() interrupt 1. A2 r. g7 A8 V) f, W
{
2 L* w; V0 b8 B( C static unsigned char tmr200ms = 0;
6 u- G( D( {% x, U
3 t1 D! T4 m, \4 B) o; Q8 } TH0 = T0RH; //重新加载重载值
7 O. w* e0 u- p4 m) G4 p TL0 = T0RL;- e, r1 e9 L6 G8 ^4 p: {. l
UartRxMonitor(1); //串口接收监控
( Y9 `. Q- J* M8 B7 A4 t tmr200ms++;+ s: V7 ]5 E5 M. R" T
if (tmr200ms >= 200) //定时200ms. |, U' k: w7 w8 b9 P
{2 C* z* s$ V( ^/ r! n
tmr200ms = 0;* g; q6 b' l5 W
flag200ms = 1;
. r! `, Y9 x0 V+ C: \ }
3 W! g( D5 i1 k1 A5 z}6 ?6 b; I4 w: G ~1 ?
6 U5 S) G: B/ w5 i1 ^+ B4 A |
|