1. Modbus协议¶
本例程是裸机例程,效率相对带操作系统的Modbus较低,推荐使用带操作系统的Modbus。
基于RT-Thread的modbus官方资料:http://packages.rt-thread.org/detail.html?package=freemodbus
1.1. 协议简介¶
Modbus是一种串行通信协议,是Modicon公司(现在的施耐德电气 Schneider Electric)于1979年为使用可编程逻辑控制器(PLC)通信而发表。Modbus已经成为工业领域通信协议的业界标准(De facto),并且现在是工业电子设备之间常用的连接方式。
modbus入门请参考:http://www.elecfans.com/rengongzhineng/596297.html
1.2. 主机例程¶
首先来看一下例程用到的外设宏定义,modbus的通信串口定义为串口2,A2引脚为TX,A3引脚为RX,也可以改为其他串口。串口波特率定义为115200,无校验位。 C2引脚为RS485控制引脚,如果需要使用RS485通信则开启 MODBUS_MASTER_USE_CONTROL_PIN 宏定义即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | // ... ...
//引脚定义
/***********************usart*****************************/
#define DEBUG_USART USART2 //串口2
#define DEBUG_USART_CLK_ENABLE() __USART2_CLK_ENABLE(); //串口2时钟使能
#define RCC_PERIPHCLK_UARTx RCC_PERIPHCLK_USART2
#define RCC_UARTxCLKSOURCE_SYSCLK RCC_USART2CLKSOURCE_SYSCLK
#define __HAL_RCC_USARTx_CLK_DISABLE __HAL_RCC_USART2_CLK_DISABLE();
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define DEBUG_USART_RX_PIN GPIO_PIN_3
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define DEBUG_USART_TX_PIN GPIO_PIN_2
#define DEBUG_USART_IRQHandler USART2_IRQHandler
#define DEBUG_USART_IRQ USART2_IRQn
#define MB_MASTER_USARTx 2 //使用串口2
#define MB_MASTER_USART_BAUDRATE 115200 //波特率
#define MB_MASTER_USART_PARITY UART_PARITY_NONE
/*********************usart end***************************************/
/***********************485*****************************/
/* 如果需要使用串口转485 请打开此宏 */
//#define MODBUS_MASTER_USE_CONTROL_PIN
#define MODBUS_MASTER_GPIO_PORT GPIOC
#define MODBUS_MASTER_GPIO_PIN GPIO_PIN_2 //RS485控制引脚
#define MODBUS_MASTER_GPIO_PIN_HIGH GPIO_PIN_SET
#define MODBUS_MASTER_GPIO_PIN_LOW GPIO_PIN_RESET
#define MODBUS_MASTER_GPIO_CLK_ENABLE() __GPIOC_CLK_ENABLE()
/***********************485 end*************************************/
// ... ...
|
下面看一下main函数内的程序框架,在main函数中需要先初始化HAL库、系统时钟,然后初始化管脚及定时器,初始化完FreeModbus主机后就可以启动主机。 最后再循环中不断轮训主机及测试函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | int main(void)
{
/* HAL库初始化 */
HAL_Init();
/* 系统时钟初始化 */
SystemClock_Config();
/* 管脚时钟初始化 */
MX_GPIO_Init();
/* 定时器4初始化 */
MX_TIM4_Init();
/* 串口2初始化在portserial.c中 */
/* FreeModbus主机初始化 */
eMBMasterInit(MB_RTU, MB_MASTER_USARTx, MB_MASTER_USART_BAUDRATE, MB_MASTER_USART_PARITY);
/* 启动FreeModbus主机 */
eMBMasterEnable();
while (1)
{
/* 主机轮训 */
eMBMasterPoll();
/* 测试函数 通过宏定义选择哪种操作 函数在modbus_master_test.c中*/
test(MB_USER_INPUT_REG);
/* 延时1秒 */
HAL_Delay(MB_POLL_CYCLE_MS);
}
}
|
下面是测试函数相关的宏定义,在main函数内的test函数传入 下面宏定义的 MB_USER_HOLD 时表示写多个保持寄存器,MB_USER_COILS为写多个线圈,MB_USER_INPUT_REG为读输入寄存器,
1 2 3 4 5 6 7 8 9 10 11 | /* 测试功能参数 */
#define MB_SAMPLE_TEST_SLAVE_ADDR 1 //从机设备地址
#define MB_REG_START 1 //发送数据起始位置
#define MB_SEND_REG_NUM 4 //发送数据数量
#define MB_READ_REG_NUM 4 //接收数据数量
#define WAITING_FOREVER -1 //永久等待
/* 通过宏定义选择功能 */
#define MB_USER_HOLD 1 //写多个保持寄存器
#define MB_USER_COILS 2 //写多个线圈
#define MB_USER_INPUT_REG 3 //读输入寄存器
|
最后来看一下测试函数内做了些什么,在测试函数中不断的在 Hlod_buff 中更新时钟信息,再根据形参MB选择当前要做的操作, 当 MB 为MB_USER_HOLD时执行写多个保持寄存器,MB_SAMPLE_TEST_SLAVE_ADDR为从机设备地址,MB_REG_START为数据起始位置, MB_SEND_REG_NUM为写数据总数,Hlod_buff为数据,WAITING_FOREVER为执行这个操作的等待时间,这些信息在上面的modbus_master_test.h中已经定义好了。
用switch只是为了方便调用,与modbus协议本身并没有关系。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 | void test(char MB)
{
USHORT Hlod_buff[4];
UCHAR Coils[4]={1,0,1,0};
Hlod_buff[0] = HAL_GetTick() & 0xff; //获取时间戳 提出1至8位
Hlod_buff[1] = (HAL_GetTick() & 0xff00) >> 8; //获取时间戳 提出9至16位
Hlod_buff[2] = (HAL_GetTick() & 0xff0000) >> 16 ; //获取时间戳 提出17至24位
Hlod_buff[3] = (HAL_GetTick() & 0xff000000) >> 24; //获取时间戳 提出25至32位
/* 注:各操作的API在mb_m.h中 */
switch(MB)
{
case MB_USER_HOLD:
/* 写多个保持寄存器值 */
eMBMasterReqWriteMultipleHoldingRegister(MB_SAMPLE_TEST_SLAVE_ADDR, //从机设备地址
MB_REG_START, //数据起始位置
MB_SEND_REG_NUM, //写数据总数
Hlod_buff, //数据
WAITING_FOREVER); //永久等待
break;
case MB_USER_COILS:
/* 写多个线圈 */
eMBMasterReqWriteMultipleCoils(MB_SAMPLE_TEST_SLAVE_ADDR, //从机设备地址
MB_REG_START, //数据起始位置
MB_SEND_REG_NUM, //写数据总数
Coils, //数据
WAITING_FOREVER); //永久等待
break;
case MB_USER_INPUT_REG:
/* 读输入寄存器 */
eMBMasterReqReadInputRegister(MB_SAMPLE_TEST_SLAVE_ADDR, //从机设备地址
MB_REG_START-1, //数据起始位置
MB_READ_REG_NUM-2, //读数据总数
WAITING_FOREVER); //永久等待
break;
}
}
|
将主机代码下载至开发板后可用上位机进行测试,用USB转串口模块连接开发板串口2再接入电脑,打开Modbus Slave 上位机进行测试(上位机在modbus资料中)。
modbus上位机使用方法请参考:https://blog.csdn.net/byxdaz/article/details/77979114
1.3. 从机例程¶
从机代码与主机代码外设宏定义相同,都是采用串口2,A2引脚为TX,A3引脚为RX,也可以改为其他串口。串口波特率定义为115200,无校验位。 C2引脚为RS485控制引脚,如果需要使用RS485通信则开启 MODBUS_MASTER_USE_CONTROL_PIN 宏定义即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | // ... ...
//引脚定义
/***********************usart*****************************/
#define DEBUG_USART USART2 //串口2
#define DEBUG_USART_CLK_ENABLE() __USART2_CLK_ENABLE(); //串口2时钟使能
#define RCC_PERIPHCLK_UARTx RCC_PERIPHCLK_USART2
#define RCC_UARTxCLKSOURCE_SYSCLK RCC_USART2CLKSOURCE_SYSCLK
#define __HAL_RCC_USARTx_CLK_DISABLE __HAL_RCC_USART2_CLK_DISABLE();
#define DEBUG_USART_RX_GPIO_PORT GPIOA
#define DEBUG_USART_RX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define DEBUG_USART_RX_PIN GPIO_PIN_3
#define DEBUG_USART_TX_GPIO_PORT GPIOA
#define DEBUG_USART_TX_GPIO_CLK_ENABLE() __GPIOA_CLK_ENABLE()
#define DEBUG_USART_TX_PIN GPIO_PIN_2
#define DEBUG_USART_IRQHandler USART2_IRQHandler
#define DEBUG_USART_IRQ USART2_IRQn
#define MB_MASTER_USARTx 2 //使用串口2
#define MB_MASTER_USART_BAUDRATE 115200 //波特率
#define MB_MASTER_USART_PARITY UART_PARITY_NONE
/*********************usart end***************************************/
/***********************485*****************************/
/* 如果需要使用串口转485 请打开此宏 */
//#define MODBUS_MASTER_USE_CONTROL_PIN
#define MODBUS_MASTER_GPIO_PORT GPIOC
#define MODBUS_MASTER_GPIO_PIN GPIO_PIN_2 //RS485控制引脚
#define MODBUS_MASTER_GPIO_PIN_HIGH GPIO_PIN_SET
#define MODBUS_MASTER_GPIO_PIN_LOW GPIO_PIN_RESET
#define MODBUS_MASTER_GPIO_CLK_ENABLE() __GPIOC_CLK_ENABLE()
/***********************485 end*************************************/
// ... ...
|
下面看一下main函数内的程序框架,在main函数中需要先初始化HAL库、系统时钟,然后初始化管脚及定时器,初始化完FreeModbus从机后就可以启动从机。 在循环中不断更新保持寄存器值、输入寄存器值、线圈、离散输入变量的值,这些值实际上是时间戳,然后是从机轮询,等待主机的命令。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | int main(void)
{
/* HAL库初始化 */
HAL_Init();
/* 系统时钟初始化 */
SystemClock_Config();
/* 管脚时钟初始化 */
MX_GPIO_Init();
/* 定时器4初始化 */
MX_TIM4_Init();
/* 串口2初始化在portserial.c中 */
/* Modbus初始化 */
eMBInit( MB_RTU, MB_SAMPLE_TEST_SLAVE_ADDR, MB_MASTER_USARTx, MB_MASTER_USART_BAUDRATE, MB_PAR_NONE);
/* 启动Modbus从机 */
eMBEnable();
while (1)
{
/* 更新保持寄存器值 */
usSRegHoldBuf[0] = HAL_GetTick() & 0xff; //获取时间戳 提出1至8位
usSRegHoldBuf[1] = (HAL_GetTick() & 0xff00) >> 8; //获取时间戳 提出9至16位
usSRegHoldBuf[2] = (HAL_GetTick() & 0xff0000) >> 16 ; //获取时间戳 提出17至24位
usSRegHoldBuf[3] = (HAL_GetTick() & 0xff000000) >> 24; //获取时间戳 提出25至32位
/* 更新输入寄存器值 */
usSRegInBuf[0] = HAL_GetTick() & 0xff; //获取时间戳 提出1至8位
usSRegInBuf[1] = (HAL_GetTick() & 0xff00) >> 8; //获取时间戳 提出9至16位
usSRegInBuf[2] = (HAL_GetTick() & 0xff0000) >> 16 ; //获取时间戳 提出17至24位
usSRegInBuf[3] = (HAL_GetTick() & 0xff000000) >> 24; //获取时间戳 提出25至32位
/* 更新线圈 */
ucSCoilBuf[0] = HAL_GetTick() & 0xff; //获取时间戳 提出1至8位
ucSCoilBuf[1] = (HAL_GetTick() & 0xff00) >> 8; //获取时间戳 提出9至16位
ucSCoilBuf[2] = (HAL_GetTick() & 0xff0000) >> 16 ; //获取时间戳 提出17至24位
ucSCoilBuf[3] = (HAL_GetTick() & 0xff000000) >> 24; //获取时间戳 提出25至32位
/* 离散输入变量 */
ucSDiscInBuf[0] = HAL_GetTick() & 0xff; //获取时间戳 提出1至8位
ucSDiscInBuf[1] = (HAL_GetTick() & 0xff00) >> 8; //获取时间戳 提出9至16位
/* 可以不用延时,如果延时时间过长主机会timeout */
HAL_Delay(200);
/*从机轮询*/
eMBPoll();
}
}
|
将从机代码下载至开发板后可用上位机进行测试,用USB转串口模块连接开发板串口2再接入电脑,打开Modbus poll 上位机进行测试(上位机在modbus资料中)。
modbus上位机使用方法请参考:https://blog.csdn.net/byxdaz/article/details/77979114