stm32 modbus rtu通信实例讯实例,有几个疑问

_坚望銮查竺Yingyong;Jiao¨u;MODBUS;RTU通讯协议在STM32F103上的实现;王晓忠-时振伟-王启宏2;(1.无锡机电高等职业技术学校,江苏无锡2140;摘要:Modbus协议是一种开放式的、有众多支持;关键词:Modbus;STM32;通讯协议:工业;工业控制已从单机控制走向集中监控、集散控制,如今;Modbus通讯协议;1.1通讯
_坚望銮查竺Yingyong
Jiao¨u
MODBUS
RTU通讯协议在STM32F103上的实现
王晓忠-时振伟-王启宏2
(1.无锡机电高等职业技术学校,江苏无锡214028;2.奉化市职教中心学校,浙江宁波315500)
摘要:Modbus协议是一种开放式的、有众多支持厂商的广泛应用的工业协议,其已成为越来越多打算采用数据通信协议的首选协议,通过对Modbus协议的介绍,详细阐述了它在STM32系列MCU上的具体实现过程。
关键词:Modbus;STM32;通讯协议:工业控制
工业控制已从单机控制走向集中监控、集散控制,如今己进入网络集约制造时代。工业控制器连网也为网络管理提供了方便。Modbus就是工业控制器的网络协议中的一种。Modbus协议是应用于电子控制器。f:的一种通讯规约。通过此协议,摔制器相互之间、控制器经由网络(如以太网)和其他设备之间可以通信。它已成为主流的工业标准之一。不同厂商生产的控制设备通过Modbus协议可以连成工业网络,进行集中监控。
Modbus通讯协议
1.1通讯传送方式
每一个数据字节包括:每个字节的位:1个起始位、8个数据位、最小的有效位先发送、1个奇偶校验位,无校验则无、1个停止位(有校验时);2个停止位(无校验时)。
在RTU模式下每个字节的格式(11bit):编码系统:8位二进制。
字节组成:lbit起始位、8bit数据位,最低位最先发送、lbit奇偶校验位、1bit停止位(或者没奇偶校验位,就发送一个附加的停止位)。
1.2数据帧结构
每一个完整的数据帧包含:地址码、功能码、数据、CRC低字
节、CRC高字节。
地址码:地址码为通讯传送的第一个字节。这个字节表明由用户设定地址码的从机将接收由主机发送来的信息。每个从机都具有唯一的地址码,并且响应回送均以各自的地址码开始。主机发送的地址码表明将发送到的从机地址,而从机发送的地址码表明回送的从机地址。
功能码:通讯传送的第二个字节。ModBus通讯规约定义功能号为l~127。根据实际需要只利用其中的一一部分功能码。作为主机请求发送,通过功能码告诉从机执行什么动作。作为从机响应,从机发送的功能码与从主机发送来的功能码一样,并表明从机已响应主机进行操作。如果从机发送的功能码的最高位为1(功能码大于127),则表明从机没有响应操作或发送出错。
数据区:数据区根据不同的功能码而有所不同。数据区可以是实际数值、设置点、主机发送给从机或从机发送给主机的地址。
CRC码:二字节的错误检测码。冗余循环码检查(CRC)包含2个字节,即16位二进制。CRC码由发送设备计算,放置于发送信息的尾部。接收信息的设备p手重新计算接收到信息的CRC码,比较计算得到的CRC码是否与接收到的相符,如果两者不相符,则表明出错。CRC码的计算方法是,先预置16位寄存器全为l。冉逐步把每8位数据信息进行处理。在进行CRC码计算时只用8位数据位,起始位及停止位,如有奇偶校验位的话也包括奇偶校验位,都不参与CRC码计算。
在计算CRC码时,8位数据与寄存器的数据相异或。得到的结果向低位移一位,用0填补最高位。再检查最低化,如果最低位为l,把寄存器的内容与预置数相异或,如果最低能为0,不进行异
这个过程一直重复8次。第8次移位后,F一个8化^与现在寄存器的内容相异或,这个过程与以上一样重复8次。当所有的数据信息处理完后,最后寄存器的内容即为CRC码值。CRC码巾的数据发送、接收时低字节在前。在实际应用中,为了提高运算速度,采用了查表的方法取代计算方法。
Modbus
RTU数据帧结构
在RTU模式下,每一个数据帧之问的间隔至少是3.5个字符位。一个完整的数据帧必须要连续的传送,当一帧消息中两个字节间的间距大于1.5字符位,此数据帧错误,被接受方放弃。
当通讯波特率小于等于19
bps。对1.5个字符位、3.5个字
符位计算时间有严格要求。当通讯波特率大于19200bps。1.5个字
符位固定为750Ixs,3.5个字符位固定为1.75Ills。官方的Modbus
RTU规定标准为3.5个字符长周期,不同的串u设备由于使用环境不同可能在发送巾出现5~10字长的间隙。对于ModbusRTU
来说比较安全的设置为50
1.4用到的功能代码
注意:所有寄存器都是16位(2字节)。功能码03是读取保持寄存器,作用是表示在一个或多个寄存器中的值;功能码06是预置单寄存器,是把数值写入一个寄存器;功能码07是读取异常状态,即取得8个内部线圈的通断状态,这8个线圈的地址由控制器决定,用户逻辑可以将这些线圈定义,以说明从机状态.短报文适宜于迅速读取状态;功能码16是预置多寄存器,把具体的■进制值装入一串连续的保持寄存器。
STM32F103的主要特性
2007年6月11日,半导体制造厂商意法半导体(ST)高调推出
一个新的32位微控制器系列产品STM32。它采用的微处理器是ARM公司为要求高性能(1.25DhrystoneMIPS/MHz)、低成本、低功耗的嵌入式应用专门设计的ARMCortex―M3内核。多达128kB的
嵌入式闪存、20kB的RAM和丰富的外设接口,包括2个12化模
数转换器(1¨s的转换时间)、3个USART、2个SPI(18MHz主/从控制器)、2个12C、3个16化定时器(每个定时器有4个输入捕获模块/4个输出比较器/4个PWM控制器),以及1个专门为电机控制向量驱动应用设计的内嵌死区时间控制器的6-PWM定时器、USB、CAN和7个DMA通道。内置复化电路包括上电复位、掉电复位和电压监控器,以及1个可用作主时钟的高精度工厂校准
的8MHz阻容振荡器、1个使用外部晶振的4~16MHz振荡器雨I2个看门狗。
3软件设计
3.1接收软件流程
接收软件流程如图l所示。
3.2配置串口和定时器
voidusart―initO
fUSARTlnitTypeDefUSARTlnitStructure;
TRASU=stiBpotS.:lTRASU(tiBgnidnePTIraelC_:
图1从机软件流程
USARThfitStructure.USARTBaudRate=-19200://波特率
USART―InitStructure
USART_WordLength=USARTWordl.ength_9b://8位数据加
l奇校验位
USARTInitStructure.USART
StopBits
//1停止位
USART_lnitStructure.USART_Parity=USART_Parity_Odd;//奇
USARTInitStructure.USARTHardwareFlowControl
USARTHardwareFlowControlNone;//无硬件控制USART
lnitStructure.USART
MOde=USARTMode
us』u汀ModeTx;//收发使能
USARTInitStructure.USAIUl
Clock=USARTClockDisable:
USARTInitStructure.USARTCPoL=-USARTCPoLLow:USARTlnitStructure.USARTCPHA=jUSARTCPHA2Edge;USARTInitStructureUSARTLastBit=USARTLastBitDisable;,
/.串口1参数?/
USARTInit(USARTl,&USART
InitStructure):/+串口l中
USARTITConfig(USARTl,USARTIT
RXNE,ENABLE):/+
串口1使能?/
USARTCmd(USARTl,ENABLE);,voidtimer2init(void)//36M主时钟下lOms中断
fTⅡdTimeBaselnitTypeDefTIMTimeBaseStructure;
TimeBaseStructure.TIMPeriod=100:
T蹦TimeBaseStrucmre.TIMPrescaler=3599:T蹦TimeBaseStructure.TIM
T蹦TimeBzseStructure.嗍CounterMode=TIM
ClockDivision=0;
Counter
Mode_Up;
TimeBaselnit(TM2,&TIM
TimeBaseStructure):
TlM_ITConfig(TIM2,TIMIT_Update,ENABLE)://溢出中
ⅡMSetCounter(Tm量2,O);//记数器清零
TIM_Cmd(TIM2,ENABLE);//使能定时器2,
STM32F103解包modbusRTU的程序
voidUSARTl
JRQHandler(void)
{‘(pointer
RXl++)=USART
ReceiveData(USARTl);//将数据放入缓冲队列
YingY。ng
Jiao¨u!墼里壅查_
USART
l,USARTIT
RXNE)//
清接收巾断
Rxl++://收到字节长度加l
timer2reset0://定时器复位timer2initO;//定时器重启动}
TIM2_lRQHandler(void)//判断命令包接收结束
{TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清溢出标志位如URXl=1;//收完置标志位
timer2resetO://定时器复位}
CRC查表产生函数
unsignedshortCRCl6(puchMsg,usDataLcn)unsigned
char+puchMsg;/+要进行CRC校验的消息+/
unsignedshortuchCRC枷:/+低CRC字节初始化+/
usDataLen;/+消息中字节数+/{unsignedcharuchCRCHi=oxFF;/’高CRC字节初始化}/unsigned
unsignedulndex;/+cRC循环中的索引。/while(usDataI肌一)/+传输消息缓冲区‘/{ulndex=uchCRCHi”puchMsgg++:/+计算CRC*/uchCRCHi--uchCRCLo^auchCRCHi[ulndex,:uchCRCLo=auchCRCLo[ulndex];}
returrl(uehCRCHi<<8
uchCRCLo);l/+CRC高位字节值表+/
static
unsignedcharauchCRCHi[]/*CRC低位字节值表+/
staticcharauchCRCLoL
笔者设计采用基于STM32的控制核心来实现Modbus协议。
已经成功地进行了应用,通讯可靠,而l二L实现比较容易。为系统中采用STM32微控制器进行工业产品设计通讯的应用提供了参考
[参考文献]
EliRM0008Referencemanual,02/2008。http://www.st.comE2]STM3210E―EVALfirmware.03/2008.http:}}棚.st.eom
[3]MODBUS
serial
specificationandimplementation
guideV1.02.http://www.modbus.org
[4]MODBUSAPPLICATIONPROTOCOLSPECIFICATIONV1.1a,http://ww.
Modbus―IDA.org.
收稿日期:2010一l旷19
作者简介:王晓忠(198l一),男,江苏无锡人。助教,研究方向:控
制理论与控制工程。
时振伟(1978一),男,江苏无锡人,讲师,研究方向:控制理论与控
王启宏(197l一),男,浙江奉化人,中学一级,研究方向:电工电子
机电信息2010年第36期总第282期153
MODBUS RTU通讯协议在STM32F103上的实现
作者:作者单位:刊名:英文刊名:年,卷(期):
王晓忠, 时振伟, 王启宏
王晓忠,时振伟(无锡机电高等职业技术学校,江苏,无锡,214028), 王启宏(奉化市职教中心学校,浙江,宁波,315500)
MECHANICAL AND ELECTRICAL INFORMATION2010(36)
参考文献(4条)
1.MODBUS APPLICATION PROTOCOL SPECIFICATION VL.la
2.HODBUS over serial line specification and implementation guide V1.023.STM3210E-EVAL firmware 20084.RM0008 Reference manual 2008
本文链接:http://d..cn/Periodical_jdxx.aspx
包含各类专业文献、幼儿教育、小学教育、中学教育、专业论文、生活休闲娱乐、文学作品欣赏、行业资料、应用写作文书、MODBUS+RTU通讯协议在STM32F103上的实现_图文67等内容。 
 MODBUS 通讯协议及编程 ModBus 通讯协议分为 RTU 协议和 ASCII 协议,我公司的多种仪表都采用 ModBus RTU 通讯协议,如: YD2000 智能电力监测仪、巡检表、数显表...  Modbus 协议在串行链路上的实现指南 第 3 部分:Modbus 协议在 TCP/IP 上的...请在能正常通讯情况下测试驱动。 2、设备第三部高级配置 1)32 位浮点数字节...  如果设备支持多种协议,需要在配置里面选择为 ModbusRTU 或者 ModbusAscii 协议。...32 个寄存器数据,如果点数较多并且通讯条件较好的情况 下,我们最大可以调整到 ...  ? 百特工控 福州福光百特自动化设备有限公司 MODBUS 通讯协议 使用手册 1. RTU 方式通讯协议 1.1. 硬件采用 RS-485,主从式半双工通讯,主机呼叫从机地址,从机...  MODBUS RTU通讯协议在S7-200中的应用_兵器/核科学_工程科技_专业资料。MODBUS RTU通讯协议在S7-200中的应用今日推荐 157份文档 2015...  MODBUS_RTU通讯协议_信息与通信_工程科技_专业资料。? MODBUS 通讯协议 使用手册 1. RTU 方式通讯协议 1.1. 硬件采用 RS-485,主从式半双工通讯,主机呼叫从机...  MODBUS协议(功能码及报文解析)_信息与通信_工程科技...有线、 无线通信甚至短消息和 GPRS 的不同实现。...实际上 MODBUS RTU 与 ASCII 的内容是完全相同的,...  Modbus-RTU通讯协议简介_信息与通信_工程科技_专业资料。Modbus通讯协议简介 Modbus 通讯协议 Modbus 协议 Modbus 协议最初由 Modicon 公司开发出来,在 1979 年末该...  MODBUS通讯协议及编程(VC)_信息与通信_工程科技_专业资料。MODBUS通讯协议及编程(VC).docx MODBUS 通讯协议及编程 ModBus 通讯协议分为 RTU 协议和 ASCII 协议。...STM32 移植FreeModbus 详细过程
(amoBBS 阿莫电子论坛)
我的图书馆
STM32 移植FreeModbus 详细过程
(amoBBS 阿莫电子论坛)
FreeModbus移植 经验分享一& &为什么要移植Freemodbus& && && &为什么要移植Freemodbus,这个问题需要从两个方面来回答。第一,modbus是一个非常好的应用层协议,它很简洁也相对完善。对于还没有接触过modbus的朋友来说,我非常不建议直接移植freemodbus,应该耐心的从modbus文档入手,并充分把握身边的所有资源,例如PLC的中modbus部分。第二,其实嵌入式系统的通信协议可以自己制定,但是通过实践发现自己定制的协议漏洞百出,尤其是扩展极为困难。我始终认为借鉴他人的经验是很好的途径。借鉴他人成熟的代码,可以减少调试的时间,实现的功能也多了不少。& && && &个人观点,仅供参考。& && && &freemodbus小提示& && && &freemodbus只能使用从机功能。freemodbus更适合嵌入式系统,虽然例子中也有WIN32的例子,如果想要做PC机程序并实现主机功能,推荐使用另一个modbus库——NMODBUS,使用C#开发。同样WINFORM也可以通过自己编写串口代码实现modbus功能,但是这会花费很长的时间,可能是一周也可能是一个月,如果使用现成的代码库,那么开发时间可能只有10分钟。
& && &&&自己整理的modbus协议
(111.46 KB, 下载次数: 3418)
& && &&&代码参考了这个帖子,感谢你的分享。二&&freeemodbus中如何通过串口发送和接收数据& && && &freemodbus通过串口中断的方式接收和发送数据。采用这种做法我想可以节省程序等待的时间,并且也短充分使用CPU的资源。串口中断接收毋庸置疑,在中断服务函数中把数据保存在数组中,以便稍后处理。但是串口发送中断使用哪种形式?串口发送中断至少有两种方式,第一种,数据寄存器空中断,只要数据寄存器为空并且中断屏蔽位置位,那么中断就会发生;第二种,发送完成中断,若数据寄存器的数据发送完成并且中断屏蔽位置位,那么中断也会发送。我非常建议各位使用串口发送完成中断。freemodbus多使用RS485通信中,从机要么接收要么发送,多数情况下从机处于接收状态,要有数据发送时才进入发送状态。进入发送状态时,数据被一个一个字节发送出去,当最后一个字节被发送出去之后,从机再次进入接收状态。如果使用发送寄存器为空中断,还需要使用其他的方法才可以判断最后一个字节的数据是否发送完成。如果使用数据寄存器为空中断,那么将很有可能丢失最后一个字节。(马潮老师的AVR图书中也推荐使用发送完成中断,交流性质的文章,就没有参考文献了。)
二&&freemodbus中如何判断帧结束& && && &大家应该清楚,modbus协议中没有明显的开始符和结束符,而是通过帧与帧之间的间隔时间来判断的。如果在指定的时间内,没有接收到新的字符数据,那么就认为收到了新的帧。接下来就可以处理数据了,首当其冲的就是判断帧的合法性。Modbus通过时间来判断帧是否接受完成,自然需要单片机中的定时器配合。
三& &整体代码下面给出一个STM32平台上使用FREEMODBUS最简单的例子,操作保持寄存器,此时操作指令可以为03,06和16;
#include "stm32f10x.h"
#include "mb.h"
#include "mbutils.h"
//保持寄存器起始地址
#define REG_HOLDING_START 0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS 8
//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]
= {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0xb,0x408e};
int main(void)
//初始化 RTU模式 从机地址为1 USART1 9600 无校验
eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
//启动FreeModbus
eMBEnable();
//FreeMODBUS不断查询
eMBPoll();
* @brief 保持寄存器处理函数,保持寄存器可读,可读可写
* @param pucRegBuffer 读操作时--返回数据指针,写操作时--输入数据指针
* usAddress 寄存器起始地址
* usNRegs 寄存器长度
* eMode 操作方式,读或者写
* @retval eStatus 寄存器状态
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
//错误状态
eMBErrorCode eStatus = MB_ENOERR;
int16_t iRegI
//判断寄存器是不是在范围内
if( ( (int16_t)usAddress &= REG_HOLDING_START ) \
&& ( usAddress + usNRegs &= REG_HOLDING_START + REG_HOLDING_NREGS ) )
//计算偏移量
iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);
switch ( eMode )
//读处理函数
case MB_REG_READ:
while( usNRegs & 0 )
*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] && 8 );
*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
//写处理函数
case MB_REG_WRITE:
while( usNRegs & 0 )
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ && 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
//返回错误状态
eStatus = MB_ENOREG;
复制代码先给大家一个整体的印象,先让大家会使用FREEMODBUS,再详细描述细节//保持寄存器起始地址#define REG_HOLDING_START& &&&0x0000//保持寄存器数量#define REG_HOLDING_NREGS& &&&8这两个宏定义,决定了保持寄存器的起始地址和总个数。需要强调的是,modbus寄存器的地址有两套规则,一套称为PLC地址,为5位十进制数,例如40001。另一套是协议地址,PLC地址40001意味着该参数类型为保持寄存器,协议地址为0x0000,这里面有对应关系,去掉PLC地址的最高位,然后剩下的减1即可。这会存在一个问题,PLC地址30002和PLC地址40002的协议地址同为0x0001,此时访问时是不是会冲突呢。亲们,当然不会了,30001为输入寄存器,需要使用04指令访问,而40001为保持寄存器,可以使用03、06和16指令访问。所以,用好modbus还是要熟悉协议本生,切不可着急。//保持寄存器内容uint16_t usRegHoldingBuf[REG_HOLDING_NREGS]= {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0xb,0x408e};接下来定义了保持寄存器的内容,在这里请大家注意了,保持寄存器为无符号16位数据。在测试的情况下,我随便找了一些数据进行测试。看数据的本质似乎看不出说明规律,但是usRegHoldingBuf却是以16进制保存了浮点数。
int main(void)
//初始化 RTU模式 从机地址为1 USART1 9600 无校验
eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);
//启动FreeModbus
eMBEnable();
//FreeMODBUS不断查询
eMBPoll();
接下来就进入主函数部分。有三个FREEMODBUS提供的函数,eMBInit,eMBEnable和eMBPoll。eMBInit为modbus的初始化函数,eMBEnable为modbus的使能函数,而eMBPoll为modbus的查询函数,eMBPoll也是非常单纯的函数,查询是否有数据帧到达,如果有数据到达,便进行相依的处理。再次观察这几个函数,只有eMBInit有很多的参数,这些参数和位于系统底层的硬件有关,这个应该引起移植过程的更多关注。下面几个章节再议。
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,
eMBRegisterMode eMode )
//错误状态
eMBErrorCode eStatus = MB_ENOERR;
int16_t iRegI
//判断寄存器是不是在范围内
if( ( (int16_t)usAddress &= REG_HOLDING_START ) \
&& ( usAddress + usNRegs &= REG_HOLDING_START + REG_HOLDING_NREGS ) )
//计算偏移量
iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START);
switch ( eMode )
//读处理函数
case MB_REG_READ:
while( usNRegs & 0 )
*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] && 8 );
*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
//写处理函数
case MB_REG_WRITE:
while( usNRegs & 0 )
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ && 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
//返回错误状态
eStatus = MB_ENOREG;
复制代码最后,如果收到一个有效的数据帧,那么就可以开始处理了。第一步,判断寄存器的地址是否在合法的范围内。&&if( ( (int16_t)usAddress &= REG_HOLDING_START ) \& &&&&& ( usAddress + usNRegs &= REG_HOLDING_START + REG_HOLDING_NREGS ) )第二步,判断需要操作寄存器的偏移地址。& && && &给个例子可以迅速的说明问题,例如访问寄存器的起始地址为0x0002,保持寄存器的起始地址为0x0000,那么这个访问的偏移量为2,程序就从保持寄存器数组的第2个(从0开始)开始操作。第三步,读写操作分开处理& && &case MB_REG_READ:& && &&&while( usNRegs & 0 )& && &&&{& && && & *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] && 8 );& && && & *pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );& && && & iRegIndex++;& && && & usNRegs--;& && &&&}& && &&&& && && &以读操作为例,代码不多说了,请大家注意操作的顺序。保持寄存器以16位形式保存,但是modbus通信时以字节为单位,高位字节数据在前,低位数据字节在后。四& &串口相关部分代码编写& && && &串口部分的代码编写比较常规,主要有三个函数,串口初始化,串口数据发送和串口数据接收。除了以上三个函数之外,还有串口中断服务函数。/**
* @brief 串口初始化
* @param ucPORT 串口号
* ulBaudRate 波特率
* ucDataBits 数据位
* eParity 校验位
* @retval None
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
(void)ucPORT; //不修改串口
(void)ucDataB //不修改数据位长度
(void)eP //不修改校验格式
GPIO_InitTypeDef GPIO_InitS
USART_InitTypeDef USART_InitS
//使能USART1,GPIOA
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA |
RCC_APB2Periph_USART1, ENABLE);
//GPIOA9 USART1_Tx
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //推挽输出
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOA.10 USART1_Rx
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮动输入
GPIO_Init(GPIOA, &GPIO_InitStructure);
USART_InitStructure.USART_BaudRate = ulBaudR //只修改波特率
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_N
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
//串口初始化
USART_Init(USART1, &USART_InitStructure);
//使能USART1
USART_Cmd(USART1, ENABLE);
NVIC_InitTypeDef NVIC_InitS
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);
//设定USART1 中断优先级
NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//最后配置485发送和接收模式
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_Init(GPIOD, &GPIO_InitStructure);
return TRUE;
复制代码传入的参数有端口号,波特率,数据位和校验位,可以根据实际的情况修改代码。在这里我并没有修改其他参数,至于传入的波特率是有效的。除了配置串口的相关参数之外,还需要配置串口的中断优先级。最后,由于使用485模式,还需要一个发送接收控制端,该IO配置为推挽输出模式。
* @brief 控制接收和发送状态
* @param xRxEnable 接收使能、
* xTxEnable 发送使能
* @retval None
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
if(xRxEnable)
//使能接收和接收中断
USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);
//MAX485操作 低电平为接收模式
GPIO_ResetBits(GPIOD,GPIO_Pin_8);
USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);
//MAX485操作 高电平为发送模式
GPIO_SetBits(GPIOD,GPIO_Pin_8);
if(xTxEnable)
//使能发送完成中断
USART_ITConfig(USART1, USART_IT_TC, ENABLE);
//禁止发送完成中断
USART_ITConfig(USART1, USART_IT_TC, DISABLE);
复制代码由于485使用半双工模式,从机一般处于接收状态,有数据发送时才会进入发送模式。在FreeModbus中有专门的控制接收和发送状态的函数,在这里不但可以打开或关闭接收和发送中断,还可以控制485收发芯片的发送接收端口。代码非常简单,但是还是建议各位使用发送完成中断。
xMBPortSerialPutByte( CHAR ucByte )
//发送数据
USART_SendData(USART1, ucByte);
return TRUE;
xMBPortSerialGetByte( CHAR * pucByte )
//接收数据
*pucByte = USART_ReceiveData(USART1);
return TRUE;
xMBPortSerialPutByte和xMBPortSerialGetByte两个函数用于串口发送和接收数据,在这里只要调用STM32的库函数即可。
static void prvvUARTTxReadyISR( void )
//mb.c eMBInit函数中
//pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM
//发送状态机
pxMBFrameCBTransmitterEmpty();
static void prvvUARTRxISR( void )
//mb.c eMBInit函数中
//pxMBFrameCBByteReceived = xMBRTUReceiveFSM
//接收状态机
pxMBFrameCBByteReceived();
void USART1_IRQHandler(void)
//发生接收中断
if(USART_GetITStatus(USART1, USART_IT_RXNE) == SET)
prvvUARTRxISR();
//清除中断标志位
USART_ClearITPendingBit(USART1, USART_IT_RXNE);
//发生完成中断
if(USART_GetITStatus(USART1, USART_IT_TC) == SET)
prvvUARTTxReadyISR();
//清除中断标志
USART_ClearITPendingBit(USART1, USART_IT_TC);
复制代码若进入串口中断服务函数,则要调用FreeModbus中响应的函数,串口接收中断服务函数对应prvvUARTRxISR(),其代码如下
static void prvvUARTRxISR( void )
//mb.c eMBInit函数中
//pxMBFrameCBByteReceived = xMBRTUReceiveFSM
//接收状态机
pxMBFrameCBByteReceived();
复制代码在prvvUARTRxISR中又调用了pxMBFrameCBByteReceived(),其实pxMBFrameCBTransmitterEmpty()并不是一个函数,而是一个函数指针。其定义如下,请注意函数指针的声明和函数声明的区别。BOOL( *pxMBFrameCBTransmitterEmpty ) ( void );在mb.c文件的eMBInit函数完成赋值。一般情况下都会选择RTU模式,那么pxMBFrameCBByteReceived就和xMBRTUReceiveFSM等价了,pxMBFrameCBByteReceived = xMBRTUReceiveFSM;
同理,若发生串口发送完成中断,该中断服务函数对应prvvUARTTxReadyISR,其代码如下
static void prvvUARTTxReadyISR( void )
//mb.c eMBInit函数中
//pxMBFrameCBTransmitterEmpty = xMBRTUTransmitFSM
//发送状态机
pxMBFrameCBTransmitterEmpty();
复制代码在prvvUARTTxReadyISR中又调用了pxMBFrameCBTransmitterEmpty(),pxMBFrameCBTransmitterEmpty也是函数指针,在eMBInit函数完成赋值,它等价于xMBRTUTransmitFSM。& && && &特别提醒,由于我使用的是串口发送完成中断,想要进入该中断服务函数,需要发送一个字节的数据并启动串口发送中断,代码还需要少许修改。在mbRTU.c的eMBRTUSend中稍作修改,代码如下。
复制代码/* First byte before the Modbus-PDU is the slave address. */
pucSndBufferCur = ( UCHAR * ) pucFrame - 1;
usSndBufferCount = 1;
/* Now copy the Modbus-PDU into the Modbus-Serial-Line-PDU. */
pucSndBufferCur[MB_SER_PDU_ADDR_OFF] = ucSlaveA
usSndBufferCount += usL
/* Calculate CRC16 checksum for Modbus-Serial-Line-PDU. */
usCRC16 = usMBCRC16( ( UCHAR * ) pucSndBufferCur, usSndBufferCount );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 & 0xFF );
ucRTUBuf[usSndBufferCount++] = ( UCHAR )( usCRC16 && 8 );
/* Activate the transmitter. */
//发送状态转换,在中断中不断发送
eSndState = STATE_TX_XMIT;
//插入代码 启动第一次发送,这样才可以进入发送完成中断
xMBPortSerialPutByte( ( CHAR )*pucSndBufferCur );
pucSndBufferCur++;
usSndBufferCount--;
//使能发送状态,禁止接收状态
vMBPortSerialEnable( FALSE, TRUE );
复制代码写到这里给位可能看的不是很明白,建议研究一下FreeModbus的源码,稍作一些修改使用起来才会更加方便。五 定时器相关部分代码编写& && &&&定时器的作用前面已经说明了,在这里就罗列一下相关的移植代码。定时器的代码要比串口的代码简单一些。static void prvvTIMERExpiredISR( void );BOOLxMBPortTimersInit( USHORT usTim1Timerout50us ){&&TIM_TimeBaseInitTypeDef&&TIM_TimeBaseS&&NVIC_InitTypeDef NVIC_InitS&&//&&uint16_t PrescalerValue = 0;&&&&//使能定时器4时钟&&RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);&&&&//定时器时间基配置说明&&//HCLK为72MHz,APB1经过2分频为36MHz&&//TIM4的时钟倍频后为72MHz(硬件自动倍频,达到最大)&&//TIM4的分频系数为3599,时间基频率为72 / (1 + Prescaler) = 20KHz,基准为50us&&//TIM最大计数值为usTim1Timerout50u&&PrescalerValue = (uint16_t) (SystemCoreClock / 20000) - 1;&&&//定时器1初始化&&TIM_TimeBaseStructure.TIM_Period = (uint16_t) usTim1Timerout50&&TIM_TimeBaseStructure.TIM_Prescaler = PrescalerV&&TIM_TimeBaseStructure.TIM_ClockDivision = 0;&&TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;&&TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);&&//预装载使能&&TIM_ARRPreloadConfig(TIM4, ENABLE);&&&&NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);&&//定时器4中断优先级&&NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;&&NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;&&NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;&&NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;&&NVIC_Init(&NVIC_InitStructure);&&&&//清除溢出中断标志位&&TIM_ClearITPendingBit(TIM4,TIM_IT_Update);&&//定时器4溢出中断关闭&&TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);&&//定时器4禁能&&TIM_Cmd(TIM4,&&DISABLE);&&return TRUE;}voidvMBPortTimersEnable( ){&&TIM_ClearITPendingBit(TIM4, TIM_IT_Update);&&TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);&&//设定定时器4的初始值&&TIM_SetCounter(TIM4,0x0000);&&&//定时器4启动&&TIM_Cmd(TIM4, ENABLE);}voidvMBPortTimersDisable(&&){&&TIM_ClearITPendingBit(TIM4, TIM_IT_Update);&&TIM_ITConfig(TIM4, TIM_IT_Update, DISABLE);&&TIM_SetCounter(TIM4,0x0000);&&&//关闭定时器4&&TIM_Cmd(TIM4, DISABLE);}static void prvvTIMERExpiredISR( void ){& & ( void )pxMBPortCBTimerExpired();}void TIM4_IRQHandler(void){&&if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)&&{& & //清除定时器T4溢出中断标志位& & TIM_ClearITPendingBit(TIM4, TIM_IT_Update);& & prvvTIMERExpiredISR( );&&}}复制代码在这里请注意STM32的TIM有一个自动倍频的功能,如果APB1被分频的话,TIM的时钟就会自动倍频,当然再如何倍频也不应超过72MHz。六 各种寄存器的读或写函数& && && &Modbus通信中,总共有四类的寄存器,开关输入寄存器,线圈寄存器,保持寄存器和输入寄存器。/*** @brief 输入寄存器处理函数,输入寄存器可读,但不可写。* @param pucRegBuffer 返回数据指针* usAddress 寄存器起始地址* usNRegs 寄存器长度* @retval eStatus 寄存器状态*/eMBErrorCode&eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs ){eMBErrorCode eStatus = MB_ENOERR;int16_t iRegI//查询是否在寄存器范围内//为了避免警告,修改为有符号整数if( ( (int16_t)usAddress &= REG_INPUT_START ) \&& ( usAddress + usNRegs &= REG_INPUT_START + REG_INPUT_NREGS ) ){//获得操作偏移量,本次操作起始地址-输入寄存器的初始地址iRegIndex = ( int16_t )( usAddress - REG_INPUT_START );//逐个赋值while( usNRegs & 0 ){//赋值高字节*pucRegBuffer++ = ( uint8_t )( usRegInputBuf[iRegIndex] && 8 );//赋值低字节*pucRegBuffer++ = ( uint8_t )( usRegInputBuf[iRegIndex] & 0xFF );//偏移量增加iRegIndex++;//被操作寄存器数量递减usNRegs--;}}else{//返回错误状态,无寄存器&eStatus = MB_ENOREG;}return eS}/*** @brief 保持寄存器处理函数,保持寄存器可读,可读可写* @param pucRegBuffer 读操作时--返回数据指针,写操作时--输入数据指针* usAddress 寄存器起始地址* usNRegs 寄存器长度* eMode 操作方式,读或者写* @retval eStatus 寄存器状态*/eMBErrorCode&eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs,eMBRegisterMode eMode ){//错误状态eMBErrorCode eStatus = MB_ENOERR;//偏移量int16_t iRegI//判断寄存器是不是在范围内if( ( (int16_t)usAddress &= REG_HOLDING_START ) \&& ( usAddress + usNRegs &= REG_HOLDING_START + REG_HOLDING_NREGS ) ){//计算偏移量iRegIndex = ( int16_t )( usAddress - REG_HOLDING_START );switch ( eMode ){//读处理函数&case MB_REG_READ:while( usNRegs & 0 ){*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] && 8 );*pucRegBuffer++ = ( uint8_t )( usRegHoldingBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}//写处理函数&case MB_REG_WRITE:while( usNRegs & 0 ){usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ && 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}}}else{//返回错误状态eStatus = MB_ENOREG;}return eS}/*** @brief 线圈寄存器处理函数,线圈寄存器可读,可读可写* @param pucRegBuffer 读操作---返回数据指针,写操作--返回数据指针* usAddress 寄存器起始地址* usNRegs 寄存器长度* eMode 操作方式,读或者写* @retval eStatus 寄存器状态*/eMBErrorCodeeMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode ){//错误状态eMBErrorCode eStatus = MB_ENOERR;//寄存器个数int16_t iNCoils = ( int16_t )usNC//寄存器偏移量int16_t usBitO//检查寄存器是否在指定范围内if( ( (int16_t)usAddress &= REG_COILS_START ) &&( usAddress + usNCoils &= REG_COILS_START + REG_COILS_SIZE ) ){//计算寄存器偏移量usBitOffset = ( int16_t )( usAddress - REG_COILS_START );switch ( eMode ){//读操作case MB_REG_READ:while( iNCoils & 0 ){*pucRegBuffer++ = xMBUtilGetBits( ucRegCoilsBuf, usBitOffset,( uint8_t )( iNCoils & 8 ? 8 : iNCoils ) );iNCoils -= 8;usBitOffset += 8;}//写操作case MB_REG_WRITE:while( iNCoils & 0 ){xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,( uint8_t )( iNCoils & 8 ? 8 : iNCoils ),*pucRegBuffer++ );iNCoils -= 8;}}}else{eStatus = MB_ENOREG;}return eS}/*** @brief 开关输入寄存器处理函数,开关输入寄存器,可读* @param pucRegBuffer 读操作---返回数据指针,写操作--返回数据指针* usAddress 寄存器起始地址* usNRegs 寄存器长度* eMode 操作方式,读或者写* @retval eStatus 寄存器状态*/eMBErrorCodeeMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete ){//错误状态eMBErrorCode eStatus = MB_ENOERR;//操作寄存器个数int16_t iNDiscrete = ( int16_t )usND//偏移量uint16_t usBitO//判断寄存器时候再制定范围内if( ( (int16_t)usAddress &= REG_DISCRETE_START ) &&( usAddress + usNDiscrete &= REG_DISCRETE_START + REG_DISCRETE_SIZE ) ){//获得偏移量usBitOffset = ( uint16_t )( usAddress - REG_DISCRETE_START );while( iNDiscrete & 0 ){*pucRegBuffer++ = xMBUtilGetBits( ucRegDiscreteBuf, usBitOffset,( uint8_t)( iNDiscrete & 8 ? 8 : iNDiscrete ) );iNDiscrete -= 8;usBitOffset += 8;}}else{eStatus = MB_ENOREG;}return eS}复制代码
发表评论:
TA的最新馆藏

我要回帖

更多关于 modbus rtu 的文章

 

随机推荐