[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() 관련 코드 추가
2) freertos.c
freertos.c 파일에서 작업이 필요한 내용은 다음과 같습니다.
– vApplicationStackOverflowHook 함수에 stack overflow 발생 시, LED 제어
– StartDefaultTask 함수에서 wizTcpClientTask 수행
3) wizinterface.c/h
wizinterface.c 파일에서는 다음과 같은 작업을 수행합니다.
– W5100 과 SPI 통신을 위한 함수 정의 (WIZ_SPI_XXX 함수들)
– DHCP 관련 callback 함수 정의 (cbIPAddrAssigned, cbIPAddrConflic)
– W5100 초기화 함수 정의 (WIZ_ChipInit)
– W5100 네트워크 설정 함수 정의 (WIZ_NetworkInit)
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 를 갖고 소켓 생성부터 반복
socket() 함수와 connect() 함수 사이의 delay 가 현상을 줄여주기는 합니다만 완벽하게 없어지지는 않습니다. //create socket ret = socket(CLIENT_SOCKET, Sn_MR_TCP, localPort++, 0); if(localPort == 0xFFFF) { localPort = 50000; } if (ret < 0) { HAL_GPIO_TogglePin(LED_RED_GPIO_Port, LED_RED_Pin); printf(“socket failed{%ld}.n”, ret); …
forum.wiznet.io
“설명에 앞서 제가 수정한 방법은 제가 겪은 오류만을 회피할 뿐 다른 예상치 못한 악영향을 미칠 수도 있습니다. “
테스트를 하면서 앞서 언급한 것과 같이 매우 빠른 주기로 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 명령을 전송하도록 변경하였습니다.
2) connect()
앞서 close 시에 Sn_SR 의 상태가 SOCK_ESTABLISHED 가 된다고 말씀을 드렸는데 그 원인이 저는 connect 에 있다고 보여집니다.
실제 connect 함수가 호출 후에 SOCK_ESTABLISHED 로 상태가 변경되는데 필요한 시간이 길어질 때까 있는데 이 경우에 대한 예외 코드가 되어 있지 않아서 그대로 Error 를 반환해 버리는데 이때 connect 가 에러를 주었기 때문에 socket 을 close 하려고 하면 앞선 close 의 문제가 발생하는 것입니다.
이 문제를 우회하기 위해서는 connect 시에 상태가 SOCK_ESTABLISHED 가 되지 않았더라도 바로 에러를 반환하는 것이 아니라 몇차례 retry 를 하도록 수정하였습니다.
[ 테스트 ]
테스트는 github 에 올려놓은 TcpServer 테스트 어플리케이션을 이용하여 진행하였습니다.
https://github.com/eziya/STM32F4_HAL_LWIP_LAB/tree/master/MyTcpServer
COMMENTS