BASIC4MCU | 질문게시판 | pwm 출력과 ad conversion 과정
페이지 정보
작성자 412904 작성일2022-05-05 20:05 조회8,054회 댓글18건본문
안녕하세요, 가변저항을 사용해 AD 변환값에 따라 PWM 듀티를 가변시키는 코드를 작성 중인데 질문 몇 가지 남겨봅니다.
현재 스위칭 주파수는 5kHz이고 오버플로우 인터럽트 사용하고 있습니다.
따라서 오버플로우 인터럽트 서비스 루틴을 수행하는 시간은 0.2ms이고 오버플로우가 발생했을 때 AD 변환을 수행하도록 코드를 작성했습니다.
여기서 두 가지정도 궁금한 점이 있습니다.
Q1) AD 변환에도 수 마이크로초에서 수백 마이크로초가 소요된다는 것을 데이터시트에서 보았는데,
여기서 스위칭주파수를 더 높이고 싶다면 성능이 더 좋은 DSP를 사용하는 것이 맞을까요?
Q2) 오버플로우가 발생했을 때 AD 변환을 수행하도록 코드를 작성했다면, 오버플로우가 발생해서 초기화된 카운터 값이 AD 변환을 수행하면서 같이 올라가나요, 변환이 다 끝나고 카운터값이 올라가나요?
이 부분은 글로만 보면 이해가 가지 않을 수도 있을 것 같아 이미지 사진을 첨부하도록 하겠습니다.
* 만약 전자라면, AD 변환하는 데에도 클럭이 소모되어 OCR 값이 제대로 출력이 안 될 수도 있다는 생각이 들었습니다.
근데 또 생각해보면 OCR 업데이트 지점을 TOP으로 설정해주면 그 다음 캐리어 주파수에서 반영이 될 수도 있겠구나 라는 생각도 듭니다.
무엇이 맞는 걸까요?
댓글 18
조회수 8,054master님의 댓글
master 작성일
mcu 디바이스가 뭔가요?
전체 소스코드를 첨부해서 질문하셔야 이해를 하는데 도움이 될 것 같습니다.
AVR경우 OCR값이 적용되는 시점은 데이터시트에 나와있습니다.
412904님의 댓글
412904
현재 사용하고 있는 MCU는 ATmega128입니다.
master님의 댓글
master
전체 소스코드를 텍스트로 첨부해보세요
412904님의 댓글
412904
간단하게 작성한 코드 첨부하겠습니다
#define F_CPU 16000000UL
#define k 3.1250 // the ratio of ICR1 to c(ADCW)
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
void Pwm_Generator(void);
void ADC_init(void);
unsigned int c;
ISR(TIMER1_OVF_vect) // execution this loop every 0.2ms -> switching frequency=5kHz
{
ADCSRA|=(0x01<<ADSC); // AD conversion start
c=ADCW;
OCR1A=c*k; // k=3.125
ICR1=3199; // 16MHz -> 62.5(ns/clk) -> 62.5ns*3200=0.2ms -> 5kHz
}
int main(void)
{
//// i/o settings ////
DDRB=0xff; // PWM output(using OC1A(PB5))
PORTB=0x00; // initialization
//// function call ////
cli();
Pwm_Generator();
ADC_init();
sei();
/* Replace with your application code */
while (1)
{
}
}
void Pwm_Generator(void)
{
TCCR1A=(0x01<<COM1A1)|(0x01<<COM1A0)|(0x01<<WGM11)|(0x00<<WGM10); // INV mode
TCCR1B=(0x01<<WGM13)|(0x01<<WGM12)|(0x00<<CS12)|(0x00<<CS11)|(0x01<<CS10); // prescale:1 WGM13:10=1110(Fast PWM)
TCCR1C=0x00;
TCNT1=0;
ICR1=3199; // 0.0000000625*3200 = 0.0002 -> 0.2ms
TIMSK=(0x01<<TOIE1); // overflow interrupt enable
//TIFR=0xff;
}
void ADC_init(void)
{
ADMUX=(0x00<<REFS0)|(0x00<<MUX0); // ADC0
ADCSRA=(0x01<<ADEN)|(0x00<<ADSC)|(0x00<<ADFR)|(0x01<<ADPS2)|(0x00<<ADPS1)|(0x00<<ADPS0); // AD Conversion start
}
master님의 댓글
master 작성일
Q1) AD 변환에도 수 마이크로초에서 수백 마이크로초가 소요된다는 것을 데이터시트에서 보았는데,
여기서 스위칭주파수를 더 높이고 싶다면 성능이 더 좋은 DSP를 사용하는 것이 맞을까요?
PWM주파수를 늘리고 싶다는 말인가요?
5KHz의 주기는 200us로 가장 느린 ADC 샘플링 주기보다 더 느립니다.
ADC 분주비를 변경하면 ADC 변환속도는 아주 짧게 변경이 가능합니다.
Q2) 오버플로우가 발생했을 때 AD 변환을 수행하도록 코드를 작성했다면, 오버플로우가 발생해서 초기화된 카운터 값이 AD 변환을 수행하면서 같이 올라가나요, 변환이 다 끝나고 카운터값이 올라가나요?
타이머 카운터와 ADC변환은 각각 독립적으로 실행 됩니다.
master님의 댓글
master 작성일
OCR1A=c*k; // k=3.125
실수 연산은 시간이 제법 걸립니다.
가장 긴 시간은 실수의 나누기입니다.
AVRStudio 시뮬레이션으로 실수 곱하기에 관한 시간을 측정 해보세요
인터럽트 주기는 이 시간보다 조금 더 길어야 합니다.
master님의 댓글
master 작성일
OCR1A=c*k; // k=3.125
여기서 3.125는
2 + 1 + 1/8
OCR1A=(c<<1)+c+(c>>3);
이렇게 연산하는 것이 더 빠를겁니다.
master님의 댓글
master 작성일
ICR1=3199; // 16MHz -> 62.5(ns/clk) -> 62.5ns*3200=0.2ms -> 5kHz
동일한 설정을 인터럽트에서 매번 해 줄 필요가 없겠죠?
Pwm_Generator() 함수에서 이미 하고 있으므로 인터럽트에 있는 코드는 삭제하세요
master님의 댓글
master 작성일
#define F_CPU 16000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
//
ISR(TIMER1_OVF_vect){ // 0.2ms -> 5kHz
unsigned int c;
c=ADCW;
OCR1A=(c<<1)+c+(c>>3); // k=3.125
}
//
int main(void){
DDRB=0x20; // PWM output(using OC1A(PB5))
ADCSRA=0xE4;
TCCR1A=(1<<COM1A1)|(1<<COM1A0)|(1<<WGM11); // INV mode
TCCR1B=(1<<WGM13)|(1<<WGM12)|(0x01<<CS10); // prescale:1 WGM13:10=1110(Fast PWM)
ICR1=3199; // 0.0000000625*3200 = 0.0002 -> 0.2ms
TIMSK=(1<<TOIE1);
SREG=0x80;
while(1){}
}
412904님의 댓글
412904
메인 문에서 프리러닝모드로 실행하고 인터럽트 서비스 루틴에서 변환 값만 저장한 뒤 그 다음 연산을 하는군요. 시프트 연산도 인상 깊네요. 정말 감사드립니다.
master님의 댓글
master
AVRStudio 시뮬레이션에서 인터럽트 함수 코드 실행 시간을 측정하세요
이 시간이 가장 중요합니다.
예를들어서 20us가 나왔다고 가정해보죠
인터럽트 자체의 실행에 짧은 시간이 추가되어야 하며
1~2us를 넘지 않습니다.
그렇다면 여유를 줘서 25us 주기로 보고 최대 40KHz가 가능해집니다.
//
ADC분주비 계산식을 알아야 합니다.
128분주가 104us인데
16000000/128=125000Hz가 ADC클럭이고, ADC에는 13클럭(25클럭)이 샘플링 시간이므로
125000Hz/13=9615.384615384615Hz=104us
질문코드에서는 16분주이므로 샘플링 시간은 13us 입니다.
ADC는 건드릴 것이 없군요
//
PWM을 40KHz로 높일 수 있는지 체크 해보세요
TCCR1B=(1<<WGM13)|(1<<WGM12)|(1<<CS10); // prescale:1 WGM13:10=1110(Fast PWM)
분주비 1 이므로 분주비를 건드릴 것은 없습니다.
ICR1=399; // 0.0000000625*400 = 0.000025 -> 25us
ICR을 변경하면 8배 빠르게 주기를 변경 할 수 있지만
분해능도 1/8로 낮아집니다.
master님의 댓글
master 작성일
지금보다 3배 정도 짧게 주기를 변경해도 충분하다면
ICR1=1023; // 0.0000000625*1024 = 0.000064 -> 64us
//
ADC 샘플링은 위에서 계산했으므로 충분한 것은 확인했고
16분주 대신 32분주로 변경해도 됩니다
ADCSRA=0xE5;
//
ISR(TIMER1_OVF_vect){ // 64us -> 15.625kHz
OCR1A=ADCW;
}
분해능이 10비트이므로 인터럽트 함수 안의 코드는 간단해지죠
//
OCR1A(ICR1) 분해능이 아무리 높아봤자 ADC값으로 PWM듀티를 만드는 것이므로
10비트가 한계입니다.
따라서 ICR1 및 OCR1A를 10비트로 설정해서 ADC값을 직접 넣으면 분해능은 동일합니다.
연산이 필요없어지므로 코드 실행 시간은 굉장히 짧아지죠
master님의 댓글
master 작성일
k=3.125
위 댓글에서 정확하게 3.125배 주기가 짧아졌는데요
여기에 추가로 1/2로 주기를 더 줄인다면
ICR1=511; // 0.0000000625*512 = 0.000032 -> 32us
//
ADC 샘플링은 16분주로 다시 변경합니다.
ADCSRA=0xE4;
//
ISR(TIMER1_OVF_vect){ // 31.250KHz
OCR1A=ADCW>>1; // 9비트 저장 (0~511)
}
9비트로 분해능은 1/2로 줄어들게 됩니다.
몇개의 댓글로 방법을 설명드렸으니
8비트로 변경해서 주파수를 62KHz로 높일 수도 있겠죠
412904님의 댓글
412904
정성스러운 답변 정말정말 감사드립니다.
아직 비기너 수준이라 설명해주신 것을 완벽하게 이해하지는 못했으나
제가 이해한 바로는 AD 변환 시간과 인터럽트 함수 실행 시간 등이 인터럽트 발생 주기보다 빠르면 안 된다고 이해했는데,
제가 이해한 것이 맞나요??
예를 들어, ICR 값이 399일 때 399클럭이 소모된다고 가정하면,
인터럽트는 매 400클럭마다 발생할 것인데, AD 변환에서 소모되는 클럭과 인터럽트 실행할 때 소모되는 클럭이 400클럭을 넘게 되면
문제가 될 것이라고 이해했습니다.
master님의 댓글
master
0~399 는 400 단계입니다.
인터럽트 함수의 코드 실행 시간 <-- 이 시간이 인터럽트 주기보다 길게 되면 메인함수 무한루프의 코드가 있는 경우 심각한 문제가 발생할 수 있습니다.(메인함수가 동작 안함)
ADC샘플링 주기는 큰 문제는 없습니다.
매주기 새로운 ADC값을 PWM 출력하느냐
ADC 두번째 변환된 값을 PWM 출력하느냐
같은 ADC값을 두번씩 PWM 출력하느냐
정도의 문제죠
412904님의 댓글
412904
아, 그리고 말씀하신 인터럽트 함수 실행 시간을 측정해 보라고 하셨는데,
AVR스튜디오 내에 Simulator를 이용해서 측정해 본 결과
인터럽트 실행 시간은 81클럭이 소모되더군요. 제가 한 것이 맞는지는 모르겠습니다. 이 기능이 있다는 것도 덕분에 오늘 처음 알았거든요.
master님의 댓글
master
실수 타입의 나누기 연산이 가장 오래 걸립니다.
412904님의 댓글
412904
감사합니다. 형편없는 코드임에도 많은 걸 알아가네요.