BASIC4MCU | 질문게시판 | 모터제어 중 RPM 계산 질문입니다.
페이지 정보
작성자 suuuuuuuh 작성일2024-10-24 22:43 조회55회 댓글3건본문
BLDC 모터를 제어하고 있습니다.
윤덕용 박사님의 BLDC 모터제어 기술에 나오는 코드를 활용하여(회로를 다르게 구성하였습니다.)
모터드라이버 IC 없이 MCU가 직접 제어하는 방식으로, ATMEGA128A를 사용하고 있습니다.
모터는 BL4252-4262를 사용하고 있으며
정/역방향 회전, PWM 제어로 Duty UP/DOWN 에 따라 모터는 잘 회전하고 이 상태를 LCD로 잘 표현하고 있으나,
RPM 계산이 잘 되지 않는 상황입니다. (엔코더는 없습니다..) LCD에는 계속 RPM 0000 만 나타나는 상황입니다.
먼저 3개의 홀센서의 엣지변화(상승/하강모두)를 카운트하고, 해당 카운트가 6가 될때 걸린 시간을 타이머카운트2로 계산하여 RPM을 구하려고 하고..
타이머카운터2가 동작을 제대로 안하는 것 같은데 이유를 잘 모르겠습니다 ㅠㅠ
머리를 싸매고 몇주 고민하다가 .. 질문 올려봅니다
도움주시면 정말 감사합니다...
그리고 혹시, 간혹가다 START 버튼을 누른 이후에 모터가 바로 회전하지않고 모터 축을 살짝 손으로 돌려준 이후에서야 돌기시작하는 경우가 있는데, 이것도 혹시 원인이 무엇인지 알 수 있을까요 ??
코드는 다음과 같습니다
//*********************************************
#include <mega128.h>
#include <stdio.h>
#include <delay.h>
#include <interrupt.h>
#include "mydef_ver3.h"
#include "lcd.h"
#include <stdlib.h>
#include <string.h>
#define F_CPU 16000000UL
//*************************************************
u08 sec;
u08 time_count;
u08 count;
bit motor_running = 0;
void LCD_control(u08 a);
void motor_rotate(void);
volatile unsigned char m1 = 0;
volatile unsigned char pulse=0;
volatile unsigned char pulse0 =0; //pulse count
volatile u16 timer_count = 0; // TIMER2 count
volatile u16 rpm = 0; //motor speed
volatile u16 edge_count = 0;
volatile u08 timer_overflows = 0;
volatile unsigned char TCCR1A_temp, PORTB_temp, PORTD_temp; // I/O register store data
volatile unsigned char Hall_data = 0; // Hall sensor signal data
volatile unsigned char Hall_previous = 0; // 이전 Hall 센서 값
volatile unsigned char direction = '+'; // motor direction command
volatile bit update_lcd_flag = 0; // LCD 업데이트 플래그
volatile bit hall_event_flag = 0; // Hall 센서 이벤트 플래그
u08 prev_count;
bit CW_pressed = 0;
// 모터의 상태를 주기적으로 확인하여 정지하였을때, C 충전하는 루틴
interrupt [TIM3_COMPA] void timer3_compA_isr(void){
if(m1 == 0){ // 모터가 정지상태라면
rpm = 0;
TCCR1A_temp = TCCR1A;
PORTB_temp = PORTB;
PORTD_temp = PORTD;
TCCR1A = 0x02; // PWM 출력 X, 일반 I/O 포트로 사용하게끔
// 부트스트랩 커패시터 충전
PORTB &= ~(0b11100000); // UB(PD1) = VB(PD2) = WB(PD3) = 1
PORTD |= 0b00001110;
delay_ms(10); // UT(PB5) = VT(PB6) = WT(PB7) = 0
PORTD &= ~(0b00001110);
TCCR1A = TCCR1A_temp;
PORTB = PORTB_temp;
PORTD = PORTD_temp;
}
#asm("sei")
}
// HA 신호 외부 인터럽트 처리 루틴
interrupt [EXT_INT4] void Ext_int4_isr(void){
edge_count++;
motor_rotate();
}
// HB 신호 외부 인터럽트 처리 루틴
interrupt [EXT_INT5] void Ext_int5_isr(void){
edge_count++;
motor_rotate();
}
// HC 신호 외부 인터럽트 처리 루틴
interrupt [EXT_INT6] void Ext_int6_isr(void){
edge_count++;
motor_rotate();
}
// 타이머카운터2, 분주비 1024, CTC mode, 주기 1msec
interrupt [TIM2_COMP] void timer2_comp_isr(void) {
// 펄스 수를 바탕으로 RPM 계산
// 한 바퀴 회전당 6개의 펄스 발생 (상승/하강 엣지 고려)
rpm = (edge_count * 10) / 6; // 초당 펄스 수 * 10 -> 분당 회전 수로 환산
edge_count = 0; // 카운트 초기화
}
// 부저음 루틴
void beep(void){
PORTG = PORTG | 0x08;
delay_ms(100);
PORTG = PORTG & 0x07;
}
// 모터 회전 6STEP 루틴
void motor_rotate(void){
Hall_data = PINE & 0x70;
if(direction == '+')
{ if((Hall_data & 0x70) == 0b01010000)
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000100); // 2번 비트(VB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x82; // OC1A 핀에서 PWM 출력 (WT)
}
else if((Hall_data & 0x70) == 0b00010000)
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00001000); // 3번 비트(WB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x82; // OC1A 핀에서 PWM 출력 (UT)
}
else if((Hall_data & 0x70) == 0b00110000) // position angle = 120~180
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00001000); // 3번 비트(WB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x22; // OC1B 핀에서 PWM 출력 (VT)
}
else if((Hall_data & 0x70) == 0b00100000) // position angle = 180~240
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000010); // 1번 비트(UB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x22; // OC1B 핀에서 PWM 출력 (VT)
}
else if((Hall_data & 0x70) == 0b01100000) // position angle = 240~300
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000010); // 1번 비트(UB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x0A; // OC1C 핀에서 PWM 출력 (WT)
}
else if((Hall_data & 0x70) == 0b01000000) // position angle = 300~360
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000100); // 2번 비트(VB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x0A; // OC1C 핀에서 PWM 출력 (WT)
}
}
else
{ if((Hall_data & 0x70) == 0b00110000) // position angle = 0~60
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000100); // 2번 비트(VB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x0A; // OC1C 핀에서 PWM 출력 (WT)
}
else if((Hall_data & 0x70) == 0b00010000) // position angle = 60~120
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000010); // 1번 비트(UB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x0A; // OC1C 핀에서 PWM 출력 (WT)
}
else if((Hall_data & 0x70) == 0b01010000) // position angle = 120~180
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000010); // 1번 비트(UB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x22; // OC1B 핀에서 PWM 출력 (VT)
}
else if((Hall_data & 0x70) == 0b01000000) // position angle = 180~240
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00001000); // 3번 비트(WB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x22; // OC1B 핀에서 PWM 출력 (VT)
}
else if((Hall_data & 0x70) == 0b01100000) // position angle = 240~300
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00001000); // 3번 비트(WB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x82; // OC1A 핀에서 PWM 출력 (UT)
}
else if((Hall_data & 0x70) == 0b00100000) // position angle = 300~360
{ PORTD &= ~(0b00001110); // 1, 2, 3번 비트를 0으로 만듦
PORTD |= (0b00000100); // 2번 비트(VB)만 1로 설정
PORTB &= ~(0b11100000); // 상위 스위치 클리어
TCCR1A = 0x82; // OC1A 핀에서 PWM 출력 (UT)
}
}
}
// 모터 방향 변경 시 회전속도 서서히 줄어드는 루틴
void gradual_stop(void){
while (count > 100){
if (count > 90){
count -= 20; // count를 10씩 감소
} else{
count = 100; // 이하로 내려가면 모터 정지
}
OCR1A = count;
OCR1B = count;
OCR1CL = count;
LCD_control(count);
delay_ms(900); // 서서히 감소시키는 효과
}
}
// 모터 방향 변경 시 회전속도 서서히 증가하는 루틴
void gradual_start(u08 target_duty){
while (count < target_duty){
if (count + 20 <= target_duty){
count += 20; // count를 10씩 증가
} else {
count = target_duty; // 목표 듀티 비율에 도달하면 멈춤
}
OCR1A = count;
OCR1B = count;
OCR1CL = count;
LCD_control(count);
delay_ms(900); // 서서히 증가시키는 효과
}
}
// 전류제어를 위해 ADC 루틴
#define ADC_VREF_TYPE 0x20 //AD0 INPUT
unsigned char read_adc(unsigned char adc_input)
{
ADMUX = adc_input | (ADC_VREF_TYPE & 0xff);
delay_us(10);
ADCSRA |= 0x40; // START THE AD CONVERSION
while((ADCSRA & 0x10)==0); // Wait for the AD CONVERSION TO COMPLETE
ADCSRA |= 0x10;
return ADCH;
}
// LCD에 모터 회전방향, PWM duty, RPM 표시하는 루틴
void LCD_control(u08 x){
u08 o,p,q,r,s,t;
s16 duty_ratio;
char direction_str[4];
if (direction == '+') {
strcpy(direction_str, "CW ");
} else {
strcpy(direction_str, "CCW");
}
duty_ratio = x*0.39; //100/255 , 듀티비율 변환 후 lcd에 표시
o = 0x30 + duty_ratio / 10; // 십의 자리
p = 0x30 + duty_ratio % 10; // 일의 자리
lcd_control_write(0x80); // 첫 번째 라인으로 이동
lcd_print_data(direction_str, 3); // CW 또는 CCW 출력
lcd_data_write(o); // 듀티 비율 십의 자리
lcd_data_write(p); // 듀티 비율 일의 자리
lcd_data_write('%'); // 퍼센트 기호 출력
lcd_print_data(" R",2);
lcd_control_write(0xC0);
lcd_print_data("PM",2);
q = 0x30 + (rpm / 1000); // 천의 자리
r = 0x30 + ((rpm % 1000) / 100); // 백의 자리
s = 0x30 + ((rpm % 100) / 10); // 십의 자리
t = 0x30 + rpm % 10; // 일의 자리
lcd_data_write(q); // rpm 천의 자리
lcd_data_write(r); // rpm 백의 자리
lcd_data_write(s); // rpm 십의 자리
lcd_data_write(t); // rpm 일의 자리
}
void main(void){
u08 old_key,new_key;
DDRA = 0x00;
PORTA = 0x00; //LCD DATA PORT
DDRB = 0b11100000;
PORTB = 0b00001111; // UT = VT = WT = 0
DDRD = 0b00001110;
PORTD = 0b00000000; // UB = VB = WB = 0
DDRC = 0xff;
PORTC =0x00; //BCD KEY INPUT AND LCD CONTROL PORT
DDRE = 0x00;
DDRF = 0x00;
DDRG = 0xFF;
PORTG = 0x00; //BUZZER AND RELAY CONTROL PORT
//************타이머 및 인터럽트 초기화 ***********************
TCCR1A = 0x02; //Timer1 = fast PWM mode(14)
TCCR1B = 0x19; //dont output OC1A, OC1B, OC1C
TCCR1C = 0x00; // 16MHz/1/(1+532) = 30kHz
ICR1 = 532;
OCR1A = 0;
OCR1B = 0;
OCR1CH = 0;
OCR1CL = 0;
TCCR2 = (1 << WGM21) |(0 << WGM20) | (1 << CS22)| (1 << CS21) | (1 << CS20); // CTC 모드, 프리스케일러 1024
OCR2 = 156; // 100ms 주기로 인터럽트 발생 (16MHz 클럭 기준)
TIMSK |= (1 << OCIE2); // 비교 일치 인터럽트 활성화
TCNT2 = 0x00;
TCCR3A = 0x00; // Timer3 = CTC mode(4), dont output OC3A
TCCR3B = 0x0C; // 16MHz/256/(1+2499) = 25Hz
TCCR3C = 0x00;
OCR3AH = 9;
OCR3AL = 195; // OCR3A = 2499;
TCNT3H = 0x00; // clear timer/counter3
TCNT3L = 0x00;
ETIFR = 0x10; // clear OC3A interrupt flag
ETIMSK = 0x10; // enable OC3A interrupt
DDRE = 0x00; // initialize PORTE
EICRB = 0x55; // INT4/5/6 = rising and falling edge
EIFR = 0x70;
EIMSK = 0x70;
ACSR = 0x80; //아날로그 비교기 비활성화
SFIOR = 0x00; // ADC 수동변환, 자동트리거 비활성화
ADMUX = ADC_VREF_TYPE & 0xff;
ADCSRA = 0x87; // enable ADC, 수동 변환 모드, 분주비 128(125kHz)
//**********************************************
lcd_init();
lcd_clear();
lcd_home();
lcd_control_write(0x80);
lcd_print_data("SUHYEON ",8);
delay_ms(500);
lcd_control_write(0xC0);
lcd_print_data("BLDC CON",8);
delay_ms(3000);
lcd_home();
lcd_clear();
#asm("sei")
count = 100;
BUZ_RLY = 0x00;
old_key = 0;
//****************게이트 드라이브 신호 초기화 *****************
DDRB = 0xE0;
PORTB &= ~0xE0; // UT = VT = WT = 0
DDRD = 0x0E;
PORTD = 0x00; // UB = VB = WB = 0
// 부트스트랩 커패시터 충전 UB = VB = WB = 1, UT = VT = WT = 0
PORTD |= 0x0E;
delay_ms(10);
PORTD = 0x00;
motor_rotate();
//*********************** 주 제어루틴 ********************
while(1){
new_key = PINB & 0x0F;
if(new_key!=old_key){
switch(new_key){
case START:
motor_running = !motor_running;
if(motor_running){
#asm("sei")
motor_rotate();
OCR1A = count;
OCR1B = count;
OCR1CL = count;
}else{
#asm("cli")
TCCR1A = 0x02;
PORTB &= ~0xE0;
PORTD = 0x00;
}
LCD_control(count);
break;
case DIRECTION:
if (!CW_pressed) {
CW_pressed = 1; // 스위치 눌림 상태 처리
prev_count = count; // 현재 듀티 비율을 저장
gradual_stop(); // 듀티 비를 서서히 감소시키며 정지
// 방향 변경
#asm("cli")
direction = (direction == '+') ? '-' : '+'; // 방향을 토글
// 방향에 따라 모터 재설정
#asm("sei")
motor_rotate();
}
gradual_start(prev_count); // 저장된 듀티 비율로 서서히 시작
CW_pressed = 0;
LCD_control(count);
break;
case UP:
if(count>=250) count = 250;
else count = count + 10;
OCR1A = count;
OCR1B = count;
OCR1CL = count;
LCD_control(count);
break;
case DOWN:
if(count<=100) count =100;
else count = count -10;
OCR1A = count;
OCR1B = count;
OCR1CL = count;
LCD_control(count);
break;
}
delay_ms(200);
}
LCD_control(count);
old_key = new_key;
}
}
댓글 3
조회수 55master님의 댓글
master 작성일
OCR2 = 156; // 100ms 주기로 인터럽트 발생 (16MHz 클럭 기준)
8비트 타이머로 100ms 주기가 가능한가요?
16.384ms가 한계 아닌가요?
master님의 댓글
master 작성일
질문이 여러가지라서 답변이 어려우니 원론적인 설명만 해드리겠습니다.
//
스텝모터와 BLDC모터는 원리상 동일합니다.
스텝모터는 2상, BLDC모터는 3상으로 조금 복잡할 뿐입니다.
여기에 속도 가감속하는 방법이 조금다릅니다.
스텝모터는 스텝딜레이를 변경해서 속도를 가감속하지만 BLDC는 PWM 듀티로 가감속을 하며, 모터 위치를 파악해서 다음스텝의 시작위치를 잡습니다.(홀 센서가 있다면 홀센서를 활용하겠죠)
BLDC모터도 스텝모터처럼 센서위치 무시하고 딜레이로 구동이 가능합니다.
물론 스타트 매끄럽지 못해서 손으로 돌려주기도 합니다만
예제를 만들 당시에는 매끄러운 스타트가 중요하지 않아서 신경쓰지 않았습니다.
https://cafe.naver.com/circuitsmanual/121156
https://cafe.naver.com/circuitsmanual/133992
상기 2개의 강좌글도 참고하세요
master님의 댓글
master 작성일
rpm을 계산하기 위한 방법에서
펄스 개수는 코드에서 제어하니 정확히 알 수 있습니다.
문제는 시간인데요
여유가 있다면 8비트 타이머보다는 16비트 타이머를 고려해보세요
8비트 밖에 없다면 타이머인터럽트 안에서 변수를 사용해서 카운트하면 됩니다.
//
인터럽트 안에서는 가급적 딜레이를 사용하지 않는 것이 좋으며
시간이 많이 걸리는 연산도 하지 않는 것이 좋습니다.