805721366 发表于 2022-7-31 01:00

[N32L43X评测] 6.USART实现ModbusRTU从站

<p><span style="font-family:宋体;">MODBUS协议是一种已广泛应用于当今工业控制领域的通用通讯协议。通过此协议,控制器相互之间、或控制器经由网络、总线可以和其它设备之间进行通信。Modbus协议使用的是主从通讯技术,即由主设备主动查询和操作从设备。一般将主控设备方所使用的协议称为MODBUS Master,从设备方使用的协议称为MODBUS Slave。典型的主设备包括工控机和工业控制器等;典型的从设备如PLC可编程控制器等。MODBUS通讯物理接口可以选用串口(包括RS232、RS485和RS422),也可以选择以太网口</span></p>

<p><span style="font-family:宋体;">通信遵循以下的过程:</span></p>

<p><span style="font-family:宋体;">主设备向从设备发送请求</span></p>

<p><span style="font-family:宋体;">从设备分析并处理主设备的请求,然后向主设备发送结果</span></p>

<p><span style="font-family:宋体;">如果出现任何差错,从设备将返回一个异常功能码</span></p>

<p><span style="font-family:宋体;">MODBUS的工作方式是请求/应答,每次通讯都是主站先发送指令,可以是广播,或是向特定从站的单播;从站响应指令,并按要求应答,或者报告异常。当主站不发送请求时,从站不会自己发出数据,从站和从站之间不能直接通讯</span></p>

<p><span style="font-family:宋体;">MODBUS有三种通信方式:</span></p>

<p><span style="font-family:宋体;">以太网:对应的通信模式是MODBUS TCP/IP</span></p>

<p><span style="font-family:宋体;">异步串行传输(各种介质如有线RS-232-/422/485/;光纤、无线等):对应的通信模式是MODBUS RTU或MODBUS ASCII</span></p>

<p><span style="font-family:宋体;">高速令牌传递网络:对应的通信模式是MODBUS PLUS</span></p>

<p><span style="font-family:宋体;">MODBUS协议的报文(或帧)的基本格式是:表头 + 功能码 + 数据区 + 校验码</span></p>

<p><span style="font-family:宋体;">功能码和数据区在不同类型的网络都是固定不变的,表头和校验码则因网络底层的实现方式不同而有所区别。表头包含了从站的地址,功能码告诉从站要执行何种功能,数据区是具体的信息</span></p>

<p><span style="font-family:宋体;">对于不同类型的网络,MODBUS的协议层实现是一样的,区别在于下层的实现方式,常见的有TCP/IP和串行通讯两种</span></p>

<p><span style="font-family:宋体;">MODBUS TCP基于以太网和TCP/IP协议,MODBUS RTU和MODBUS ASCII则是使用异步串行传输(通常是RS-232/422/485)</span></p>

<p><span style="font-family:宋体;">在工业控制领域,工业仪表间的通信比较常用的是基于MODBUS RTU的485口通信,MODBUS RTU协议需要用时间间隔来判断一帧报文的开始和结束,协议规定的时间为3.5个字符周期,就是说一帧报文开始前,必须有大于3.5个字符周期的空闲时间,一帧报文结束后,也必须要有3.5个字符周期的空闲时间;同时一帧报文中,字符间空闲时间大于1.5字符周期</span></p>

<p><span style="font-family:宋体;">针对3.5个字符周期,其实是一个具体时间,但是这个时间跟波特率相关。在串口通信中,1个字符包括1位起始位、8位数据位(一般情况)、1位校验位(或者没有)、1位停止位(一般情况下),因此1个字符包括11个位,那么3.5个字符就是38.5个位,波特率表示的含义是每秒传输的二进制位的个位,在波特率为9600的情况下,3.5个字符周期=1000ms/9600bit*38.5bit=4.0104167ms</span></p>

<p><span style="font-family:宋体;">MODBUS RTU详解可参考:</span></p>

<p><span style="font-family:宋体;"></span></p>

<p><span style="font-family:宋体;"></span></p>

<p><span style="font-family:宋体;">此篇主要介绍USART1实现MODBUS RTU从站功能</span></p>

<p><span style="font-family:宋体;"><span style="color:#f39c12;"><strong>硬件连接</strong></span></span></p>

<p><span style="font-family:宋体;">GND&nbsp;&mdash;&mdash;&nbsp; GND</span></p>

<p><span style="font-family:宋体;">TXD&nbsp;&mdash;&mdash;&nbsp; PA10(RX)</span></p>

<p><span style="font-family:宋体;">RXD&nbsp;&mdash;&mdash;&nbsp; PA9(TX)</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p><span style="font-family:宋体;"><span style="color:#f39c12;"><strong>软件代码</strong></span></span></p>

<p><span style="font-family:宋体;">USART代码:</span></p>

<pre>
<code>void USART_Initial(void)
{
    NVIC_InitType NVIC_InitStructure;
    GPIO_InitType GPIO_InitStructure;
    USART_InitType USART_InitStructure;
    USART_ClockInitTypeUSART_ClockStructure;

    /* Enable GPIO clock */
    RCC_EnableAPB2PeriphClk(USART_GPIO_CLK, ENABLE);
    /* Enable USART Clock */
    RCC_EnableAPB2PeriphClk(USART_CLK, ENABLE);

    /* Configure the GPIO ports */
    /* Initialize GPIO_InitStructure */
    GPIO_InitStruct(&amp;GPIO_InitStructure);
    /* Configure USART Tx as alternate function push-pull */
    GPIO_InitStructure.Pin            = USART_TxPin;
    GPIO_InitStructure.GPIO_Mode      = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Alternate = USART_Tx_GPIO_AF;
    GPIO_InitPeripheral(USART_GPIO, &amp;GPIO_InitStructure);
    /* Configure USART Rx as alternate function push-pull and pull-up */
    GPIO_InitStructure.Pin            = USART_RxPin;
    GPIO_InitStructure.GPIO_Mode      = GPIO_Mode_AF_OD;
    GPIO_InitStructure.GPIO_Pull      = GPIO_Pull_Up;
    GPIO_InitStructure.GPIO_Alternate = USART_Rx_GPIO_AF;
    GPIO_InitPeripheral(USART_GPIO, &amp;GPIO_InitStructure);

    /* USART configuration ------------------------------------------------------*/
    USART_StructInit(&amp;USART_InitStructure);
    USART_InitStructure.BaudRate            = System.Com_Baud.UWD;//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;
    /* Configure USART */
    USART_Init(USART, &amp;USART_InitStructure);

    USART_ClockStructure.Clock = USART_CLK_DISABLE;
    USART_ClockStructure.Polarity = USART_CLKPOL_LOW;
    USART_ClockStructure.Phase = USART_CLKPHA_2EDGE;
    USART_ClockStructure.LastBit = USART_CLKLB_DISABLE;
    USART_ClockInit(USART, &amp;USART_ClockStructure);

    USART_ClrFlag(USART, USART_FLAG_TXDE);
    USART_ClrFlag(USART, USART_FLAG_RXDNE);
    /* Enable USART Receive interrupts */
    USART_ConfigInt(USART, USART_INT_TXDE, DISABLE);
    USART_ConfigInt(USART, USART_INT_RXDNE, ENABLE);

    /* NVIC configuration */
    /* Configure the NVIC Preemption Priority Bits */
    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);
    /* Enable the USART Interrupt */
    NVIC_InitStructure.NVIC_IRQChannel            = USART_IRQn;
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd         = ENABLE;
    NVIC_Init(&amp;NVIC_InitStructure);

    UART_STR.Send_Flag = 0;
    UART_STR.Read_Flag = 0;
    UART_STR.Send_Dly = 0;
    UART_STR.In_Num = 0;
    UART_STR.Out_Num = 0;
    UART_STR.Start = 0;
    UART_STR.Read_Dly = 0;
    UART_STR.Read_All = 0;

    /* Enable the USART */
    USART_Enable(USART, ENABLE);
}</code></pre>

<p><span style="font-family:宋体;">MODBUS RTU从站代码:</span></p>

<pre>
<code>void Serial_Set_Time(void)
{
    switch(System.Com_Baud.UWD)
    {
    case 2400:
      System.Delay_Time = 6;
      System.Stop_Time = 7;
      break;

    case 4800:
      System.Delay_Time = 3;
      System.Stop_Time = 7;
      break;

    case 9600:
      System.Delay_Time = 2;
      System.Stop_Time = 4;
      break;

    case 19200:
    case 38400:
      System.Delay_Time = 1;
      System.Stop_Time = 2;
      break;

    default:
      System.Delay_Time = 1;
      System.Stop_Time = 2;
      break;
    }
}


//发送接收延时,每毫秒运算一次,放在1ms定时器中断函数中
void USART_SendRec_Dly(void)
{
    if(UART_STR.Send_Flag)
    {
      if(UART_STR.Send_Dly == 0)
      {
            UART_STR.Send_Flag = 0;
            USART_ConfigInt(USART, USART_INT_TXDE, ENABLE);
            USART_ConfigInt(USART, USART_INT_RXDNE, DISABLE);
            USART1-&gt;STS |= USART_INT_TXDE;
      }
      else
      {
            UART_STR.Send_Dly--;
      }
    }

    if(UART_STR.Read_Dly    &gt;=System.Delay_Time) //2400:Read_Dly&gt;=6;4800:Read_Dly&gt;=3;9600:Read_Dly&gt;=2;19200:Read_Dly&gt;=1
    {
      UART_STR.Read_Flag = 1;

      if(UART_STR.Start   &gt;   System.Stop_Time) //2400:Read_Start&gt;14;4800:Read_Start&gt;7,9600:Read_Start&gt;4,19200:Read_Start&gt;2
      {
            UART_STR.Read_All = 1;
      }
    }
    else
    {
      UART_STR.Read_Dly++;
      UART_STR.Read_Flag = 0;
    }
}


void USART_Send_Ready(void)
{
    UART_STR.Send_Flag = 1;
    UART_STR.Send_Dly = 1;
    UART_STR.In_Num = 0;
    UART_STR.Start = 0;
    UART_STR.Read_All = 0;
    UART_STR.SendBuf = UART_STR.OutBuf;
}


/*-------------------分割线-------------------*/
//Function name:    GetCRC16
//Descriptions:   计算CRC16校验码
//input parameters: 指针地址:puchMsg,数据长度:usDataLen!!!注意usDataLen&gt;2!!!
//output parameters:输出CRC16
//Returned value:   无
//MARK:             无
/*-------------------分割线-------------------*/
unsigned short int GetCRC16(unsigned char *puchMsg, unsigned short int usDataLen)
{
    unsigned char uchCRCHi = 0xFF; /* 高CRC字节初始化 */
    unsigned char uchCRCLo = 0xFF; /* 低CRC 字节初始化 */
    unsigned int uIndex = 0; /* CRC循环中的索引 */

    while (usDataLen--) /* 传输消息缓冲区 */
    {
      uIndex = uchCRCHi ^ *puchMsg++; /* 计算CRC */
      uchCRCHi = uchCRCLo ^ auchCRCHi;
      uchCRCLo = auchCRCLo;
    }

    return (unsigned short int)((unsigned short int)uchCRCHi &lt;&lt; 8 | uchCRCLo);
}


//0x06、0x10写寄存器命令,控制板载LED灯亮灭测试
void Function_Write_Address(unsigned short int Addr, unsigned short int Temp)
{
    UNION_WD_2BY Temp_Data;
    Temp_Data.HUWD = Temp;

    switch(Addr)
    {
    case 0x00:
      if(Temp_Data.HUWD)
      {
            GPIO_SetBits(LED1_PORT, LED1_PIN);
      }
      else
      {
            GPIO_ResetBits(LED1_PORT, LED1_PIN);
      }

      break;

    case 0x01:
      if(Temp_Data.HUWD)
      {
            GPIO_SetBits(LED2_PORT, LED2_PIN);
      }
      else
      {
            GPIO_ResetBits(LED2_PORT, LED2_PIN);
      }

      break;

    case 0x02:
      if(Temp_Data.HUWD)
      {
            GPIO_SetBits(LED3_PORT, LED3_PIN);
      }
      else
      {
            GPIO_ResetBits(LED3_PORT, LED3_PIN);
      }

      break;

    default:
      break;
    }
}


/*-------------------分割线-------------------*/
//Function name:    ModBus_Process
//Descriptions:   ModBus_处理函数
//input parameters: 无
//output parameters:
//Returned value:   无
//MARK:             无
/*-------------------分割线-------------------*/
unsigned int USART_Deal(unsigned char *inbuff, unsigned short int Incount, unsigned char *outbuff)
{
    static union
    {
      short int IWD;
      unsigned short int UWD;
      unsigned char UBY;
    } SSADR, SSDAT, SSEND;
    static unsigned char CommFun;
    static unsigned short int Temp_OutNum;
    static unsigned char *sbuff;
    static unsigned short int Cal_CRC16, Tem_CRC16;
    static unsigned short int Temp_Cnt;
    UNION_WD_2BY DO_Temp, Temp_Data;
    unsigned char i;

    CommFun = *(inbuff + 1);
    SSADR.UBY = *(inbuff + 2);
    SSADR.UBY = *(inbuff + 3);
    SSDAT.UBY = *(inbuff + 4);
    SSDAT.UBY = *(inbuff + 5);
    SSEND.UWD = SSADR.UWD + SSDAT.UWD;
    Temp_OutNum = 0;

    if(Incount &gt; 2)
    {
      Cal_CRC16 = GetCRC16(UART_STR.InBuf, Incount - 2);
    }

    Tem_CRC16 = *(inbuff + Incount - 2);
    Tem_CRC16 &lt;&lt;= 8;
    Tem_CRC16 += *(inbuff + Incount - 1);

    if(Cal_CRC16 == Tem_CRC16)
    {
      switch(CommFun)
      {
      case 1:
      case 2:
      case 3:
      case 4:
      case 5:
      case 6:
      case 8:
            if( Incount == 8 ) break;
            else return 0;

      case 7:
      case 11:
      case 12:
      case 17:
            if( Incount == 12 ) break;
            else return 0;

      case 15:
      case 16:
            if( Incount == (*(inbuff + 6) + 9) ) break;
            else return 0;

      case 20:
      case 21:
            if( (Incount == (*(inbuff + 2) + 5)) &amp;&amp; (*(inbuff + 3) == 6) ) break;
            else return 0;

      case 22:
            if( Incount == 10 ) break;
            else return 0;

      case 23:
            if( Incount == (*(inbuff + 10) + 13) ) break;
            else return 0;

      case 24:
            if( Incount == 6 ) break;
            else return 0;

      case 43:
            if( Incount == 7 ) break;
            else return 0;

      default:
            if( CommFun &lt; 0x80 ) break;
            else return 0;
      }
    }
    else    return 0;

    sbuff = outbuff;
    *sbuff++ = System.Com_Addr.UBY; //通讯地址
    *sbuff = CommFun;

    if(CommFun == 3 || CommFun == 4) //0x03、0x04命令读寄存器
    {
      if(SSDAT.UWD == 0 || SSDAT.UWD &gt; 0X007D) //查询最大数据不能超过125,异常码0x03
      {
            (*sbuff) += 0x80;
            *(++sbuff) = 0x03;
            Temp_OutNum = 3;
      }
      else if( SSADR.UWD &gt;= 250 || (SSEND.UWD &gt;= 251) ) //读取范围设定值,异常码0x02
      {
            (*sbuff) += 0x80;
            *(++sbuff) = 0x02;
            Temp_OutNum = 3;
      }
      else
      {
            *(++sbuff) = SSDAT.UWD * 2;
            sbuff = outbuff + 3;

            for(Temp_Cnt = SSADR.UWD; Temp_Cnt &lt; SSEND.UWD; Temp_Cnt++) //100~240
            {
                if(Temp_Cnt &gt;= 0x00 &amp;&amp; Temp_Cnt &lt; 0x31)
                {
                  switch(Temp_Cnt)
                  {
                  //LED1状态显示
                  case 0x00:
                        LED1_State = GPIO_ReadOutputDataBit(LED1_PORT, LED1_PIN);
                        *sbuff++ = 0x00;
                        *sbuff++ = LED1_State;
                        break;

                  //LED2状态显示
                  case 0x01:
                        LED2_State = GPIO_ReadOutputDataBit(LED2_PORT, LED2_PIN);
                        *sbuff++ = 0x00;
                        *sbuff++ = LED2_State;
                        break;

                  //LED3状态显示
                  case 0x02:
                        LED3_State = GPIO_ReadOutputDataBit(LED3_PORT, LED3_PIN);
                        *sbuff++ = 0x00;
                        *sbuff++ = LED3_State;
                        break;

                  //KEY1状态显示
                  case 0x03:
                        KEY1_State = GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_4);
                        *sbuff++ = 0x00;
                        *sbuff++ = KEY1_State;
                        break;

                  //KEY2状态显示
                  case 0X04:
                        KEY2_State = GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_5);
                        *sbuff++ = 0x00;
                        *sbuff++ = KEY2_State;
                        break;

                  //KEY3状态显示
                  case 0X05:
                        KEY3_State = GPIO_ReadInputDataBit(GPIOA, GPIO_PIN_6);
                        *sbuff++ = 0x00;
                        *sbuff++ = KEY3_State;
                        break;

                  //...

                  default:
                        *sbuff++ =0;
                        *sbuff++ = 0;
                        break;
                  }
                }
                else
                {
                  *sbuff++ =0;
                  *sbuff++ = 0;
                }
            }

            Temp_OutNum = *(outbuff + 2) + 3;
      }
    }//03读寄存器命令结束

    //06写单个寄存器命令
    else if(CommFun == 6)
    {
//                                if(SSDAT.UWD &gt;= 0x00 &amp;&amp; SSDAT.UWD &lt;= 0xFFFF)
//      {
//            (*sbuff) += 0x80;
//            *(++sbuff) = 0x03;
//            Temp_OutNum = 3;
//      }
//      else
      {
            if((SSADR.UWD &gt;= 0x03 &amp;&amp; SSADR.UWD &lt;= 0X05) || SSADR.UWD &gt;= 250 )//0x06写入命令地址设定
            {
                (*sbuff) += 0x80;
                *(++sbuff) = 0x02;
                Temp_OutNum = 3;
            }
            else
            {
                Function_Write_Address(SSADR.UWD, SSDAT.UWD);

                sbuff = outbuff + 2;
                *sbuff++ = SSADR.UBY;
                *sbuff++ = SSADR.UBY;
                *sbuff++ = SSDAT.UBY;
                *sbuff++ = SSDAT.UBY;
                Temp_OutNum = 6;
            }
      }
    }
    //写多个寄存器命令
    else if(CommFun == 16)
    {
      if(SSDAT.UWD == 0 || SSDAT.UWD &gt; 0x7b || SSDAT.UWD * 2 != *(inbuff + 6) )
      {
            (*sbuff) += 0x80;
            *(++sbuff) = 0x03;
            Temp_OutNum = 3;
      }
      else if( SSADR.UWD &gt;= 250 || (SSEND.UWD &gt;= 251)) //约束写入地址
      {
            (*sbuff) += 0x80;
            *(++sbuff) = 0x02;
            Temp_OutNum = 3;
      }
      else
      {
            if(*(inbuff + 6) &gt; 0x07) //判断寄存器数量
            {
                (*sbuff) += 0x80;
                *(++sbuff) = 0x03;
                Temp_OutNum = 3;
            }
            else
            {
                ModBus_CodeAddress.UBY = *(inbuff + 2);
                ModBus_CodeAddress.UBY = *(inbuff + 3); //根据通讯地址来写入数据
                ModBus_DataLength.UBY = *(inbuff + 4);
                ModBus_DataLength.UBY = *(inbuff + 5);
                ModBus_DataEnd = ModBus_CodeAddress.UWD + ModBus_DataLength.UWD;

                for(i = 7, ModBus_Temp = ModBus_CodeAddress.UWD; ModBus_Temp &lt; ModBus_DataEnd; ModBus_Temp++, i = i + 2)                  //100~240
                {
                  Temp_Data.UBY = *(inbuff + i);
                  Temp_Data.UBY = *(inbuff + i + 1);
                  Function_Write_Address(ModBus_Temp, Temp_Data.HUWD); //写入的地址及数据(对应0X06命令)
                }

                sbuff = outbuff + 2;
                *sbuff++ = SSADR.UBY;
                *sbuff++ = SSADR.UBY;
                *sbuff++ = SSDAT.UBY;
                *sbuff++ = SSDAT.UBY;
                Temp_OutNum = 6;
            }
      }
    }

    else
    {
      (*sbuff) += 0x80;
      *(++sbuff) = 0x01;
      Temp_OutNum = 3;
    }

    sbuff = outbuff;

    if(Temp_OutNum &gt; 2)
      Cal_CRC16 = GetCRC16(sbuff, Temp_OutNum);

    sbuff = outbuff + Temp_OutNum;
    *(sbuff++) = (Cal_CRC16 &gt;&gt; 8);
    *(sbuff++) = Cal_CRC16;
    Temp_OutNum += 2;
    return Temp_OutNum;

}


/*
* @brief This function handles USART global interrupt request.
*/
void USART1_IRQHandler(void)
{
    unsigned char TempData;

    if (USART_GetIntStatus(USART, USART_INT_RXDNE) != RESET)
    {
      /* Read one byte from the receive data register */
      TempData = USART_ReceiveData(USART);
      USART_ClrIntPendingBit(USART, USART_INT_RXDNE);

      if(UART_STR.In_Num == 0)
      {
            if(TempData == System.Com_Addr.UBY &amp;&amp; UART_STR.Read_All == 0 &amp;&amp; UART_STR.Read_Flag == 1) //接受第一个字符时T3.5有没有到 没有到当前字符取消
            {
                UART_STR.InBuf = TempData;
                UART_STR.Start = 1;
            }
      }
      else if(UART_STR.Read_Flag == 0) //接受其他字符时候没有超过T1.5的延时
      {
            if(UART_STR.In_Num &gt;= 260)
            {
                UART_STR.In_Num = 0;    //接受长度不对重新接受
                UART_STR.Start = 0;
            }
            else
            {
                UART_STR.Start++;   //等待3.5个字符的延时表示接受结束
                UART_STR.InBuf = TempData;
            }
      }
      else
      {
            UART_STR.In_Num = 0;
      }

      UART_STR.Read_Dly = 0;
      UART_STR.Read_Flag = 0; //防止在帧前格外加的字符,如果是第1个字符不是地址重新等待1.5个字符
    }

    else if (USART_GetIntStatus(USART, USART_INT_TXDE) != RESET)
    {
      if(UART_STR.Out_Num != 0)
      {
            UART_STR.Out_Num--;
            USART_SendData(USART, *UART_STR.SendBuf);
            UART_STR.SendBuf++;
      }
      else
      {
            USART_ConfigInt(USART, USART_INT_TXDE, DISABLE);
      }

      USART_ClrIntPendingBit(USART, USART_INT_TXDE); //Clear the Uart_Hard transmit interrupt

      if(UART_STR.Out_Num &lt;= 1)
            USART_ConfigInt(USART, USART_INT_RXDNE, ENABLE);
    }
    else
    {
      USART_ClrFlag(USART, USART_FLAG_OREF | USART_FLAG_NEF | USART_FLAG_FEF | USART_FLAG_PEF); //注意,清除出错标志
    }

}</code></pre>

<p><span style="color:#f39c12;"><strong><span style="font-family:宋体;">运行测试</span></strong></span></p>

<p><span style="font-family:宋体;">从站地址设为0x01,波特率设为115200,初始设置板载LED1、LED3亮,LED2灭,KEY1、KEY2、KEY3按键松开(GPIO设置为上拉输入,按下时为0低电平,松开时为1高电平)</span></p>

<p><span style="font-family:宋体;">0x03读寄存器命令</span></p>

<p><span style="font-family:宋体;">使用modscan32测试,设置从站地址、0x03读命令</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p><span style="font-family:宋体;">点击连接设置--&gt;连接,选择调试串口,设置串口参数,点击确认</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p><span style="font-family:宋体;">连接成功后,显示LED1-3,KEY1-3当前状态</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p><span style="font-family:宋体;">0x06写寄存器命令</span></p>

<p><span style="font-family:宋体;">双击更新设置LED1、LED2、LED3寄存器值,LED1、LED3灭,LED2亮</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p><span style="font-family:宋体;">更新后,显示</span></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p class="imagemiddle" style="text-align: center;"></p>

<p><span style="color:#f39c12;"><strong><span style="font-family:宋体;">测试代码</span></strong></span></p>

<p></p>

<p>modscan32软件</p>

lugl4313820 发表于 2022-7-31 11:11

感谢分享, modbus写得非常好,学习了。
页: [1]
查看完整版本: [N32L43X评测] 6.USART实现ModbusRTU从站