이글의 전부 또는 일부, 사진, 소스프로그램 등은 저작자의 동의 없이는 상업적인 사용을 금지합니다. 또한, 비상업적인 목적이라 하더라도 출처를 밝히지 않고 게시하는 것은 금지합니다.



2019/11/07 - [STM32F103] - STM32F103으로 IR 신호 다루기(1편)에 이어지는 내용입니다. 앞의 글에서는 프로젝트를 만들었고, IR 신호 정보를 담을 ring형 큐를 만들어 인터럽트 서비스 루틴에서 신호 길이를 측정하여 큐에 넣는 작업까지 진행했습니다.


3. IR 신호 분석 함수



앞의 글에서 작성한 인터럽트 서비스 루틴에서 리모콘의 신호를 받아 ring형 queue인 IRQueue에 담아 놓았습니다. 다음의 함수 CheckRemocon은 IRQueue에 있는 리모콘으로부터 받은 데이터들을 USB로 시리얼 전송하고, 그 데이터들을 분석해서 리모콘이 송신한 신호를 찾아냅니다.

#define	NEC_LEADER_LOW			9000
#define	NEC_LEADER_HIGH			4500
#define	NEC_LEADER_LENGTH		(NEC_LEADER_LOW + NEC_LEADER_HIGH)	// 0x34BC
#define	NEC_0_LOW			560
#define	NEC_0_HIGH			565
#define	NEC_0_LENGTH			(NEC_0_LOW + NEC_0_HIGH)		// 0x0465
#define	NEC_1_LOW			560
#define	NEC_1_HIGH			1690
#define	NEC_1_LENGTH			(NEC_1_LOW + NEC_1_HIGH)		// 0x08CA
#define	NEC_MIN_LEADER_LENGTH		(NEC_LEADER_LENGTH - 0x03BC)		// 0x3100 12,544
#define	NEC_MAX_LEADER_LENGTH		(NEC_LEADER_LENGTH + 0x02B4)		// 0x3770 14,192

#define	SAMSUNG_LEADER_LOW		3000
#define	SAMSUNG_LEADER_HIGH		9000
#define	SAMSUNG_LEADER_LENGTH		(SAMSUNG_LEADER_LOW + SAMSUNG_LEADER_HIGH)	// 0x2EE0 12,000
#define	SAMSUNG_MIN_LEADER_LENGTH	(SAMSUNG_LEADER_LENGTH - 100)	// 0x2E7C 11,900
#define	SAMSUNG_MAX_LEADER_LENGTH	(SAMSUNG_LEADER_LENGTH + 100)	// 0x2F44 12,100

#define	TC9012_LEADER_LOW		4500
#define	TC9012_LEADER_HIGH		4500
#define	TC9012_LEADER_LENGTH		(TC9012_LEADER_LOW + TC9012_LEADER_HIGH)	//0x2328 9000
#define	TC9012_MIN_LEADER_LENGTH	(TC9012_LEADER_LENGTH - 0x0060)				//0x22C8 8904
#define	TC9012_MAX_LEADER_LENGTH	(TC9012_LEADER_LENGTH + 0x0198)				//0x24C0 9408

#define	MIN_0_LENGTH			(NEC_0_LENGTH - 125)			// 1000
#define	MAX_0_LENGTH			(NEC_0_LENGTH + 75)				// 1200
#define	MIN_1_LENGTH			(NEC_1_LENGTH - 250)			// 2000
#define	MAX_1_LENGTH			(NEC_1_LENGTH + 150)			// 2400

void ProcessQueueData(void)
{
	printfCDC("Tail:%3d -%5d +%5d Sum=%5d Bits:%d\n", IRQueue.Tail, IRQueue.QueueLow[IRQueue.Tail],
			IRQueue.QueueHigh[IRQueue.Tail] - IRQueue.QueueLow[IRQueue.Tail], IRQueue.QueueHigh[IRQueue.Tail], IRQueue.Bits);
	HAL_Delay(7);
	IRQueue.Tail = (IRQueue.Tail + 1) % MAX_IR_QUEUESIZE;
	IRQueue.Data--;
}

uint8_t CheckRemocon(void)
{
	uint16_t data = 0;

	if((IRQueue.State != IR_NO_MORE) && (IRQueue.State != IR_SAMSUNG_LEADER))
		return FAIL;

	if(IRQueue.State == IR_NO_MORE)
		memset(&RemoconCode, 0, sizeof(REMOCONCODE));
	while(IRQueue.Data > 0) {
		data = IRQueue.QueueHigh[IRQueue.Tail];
		ProcessQueueData();
		if(data >= NEC_MIN_LEADER_LENGTH &&  data <= NEC_MAX_LEADER_LENGTH) {
			IRQueue.State = IR_NEC_LEADER;
			break;
		}
		else if(data >= TC9012_MIN_LEADER_LENGTH &&  data <= TC9012_MAX_LEADER_LENGTH) {
			IRQueue.State = IR_TC9012_LEADER;
			break;
		}
		else if(data >= SAMSUNG_MIN_LEADER_LENGTH &&  data <= SAMSUNG_MAX_LEADER_LENGTH) {
			IRQueue.State = IR_SAMSUNG_LEADER;
			break;
		}
	}
	if(IRQueue.Data == 0) {
		IRQueue.State = IR_STANDBY;
		return FAIL;
	}
	IRQueue.Bits = 0;
	while(IRQueue.Data > 0) {
		data = IRQueue.QueueHigh[IRQueue.Tail];
		ProcessQueueData();
		if(data >= MIN_0_LENGTH && data <= MAX_0_LENGTH) IRQueue.Bits++;
		else if(data >= MIN_1_LENGTH && data <= MAX_1_LENGTH) {
			RemoconCode.Code[RemoconCode.Bytes + IRQueue.Bits / 8] |= (0x01 << (IRQueue.Bits % 8));
			IRQueue.Bits++;
		}
		else {
			break;
		}
	}
	if(IRQueue.Data == 0)
		IRQueue.State = IR_STANDBY;
	else if(IRQueue.State == IR_SAMSUNG_LEADER) {						// for SAMSUNG continuous IR sign
		if(IRQueue.Tail == 0) IRQueue.Tail = MAX_IR_QUEUESIZE - 1;
		else IRQueue.Tail--;
		IRQueue.Data++;
		IRQueue.Bits--;
	}
	RemoconCode.Bytes += (IRQueue.Bits / 8);
	return (IRQueue.Bits / 8);
}


ProcessQueueData 함수는 IRQueue의 Tail에 있는 데이터를 출력하고(32, 33행), 잠시 대기합니다(34행). STM32F103의 실행속도가 빨라서 여기에 대기 시간을 주지 않으면 PC의 일부 데이터가 표시되지 않기도 합니다. 그 후에 Tail을 1만큼 증가시킵니다. 이 때에 Tail이 IRQueue의 영역을 벗어나게 되면 0의 값을 갖도록 합니다(35행). IRQueue가 가지고 있는 데이터 수를 하나 줄입니다(36행)
데이터의 queue 상의 위치(IRQueue.Tail), low 신호의 길이, high 신호의 길이, 전체 신호의 길이, 현재 처리하는 비트의 수 등을 출력합니다. "Tail:", 다음의 숫자는 데이터의 queue 상의 위치, '-' 신호 뒤의 숫자는 low 신호의 길이, '+' 신호 뒤의 숫자는 high 신호의 길이, "Sum=" 뒤의 숫자는 전체 신호의 길이, "Bits:" 뒤의 숫자는 현재 처리하고 있는 비트의 수입니다.

CheckRemocon 함수를 살펴봅니다. IRQueue의 상태를 나타내는 IRQueue.State 변수를 검사해서 이 변수의 값이 IR_NO_MORE나 IR_SAMSUNG_LEADER일 경우에만 데이터를 분석합니다(43행). 앞의 글에서 다룬 인터럽트 서비스 TIM1_UP_IRQHandler에서 데이터가 더 이상 들어 오지 않을 때에 IQueue.State에 IR_NO_MORE를 넣었습니다. 삼성 천정형 에어콘 리모콘의 경우 연달아 두 개의 신호를 보내오는 경우가 있어서 이 때의 데이터도 처리하기 위해서 IRQueue.State의 값이 IR_SAMSUNG_LEADER일 때에도 이 함수가 실행되도록 했습니다.
IRQueue.State의 값이 IR_NO_MORE이면 RemoconCode의 내용을 모두 0으로 초기화합니다(46, 47행). 삼성 하우젠 천정형 에어콘 리모콘이 연달아 두 개의 신호를 보내오는 경우에 그 신호들의 값을 모두 구조체 RemoconCode에 담아두기 위해서 IRQueue.State의 값이 IR_SAMSUNG_LEADER일 때에는 초기화 하지 않습니다.
IRQueue에 있는 데이터를 하나씩 가져와서 NEC_LEADER, TC9012_LEADER, SAMSUNG_LEADER에 해당하는 값이 있는지 검사합니다(48행 ~ 63행). LEADER 신호를 찾으면 IRQueueState에 각각의 LEADER에 해당하는 값을 넣고 while 루프를 끝냅니다. 모든 데이터를 검사했는데도 LEADER 신호를 찾지 못했으면 IRQueue.State에 IR_STANDBY를 넣고 리턴합니다(64 ~ 67행).
IRQueue.Bits는 현재까지 분석한 비트 수를 담는 변수로 사용합니다. LEADER 신호를 찾았으면 아직 분석한 데이터가 없으므로 IRQueue.Bits에 0을 넣습니다(68행). 데이터들이 0에 해당하는 신호인지, 1에 해당하는 신호인지에 따라 RemoconCode에 적절한 값을 넣습니다(69 ~ 80행). 도중에 0에 해당하는 신호도 아니고 1에 해당하는 신호도 아니면 while 루프를 빠져나옵니다(77 ~ 79행).
데이터를 다 처리한 경우라면 IRQueue.State에 IR_STANDBY 값을 넣어서 다음 데이터를 받을 준비를 합니다(81, 82행). 데이터를 다 처리하지 않았음에도 while 루프를 빠져나오게 된 경우에 삼성 하우젠 천정형 에어콘 리모콘인 경우는 연속된 데이터가 오기도 하기 때문에 LEADET 신호를 잃지 않도록 Tail을 하나 뒤로 설정하는 등 적절한 조치를 취합니다(83 ~ 88행).
IRemoconCode에 리모콘 신호의 바이트 수를 기록하고(89행), RQueue에서 처리한 바이트 수를 리턴합니다(90행).

다음은 위의 CheckRemocon 함수를 호출하는 main 함수입니다.

int main(void)
{
  /* USER CODE BEGIN 1 */
	uint8_t i, pre, ret;
	char printBuff[256] = {0,};
	char imsiBuff[128] = {0,};
	char *pszCopyright = "(C) BOO YEONG JEONG SendIR";
  /* USER CODE END 1 */
  

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
  InitUartQueue(&CDCQueue);

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_TIM1_Init();
  MX_USB_DEVICE_Init();
  /* USER CODE BEGIN 2 */
  	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, SET);
	HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
	HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2);
	HAL_TIM_Base_Start_IT(&htim1);
	memset(&IRQueue, 0, sizeof(IRQUEUE));
	InitUartQueue(&CDCQueue);
	DWT_Init();
	printfCDC("%s\n", pszCopyright);

  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
	while (1) {
		pre = 0;
		while((ret = CheckRemocon()) != FAIL) {
			*printBuff = 0;
			for(i = pre;i < pre + ret;i++) {
				sprintf(imsiBuff,"%02X", RemoconCode.Code[i]);
				strcat(printBuff,imsiBuff);
			}
			printfCDC("%2dBits 0x%s\n\n", IRQueue.Bits, printBuff);
			pre = RemoconCode.Bytes;
		  	//HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, SET);
		}
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	}
  /* USER CODE END 3 */
}





4. IR 신호 보내기



앞의 글에 있는 SM32F103 IR 연결도에서 PA10을 NPN 트랜지스터의 베이스에 연결하여, 이 핀으로 IR LED를 제어하도록 했습니다. 프로젝트를 생성할 때에는 TIM1의 Channel3을 PWM Generation CH3으로 설정했습니다. 즉 PA10 핀을 통해서 PWM으로 IR LED를 켰다 껐다할 것입니다.

이미 TIM1은 IR 신호를 받아서 분석하기 위해 1uS에 카운터 값이 1증가하도록 설정되어 있습니다. 즉 1MHZ의 주파수로 동작하고 있습니다. 하지만, IR 리모콘은 38KHz 주파수로 적외선을 송신해야 하기 때문에 TIM1 Channel3을 38KHz로 동작시켜야 합니다. 따라서 IR 송신을 할 때에는 TIM1을 38KHz로 동작시키고, IR 송신이 끝나면 다시 1MHz로 동작 시키도록 해야 합니다. 다음과 같이 간단한 함수를 하나 만들어 TIM1 레지스터 값을 변경함으로써 이 기능을 수행하도록 합니다.

uint16_t prescaler, autoreload;

void ChangeTIM1(uint16_t pre, uint16_t reload)
{
	htim1.Instance->PSC = pre;
	htim1.Instance->ARR = reload;
	htim1.Instance->EGR = TIM_EGR_UG;
}


기존 TIM1의 프리스케일러(PSC)와 자동재적재 레지스터(ARR) 값을 저장해 두기 위해서 전역 변수 uint16_t prescaler와 autoreload를 만듭니다. ChangeTIM1 함수는 매개변수로 pre와 reload를받아서 TIM1의 프리스케일러와 자동재적재 레지스터의 값을 바꿉니다(5, 6행). 이어서 EGR 레지스터의 UG 비트를 설정해서 TIM1이 업데이트되도록 합니다(7행). 앞글에서 프로젝트를 만들 때에 설명했던대로 프리스케일러(PSC)에는 0을 자동재적재 레지스터(ARR)에는 236을 넣으면 대략 38KHz로 동작합니다. 또 앞 글에서 프로젝트를 만들 때에 Channel3의 Pulse 값을 117로 입력했기 때문에 duty비는 약 50%가 됩니다.

다음은 실제로 IR 신호를 송신하기 위한 함수들입니다.

#define NEC_LEADER_HIGH_LENGTH		8686
#define NEC_LEADER_LOW_LENGTH		4343
#define NEC_PULSE_LENGTH		520
#define NEC_0_SPACE_LENGTH		580
#define NEC_1_SPACE_LENGTH		1650
#define SAMSUNG_LEADER_HIGH_LENGTH	8100
#define SAMSUNG_LEADER_LOW_LENGTH	4000
#define SAMSUNG_PULSE_LENGTH		NEC_PULSE_LENGTH
#define SAMSUNG_0_SPACE_LENGTH		(NEC_0_SPACE_LENGTH)
#define SAMSUNG_1_SPACE_LENGTH		(NEC_1_SPACE_LENGTH)
#define TC9012_LEADER_HIGH_LENGTH	4450
#define TC9012_LEADER_LOW_LENGTH	4550

#define PWM_PRESCALER			0
#define PWM_AUTORELOAD			236

void PrepareIROut(void)
{
  	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, RESET);
	prescaler = htim1.Instance->PSC;
	autoreload = htim1.Instance->ARR;
	HAL_TIM_IC_Stop_IT(&htim1, TIM_CHANNEL_1);
	HAL_TIM_IC_Stop_IT(&htim1, TIM_CHANNEL_2);
	HAL_TIM_Base_Stop_IT(&htim1);
	ChangeTIM1(PWM_PRESCALER, PWM_AUTORELOAD);
}

void EndingIROut(void)
{
	ChangeTIM1(prescaler, autoreload);
	HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_1);
	HAL_TIM_IC_Start_IT(&htim1, TIM_CHANNEL_2);
	HAL_TIM_Base_Start_IT(&htim1);
  	HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, SET);
}

void IRPulse(uint16_t length)
{
	HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3);
	//DelayUsecTim3(length);
	DWT_Delay(length);
	HAL_TIM_PWM_Stop(&htim1, TIM_CHANNEL_3);
}

void IRSendByte(uint8_t byte)
{
	uint8_t i;

	for(i = 0;i < 8;i++) {
		IRPulse(NEC_PULSE_LENGTH);
		DWT_Delay(byte & (0x01 << i) ? NEC_1_SPACE_LENGTH:NEC_0_SPACE_LENGTH);
	}
}

void IRSendNEC(void)
{
	PrepareIROut();
	//Send LEADER
	IRPulse(NEC_LEADER_HIGH_LENGTH);
	DWT_Delay(NEC_LEADER_LOW_LENGTH);
	//send 4 Bytes
	IRSendByte(0x04);
	IRSendByte(0xFB);
	IRSendByte(0x08);
	IRSendByte(0xF7);
	IRPulse(NEC_PULSE_LENGTH);
	EndingIROut();
}

void IRSendTC9012(void)
{
	PrepareIROut();
	//Send LEADER
	IRPulse(TC9012_LEADER_HIGH_LENGTH);
	DWT_Delay(TC9012_LEADER_LOW_LENGTH);
	//send 4 Bytes
	IRSendByte(0x07);
	IRSendByte(0x07);
	IRSendByte(0x02);
	IRSendByte(0xFD);
	IRPulse(NEC_PULSE_LENGTH);
	EndingIROut();
}

void AirconControl(uint8_t *ptr)
{
	PrepareIROut();
	//Send LEADER
	IRPulse(SAMSUNG_LEADER_HIGH_LENGTH);
	DWT_Delay(SAMSUNG_LEADER_LOW_LENGTH);
	for(uint8_t i = 0;i < 7;i++)
		IRSendByte(*(ptr + i));
	IRPulse(SAMSUNG_PULSE_LENGTH);
	EndingIROut();
}

void AirconOn(void)
{
	uint8_t ptr[] = {0x01, 0xB2, 0xFE, 0x61, 0xB1, 0x0D, 0xF0};
	AirconControl(ptr);
}

void AirconOff(void)
{
	uint8_t ptr[] = {0x01, 0xD2, 0xFE, 0x61, 0xB1, 0x0D, 0xC0};
	AirconControl(ptr);
}

1행부터 12행까지는 각각의 신호 길이를 정하는 매크로입니다. 매크로 PWM_PRESCALER와 PWM_AUTORELOAD는 TIM1을 38KHz 주파수로 동작시키기 위한 TIM1에 설정할 값입니다.
PrepareIROut 함수는 이름과 같이 IR 신호 송출을 준비하는 기능을 합니다. IR 신호를 송출한다는 것을 표시하기 위해서 LED를 켜고(19행), 기존의 PSC와 ARR 값을 가져와 각각 전역변수 prescaler와 autoreload에 저장합니다(20, 21행). TIM1의 Input Capture 인터럽트와 기본 동작을 중지시키고(22 ~ 24행), 38KHz로 설정하기 위해서 PWM_PRESCALER와 PWM_AUTORELOAD를 매개변수로 하여 ChangeTIM1 함수를 호출합니다(25행).
EndingIROut 함수는 PrepareIROut 함수와 상대되는 함수로 IR 출력을 종료하는 뒷처리를 담당합니다. TIM1의 프리스케일러와 자동재적재 레지스터 값을 원래 값으로 복구시키고(30행), 중단시켰던 TIM1의 Input Capture 인터럽트와 기본 동작을 활성화시킵니다(31 ~ 33행). 이어서 PrepareIROut 함수에서 켜 놓은 LED를 끕니다(34행).
IRPulse 함수는 매개변수로 받은 length만큼 38KHz로 PWM 동작을 합니다.
IRSendByte 함수는 매개변수로 받은 byte의 하위 비트부터 시작하여 8개 비트를 IR 신호로 전송합니다. 매 비트마다 IRPulse를 호출하여 NEC_PULSE_LENGTH 길이의 38KHz의 IR 신호를 전송(51행)한 후에 0 혹은 1에 해당하는 시간만큼 지연(52행)하는 방법으로 신호를 전송합니다.
IRSendNEC 함수는 NEC 프로토콜로 신호를 전송하는 함수입니다. PrepareIROut 함수를 호출하여 IR 신호 전송을 준비하고(57행), NEC LEADER 신호를 전송합니다(59, 60행). 이어서 4 바이트의 명령(0x04, 0xFB, 0x08, 0xF7)을 전송합니다(62 ~ 65행). 이 명령들은 LG TV 리모콘의 power 버튼 값입니다. Stop 비트를 전송한(66행) 다음 EndingIROut 함수를 호출하여 IR 신호 전송을 마칩니다(67행).
IRSendTC9012 함수와 AirconControl 함수는 IRSendNEC 함수와 같은 방식으로 각각 TC9012 프로토콜과 삼성 하우젠 천정형 에어콘의 IR 신호를 전송합니다. 각각의 LEADER 신호 길이가 다르고, 삼성 하우젠 천정형 에어콘의 경우 7바이트를 전송합니다. AirconOn 함수와 AirconOff 함수는 각각 삼성 하우젠 천정형 에어콘을 켜고 끄는 명령을 전송합니다.

위에 있는 main 함수 내의 while 문 안에 약간의 코드를 더해서 PC로부터 '1'을 전달 받으면 삼성 하우젠 천정형 에어콘을 켜고, '2'를 전달 받으면 삼성 하우젠 천정형 에어콘을 끄고, '3'을 전달 받으면 LG TV 리모콘의 power 버튼을 누른 것처럼, '4'를 전달 받으면 삼성 TV 리모콘의 power 버튼을 누른 것처럼 동작하는 프로그램을 만들어 봅니다. while 문 안의 내용만 제시합니다.

	while (1) {
		if (CDCQueue.data > 0) {
			ret = GetDataFromUartQueue(&CDCQueue);
			switch (ret) {
			case '1':
				AirconOn();
				break;
			case '2':
				AirconOff();
				break;
			case '3':
				IRSendNEC();
				break;
			case '4':
				IRSendTC9012();
				break;
			}
		}
		pre = 0;
		while((ret = CheckRemocon()) != FAIL) {
			*printBuff = 0;
			for(i = pre;i < pre + ret;i++) {
				sprintf(imsiBuff,"%02X", RemoconCode.Code[i]);
				strcat(printBuff,imsiBuff);
			}
			printfCDC("%2dBits 0x%s\n\n", IRQueue.Bits, printBuff);
			pre = RemoconCode.Bytes;
		  	//HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, SET);
		}
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	}

STM32F103IR.zip
1.2 MB

블로그 이미지

엠쿠스

Microprocessor(STM32, AVR)로 무엇인가를 만들어 보고자 학습 중입니다.

,