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

单片机RS485通信与Modbus协议

  [复制链接]

该用户从未签到

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

EDA365欢迎您登录!

您需要 登录 才可以下载或查看,没有帐号?注册

x
RS485通信的特点  v0 E4 [5 p( X+ v& }7 ^
n1、采用差分信号。7 I+ U9 }8 I4 L, @; x1 g+ G; C2 R
n2、RS485通信速率快,最大传输速率可以达到10Mb/s以上。8 _# U9 j5 V7 {+ H2 }$ x
n3、RS485内部采用平衡驱动器和差分接收器的组合,抗干扰能力大大增加。
* J! a  ~9 e7 }+ ~) {n4、传输距离最远可以达到1200米左右。: F# O4 @# x2 `* z
n5、可以在总线上进行联网多机通信。: N; i9 c% @4 ?- t5 x1 u
n6、RS485接口非常简单。
$ f( h7 \/ c+ t& `; d  T& iModbus通信协议介绍
7 N/ G# \7 |. l# I. n3 T! b! C0 sn1、Modbus产生的背景。
" Y- r9 R% p3 H1 B: Ln2、Modbus协议特点。
$ q/ C5 F  o. |' on3、RTU协议帧数据
! p6 X* a0 u. u/ L0 V- J9 Wn4、Modbus功能码1 W! K# y# {5 Z6 V, y7 }0 L8 b
/* 备  注:
" U8 S3 f; S: D8 X1 s4 T, d* 1、在lesson15_3的基础上去掉按键校时,添加lesson18_2中的Modbus协议支持8 t8 i! F0 U! @, d# o
* 2、利用Modbus调试精灵的写寄存器功能,可修改日期时间的每一个字节
$ W4 c6 N  z5 R( _0 W- M. v" a* 3、寄存器地址0x0000~0x0006分别对应“年/月/日/时/分/秒/星期”
) N- Q: _  [- `* V# K' l, E* 4、RS485方向控制信号由原来的P1.7改为P2.0,因本例使用了DS1302而未使用按键( i" v- W( o( D2 p
*******************************************************************************% S+ w$ r+ A$ Z$ O
*/
$ X! V3 q" d8 a5 Q
" t4 g4 J$ s* A#include <reg52.h>7 V( I6 r7 ]' X3 r- E) e
( P# p- E! E$ ^7 w
struct sTime {  //日期时间结构体定义0 a  W3 g  M& I; M
    unsigned int  year;
1 p7 j5 O4 y# Q2 P7 b4 `    unsigned char mon;
" H$ X, }6 L1 V: S/ W$ }    unsigned char day;
5 Q! X# W& j* q0 B2 i* K    unsigned char hour;: c$ V' _6 k6 ?& T8 K* N
    unsigned char min;
4 ]/ U" ?6 ?7 h    unsigned char sec;
1 R. ?. @& r+ N$ g# V0 X* k    unsigned char week;) a9 I! V' C' j( U+ z' H
};
$ L$ Y; }1 Q$ a8 U& y& K$ o
0 m6 `/ d; q) tbit flag200ms = 1;  //200ms定时标志
1 m# l3 k: O9 t& gbit reqRefresh = 0;  //时间刷新请求3 Q% L- B  O* Y. j; d- O
struct sTime bufTime;  //日期时间缓冲区
$ m2 @8 x4 I" o# U& `' D4 Sunsigned char T0RH = 0;  //T0重载值的高字节
) z7 }, j) `9 j# h% b, l; }0 |, punsigned char T0RL = 0;  //T0重载值的低字节
! l2 l- _( v: y3 V( ]5 C
7 n7 y7 v& }( p/ W5 \) q! m- M& t4 Uvoid ConfigTimer0(unsigned int ms);3 @) N$ I: k5 \
void RefreshTimeShow();! t" {& V' ^% Z/ y; @. o
extern void InitDS1302();$ S" X7 i/ {" l. G" @# l1 C
extern void GetRealTime(struct sTime *time);
3 `5 {% u5 ]2 u7 K; x0 Q5 T0 Lextern void SetRealTime(struct sTime *time);
; p$ Q+ \4 U* j: q% P* Z/ Cextern void UartDriver();- t6 R! T) d+ }7 o, P
extern void ConfigUART(unsigned int baud);
. r8 `/ ^$ v% n& aextern void UartRxMonitor(unsigned char ms);$ x8 N! w7 ]. f1 r$ ?, l0 T
extern void UartWrite(unsigned char *buf, unsigned char len);4 w( x! f5 m# Z" m2 O( d4 A! H
extern unsigned int GetCRC16(unsigned char *ptr,  unsigned char len);* P* @2 W2 I8 X5 N
extern void InitLcd1602();
/ B" _) ~: T1 H5 h0 jextern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);
$ r4 T$ V/ X5 c2 D+ r# {+ e" G1 b  }2 |/ d
void main()
# v- h% S" a$ l1 D- q3 h{
1 c3 T2 X" k# y: B3 c6 B    unsigned char psec=0xAA;  //秒备份,初值AA确保首次读取时间后会刷新显示
' |) D. Q  ]; B# @8 H4 l2 V2 i+ F7 ~
    EA = 1;            //开总中断
2 A; r' M4 d* i+ ?: s, g    ConfigTimer0(1);   //配置T0定时1ms* _$ r, g' _; J" g+ M4 ^) j% T
    ConfigUART(9600);  //配置波特率为9600$ Y" i; _9 L3 ?; n
    InitDS1302();      //初始化实时时钟0 x4 g& f8 V* n
    InitLcd1602();     //初始化液晶
) x3 H# l6 M7 `8 v9 r  O; A; `9 M8 O# f4 K. c% i' ^) V
    //初始化屏幕上固定不变的内容, r7 Y0 d8 t: U
    LcdShowStr(3, 0, "20  -  -  ");$ U' C5 ^& W8 a. b$ ?
    LcdShowStr(4, 1, "  :  :  ");6 p$ W" d* Z8 {3 d! m9 ^9 J

# Z$ k% A; _7 p& w! E& J    while (1)
/ j; s& b5 w2 r' k. u1 b" s6 ]    {
8 t* U- j* P7 ~# q% H+ |5 ], P        UartDriver();  //调用串口驱动, R  @% g! F7 M. @( w
        if (flag200ms)
. ?+ }% R; Z7 A1 t        {/ e: S1 P8 p" e+ O* o* C3 j. t
            flag200ms = 0;7 U' I2 m( c. ^: J7 A
            GetRealTime(&bufTime);    //获取当前时间  U# J/ ~4 }: e6 W! _
            if (reqRefresh || (psec!=bufTime.sec))0 X+ m: E9 w( z9 `! {* P
            {   //检测到时间刷新请求或时间有变化时刷新显示4 z+ G) `7 U# x. y- e
                RefreshTimeShow();7 g9 U+ e1 G  k0 o( F: @' c+ T& J
                psec = bufTime.sec;   //用当前值更新上次秒数2 ]- X% `  d" h  e4 ~( n% \
            }+ h! w7 ~+ z* K. j' j+ o6 O5 R
        }+ n, f2 q& H. |% u# O
    }
, h% H1 u7 n+ q. |( I. h; }  Q}
9 d) w8 x- G9 C3 z; L" M/* 将一个BCD码字节显示到屏幕上,(x,y)-屏幕起始坐标,bcd-待显示BCD码 */0 v4 u( U; }5 q6 F; o; Y
void ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd)
5 s1 W# V$ c2 ~6 }$ q4 w7 }{
7 R) N: Y5 o$ Y& x    unsigned char str[4];
  @- I- P; n5 X  H8 h: P8 X* P
9 a7 W, X/ M. J' H    str[0] = (bcd >> 4) + '0';1 ^  p5 b6 `1 ?$ q) m- a
    str[1] = (bcd&0x0F) + '0';
8 K+ X' r7 C$ S* T4 h    str[2] = '\0';' F. B  O: b+ Y/ _
    LcdShowStr(x, y, str);  M; _+ w) k- a6 p2 S$ X7 D
}
8 V; i6 s6 w+ X# q/* 刷新日期时间的显示 */
$ @' X8 Y5 T6 m& s$ J* svoid RefreshTimeShow()
' t. X+ |3 _4 ~2 D' A  t, @{4 A" }$ ~$ U6 s& S5 c
    ShowBcdByte(5,  0, bufTime.year);2 {6 r: |5 m& |5 [' E
    ShowBcdByte(8,  0, bufTime.mon);
$ l" B* l) w8 \4 k    ShowBcdByte(11, 0, bufTime.day);+ }2 E7 v, e5 v, v2 Z
    ShowBcdByte(4,  1, bufTime.hour);0 @8 v) ]8 `+ h( K! w  [
    ShowBcdByte(7,  1, bufTime.min);! M9 y) t  m6 @# g9 {
    ShowBcdByte(10, 1, bufTime.sec);
6 S  p% t% C  W# ]}
  ^$ o% \) `7 v# H. K0 L% n$ G& |7 f* f/* 串口动作函数,根据接收到的命令帧执行响应的动作0 v, U6 k; I/ N  B8 p/ f
   buf-接收到的命令帧指针,len-命令帧长度 */
' I5 i5 k% ~) O- X; ~0 a5 G* D- svoid UartAction(unsigned char *buf, unsigned char len), h# u& q0 W" r7 S  r4 r
{
9 [- Y% i. H' ~/ p; i3 l    unsigned int  crc;% O1 T" T  R/ H+ X
    unsigned char crch, crcl;
' m# P% Y' D+ }0 I6 K* ]. g3 _9 }4 p; W1 `; R  K" ~# p* n
    if (buf[0] != 0x01) //本例中的本机地址设定为0x01,
' q( q7 ^6 u: S" |    {                   //如数据帧中的地址字节与本机地址不符,, U& J6 l* {8 b/ e, b; Z7 O
        return;         //则直接退出,即丢弃本帧数据不做任何处理
$ B* |2 s0 S# `9 R% k% D  s& Y    }, L  E( M& V, M8 H& U  p6 c- e
    //地址相符时,再对本帧数据进行校验
6 E' O" ?" m( t/ c    crc = GetCRC16(buf, len-2);  //计算CRC校验值; ^, l8 ]: \* W; P6 p
    crch = crc >> 8;
. _" ~. B. O4 t% q4 t    crcl = crc & 0xFF;# a+ }; G; C# K! _
    if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
  d6 @1 g4 A6 O: B+ S    {  l7 z" u$ s$ E8 G
        return;   //如CRC校验不符时直接退出
& P9 m# j$ W; x; I# W- `; C/ ?    }) y9 {9 v# ]7 [+ b
    //地址和校验字均相符后,解析功能码,执行相关操作5 m# j. t5 z! K+ c9 p/ C. y
    switch (buf[1])2 f" N; P; f$ [2 c* i5 O
    {
- D8 D7 M. ]6 W/ i& Q: a        case 0x06:  //写入单个寄存器  z! {  H1 d  K5 I
            if ((buf[2]==0x00) && (buf[3]<=0x06)) //地址0x0000~0x0006分别对应
; Y. `2 v$ Q8 U            {                                     // “年/月/日/时/分/秒/星期”
& Z: ?; w6 T! D; [) `, X* _                GetRealTime(&bufTime);  //获取当前时间
# B2 x3 O3 [2 V, V2 r4 |; Z' G( S                switch (buf[3])  //由寄存器地址决定要修改的时间位
  a5 E; m/ M$ q/ w) C1 u* b                {: L8 E, c& p9 R0 U* b
                    case 0: bufTime.year = 0x2000 + buf[5]; break;: q" v2 O% Y. K8 R4 ^: }7 g
                    case 1: bufTime.mon  = buf[5]; break;- h  c1 U/ \6 m4 L
                    case 2: bufTime.day  = buf[5]; break;) V+ }+ i( I+ B- {( j
                    case 3: bufTime.hour = buf[5]; break;
) E- f4 V3 G( _4 ?7 V                    case 4: bufTime.min  = buf[5]; break;7 n! i! A) g5 k5 b! K8 F: A" D
                    case 5: bufTime.sec  = buf[5]; break;
4 G% J% G7 q# O/ K                    case 6: bufTime.week = buf[5]; break;
$ I$ L" f3 d' D& N                    default: break;
( N' {1 e9 K  T! i$ u) S  {" B1 {                }
: j- k4 m! z; ]                SetRealTime(&bufTime);  //写入新修改后的时间
% e9 a. o. J1 Y9 p                reqRefresh = 1;  //设置显示刷新请求! l+ v) x( R3 T$ _
                len -= 2;  //长度-2以重新计算CRC并返回原帧
6 _9 v0 P) v, _                break;
$ j8 o5 I/ t0 N3 E% N0 C            }
5 n9 \& r3 ~7 \5 H7 ]7 x            else  //寄存器地址不被支持时,返回错误码8 b1 g- Y! ^* i/ e, l3 u7 n* Y
            {8 B+ |6 ?% ?9 t. H: ]) w
                buf[1] = 0x86;  //功能码最高位置1
- |* M" ^9 P0 Q$ @                buf[2] = 0x02;  //设置异常码为02-无效地址, Y* A( l. C9 @! B8 r
                len = 3;
% O; P7 u# A$ A8 j                break;
0 M' @- z# b* @. e  [            }2 X; p$ z9 @4 ^& P; `

' d' o. Z/ d& B0 U        default:  //其它不支持的功能码
& \, U* X4 p! z# D/ k' m# f, _            buf[1] |= 0x80;  //功能码最高位置1, L( W  q% W& }% M5 w" ]; T; D
            buf[2] = 0x01;   //设置异常码为01-无效功能
' Z5 u4 T5 D. N( ]* Y            len = 3;
) K( i1 j3 Y/ a: r% X- O            break;
1 M1 n$ _2 y& u9 U( S: @3 c! ^    }
+ S( B' T$ v$ x2 ?$ |) Y+ X    crc = GetCRC16(buf, len); //计算返回帧的CRC校验值" A  j* t: Y3 L
    buf[len++] = crc >> 8;    //CRC高字节3 }2 F7 H7 P) X
    buf[len++] = crc & 0xFF;  //CRC低字节
, K* e& Q* N& T- ~; _; B3 K3 d    UartWrite(buf, len);      //发送返回帧: _: ]+ N( g0 O7 w) x! q
}
; Q8 E1 G# [6 [7 [: h2 I- E: `/* 配置并启动T0,ms-T0定时时间 */! ~5 G% ~1 S6 C: X( H
void ConfigTimer0(unsigned int ms)
( r7 P! I8 x  s6 Q2 @0 }" p; M{
4 s2 `, i3 a0 r) S9 p    unsigned long tmp;  //临时变量% t. v6 d. H% E4 j( q$ q. M
8 v2 [1 U9 P- g0 [4 y% ?
    tmp = 11059200 / 12;      //定时器计数频率) N8 R" D: j; J' W
    tmp = (tmp * ms) / 1000;  //计算所需的计数值
  s3 c# U' [$ A    tmp = 65536 - tmp;        //计算定时器重载值
3 Z: G" Z+ `2 G6 I    tmp = tmp + 33;           //补偿中断响应延时造成的误差
+ o- q) l  L$ ?9 g$ R9 a    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节) T8 P8 w2 g1 W+ g# m
    T0RL = (unsigned char)tmp;
1 p& d0 ^. K6 O: L6 z# r1 P0 k    TMOD &= 0xF0;   //清零T0的控制位
- W8 q/ z/ r1 |7 F, V( A) @2 V    TMOD |= 0x01;   //配置T0为模式1% o/ K) A- Q, T1 t
    TH0 = T0RH;     //加载T0重载值
2 X) p7 J6 r3 H& q  m    TL0 = T0RL;
% z2 w8 I: I  l    ET0 = 1;        //使能T0中断
0 Z! ]& Q( r* j" l& o    TR0 = 1;        //启动T0
5 o- Y, g) J' v3 q- B}$ U$ R# p+ y2 v2 y, T- ]
/* T0中断服务函数,执行按键扫描和200ms定时 */
& s1 l5 X% L% t9 q( [% U1 ^void InterruptTimer0() interrupt 1
8 U! T, H6 L; M" @{
0 v1 e9 t& n1 R9 l% I    static unsigned char tmr200ms = 0;
+ F3 n8 C: v5 ^9 H  L  m$ w1 t9 l9 c* r- x9 w
    TH0 = T0RH;  //重新加载重载值* a5 O" G  L" [/ X
    TL0 = T0RL;' i- ~! }5 \: D% t/ d
    UartRxMonitor(1);  //串口接收监控
  _$ y0 M- O* R0 E    tmr200ms++;+ W; A' M/ U) W3 S5 h/ M
    if (tmr200ms >= 200)  //定时200ms
  L+ x# P, f# _# u+ M* b; }- z. j7 I    {
/ v# D' _1 i, H! V        tmr200ms = 0;+ z) t3 V8 u% @, S+ T
        flag200ms = 1;% d) f6 {8 d- n4 f
    }
) p" T/ M8 c  X' q* u. K0 ]}6 d, ?2 ^8 \, Q

* `/ `) S2 y" |/ G* J3 y

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-8-4 21:28 , Processed in 0.125000 second(s), 23 queries , Gzip On.

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

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

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