letchgo 发表于 2025-6-28 09:57

【灵动微电子MM32F0121测评】-003-Modbus从机实现

本帖最后由 letchgo 于 2025-6-28 09:58 编辑

# modbus相关介绍

本次使用的是modbusrtu协议,也是平时工业控制中用的最多的协议,一般支持232、485、422接口方式,主要特点是CRC校验、一主多从方式的多机通信、数据靠间隔分包。

modbusrtu通信遵循T1.5以及T3.5时间间隔

(1)T1.5间隔

T1.5是用来描述同一modbus报文帧内部两个字符之间的传输间隔时间(实际工程应用中T1.5机制使用比较少,本文忽略)T1.5的图示如下:

!(data/attachment/forum/202506/28/094440y07iie190i0f7eq6.png "image.png")

2)T3.5间隔

在串口接收的连续字符流之中若出现两个字符之间的接收时间间隔超过3.5个字符时间,则判断为两个数据帧的间隔。接收驱动程序上实现的思路是

串口在接收到一个字符(假设字符’a’)时便开始计时,时间记为Time1,在接收到下一个字符(假设字符‘b’)时,时间记为Time2。如果Time2 - Time1的时间差值大于3.5个字符时间,则判断字符‘b’为下一数据帧的起始字符。字符‘a’为本帧数据的结束字符。

!(data/attachment/forum/202506/28/094611hkvvi0rv09s0is8g.png "image.png")

# MM32相关

本次主要为了方便直接使用MM32开发板自带的串口1来实现,使用空闲中断接收数据,发送则是等待发送

```



void PLATFORM_InitConsole(uint32_t Baudrate)
{
GPIO_InitTypeDef GPIO_InitStruct;

NVIC_InitTypeDef NVIC_InitStruct;

USART_InitTypeDef USART_InitStruct;

RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

USART_StructInit(&USART_InitStruct);

USART_InitStruct.USART_BaudRate = Baudrate;

USART_InitStruct.USART_WordLength = USART_WordLength_8b;

USART_InitStruct.USART_StopBits = USART_StopBits_1;

USART_InitStruct.USART_Parity = USART_Parity_No;

USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;

USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None;

USART_Init(USART1, &USART_InitStruct);

RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOB, ENABLE);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_0);

GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_0);

GPIO_StructInit(&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;

GPIO_InitStruct.GPIO_Speed = GPIO_Speed_High;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;

GPIO_Init(GPIOB, &GPIO_InitStruct);

GPIO_StructInit(&GPIO_InitStruct);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_FLOATING;

GPIO_Init(GPIOB, &GPIO_InitStruct);

NVIC_InitStruct.NVIC_IRQChannel = USART1_IRQn;

NVIC_InitStruct.NVIC_IRQChannelPriority = 0;

NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;

NVIC_Init(&NVIC_InitStruct);

USART_ITConfig(USART1, USART_IT_PE, DISABLE);

USART_ITConfig(USART1, USART_IT_ERR, DISABLE);

USART_ITConfig(USART1, USART_IT_CTS, DISABLE);

USART_ITConfig(USART1, USART_IT_TC, DISABLE);

// USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);

USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);

USART_Cmd(USART1, ENABLE);
}
extern uint8_t uart1_recv_buf;
extern uint8_t uart1_recv_flag;
extern uint8_t uart1_recv_len;
void USART1_IRQHandler(void)
{
static uint8_t index_buf = 0;
uint8_t RxData = 0;

// if ((RESET != USART_GetITStatus(USART1, USART_IT_PE)) ||
//   (RESET != USART_GetITStatus(USART1, USART_IT_ERR)))
// {
//   USART_ReceiveData(USART1);
//   USART_ClearITPendingBit(USART1, USART_IT_PE | USART_IT_ERR);
// }

if (RESET != USART_GetITStatus(USART1, USART_IT_RXNE))
{
    USART_ClearITPendingBit(USART1, USART_IT_RXNE);
    RxData = USART_ReceiveData(USART1);

    if (index_buf < 32)
    {
      uart1_recv_buf = RxData;
      // if (index_buf == 32)
      // {
      //   // uart1_recv_flag = 1;
      //   uart1_recv_len = index_buf;
      //   index_buf = 0;
      //   // rintf("rxne\n");
      // }
    }
}
if (RESET != USART_GetITStatus(USART1, USART_IT_IDLE))
{
    int a = 0;
    // USART_ClearITPendingBit(USART1, USART_IT_IDLE);
    //
    USART_ReceiveData(USART1); // 需要清除寄存器中的数据,不然一直进中断
    USART_ClearITPendingBit(USART1, USART_IT_IDLE);
    // a = USART1->SR;
    // a = USART1->DR; // 软件序列清除IDLE标志位
    //
    if ((index_buf > 0) && (uart1_recv_flag == 0))
    {
      uart1_recv_flag = 1;
      uart1_recv_len = index_buf;
      index_buf = 0;
    }
}
}
```

# modbus从机软件实现

考虑到MM32内存较小,已经运行了rtt-nano,就不用开源协议栈实现了,自己写了个简单的从机协议,实现了0x06/0x03等功能,如下为本次代码:

```
/**
* @file mc_easy_modbus_slave.c
* @author your name (you@domain.com)
* @brief
* 0x01/0x02-读IO线圈,上位机测试通过
* 0x03、04-读模拟量或者参数,上位机测试通过
* 0x05-写单个线圈测试通过
* 0x06--写单个保持寄存器,测试通过
* 0x10--写多个保持寄存器,测试通过
* 0x0f--上位机无测试接口,无法测试
* @version 0.1
* @date 2025-01-17
*
* @CopyRight Copyright (c) 2025
*
*/
#include "stdio.h"
#include "string.h"
#include "mc_easy_modbus_slave.h"

#define POINTER_FORCE_CHANGE() \
      easy_modbus_slave_t *p;\
      p = (easy_modbus_slave_t *)ptr;

static void soft_calc_crc16_sum(uint8_t *source, uint32_t len, uint16_t polynomial, uint16_t *origin_result)
{
      uint8_t i;
      if (polynomial == 0 || len == 0)
      {
            return;
      }

      if (0 == *origin_result)
      {
            *origin_result = 0xFFFF;
      }

      while (len--)
      {
            *origin_result ^= *source;
            for (i = 0; i < 8; i++)
            {
                  if (*origin_result & 1)
                  {
                        *origin_result >>= 1;
                        *origin_result ^= polynomial;
                  }
                  else
                  {
                        *origin_result >>= 1;
                  }
            }
            source++;
      }
}

static uint16_t modubus_crc16_calculate(uint8_t *pbuf, uint16_t cal_length)
{
      uint16_t crc_result = 0xffff;
      soft_calc_crc16_sum(pbuf, cal_length, 0xA001, &crc_result);
      return crc_result;
}
/**
* @brief 写单个线圈
*
* @param ptr
*/
void ModBusRTU_05(void *ptr)
{
      POINTER_FORCE_CHANGE();
      uint16_t crc_16_cal, crc_16_recv;
      uint16_t start_address = 0;
      uint16_t coil_value = 0;
      crc_16_recv = (p->prv.recv_data << 8 | p->prv.recv_data);
      crc_16_cal = modubus_crc16_calculate(p->prv.recv_data, 6);
      if (crc_16_recv == crc_16_cal) // CRC16是对的
      {
            coil_value = (p->prv.recv_data << 8 | p->prv.recv_data);    // 写入的数据
            start_address = (p->prv.recv_data << 8 | p->prv.recv_data); // 起始地址
            uint16_t coil_byte_index = start_address / 8;
            uint16_t coil_bit_index = start_address % 8;
            uint8_t *reg_status = p->shr.coils; // 指明线圈寄存器数组
            uint16_t reg_status_size = sizeof(p->shr.coils) / sizeof(p->shr.coils);
            if ((0 + coil_byte_index) >= reg_status_size)
            {
                  printf("Error: Reading out of range.\n");
                  return; // 读取的数量超出范围
            }
            if (coil_value == 0xFF00)
            {
                  reg_status |= (1 << coil_bit_index); // 置位
            }
            else
            {
                  reg_status &= ~(1 << coil_bit_index); // 复位
            }
            // printf("coil_byte_index=%d,%x,%x,%x\n",coil_byte_index,p->shr.coils,p->shr.coils,p->shr.coils);
            p->mb_port.send(p->prv.recv_data, 8); // 数据原样返回来回应
            p->mb_port.prv_data_user_get_cb(p);
      }
}
static uint8_t combineData(uint8_t *data, uint8_t n)
{
      if (n > 8)
      {
            printf("Error: n should be in the range of 0 to 8\n");
            return 0;
      }
      // 提取 data 的高 N 位
      uint8_t highBits = data >> (8 - n);
      // 提取 data 的低 8 - N 位
      uint8_t lowBits = data & ((1 << (8 - n)) - 1);
      // 组合成新的 uint8 数据
      uint8_t result = (highBits << (8 - n)) | lowBits;
      return result;
}
/**
* @brief 写多个线圈
*
* @param ptr
*/
void ModBusRTU_0F(void *ptr)
{
      POINTER_FORCE_CHANGE();
      uint16_t crc_16_cal, crc_16_recv;
      uint16_t start_address = 0;
      uint16_t num_coils = 0;
      uint8_t num_bytes = p->prv.recv_data;
      crc_16_recv = (p->prv.recv_data << 8 | p->prv.recv_data);
      crc_16_cal = modubus_crc16_calculate(p->prv.recv_data, 7 + num_bytes);
      if (crc_16_recv == crc_16_cal) // CRC16是对的
      {
            num_coils = (p->prv.recv_data << 8 | p->prv.recv_data);   // 线圈数量
            start_address = (p->prv.recv_data << 8 | p->prv.recv_data); // 起始地址
            uint16_t coil_byte_index = start_address / 8;
            uint8_t start_bit = start_address % 8;
            uint8_t offset = 8 - start_bit;
            uint8_t *coil_value = &p->prv.recv_data; // 数据起始位
            uint8_t *reg_status = p->shr.coils;         // 指明线圈寄存器数组
            uint16_t reg_status_size = sizeof(p->shr.coils) / sizeof(p->shr.coils);
            uint8_t temp = 0;
            if (p->prv.recv_len < (8 + num_bytes))
            {
                  printf("Error: Insufficient data in message.\n");
                  return;
            }
            for (uint16_t i = 0; i < num_bytes; i++)
            {
                  coil_byte_index += i;
                  if (i == 0)
                  {
                        temp = (coil_value << start_bit) | (~((1 << start_bit) - 1)); // 左移 n 位并将其余位补 1 的操作
                        reg_status &= temp;
                  }
                  else if (i == num_bytes - 1)
                  {
                        uint8_t remain_last_bits = num_coils % 8 - (8 - start_bit); // 最后一个字节的有效位数
                        temp = coil_value >> (8 - start_bit);
                        temp = (temp | (~((1 << (8 - remain_last_bits)) - 1))); // 将temp的高n位置1
                        reg_status &= temp;
                  }
                  else
                  {
                        reg_status = combineData(&coil_value, start_bit);
                  }
            }
            // 构建响应消息
            unsigned char response;
            response = p->prv.recv_data;   // 从机地址
            response = p->prv.func_code;      // 功能码
            response = p->prv.recv_data;   // 起始线圈地址高字节
            response = p->prv.recv_data;   // 起始线圈地址低字节
            response = (num_coils >> 8) & 0xFF; // 要写入的线圈数量高字节
            response = num_coils & 0xFF;      // 要写入的线圈数量低字节

            // 计算 CRC 校验并添加到响应消息中
            uint16_t crc = modubus_crc16_calculate(response, 6);
            response = crc & 0xFF;
            response = (crc >> 8) & 0xFF;
            p->mb_port.send(response, 8); // 数据原样返回来回应
            p->mb_port.prv_data_user_get_cb(p);
      }
}
/**
* @brief 写单个保持寄存器
*
* @param ptr
*/
void ModBusRTU_06(void *ptr)
{
      POINTER_FORCE_CHANGE();
      uint16_t crc_16_cal, crc_16_recv;
      uint16_t start_address = 0;
      uint16_t data_registers = 0;
      crc_16_recv = (p->prv.recv_data << 8 | p->prv.recv_data);
      crc_16_cal = modubus_crc16_calculate(p->prv.recv_data, 6);
      if (crc_16_recv == crc_16_cal) // CRC16是对的
      {
            data_registers = (p->prv.recv_data << 8 | p->prv.recv_data); // 写入的数据
            start_address = (p->prv.recv_data << 8 | p->prv.recv_data);// 起始地址

            uint16_t *reg_status = p->shr.hold_registers; // 指明线圈寄存器数组
            uint16_t reg_status_size = sizeof(p->shr.hold_registers) / sizeof(p->shr.hold_registers);
            if ((0 + start_address) >= reg_status_size)
            {
                  printf("Error: Reading out of range.\n");
                  return; // 读取的数量超出范围
            }
            reg_status = data_registers;
            p->mb_port.send(p->prv.recv_data, 8); // 数据原样返回来回应
            p->mb_port.prv_data_user_get_cb(p);
      }
}
/**
* @brief 写多个保持寄存器
*
* @param ptr
*/
void ModBusRTU_10(void *ptr)
{
      POINTER_FORCE_CHANGE();
      uint16_t crc_16_cal, crc_16_recv;
      uint16_t start_address = 0;
      uint16_t num_registers = 0;
      uint16_t num_bytes = 0;
      num_bytes = p->prv.recv_data;
      crc_16_recv = (p->prv.recv_data << 8 | p->prv.recv_data);
      crc_16_cal = modubus_crc16_calculate(p->prv.recv_data, 7 + num_bytes);
      if (crc_16_recv == crc_16_cal) // CRC16是对的
      {
            num_registers = (p->prv.recv_data << 8 | p->prv.recv_data); // 写入寄存器的个数
            start_address = (p->prv.recv_data << 8 | p->prv.recv_data); // 起始地址

            uint16_t *reg_status = p->shr.hold_registers;
            uint16_t reg_status_size = sizeof(p->shr.hold_registers) / sizeof(p->shr.hold_registers);
            if ((num_registers + start_address) >= reg_status_size)
            {
                  printf("Error: WRITE out of range.\n");
                  return; // 读取的数量超出范围
            }
            for (int i = 0; i < num_registers; i++)
            {
                  reg_status = (p->prv.recv_data << 8 | p->prv.recv_data);
            }
            uint8_t *response = p->prv.send_buf;
            response = p->prv.dev_addr;             // 从机地址
            response = p->prv.func_code;            // 功能码
            response = (start_address >> 8) & 0xff; // 寄存器起始地址
            response = (start_address)&0xff;
            response = (num_registers >> 8) & 0xff; // 寄存器数量
            response = (num_registers)&0xff;
            uint16_t crc = modubus_crc16_calculate(response, 6);
            response = crc & 0xFF;
            response = (crc >> 8) & 0xFF;
            p->mb_port.send(response, 8);
            p->mb_port.prv_data_user_get_cb(p);
      }
}
/**
* @brief 读寄存器
*
* @param ptr
*/
void ModBusRTU_03_04(void *ptr)
{
      POINTER_FORCE_CHANGE();
      uint16_t crc_16_cal, crc_16_recv;
      uint16_t start_address = 0;
      uint16_t num_registers = 0;
      uint16_t num_bytes = 0;

      crc_16_recv = (p->prv.recv_data << 8 | p->prv.recv_data);
      crc_16_cal = modubus_crc16_calculate(p->prv.recv_data, 6);
      if (crc_16_recv == crc_16_cal) // CRC16是对的
      {
            p->mb_port.prv_data_user_fill_cb(p);
            // printf("mb_0x03_04 crc ok\n");
            num_registers = (p->prv.recv_data << 8 | p->prv.recv_data); // 数据大小,以bit作单位
            start_address = (p->prv.recv_data << 8 | p->prv.recv_data); // 起始地址
            uint16_t *reg_status = p->shr.hold_registers;                     // 指明线圈寄存器数组
            num_bytes = num_registers * 2;
            uint16_t reg_status_size = sizeof(p->shr.hold_registers) / sizeof(p->shr.hold_registers);
            uint8_t *response = p->prv.send_buf;
            if (p->prv.func_code == 0x03)
            {
                  reg_status = p->shr.hold_registers;
                  reg_status_size = sizeof(p->shr.hold_registers) / sizeof(p->shr.hold_registers);
            }
            else if (p->prv.func_code == 0x04)
            {
                  reg_status = p->shr.input_registers;
                  reg_status_size = sizeof(p->shr.input_registers) / sizeof(p->shr.input_registers);
            }
            if ((num_registers + start_address) >= reg_status_size)
            {
                  printf("Error: Reading out of range.\n");
                  return; // 读取的数量超出范围
            }
            response = p->prv.dev_addr;// 从机地址
            response = p->prv.func_code; // 功能码
            response = num_bytes;      // 字节数
            for (uint16_t i = 0; i < num_registers; i++)
            {
                  response = (reg_status >> 8) & 0xFF;
                  response = (reg_status) & 0xFF;
            }
            uint16_t crc = modubus_crc16_calculate(response, 3 + num_bytes);
            response = crc & 0xFF;
            response = (crc >> 8) & 0xFF;
            p->mb_port.send(response, 4 + num_bytes + 1);
      }
      else
      {
            printf("mb_0x03_04 crc error %x,%x\n", crc_16_recv, crc_16_cal);
      }
}
/**
* @brief 读线圈寄存器
*
* @param ptr
*/
void ModBusRTU_01_02(void *ptr)
{
      POINTER_FORCE_CHANGE();
      uint16_t crc_16_cal, crc_16_recv;
      uint16_t start_address = 0;
      uint16_t num_coils = 0;
      uint16_t start_byte;
      uint16_t start_bit; // 计算起始地址在字节内的偏移
      uint16_t num_bytes; // 计算需要的字节数
      crc_16_recv = (p->prv.recv_data << 8 | p->prv.recv_data);
      crc_16_cal = modubus_crc16_calculate(p->prv.recv_data, 6);
      if (crc_16_recv == crc_16_cal) // CRC16是对的
      {
            p->mb_port.prv_data_user_fill_cb(p);
            num_coils = (p->prv.recv_data << 8 | p->prv.recv_data);   // 数据大小,以bit作单位
            start_address = (p->prv.recv_data << 8 | p->prv.recv_data); // 起始地址

            start_byte = start_address / 8;
            start_bit = start_address % 8; // 当前字节低 start_bit位丢了,获取下一个字节的低start_bit位作为高位
            num_bytes = (num_coils + 7) / 8;
            uint8_t *coil_status = p->shr.coils; // 指明线圈寄存器数组
            uint16_t coil_status_size = sizeof(p->shr.coils) / sizeof(p->shr.coils);
            uint8_t *response = p->prv.send_buf;
            if (p->prv.func_code == 0x01)
            {
                  coil_status = p->shr.coils; // 指明线圈寄存器数组
                  coil_status_size = sizeof(p->shr.coils) / sizeof(p->shr.coils);
            }
            else if (p->prv.func_code == 0x02)
            {
                  coil_status = p->shr.discrete_inputs; // 指明线圈寄存器数组
                  coil_status_size = sizeof(p->shr.discrete_inputs) / sizeof(p->shr.discrete_inputs);
            }

            response = p->prv.dev_addr;// 从机地址
            response = p->prv.func_code; // 功能码
            response = num_bytes;      // 字节数

            // 处理起始地址不是 8 的倍数的情况
            for (uint16_t i = 0; i < num_bytes; i++)
            {
                  uint16_t coil_index = start_byte + i;
                  unsigned char shifted_status = 0;
                  if (coil_index < coil_status_size)
                  {
                        if (i < num_bytes - 1)
                        {
                              // 对于第一个字节,处理起始地址偏移
                              shifted_status = coil_status >> start_bit;
                              if (i + 1 < num_bytes && coil_index + 1 < coil_status_size)
                              {
                                    // 从下一个字节获取低 start_bit 位并填充到当前字节的高 start_bit 位
                                    shifted_status |= (coil_status << (8 - start_bit)) & 0xFF;
                              }
                              if (i == num_bytes - 1)
                              {
                                    uint16_t remaining_bits = (num_coils) % 8;
                                    if (remaining_bits != 0)
                                    {
                                          shifted_status = shifted_status & ((1 << remaining_bits) - 1);
                                    }
                              }
                        }
                        else if (i == num_bytes - 1)
                        {
                              uint16_t remaining_bits = num_coils - i * 8; // 最后剩余的字节数

                              if (remaining_bits <= (8 - start_bit))
                              {
                                    shifted_status = (coil_status >> start_bit) & (0xff >> (8 - remaining_bits));
                              }
                              else
                              {
                                    shifted_status = coil_status >> start_bit;
                                    shifted_status |= (coil_status << (8 - start_bit)) & 0xFF;
                                    shifted_status &= (0xff >> (8 - remaining_bits));
                              }
                        }
                        response = shifted_status;
                        printf("shifted_status=%x,coil_status[%d]=%x\n", shifted_status, coil_index, coil_status);
                  }
                  else
                  {
                        response = 0; // 超出范围,填充 0
                  }
            }
            uint16_t crc = modubus_crc16_calculate(response, 3 + num_bytes);
            response = crc & 0xFF;
            response = (crc >> 8) & 0xFF;
            p->mb_port.send(response, 4 + num_bytes + 1);
      }
}
static void _run(void *ptr)
{
      POINTER_FORCE_CHANGE();
      if (p->prv.recv_len < 8)
      {
            printf("Error: Message length is too short.\n");
            return;
      }

      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x01))
      {
            p->prv.func_code = 0x01;
            ModBusRTU_01_02(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x02))
      {
            p->prv.func_code = 0x02;
            ModBusRTU_01_02(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x03))
      {
            p->prv.func_code = 0x03;
            ModBusRTU_03_04(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x04))
      {
            p->prv.func_code = 0x04;
            ModBusRTU_03_04(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x05))
      {
            p->prv.func_code = 0x05;
            ModBusRTU_05(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x06))
      {
            p->prv.func_code = 0x06;
            ModBusRTU_06(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x10))
      {
            p->prv.func_code = 0x10;
            ModBusRTU_10(p);
      }
      if ((p->prv.recv_data == p->prv.dev_addr) && (p->prv.recv_data == 0x1F))
      {
            p->prv.func_code = 0x1F;
            ModBusRTU_0F(p);
      }
      p->prv.recv_len = 0;
}

uint8_t _rx_data_inject(void *ptr, uint8_t *recv_data, uint16_t len)
{
      POINTER_FORCE_CHANGE();
      memcpy(p->prv.recv_data, recv_data, len);
      p->prv.recv_len = len;
}
char mc_easy_modbus_slave_obj_setup(easy_modbus_slave_t *p, s_mb_cfg_t cfg_port, uint8_t dev_addr)
{
      memset(p, 0x00, sizeof(easy_modbus_slave_t));
      p->mb_port = cfg_port;
      p->prv.dev_addr = dev_addr;
      p->run = _run;
      p->rx_data_inject = _rx_data_inject;
}

```

```
/**
* @file mc_easy_modbus_slave.h
* @author your name (you@domain.com)
* @brief 基于对于协议理解的基础上简化代码设计
* 从站设计目前支持四种数据类型
* (1)线圈状态,可读可写,如PLC的DO或者继电器输出
* (2)离散输入状态,只读,如PLC DIN
* (3)保持寄存器,可读可写,输出参数或者保持参数,如PLC AO或者一些配置参数
* (4)输入寄存器,只读,输入参数,如PLC的AIN或者一些传感器测量数据等
* 一般可以抛开具体功能简单的以位、字来区别,再以可读和可写来区分。
*
* 关于线程保护和互斥可能在后续应用中需要继续完善
* @version 0.1
* @date 2025-01-15
*
* @CopyRight Copyright (c) 2025
*
*/
#ifndef __MC_EASY_MODBUS_SLAVE_H
#define __MC_EASY_MODBUS_SLAVE_H
#include "stdio.h"
/**
* @brief   modbus slave 寄存器类型
*/
typedef enum
{
CIOLS = 0x00,         // 线圈地址
INPUTS = 0x01,          // 离散输入
INPUT_REGISTERS = 0x03, // 输入寄存器
REGISTERS = 0x04,       // 保持寄存器
} modbus_register_type_t;
typedef struct
{
void (*prv_data_user_fill_cb)(void *self); /*有主机要读取数据,用户更新最新值*/
void (*prv_data_user_get_cb)(void *self);/*有主机要写入数据,用户获取该数据*/
void (*send)(uint8_t *dat, uint16_t len);// modbus报文的发送
} s_mb_cfg_t;
typedef struct
{
uint8_t recv_data; // 接收到主机的数据
uint8_t recv_len;
uint8_t send_buf; // 需要发送的数据buf
uint8_t dev_addr;      // 设备地址
uint8_t func_code;   // 功能码
} s_mb_private_t;
typedef struct
{
uint8_t discrete_inputs;// 离散量输入
uint8_t coils;         // 线圈状态
uint16_t input_registers; // 输入寄存器
uint16_t hold_registers; // 保持寄存器
} s_share_t;                  // 参数的数量可以根据实际使用调整,这里统一使用一个默认的大小,由用户来更新或者获取这些数据,modbus接收指令也会获取和更新这些数据,为避免冲突,统一在接收到modbus指令更新一次
typedef struct
{
s_mb_cfg_t mb_port;
s_mb_private_t prv;
s_share_t shr;                                                      // 私有参数
uint8_t (*rx_data_inject)(void *self, uint8_t *data, uint16_t len); /*输入数据*/
void (*run)(void *self);                                          // 循环运行程序
} easy_modbus_slave_t;
char mc_easy_modbus_slave_obj_setup(easy_modbus_slave_t *p, s_mb_cfg_t cfg_port,uint8_t dev_addr);

#endif

```

## 测试结果

使用了简单的modbus上位机进行几个指令的测试。

1、0x03/0x04指令测试 ok

!(data/attachment/forum/202506/28/095521khlglddgrkggtaah.png "image.png")

!(data/attachment/forum/202506/28/095534vmhldk6zh2dff8ti.png "image.png")

2、测试0x10 写多个保持寄存器,没有问题

!(data/attachment/forum/202506/28/095610cn0awtj0hnx0ubvv.png "image.png")

classroom 发表于 2025-6-30 16:35

MM32F0121通过硬件加速或优化算法,可在更短时间内完成校验计算。

flycamelaaa 发表于 2025-6-30 16:36

有哪些应用场景?

jf101 发表于 2025-6-30 23:40

Modbus从机实现案例非常不错

lemonhub 发表于 2025-7-2 22:36

能否提供完整的工程代码呢
页: [1]
查看完整版本: 【灵动微电子MM32F0121测评】-003-Modbus从机实现