STM32 W5500 implements TCP, DHCP and Web server

w5500
ORIGINAL POST
By unistake
details

이미지 002.png

1.1 introduction to Modbus Protocol

Modbus protocol is a message structure, which is widely used to establish master-slave communication between intelligent devices. The Modbus messages sent from the master to the slave include the slave address, commands (such as “read register” or “write register”), data and checksum (LRC or CRC).

Since Modbus protocol is only a message passing structure, it is independent of the underlying physical layer. Traditionally, RS232, RS422 or RS485 are used

The main reasons why Modbus is more widely used than other communication protocols are:

  • Published without copyright requirements
  • Easy to deploy and maintain
  • There are no many restrictions for vendors to modify and move local bits or bytes

1.2 master/slave architecture of Modbus

Modbus is a master / slave architecture protocol. One node is a master node and the other nodes are slave nodes. Each slave node has a unique address. In serial and MB + networks, only the node designated as the master node can start a command. On the Ethernet, any device can send a MODBUS command, but usually there is only one master node device startup command.

1.3 Modbus command

A MODBUS command includes the Modbus address of the device to be executed. All devices will receive the command, but only the devices with the corresponding address will run and respond to the command (except address 0. The command specifying address 0 is a broadcast command, and all devices that receive the command will run but will not respond). All MODBUS commands contain check codes to ensure that the incoming commands are not corrupted. The basic MODBUS command can instruct an RTU (remote terminal unit) to change a value of its register, control or read an I/O port, and the command equipment will send data in one or more registers.

1.4 Modbus request

The function code in the request tells the addressed slave what action to perform. The data byte contains any additional information required by the slave station to perform this function. For example, function code 03 will request the slave to read the holding register and respond to its contents. The data field must contain information that tells the slave which register to start with and how many registers to read. The error check field provides a method for the slave to verify the integrity of the message content.

1.5 Modbus response

If the slave makes a normal response, the function code in the response is the echo of the function code in the request. Data bytes contain data collected from the station, such as register values or status. If an error occurs, the function code is modified to indicate that the response is an error response, and the data byte contains a code describing the error. The error check field allows the host to confirm that the message content is valid.

1.6 Modbus transmission mode

When communicating on a standard Modbus network, the controller can be set to use one of two transmission modes: ASCII or RTU.

ASCII mode when the controller is set to communicate on the Modbus network using ASCII (American Standard Code for information interchange) mode, each octet in the message is sent as two ASCII characters. The main advantage of this mode is that it allows a time interval of up to one second between characters without causing an error.

Encoding system hexadecimal ASCII printable character 0 9, A … F bits per byte 1 start bit 7 data bits, LSB first sends 1 bit for even / odd parity – no bit for no parity 1 stop bit if parity check if there is no parity error check longitudinal redundancy check (LRC), 2 bits are used

ASCII frame

In ASCII mode, Messages begin with a colon (:) character (ASCII 3A hex) and end with carriage return line feed (CRLF) pairs (ASCII 0D and 0A hex). The allowed characters transmitted for all other fields are hexadecimal 0… 9, a… F. networked devices continuously monitor the network bus for colon characters. When one is received, each device evaluates the next field (address field) is decoded to determine whether it is an addressed device. The characters in the message can be separated by up to one second. If a larger interval occurs, the receiving device assumes that an error has occurred. A typical message frame is as follows:

RTU mode when the controller is set to communicate on the Modbus network using RTU (remote terminal unit) mode, each octet in the message contains two four digit hexadecimal characters. The main advantage of this mode is that its greater character density allows better data throughput than ASCII at the same baud rate. Each message must be transmitted in a continuous stream.

Coding system 8-bit binary, hexadecimal 0 9,A … The two hexadecimal characters contained in each octet field of the f message have 1 starting bit and 8 data bits per byte. The least significant bit is sent first. 1 bit is used for even / odd parity – no parity bit. If parity is used, it is 1 stop bit – if there is no parity error, the check field is 2-bit cyclic redundancy check (CRC)

RTU frame in RTU mode, the message starts with a silence interval of at least 3.5 characters. This is most easily implemented as a multiple of the character time of the baud rate being used on the network (shown as T1-T2-T3-T4 in the figure below). Then the first field transmitted is the device address. The allowed characters transmitted for all fields are hexadecimal 0… 9, a… F. the networked device continues to monitor the network bus, including during the silence interval. When the first field is received (address field), each device decodes it to determine whether it is an addressed device. After the last transmitted character, a similar interval of at least 3.5 characters marks the end of the message. After this interval, a new message can be started. The whole message frame must be transmitted as a continuous stream. If more than 1.5 characters appear before the frame is completed The receiving device will refresh the unfinished message and assume that the next byte will be the address field of the new message. Similarly, if the start time of a new message is earlier than 3.5 characters after the previous message, the receiving device will treat it as a continuation of the previous message. This will set an error because the value in the final CRC field will not be valid for the combined message. A typical message frame is as follows:

1.7 message frame

Address field the address field of the message frame contains two characters (ASCII) or eight bits (RTU). The address range assigned by each slave device is 1 247.

Function field

The function code field tells the addressed slave what functions to perform. Modbus poll supports the following functions:

01 read coil state 02 read input state 03 read hold register 04 read input register 05 write single coil 06 write single register 15 write multiple coils 16 write multiple registers

The data field contains the requested or sent data.

The contents of the error check field the standard Modbus network uses two error check methods. The contents of the error check field depend on the method being used.

ASCII when ASCII mode is used for character framing, the error check field contains two ASCII characters. The error check character is the result of the longitudinal redundancy check (LRC) calculation performed on the message content, excluding the start colon and end CRLF characters. The LRC character is appended to the message as the last field before the CRLF character.

BYTE LRC (BYTE *nData, WORD wLength)
{
BYTE nLRC = 0 ; // LRC char initialized
​
for (int i = 0; i < wLength; i++)
nLRC += *nData++;
​
return (BYTE)(-nLRC);
​
} // End: LRC

RTU when RTU mode is used for character frame, the error check field contains a 16 bit value, which is implemented as two 8-bit bytes. The error check value is the result of the cyclic redundancy check calculation performed on the message content. The CRC field is appended to the message as the last field in the message. When complete, first append the low byte of the field, then the high byte. The CRC high byte is the last byte to be sent in the message.

WORD CRC16 (const BYTE *nData, WORD wLength)
{
static const WORD wCRCTable[] = {
   0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
   0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
   0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
   0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
   0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
   0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
   0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
   0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
   0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
   0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
   0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
   0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
   0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
   0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
   0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
   0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
   0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
   0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
   0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
   0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
   0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
   0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
   0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
   0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
   0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
   0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
   0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
   0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
   0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
   0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
   0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
   0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };
​
BYTE nTemp;
WORD wCRCWord = 0xFFFF;
​
   while (wLength--)
   {
      nTemp = *nData++ ^ wCRCWord;
      wCRCWord >>= 8;
      wCRCWord  ^= wCRCTable[nTemp];
   }
   return wCRCWord;
} // End: CRC16

1.8 experimental results

2. DHCP

2.1 introduction to DHCP

DHCP (Dynamic Host Configuration Protocol) is a LAN network protocol. It refers to a range of IP addresses controlled by the server. When the client logs in to the server, it can automatically obtain the IP address and subnet mask assigned by the server.

2.2 DHCP function

  1. Ensure that any IP address can only be used by one DHCP client at the same time.
  2. DHCP should be able to assign a permanently fixed IP address to the user.
  3. DHCP should coexist with the host that obtains the IP address by other methods (such as the host that manually configures the IP address).
  4. The DHCP server should provide services to existing BOOTP clients.

2.3 DHCP allocation method

DHCP has three mechanisms for assigning IP addresses:

  1. Automatic Allocation: the DHCP server assigns a permanent IP address to the host. Once the DHCP client successfully leases the IP address from the DHCP server for the first time, it can permanently use the address.
  2. In Dynamic Allocation, the DHCP server assigns an IP address with time limit to the host. When the time expires or the host explicitly gives up the address, the address can be used by other hosts.
  3. In Manual Allocation, the IP address of the client is specified by the network administrator, and the DHCP server only tells the specified IP address to the client host.

2.4 working principle of DHCP

The DHCP protocol uses UDP as the transmission protocol. The host sends a request message to port 67 of the DHCP server, and the DHCP server responds to the response message to port 68 of the host. The detailed interaction process is as follows:

2.5 W5500+STM32F103 realize DHCP code

W5500 is the DHCP client and router is the DHCP server. After connecting to the router, the router dynamically assigns the IP address to w5500.

The process of DHCP request includes four main stages: discovery stage, provision stage, selection stage and confirmation stage.

First, the W5500 client sends a DHCP DISCOVER message (IP address lease application). This message is sent by broadcast, and all DHCP servers in the network will receive this message. Then, the DHCP server in the network will respond to a DHCPOFFER message (provided by IP address leasing). Since the client does not have a network address at this time, DHCP OFFER is also sent by broadcasting. After that, send a DHCP REQUEST message to the server. The DHCP REQUEST message will contain the IP address requested by the client. Finally, the DHCP server will send back the response message of DHCP ACK to inform the client that the IP address can be used The confirmation contains the assigned IP address and a lease with a stable term of the address (8 days by default), and updates the DHCP database at the same time.

Main code:

while(1)
{
​
 DHCP_run();
​
}
​
uint8_t DHCP_run(void)
{
​
  uint8_t  type;
​
  uint8_t  ret;
​
  if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;
​
  if(getSn_SR(SOCK_DHCP) != SOCK_UDP)
​
  socket(SOCK_DHCP, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);
​
  ret = DHCP_RUNNING;
​
  type = parseDHCPMSG();
​
  switch ( dhcp_state )
​
  {
​
      case STATE_DHCP_READY :
​
          DHCP_allocated_ip[0] = 0;
​
          DHCP_allocated_ip[1] = 0;
​
          DHCP_allocated_ip[2] = 0;
​
          DHCP_allocated_ip[3] = 0;   
​
          send_DHCP_DISCOVER();
​
          dhcp_time = 0;
​
          dhcp_state = STATE_DHCP_DISCOVER;
​
      break;
​
      case STATE_DHCP_DISCOVER :
​
          if (type == DHCP_OFFER)
​
          {
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Receive DHCP_OFFERrn");
​
              #endif
​
              DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
​
              DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
​
              DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
​
              DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3]; 
​
              send_DHCP_REQUEST();
​
              dhcp_time = 0;
​
              dhcp_state = STATE_DHCP_REQUEST;
​
          }
​
              else
​
              ret = check_DHCP_timeout();
​
      break;
​
      case STATE_DHCP_REQUEST :
​
          if (type == DHCP_ACK)
​
              {
​
                  #ifdef _DHCP_DEBUG_
​
                  printf("> Receive DHCP_ACKrn");
​
                  #endif
​
              if (check_DHCP_leasedIP())
​
              {
​
                  printf("ip:%d.%d.%d.%drn",DHCP_allocated_ip[0],DHCP_allocated_ip[1],DHCP_allocated_ip[2],DHCP_allocated_ip[3]);
​
                  printf("sn:%d.%d.%d.%drn",DHCP_allocated_sn[0],DHCP_allocated_sn[1],DHCP_allocated_sn[2],DHCP_allocated_sn[3]);
​
                  printf("gw:%d.%d.%d.%drn",DHCP_allocated_gw[0],DHCP_allocated_gw[1],DHCP_allocated_gw[2],DHCP_allocated_gw[3]);
​
                  dhcp_ip_assign();
​
                  reset_DHCP_timeout();
​
                  hcp_state = STATE_DHCP_LEASED;
​
              }
​
              else
​
              {
​
                  reset_DHCP_timeout();
​
                  dhcp_ip_conflict();
​
                  dhcp_state = STATE_DHCP_READY;
​
              }
​
              }
​
              else if (type == DHCP_NAK)
​
              {
​
                  #ifdef _DHCP_DEBUG_
​
                  printf("> Receive DHCP_NACKrn");
​
                  #endif
​
                  reset_DHCP_timeout();
​
                  dhcp_state = STATE_DHCP_DISCOVER;
​
              }
​
              else
​
              ret = check_DHCP_timeout();
​
      break;
​
      case STATE_DHCP_LEASED :
​
          ret = DHCP_IP_LEASED;        
​
          if ((dhcp_lease_time != DEFAULT_LEASETIME) && ((dhcp_lease_time/2) < dhcp_time))
​
          {  
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Maintains the IP address rn");
​
              #endif
​
              type = 0;
​
              OLD_allocated_ip[0] = DHCP_allocated_ip[0];
​
              OLD_allocated_ip[1] = DHCP_allocated_ip[1];
​
              OLD_allocated_ip[2] = DHCP_allocated_ip[2];
​
              OLD_allocated_ip[3] = DHCP_allocated_ip[3];
​
              DHCP_XID++;
​
              send_DHCP_REQUEST();               
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_REREQUEST;
​
          }
​
      break;
​
      case STATE_DHCP_REREQUEST :
​
          ret = DHCP_IP_LEASED;
​
          if (type == DHCP_ACK)
​
          {
​
              dhcp_retry_count = 0;
​
              if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
​
              OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
​
              OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
​
              OLD_allocated_ip[3] != DHCP_allocated_ip[3])
​
          {
​
              ret = DHCP_IP_CHANGED;
​
              dhcp_ip_update();
​
              #ifdef _DHCP_DEBUG_
​
              printf(">IP changed.rn");
​
              #endif                 
​
          }
​
              #ifdef _DHCP_DEBUG_
​
              else printf(">IP is continued.rn");
​
              #endif                         
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_LEASED;
​
          }
​
          else if (type == DHCP_NAK)
​
          {
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Receive DHCP_NACK, Failed to maintain iprn");
​
              #endif
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_DISCOVER;
​
          }
​
          else ret = check_DHCP_timeout();
​
      break;
​
      default :
​
      break;
​
      }
​
  return ret;
}

2.6 code running results

Connect the local machine to the router, and the local IP address is as follows:

Connect the W5500+STM32 to the router and output it through the STM32 serial port. Check the IP address of the W5500 as follows:

Local ping W5500 result:

Connection succeeded.

3. W5500 TCP

3.1 establish TCP server locally

Turn off the local network and use the TCP test tool to establish a TCP server

3.2 use W5500 to set static IP and establish TCP client

Connect the W5500 to the local machine, run the program, start the TCP server, and view the data sent by the W5500 client

int main(void)
{
    System_Initialization();    //STM32 system initialization function (initializing STM32 clock and peripherals)
    Load_Net_Parameters();      //Load network parameters    
    W5500_Hardware_Reset();     //Hardware reset W5500
    W5500_Initialization();     //W5500 initial cargo configuration
    while (1)
    {
        W5500_Socket_Set();//W5500 port initialization configuration
​
        if(W5500_Interrupt)//Handle W5500 interrupt      
        {
            W5500_Interrupt_Process();//W5500 interrupt handler framework
        }
        if((S0_Data & S_RECEIVE) == S_RECEIVE)//If Socket0 receives data
        {
            S0_Data&=~S_RECEIVE;
            Process_Socket_Data(0);//The W5500 receives and sends the received data
        }
        else if(W5500_Send_Delay_Counter >= 500)//Timed send string
        {
            if(S0_State == (S_INIT|S_CONN))
            {
                S0_Data&=~S_TRANSMITOK;
                memcpy(Tx_Buffer, "rnWelcome To NiRenElec!rn", 23); 
                Write_SOCK_Data_Buffer(0, Tx_Buffer, 23);//Specify Socket(0~7) to send data processing, and port 0 to send 23 bytes of data
            }
            W5500_Send_Delay_Counter=0;
        }
    }
}

3.3 experimental results

4. Embedded Web server

4.1 wiring

Connect the W5500 to the router, and the PC is also connected to the router for easy access to web pages.

4.2 code part

Use socket to encapsulate TCP protocol and implement socket communication based on TCP

#include "socket.h"
#include "config.h"
#include "stdio.h"
#include "w5500.h"
#include "ult.h"
​
static uint16 local_port;
extern uint16 sent_ptr;
​
/**
@brief   This Socket function initialize the channel in perticular mode, and set the port and wait for W5200 done it.
@return  1 for sucess else 0.
*/
uint8 socket(SOCKET s, uint8 protocol, uint16 port, uint8 flag)
{
   uint8 ret;
   if (
        ((protocol&0x0F) == Sn_MR_TCP)    ||
        ((protocol&0x0F) == Sn_MR_UDP)    ||
        ((protocol&0x0F) == Sn_MR_IPRAW)  ||
        ((protocol&0x0F) == Sn_MR_MACRAW) ||
        ((protocol&0x0F) == Sn_MR_PPPOE)
      )
   {
      close(s);
      IINCHIP_WRITE(Sn_MR(s) ,protocol | flag);
      if (port != 0) {
         IINCHIP_WRITE( Sn_PORT0(s) ,(uint8)((port & 0xff00) >> 8));
         IINCHIP_WRITE( Sn_PORT1(s) ,(uint8)(port & 0x00ff));
      } else {
         local_port++; // if don't set the source port, set local_port number.
         IINCHIP_WRITE(Sn_PORT0(s) ,(uint8)((local_port & 0xff00) >> 8));
         IINCHIP_WRITE(Sn_PORT1(s) ,(uint8)(local_port & 0x00ff));
      }
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_OPEN); // run sockinit Sn_CR
​
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s)) )
         ;
      /* ------- */
      ret = 1;
   }
   else
   {
      ret = 0;
   }
   return ret;
}
​
​
/**
@brief   This function close the socket and parameter is "s" which represent the socket number
*/
void close(SOCKET s)
{
​
   IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_CLOSE);
​
   /* wait to process the command... */
   while( IINCHIP_READ(Sn_CR(s) ) )
      ;
   /* ------- */
        /* all clear */
   IINCHIP_WRITE( Sn_IR(s) , 0xFF);
}
​
​
/**
@brief   This function established  the connection for the channel in passive (server) mode. This function waits for the request from the peer.
@return  1 for success else 0.
*/
uint8 listen(SOCKET s)
{
   uint8 ret;
   if (IINCHIP_READ( Sn_SR(s) ) == SOCK_INIT)
   {
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_LISTEN);
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s) ) )
         ;
      /* ------- */
      ret = 1;
   }
   else
   {
      ret = 0;
   }
   return ret;
}
​
​
/**
@brief   This function established  the connection for the channel in Active (client) mode.
      This function waits for the untill the connection is established.
​
@return  1 for success else 0.
*/
uint8 connect(SOCKET s, uint8 * addr, uint16 port)
{
    uint8 ret;
    if
        (
            ((addr[0] == 0xFF) && (addr[1] == 0xFF) && (addr[2] == 0xFF) && (addr[3] == 0xFF)) ||
            ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) ||
            (port == 0x00)
        )
    {
      ret = 0;
    }
    else
    {
        ret = 1;
        // set destination IP
        IINCHIP_WRITE( Sn_DIPR0(s), addr[0]);
        IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
        IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
        IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
        IINCHIP_WRITE( Sn_DPORT0(s), (uint8)((port & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_DPORT1(s), (uint8)(port & 0x00ff));
        IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_CONNECT);
        /* wait for completion */
        while ( IINCHIP_READ(Sn_CR(s) ) ) ;
​
        while ( IINCHIP_READ(Sn_SR(s)) != SOCK_SYNSENT )
        {
            if(IINCHIP_READ(Sn_SR(s)) == SOCK_ESTABLISHED)
            {
                break;
            }
            if (getSn_IR(s) & Sn_IR_TIMEOUT)
            {
                IINCHIP_WRITE(Sn_IR(s), (Sn_IR_TIMEOUT));  // clear TIMEOUT Interrupt
                ret = 0;
                break;
            }
        }
    }
​
   return ret;
}
​
​
​
/**
@brief   This function used for disconnect the socket and parameter is "s" which represent the socket number
@return  1 for success else 0.
*/
void disconnect(SOCKET s)
{
   IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_DISCON);
​
   /* wait to process the command... */
   while( IINCHIP_READ(Sn_CR(s) ) )
      ;
   /* ------- */
}
​
​
/**
@brief   This function used to send the data in TCP mode
@return  1 for success else 0.
*/
uint16 send(SOCKET s, const uint8 * buf, uint16 len)
{
  uint8 status=0;
  uint16 ret=0;
  uint16 freesize=0;
​
  if (len > getIINCHIP_TxMAX(s)) ret = getIINCHIP_TxMAX(s); // check size not to exceed MAX size.
  else ret = len;
​
  // if freebuf is available, start.
  do
  {
    freesize = getSn_TX_FSR(s);
    status = IINCHIP_READ(Sn_SR(s));
    if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT))
    {
      ret = 0;
      break;
    }
  } while (freesize < ret);
​
​
  // copy data
  send_data_processing(s, (uint8 *)buf, ret);
  IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND);
​
  /* wait to process the command... */
  while( IINCHIP_READ(Sn_CR(s) ) );
​
  while ( (IINCHIP_READ(Sn_IR(s) ) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
  {
    status = IINCHIP_READ(Sn_SR(s));
    if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT) )
    {
      printf("SEND_OK Problem!!rn");
      close(s);
      return 0;
    }
  }
  IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
​
#ifdef __DEF_IINCHIP_INT__
   putISR(s, getISR(s) & (~Sn_IR_SEND_OK));
#else
   IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
#endif
​
   return ret;
}
​
​
​
/**
@brief   This function is an application I/F function which is used to receive the data in TCP mode.
      It continues to wait for data as much as the application wants to receive.
​
@return  received data size for success else -1.
*/
uint16 recv(SOCKET s, uint8 * buf, uint16 len)
{
   uint16 ret=0;
   if ( len > 0 )
   {
      recv_data_processing(s, buf, len);
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s) ));
      /* ------- */
      ret = len;
   }
   return ret;
}
​
​
/**
@brief   This function is an application I/F function which is used to send the data for other then TCP mode.
      Unlike TCP transmission, The peer's destination address and the port is needed.
​
@return  This function return send data size for success else -1.
*/
uint16 sendto(SOCKET s, const uint8 * buf, uint16 len, uint8 * addr, uint16 port)
{
   uint16 ret=0;
​
   if (len > getIINCHIP_TxMAX(s)) ret = getIINCHIP_TxMAX(s); // check size not to exceed MAX size.
   else ret = len;
​
   if( ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) || ((port == 0x00)) )//||(ret == 0) )
   {
      /* added return value */
      ret = 0;
   }
   else
   {
      IINCHIP_WRITE( Sn_DIPR0(s), addr[0]);
      IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
      IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
      IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
      IINCHIP_WRITE( Sn_DPORT0(s),(uint8)((port & 0xff00) >> 8));
      IINCHIP_WRITE( Sn_DPORT1(s),(uint8)(port & 0x00ff));
      // copy data
      send_data_processing(s, (uint8 *)buf, ret);
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND);
      /* wait to process the command... */
      while( IINCHIP_READ( Sn_CR(s) ) )
         ;
      /* ------- */
​
      while( (IINCHIP_READ( Sn_IR(s) ) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
      {
         if (IINCHIP_READ( Sn_IR(s) ) & Sn_IR_TIMEOUT)
         {
            /* clear interrupt */
            IINCHIP_WRITE( Sn_IR(s) , (Sn_IR_SEND_OK | Sn_IR_TIMEOUT)); /* clear SEND_OK & TIMEOUT */
            return 0;
         }
      }
      IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
   }
   return ret;
}
​
​
/**
@brief   This function is an application I/F function which is used to receive the data in other then
   TCP mode. This function is used to receive UDP, IP_RAW and MAC_RAW mode, and handle the header as well.
​
@return  This function return received data size for success else -1.
*/
uint16 recvfrom(SOCKET s, uint8 * buf, uint16 len, uint8 * addr, uint16 *port)
{
   uint8 head[8];
   uint16 data_len=0;
   uint16 ptr=0;
   uint32 addrbsb =0;
   if ( len > 0 )
   {
      ptr     = IINCHIP_READ(Sn_RX_RD0(s) );
      ptr     = ((ptr & 0x00ff) << 8) + IINCHIP_READ(Sn_RX_RD1(s));
      addrbsb = (uint32)(ptr<<8) +  (s<<5) + 0x18;
      
      switch (IINCHIP_READ(Sn_MR(s) ) & 0x07)
      {
      case Sn_MR_UDP :
        wiz_read_buf(addrbsb, head, 0x08);        
        ptr += 8;
        // read peer's IP address, port number.
        addr[0]  = head[0];
        addr[1]  = head[1];
        addr[2]  = head[2];
        addr[3]  = head[3];
        *port    = head[4];
        *port    = (*port << 8) + head[5];
        data_len = head[6];
        data_len = (data_len << 8) + head[7];
​
        addrbsb = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);                
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      case Sn_MR_IPRAW :
        wiz_read_buf(addrbsb, head, 0x06);        
        ptr += 6;
        addr[0]  = head[0];
        addr[1]  = head[1];
        addr[2]  = head[2];
        addr[3]  = head[3];
        data_len = head[4];
        data_len = (data_len << 8) + head[5];
​
        addrbsb  = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);        
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      case Sn_MR_MACRAW :
        wiz_read_buf(addrbsb, head, 0x02);
        ptr+=2;
        data_len = head[0];
        data_len = (data_len<<8) + head[1] - 2;
        if(data_len > 1514)
        {
           printf("data_len over 1514rn");
           while(1);
        }
​
        addrbsb  = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      default :
            break;
      }
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
​
      /* wait to process the command... */
      while( IINCHIP_READ( Sn_CR(s)) ) ;
      /* ------- */
   }
   return data_len;
}
​
#ifdef __MACRAW__
void macraw_open(void)
{
  uint8 sock_num;
  uint16 dummyPort = 0;
  uint8 mFlag = 0;
  sock_num = 0;
​
​
  close(sock_num); // Close the 0-th socket
  socket(sock_num, Sn_MR_MACRAW, dummyPort,mFlag);  // OPen the 0-th socket with MACRAW mode
}
​
​
uint16 macraw_send( const uint8 * buf, uint16 len )
{
   uint16 ret=0;
   uint8 sock_num;
   sock_num =0;
​
​
   if (len > getIINCHIP_TxMAX(sock_num)) ret = getIINCHIP_TxMAX(sock_num); // check size not to exceed MAX size.
   else ret = len;
​
   send_data_processing(sock_num, (uint8 *)buf, len);
​
   //W5500 SEND COMMAND
   IINCHIP_WRITE(Sn_CR(sock_num),Sn_CR_SEND);
   while( IINCHIP_READ(Sn_CR(sock_num)) );
   while ( (IINCHIP_READ(Sn_IR(sock_num)) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK );
   IINCHIP_WRITE(Sn_IR(sock_num), Sn_IR_SEND_OK);
​
   return ret;
}
​
uint16 macraw_recv( uint8 * buf, uint16 len )
{
   uint8 sock_num;
   uint16 data_len=0;
   uint16 dummyPort = 0;
   uint16 ptr = 0;
   uint8 mFlag = 0;
   sock_num = 0;
​
   if ( len > 0 )
   {
​
      data_len = 0;
​
      ptr = IINCHIP_READ(Sn_RX_RD0(sock_num));
      ptr = (uint16)((ptr & 0x00ff) << 8) + IINCHIP_READ( Sn_RX_RD1(sock_num) );
      //-- read_data(s, (uint8 *)ptr, data, len); // read data
      data_len = IINCHIP_READ_RXBUF(0, ptr);
      ptr++;
      data_len = ((data_len<<8) + IINCHIP_READ_RXBUF(0, ptr)) - 2;
      ptr++;
​
      if(data_len > 1514)
      {
         printf("data_len over 1514rn");
         printf("rnptr: %X, data_len: %X", ptr, data_len);
         //while(1);
         /** recommand : close and open **/
         close(sock_num); // Close the 0-th socket
         socket(sock_num, Sn_MR_MACRAW, dummyPort,mFlag);  // OPen the 0-th socket with MACRAW mode
         return 0;
      }
​
      IINCHIP_READ_RXBUF_BURST(sock_num, ptr, data_len, (uint8*)(buf));
      ptr += data_len;
​
      IINCHIP_WRITE(Sn_RX_RD0(sock_num),(uint8)((ptr & 0xff00) >> 8));
      IINCHIP_WRITE(Sn_RX_RD1(sock_num),(uint8)(ptr & 0x00ff));
      IINCHIP_WRITE(Sn_CR(sock_num), Sn_CR_RECV);
      while( IINCHIP_READ(Sn_CR(sock_num)) ) ;
   }
​
   return data_len;
}
#endif
​

Connect W5500 and STM32 using SPI2

#include "stm32f10x.h"
#include "config.h"
#include "socket.h"
#include "w5500.h"
#include "ult.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
​
void WIZ_SPI_Init(void)
{
    SPI_InitTypeDef   SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO , ENABLE);  
  // Port B output
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_SetBits(GPIOB, GPIO_Pin_12);
  /* Configure SPIy pins: SCK, MISO and MOSI */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13| GPIO_Pin_14| GPIO_Pin_15;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
      /* SPI Config -------------------------------------------------------------*/
      SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
      SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
      SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
      SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
      SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
      SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
      SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
      SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
      SPI_InitStructure.SPI_CRCPolynomial = 7;
​
      SPI_Init(SPI2, &SPI_InitStructure);
      SPI_Cmd(SPI2, ENABLE);
}
​
// Connected to Data Flash
void WIZ_CS(uint8_t val)
{
    if (val == LOW) 
    {
        GPIO_ResetBits(GPIOB, WIZ_SCS); 
    }
    else if (val == HIGH)
    {
        GPIO_SetBits(GPIOB, WIZ_SCS); 
    }
}
​
uint8_t SPI2_SendByte(uint8_t byte)
{
      while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
         
      SPI_I2S_SendData(SPI2, byte);
          
      while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
          
      return SPI_I2S_ReceiveData(SPI2);
}

In the http part of the web, the html content is written into the code, and the web page is realized in conjunction with the status code of the http protocol

#ifndef __WEBPAGE_H
#define __WEBPAGE_H
#define INDEX_HTML  "<!DOCTYPE html>"
"<html>"
"<head>"
"<title>Ç峿µÄÍøÒ³ÅäÖÃ</title>"
"<meta http-equiv='Content-Type' content='text/html; charset=GB2312'/>"
"<style type='text/css'>"
"body {text-align:left; background-color:#c0deed;font-family:Verdana;}"
"#main {margin-right:auto;margin-left:auto;margin-top:30px;}"
"label{display:inline-block;width:150px;}"
"#main h3{color:#66b3ff; text-decoration:underline;}"
"</style>"
"<script>"
"function $(id) { return document.getElementById(id); };"
"function settingsCallback(o) {"
"if ($('txtVer')) $('txtVer').value = o.ver;"
"if ($('txtMac')) $('txtMac').value = o.mac;"
"if ($('txtIp')) $('txtIp').value = o.ip;"
"if ($('txtSub')) $('txtSub').value = o.sub;"
"if ($('txtGw')) $('txtGw').value = o.gw;"

"if ($('txtCode')) $('txtCode').value = o.Code;"
"if ($('txtDTMB_Freq')) $('txtDTMB_Freq').value = o.DTMB_Freq;"
"if ($('txtFM_Freq')) $('txtFM_Freq').value = o.FM_Freq;"
"if ($('txtSx_Freq')) $('txtSx_Freq').value = o.Sx_Freq;"

"};"
"</script>"
"</head>"
"<body>"
"<div id='main'>"
"<div style='background:snow; display:block;padding:10px 20px;'>"
"<h3 align='center'>²é¿´ÍøÂç²ÎÊý</h3>"
"<form id='frmSetting' method='POST' action='config.cgi'>"
"<p><label for='txtIp'>¹Ì¼þ°æ±¾ºÅ:</label><input type='text' id='txtVer' name='ver' size='16' disabled='disabled' /></p>"
"<p><label for='txtIp'>MACµØÖ·:</label><input type='text' id='txtMac' name='mac' size='16' disabled='disabled' /></p>"
"<p><label for='txtIp'>IPµØÖ·:</label><input type='text' id='txtIp' name='ip' size='16' disabled='disabled'/></p>"
"<p><label for='txtSub'>×ÓÍøÑÚÂë:</label><input type='text' id='txtSub' name='sub' size='16' disabled='disabled'/></p>"
"<p><label for='txtGw'>ĬÈÏÍø¹Ø:</label><input type='text' id='txtGw' name='gw' size='16' disabled='disabled'/></p>"

"<p><label for='txtCode'>ÐÐÕþ±àÂë:</label><input type='text' id='txtCode' name='Code' size='16' disabled='disabled'/></p>"
"<p><label for='txtDTMB_Freq'>DTMBƵÂÊ:</label><input type='text' id='txtDTMB_Freq' name='DTMB_Freq' size='16' disabled='disabled' /></p>"
"<p><label for='txtFM_Freq'>µ÷ƵƵÂÊ:</label><input type='text' id='txtFM_Freq' name='FM_Freq' size='16' disabled='disabled'/></p>"
"<p><label for='txtSx_Freq'>ÎÞÏßƵÂÊ:</label><input type='text' id='txtSx_Freq' name='Sx_Freq' size='16' disabled='disabled' /></p>"
"<p style='color:DarkBlue'><label for='txtPassword'>µÇ¼ÃÜÂë6λ:</label><input type='password' id='txtPassword' name='Password' size='16' enabled='enabled'/></p>"
"<input type='submit' value=' µÇ  ¼ ' /></p>"

"</form>"
"</div>"
"</div>"
"<div style='margin:5px 5px;'>"
"&copy;Copyright 2017 by Ç峿"
"</div>"
"<script type='text/javascript' src='w5500.js'></script>"
"</body>"
"</html>"
​
#endif
/* HTML Doc. for ERROR */
#define ERROR_HTML_PAGE "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: 78rnrn<HTML>rn<BODY>rnSorry, the page you requested was not found.rn</BODY>rn</HTML>rn"
//static char  ERROR_HTML_PAGE[] = "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: 78rnrn<HTML>rn<BODY>rnSorry, the page you requested was not found.rn</BODY>rn</HTML>rn";
​
#define ERROR_REQUEST_PAGE "HTTP/1.1 400 OKrnContent-Type: text/htmlrnContent-Length: 50rnrn<HTML>rn<BODY>rnInvalid request.rn</BODY>rn</HTML>rn"
//static char ERROR_REQUEST_PAGE[] = "HTTP/1.1 400 OKrnContent-Type: text/htmlrnContent-Length: 50rnrn<HTML>rn<BODY>rnInvalid request.rn</BODY>rn</HTML>rn";
​
#define RETURN_CGI_PAGE "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: 59rnrn<html><head><title>iWeb - Configuration</title></head><BODY>CGI command was executed.</BODY></HTML>"
​
​
/* Response header for HTML*/
#define RES_HTMLHEAD_OK "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: "
//static PROGMEM char RES_HTMLHEAD_OK[] = "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: ";
/* Response head for TEXT */
#define RES_TEXTHEAD_OK "HTTP/1.1 200 OKrnContent-Type: text/plainrnContent-Length: "
​
/* Response head for GIF */
#define RES_GIFHEAD_OK  "HTTP/1.1 200 OKrnContent-Type: image/gifrnContent-Length: "
​
/* Response head for JPEG */
#define RES_JPEGHEAD_OK "HTTP/1.1 200 OKrnContent-Type: image/jpegrnContent-Length: "       
​
/* Response head for FLASH */
#define RES_FLASHHEAD_OK "HTTP/1.1 200 OKrnContent-Type: application/x-shockwave-flashrnContent-Length: "
//static PROGMEM char RES_FLASHHEAD_OK[] = "HTTP/1.1 200 OKrnContent-Type: application/x-shockwave-flashrnContent-Length: ";
​
/* Response head for MPEG */
#define RES_MPEGHEAD_OK "HTTP/1.1 200 OKrnContent-Type: video/mpegrnContent-Length: "   
​
/* Response head for PDF */
#define RES_PDFHEAD_OK "HTTP/1.1 200 OKrnContent-Type: application/pdfrnContent-Length: "
​
//digital I/O out put control result response
#define DOUT_RES_1  "HTTP/1.1 200 OKrnContent-Type: text/plainrnContent-Length: 1rnrn1"
#define DOUT_RES_0  "HTTP/1.1 200 OKrnContent-Type: text/plainrnContent-Length: 1rnrn0"

4.3 experimental results

5. Reference

[1] Get dynamic IP address from router

[2] DHCP

[3] STM32 porting FreeModbus detailed process

[4] Modbus communication protocol (II) – RTU

[5] web service made by STM32F103+W5500

Tags: Embedded system

Posted by unistake on Wed, 29 Dec 2021 11:03:04 +1030

이미지 002.png

1.1 introduction to Modbus Protocol

Modbus protocol is a message structure, which is widely used to establish master-slave communication between intelligent devices. The Modbus messages sent from the master to the slave include the slave address, commands (such as “read register” or “write register”), data and checksum (LRC or CRC).

Since Modbus protocol is only a message passing structure, it is independent of the underlying physical layer. Traditionally, RS232, RS422 or RS485 are used

The main reasons why Modbus is more widely used than other communication protocols are:

  • Published without copyright requirements
  • Easy to deploy and maintain
  • There are no many restrictions for vendors to modify and move local bits or bytes

1.2 master/slave architecture of Modbus

Modbus is a master / slave architecture protocol. One node is a master node and the other nodes are slave nodes. Each slave node has a unique address. In serial and MB + networks, only the node designated as the master node can start a command. On the Ethernet, any device can send a MODBUS command, but usually there is only one master node device startup command.

1.3 Modbus command

A MODBUS command includes the Modbus address of the device to be executed. All devices will receive the command, but only the devices with the corresponding address will run and respond to the command (except address 0. The command specifying address 0 is a broadcast command, and all devices that receive the command will run but will not respond). All MODBUS commands contain check codes to ensure that the incoming commands are not corrupted. The basic MODBUS command can instruct an RTU (remote terminal unit) to change a value of its register, control or read an I/O port, and the command equipment will send data in one or more registers.

1.4 Modbus request

The function code in the request tells the addressed slave what action to perform. The data byte contains any additional information required by the slave station to perform this function. For example, function code 03 will request the slave to read the holding register and respond to its contents. The data field must contain information that tells the slave which register to start with and how many registers to read. The error check field provides a method for the slave to verify the integrity of the message content.

1.5 Modbus response

If the slave makes a normal response, the function code in the response is the echo of the function code in the request. Data bytes contain data collected from the station, such as register values or status. If an error occurs, the function code is modified to indicate that the response is an error response, and the data byte contains a code describing the error. The error check field allows the host to confirm that the message content is valid.

1.6 Modbus transmission mode

When communicating on a standard Modbus network, the controller can be set to use one of two transmission modes: ASCII or RTU.

ASCII mode when the controller is set to communicate on the Modbus network using ASCII (American Standard Code for information interchange) mode, each octet in the message is sent as two ASCII characters. The main advantage of this mode is that it allows a time interval of up to one second between characters without causing an error.

Encoding system hexadecimal ASCII printable character 0 9, A … F bits per byte 1 start bit 7 data bits, LSB first sends 1 bit for even / odd parity – no bit for no parity 1 stop bit if parity check if there is no parity error check longitudinal redundancy check (LRC), 2 bits are used

ASCII frame

In ASCII mode, Messages begin with a colon (:) character (ASCII 3A hex) and end with carriage return line feed (CRLF) pairs (ASCII 0D and 0A hex). The allowed characters transmitted for all other fields are hexadecimal 0… 9, a… F. networked devices continuously monitor the network bus for colon characters. When one is received, each device evaluates the next field (address field) is decoded to determine whether it is an addressed device. The characters in the message can be separated by up to one second. If a larger interval occurs, the receiving device assumes that an error has occurred. A typical message frame is as follows:

RTU mode when the controller is set to communicate on the Modbus network using RTU (remote terminal unit) mode, each octet in the message contains two four digit hexadecimal characters. The main advantage of this mode is that its greater character density allows better data throughput than ASCII at the same baud rate. Each message must be transmitted in a continuous stream.

Coding system 8-bit binary, hexadecimal 0 9,A … The two hexadecimal characters contained in each octet field of the f message have 1 starting bit and 8 data bits per byte. The least significant bit is sent first. 1 bit is used for even / odd parity – no parity bit. If parity is used, it is 1 stop bit – if there is no parity error, the check field is 2-bit cyclic redundancy check (CRC)

RTU frame in RTU mode, the message starts with a silence interval of at least 3.5 characters. This is most easily implemented as a multiple of the character time of the baud rate being used on the network (shown as T1-T2-T3-T4 in the figure below). Then the first field transmitted is the device address. The allowed characters transmitted for all fields are hexadecimal 0… 9, a… F. the networked device continues to monitor the network bus, including during the silence interval. When the first field is received (address field), each device decodes it to determine whether it is an addressed device. After the last transmitted character, a similar interval of at least 3.5 characters marks the end of the message. After this interval, a new message can be started. The whole message frame must be transmitted as a continuous stream. If more than 1.5 characters appear before the frame is completed The receiving device will refresh the unfinished message and assume that the next byte will be the address field of the new message. Similarly, if the start time of a new message is earlier than 3.5 characters after the previous message, the receiving device will treat it as a continuation of the previous message. This will set an error because the value in the final CRC field will not be valid for the combined message. A typical message frame is as follows:

1.7 message frame

Address field the address field of the message frame contains two characters (ASCII) or eight bits (RTU). The address range assigned by each slave device is 1 247.

Function field

The function code field tells the addressed slave what functions to perform. Modbus poll supports the following functions:

01 read coil state 02 read input state 03 read hold register 04 read input register 05 write single coil 06 write single register 15 write multiple coils 16 write multiple registers

The data field contains the requested or sent data.

The contents of the error check field the standard Modbus network uses two error check methods. The contents of the error check field depend on the method being used.

ASCII when ASCII mode is used for character framing, the error check field contains two ASCII characters. The error check character is the result of the longitudinal redundancy check (LRC) calculation performed on the message content, excluding the start colon and end CRLF characters. The LRC character is appended to the message as the last field before the CRLF character.

BYTE LRC (BYTE *nData, WORD wLength)
{
BYTE nLRC = 0 ; // LRC char initialized
​
for (int i = 0; i < wLength; i++)
nLRC += *nData++;
​
return (BYTE)(-nLRC);
​
} // End: LRC

RTU when RTU mode is used for character frame, the error check field contains a 16 bit value, which is implemented as two 8-bit bytes. The error check value is the result of the cyclic redundancy check calculation performed on the message content. The CRC field is appended to the message as the last field in the message. When complete, first append the low byte of the field, then the high byte. The CRC high byte is the last byte to be sent in the message.

WORD CRC16 (const BYTE *nData, WORD wLength)
{
static const WORD wCRCTable[] = {
   0X0000, 0XC0C1, 0XC181, 0X0140, 0XC301, 0X03C0, 0X0280, 0XC241,
   0XC601, 0X06C0, 0X0780, 0XC741, 0X0500, 0XC5C1, 0XC481, 0X0440,
   0XCC01, 0X0CC0, 0X0D80, 0XCD41, 0X0F00, 0XCFC1, 0XCE81, 0X0E40,
   0X0A00, 0XCAC1, 0XCB81, 0X0B40, 0XC901, 0X09C0, 0X0880, 0XC841,
   0XD801, 0X18C0, 0X1980, 0XD941, 0X1B00, 0XDBC1, 0XDA81, 0X1A40,
   0X1E00, 0XDEC1, 0XDF81, 0X1F40, 0XDD01, 0X1DC0, 0X1C80, 0XDC41,
   0X1400, 0XD4C1, 0XD581, 0X1540, 0XD701, 0X17C0, 0X1680, 0XD641,
   0XD201, 0X12C0, 0X1380, 0XD341, 0X1100, 0XD1C1, 0XD081, 0X1040,
   0XF001, 0X30C0, 0X3180, 0XF141, 0X3300, 0XF3C1, 0XF281, 0X3240,
   0X3600, 0XF6C1, 0XF781, 0X3740, 0XF501, 0X35C0, 0X3480, 0XF441,
   0X3C00, 0XFCC1, 0XFD81, 0X3D40, 0XFF01, 0X3FC0, 0X3E80, 0XFE41,
   0XFA01, 0X3AC0, 0X3B80, 0XFB41, 0X3900, 0XF9C1, 0XF881, 0X3840,
   0X2800, 0XE8C1, 0XE981, 0X2940, 0XEB01, 0X2BC0, 0X2A80, 0XEA41,
   0XEE01, 0X2EC0, 0X2F80, 0XEF41, 0X2D00, 0XEDC1, 0XEC81, 0X2C40,
   0XE401, 0X24C0, 0X2580, 0XE541, 0X2700, 0XE7C1, 0XE681, 0X2640,
   0X2200, 0XE2C1, 0XE381, 0X2340, 0XE101, 0X21C0, 0X2080, 0XE041,
   0XA001, 0X60C0, 0X6180, 0XA141, 0X6300, 0XA3C1, 0XA281, 0X6240,
   0X6600, 0XA6C1, 0XA781, 0X6740, 0XA501, 0X65C0, 0X6480, 0XA441,
   0X6C00, 0XACC1, 0XAD81, 0X6D40, 0XAF01, 0X6FC0, 0X6E80, 0XAE41,
   0XAA01, 0X6AC0, 0X6B80, 0XAB41, 0X6900, 0XA9C1, 0XA881, 0X6840,
   0X7800, 0XB8C1, 0XB981, 0X7940, 0XBB01, 0X7BC0, 0X7A80, 0XBA41,
   0XBE01, 0X7EC0, 0X7F80, 0XBF41, 0X7D00, 0XBDC1, 0XBC81, 0X7C40,
   0XB401, 0X74C0, 0X7580, 0XB541, 0X7700, 0XB7C1, 0XB681, 0X7640,
   0X7200, 0XB2C1, 0XB381, 0X7340, 0XB101, 0X71C0, 0X7080, 0XB041,
   0X5000, 0X90C1, 0X9181, 0X5140, 0X9301, 0X53C0, 0X5280, 0X9241,
   0X9601, 0X56C0, 0X5780, 0X9741, 0X5500, 0X95C1, 0X9481, 0X5440,
   0X9C01, 0X5CC0, 0X5D80, 0X9D41, 0X5F00, 0X9FC1, 0X9E81, 0X5E40,
   0X5A00, 0X9AC1, 0X9B81, 0X5B40, 0X9901, 0X59C0, 0X5880, 0X9841,
   0X8801, 0X48C0, 0X4980, 0X8941, 0X4B00, 0X8BC1, 0X8A81, 0X4A40,
   0X4E00, 0X8EC1, 0X8F81, 0X4F40, 0X8D01, 0X4DC0, 0X4C80, 0X8C41,
   0X4400, 0X84C1, 0X8581, 0X4540, 0X8701, 0X47C0, 0X4680, 0X8641,
   0X8201, 0X42C0, 0X4380, 0X8341, 0X4100, 0X81C1, 0X8081, 0X4040 };
​
BYTE nTemp;
WORD wCRCWord = 0xFFFF;
​
   while (wLength--)
   {
      nTemp = *nData++ ^ wCRCWord;
      wCRCWord >>= 8;
      wCRCWord  ^= wCRCTable[nTemp];
   }
   return wCRCWord;
} // End: CRC16

1.8 experimental results

2. DHCP

2.1 introduction to DHCP

DHCP (Dynamic Host Configuration Protocol) is a LAN network protocol. It refers to a range of IP addresses controlled by the server. When the client logs in to the server, it can automatically obtain the IP address and subnet mask assigned by the server.

2.2 DHCP function

  1. Ensure that any IP address can only be used by one DHCP client at the same time.
  2. DHCP should be able to assign a permanently fixed IP address to the user.
  3. DHCP should coexist with the host that obtains the IP address by other methods (such as the host that manually configures the IP address).
  4. The DHCP server should provide services to existing BOOTP clients.

2.3 DHCP allocation method

DHCP has three mechanisms for assigning IP addresses:

  1. Automatic Allocation: the DHCP server assigns a permanent IP address to the host. Once the DHCP client successfully leases the IP address from the DHCP server for the first time, it can permanently use the address.
  2. In Dynamic Allocation, the DHCP server assigns an IP address with time limit to the host. When the time expires or the host explicitly gives up the address, the address can be used by other hosts.
  3. In Manual Allocation, the IP address of the client is specified by the network administrator, and the DHCP server only tells the specified IP address to the client host.

2.4 working principle of DHCP

The DHCP protocol uses UDP as the transmission protocol. The host sends a request message to port 67 of the DHCP server, and the DHCP server responds to the response message to port 68 of the host. The detailed interaction process is as follows:

2.5 W5500+STM32F103 realize DHCP code

W5500 is the DHCP client and router is the DHCP server. After connecting to the router, the router dynamically assigns the IP address to w5500.

The process of DHCP request includes four main stages: discovery stage, provision stage, selection stage and confirmation stage.

First, the W5500 client sends a DHCP DISCOVER message (IP address lease application). This message is sent by broadcast, and all DHCP servers in the network will receive this message. Then, the DHCP server in the network will respond to a DHCPOFFER message (provided by IP address leasing). Since the client does not have a network address at this time, DHCP OFFER is also sent by broadcasting. After that, send a DHCP REQUEST message to the server. The DHCP REQUEST message will contain the IP address requested by the client. Finally, the DHCP server will send back the response message of DHCP ACK to inform the client that the IP address can be used The confirmation contains the assigned IP address and a lease with a stable term of the address (8 days by default), and updates the DHCP database at the same time.

Main code:

while(1)
{
​
 DHCP_run();
​
}
​
uint8_t DHCP_run(void)
{
​
  uint8_t  type;
​
  uint8_t  ret;
​
  if(dhcp_state == STATE_DHCP_STOP) return DHCP_STOPPED;
​
  if(getSn_SR(SOCK_DHCP) != SOCK_UDP)
​
  socket(SOCK_DHCP, Sn_MR_UDP, DHCP_CLIENT_PORT, 0x00);
​
  ret = DHCP_RUNNING;
​
  type = parseDHCPMSG();
​
  switch ( dhcp_state )
​
  {
​
      case STATE_DHCP_READY :
​
          DHCP_allocated_ip[0] = 0;
​
          DHCP_allocated_ip[1] = 0;
​
          DHCP_allocated_ip[2] = 0;
​
          DHCP_allocated_ip[3] = 0;   
​
          send_DHCP_DISCOVER();
​
          dhcp_time = 0;
​
          dhcp_state = STATE_DHCP_DISCOVER;
​
      break;
​
      case STATE_DHCP_DISCOVER :
​
          if (type == DHCP_OFFER)
​
          {
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Receive DHCP_OFFERrn");
​
              #endif
​
              DHCP_allocated_ip[0] = pDHCPMSG->yiaddr[0];
​
              DHCP_allocated_ip[1] = pDHCPMSG->yiaddr[1];
​
              DHCP_allocated_ip[2] = pDHCPMSG->yiaddr[2];
​
              DHCP_allocated_ip[3] = pDHCPMSG->yiaddr[3]; 
​
              send_DHCP_REQUEST();
​
              dhcp_time = 0;
​
              dhcp_state = STATE_DHCP_REQUEST;
​
          }
​
              else
​
              ret = check_DHCP_timeout();
​
      break;
​
      case STATE_DHCP_REQUEST :
​
          if (type == DHCP_ACK)
​
              {
​
                  #ifdef _DHCP_DEBUG_
​
                  printf("> Receive DHCP_ACKrn");
​
                  #endif
​
              if (check_DHCP_leasedIP())
​
              {
​
                  printf("ip:%d.%d.%d.%drn",DHCP_allocated_ip[0],DHCP_allocated_ip[1],DHCP_allocated_ip[2],DHCP_allocated_ip[3]);
​
                  printf("sn:%d.%d.%d.%drn",DHCP_allocated_sn[0],DHCP_allocated_sn[1],DHCP_allocated_sn[2],DHCP_allocated_sn[3]);
​
                  printf("gw:%d.%d.%d.%drn",DHCP_allocated_gw[0],DHCP_allocated_gw[1],DHCP_allocated_gw[2],DHCP_allocated_gw[3]);
​
                  dhcp_ip_assign();
​
                  reset_DHCP_timeout();
​
                  hcp_state = STATE_DHCP_LEASED;
​
              }
​
              else
​
              {
​
                  reset_DHCP_timeout();
​
                  dhcp_ip_conflict();
​
                  dhcp_state = STATE_DHCP_READY;
​
              }
​
              }
​
              else if (type == DHCP_NAK)
​
              {
​
                  #ifdef _DHCP_DEBUG_
​
                  printf("> Receive DHCP_NACKrn");
​
                  #endif
​
                  reset_DHCP_timeout();
​
                  dhcp_state = STATE_DHCP_DISCOVER;
​
              }
​
              else
​
              ret = check_DHCP_timeout();
​
      break;
​
      case STATE_DHCP_LEASED :
​
          ret = DHCP_IP_LEASED;        
​
          if ((dhcp_lease_time != DEFAULT_LEASETIME) && ((dhcp_lease_time/2) < dhcp_time))
​
          {  
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Maintains the IP address rn");
​
              #endif
​
              type = 0;
​
              OLD_allocated_ip[0] = DHCP_allocated_ip[0];
​
              OLD_allocated_ip[1] = DHCP_allocated_ip[1];
​
              OLD_allocated_ip[2] = DHCP_allocated_ip[2];
​
              OLD_allocated_ip[3] = DHCP_allocated_ip[3];
​
              DHCP_XID++;
​
              send_DHCP_REQUEST();               
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_REREQUEST;
​
          }
​
      break;
​
      case STATE_DHCP_REREQUEST :
​
          ret = DHCP_IP_LEASED;
​
          if (type == DHCP_ACK)
​
          {
​
              dhcp_retry_count = 0;
​
              if (OLD_allocated_ip[0] != DHCP_allocated_ip[0] ||
​
              OLD_allocated_ip[1] != DHCP_allocated_ip[1] ||
​
              OLD_allocated_ip[2] != DHCP_allocated_ip[2] ||
​
              OLD_allocated_ip[3] != DHCP_allocated_ip[3])
​
          {
​
              ret = DHCP_IP_CHANGED;
​
              dhcp_ip_update();
​
              #ifdef _DHCP_DEBUG_
​
              printf(">IP changed.rn");
​
              #endif                 
​
          }
​
              #ifdef _DHCP_DEBUG_
​
              else printf(">IP is continued.rn");
​
              #endif                         
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_LEASED;
​
          }
​
          else if (type == DHCP_NAK)
​
          {
​
              #ifdef _DHCP_DEBUG_
​
              printf("> Receive DHCP_NACK, Failed to maintain iprn");
​
              #endif
​
              reset_DHCP_timeout();
​
              dhcp_state = STATE_DHCP_DISCOVER;
​
          }
​
          else ret = check_DHCP_timeout();
​
      break;
​
      default :
​
      break;
​
      }
​
  return ret;
}

2.6 code running results

Connect the local machine to the router, and the local IP address is as follows:

Connect the W5500+STM32 to the router and output it through the STM32 serial port. Check the IP address of the W5500 as follows:

Local ping W5500 result:

Connection succeeded.

3. W5500 TCP

3.1 establish TCP server locally

Turn off the local network and use the TCP test tool to establish a TCP server

3.2 use W5500 to set static IP and establish TCP client

Connect the W5500 to the local machine, run the program, start the TCP server, and view the data sent by the W5500 client

int main(void)
{
    System_Initialization();    //STM32 system initialization function (initializing STM32 clock and peripherals)
    Load_Net_Parameters();      //Load network parameters    
    W5500_Hardware_Reset();     //Hardware reset W5500
    W5500_Initialization();     //W5500 initial cargo configuration
    while (1)
    {
        W5500_Socket_Set();//W5500 port initialization configuration
​
        if(W5500_Interrupt)//Handle W5500 interrupt      
        {
            W5500_Interrupt_Process();//W5500 interrupt handler framework
        }
        if((S0_Data & S_RECEIVE) == S_RECEIVE)//If Socket0 receives data
        {
            S0_Data&=~S_RECEIVE;
            Process_Socket_Data(0);//The W5500 receives and sends the received data
        }
        else if(W5500_Send_Delay_Counter >= 500)//Timed send string
        {
            if(S0_State == (S_INIT|S_CONN))
            {
                S0_Data&=~S_TRANSMITOK;
                memcpy(Tx_Buffer, "rnWelcome To NiRenElec!rn", 23); 
                Write_SOCK_Data_Buffer(0, Tx_Buffer, 23);//Specify Socket(0~7) to send data processing, and port 0 to send 23 bytes of data
            }
            W5500_Send_Delay_Counter=0;
        }
    }
}

3.3 experimental results

4. Embedded Web server

4.1 wiring

Connect the W5500 to the router, and the PC is also connected to the router for easy access to web pages.

4.2 code part

Use socket to encapsulate TCP protocol and implement socket communication based on TCP

#include "socket.h"
#include "config.h"
#include "stdio.h"
#include "w5500.h"
#include "ult.h"
​
static uint16 local_port;
extern uint16 sent_ptr;
​
/**
@brief   This Socket function initialize the channel in perticular mode, and set the port and wait for W5200 done it.
@return  1 for sucess else 0.
*/
uint8 socket(SOCKET s, uint8 protocol, uint16 port, uint8 flag)
{
   uint8 ret;
   if (
        ((protocol&0x0F) == Sn_MR_TCP)    ||
        ((protocol&0x0F) == Sn_MR_UDP)    ||
        ((protocol&0x0F) == Sn_MR_IPRAW)  ||
        ((protocol&0x0F) == Sn_MR_MACRAW) ||
        ((protocol&0x0F) == Sn_MR_PPPOE)
      )
   {
      close(s);
      IINCHIP_WRITE(Sn_MR(s) ,protocol | flag);
      if (port != 0) {
         IINCHIP_WRITE( Sn_PORT0(s) ,(uint8)((port & 0xff00) >> 8));
         IINCHIP_WRITE( Sn_PORT1(s) ,(uint8)(port & 0x00ff));
      } else {
         local_port++; // if don't set the source port, set local_port number.
         IINCHIP_WRITE(Sn_PORT0(s) ,(uint8)((local_port & 0xff00) >> 8));
         IINCHIP_WRITE(Sn_PORT1(s) ,(uint8)(local_port & 0x00ff));
      }
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_OPEN); // run sockinit Sn_CR
​
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s)) )
         ;
      /* ------- */
      ret = 1;
   }
   else
   {
      ret = 0;
   }
   return ret;
}
​
​
/**
@brief   This function close the socket and parameter is "s" which represent the socket number
*/
void close(SOCKET s)
{
​
   IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_CLOSE);
​
   /* wait to process the command... */
   while( IINCHIP_READ(Sn_CR(s) ) )
      ;
   /* ------- */
        /* all clear */
   IINCHIP_WRITE( Sn_IR(s) , 0xFF);
}
​
​
/**
@brief   This function established  the connection for the channel in passive (server) mode. This function waits for the request from the peer.
@return  1 for success else 0.
*/
uint8 listen(SOCKET s)
{
   uint8 ret;
   if (IINCHIP_READ( Sn_SR(s) ) == SOCK_INIT)
   {
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_LISTEN);
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s) ) )
         ;
      /* ------- */
      ret = 1;
   }
   else
   {
      ret = 0;
   }
   return ret;
}
​
​
/**
@brief   This function established  the connection for the channel in Active (client) mode.
      This function waits for the untill the connection is established.
​
@return  1 for success else 0.
*/
uint8 connect(SOCKET s, uint8 * addr, uint16 port)
{
    uint8 ret;
    if
        (
            ((addr[0] == 0xFF) && (addr[1] == 0xFF) && (addr[2] == 0xFF) && (addr[3] == 0xFF)) ||
            ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) ||
            (port == 0x00)
        )
    {
      ret = 0;
    }
    else
    {
        ret = 1;
        // set destination IP
        IINCHIP_WRITE( Sn_DIPR0(s), addr[0]);
        IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
        IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
        IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
        IINCHIP_WRITE( Sn_DPORT0(s), (uint8)((port & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_DPORT1(s), (uint8)(port & 0x00ff));
        IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_CONNECT);
        /* wait for completion */
        while ( IINCHIP_READ(Sn_CR(s) ) ) ;
​
        while ( IINCHIP_READ(Sn_SR(s)) != SOCK_SYNSENT )
        {
            if(IINCHIP_READ(Sn_SR(s)) == SOCK_ESTABLISHED)
            {
                break;
            }
            if (getSn_IR(s) & Sn_IR_TIMEOUT)
            {
                IINCHIP_WRITE(Sn_IR(s), (Sn_IR_TIMEOUT));  // clear TIMEOUT Interrupt
                ret = 0;
                break;
            }
        }
    }
​
   return ret;
}
​
​
​
/**
@brief   This function used for disconnect the socket and parameter is "s" which represent the socket number
@return  1 for success else 0.
*/
void disconnect(SOCKET s)
{
   IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_DISCON);
​
   /* wait to process the command... */
   while( IINCHIP_READ(Sn_CR(s) ) )
      ;
   /* ------- */
}
​
​
/**
@brief   This function used to send the data in TCP mode
@return  1 for success else 0.
*/
uint16 send(SOCKET s, const uint8 * buf, uint16 len)
{
  uint8 status=0;
  uint16 ret=0;
  uint16 freesize=0;
​
  if (len > getIINCHIP_TxMAX(s)) ret = getIINCHIP_TxMAX(s); // check size not to exceed MAX size.
  else ret = len;
​
  // if freebuf is available, start.
  do
  {
    freesize = getSn_TX_FSR(s);
    status = IINCHIP_READ(Sn_SR(s));
    if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT))
    {
      ret = 0;
      break;
    }
  } while (freesize < ret);
​
​
  // copy data
  send_data_processing(s, (uint8 *)buf, ret);
  IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND);
​
  /* wait to process the command... */
  while( IINCHIP_READ(Sn_CR(s) ) );
​
  while ( (IINCHIP_READ(Sn_IR(s) ) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
  {
    status = IINCHIP_READ(Sn_SR(s));
    if ((status != SOCK_ESTABLISHED) && (status != SOCK_CLOSE_WAIT) )
    {
      printf("SEND_OK Problem!!rn");
      close(s);
      return 0;
    }
  }
  IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
​
#ifdef __DEF_IINCHIP_INT__
   putISR(s, getISR(s) & (~Sn_IR_SEND_OK));
#else
   IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
#endif
​
   return ret;
}
​
​
​
/**
@brief   This function is an application I/F function which is used to receive the data in TCP mode.
      It continues to wait for data as much as the application wants to receive.
​
@return  received data size for success else -1.
*/
uint16 recv(SOCKET s, uint8 * buf, uint16 len)
{
   uint16 ret=0;
   if ( len > 0 )
   {
      recv_data_processing(s, buf, len);
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
      /* wait to process the command... */
      while( IINCHIP_READ(Sn_CR(s) ));
      /* ------- */
      ret = len;
   }
   return ret;
}
​
​
/**
@brief   This function is an application I/F function which is used to send the data for other then TCP mode.
      Unlike TCP transmission, The peer's destination address and the port is needed.
​
@return  This function return send data size for success else -1.
*/
uint16 sendto(SOCKET s, const uint8 * buf, uint16 len, uint8 * addr, uint16 port)
{
   uint16 ret=0;
​
   if (len > getIINCHIP_TxMAX(s)) ret = getIINCHIP_TxMAX(s); // check size not to exceed MAX size.
   else ret = len;
​
   if( ((addr[0] == 0x00) && (addr[1] == 0x00) && (addr[2] == 0x00) && (addr[3] == 0x00)) || ((port == 0x00)) )//||(ret == 0) )
   {
      /* added return value */
      ret = 0;
   }
   else
   {
      IINCHIP_WRITE( Sn_DIPR0(s), addr[0]);
      IINCHIP_WRITE( Sn_DIPR1(s), addr[1]);
      IINCHIP_WRITE( Sn_DIPR2(s), addr[2]);
      IINCHIP_WRITE( Sn_DIPR3(s), addr[3]);
      IINCHIP_WRITE( Sn_DPORT0(s),(uint8)((port & 0xff00) >> 8));
      IINCHIP_WRITE( Sn_DPORT1(s),(uint8)(port & 0x00ff));
      // copy data
      send_data_processing(s, (uint8 *)buf, ret);
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_SEND);
      /* wait to process the command... */
      while( IINCHIP_READ( Sn_CR(s) ) )
         ;
      /* ------- */
​
      while( (IINCHIP_READ( Sn_IR(s) ) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK )
      {
         if (IINCHIP_READ( Sn_IR(s) ) & Sn_IR_TIMEOUT)
         {
            /* clear interrupt */
            IINCHIP_WRITE( Sn_IR(s) , (Sn_IR_SEND_OK | Sn_IR_TIMEOUT)); /* clear SEND_OK & TIMEOUT */
            return 0;
         }
      }
      IINCHIP_WRITE( Sn_IR(s) , Sn_IR_SEND_OK);
   }
   return ret;
}
​
​
/**
@brief   This function is an application I/F function which is used to receive the data in other then
   TCP mode. This function is used to receive UDP, IP_RAW and MAC_RAW mode, and handle the header as well.
​
@return  This function return received data size for success else -1.
*/
uint16 recvfrom(SOCKET s, uint8 * buf, uint16 len, uint8 * addr, uint16 *port)
{
   uint8 head[8];
   uint16 data_len=0;
   uint16 ptr=0;
   uint32 addrbsb =0;
   if ( len > 0 )
   {
      ptr     = IINCHIP_READ(Sn_RX_RD0(s) );
      ptr     = ((ptr & 0x00ff) << 8) + IINCHIP_READ(Sn_RX_RD1(s));
      addrbsb = (uint32)(ptr<<8) +  (s<<5) + 0x18;
      
      switch (IINCHIP_READ(Sn_MR(s) ) & 0x07)
      {
      case Sn_MR_UDP :
        wiz_read_buf(addrbsb, head, 0x08);        
        ptr += 8;
        // read peer's IP address, port number.
        addr[0]  = head[0];
        addr[1]  = head[1];
        addr[2]  = head[2];
        addr[3]  = head[3];
        *port    = head[4];
        *port    = (*port << 8) + head[5];
        data_len = head[6];
        data_len = (data_len << 8) + head[7];
​
        addrbsb = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);                
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      case Sn_MR_IPRAW :
        wiz_read_buf(addrbsb, head, 0x06);        
        ptr += 6;
        addr[0]  = head[0];
        addr[1]  = head[1];
        addr[2]  = head[2];
        addr[3]  = head[3];
        data_len = head[4];
        data_len = (data_len << 8) + head[5];
​
        addrbsb  = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);        
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      case Sn_MR_MACRAW :
        wiz_read_buf(addrbsb, head, 0x02);
        ptr+=2;
        data_len = head[0];
        data_len = (data_len<<8) + head[1] - 2;
        if(data_len > 1514)
        {
           printf("data_len over 1514rn");
           while(1);
        }
​
        addrbsb  = (uint32)(ptr<<8) +  (s<<5) + 0x18;
        wiz_read_buf(addrbsb, buf, data_len);
        ptr += data_len;
​
        IINCHIP_WRITE( Sn_RX_RD0(s), (uint8)((ptr & 0xff00) >> 8));
        IINCHIP_WRITE( Sn_RX_RD1(s), (uint8)(ptr & 0x00ff));
        break;
​
      default :
            break;
      }
      IINCHIP_WRITE( Sn_CR(s) ,Sn_CR_RECV);
​
      /* wait to process the command... */
      while( IINCHIP_READ( Sn_CR(s)) ) ;
      /* ------- */
   }
   return data_len;
}
​
#ifdef __MACRAW__
void macraw_open(void)
{
  uint8 sock_num;
  uint16 dummyPort = 0;
  uint8 mFlag = 0;
  sock_num = 0;
​
​
  close(sock_num); // Close the 0-th socket
  socket(sock_num, Sn_MR_MACRAW, dummyPort,mFlag);  // OPen the 0-th socket with MACRAW mode
}
​
​
uint16 macraw_send( const uint8 * buf, uint16 len )
{
   uint16 ret=0;
   uint8 sock_num;
   sock_num =0;
​
​
   if (len > getIINCHIP_TxMAX(sock_num)) ret = getIINCHIP_TxMAX(sock_num); // check size not to exceed MAX size.
   else ret = len;
​
   send_data_processing(sock_num, (uint8 *)buf, len);
​
   //W5500 SEND COMMAND
   IINCHIP_WRITE(Sn_CR(sock_num),Sn_CR_SEND);
   while( IINCHIP_READ(Sn_CR(sock_num)) );
   while ( (IINCHIP_READ(Sn_IR(sock_num)) & Sn_IR_SEND_OK) != Sn_IR_SEND_OK );
   IINCHIP_WRITE(Sn_IR(sock_num), Sn_IR_SEND_OK);
​
   return ret;
}
​
uint16 macraw_recv( uint8 * buf, uint16 len )
{
   uint8 sock_num;
   uint16 data_len=0;
   uint16 dummyPort = 0;
   uint16 ptr = 0;
   uint8 mFlag = 0;
   sock_num = 0;
​
   if ( len > 0 )
   {
​
      data_len = 0;
​
      ptr = IINCHIP_READ(Sn_RX_RD0(sock_num));
      ptr = (uint16)((ptr & 0x00ff) << 8) + IINCHIP_READ( Sn_RX_RD1(sock_num) );
      //-- read_data(s, (uint8 *)ptr, data, len); // read data
      data_len = IINCHIP_READ_RXBUF(0, ptr);
      ptr++;
      data_len = ((data_len<<8) + IINCHIP_READ_RXBUF(0, ptr)) - 2;
      ptr++;
​
      if(data_len > 1514)
      {
         printf("data_len over 1514rn");
         printf("rnptr: %X, data_len: %X", ptr, data_len);
         //while(1);
         /** recommand : close and open **/
         close(sock_num); // Close the 0-th socket
         socket(sock_num, Sn_MR_MACRAW, dummyPort,mFlag);  // OPen the 0-th socket with MACRAW mode
         return 0;
      }
​
      IINCHIP_READ_RXBUF_BURST(sock_num, ptr, data_len, (uint8*)(buf));
      ptr += data_len;
​
      IINCHIP_WRITE(Sn_RX_RD0(sock_num),(uint8)((ptr & 0xff00) >> 8));
      IINCHIP_WRITE(Sn_RX_RD1(sock_num),(uint8)(ptr & 0x00ff));
      IINCHIP_WRITE(Sn_CR(sock_num), Sn_CR_RECV);
      while( IINCHIP_READ(Sn_CR(sock_num)) ) ;
   }
​
   return data_len;
}
#endif
​

Connect W5500 and STM32 using SPI2

#include "stm32f10x.h"
#include "config.h"
#include "socket.h"
#include "w5500.h"
#include "ult.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
​
void WIZ_SPI_Init(void)
{
    SPI_InitTypeDef   SPI_InitStructure;
  GPIO_InitTypeDef GPIO_InitStructure;
  RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);
  RCC_APB2PeriphClockCmd( RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO , ENABLE);  
  // Port B output
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12; 
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
  GPIO_SetBits(GPIOB, GPIO_Pin_12);
  /* Configure SPIy pins: SCK, MISO and MOSI */
  GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13| GPIO_Pin_14| GPIO_Pin_15;
  GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
  GPIO_Init(GPIOB, &GPIO_InitStructure);
      /* SPI Config -------------------------------------------------------------*/
      SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;
      SPI_InitStructure.SPI_Mode = SPI_Mode_Master;
      SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;
      SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;
      SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;
      SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;
      SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;
      SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;
      SPI_InitStructure.SPI_CRCPolynomial = 7;
​
      SPI_Init(SPI2, &SPI_InitStructure);
      SPI_Cmd(SPI2, ENABLE);
}
​
// Connected to Data Flash
void WIZ_CS(uint8_t val)
{
    if (val == LOW) 
    {
        GPIO_ResetBits(GPIOB, WIZ_SCS); 
    }
    else if (val == HIGH)
    {
        GPIO_SetBits(GPIOB, WIZ_SCS); 
    }
}
​
uint8_t SPI2_SendByte(uint8_t byte)
{
      while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) == RESET);
         
      SPI_I2S_SendData(SPI2, byte);
          
      while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) == RESET);
          
      return SPI_I2S_ReceiveData(SPI2);
}

In the http part of the web, the html content is written into the code, and the web page is realized in conjunction with the status code of the http protocol

#ifndef __WEBPAGE_H
#define __WEBPAGE_H
#define INDEX_HTML  "<!DOCTYPE html>"
"<html>"
"<head>"
"<title>Ç峿µÄÍøÒ³ÅäÖÃ</title>"
"<meta http-equiv='Content-Type' content='text/html; charset=GB2312'/>"
"<style type='text/css'>"
"body {text-align:left; background-color:#c0deed;font-family:Verdana;}"
"#main {margin-right:auto;margin-left:auto;margin-top:30px;}"
"label{display:inline-block;width:150px;}"
"#main h3{color:#66b3ff; text-decoration:underline;}"
"</style>"
"<script>"
"function $(id) { return document.getElementById(id); };"
"function settingsCallback(o) {"
"if ($('txtVer')) $('txtVer').value = o.ver;"
"if ($('txtMac')) $('txtMac').value = o.mac;"
"if ($('txtIp')) $('txtIp').value = o.ip;"
"if ($('txtSub')) $('txtSub').value = o.sub;"
"if ($('txtGw')) $('txtGw').value = o.gw;"

"if ($('txtCode')) $('txtCode').value = o.Code;"
"if ($('txtDTMB_Freq')) $('txtDTMB_Freq').value = o.DTMB_Freq;"
"if ($('txtFM_Freq')) $('txtFM_Freq').value = o.FM_Freq;"
"if ($('txtSx_Freq')) $('txtSx_Freq').value = o.Sx_Freq;"

"};"
"</script>"
"</head>"
"<body>"
"<div id='main'>"
"<div style='background:snow; display:block;padding:10px 20px;'>"
"<h3 align='center'>²é¿´ÍøÂç²ÎÊý</h3>"
"<form id='frmSetting' method='POST' action='config.cgi'>"
"<p><label for='txtIp'>¹Ì¼þ°æ±¾ºÅ:</label><input type='text' id='txtVer' name='ver' size='16' disabled='disabled' /></p>"
"<p><label for='txtIp'>MACµØÖ·:</label><input type='text' id='txtMac' name='mac' size='16' disabled='disabled' /></p>"
"<p><label for='txtIp'>IPµØÖ·:</label><input type='text' id='txtIp' name='ip' size='16' disabled='disabled'/></p>"
"<p><label for='txtSub'>×ÓÍøÑÚÂë:</label><input type='text' id='txtSub' name='sub' size='16' disabled='disabled'/></p>"
"<p><label for='txtGw'>ĬÈÏÍø¹Ø:</label><input type='text' id='txtGw' name='gw' size='16' disabled='disabled'/></p>"

"<p><label for='txtCode'>ÐÐÕþ±àÂë:</label><input type='text' id='txtCode' name='Code' size='16' disabled='disabled'/></p>"
"<p><label for='txtDTMB_Freq'>DTMBƵÂÊ:</label><input type='text' id='txtDTMB_Freq' name='DTMB_Freq' size='16' disabled='disabled' /></p>"
"<p><label for='txtFM_Freq'>µ÷ƵƵÂÊ:</label><input type='text' id='txtFM_Freq' name='FM_Freq' size='16' disabled='disabled'/></p>"
"<p><label for='txtSx_Freq'>ÎÞÏßƵÂÊ:</label><input type='text' id='txtSx_Freq' name='Sx_Freq' size='16' disabled='disabled' /></p>"
"<p style='color:DarkBlue'><label for='txtPassword'>µÇ¼ÃÜÂë6λ:</label><input type='password' id='txtPassword' name='Password' size='16' enabled='enabled'/></p>"
"<input type='submit' value=' µÇ  ¼ ' /></p>"

"</form>"
"</div>"
"</div>"
"<div style='margin:5px 5px;'>"
"&copy;Copyright 2017 by Ç峿"
"</div>"
"<script type='text/javascript' src='w5500.js'></script>"
"</body>"
"</html>"
​
#endif
/* HTML Doc. for ERROR */
#define ERROR_HTML_PAGE "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: 78rnrn<HTML>rn<BODY>rnSorry, the page you requested was not found.rn</BODY>rn</HTML>rn"
//static char  ERROR_HTML_PAGE[] = "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: 78rnrn<HTML>rn<BODY>rnSorry, the page you requested was not found.rn</BODY>rn</HTML>rn";
​
#define ERROR_REQUEST_PAGE "HTTP/1.1 400 OKrnContent-Type: text/htmlrnContent-Length: 50rnrn<HTML>rn<BODY>rnInvalid request.rn</BODY>rn</HTML>rn"
//static char ERROR_REQUEST_PAGE[] = "HTTP/1.1 400 OKrnContent-Type: text/htmlrnContent-Length: 50rnrn<HTML>rn<BODY>rnInvalid request.rn</BODY>rn</HTML>rn";
​
#define RETURN_CGI_PAGE "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: 59rnrn<html><head><title>iWeb - Configuration</title></head><BODY>CGI command was executed.</BODY></HTML>"
​
​
/* Response header for HTML*/
#define RES_HTMLHEAD_OK "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: "
//static PROGMEM char RES_HTMLHEAD_OK[] = "HTTP/1.1 200 OKrnContent-Type: text/htmlrnContent-Length: ";
/* Response head for TEXT */
#define RES_TEXTHEAD_OK "HTTP/1.1 200 OKrnContent-Type: text/plainrnContent-Length: "
​
/* Response head for GIF */
#define RES_GIFHEAD_OK  "HTTP/1.1 200 OKrnContent-Type: image/gifrnContent-Length: "
​
/* Response head for JPEG */
#define RES_JPEGHEAD_OK "HTTP/1.1 200 OKrnContent-Type: image/jpegrnContent-Length: "       
​
/* Response head for FLASH */
#define RES_FLASHHEAD_OK "HTTP/1.1 200 OKrnContent-Type: application/x-shockwave-flashrnContent-Length: "
//static PROGMEM char RES_FLASHHEAD_OK[] = "HTTP/1.1 200 OKrnContent-Type: application/x-shockwave-flashrnContent-Length: ";
​
/* Response head for MPEG */
#define RES_MPEGHEAD_OK "HTTP/1.1 200 OKrnContent-Type: video/mpegrnContent-Length: "   
​
/* Response head for PDF */
#define RES_PDFHEAD_OK "HTTP/1.1 200 OKrnContent-Type: application/pdfrnContent-Length: "
​
//digital I/O out put control result response
#define DOUT_RES_1  "HTTP/1.1 200 OKrnContent-Type: text/plainrnContent-Length: 1rnrn1"
#define DOUT_RES_0  "HTTP/1.1 200 OKrnContent-Type: text/plainrnContent-Length: 1rnrn0"

4.3 experimental results

5. Reference

[1] Get dynamic IP address from router

[2] DHCP

[3] STM32 porting FreeModbus detailed process

[4] Modbus communication protocol (II) – RTU

[5] web service made by STM32F103+W5500

Tags: Embedded system

Posted by unistake on Wed, 29 Dec 2021 11:03:04 +1030

COMMENTS

Please Login to comment
  Subscribe  
Notify of
POSTED BY
TAGS
Reusable S/W