[STM32 HAL] WIZNET W5100(W5500) + ECHO SERVER

WIZNET W5100(W5500) + ECHO SERVER
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]

앞선 TCP Client 예제와 비교할 때 CubeMx 구성에 변경 사항이 없습니다.

[Project 구성]

프로젝트는 다음과 같이 구성되어 있습니다.

1) ioLibrary 추가

github 에서 다운로드 받은 ioLibrary 폴더를 /Middlewares/Third_Party 폴더에 추가하여 줍니다.

저는 Client 개발 시에는 Sn_SR 상태 이상 등으로 수정한 라이브러리를 사용하였습니다.

2) ioLibrary 를 Include Path 에 추가

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

3) wizInterface.c / h 파일을 추가

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

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

4) echoserver.c / h 파일 추가

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

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

[Sources]

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 함수에서 wizEchoServerTask 수행

/* Includes ——————————————————————*/ #include <echoserver.h> #include “FreeRTOS.h” #include “task.h” #include “main.h” #include “cmsis_os.h” /* Private includes ———————————————————-*/ /* USER CODE BEGIN Includes */ /* 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 Variables */ osThreadId wizEchoServerTaskHandle; /* USER CODE END Variables */ osThreadId defaultTaskHandle; /* Private function prototypes ———————————————–*/ /* USER CODE BEGIN FunctionPrototypes */ void StartWizTcpClientTask(void const * argument); /* USER CODE END FunctionPrototypes */ void StartDefaultTask(void const * argument); void MX_FREERTOS_Init(void); /* (MISRA C 2004 rule 8.1) */ /* GetIdleTaskMemory prototype (linked to static allocation support) */ void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize ); /* Hook prototypes */ void vApplicationStackOverflowHook(xTaskHandle xTask, signed char *pcTaskName); /* 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 BEGIN GET_IDLE_TASK_MEMORY */ static StaticTask_t xIdleTaskTCBBuffer; static StackType_t xIdleStack[configMINIMAL_STACK_SIZE]; void vApplicationGetIdleTaskMemory( StaticTask_t **ppxIdleTaskTCBBuffer, StackType_t **ppxIdleTaskStackBuffer, uint32_t *pulIdleTaskStackSize ) { *ppxIdleTaskTCBBuffer = &xIdleTaskTCBBuffer; *ppxIdleTaskStackBuffer = &xIdleStack[0]; *pulIdleTaskStackSize = configMINIMAL_STACK_SIZE; /* place for user code */ } /* USER CODE END GET_IDLE_TASK_MEMORY */ /** * @brief FreeRTOS initialization * @param None * @retval None */ void MX_FREERTOS_Init(void) { /* USER CODE BEGIN Init */ /* USER CODE END Init */ /* USER CODE BEGIN RTOS_MUTEX */ /* add mutexes, … */ /* USER CODE END RTOS_MUTEX */ /* USER CODE BEGIN RTOS_SEMAPHORES */ /* add semaphores, … */ /* USER CODE END RTOS_SEMAPHORES */ /* USER CODE BEGIN RTOS_TIMERS */ /* start timers, add new ones, … */ /* USER CODE END RTOS_TIMERS */ /* USER CODE BEGIN RTOS_QUEUES */ /* add queues, … */ /* USER CODE END RTOS_QUEUES */ /* Create the thread(s) */ /* definition and creation of defaultTask */ osThreadDef(defaultTask, StartDefaultTask, osPriorityNormal, 0, 256); defaultTaskHandle = osThreadCreate(osThread(defaultTask), NULL); /* USER CODE BEGIN RTOS_THREADS */ /* add threads, … */ /* USER CODE END RTOS_THREADS */ } /* USER CODE BEGIN Header_StartDefaultTask */ /** * @brief Function implementing the defaultTask thread. * @param argument: Not used * @retval None */ /* USER CODE END Header_StartDefaultTask */ void StartDefaultTask(void const * argument) { /* USER CODE BEGIN StartDefaultTask */ osThreadDef(wizEchoServerTask, StartWizEchoServerTask, osPriorityNormal, 0, 512); wizEchoServerTaskHandle = osThreadCreate(osThread(wizEchoServerTask), NULL); /* Infinite loop */ for(;;) { HAL_GPIO_TogglePin(LED_GREEN_GPIO_Port, LED_GREEN_Pin); osDelay(500); } /* USER CODE END StartDefaultTask */ }

3) wizinterface.c

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) echoserver.c

Echo Server 는 가장 단순한 방식으로 동작합니다.

소켓생성(바인드) => Listen => Accept => Receive => Send => Close

W5x00 시리즈의 소켓 API 는 소켓 생성 시 포트가 함께 할당되며 별도로 bind 함수를 사용하지 않습니다.

소켓은 LwIP 나 BSD 소켓처럼 Backlog Queue 가 없습니다.

BSD 에서는 Accept 시에 child 소켓을 생성하고 Listen 소켓은 그대로 Listen 을 수행하고 child 소켓을 이용해서 통신하지만 W5x00 에서는 Listen 하던 소켓이 그대로 다시 통신에 사용됩니다.

따라서 데이터를 주고 받는 동안은 다른 Connect 요청을 처리할 수 없습니다.

소켓의 상태변화는 Sn_SR 레지스터를 확인하여 SOCK_LISTEN, SOCK_ESTABLISHED, SOCK_CLOSED 등의 상태 변화를 확인하여야 합니다.

Client 개발 시에는 Sn_SR 상태 이상 등으로 수정한 라이브러리를 그대로 사용하였고 제 예제에서는 문제 없이 동작하는 상황입니다.

혹시 동작에 이상이 있으신분이라면,

수정한 ioLibrary 대신 원본을 사용해 보시거나 혹은 그 반대로 원본 사용시 문제가 있으시다면 수정한 라이브러리를 사용해 보시길 바랍니다. connect() 동작은 서버 예제에서 영향이 없겠지만 close() 는 영향이 있을 수도 있을거 같습니다.

/* * echoserver.c * * Created on: 2020. 5. 31. * Author: eziya76@gmail.com */ #include <echoserver.h> #include “FreeRTOS.h” #include “task.h” #include “main.h” #include “cmsis_os.h” #include “wizInterface.h” #include <stdio.h> #define LISTEN_PORT 7 //server port uint8_t buffer[8]; //client sends 8 bytes //tcp client task void StartWizEchoServerTask(void const *argument) { int32_t ret; uint8_t remoteIP[4]; uint16_t remotePort; 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) { //create socket ret = socket(CLIENT_SOCKET, Sn_MR_TCP, LISTEN_PORT, SF_TCP_NODELAY); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“socket failed{%ld}.n”, ret); close(CLIENT_SOCKET); osDelay(100); continue; } //check initialization while(getSn_SR(CLIENT_SOCKET) != SOCK_INIT) { osDelay(10); } printf(“listening….n”); ret = listen(CLIENT_SOCKET); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“listen failed{%ld}.n”, ret); close(CLIENT_SOCKET); osDelay(100); continue; } //check listening status while(getSn_SR(CLIENT_SOCKET) == SOCK_LISTEN) { osDelay(10); } if(getSn_SR(CLIENT_SOCKET) == SOCK_ESTABLISHED) { //client accepted printf(“accepted….n”); //get remote information getsockopt(CLIENT_SOCKET, SO_DESTIP, remoteIP); getsockopt(CLIENT_SOCKET, SO_DESTPORT, (uint8_t*)&remotePort); printf(“remote IP[PORT]:%03d.%03d.%03d.%03d[%05d]n”, remoteIP[0], remoteIP[1], remoteIP[2], remoteIP[3], remotePort); //receive data ret = recv(CLIENT_SOCKET, buffer, sizeof(buffer)); if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“recv failed.{%ld}n”, ret); close(CLIENT_SOCKET); //unexpected close continue; } printf(“received…n”); //send back data ret = send(CLIENT_SOCKET, buffer, sizeof(buffer)); 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; } printf(“sent back…n”); HAL_GPIO_TogglePin(LED_BLUE_GPIO_Port, LED_BLUE_Pin); } else { printf(“getSn_SR() != SOCKET_ESTABLISHED.n”); } //close socket close(CLIENT_SOCKET); printf(“closed…n”); } vTaskDelete(NULL); //clear task }

[테스트]

테스트는 LwIP 테스트 시에 만들었던 TCP Client 를 수정하였습니다.

LwIP 는 Backlog Queue 를 지원하기 때문에 3개의 Thread 가 동시에 서버와 통신하도록 하였는데

W5100 은 Backlog Queue 를 지원하지 않기 때문에 1개의 Thread 만 동작하도록 수정하였습니다.

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

documents
Code
source code

COMMENTS

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