본문 바로가기

Enginius/Firmware

NEC 코드 리모컨


NEC  IR Format은 일반적으로 사용하는 IR 리모컨의 통신 프로토콜이다.
이를 이용하기 위해서는 다음과 같은 IR 수신부가 있어야 한다.
이 IR 수신부의 출력단을 인터럽트 포트에 연결해야 한다.

이 IR 수신부에서 하는 일은 Comparator를 이용해서 파형을 구형파로 만들어 준다. (출력)





NEC IR Format의 전체적은 프로토콜은 다음과 같다.

리더 코드후에 커스텀 코드(특정 회사를 나타냄)와 Data code와 Data code가 반전되서 들어온다. data가 잘 읽혔나 확인하기 위해서 사용된다.

여기서 리더 코드는 다음과 같다. 위에 그림에서는 리더 코드와 리피트 리더 코드가 다르다고 나오지만, 기기에 따라서 없을 수도 있다. 우리가 사용하는 리모콘에는 리피트 리더 코드가 없다.





이 포맷에서 1과 0은 다음과 같이 확인한다.


즉 리더 코드 후에 36개의 data코드를 확인하면 된다.



//예제 소스 시작
예제 소스에서는 인터럽트의 모든 엣지에서 인터럽트가 걸리기 때문에 High와 Low로 유지되는 시간 모두를 체크한다.
하지만 대부분의 예제 소스(소스포지 등)에서는 라이징 엣지에서만 체크를 한다. 실은 이 경우가 더 쉬운데 현재 로봇의 (PIC32)의 인터럽트 포트가 아니라 인풋캡쳐 포트에 연결이 되어 있기 때문이다. 아이러니 한 것은 원래 IR 리시버가 인터럽트 포트에 연결이 되어 있었는데 인터럽트 포트는 하나의 엣지 밖에 인터럽트를 못 발생시키기 때문에 회로를 변경 한 것이다. 조금 더 먼저 알았다면 회로도 안바꿔도 되고, 더 쉽게 소스를 짤 수 있었틀 텐데..
// 리모컨 NEC format의 신호 타임 정의 _________________________________________
#define NEC_LEADER_CODE_HIGH			141		// 9000us / 64us = 141 (64us는 timer0 주기)
#define NEC_LEADER_CODE_LOW			70		// 4500us / 64us = 70
#define NEC_BIT_START				9		// 562.5us / 64us = 9
#define NEC_BIT_LOW					9		// 562.5us / 64us = 9
#define NEC_BIT_HIGH					26		// 1687.5us / 64us = 26  

Bool IsNEC( Uint32 diff, Uint32 type)
{
	#define NEC_ROUNDOFF	6
	if( absI ( diff - type ) <= NEC_ROUNDOFF )	
		return True;
	else 
		return False;
}

#define NEW_NEC	1
#define OLD_NEC		0
#define NEC_NONE	0
#define NEC_SHORT	1
#define NEC_LONG	2
#define NEC_HEADER1	3
#define NEC_HEADER2	4
void __ISR(_INPUT_CAPTURE_5_VECTOR, ipl3) _ic5(void) 
{
	mIC5ClearIntFlag();

#if NEW_NEC

	Uint32 rmc_tick_diff;
	
	unsigned char rmc_data_done = False;
	unsigned char customCode, customCode_;
	unsigned char dataCode, dataCode_;

	static unsigned char rmc_data_rx = False, nec_data = NEC_NONE;
	static unsigned long long rmc_tick_prev = 0, rmc_data_tick = 0;
	static int rmc_data_rx_step = 0, rmc_rx_data_num = 0;

	rmc_tick_diff = (Uint32)(g_Tick0 - rmc_tick_prev);
	rmc_tick_prev = g_Tick0;
	
	//64us를 하나의 tick이라 할 때 
	//시작을 알림 (9ms + 4.5ms)
	// 9ms   : 140 . 625
	// 4.5ms : 70 . 312
	// Logic 1 (560us + 1690us)
	// 560us  : 8.75
	// 1690us : 26.40
	// Logic 0 (560us + 560us)
	// 560us  : 8.75
	// 560us  : 8.75
	#define NEC_START1		138
	#define NEC_START2		68
	#define NEC_DATA_PRE		9
	#define NEC_HIGH		27
	#define NEC_LOW		9


	if( IsNEC( rmc_tick_diff , NEC_START1 ) )
		nec_data = NEC_HEADER1;
	else if( IsNEC( rmc_tick_diff , NEC_START2 ) )
	{
		if(nec_data == NEC_HEADER1)
		{//if Header2 comes after Header1 then Initialize for getting RMC data
			nec_data = NEC_HEADER2;
			rmcRecvData = 0;
			rmc_rx_data_num = 0;
			rmc_data_rx_step = 10;
		}
	}
	else if( IsNEC( rmc_tick_diff , NEC_LOW ) )	nec_data = NEC_SHORT;
	else if( IsNEC( rmc_tick_diff , NEC_HIGH ) )	nec_data = NEC_LONG;
	else	nec_data = NEC_NONE;		
	
	switch(rmc_data_rx_step)
	{
	case 0://IDLE

		break;	
	case 10://get low 
		if(nec_data == NEC_SHORT)
			rmc_data_rx_step = 11;	
		break;
	case 11://check data
		if(nec_data == NEC_SHORT)
		{
//			myprintf("0");
			rmcRecvData = rmcRecvData << 1;
			rmcRecvData |= 0;
			rmc_data_rx_step =10;	
			rmc_rx_data_num++;
		}
		else if(nec_data == NEC_LONG)
		{
//			myprintf("1");
			rmcRecvData = rmcRecvData << 1;
			rmcRecvData |= 1;
			rmc_data_rx_step =10;
			rmc_rx_data_num++;
		}
		break;
	}

	if( rmc_rx_data_num == 32 )
	{
		rmc_rx_data_num = 0;
		rmc_data_rx_step = 0;		
		rmc_data_done = True;
	}

	if(rmc_data_done)
	{
		//myprintf(" 0x%X\r\n", rmcRecvData);
		customCode 	= (unsigned char)((rmcRecvData >> 24) & 0xFF);
		customCode_ 	= (unsigned char)((rmcRecvData >> 16) & 0xFF);
		dataCode 	= (unsigned char)((rmcRecvData >> 8) & 0xFF);
		dataCode_ 	= (unsigned char)((rmcRecvData >> 0) & 0xFF);
		//myprintf(" 0x%X 0x%X 0x%X 0x%X\r\n", customCode, customCode_, dataCode, dataCode_);
		
		if(customCode == 0x91 && customCode_ == 0x6F)
		{
			Uint16 rmc_data_tick_diff = (Uint16)(g_Tick2 - rmc_data_tick);
			//myprintf("data: %d del: %d\r\n", dataCode, rmc_data_tick_diff);
			

			// Standby에서 막 깨어났을 때 리모컨의 ON/OFF버튼이 눌려서 깨어났는지 확인
			if(dataCode == RMC_ONOFF) g_WhoWakeMeUp = RMC_ONOFF;
			
			//계속 누르고 있다. 
			if(rmc_data_tick_diff <= 300)	flagRmcHold = TRUE;

			flagRmcTimerOn = TRUE;	// 리모컨 신호가 들어왔다는 의미		
			
			// 리모컨 신호가 이제 막 들어왔다.
			if(flagRmcHold == FALSE)		
			{
				// ON/OFF버튼이면 시간을 재기 시작한다(1.6초)
				if(dataCode == RMC_ONOFF)	
				{
					tickRecvRmcOnOff = g_Tick1;					
					rmcEvent = dataCode;
					rmcPrevData = dataCode;
					//PrintS("here - 2\r\n");
				}
				else
				{					
					rmcEvent = dataCode;					
					rmcPrevData = dataCode;
					//PrintS("here - 3\r\n");
				}
				rmc_btn_pressed_flag = ON;
			}
			// 리모컨이 계속 눌려서 들어오는 신호이다.(350ms 이내)
			else							
			{
				//PrintS("here - 4\r\n");
				// 300ms 이내에 다른 버튼을 눌렀다.
				if(rmcPrevData != dataCode)
				{						
					//rmc_btn_pressed_flag = OFF;
					rmc_btn_pressed_flag = ON;
					//Led_Control(RED_OFF);
					flagRmcTimerOn = FALSE;
					flagRmcHold = FALSE;		// 350ms동안 리모컨 신호가 없다는 것은 버튼을 뗐다.
					tickRecvRmc = 0;						
					//rmcEvent = RMC_NONE;
					rmcEvent = dataCode;
					rmcPrevData = dataCode;	
					//PrintS("here - 5\r\n");
				}
				else if(dataCode == RMC_ONOFF)
				{
					//PrintS("here - 6\r\n");
					// 1초간 누르고 있으면 Power Save 
					if((g_Tick1 - tickRecvRmcOnOff) > 1785) 
					{
						PrintS("here - 7\r\n");
						// 이제 막 깨어난게 아니어야 한다
						if(g_FlagWakeup == TRUE)
						{
							rmcEvent = RMC_POWERSAVE;					
							flagRmcPowerSave = TRUE;								
							PrintS("here - 8\r\n");	
						}
						rmcPrevData = dataCode;
					}
				}				
			}





			rmc_btn_tick = g_Tick2;
			tickRecvRmc = g_Tick1;	
			rmc_data_tick = g_Tick2;	//이전에 data를 받은 시간 
		}
	}

	

#endif


#if OLD_NEC

	unsigned char isFail = TRUE;
	unsigned char customCode;//, customCode_;
	unsigned char dataCode, dataCode_;
	Uint32 temp ;
	int32 cmpTick[2] = {NEC_LEADER_CODE_HIGH, NEC_LEADER_CODE_LOW};

	temp = (Uint32)(g_Tick0 - rmcPrevTick0);

	rmcPrevTick0 = g_Tick0;	

//	myprintf("diff: %d\r\n", temp);
	
	switch(rmcRecvState)
	{
		case 0:	
			// Leader Code 확인
			// (+, -) 3tick 정도의 시간오차는 허용 (3tick = 3 * 64us = 252us)
			if(absI(cmpTick[rmcRecvCnt] - temp) <= 4)
			{
				rmcCount++;
				if(++rmcRecvCnt == 2) 
				{
					rmcRecvCnt = 32;			// custom & data code 합치면 모두 32비트데이터이다.
					rmcRecvState = 1;
					rmcRecvData = 0x00;		// custom & data code 받을 준비
				}
				isFail = FALSE;
			}
			break;
		case 1:	// custom code & data code 받기
			if(absI(NEC_BIT_START - temp) <= 4)
			{
				rmcCount++;
				rmcRecvState = 2;
				isFail = FALSE;
			}
			break;
		case 2:
			if(absI(NEC_BIT_HIGH - temp) <= 4)
			{
				rmcCount++;
				rmcRecvData |= (0x80000000 >> (rmcRecvCnt - 1));
				if(--rmcRecvCnt == 0)	rmcRecvState = 3;
				else				rmcRecvState = 1;
				isFail = FALSE;
			}	
			else if(absI(NEC_BIT_LOW - temp) <= 4)
			{
				rmcCount++;
				if(--rmcRecvCnt == 0)	rmcRecvState = 3;
				else				rmcRecvState = 1;
				isFail = FALSE;
			}
			break;		
		default:
			break;

	}

	// 신호를 다 받았으면 ?왔는지 검사한다.
	// custom과 data 모두 반전된 값이 오므로 비교 검사한다.
	if(rmcRecvState == 3)
	{
		customCode = (unsigned char)(rmcRecvData & 0xFF);
		//customCode_ = (unsigned char)((rmcRecvData >> 8) & 0xFF);
				
		// FIXME : remocon의  custom code는 무엇인가?
		// (주)한울로보틱스 custom code == 137		
		//if((customCode == ~customCode_) && customCode == 0x89)
		if(customCode == 0x89)
		{
			dataCode = (unsigned char)((rmcRecvData >> 16) & 0xFF);
			dataCode_ = (unsigned char)((rmcRecvData >> 24) & 0xFF);

			dataCode_ = ~dataCode_ & 0xFF;		
				
			rmcCount = 0;		
			rmcRecvCnt = 0;
			rmcRecvState = 0;

			//SJ_DEBUG(StringInt, "RMC: ","",dataCode,0.0);

			// Standby에서 막 깨어났을 때 리모컨의 ON/OFF버튼이 
			// 눌려서 깨어났는지 확인
			if(dataCode == RMC_ONOFF) g_WhoWakeMeUp = RMC_ONOFF;
			
			// 이전 신호와 지금 신호간의 시간차가 350ms이내 이면 버튼을 누르고 있다는 의미
			if((g_Tick1 - tickRecvRmc) < 625) 	flagRmcHold = TRUE;
			
	//		PrintS("tick, "); PrintI(g_Tick1); PrintS(", prev,"); PrintI(tickRecvRmc); PrintS(", diff, "); PrintS("\r\n");
			tickRecvRmc = g_Tick1;	
			flagRmcTimerOn = TRUE;	// 리모컨 신호가 들어왔다는 의미		

			// 리모컨 신호가 이제 막 들어왔다.
			if(flagRmcHold == FALSE)		
			{
		//	 	PrintS("here - 1\r\n");
				// ON/OFF버튼이면 시간을 재기 시작한다(1.6초)
				if(dataCode == RMC_ONOFF)	
				{
					tickRecvRmcOnOff = g_Tick1;					
					rmcEvent = dataCode;
					rmcPrevData = dataCode;
	//				PrintS("here - 2\r\n");
				}
				else
				{					
					rmcEvent = dataCode;					
					rmcPrevData = dataCode;
	//				PrintS("here - 3\r\n");
				}
				rmc_btn_pressed_flag = ON;
			}
			// 리모컨이 계속 눌려서 들어오는 신호이다.(350ms 이내)
			else							
			{
		//		PrintS("here - 4\r\n");
				// 300ms 이내에 다른 버튼을 눌렀다.
				if(rmcPrevData != dataCode)
				{						
					rmc_btn_pressed_flag = OFF;
					Led_Control(RED_OFF);
					//SJ_DEBUG(StringOnly, "off", "", 0, 0.0);
					flagRmcTimerOn = FALSE;
					flagRmcHold = FALSE;		// 350ms동안 리모컨 신호가 없다는 것은 버튼을 뗐다.
					tickRecvRmc = 0;						
					rmcEvent = RMC_NONE;
					rmcPrevData = dataCode;	
	//				PrintS("here - 5\r\n");
				}
				else if(dataCode == RMC_ONOFF)
				{
		//			PrintS("here - 6\r\n");
					// 1초간 누르고 있으면 Power Save 
					if((g_Tick1 - tickRecvRmcOnOff) > 1785) 
					{
		//				PrintS("here - 7\r\n");
						// 이제 막 깨어난게 아니어야 한다
						if(g_FlagWakeup == TRUE)
						{
							rmcEvent = RMC_POWERSAVE;					
							flagRmcPowerSave = TRUE;								
		//					PrintS("here - 8\r\n");	
						}
						rmcPrevData = dataCode;
					}
				}				
			}

		}
		else
			isFail = TRUE;
	}

	// 신호 수신중 실패하면 초기화하고 다시 받을 준비한다.
	if(isFail == TRUE)
	{	
		rmcCount = 0;		
		rmcRecvCnt = 0;
		rmcRecvState = 0;
	}

#endif
//	myprintf( "rmc: %d \r\n", rmcRecvState );

}




//예제 소스 끝

'Enginius > Firmware' 카테고리의 다른 글

Atmel Studio6환경에서 JMOD 128-1에 UART 인터럽트와 IO제어  (6) 2012.05.31
VUSB (AVRUSB) usb를 avr의 gpio를 이용해서 제어.  (0) 2011.01.26
PIC32 - UART  (2) 2010.04.13
PIC32 - TIMER  (0) 2010.04.13
PIC32 - SPI  (0) 2010.04.13