[STM32 HAL] WIZNET W5100(W5500) + TCP CLIENT

[STM32 HAL] WIZNET W5100(W5500) + TCP CLIENT
components
Hardware Components
STM32F4-DISCOVERY
X 1
Arduino Ethernet Shield
X 1
Software Apps and online services
STM32CubeIDE 1.3.1
details

image.png

[Environments]

개발 환경은 다음과 같이 사용하였습니다.

– Board : STM32F4-DISCOVERY

– W5100 : Arduino Ethernet Shield

– STM32CubeMx 5.6.1

– STM32CubeIDE 1.3.1

[Ethernet Shield Pinout]

아래 schematic 과 같이 SPI 관련 핀들과 전원이 ICSP 커넥터쪽으로 연결되어 있기 때문에 해당 커넥터를 사용해야 합니다.

[STM32CubeMx]

기존에는 LwIP 방식으로 TCP 어플리케이션을 구현하였지만 이번에는 외부 TCPIP 칩을 사용하기 때문에 STM32F4-DISCOVERY 보드를 사용하였습니다.

1) 전체 Pinout 은 다음과 같습니다.

2) RCC 항목에서 Clock 소스를 설정하여 줍니다.

3) SYS 항목에서 Debug 와 Timebase 소스를 설정하여 줍니다. FreeRTOS 를 사용하기 때문에 Timebase Source 를 TIM6 로 변경하여 줍니다.

4) SPI1 항목을 Full-Duplex Master 로 설정하여 줍니다.

W5100 은 데이터시트에 70ns 로 적혀있는데 대략 13Mbps 정도 되니,

Prescaler 값은 8로 설정하여 Baudrate 는 10.5Mbps 로 설정합니다.

5) FREERTOS 항목을 아래와 같이 설정하여 줍니다.

Default 설정값에서 FPU, stack 사이즈, heap 사이즈, stack overflow check 등을 수정하였습니다.

Task 는 Default Task 만 정의하고 Tcp Client Task 는 Default Task 에서 직접 생성하는 방식으로 작업합니다.

6) Clock Configuration 으로 이동하여 Clock 은 168MHz max 클럭으로 동작하도록 설정합니다.

7) Project Manager 탭에서 프로젝트 명칭 및 code generator 옵션를 설정하여 준 후 프로젝트를 생성합니다.

[Project 구성]

프로젝트를 변경하는 지점은 크게 4가지 입니다.

1) ioLibrary 추가

References 를 참고하여 WIZNET ioLibrary 를 다운로드 받은 후 /Middlewares/Third_Party 폴더에 아래와 같이 추가하여 줍니다.

2) include path 설정

GCC 설정에서 아래와 같이 Ethernet, W5100, DHCP 폴더의 path 를 include paths 에 추가하여 줍니다.

3) wizInterface.c/h 파일 추가

wizinterface.c/h 파일은 W5100 의 SPI 통신, 초기화, 설정과 관련된 함수들을 위한 파일입니다.

세부 내용은 아래 Sources 항목에서 설명하겠습니다.

4) tcpclient.c/h 파일 추가

tcpclient.c/h 파일은 FreeRTOS 에서 동작하는 Tcp Client Task 를 위한 파일입니다.

세부 내용은 아래 Sources 항목에서 설명하겠습니다.

[Sources]

코드 작업이 필요한 파일은, main.c | freertos.c | wizinterface.c/h | tcpclient.c/h 입니다.

1) main.c

main.c 에서 필요한 작업은 아래와 같습니다.

– SWV ITM 출력을 위한 _write() 함수 재정의

– FreeRTOS 에서 printf exception 발생을 막기위한 코드 추가

– ioLibrary DHCP 동작을 위한 DHCP_time_handler() 관련 코드 추가

/* Includes ——————————————————————*/ #include “main.h” #include “cmsis_os.h” #include “spi.h” #include “gpio.h” /* Private includes ———————————————————-*/ /* USER CODE BEGIN Includes */ #include <stdio.h> #include “dhcp.h” /* USER CODE END Includes */ /* Private typedef ———————————————————–*/ /* USER CODE BEGIN PTD */ /* USER CODE END PTD */ /* Private define ————————————————————*/ /* USER CODE BEGIN PD */ /* USER CODE END PD */ /* Private macro ————————————————————-*/ /* USER CODE BEGIN PM */ /* USER CODE END PM */ /* Private variables ———————————————————*/ /* USER CODE BEGIN PV */ static uint16_t wizDHCPticks = 0; /* USER CODE END PV */ /* Private function prototypes ———————————————–*/ void SystemClock_Config(void); void MX_FREERTOS_Init(void); /* USER CODE BEGIN PFP */ /* USER CODE END PFP */ /* Private user code ———————————————————*/ /* USER CODE BEGIN 0 */ int _write(int file, char *ptr, int len) { int DataIdx; for (DataIdx = 0; DataIdx < len; DataIdx++) { ITM_SendChar(*ptr++); } return len; } /* USER CODE END 0 */ /** * @brief The application entry point. * @retval int */ int main(void) { /* USER CODE BEGIN 1 */ /* USER CODE END 1 */ /* MCU Configuration——————————————————–*/ /* Reset of all peripherals, Initializes the Flash interface and the Systick. */ HAL_Init(); /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* Configure the system clock */ SystemClock_Config(); /* USER CODE BEGIN SysInit */ /* USER CODE END SysInit */ /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_SPI1_Init(); /* USER CODE BEGIN 2 */ printf(“Don’t remove this printf to prevent hard fault.rn”); /* USER CODE END 2 */ /* Call init function for freertos objects (in freertos.c) */ MX_FREERTOS_Init(); /* Start scheduler */ osKernelStart(); /* We should never get here as control is now taken by the scheduler */ /* Infinite loop */ /* USER CODE BEGIN WHILE */ while (1) { /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ } /* USER CODE END 3 */ }
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { /* USER CODE BEGIN Callback 0 */ /* USER CODE END Callback 0 */ if (htim>Instance == TIM6) { HAL_IncTick(); } /* USER CODE BEGIN Callback 1 */ if(htim>Instance == TIM6) { wizDHCPticks++; if(wizDHCPticks >= 1000) { wizDHCPticks = 0; DHCP_time_handler(); } } /* USER CODE END Callback 1 */ }

2) freertos.c

freertos.c 파일에서 작업이 필요한 내용은 다음과 같습니다.

– vApplicationStackOverflowHook 함수에 stack overflow 발생 시, LED 제어

– StartDefaultTask 함수에서 wizTcpClientTask 수행

/* USER CODE BEGIN 4 */ __weak void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName) { /* Run time stack overflow checking is performed if configCHECK_FOR_STACK_OVERFLOW is defined to 1 or 2. This hook function is called if a stack overflow is detected. */ HAL_GPIO_WritePin(LED_ORANGE_GPIO_Port, LED_ORANGE_Pin, GPIO_PIN_SET); //turn on orange led when system detects stack overflow } /* USER CODE END 4 */
/* USER CODE END Header_StartDefaultTask */ void StartDefaultTask(void const * argument) { /* USER CODE BEGIN StartDefaultTask */ osThreadDef(wizTcpClientTask, StartWizTcpClientTask, osPriorityNormal, 0, 512); wizTcpClientTaskHandle = osThreadCreate(osThread(wizTcpClientTask), NULL); /* Infinite loop */ for(;;) { HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin); osDelay(500); } /* USER CODE END StartDefaultTask */ }

3) wizinterface.c/h

wizinterface.c 파일에서는 다음과 같은 작업을 수행합니다.

– W5100 과 SPI 통신을 위한 함수 정의 (WIZ_SPI_XXX 함수들)

– DHCP 관련 callback 함수 정의 (cbIPAddrAssigned, cbIPAddrConflic)

– W5100 초기화 함수 정의 (WIZ_ChipInit)

– W5100 네트워크 설정 함수 정의 (WIZ_NetworkInit)

/* * wizInterface.c * * Created on: 2020. 5. 31. * Author: eziya76@gmail.com */ #include “wizInterface.h” #include “FreeRTOS.h” #include “cmsis_os.h” extern SPI_HandleTypeDef hspi1; #define WIZ_SPI_HANDLE &hspi1 static bool ip_assigned = 0; static uint8_t buff_size[] = { 2, 2, 2, 2 }; #ifdef USE_DHCP static uint8_t dhcp_buffer[1024]; static uint16_t dhcp_retry = 0; #endif //network information wiz_NetInfo netInfo = { .mac = { 0x00, 0x08, 0xdc, 0xab, 0xcd, 0xef }, .ip = { 192, 168, 1, 180 }, .sn = { 255, 255, 255, 0 }, .gw = { 192, 168, 1, 1 } }; wiz_NetTimeout timeout = { .retry_cnt = 3, //RCR = 3 .time_100us = 5000}; //500ms void WIZ_SPI_Select(void) { HAL_GPIO_WritePin(WIZ_SPI1_CS_GPIO_Port, WIZ_SPI1_CS_Pin, GPIO_PIN_RESET); } void WIZ_SPI_Deselect(void) { HAL_GPIO_WritePin(WIZ_SPI1_CS_GPIO_Port, WIZ_SPI1_CS_Pin, GPIO_PIN_SET); } void WIZ_SPI_TxByte(uint8_t byte) { HAL_SPI_Transmit(WIZ_SPI_HANDLE, &byte, 1, HAL_MAX_DELAY); } uint8_t WIZ_SPI_RxByte(void) { uint8_t ret; HAL_SPI_Receive(WIZ_SPI_HANDLE, &ret, 1, HAL_MAX_DELAY); return ret; } void WIZ_SPI_TxBuffer(uint8_t *buffer, uint16_t len) { HAL_SPI_Transmit(WIZ_SPI_HANDLE, buffer, len, HAL_MAX_DELAY); } void WIZ_SPI_RxBuffer(uint8_t *buffer, uint16_t len) { HAL_SPI_Receive(WIZ_SPI_HANDLE, buffer, len, HAL_MAX_DELAY); } //dhcp callbacks static void cbIPAddrAssigned(void) { printf(“IP Address is assigned.n”); ip_assigned = true; } static void cbIPAddrConfict(void) { printf(“IP Address is conflicted.n”); ip_assigned = false; } bool WIZ_ChipInit(void) { int32_t ret; uint8_t tmpstr[6] = { 0, }; //power reset arduino ethernet shield HAL_GPIO_WritePin(WIZ_RESET_GPIO_Port, WIZ_RESET_Pin, GPIO_PIN_RESET); osDelay(500); HAL_GPIO_WritePin(WIZ_RESET_GPIO_Port, WIZ_RESET_Pin, GPIO_PIN_SET); osDelay(500); #if (_WIZCHIP_ == W5100) //register spi functions reg_wizchip_cs_cbfunc(WIZ_SPI_Select, WIZ_SPI_Deselect); reg_wizchip_spi_cbfunc(WIZ_SPI_RxByte, WIZ_SPI_TxByte); //set rx,tx buffer sizes ret = wizchip_init(buff_size, buff_size); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“wozchip_init failed.n”); return false; } #elif (_WIZCHIP_ == W5500) //register spi functions reg_wizchip_cs_cbfunc(WIZ_SPI_Select, WIZ_SPI_Deselect); reg_wizchip_spi_cbfunc(WIZ_SPI_RxByte, WIZ_SPI_TxByte); reg_wizchip_spiburst_cbfunc(WIZ_SPI_RxBuffer, WIZ_SPI_TxBuffer); //check version register uint8_t version = getVERSIONR(); if(version != 0x04) { printf(“getVERSIONR returns wrong version!n”); return false; } //check PHY status wiz_PhyConf phyConf; wizphy_getphystat(&phyConf); printf(“PHY conf.by = {%d}, conf.mode={%d}, conf.speed={%d}, conf.duplex={%d}n”, phyConf.by, phyConf.mode, phyConf.speed, phyConf.duplex); #endif return true; } bool WIZ_NetworkInit(void) { wiz_NetInfo tmpInfo; wiz_NetTimeout tmpTimeout; #ifdef USE_DHCP setSHAR(netInfo.mac); //set MAC address DHCP_init(DHCP_SOCKET, dhcp_buffer); //init DHCP reg_dhcp_cbfunc(cbIPAddrAssigned, cbIPAddrAssigned, cbIPAddrConfict); //register DHCP callbacks //get ip from dhcp server dhcp_retry = 0; while (!ip_assigned && dhcp_retry < 100000) { dhcp_retry++; DHCP_run(); } //if dhcp assigned an ip address. if (ip_assigned) { getIPfromDHCP(netInfo.ip); getGWfromDHCP(netInfo.gw); getSNfromDHCP(netInfo.sn); } #endif //set network information wizchip_setnetinfo(&netInfo); //get network information wizchip_getnetinfo(&tmpInfo); printf(“IP: %03d.%03d.%03d.%03dnGW: %03d.%03d.%03d.%03dnNet: %03d.%03d.%03d.%03dn”, tmpInfo.ip[0], tmpInfo.ip[1],tmpInfo.ip[2], tmpInfo.ip[3], tmpInfo.gw[0], tmpInfo.gw[1], tmpInfo.gw[2], tmpInfo.gw[3], tmpInfo.sn[0], tmpInfo.sn[1], tmpInfo.sn[2], tmpInfo.sn[3]); if(tmpInfo.mac[0] != netInfo.mac[0] || tmpInfo.mac[1] != netInfo.mac[1] || tmpInfo.mac[2] != netInfo.mac[2] || tmpInfo.mac[3] != netInfo.mac[3]) { printf(“wizchip_getnetinfo failed.n”); return false; } //set timeout ctlnetwork(CN_SET_TIMEOUT,(void*)&timeout); ctlnetwork(CN_GET_TIMEOUT, (void*)&tmpTimeout); if(tmpTimeout.retry_cnt != timeout.retry_cnt || tmpTimeout.time_100us != timeout.time_100us) { printf(“ctlnetwork(CN_SET_TIMEOUT) failed.n”); return false; } return true; }

4) tcpclient.c/h

tcpclient.c 파일의 StartWizTcpClientTask 에서는 다음과 같은 작업을 수행합니다.

– WIZ_ChipInit 호출하여 초기화

– WIZ_NetworkInit 호출하여 네트워크 설정 (IP 설정 또는 DHCP 를 통한 IP 할당)

– socket API 를 호출하여 소켓 생성

– connect API 를 호출하여 서버 접속

– send API 를 호출하여 REQ 전송

– recv API 를 호출하여 RESP 수신

– 수신된 데이터를 체크

– close API 를 호출하여 소켓 close

– 이후 동작은 10ms delay 를 갖고 소켓 생성부터 반복

/* * tcpclient.c * * Created on: 2020. 5. 31. * Author: eziya76@gmail.com */ #include “FreeRTOS.h” #include “task.h” #include “main.h” #include “cmsis_os.h” #include “tcpclient.h” #include “wizInterface.h” #include <stdio.h> #define SERVER_IP1 192 //server ip #define SERVER_IP2 168 #define SERVER_IP3 1 #define SERVER_IP4 227 #define SERVER_PORT 1234 //server port static uint8_t serverIP[] = { SERVER_IP1, SERVER_IP2, SERVER_IP3, SERVER_IP4 }; static uint16_t localPort = 50000; //tcp client task void StartWizTcpClientTask(void const *argument) { if(!WIZ_ChipInit()) { printf(“WIZ_ChipInit failed.n”); vTaskDelete(NULL); //clear task } if(!WIZ_NetworkInit()) { printf(“WIZ_NetworkInit failed.n”); vTaskDelete(NULL); //clear task } while (1) { int32_t ret; //create socket ret = socket(CLIENT_SOCKET, Sn_MR_TCP, localPort++, SF_TCP_NODELAY); if(localPort == 0xFFFF) { localPort = 50000; } if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“socket failed{%ld}.n”, ret); close(CLIENT_SOCKET); osDelay(500); continue; } while(getSn_SR(CLIENT_SOCKET) != SOCK_INIT); //connect to the server ret = connect(CLIENT_SOCKET, serverIP, SERVER_PORT); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“connect failed{%ld}.Sn_SR()={%d}n”, ret, getSn_SR(CLIENT_SOCKET)); close(CLIENT_SOCKET); osDelay(500); continue; } //prepare data to send & receive struct time_packet packet; memset(&packet, 0, sizeof(struct time_packet)); packet.head = 0xAE; //head packet.type = REQ; //request type packet.tail = 0xEA; //tail uint8_t failed = 0; int16_t written = 0; int16_t read = 0; //send request ret = send(CLIENT_SOCKET, (uint8_t*) (&packet + written), sizeof(struct time_packet) written); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“send failed{%ld}.n”, ret); close(CLIENT_SOCKET); //unexpected close continue; } //receive response while (1) { ret = recv(CLIENT_SOCKET, (uint8_t*) (&packet + read), sizeof(struct time_packet) read); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“recv failed.{%ld}n”, ret); close(CLIENT_SOCKET); //unexpected close failed = 1; break; } read += ret; if (read >= sizeof(struct time_packet)) //overflow break; } if (failed) continue; //start again //if received length is valid, print time information & toggle led if (read == sizeof(struct time_packet) && packet.type == RESP) { printf(“%04d-%02d-%02d %02d:%02d:%02dn”, packet.year + 2000, packet.month, packet.day, packet.hour, packet.minute, packet.second); HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin); } close(CLIENT_SOCKET); //close socket osDelay(10); } vTaskDelete(NULL); //clear task }

“설명에 앞서 제가 수정한 방법은 제가 겪은 오류만을 회피할 뿐 다른 예상치 못한 악영향을 미칠 수도 있습니다. “

테스트를 하면서 앞서 언급한 것과 같이 매우 빠른 주기로 socket-connect-send-recv-close 를 반복하면 빈번하게 connect 시에 -4(SOCKERR_SOCKCLOSED) 를 반환하면서 접속에 실패하고 Sn_SR 값이 0x10 으로 정의되지 않은 값을 반환하는 문제가 있었습니다.

우선 문제가 되는 부분은 접속이 실패하는 것은 괜찮은데 이후 에러 처리 시 소켓을 정리하기 위한 close 함수 호출 시 무한 loop 에 걸리면서 task 가 중지하는 것이 문제 였습니다.

1) close()

아래 코드를 보면,

while(get_Sn_SR(sn) != SOCK_CLOSED);

는 SOCK_CLOSED 가 되지 않으면 무한 대기를 하기 되어 있습니다. 그런데 제가 확인한 현상은

close() 함수에서 setSn_CR(sn,Sn_CR_CLOSE) 을 이용해서 CLOSE 명령을 전달한 후에 Sn_SR 상태가 SOCK_ESTABLISHED 로 변경되면서 무한 루프에 빠지는 현상이었습니다.

이런 케이스가 발생하는 것을 우회하기 위해서 Sn_SR 상태가 SOCK_CLOSED 가 아니라면 무한 대기 하는 것이 아니라 다시 CLOSE 명령을 전송하도록 변경하였습니다.

int8_t close(uint8_t sn) { CHECK_SOCKNUM(); setSn_CR(sn,Sn_CR_CLOSE); /* wait to process the command… */ while( getSn_CR(sn) ); /* clear all interrupt of the socket. */ setSn_IR(sn, 0xFF); //A20150401 : Release the sock_io_mode of socket n. sock_io_mode &= ~(1<<sn); // sock_is_sending &= ~(1<<sn); sock_remained_size[sn] = 0; sock_pack_info[sn] = 0; while(getSn_SR(sn) != SOCK_CLOSED) { //2020.06.08 eziya, getSn_SR sometimes returns SOCK_ESTABLISHED setSn_CR(sn,Sn_CR_CLOSE); while( getSn_CR(sn) ); } return SOCK_OK; }

2) connect()

앞서 close 시에 Sn_SR 의 상태가 SOCK_ESTABLISHED 가 된다고 말씀을 드렸는데 그 원인이 저는 connect 에 있다고 보여집니다.

실제 connect 함수가 호출 후에 SOCK_ESTABLISHED 로 상태가 변경되는데 필요한 시간이 길어질 때까 있는데 이 경우에 대한 예외 코드가 되어 있지 않아서 그대로 Error 를 반환해 버리는데 이때 connect 가 에러를 주었기 때문에 socket 을 close 하려고 하면 앞선 close 의 문제가 발생하는 것입니다.

이 문제를 우회하기 위해서는 connect 시에 상태가 SOCK_ESTABLISHED 가 되지 않았더라도 바로 에러를 반환하는 것이 아니라 몇차례 retry 를 하도록 수정하였습니다.

nt8_t connect(uint8_t sn, uint8_t * addr, uint16_t port) { CHECK_SOCKNUM(); CHECK_SOCKMODE(Sn_MR_TCP); CHECK_SOCKINIT(); //M20140501 : For avoiding fatal error on memory align mismatched //if( *((uint32_t*)addr) == 0xFFFFFFFF || *((uint32_t*)addr) == 0) return SOCKERR_IPINVALID; { uint32_t taddr; taddr = ((uint32_t)addr[0] & 0x000000FF); taddr = (taddr << 8) + ((uint32_t)addr[1] & 0x000000FF); taddr = (taddr << 8) + ((uint32_t)addr[2] & 0x000000FF); taddr = (taddr << 8) + ((uint32_t)addr[3] & 0x000000FF); if( taddr == 0xFFFFFFFF || taddr == 0) return SOCKERR_IPINVALID; } // if(port == 0) return SOCKERR_PORTZERO; setSn_DIPR(sn,addr); setSn_DPORT(sn,port); setSn_CR(sn,Sn_CR_CONNECT); while(getSn_CR(sn)); if(sock_io_mode & (1<<sn)) return SOCK_BUSY; while(getSn_SR(sn) != SOCK_ESTABLISHED) { if (getSn_IR(sn) & Sn_IR_TIMEOUT) { setSn_IR(sn, Sn_IR_TIMEOUT); return SOCKERR_TIMEOUT; } //2020.06.09 eziya76, 때때로 Sn_SR 이 SOCK_ESTABLISHED 로 천천히 변하는 현상이 보임 //따라서 CONNECT 요청 후 getSn_SR 이 SOCK_ESTABLISHED 가 아닌 경우에는 여러번 확인하고 CLOSE 처리 uint16_t retry = 0; if (getSn_SR(sn) == SOCK_CLOSED) { retry++; if(retry >= 100) { return SOCKERR_SOCKCLOSED; } } } return SOCK_OK; }

[ 테스트 ]

테스트는 github 에 올려놓은 TcpServer 테스트 어플리케이션을 이용하여 진행하였습니다.

https://github.com/eziya/STM32F4_HAL_LWIP_LAB/tree/master/MyTcpServer

documents
Code
source code

COMMENTS

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