VGA를 8bit Gray Scale로 변환하는 방법에 대해 알아본다.

소프트웨어가 아니라 Qsys를 이용하여 수정하도록 한다.

1 . Qsys 수정하기

먼저, Qsys를 수정하기로 한다. Qsys를 실행하고, [File] - [Open]을 클릭한다.

image

여기서, Computer_System.qsys을 열 경우, 더 작은 단위의 서브 모듈들의 파라미터 수정이 불가 ( Unable 됨 )하므로 VGA_Subsystem.qsys부터 열어서 확인하고 수정하도록 한다.

열어보면 아래와 같은 모습을 볼 수 있는데, 주목해야 할 것은 각 모듈간의 관계이다.

image

사진에서 [-]표시가 붙은 굵은 이름들은 VGA_Subsystem안의 서브 모듈들이다. ( Sys_Clk , VGA_PLL, VGA_Pixel_DMA 등 )

이 모듈들은 입력과 출력이 있는데, 각 모듈이 정확히 무슨 일을 하는지는 모르지만, 조금 있다가 대략적으로 추론 해 보기로 하자.

사진의 색이 칠해진 부분은 다음과 같다.

구분 설명
빨강 입력 모듈로 들어오는 신호
파랑 출력 모듈의 출력 신호
초록 외부 핀 VGA_Subsystem 밖으로 나가는 신호
노랑 클락 모듈들이 동기화 되도록 연결하는 신호

Qsys를 확인 해 보면, 어떤 모듈의 출력이 바로 아래 모듈의 입력으로 들어가는 모습을 볼 수 있는데, 이를 그림으로 확인하면 아래와 같음을 알 수 있다.

image

각 모듈의 출력이 다음 모듈의 출력에 물려 있으므로, 두 모듈을 연결하는 사이가 서로 맞지 않으면 문제가 생길 수 있으니, 이를 반드시 맞추도록 주의해야 한다.

먼저, 프로그래머가 직접 VGA의 픽셀 하나 하나를 수정 할 수 있도록 도와주는 DMA ( Direct Memory Access )모듈부터 보도록 하자.

image

DMA 모듈을 한 번 클릭하면 위 사진과 같은 정보를 조회하고, 수정 할 수 있다. Parameters 창이 보이지 않는다면, [View] - [Parameters] 로 창을 띄울 수 있다.

일단, DMA모듈은 앞서 말 했듯이 VGA를 사용하기 위해 픽셀 정보를 수정하려고 프로그래머가 접근 할 수 있도록 도와주는 모듈을 말한다.

image

위 자료는 16Bit RGB의 경우를 말 하는데, 한 픽셀당 RGB 정보가 16bit, 즉 2Byte 간격으로 저장이 되어 있다. 이 때, X-Y Addressing Mode를 사용 할 경우, 0,0 픽셀 ( 왼쪽 최상단) 주소에서 X,Y만큼 떨어져 있는픽셀에 데이터를 넣고 싶다면, < 0,0 픽셀 주소 + 자료의 (b) 값>을 더한 값을 ‘참조’하면 된다.

이해가 잘 가지 않는다면 아래를 참고하면 된다. 아래는 x,y에 2Byte RGB 정보를 넣는 함수이다.

void plot_pixel(int x, int y, short line_color){
    *(short *)(pixel_buffer_start + (y << 10) + (x<<1)) = line_color;
}

X-Y Addressing Mode 에서 0번째 비트를 사용하지 않는 이유, 즉 1번째 비트부터 사용하는 이유는 16Bit RGB 모드가 2Byte 간격으로 띄워져 있기 때문에 2칸씩 참고하기 위해 이를 Shift Left 1 ( X 2 ) 해 준 것이다.

좌우간 여기까지 수정 했다면, 아래와 같이 수정하도록 한다.

image

그럼 오류가 날 것이다.

[ DMA 모듈 ] –> [ FIFO 모듈]

위 구조를 생각했을 때, 조금 전까지의 세팅은 DMA가 16Bit RGB였고, 해당 모듈의 출력을 입력으로 받는 RGB_Pixel_FIFO 또한 16Bit RGB였다. 다시 말해 출력과 입력의 포멧이 같아서 문제가 되지 않았지만, 방금DMA의 출력을 강제로 8Bit GrayScale로 바꿔버려서 매칭에 문제가 생긴 것이다.

그림으로 그리면 아래와 같다.

image

따라서 이 부분을 수정하기 위해 Dual Clock FIFO의 Color Bits Parameter를 8로 수정한다.

image

그러나, 이번엔 같은 이유로 FIFO와 Resempler사이에서 문제가 생긴다. FIFO의 출력은 8Bit GrayScale인데 반해, Resempler의 입력은 16Bit RGB이기 때문이다.

따라서 아래와 같이 Resempler의 파라미터의 값의 Incoming Format을 8Bit GrayScale로 바꿔준다.

image

이러면 모든 문제가 해결된다. RGB_Pixel_Resempler에 의해 사용자가 입력한 8Bit GrayScale 입력 신호가 30Bit RGB로, 즉 흑백 도메인에서 컬러 도메인으로 넘어가지만, 컬러 도메인이 흑백 도메인보다 더 크므로, 손실 없이 똑같은 흑백으로 도메인만 넘어가게 되는 것이다.

예를 들어, 정수 3을 정수 도메인에서 실수 도메인으로 변환 한다고 한다면, 정수 도메인보다 실수 도메인이 더 크므로, 아무 문제와 손실 없이 변환될 수 있는 원리와 같다. 정수 3은 실수로 변환되어 3.0으로 바뀔 것이다. 값은 그대로 유지 되며 도메인만 실수로 바뀐 셈이다.

아웃풋은 왜 30Bit RGB를 사용할까? 이 30Bit RGB를 1Bit Black&White로 바꾸면 안될까?

일단은 가능하다. Resempler와 Scaler의 연결도 1bit 로 맞춰주고, Scaler와 Alpha Blender의 연결도 1bit로 맞춰 주면 된다.

하지만, Alpha Blender의 특징 때문에 불가능하다. Alpha Blender의 BackGround는 반드시 30 Bit RBG Format을 가져야 하기 때문이다.

따라서 Alpha Blender의 입력은 항상 30Bit RGB여야 하므로, Scaler의 출력은 항상 30Bit RGB여야 한다. 이것은 다시 말해서 Scaler의 입력 또한 30Bit RGB이어야 함을 뜻하고, 이는 Resempler의 출력이 항상 30Bit RGB여야 함을 의미한다.

좌우간, 여기까지 수정했다면 콘솔 창에 오류가 뜨지 않을 것이다. 오류가 없다면 저장하고, [File] - [Open] 을 눌러 Computer_system.qsys를 연다.

image

VGA_Subsystem을 잘 저장했다면, Computer_System에서 확인 했을 때 위 사진 처럼 변경 사항이 잘 적용된 것을 볼 수 있다. 이를 확인하고, Generate를 눌러 Computer_System.v 파일과 Computer_System.qip파일, Computer_System.cmp 파일을 만든다.


2 . 컴파일 (sof파일과 sopcinfo 파일 만들기)

일단 DE1-SoC_Computer 프로젝트를 연다.

image

Project Navigator를 File로 바꾼다음, Files를 오른쪽 클릭하여 Add/Remove Files in Project를 클릭한다.

일단, 기존에 있던 Computer_System.v, Computer_System.qip파일을 지운다. 만약 Computer_System.cmp도 있다면 그것도 지운다.

그 후, 아까 생성한 Computer_System.v 파일과 Computer_System.qip파일, Computer_System.cmp 파일을 불러들인다.

그럼 아래와 같이 될 것이다.

image

이 상태에서 컴파일을 한다. 이 작업은 굉장히 오래 걸리기 때문에 다른 일을 하고 오길 바란다.

대략 30~40분 정도가 소요된다.


3 . 프로그래머

여기까진 아마 별 문제 없이 컴파일이 될 것이다.

이제 본격적으로 DE1-SoC를 연결하고, 쿼터스의 Programmer를 클릭한다.

그 후, 늘 하던 대로 DE1-SoC에 sof 파일을 올리는 것 처럼 진행한다. 아마 별 문제 없이 진행 되었다면, DE1_SoC_Computer_어쩌구저쩌구.sof 파일이 생겼을 것이다. 정확한 파일 명은 기억이 잘 나질 않는데, 좌우간 무슨 Limit라는 단어가 들어갔던 것 같기도 하다.

이를 올리려고 하면,

image

이런 창이 뜨는데 그냥 OK 한다. 그 후 Start를 하게 되면

image

이런 새로운 모습을 보여주는데, 겁먹지 말고 그냥 Cancel누르고 진행한다.

이제 거의 끝났다. 이클립스를 실행한다.


4 . 이클립스

[File] - [New] - [Nios II Application and BSP from Template] 클릭 한다.

SOPC를 고르는 화면에서, 아까 만들어졌던 sopcinfo 파일을 선택하고, Hello World Template 선택.

그리고 Finish.

그 후 반드시 BSP 에디터를 수정해 SDRAM에 맞추도록 한다.

그리고 Run > Nios II Hardware를 했을 때 Hello Nios II Process (?)와 같은 환영 문구가 뜬다면 모든 준비가 끝난다.


5 . C기반 VGA 프로그래밍

아래와 같은 코드를 입력한다.

#include <stdio.h>
#include <system.h>
#include <sys/alt_irq.h>


void Timer_ISR();
void wait_for_vsync(void);
void fillColor(char rgb);
void plot_pixel(int x, int y, char line_color);
void clear_screen();

volatile int * pTimer       = (int *)0xFF202000;
volatile int * pTime_low    = (int *)0xFF202008;
volatile int * pTime_high    = (int *)0xFF20200C;
volatile int count = 0;

volatile int *pixel_ctrl_ptr = (int*)VGA_SUBSYSTEM_VGA_PIXEL_DMA_BASE;
short int front_buffer[512 * 256];
short int back_buffer[512 * 256];
volatile int pixel_buffer_start;

int main() {
   int pb_ctxt=0;
   *(pTime_low)     = 0x2D00;
   *(pTime_high)    = 0x0131;
   *(pTimer+1)      = 0x0007;
   alt_irq_register(0, (void *)&pb_ctxt, (void *)Timer_ISR);

   *(pixel_ctrl_ptr + 1) = front_buffer;
   wait_for_vsync();
   pixel_buffer_start = *pixel_ctrl_ptr;
   clear_screen();
   *(pixel_ctrl_ptr + 1) = back_buffer;
   while(1){
   }

   return 0;
}

void Timer_ISR() {
   *pTimer = 0;
   count ++;
   if (count >= 255) count = 0;
   pixel_buffer_start = *(pixel_ctrl_ptr + 1);
   //clear_screen();
   fillColor((char)count);
   wait_for_vsync();
}

void fillColor(char rgb){
   for(int i=0;i<320;i++)
      for(int j=0;j<240;j++)
         plot_pixel(i, j, rgb);
}

void wait_for_vsync(void){
   register int status;
   *pixel_ctrl_ptr = 1;

   status = *(pixel_ctrl_ptr +3);
   while ((status & 0x01)!=1){
      status = *(pixel_ctrl_ptr + 3);
   }
}

void plot_pixel(int x, int y, char line_color){
   *(char *)(pixel_buffer_start + (y << 9) + (x)) = line_color;
}

void clear_screen(){
   for (int x = 0; x<320; x++)
      for (int y = 0; y<240; y++)
         plot_pixel(x, y, 0xffff);
}

여기서 한 가지 주목할 점은,

위에서 설명했던 plot_pixel 코드가 살짝 달라졌다는 점인데,

void plot_pixel(int x, int y, short line_color){
    *(short *)(pixel_buffer_start + (y << 10) + (x<<1)) = line_color;
}

에서

void plot_pixel(int x, int y, char line_color){
   *(char *)(pixel_buffer_start + (y << 9) + (x)) = line_color;
}

으로 바뀌었다. 왜냐하면, 이제는 한 픽셀을 표현하는데 드는 Bit가 8Bit, 즉 1byte 이므로 2칸씩 움직일 필요가 없어져 Shift Left 1을 해주지 않아도 되는 것이다.

그 후, Run Nios II Hardware를 하면, 시간이 지날 수 록 밝아지는, 채도가 전혀 없이 흰색/회색/검정 으로만 구성되어 있는 8Bit GrayScale 화면을 볼 수 있을 것이다.

추가적인 사항은 나의 KAU_EMBEDED_19 Git을 확인할 것. (wiki 또는 close issue 페이지)

추가적인 문의는 issue 게시판에 질문 할 것! (나에게 연락 할 수 있는 가장 확실하고 빠른 방법…)