简介
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。
Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
Modbus通信协议是一种主从的协议,在一个Modbus网络里面只能有一个主机,但是可以有多个从机,从机与从机之间通过地址进行区分,每个从机都具有唯一的设备地址。
每次通讯必须由主机进行发起,从机进行响应。
本次基于开发板的485硬件接口移植Modbus开源协议栈FreeModbus实现Modbus从机。
(FreeModbus目前只能支持从机)
涉及的硬件资源
1、USART
2、timer
USART驱动
使用USART3作为modbus的串口。
配置为发送使能,接收使能,接收中断,发送完成中断,9600bps波特率,8N1。
配置代码:
static void NVIC_Configuration_Usart_Modbus(void);
static void GPIO_Configuration_Usart_Modbus(void);
void usart_modbus_com_config(void);
void usart_modbus_init(void)
{
RCC_EnableAPB2PeriphClk(USART_MODBUS_GPIO_CLK | RCC_APB2_PERIPH_AFIO, ENABLE);
USART_MODBUS_APBxClkCmd(USART_MODBUS_CLK, ENABLE);
NVIC_Configuration_Usart_Modbus();
GPIO_Configuration_Usart_Modbus();
usart_modbus_com_config();
}
static void NVIC_Configuration_Usart_Modbus(void)
{
NVIC_InitType NVIC_InitStructure;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
NVIC_InitStructure.NVIC_IRQChannel = USART_MODBUS_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
static void GPIO_Configuration_Usart_Modbus(void)
{
GPIO_InitType GPIO_InitStructure;
GPIO_InitStructure.Pin = USART_MODBUS_RxPin;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_InitPeripheral(USART_MODBUS_GPIO, &GPIO_InitStructure);
GPIO_InitStructure.Pin = USART_MODBUS_TxPin;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitPeripheral(USART_MODBUS_GPIO, &GPIO_InitStructure);
}
void usart_modbus_com_config(void)
{
USART_InitType USART_InitStructure;
USART_InitStructure.BaudRate = 9600;
USART_InitStructure.WordLength = USART_WL_8B;
USART_InitStructure.StopBits = USART_STPB_1;
USART_InitStructure.Parity = USART_PE_NO;
USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
USART_InitStructure.Mode = USART_MODE_RX | USART_MODE_TX;
USART_Init(USART_MODBUS, &USART_InitStructure);
USART_ClrIntPendingBit(USART_MODBUS, USART_INT_RXDNE);
USART_ClrFlag(USART_MODBUS, USART_FLAG_RXDNE);
USART_ConfigInt(USART_MODBUS, USART_INT_RXDNE, ENABLE);
USART_ClrIntPendingBit(USART_MODBUS, USART_INT_TXC);
USART_ClrFlag(USART_MODBUS, USART_FLAG_TXC);
USART_ConfigInt(USART_MODBUS, USART_INT_TXC, ENABLE);
USART_Enable(USART_MODBUS, ENABLE);
}
void modbus_put_str(uint8_t *str)
{
while(*str != '\0')
{
while(USART_GetFlagStatus(USART_MODBUS, USART_FLAG_TXDE) == RESET);
USART_SendData(USART_MODBUS, *str);
str++;
}
}
void modbus_rx_int_config(uint8_t enable)
{
USART_ConfigInt(USART_MODBUS, USART_INT_RXDNE, enable);
}
void modbus_tx_int_config(uint8_t enable)
{
USART_ConfigInt(USART_DEBUG, USART_INT_TXDE, enable);
}
Timer驱动
FreeModbus需要用到一个50us时基的定时器,所以这里先将timer的驱动编写好,并测试成功。
timer驱动配置:
时基50us,带有输入参数,参数表示50us的n倍,也就是参数为1表示定时50us,参数为2表示2*50us,以此类推。
配置代码如下:
#include "timer.h"
void TIM1_config(uint16_t n_us_50)
{
TIM_TimeBaseInitType TIM_TimeBaseStructure;
RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_TIM1, ENABLE);
TIM_TimeBaseStructure.Period = 36 * n_us_50 * 50;
TIM_TimeBaseStructure.Prescaler = 3;
TIM_TimeBaseStructure.ClkDiv = 0;
TIM_TimeBaseStructure.CntMode = TIM_CNT_MODE_UP;
TIM_InitTimeBase(TIM1, &TIM_TimeBaseStructure);
TIM_ConfigPrescaler(TIM1, 3, TIM_PSC_RELOAD_MODE_IMMEDIATE);
TIM_ConfigInt(TIM1, TIM_INT_UPDATE, ENABLE);
TIM_Enable(TIM1, ENABLE);
}
void NVIC_Config_timer1(void)
{
NVIC_InitType NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM1_UP_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void timer1_init(void)
{
NVIC_Config_timer1();
TIM1_config(20);
}
void timer1_init_for_modbus(uint16_t us_50)
{
NVIC_Config_timer1();
TIM1_config(us_50);
}
FreeModbus库驱动对接
重点来了,这是本篇最核心的东西。
1、下载FreeModbus源码
源码下载地址:https://www.embedded-experts.at/en/freemodbus-downloads/
2、将源码加入工程
源码解压后,得到如下文件和文件夹:
我们需要的是modbus这个文件夹,和demo->BARE下的port文件夹。
将这个两个文件夹添加到工程中:
添加头文件路径“
3、修改portserial.c文件
portserial.c文件主要是对UART驱动的对接,需要对接的函数主要有4个。
① void vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
用于对串口发送和接收进行使能或者失能
② BOOL xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
用于对485进行初始化
③BOOLxMBPortSerialPutByte( CHAR ucByte )
用于485发送一个字节
④BOOLxMBPortSerialGetByte( CHAR * pucByte )
用于485读取一个字节
另外,我将uart的回调函数也是放在这个文件中。
对接后完整portserial.c文件如下:
#include "port.h"
#include "uart.h"
#include "mb.h"
#include "mbport.h"
static void prvvUARTTxReadyISR( void );
static void prvvUARTRxISR( void );
void
vMBPortSerialEnable( BOOL xRxEnable, BOOL xTxEnable )
{
if (xRxEnable == TRUE)
{
modbus_rx_state = 0;
}
else
{
modbus_rx_state = 1;
}
if (xTxEnable == TRUE)
{
modbus_tx_state = 0;
}
else
{
modbus_tx_state = 1;
}
}
BOOL
xMBPortSerialInit( UCHAR ucPORT, ULONG ulBaudRate, UCHAR ucDataBits, eMBParity eParity )
{
usart_modbus_init();
return TRUE;
}
BOOL
xMBPortSerialPutByte( CHAR ucByte )
{
USART_SendData(USART_MODBUS, ucByte);
return TRUE;
}
BOOL
xMBPortSerialGetByte( CHAR * pucByte )
{
*pucByte = modbus_rx_char;
return TRUE;
}
static void prvvUARTTxReadyISR( void )
{
pxMBFrameCBTransmitterEmpty( );
}
static void prvvUARTRxISR( void )
{
pxMBFrameCBByteReceived( );
}
void USART_MODBUS_IRQHandler(void)
{
if (USART_GetIntStatus(USART_MODBUS, USART_INT_RXDNE) != RESET)
{
if (modbus_rx_state == 0)
{
modbus_rx_char = USART_ReceiveData(USART_MODBUS);
prvvUARTRxISR();
}
else
{
USART_ReceiveData(USART_MODBUS);
}
USART_ClrIntPendingBit(USART_MODBUS, USART_INT_RXDNE);
USART_ClrFlag(USART_MODBUS, USART_FLAG_RXDNE);
}
if (USART_GetIntStatus(USART_MODBUS, USART_INT_TXC) != RESET)
{
if (modbus_tx_state == 0)
{
prvvUARTTxReadyISR();
}
USART_ClrIntPendingBit(USART_MODBUS, USART_INT_TXC);
USART_ClrFlag(USART_MODBUS, USART_FLAG_TXDE);
}
}
4、修改porttimer.c文件
该文件需要对接3个函数。
① BOOL xMBPortTimersInit( USHORT usTim1Timerout50us )
用于对timer进行初始化
② void vMBPortTimersEnable( )
用于使能timer,让timer开始计数
③ void vMBPortTimersDisable( )
用于失能timer,让timer停止计数
另外,timer中断的回调函数也是放在这个文件中。
对接后完整porttimer.c文件如下:
#include "port.h"
#include "mb.h"
#include "mbport.h"
#include "timer.h"
#include "led.h"
#include <stdio.h>
static void prvvTIMERExpiredISR( void );
BOOL
xMBPortTimersInit( USHORT usTim1Timerout50us )
{
timer1_init_for_modbus(usTim1Timerout50us);
return TRUE;
}
void
vMBPortTimersEnable( )
{
TIM_SetCnt(TIM1, 0);
TIM_Enable(TIM1, ENABLE);
}
void
vMBPortTimersDisable( )
{
TIM_Enable(TIM1, DISABLE);
}
static void prvvTIMERExpiredISR( void )
{
( void )pxMBPortCBTimerExpired( );
}
uint16_t timer_int_cnt = 0;
void TIM1_UP_IRQHandler(void)
{
if (TIM_GetIntStatus(TIM1, TIM_INT_UPDATE) != RESET)
{
TIM_ClrIntPendingBit(TIM1, TIM_INT_UPDATE);
prvvTIMERExpiredISR();
timer_int_cnt++;
if(timer_int_cnt > 1000)
{
LedBlink(LED1_PORT, LED1_PIN);
timer_int_cnt = 0;
}
}
}
5、定义Modbus寄存器并实现读写函数
我将寄存器定义和读写函数的实现放在了自己定义的modbus_read_write.c文件中,完整代码如下:
注意:这个几个函数必须实现,否则会报错
#include "port.h"
#include "mb.h"
#include "mbport.h"
#define REG_INPUT_START 1000
#define REG_INPUT_NREGS 4
#define REG_HOLDING_START 1000
#define REG_HOLDING_NREGS 130
static USHORT usRegInputStart = REG_INPUT_START;
static USHORT usRegInputBuf[REG_INPUT_NREGS] = {1,2,3,4};
static USHORT usRegHoldingStart = REG_HOLDING_START;
static USHORT usRegHoldingBuf[REG_HOLDING_NREGS] = {10,11,12,13};
void free_modbus_init(eMBMode eMode, UCHAR ucSlaveAddress, UCHAR ucPort, ULONG ulBaudRate, eMBParity eParity)
{
eMBInit( eMode, ucSlaveAddress, ucPort, ulBaudRate, eParity );
eMBEnable();
}
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++ = ( unsigned char )( usRegInputBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( usRegInputBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
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:
while( usNRegs > 0 )
{
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] >> 8 );
*pucRegBuffer++ = ( unsigned char )( usRegHoldingBuf[iRegIndex] & 0xFF );
iRegIndex++;
usNRegs--;
}
break;
case MB_REG_WRITE:
while( usNRegs > 0 )
{
usRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
usRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
iRegIndex++;
usNRegs--;
}
}
}
else
{
eStatus = MB_ENOREG;
}
return eStatus;
}
eMBErrorCode
eMBRegCoilsCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNCoils, eMBRegisterMode eMode )
{
return MB_ENOREG;
}
eMBErrorCode
eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
return MB_ENOREG;
}
6、修改main.c文件
在main()函数中加入Modbus初始化相关内容和状态轮询函数,完整内容如下:
/*****************************************************************************
* Copyright (c) 2019, Nations Technologies Inc.
*
* All rights reserved.
* ****************************************************************************
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the disclaimer below.
*
* Nations' name may not be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* DISCLAIMER: THIS SOFTWARE IS PROVIDED BY NATIONS "AS IS" AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT ARE
* DISCLAIMED. IN NO EVENT SHALL NATIONS BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
* OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
* ****************************************************************************/
/**
* [url=home.php?mod=space&uid=1455510]@file[/url] main.c
* [url=home.php?mod=space&uid=40524]@author[/url] Nations
* [url=home.php?mod=space&uid=644434]@version[/url] v1.0.0
*
* [url=home.php?mod=space&uid=855824]@copyright[/url] Copyright (c) 2019, Nations Technologies Inc. All rights reserved.
*/
#include "main.h"
#include <stdio.h>
#include <stdint.h>
#include "led.h"
#include "clock.h"
#include "uart.h"
#include "timer.h"
#include "modbus_read_write.h"
#include "can.h"
/**
* @brief Inserts a delay time.
* [url=home.php?mod=space&uid=3142012]@param[/url] count specifies the delay time length.
*/
void Delay(uint32_t count)
{
for (; count > 0; count--)
;
}
/**
* @brief Main program.
*/
int main(void)
{
/*SystemInit() function has been called by startup file startup_n32g45x.s*/
clock_init();
/* Initialize Led1~Led5 as output pushpull mode*/
LedInit(LED1_PORT, LED1_PIN);
LedInit(LED2_PORT, LED2_PIN);
LedInit(LED3_PORT, LED3_PIN);
LedOff(LED3_PORT, LED3_PIN);
//usart_debug_init();
/* 初始化Modbus端口和状态 */
free_modbus_init(MB_RTU, 1, 5, 9600, MB_PAR_NONE);
//usart_modbus_init();
//timer1_init();
Tx_Frame_Message_Init();
/* Configures CAN IOs */
CAN_GPIO_init();
/* Configures CAN */
CAN_init();
CAN_Tx_Process();
while (1)
{
/* 状态机轮询 */
( void )eMBPoll();
CAN_Recieve_Process();
//printf((const char *)"N32G45x\r\n");
//put_str((uint8_t *)"modbus test...\r\n");
//modbus_put_str((uint8_t *)"modbus test...\r\n");
/* Insert delay */
//Delay(0x28FFFF);
}
}
/**
* @brief Assert failed function by user.
* @param file The name of the call that failed.
* @param line The source line number of the call that failed.
*/
#ifdef USE_FULL_ASSERT
void assert_failed(const uint8_t* expr, const uint8_t* file, uint32_t line)
{
while (1)
{
}
}
#endif // USE_FULL_ASSERT
/**
* @}
*/
7、修改mbrtu.c文件
修改mbrtu.c中eMBRTUSend函数,如果不修改无法触发485发送。
8、修改mbconfig.h文件
取消对ASCII的支持
9、去掉寄存器地址自动加一
需要修改四个文件,分别为mbfunccoils.c、mbfuncdisc.c、mbfuncholding.c、mbfuncinput.c
直接去对应的文件搜索:usRegAddress++;
然后屏蔽掉。
mbfunccoils.c里面有三处
mbfuncdisc.c里面有一处
mbfuncholding.c里面有三处
mbfuncinput.c里面有一处
如果不修改,绝对不能正常工作。
测试
编译下载运行,并使用PC端工具Modbus Poll模拟Modbus主机来进行测试。
如下:
经过测试,实测开发板能正确与Modbus Poll相互通讯。
至此,FreeModbus开源库移植成功/