
CH32V208을 이용하여 기본적인 이더넷 테스트가 되었기 때문에 Ethernet Web Server 테스트를 해 보자. 28핀 소형 QFN패키지에도 Ehternet Phy(10M) 가내장되어 있어 소형의 이더넷 웹서를 제작할 수 있을것 같다.
우선 가장 간단하게 웹페이지를 출려하는 웹서버를 만들어 보자.
CH32V208을 이용한 WebServer 기본 뼈대 코드는 아래와 같다.
int main(void)
{
u8 i; // 반복문에서 사용할 변수 (8비트 정수)
Delay_Init(); // 시스템 딜레이 초기화 (타이머 기반)
USART_Printf_Init(115200); // USART 초기화 (보드와 PC 간의 시리얼 통신, 속도 115200bps)
GPIOInit(); // GPIO 포트 초기화
printf("WEB SERVER\r\n"); // 프로그램 시작 메시지 출력
printf("SystemClk:%d\r\n", SystemCoreClock); // 현재 시스템 클록 주파수 출력
printf("net version:%x\n", WCHNET_GetVer()); // 네트워크 라이브러리 버전 확인
// 컴파일 시 정의된 라이브러리 버전과 실제 동작 중인 네트워크 스택 버전 비교
if ( WCHNET_LIB_VER != WCHNET_GetVer()) {
printf("version error.\n"); // 버전 불일치 시 에러 메시지 출력
}
// 현재 설정된 IP 주소 출력
printf("ip:\n");
for (i = 0; i < 4; i++)
printf("%d.", IPAddr[i]);
printf("\n");
WCHNET_GetMacAddr(MACAddr); // 칩의 고유 MAC 주소를 가져옴
printf("mac addr:");
for (i = 0; i < 6; i++)
printf("%x ", MACAddr[i]); // MAC 주소 6바이트 출력
printf("\n");
http_request = (st_http_request*) RecvBuffer; // 수신 버퍼를 HTTP 요청 구조체로 캐스팅
TIM2_Init(); // TIM2 타이머 초기화 (주기적 이벤트 처리용)
// 이더넷 라이브러리 초기화 (IP, 게이트웨이, 서브넷, MAC 주소 등록)
i = ETH_LibInit(IPAddr, GWIPAddr, IPMask, MACAddr);
mStopIfError(i); // 에러 발생 시 멈추기
if (i == WCHNET_ERR_SUCCESS)
printf("WCHNET_LibInit Success\r\n"); // 초기화 성공 메시지
WCHNET_CreateTcpSocketListen(); // TCP 소켓 생성 및 리슨 모드 설정 (웹 서버 포트용)
// 설정용 TCP 소켓 생성 (외부 장치와 통신하는 별도의 포트)
WCHNET_CreateCfgSocket(Port_CfgBuf->mode, Port_CfgBuf->des_ip, DESPORT, SRCPORT);
// 메인 루프: 네트워크 스택과 웹 서버 동작 유지
while(1)
{
/* 이더넷 스택의 메인 태스크 수행
* 내부 데이터 처리 및 네트워크 패킷 관리 */
WCHNET_MainTask();
/* 이더넷 전역 인터럽트 발생 여부 확인
* 발생 시 전역 인터럽트 핸들러 실행 */
if(WCHNET_QueryGlobalInt())
{
WCHNET_HandleGlobalInt();
}
// 웹 서버 동작 함수 호출 (HTTP 요청 처리 등)
Web_Server();
}
}
Web_Server 함수에서 웹페이 요청에 대한 처리를 해 주면된다.
특히 METHOD_GET 메시지에 맞게 index 페이지나 led 제어 요청에 대한 페이지 처리를 해 주면 된다.
void Web_Server(void)
{
char *para_p; // POST 요청에서 전달된 파라미터 위치를 가리키는 포인터
u32 len; // 전송할 데이터 길이
// 새로운 데이터가 수신되었을 때만 실행
if(DealDataflag){
DealDataflag = 0; // 플래그 리셋 (데이터 처리 완료 상태)
// 수신된 HTTP 요청을 파싱하여 http_request 구조체에 저장
ParseHttpRequest(http_request, RecvBuffer);
// HTTP 요청 메소드(GET / POST / ERROR)에 따라 분기 처리
switch (http_request->METHOD)
{
case METHOD_ERR:
// 파싱 실패나 지원하지 않는 메소드일 경우
break;
case METHOD_POST: // 'POST' 요청 처리
break;
case METHOD_GET: // 'GET' 요청 처리
name = (char*)GetURLName(http_request->URL); // 요청된 URL 경로 추출
printf("URL name: %s\n",name);
ParseURLType(&http_request->TYPE, name); // 리소스 타입 판별
// URL이 "/"(루트)
if (strlen(name) == 1) {
len = sizeof(Html_index);
copy_flash(Html_index, len );
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
// 브라우저가 자동으로 요청하는 favicon.ico (아이콘) 무시
else if(strstr(name, "favicon.ico") != NULL) {
printf("-> %s\n", name);
}
// "/off" 요청 → LED OFF
else if(strstr(name, "off") != NULL) {
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 1); // LED OFF
printf("LED OFF-> %s\n", name);
len = sizeof(Html_index);
copy_flash(Html_index, len );
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
// "/on" 요청 → LED ON
else if(strstr(name, "on") != NULL) {
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 0); // LED ON
printf("LED ON-> %s\n", name);
len = sizeof(Html_index);
copy_flash(Html_index, len );
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
// 처리 완료 후 소켓 연결 종료
WCHNET_SocketClose(socket, TCP_CLOSE_NORMAL);
break;
default:
// 정의되지 않은 HTTP 메소드 처리
break;
}
}
}
index 페이지는 Html_index 버퍼에 저장하면된다. 우선 간단하게 아스키로 페이지를 만들고 추후 복잡한 페이지는 바이너리로 변환해서 저장 하도록 하면 된다.
const char Html_index[] = {
"<!DOCTYPE html><html><body><center><h1>CH32V208 Simple Web Server - 😊</h1>\
<a href=\"led_on\"><button>LEDON</button></a> \
<a href=\"led_off\"><button>LEDOFF</button></a></center></body></html>"
};
이렇게 수정후 웹서버에 접속하면 간단한 페이지가 출력되고 LED ON, LED OFF 버튼의 요청에 따라 페이지를 출력하고 LED를 제어 하는 코드가 수행된다.

이제 조금 더 복잡한 웹페이지를 수행해 보자.
Web_Server 함수에서 웹페이지에서 요청하는 다양한 페이지에 대한 처리를 추가 해주면 된다.
void Web_Server(void)
{
char *para_p;
u32 len;
if(DealDataflag){
DealDataflag = 0;
ParseHttpRequest(http_request, RecvBuffer);
switch (http_request->METHOD)
{
case METHOD_ERR:
break;
case METHOD_POST: //'post' request
break;
case METHOD_GET: //'get' request
name = (char*)GetURLName(http_request->URL);
printf("URL name: %s\n",name);
ParseURLType(&http_request->TYPE, name);
if (strlen(name) == 1) {
//Refresh_Html(Html_index, Para_Login, 2);
copy_flash(Html_index, sizeof(Html_index));
len = strlen(HtmlBuffer);
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
else if(strstr(name, "favicon.ico") != NULL) { //Request to get the "main" web page
printf("-> %s\n", name);
}
else if(strstr(name, "led_off") != NULL) { //Request to get the "main" web page
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 1);
printf("LED OFF-> %s\n", name);
//Refresh_Html(Html_index, Para_Login, 2);
copy_flash(Html_index, sizeof(Html_index));
len = strlen(HtmlBuffer);
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
else if(strstr(name, "led_on") != NULL) { //Request to get the "main" web page
//GPIO_ResetBits(GPIOA, GPIO_Pin_0);
GPIO_WriteBit(GPIOA, GPIO_Pin_0, 0);
printf("LED ON-> %s\n", name);
//Refresh_Html(Html_index, Para_Login, 2);
copy_flash(Html_index, sizeof(Html_index));
len = strlen(HtmlBuffer);
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
else if(strstr(name, "lamp_1.png") != NULL) {
len = sizeof(PNG_lamp_1);
copy_flash(PNG_lamp_1, len);
WCHNET_SocketSend(socket, HtmlBuffer, &len);
}
/*After the request is processed, the current
* socket connection is closed, and a new connection
* will be established when the browser sends the next
* request.*/
WCHNET_SocketClose(socket, TCP_CLOSE_NORMAL);
break;
default:
break;
}
}
}
이미지도 출력하고 좀더 복잡한 페이지도 쉽게(? 불편한점이 많다) 출력 가능하다. 비교 할만한 칩셋으로 ESP32C3 + W5500 의 웹서버 테스트 예제와 비교하면 부족한 점이 많지만 가격대비 성능로 보면 간단한 프로젝트에는 적용하기 좋을것 같다.

반응형