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

单片机RS485通信与Modbus协议

  [复制链接]

该用户从未签到

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

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) [

该用户从未签到

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

本版积分规则

关闭

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

EDA365公众号

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

GMT+8, 2025-10-9 05:21 , Processed in 0.140625 second(s), 23 queries , Gzip On.

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

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

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