W5500 Interrupt

Just like other microcontrollers, W5500 has its own pins for interrupt. Using interrupt instead of polling with W5500 allows users to free the socket from having it connected continuously, even there would be no data received for a while. In other words, you can use interrupt to achieve many more sockets than W5500 has (Max 8 sockets for W5500).




Before burning the bin file into your EVB:

      1. Connect W5500 EVB to the router/NAT that you are using, or any other device that allows you to connect to the internet
      2. Manually change the Network configuration:
          • Go to the definition of function “set_default()” (Should be inside device.c), make sure the subnet mask, local IP address, gateway and DNS are set according to the internet setting you are using with your router/NAT
      3. Rebuild the sample code and burn it into W5500EVB
      4. Change your PC’s IP address to a static IP if you are not using router


Code Explanation

This is a fundamental example of using W5500 Interrupt. In this sample code, W5500EVB would first run as an DHCP Client to get an IP address from the server. For more information about DHCP Client please refer to the related tutorial page. Then, it starts running as TCP Server in the way of interrupt.

Throughout the sample, please bear in mind that the whole process would be dominated by the interrupt pins. Please pay much attention about when and what the value of interrupts pins would be so as not to get lost during the reading.

Firstly, W5500 initializes necessary configuration and runs as a DHCP Client for IP address.
Please notice that the initializations are more than usual, which has been commented for elaboration.

int main()
{
  uint8 dhcpret=0;
  uint8 tmp_array[4] = {0};
  RCC_Configuration();
  GPIO_Configuration();
  NVIC_Configuration();
  
  Systick_Init(72);
  USART1_Init();
  at24c16_init();
  printf("W5500 EVB initialization over.\r\n");
  
  Reset_W5500();
  WIZ_SPI_Init();
  printf("W5500 initialized!\r\n");  
  
  set_default(); 
  init_dhcp_client();
	
  setRTR(2000);                      // Setting retransmission time
  setRCR(5);                         // Setting number of retransmission counter
  IINCHIP_WRITE(Sn_MR(7), 0x20);     // *****should it be turn on no delayed ACK?
  IINCHIP_WRITE(Sn_IMR(7), 0x0F);    // Enable alll functions of interrupt of Socket(7) 

  IINCHIP_WRITE(IMR, 0xF0);          // Configuration for Interrupt Difference
  IINCHIP_WRITE(SIMR, 0xFE);         // Enable interrupt of all socket except Sn(0), which is reserved for DHCP
  while(1)
  {
    dhcpret = check_DHCP_state(SOCK_DHCP);
    switch(dhcpret)
    {
      case DHCP_RET_NONE:
        break;
      case DHCP_RET_TIMEOUT:
        break;
      case DHCP_RET_UPDATE:
	  	Set_network();
	    printf("DHCP OK!\r\n");
		C_Flag = 1;
        break;
      case DHCP_RET_CONFLICT:
	    C_Flag = 0;
		printf("DHCP Fail!\r\n");
		dhcp_state = STATE_DHCP_READY;
		break; 
      default:
        break;
    }


Then it starts looping the function of TCP Server working with interrupt. You can see there are two same loopback functions with different parameters. You are suggested to kindly test the difference in result by connecting them and sending messages respectively. Please be reminded that some initializations about Socket 7 were done, while none for Socket 6.

    if(C_Flag == 1)
    {
      getSIPR(tmp_array);    // Get IP address of the source, not quite applicable in this tutorial
      loopback_tcps(7, 8080);
      loopback_tcps(6, 8060);
    }
  }
}


loopback_tcps()

For the loopback function, it would be explained separately for better understanding. There are five parts in total. If you find it difficult to understand how the variable works, you are suggested to first study the interrupt part.

First, if the Socket is closed, it opens the socket and goes to listen state. Please notice that the variable “ch_status[s]” indicates whether the socket is closed, opened or connected.

void loopback_tcps(SOCKET s, uint16 port)
{	
  uint16 len = 0;
  uint8 * data_buf = (uint8*) TX_BUF;
  uint8 tmp = 0;

  if(I_STATUS[s] == SOCK_CLOSED) // close
  {
    if(!ch_status[s]) 
    {
      ch_status[s] = 1;
      if(socket(s,Sn_MR_TCP,port,0x00) == 0)	  /* reinitialize the socket */
        {
          ch_status[s] = 0;
        }
      else	
      {
        listen(s);
      }
    }
  } 


Then, if the socket is connected, “ch_status[s]” changes. It also processes the “I_STATUS[s]” to make sure that the interrupt pins are set correctly.

  if(I_STATUS[s]&Sn_IR_CON) // connected
  {
    ch_status[s] = 2;
    I_STATUS[s] &= ~(0x01);
    //break;
  }


After that, if the interrupt pins change to disconnected, it indicates that a packet is well received. Then it retrieves the packet from W5500 and stores it in the STM32 microcontroller.

Then it disconnects the socket immediately. This is the main difference between using polling and using interrupt of W5500.

  if(I_STATUS[s]&Sn_IR_DISCON)  // discon
  {
    if ((len = getSn_RX_RSR(s)) > 0)    /* check Rx data */
    {
      if (len > TX_RX_MAX_BUF_SIZE) 
      len = TX_RX_MAX_BUF_SIZE;     /* if Rx data size is lager than TX_RX_MAX_BUF_SIZE */
      recv(s, data_buf, len);		/* read the received data */
      data_buf[len]=0x00;
      printf("%s\r\n",data_buf);
    }
    SOCK_DISCON(s);
  }


Once a packet is received, to prevent the register from being erased, W5500 disables the ISR. One may be confused about why the ISR should be disabled when the packet has already been stored in STM32 Microcontroller: Since this is a loopback function, the packet is kept for a while more for sending a loopback packet back to the client.

  if(I_STATUS[s]&Sn_IR_RECV)
  {
    IINCHIP_WRITE(IMR, 0x00);  // Disable W5500 Interrupt
    IINCHIP_WRITE(SIMR, 0x00); // Disable W5500 Interrupt
    IINCHIP_ISR_DISABLE();
    tmp = I_STATUS[s];
    I_STATUS[s] &= ~(0x04); // RECV
    IINCHIP_ISR_ENABLE();
 
    IINCHIP_WRITE(IMR, 0xF0);
    IINCHIP_WRITE(SIMR, 0xFF);
    // recv
    if (tmp & Sn_IR_RECV)
    {
      if((len = getSn_RX_RSR(s)) > 0)	/* check Rx data */
      {
        if (len > TX_RX_MAX_BUF_SIZE) 
        len = TX_RX_MAX_BUF_SIZE;	    /* if Rx data size is lager than TX_RX_MAX_BUF_SIZE */
        len = recv(s, data_buf, len);   /* read the received data */
        data_buf[len]=0;
        printf("%s\r\n",data_buf);
        send(s,data_buf,len);
        tmp = I_STATUS[s];
      }
    }
    //break;
  }


Setting “I_STATUS[s]”  to “Sn_IR_SEND_OK” means the data transmission completes.

  if(I_STATUS[s]&Sn_IR_SEND_OK)  // send ok
  {
    I_STATUS[s] &= ~(0x10);	 //break;
  }

#if (__MCU_TYPE__ == __MCU_STM32F103__)
    /**/
    if(GPIO_ReadInputDataBit(GPIOC, GPIO_Pin_6) == 0x00) EXTI_GenerateSWInterrupt(EXTI_Line6);
#endif

}


Interrupt

To make use of the Interrupt pin of W5500, you should also make use of the interrupt pins of your MCU. By using two sets of interrupt pins, doing data transmission can be easily achieved.

The following is the code for a normal IRQHandler of MCU. Please be reminded that initialization of External Interrupt has already been done previously.

void EXTI9_5_IRQHandler(void)
{
  if(EXTI_GetITStatus(EXTI_Line6) != RESET)
  {
    Int_numbers++;
    printf("%d\r\n",Int_numbers);
    ISR();  // Interrupt Service Routine of W5500 Interrupt
    EXTI_ClearITPendingBit(EXTI_Line6);  
  }                  
}


Within the IRQHandler, there is only one additional line of code, “ISR()”, which is the function manipulating our W5500 interrupt pins.

First it disables the interrupt pins and Interrupt Service Routine for fear that a new interrupt packet would be received and would overwrite the earlier received packet. Then it stores the value of the registers for later use.

void ISR(void)
{
  uint8 IR_val = 0, SIR_val = 0;
  uint8 tmp,s;
	
  IINCHIP_WRITE(IMR, 0x00);
  IINCHIP_WRITE(SIMR, 0x00);
  IINCHIP_ISR_DISABLE();

  IR_val = IINCHIP_READ(IR);
  SIR_val = IINCHIP_READ(SIR);

  printf("\r\n First Sn_IR(%u) 0x%.2x\r\n",s,Sn_IR(s));
  printf("IR_val is 0x%.2x ,  SIR_val is 0x%.2x\r\n",IR_val,SIR_val);

/** The following are no applicable to this tutorial, for more **/
/** details please refer to "Remark"                           **/
  if (IR_val > 0) {
    if (IR_val & IR_CONFLICT)
    {
    }
    if (IR_val & IR_MAGIC)
    {
    }
    if (IR_val & IR_PPPoE)
    {
    }
    if (IR_val & IR_UNREACH)
    {
    }
   //Clear the Interrupt Pins, please go to Remark for detail
    IINCHIP_WRITE(IR, IR_val); 

  /******************************************************************/
  }


Then it loops eight times, from Socket 0 to Socket 7, and saves the interrupt value if there is any. Please be noted that, the variable “I_STATUS[s]” mentioned before, which plays an important role throughout the loopback function, would be adjusted here based on the interrupt pin.

Finally it enables the interrupt pins and Interrupt Service routine for the next packet.

  for(s = 0;s < 8;s ++)
  {
    tmp = 0;
    if (SIR_val & IR_SOCK(s))
    {
      /* save interrupt value*/
      tmp = IINCHIP_READ(Sn_IR(s));
      I_STATUS[s] |= tmp;
      tmp &= 0x0F; 

      // Clear the Socket Interrupt Pins, please go to Remark for detail
      IINCHIP_WRITE(Sn_IR(s), tmp); 
    }
  }
  IINCHIP_ISR_ENABLE();
  IINCHIP_WRITE(IMR, 0xF0);
  IINCHIP_WRITE(SIMR, 0xFF); 
}




Remarks

  • There are four registers that you may get confused with: IR, IMR, SIMR, Sn_IR(s):
    • IR: Interrupt Register
    • IMR: Interrupt Mask Register
    • SIMR: Socket Interrupt Mask Register
    • Sn_IR(n): Socket n Interrupt Register, showing the state of interrupt of respective Socket
  • There are two functions working as clearing pins, please refer to datasheet v109e Eng P.48 for more details:
    • IINCHIP_WRITE(IR, IR_val); 
    • IINCHIP_WRITE(Sn_IR(s), tmp);
  • It is always suggested to use polling over interrupt, despite the convenience of the sockets not needing to continuously be connected. It is because…..



Download