利用stm32cubemx移植freemodbus代码分享,片子是stm32F407,记录整个调试过程,供大家参考
测试modbus用stm32cubemx的地方很少,只需要生成USART6的串口和TIM4的定时预制
又用了一个led端口,其他的就是下载freemodbus官方提供的最新的modbus1.5.0版本
然后改改里面的设置.
本站相关程序均提供免费下载!
请到本站 区下载!
下面是调试过程:
打开官方的那个modbus1.5解压后,将modbus文件夹整体复制到你工程的src文件夹里面,重命名为modbus_driver
再次在src文件夹里面建立一个名字是modbus_user的文件夹
找到解压后的文件夹里面的demo文件夹下的BARE里面的文件都复制到modbus_user文件夹里面
首先工程中添加了modbus官方驱动代码modbus_driver文件夹,又新建了个文件夹modbus_user
里面添加文件
portevent.c , portserial.c , porttimer.c 文件
继续打开keil配置器,将添加的几个文件夹里面的H文件路径加好
第一步修改portserial.c文件内容
如下:
又用了一个led端口,其他的就是下载freemodbus官方提供的最新的modbus1.5.0版本
然后改改里面的设置.
本站相关程序均提供免费下载!
请到本站 区下载!
下面是调试过程:
打开官方的那个modbus1.5解压后,将modbus文件夹整体复制到你工程的src文件夹里面,重命名为modbus_driver
再次在src文件夹里面建立一个名字是modbus_user的文件夹
找到解压后的文件夹里面的demo文件夹下的BARE里面的文件都复制到modbus_user文件夹里面
首先工程中添加了modbus官方驱动代码modbus_driver文件夹,又新建了个文件夹modbus_user
里面添加文件
portevent.c , portserial.c , porttimer.c 文件
继续打开keil配置器,将添加的几个文件夹里面的H文件路径加好
第一步修改portserial.c文件内容
如下:
#include "port.h"
#include "stm32f4xx_hal.h"
#include "usart.h"
/[i] ----------------------- Modbus includes ----------------------------------[/i]/
#include "mb.h"
#include "mbport.h"
/[i] ----------------------- static functions ---------------------------------[/i]/
//void prvvUARTTxReadyISR( void );
//void prvvUARTRxISR( void );
/[i] ----------------------- Start implementation -----------------------------[/i]/
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if (xRxEnable)
{
__HAL_UART_ENABLE_IT(&huart6,UART_IT_RXNE);
}
else
{
__HAL_UART_DISABLE_IT(&huart6,UART_IT_RXNE);
}
if (xTxEnable)
{
__HAL_UART_ENABLE_IT(&huart6,UART_IT_TXE);
}
else
{
__HAL_UART_DISABLE_IT(&huart6,UART_IT_TXE);
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
return TRUE ;
}
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
/* Put a byte in the UARTs transmit buffer. This function is called
* by the protocol stack if pxMBFrameCBTransmitterEmpty( ) has been
[i] called. [/i]/
if(HAL_UART_Transmit (&huart6 ,(uint8_t *)&ucByte,1,0x01) != HAL_OK )
return FALSE ;
else
return TRUE;
}
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
/* Return the byte in the UARTs receive buffer. This function is called
* by the protocol stack after pxMBFrameCBByteReceived( ) has been called.
*/
if(HAL_UART_Receive (&huart6 ,(uint8_t *)pucByte,1,0x01) != HAL_OK )
return FALSE ;
else
return TRUE;
}
/* Create an interrupt handler for the transmit buffer empty interrupt
* (or an equivalent) for your target processor. This function should then
* call pxMBFrameCBTransmitterEmpty( ) which tells the protocol stack that
* a new character can be sent. The protocol stack will then call
* xMBPortSerialPutByte( ) to send the character.
*/
void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
/* Create an interrupt handler for the receive interrupt for your target
* processor. This function should then call pxMBFrameCBByteReceived( ). The
* protocol stack will then call xMBPortSerialGetByte( ) to retrieve the
* character.
*/
void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
40 个回复
admin
赞同来自: 春水长天 、Nuclear 、迷茫的渔夫
测试软件,可以用普通串口调试工具或者modbus poll软件
普通串口的话,发送代码的时候注意代码的格式,
楼上上有代码格式图片,自己去看啊,
我就写下自己测试用的代码如下:
01---------04--------00--00----------00--04----------F1--C9
从机id---功能码---请求起始地址---请求寄存器数量---CRC校验码
意思是:我准备从id为01号的片子里面连续读多个寄存器值,寄存器的起始地址从0x0000开始,读的数量是0x0004
CRC校验码由软件计算得来的,懒得手算.........如下图串口工具包含了这个功能...
你把01 04 00 00 00 04写进去点就出校验码了.
连接好你的串口,打开,波特率都写对,然后,发送,看到返回的数据了吗
返回的数据是:01 04 08 00 11 00 22 00 33 00 44 2D 37
01---------- 04-------- 08----------00 11------- 00 22--- 00 33 ---00 44--- 2D 37
从机的id---功能码---返回字节数---第一个数据---第二个---第三个---第四个---校验码
正好和我们设置的初始数据一致,所以代码移植成功!
也可以用modbus poll软件测试
如下图:打开后在白色任意地方点鼠标右键,选择
slave ID 写1 从机地址我们片子里面定义的是0x01嘛
Function选择04 我们测试上面都说了只用了0x04嘛
Address写0 请求地址从0x00开始
Quantity 写4 ,请求数量是4,即连续读取4个数据字节
ScanRate写1000ms,这个默认就行
Display 选择Hex吧,好看
设置完成后,点击导航栏的Connection按钮,选择波特率和端口,确认
连接ok了,看到里面的数据了吗?正是11 22 33 44
是我们定义的那个
ok,结束
admin
赞同来自: 春水长天 、、低調ヤ尛儍 、Nuclear 、nippon1218 、lukunlei
串口代码生成后不做任何修改.
打开stm32f4xx_it.c这个中断文件,头部添加
修改串口中断函数为
对了,先去1楼中的portserial.c文件里面删掉
void prvvUARTTxReadyISR( void );
void prvvUARTRxISR( void );
前面的static 啊,不删就没办法在中断文件里面引用了
admin
赞同来自: 春水长天 、Nuclear 、仁深似海 、jk407529339 、zhouzhi666
这次打开porttimer.c文件,处理定时部分
不解释了啊.一看就懂,基本没修改该文件结构,
对了,此处的定时时间初始化函数xMBPortTimersInit( USHORT usTim1Timerout50us )
我就没写代码进去,因为我用stm32cubemx生成的tim4定时程序,已经自动配置好了,无需
再次初始化了
贴上定时初始化代码
定时器的中断优先级如下
再次,修改stm32f4xx_it.c中断文件里面的TIM中断
额,这次不用修改了,只需在文件的末尾添加如下代码
含义是:定时器更新中断,调用的中断回调函数,执行的prvvTIMERExpiredISR( );这个事porttimer.c里面的一个函数.至于这个函数的意思如下
Create an ISR which is called whenever the timer has expired. This function
must then call pxMBPortCBTimerExpired( ) to notify the protocol stack that
the timer has expired.
百度翻译为:创建一个ISR即每当计时器过期了。这个函数必须再打电话pxmbportcbtimerexpired()通知协议栈,计时器过期了。
哈哈
admin
赞同来自: 春水长天 、jk407529339
地址;http://www.tuicool.com/articles/iQF7ba
admin
赞同来自: 春水长天 、jk407529339
port.h文件里面也要改个东西
打开后修改这两句
它自带的那个没定义完全,为了适应不同的片子
好,以上驱动修改完了.
打开下载的modbus1.5文件夹,找到里面的demo,打开里面的BARE里面的demo.c,将
里面的代码复制到用stm32cubemx生成的mian.c文件里面
demo.c里面是一个例子,下面我们修改下,然后上机测试
修改如下:
admin
赞同来自: 春水长天 、jk407529339
下面是主机发送过来的代码格式图:
发送的数据帧里包括从机地址、功能码,寄存器起始地址、以及读寄存器的长度、CRC校验。eMBFuncReadInputRegister要做的事儿就是读出寄存器地址以及寄存器长度后调用eMBRegInputCB,读取成功后返回一个MB_ENOERR状态,表明没有错误发生。
下图是Freemodbus支持以下功能码:
Modbus支持的功能码
如果检测到该帧数据是协议栈支持的功能码,就调用相应的函数进行处理,比如说Read input register,就会调用 在mb.c中定义的static xMBFunctionHandler xFuncHandlers这个二维数组中注册的eMBFuncReadInputRegister函数进行处理
admin
赞同来自: 春水长天 、yuanxin
例如主机发送:01 06 00 01 00 17 98 04
01------------06-----------00 01--------00 17------98 04
从机地址------功能号-------数据地址-------数据-----CRC校验
这一串数据的意思是:把数据 0x0017(十进制23) 写入 1号从机地址的它的 0x0001数据的地址里。
/////////////////////////////////////////////////////////////////////////////////////////////////////////
主机进行读HoldDataReg 操作,则报文是:
01----------- 03----------00 01---------00 01-----------D5 CA
从机地址-------功能号------数据地址------读取数据个数---- CRC校验
那么单片机接收到这串数据根据数据计算CRC校验判断数据是否正确,如果判断数据无误,则结果是:返回信息给主机,返回的信息也是有格式的:
返回内容:
01--------- 03 -----------02-----------0017 ---------F8 4A
从机地址---功能号-----数据字节个数----两个字节数据----CRC校验
MODBUS主机就完成了一次对从机数据的读操作,实现了通讯。
admin
赞同来自: 春水长天
官方代码不做任何修改的情况下,利用modbus poll软件调试
发现读数据的时候,地址不是按照设想的起始地址开始读的,这也导致读取的数量必须减一,要不然
会提示地址不合法........
意思就是读地址数据的时候只能从第二个地址开始读,读取的数量必须是你设想的减一,哎
不管理解没理解,反正这个貌似是个问题.
找了下,在读取函数文件名是mbfuncinput.c文件里面
有个函数
eMBFuncReadInputRegister( UCHAR pucFrame, USHORT usLen )
{
USHORT usRegAddress;
USHORT usRegCount;
UCHAR *pucFrameCur;
eMBException eStatus = MB_EX_NONE;
eMBErrorCode eRegStatus;
if( *usLen == ( MB_PDU_FUNC_READ_SIZE + MB_PDU_SIZE_MIN ) )
{
usRegAddress = ( USHORT )( pucFrame << 8 );
usRegAddress |= ( USHORT )( pucFrame );
// usRegAddress++; //这句话把传来的地址自加了1,其实不应该加的,所以要注释掉
usRegCount = ( USHORT )( pucFrame << 8 );
usRegCount |= ( USHORT )( pucFrame );
/* Check if the number of registers to read is valid. If not
* return Modbus illegal data value exception.
*/
if( ( usRegCount >= 1 )
&& ( usRegCount < MB_PDU_FUNC_READ_REGCNT_MAX ) )
{
/ Set the current PDU data pointer to the beginning. /
pucFrameCur = &pucFrame;
*usLen = MB_PDU_FUNC_OFF;
............................................下面省略.......................................................
如上粗体所示,就是它导致了上面提到的bug,删掉就可以了,不会影响使用的.
特此记录..............................................................................
admin
赞同来自: leefine 、春水长天 、Nuclear
这里说一下.
这个文件里面的代码结构基本stm32cubemx生成什么样就什么样,不要修改,只需添加一些
这个必须调用,头文件嘛,
定义寄存器输入开始地址0x00,和寄存器数量NREGS=number Regs(猜的)4
定义一个用户要使用的起始地址和定义一个缓存器(比如你的AD值就可以放进去,到时候传给上位机)
初始化modbus的串口和定时器....额,其实这个有用的也就是RTU和0x01了,RTU说明是使用的RTU方式,0x01
说明从机的id是0x01,上位机到时候要用到的,其他的用stm32cubemx的生成的代码已经初始化好了...........
使能并连续查询modbus是否有数据接受到....................
下面是关键点了.
你百度modbus了的话,
eMBRegInputCB();
eMBRegHoldingCB();
eMBRegCoilsCB();
eMBRegDiscreteCB();
四个函数对应的不同的操作码,分别是
//读数字寄存器 功能码0x04
// 寄存器的读写函数 支持的命令为读 0x03 和写0x06
//读/写开关寄存器 0x01 x05
//读开关寄存器 0x02
我们测试之用到了04功能码
我们初始化一个数组值为
当上位机发送04功能码来的时候,eMBRegInputCB();就来处理
①,当上位机请求地址usAddress这个变量值大于系统起始地址REG_INPUT_START,并且请求的起始地址加上请求的数量小于等于系统起始地址加上系统寄存器数量,就执行下面的代码
说白了,就是请求的地址和数量必须在系统有效范围内.汗
我们刚才定义了REG_INPUT_START =0 和REG_INPUT_NREGS = 4 的说
所以只要请求地址最大和最小范围不超过系统定义就行了.
②,开始处理数据,将寄存器里面的值按照请求的地址一个一个取出来
leefine - ST产品开发大佬
赞同来自: №鸭梨鸭梨大鸭梨大鸭梨大
hagiliak
赞同来自:
阿躍
赞同来自:
版主你好,为何照步骤去移植modbus,只有第一次传输是正确,第二次以上则会出现timeout呢?,无法像文章中里面的一样每次传送正确
阿躍
赞同来自:
石缝中的荨麻
赞同来自:
单细胞
赞同来自:
单细胞
赞同来自:
{
/* If xRXEnable enable serial receive interrupts. If xTxENable enable
* transmitter empty interrupts.
*/
if (xRxEnable)
{
__HAL_UART_ENABLE_IT(&huart6,UART_IT_RXNE);
}
else
{
__HAL_UART_DISABLE_IT(&huart6,UART_IT_RXNE);
}
if (xTxEnable)
{
__HAL_UART_ENABLE_IT(&huart6,UART_IT_TXE);
}
else
{
__HAL_UART_DISABLE_IT(&huart6,UART_IT_TXE);
}
}
这里说的利用串口中断接受数据的时候的函数。那我改用usb传输的时候这部分是该怎么做?还有那个potrtimer.c这个函数其实他的定时是做什么用的?我是新手很多东西都还不懂,望指教。
单细胞
赞同来自:
bavol
赞同来自: leafbo 、ZG.Kuang
eMBRTUReceive( UCHAR * pucRcvAddress, UCHAR ** pucFrame, USHORT * pusLength )
{
BOOL xFrameReceived = FALSE;
eMBErrorCode eStatus = MB_ENOERR;
ENTER_CRITICAL_SECTION( );
assert( usRcvBufferPos < MB_SER_PDU_SIZE_MAX );
/* Length and CRC check */
if( ( usRcvBufferPos >= MB_SER_PDU_SIZE_MIN )
&& ( usMBCRC16( ( UCHAR * ) ucRTUBuf, usRcvBufferPos ) == 0 ) )
{
/* Save the address field. All frames are passed to the upper layed
* and the decision if a frame is used is done there.
*/
*pucRcvAddress = ucRTUBuf[MB_SER_PDU_ADDR_OFF];
/* Total length of Modbus-PDU is Modbus-Serial-Line-PDU minus
* size of address field and CRC checksum.
*/
*pusLength = ( USHORT )( usRcvBufferPos - MB_SER_PDU_PDU_OFF - MB_SER_PDU_SIZE_CRC );
/* Return the start of the Modbus PDU to the caller. */
*pucFrame = ( UCHAR * ) & ucRTUBuf[MB_SER_PDU_PDU_OFF];
xFrameReceived = TRUE;
}
else
{
eStatus = MB_EIO;
}
EXIT_CRITICAL_SECTION( );
return eStatus;
}
..\Src\modbus\rtu\mbrtu.c(153): warning: #550-D: variable "xFrameReceived" was set but never used
请问为什么有这个警告,xFrameReceived不是已经被调用了吗?
miko的脑残粉1号
赞同来自:
李川 - 90后嵌入式
赞同来自:
miao0256
赞同来自: 夏霜
没有中断发送!!??
Beloring
赞同来自: 兔斯基 、ASWaterbenben
求楼主公开源码,讲的云里雾里,不知所云
wansaiyon
赞同来自:
求楼主公开代码
我歌月徘徊 - QQ778575669
赞同来自:
学习
我歌月徘徊 - QQ778575669
赞同来自:
我在试着移植到429上面,hal库,但是遇到了问题。串口中断 定时器中断都没问题 用串口助手发送0A 03 00 00 00 01 85 71 仿真进去 就收到了一个字节。。。
那后来呢
赞同来自:
楼主请问您定时器四设置的时间多长?50us吗?
Lotus.
赞同来自:
多多学习!
gcrisis
赞同来自:
楼主 你好
我调用 __set_PRIMASK(1)提示未定义,然后我发现这个函数被定义为静态内联函数,请问你是把静态属性去掉了吗?
D&dol
赞同来自:
太长有点晕菜啊,想吐!
一见如故
赞同来自:
学习,借鉴
兰生
赞同来自:
按照楼主的方法弄可以进行modbus通讯。但是我在cubemx添加其他的定时器中断,就不行了。求解?
leafbo
赞同来自:
同问:
..\Src\modbus\rtu\mbrtu.c(153): warning: #550-D: variable "xFrameReceived" was set but never used
请问为什么有这个警告,?
flyshaw
赞同来自:
学习了,感谢!
tmooc
赞同来自:
感谢
志成科技
赞同来自:
学习中,感谢!!
逆流沙
赞同来自:
楼主讲的很好,谢谢分享
老罗
赞同来自:
讲的不错 谢谢分享
ASWaterbenben
赞同来自:
楼主讲的很好,但是stm32cube定时器的时钟源频率具体多少没有说,在移植定时器部分有一定困难
口袋里的糖果
赞同来自:
多多学习!
牧羊人qq
赞同来自:
楼主这个是从设备仿真吧,有没有主设备仿真的