Agile Modbus移植教程--基于GD32F103C8T6+RT-Thread+mdk5

主机移植

0.下载源码

开源地址:GitHub - loogg/agile_modbus

1.复制源码

1.2、目录结构

名称 说明
doc 文档
examples 例子 参考示例
figures 素材
inc 头文件 移植需要
src 源代码 移植需要
util 提供简单实用的组件 移植需要

本次移植需要的有

  • 参考demo

  • 头文件

  • 源码

  • 从机辅助文件


2.添加源码

3.头文件

4.接口实现

1.串口发送接口

​ 串口的发送时基于发送缓冲为空的基础实现

2.接收一帧完整数据接口

基于RT-Thread前提下,本次移植使用的串口中断方式接收数据,每次收到一个数据就开启一个单次定时器,在定时器未超时之前收到数据立即重置定时器,当定时器超时时表示一帧数据接收完毕,完毕后使用信号量通知主线程

3.本次实现基于GD32F103C8T6+RT-Thread+mdk5

5.主机使用示例

c 复制代码
#include "agile_modbus.h"
#include <stdio.h>
#include <stdlib.h>

#include "bsp_uart.h"
#include "main.h"
#include "bsp_timer_base.h"
#include "rtthread.h"


static uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];//发送缓存
static uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];//接收缓存
static uint16_t ctx_read_buf_index=0;
static rt_timer_t timer1;
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
/* 定时器 1 超时函数 */
static void timeout1(void *parameter)
{
    //通知数据接收完毕
    rt_timer_stop(timer1);
    rt_sem_release(dynamic_sem);

}

static void modbus_recv_callback(uint8_t ch)
{

    /* 重置定时器 */
    if (timer1 != RT_NULL)
    {
        rt_timer_stop(timer1);
        rt_timer_start(timer1);
    }
    //保存数据
    ctx_read_buf[ctx_read_buf_index++%AGILE_MODBUS_MAX_ADU_LENGTH]=ch;
}

void  modbus_cycle_entry(void)
{

    uint16_t hold_register[10];
    uint8_t coils[10];

    agile_modbus_rtu_t ctx_rtu;
    agile_modbus_t *ctx = &ctx_rtu._ctx;
    //rtu初始化
    agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));
    //设置从机地址
    agile_modbus_set_slave(ctx, 1);

    rt_kprintf("agile_modbus master runing...\n");
    //设置串口回调函数
    bsp_uart_set_recv_callback(BSP_UART_ID_0,modbus_recv_callback);
    /* 创建定时器 1 周期定时器 */
    timer1 = rt_timer_create("timer1", timeout1,
                             RT_NULL, 5,
                             RT_TIMER_FLAG_PERIODIC);
    /* 创建一个动态信号量,初始值是 0 ,用于通知一帧数据接收完毕*/
    dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);
    while (1) {

        rt_thread_delay(1000*5);
        //构建线圈读取指令
        int send_len = agile_modbus_serialize_read_bits(ctx, 0, 10);
        //清空数据
        ctx_read_buf_index=0;
        //发送数据
        bsp_uart_send_datas(BSP_UART_ID_0,ctx->send_buf, send_len);
        //等待数据接收完毕
        int  result = rt_sem_take(dynamic_sem, 1000);//RT_WAITING_FOREVER

        if (RT_EOK ==result)
        {
            //解析数据
            int rc = agile_modbus_deserialize_read_bits(ctx, ctx_read_buf_index, coils);

            if (rc >0)
            {
                rt_kprintf("Coils:[");
                for (int i = 0; i < 10; i++)
                {
                    rt_kprintf(" %d", coils[i]);

                }
                rt_kprintf(" ]\n");
            }
            else {
                rt_kprintf("exp code %d\n",-128 - rc);//得到异常码
            }

        }


        /**************************/
        //构建保持寄存器读取指令
        send_len = agile_modbus_serialize_read_registers(ctx, 0, 10);
        //清空缓存
        ctx_read_buf_index=0;
        //发送
        bsp_uart_send_datas(BSP_UART_ID_0,ctx->send_buf, send_len);
        //等待从机回复
        result = rt_sem_take(dynamic_sem, 1000);


        if (RT_EOK ==result)
        {
            //解析保持寄存器数据
            int   rc = agile_modbus_deserialize_read_registers(ctx, ctx_read_buf_index, hold_register);
            if (rc >0 )
            {
                rt_kprintf("Hold Registers:[");
                for (int i = 0; i < 10; i++)
                {
                    rt_kprintf(" 0x%04X", hold_register[i]);

                }
                rt_kprintf(" ]\n");
            }
            else {
                rt_kprintf(" exp code %d\n",-128 - rc);
            }
        }



    }


}

从机移植

从机的移植和主机一样,需要提供串口的发送接口,一帧数据接收接口

从机使用流程

c 复制代码
#include "agile_modbus.h"
#include <stdio.h>
#include <stdlib.h>
#include "agile_modbus_slave_util.h"
#include "bsp_uart.h"
#include "main.h"
#include "bsp_timer_base.h"
#include "rtthread.h"
//这个定义在bits.c中
extern const agile_modbus_slave_util_map_t bit_maps[1];
//这个定义在input_bits.c中
extern const agile_modbus_slave_util_map_t input_bit_maps[1];
//这个定义在registers.c中
extern const agile_modbus_slave_util_map_t register_maps[1];
//这个定义在input_registers.c中
extern const agile_modbus_slave_util_map_t input_register_maps[1];
 //发送缓存
static     uint8_t ctx_send_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
//接收缓存
static    uint8_t ctx_read_buf[AGILE_MODBUS_MAX_ADU_LENGTH];
//接收数据长度
static  uint16_t ctx_read_buf_index=0;
static rt_timer_t timer1;
/* 指向信号量的指针 */
static rt_sem_t dynamic_sem = RT_NULL;
/* 定时器 1 超时函数 */
static void timeout1(void *parameter)
{

    rt_timer_stop(timer1);
    rt_sem_release(dynamic_sem);

}

static void modbus_recv_callback(uint8_t ch)
{

    /* 启动定时器 1 */
    if (timer1 != RT_NULL)
    {
        rt_timer_stop(timer1);
        rt_timer_start(timer1);
    }

    ctx_read_buf[ctx_read_buf_index++%AGILE_MODBUS_MAX_ADU_LENGTH]=ch;
}




static int addr_check(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info)
{
    int slave = slave_info->sft->slave;
    if ((slave != ctx->slave) && (slave != AGILE_MODBUS_BROADCAST_ADDRESS) && (slave != 0xFF))
        return -AGILE_MODBUS_EXCEPTION_UNKNOW;

    return 0;
}

const agile_modbus_slave_util_t slave_util = {
    bit_maps,
    sizeof(bit_maps) / sizeof(bit_maps[0]),
    input_bit_maps,
    sizeof(input_bit_maps) / sizeof(input_bit_maps[0]),
    register_maps,
    sizeof(register_maps) / sizeof(register_maps[0]),
    input_register_maps,
    sizeof(input_register_maps) / sizeof(input_register_maps[0]),
    addr_check,
    NULL,
    NULL};

 void rtu_slave_entry(void )
{


    agile_modbus_rtu_t ctx_rtu;
    agile_modbus_t *ctx = &ctx_rtu._ctx;
    agile_modbus_rtu_init(&ctx_rtu, ctx_send_buf, sizeof(ctx_send_buf), ctx_read_buf, sizeof(ctx_read_buf));
    agile_modbus_set_slave(ctx, 1);
	
	
	    bsp_uart_set_recv_callback(BSP_UART_ID_0,modbus_recv_callback);
    /* 创建定时器 1 周期定时器 */
    timer1 = rt_timer_create("timer1", timeout1,
                             RT_NULL, 10,
                             RT_TIMER_FLAG_PERIODIC);
    /* 创建一个动态信号量,初始值是 0 */
    dynamic_sem = rt_sem_create("dsem", 0, RT_IPC_FLAG_PRIO);

    rt_kprintf("Running.");

    while (1) {
      
				ctx_read_buf_index=0;
        int  result = rt_sem_take(dynamic_sem, RT_WAITING_FOREVER);//
			  if (RT_EOK ==result) 
				{
					 int send_len = agile_modbus_slave_handle(ctx, ctx_read_buf_index, 0, agile_modbus_slave_util_callback, &slave_util, NULL);
					if(send_len>0)
					{
						bsp_uart_send_datas(BSP_UART_ID_0,ctx->send_buf, send_len);
					}
				}

      
    }

}

定义一些从机相关的寄存器

bits.c

c 复制代码
#include "slave.h"
/*

	01指令 读取线圈寄存器
*/
static uint8_t _tab_bits[10] = {0, 0, 0, 1, 0, 1, 0, 1, 1, 0};//0xA801 

static int get_map_buf(void *buf, int bufsz)
{
    uint8_t *ptr = (uint8_t *)buf;

   // pthread_mutex_lock(&slave_mtx);
    for (int i = 0; i < sizeof(_tab_bits); i++) {
        ptr[i] = _tab_bits[i];
    }
   // pthread_mutex_unlock(&slave_mtx);

    return 0;
}

static int set_map_buf(int index, int len, void *buf, int bufsz)
{
    uint8_t *ptr = (uint8_t *)buf;

   // pthread_mutex_lock(&slave_mtx);
    for (int i = 0; i < len; i++) {
        _tab_bits[index + i] = ptr[index + i];
    }
   // pthread_mutex_unlock(&slave_mtx);

    return 0;
}
/*
	初始化线圈,
	1.设置开始结束地址
	2.设置get和set接口
*/
const agile_modbus_slave_util_map_t bit_maps[1] = {
    {50, 59, get_map_buf, set_map_buf}};

input_bits.c

c 复制代码
#include "slave.h"
/*

	02指令 读取离散输入寄存器
	
	响应各离散输入寄存器状态,分别对应数据区中的每位值,1 代表 ON;0 代表 OFF。
	第一个数据字节的 LSB(最低字节)为查询的寻址地址,其他输入口按顺序在该字节中由低字节向高字节排列,直到填充满 8 位。下一个字节中的 8 个输入位也是从低字节到高字节排列。若返回的输入位数不是 8
的倍数,则在最后的数据字节中的剩余位至该字节的最高位使用 0 填充
*/
static uint8_t _tab_input_bits[10] = {1, 1, 1, 0, 0, 1, 1, 0, 1, 1};//0x6703  

static int get_map_buf(void *buf, int bufsz)
{
    uint8_t *ptr = (uint8_t *)buf;

//    pthread_mutex_lock(&slave_mtx);
    for (int i = 0; i < sizeof(_tab_input_bits); i++) {
        ptr[i] = _tab_input_bits[i];
    }
//    pthread_mutex_unlock(&slave_mtx);

    return 0;
}
/*
	初始化输入离散数据,
	1.设置开始结束地址
	2.设置get和set接口
*/
const agile_modbus_slave_util_map_t input_bit_maps[1] = {
    {10, 19, get_map_buf, NULL}};

register.c

c 复制代码
#include "slave.h"

/*

	03指令 读取保持寄存器
*/
static uint16_t _tab_registers[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};

static int get_map_buf(void *buf, int bufsz)
{
    uint16_t *ptr = (uint16_t *)buf;

   
    for (int i = 0; i < sizeof(_tab_registers) / sizeof(_tab_registers[0]); i++) {
        ptr[i] = _tab_registers[i];
    }
   

    return 0;
}

static int set_map_buf(int index, int len, void *buf, int bufsz)
{
    uint16_t *ptr = (uint16_t *)buf;

   // pthread_mutex_lock(&slave_mtx);
    for (int i = 0; i < len; i++) {
        _tab_registers[index + i] = ptr[index + i];
    }
   // pthread_mutex_unlock(&slave_mtx);

    return 0;
}
/*
	初始化保持寄存器
	1.设置开始结束地址
	2.设置get和set接口
*/
const agile_modbus_slave_util_map_t register_maps[1] = {
    {30, 39, get_map_buf, set_map_buf}};

input_register.c

c 复制代码
#include "slave.h"
/*

	04指令  读取输入寄存器
*/ 
static uint16_t _tab_input_registers[10] = {0, 1, 2, 3, 4, 9, 8, 7, 6, 5};

static int get_map_buf(void *buf, int bufsz)
{
    uint16_t *ptr = (uint16_t *)buf;

    //pthread_mutex_lock(&slave_mtx);
    for (int i = 0; i < sizeof(_tab_input_registers) / sizeof(_tab_input_registers[0]); i++) {
        ptr[i] = _tab_input_registers[i];
    }
   // pthread_mutex_unlock(&slave_mtx);

    return 0;
}
/*
	初始化输入寄存器
	1.设置开始结束地址
	2.设置get和set接口
*/
const agile_modbus_slave_util_map_t input_register_maps[1] = {
    {20, 29, get_map_buf, NULL}};

官方Agile Modbus文档

开源地址:GitHub - loogg/agile_modbus

Agile Modbus 即:轻量型 modbus 协议栈

1.1、特性

  1. 支持 rtu 及 tcp 协议,使用纯 C 开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用。
  2. 由于其使用纯 C 开发、不涉及硬件,完全可以在串口上跑 tcp 协议,在网络上跑 rtu 协议。
  3. 支持符合 modbus 格式的自定义协议。
  4. 同时支持多主机和多从机。
  5. 使用简单,只需要将 rtu 或 tcp 句柄初始化好后,调用相应 API 进行组包和解包即可。

1.2、目录结构

名称 说明
doc 文档
examples 例子
figures 素材
inc 头文件
src 源代码
util 提供简单实用的组件

1.3、许可证

Agile Modbus 遵循 Apache-2.0 许可,详见 LICENSE 文件。

2、使用 Agile Modbus

帮助文档请查看 doc/doxygen/Agile_Modbus.chm

2.1、移植

  • 用户需要实现硬件接口的 发送数据等待数据接收结束清空接收缓存 函数

    对于 等待数据接收结束,提供如下几点思路:

    1. 通用方法

      每隔 20 / 50 ms (该时间可根据波特率和硬件设置,这里只是给了参考值) 从硬件接口读取数据存放到缓冲区中并更新偏移,直到读取不到或缓冲区满,退出读取。

      本次移植使用的串口中断方式接收数据,每次收到一个数据就开启一个单次定时器,在定时器未超时之前收到数据立即重置定时器,当定时器超时时表示一帧数据接收完毕,完毕后使用信号量通知主线程

      这对于裸机或操作系统都适用,操作系统可通过 select信号量 方式完成阻塞。

    2. 串口 DMA + IDLE 中断方式

      配置 DMA + IDLE 中断,在中断中使能标志,应用程序中判断该标志是否置位即可。

      但该方案容易出问题,数据字节间稍微错开一点时间就不是一帧了。推荐第一种方案。

  • 主机:

    1. agile_modbus_rtu_init / agile_modbus_tcp_init 初始化 RTU/TCP 环境
    2. agile_modbus_set_slave 设置从机地址
    3. 清空接收缓存
    4. agile_modbus_serialize_xxx 打包请求数据
    5. 发送数据
    6. 等待数据接收结束
    7. agile_modbus_deserialize_xxx 解析响应数据
    8. 用户处理得到的数据
  • 从机:

    1. 实现 agile_modbus_slave_callback_t 类型回调函数
    2. agile_modbus_rtu_init / agile_modbus_tcp_init 初始化 RTU/TCP 环境
    3. agile_modbus_set_slave 设置从机地址
    4. 等待数据接收结束
    5. agile_modbus_slave_handle 处理请求数据
    6. 清空接收缓存 (可选)
    7. 发送数据
  • 特殊功能码

    需要调用 agile_modbus_set_compute_meta_length_after_function_cbagile_modbus_set_compute_data_length_after_meta_cb API 设置特殊功能码在主从模式下处理的回调。

    • agile_modbus_set_compute_meta_length_after_function_cb

      msg_type == AGILE_MODBUS_MSG_INDICATION: 返回主机请求报文的数据元长度(uint8_t 类型),不是特殊功能码必须返回 0。

      msg_type == MSG_CONFIRMATION: 返回从机响应报文的数据元长度(uint8_t 类型),不是特殊功能码必须返回 1。

    • agile_modbus_set_compute_data_length_after_meta_cb

      msg_type == AGILE_MODBUS_MSG_INDICATION: 返回主机请求报文数据元之后的数据长度,不是特殊功能码必须返回 0。

      msg_type == MSG_CONFIRMATION: 返回从机响应报文数据元之后的数据长度,不是特殊功能码必须返回 0。

  • agile_modbus_rtu_init / agile_modbus_tcp_init

    初始化 RTU/TCP 环境时需要用户传入 发送缓冲区接收缓冲区,建议这两个缓冲区大小都为 AGILE_MODBUS_MAX_ADU_LENGTH (260) 字节。特殊功能码 情况用户根据协议自行决定。

    但对于小内存 MCU,这两个缓冲区也可以设置小,所有 API 都会对缓冲区大小进行判断:

    发送缓冲区设置:如果 预期请求的数据长度预期响应的数据长度 大于 设置的发送缓冲区大小,返回异常。

    接收缓冲区设置:如果 主机请求的报文长度 大于 设置的接收缓冲区大小,返回异常。这个是合理的,小内存 MCU 做从机肯定是需要对某些功能码做限制的。

相关推荐
寅双木3 个月前
STM32cubeMX + VScode开发GD32移植(HAL库通用),保姆级!!!!!!!
笔记·vscode·stm32cubemx·hal库·移植·gd32·mdk
寅双木3 个月前
VScode开发GD32移植(标准库通用),保姆级!!!!!!!
ide·笔记·vscode·stm32cubemx·移植·gd32·stm32cubeide
爱桥代码的程序媛3 个月前
鸿蒙OpenHarmony【轻量系统芯片移植】轻量系统STM32F407芯片移植案例
stm32·华为·harmonyos·鸿蒙·鸿蒙系统·移植·openharmony
爱桥代码的程序媛3 个月前
鸿蒙OpenHarmony【轻量系统芯片移植】物联网解决方案之芯海cst85芯片移植案例
物联网·华为·harmonyos·鸿蒙·鸿蒙系统·移植·openharmony
江湖上都叫我秋博3 个月前
CANopen开源库canfestival的移植
移植·canopen·canfestival
爱桥代码的程序媛3 个月前
鸿蒙OpenHarmony【轻量系统芯片移植案例】标准系统方案之瑞芯微RK3568移植案例
嵌入式硬件·harmonyos·鸿蒙·鸿蒙系统·移植·openharmony·鸿蒙开发
OH五星上将3 个月前
OpenHarmony(鸿蒙南向开发)——标准系统移植指南(一)
harmonyos·移植·openharmony·鸿蒙开发·鸿蒙内核·子系统
OH五星上将3 个月前
OpenHarmony(鸿蒙南向开发)——轻量系统芯片移植指南(二)
嵌入式硬件·移动开发·harmonyos·移植·openharmony·鸿蒙开发
effort肆4 个月前
【OpenHarmony】openharmony移植到RK3568------获取源码编译OpenHarmony源码
移植·openharmony·rk3568