参考:
https://www.amobbs.com/thread-5491615-1-1.html
https://wenku.baidu.com/view/fdae481db52acfc789ebc982.html

简介

FreeModbus是一款适合嵌入式系统使用,只具有从机功能的开源库。

运行机制

  1. 如何通过串口收发数据

freemodbus通过串口中断的方式接收和发送数据。串口中断接收毋庸置疑,在中断服务函数中把数据保存在数组中,以便稍后处理。但是串口发送中断使用哪种形式?
串口发送中断至少有两种方式,第一种,数据寄存器空中断,只要数据寄存器为空并且中断屏蔽位置位,那么中断就会发生;第二种,发送完成中断,若数据寄存器的数据发送完成并且中断屏蔽位置位,那么中断也会发送。建议各位使用串口发送完成中断。freemodbus多使用RS485通信中,从机要么接收要么发送,多数情况下从机处于接收状态,要有数据发送时才进入发送状态。进入发送状态时,数据被一个一个字节发送出去,当最后一个字节被发送出去之后,从机再次进入接收状态。如果使用发送寄存器为空中断,还需要使用其他的方法才可以判断最后一个字节的数据是否发送完成。如果使用数据寄存器为空中断,那么将很有可能丢失最后一个字节。

  1. 如何判断帧结束

Modbus协议中没有明显的开始符和结束符,而是通过帧与帧之间的间隔时间来判断的。如果在指定的时间内(3.5个字符时间),没有接收到新的字符数据,那么就认为收到了新的帧。Modbus通过时间来判断帧是否接受完成,自然需要单片机中的定时器配合。

需改动文件

  1. portserial.c
#include "port.h"
#include "stm32f10x.h"
#include "bsp_usart.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"/* ----------------------- static functions ---------------------------------*/
static void prvvUARTTxReadyISR(void);  //发送中断处理
static void prvvUARTRxISR(void);  //接收中断处理/* ----------------------- Start implementation -----------------------------*/
void
vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)//适配中断使能
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*/if (xRxEnable){//使能串口接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);}else{USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);}if (xTxEnable){//使能串口发送完成中断USART_ITConfig(USART1, USART_IT_TC, ENABLE);}else{USART_ITConfig(USART1, USART_IT_TC, DISABLE);}}BOOL
xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)//适配串口初始化
{USART_Config(ulBaudRate);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* called. */USART_SendData(USART1, ucByte);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.*/*pucByte = USART_ReceiveData(USART1);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.*/
static 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.*/
static void prvvUARTRxISR(void)
{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_ORE) == SET) //接收溢出中断{USART_ClearITPendingBit(USART1, USART_IT_ORE);prvvUARTRxISR();}if (USART_GetITStatus(USART1, USART_IT_TC) == SET) //发送完成中断{prvvUARTTxReadyISR();USART_ClearITPendingBit(USART1, USART_IT_TC);//}}
  1. porttimer.c
#include "port.h"
#include "bsp_timer2.h"
#include "stm32f10x.h"/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mbport.h"/* ----------------------- static functions ---------------------------------*/
static void prvvTIMERExpiredISR(void);/* ----------------------- Start implementation -----------------------------*/
BOOL
xMBPortTimersInit(USHORT usTim1Timerout50us)  //适配定时器初始化
{timer2_init(usTim1Timerout50us);return TRUE;
}void
vMBPortTimersEnable()
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */TIM_ClearITPendingBit(TIM2, TIM_IT_Update);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);TIM_SetCounter(TIM2, 0x0000);TIM_Cmd(TIM2, ENABLE);
}void
vMBPortTimersDisable()
{/* Disable any pending timers. */TIM_ClearITPendingBit(TIM2, TIM_IT_Update);TIM_ITConfig(TIM2, TIM_IT_Update, DISABLE);TIM_SetCounter(TIM2, 0x0000);TIM_Cmd(TIM2, DISABLE);
}/* 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.*/
static void prvvTIMERExpiredISR(void)
{(void)pxMBPortCBTimerExpired();
}void TIM2_IRQHandler(void)//适配定时器中断服务函数
{if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET){prvvTIMERExpiredISR();TIM_ClearITPendingBit(TIM2, TIM_IT_Update);}
}
  1. mbrtu.c
eMBErrorCode
eMBRTUSend( UCHAR ucSlaveAddress, const UCHAR * pucFrame, USHORT usLength )
{eMBErrorCode    eStatus = MB_ENOERR;USHORT          usCRC16;ENTER_CRITICAL_SECTION(  );/* Check if the receiver is still in idle state. If not we where to* slow with processing the received frame and the master sent another* frame on the network. We have to abort sending the frame.*/if( eRcvState == STATE_RX_IDLE ){/* 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] = ucSlaveAddress;usSndBufferCount += usLength;/* 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++;  /* next byte in sendbuffer. */usSndBufferCount--;
/**************************************************************/vMBPortSerialEnable( FALSE, TRUE );}else{eStatus = MB_EIO;}EXIT_CRITICAL_SECTION(  );return eStatus;
}

自定义函数

#include "stm32f10x.h"
#include "mb.h"
#include "mbutils.h"// PCLK2=HCLK=SYSCLK=PLLCLK=72M  PCLK1= HCLK/2 = 36M//输入寄存器起始地址
#define REG_INPUT_START       0x0000
//输入寄存器数量
#define REG_INPUT_NREGS       8
//保持寄存器起始地址
#define REG_HOLDING_START     0x0000
//保持寄存器数量
#define REG_HOLDING_NREGS     8//线圈起始地址
#define REG_COILS_START       0x0000
//线圈数量
#define REG_COILS_SIZE        16//开关寄存器起始地址
#define REG_DISCRETE_START    0x0000
//开关寄存器数量
#define REG_DISCRETE_SIZE     16//输入寄存器内容
uint16_t usRegInputBuf[REG_INPUT_NREGS] = {0x1000,0x1001,0x1002,0x1003,0x1004,0x1005,0x1006,0x1007};
//输入寄存器起始地址
uint16_t usRegInputStart = REG_INPUT_START;//保持寄存器内容
uint16_t usRegHoldingBuf[REG_HOLDING_NREGS] = {0x147b,0x3f8e,0x147b,0x400e,0x1eb8,0x4055,0x147b,0x408e};
//保持寄存器起始地址
uint16_t usRegHoldingStart = REG_HOLDING_START;//线圈状态
uint8_t ucRegCoilsBuf[REG_COILS_SIZE / 8] = {0x01,0x02};
//开关输入状态
uint8_t ucRegDiscreteBuf[REG_DISCRETE_SIZE / 8] = {0x01,0x02};/*** @Brief : 读输入寄存器处理函数,功能码04* @param  pucRegBuffer    数据缓存区   大端模式,高字节在前* @param  usAddress       寄存器地址 * @param  usNRegs         读取个数* @return eMBErrorCode */
eMBErrorCode
eMBRegInputCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if( ( usAddress >= REG_INPUT_START )\&& ( usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS ) ){iRegIndex = ( int )( usAddress - usRegInputStart );while( usNRegs > 0 ){*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] >> 8 );*pucRegBuffer++ = ( UCHAR )( usRegInputBuf[iRegIndex] & 0xFF );iRegIndex++;usNRegs--;}}else{eStatus = MB_ENOREG;}return eStatus;
}/*** @Brief : 读保持寄存器处理函数,功能码03* @param  pucRegBuffer     * @param  usAddress        * @param  usNRegs          * @param  eMode            * @return eMBErrorCode */
eMBErrorCode
eMBRegHoldingCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs, eMBRegisterMode eMode )
{eMBErrorCode    eStatus = MB_ENOERR;int             iRegIndex;if((usAddress >= REG_HOLDING_START)&&\((usAddress+usNRegs) <= (REG_HOLDING_START + REG_HOLDING_NREGS))){iRegIndex = (int)(usAddress - usRegHoldingStart);switch(eMode){                                       case MB_REG_READ://读 MB_REG_READ = 0while(usNRegs > 0){*pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] >> 8);            *pucRegBuffer++ = (u8)(usRegHoldingBuf[iRegIndex] & 0xFF); iRegIndex++;usNRegs--;                 }                            break;case MB_REG_WRITE://写 MB_REG_WRITE = 1while(usNRegs > 0){usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}               }}else//错误{eStatus = MB_ENOREG;}   return eStatus;
}/*** @Brief : 读线圈寄存器处理函数,功能码01* @param  pucRegBuffer     * @param  usAddress        * @param  usNCoils         * @param  eMode            * @return eMBErrorCode */
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode )
{//错误状态eMBErrorCode eStatus = MB_ENOERR;//寄存器个数int16_t iNCoils = ( int16_t )usNCoils;//寄存器偏移量int16_t usBitOffset;//检查寄存器是否在指定范围内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;}break;//写操作case MB_REG_WRITE:while( iNCoils > 0 ){xMBUtilSetBits( ucRegCoilsBuf, usBitOffset,\( uint8_t )( iNCoils > 8 ? 8 : iNCoils ),*pucRegBuffer++ );iNCoils -= 8;}break;}}else{eStatus = MB_ENOREG;}return eStatus;
}/*** @Brief : 读离散量寄存器处理函数,功能码02* @param  pucRegBuffer     * @param  usAddress        * @param  usNDiscrete      * @return eMBErrorCode */
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{//错误状态eMBErrorCode eStatus = MB_ENOERR;//操作寄存器个数int16_t iNDiscrete = ( int16_t )usNDiscrete;//偏移量uint16_t usBitOffset;//判断寄存器时候再制定范围内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 eStatus;
}int main(void)
{   eMBInit(MB_RTU, 0x01, 0x01, 9600, MB_PAR_NONE);eMBEnable();while (1){eMBPoll();}
}

所遇bug

  1. 查询寄存器地址01,返回02的数据。

寄存器地址:从机内部定义的寄存器地址,要对寻址用的寄存器地址+1【Modbus标准协议规定】。即:从机内部定义的寄存器地址,必须大于1;对于寄存器1-16,寻址时通过0-15来寻址(如:查询寄存器1的值时,指令中的寄存器地址为00 00)。
所以,这是正常的,协议里就是这么规定的。

  1. 波特率设置为115200时无返回
    将定时器节拍设为50us后就正常了,之前分频错了。

程序源码:https://gitee.com/ll0_0ll/FreeModbus_STM32F103
ModbusRTU协议描述:https://download.csdn.net/download/lblmlms/15437417

FreeModbus-移植到stm32f103相关推荐

  1. FreeModbus移植到STM32F103(串行传输方式)

    1.创建工程 2.将FreeModbus源码,拷贝到工程目录 3.将FreeModbus文件添加进工程 添加好之后,编译出现错误 4.移植底层接口 先看第一个错误,缺少port.h 借鉴AVR架构的程 ...

  2. FreeModbus 移植笔记- 1-认识FreeModbus

    FreeModbus 移植笔记 目录 1 FreeMODBUS介绍 2 FreeMODBUS官网及源码下载地址 3 移植之前的准备 3.1 FreeModbus V1.6 ​​​​​​​3.2 Mod ...

  3. STM32F407 freemodbus移植

    STM32F407 freemodbus移植 一.ModBus介绍 Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气Schneider Electric)于1979年为使用可编程 ...

  4. LiteOS移植到STM32F103系列单片机,非常简单

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 LiteOS移植到STM32F103系列单片机 前言 一.下载老版本liteos 二.参考官方的推荐移植步骤 1.新建项目 1.1打开 ...

  5. 将uc/OS移植到stm32F103上

    将uc/OS移植到stm32F103上 一.嵌入式实时操作系统(RTOS) 1.定义 2.实时任务 3.特征 二.使用CubeMX建立STM32F103C8T6HAL库 三.移植uC/OS-III 四 ...

  6. uC/OS-III移植到STM32F103

    **广西河池学院广西高校重点实验室培训基地系统控制与信息处理重点实验室本篇文章来自河池学院:OpenWRT无线路由组写作时间:2021年07月22日** uC/OS-III移植到STM32F103 4 ...

  7. FreeModbus 移植于STM32 实现Modbus RTU通信

    http://ntn314.blog.163.com/blog/static/161743584201233084434579/ 毕业设计自己要做个基于STM32的PLC能直接跑语句表的,现在看来好像 ...

  8. FreeModbus移植到STM32F107(以太网传输方式)

    1.创建工程 配置好之后生成工程 2.将FreeModbus源码,拷贝到工程目录 3.将FreeModbus文件添加进工程 打开mbtcp.c文件发现,受MB_TCP_ENABLED宏定义的影响,所有 ...

  9. FreeRTOS移植到STM32F103

    1.创建工程 2.将FreeRTOS源码,拷贝到工程目录 3.删除其它架构和编译器的代码 查看portable文件夹发现 MemMang为内存管理相关文件需要保留 Commom里面有个mpu相关代码, ...

  10. UCOS II移植到STM32F103开发板

    早期嵌入式开发没有嵌入式操作系统的概念 ,直接操作裸机,在裸机上写程序,比如用51单片机基本就没有操作系统的概念.通常把程序分为两部分:前台系统和后台系统. 简单的小系统通常是前后台系统,这样的程序包 ...

最新文章

  1. Github 3.4k星,200余行代码,让你实时从视频中隐身
  2. Codeforces 1004F Sonya and Bitwise OR (线段树)
  3. 解决spark on yarn报错:File /tmp/hadoop-root/nm-local-dir/filecache does not exist
  4. 禁止Dockpanel拖动
  5. html图片显示原始大小,我如何使PHP / HTML图像在单击时显示原始大小?
  6. SVG(可扩展矢量图)系列教程
  7. Spark : DNS resolution failed for url in bootstrap.servers xx:9092
  8. 【Computer Organization笔记25】I/O:程序直接控制,程序中断方式,直接存储访问(DMA),通道控制方式
  9. springMVC浏览器接受json报406错误的解决方法
  10. 你可能不知道的 Android Studio 小技巧之「多行编辑」
  11. Linux中级之keepalived配置
  12. mfc radio group 设置
  13. GO版本家庭记账系统
  14. Java应用场景 一 模拟: 支付宝支付 + 短信验证
  15. TypeWriter: Neural Type Prediction with Search-based Validation基于搜索的神经网络预测器
  16. 关于Echarts南丁格尔玫瑰图的部分解释
  17. 数电实验:数字时钟设计 (经验分享,仅供参考)
  18. 轻型载货汽车(离合器及传动轴设计)
  19. 【AirSim】Windows下搭建AirSim
  20. 打开window桌面文件夹路径的方法

热门文章

  1. 用余弦相似度破解字体反爬
  2. DOS、DDos攻击详解
  3. 绿盟赛—鲲鹏软件迁移实践
  4. 绿盟SecXOps安全智能分析技术白皮书
  5. Java课程实验报告 实验三 敏捷开发与XP实践
  6. 如果看待HarmonyOS鸿蒙开闭源的抉择
  7. 阿里巴巴为何选择香港 上市部分股本比例是关键
  8. 《LoadRunner 12七天速成宝典》—第1章 1.3节环境搭建之虚拟机配置
  9. 玩转UCI心脏病二分类数据集 领学课笔记
  10. linux执行系统命令卡死问题解决