[APM32F4] 【APM32F402R Micro-EVB开发板测评】基于RT-Thread及Freemodbus在Geehy APM32F402R Micro-EVB上实现Modbus主机

[复制链接]
 楼主| hbzjt2011 发表于 2025-8-2 14:07 | 显示全部楼层 |阅读模式
本帖最后由 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)
系统架构
  1. 应用层 (user_mb_app.c)
  2.     ↓
  3. Modbus协议栈 (mb.c, mbrtu.c等)
  4.     ↓  
  5. 移植层 (mb_port_*.c)
  6.     ↓
  7. ThreadX RTOS
  8.     ↓
  9. APM32F402硬件层


2. 环境搭建2.1 RT-Thread配置在RT-Thread配置中启用以下组件:
  1. RT-Thread Components → Device Drivers → Using Serial Device Drivers
  2. RT-Thread Components → Device Drivers → Using Pin Device Drivers
  3. RT-Thread online packages → IoT - internet of things → freemodbus
52356688da6af54ac9.png
2620688da6dac18e4.png
2.2 Freemodbus软件包配置
在RT-Thread配置菜单中设置:
93630688da725ec876.png
34061688da7654847e.png
使用pkgs --update更新组件
90260688da7a476620.png
使用scons --target=mdk5更新工程
76549688da7f0bd9a5.png


3. 软件实现
生成的Keil工程如下:
22365688da83aaf868.png
board.c
  1. /*
  2. * Copyright (c) 2006-2022, RT-Thread Development Team
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. *
  6. * Change Logs:
  7. * Date           Author       Notes
  8. * 2020-08-20     Abbcc        first version
  9. */

  10. #include "board.h"

  11. void apm32_usart_init(void)
  12. {
  13.     GPIO_Config_T GPIO_ConfigStruct = {0U};

  14. #ifdef BSP_USING_UART1
  15.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
  16.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_USART1);

  17.     GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;
  18.     GPIO_ConfigStruct.pin = GPIO_PIN_9;
  19.     GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
  20.     GPIO_Config(GPIOA, &GPIO_ConfigStruct);

  21.     GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
  22.     GPIO_ConfigStruct.pin = GPIO_PIN_10;
  23.     GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
  24.     GPIO_Config(GPIOA, &GPIO_ConfigStruct);
  25. #endif

  26. #ifdef BSP_USING_UART2
  27.     RCM_EnableAPB2PeriphClock(RCM_APB2_PERIPH_GPIOA);
  28.     RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_USART2);

  29.     GPIO_ConfigStruct.mode = GPIO_MODE_AF_PP;
  30.     GPIO_ConfigStruct.pin = GPIO_PIN_2;
  31.     GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
  32.     GPIO_Config(GPIOA, &GPIO_ConfigStruct);

  33.     GPIO_ConfigStruct.mode = GPIO_MODE_IN_FLOATING;
  34.     GPIO_ConfigStruct.pin = GPIO_PIN_3;
  35.     GPIO_ConfigStruct.speed = GPIO_SPEED_50MHz;
  36.     GPIO_Config(GPIOA, &GPIO_ConfigStruct);
  37. #endif
  38. }

  39. void apm32_msp_can_init(void *Instance)
  40. {
  41. #if defined(BSP_USING_CAN1) || defined(BSP_USING_CAN2)
  42.     GPIO_Config_T  GPIO_InitStructure;
  43.     CAN_T *CANx = (CAN_T *)Instance;

  44.     if (CAN1 == CANx)
  45.     {
  46.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);

  47.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);

  48.         /* PB8: CAN1_RX, PB9: CAN1_TX */
  49.         GPIO_InitStructure.pin = GPIO_PIN_8 | GPIO_PIN_9;
  50.         GPIO_InitStructure.mode = GPIO_MODE_AF;
  51.         GPIO_InitStructure.otype = GPIO_OTYPE_PP;
  52.         GPIO_InitStructure.speed = GPIO_SPEED_100MHz;
  53.         GPIO_InitStructure.pupd = GPIO_PUPD_UP;
  54.         GPIO_Config(GPIOB, &GPIO_InitStructure);

  55.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_8, GPIO_AF_CAN1);
  56.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_9, GPIO_AF_CAN1);
  57.     }
  58.     else if (CAN2 == CANx)
  59.     {
  60.         /* When using the CAN2 peripheral, the CAN1 clock must be turned on */
  61.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN1);
  62.         RCM_EnableAPB1PeriphClock(RCM_APB1_PERIPH_CAN2);

  63.         RCM_EnableAHB1PeriphClock(RCM_AHB1_PERIPH_GPIOB);

  64.         /* PB12: CAN2_RX, PB13: CAN2_TX */
  65.         GPIO_InitStructure.pin = GPIO_PIN_12 | GPIO_PIN_13;
  66.         GPIO_InitStructure.mode = GPIO_MODE_AF;
  67.         GPIO_InitStructure.otype = GPIO_OTYPE_PP;
  68.         GPIO_InitStructure.speed = GPIO_SPEED_100MHz;
  69.         GPIO_InitStructure.pupd = GPIO_PUPD_UP;
  70.         GPIO_Config(GPIOB, &GPIO_InitStructure);

  71.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_12, GPIO_AF_CAN2);
  72.         GPIO_ConfigPinAF(GPIOB, GPIO_PIN_SOURCE_13, GPIO_AF_CAN2);
  73.     }
  74. #endif
  75. }
Modbus主机轮询程序:
  1. /*
  2. * Copyright (c) 2006-2022, RT-Thread Development Team
  3. *
  4. * SPDX-License-Identifier: Apache-2.0
  5. *
  6. * Change Logs:
  7. * Date           Author       Notes
  8. * 2019-06-21     flybreak     first version
  9. */

  10. #include <rtthread.h>

  11. #include "mb.h"
  12. #include "mb_m.h"

  13. #ifdef PKG_MODBUS_MASTER_SAMPLE
  14. #define SLAVE_ADDR      MB_SAMPLE_TEST_SLAVE_ADDR
  15. #define PORT_NUM        MB_MASTER_USING_PORT_NUM
  16. #define PORT_BAUDRATE   MB_MASTER_USING_PORT_BAUDRATE
  17. #else
  18. #define SLAVE_ADDR      0x01
  19. #define PORT_NUM        3
  20. #define PORT_BAUDRATE   115200
  21. #endif
  22. #define PORT_PARITY     MB_PAR_EVEN

  23. #define MB_POLL_THREAD_PRIORITY  10
  24. #define MB_SEND_THREAD_PRIORITY  RT_THREAD_PRIORITY_MAX - 1

  25. #define MB_SEND_REG_START  2
  26. #define MB_SEND_REG_NUM    2

  27. #define MB_POLL_CYCLE_MS   500

  28. static void send_thread_entry(void *parameter)
  29. {
  30.     eMBMasterReqErrCode error_code = MB_MRE_NO_ERR;
  31.     rt_uint16_t error_count = 0;
  32.     USHORT data[MB_SEND_REG_NUM] = {0};

  33.     while (1)
  34.     {
  35.         /* Test Modbus Master */
  36.         data[0] = (USHORT)(rt_tick_get() / 10);
  37.         data[1] = (USHORT)(rt_tick_get() % 10);

  38.         error_code = eMBMasterReqWriteMultipleHoldingRegister(SLAVE_ADDR,          /* salve address */
  39.                                                               MB_SEND_REG_START,   /* register start address */
  40.                                                               MB_SEND_REG_NUM,     /* register total number */
  41.                                                               data,                /* data to be written */
  42.                                                               RT_WAITING_FOREVER); /* timeout */

  43.         /* Record the number of errors */
  44.         if (error_code != MB_MRE_NO_ERR)
  45.         {
  46.             error_count++;
  47.         }
  48.     }
  49. }

  50. static void mb_master_poll(void *parameter)
  51. {
  52.     eMBMasterInit(MB_RTU, PORT_NUM, PORT_BAUDRATE, PORT_PARITY);
  53.     eMBMasterEnable();

  54.     while (1)
  55.     {
  56.         eMBMasterPoll();
  57.         rt_thread_mdelay(MB_POLL_CYCLE_MS);
  58.     }
  59. }

  60. static int mb_master_sample(int argc, char **argv)
  61. {
  62.     static rt_uint8_t is_init = 0;
  63.     rt_thread_t tid1 = RT_NULL, tid2 = RT_NULL;

  64.     if (is_init > 0)
  65.     {
  66.         rt_kprintf("sample is running\n");
  67.         return -RT_ERROR;
  68.     }
  69.     tid1 = rt_thread_create("md_m_poll", mb_master_poll, RT_NULL, 512, MB_POLL_THREAD_PRIORITY, 10);
  70.     if (tid1 != RT_NULL)
  71.     {
  72.         rt_thread_startup(tid1);
  73.     }
  74.     else
  75.     {
  76.         goto __exit;
  77.     }

  78.     tid2 = rt_thread_create("md_m_send", send_thread_entry, RT_NULL, 512, MB_SEND_THREAD_PRIORITY - 2, 10);
  79.     if (tid2 != RT_NULL)
  80.     {
  81.         rt_thread_startup(tid2);
  82.     }
  83.     else
  84.     {
  85.         goto __exit;
  86.     }

  87.     is_init = 1;
  88.     return RT_EOK;

  89. __exit:
  90.     if (tid1)
  91.         rt_thread_delete(tid1);
  92.     if (tid2)
  93.         rt_thread_delete(tid2);

  94.     return -RT_ERROR;
  95. }
  96. MSH_CMD_EXPORT(mb_master_sample, run a modbus master sample);
编译下载程序,在rt-thread的finsh命令中运行mb_master_sample程序
8835688da91760041.png

4. 使用Modbus Slave进行测试
4.1 Modbus Slave设置
  • 下载并安装Modbus Slave工具
  • 配置串口参数:
    • 端口:选择对应的COM口
    • 波特率:115200
    • 数据位:8
    • 停止位:1
    • 校验位:None
    • 从站地址:1

93560688da9bc359ee.png
4.2 Modbus Slave测试
可以对保持寄存器进行读取和设置
11973688daa8ec18c3.png

5. 总结
本文介绍了在Geehy APM32F402R Micro-EVB开发板上基于RT-Thread和Freemodbus实现Modbus主机的过程:
  • 掌握RT-Thread下Freemodbus的配置和使用
  • 理解Modbus主机的工作原理和实现方法
  • 学会使用Modbus Slave工具进行通信测试
  • 具备扩展更多Modbus功能的能力


逆鳞风暴 发表于 2025-8-4 14:54 | 显示全部楼层
这个测评很详细,学习了如何在APM32F402R上实现Modbus主机功能,感谢分享!
聪聪哥哥 发表于 2025-8-12 16:56 | 显示全部楼层
老哥 ,这个代码能否分享一下呢?
您需要登录后才可以回帖 登录 | 注册

本版积分规则

个人签名:欢迎参与LabVIEW版块的讨论学习! 点我一键即达

256

主题

2827

帖子

44

粉丝
快速回复 在线客服 返回列表 返回顶部