本帖最后由 hbzjt2011 于 2025-8-2 14:11 编辑
1. 概述 本文将介绍如何在Geehy APM32F402R Micro-EVB开发板上,基于RT-Thread实时操作系统和Freemodbus开源库实现Modbus主机功能,并使用Modbus Slave工具进行通信测试。
1.1 硬件平台- 开发板: Geehy APM32F402R Micro-EVB
- MCU: APM32F402RBT6 (Cortex-M4F, 128KB Flash, 32KB SRAM)
- 通信接口: UART2
1.2 软件环境- 操作系统: RT-Thread 5.2.1
- Modbus库: Freemodbus 1.6
- 开发环境: Keil MDK
- 测试工具: Modbus Slave
1.3 FreeModbus 简介FreeModbus 是一个基于 BSD 协议的开源嵌入式 Modbus 协议栈,支持 RTU、ASCII 及 TCP 三种传输模式,具有高度可移植性和模块化设计。它可运行于裸机系统,也适用于集成 RTOS 的嵌入式平台,特别适合 STM32、AVR、ARM Cortex-M 等微控制器平台。FreeModbus 提供从站和主站功能接口,便于快速集成至工业控制、自动化设备中。我们将采用 RTU 从站模式与主机进行通信,结合 UART 作为物理接口。 标准Modbus功能: - 支持保持寄存器读写(功能码03/06/16)
- 支持输入寄存器读取(功能码04)
- 支持线圈读写(功能码01/05/15)
- 支持离散输入读取(功能码02)
系统架构
- 应用层 (user_mb_app.c)
- ↓
- Modbus协议栈 (mb.c, mbrtu.c等)
- ↓
- 移植层 (mb_port_*.c)
- ↓
- ThreadX RTOS
- ↓
- APM32F402硬件层
2. 环境搭建2.1 RT-Thread配置在RT-Thread配置中启用以下组件:
- RT-Thread Components → Device Drivers → Using Serial Device Drivers
- RT-Thread Components → Device Drivers → Using Pin Device Drivers
- RT-Thread online packages → IoT - internet of things → freemodbus
2.2 Freemodbus软件包配置在RT-Thread配置菜单中设置: 使用pkgs --update更新组件 使用scons --target=mdk5更新工程
3. 软件实现
生成的Keil工程如下:
board.c
- /*
- * Copyright (c) 2006-2022, RT-Thread Development Team
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Change Logs:
- * Date Author Notes
- * 2020-08-20 Abbcc first version
- */
- #include "board.h"
- void apm32_usart_init(void)
- {
- GPIO_Config_T GPIO_ConfigStruct = {0U};
- #ifdef BSP_USING_UART1
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1);
- GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;
- GPIO_ConfigStruct.pin = GPIO_PIN_9;
- GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
- GPIO_Config(GPIOA, &GPIO_ConfigStruct);
- GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
- GPIO_ConfigStruct.pin = GPIO_PIN_10;
- GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
- GPIO_Config(GPIOA, &GPIO_ConfigStruct);
- #endif
- #ifdef BSP_USING_UART2
- RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART2);
- GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;
- GPIO_ConfigStruct.pin = GPIO_PIN_2;
- GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
- GPIO_Config(GPIOA, &GPIO_ConfigStruct);
- GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
- GPIO_ConfigStruct.pin = GPIO_PIN_3;
- GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
- GPIO_Config(GPIOA, &GPIO_ConfigStruct);
- #endif
- }
- void apm32_msp_can_init(void *Instance)
- {
- #if defined(BSP_USING_CAN1) || defined(BSP_USING_CAN2)
- GPIO_Config_T GPIO_InitStructure;
- CAN_T *CANx = (CAN_T *)Instance;
- if (CAN1 == CANx)
- {
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
- /* PB8: CAN1_RX, PB9: CAN1_TX */
- GPIO_InitStructure.pin = GPIO_PIN_8 | GPIO_PIN_9;
- GPIO_InitStructure.mode = GPIO_MODE_AF;
- GPIO_InitStructure.otype = GPIO_OTYPE_PP;
- GPIO_InitStructure.speed = GPIO_SPEED_100MHz;
- GPIO_InitStructure.pupd = GPIO_PUPD_UP;
- GPIO_Config(GPIOB, &GPIO_InitStructure);
- GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_CAN1);
- GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_CAN1);
- }
- else if (CAN2 == CANx)
- {
- /* When using the CAN2 peripheral, the CAN1 clock must be turned on */
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);
- RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN2);
- RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);
- /* PB12: CAN2_RX, PB13: CAN2_TX */
- GPIO_InitStructure.pin = GPIO_PIN_12 | GPIO_PIN_13;
- GPIO_InitStructure.mode = GPIO_MODE_AF;
- GPIO_InitStructure.otype = GPIO_OTYPE_PP;
- GPIO_InitStructure.speed = GPIO_SPEED_100MHz;
- GPIO_InitStructure.pupd = GPIO_PUPD_UP;
- GPIO_Config(GPIOB, &GPIO_InitStructure);
- GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_12, GPIO_AF_CAN2);
- GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_13, GPIO_AF_CAN2);
- }
- #endif
- }
Modbus主机轮询程序:
- /*
- * Copyright (c) 2006-2022, RT-Thread Development Team
- *
- * SPDX-License-Identifier: Apache-2.0
- *
- * Change Logs:
- * Date Author Notes
- * 2019-06-21 flybreak first version
- */
- #include <rtthread.h>
- #include "mb.h"
- #include "mb_m.h"
- #ifdef PKG_MODBUS_MASTER_SAMPLE
- #define SLAVE_ADDR MB_SAMPLE_TEST_SLAVE_ADDR
- #define PORT_NUM MB_MASTER_USING_PORT_NUM
- #define PORT_BAUDRATE MB_MASTER_USING_PORT_BAUDRATE
- #else
- #define SLAVE_ADDR 0x01
- #define PORT_NUM 3
- #define PORT_BAUDRATE 115200
- #endif
- #define PORT_PARITY MB_PAR_EVEN
- #define MB_POLL_THREAD_PRIORITY 10
- #define MB_SEND_THREAD_PRIORITY RT_THREAD_PRIORITY_MAX - 1
- #define MB_SEND_REG_START 2
- #define MB_SEND_REG_NUM 2
- #define MB_POLL_CYCLE_MS 500
- static void send_thread_entry(void *parameter)
- {
- eMBMasterReqErrCode error_code = MB_MRE_NO_ERR;
- rt_uint16_t error_count = 0;
- USHORT data[MB_SEND_REG_NUM] = {0};
- while (1)
- {
- /* Test Modbus Master */
- data[0] = (USHORT)(rt_tick_get() / 10);
- data[1] = (USHORT)(rt_tick_get() % 10);
- error_code = eMBMasterReqWriteMultipleHoldingRegister(SLAVE_ADDR, /* salve address */
- MB_SEND_REG_START, /* register start address */
- MB_SEND_REG_NUM, /* register total number */
- data, /* data to be written */
- RT_WAITING_FOREVER); /* timeout */
- /* Record the number of errors */
- if (error_code != MB_MRE_NO_ERR)
- {
- error_count++;
- }
- }
- }
- static void mb_master_poll(void *parameter)
- {
- eMBMasterInit(MB_RTU, PORT_NUM, PORT_BAUDRATE, PORT_PARITY);
- eMBMasterEnable();
- while (1)
- {
- eMBMasterPoll();
- rt_thread_mdelay(MB_POLL_CYCLE_MS);
- }
- }
- static int mb_master_sample(int argc, char **argv)
- {
- static rt_uint8_t is_init = 0;
- rt_thread_t tid1 = RT_NULL, tid2 = RT_NULL;
- if (is_init > 0)
- {
- rt_kprintf("sample is running\n");
- return -RT_ERROR;
- }
- tid1 = rt_thread_create("md_m_poll", mb_master_poll, RT_NULL, 512, MB_POLL_THREAD_PRIORITY, 10);
- if (tid1 != RT_NULL)
- {
- rt_thread_startup(tid1);
- }
- else
- {
- goto __exit;
- }
- tid2 = rt_thread_create("md_m_send", send_thread_entry, RT_NULL, 512, MB_SEND_THREAD_PRIORITY - 2, 10);
- if (tid2 != RT_NULL)
- {
- rt_thread_startup(tid2);
- }
- else
- {
- goto __exit;
- }
- is_init = 1;
- return RT_EOK;
- __exit:
- if (tid1)
- rt_thread_delete(tid1);
- if (tid2)
- rt_thread_delete(tid2);
- return -RT_ERROR;
- }
- MSH_CMD_EXPORT(mb_master_sample, run a modbus master sample);
编译下载程序,在rt-thread的finsh命令中运行mb_master_sample程序
4. 使用Modbus Slave进行测试
4.1 Modbus Slave设置- 下载并安装Modbus Slave工具
- 配置串口参数:
- 端口:选择对应的COM口
- 波特率:115200
- 数据位:8
- 停止位:1
- 校验位:None
- 从站地址:1
4.2 Modbus Slave测试
可以对保持寄存器进行读取和设置
5. 总结本文介绍了在Geehy APM32F402R Micro-EVB开发板上基于RT-Thread和Freemodbus实现Modbus主机的过程: - 掌握RT-Thread下Freemodbus的配置和使用
- 理解Modbus主机的工作原理和实现方法
- 学会使用Modbus Slave工具进行通信测试
- 具备扩展更多Modbus功能的能力
|