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

单片机RS485通信与Modbus协议

  [复制链接]

该用户从未签到

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

EDA365欢迎您登录!

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

x
RS485通信的特点! M! ~% W9 _- X9 P7 |+ Q
n1、采用差分信号。; W  W$ w# H# K5 }
n2、RS485通信速率快,最大传输速率可以达到10Mb/s以上。
7 P1 z  }: k/ b4 a+ ~3 fn3、RS485内部采用平衡驱动器和差分接收器的组合,抗干扰能力大大增加。
# {. U& o, M& Q% Z- \n4、传输距离最远可以达到1200米左右。6 C! ~0 G3 d; x# X( i9 s
n5、可以在总线上进行联网多机通信。
- m+ N  e; J4 Z$ kn6、RS485接口非常简单。
. i" W9 R+ Z) Y% F- UModbus通信协议介绍
. P( U5 _" v) ~$ ?n1、Modbus产生的背景。: T" n$ q% m# Q: ^# P. {
n2、Modbus协议特点。
, H0 Y4 e+ W9 K  Z$ C# i+ J& kn3、RTU协议帧数据
& u( V3 @* x" M/ q3 y1 C/ l6 Bn4、Modbus功能码
9 e' X! {& i2 |7 P! ]/* 备  注:
  Z& g% |% j& S; y* @# N- x* 1、在lesson15_3的基础上去掉按键校时,添加lesson18_2中的Modbus协议支持; ?. O" J6 j, v4 p$ G3 k
* 2、利用Modbus调试精灵的写寄存器功能,可修改日期时间的每一个字节0 O' H% ^  E1 L$ P
* 3、寄存器地址0x0000~0x0006分别对应“年/月/日/时/分/秒/星期”
, r8 ~4 H* [- l4 H3 V* 4、RS485方向控制信号由原来的P1.7改为P2.0,因本例使用了DS1302而未使用按键
" q2 T& H9 Y. o. f* {. Q*******************************************************************************( D+ ?2 k8 j% A- S
*/
6 |6 g2 w" L$ W/ }
7 w) o6 k( a( J# }: L#include <reg52.h>; J: v2 ^  _; t: U1 y. n
, |0 ^$ T' ~( v8 v- \5 o- g
struct sTime {  //日期时间结构体定义
  a# d7 d/ F: A( C. `% H) B    unsigned int  year;
7 t0 S' U5 f1 Q6 e    unsigned char mon;
3 \( B- x0 i, |$ m% `4 P    unsigned char day;0 @& N' r( X) I* [& k* Q8 |
    unsigned char hour;& p1 j& l0 |# }
    unsigned char min;
, R4 ~; I7 l% d/ N% j, V& J    unsigned char sec;" I; o. i* v- S* L0 l: V+ [* s; X; u, w
    unsigned char week;! `9 P" y* s, Y1 F0 C2 s
};. P& k7 t$ D9 N9 q  r
' h( C; C* o6 @1 {6 r9 w
bit flag200ms = 1;  //200ms定时标志6 L- w/ Z: u5 ]5 L3 b. Q& Q
bit reqRefresh = 0;  //时间刷新请求( i* V2 ~- M: z, J8 K6 K4 @  L
struct sTime bufTime;  //日期时间缓冲区
9 i% |- \% B" J" vunsigned char T0RH = 0;  //T0重载值的高字节
3 u% o3 q( {8 f. E) N" junsigned char T0RL = 0;  //T0重载值的低字节
' `+ |! z, H3 @7 D( V9 N- I" L/ G& p9 H( {2 j- b" t5 H4 R( K( m
void ConfigTimer0(unsigned int ms);
* D- G6 g0 r" a- V- B  mvoid RefreshTimeShow();. x/ q# t+ P4 l
extern void InitDS1302();/ G9 n3 _& U7 [+ I
extern void GetRealTime(struct sTime *time);) A8 Y0 a+ b- \# j
extern void SetRealTime(struct sTime *time);! k$ C+ q0 L5 l( Z
extern void UartDriver();
) Y& R* P& m9 L" J& d) C; R! a1 P' Oextern void ConfigUART(unsigned int baud);0 m: d/ q' b* S6 \+ O
extern void UartRxMonitor(unsigned char ms);
7 G* W9 E4 t2 R3 l" mextern void UartWrite(unsigned char *buf, unsigned char len);0 a9 J3 N: [. J# d$ \
extern unsigned int GetCRC16(unsigned char *ptr,  unsigned char len);
2 B4 T4 K' s& sextern void InitLcd1602();  q/ Y' D9 Q) u. n
extern void LcdShowStr(unsigned char x, unsigned char y, unsigned char *str);& v, f, c3 z( T; k7 p) ~7 O

! V$ v* G, V! R% dvoid main()5 \, ?7 [: d5 S
{' R" I# E# c! R  ^: i- y( [7 N3 n
    unsigned char psec=0xAA;  //秒备份,初值AA确保首次读取时间后会刷新显示7 ~/ \8 j+ H" ]( ?! p, T+ \: s' \
# H2 f7 ~9 r+ }1 F$ D# g
    EA = 1;            //开总中断# L/ [7 V$ B, m# h' x
    ConfigTimer0(1);   //配置T0定时1ms( _/ F2 u* r- q  v% p
    ConfigUART(9600);  //配置波特率为9600
0 Z6 V" u4 t% I  W: P    InitDS1302();      //初始化实时时钟
( e# [$ Q7 a+ @* C  F3 x7 k" K    InitLcd1602();     //初始化液晶) k/ B; O+ P. W  ]3 q, K

0 ^: z! g2 p; r& f- ~    //初始化屏幕上固定不变的内容
0 J" [4 ?# ?$ [1 c2 y5 U    LcdShowStr(3, 0, "20  -  -  ");
! f( q) l) N4 A* S) ?    LcdShowStr(4, 1, "  :  :  ");
" U, t+ s' ]5 c1 t
+ e# N9 z8 V* b    while (1)" ], K: ]# L2 z( x8 v
    {
; h4 t. g+ R' g2 J  ^        UartDriver();  //调用串口驱动
4 q% b; n- Z8 i7 L        if (flag200ms)
3 H1 o9 @6 y' |" G1 v        {( m* o8 \' u( C5 D$ k0 X9 T/ t3 D
            flag200ms = 0;3 \! a8 A3 @) N9 D
            GetRealTime(&bufTime);    //获取当前时间; w6 r1 J$ R% V
            if (reqRefresh || (psec!=bufTime.sec))
  Q' q' z5 \7 F+ O9 j* R# `            {   //检测到时间刷新请求或时间有变化时刷新显示
7 ~% g  s1 y0 {! Q) h6 i% K9 d                RefreshTimeShow();
0 _2 b% T# G5 P4 x1 C                psec = bufTime.sec;   //用当前值更新上次秒数5 W2 Z* \9 P4 R2 n9 `. V9 a$ B
            }
. L9 |: h3 `' L! F- `. A        }5 m6 R. r/ w& S4 A7 G
    }
/ z" d2 Z* ~4 Y- r+ W' P7 Y}
  [0 l; |* |$ J" Y3 ~/* 将一个BCD码字节显示到屏幕上,(x,y)-屏幕起始坐标,bcd-待显示BCD码 */
$ |0 S6 F# f! avoid ShowBcdByte(unsigned char x, unsigned char y, unsigned char bcd)
( p  ^& J; `, T5 X! n" c{
0 @0 j3 q8 ?* k+ y5 z% v    unsigned char str[4];
3 N4 h, x' p6 }  ^6 m8 c/ j3 d& p3 V
    str[0] = (bcd >> 4) + '0';
5 u# [5 u, H; o3 u4 b    str[1] = (bcd&0x0F) + '0';; t2 U  _. U7 K% r- z8 A
    str[2] = '\0';, i/ @5 t6 c3 V- t$ i" o0 s8 @8 e
    LcdShowStr(x, y, str);. l  Q! j# S" x$ |* q+ l: k
}! t' Y" t: V; H5 N7 \1 {' a  \& t
/* 刷新日期时间的显示 */8 u( M& M9 N$ ^8 V
void RefreshTimeShow()1 A! r3 e4 y6 ?7 c, p& `, Q) t" H
{% n# g8 p) q3 ?$ f( q
    ShowBcdByte(5,  0, bufTime.year);
: K" k5 l7 j6 \* J# d    ShowBcdByte(8,  0, bufTime.mon);
* ~3 g) ]0 B1 i+ X2 `; r    ShowBcdByte(11, 0, bufTime.day);
, {/ B% P$ G$ _/ w0 w' L    ShowBcdByte(4,  1, bufTime.hour);
4 s+ v& R1 x2 ]  e+ G2 f    ShowBcdByte(7,  1, bufTime.min);
5 a2 d% ^8 m+ j. O6 a  c. Q8 w    ShowBcdByte(10, 1, bufTime.sec);4 f( Z' q$ E9 U8 x' p4 f' V- E0 d  l; A
}
/ I5 Q! K/ [* h( j) z/* 串口动作函数,根据接收到的命令帧执行响应的动作( ]& m6 y  j) g; S' D) i
   buf-接收到的命令帧指针,len-命令帧长度 */
% O9 K; F* ^* x) I( F4 i5 x, O! Yvoid UartAction(unsigned char *buf, unsigned char len)+ ~( W0 o, a3 P  A/ H+ G; i
{
: @8 c9 O2 a% T0 z+ ?    unsigned int  crc;
! a+ V9 l  Q# o# q3 Y5 h    unsigned char crch, crcl;: j+ r2 Y5 Q4 X3 U; K$ c- J

, O9 ?$ f3 h4 J" V- @+ T    if (buf[0] != 0x01) //本例中的本机地址设定为0x01,
- R  A. q3 x) L' ?! G    {                   //如数据帧中的地址字节与本机地址不符,7 ^$ G* C8 _. g6 ~# u* U  Y
        return;         //则直接退出,即丢弃本帧数据不做任何处理
% f$ ^+ P- K3 t    }
! h- w1 d# E4 z- \    //地址相符时,再对本帧数据进行校验
6 X9 c* x5 z; ?) A/ P    crc = GetCRC16(buf, len-2);  //计算CRC校验值% Q0 a( V& l' c  j5 w0 o; x6 c
    crch = crc >> 8;
# v- k, h- K) Q2 q$ Q' H6 t. ^    crcl = crc & 0xFF;
  K1 j5 @2 h9 R1 t    if ((buf[len-2]!=crch) || (buf[len-1]!=crcl))
- C( D: V/ E& M    {' Q/ V6 }9 K( {7 @% X
        return;   //如CRC校验不符时直接退出
/ E7 x$ S4 A- _$ y& R    }
: C1 n) Q% d/ ~" \    //地址和校验字均相符后,解析功能码,执行相关操作; d* M$ X8 \; p$ Z7 j
    switch (buf[1])
7 }8 K1 M* v: B! z0 D- o6 J) `) J    {
9 D  C! z9 U& A& {        case 0x06:  //写入单个寄存器
5 P1 f, y9 Q9 ~8 ?            if ((buf[2]==0x00) && (buf[3]<=0x06)) //地址0x0000~0x0006分别对应
/ B& X8 b6 g- a            {                                     // “年/月/日/时/分/秒/星期”
3 _" o3 K: n; {( e3 O: Z/ n; Y                GetRealTime(&bufTime);  //获取当前时间' C* e$ R1 H  h2 \9 S2 q
                switch (buf[3])  //由寄存器地址决定要修改的时间位
- t8 d& |" @7 ?4 i$ ]$ }                {
0 D3 ]/ `" N! s$ j                    case 0: bufTime.year = 0x2000 + buf[5]; break;
6 e3 j5 x* X% C+ v+ i3 |                    case 1: bufTime.mon  = buf[5]; break;2 r! l& f% S9 m) |, m: h
                    case 2: bufTime.day  = buf[5]; break;! y/ a8 s1 Z" [1 C
                    case 3: bufTime.hour = buf[5]; break;
& q+ a) k5 [# r4 Z2 P! t                    case 4: bufTime.min  = buf[5]; break;' u( y9 k8 K) Q  @/ J* w& |
                    case 5: bufTime.sec  = buf[5]; break;
' k9 `( d# o" \2 U                    case 6: bufTime.week = buf[5]; break;- U# w+ Y( j6 ~  i( m; r6 u; {
                    default: break;, N% ?; I5 {" S8 }6 l& ^5 C0 `5 T4 j% C) f
                }. [* h+ ~+ V1 u" P1 e
                SetRealTime(&bufTime);  //写入新修改后的时间
0 ?  k- w. m2 D& x$ f( F' Y: _6 |2 A                reqRefresh = 1;  //设置显示刷新请求
$ J# E1 q% b/ u# @5 K% L                len -= 2;  //长度-2以重新计算CRC并返回原帧
$ m) U/ B+ S0 G                break;/ x! l1 o( L$ C# ]/ Y
            }
. ?, e; `$ \$ N            else  //寄存器地址不被支持时,返回错误码& v9 h1 ]- O6 ^
            {
  J' K$ ]) {5 l- y' \  b                buf[1] = 0x86;  //功能码最高位置1; Q& n; O9 H* U0 Q
                buf[2] = 0x02;  //设置异常码为02-无效地址8 F' R3 F4 k2 j; \
                len = 3;, K8 E6 W" U$ D: H& Y3 A) D
                break;
6 R& R% }: G4 }3 m6 h! O+ s            }
8 i; U! p+ V* V5 a* P& B; P
5 ^4 V7 b" O2 S: D' R        default:  //其它不支持的功能码, y- Y" ?2 c" a4 J  D9 ?7 s
            buf[1] |= 0x80;  //功能码最高位置1
5 J; I% }2 ~4 w' w! M; U$ v6 m            buf[2] = 0x01;   //设置异常码为01-无效功能" f" F8 l0 Q4 m  Y
            len = 3;( A# h% K$ n* f4 i8 [; y* @
            break;
( t( _& G9 t. c6 Y    }
# @* b+ t! E: N: M5 {) i    crc = GetCRC16(buf, len); //计算返回帧的CRC校验值
8 h- ^. F& h( ?' K6 s    buf[len++] = crc >> 8;    //CRC高字节+ i* @* W9 g, w. u) o2 }) R! b  a
    buf[len++] = crc & 0xFF;  //CRC低字节' B* q! m' U8 x% {5 E$ L
    UartWrite(buf, len);      //发送返回帧
, Y6 O" j# ]3 Y' t}
* I1 [, f1 p+ q% K$ J/* 配置并启动T0,ms-T0定时时间 */
; y9 O4 d: i3 F8 }void ConfigTimer0(unsigned int ms)
9 ~  c9 N5 R7 q  N{; s) l+ H& V$ E1 @+ u- S+ t
    unsigned long tmp;  //临时变量
8 _# r7 |) \6 B' l3 Z4 I: O, f
& N2 I( A- W! ]& m! j    tmp = 11059200 / 12;      //定时器计数频率& h( w9 e9 z6 i1 f- x5 z
    tmp = (tmp * ms) / 1000;  //计算所需的计数值" G& t( R+ T' z; R2 s0 a% H
    tmp = 65536 - tmp;        //计算定时器重载值" l7 I, G$ {/ g! }% U" e
    tmp = tmp + 33;           //补偿中断响应延时造成的误差
2 d5 B2 G" D1 x    T0RH = (unsigned char)(tmp>>8);  //定时器重载值拆分为高低字节
: {& ^4 Y2 k7 k, z; Y( ]; T! m    T0RL = (unsigned char)tmp;2 l+ g) c( s' b6 c( E0 t
    TMOD &= 0xF0;   //清零T0的控制位( z+ M/ ?- X! e7 b, ^) L) j  Q
    TMOD |= 0x01;   //配置T0为模式19 f9 \3 `7 ~- x. C+ r6 }* Z
    TH0 = T0RH;     //加载T0重载值% F, Y; O- S" F) d6 v7 Z' e/ w
    TL0 = T0RL;. d, C7 g$ }) l- r# K! C! J
    ET0 = 1;        //使能T0中断. v, G% V3 C! H, r- u% u, Z
    TR0 = 1;        //启动T0
: ?3 H3 c. f7 k}0 h+ }/ E1 |7 P* |
/* T0中断服务函数,执行按键扫描和200ms定时 */$ ~3 r2 A1 ]/ J3 `
void InterruptTimer0() interrupt 1
2 m' k  q; b4 Y5 e8 L2 p( A/ N{
9 ]; C' f! h  D3 w    static unsigned char tmr200ms = 0;4 o! g5 i9 P) c3 B3 B0 ]% ?- h
+ i0 z9 J6 Y6 |4 u9 f  k3 ^
    TH0 = T0RH;  //重新加载重载值
3 X& A' @; `$ N! o: P3 J    TL0 = T0RL;
* g; J) M2 O% B( u    UartRxMonitor(1);  //串口接收监控
: q' r/ R# N- A& E7 D- q3 q% w    tmr200ms++;
: J3 H! z7 {6 K9 w+ E, R7 ~    if (tmr200ms >= 200)  //定时200ms
: Y4 y! a! @; `6 L9 o    {
7 z/ q1 Z. Y2 L: p' W  ?8 W        tmr200ms = 0;
: Z+ d* g) J$ z4 s* {$ `# Y7 d9 |        flag200ms = 1;
5 j& a2 i# B, ]- Q/ c1 @    }
! [; A( ?8 K9 f; V}
1 b# S7 `' X% O: @/ I
( X6 d) m7 n+ s8 O8 Y6 M. @

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-7-19 08:16 , Processed in 0.109375 second(s), 23 queries , Gzip On.

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

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

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