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 宏定义即可。

usart.h
 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主机后就可以启动主机。 最后再循环中不断轮训主机及测试函数。

main.c
 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为读输入寄存器,

modbus_master_test.h
 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协议本身并没有关系。

modbus_master_test.c
 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 宏定义即可。

usart.h
 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从机后就可以启动从机。 在循环中不断更新保持寄存器值、输入寄存器值、线圈、离散输入变量的值,这些值实际上是时间戳,然后是从机轮询,等待主机的命令。

main.c
 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