OpenEdv-开源电子网

 找回密码
 立即注册

扫一扫,访问微社区

正点原子全套STM32开发资料,上千讲STM32视频教程,RT1052教程免费下载啦...

123
返回列表 发新帖
楼主: xiatianyun

学习精英版的学习笔记

[复制链接]

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 2018-7-12 10:58:23 | 显示全部楼层
正点原子公众号
目前我努力的方向是实现RS485的Modbus-RTU通讯,和我的PLC进行数据传输。
我现在可以想象的问题是:
通讯的地址是如何处理?
主站和从站在设置上有什么特点?
发送和接收如何才能在一帧传输后才来中断处理而不是一个字节传输就中断处理?
通讯协议是如何解析的?
协议是如何封装成数据帧的?
怎样更高效优雅地解析协议?
回复 支持 反对

使用道具 举报

  离线 

18

主题

556

帖子

1

精华

高级会员

Rank: 4

积分
946
金钱
946
注册时间
2018-5-11
在线时间
178 小时
发表于 2018-7-12 20:30:58 | 显示全部楼层
xiatianyun 发表于 2018-7-11 17:27
精英版没有直接的RS232接口,是把芯片的串口经过转换成为USB接口,使用USB来模拟串口的,需要在电脑端安装R ...
学习了。
个人理解,是不是全双工主要指的是信道,
如果收和发是双向互不影响,同时可进行的,就是全双工
至于电脑的处理是另外的事情,处理不过来或方法不当是终端资源和人为失误
回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 2018-7-13 10:57:24 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-13 16:30 编辑

以串口1为例来学习。
发送数据口TX是PA9,该端口配置成复用推挽输出模式。数据首先被送到发送数据寄存器TDR,然后被自动送入发送移位寄存器,每个bit在时钟驱动下由发送控制器顺序发到TX口发送出去。
接收数据口RX是PA10,该端口配置成浮空输入模式。数据的每个bit从RX口由时钟驱动下被接收控制器送到接收移位寄存器合成数据,然后送到接收数据寄存器RDR中,然后被程序接收。
TDR后RDR其实在系统中是同一个寄存器寻址DR,如何判断是哪个呢?
如果是接收,也就是读DR,则读取的是RDR。如果是写DR,则写的是TDR。
系统就是根据读写来分别对RDR和TDR进行操作的。
DR是个32位寄存器,但只有低9位有效[9~0]。之所以不是低8位,就是因为有个奇偶校验位存在,如果没有校验,则是低8位。
奇偶校验是由硬件自动完成的,不用设计程序。如果校验失败,则可能会产生中断以便程序处理。

从框图可知,接收和发送没有多余的缓冲区。只有一个缓冲,也就是DR。需要程序来提供适当的数据缓冲。
------------------------------
时钟如何确定?
串口1的时钟来源与PCLK2,也就是APB2,开发板是72MHz。
其他串口2~5时钟来源与PCLK1,也就是APB1,是36MHz。
串口的波特率是在时钟驱动下完成的单位时间1s内的bit传输率,常用的有9600、19200、115200等等。
知道了所需的波特率如何设置呢?
使用的是BRR寄存器设置,BRR的值被看成定点数。定点数比浮点数简单,用于寄存器设置完全够用了。
为什么还要有定点数设置波特率呢?因为PCLKx不直接用来驱动数据传输,而是PCLKx的时钟频率先经过BRR分频再经过16分频得到的时钟信号再驱动数据传输的。
也就是BRR只是波特率构成的一个变因,具体公式是:BoundRate=PCLKx/(16*BRR)。
则BRR=PCLKx/(16*BoundRate)
说过BRR是定点数,0~3位是小数部分,4~15位是整数部分。
计算就不用了,使用库函数设置串口参数后进行串口初始化会自动计算的。具体是USART_Init().
---------------------------------
使用串口通讯可以不断地查询一次通讯是否完成来处理,也可以用中断来处理。
做PLC程序时用串口通讯我一般采用两种方式,一种是定时中断,定时到查询状态,一种是不断查询状态。
定时中断来进行通讯大多用于主站端,定时发送数据到总线上。而从站如果采用定时中断接收数据存在问题,会有错过数据的情况,所以从站一般是不断查询。
而STM32有提供接收和发送中断,可以在中断后处理。
串口中断其实每个串口只有一个中断号,即USARTx_IRQn,共5个。
但每个串口中断可以可以由很多个标识位来触发,需要配置由哪个标识位来触发中断。
常用的触发事件是:
TXE:发送数据为空,表示发送数据寄存器空,可以接受新数据发送。通过读手册发现是TDR数据转移到发送移位寄存器后产生该标识。产生该标识时可以接受新的发送数据(1byte)。
TC: 发送完成。可能是指发送移位寄存器里面的数据发送完毕,其后新的发送数据来自发送数据寄存器TDR。通过读手册发现是用于多数据通讯中,此时一帧发送完毕且TXE被置位。系统是怎么判断我有一帧数据发送的呢?
RXNE:准备好读取接收到的数据,可能是指接收移位寄存器数据合成完毕被送往RDR,可以读取RDR了。如果不读则该数据会被后来的数据覆盖。
PE:奇偶校验失败。失败后可能需要重新发送相同的指令重新读取数据。具体如何重新读取,这个和协议有关。
-----------------------------
在学习外部IO口中断时在中断服务函数结束前需要复位中断标识位以便中断继续,但串口通讯可以由读取或写入DR操作来自动清楚标识位,不用手动清除。当然,也可以手动清除。




回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 2018-7-13 13:13:12 | 显示全部楼层
写一个简单的串口通讯试验的流程是:
确定用哪个串口,串口1的话就是用PA9/PA10,当然复用重映射也是可以的。
查手册看通讯用的外设GPIO口需要设置成什么IO模式。
首先是端口初始化:
GPIO口时钟使能、串口时钟时钟。
GPIO端口工作模式设置初始化。
串口初始化。
串口使能。
如果需要使用串口中断的话还需要串口中断初始化,使串口中断和中断事件绑定。
中断向量初始化,确定串口中断号和优先级。之前必须先设置好优先级分组采用哪个分组模式。
编写串口中断服务函数,中断函数名应当和系统初始化的向量名称一致。
最后就是编写数据解析和合成。
回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 2018-7-15 19:51:47 | 显示全部楼层
正点原子公众号
本帖最后由 xiatianyun 于 2018-7-15 20:57 编辑

找了两个Modbus通讯程序来看,看得一头雾水。
可能是Modbus本身就不是一个统一的通讯协议吧,各家根据自己的需要可以稍作修改以适应需求,所以和我以前认识的有所偏差。
还是老实地一步一步来吧。
先来看后面C盘的485教程,发现原来485只是串口外接的一个电平转换电路,和232道理一样。
不过还是有不一样的地方:232不用控制发送和接收,因为232是全双工的,而485则需要人工控制收发,需要在程序里面来协调收发步骤。具体就是通过PD7口来控制的。
所以,需要接收时除了程序接收外还需要控制PD7口为0,需要发送时除了程序发送外还需要控制PD7口为1.
其他的和普通串口通讯一样的配置。
精英版的485口从USART2引出,使用的是复用功能的PA2/PA3口。
RS485的引入其实是为了解决RS232的一些问题,首先是通讯距离短,最大也就50来米,短距离通讯没问题,但使用在工业上就难以应付了。其次是RS232使用的通讯电压比较高,正负12伏,这个很可能对通讯接口造成损伤,导致设备损坏。
RS485通讯距离比较远,1km也是可以的,当然距离长波特率就低。如果又需要距离远有需要一定的波特率就每隔500m加通讯中继器。当然,这也不是任意的。
RS585还有个特点,在链路上的两端需要加终端匹配电阻,并且串口通讯是不能做成环网的也不可以是星型。
-----------------------------------------
还是来认识一下Modbus-RTU通讯协议吧。

回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 7 天前 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-16 22:52 编辑

今天看了下Modbus通讯相关手册,重点是数据帧部分。Modbus的核心数据帧为:功能码+数据
根据不同的实现添加了一些其他的域,在串行通讯上的Modbus帧为:地址域+功能码+数据+CRC校验。也即添加了地址域和校验域。
一帧数据通常还包含帧起始和帧结束部分,以便对方知道是否开始接收。
Modbus是主从通讯协议,链路上只能有一个主站,可以有多个从站。
只能由主站发起通讯,从站被动应答。可以类比从站为服务器而主站是客户机,只是服务器有多个而客户机只有一个。
同一时刻主站只会发起一个通讯事务,如果不是广播则只会是向一个从站通讯,而从站间是不会互相通讯的。
从站间通讯必须经过主站中转。这种中转不是协议的一部分,而是程序通过两次通信来处理。
--------------------------------
Modbus是应用型协议,可以搭载在串行也可以是TCP。
Modbus的串行链路分两种细分模式:RTU和ASCII模式。
我觉得RTU和ASCII除了数据的表现方式不同外,还有帧起始和结束很不同。
ASCII的帧启停很好理解,就像规定一样,遇到冒号就是帧起始,需要接收接下来的数据,遇到回车换行接意味着帧结束。这个结束和教程串口通信试验一样。
Modbus-RTU的比较特别,它并没有规定用什么来做帧启停标识。当接收到第一个字节时就是帧起始,而帧结束是用时间来检测的。
RTU规定,帧间必须要有不小于3.5个字符传输的时间做间隔。又规定,帧数据必须是连续字节流,什么是连续呢?
字节和字节间的时间间隔必须小于1.5个字节传输时间。
这样可以理解为:链路上的各节点只要统一了间隔时间就能很好地识别帧起始和帧结束。
这不统一呀。同一个链路如果不是统一开发的呢?比如有第三方设备呢。而这是RTU的常态。
手册上建议,如果比特率超过19200bps,那么最后固定时间间隔。帧间隔t3.5=1.75ms,字节间隔t1.5=750us。
这样Modbus-RTU就和定时器搭上了关系。
接收一个字节完毕就复位t1.5定时器开始定时,如果在定时器还没有ON时就接收到了下个字节则字节有效,再次复位t1.5定时器开始定时。如果t1.5ON则认为其后接收的字节数据无效或帧数据终止可以做其他处理了。比如开始判断是不是发到本站的、CRC校验等等。
收到t1.5ON时紧接着开始t3.5定时,t3.5ON时认为帧结束。
发送则只用到t3.5定时器,因为字节发送在本端检测字节是否连续没有多少作用,通常会极快结束。t1.5字符连续主要用于接收检测。
发送的结束是主动的,一开始就知道哪里结束,所以结束时开始t3.5定时,定时ON进入空闲等待下个处理事务。
所以,接收比较起发送来就复杂得多。
如果接收字节时出现t1.5ON,进行判断是否发到本站的。如果不是,那么前面接收的数据就多余了。所以,最好在帧起始接收到帧地址数据时就进行判断,如果不是就回到空闲,也不用继续接收了。
那么说来,其实是靠程序来判断地址的,即使不是发到本站的也可以接收数据,并没有强制机制。全靠自觉。
对了,Modbus地址域的地址是从站地址,主站没有地址。地址为1个字节,范围就是0~255。但是,并不是说所有地址都可用,有些是保留地址,至于做什么,不知道。
链路在没有中继情况下最大可以挂接32台设备,包括主站。这主要是从可靠性出发来约束的,如果有中继则可以挂更多设备。


回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 7 天前 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-17 14:25 编辑

串口通信配置的校验位又是怎么回事呢?
其实串口配置的波特率、校验、停止位都是配置硬件。
这些由串口硬件自动完成,不必编程。
当配置好后,一个字节其实发送接收的不是一个8位字节,而是11bit:1bit起始位、8bit数据位、1bit奇偶校验位、1bit停止位。如果没有校验则校验位填充停止位。
至于启停位是什么值由硬件来处理,不必纠结。
程序只需处理发送的8bit数据和结束8bit数据,想接收数据位外的位也是不能做到的。
如果出现校验失败,会有中断产生(如果开启的话),有标识位置位,可以在收到时处理。(丢弃或发起重发指令)
同样,帧校验失败也是丢弃或发起重发指令。不过帧校验需要编程。
----------------------
Modbus-RTU数据帧:
地址1byte + 功能码1byte + 数据0~252byte + CRC校验2byte.
CRC由2字节组成,LSB在前HSB在后,这和大多数多字节数据一致。
这样,一个RTU帧最大256字节。数据帧是可变长度的。
这些是Modbus-RTU通信的基本知识,有了这些东西就可以完成基本Modbus-RTU通信的编程了。
接下来就是功能码和数据域打包和解包了。
这也是RTU的关键。
---------------------------------------
CRC校验算法有两种做法:通用算法和查表算法。我觉得通用好理解些。
不过CRC校验的原理是什么呢?
-------------

看了半天,也没有看出CRC校验的原理,网上搜的大多是实现方法。
还是检现成的吧,有时间再看看能不能看懂CRC校验原理。
回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 6 天前 | 显示全部楼层
要用到定时器做通信,以前学的是SysTick,现在学习定时器。
STM32F10x一共有8个定时器TIMx,分三种,最简单的是基本定时器TIM6和TIM7,通用型是TIM2~TIM5,高级定时器是TIM1和TIM8.
其实定时器的基本定时原理和以前我用SysTick实现的通用延时定时器一样的道理,就是对定时计数器的时钟脉冲进行计数,计数值可以从0开始到预置值,也可以从预置值倒计数至0,还可以0到预置值再从预置值到0反复来回计数。
这样就有了计数模式:向上、向下、中间(向上和向下)三种。
不过基本定时器TIM6/TIM7只有向上一种计数模式。
计数到达预置值产生中断,是TIM_IT_Update,叫中断更新。可以看成是中断事件,教程里叫做中断源。
其实,除了高级定时器有多个中断号外其他6个都只有一个中断号:TIMx_IRQn.
最常用的就是TIM_IT_Update了,当计数至模式设定的方向值时产生中断,但是并不需要手动更新装填预置值,会自动更新的。
------------------------
来看看,计数时钟。
计数时钟是CK_CNT,其时钟频率叫CK_CLK。其实我也搞不清有什么区别,为什么叫不同的名字,一会CK_CNT,一会CK_CLK。
计数时钟来源于TIMxCLK 的PSC分频器,PSC就是分频值。
TIMxCLK时钟就是定时器时钟,其实只是定时器间接时钟,不能把它看作定时器时钟,定时器时钟应该就是CK_CLK,这个才是定时计数器的直接计数时钟,时间就由CK_CLK决定。
TIMxCLK和系统初始化后的APB1或APB2有关。
如果是基本定时器则和低速总线APB1有关,如果APB1的预分频值不是1则TIMxCLK是APB1的2倍频,精英版就是这种情况,APB1是36MHz,那么TIMxCLK就是72MHz.
这样,CK_CLK=TIMxCLK/(PSC-1) ,如果需要一个10kHz的计数时钟,则10k=72M/(PSC-1),PSC=7199.
10kHz时钟的周期是1/10k s,即0.1ms。
如果要定时中断在500ms时产生,则预置值RCC*0.1ms=500ms,预置值为5000。实际应该少1,RCC=4999.
基本型定时器除了定时没有太多功能,简单实用。我打算用TIM6做Modbus-RTU通信的t3.5用TIM7做t1.5。
也可以用SysTick来实现,不过SysTick脉冲计数型有误差,可能是1个脉冲周期。
回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 前天 12:03 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-21 15:09 编辑

陆陆续续学习了RS485通讯的基本知识。现在来实现,记录下过程中的一些关键点。
首先实现Modbus-RTU的通讯收发,至于协议如果解码编码,下一步了。
1、RS485端口初始化。
这一步使精英板所带RS485端口初始化,有几个点需要注意。除了像RS232普通串口一样初始化USART2外,RS485是半双工通讯,电平转换芯片需要一个信号来切换收发,这个是PD.7口,0信号是接收数据1信号是发送数据。PD.7口使用位带操作来读写比较方便。
主站和从站都初始化为接收数据模式。本来主站应该初始化为发送数据才适合,不过收发还是需要初始化外的程序来控制的,所以干脆一律初始化为接收。
初始化除了需要初始化PD.7外还需要初始化两个定时器TIM6和TIM7,这两个都是基本定时器,TIM7用来作为通讯中检测字节连续性的t1.5定时器使用,而TIM6作为帧间间隔t3.5使用。t1.5的作用像看门狗,如果定时时间内检测到字节数据就复位定时器重新开始定时,如果t1.5动作则认为帧接收结束。实际上t1.5动作可能是帧结束也可能是通讯中出现故障不能及时收到数据,无论如何都判定帧结束可以做帧解析了。TIM6和TIM7初始化为100kHz,这样PSC为719。重装载寄存器为1750和750,也即1.75ms和750us。可以更大。定时器初始化后不使能,由外部程序在适当时候启动定时。
2、接收逻辑。
使用接收中断,中断服务函数中开始判定系统故障标识位是否置位,置位则置位出错标识位不作进一步处理,也可以直接结束数据接收。暂时接续接收数据,因为出错,接下来的帧有效性判断会失败。如果无错就转储数据到缓冲区。转储后复位t1.5并启动t1.5定时看门狗。
t1.5定时到达后复位t1.5并失能停止定时。接着复位t3.5并启动t3.5定时器。然后进行帧有效性判断(CRC16校验),校验正确进行解码。解码是否放在t1.5中断内还没有明确。
如果t3.5定时达到,则帧间隔被添加,复位t3.5并停止t3.5工作。接收终止。
解码可以不放在t1.5内,外部程序不断读取帧结束标识和可以读取数据标识再进行解码。
t1.5中断服务函数:
[C] 纯文本查看 复制代码
void TIM7_IRQHandler(void)
{
    if(TIM_GetITStatus(T1_5, TIM_IT_Update) == SET)
    {
        //复位t1.5并停止工作。
        TIM_ClearITPendingBit(T1_5, TIM_IT_Update);        
        TIM_SetCounter(T1_5, 0); //复位向上计数器当前值为0.
        TIM_Cmd(T1_5, DISABLE); //使能定时器开始定时.
        
        //t1.5中断时启动t3.5定时器,监测帧是否结束.
        TIM_ClearITPendingBit(T3_5, TIM_IT_Update); //清除定时器中断更新标识.
        TIM_SetCounter(T3_5, 0); //复位向上计数器当前值为0.
        TIM_Cmd(T3_5, ENABLE); //使能定时器开始定时.
        
        //判断数据帧的有效性.
        //Modbus_Control_Struct.bFrameOk = Pkg_Validity();
        //如果帧有效则进行接收到的包解码.
        if(Pkg_Validity())
            Pkg_Uncode();
    }
}


t3.5中断服务函数:
[C] 纯文本查看 复制代码
void TIM6_IRQHandler(void)
{
    if(TIM_GetITStatus(T3_5, TIM_IT_Update) == SET)
    {
        //复位t1.5和t3.5定时器并失能,停止定时监测.
//        TIM_ClearITPendingBit(T1_5, TIM_IT_Update);
//        TIM_SetCounter(T1_5, 0); //复位向上计数器当前值为0.
//        TIM_Cmd(T1_5, DISABLE); //使能定时器开始定时.
        TIM_ClearITPendingBit(T3_5, TIM_IT_Update);
        TIM_SetCounter(T3_5, 0); //复位向上计数器当前值为0.
        TIM_Cmd(T3_5, DISABLE); //使能定时器开始定时.
        
        //如果是接收状态的帧结束,则置位接收结束标识。
        if(Modbus_Control_Struct.u8Status == 2)        
            Modbus_Control_Struct.bRxEnd = TRUE;
        //如果是发送状态的帧结束,则置位发送结束标识。
        if(Modbus_Control_Struct.u8Status == 1)
            Modbus_Control_Struct.bTxEnd = TRUE;
        
        //设置系统状态进入空闲.        
        Modbus_Control_Struct.u8Status = 0;    
        Modbus_Control_Struct.bBusy = FALSE; 
               
    }
}

3、发送逻辑。
发送是主动的,填充发送缓冲区后发送。先进行发送失能,然后发送。发送后启动t3.5添加帧间隔。
[C] 纯文本查看 复制代码
//发送数据帧
void SendFrame(ModRTU_TX_Struct TX_Struct)
{
    RS485_TXENB; //发送使能
    Modbus_Control_Struct.bTxEnd = FALSE;  //复位发送结束标识。
    Modbus_Control_Struct.u8Status = 1;
    
    for(u8 i = 0; i < TX_Struct.u16Index; i++)
    {
        USART_SendData(USART2,TX_Struct.Buffer[i]);
        while(USART_GetFlagStatus(USART2,USART_FLAG_TXE) != SET);
    }        
    //等待全部连续数据发送完毕。
    while(USART_GetFlagStatus(USART2, USART_FLAG_TC) != SET);
    
    //发送缓冲区清空。
    TX_Struct.u16Index = 0; 
    
    //帧间延时,开启t3.5。
    TIM_ClearITPendingBit(T3_5, TIM_IT_Update); //清除定时器中断更新标识.
    TIM_SetCounter(T3_5, 0); //复位向上计数器当前值为0.
    TIM_Cmd(T3_5, ENABLE); //使能定时器开始定时.
        
}


----------
接收:
[C] 纯文本查看 复制代码
//接收数据帧
void ReceiveFrame(ModRTU_RX_Struct RX_Struct)
{
    //清空接收缓冲区。
    RX_Struct.u16Index = 0;
    
    RS485_RXENB; //接收使能
    Modbus_Control_Struct.bRxEnd = FALSE; //复位接收结束标识。
    //数据帧接收后会自动添加延时。
}

回复 支持 反对

使用道具 举报

  离线 

0

主题

3

帖子

0

精华

新手入门

积分
18
金钱
18
注册时间
2018-7-21
在线时间
1 小时
发表于 前天 18:16 | 显示全部楼层
HAOXIGUAN,坚持下去,就能成功啦
回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 前天 18:30 | 显示全部楼层
本帖最后由 xiatianyun 于 2018-7-21 18:32 编辑

Modbus-RTU模式的非协议通讯终于调通了。
有几点体会:
1、CRC16校验并不能自动校正通讯数据错误,仅仅是判定发生了数据错误。
2、CRC16的多项式根据不同的标准有多个选择,CRC16_Modbus标准只是其中之一。其要求是:CRC16初始值为0xFFFF,多项式为0xA001,其实应该是0x8005,是该值逆序重排值。
多项式在主站和从站均必须固定不变,如果有节点不是这个值就不能通讯成功,因为不同的多项式其CRC校验结果值不同。
我为了调试CRC16把传入的数据(含CRC)又作了CRC16校验,然后把数据发到助手,发现后面添加的CRC值为0x00。这也是CRC校验的目的。把含CRC的数据再校验结果为0.
3、用助手定时发送数据到开发板会偶尔产生校验错误,我认为是定时的问题。因为不同步,助手自发自的数据,而开发板收到数据后回传给助手,开发板收发是步调协同的,而助手只按时发送,就导致了开发板发送时可能是助手发送时,而开发板接收时可能助手已经在发送中了,导致不能完整接收数据。调整定时时间也是如此,只是多少频率的问题。
所以,我想如果是半双工,从站和主站是需要协同才能正常工作的。以前遇到过从站一味发送数据,不管有没有站点提出请求的情况可能是RS232.也许我记不清了。

--------------------------
下面学习Modbus-RTU数据解析,功能码、数据地址、打包、解包。
回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 昨天 09:33 | 显示全部楼层
先来试验功能码0x10.
0x10是写连续多个保持寄存器。
主站发送的请求数据帧格式:
MB_Addr 1B + 0x10 1B + 元件基址 2B + 元件数量 2B + 后续数据字节长度N 1B + 可变数据域  N*2B  + CRC 2B
一共最大通讯长度为256B,则可变的数据占用最大为256-9=247B ,如果HoldReg一个元件需要2B数据赋值的话,只能是247/2=123个元件。也即,一次通讯最大写123个连续元件。
即元件数量最大123=0x7B。后续数据长度最大N为246=0xF6。
元件数量为何要占2个字节?
从站响应帧结构:
如果正常响应:
MB_Addr 1B + 0x10 1B + 元件基址 2B + 元件数量 2B + CRC 2B
也即请求帧的前半段。
如果出错,分情况:如果CRC校验错误等无法接收则不响应,主站将超时。如果能接收但不能如约响应,则功能码最高位置1并后接异常信息1B。这个可以查资料。
正常功能码最高位是0,也即不会超过0x80。
------------------------------------------
元件基址问题:
一般有两种基址预定义,一种是以1为基址,比如西门子PLC就是如此;一种是通用以0为基址。
基址只涉及数据地址解析,其实一般定义通讯所用元件基址为以0为基址,用户外部程序应该根据实际从站的基址定义来使用元件基址。
其实基址只对设计通讯程序有效,对于外部程序来说并不需要知道。
外部只需要知道:元件类型及地址、数量、读或写、写的数据来源及读的数据存在哪里即可。
比如:对于很多PLC、变频器来说,4xxxx表示保持寄存器和地址(xxxx),这样就决定了可以使用的功能有哪些以及元件基址(xxxx-1)。
如果需要对40005这个元件设置连续5个的值,则:
功能码:0x10
元件基址:0004
元件数量:0005
数据长度:000A
还需要指定数据地址指针来填充后续数据域。
帧为:MB_Addr + 10 00 04 00 05 00 0A 数据 N*2  CRC_L CRC_H
从站正常响应:MB_Addr+10 00 04 00 05 CRC


回复 支持 反对

使用道具 举报

  离线 

12

主题

134

帖子

1

精华

中级会员

Rank: 3Rank: 3

积分
431
金钱
431
注册时间
2018-4-13
在线时间
65 小时
 楼主| 发表于 14 小时前 | 显示全部楼层
元件基址问题再讨论:
经过测试发现了以前对于基址问题认识的不足。
其实基址从1还是从0开始完全取决于从站对元件地址如何解析。所谓PLC Addr Base 1,指的是从站对于请求的元件地址解析为Addr -1,而从站元件地址是从0开始的,这样40001就解析为HoldReg0,40002解析为HoldReg1......。请求的元件地址必须从40001开始,不能是40000。Base0又叫协议地址,是默认解析方式。
所以,对于一个具体的RTU设备,必须先清楚通讯的元件地址和设备自身响应的元件地址的对应关系才能正确发出请求元件地址。
目前还没有碰到从站本身的元件资源地址从1开始的情况。
这些都是应用问题,不在Modbus-RTU主站开发中涉及,但对于一个具体的从站却需要首先解决:从站对于请求的地址如何和本站响应的元件地址进行对应,是Base1还是Base0。
我遇到的都是Base1。
还有需要顺便说明的是从站元件资源地址其实和元件寻址有关,比如元件寻址基于字节,但元件是2byte单元,连续元件的地址就不连续。比如西门子PLCword单元是偶数开始的地址:0、2、4......,对应协议地址就是40001、40002、40003。如果从站元件寻址基于字,连续元件地址就是连续的,元件0、1、2对应协议地址是40001、40002、40003.

回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则




关闭

"原子哥”推荐上一条 /1 下一条

QQ|联系我们|手机版|官方淘宝店|新浪微博|微信公众平台|OpenEdv-开源电子网 ( 粤ICP备12000418号-1 )

GMT+8, 2018-7-23 21:33

Powered by OpenEdv-开源电子网

© 2001-2030 OpenEdv-开源电子网

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