аналоговый выход
- Войдите на сайт для отправки комментариев
Приветствую. Сегодня первый день как начал играться с Arduino UNO, опыта в программировании контроллеров у меня раньше не было. Начал с первого урока -- моргающий светодиод. Усложнил задачу, чтобы диод стал медленно гаснуть и загораться без использования analogWrite. Не понравилась идея 490 Гц, да и вообще, в учёбе не должно быть лёгких путей. Помучившись изрядно, добился результата, теперь хотелось бы услышать комментарии и ответы на возникшие вопросы. Код немного длинноват, поскольку писался в Eclipse, а не в стандартной среде.
Основные вопросы у меня по скорости работы разных участков. Например, как я понимаю, при OCR1A = 1 и TCCR1B |= (1 << CS10) -- прерывание таймера должно работать с максимальной скоростью, но я, признаться, так и не понял с какой именно. Увеличивая счётчики прескейлинга или OCR1A, скрость падает, но как-то не так линейно как я ожидал.
В программе не использовал плавающую точку нигде, все функции на первый взгляд кажутся почти оптимильными. Никак не мог понять что происходит, пока не перенёс запись в pin13 внутрь обработчика прерывания, в связи с этим второй вопрос -- не съел ли мой таймерный обаботчик всё быстродействие, что на loop() уже почти ничего не осталось?
Проблема в том, что если в loop не использовать delay, а сделать эту логику через millis, то переменная current_grad перестаёт увеличиваться совсем -- это совсем не удаётся понять. В итоге сделал увеличение current_grad безусловно и визуально скорость смены яркости вполне комфортная.
Логика аналогового выхода в том, что уровни HIGH и LOW распределяются равномерно и при этом пропорционально нужному коэффициенту.
#include <Arduino.h>
#include <avr/io.h>
#include "HardwareSerial.h"
typedef struct _int_ratio {
int ante, conseq;
} int_ratio;
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 };
int_ratio irsinus(int grad) {
int_ratio ret;
int ag;
grad %= 360;
if (grad < 0)
grad += 360;
ag = grad % 180;
if (ag > 90)
ag = 180 - ag;
ret.ante = tbl_sinus[ag];
ret.conseq = tbl_sinus_scale - ret.ante;
if (grad > 180)
ret.ante = -ret.ante;
return ret;
}
typedef struct _int_bresenham {
int ante, conseq;
int ante_err, conseq_err;
} int_bresenham;
int_ratio dither_bresenham(int_bresenham *br) {
int_ratio ret = { 0, 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_err >= br->ante) {
++ret.ante;
br->ante_err -= br->ante;
}
if (br->conseq_err >= br->conseq) {
++ret.conseq;
br->conseq_err -= br->conseq;
}
return ret;
}
int led = 13;
// the setup routine runs once when you press reset:
void setup() {
// initialize the digital pin as an output.
pinMode(led, OUTPUT);
// initialize 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 prescaler
TIMSK1 |= (1 << OCIE1A); // enable timer compare interrupt
interrupts();
// enable all interrupts
}
int current_grad = 1;
unsigned long next_turn = 0;
int_ratio current_state = { 0, 0 };
int_bresenham current_br = { 0, 0, 0, 0 };
void loop() {
unsigned long time = millis();
// if (time > next_turn) {
++current_grad;
if (current_grad >= 360)
current_grad -= 360;
int_ratio cs = irsinus(current_grad);
if (cs.ante < 0)
cs.ante = -current_state.ante;
current_br.ante = cs.ante;
current_br.conseq = cs.conseq;
current_br.ante_err = current_br.conseq_err = 0;
next_turn = time + 10;
// }
}
ISR(TIMER1_COMPA_vect) // timer compare interrupt service routine
{
int led_level = HIGH;
if (current_state.ante == 0 && current_state.conseq == 0) {
current_state = dither_bresenham(¤t_br);
}
if (current_state.ante > 0) {
led_level = HIGH;
--current_state.ante;
} else if (current_state.conseq > 0) {
led_level = LOW;
--current_state.conseq;
}
digitalWrite(led, led_level); // включаем LED
}
int main(void) {
init();
setup();
//endless loop
for (;;) {
loop();
}
return 0;
}
Ужас Вы чего наворотили ?
Если вы все равно используете таймер, то настройте его в режим fast PWM и сделайте 2-ва прерывания - первое по COMPA, второе по OVER. В первом гасите светодиод (побоку на каком пине хоть на всех сразу), во втором зажигаете. Функция loop() - меняете OCRA до упора в верх и вниз, _delay_ms() задаст скорость. Если хотите синусоиду, можно использоват sin(); для такого медленного процесса как управление светодиодом быстродействие не критично (все равно у выс процессор основное время будет находится в паузе).
Ужас Вы чего наворотили ?
Тут дело не в решаемой задаче, а в освоении основ. Моделирование аналогового выхода методом ручного PWM мне интересно в принципе сделать, чтобы понимать как это всё работает.
Если вы все равно используете таймер, то настройте его в режим fast PWM и сделайте 2-ва прерывания - первое по COMPA, второе по OVER. В первом гасите светодиод (побоку на каком пине хоть на всех сразу), во втором зажигаете. Функция loop() - меняете OCRA до упора в верх и вниз, _delay_ms() задаст скорость. Если хотите синусоиду, можно использоват sin(); для такого медленного процесса как управление светодиодом быстродействие не критично (все равно у выс процессор основное время будет находится в паузе).
Я же только учусь, это был мой первый урок. По синусоиде колебания красивее, а вообще это часть дальнейшего плана. На счёт плавучки я читал предостережения, что тормозная она, так что я тут её избежал совсем. Даже умножений-делений избежал, только целые числа и только простые операции -- тоже в общем часть моего упражнения. За информацию, что управление светодиодом медленный процесс -- спасибо.
Я правильно понимаю, что, если OCRA = 40, то CMPA случится на 40-м цикле таймерного "клика", а OVERA на 41м?
нет, точнее первая часть правильно а вот переполнение случится при переходе через 0. Тоесть когда счетчик с 255 перейдет в 0. У таймеров есть режим (см даташит) когда в OCRA = предел, а остальные (к примеру OCRB) = скважности. Таким образом можно играться не только скважностью, но и длинной импульса.
Вот последняя версия https://github.com/jabbervorx/arduino-first-lesson/blob/master/arduino-first-lesson/src/wave.cpp
Про таймеры сейчас пытаюсь понять. Почему 255? Я помню там каунтер можно на инкремент или декримент настроить, соотно переполнение в случае декримента когда через 0 проходит, а при инкременте, когда превышает значение OCRA, я неправ? Длина импульса понятна, но мне надо было "рассеять" импульс равномерно.