PWM своими руками
- Войдите на сайт для отправки комментариев
Изучая второе занятие с Arduino поставил себе задачу сделать PWM без использования analogWrite. Ну, почти получилось, я может быть ещё не все моменты понял, но результат, на мой взгляд, заметно лучше. Во-первых, разрешение в области малых значений лучше, во вторых, можно получить устойчивую светимость светодиода (мой индикатор) при меньшей яркости.
Иллюстрация.
void setup_timer1 () {
noInterrupts();
// disable all interrupts
TCCR1A = 0;
TCCR1B = 0;
TCNT1 = 0;
OCR1A = 1; // compare match register 16MHz/256/2Hz
TCCR1B |= (1 << WGM12); // CTC mode
TCCR1B |= (1 << CS10)|(1 << CS11); // 64 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts();
}
Это установка таймера. Я, надо признаться, не совсем понимаю, срабатывает он каждый тик или каждый второй тик. Прошу комментария на эту тему.
int led = 9;
void setup() {
pinMode(led, OUTPUT);
setup_timer1();
}
Светиться будет светодиод на 9-м пине. Сделал так, чтобы было проще сравнить с тем, что делает analogWrite.
byte current_state = 0;
/* 1.25 microsecs */
static inline void ledWriteB(byte pin) {
pin -= 8;
byte pins = PORTB;
byte old_pins = pins;
if (current_state & 1) {
pins |= (HIGH << pin);
--current_state;
} else if (current_state & 2) {
pins &= ~(HIGH << pin);
current_state -= 2;
}
if (old_pins != pins)
PORTB = pins;
}
Несколько оптимизированный вариант digitalWrite, по моим замерам, довольно быстрый. Совмещено с логикой распределения импульсов 1 и 0. Если в 0м бите 1, то выдать 1, если в 1м бите 1, то надо выдать 0. Биты устанавливаются специальной логикой так, чтобы уровень 0.5 выдавал всегда 1 и 0 подряд, равномерно, если уровень выше, то 1-цы будут идти соответственно чаще, но равномерно.
typedef struct _int_bresenham {
int ante, conseq;
int ante_err, conseq_err;
} int_bresenham;
/* 1.2 microsecs */
static inline byte dither_bresenham(int_bresenham *br) {
byte ret = 0;
if (br->ante_err < br->ante && br->conseq_err < br->conseq) {
br->conseq_err += br->ante;
br->ante_err += br->conseq;
}
if (br->ante <= br->ante_err) {
++ret;
br->ante_err -= br->ante;
}
if (br->conseq <= br->conseq_err) {
ret |= 2;
br->conseq_err -= br->conseq;
}
return ret;
}
Это собственно логика, распределяющая переходы равномерно в заданном соотношении. Авторство моё :) Идея позаимствована у Брезенхема из целочисленного алгоритма рисования линий.
void loop() {
short pot_value = analogRead(A0);
// analogWrite(led, pot_value / 4);
current_br.ante = 1023 - pot_value;
current_br.conseq = pot_value;
}
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
if (current_state == 0) {
current_state = dither_bresenham(¤t_br);
}
ledWriteB(led);
}
Тут происходит действие -- считываются показания потенциометра, которые используются для расчёта коэффициентов. В прерывании таймера вычисляется соотношение 1 и 0 на следующий шаг и подсвечивается светодиод нужным уровнем яркости.
Интересует мнение опытного народа о потобных извратах, имеет ли это когда либо смысл или я просто чего-то не знаю? Я всего лишь второй день с новой игрушкой, никогда раньше не имел дел с микроконтроллерами.
Вы изобретаете велосипед : ) http://arduino.ru/forum/programmirovanie/analogwrite-na-lyubom-vyvode
Вы изобретаете велосипед : ) http://arduino.ru/forum/programmirovanie/analogwrite-na-lyubom-vyvode
Файл библиотеки лежит на таком хостинге, с которого у меня не получается скачать. Нажимаю "скачать", а мне в ответ "http://download73.files.attachmail.ru/DA49F37530AB4AED8D38E2670A7CA509/865449855ff0d45e8b066fdd4a59a1f2/PWM.rar не найден". Поэтому, мне не удалось посмотреть что же там внутри, было интересно сравнить реализации. Собственно, я ж начинающий, это моя обязанность -- изобрести велосипеды самостоятельно. Главное, чтобы колёса квадратными не оказались -- моя тема об этом.
Там совсем другая история...
Вы используете аппаратный ШИМ вам сюда.
void setup() { cli(); DDRB |= 1<<1 | 1<<2; PORTB &= ~(1<<1 | 1<<2); TCCR1A = 0b00000010; //TCCR1A = 0b10100010; TCCR1B = 0b00011001; ICR1H = 255; ICR1L = 255; sei(); Serial.begin(9600); Serial.println("PWM resolution: 16-bit"); Serial.println("PWM frequency: 244 Hz"); Serial.println("PWM duty OC1A(D9): 0"); Serial.println("PWM duty OC1B(D10): 0"); Serial.println(); } void loop() { if(Serial.available()) { char c = Serial.read(); uint16_t pwm = Serial.parseInt(); if(c == 'A') { pwm ? TCCR1A|=1<<7 : TCCR1A&=~(1<<7); OCR1AH = highByte(pwm); OCR1AL = lowByte(pwm); Serial.print("PWM duty OC1A(D9): "); Serial.println(pwm); } if(c == 'B') { pwm ? TCCR1A|=1<<5 : TCCR1A&=~(1<<5); OCR1BH = highByte(pwm); OCR1BL = lowByte(pwm); Serial.print("PWM duty OC1B(D10): "); Serial.println(pwm); } if(c == 'R') { Serial.println(); Serial.print("PWM resolution: "); Serial.print(pwm); Serial.println("-bit"); pwm = pow(2, pwm); ICR1H = highByte(pwm); ICR1L = lowByte(pwm); Serial.print("PWM frequency: "); Serial.print(16000000/(pwm+1UL)); Serial.println(" Hz"); Serial.println(); } } }Как реализован программный ШИМ (то что в архиве) написано в сообщении #30.
Там совсем другая история...
Вы используете аппаратный ШИМ вам сюда.
Да неправда, у меня самый настоящий програмный PWM.
Как реализован программный ШИМ (то что в архиве) написано в сообщении #30.
Я, к сожалению, плохо (т.е. никак) знаю ассемблер AVR, можешь объяснить что делает CHECK словами? Мой последний чем-то похожий ассемблер, который я знал, был Z80, на котором я писал года так 22 назад.
Как реализован программный ШИМ (то что в архиве) написано в сообщении #30.
Хочу задать вопрос о выборе прескейл значения. Если я правильно прочитал, то у тебя 256, правильно? Т.е. частота таймера у тебя будет будет 16мгц/256/1 = 62500, правильно? Быстрее просто не успевает обработчик прерывания?
Добавлю иллюстрацию использования метода. Это мой третий урок по Ардуино, подключен RGB LED и переменный резистор (потенциометер). Три разных цвета RGB зажигаются-гаснут по синусоиде, расхождение угла между ними задаётся положением резистора.
#include <Arduino.h> #include "HardwareSerial.h" typedef struct _int_bresenham { short ante, conseq; short ante_err, conseq_err; } int_bresenham; /* 1.2 microsecs */ static inline byte dither_bresenham(int_bresenham *br) { byte ret; if (br->ante == 0) return 1; if (br->conseq == 0) return 2; ret = 0; if (br->ante_err < br->ante && br->conseq_err < br->conseq) { br->conseq_err += br->ante; br->ante_err += br->conseq; } if (br->ante <= br->ante_err) { ++ret; br->ante_err -= br->ante; } if (br->conseq <= br->conseq_err) { ret |= 2; br->conseq_err -= br->conseq; } return ret; } // the setup routine runs once when you press reset: void setup_timer1() { noInterrupts(); // disable all interrupts TCCR1A = 0; TCCR1B = 0; TCNT1 = 0; OCR1A = 1; TCCR1B |= (1 << WGM12); // CTC mode // TCCR1B |= (1 << CS10) | (1 << CS11); // 64 prescaler TCCR1B |= (1 << CS12); // 256 prescaler TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt interrupts(); } typedef struct _pwm_state { int_bresenham br; byte state; } t_pwm_state; t_pwm_state pwm_state[14]; void setup() { // initialize the digital pin as an output. for (byte pcnt = 11; pcnt < 14; ++pcnt) pinMode(pcnt, OUTPUT); setup_timer1(); for (int i = 0; i < 13; ++i) { pwm_state[i].br.ante = pwm_state[i].br.ante_err = pwm_state[i].br.conseq = pwm_state[i].br .conseq_err = 0; pwm_state[i].state = 0; } // Serial.begin(115200); } int tbl_sinus_scale = 255; byte tbl_sinus[] = { 0, 4, 9, 13, 18, 22, 27, 31, 35, 40, 44, 49, 53, 57, 62, 66, 70, 75, 79, 83, 87, 91, 96, 100, 104, 108, 112, 116, 120, 124, 127, 131, 135, 139, 143, 146, 150, 153, 157, 160, 164, 167, 171, 174, 177, 180, 183, 186, 190, 192, 195, 198, 201, 204, 206, 209, 211, 214, 216, 219, 221, 223, 225, 227, 229, 231, 233, 235, 236, 238, 240, 241, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 253, 254, 254, 254, 255, 255, 255, 255 }; short irsinus(int grad) { short ret; int ag; grad %= 360; if (grad < 0) grad += 360; ag = grad % 180; if (ag > 90) ag = 180 - ag; ret = tbl_sinus[ag]; if (grad > 180) ret = -ret; return ret; } /* 1.25 microsecs */ static inline void maskWriteB(byte set, byte mask) { byte state = PORTB; byte old_state = state; state &= ~mask; state |= set; if (old_state != state) PORTB = state; } int current_grad = 1; unsigned long next_turn = 0; void loop() { short pot_value = analogRead(A0); unsigned long time = millis(); if (time > next_turn) { ++current_grad; if (current_grad >= 360) current_grad -= 360; short cs = irsinus(current_grad); if (cs < 0) cs = -cs; pwm_state[11].br.ante = cs; pwm_state[11].br.conseq = tbl_sinus_scale - cs; cs = irsinus(current_grad + pot_value / 4); if (cs < 0) cs = -cs; pwm_state[12].br.ante = cs; pwm_state[12].br.conseq = tbl_sinus_scale - cs; cs = irsinus(current_grad + pot_value / 2); if (cs < 0) cs = -cs; pwm_state[13].br.ante = cs; pwm_state[13].br.conseq = tbl_sinus_scale - cs; next_turn = time + 10; } } ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine { byte set = 0; for (byte pcnt = 11; pcnt < 14; ++pcnt) { #define bpin (pcnt-8) #define cpwm pwm_state[pcnt] if (cpwm.state == 0) { cpwm.state = dither_bresenham(&cpwm.br); } if (cpwm.state & 1) { set |= (HIGH << bpin); --cpwm.state; } else if (cpwm.state & 2) { cpwm.state -= 2; } } maskWriteB(set, 0b00111000); } int main(void) { init(); setup(); //endless loop for (;;) { loop(); } return 0; }Да неправда, у меня самый настоящий програмный PWM.
Да, наверное, не вчитывался... где то промелькнуло про FastPWM вот и подумал...
что делает CHECK словами?
Хочу задать вопрос о выборе прескейл значения. Если я правильно прочитал, то у тебя 256, правильно? Т.е. частота таймера у тебя будет будет 16мгц/256/1 = 62500, правильно? Быстрее просто не успевает обработчик прерывания?
Будет ли успевать обработчик или нет зависит от количества задействованных выводов, измерений не проводил, так что при разных прескалерах и количестве выводов какая реальная частота ШИМ не могу сказать.
Да собственно сравнивает pwms[0] с pwm и устанавливает или сбрасывает бит порта.
Логика действия ускользнула. Можно рассказать на пальцах?
Будет ли успевать обработчик или нет зависит от количества задействованных выводов, измерений не проводил, так что при разных прескалерах и количестве выводов какая реальная частота ШИМ не могу сказать.
Это какой-то другой даташит, не от Arduino Uno. В том, что я читал, были CS12-CS10.
Да собственно сравнивает pwms[0] с pwm и устанавливает или сбрасывает бит порта.
Логика действия ускользнула. Можно рассказать на пальцах?
Если интересно, я могу рассказать логику действий dither_bresenham, если по коду непонятно. В этом исполнении разрядность PWM динамическая и плавность по большей части зависит только от быстродействия.
Это какой-то другой даташит, не от Arduino Uno. В том, что я читал, были CS12-CS10.
Сорри, осознал, что мы про разные таймеры говорим. Я про таймер1, а ты про таймер2.
Последняя версия исправленного и оптимизированного dither_bresenham
typedef struct _int_bresenham { byte ante, conseq; byte err; } int_bresenham; static inline byte dither_bresenham(int_bresenham *br) { byte ret; int_bresenham brl; brl.ante = br->ante; if (brl.ante == 0) return 1; brl.conseq = br->conseq; if (brl.conseq == 0) return 2; if (brl.ante == brl.conseq) return 3; brl.err = br->err; if (brl.ante > brl.conseq) { brl.err += brl.conseq; if (brl.err >= brl.ante) { ret = 3; brl.err -= brl.ante; } else { ret = 2; } } else { brl.err += brl.ante; if (brl.err >= brl.conseq) { brl.err -= brl.conseq; ret = 3; } else { ret = 1; } } br->err = brl.err; return ret; }Наконец-то зачитал популярную статейку о ШИМ, понял как работает код у maksim. Задумался...
Моя реализация алгоритма Брезенхема тут.
Видео демо тут.
Собирал термостат с ПИД регулированием.
оно на любой ардуинке будет работать? Или зависит от камушка?
оно на любой ардуинке будет работать? Или зависит от камушка?
Алгоритм Брезенхема на любой! Или Вы не об этом?
я про прямое указание портов в скетче.
У одного класса ардуин как правило да. Вообще, скетч вот, просто попробуй на той, что тебя интересует: https://github.com/jabbervorx/arduino-rgb-lesson/blob/master/src/wave-rgb.cpp
я про прямое указание портов в скетче.
для Arduino Uno можно вот моё взять: https://github.com/jabbervorx/arduino-any-pc-pwm/blob/master/include/digitalPortsFast.h
если нужно универсально, то есть другие, очень похожие по смыслу варианты. более полные библиотеки попадались, но мне хватало моей.