FreeModbus开源协议栈的(五)野火指南者+Keil+FreeModbus 的Modbus RTU从站
在网上关于STM32F103+FreeModbus 的Modbus RTU从站移植的移植有很多,在此记录一下自己在野火的指南者开发板上基于FreeModbus的Modbus RTU从站的过程。
文章目录
目录
文章目录
硬件准备
软件准备
工程准备工作
移植FreeModbus需要修改的文件
port.h
portserial.c
porttimer.c
portevent.c
mbrtu.c
最后一步 main.c文件
main函数
定义Modbus对象
线圈量
离散输入量
保持寄存器
输入寄存器
测试
硬件准备
野火指南者(STM32F103VET6)
软件准备
2、指南者开发板工程模板(随便一个工程都可以,我习惯用一个移植好库函数的空白工程模板)
工程准备工作
移植FreeModbus需要修改的文件
port.h
port.h的修改主要补充两个关于控制器开关总中断的两个宏定义
#define ENTER_CRITICAL_SECTION( ) __set_PRIMASK(1);//关总中断
#define EXIT_CRITICAL_SECTION( ) __set_PRIMASK(0);//开总中断
补充一下
__set_PRIMASK()这个函数在core_cm3.c里面定义
具体函数定义:
/*** @brief Set the Priority Mask value** @param priMask PriMask** Set the priority mask bit in the priority mask register*/
__ASM void __set_PRIMASK(uint32_t priMask)
{msr primask, r0bx lr
}
portserial.c
//该函数用于RS485接收/发送状态的切换
void vMBPortSerialEnable(BOOL xRxEnable, BOOL xTxEnable)
{/* If xRXEnable enable serial receive interrupts. If xTxENable enable* transmitter empty interrupts.*///接收使能if (xRxEnable == TRUE){//MODBUS_RECIEVE();使用485时使用该函数将485芯片更改为接收状态if (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) == SET){USART_ClearFlag(USART1, USART_FLAG_RXNE);}USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);}else if (xRxEnable == FALSE){//MODBUS_SEND();使用485时使用该函数将485芯片更改为发送状态USART_ITConfig(USART1, USART_IT_RXNE, DISABLE);}//发送使能if (xTxEnable == TRUE){//MODBUS_SEND();使用485时使用该函数将485芯片更改为发送状态if (USART_GetFlagStatus(USART1, USART_FLAG_TC) == SET){USART_ClearFlag(USART1, USART_FLAG_TC);}USART_ITConfig(USART1, USART_IT_TC, ENABLE);}else if (xTxEnable == FALSE){//MODBUS_RECIEVE();使用485时使用该函数将485芯片更改为接收状态USART_ITConfig(USART1, USART_IT_TC, DISABLE);}
}
//该函数用于串口的初始化,要根据工程实际使用的板子进行初始化
//如果加上485的话还要在代码里面增加485芯片发送/接收使能引脚的驱动代码
BOOL xMBPortSerialInit(UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity)
{GPIO_InitTypeDef GPIO_InitStructure;USART_InitTypeDef USART_InitStructure;// 串口中断优先级配置NVIC_InitTypeDef NVIC_InitStructure;// 打开串口GPIO的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);// 打开串口外设的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);// 将USART Tx的GPIO配置为推挽复用模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);// 将USART Rx的GPIO配置为浮空输入模式GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_Init(GPIOA, &GPIO_InitStructure);// 配置串口的工作参数// 配置波特率USART_InitStructure.USART_BaudRate = 9600;// 配置 针数据字长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_None;// 配置工作模式,收发一起USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;// 完成串口的初始化配置USART_Init(USART1, &USART_InitStructure);/* 嵌套向量中断控制器组选择 */NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);/* 配置USART为中断源 */NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;/* 抢断优先级*/NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;/* 子优先级 */NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;/* 使能中断 */NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;/* 初始化配置NVIC */NVIC_Init(&NVIC_InitStructure);// 使能串口接收中断USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);// 使能串口USART_Cmd(USART1, ENABLE);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;
}
prvvUARTTxReadyISR()和prvvUARTRxISR()两个函数不需要更改,这两个函数需要串口中断中调用。
/* 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();
}
最后一个是串口的中断服务函数,为了方便管理我把串口的中断服务函数写在了portserial.c这个文件里面。因为指南者的usart1连接的是TTL转USB,所以我在这个工程里面使用的是usart1
void USART1_IRQHandler(void)
{if (USART_GetITStatus(USART1, USART_IT_RXNE) != RESET){prvvUARTRxISR();//在串口接收中断,调用FreeModbus的prvvUARTRxISR()函数USART_ClearITPendingBit(USART1, USART_IT_RXNE);}if (USART_GetITStatus(USART1, USART_IT_TC) != RESET){prvvUARTTxReadyISR();//在串口发送中断中,调用FreeModbus的prvvUARTTxReadyISR()函数USART_ClearITPendingBit(USART1, USART_IT_TC);}
}
porttimer.c
//该函数用于初始化定时器
//需要初始化一个定时时间为50us的定时器
//处理器设定主频为72MHz
//这里用到了F103的基本定时器TIM6
BOOL xMBPortTimersInit(USHORT usTim1Timerout50us)
{TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6, ENABLE);// 自动重装载寄存器的值,累计TIM_Period+1个频率后产生一个更新或者中断 计时器计数1次为50usTIM_TimeBaseStructure.TIM_Period = usTim1Timerout50us;// 时钟预分频数为50usTIM_TimeBaseStructure.TIM_Prescaler = 3600 - 1;// 时钟分频因子 ,基本定时器没有,不用管//TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1;// 计数器计数模式,基本定时器只能向上计数,没有计数模式的设置//TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up;// 重复计数器的值,基本定时器没有,不用管//TIM_TimeBaseStructure.TIM_RepetitionCounter=0;// 初始化定时器TIM_TimeBaseInit(TIM6, &TIM_TimeBaseStructure);// 清除计数器中断标志位TIM_ClearFlag(TIM6, TIM_FLAG_Update);// 开启计数器中断TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);// 设置中断组为0NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);// 设置中断来源NVIC_InitStructure.NVIC_IRQChannel = TIM6_IRQn;// 设置主优先级为 0NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;// 设置抢占优先级为3NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;NVIC_Init(&NVIC_InitStructure);return TRUE;
}
//定时器使能函数
//调用库函数使能TIM6
void vMBPortTimersEnable()
{/* Enable the timer with the timeout passed to xMBPortTimersInit( ) */TIM_ClearITPendingBit(TIM6, TIM_IT_Update);TIM_ITConfig(TIM6, TIM_IT_Update, ENABLE);TIM_SetCounter(TIM6, 0x00000000);TIM_Cmd(TIM6, ENABLE);
}
//定时器失能函数
//调用库函数失能TIM6
void vMBPortTimersDisable()
{/* Disable any pending timers. */TIM_ClearITPendingBit(TIM6, TIM_IT_Update);TIM_ITConfig(TIM6, TIM_IT_Update, DISABLE);TIM_SetCounter(TIM6, 0x00000000);TIM_Cmd(TIM6, DISABLE);
}
//prvvTIMERExpiredISR()函数不需要更改
//需要在定时器定时结束后调用prvvTIMERExpiredISR()
static void prvvTIMERExpiredISR(void)
{(void)pxMBPortCBTimerExpired();
}//TIM6定时溢出中断函数
//为了方便管理把TIM6的中断服务函数放在porttimer.c函数
//定时器溢出后清除定时标志
//再调用FreeModbus的prvvTIMERExpiredISR()
void TIM6_IRQHandler(void)
{if (TIM_GetITStatus(TIM6, TIM_IT_Update) != RESET){TIM_ClearITPendingBit(TIM6, TIM_FLAG_Update);prvvTIMERExpiredISR();}
}
portevent.c
mbrtu.c
在这个工程里面,串口发送中断函数使用的时串口发送完成中断,所以在mbrtu.c文件的 eMBRTUSend() 函数里面还要加上一段函数用于手动发送第一个字节。
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++;usSndBufferCount--;/*结束*/vMBPortSerialEnable(FALSE, TRUE);}else{eStatus = MB_EIO;}EXIT_CRITICAL_SECTION();return eStatus;
}
到这里FreeModbus的移植(Modbus RTU)已经基本完成。
接下来是编写main函数。
最后一步 main.c文件
main函数
int main(void)
{eMBInit(MB_RTU, 0X01, 1, 9600, MB_PAR_NONE);//初始化FreeModbuseMBEnable();//FreeModbus使能while (1){eMBPoll();//在while (1)循环调用eMBPoll()}
}
定义Modbus对象
线圈量
#define COIL_START 0
#define COIL_NCOILS 100
static USHORT usCoilStart = COIL_START;
static UCHAR usCoilBuf[COIL_NCOILS] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10};
#if S_COIL_NCOILS % 8
UCHAR ucSCoilBuf[COIL_NCOILS / 8 + 1];
#else
UCHAR ucSCoilBuf[COIL_NCOILS / 8];
#endif/*** Modbus slave coils callback function.** @param pucRegBuffer coils buffer* @param usAddress coils address* @param usNCoils coils number* @param eMode read or write** @return result*/
eMBErrorCode
eMBRegCoilsCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNCoils,eMBRegisterMode eMode)
{eMBErrorCode eStatus = MB_ENOERR;USHORT iRegIndex, iRegBitIndex, iNReg;iNReg = usNCoils / 8 + 1;usAddress--;if ((usAddress >= usCoilStart) && (usAddress + usNCoils <= usCoilStart + COIL_NCOILS)){iRegIndex = (USHORT)(usAddress - usCoilStart) / 8;iRegBitIndex = (USHORT)(usAddress - usCoilStart) % 8;switch (eMode){/* read current coil values from the protocol stack. */case MB_REG_READ:while (iNReg > 0){*pucRegBuffer++ = xMBUtilGetBits(&usCoilBuf[iRegIndex++], iRegBitIndex, 8);iNReg--;}pucRegBuffer--;/* last coils */usNCoils = usNCoils % 8;/* filling zero to high bit */*pucRegBuffer = *pucRegBuffer << (8 - usNCoils);*pucRegBuffer = *pucRegBuffer >> (8 - usNCoils);break;/* write current coil values with new values from the protocol stack. */case MB_REG_WRITE:while (iNReg > 1){xMBUtilSetBits(&usCoilBuf[iRegIndex++], iRegBitIndex, 8, *pucRegBuffer++);iNReg--;}/* last coils */usNCoils = usNCoils % 8;/* xMBUtilSetBits has bug when ucNBits is zero */if (usNCoils != 0){xMBUtilSetBits(&usCoilBuf[iRegIndex++], iRegBitIndex, usNCoils, *pucRegBuffer++);}break;}}else{eStatus = MB_ENOREG;}return eStatus;
}
离散输入量
#define DISCRETE_START 0
#define DISCRETE_NDISCRETES 100
static USHORT usNDiscreteStart = DISCRETE_START;
static UCHAR usDiscreteBuf[DISCRETE_NDISCRETES] = {0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10};
#if DISCRETE_NDISCRETES % 8
UCHAR ucSDiscInBuf[DISCRETE_NDISCRETES / 8 + 1];
#else
UCHAR ucSDiscInBuf[DISCRETE_NDISCRETES / 8];
#endif/*** Modbus slave discrete callback function.** @param pucRegBuffer discrete buffer* @param usAddress discrete address* @param usNDiscrete discrete number** @return result*/
eMBErrorCode
eMBRegDiscreteCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNDiscrete)
{eMBErrorCode eStatus = MB_ENOERR;USHORT iRegIndex, iRegBitIndex, iNReg;iNReg = usNDiscrete / 8 + 1;usAddress--;if ((usAddress >= usNDiscreteStart) && (usAddress + usNDiscrete <= usNDiscreteStart + DISCRETE_NDISCRETES)){iRegIndex = (USHORT)(usAddress - usNDiscreteStart) / 8;iRegBitIndex = (USHORT)(usAddress - usNDiscreteStart) % 8;while (iNReg > 0){*pucRegBuffer++ = xMBUtilGetBits(&usDiscreteBuf[iRegIndex++],iRegBitIndex, 8);iNReg--;}pucRegBuffer--;/* last discrete */usNDiscrete = usNDiscrete % 8;/* filling zero to high bit */*pucRegBuffer = *pucRegBuffer << (8 - usNDiscrete);*pucRegBuffer = *pucRegBuffer >> (8 - usNDiscrete);}else{eStatus = MB_ENOREG;}return eStatus;
}
保持寄存器
//保持寄存器变量
#define REG_HOLDING_START 0
#define REG_HOLDING_NREGS 100
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};/*** Modbus slave holding register callback function.** @param pucRegBuffer holding register buffer* @param usAddress holding register address* @param usNRegs holding register number* @param eMode read or write** @return result*/
eMBErrorCode
eMBRegHoldingCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs,eMBRegisterMode eMode)
{eMBErrorCode eStatus = MB_ENOERR;USHORT iRegIndex;/* it already plus one in modbus function method. */usAddress--;if ((usAddress >= usRegHoldingStart) && (usAddress + usNRegs <= usRegHoldingStart + REG_HOLDING_NREGS)){iRegIndex = usAddress - usRegHoldingStart;switch (eMode){/* read current register values from the protocol stack. */case MB_REG_READ:while (usNRegs > 0){*pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] >> 8);*pucRegBuffer++ = (UCHAR)(usRegHoldingBuf[iRegIndex] & 0xFF);iRegIndex++;usNRegs--;}break;/* write current register values with new values from the protocol stack. */case MB_REG_WRITE:while (usNRegs > 0){usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;iRegIndex++;usNRegs--;}break;}}else{eStatus = MB_ENOREG;}return eStatus;
}
输入寄存器
//输入寄存器变量
#define REG_INPUT_START 0
#define REG_INPUT_NREGS 100
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10};eMBErrorCode
eMBRegInputCB(UCHAR *pucRegBuffer, USHORT usAddress, USHORT usNRegs)
{eMBErrorCode eStatus = MB_ENOERR;int iRegIndex;usAddress--;if ((usAddress >= usRegInputStart) && (usAddress + usNRegs <= usRegInputStart + REG_INPUT_NREGS)) //请求地址大于起始地址 && 地址长度小于设定长度{iRegIndex = (int)(usAddress - usRegInputStart);while (usNRegs > 0){*pucRegBuffer++ =(unsigned char)(usRegInputBuf[iRegIndex] >> 8);*pucRegBuffer++ =(unsigned char)(usRegInputBuf[iRegIndex] & 0xFF);iRegIndex++;usNRegs--;}}else{eStatus = MB_ENOREG;}return eStatus;
}
到这里基于FreeModbus的Modbus RTU工程已经完成移植了,接下来是测试各个功能码
测试
电脑使用Modbus Poll模拟主站,开发板通过USB与电脑连接,功能测试正常。
FreeModbus开源协议栈的(五)野火指南者+Keil+FreeModbus 的Modbus RTU从站相关推荐
- FreeModbus开源协议栈的移植和详解(三)- RTU协议代码分析
FreeModbus开源协议栈的移植和详解(三) 目录 概述 一.RTU文件夹的文件 二.mbrtu.c文件 2.1数据类型说明 2.2函数说明 2.1eMBRTUInit()函数 2.2eMBRTU ...
- FreeModbus开源协议栈的移植和详解(一)- FreeModbus的下载和文件结构
FreeModbus开源协议栈的移植和详解(一) 引言 一.FreeModbus的获取 二.FreeModbus文件夹的介绍 三.Modbus文件夹介绍 四.小结 引言 很多做单片机或者嵌入式的朋友对 ...
- Modbus协议栈应用实例之二:Modbus RTU从站应用
自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例.所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,这一篇中我们将使用协议栈实现一个Modbu ...
- FreeModbus开源协议栈-mb.c
mb.c定义了一系列的宏定义.函数指针和全局变量,并使用优先编译指令预编译一些程序代码. eMBErrorCode eMBInit( eMBMode eMode, UCHAR ucSlaveAddre ...
- STM32CubeMX | Modbus RTU 主机协议栈实现(国产单片机、FreeModbus无缝使用)
STM32CubeMX | Modbus RTU 主机协议栈实现 目录 1.前言 2.协议栈API介绍 2.1 控制结构 2.2 主机读线圈状态(CMD1) 2.2 主机读离散量输入(CMD2) 2. ...
- Modbus协议栈应用实例之六:Modbus ASCII从站应用
自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例.所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,这一篇中我们来使用协议栈实现Modbus ...
- Modbus协议栈应用实例之一:Modbus RTU主站应用
自从开源了我们自己开发的Modbus协议栈之后,有很多朋友建议我针对性的做几个示例.所以我们就基于平时我们的应用整理了几个简单但可以说明基本的应用方法的示例,在这一篇中我们先来使用协议栈实现Modbu ...
- 野火指南者移植hal+rtthread+lvgl
作为一个新手,想移植hal+rtthread+lvgl,移植了好多天,碰见了各种问题,最后在rtthread官方找到灵感,参照官方例程移植成功. 通过rtthread env工具新建模板 rtthre ...
- FreeModbus开源协议简介
个人笔记,供个人查阅. 目录 FreeModbus软硬件需求 物理层接口 portserial.c porttimer.c 应用层回调 主函数 运行流程 功能码使用 Function Code:01, ...
最新文章
- linux socket 详解
- Spring构造注入重载
- 开发SPI时不要犯这个错误
- 【OS学习笔记】五 VirtualBox的下载、安装和配置
- 中文依存句法分析概述及应用
- unity3d之kinect 初识
- 七雄争霸mysql修改_七雄争霸单机版游戏
- php 获取手机信息
- Linux 修改环境变量设置的三种方式
- 混沌理论(Chaos Theory)
- 智利车厘子的尺寸说明,给大家扫盲
- Android 微信双开
- 【组合数学】递推方程 ( 递推方程解与特征根之间的关系定理 | 递推方程解的线性性质定理 | 递推方程解的形式 )
- html编辑器贴吧,推荐HTML编辑器
- 2019年北航计算机夏令营
- c语言数组读心术,读心术
- PyNN:神经网络模拟器的通用接口
- surface pro linux服务器,在Surface Pro上安装Ubuntu
- 【Hydro】龙格-库塔方法的公式推导
- 武汉理工大学计算机基础与编程综合实验——网吧计费管理系统第二个版本