버려진 PCB 에서 릴레이보드를 잘라내고, 전원과 opamp 가 있는 다른 PCB 와 연결하고 터미널 단자를 달아서 박수소리 스위치 하드웨어 부분을 모두 완성했어요.
이제 원하는 동작을 구현해 볼 차례입니다.
마이크로폰에서 출력되는 소리 전압을 OPAMP 에서 증폭하고 그것을 MCU 의 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 를 제외한 나머지는 다른 기능입니다.
여기까지 쓸데없이 코드만 올리고 내용이 없네요.
어쨌든 동작이 되는지 확인해볼 차례입니다.
이제 딸랑구 텐트 등 스위치에 연결해 볼 차례입니다.
그리고 박수를 쳐서 켜고 끄는 걸 보여주면... 으흐흐 ~ 딸내미가 좋아서 팔딱팔딱 뛰겠죠? 기대됩니다. ㅋㅋ
- 추가 영상 -
아이의 비밀공간 텐트 전등에 박수소리 스위치를 설치 했어요.
요즘 딸내미가 여기서 잠을 자는데요.
자기전에 박수로 '짝 짝' 불을 끄고 자는게 너무 귀여워요 ㅋㅋ
뭘 만들어주는 보람이 있다니깐요 ㅎㅎ
'아빠가 개발자면 생기는 일' 카테고리의 다른 글
이야기 들려주는 곰 인형 만들기 (2) | 2020.07.21 |
---|---|
패트병으로 통발 만들기 (0) | 2020.06.07 |
박수스위치 만들기 (회로 + C 코딩) #1/2 (0) | 2020.04.22 |
10포트 USB 충전기 만들기 (1) | 2020.04.16 |
탄약 장착식 고무줄총 만들기 (0) | 2020.04.13 |
댓글