【RTT-Studio】详细使用教程十七:FreeModbus通信--LCD

一、简介

  Modbus协议是一种用于工业控制的网络通讯协议,可以片面的理解为,Modbus协议一种机器与机器之间进行数据、信息传递的一种格式规范。
  Modbus协议还遵循主从协议,支持单主机,多从机,最多支持247个从机设备。并且,在同一个通信线路上只会有一个主机,所有的通讯过程全部由主机主动发起,从机接收到主机请求后,会对请求做出响应。从机不会主动进行数据的发送,从机之间也不会有通讯过程。

  Modbus的通讯方式有串行通讯方式、以太网通讯方式、串行-以太网转换方式、无线通讯方式。表现在物理层就是,可使用3线232、2线485、4线422进行主从机之间的连接,或通过光纤、网线、无线网络等进行主从机之间的连接。

  特点:标准、开放、免费;支持多种电器接口,如串行接口RS-232、RS-485等,还可以在各种介质上传递,如:光纤、无线等;Modbus的帧格式简单、紧凑、通俗易懂。用户使用简单,厂商开发简单。

  Modbus的4种寄存器类型线圈(Coils)、离散量输入(Discrete Input)、输入寄存器(Input registers)、保持寄存器(Holding registers)。

区块访问长度访问方式说明
离散量输入位(bit)只读数据由IO系统提供
线圈位(bit)读/写可通过应用程序改写
输入寄存器字(word)只读数据由IO系统提供
保持寄存器字(word)读/写可通过应用程序改写
  • 线圈(Coils):线圈就像是电路中的一个开关,它只有两种状态:开(1)或关(0)。在工业控制系统中,线圈通常用来表示某个设备是否应该开启或关闭,比如启动一个电机或打开一个阀门。
  • 离散量输入(Discrete Input):离散量输入就像是传感器或按钮的读数,它告诉你某个条件是否为真。例如,它可能表示一个限位开关是否被触发,或者一个安全门是否关闭。这些输入是只读的,因为它们只是反映外部设备的状态。
  • 输入寄存器(Input registers):输入寄存器用于存储从传感器读取的数值数据,比如温度、湿度或压力等。这些寄存器也是只读的,因为它们提供的是实时监测到的数据。
  • 保持寄存器(Holding registers):保持寄存器可以存储和修改数据,它们通常用于配置参数或控制设置。例如,你可以设置一个保持寄存器来定义一个电机应该运行的速率,或者一个过程应该维持的温度。这些寄存器是可读可写的,允许你读取它们的当前值或者改变它们的值。

总结:线圈和离散量输入通常用于表示二进制状态(开/关),而输入寄存器和保持寄存器用于存储数值数据,其中输入寄存器是只读的,而保持寄存器可以读写。


二、Modbus通信

  包括Modbus RTU、Modbus ASCII、Modbus TCP/IP、Modbus UDP/IP等:

  • Modbus RTU与Modbus ASCII:都使用串口通讯协议,Modbus RTU使用二进制格式进行数据传输,通讯效率更高,Modbus ASCII使用ASCII码进行数据传输,可读性好,但通讯效率更低。
  • Modbus TCP/IP:是基于以太网的一种通讯方式,它将Modbus协议封装在TCP/IP协议栈中,通过以太网传输数据。具有高速、稳定的特点。
  • Modbus UDP/IP:是基于UDP/IP协议的一种通讯方式。与Modbus TCP/IP不同,Modbus UDP/IP采用无连接的通讯方式,不保证数据的可靠性和顺序。相比于Modbus TCP/IP,Modbus UDP/IP的通讯开销较小,可以减少网络负载。

1.Modbus数据帧格式
数据帧格式无论哪一种Modbus协议版本的帧格式都是一样的,包含以上几个字节:

  • 地址域:主机要访问的从机的地址
  • 功能码:主机对从机实现的操作,功能码有很多,不同的功能码也对应操作不同类型的寄存器。比如:0x01读线圈、0x03读保持寄存器、0x06写单个寄存器、0x10写多个寄存器等。
  • 数据:根据功能的不同,以及传输的数据为请求数据还是响应数据的不同,会有不同的内容。
  • 差错校验:为保障传输数据的准确性,modbus会进行差错校验,如Modbus CRC16校验等。
Modbus功能码列表(全)
功能码翻译解释作用
0x01Read Coils读线圈状态读取远程设备中1到2000个连续的线圈的状态
0x02Read Discrete Inputs读离散输入状态读取远程设备中1到2000个连续的离散输入的状态
0x03Read Holding Registers读保持寄存器内容读取远程设备中1到125个连续的保持寄存器的内容
0x04Read Input Registers读输入寄存器内容读取远程设备中1到125个连续的输入寄存器的内容
0x05Write Single Coil写单个线圈在远程设备中把单个线圈状态改变为打开或关闭的状态
0x06Write Single Register写单个保持寄存器在远程设备中写入单个保持寄存器
0x07Read Exception Status (Serial Line only)读取异常状态(仅限串行线路)读取远程设备中八个异常状态输出的内容
0x08Diagnostics (Serial Line only)通信系统诊断(仅限串行线路)
0x0BGet Comm Event Counter (Serial Line only)获取通讯事件计数器(仅限串行线路)从远程设备的通信事件计数器获取状态字和事件计数
0x0CGet Comm Event Log (Serial Line only)获取通讯事件日志(仅限串行线路)从远程设备获取状态字、事件计数、消息计数和事件字节字段
0x0FWrite Multiple Coils写多个线圈强制远程设备中线圈序列中的每个线圈接通或断开
0x10Write Multiple registers写多个保持寄存器在远程设备中写入连续寄存器块
0x11Report Slave ID (Serial Line only)报导从机信息(仅限串行线路)读取远程设备特有的类型、当前状态和其他信息的说明。数据内容特定于每种类型的设备
0x14Read File Record读取文件记录
0x15Write File Record写文件记录
0x16Mask Write Register带屏蔽字写入寄存器
0x17Read/Write Multiple registers读、写多个寄存器执行一次连续写和连续读,写入操作在读取之前执行
0x18Read FIFO Queue读取先进先出队列
0x2BEncapsulated Interface Transport封装接口传输

2.报文结构解析
  使用Modbus RTU版本、使用Modbus CRC16校验的保持寄存器(Holding registers)做演示,解析其三个常用功能0x03读、0x06写单个、0x10写的报文结构。

(1)0x03请求应答方式
在这里插入图片描述
示例:01 03 00 01 00 0A 94 0D

含义:从机设备地址(01)+功能码(03)+起始寄存器完整地址(00 01)+要读取的寄存器个数(00 0A)+CRC16校验码(94 0D)

解析:从地址为1的从机读取寄存器块内容,寄存器开始地址为1,连续读取10个寄存器,即读取地址为1到10的寄存器块。

在这里插入图片描述
示例:01 03 14 00 D7 3F 70 00 14 00 0F 00 11 00 08 00 0B 00 0B 00 02 00 00 7E 3F

含义:从机设备地址(01)+功能码(03)+数据字节数(14)+读取到的数据内容(00 D7 3F 70 00 14 00 0F 00 11 00 08 00 0B 00 0B 00 02 00 00)+CRC16校验码(7E 3F)

解析:从地址为1的从机读取寄存器块内容,返回的数据字节20个,寄存器返回数据:寄存器1–215,寄存器2–16240,寄存器3–20,寄存器4–15,寄存器5–17,寄存器6–8,寄存器7–11,寄存器8–11,寄存器9–2,寄存器10–0。

(2)0x06请求应答方式
在这里插入图片描述
示例:01 06 27 11 00 01 12 BB

含义:从机设备地址(01)+功能码(06)+寄存器完整地址(27 11)+写入的数据(00 01)+CRC16校验码(12 BB)

解析:在地址为1的从机中,向地址为10001的寄存器,写入数据1。

在这里插入图片描述
示例:01 06 27 11 00 01 12 BB

含义:从机设备地址(01)+功能码(06)+寄存器完整地址(27 11)+成功写入的数据(00 01)+CRC16校验码(12 BB)

解析:在地址为1的从机,地址为10001的寄存器中,成功写入数据1。如果06功能写入成功的话,请求码和响应码会是一样的。

(3)0x10请求应答方式
在这里插入图片描述
示例:01 10 4E 21 00 03 06 00 01 00 11 00 08 BB 05

含义:从机设备地址(01)+功能码(10)+起始寄存器地址(4E 21)+写入的寄存器个数(00 03)+数据字节数(00 06)+数据内容(00 01、00 11、00 08)+CRC16校验码(BB 05)

解析:在地址为1的从机中,向起始地址为20001的连续3个寄存器,分别写入1、17、8,字节数6个。

在这里插入图片描述
示例:01 10 4E 21 00 03 C7 2A

含义:从机设备地址(01)+功能码(10)起始寄存器地址(4E 21)+写入的寄存器个数(00 03)+CRC16校验码(C7 2A)

解析:在地址为1的从机,起始地址为20001的连续3个寄存器中(20001、20002、20003),写入数值。

(4)0x01 读线圈状态
在这里插入图片描述
设备地址(1字节) 功能码(1字节) 寄存器起始地址(2字节)线圈数量(2字节) CRC校验码(2字节)

在这里插入图片描述
示例指令:

请求:01 01 00 00 00 0A BC 0D

含义:从机设备地址(01)+ 功能码(01)+ 起始寄存器完整地址(00 00)+ 线圈数量(00 0A)+ CRC16校验码(BC 0D)

解释:从地址为1的从机读取寄存器开始地址为1,连续读10个线圈。

应答:01 01 02 FF 03 B8 0D

含义:从机设备地址(01)+ 功能码(01)+ 数据字节数(02) + 读取到的数据内容(FF 03)+ CRC16校验码(B8 0D)

解释:从地址为1的从机中读取到两个字节的数据,读出的内容是FF 03。

(5)0x05 写单个线圈
请求:01 05 00 00 FF 00 8C 3A

含义: 从机设备地址(01) + 功能码(05)+ 线圈起始地址(00 00)+ 写入状态(FF 00)+ CRC16校验码(8C 3A)
解释:从地址为1的从机中写入状态1到线圈地址为0中。

注意: 在写入状态中(FF 00 )代表写入1 ,(00 00) 代表写入0.

应答:01 05 00 00 FF 00 8C 3A

含义:从机设备地址(01) + 功能码(05)+ 线圈起始地址(00 00)+ 写入状态(FF 00)+ CRC16校验码(8C 3A)

解释:成功写入状态1到从机设备地址为1中

(6)0x0F 写多个线圈
请求:01 0F 00 00 00 08 01 FF BE D5

含义:从机设备地址(01)+ 功能码(0F)+ 线圈起始地址(00 00 )+ 线圈个数(00 08)+ 所占字节数(01)+ 线圈状态(FF)+ CRC16校验码(BE D5)

解释: 从地址为1的从机中的线圈起始地址(0000)中写入8个状态,状态是(FF)。

响应:01 0F 00 00 00 08 54 0D

含义:从机设备地址(01)+ 功能码(0F)+ 线圈起始地址(00 00 )+ 线圈个数(00 08)+ CRC16校验码(BE D5)

解释:从地址为1的从机的线圈地址中成功写入线圈个数8个。

(7)0x02 读离线输入
请求:01 02 00 00 00 05 B8 09

含义:从机设备地址(01)+ 功能码(02)+ 起始地址(00 00 )+ 读取数量(00 05)+ CRC16校验码(B8 09)

解释:从设备地址为1的起始地址为0读取5个Bit

应答:01 02 01 02 20 49

含义:从机设备地址(01)+ 功能码(02)+ 所占字节(01)+ 读取的数据(02)+ CRC16校验码(20 49 )

解释:从从机设备地址为1中读取了1个字节的数据,数据是02。

(8)0x04 读输入寄存器
请求:01 04 00 00 00 02 71 CB

含义:从机设备地址(01)+ 功能码(04)+ 起始地址(00 00 )+ 读取数量(00 02)+ CRC16校验码(71 CB)

解释:从设备地址为1的起始地址为0中读取两个输入寄存器的值。

应答:01 04 04 00 01 00 01 6B 84

含义:从机设备地址(01)+ 功能码(04)+ 所占字节(04)+ 读出来的两个寄存器的值(00 01 00 01)+ CRC16校验码(6B 84)

解释:从设备地址为1中成功读取4个字节,第一个寄存器的值是(00 01),第二个寄存器的值是(00 01)。

(9)0x17读/写多个寄存器(写完再读出来)
请求:01 17 00 00 00 02 00 00 00 02 04 12 34 56 78 BC 74

含义:设备地址(01)+ 功能码(17)+ 读寄存器起始地址(00 00)+ 读寄存器个数(00 02)+ 写寄存器起始地址(00 00 )+ 写寄存器个数(00 02)+ 所占字节数(04)+ 写寄存器值(12 34 56 78)+ CRC16校验码(BC 74)

解释:在地址为1的从机中,向起始地址为00 00 的连续两个2寄存器中分别写入 12 34 、56 78,然后再读出来。

应答:01 17 04 12 34 56 78 82 13

含义:设备地址(01)+ 功能码(17)+ 所占字节(04)+ 读出来的数据(12 34 56 78)+ CRC16校验码

解释:成功在地址为1的从机中读出来4个字节的数据,读出来的数据是1234、5678。


三、LCD通信

1.界面编写:使用Modbus通信协议编写UI界面程序,使用0x03保持寄存器的地址来进行通信,使其能够持续进行数据的查询。

在这里插入图片描述

2.设置模拟串口:使用模拟串口进行串口模拟,分别模拟出两个串口进行数据通信。

在这里插入图片描述

3.数据通信:使用Modbus Slave进行数据通信,先将两个模拟串口进行连接,然后进行设置寄存器的地址。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.通信测试:进行通信,通过更改UI界面的数据和模拟串口的数据来观察数据的变化,可以看到数据通信正常。

在这里插入图片描述


四、配置FreeModbus通信协议

1.配置RT-Thread Settings:保存并添加到工程中
在这里插入图片描述

2.开启board.h宏定义

在这里插入图片描述

3.根据需求配置modbus通讯参数:配置参数都在sample_mb_master.c中

在这里插入图片描述
在这里插入图片描述


四、完整代码

1.代码分析

  • 在初始化时使用eMBInit()这个函数时,设置为MB_RTU模式,并且将校验位设置为无校验MB_PAR_NONE
  • 创建一个线程,专门用来进行屏幕和控制板之间数据的收发,使用到的寄存器数据的接收和发送全部在回调函数中进行即可。
  • 回调函数大部分使用的是保持寄存器回调函数,直接在该函数里进行数据的发送处理和数据的接收处理,也可以进行命令的执行。

2.sample_mb_slave.c

#include <rtthread.h>

#include "mb.h"
#include "user_mb_app.h"

#ifdef PKG_MODBUS_SLAVE_SAMPLE
#define SLAVE_ADDR      MB_SAMPLE_SLAVE_ADDR
#define PORT_NUM        MB_SLAVE_USING_PORT_NUM
#define PORT_BAUDRATE   MB_SLAVE_USING_PORT_BAUDRATE
#else
#define SLAVE_ADDR      0x01
#define PORT_NUM        2
#define PORT_BAUDRATE   115200
#endif

#define PORT_PARITY     MB_PAR_NONE

#define MB_POLL_THREAD_PRIORITY  10
#define MB_SEND_THREAD_PRIORITY  RT_THREAD_PRIORITY_MAX - 1

#define MB_POLL_CYCLE_MS 20

extern USHORT usSRegHoldBuf[S_REG_HOLDING_NREGS];

void send_thread_entry(void *parameter)
{
    USHORT         *usRegHoldingBuf;
    usRegHoldingBuf = usSRegHoldBuf;
    rt_base_t level;

    while (1)
    {
        /* Test Modbus Master */
        level = rt_hw_interrupt_disable();

        usRegHoldingBuf[3] = (USHORT)(rt_tick_get() / 100);

        rt_hw_interrupt_enable(level);

        rt_thread_mdelay(1000);
    }
}

static void mb_slave_poll(void *parameter)
{
    if (rt_strstr(parameter, "RTU"))
    {
#ifdef PKG_MODBUS_SLAVE_RTU
        eMBInit(MB_RTU, SLAVE_ADDR, PORT_NUM, PORT_BAUDRATE, PORT_PARITY);
#else
        rt_kprintf("Error: Please open RTU mode first");
#endif
    }
    else if (rt_strstr(parameter, "ASCII"))
    {
#ifdef PKG_MODBUS_SLAVE_ASCII
        eMBInit(MB_ASCII, SLAVE_ADDR, PORT_NUM, PORT_BAUDRATE, PORT_PARITY);
#else
        rt_kprintf("Error: Please open ASCII mode first");
#endif
    }
    else if (rt_strstr(parameter, "TCP"))
    {
#ifdef PKG_MODBUS_SLAVE_TCP
        eMBTCPInit(0);
#else
        rt_kprintf("Error: Please open TCP mode first");
#endif
    }
    else
    {
        rt_kprintf("Error: unknown parameter");
    }
    eMBEnable();

    while (1)
    {
        eMBPoll();

        rt_thread_mdelay(MB_POLL_CYCLE_MS);
    }
}

static int mb_slave_sample()
{
    static rt_uint8_t is_init = 0;
    rt_thread_t tid1 = RT_NULL;

    if (is_init > 0)
    {
        rt_kprintf("sample is running\n");
        return -RT_ERROR;
    }

    tid1 = rt_thread_create("md_s_poll", mb_slave_poll, "RTU", 5120, MB_POLL_THREAD_PRIORITY, 20);
    if (tid1 != RT_NULL)
    {
        rt_thread_startup(tid1);
    }
    else
    {
        goto __exit;
    }

//    tid2 = rt_thread_create("md_s_send", send_thread_entry, RT_NULL, 1024, MB_SEND_THREAD_PRIORITY, MB_SEND_CYCLE_MS);
//    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_slave_sample, run a modbus slave sample);
INIT_ENV_EXPORT(mb_slave_sample);

3.user_mb_app.c

#include "user_mb_app.h"

extern uint16_t param1_value;
extern uint16_t param2_value;
extern uint16_t param3_value;
extern uint16_t param4_value;
extern uint16_t param5_value;
extern uint16_t param6_value;

/*------------------------Slave mode use these variables----------------------*/
//Slave mode:DiscreteInputs variables
USHORT   usSDiscInStart                               = S_DISCRETE_INPUT_START;
#if S_DISCRETE_INPUT_NDISCRETES%8
UCHAR    ucSDiscInBuf[S_DISCRETE_INPUT_NDISCRETES/8+1];
#else
UCHAR    ucSDiscInBuf[S_DISCRETE_INPUT_NDISCRETES/8]  ;
#endif
//Slave mode:Coils variables
USHORT   usSCoilStart                                 = S_COIL_START;
#if S_COIL_NCOILS%8
UCHAR    ucSCoilBuf[S_COIL_NCOILS/8+1]                ;
#else
UCHAR    ucSCoilBuf[S_COIL_NCOILS/8]                  ;
#endif
//Slave mode:InputRegister variables
USHORT   usSRegInStart                                = S_REG_INPUT_START;
USHORT   usSRegInBuf[S_REG_HOLDING_NREGS]               ;
//Slave mode:HoldingRegister variables
USHORT   usSRegHoldStart                              = S_REG_HOLDING_START;
USHORT   usSRegHoldBuf[S_REG_HOLDING_NREGS]             ;

/**
 * Modbus slave input register callback function.
 *
 * @param pucRegBuffer input register buffer
 * @param usAddress input register address
 * @param usNRegs input register number
 *
 * @return result
 */
eMBErrorCode eMBRegInputCB(UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNRegs )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex;
    USHORT *        pusRegInputBuf;
    USHORT          REG_INPUT_START;
    USHORT          REG_INPUT_NREGS;
    USHORT          usRegInStart;

    pusRegInputBuf = usSRegInBuf;
    REG_INPUT_START = S_REG_INPUT_START;
    REG_INPUT_NREGS = S_REG_INPUT_NREGS;
    usRegInStart = usSRegInStart;

    /* it already plus one in modbus function method. */
    usAddress--;

    if ((usAddress >= REG_INPUT_START)
            && (usAddress + usNRegs <= REG_INPUT_START + REG_INPUT_NREGS))
    {
        iRegIndex = usAddress - usRegInStart;
        while (usNRegs > 0)
        {
            *pucRegBuffer++ = (UCHAR) (pusRegInputBuf[iRegIndex] >> 8);
            *pucRegBuffer++ = (UCHAR) (pusRegInputBuf[iRegIndex] & 0xFF);
            iRegIndex++;
            usNRegs--;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

/**
 * Modbus slave holding register callback function.
 *
 * @param pucRegBuffer holding register buffer
 * @param usAddress holding register address
 * @param usNRegs holding register number
 * @param eMode read or write
 *
 * @return result
 */
eMBErrorCode eMBRegHoldingCB(UCHAR * pucRegBuffer, USHORT usAddress,
        USHORT usNRegs, eMBRegisterMode eMode)
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex;
    USHORT          nowIRegIndex;
    USHORT *        pusRegHoldingBuf;
    USHORT          REG_HOLDING_START;
    USHORT          REG_HOLDING_NREGS;
    USHORT          usRegHoldStart;

    pusRegHoldingBuf = usSRegHoldBuf;
    REG_HOLDING_START = S_REG_HOLDING_START;
    REG_HOLDING_NREGS = S_REG_HOLDING_NREGS;
    usRegHoldStart = usSRegHoldStart;

    /* it already plus one in modbus function method. */
    usAddress--;

    if ((usAddress >= REG_HOLDING_START)
            && (usAddress + usNRegs <= REG_HOLDING_START + REG_HOLDING_NREGS))
    {
        iRegIndex = usAddress - usRegHoldStart;
        switch (eMode)
        {
        /* read current register values from the protocol stack. */
        case MB_REG_READ:
            // 数据赋值
            switch (iRegIndex / 2)
            {
                case 0: pusRegHoldingBuf[iRegIndex] = param1_value;  break;
                case 1: pusRegHoldingBuf[iRegIndex] = param2_value;  break;
                case 2: pusRegHoldingBuf[iRegIndex] = param3_value;  break;
                case 3: pusRegHoldingBuf[iRegIndex] = param4_value;  break;
                case 4: pusRegHoldingBuf[iRegIndex] = param5_value;  break;
                case 5: pusRegHoldingBuf[iRegIndex] = param6_value;  break;
            }

            // 读取数据
            while (usNRegs > 0)
            {
                *pucRegBuffer++ = (UCHAR) (pusRegHoldingBuf[iRegIndex] >> 8);
                *pucRegBuffer++ = (UCHAR) (pusRegHoldingBuf[iRegIndex] & 0xFF);
                iRegIndex++;
                usNRegs--;
            }
            break;

        /* write current register values with new values from the protocol stack. */
        case MB_REG_WRITE:
            // 写入数据
            nowIRegIndex = iRegIndex;
            while (usNRegs > 0)
            {
                pusRegHoldingBuf[iRegIndex] = *pucRegBuffer++ << 8;
                pusRegHoldingBuf[iRegIndex] |= *pucRegBuffer++;
                iRegIndex++;
                usNRegs--;
            }

            // 数据处理
            switch (nowIRegIndex / 2)
            {
                case 0: param1_value = pusRegHoldingBuf[nowIRegIndex];  break;
                case 1: param2_value = pusRegHoldingBuf[nowIRegIndex];  break;
                case 2: param3_value = pusRegHoldingBuf[nowIRegIndex];  break;
                case 3: param4_value = pusRegHoldingBuf[nowIRegIndex];  break;
                case 4: param5_value = pusRegHoldingBuf[nowIRegIndex];  break;
                case 5: param6_value = pusRegHoldingBuf[nowIRegIndex];  break;
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

/**
 * Modbus slave coils callback function.
 *
 * @param pucRegBuffer coils buffer
 * @param usAddress coils address
 * @param usNCoils coils number
 * @param eMode read or write
 *
 * @return result
 */
eMBErrorCode eMBRegCoilsCB(UCHAR * pucRegBuffer, USHORT usAddress,
        USHORT usNCoils, eMBRegisterMode eMode)
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex , iRegBitIndex , iNReg;
    UCHAR *         pucCoilBuf;
    USHORT          COIL_START;
    USHORT          COIL_NCOILS;
    USHORT          usCoilStart;
    iNReg =  usNCoils / 8 + 1;

    pucCoilBuf = ucSCoilBuf;
    COIL_START = S_COIL_START;
    COIL_NCOILS = S_COIL_NCOILS;
    usCoilStart = usSCoilStart;

    /* it already plus one in modbus function method. */
    usAddress--;

    if( ( usAddress >= COIL_START ) &&
        ( usAddress + usNCoils <= COIL_START + COIL_NCOILS ) )
    {
        iRegIndex = (USHORT) (usAddress - usCoilStart) / 8;
        iRegBitIndex = (USHORT) (usAddress - usCoilStart) % 8;
        switch ( eMode )
        {
        /* read current coil values from the protocol stack. */
        case MB_REG_READ:
            while (iNReg > 0)
            {
                *pucRegBuffer++ = xMBUtilGetBits(&pucCoilBuf[iRegIndex++],
                        iRegBitIndex, 8);
                iNReg--;
            }
            pucRegBuffer--;
            /* last coils */
            usNCoils = usNCoils % 8;
            /* filling zero to high bit */
            *pucRegBuffer = *pucRegBuffer << (8 - usNCoils);
            *pucRegBuffer = *pucRegBuffer >> (8 - usNCoils);
            break;

            /* write current coil values with new values from the protocol stack. */
        case MB_REG_WRITE:
            while (iNReg > 1)
            {
                xMBUtilSetBits(&pucCoilBuf[iRegIndex++], iRegBitIndex, 8,
                        *pucRegBuffer++);
                iNReg--;
            }
            /* last coils */
            usNCoils = usNCoils % 8;
            /* xMBUtilSetBits has bug when ucNBits is zero */
            if (usNCoils != 0)
            {
                xMBUtilSetBits(&pucCoilBuf[iRegIndex++], iRegBitIndex, usNCoils,
                        *pucRegBuffer++);
            }
            break;
        }
    }
    else
    {
        eStatus = MB_ENOREG;
    }
    return eStatus;
}

/**
 * Modbus slave discrete callback function.
 *
 * @param pucRegBuffer discrete buffer
 * @param usAddress discrete address
 * @param usNDiscrete discrete number
 *
 * @return result
 */
eMBErrorCode eMBRegDiscreteCB( UCHAR * pucRegBuffer, USHORT usAddress, USHORT usNDiscrete )
{
    eMBErrorCode    eStatus = MB_ENOERR;
    USHORT          iRegIndex , iRegBitIndex , iNReg;
    UCHAR *         pucDiscreteInputBuf;
    USHORT          DISCRETE_INPUT_START;
    USHORT          DISCRETE_INPUT_NDISCRETES;
    USHORT          usDiscreteInputStart;
    iNReg =  usNDiscrete / 8 + 1;

    pucDiscreteInputBuf = ucSDiscInBuf;
    DISCRETE_INPUT_START = S_DISCRETE_INPUT_START;
    DISCRETE_INPUT_NDISCRETES = S_DISCRETE_INPUT_NDISCRETES;
    usDiscreteInputStart = usSDiscInStart;

    /* it already plus one in modbus function method. */
    usAddress--;

    if ((usAddress >= DISCRETE_INPUT_START)
            && (usAddress + usNDiscrete    <= DISCRETE_INPUT_START + DISCRETE_INPUT_NDISCRETES))
    {
        iRegIndex = (USHORT) (usAddress - usDiscreteInputStart) / 8;
        iRegBitIndex = (USHORT) (usAddress - usDiscreteInputStart) % 8;

        while (iNReg > 0)
        {
            *pucRegBuffer++ = xMBUtilGetBits(&pucDiscreteInputBuf[iRegIndex++],
                    iRegBitIndex, 8);
            iNReg--;
        }
        pucRegBuffer--;
        /* last discrete */
        usNDiscrete = usNDiscrete % 8;
        /* filling zero to high bit */
        *pucRegBuffer = *pucRegBuffer << (8 - usNDiscrete);
        *pucRegBuffer = *pucRegBuffer >> (8 - usNDiscrete);
    }
    else
    {
        eStatus = MB_ENOREG;
    }

    return eStatus;
}

4.user_mb_app.h

#ifndef    USER_APP
#define USER_APP
/* ----------------------- Modbus includes ----------------------------------*/
#include "mb.h"
#include "mb_m.h"
#include "mbconfig.h"
#include "mbframe.h"
#include "mbutils.h"

/* -----------------------Slave Defines -------------------------------------*/
#define S_DISCRETE_INPUT_START                    RT_S_DISCRETE_INPUT_START
#define S_DISCRETE_INPUT_NDISCRETES               RT_S_DISCRETE_INPUT_NDISCRETES
#define S_COIL_START                              RT_S_COIL_START
#define S_COIL_NCOILS                             RT_S_COIL_NCOILS
#define S_REG_INPUT_START                         RT_S_REG_INPUT_START
#define S_REG_INPUT_NREGS                         RT_S_REG_INPUT_NREGS
#define S_REG_HOLDING_START                       RT_S_REG_HOLDING_START
#define S_REG_HOLDING_NREGS                       RT_S_REG_HOLDING_NREGS
/* salve mode: holding register's all address */
#define          S_HD_RESERVE                     RT_S_HD_RESERVE
/* salve mode: input register's all address */
#define          S_IN_RESERVE                     RT_S_IN_RESERVE
/* salve mode: coil's all address */
#define          S_CO_RESERVE                     RT_S_CO_RESERVE
/* salve mode: discrete's all address */
#define          S_DI_RESERVE                     RT_S_DI_RESERVE

/* -----------------------Master Defines -------------------------------------*/
#define M_DISCRETE_INPUT_START                    RT_M_DISCRETE_INPUT_START
#define M_DISCRETE_INPUT_NDISCRETES               RT_M_DISCRETE_INPUT_NDISCRETES
#define M_COIL_START                              RT_M_COIL_START
#define M_COIL_NCOILS                             RT_M_COIL_NCOILS
#define M_REG_INPUT_START                         RT_M_REG_INPUT_START
#define M_REG_INPUT_NREGS                         RT_M_REG_INPUT_NREGS
#define M_REG_HOLDING_START                       RT_M_REG_HOLDING_START
#define M_REG_HOLDING_NREGS                       RT_M_REG_HOLDING_NREGS
/* master mode: holding register's all address */
#define          M_HD_RESERVE                     RT_M_HD_RESERVE
/* master mode: input register's all address */
#define          M_IN_RESERVE                     RT_M_IN_RESERVE
/* master mode: coil's all address */
#define          M_CO_RESERVE                     RT_M_CO_RESERVE
/* master mode: discrete's all address */
#define          M_DI_RESERVE                     RT_M_DI_RESERVE

#endif

5.main.c

/*
 * Copyright (c) 2006-2024, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024-10-15     RT-Thread    first version
 */
#include <string.h>
#include <stdlib.h>
#include <rtthread.h>
#include <drv_common.h>
#define DBG_TAG "main"
#define DBG_LVL DBG_LOG
#include <rtdbg.h>

short param1_value = 0;
short param2_value = 0;
short param3_value = 0;
short param4_value = 0;
short param5_value = 0;
short param6_value = 0;

/* 心跳灯线程的入口函数 */
static void thread_heartbeat_entry(void *parameter)
{
    int count = 1;

    while (1)
    {
        count++;
        rt_pin_write(GET_PIN(E, 12), count % 2);
        rt_thread_mdelay(1000);
    }
}

/* 创建心跳灯线程 */
static int thread_heartbeat(void)
{
    rt_pin_mode(GET_PIN(E, 12), PIN_MODE_OUTPUT);

    /* 创建线程 1,名称是 thread1,入口是 thread1_entry,动态创建*/
    rt_thread_t tid1 = rt_thread_create("heartbeat", thread_heartbeat_entry, RT_NULL, 256, 25, 5);

    /* 如果获得线程控制块,启动这个线程 */
    if (tid1 != RT_NULL)
        rt_thread_startup(tid1);

    return 0;
}

int main(void)
{
    int count = 1;

    thread_heartbeat();

    while (count)
    {
        rt_thread_mdelay(1000);
    }

    return RT_EOK;
}

/**
 * @brief 设置参数的值
 */
static void Set_Param_Value(int argc, char **argv)
{
    if (argc == 1)
    {
        if (strcmp(argv[0], "printf") == 0)
        {
            rt_kprintf("param1: %d\n", param1_value);
            rt_kprintf("param2: %d\n", param2_value);
            rt_kprintf("param3: %d\n", param3_value);
            rt_kprintf("param4: %d\n", param4_value);
            rt_kprintf("param5: %d\n", param5_value);
            rt_kprintf("param6: %d\n", param6_value);
        }
    }
    else if (argc == 3)
    {
        if (strcmp(argv[0], "set") == 0)
        {
            if (strcmp(argv[1], "param1") == 0)
            {
                param1_value = atof(argv[2]);
            }
            else if (strcmp(argv[1], "param2") == 0)
            {
                param2_value = atof(argv[2]);
            }
            else if (strcmp(argv[1], "param3") == 0)
            {
                param3_value = atof(argv[2]);
            }
            else if (strcmp(argv[1], "param4") == 0)
            {
                param4_value = atof(argv[2]);
            }
            else if (strcmp(argv[1], "param5") == 0)
            {
                param5_value = atof(argv[2]);
            }
            else if (strcmp(argv[1], "param6") == 0)
            {
                param6_value = atof(argv[2]);
            }
        }
    }
}
MSH_CMD_EXPORT_ALIAS(Set_Param_Value, set, set param value);
MSH_CMD_EXPORT_ALIAS(Set_Param_Value, printf, printf set param value);


五、测试验证

  通过串口进行数据的发送和接收来验证数据是否能够同步。通过设置不同的数据,发现显示的数据和设置的数据是一致的,则说明显示屏和控制板可以实现数据的通信。
在这里插入图片描述

=== 》》》FreeModbus-大彩屏程序
=== 》》》FreeModbus通信-LCD例程


### 如何在RTT-Studio中读取或查看外设寄存器 #### 使用调试工具访问寄存器 为了在 RTT-Studio 中读取或查看 STM32 的外设寄存器,通常依赖于集成的调试功能。当项目被加载到支持 JTAG 或 SWD 接口的硬件上时,可以通过连接调试器(如 ST-LINK)实现对目标板上的寄存器进行实时监控。 #### 利用MDK自带的功能 如果使用的是基于 MDK (Keil) 平台构建的应用程序,则可以在 Keil μVision IDE 内部利用其提供的“Peripheral”窗口来观察特定外设的状态以及它们各自的控制/状态寄存器值[^1]。 对于 RTT-Studio 用户来说,虽然界面有所不同,但是原理相似: - **启动调试会话**:确保已经正确设置了项目的调试配置,并且能够成功建立与目标设备之间的通信链路。 - **定位至所需外设**:进入 Debug 模式后,在左侧资源管理器找到并展开 "Peripherals" 节点下的对应模块(比如 USART、SPI 等),这里列出了该类外设有多少实例存在。 - **浏览具体寄存器**:点击某个具体的外设实例名称即可看到它所拥有的所有寄存器列表及其当前数值;双击某一项还能进一步编辑它的内容以便即时修改参数设置。 另外一种方法是在代码里加入断点,运行到指定位置暂停下来之后再手动查阅相关联的寄存器信息。这种方式特别适用于想要了解某一时刻下某些关键寄存器确切值得情况。 ```c // 假定我们要检查USART1的相关寄存器 void check_USART1_registers(void){ __asm volatile ("bkpt"); // 设置软件断点 } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值