AVR초음파 센서

2023. 3. 3. 12:55AVR ATmega128

이번에는 외부 인터럽트와 타이머를 사용해서 초음파 센서를 배워볼 것이다. 

타이밍 다이그램을 보면, 10us이상의 신호를 주면, 초음파의 Trig신호에서 40kHz의 초음파 신호 8개가 나가게 된다.

직후, Echo핀의 핀 Level이 HIGH로 올라간 후, 위의 초음파 신호가 되돌아오면 Echo핀이 0으로 떨어지게 된다. 

여기서 거리를 알려면, 거리 = 속도 * 시간을 이용해서 소리의 속도 * Echo핀이 High였다가 Low가 된 시간을 곱하면 

거리를 알 수 있게된다. 

 

이제 초음파 센서로부터 오는 시간을 알기 위해 외부 인터럽트를 알아보자.

1.외부 인터럽트

외부 인터럽트 핀

아트메가 128은 8개의 인터럽트 핀이 있다. 아두이노에서는 PulseIn()함수로 펄스의 시간을 셀 수 있는데, PulseIn함수는 atmega328P의 Pin Change Interrupt를 사용하여 펄스를 재는 함수이다. 328P는 모든 외부핀이 PCINT를 사용할 수 있지만, 128은 PCINT가 없기 때문에, 외부 인터럽트를 사용해야한다. 

외부 인터럽트를 사용할려면 데이터시트의 89페이지부터 나오는 외부 인터럽트 레지스터를 보면 된다.

먼저 EICRA 레지스터의 ISCnx 비트들은 인터럽트 0~3번이 언제 발생할지를 결정해주는 레지스터이다.

이번에는 인터럽트 2번을 사용할 예정이고, Echo핀이 펄스 입력을 받기 위해 High로 켜지는 시점이 인터럽트가 발생되는 시점이니 Rising edge 트리거를 사용하기 위해 ISC21, ISC20을 모두 1로 설정하면 된다. 

다음으로, 몇번 인터럽트를 사용할 것인지 알려주는 EIMSK 인터럽트의 2번핀을 1로 설정해준다. 

그다음, 상태 레지스터의 글로벌 인터럽트를 담당하는 7번 비트를 1로 설정해준다.

<avr/Interrupt.h>를 include 시킨다음 SREG |= 0x80으로 설정하거나 sei() 함수를 사용하면 된다. 

반대로 인터럽트를 비활성화 시키고 싶으면 cli()함수를 사용하거나 SREG &= ~(0x80)과 같은 방법으로 7번 비트를 0으로 리셋해준다

EIFR레지스터는 인터럽트가 발생되었을 때 자동적으로 해당하는 인터럽트의 비트가 셋 되는 레지스터이다. 이번에는 따로 설정해 줄 필요는 없다.

 

2. 회로

3. 타이머

시간을 재기 위해 타이머 1번을 사용할 것이다. 

이번에는 OCx 핀을 사용하지 않을 것이기 때문에 Compare Match 비트인 COM1Xn 비트는 모두 0으로 설정해주고, 

노말 모드로 사용할거기 때문에 WGM1n 비트들도 모두 0으로 설정할 것이다. 

분주비는 64로 사용해 4마이크로초마다 1씩 증가하게 설정한다. 분주비를 설정할려면 CS11과 CS10 비트를  1로 만들어준다.

 
 

4. ISR 설정

 

ISR(INT2_vect)
{
	if(USONIC_PIN & (1<<USONIC_ECHO)) //ECHO핀이 HIGH이면
    //if(((USONIC_EICR & (1<<USONIC_ISC0)) == 1)&&((USONIC_EICR & (1<<USONIC_ISC1)) == 1)) EICRA의 레지스터에 저장된 값이 라이징 엣지 였을때
	{
		Ultrasonic_timerStart();
		Ultrasonic_switchInterruptMode(FALLING);
	}
	else 
	{
		Ultasonic_timerStop();
		Ultrasonic_switchInterruptMode(RISING);
	}	
}

거리를 재기 위해 Trig에서 전파를 막 쏜 시점에서는 Echo의 핀이 1로 올라가는 것을 감지하기 위해 초기값을 라이징 엣지로 설정해놓고, 아직 음파가 되돌아오기전인 Echo핀이 1인 시점에서, EICRA 레지스터를 건들여 폴링 엣지에서 인터럽트가 발생되게 해야하고, 음파가 되돌아온 시점에서는 거리를 계산하고 다음 거리를 계산하기 위해 다시 라이징 엣지에서 인터럽트가 발생하도록 변경해주어야한다. 

또는 EICRA레지스터를 체크해서 라이징 엣지 인터럽트 상태에서는 폴링 엣지 인터럽트로 변경해주고, 폴링 엣지 인터럽트 상태에서는 다시 라이징 엣지 상태로 변경해주면 될 것이다.

 

5. 함수 설정

void Ultrasonic_init()
{	
	USONIC_DDR |= 1<<USONIC_TRIG; //Trig
	USONIC_DDR &= ~1<<USONIC_ECHO; //Echo
	EICRA |= 1<<USONIC_ISC0 | 1<<USONIC_ISC1 //Rising Edge
	EIMSK |= 1<<USONIC_INT; //Interrupt 2 enable
	TCCR1B = 1<<CS11 | 1<<CS10; //PreScaler 64
	SREG |= 1<<0x80; //Global Interrupt enable
	ultrasonicDistanceFlag = 0;
}

초기화 함수이다. 위의 내용을 코드로 적은 것이다

void Ultrasonic_switchInterrupt(uint8_t mode)
{
	if(mode == FALLING_EDGE)
	{
		EICRA |= 1<<USONIC_ISC1;
		EICRA &= ~(1<<USONIC_ISC0);
	}
	else
	{
		EICRA |= 1<<USONIC_ISC0 | 1<<USONIC_ISC1; //Rising Edge
	}
}

인터럽트 모드를 스위칭해주는 함수이다. FALLING일때는 ISC1n을 10, RISING일때는 ISC1n을 11로 설정한다.

void Ultrasonic_trigger()
{
	USONIC_PORT |= 1<<USONIC_TRIG;
	_delay_us(15);
	USONIC_PORT &= ~(1<<USONIC_TRIG);
}

 

타이밍다이어그램에서 10us의 High핀을 보내줘야 작동한다 했으므로, 15마이크로초의 HIGH신호를 Trig신호로 출력한다

void Ultrasonic_timerStart()
{
	USONIC_TCNT = 0;
	TCCR1B |= 1<<CS11 | 1<<CS10;
}

void Ultrasonic_timerStop()
{
	TCCR1B &= ~(1<<CS11 | 1<<CS10 | 1<<CS12);
}

타이머를 제어하는 함수이다. 타이머는  CS비트가 모두 0이되면 작동하지 않는다. 

uint16_t Ultrasonic_getDistance()
{
	return USONIC_TCNT * 0.068; // cm단위
}

거리를 재는 함수이다. 4마이크로초가 지날 때마다 TCNT1값이 1씩 증가한다.

이 식을 이용하면 M단위의 값이 나오는데, 상수값을 직접 계산해서 cm단위로 바꿔준다. 

uint8_t Ultrasonic_getCompleteFlag()
{
	return ultrasonicDistanceFlag;
}
void Ultrasonic_setCompleteFlag()
{
	ultrasonicDistanceFlag = 1;
}

void Ultrasonic_clearCompleteFlag()
{
	ultrasonicDistanceFlag = 0;
}

초음파 센서의 완료 플래그이다. 

ISR(INT2_vect)
{
	if(USONIC_PIN & (1<<USONIC_ECHO)) //ECHO핀이 HIGH이면
    //if(((USONIC_EICR & (1<<USONIC_ISC0)) == 1)&&((USONIC_EICR & (1<<USONIC_ISC1)) == 1)) EICRA의 레지스터에 저장된 값이 라이징 엣지 였을때
	{
		Ultrasonic_timerStart();
		Ultrasonic_switchInterrupt(FALLING_EDGE);
	}
	else 
	{
		Ultrasonic_timerStop();
		Ultrasonic_setCompleteFlag();
		Ultrasonic_switchInterrupt(RISING_EDGE);
	}	
}

Echo핀의 HIGH상태 시간을 측정하기 위한 인터럽트 서비스 루틴이다.

int main(void)
{
	Ultrasonic_init();
	LCD_init();
	char buff[30];
	uint16_t distance;

    /* Replace with your application code */
    while (1) 
    {
		Ultrasonic_trigger(); //트리거 신호 보냄
		_delay_ms(500);
		if(Ultrasonic_getCompleteFlag()) //거리측정 완료
		{
			Ultrasonic_clearCompleteFlag(); 
			distance = Ultrasonic_getDistance(); //cm거리계산
			sprintf(buff, "distance = %03dcm", distance);
			LCD_writeStringXY(1,0,buff);
		}
		
    }
}

main함수이다. 

트리거 신호를 보내고 거리측정이 완료될때까지 잠시 대기한다. (초음파는 인터럽트를 사용하므로 _delay_ms에 영향을 받지 않는다)

거리 측정이 완료되었으면 초음파 Flag를 리셋하고 LCD에 출력한다. 

 

/*
 * SuperSonic.c
 *
 * Created: 2023-03-03 오전 9:15:30
 * Author : yrt12
 */ 
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "driver/LCD/LCD.h"
#include <stdio.h>

#define USONIC_PIN PIND
#define USONIC_PORT PORTD
#define USONIC_DDR DDRD
#define USONIC_TRIG 3
#define USONIC_ECHO 2
#define USONIC_ISC0 ISC20
#define USONIC_ISC1 ISC21
#define USONIC_EICR EICRA
#define USONIC_INT INT2
#define USONIC_TCNT TCNT1
enum {FALLING_EDGE, RISING_EDGE};
uint8_t ultrasonicDistanceFlag;
void Ultrasonic_timerStart()
{
	USONIC_TCNT = 0;
	TCCR1B |= 1<<CS11 | 1<<CS10;
}

void Ultrasonic_timerStop()
{
	TCCR1B &= ~(1<<CS11 | 1<<CS10 | 1<<CS12);
}

uint16_t Ultrasonic_getDistance()
{
	return USONIC_TCNT * 0.068; // cm단위
}
void Ultrasonic_init()
{	
	USONIC_DDR |= 1<<USONIC_TRIG; //Trig
	USONIC_DDR &= ~1<<USONIC_ECHO; //Echo
	EICRA |= 1<<USONIC_ISC0 | 1<<USONIC_ISC1;//Rising Edge
	EIMSK |= 1<<USONIC_INT; //Interrupt 2 enable
	TCCR1B = 1<<CS11 | 1<<CS10; //PreScaler 64
	sei();
	ultrasonicDistanceFlag = 0;
}

void Ultrasonic_trigger()
{
	USONIC_PORT |= 1<<USONIC_TRIG;
	_delay_us(15);
	USONIC_PORT &= ~(1<<USONIC_TRIG);
}


void Ultrasonic_switchInterrupt(uint8_t mode)
{
	if(mode == FALLING_EDGE)
	{
		EICRA |= 1<<USONIC_ISC1;
		EICRA &= ~(1<<USONIC_ISC0);
	}
	else
	{
		EICRA |= 1<<USONIC_ISC0 | 1<<USONIC_ISC1; //Rising Edge
	}
}

uint8_t Ultrasonic_getCompleteFlag()
{
	return ultrasonicDistanceFlag;
}
void Ultrasonic_setCompleteFlag()
{
	ultrasonicDistanceFlag = 1;
}

void Ultrasonic_clearCompleteFlag()
{
	ultrasonicDistanceFlag = 0;
}

ISR(INT2_vect)
{
	if(USONIC_PIN & (1<<USONIC_ECHO)) //ECHO핀이 HIGH이면
    //if(((USONIC_EICR & (1<<USONIC_ISC0)) == 1)&&((USONIC_EICR & (1<<USONIC_ISC1)) == 1)) EICRA의 레지스터에 저장된 값이 라이징 엣지 였을때
	{
		Ultrasonic_timerStart();
		Ultrasonic_switchInterrupt(FALLING_EDGE);
	}
	else 
	{
		Ultrasonic_timerStop();
		Ultrasonic_setCompleteFlag();
		Ultrasonic_switchInterrupt(RISING_EDGE);
	}	
}



int main(void)
{
	Ultrasonic_init();
	LCD_init();
	char buff[30];
	uint16_t distance;

    /* Replace with your application code */
    while (1) 
    {
		Ultrasonic_trigger(); //트리거 신호 보냄
		_delay_ms(500);
		if(Ultrasonic_getCompleteFlag()) //거리측정 완료
		{
			Ultrasonic_clearCompleteFlag(); 
			distance = Ultrasonic_getDistance(); //cm거리계산
			sprintf(buff, "distance = %03dcm", distance);
			LCD_writeStringXY(1,0,buff);
		}
		
    }
}

6. 작동영상

 

동작영상

 

'AVR ATmega128' 카테고리의 다른 글

UART 활용  (0) 2023.03.06
UART  (0) 2023.03.06
I2C(TWI) LCD  (1) 2023.03.03
초음파센서 드라이브 만들기  (0) 2023.03.03
Timer를 이용한 Fan만들기  (0) 2023.03.02