xld0932 发表于 2022-7-18 20:09

【国民技术低功耗系列N32L43x测评】06.基于USART实现Agile Modbus主从通讯

<p><strong><span style="color:#e74c3c;">概述</span></strong></p>

<p>Agile Modbus是轻量型Modbus协议栈,遵循Apache-2.0许可,满足用户任何场景下的使用需求,具有如下特性:</p>

<ul>
        <li style="">支持RTU和TCP协议,使用纯C开发,不涉及任何硬件接口,可在任何形式的硬件上直接使用</li>
        <li style="">由于其使用纯C开发、不涉及硬件,完全可以在串口上跑TCP协议,在网络上跑RTU协议</li>
        <li style="">支持符合Modbus格式的自定义协议</li>
        <li style="">同时支持多主机和多从机</li>
        <li style="">使用简单,只需要将RTU或TCP句柄初始化好后,调用相应API进行组包和解包即可</li>
</ul>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">资源占用情况</span></strong></p>

<table class="MsoTableGrid" style="border-collapse:collapse; border:none;Times New Roman&quot;">
        <tbody>
                <tr>
                        <td style="border-bottom:1px solid black; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">开发环境</p>
                        </td>
                        <td style="border-bottom:1px solid black; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">FLASH</p>
                        </td>
                        <td style="border-bottom:1px solid black; border-top:1px solid black; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">RAM</p>
                        </td>
                </tr>
                <tr>
                        <td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">KEIL MDK</p>
                        </td>
                        <td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">7192</p>
                        </td>
                        <td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">0</p>
                        </td>
                </tr>
                <tr>
                        <td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">GCC</p>
                        </td>
                        <td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">13970</p>
                        </td>
                        <td style="border-bottom:1px solid black; border-top:none; border-right:1px solid black; border-left:1px solid black" valign="top">
                        <p align="center" style="">24</p>
                        </td>
                </tr>
        </tbody>
</table>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">硬件环境准备</span></strong></p>

<p style=""></p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">在N32L43X系列MCU上使用Agile Modbus</span></strong></p>

<p style="">我们使用MCU的USART2引脚(PA2/PA3)与RS485模块进行连接,然后将RS485模块的AB与USB转RS485工具进行连接,在电脑端使用串口调试助手进行辅助测试。</p>

<p style="">对于USART2除了时行常规配置外(115200/N/8/1),另外使能串口接收中断,将收到的数据存放在队列中,使能接收空闲中断,在接收完成后置接收完成标志位,供agile modbus使用,具体实现代码如下所示:</p>

<pre>
<code class="language-cpp">/* Private variables ---------------------------------------------------------*/
uint8_t RS485_IDLEF = 0;

/*******************************************************************************
* @brief * @param      
* @retval      
* @attention *******************************************************************************/
void RS485_Init(void)
{
    GPIO_InitType   GPIO_InitStructure;
    NVIC_InitType   NVIC_InitStructure;
    USART_InitTypeUSART_InitStructure;

    QUEUE_INIT(QUEUE_RS485_RX_IDX);

    RCC_EnableAPB2PeriphClk(RCC_APB2_PERIPH_GPIOA, ENABLE);
    RCC_EnableAPB1PeriphClk(RCC_APB1_PERIPH_USART2, ENABLE);

    NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    NVIC_Init(&amp;NVIC_InitStructure);

    GPIO_InitStruct(&amp;GPIO_InitStructure);
    GPIO_InitStructure.Pin            = GPIO_PIN_2;
    GPIO_InitStructure.GPIO_Mode      = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Alternate   = GPIO_AF4_USART1;
    GPIO_InitPeripheral(GPIOA, &amp;GPIO_InitStructure);

    GPIO_InitStruct(&amp;GPIO_InitStructure);
    GPIO_InitStructure.Pin            = GPIO_PIN_3;
    GPIO_InitStructure.GPIO_Pull      = GPIO_Pull_Up;
    GPIO_InitStructure.GPIO_Alternate   = GPIO_AF4_USART1;
    GPIO_InitPeripheral(GPIOA, &amp;GPIO_InitStructure);

    USART_StructInit(&amp;USART_InitStructure);
    USART_InitStructure.BaudRate            = 115200;
    USART_InitStructure.WordLength          = USART_WL_8B;
    USART_InitStructure.StopBits            = USART_STPB_1;
    USART_InitStructure.Parity            = USART_PE_NO;
    USART_InitStructure.HardwareFlowControl = USART_HFCTRL_NONE;
    USART_InitStructure.Mode                = USART_MODE_RX | USART_MODE_TX;
    USART_Init(USART2, &amp;USART_InitStructure);

    USART_ClrFlag(USART2, USART_FLAG_RXDNE);
    USART_ConfigInt(USART2, USART_INT_RXDNE, ENABLE);

    USART_ClrFlag(USART2, USART_FLAG_IDLEF);
    USART_ConfigInt(USART2, USART_INT_IDLEF, ENABLE);

    USART_Enable(USART2, ENABLE);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void USART2_IRQHandler(void)
{
    if(USART_GetIntStatus(USART2, USART_INT_IDLEF) != RESET)
    {
      uint8_t RxData = USART_ReceiveData(USART2);
      RS485_IDLEF = 1;
    }

    if(USART_GetIntStatus(USART2, USART_INT_RXDNE) != RESET)
    {
      QUEUE_WRITE(QUEUE_RS485_RX_IDX, USART_ReceiveData(USART2));
      USART_ClrIntPendingBit(USART2, USART_INT_RXDNE);
    }
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void RS485_SendData(uint8_t Data)
{
    USART_SendData(USART2, Data);
    while (USART_GetFlagStatus(USART2, USART_FLAG_TXDE) == RESET);
}</code></pre>

<p style="">&nbsp;</p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">主机实现步骤</span></strong></p>

<ul>
        <li style="">使用agile_modbus_rtu_init函数初始化RTU环境</li>
        <li style="">使用agile_modbus_set_slave设置从机地址</li>
        <li style="">清空接收缓存</li>
        <li style="">使用agile_modbus_serialize_xxx打包请求数据</li>
        <li style="">发送数据</li>
        <li style="">等待数据接收完成</li>
        <li style="">使用agile_modbus_deserialize_xxx解析响应的数据</li>
        <li style="">用户处理得到的数据</li>
</ul>

<p style="">&nbsp;</p>

<p style=""><span style="color:#e74c3c;"><strong>主机实现代码</strong></span></p>

<pre>
<code class="language-cpp">/* Private variables ---------------------------------------------------------*/
agile_modbus_rtu_t rtu_master;


/* Private variables ---------------------------------------------------------*/
uint8_t rtu_master_send_buf;
uint8_t rtu_master_recv_buf;


/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/


/* Exported variables --------------------------------------------------------*/
extern uint8_t RS485_IDLEF;


/* Exported function prototypes ----------------------------------------------*/


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void rtu_master_init(void)
{
    agile_modbus_rtu_init(&amp;rtu_master, rtu_master_send_buf, sizeof(rtu_master_send_buf),
                                       rtu_master_recv_buf, sizeof(rtu_master_recv_buf));

    agile_modbus_set_slave(&amp;rtu_master._ctx, 1);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void rtu_master_handler(void)
{
    uint16_t hold_register;

    static int state = 0;

    switch(state)
    {
      case 0:
      {
            int send_len = 0;

            send_len = agile_modbus_serialize_read_registers(&amp;rtu_master._ctx, 0, 10);

            if(send_len)
            {
                for(int i = 0; i &lt; send_len; i++)
                {
                  RS485_SendData(rtu_master._ctx.send_buf);
                }

                state = 1;
            }
      }
      break;

      case 1:
      {
            if(RS485_IDLEF == 1)
            {
                RS485_IDLEF = 0;

                int recv_len = 0;

                while(QUEUE_EMPTY(QUEUE_RS485_RX_IDX) == 0)
                {
                  rtu_master._ctx.read_buf = QUEUE_READ(QUEUE_RS485_RX_IDX);
                }

#if 0
                printf("\r\nrecv_len[%d] : ", recv_len);

                for(int i = 0; i &lt; recv_len; i++)
                {
                  printf("0x%02x ", rtu_master._ctx.read_buf);
                }

                printf("\r\n");
#endif

                if(recv_len)
                {
                  int rc = agile_modbus_deserialize_read_registers(&amp;rtu_master._ctx, recv_len, hold_register);

                  if(rc &gt;= 0)
                  {
                        printf("\r\nHold Register : \r\n");

                        for(int i = 0; i &lt; 10; i++)
                        {
                            printf("0x%02x ", hold_register);
                        }

                        printf("\r\n");
                  }
                }

                state = 0;
            }
      }
      break;

      default:
            break;
    }
}</code></pre>

<p style="">&nbsp;</p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">主机测试结果</span></strong></p>

<p style=""></p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">从机实现步骤</span></strong></p>

<ul>
        <li style="">实现agile_modbus_slave_callback_t类型回调函数</li>
        <li style="">使用agile_modbus_rtu_init初始化RTU环境</li>
        <li style="">使用agile_modbus_set_slave设置从机地址</li>
        <li style="">等待数据接收完成</li>
        <li style="">使用agile_modbus_slave_handler处理请求数据</li>
        <li style="">清空接收缓存(可选)</li>
        <li style="">发送数据</li>
</ul>

<p style="">&nbsp;</p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">从机实现代码</span></strong></p>

<pre>
<code class="language-cpp">/* Private define ------------------------------------------------------------*/
#define TAB_MAX_NUM   10


/* Private macro -------------------------------------------------------------*/


/* Private variables ---------------------------------------------------------*/
agile_modbus_rtu_t rtu_slave;


/* Private variables ---------------------------------------------------------*/
uint8_t rtu_slave_send_buf;
uint8_t rtu_slave_recv_buf;


/* Private variables ---------------------------------------------------------*/
static uint8_t_tab_bits          = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
static uint8_t_tab_input_bits    = {0, 1, 0, 1, 0, 1, 0, 1, 0, 1};
static uint16_t _tab_registers       = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
static uint16_t _tab_input_registers = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};


/* Private function prototypes -----------------------------------------------*/
/* Private functions ---------------------------------------------------------*/


/* Exported variables --------------------------------------------------------*/
extern uint8_t RS485_IDLEF;


/* Exported function prototypes ----------------------------------------------*/


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
int rtu_slave_callback(agile_modbus_t *ctx, struct agile_modbus_slave_info *slave_info)
{
    int function = slave_info-&gt;sft-&gt;function;
    int ret = 0;

    switch(function)
    {
      case AGILE_MODBUS_FC_READ_COILS:
      case AGILE_MODBUS_FC_READ_DISCRETE_INPUTS:
      {
            int address = slave_info-&gt;address;
            int nb = slave_info-&gt;nb;
            int send_index = slave_info-&gt;send_index;
            int is_input = (function == AGILE_MODBUS_FC_READ_DISCRETE_INPUTS);

            for(int now_address = address, i = 0; now_address &lt; address + nb; now_address++, i++)
            {
                if(now_address &gt;= 0 &amp;&amp; now_address &lt; TAB_MAX_NUM)
                {
                  int index = now_address - 0;
                  agile_modbus_slave_io_set(ctx-&gt;send_buf + send_index, i, is_input ? _tab_input_bits : _tab_bits);
                }
            }
      }
      break;

      case AGILE_MODBUS_FC_READ_HOLDING_REGISTERS:
      case AGILE_MODBUS_FC_READ_INPUT_REGISTERS:
      {
            int address = slave_info-&gt;address;
            int nb = slave_info-&gt;nb;
            int send_index = slave_info-&gt;send_index;
            int is_input = (function == AGILE_MODBUS_FC_READ_INPUT_REGISTERS);

            for(int now_address = address, i = 0; now_address &lt; address + nb; now_address++, i++)
            {
                if(now_address &gt;= 0 &amp;&amp; now_address &lt; TAB_MAX_NUM)
                {
                  int index = now_address - 0;
                  agile_modbus_slave_register_set(ctx-&gt;send_buf + send_index, i, is_input ? _tab_input_registers : _tab_registers);
                }
            }
      }
      break;

      case AGILE_MODBUS_FC_WRITE_SINGLE_COIL:
      case AGILE_MODBUS_FC_WRITE_MULTIPLE_COILS:
      {
            int address = slave_info-&gt;address;

            if(function == AGILE_MODBUS_FC_WRITE_SINGLE_COIL)
            {
                if (address &gt;= 0 &amp;&amp; address &lt; TAB_MAX_NUM)
                {
                  int index = address - 0;
                  int data= *((int *)slave_info-&gt;buf);

                  _tab_bits = data;
                }
            }
            else
            {
                int nb = slave_info-&gt;nb;

                for(int now_address = address, i = 0; now_address &lt; address + nb; now_address++, i++)
                {
                  if(now_address &gt;= 0 &amp;&amp; now_address &lt; TAB_MAX_NUM)
                  {
                        int index = now_address - 0;
                        uint8_t status = agile_modbus_slave_io_get(slave_info-&gt;buf, i);

                        _tab_bits = status;
                  }
                }
            }
      }
      break;

      case AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER:
      case AGILE_MODBUS_FC_WRITE_MULTIPLE_REGISTERS:
      {
            int address = slave_info-&gt;address;

            if(function == AGILE_MODBUS_FC_WRITE_SINGLE_REGISTER)
            {
                if (address &gt;= 0 &amp;&amp; address &lt; TAB_MAX_NUM)
                {
                  int index = address - 0;
                  int data = *((int *)slave_info-&gt;buf);

                  _tab_registers = data;
                }
            }
            else
            {
                int nb = slave_info-&gt;nb;

                for(int now_address = address, i = 0; now_address &lt; address + nb; now_address++, i++)
                {
                  if(now_address &gt;= 0 &amp;&amp; now_address &lt; TAB_MAX_NUM)
                  {
                        int index = now_address - 0;
                        uint16_t data = agile_modbus_slave_register_get(slave_info-&gt;buf, i);

                        _tab_registers = data;
                  }
                }
            }
      }
      break;

      case AGILE_MODBUS_FC_MASK_WRITE_REGISTER:
      {
            int address = slave_info-&gt;address;

            if(address &gt;= 0 &amp;&amp; address &lt; TAB_MAX_NUM)
            {
                int index = address - 0;
                uint16_t data = _tab_registers;

                uint16_t and_op = (slave_info-&gt;buf &lt;&lt; 8) + slave_info-&gt;buf;
                uint16_t or_op= (slave_info-&gt;buf &lt;&lt; 8) + slave_info-&gt;buf;

                data = (data &amp; and_op) | (or_op &amp;(~and_op));

                _tab_registers = data;
            }
      }
      break;

      case AGILE_MODBUS_FC_WRITE_AND_READ_REGISTERS:
      {
            int address = slave_info-&gt;address;
            int nb = (slave_info-&gt;buf &lt;&lt; 8) + slave_info-&gt;buf;

            uint16_t address_write = (slave_info-&gt;buf &lt;&lt; 8) + slave_info-&gt;buf;

            int nb_write = (slave_info-&gt;buf &lt;&lt; 8) + slave_info-&gt;buf;
            int send_index = slave_info-&gt;send_index;

            /* Write first. 7 is the offset of the first values to write */
            for(int now_address = address_write, i = 0; now_address &lt; address_write + nb_write; now_address++, i++)
            {
                if(now_address &gt;= 0 &amp;&amp; now_address &lt; TAB_MAX_NUM)
                {
                  int index = now_address - 0;
                  uint16_t data = agile_modbus_slave_register_get(slave_info-&gt;buf + 7, i);

                  _tab_registers = data;
                }
            }

            /* and read the data for the response */
            for(int now_address = address, i = 0; now_address &lt; address + nb; now_address++, i++)
            {
                if(now_address &gt;= 0 &amp;&amp; now_address &lt; TAB_MAX_NUM)
                {
                  int index = now_address - 0;
                  agile_modbus_slave_register_set(ctx-&gt;send_buf + send_index, i, _tab_registers);
                }
            }
      }
      break;

      default:
      {
            ret = -AGILE_MODBUS_EXCEPTION_ILLEGAL_FUNCTION;
      }
      break;
    }

    return ret;
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void rtu_slave_init(void)
{
    agile_modbus_rtu_init(&amp;rtu_slave, rtu_slave_send_buf, sizeof(rtu_slave_send_buf),
                                    rtu_slave_recv_buf, sizeof(rtu_slave_recv_buf));

    agile_modbus_set_slave(&amp;rtu_slave._ctx, 1);
}


/*******************************************************************************
* @brief      
* @param      
* @retval      
* @attention   
*******************************************************************************/
void rtu_slave_handler(void)
{
    if(RS485_IDLEF == 1)
    {
      RS485_IDLEF = 0;

      int recv_len = 0;

      while(QUEUE_EMPTY(QUEUE_RS485_RX_IDX) == 0)
      {
            rtu_slave._ctx.read_buf = QUEUE_READ(QUEUE_RS485_RX_IDX);
      }

#if 1
      printf("\r\nrecv_len[%d] : ", recv_len);

      for(int i = 0; i &lt; recv_len; i++)
      {
            printf("0x%02x ", rtu_slave._ctx.read_buf);
      }

      printf("\r\n");
#endif

      if(recv_len)
      {
            int send_len = agile_modbus_slave_handle(&amp;rtu_slave._ctx, recv_len, 1, rtu_slave_callback, NULL);

            if(send_len)
            {
                for(int i = 0; i &lt; send_len; i++)
                {
                  RS485_SendData(rtu_slave._ctx.send_buf);
                }
            }
      }
    }
}</code></pre>

<p style="">&nbsp;</p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">从机测试结果</span></strong></p>

<p style=""></p>

<p style="">&nbsp;</p>

<p style=""><strong><span style="color:#e74c3c;">附件</span></strong></p>

<p style="">软件工程源代码:</p>

lugl4313820 发表于 2022-7-19 07:55

这工程很有实用性,先学习了,谢谢分享!

xld0932 发表于 2022-7-19 08:33

lugl4313820 发表于 2022-7-19 07:55
这工程很有实用性,先学习了,谢谢分享!

<p><img height="50" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan33.gif" width="58" /></p>

发生的 发表于 2022-7-20 15:29

lugl4313820 发表于 2022-7-19 07:55
这工程很有实用性,先学习了,谢谢分享!

<p>666666666666666666666</p>

freebsder 发表于 2022-7-20 19:09

<p>为啥keil和gcc差的那么多。</p>

dwdsp 发表于 2022-7-21 08:46

<p>是开源的吗?要版权不</p>

<p>&nbsp;</p>

xld0932 发表于 2022-7-21 08:53

dwdsp 发表于 2022-7-21 08:46
是开源的吗?要版权不

&nbsp;

<p>有链接(https://gitee.com/RT-Thread-Mirror/agile_modbus),自己研究哈</p>

jack4103 发表于 2022-7-22 17:31

<p>收藏一下</p>
页: [1]
查看完整版本: 【国民技术低功耗系列N32L43x测评】06.基于USART实现Agile Modbus主从通讯