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체배 | 1 | 11 | 32.73(정확도 낮음) |
2체배 | 2 | 22 | 16.36 |
4체배 | 4 | 44 | 8.18(정확도 높음) |
- 자기장이 흐르는 곳에 코일을 감은 회전축에 직류 전류를 흘려주어 발생하는 전자력에 의해서 코일 회전
- a : 힘의방향 아래쪽, b : 힘의방향 윗쪽 ➡ 180도 회전 후 2번과 같은 상태
- 결과적으로 모터는 반대방향으로 180도 회전
- 전류의 방향이 바뀌지 않으면 직류 전동기는 한 방향으로 회전 불가능
- 앞의 문제점을 해결하기 위해 정류자 사용
- 정류자 : 회전자가 계속하여 회전할 수 있도록 코일의 전류 방향을 바꿔주는 역할
- 1번 그림에서 180도 회전 후 정류자를 이용해 전류 방향을 변경하여 같은 방향으로 계속 회전
내가 사용한 모터는 브러쉬 모터였지만, 세미나 공부를 하면서 다른 모터들에 대해서도 알아보았다.
- 모터의 속도를 감속시켜줌
- 파워를 높여줌
- 회전축은 모터에 비해 천천히 돌지만, 힘은 매우 강력해짐
- 쉽게 마모되고, 파손률이 높음
- 감속기의 감속비가 높을 수록 토크도 그만큼 증가됨
토크란 회전축에서 힘의 작용점까지의 거리와 회전축과 작용점을 잇는 직선에 수직인 힘의 성분과의 곱을 의미한다.
알아보니 이런 공식이 있던데, 이 공식대로라면 감속기의 감속비가 높을수록 감속기의 토크가 증가한다는 건 당연한 말이었다.
- 다수의 톱니모양 전자석이 금속 기어를 중심으로 주변에 매치 되어있음
- 제어회로로부터 전류를 받아 작동하므로 제어장치 드라이버 필요
- 진동, 소음 발생이 쉬움
- 전류가 공급되면 브러시를 통해 정류자로 전류 전달
- 정류자로 이동한 전류는 모터 내부 코일로 이동
- 입력측(정류자 쪽)에서 출력측(구동부와 연결되는 쪽)으로 움직이는 전류는 코일을 위로 움직이게하고, 다시 반대로 돌아오는 전류는 코일을 아래쪽으로 움직이게 하여 축 회전
- 전기를 가해주면 코일이 감겨있는 중앙부분이 자기화되면서 힘이 생겨 돌아감
- 장점 : 가격 저렴, 구동 간단
- 단점 : 브러쉬가 정류자에 접촉되어 있어 모터가 회전하게 되면 정류자가 달아서 없어지고, 열이 발생하여 모터가 뜨거워짐
- 외부의 영구자석 쪽이 회전을 하고, 중심부가 고정되어 있음
- 장점 : 브러쉬가 없어 수명이 길고, 발열이 적음
- 단점 : 가격이 비쌈
모터의 회전속도를 측정하는 방법은 M 방법과 T 방법으로 나뉜다.
나 같은 경우, 모터를 돌릴 때 M 방법을 사용하여 구현하였다.
- M 방법 : 일정 시간마다 펄스를 세는 방법
- 1초동안 펄스가 몇 개 들어오는지 세고 있다가 1초마다 센 펄스의 숫자를 이용하여 회전 속도를 측정하는 방법
- 회전 속도가 매우 느려질 때 문제가 발생한다.
예를 들어, 1초마다 펄스의 개수를 세는데 펄스가 3초에 한 번씩 들어와버리면 제대로 측정을 못하게 된다.
RPM 즉, 분당 회전수를 구하는 공식은 위와 같다.
- T 방법 : 모터에서 들어오는 펄스 사이의 간격을 측정하는 방법
- 첫 번째 신호가 들어오고, 다음 신호가 몇 초 뒤에 들어오는가를 기준으로 계산
- 고속일 때 문제가 발생한다.
카운터가 하나 증가하는 동안 수십개의 모터 펄스가 들어오면 속도를 정확히 알 수 없다.
RPM을 구하는 공식은 위와 같다.
내가 사용한 모터는 RB-35GM 21TYPE(12V) with 2channel Encoder인데, 이 모터의 사양에 대해 정리하기 전, 기어비와 토크가 무엇인지에 대해서 정리해보려고 한다.
- 맞물린 두 기어의 잇수 간의 비율 (입력 측 잇수/출력 측 잇수)
- EX) 기어의 잇수가 각각 20개, 40개일 경우 1:2비율 ➡ 감속비 = 1/2
- 회전체를 돌리기 위한 회전력
- 정격 토크 : 모터의 정격 회전수에서 낼 수 있는 회전력
내가 사용한 모터의 기어비는 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
홀 센서 : 자기장을 감지하는 센서
홀 효과 : 도체 또는 반도체 내부에 흐르는 전하의 이동방향에 수직한 방향으로 자기장을 가하게 되면 금속 내부의 전하 흐름에 수직한 방향으로 전위차가 형성되게 되는 현상
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