|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
RS485通信的特点
% ~% ^% n) Z4 H2 ^1 A. an1、采用差分信号。
8 J9 q, P( o% U3 Sn2、RS485通信速率快,最大传输速率可以达到10Mb/s以上。
1 I; L9 c# j! I4 C* v# I% V# Mn3、RS485内部采用平衡驱动器和差分接收器的组合,抗干扰能力大大增加。9 X/ _/ ^; J( G9 N( g
n4、传输距离最远可以达到1200米左右。
. F( p& P$ V+ `! D; Xn5、可以在总线上进行联网多机通信。
# o4 ^/ O, o& j: D( V4 h3 X) _: pn6、RS485接口非常简单。% j3 t: \6 e$ l9 S* R6 Y: y2 T
Modbus通信协议介绍
3 @0 e2 w/ s& u1 b vn1、Modbus产生的背景。
4 R- K, _+ `1 p" V) N, }n2、Modbus协议特点。1 U8 |$ [+ L$ Y2 O: {# d8 j M% s
n3、RTU协议帧数据
/ `3 F' Q8 f! `3 G/ {n4、Modbus功能码3 W& t/ ^/ n1 `7 |
/* 备 注:
. D, j+ j2 s- P2 m* 1、在lesson15_3的基础上去掉按键校时,添加lesson18_2中的Modbus协议支持
( O- E# {, D e9 ?, i( f* 2、利用Modbus调试精灵的写寄存器功能,可修改日期时间的每一个字节$ U% Z7 c# v' `8 y* }% ~7 l" a
* 3、寄存器地址0x0000~0x0006分别对应“年/月/日/时/分/秒/星期”
9 M( u4 y* Z% f1 P, D$ g* S* 4、RS485方向控制信号由原来的P1.7改为P2.0,因本例使用了DS1302而未使用按键7 J0 e* S2 w5 d1 }6 f
*******************************************************************************8 X; k1 T6 U3 H9 x
*/0 z7 V+ u& x2 k. U9 h( N8 V( z
. C. N! i3 [7 Q8 g' k E
#include <reg52.h>
; O, V- x4 M* i( t" O3 i/ B+ n5 x0 s; H8 \
struct sTime { //日期时间结构体定义
' t# T. Z& z. Y* D unsigned int year;. Q: ?7 B- u8 j7 B
unsigned char mon;9 i. X. W- H# z0 k
unsigned char day;* ~0 Y# x% b& g! W- p
unsigned char hour;9 h3 v7 ~2 R- t
unsigned char min;
" q: l+ N* B8 s0 E% f unsigned char sec;
" n1 |; O- g! |0 | unsigned char week;7 f# x. E/ f: v! c9 @& {
};8 t4 }; ~" y9 { O1 P; A! n
# A$ D' z% E* K& d0 v9 ?. dbit flag200ms = 1; //200ms定时标志
7 C. B$ G' n: ~bit reqRefresh = 0; //时间刷新请求: B) b# }+ `# i5 V
struct sTime bufTime; //日期时间缓冲区
4 n2 h8 L& y* g% n+ Runsigned char T0RH = 0; //T0重载值的高字节9 v% L u) j5 J* ^% y
unsigned char T0RL = 0; //T0重载值的低字节
" c% H8 c# _* V/ N6 v) w9 x( y2 t" \& Z, G/ i+ `
void ConfigTimer0(unsigned int ms);6 k8 C# \. C# e9 ?1 T. u: g
void RefreshTimeShow();
3 C' h; t5 J/ b& ?+ Y$ ]extern void InitDS1302();5 \/ D& Y2 K# X; T9 [8 v: O! E* W
extern void GetRealTime(struct sTime *time);! h8 x; o' D% f W" Y
extern void SetRealTime(struct sTime *time);; X* n" b, l+ r. P8 D
extern void UartDriver();
4 }" ?% O. h1 M( m' r# A4 Y8 ^% Eextern void ConfigUART(unsigned int baud);
1 i: ]1 ]7 Q, n4 c! p- q8 p' B3 iextern void UartRxMonitor(unsigned char ms);
: s1 D/ M) _5 q- ~extern void UartWrite(unsigned char *buf, unsigned char len);
- j, p/ h8 R7 b- \* {3 Uextern unsigned int GetCRC16(unsigned char *ptr, unsigned char len);. \) @- B( |- U8 R4 ~6 w
extern void InitLcd1602();
5 ^6 t* H; [( L) l6 _0 Vextern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
% z5 J& s0 D0 Z% j8 g* u
/ `4 F. b9 \1 evoid main()
3 I, K Z3 Q7 \# Z7 x) ^# ~{- d4 s8 T e* ?) T+ e
unsigned char psec=0xAA; //秒备份,初值AA确保首次读取时间后会刷新显示
& _& H9 G4 ]2 N7 I) p& h/ I3 d' z1 T& Y6 _
EA = 1; //开总中断
7 E, n a* Q4 F5 L9 G6 q ConfigTimer0(1); //配置T0定时1ms- b% D/ l" B. k1 O; a/ Y- }: H
ConfigUART(9600); //配置波特率为9600' q2 ^; z7 }' R. v5 C2 d2 ~
InitDS1302(); //初始化实时时钟9 h" G; N) \$ {( U3 B# |+ k& u
InitLcd1602(); //初始化液晶% |0 e- y: e7 `7 z+ z3 N
7 \( G h3 x; C) E. Q( }* D( S6 x //初始化屏幕上固定不变的内容& ^1 r- v1 N: V/ N
LcdShowStr(3, 0, "20 - - ");
2 d# {8 A5 K9 u2 H LcdShowStr(4, 1, " : : ");
0 f( ^5 y# Y I2 e; {. e4 |* Y3 ~6 e
while (1)
) v T. Y' v5 m% b {7 w! @. q& n/ [# m: \ _1 A
UartDriver(); //调用串口驱动
+ D1 `7 I5 |/ \' n% V8 f if (flag200ms)
0 T; m. Q5 ?1 H& q9 M6 f {
, {" ? i$ t7 z A4 q flag200ms = 0;# G* C; s$ R# @6 P+ D
GetRealTime(&bufTime); //获取当前时间
9 W# Z5 ^: R+ T if (reqRefresh || (psec!=bufTime.sec))' U: J) b" k# g, R, j
{ //检测到时间刷新请求或时间有变化时刷新显示$ o- T0 P' p4 U' n3 l
RefreshTimeShow();" P2 W3 P8 i+ a5 B, [
psec = bufTime.sec; //用当前值更新上次秒数% @$ M) i" r4 K$ T7 t" m' C
}
" |( h( H, U5 [ }
4 w7 ^1 |" ]) W2 C: F7 Z$ z }
, s5 `0 z2 b8 m}
( H1 H( ?3 S6 R3 {. f4 c/* 将一个BCD码字节显示到屏幕上,(x,y)-屏幕起始坐标,bcd-待显示BCD码 */5 T, g8 k% O/ o7 C y# j
void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd)
) D1 h! t$ K# \3 d3 w{
: p" F- k7 W" N t' _9 Y# ]" s unsigned char str[4];
, G, }* f1 ]' d' S! H1 t
1 J! K: j3 j3 I7 z str[0] = (bcd >> 4) + '0';+ z* `7 H0 N& y4 \2 }
str[1] = (bcd&0x0F) + '0';
/ ?) I2 t, Y6 }: j/ i9 k str[2] = '\0';
; a j6 }& R$ w/ [0 i* U6 v! ^ LcdShowStr(x, y, str);
' @' T$ y8 K L) S3 l& b" D1 G+ a}
& h# }5 a; n* s- L. C/* 刷新日期时间的显示 */
0 y6 t9 j& Q( {. S$ p" |void RefreshTimeShow()
# s2 U! l" T% {* y+ D/ C. s& a{
& s4 E& X6 y# W ShowBcdByte(5, 0, bufTime.year);
+ o* u% @" n' u7 w, `8 l# |; K ShowBcdByte(8, 0, bufTime.mon);
( ~# X* I: c7 t0 f" o& } r/ c ShowBcdByte(11, 0, bufTime.day);0 _ E7 s# Q) M% ?! J
ShowBcdByte(4, 1, bufTime.hour);6 s w/ w/ B6 `% Z/ }/ W1 ] ]
ShowBcdByte(7, 1, bufTime.min);
P7 |4 s1 v( z2 ? ShowBcdByte(10, 1, bufTime.sec);
' B: ?; X; F4 A. e* I9 h! O" l}
( e+ D* X7 F/ v9 Z/* 串口动作函数,根据接收到的命令帧执行响应的动作
% q N1 d) ] m( U' h& F/ R buf-接收到的命令帧指针,len-命令帧长度 */1 ^) z; g/ \2 G' r3 v6 k, o; P
void UartAction(unsigned char *buf, unsigned char len); P3 e' y6 |$ Y/ `" s" L/ M6 N
{
* ~9 H( `5 B3 n0 [$ | unsigned int crc; L$ @6 ?( B" C8 S- |" j4 u
unsigned char crch, crcl;
H/ j, z$ ]) ]3 b: Q3 n# P: a, D# \4 q8 E2 m# c8 r1 ?
if (buf[0] != 0x01) //本例中的本机地址设定为0x01,
N* J( k9 G7 l% N% m { //如数据帧中的地址字节与本机地址不符,
( P- w% E2 T$ W: y) Y return; //则直接退出,即丢弃本帧数据不做任何处理$ I! g8 I- _; Q! _( ]* E7 o0 U- Y
}* g( f/ H0 [% A3 Y* G
//地址相符时,再对本帧数据进行校验: H* u2 Z1 c T8 e, h
crc = GetCRC16(buf, len-2); //计算CRC校验值% o- E/ g ?# T q( A8 u L
crch = crc >> 8;
; I% W5 C% N8 d3 P- E6 g2 ?2 Y crcl = crc & 0xFF;6 I4 c2 _ H) W; B0 _# ^2 ~8 n6 n
if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))2 F& {6 B& O& Z! L4 _2 P4 E: \
{
: a7 b; V& t! j, o8 z+ s return; //如CRC校验不符时直接退出
- \5 }, S0 U5 r0 z) k0 n }7 x" ^4 g- G D1 d# K- L# x
//地址和校验字均相符后,解析功能码,执行相关操作
# u4 Y: [* C8 v switch (buf[1])# d( ]: T0 c, Q3 u& }
{4 n7 r1 \- J' ^# D6 d, f
case 0x06: //写入单个寄存器
! \; ?9 n6 Q. J2 c: D" I if ((buf[2]==0x00) && (buf[3]<=0x06)) //地址0x0000~0x0006分别对应
2 \) E: H' V: N { // “年/月/日/时/分/秒/星期”
, w8 @) \8 \ i% ^' p/ { GetRealTime(&bufTime); //获取当前时间
, o0 x, Z8 ~4 Y2 G! d( G switch (buf[3]) //由寄存器地址决定要修改的时间位* b6 W r9 c! I6 ^/ f- F: o
{
4 O6 W4 @9 w0 S3 q4 N" Y( f0 _# X case 0: bufTime.year = 0x2000 + buf[5]; break;& h; F; V3 Q2 [" `7 c
case 1: bufTime.mon = buf[5]; break;
3 A0 ~7 j3 s* K& S case 2: bufTime.day = buf[5]; break;- S$ u! F4 U# n1 \1 }
case 3: bufTime.hour = buf[5]; break;4 g' x, ]% D, L
case 4: bufTime.min = buf[5]; break;
. i5 T' T6 k6 N% e7 ? case 5: bufTime.sec = buf[5]; break;
4 O1 z( H, ]& k3 | case 6: bufTime.week = buf[5]; break;
$ L! t9 @6 a3 U! p default: break;
- X5 J/ T! e+ y0 s! y$ ^ }( O5 G" c/ i5 f% y" G: V$ q( ~
SetRealTime(&bufTime); //写入新修改后的时间
: M, l! o+ c; R0 d/ l! j2 Y reqRefresh = 1; //设置显示刷新请求% |, ]2 d' k+ x6 J; u
len -= 2; //长度-2以重新计算CRC并返回原帧: i8 y" V1 E8 p+ o
break;' Q9 Y3 i" k, j$ ?( ?
}$ r; l, d6 m, O: q% U4 e9 g
else //寄存器地址不被支持时,返回错误码
& d1 _& W% n7 f7 O8 [) U; N2 y1 u {
+ y: t( |9 P$ C) V! R9 g buf[1] = 0x86; //功能码最高位置1 e" k( U8 G0 s# O) _
buf[2] = 0x02; //设置异常码为02-无效地址) m+ d2 J% T e% O: R9 a& J5 _
len = 3;
0 V: J: j0 d6 F+ U' ^- |; G3 A: C2 h break;
" t. V0 z: o! X' q$ J+ D }2 |$ k7 E5 W: W8 x# H4 L/ `7 E2 m, H
7 x' H3 N1 x {' B. ]) b6 A
default: //其它不支持的功能码7 a! [ ]/ @! f0 s5 S; n
buf[1] |= 0x80; //功能码最高位置1( ^ v4 ^% u& a
buf[2] = 0x01; //设置异常码为01-无效功能
2 e/ @ E- P X" B" Q len = 3;; O8 r. W9 N( H4 }' U
break;
+ |) ^* N- q3 V/ R6 P* F }
% o, S8 x* s+ n" D, W, ^ crc = GetCRC16(buf, len); //计算返回帧的CRC校验值$ c$ p; J* h; v0 o1 S' c
buf[len++] = crc >> 8; //CRC高字节
! m% T( h/ V) d buf[len++] = crc & 0xFF; //CRC低字节
" p$ |2 V; B, B& [& w/ W' s9 w UartWrite(buf, len); //发送返回帧
/ z2 S$ q! q% g, l1 R6 `( @% N+ Q}
$ t0 ~0 e7 e2 {* V R. Q" y1 [% f/* 配置并启动T0,ms-T0定时时间 */8 F0 ]; W- b5 z) q1 E
void ConfigTimer0(unsigned int ms)) x2 U) h; W, Y1 e' T* Z" T9 H
{2 ^1 C& v, |1 p1 Q- U
unsigned long tmp; //临时变量
" A# F% C; r+ g& @# M& u% a4 c; ]' R
5 b7 M" H2 i3 M, y tmp = 11059200 / 12; //定时器计数频率
$ z7 z ?! B3 Q( z, o8 b9 b8 t6 p$ L tmp = (tmp * ms) / 1000; //计算所需的计数值
% x `; H' R. Q4 y4 ^ tmp = 65536 - tmp; //计算定时器重载值, ^% Y' q+ ]# D
tmp = tmp + 33; //补偿中断响应延时造成的误差
( B& j# T4 \4 [" R0 |" z T0RH = (unsigned char)(tmp>>8); //定时器重载值拆分为高低字节
5 S3 H6 z+ b% ` T0RL = (unsigned char)tmp;
' M9 x S7 O, f: `3 }; J TMOD &= 0xF0; //清零T0的控制位
2 T0 u' I# P ~% @: f6 m U TMOD |= 0x01; //配置T0为模式16 N) G! M1 k4 h
TH0 = T0RH; //加载T0重载值: ~( m5 R+ s9 D5 _
TL0 = T0RL;
$ t7 S$ O3 t5 m+ ^6 X ET0 = 1; //使能T0中断1 ~" {, Z5 L u$ a( L
TR0 = 1; //启动T0: W. V7 A: `9 { \5 y9 M7 l
}6 R+ n8 `; s3 N0 m: ]1 f
/* T0中断服务函数,执行按键扫描和200ms定时 */9 T) L/ l2 j+ ~( G$ y2 o* n
void InterruptTimer0() interrupt 1
9 l* o* M2 l1 @ Z2 p{
. p% w" ~4 x5 u" V7 m2 D static unsigned char tmr200ms = 0;
& T# r. y' B$ [4 U$ B: U% f- d9 L( |5 k
TH0 = T0RH; //重新加载重载值3 F" V$ s! p$ F! S
TL0 = T0RL;/ G- O6 I% [1 c% u* K& J& k
UartRxMonitor(1); //串口接收监控 w; g* x$ u3 X
tmr200ms++;7 M% N5 [. c! o
if (tmr200ms >= 200) //定时200ms) y2 |; _/ I" S1 I2 e
{. N6 Q1 t" O( i$ n; R
tmr200ms = 0;, s( {# B, l6 l$ d
flag200ms = 1;
* _ w* `7 H) |+ R+ _9 J7 y' a0 l5 O }
+ g( Y" @7 g: n3 l}
: X2 b) C1 N5 t; o; _6 H* y' Q) p% d2 Y7 V* A: Q/ {6 N) [
|
|