본문 바로가기

RaspberryPi/W55RP20

W55RP20 - 네트웍 기반의 저렴한 HDMI 출력 모듈 보드 테스트

 

W55RP20은 저렴한 Ethernet 솔루션 칩인 W5500과 ARM 듀얼 코어 베이스의 저렴한  RP2040을 하나의 칩으로 제작한 SIP(System in Package) 이다.

작고 저렴한 이더넷 솔루션에 사용하기 너무 좋은 칩 인것 같다.

 

 

작고 저렴한 네트워크 예제로 이더넷을 통해 전송받은 광고 이미지를 HDMI 모니터에 출력을 해 줄 수 있는 저렴한 모듈을 제작 해 보고자 한다.

 

프로젝트 소스코드

https://github.com/elabsystem/W55RP20_NET_HDMI



시스템 블록도

PC에서 네트웍을 통해 이미지를 전송하면 W55RP20에서 받아서 HDMI 모니터로 출력 하는 시스템이다. 그리고 자동으로 화면 캡쳐후 전송하는 기능도 추가 하였다.



하드웨어 제작

W55RP20은 최대한 작게 제작해서 모듈(W55RP20 SSM EVM - https://nexp.tistory.com/4046 )로 사용 할 수 있도록 했다

HDMI 출력 모듈은 W55RP20 모듈과 연결 될수 있도록 제작 했다.

 

 

 

회로도

 

 

 

네트웍 성능 테스트

W55RP20의 장점은 hardware TCP/IP stack 으로 가격대비 우수한 네트워크 성능을 보여준다.

iperf 를 이용한 W5500 TCP 전송률 테스트 결과 ( https://nexp.tistory.com/4049 ) 24Mbps 정도의 전송율을 보여준다.

 



 

HDMI 출력 테스트

W55RP20은 저렴한 MCU이지만 하드웨어로 처리되는 PIO 기능을 이용하면 제한적인 HDMI출력을 할 수 있다. 부가적인 칩을 사용하지 않고 MCU에서 직접 HDMI출력 하면 저렴하고 간단하게 광고 디스플레이를 구현 할 수 있다. HDMI 출력 관련 라이브러리 -  https://github.com/Wren6991/PicoDVI 

 

W55RP20 HDMI 출력 테스트 참고(https://nexp.tistory.com/4055)

 


펌웨어

W55RP20는 Raspberry Pi 제단의 RP2040을 기반으로 하고 있기 때문에 Pico SDK 2.0를 설치 하면 간단하게 W55RP20 개발환경을 구축 할 수 있다.



네트웍으로 전송되는 이미지 데이터는 TCP프로토콜로 전송되므로 CPU 코어 0는 TCP 처리 함수가 수행되고 CPU 코어 1은 HDMI출력에 사용된다.

void core1_main() {
	dvi_register_irqs_this_core(&dvi0, DMA_IRQ_0);
	dvi_start(&dvi0);
	dvi_scanbuf_main_16bpp(&dvi0);
	__builtin_unreachable();
}



int main()
 {
	int cnt  = 0;
	int y = 0;
	int x = 0;
    	stdio_init_all();
	vreg_set_voltage(VREG_VSEL);
	sleep_ms(10);

	#ifdef RUN_FROM_CRYSTAL
	set_sys_clock_khz(12000, true);
	#else
	// Run system at TMDS bit clock
	set_sys_clock_khz(DVI_TIMING.bit_clk_khz, true);
	#endif

	gpio_init(LED1_PIN);
	gpio_set_dir(LED1_PIN, GPIO_OUT);

	printf("Configuring DVI..\n");
	InitEthernet();

	dvi0.timing = &DVI_TIMING;
	dvi0.ser_cfg = DVI_DEFAULT_SERIAL_CONFIG;
	dvi0.scanline_callback = core1_scanline_callback;
	dvi_init(&dvi0, next_striped_spin_lock_num(), next_striped_spin_lock_num());

	// Once we''ve given core 1 the framebuffer, it will just keep on displaying
	// it without any intervention from core 0
	sprite_fill16(framebuf, 0xffff, FRAME_WIDTH * FRAME_HEIGHT);
	uint16_t *bufptr = framebuf;
	queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);
	bufptr += FRAME_WIDTH;
	queue_add_blocking_u32(&dvi0.q_colour_valid, &bufptr);

	printf("Core 1 start\n");
	multicore_launch_core1(core1_main);

	printf("Start rendering\n");

	while (1)
	{
	__wfe();

	process_tcps(0, g_tcp_buf, 5000);
	}
	__builtin_unreachable();
}

 

TCP 처리는 process_tcps 함수에서 수행되고 수신한 데이터를  ProcessCommand()에서 처리한다.

int32_t Process_tcps(uint8_t sn, uint8_t* buf, uint16_t port)
{
   int32_t ret;
   uint16_t size = 0, sentsize=0;

   switch(getSn_SR(sn))
   {
      case SOCK_ESTABLISHED :
         if(getSn_IR(sn) & Sn_IR_CON)
         {
         setSn_IR(sn,Sn_IR_CON);
         }
       if((size = getSn_RX_RSR(sn)) > 0) // Don''t need to check SOCKERR_BUSY because it doesn''t not occur.
         {
         if(size > DATA_BUF_SIZE) size = DATA_BUF_SIZE;
         ret = recv(sn, buf, size);

         ProcessCommand(buf, ret);
         }
         break;
         
      case SOCK_CLOSE_WAIT :
         if((ret = disconnect(sn)) != SOCK_OK) return ret;
         break;
         
      case SOCK_INIT :
         if( (ret = listen(sn)) != SOCK_OK) return ret;
         break;
      case SOCK_CLOSED:

         if((ret = socket(sn, Sn_MR_TCP, port, 0x00)) != sn) return ret;
         break;
         
      default:
         break;
   }
   return 1;
}

 

 

ProcessCommand() 함수에 이미지 사이즈 및 데이터의 정보를 수신하고 처리 할 수 있도록 했다.

void ProcessCommand(unsigned char *buf, unsigned int Size)
{
   char *ptr;
   int size = 0;
   int i;
   unsigned int pixel_data = 0;

   if(gCmdStatus == 1)
   {
       for(i=0;i<Size/2;i++)
       {
           pixel_data = (buf[2*i]) | buf[2*i+1]<<8;

           if(gImgIdx>FRAME_BUF_SIZE)gImgIdx = FRAME_BUF_SIZE-1;
           framebuf[gImgIdx++] = pixel_data;
       }

       if(gImgIdx>=FRAME_BUF_SIZE)
       {
           printf("%d,%d, %d\r\n", Size, gImgIdx, gErrCnt);

           gCmdStatus = 0;
           gImgIdx = 0;

           gErrCnt =0 ;
          
       }
       gErrCnt++;

       if(gErrCnt>1000)
       {
           gCmdStatus = 0;
           printf("%d,%d\r\n", Size, gImgIdx);
       }
   }
   else
   {
       if(strstr((const char*)buf, "CMD_LED_ON"))
       {
           Led1On();
           printf("Led On Command\r\n");

           fill_color(framebuf, 0xffff);
       }
       else if(strstr((const char*)buf, "CMD_LED_OFF"))
       {
           Led1Off();
           printf("Led Off Command\r\n");

           fill_color(framebuf, 0);
       }
       else
       {
           ptr = strstr((const char*)buf, "CMD_BMP_SEND");
           if(ptr)
           {
               printf(ptr+strlen("CMD_BMP_SEND"));
               size = atoi((const char*)(ptr+strlen("CMD_BMP_SEND")));
               printf("bmp size = %d.\r\n", size);

               gCmdStatus = 1;
               gImgIdx = 0;
               gErrCnt = 0;
           }
       }      
   }
}

 

 


소프트웨어

PC소프트웨어는 HDMI에 출력할 이미지를 선택해서 네트워크로 전송한다. 프로그램은 TCP Client로 구동되며 보드의 IP로 접속해서 이미지데이터를 전송할 수 있다.  부가 기능으로 Capture Auto를 선택하면 실시간으로 화면을 캡쳐하고 캡쳐한 이미지를 네트웍으로 전송한다.




테스트 결과 

원하는 이미지를 선택하고 전송하면 화면에 바로 네트워크로 전송되고 바로 HDMI로 출력되는 것을 확인 할 수 있다.



https://youtu.be/-AXGt6dG-68

 

반응형