STB 를 개발하는 데, 알면 좋고, 모르면 큰일나는 팁들을 적어 보았다. 내가 경험한 에피소드와 함께, 새롭게 알게된 것들을 정리했다.
64 or 256
듣기는 많이 들었었다. 흔히들 64, 256 QAM 이라고 부르는데, 이것은 스트림서버에서 스트림을 play 시키는 속도를 의미한다.
이 속도에 맞지 않을 경우, 제대로된 스트림을 볼 수 없게 된다.
비단, 스트림서버만 제대로 된 rate 값을 준다고 해서, 끝나는 것은 아니다.
모듈레이터, 스트림서버, 프로그램 소스 이렇게 3박자가 맞아야 제대로 된 스트림을 볼 수 있는 것이다.
- 64 QAM → 26970350 rate
- 256 QAM → 38810700 rate
소스에서는 /src/Cm/cmChange.c 파일을 수정해주어야 한다. 관련 함수는 digitalChannelChange() 이다. 함수 전체의 일부분이다.
ARENA_RTN_t digitalChannelChange (void) { clock_t qTimeOut; MSG_TYPE *pMsgCm, msgCm; ARENA_RTN_t errVal, nimRetrun, errValAv; while (1) { pMsgCm = message_receive_timeout (pChgDigitalQid, TIMEOUT_IMMEDIATE);//TIMEOUT_INFINITY if (pMsgCm == NULL) break; else message_release (pChgDigitalQid, pMsgCm); } // Channel Change 시 Stream Decode 정지 - KHK avStopDecode(); cmVchListInitial (&tsVchList); //pys_test requestVch.NumInfo.modFormat = CM_QAM256; <- 바로 이 부분을 제대로 맞춰주어야 스트림을 볼 수 있다. //requestVch.NumInfo.carrierFreq = 663000000; //pys_test nimRetrun = cmTunePhysical (CM_FREQUENCY, &requestVch.NumInfo); currentVch = requestVch; if (nimRetrun == RTN_OK)
이번에는 모듈레이터를 수정해보자!! 256 이었다면, 64 로 바꾸어주어야 한다.
우선 DataRate 부터 바꿔주고, 그 다음에 QAM 을 맞춰준다.
'Enter' 키를 누르면 수정이 가능하다.
open cable 에 대한 이해
각각의 케이블 마다, 사용하는 대역대가 다르기 마련이다. 아래의 그림을 보면서 확인해보자!
별 내용은 없다. 그림에 대한 설명부터 하자면, 그게 두 부분으로 나눌 수 있다. 5 - 45 MHZ 부분과 54 - 860 MHZ 부분이다. 앞의 부분은 통신을 하는 데 사용되는 oob-up 채널이다. 뒷 부분은 in-band 부분이다. oob 라는 것이 out-of-band 라는 뜻으로 in-band 의 반대이다. oob-up 채널이라는 것은 호스트와 pod 가 headend 쪽으로 데이터를 보내는 채널이다.
그런데 좀 이상하다. in-band 부분에 oob 채널이 있다니, 안의 SI 영역을 제외한 부분은 각 채널의 영상이나 소리의 데이터들이 들어 있다. SI 부분에는 모든 채널 정보나, ca 관련한 내용이다. 그런데 oob-down 이라고 부르는 이유는 headend 에서 받아오는 데이터는 채널이기 때문이다.
또한 oob-up 채널의 경우에 1MHZ 마다 하나의 채널로 나눈다. 그에 반해 in-band 채널은 6MHZ 마다 하나의 채널로 나눈다.
전반적인 5517 기반의 처리과정 (SD)
오픈 케이블 STB 의 처리과정을 알아보자!!
가장 먼저 왼쪽부터 오른쪽의 순으로 설명하겠다. 가장 먼저, CABLE 이 들어온다. 크게 튜너를 통해, 각각 in-band 채널과 oob 채널이 들어온다.
이 들어온 신호를 BCM 3125 튜너에서 뽑아낸다. 참고로이 이 튜너는 in-band 와 oob 를 모두 튜닝할 수 있다. 기본적으로 oob 채널은 pod 를 거쳐야만 들어온다. in-band 의 경우에는 clear 채널의 경우에는 POD 를 거치지 않아도 튜닝이 되어 바로 TS DEMUX 로 보낸다. 그래서 화면을 볼 수 있다.
이번에는 디스크램블링 된 채널의 경우에는 oob 와 마찬가지로 pod 로 보낸다. 여기서 ca 를 거쳐서 TS DEMUX 로 보낸다.
TS DEMUX 를 통해 튜닝된 데이터들은 각각 MPEG Decoder(display), Graphic, AC-3 Decoder(sound) 로 거쳐 나오게 된다.
와이드 TV 에서 STB 화면으로 바꿀때
TV 리모콘의 '입력선택' 을 선택하여 'Video 2' 로 선택하면 된다.
UI 에서 테스트 모드로 들어갈 때
지금 현재의 채널의 범위를 모를 때는 테스트 모드로 들어가서 스캔할 채널의 범위를 알아야 한다.
일반 모드에서 테스트 모드로 들어갈 때는 리모콘의 '숫자 0' 누른후 'OK' 버튼을 누르면 된다. 그리고 나서 'MENU'를 눌러보자!! 테스트 모드가 될 것이다.
다시 일반 모드로 돌아갈 때도 역시 마찬가지다. 리모콘의 '숫자 0' 누른후 'OK' 버튼을 누르면 된다.
채널 스캔이 안될 때
프로그램(DACS1000)을 돌리면 맨 처음 채널 스캔을 해야 한다.
만일 채널 스캔시에 100% 가 되었는데도 아래와 같이 튜닝 에러가 날 경우에는
CM_MSG : NO_DB, can't execute dbPchListGet() dbPchListGet failed CM Message : Auto Scan Finished ===== SCRAMBLED_NOTIFY ===== ==== PMT is not prepared ====
하드웨어 적으로 의심을 해봐야 한다.
- 스트림 서버가 꺼져 있지는 않은지
- 모듈레이터가 꺼져 있지는 않은지
첫번째의 경우 play 를 시켜주면된다. 두번째의 경우는 역시 모둘레이터를 켜주면 되는 데, 앞에 QAM 모듈레이터라고 적혀 있는 놈을 켜준다.
그리고 나서 다시 채널 스캔을 해보자!!
정의되지 않은 타입 때문에 에러날 때
이번에 새롭게 0294B 라는 튜너의 테스트 프로그램을 기존의 DACS1000 에 넣을 일이 생겼다. 일단 src 디렉토리 아래에 stic 라는 디렉토리를 만들고, 그 안에 소스파일들을 복사했다.
그리고 나서 컴파일을 했더니, 언제나 그렇듯 에러가 났다.
원인은 정의되지 않은 타입이 함수를 정의 하는 부분에서 나왔기 때문이었다. 예를 들면 아래와 같다.
void SetFieldRegNoOut(PTR32 RegPtr, U32 Mask1, U32 Mask2, U8 Shift, S32 Value); // PTR32 <- 이것이 문제다.
방법은 하나, 타입을 지정해주는 것이다. 주석을 보니, 'PTR32 RegPtr: pointer to the register' 이런식으로 적혀있었다.
일단 임시방편식으로, 문제가 되는 HwAbstract.h 파일의 윗부분에 아래와 같이 적어주었다.
#define PTR32 U32* // PTR32 를 U32* 로 치환(?) 해주는 구문이다. #define PTR16 U16*
그리고 나서 컴파일 했더니, 더이상 에러는 발생하지 않았다.
특정비트 바꾸기
어찌 보면 간단하면서도, 어려워보이는 것 중에 하나가 바로 비트 연산이다. 아마도 실생활에서 거의 사용되는 10진수에 익숙해서 일 것이다. 하지만, 컴퓨터는 2진수로 연산한다.
이제 본론으로 들어가서 특정비트를 바꾸는 예제를 하나 보기로 하자!!
const char status_mask=0x04; extern volatile char device_register; device_register = device_register | status_mask; // 오른쪽에서 3번째 비트를 1로 바꿈 device_register = device_register & (~status_mask); // 오른쪽에서 3번째 비트를 0으로 바꿈 device_register = device_register ^ status_mask; // 오른쪽에서 3번째 비트의 상태를 바꿈
아래의 짧은 문장으로 똑같은 결과를 얻을 수도 있다.
device_register |= status_mask; device_register &= (~status_mask); device_register ^= status_mask;
Ver2 보드 화면이 멈출 때
VSB 튜너가 달린 보드를 돌리면서 화면이 시작하자 마자 멈추거나, 얼마 못가서 멈춰버리면, SMPS 를 바꾸어 보자!!
GPIO 콘트롤이 안될 때
현재 쓰고 있는 5517 CPU 의 경우에는 총 48 개 GPIO 를 지원하고 있다. 이 중에 쓰는 것도 있고, 안쓰는 것도 있다. 이번에 DVI 를 하면서, 2개의 GPIO 콘트롤이 필요하게 되었다. 그런데 이상한 것은 제대로 콘트롤을 했음에도 불구하고, 오실로 스코프로 찍어봐도 파형이 나오지 않는 것이었다. 마치 전혀 콘트롤이 안 먹힌 듯 말이다.
결국, 문제는 초기 루틴에 있었다. 가장 먼저 실행되는 함수였던, 'BasicInfraStructure_Init()' 에서 UART 와 I2C 를 init 해주는 부분에 있었다.
uart_init() 함수에서는 기본으로 uart 에 할당된 gpio 이외에도 몇가지 gpio 를 init 하고 있었다. 결국 이미 먼저 uart 에서 세팅을 해놓은 뒤라, 내가 나중에 콘트롤을 해도 먹지 않았던 것이다.
i2c_init() 함수에서도, 기본으로 i2c 에 할당된 gpio 를 init 하고 있었다. 그래서 이것 또한 콘트롤이 먹지 않았던 것이었다.
나중에 uart, tbx, i2c 초기화 루틴을 주석처리한 후에 확인해보니 모든 gpio 포트가 콘트롤 되었다. 또한 uart_init() 를 아래와 같이 수정하였다.
ST_ErrorCode_t UART_Setup(void) { ST_ErrorCode_t ST_ErrorCode = ST_NO_ERROR; U8 InitList[] = { MODEM_UART_DEV, UART_END_OF_LIST}; U8 index; for ( index = 0; InitList[index] != UART_END_OF_LIST; index++ ) { ST_ErrorCode |= uart_init(InitList[index]); } // ST_ErrorCode = uart_init(3); return ( ST_ErrorCode ); }
채널 스캔이 안될 때
안되는 이유에는 여러가지 많은 이유들이 있을 수 있다. 안된다면, 가장 먼저 살펴보아야 할 것이 바로 현재 스캔을 할 환경이 어떤 것이냐 하는 것이다.
OPEN CABLE 모드 인지 VBS(지상파) 모드인지 말이다. 하드웨어가 Ver2 로 바뀌면서, 기존에 사용하던 QAM 튜너옆에 새로운 튜너가 추가되었다. 이것은 OPEN CABLE 과 VSB 를 모두 잡아낼 수 있다. 비록 동시에 채널 스캔할 수는 없지만, 따로 따로 사용가능하다. 따로따로라는 것은 서로 다른 소스 프로그램이 필요하다는 뜻이 된다.
그래서 현재 DAC5000 소스는 크게 OPEN CABLE 과 VSB 로 나눌 수 있다. 각각의 목적에 맞게 프로그램을 돌려야 한다. Ver2 타겟보드의 경우, 모두 VSB ONLY 모드로 하드웨어적으로 세팅되어 있다. 그래서 반드시 VSB 프로그램을 돌려야 한다.
Ver1 타겟보드의 경우는 이와 반대다.
타겟보드 | 프로그램 |
Ver1 | OPEN CABLE |
Ver2 | VSB |
얘기가 좀 빗나갔는데, 다시 바로 잡자! 채널 스캔이 제대로 안된다면, 스캔을 하려고 하는 맵과 다르거나, 주파수가 달라서 일 수도 있다.
지상파의 경우, 우리나라의 경우 모두 같은 채널 맵과 주파수로 송수신 한다. 하지만, 케이블의 경우 각 서비스하고 있는 지역 SO 마다 다른 채널 맵과 주파수를 가지고 있다.
☞ 현재 우리가 개발중인 STB 는 채널 스캔 속도가 많이 느리다. 그런데 만일 한 케이블에 50 ~ 60 개 정도의 채널이 있다면, 많은 시간을 소비하게 될 것이다. 그래서 테스트를 위해서, 알려진 몇개의 채널만 스캔하도록 할 것이다.
회사안에서는 스트림장비로 부터 들어오는 케이블을 통해서 타겟보드의 튜너로 들어오기 때문에 스트림이 몇 번 채널이고, 주파수를 세팅할 수 있다.
현재 들어오고 있는 스트림을 알았다면, 프로그램 소스에서 수정해주어야 한다.
src/cm 아래에 보면, cmScan.c 파일이 있다. channelScan() 함수를 보자!!
/************************************************************************** DESCRIPTION: INPUT : OUTPUT : RETURN : ERRNO : */ void channelScan (U32 *pStartEndCh) { float scannedChannel, scanCount, endChannel,startChannel,physicalNum; ARENA_RTN_t errVal; VCH_LIST *pVchListRef; autoScanInitial (); switch(cmCheckCurrentBroadcastMode()) { case TERRESTRIAL_MODE : pVchListRef = &(allVchPsipList.TerrVchList); break; case CABLE_MODE : pVchListRef = &(allVchPsipList.CableVchList); break; } if (pStartEndCh == NULL) { switch(cmCheckCurrentBroadcastMode()) { case TERRESTRIAL_MODE : // VSB 모드 startChannel = 14; // 시작 채널 번호 endChannel = 14; // 끝 채널 번호 break; case CABLE_MODE : // OPEN CABLE 모드 startChannel = 102; // 시작 채널 번호 endChannel = 103; // 끝 채널 번호 break; } } else { startChannel = (float)((*pStartEndCh & 0xFFFF0000) >> 16); endChannel = (float)(*pStartEndCh & 0xFFFF); } for (physicalNum = startChannel; physicalNum <= endChannel; physicalNum++) { vchInitial (&scaningPch); scaningPch.NumInfo.physicalNum = physicalNum; switch(cmCheckCurrentBroadcastMode()) { case TERRESTRIAL_MODE : scaningPch.NumInfo.chType = SOURCE_D_AIR; break; case CABLE_MODE : scaningPch.NumInfo.chType = SOURCE_D_CABLE; break; } errVal = digitalTuneAndDBmake (); if (errVal == RTN_ABORT) goto SCAN_ABORT_JMP; scanCount++; scannedChannel = scanCount / (1 + endChannel - startChannel); percentOfScannedChannel = (scannedChannel * 100) - 1; ARENA_Print (("Percent of scanning = %d%%\n", percentOfScannedChannel)); } autoScanComplete (); ARENA_Print (("\nCM Message : Auto Scan Finished\n\n")); epgParsingEndNotifyToUi(); // scrambled stream인지 알 수 있는 시점, 모든 channel의 change 시마다 호출 isAutoScanning = FALSE; semaphore_signal (pCmSemaId); // 원래 SCAN_COMPLETE는 반드시 성공하는 것을 의미 하지는 않으나, 여기서는 db가 만들어진 경우에만 complete메시지를 보낸다. // db가 없는데 complete 메시지를 받고 cmchange관련 함수들이 불려지면 안된다. percentOfScannedChannel = DB_COMPLETE; if(pVchListRef->dbStatus == DB_COMPLETE) sendMsgToUI(SCAN_COMPLETE); else if(pVchListRef->dbStatus == DB_EMPTY) sendMsgToUI(SCAN_FAIL); return; SCAN_ABORT_JMP: ARENA_Print (("\nCM MSG : Auto Scan Canceled, No Update\n\n")); cmVchListInitial (pVchListRef); //test_nvm // nvmCmInitialRead (); isAutoScanning = FALSE; semaphore_signal (pCmSemaId); vchInitial (¤tVch); cmPlayLastVch (); return; }
위에 주석처리해 준 부분을 보자! 채널을 시작할 번호와 끝 번호를 적어주면 된다. 수정했다면, 이제 채널 스캔을 해보자!!
함수를 호출하는 새로운 방법
printf 문을 이용해서도, 함수를 호출할 수 있다. 다음의 예제를 보자!!
printf("Current value (luma factor=%d, Chroma factor=%d, luma offset=%d, Chroma factor=%d)\n",GetLumaFactor(),GetChromaFactor(),GetLumaOffset(),GetChromaOffset());
위에서는 모두 4개의 함수를 호출하고 있는 데, 문법상 오류가 나지 않으려면 각 함수들의 리턴형은 int 형이어야 한다. 이제 이들 함수들을 살펴보자!
U32 GetLumaFactor() { return STSYS_ReadRegDev32LE((void *) (0x60000000 + 0x6200 + 0x003c)); } U32 GetChromaFactor() { return STSYS_ReadRegDev32LE((void *) (0x60000000 + 0x6200 + 0x0040)); } U32 GetLumaOffset() { return STSYS_ReadRegDev32LE((void *) (0x60000000 + 0x6200 + 0x0048)); } U32 GetChromaOffset() { return STSYS_ReadRegDev32LE((void *) (0x60000000 + 0x6200 + 0x0044)); }
아주 간단한 구조로 되어있는 데, 리턴형이 U32 로 되어있다. 바로 이것 때문에 에러가 나지 않는다. 리턴하면서, STSYS_ReadRegDev32LE() 함수를 호출한다.
결국 특정 레지스터에서 읽은 값을 리턴하는 셈이 된다. 참 편리하다!!
ST20RUN 커맨드로 실행하기
일반적으로 실행할 때, 간단히 'gmake run' 으로 하는 경우가 대부분이다.
하지만, 어떤 특정한 작업(?)만 필요할 때는
>st20run -p
해보자! 바로 꺽쇠가 나오면서, 명령어를 직접 입력할 수 있다. 다음은 명령어로 수행하는 예이다.
>st20run -p >st20toolsetversion This is ST20 toolset version R2.1.2 >write -x (st20toolsetversion -q) 0x02010200
유용하게 쓰일 수 있다.
리모콘 키와 함수 매핑하기
리모콘 키를 누를 때마다, 특정 키 값을 프론트(front)가 받아서, STB 에 전송한다. 그러면, 특정 UI 함수가 실행되서 화면에 이미지를 뿌린다.
내가 이번에 하려고 하는 것은 리모콘의 특정 버튼을 누르면, 내가 원하는 함수가 실행되도록 하는 것이다. 그러기 위해서는 키 값에 함수를 매핑시켜야 한다.
/src/ui/ui_system.c 의 NonMenuMsgProcessing() 함수가 바로 그것이다.
메모리 파티션의 용량을 초과할 때
7710 기반의 DACS7000 을 컴파일하다가 다음과 같은 에러메세지를 만났다.
r.tco card_main.tco card_buffNego.tco card_cis.tco card_common.tco card_device.tco eprom.tco avControlApi .tco stv2310.tco tda9873h.tco interface.tco interFunc.tco C:/STM/DACS7000/lib/stapi_stpti4.lib os20.lib o p.lku Information-st20link- Failed when placing section def_const (size 2821236) into Information-st20link- segment EXTERNAL (size 8321024). Information-st20link- Sections already placed in segment EXTERNAL are as follows: Information-st20link- def_code (size 687762) Information-st20link- def_bss (size 1135232) Information-st20link- def_data (size 57612) Information-st20link- system_section (size 4194304) Information-st20link- Space left in segment is 2246114 Serious-st20link- unable to place section def_const, segment details not present or section too large st20cc: Error: in attempting to run st20link gmake[1]: *** [top.lku] Error 1 gmake[1]: Leaving directory `C:/STM/DACS7000/obj' gmake: *** [default] Error 2
에러메세지의 내용을 보면, def_const 파티션의 용량이 초과해서 에러가 났음을 알 수 있다. def_const 는 전역변수를 저장하는 파티션이다.
원인은 bunner.c 의 다음의 소스코드 였다.
/* Global ----------------------------------------------------------------- */ STUART_Handle_t Dev_Uart_Handle; message_queue_t *pBunnerQid; U8 buf[1000000],val,commandBuf[20],bootCodeBuf[100],inputBuf[10]; <<<---- buf[1000000] 가 문제의 원인!! U32 readLen,countInput=0,countComdInput=0,countBootCodeInput=0; //U8 bufferd[3000000]; BOOL bBurnning=1; U8 uCountWriteBlock=0,uCountS=0,uCountX=0,uCountCmd=0;
위의 부분은 소스코드에서 첫 부분에 나오는 전역 변수를 선언하는 부분이다. 주석이 있는 줄을 보면 buf 라는 배열을 무려 1000000 개 선언하는 것을 볼 수 있다. 바로 이 것이 문제의 원인이었다.
이것을 주석처리했더니, 에러없이 컴파일 되었다.