Этюды для начинающих: blink и без delay, и без millis

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Коллеги, я помню, что я обещал продолжение про память и обязательно сделаю, просто как-то сейчас не идёт. Зато вот последние темы на форуме навеяли желание поговорить о «ногодрыгах». Итак, стоит задача получить на какой-нибудь ноге меандр заданной частоты. Собственно, такой же, как в горячо любимом Blink’е.

На самом деле, если задача просто получить меандр, то для «блинка» в достаточно широком диапазоне частот не нужен не только dealy(), но и millis(). Всё что для этого нужно, контроллер умеет делать сам – его только надо правильно сконфигурировать.

Для того, чтобы это сделать, нам надо знать три  вещи о таймерах:

1.
у таймеров есть делители частоты. Т.е. таймеру можно сказать как часто ему тикать. Не тикать вовсе (выключиться),  тикать синхронно с тактами процессора, и тикать в N раз реже (на каждый N-ый такт процессора).

2.
в процессе «тикания» таймер начинает считать от нуля и считает до некоторого верхнего значения. Когда верхнее значение достигнуто, он снова начинает с нуля.

3.
в одном из режимов работы таймера (а именно в CTC) можно вручную устанавливать верхнее значение и запросить таймер инвертировать некий пин по достижению этого верхнего значения.

Как именно это делается, мы увидим чуть позже. Главное сейчас понять, эти три возможности. Именно понять, а не просто запомнить. Чтобы проверить своё понимание, попробуйте ответить на

контрольный вопрос:
какова максимальная частота меандра, который можно получить, основываясь на этой технике. Подумайте, прежде, чем читать дальше. Всё, что нужно для ответа, Вы уже знаете.

Подумали? Давайте сверим ответы.

Ответ на контрольный вопрос:
для получения максимальной частоты, мы установим делитель частоты таймера так, чтобы он тикал с тактовой частотой процессора. Далее, в качестве максимального значения установим 0. Ну, и попросим таймер инвертировать пин при достижении максимального значения. Что получили? В начале работы таймер сразу видит, что счётчик достиг максимального значения. Он инвертирует пин и сбрасывает значение в 0. На следующем такте процессора, таймер снова видит. Что он достиг максимального значения. Он снова инвертирует пин и сбрасывает значение в 0. И так на каждом такте. Т.е. пин инвертируется на каждом такте процессора. В течение одного такта пин находится в состоянии HIGH, в течение следующего – в LOW, на следующем такте опять в HIGH и т.д. Таким образом частота равно половине тактовой частоты процессора. Это максимальная частота для данного метода «мигания». Быстрее не получишь. Если у Вас 16МГц Ардуино, то самый быстрый «блинк», который Вы можете сделать, с использованием данной техники, будет мигать с частотой 8МГц.

Ну. Теперь , давайте перейдём к примерам. Вот этот скетч, на пине 9 даст Вам «блинк» с частотой 8МГц.

void setup() {
	pinMode(9, OUTPUT);
	TCCR1A = 0x40;	// Переключение пина 9 по сравнению
	TCCR1B = 0x09;	// Установить СТС режим и делитель частоты 1
	OCR1A = 0;	// установить TOP равным 0
}

void loop() {}

ну, на глаз заметить мигание светодиода с частотой 8МГц мне не по зрению, а вот, что показывает осциллогаф:


На меандр как-то не очень похоже, но, как говаривал бывший президент наших юго-западных соседей:  «Що маємо, то маємо».

Что там у нас в скетче? Да, собственно, ничего особенного, всё прокомментировано.

Теперь давайте договоримся –

1) мы работаем с таймером/счётчиком №1.
2) у этого таймера/счётчика есть пять делителей частоты. Они позволяют делить исходную частоту (частоту процессора) на 1, 8, 64, 256 и 1024.
3) ну, и наконец, это 16-тиразрядный таймер и значит максимальное знечение мы можем задавать в диапазоне от 0 до 65535.

Давайте чуть-чуть поменяем скетч, чтобы видно было в каком именно месте мы вводим делитель частоты (и как именно вводим). Но прежде, чем запускать скетч, попробует ответить на

контрольный вопрос:
какова будет частота «блинка», если мы по прежнему оставим максимальное значение нулём, а делитель частоты выберем 1024?

Пока думаете, можно установить и загрузить скетч:

enum Prescalers {
	PRESCALER_1 = 1, PRESCALER_8 = 2, PRESCALER_64 = 3, PRESCALER_256 = 4, PRESCALER_1024 = 5
};

void setup() {
	uint8_t prescaler = PRESCALER_1024;
	uint16_t topValue = 0;
	pinMode(9, OUTPUT);
	TCCR1A = 0x40;					//	Инвертирование пина 9 по сравнению
	TCCR1B = 0x08 | prescaler;	// Установить СТС режим и делитель частоты
	OCR1A = topValue;				//	установить TOP равным topValue
}

void loop() {}

Ну, что. Есть решение? Давайте запустим скетч и «сравним с ответом».

Ну, что таймер стал тикать в 1024 раза медленнее, а больше мы не поменяли ничего. Стало быть и частота у нас просто уменьшилась в 1024 раза и стала  7,81КГц – надеюсь, никто и не ожидал ничего другого? И, кстати, на такой частоте картинка и на меандр похожа гораздо больше :)

Ну, а какова же минимальная частота, которую мы можем получить? Очевидно, что минимальная частота будет при делителе 1024 и максимальном значении 65535. Давайте попробуем:

enum Prescalers {
	PRESCALER_1 = 1, PRESCALER_8 = 2, PRESCALER_64 = 3, PRESCALER_256 = 4, PRESCALER_1024 = 5
};

void setup() {
	uint8_t prescaler = PRESCALER_1024;
	uint16_t topValue = 65535;
	pinMode(9, OUTPUT);
	TCCR1A = 0x40;			// Инвертирование пина 9 по сравнению
	TCCR1B = 0x08 | prescaler;	// Установить СТС режим и делитель частоты
	OCR1A = topValue;		// установить TOP равным topValue
}

void loop() {}

Вот так - 0,12Гц. Медленне мигать мы не можем.

ну, наверное. все уже давно догадались, как посчитать частоту? Правильно, нужно тактовую частоту микроконтроллера разделить на 2 (потому, что период сосоит и двух полупериодов HIGH и LOW) умноженное на делитель частоты и на увеличенное на 1 максимальное значение. Именно такую формулу мы можем найти в датажите на странице 123.

где fclk - такстовая частота контроллера, N - делитель частоты, а Top - устанавливаемое нами максимальное значение.

Нетрудно заметить, что самые большие частоты идут редко. Самая большая, как мы уже выяснили - 8МГц, а следующая (при Top=1) будет уже 4МГц. Зато потом частоты встречаются гораздо чаще и многие из них можно получить даже разными способами. Например, частоту 124Гц можно получить с любым делителем частоты:

Prescaler: 1; MaxValue: 64515; Frequency: 124.00Hz;
Prescaler: 8; MaxValue: 8064; Frequency: 123.99Hz; 
Prescaler: 64; MaxValue: 1007; Frequency: 124.01Hz; 
Prescaler: 256; MaxValue: 251; Frequency: 124.01Hz; 
Prescaler: 1024; MaxValue: 62; Frequency: 124.01Hz; 

Может Возникнуть вопрос, а какой делитель частоты в этом случае выбрать? Ответ: если у Вс возник такой вопрос, то любой. Если бы Вам было не всё равно, Вы бы видели и понимали разницу для себя, и такой вопрос бы не возникал. Разница бывает, например, когда Вам нужно использовать этот таймер для чего-то ещё (время отсчитывать, прерывания ловить и т.п.). Вот тогда, Вам не всё равно какой там делитель, т.к. в другом применении тоже не абы какой делитель нужен.

Ну, что всем понятно, как получить на 9-ом пине "blink" с частотой от 0,12Гц до 8МГц?

Тем, кто дочитал до этого места - подарок. Калькулятор с помощью которого можно, зная нужную частоту, посчитать необходимые делитель и максимальное значение. Вот он.

/////////////////////////////////////////////////////////////////////////////////////////////////////
//
//	Калькулятор тактов и делителя частоты СТС режима таймера/счётчика 1 микроконтроллера ATMega328
//	
//	По заданной желаемой частоте вычисляет количество тактов для всех пяти делителей частоты.
//
//	Результат выдаётся для всех делителей частоты в порядке возрастания погрешности. Делители для
//	которых такая частота невозможна, печатаются последними.
//
//	Результат считается для текущей тактовой частоты микроконтроллера, на котором на котором
//	производится расчёт. Частоту, для которой считается результат, можно изменить - см. комментарий
//	в тексте скетча.
//	
//	Условия использования:
//	
//	- допускается свободное и безвозмездное использование скетча в коммерческих и некоммерческих целях;
//	- модификация допускается только с указанием факта (и автора) модификации в заголовке файла;
//	- не допускается использование в проектах, связанных с разработкой устройств, предназначенных для
//		совершения или подготовки терактов, незаконного сбора информации и любых иных устройств,
//		предназначенных для противозаконной деятельности.
//	
//	Ответственность и обязательства автора:
//
//	- каждый разработчик может использовать или не использовать данный код на свой страх и риск;
//	- автор не несёт решительно никакой ответственности за прямые или косвенные негативные или 
//		позитивные последствия использования или неиспользования данного кода;
//	- автор с благодарностью примет сообщения о проблемах, ошибках и т.п., а также предложения 
//		по совершенствованию скетча и постарается исправить код в кратчайшие сроки, однако 
//		никаких обязательств по оперативному исправлению на себя не берёт;
//	- автор по мере возможности, сил и времени готов ответить на вопросы пользователей, но
//		обязательств на этот счёт на себя не берёт.
//
//	Расширение обязательств и ответственности автора:
//
//	- если код предполагается использовать в серьёзном проекте, где обязательства автора по 
//		исправлению ошибок и предоставлению консультаций необходимы, следует связаться с автором
//		на предмет заключения гражданско-правового договора. Тогда обязанности и ответственность 
//		автора будут определяться не настоящей декларацией, а условиями договора
//		
//	Если данный код Вам пригодился как готовый скетч, или как учебное пособие, и у Вас возникло желание
//	материально поддержать автора (что ни в коей мере НЕ является обязательным условием использования 
//	кода), Вы можете воспользоваться службой WebMoney, кошелёк № R626206676373
//
//
#include <limits.h>

///////////////////////////////////////////////////////////////////////
//
// !!! ВАЖНО !!!
//	Результат считется для текущей тактовой частоты микроконтроллера, на котором на котором
//	производится расчёт. Она доступна в константе F_CPU.
//	Если необходимо посчитать для другой тактовой частоты, следует в строке ниже вместо F_CPU
//	указать желаемую тактовую частоту в герцах. Например,
// static const double fCPU = 8000000.0;	// 8МГц
//
static const double fCPU = F_CPU;

// Всего у таймера/счётчика 1 пять делителей частоты
#define	TOTAL_PRESCALERS	5	

//	Потоковая печать в Serial
template <typename T> inline Print & operator << (Print &s, T n) { s.print(n); return s; }

////////////////////////////////////////////
//
//	Класс, для расчёта количества тактов и делителя
//
//	Расчёт производится в функции Calculate по формуле 
//	со стр.123 ATmega48A/PA/88A/PA/168A/PA/328/P [DATASHEET]
//	Сначала производится прямой расчёт для данного делителя,
//	Затем, после округления, обратный и определяется разница
//	желаемой и полученной частоты.
//
class CalcResult : public Printable {
public:
	CalcResult(uint16_t p) {
		prescaler = p;
		valid = false;
	}

	void Calculate(const double freq) {
		frequency = floor(fCPU / (2.0 * prescaler * freq) - 0.5);
		if (frequency < 0.0 || frequency > (double)UINT_MAX) return;
		valid = true;
		counter = (uint16_t) frequency;
		frequency = fCPU / (2.0 * prescaler * (1.0 + counter));
		difference = fabs(frequency - freq);
	}

protected:
	size_t printTo(Print& p) const {
		size_t res = p.print("Prescaler: ");
		res += p.print(prescaler);
		if (! valid) return res + p.println(" - Not possible");
		res += p.print("; MaxValue: ");
		res += p.print(counter);
		res += p.print("; Frequency: ");
		res += p.print(frequency);
		res += p.print("Hz; Diff: ");
		res += p.print(difference);
		return res += p.println("Hz");
	}

private:
	uint16_t counter; 
	int16_t prescaler;
	double frequency;
	double difference;
	bool valid;

friend int cmp(const void *, const void *);	
};

/////////////////////////////////////////////////////////
//
//	Функция сравнения двух результатов для qsort
//	Правила: 
//	1. если для одного делителя результат получен, а для другого нет, то первый меньше второго
//	2. Из двух результатов меньше тот, у которого меньше difference (погрешность частоты)
//
static int cmp(const void * va, const void * vb) {
	const CalcResult * a = (const CalcResult *) va;
	const CalcResult * b = (const CalcResult *) vb;
	if (a->valid && ! b->valid) return -1;
	if (! a->valid && b->valid) return 1;
	if (! a->valid && ! b->valid) return 0;
	if (a->difference - b->difference < 0) return -1;
	if (a->difference - b->difference > 0) return 1;
	return 0;
}

////////////////////////////////////////////////////////////////||||||
//
//	расчёт для пяти делителей, сотрировка и вывод результатов в Serial
//
void CalculateParameters(const double frequency) {
	CalcResult results [TOTAL_PRESCALERS] = {
		CalcResult(1), CalcResult(8), CalcResult(64), CalcResult(256), CalcResult(1024) 
	};
	for (int8_t i = 0; i < TOTAL_PRESCALERS; i++) results[i].Calculate(frequency);
	qsort(results, TOTAL_PRESCALERS, sizeof(results[0]), cmp);
	for (int8_t i = 0; i < TOTAL_PRESCALERS; Serial << results[i++]);
}



////////////////////////////////////////////////////////////////||||||
//
//	инициализация
//
void setup() {
	Serial.begin(115200);
	Serial.setTimeout(LONG_MAX);	
}

////////////////////////////////////////////////////////////////||||||
//
//	На каждом шаге производим расчёт для одного значения частоты
//
void loop() {
	Serial << "\nEnter desired frequency in Hz\n(use decimal point for fractions. I.e. 0.12 means 0,12Hz):\n";
	const double frequency = Serial.parseFloat();
	Serial << "\nResults for frequency: " << frequency << "Hz\n";
	if (frequency < 0) {
		Serial << "ERROR: frequency cannot be negative.\n";
	} else if (frequency > fCPU / 2.0) {
		Serial << "ERROR: frequency should bot be greater then " << fCPU / 2.0 << "Hz\n";
	} else CalculateParameters(frequency);
}

//	собственно. всё.

Запустите скетч, задайте в Serial нужную частоту, он расскажет при каких делителях и каких максимальных значениях её можно получить и, если она получается не совсем точно, покажет и погрешность.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015
Кстати, таймер 1 обслуживает ещё и 10-ый пин. Чтобы переделать скетч на 10-ый, достаточно в строке 8 поменять 9 на 10 и в строке 9 поменять 40 на 10.
 
Вот, скетч для 10-го пина с кошерной блинковой частотой 0,5Гц.
enum Prescalers {
   PRESCALER_1 = 1, PRESCALER_8 = 2, PRESCALER_64 = 3, PRESCALER_256 = 4, PRESCALER_1024 = 5
};

void setup() {
   uint8_t prescaler = PRESCALER_256;
   uint16_t topValue = 62499;
   pinMode(10, OUTPUT);
   TCCR1A = 0x10;              // Инвертирование пина 10 по сравнению
   TCCR1B = 0x08 | prescaler;  // Установить СТС режим и делитель частоты
   OCR1A = topValue;           // установить TOP равным topValue
}

void loop() {}

И ещё, я забыл сказать, как всё это безобразие прекратить. Чтобы пин перестал "мигать" нужно перевести его в обычный режим порта ввода/вывода:

для пина 9: TCCR1A &= ~0xC0;
 
для пина 10: TCCR1A &= ~0x30;
jeka_tm
jeka_tm аватар
Offline
Зарегистрирован: 19.05.2013

спасибо. понятней стало

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

Привет всем!
    А если не лезть так глыбоко, как некоторые, какую максимальную частоту меандра можно получить без delay()?
    В первом примере, понятно, f=1 Гц, T=1/f= 1 сек.



/* Генерация меандра на Pin 13 
 * с периодом 1 сек.
 */
# define period 1000                // период меандра в миллисекундах

void setup() {
  pinMode(13, OUTPUT);
  Serial.begin(115200);
  }
   
  void loop() {
    digitalWrite(13, HIGH);
    Serial.println(1);
    delay(period/2);
    digitalWrite(13, LOW);
    Serial.println(0);
    delay(period/2); 
  }

А вот что получится во втором примере-мне неведомо,так как частотомера или осциллографа нет:(

/* Генерация меандра на Pin 13 с
 *  с максимально возможной частотой
 */

void setup() {
  pinMode(13, OUTPUT);
  }
   
  void loop() {
    digitalWrite(13, HIGH);
    digitalWrite(13, LOW);
  }

Ну а Led 13 очень тускло светит в надежде, что где-то там есть сколько-то Мегагерц.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Олег М. пишет:

А вот что получится во втором примере-мне неведомо,так как частотомера или осциллографа нет:(

/* Генерация меандра на Pin 13 с
 *  с максимально возможной частотой
 */

void setup() {
  pinMode(13, OUTPUT);
  }
   
  void loop() {
    digitalWrite(13, HIGH);
    digitalWrite(13, LOW);
  }

Ну а Led 13 очень тускло светит в надежде, что где-то там есть сколько-то Мегагерц.

Ну, во-первых это не меандр. Скважность этого сигнала приблизительно около 4.

Поменяйте местами строки 10 и 11, скважность перевернётся и светить станет гораздо ярче. Частоту могу померять, когда домой приду. Да и скважность поточнее. Но что-то мне мне подсказыват, что частота порядка 120КНz.

Araris
Offline
Зарегистрирован: 09.11.2012

ЕвгенийП,  прежде всего, спасибо большое Вам за труды, я с немалым удовольствием читал все "Этюды для начинающих", кое-что понял, кое-что почерпнул. Но одна мысль не оставляет, решусь её озвучить. Почему "для начинающих" ?? Возможно, стоит как-то привести общую часть названия к уровню тем и подачи материала, либо слово "начинающих" в кавычки брать ? 

Я не полемики ради.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Araris пишет:

Я не полемики ради.

Да хоть бы и ради неё, а в чём проблема? Что плохого в полемике?

"Для начинающих" потому, что я действительно считаю, что неначинающие в таких материалах не нуждаются.  Не потому, что они всё это знают - все мы чего-то не знаем, с чем Бог ещё не сводил. Но любой среднего уровня специалист, если что-то из того, что я здесь пишу ему вдруг понадобится, легко откроет даташит или ещё что-нибудь, и всё что ему нужно узнает быстро и без особых усилий. Новичёк же такой возможности пока лишен. Он ещё не имеет практики в чтении и понимании даташитов (которые, кстати сказать, пишутся для специалистов, а не для чайников), он просто не знает куда лезть и по каким словам искать. Вы замечали, что новички здесь на сайте часто не могут сформулировать вопрос? Так вот, пока они не научатся формулировать вопросы, самостоятельно добывать знания они не смогут и им нужны такие материалы. Человек же, который ориентируется в вопросе, гораздо меньше времени потратит на даташит, чем на мои (или чьи-то ещё) "много букв". 

Buldakov
Offline
Зарегистрирован: 17.01.2016

Хорошая статья. Для начинающих - самое то.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ЕвгенийП, я как любитель таймеров конечно не могу не прокомментировать ) В принципе, если судить по поставленной задаче ("помигать"), то статья весьма полезная. Разве что визуально удобнее давать запись в регистры не так:

TCCR1A = 0x40;  // Переключение пина 9 по сравнению

а вот так:

TCCR1A=1<<COM1A0; 

Мне в своё время kisoft сделал аналогичное замечание, теперь всегда пишу так, и считаю это хорошим тоном по отношению к тем, кто будет это читать. Граздо удобнее теперь сказать, что что бы поменять вывод в другой порт поменяйте COM1A0 на COM1B0

А вот для тех, кто хочет изучить таймер подробнее тут слишком мало информации. Но поскольку тема этого и не подразумевала, то конечно зачёт.

 

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Олег М. пишет:

А вот что получится во втором примере-мне неведомо,так как частотомера или осциллографа нет:(

/* Генерация меандра на Pin 13 с
 *  с максимально возможной частотой
 */

void setup() {
  pinMode(13, OUTPUT);
  }
   
  void loop() {
    digitalWrite(13, HIGH);
    digitalWrite(13, LOW);
  }

Ну а Led 13 очень тускло светит в надежде, что где-то там есть сколько-то Мегагерц.

Олег, спасибо, Ваш пример помог мне "поймать" очень полезный эффект, которыя я обязательно хочу донести до аудитории - очень Важно!

Ну, для начала - это действительно не совсем меадр, хотя про скважность 4 я загнул, она почти 2, но всё же не 2. А вот частота очень сильно гуляет - от 35 до 71 КГц.

Почему же она гуляет? А вот посмотрите:

Видите, сбой по длительности слева от центра экрана. Откуда он взялся? Видимо, в этот момент произошло прерывание от таймера и контроллер занялся своей работой по подсчёту миллис и т.п., а наша программа покорно ждала пока там всё сделается. То, что сбой пришёлся на LOW-полупериод - случайность. Вполне мог бы прийтись и на HIGH.

Если бы мы работали не в цикле loop,  от от таймера, такого безобразия бы не было. Так что, коллеги имеем в виду, таймер даёт гораздо большую стабильность, чем loop, который прерывают все, кому не лень. Имейте это в виду при обновлении семисегментных экранов с динамической индикацией и особенно при организации связи.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

dimax пишет:

TCCR1A = 0x40;  // Переключение пина 9 по сравнению

а вот так:

TCCR1A=1<<COM1A0; 

Спасибо. Примерно такого замечания я ожидал от kisoft, но он, наверное, уже забил на меня, как на безнадёжный, не подлежащий вылечиванию экземпляр :))))

По сути, я согласен, что так, как Вы говорите "красивше", но вот знаете, такую запись, как у Вас (как и ту, что у меня) я всё равно не могу читать без даташита. А если уж я взял в руки даташит, то мне попросту проще и легче читать биты, чем названия. Впрочем, в материалах для начинающих, наверное надо писать через константы, Вы правы.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

dimax пишет:
... Разве что визуально удобнее давать запись в регистры не так:

TCCR1A = 0x40;  // Переключение пина 9 по сравнению

а вот так:

TCCR1A=1<<COM1A0; 

А ещё удобнее записывать так:

#include "arhat.h"

timerControl(1,A) = 1<<COM1A0;

// или даже так:

pwmSet(9);

:)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Да ну нафиг .. wiring обрабатывает прерывание почти 10 микросекунд?!? Замерял свои функции .. даже micros() отрабатывает за 2.5 или около того, а обработчик прерывания wiring.c в целом несколько проще. Непохоже как-то.

P.S. Судя по нарисованному - задержка обработки составила более 10 микросекунд .. за это время МК обработает около 160 команд. Нет там столько.

А вот то, что wiring отрабатывает ногодрыг с такой скоростью, что программный ШИМ выше 40 килогерц практически невозможен (реально гораздо менее) - за то пасибки.

Ну и ваш вывод, конечно же верен: любое аппаратное действо куда как стабильнее программной эмуляции. Относится не только к ногодрыгам и таймерам. Тут очень любят юзать "библиотеки" реализующике программно все то, что есть в аппаратном варианте. Относится в равной степени к любой программной эмуляции.

Ну и конечно заметно что учитесь сами. Появилась существенная разница между вашими первыми этюдами и последними образцами кода. Многим с вас стоит пример взять. :)

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Arhat109-2 пишет:

Ну и конечно заметно что учитесь сами. Появилась существенная разница между вашими первыми этюдами и последними образцами кода. 

Ну, спасибо! Вот порадовал! :-))))))))))))

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

dimax пишет:

А вот для тех, кто хочет изучить таймер подробнее тут слишком мало информации. 

Это точно. Для изучения таймера во всех подробностях нужна не статья а целая книга. Ну или дробить по режимам работы. 

Вот, кстати, у меня к Вам просьба, как к специалисту по таймерам. Я тут столкнулся с затруднением объяснить человеку разницу между Fast PWM и Phase Correct PWM (не по скорости, со скоростью-то всё ясно). Вернее, словами-то я объясняю, но никак  не могу подобрать примера в котором разница была бы чётко видна на осциллографе. У Вас нет в загашнике хорошего примера, чтобы именно наглядно разницу продемонстрировать?

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

Хотел задать вопрос, но, оказывается ответы уже были.
Хотя очень странно, что у меня они не появлялись :(

Олег М.
Олег М. аватар
Offline
Зарегистрирован: 22.11.2015

Поменял местами строки - яркость, как Вы и сказали, заметно возросла.
Но когда повторил в лупе многократно (16 раз) пару

      digitalWrite(13, LOW);
      digitalWrite(13, HIGH);

- led 13 сильно потускнел.
Получается, самая большая задержка где-то на "концах" лупа. Ведь на практике выход digitalWrite() позволяет работать на 8 МГц.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Олег М. пишет:

Поменял местами строки - яркость, как Вы и сказали, заметно возросла.
Но когда повторил в лупе многократно (16 раз) пару

      digitalWrite(13, LOW);
      digitalWrite(13, HIGH);

- led 13 сильно потускнел.
Получается, самая большая задержка где-то на "концах" лупа. Ведь на практике выход digitalWrite() позволяет работать на 8 МГц.

Разумеется. Задержка - это выход из loop() и новый вызов loop(). Отсюда и скважность не равна двум.

Ворота
Ворота аватар
Offline
Зарегистрирован: 10.01.2016

Это он твою архат.аш поизучал, вот и поднаучился койчему :)))

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ЕвгенийП пишет:

Видите, сбой по длительности слева от центра экрана. Откуда он взялся? Видимо, в этот момент произошло прерывание от таймера и контроллер занялся своей работой по подсчёту миллис и т.п., а наша программа покорно ждала пока там всё сделается. То, что сбой пришёлся на LOW-полупериод - случайность. Вполне мог бы прийтись и на HIGH.

Вы совершенно правы. Дрожъ меандра -это чисто ардуиновские проделки. Достаточно сделать так:

void setup() {
pinMode(13, OUTPUT);
TCCR0B=0;
}
void loop() {
digitalWrite(13, LOW);
digitalWrite(13, HIGH);
}

И можно будет наслаждаться чистым незамутнённым сигналом :) Как вариант можно  cli() вместо гашения таймера.

 

      
ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Ворота пишет:
Это он твою архат.аш поизучал, вот и поднаучился койчему :)))
Точно! Так и было :))))

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

dimax, если нетрудно - пост №14 гляньте.

kisoft
kisoft аватар
Offline
Зарегистрирован: 13.11.2012

ЕвгенийП пишет:

Спасибо. Примерно такого замечания я ожидал от kisoft, но он, наверное, уже забил на меня, как на безнадёжный, не подлежащий вылечиванию экземпляр :))))

Не забил, может это не заметно, но если я сделал "замечание", но человек не прислушивается, то я не трачу впустую время на продолжение разговора. К тому же мои "замечания" - это всего лишь выжимки из прочтения разных статей с рекомендациями, а также свой собственный опыт. Это всего лишь попытка помочь, не более того. Ну и, в конце концов, я тоже не идеал, я всего лишь средний программист (это я только для этого форума могу считаться типа "гуру"), который тоже имеет заблуждения и ошибается.

Короче, правило "каждый остается при своих" - это моё, я не учитель :) А еще, я стараюсь не ввязываться в пустые дискуссии (это я не про эту тему, а больше о себе), одно из моих правил - "первым уходит тот, кто умней". Впрочем, мы всего лишь высказываем своё мнение, а кто умней, пусть решают другие, даже если решение будет неправильным, то это не важно для меня.

Я сейчас другие таймеры мучаю, в режиме PWM входа, пока ковыряю декодирование IR сигналов. Понятно, что не на AVR. На Ардуине уж очень мало таймеров, хотя на Леонардо их чуть поболее и поразнообразнее (есть еще один 10-битный). Это мой легендарный Леонардо (четвертый год пошел), моя первая Ардуина :) До сих пор жив и время от времени попадает под "пресс": быстро достал, подключил, набросал скетч, прошил, работает. Вставляешь в форум исходник, Леонардо на место, продолжаешь заниматься своими делами :) Этим она и хороша Ардуина.

Ох и понесло меня, всё, ухожу :)

 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

ЕвгенийП пишет:

не могу подобрать примера в котором разница была бы чётко видна на осциллографе. У Вас нет в загашнике хорошего примера, чтобы именно наглядно разницу продемонстрировать?

Я думал-думал, но увы...  как показать разницу не придумал. Наверное достаточно знать про него пару основных моментов -что режимы с коррекциями работают в 2 раза медленнее обычных fastPWM И CTC (при прочих равных настройках в 2 раза будет меньше выходная частота). А все его коррекции актуальны только  когда "на ходу" идёт изменения регистра OCR. В режиме FastPWM в момент изменения OCR на мгновение нарушается длина импульса, а в режимах с коррекцией -не нарушается. Если не использовать ШИМ в особо ответственных схемах, типа всяких высоковольтных двухтактных мосфет ключей, где действительно недопустим подобный рассинхрон -то эти хитрые режимы не нужны.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Спасибо. Наверное, попробую помучать OCR и поймать момент нарушения.

saftik
Offline
Зарегистрирован: 08.04.2015

высший пилотаж. я человек новый и самоучка но даже мне понятно. спасибо за статью

Jatixo
Offline
Зарегистрирован: 13.01.2016

Спасибо, а если нужно не ногой "дрыгать", а функцию определенную запускать скажем раз в секунду, то как это будет выглядить в коде? Просто думаю, зачем каждый цикл loop время узнавать, чтобы узнать настало или нет, чтобы сделать определенное действие, если можно через таймер тоже самое делать, но не нагружая так процессор, все равно большая точность не нужна. Единственное боюсь, если в этой функции что-то своё делать, можно что-нибудь не учесть... помимо, например, volatile переменных...

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Jatixo пишет:

Спасибо, а если нужно не ногой "дрыгать", а функцию определенную запускать скажем раз в секунду, то как это будет выглядить в коде? Просто думаю, зачем каждый цикл loop время узнавать, чтобы узнать настало или нет, чтобы сделать определенное действие, если можно через таймер тоже самое делать, но не нагружая так процессор, все равно большая точность не нужна. Единственное боюсь, если в этой функции что-то своё делать, можно что-нибудь не учесть... помимо, например, volatile переменных...

Таймер при переполнения выдаёт прерывание (если ему разрешить). так вот, нужно всего лишь разрешить прерывание и подстваить свою функцию обработки этого прерывания. Если надо, я могу показать пример, но надо учитывать - таймеры товар дефицитный. Их всего три и один из них занят средой, если его обидеть, то перестанет работать millis и всё, что с нею связано. Другие частенько занимаются библиотеками и на них живут ШИМ и tone().  Так что тут надо осторожно, чтобы с другими кусками программы не подраться.

В другом этюде я показывал как можно организовать периодический вызов функции без того, чтобы монопольно занимать таймер - я там просто паразитировал на таймере, занятом системой, не обижая его. Посмотрите. Но если Вам надо на отдельном таймере, скажите, это несложно.

Кстати, есть ещё один забавный способ. Можно дрыгать любой ногой (как здесь), а на ноге обрабатывать PCINT прерывание. немного "через Альпы", но работать будет.

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

Есть и четвёртый таймер в меге328 -ватчдог, обычно он свободен, и с радость готов послужить не только по прямому, но и по любому другому назначению :) Например выполнять что-то из своего прерывания. Пример работы в соседней теме

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

dimax пишет:

Есть и четвёртый таймер в меге328 -ватчдог, обычно он свободен, и с радость готов послужить не только по прямому, но и по любому другому назначению :) Например выполнять что-то из своего прерывания. Пример работы в соседней теме

Точно, я о нём не подумал. Это, кстати, "менее через Альпы", чем PCINT.

Pyotr
Offline
Зарегистрирован: 12.03.2014

ЕвгенийП пишет:

И ещё, я забыл сказать, как всё это безобразие прекратить. Чтобы пин перестал "мигать" нужно перевести его в обычный режим порта ввода/вывода:

для пина 9: TCCR1A &= ~0xC0;
 
для пина 10: TCCR1A &= ~0x30;

Ещё "прекратить безобразие")) можно отключив предделитель.
TCCR1B &=~((1<<CS12) | (1<<CS11) | (1<<CS10));

А в режиме по преполнению можно просто обнулить регистр  TCCR1B = 0;

Jatixo
Offline
Зарегистрирован: 13.01.2016

 

ЕвгенийП пишет:

В другом этюде я показывал как можно организовать периодический вызов функции без того, чтобы монопольно занимать таймер - я там просто паразитировал на таймере, занятом системой, не обижая его. Посмотрите.

"Другой этюд" читал, и видел способ без изменения системных файлов здесь. Правда не очень понял как работает, а именно "прерывание где-то в середине", почему в середине, а не в начале или в конце и т.п.

В принципе мне ШИМ не нужен, только tone на любой ноге. Так что интересно было бы посмотреть как это сделать на 1 или 2 таймере, и за что какая строка отвечает. Как первый пост, очень подробно и всё понятно, большое спасибо!

А вот дрыгать ногой и на ноге прерывание мне кажется не очень, зачем лишний раз это делать, если можно без, тем более может там что-нибудь изнашивается (транзистор может) =)

И вачдог мне нужен, как-то спокойнее, что если зависнет, само начнется сначало, главное все протестировать.

А и еще, читал что свои функции в прерывании лучше не использовать если используется serial, а у меня как раз обмен с gsm модулем, так что может ну их прерывания, пусть в loop по millis мигает светодиод, пищит динамик и т.п.?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Jatixo пишет:

"Другой этюд" читал, и видел способ без изменения системных файлов здесь

Блин, как же я воодушевился ("неужели кто-то это сделал!") и как был разочарован :(

Они занимают канал сравнения А на нулевом таймере. В переводе на русский язык это значит, если Вы используете их метод, скажите "good bye" ШИМу и Squawk'у на 6-ой ноге, а также tone на какой-нибудь ноге. Нет, так и на любом другом таймере можно. Моя-то метода ничему не мешает, в том и ценность её. А жаль, я уж размечтался, что кто-то догадался как это сделать и я сейчас типа опыт перейму :)

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Кто мешает реализвать многозадачность на watchdog таймере тем же самым способом?

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Вы о чём? Какую многозадчаность и каким способом?

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

ЕвгенийП пишет:

В переводе на русский язык это значит, если Вы используете их метод, скажите "good bye" ШИМу и Squawk'у на 6-ой ноге, а также tone на какой-нибудь ноге. Нет, так и на любом другом таймере можно. Моя-то метода ничему не мешает, в том и ценность её. А жаль, я уж размечтался, что кто-то догадался как это сделать и я сейчас типа опыт перейму :)

"Все этюды хороши ! - выбирай на вкус !"
- какие счётчики использует система по умолчанию и для чего ? ( где прочитать ? )

- при использовании сторонних библ - как определить - какая УЖЕ использует какой-то счётчик ?

суть :
...что-то придумываешь и бац ! - не работает.... оказывается счётчик занят на другую задачЮ :(
как ЭТО выяснить ?

куда делся БОБЁР ?!!!!!!
 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Для миллиса и всего прочего - нулевой.

Для ШИМа

UNO-подобные

Pins 5 and 6: controlled by timer0
Pins 9 and 10: controlled by timer1
Pins 11 and 3: controlled by timer2
 
On the Arduino Mega we have 6 timers and 15 PWM outputs:
 
Pins 4 and 13: controlled by timer0
Pins 11 and 12: controlled by timer1
Pins 9 and10: controlled by timer2
Pin 2, 3 and 5: controlled by timer 3
Pin 6, 7 and 8: controlled by timer 4
Pin 46, 45 and 44:: controlled by timer 5
 
vladimir62
vladimir62 аватар
Offline
Зарегистрирован: 10.11.2015

На сайте вопрос в заголовке есть "Почему Ардуино?" и ответ: "Простая и понятная среда программирования – среда Arduino подходит как для начинающих пользователей, так и для опытных..." Пишешь диджиталрайты, делэи, мэпы аналоговые и радуешься своим знаниям, а потом натыкаешься на какой-то PCINT... да не, не может быть такого... опечатка, потом на TCCR1 и интуитивная понятливость уносится куда-то вдаль, махая руками, то ли прощаясь, то ли отмахиваясь от демонов. Это мои ощущения от двух месяцев знакомства с Ардуиной. Спасибо ЕвгениюП, пишет для начинающих и внушает уверенность в своих силах. А если серьезно, то что со скважностью? Как регулировать? У Dimaxа про это есть в Генераторе с регулируемоей частотой на ардуино, но хотелось бы текста для начинающих.

Romanoff
Offline
Зарегистрирован: 29.09.2015

ЕвгенийП пишет:

void setup() {
	pinMode(9, OUTPUT);
	TCCR1A = 0x40;	// Переключение пина 9 по сравнению
	TCCR1B = 0x09;	// Установить СТС режим и делитель частоты 1
	OCR1A = 0;	// установить TOP равным 0
}

void loop() {}

 

Скажите пожалуйста  0x40, 0x09 это что? На какой странице в даташите можно посмотреть. Пока что при открытии даташита читаю книгу вижу сами знаете что(

SU-27-16
SU-27-16 аватар
Offline
Зарегистрирован: 13.08.2012

переводим 0х40 и 0х09 в B01000000 и B00001001
смотрим в ДШ назначение битов в регистрах TCCR1A и TCCR1B.....

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Romanoff пишет:

Скажите пожалуйста  0x40, 0x09 это что? На какой странице в даташите можно посмотреть. Пока что при открытии даташита читаю книгу вижу сами знаете что(

Я ответил на этот вопрос вот здесь.

timofei
Offline
Зарегистрирован: 12.03.2016

Добрый день.

Надеелся найти ответ на свой ворпос.

На сайте есть пример "Мигаем без delay" 

проблема в том, что время в режиме LOW и HIGT должно быть разным.

Как можно задать это условие? Сразу скажу что LOW должно быть примерно 80000, HIGT -5000мс

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Изменением той величины с которой сравниваете интервал. Заводите переменную под интервал, и устанавливаете ей значение в зависимости от того, что исполняется "сейчас": если сейчас "хай", то ставите интервал для следующего "лоу" и наоборот.

Вот так, как-то.

timofei
Offline
Зарегистрирован: 12.03.2016

Спасибо за ответ. Только это не мой уровень знаний. Пока понимаю в формате примера. Если не сложно, то как изменить образец?

/* Blink without Delay
 2005
 by David A. Mellis
 modified 8 Feb 2010
 by Paul Stoffregen
 */
 
const int ledPin =  13;      // номер выхода, подключенного к светодиоду
// Variables will change:
int ledState = LOW;             // этой переменной устанавливаем состояние светодиода
long previousMillis = 0;        // храним время последнего переключения светодиода
 
long interval = 1000;           // интервал между включение/выключением светодиода (1 секунда)
 
void setup() {
  // задаем режим выхода для порта, подключенного к светодиоду
  pinMode(ledPin, OUTPUT);     
}
 
void loop()
{
  // здесь будет код, который будет работать постоянно
  // и который не должен останавливаться на время между переключениями свето
  unsigned long currentMillis = millis();
  
  //проверяем не прошел ли нужный интервал, если прошел то
  if(currentMillis - previousMillis > interval) {
    // сохраняем время последнего переключения
    previousMillis = currentMillis; 
 
    // если светодиод не горит, то зажигаем, и наоборот
    if (ledState == LOW)
      ledState = HIGH;
    else
      ledState = LOW;
 
    // устанавливаем состояния выхода, чтобы включить или выключить светодиод
    digitalWrite(ledPin, ledState);
  }
}

 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Расскажите мне, для начала, что в этом примере что отвечает за интервал времени срабатывания? :)

timofei
Offline
Зарегистрирован: 12.03.2016

как я понимаю функция 

 
long interval = 1000;

 

устанавливает временной интервал, если изменить в любую сторону, то время включения и выклюения будет равна этому интервалу. это как я понимаю.

 

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

timofei пишет:

как я понимаю функция 

Тимофей, только без обид, при том уровне знаний, который Вы демонстрируете, помочь Вам нельзя. Сделать за Вас можно, а вот помочь Вам сделать самому - нельзя.

Поэтому, лучшее, что можно сейчас сделать, это прочитать вот эту книгу - http://people.toiit.sgu.ru/Sinelnikov/PT/C/Kern_Ritch.pdf внимательно, разбирая все примеры.

Многие вопросы отпадут сами собой. А с теми, что остануться, приходите.

timofei
Offline
Зарегистрирован: 12.03.2016

не обидился, но запомнил (;

спасибо за ссылку, если что не пойму приду.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Замечательно. То есть, как вы уже догадываетесь, где-то в коде надо проставить то или иное значение тем же самым способом. Осталось только найти это счастливое место.

Есть идеи КАК его найти, согласно словесному описанию выше? :)

timofei
Offline
Зарегистрирован: 12.03.2016

есть идея, без знаний в реализации тяжело.

понимаю, что здесь в учителя никто не нанимался, но если своими словами...

if(currentMillis - previousMillis > interval) { //если прошло больше чем интервал заданный в конст

ledState = HIGH;// положение диода задано в конст. по умолчанию он выключен

if(currentMillis - previousMillis > interval/4) // делим интервал на 4 (место в котором точно не уверен, ведь не математика)

ledState = LOW; //выключаем диод

digitalWrite(ledPin, ledState); //запоминаем состояние

судя по всему не может быть так просто.

Клапауций 322
Offline
Зарегистрирован: 31.12.2015

timofei пишет:

судя по всему не может быть так просто.

так и есть - юзать две переменных интервала для нажатия и отпускания или, как у тебя производные от одной переменных, но всё-равно их будет две.