找回密码
 注册
关于网站域名变更的通知
查看: 198|回复: 1
打印 上一主题 下一主题

单片机RS485通信与Modbus协议

  [复制链接]

该用户从未签到

跳转到指定楼层
1#
发表于 2022-6-14 10:15 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

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

该用户从未签到

2#
发表于 2022-6-14 11:10 | 只看该作者
(⊙o⊙)…,看一看,看一看。。。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

推荐内容上一条 /1 下一条

EDA365公众号

关于我们|手机版|EDA365电子论坛网 ( 粤ICP备18020198号-1 )

GMT+8, 2025-6-18 19:50 , Processed in 0.078125 second(s), 23 queries , Gzip On.

深圳市墨知创新科技有限公司

地址:深圳市南山区科技生态园2栋A座805 电话:19926409050

快速回复 返回顶部 返回列表