[AVR] PWM을 이용한 Motor 회전

이여진·2023년 7월 26일
0

AVR

목록 보기
6/6
post-thumbnail

PWM을 이용해서 모터를 돌린 후 엔코더를 이용해 속도를 피드백 받는 것을 구현하였는데, 구현한 코드와 이와 관련 되어 개념 공부한 내용들에 대해서 정리해보려 한다.

구현 내용은 다음과 같다.

  • DC모터 드라이버를 이용해 모터 드라이버 제작
  • PWM을 이용해 DC모터 구동
  • 엔코더 펄스를 외부 인터럽트로 카운팅 한 후 속도 계산
  • 3초 간 시계방향, 3초 간 정지, 3초 간 반시계방향, 3초 간 정지

PWM과 관련해서는 이전에 공부한 적이 있으므로 바로 엔코더에 대해서 공부한 내용들을 정리해보려 한다.

 

📌 엔코더

엔코더란 물리적인 환경으로부터 피드백을 제공하는 장치이다.
정밀한 움직임의 위치를 피드백 받아서 제어를 할 수 있기 때문에 모터의 회전 속도, 회전 방향등을 알 수 있다.

엔코더는 물체의 움직이는 운동방식, 검출 방식, 움직이는 물체의 위치를 파악하는 방식에 따라 구분이 가능하다.

 

🍀물체의 움직이는 운동방식

물체의 움직이는 운동방식에 따라서는 로터리 엔코더리니어 엔코더로 구분할 수 있다.

✨로터리 엔코더

  • 회전하는 물체의 움직임의 위치를 피드백하는 장치
  • 원형으로 된 스케일과 스케일 사이의 간격을 읽어들이는 헤드로 구성
  • 헤드가 스케일 사이의 간격마다 펄스 신호를 읽어들여 아날로그 신호를 디지털 신호로 변환하여 회전하는 모터의 회전 속도, 회전량을 측정할 때 사용

✨리니어 엔코더

  • 직선 운동을 하는 물체의 움직임의 위치를 피드백 하는 장치
  • 스케일과 스케일 사이의 간격을 읽어들이는 헤드로 구성
  • 헤드가 스케일 사이의 간격마다 펄스 신호를 읽어들여 직선운동을 하는 물체의 위치와 속도를 측정할 때 사용

 

🍀검출 방식

검출 방식에 따라서는 접촉식비접촉식으로 구분이 가능한데, 비접촉식방식의 경우 광학식자기식으로 나눌 수 있다.

✨광학식 엔코더

  • 신호검출에 광센서 사용
  • 일정한 패턴으로 구멍이 뚫려있는 디스크를 가진 회전부와 이 회전부를 중심으로 앞 쪽은 발광부, 뒷쪽은 수광부로 이루어짐
  • 발광부의 빛이 회전 디스크를 통과하여 수광부에서 수신되는 빛을 이용한 전기 신호를 제공하는 광학센서를 사용하여 디지털 펄스 신호로 출력

✨자기식 엔코더

  • 신호 검출에 자기센서 사용
  • 일정 간격으로 자화된 자기 드럼이 회전하면, 회전 각도에 따라 홀 소자로부터 출력되는 펄스 수를 계수해서 각도 검출
  • 광학식에서는 발광소자가 전력을 소비하는데 비해서 자기식에서는 소비 전력이 작음
  • 단점 : 자계가 존재하는 장소에서는 사용에 제약을 받음

 

🍀움직이는 물체의 위치를 파악하는 방식

움직이는 물체의 위치를 파악하는 방식에 따라서는 인크리멘탈 엔코더절대식 엔코더로 나뉜다.

✨인크리멘탈형 엔코더

  • 점진적으로 증가하는 형식
  • 엔코더가 돌아갈 때 발생되는 파형의 횟수를 통해 회전축의 회전속도 측정
  • 일정한 방향으로 회전하는 기기에 사용하기 적합한 방법

✨절대식형 엔코더

  • 회전축의 0도 지점을 기준으로 360도를 일정한 비율로 분할
  • 분할 된 각도마다 인식 가능한 전기적인 디지털 코드를 지정하여 측정
  • 이 때, 출력 값을 출발점(기준점)으로 다시 설정해주어야 함

 

📌 엔코더 펄스 수

엔코더 펄스 수에 대해서 정리하기 전에 PPR체배가 무엇인지에 대해서 먼저 알아야한다.

PPR이란 Pulse Per Revolution의 약자로 분해능을 의미한다.
즉, 엔코더의 회전축이 1회전 하는 동안 출력되는 펄스의 수를 의미한다.

 

체배란 A rising, A falling, B rising, B falling등의 edge를 몇 개 사용하여 각도를 측정할 것인가에 대한 것이다. 4개 중 1개의 edge를 사용하는 것이 1체배, 2개를 사용하는 것이 2체배, 4개를 사용하는 것이 4체배이다.

체배가 높을 수록 보다 정확한 각도를 알 수 있다.

예를 들어, 11PPR 엔코더를 생각해보자. 엔코더의 회전축이 1회전 하는 동안 한 개의 채널에서 11개의 펄스가 출력이 되며, 2개의 채널이 존재한다. 따라서 펄스 수22개이며, 한 펄스 당 2개의 edge가 존재한다. 따라서, 총 44개의 edge가 발생하게 된다.

즉, 한 바퀴에 44개의 edge를 발생시킨다. 다시 말하면, 360도 회전할 때 44개의 edge를 발생시킨다.

한 펄스당 감지하는 edge의 개수한 바퀴당 감지하는 edge의 개수한 edge당 회전 각도
1체배11132.73(정확도 낮음)
2체배22216.36
4체배4448.18(정확도 높음)

 

📌 모터 회전 원리

  • 자기장이 흐르는 곳에 코일을 감은 회전축에 직류 전류를 흘려주어 발생하는 전자력에 의해서 코일 회전
  • a : 힘의방향 아래쪽, b : 힘의방향 윗쪽 ➡ 180도 회전 후 2번과 같은 상태
  • 결과적으로 모터는 반대방향으로 180도 회전
  • 전류의 방향이 바뀌지 않으면 직류 전동기는 한 방향으로 회전 불가능

  • 앞의 문제점을 해결하기 위해 정류자 사용
  • 정류자 : 회전자가 계속하여 회전할 수 있도록 코일의 전류 방향을 바꿔주는 역할
  • 1번 그림에서 180도 회전 후 정류자를 이용해 전류 방향을 변경하여 같은 방향으로 계속 회전

 

📌 모터 종류

내가 사용한 모터는 브러쉬 모터였지만, 세미나 공부를 하면서 다른 모터들에 대해서도 알아보았다.

✨기어드 모터

  • 모터의 속도를 감속시켜줌
  • 파워를 높여줌
  • 회전축은 모터에 비해 천천히 돌지만, 힘은 매우 강력해짐
  • 쉽게 마모되고, 파손률이 높음
  • 감속기의 감속비가 높을 수록 토크도 그만큼 증가됨

토크란 회전축에서 힘의 작용점까지의 거리와 회전축과 작용점을 잇는 직선에 수직인 힘의 성분과의 곱을 의미한다.

알아보니 이런 공식이 있던데, 이 공식대로라면 감속기의 감속비가 높을수록 감속기의 토크가 증가한다는 건 당연한 말이었다.

✨스태퍼 모터

  • 다수의 톱니모양 전자석이 금속 기어를 중심으로 주변에 매치 되어있음
  • 제어회로로부터 전류를 받아 작동하므로 제어장치 드라이버 필요
  • 진동, 소음 발생이 쉬움

✨서보 모터

  • 전류가 공급되면 브러시를 통해 정류자로 전류 전달
  • 정류자로 이동한 전류는 모터 내부 코일로 이동
  • 입력측(정류자 쪽)에서 출력측(구동부와 연결되는 쪽)으로 움직이는 전류는 코일을 위로 움직이게하고, 다시 반대로 돌아오는 전류는 코일을 아래쪽으로 움직이게 하여 축 회전

✨브러쉬 모터

  • 전기를 가해주면 코일이 감겨있는 중앙부분이 자기화되면서 힘이 생겨 돌아감
  • 장점 : 가격 저렴, 구동 간단
  • 단점 : 브러쉬가 정류자에 접촉되어 있어 모터가 회전하게 되면 정류자가 달아서 없어지고, 열이 발생하여 모터가 뜨거워짐

✨브러쉬리스 모터

  • 외부의 영구자석 쪽이 회전을 하고, 중심부가 고정되어 있음
  • 장점 : 브러쉬가 없어 수명이 길고, 발열이 적음
  • 단점 : 가격이 비쌈

 

📌 모터의 회전 속도

모터의 회전속도를 측정하는 방법은 M 방법T 방법으로 나뉜다.
나 같은 경우, 모터를 돌릴 때 M 방법을 사용하여 구현하였다.

 

🍀M 방법

  • M 방법 : 일정 시간마다 펄스를 세는 방법
  • 1초동안 펄스가 몇 개 들어오는지 세고 있다가 1초마다 센 펄스의 숫자를 이용하여 회전 속도를 측정하는 방법
  • 회전 속도가 매우 느려질 때 문제가 발생한다.
    예를 들어, 1초마다 펄스의 개수를 세는데 펄스가 3초에 한 번씩 들어와버리면 제대로 측정을 못하게 된다.

RPM 즉, 분당 회전수를 구하는 공식은 위와 같다.

 

🍀T 방법

  • T 방법 : 모터에서 들어오는 펄스 사이의 간격을 측정하는 방법
  • 첫 번째 신호가 들어오고, 다음 신호가 몇 초 뒤에 들어오는가를 기준으로 계산
  • 고속일 때 문제가 발생한다.
    카운터가 하나 증가하는 동안 수십개의 모터 펄스가 들어오면 속도를 정확히 알 수 없다.

RPM을 구하는 공식은 위와 같다.

 

📌 RB-35GM 21TYPE(12V) with 2channel Encoder

내가 사용한 모터는 RB-35GM 21TYPE(12V) with 2channel Encoder인데, 이 모터의 사양에 대해 정리하기 전, 기어비토크가 무엇인지에 대해서 정리해보려고 한다.

✨기어비

  • 맞물린 두 기어의 잇수 간의 비율 (입력 측 잇수/출력 측 잇수)
  • EX) 기어의 잇수가 각각 20개, 40개일 경우 1:2비율 ➡ 감속비 = 1/2

✨토크

  • 회전체를 돌리기 위한 회전력
  • 정격 토크 : 모터의 정격 회전수에서 낼 수 있는 회전력

✨RB-35GM 21TYPE(12V) with 2channel Encoder


내가 사용한 모터의 기어비1/18이며, 무부하 회전수6200 RPM이었다.

  • 듀티비 100% : RPM = 6200/18 = 344.44
  • 듀티비 60% : RPM = 6200/18 * 0.6 = 206.66
  • 듀티비 50% : RPM = 6200/18 * 0.5 = 172.22
  • 듀티비 40% : RPM = 6200/18 * 0.4 = 137.77

 

📌 엔코더 홀센서

홀 센서 : 자기장을 감지하는 센서
홀 효과 : 도체 또는 반도체 내부에 흐르는 전하의 이동방향에 수직한 방향으로 자기장을 가하게 되면 금속 내부의 전하 흐름에 수직한 방향으로 전위차가 형성되게 되는 현상

 

📌 L298N 모터 드라이버

5V의 전압으로 높은 전압, 전류의 모터를 돌려야하기 때문에 L298N 모터 드라이버를 사용하였다.


  • EN_A를 High로 하고 INPUT1,2에 MCU 핀과 연결
  • MCU에서 +-이면 정회전, -+이면 역회전
  • AND 게이터의 한 쪽이 EN_A와 연결되어 있어 EN_A가 High가 아니면 트랜지스터에 전압이 공급되지 않음

L298N 모터 드라이버 관련 납땜 회로도인데, 커패시터다이오드도 사용해주었다.

  • 커패시터 : 노이즈 방지용
  • 다이오드 : 모터가 회전하면 전기가 발생하는데, 다이오드 없이 그대로 연결하면 L298N 칩에 손상이 가해져서 사용

하나의 L298N 모터 드라이버에 두 개의 모터를 연결해줄 수 있는데, 나는 하나의 모터만 연결할 것이므로 INPUT1은 PF0와 연결해주었고, INPUT2는 PF1, 그리고 EN_A은 PB4과 연결해주었다.

 

📌 엔코더 커넥터

엔코더 커넥터의 경우 사진의 오른쪽에 보이는 것처럼 연결해주었다.

처음에 납땜할 때 출력 회로 구성 부분을 못 봤어서 저항없이 바로 연결할 뻔 했는데, 출력 회로 구성 시 A Ch output과 B Ch output 부분Hall Sensor Vcc를 연결할 때 사이에 저항을 연결해주어야 한다.

 

🧷 코드

#define F_CPU 16000000UL							// 16MHz
#define per_sec 1000								// 타이머 카운터 1초
#define sbi(PORTX,bitX) PORTX |= (1 << bitX)		// Set Bit Instruction
#define cbi(PORTX,bitX) PORTX &= ~(1 << bitX)	// Clear Bit Instruction

#include <avr/io.h>
#include <stdio.h>
#include <avr/interrupt.h>
#include <util/delay.h>
#include "lcd.h"					// lcd 헤더파일
#include "init_fn.h"				// 포트, 레지스터, 듀티비 설정 헤더파일

volatile unsigned int time_cnt = 0;		// 1초마다 1씩 올라가는 변수
volatile int pulse_cnt = 0;					// 펄스 카운트 해주는 변수
volatile float rpm = 0;							// rpm값
volatile char str_pulse_cnt[16];			// pulse_cnt값 lcd에 출력하기 위한 문자열
volatile char str_rpm[16];					// rpm값 lcd에 출력하기 위한 문자열
volatile int dir = 0;								// CW, 정지, CCW, 정지

코드가 너무 길어져 포트, 레지스터, 듀티비 설정 관련해서는 파일분할을 해주었다.

#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#define per_sec 1000

void init_port(void)
{
	//포트 초기 설정
	DDRA = 0xff;	//LED
	DDRC = 0xff;	//LCD
	DDRE = 0xe0;	//LCD
	DDRB = (1 << PB4);	//모터드라이버 EN_A
	DDRD = (0 << PD0) | (0 << PD1);	//홀센서 입력
	DDRF = (1 << PF0) | (1 << PF1);		//모터드라이버 출력
	PORTA = 0xff;
}

void init_int0_reg(void)
{
	//INT0 인터럽트 레지스터 초기 설정
	EICRA = (1 << ISC01) | (1 << ISC00);	//상승엣지일 때 인터럽트 발생
	EIMSK = (1 << INT0);	//INT0 허용
}

void init_tc0_reg(void)
{
	//타이머카운터0 레지스터 설정
	TCNT0 =  256 - (F_CPU / per_sec / 64);
	TCCR0 = (1 << CS02) | (1 << WGM01) | (1 << WGM00) | (1 << COM01);	 
	// 분주비64, 비교매치OC0 출력, Fast PWM Mode
	TIMSK = (1 << TOIE0);	//오버플로우 인터럽트 허용
}

void set_dutyratio(float ratio)		//원하는 듀티비 입력받아 OCR0값 계산
{
	OCR0 = 255.0 * (ratio / 100.0);
}

포트, 레지스터, 듀티비 설정 관련 부분이다.

ISR(INT0_vect)			// 외부 인터럽트
{
	if(PIND == 0x01)	// CW
	{
		pulse_cnt++;
	}
	else							//CCW
	{
		pulse_cnt--;
	}
}

ISR(TIMER0_OVF_vect)		// 타이머카운터0 오버플로우 인터럽트
{
	time_cnt++;					//타이머 발생횟수 1 증가
	TCNT0 =  256 - (F_CPU / per_sec / 64);
	
	if (time_cnt <= 3000)		// 3초동안
	{
		if (dir == 0)					// CW
		{
			cbi(PORTF,1);
			sbi(PORTF,0);
			PORTA = 0b11111111;
		}
		else if(dir == 1)			// 정지
		{
			cbi(PORTF,0);
			cbi(PORTF,1);
			PORTA = 0b11111100;
			pulse_cnt = 0;
			rpm = 0;
		}
		else if(dir == 2)			// CCW
		{
			cbi(PORTF,0);
			sbi(PORTF,1);
			PORTA = 0b11110000;
		}
		else if(dir == 3)			// 정지
		{
			cbi(PORTF,0);
			cbi(PORTF,1);
			PORTA = 0b11000000;
			pulse_cnt = 0;
			rpm = 0;
		}
	}
	else							// time_cnt가 3000보다 커지면
	{
		time_cnt = 0;		// time_cnt 0으로 초기화
		dir++;					// dir값 +1
		dir %= 4;				// dir값 0~3사이만 나오게
	}
}

외부 인터럽트와 타이머 카운터0 오버플로우 인터럽트를 사용해주었다.

int main(void)
{
	init_port();				// 포트 초기 설정
	init_int0_reg();			// INT0 외부 인터럽트 레지스터 초기 설정
	init_tc0_reg();			// 타이머카운터0 레지스터 초기설정
	set_dutyratio(50.0);	// 듀티비 50으로 설정

	lcd_init();					// LCD 초기화
	cursor_off();				// LCD 커서 안보이게
	
	sei();						// SREG 7번비트 1로 세트
	
	while (1)
	{
		while(EIFR & (1 << INTF0) == 0);		// rising edge 발생 -> ISR 실행

		sprintf(str_pulse_cnt, "PULSE : %d", pulse_cnt);	
		rpm = (60 * (float)pulse_cnt) / (3 * 13 * 18);
		// rpm = (60 * 펄스 수) / (3초 * PPR(!3) * 기어비(18))
		sprintf(str_rpm, "RPM : %.2f", rpm);
		
		//lcd 출력
		lcd_gotoxy(0, 1); lcd_puts(str_pulse_cnt);
		lcd_gotoxy(0, 0); lcd_puts(str_rpm);
		_delay_ms(200);
		lcd_clear();
	}
}

 

🧷 결과


사진처럼 LCD에 PULSE 수와 RPM값이 뜨고, 듀티비를 50으로 설정해주어서 RPM이 대략 172.22정도가 나오는 PPT에 첨부해놓은 동영상이 있었는데, 영상 재생을 하니깐 검정화면밖에 안 뜬다...

시계방향으로 돌 때와 반시계방향으로 돌 때 오실로스코프로 출력한 파형이다.
A상B상이 각각 반대임을 확인할 수 있다.

듀티비 50으로 설정했을 때와 듀티비 80으로 설정했을 때의 파형이다.

 

🤣참고사진

https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ina_om&logNo=220822692729

 

profile
The sky is the limit

0개의 댓글