본문 바로가기
  • 오늘도 신나게
아빠가 개발자면 생기는 일

박수스위치 만들기 (회로 + C 코딩) #2/2(완료)

by 앵그리선반장 2020. 4. 23.

버려진 PCB 에서 릴레이보드를 잘라내고, 전원과 opamp 가 있는 다른 PCB 와 연결하고 터미널 단자를 달아서 박수소리 스위치 하드웨어 부분을 모두 완성했어요.

이제 원하는 동작을 구현해 볼 차례입니다.

마이크로폰에서 출력되는 소리 전압을 OPAMP 에서 증폭하고 그것을 MCU 의 ADC 로 그냥 집어 넣었기 때문에 외부에서 어떤 소리가 감지되면 소리주파수가 그대로 ADC 로 입력됩니다.

따라서 소리가 입력되지 않으면 adc 값은 변화가 없거나 아주 작고, 소리가 입력되면 adc 값이 변화가 될 것입니다.

사운드 파형 adc 샘플링

중앙 곡선 : 박수소리 입력 시 변화 전압 파형 (예시)

화살표(파, 빨모도) ADC 샘플링

화살표 빨 : 시스템에서 실제로 가져와서 사용할 수 있는 ADC 값.

-----

실제로 MCU 에서 ADC 로 박수소리를 감지해 내는 건 생각보다 더 복잡한 과정이 있습니다.

너무 자세한 설명은 무의미 하니 이해를 돕기 위한 정도만 설명을 드리자면, 박수 소리의 실제 주파수는 측정해 보지는 않았지만 아마 2Khz 정도의 주파수로 약 10 mSec 정도의 시간 동안 진동을 일으킵니다.

여기서 만약 박수 소리를 감지하는 adc 의 샘플링 속도가 10mSec 간격보다 더 길다면, 박수소리를 감지하지 못하는 상황이 올 수 있겠지요.

하지만, 제가 만든 시스템에서의 ADC 샘플링 속도는 약 15Khz입니다.

15Khz 이면 거의 모든 가청 주파수보다 빠른 스피드 이므로, 우리가 들을 수 있는 소리는 놓치지 않고 그 변화를 감지할 수 있다는 뜻이지요.

그래서 위의 그래프와 같이 소리 주파수의 변화보다 감지해내는 빈도가 더 빠르지요.

ADC 데이터는 DMA를 통해 특정 버퍼에 저장하는데, 그 속도가 그 정도로 빠른 것이고요, 실제로 로직에서 가져다가 쓰는 속도는 프로그램의 1 사이클 속도에 따라 동작하게 됩니다.

그 속도가 위 그래프에서 빨간색으로 표시된 부분이지요. 

( 실제로 속도를 계산해서 넣은 것은 아니고, 이해를 돕기 위한 겁니다. 실제로는 차이가 좀 있어요. )

어쨌든, 박수소리가 한번 발생하면 adc 값이 최소한 3번 정도의 변화를 감지할 수 있다는 뜻입니다.

 

이제 박수 카운팅을 어떻게 할지 고민해 봅니다.

먼저 박수가 한번 발생되면 실제 adc 측정값의 변화는 한 번이 아니라 3~10번 까지도 발생됩니다.

하지만 그 시간 간격이 매우 짧지요.

그래서 그 발생 빈도 간격이 100 mSec 이하라면 이것은 한 번의 박수에 해당하는 것으로 판단합니다.

그리고, 그 빈도의 시간을 체크해서 박수가 한번 발생하고 나서 500 mSec 가 지나가는 동안 다시 발생하지 않는다면, 박수의 카운팅일 종료 합니다.

만약 그 안에 다시 박수가 감지되면 박수 카운팅을 증가합니다.

즉, 0.5초 안에 다음 박수가 감지되어야 박수의 카운터를 증가한다는 겁니다.

그렇게 코드를 작성합니다.

먼저 사용할 gpio를 설정하고, 제어를 하기 위한 매크로 함수를 작성합니다.

 

//ADC
#define	PIN_SOUND_ADC			GPIO_Pin_1	//GPA_Pin_1 , IN , Sound adc input

//제어용 GPIO 
#define	PIN_PWR_SW				GPIO_Pin_8	//GPA_Pin_8 , IN , Power button input
#define	PIN_PWR_HOLD			GPIO_Pin_11	//GPA_Pin_11 ,OUT , Power holde out
#define	PIN_RLY					GPIO_Pin_0	//GPB_Pin_0 , IN , Relay out

// input gpio 메크로 함수
#define	GET_PWR_SW				GPIO_ReadInputDataBit(GPIOA, PIN_PWR_SW)

//output gpio 매트로 함수
#define	SET_PWR_HOLD(x)			GPIO_WriteBit(GPIOA, PIN_PWR_HOLD, (x))
#define	RLY_OUT(x)				GPIO_WriteBit(GPIOB, PIN_RLY, (x))

그리고 gpio를 초기화합니다.

	gpio_tb.GPIO_Pin	= ( PIN_RLY ) ;				   	   
	gpio_tb.GPIO_OType	= GPIO_OType_PP;
	gpio_tb.GPIO_Mode 	= GPIO_Mode_OUT;
	gpio_tb.GPIO_PuPd	= GPIO_PuPd_NOPULL;//GPIO_PuPd_UP;
	gpio_tb.GPIO_Speed	= GPIO_Speed_50MHz;
	GPIO_Init(GPIOB, &gpio_tb);

그리고 adc 처리를 위한 설정을 해 줍니다.

//----------------------------------------------------------------------------------------
// 기능 : adc 를 초기화 한다.
// 주의 : 
//----------------------------------------------------------------------------------------
void board_adc_init(void)
{
	ADC_InitTypeDef     adc_t;
	GPIO_InitTypeDef    gpio_t;	
	
	/* ADC1 DeInit */  
	ADC_DeInit(ADC1);
	/* GPIOC Periph clock enable */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_GPIOA, ENABLE);
	
	/* ADC1 Periph clock enable */
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
	/* Configure ADC Channel11 as analog input */
	gpio_t.GPIO_Pin = PIN_SOUND_ADC ;
	gpio_t.GPIO_Mode = GPIO_Mode_AN;
	gpio_t.GPIO_PuPd = GPIO_PuPd_NOPULL ;
	GPIO_Init(GPIOA, &gpio_t);
	
	/* ADC DMA request in circular mode */
	ADC_DMARequestModeConfig(ADC1, ADC_DMAMode_Circular);
	
	/* Enable ADC_DMA */
	ADC_DMACmd(ADC1, ENABLE);  
	
	/* Initialize ADC structure */
	ADC_StructInit(&adc_t);
	
	/* Configure the ADC1 in continous mode withe a resolutuion equal to 12 bits  */
	adc_t.ADC_Resolution = ADC_Resolution_12b;
	adc_t.ADC_ContinuousConvMode = ENABLE; 
	adc_t.ADC_ExternalTrigConvEdge = ADC_ExternalTrigConvEdge_None;
	adc_t.ADC_DataAlign = ADC_DataAlign_Right;
	adc_t.ADC_ScanDirection = ADC_ScanDirection_Backward;
	ADC_Init(ADC1, &adc_t); 
	
	/* Convert the ADC1 Channel 1 with 55.5 Cycles as sampling time */ 

	ADC_ChannelConfig(ADC1, ADC_Channel_1 , ADC_SampleTime_28_5Cycles);
		
	/* ADC Calibration */
	ADC_GetCalibrationFactor(ADC1);
	
	/* Enable ADC1 */
	ADC_Cmd(ADC1, ENABLE);     
	mdelay(10);
	/* Wait the ADCEN falg */
	while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_ADEN)); 
	
	/* ADC1 regular Software Start Conv */ 
	ADC_StartOfConversion(ADC1);
}

//----------------------------------------------------------------------------------------
// 기능 : adc채널을  dma로 설정 한다.
// 주의 : 
//----------------------------------------------------------------------------------------
void adc_dma_config(void)
{
	DMA_InitTypeDef   dma_t;
	/* DMA1 clock enable */
	RCC_AHBPeriphClockCmd(RCC_AHBPeriph_DMA1 , ENABLE);
	
	/* DMA1 Channel1 Config */
	DMA_DeInit(DMA1_Channel1);
	dma_t.DMA_PeripheralBaseAddr	= (uint32_t)ADC1_DR_Address;
	dma_t.DMA_MemoryBaseAddr		= (uint32_t)sys_info.meas.adc_val;
	dma_t.DMA_DIR					= DMA_DIR_PeripheralSRC;
	dma_t.DMA_BufferSize			= 2;
	dma_t.DMA_PeripheralInc			= DMA_PeripheralInc_Disable;
	dma_t.DMA_MemoryInc				= DMA_MemoryInc_Enable;
	dma_t.DMA_PeripheralDataSize	= DMA_PeripheralDataSize_HalfWord;
	dma_t.DMA_MemoryDataSize		= DMA_MemoryDataSize_HalfWord;
	dma_t.DMA_Mode					= DMA_Mode_Circular;
	dma_t.DMA_Priority				= DMA_Priority_High;
	dma_t.DMA_M2M					= DMA_M2M_Disable;
	DMA_Init(DMA1_Channel1, &dma_t);
	/* DMA1 Channel1 enable */
	DMA_Cmd(DMA1_Channel1, ENABLE);
}

이제 소리가 들어오면 명령으로 변경할 메인 로직을 만들어줍니다.

//----------------------------------------------------------------------------------------
// 기능 : 박수 소리 감지 
// 주의 :
//----------------------------------------------------------------------------------------
uint8_t sound_adc_process(void)
{
	uint8_t cmd = 0;
	
	uint16_t val = 0;
	uint16_t diff = 0; // 두 adc 값의 차이
	
	//printf("ADC avg:%d  now:%d \n",sys_info.meas.adc_avg,val );
		
	val = sys_info.meas.adc_val[0];
	
	if(val > sys_info.meas.adc_prev)
		diff = val - sys_info.meas.adc_prev;
	else
		diff = 0;
		
	if(diff > 40)
	{
		//printf("sound In  prev %d -> now %d   diff %d  tcount %d\n", sys_info.meas.adc_prev, val, diff ,sys_info.clap.interval_tcount);
		
		if(!sys_info.clap.interval_tcount)
		{
			sys_info.clap.count = 1;
			sys_info.clap.interval_tcount = 1;
			led_out_byte(0xFF,0x00);
		}
		else if(sys_info.clap.interval_tcount > 100)
		{
			sys_info.clap.interval_tcount = 1;
			if(sys_info.clap.count)
			{
				sys_info.clap.count ++;
				
				if(sys_info.clap.count>2)
					led_out_byte(0xFF,0xFF);
				else
					led_out_byte(0x00,0xFF);
			}
		}
		else
		{
			sys_info.clap.interval_tcount = 1;
		}
	}
	
	if(sys_info.clap.interval_tcount > 500)
	{
		sys_info.clap.interval_tcount = 0;
		
		switch (sys_info.clap.count)
		{
			case  1:
				printf ("CLAP : 1 \n");
				cmd = 1;
				printf("releay out 1\n");
				RLY_OUT(1);
				mdelay(500);
				led_out_byte(0x00,0x00);
				break;
			
			case  2:
				printf ("CLAP : 2 \n");
				cmd = 2;
				printf("releay out 0\n");
				RLY_OUT(0);
				mdelay(500);
				led_out_byte(0x00,0x00);
				break;
			
			case  3:
				printf ("CLAP : 3 \n");
				cmd = 3;
				mdelay(500);
				led_out_byte(0x00,0x00);
				break;
			
			default :
				printf ("CLAP ? : %d \n",sys_info.clap.count);
				break;
				
		}
	
	}
	sys_info.meas.adc_prev = val;
	return cmd;
}

위 코드에서 보듯이 박수가 1번 2번 3번까지 증가할 때마다 led를 켜줍니다.

그리고, 1 에서는 릴레이를 켜고, 2에서는 릴레이를 꺼줍니다.

led를 켜는 부분은 조금 복잡한 부분이라 나중에 다른 프로젝트 때 자세히 다루겠습니다.

그리고 그 이상으로 올라가면 아무런 처리도 하지 않고 그저 콘솔로 카운팅만 보여줍니다.

다 됐어요 핵 핵..

마지막으로 메인 함수에서 호출하면 땡이지요.

//----------------------------------------------------------------------------------------
// 기능 : Battery pack whith Bluetooth동작
// 주의 : 
//----------------------------------------------------------------------------------------
void main_process(void)
{
	sound_adc_process();
	check_console_cmd();
//	sound_ledbar_out();
	led_display();
	check_input();
}

//****************************************************************************************
// 기능 : 메인 함수
// 주의 : 
//****************************************************************************************
int main(void)
{	
	//제품의 동작을 위한 주변장치들을 초기화 한다.     
	sys_peripheral_init();
	
	//시스템 정보 출력
	print_system_info();
		
	//시스템 초기화
	sys_init();
	
	printf("\r\n]#");
	
	while(1)
	{
		main_process();	
    }
}

main process에 있는 함수들 중 sound_adc_process 를 제외한 나머지는 다른 기능입니다.

여기까지 쓸데없이 코드만 올리고 내용이 없네요.

어쨌든 동작이 되는지 확인해볼 차례입니다.

이제 딸랑구 텐트 등 스위치에 연결해 볼 차례입니다.

그리고 박수를 쳐서 켜고 끄는 걸 보여주면... 으흐흐 ~ 딸내미가 좋아서 팔딱팔딱 뛰겠죠? 기대됩니다. ㅋㅋ

 

 

- 추가 영상 -

아이의 비밀공간 텐트 전등에 박수소리 스위치를 설치 했어요.

요즘 딸내미가 여기서 잠을 자는데요. 

자기전에 박수로 '짝 짝' 불을 끄고 자는게 너무 귀여워요 ㅋㅋ

뭘 만들어주는 보람이 있다니깐요 ㅎㅎ 

댓글