RTT小师弟 · 2021年04月26日

RT-Thread 应用笔记 - libmodbus RTU RS485 从机

RT-Thread 应用笔记 - 不正确使用LOG也会引发hard fault

RT-Thread 应用笔记 - RTC Alarm 组件的使用

RT-Thread 应用笔记 - freemodbus RTU RS485 从机

RT-Thread 应用笔记 - freemodbus RTU RS485 主机

RT-Thread 应用笔记 - libmodbus RTU RS485 从机

RT-Thread 应用笔记 - libmodbus RTU RS485 主机

RT-Thread 应用笔记 - STM32 CAN 通信双机

RT-Thread USB学习实践系列

背景

  • modbus协议,简单,实用,一些小数据通信控制场合,用的比较多。
  • 部分用户使用起来,并不是想象中那么的顺利。
  • libmodbus,可用于modbus主从机的开发。
  • libmodbus代码基于C库,有动态内存的使用,与freemodbus相比,在适配时稍有不同。
  • 基于rt-thread,已经有libmodbus的软件包,默认是主机。
  • 这里基于STM32F103 实现modbus RS485 RTU从机的功能。
  • 部分使用libmodbus的用户,配置好libmodbus,发现无法通信,就开始【求助】,可能需要了解引脚配置!

前言

  • 嵌入式软件工程师,需要了解原理图,了解引脚定义,如串口、RS485控制引脚等等。
  • 我的板子,MCU 为STM32F103C8T6, Flash与SRAM都相对不大。
  • UART1用于rt-thread MSH串口。
  • UART3用于RS485,用于modbus rtu串口。

移植

  • 首先需要配置好rt-thread最小系统,在rt-thread源码工程里,找一个与使用MCU平台相似的BSP,修改使用。
  • 下载配置libmodbus软件包,使用ENV menuconfig

2020-12-02_221952.png

2020-12-02_222024.png

  • 配置完成后,需要ENV pkg --update,更新下载libmodbus。
  • ENV scons --target=mdk5生成Keil MDK5的工程。
  • 编译,STM32F103C8T6,发现Flash不够了,需要更改编译选项: -O2.

2020-12-02_222742.png

  • 需要更改libmodbus的demo,默认为主机,这里,我简单修改了一下,实现从机功能
#include <rtthread.h>
#include <board.h>
#include "modbus.h"

#define RS485_RE GET_PIN(A, 8) //RS485控制引脚

static void mb_slave_thread(void *param)
{
    modbus_t *ctx = RT_NULL;
    int rc = 0;
    modbus_mapping_t *mb_mapping = NULL; /* 线圈与寄存器 */
    uint8_t mb_reply[MODBUS_TCP_MAX_ADU_LENGTH];

    ctx = modbus_new_rtu("/dev/uart3", 115200, 'N', 8, 1); /* uart3 */
    modbus_rtu_set_serial_mode(ctx, MODBUS_RTU_RS485);
    modbus_rtu_set_rts(ctx, RS485_RE, MODBUS_RTU_RTS_UP);
    modbus_set_slave(ctx, 3); /* 设置从机地址 */

    mb_mapping = modbus_mapping_new(16, 16, 10, 10);
    if (mb_mapping == RT_NULL)
    {
        rt_kprintf("modbus_mapping_new failed! \n");
        modbus_free(ctx);
        return;
    }
    mb_mapping->tab_registers[0] = 'R';
    mb_mapping->tab_registers[1] = 'T';
    mb_mapping->tab_registers[2] = '-';
    mb_mapping->tab_registers[3] = 'T';
    mb_mapping->tab_registers[4] = 'h';
    mb_mapping->tab_registers[5] = 'r';
    mb_mapping->tab_registers[6] = 'e';
    mb_mapping->tab_registers[7] = 'a';
    mb_mapping->tab_registers[8] = 'd';
    modbus_connect(ctx);
    while (1)
    {
        //modbus loop
        rc = modbus_receive(ctx, mb_reply);
        if (rc > 0)
        {
            modbus_reply(ctx, mb_reply, rc, mb_mapping);
        }
        else if (rc  == -1)
        {
            /* Connection closed by the client or error */
            break;
        }
    }
    modbus_close(ctx);
    modbus_free(ctx);
}

static int rtu_test_init(void)
{
    rt_pin_mode(RS485_RE, PIN_MODE_OUTPUT);
    rt_thread_t tid;
    tid = rt_thread_create("mb_test",
                           mb_slave_thread, RT_NULL,
                           2048,
                           12, 10);
    if (tid != RT_NULL)
        rt_thread_startup(tid);

    return RT_EOK;
}

INIT_APP_EXPORT(rtu_test_init);
  • 注意RS485 引脚的配置:串口引脚,这里是uart3,RS485控制引脚,这里是PA8。
  • uart3的串口引脚配置,rt-thread使用uart3设备,并没有UART3具体引脚的配置。需要手动更改xxx_msp.c文件!

这里修改:stm32f1xx_hal_msp.c

void HAL_UART_MspInit(UART_HandleTypeDef* huart)
{
  GPIO_InitTypeDef GPIO_InitStruct = {0};
  if(huart->Instance==USART1)
  {
  /* USER CODE BEGIN USART1_MspInit 0 */

  /* USER CODE END USART1_MspInit 0 */
    /* Peripheral clock enable */
    __HAL_RCC_USART1_CLK_ENABLE();
  
    __HAL_RCC_GPIOA_CLK_ENABLE();
    /**USART1 GPIO Configuration    
    PA9     ------> USART1_TX
    PA10     ------> USART1_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_9;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

  /* USER CODE BEGIN USART1_MspInit 1 */

  /* USER CODE END USART1_MspInit 1 */
  }
  else if (huart->Instance==USART3)
  {
    __HAL_RCC_USART3_CLK_ENABLE();
  
    __HAL_RCC_GPIOB_CLK_ENABLE();
    /**USART3 GPIO Configuration    
    PB10     ------> USART3_TX
    PB11     ------> USART3_RX 
    */
    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    GPIO_InitStruct.Pin = GPIO_PIN_11;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull = GPIO_NOPULL;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
  }
}
  • 如果串口不通,首先确认下串口引脚配置,RS485的控制引脚
  • libmodbus,默认打开了dfs,所以,ROM空间占用较大,使用动态内存,使用 stdlib string等C库。
  • 可以打开Keil MDK的微库 Use microLib支持。

2020-12-02_234759.png

调试

  • 因为配置的为从机,需要RS485主机进行调试
  • 这里使用USB转RS485,接入电脑,打开modbus poll,开启主机功能。
  • 从机配置的是保持寄存器的读取

2020-12-02_235843.png

  • 通信成功的样子。

总结

  • 参考demo
https://gitee.com/zhangsz0516/rtt_stm32_libmodbus
  • 如果调试不通,需要检查引脚配置、波特率设置、modbus主机设置,可以使用逻辑分析仪,查看串口的数据。
  • libmodbus,主机功能挺全面,可以用于modbus 主从一体协议的实现,
  • libmodbus,好像没有从机地址的判断过滤接口,需要实现时,自己注意,否则,收到主机的命令,从机都会响应。
  • 总线上有多个从机时,每个从机接收数据包后,判断是自己的从机地址,才能响应。
推荐阅读
关注数
8074
内容数
181
小而美的物联网操作系统,经过14年的累积发展,RT-Thread 已经拥有一个国内最大的嵌入式开源社区,同时被广泛应用于能源、车载、医疗、消费电子等多个行业,累积装机量超过4亿台,成为国人自主开发、国内最成熟稳定和装机量最大的开源 RTOS。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息