Доброго всем времени суток! Может кому пригодиться... Сегодня понадобился управляемый генератор меандра 5V с частотой до 8МГц. Набросал на листочке... Управляется через Serial интерфейс. Спойлер: Листочек Код (C++): /* * This source file was created by AVR Eclipse. */ /* * Для плат Arduino на основе ATmega328 (UNO, Nano и т.п.). * * Генератор меандра с заданной частотой. * Меандр генерируется на 11-й ноге (PB3, MOSI). * Генерация аппаратная, используется таймер Timer2 в режиме CTC. * Минимальная частота генерации -- 32 Гц. * Максимальная частота генерации -- 8 МГц. * * После включения Arduino, плата находится в режиме ожидания. * Для включения генератора необходимо подключиться к плате * через Serial интерфейс, используя какой-либо терминал. * Скорость подключения -- 115200 Б/с. Режим -- 8N1. * Для подключения можно использовать, например, монитор порта в Arduino IDE, * либо программу типа minicom и т.п. * После подключения необходимо ввести частоту в герцах * и передать символ перевода строки или возврата каретки (нажать Enter). * В ответ придёт сообщение: * freq: xxxxxxx -- реальная частота выдаваемая генератором; * freq: error (wrong value) -- нет возможности установить заданную частоту; * freq: off -- генератор выключен. * * Частота изменяется путём ввода числа и нажатия Enter. */ #include <Arduino.h> // Выход генератора -- вывод 11 (PB3, MOSI) #define FREQ_OUT_PIN 11 extern "C" void setup(); extern "C" void loop(); // Предварительные декларации функций. uint32_t readFreq(); void setupFreq(uint32_t freq); /* * Will be called once on board startup/reset */ void setup() { Serial.begin(115200); } /* * Will be called an infinite number of times. * This functions called in main loop. */ void loop() { // Блокирующий вызов (функция 'loop()' зависнет). // Ждёт ввода числа и перевода строки или возврата каретки. // freq -- частота в герцах uint32_t freq = readFreq(); // "Дежурный" перевод строки Serial.println(); // Установка частоты. setupFreq(freq); } // Чтение данных из последовательного порта uint32_t readFreq() { uint32_t result = 0; // буфер для сохранения частоты while(1) { // Бесконечный цикл (из-за чего функция блокирующая). // Выход в теле по условию (см. ниже). int ch = Serial.read(); // Читаем символ из Serial интерфейса. if (ch > 0) { // Если символ есть, Serial.write(ch); // отправляем обратно -- делаем "эхо". // Без "эха" не поймём, что за символ отправили // в нашу Ардуинку (полезно в терминалах). if ((ch >= '0') && (ch <= '9')) { // Если отправленный символ -- число, result *= 10; // сдвигаем на "десятку" и result += (ch - '0'); // добавляем единицы (преобразуем из символа в число). // В итоге получим число. } else if ((ch == '\n') || (ch == '\r')) { // Если перевод строки (\n) или возврат каретки (\r), return result; // возвращаем число -- частоту в герцах } else { // При любых других символах result = 0; // сбрасываем буфер (частоту) в ноль } } } } // Массив значений предделителя в соответствии с битами регистра TCCR2B. // Было бы не плохо во flash это всё "загнать", // но в контексте этой маленькой программы -- "зачем?". static const uint16_t prescallers[] = {0, 1, 8, 32, 64, 128, 256, 1024}; // Установка частоты. void setupFreq(uint32_t freq) { uint32_t ocr = 0; // Временный буфер для хранения значения регистра OCR2A. uint8_t prescallerBits = 1; // Биты предделителя. uint16_t prescaller = prescallers[prescallerBits]; // Значение предделителя на основе 'prescallerBits'. TIMSK2 = 0; // Прерывания таймера не нужны -- отключаем. if (freq) { // Если частота не '0' do // Пытаемся подобрать значения регистра OCR2A и предделителя // для заданной частоты. { ocr = F_CPU / freq / 2 / prescaller - 1; // Вычисляем значение для регистра OCR2A. if (ocr < 256) // Если значение верное (регистр 8-мибитный и // всё, что меньше 256, допустимо). { break; // Завершаем цикл. } prescallerBits += 1; // Если значение рагистра OCR2A не допустимо (условие выше не выполняется), // увеличиваем значение предделителя (понижаем частоту). prescaller = prescallers[prescallerBits]; // Новое значение предделителя. } while (prescallerBits < 8); // Подбираем значение регистра и предделителя, // пока биты предделителя не выходят за допустимые пределы. if (ocr < 256) // Если подобрано корректное значение регистра OCR2A. { digitalWrite(FREQ_OUT_PIN, LOW); // Вывод 11 в '0'. pinMode(FREQ_OUT_PIN, OUTPUT); // Вывод 11 как выход. // Устанавливаем для таймера 2 режим CTC. TCCR2A = (1 << COM2A0) // Задаём смену уровня вывода 11 при совпадении // счётчика таймера и регистра OCR2A. | (1 << WGM21); // Режим CTC. OCR2A = ocr; // Задаём предел счёта (регистр OCR2A). TCCR2B = prescallerBits; // Устанавливаем предделитель для таймера. freq = F_CPU / 2 / prescaller / (ocr + 1); // Вычисляем полученную частоту // (она может отличаться от заданной). Serial.print(F("freq: ")); // Сообщаем о частоте. Serial.println(freq); } else // Если не смогли подобрать значение регистра OCR2A. { Serial.println(F("freq: error (wrong value)")); // Сообщаем о неправильных данных. } } else { // Если частота '0' TCCR2B = 0; // Выключаем PWM на ноге 11. pinMode(FREQ_OUT_PIN, INPUT); // Ногу 11 делаем входом. digitalWrite(FREQ_OUT_PIN, LOW); // Отключаем поддтяжку на ноге 11 (на всякий случай). Serial.println(F("freq: off")); // Сообщаем о выключении генератора. } }
"Стандартные" частоты тракта УПЧ радиоприемников (465КГц и 455КГц ) - не дает. Дает 480ххх. Тракт ужЕ не настроишь. Наверняка оно никому нафиг не надо, но - "имеет место быть". ПС. "Часовую" частоту (32768) не даёт - даёт 32786. И тд.
Для меандра - гармоники - 3-я ( 5-я, 7-я и тд.). Далеко. Вряд ли помешают основной частоте (если это не ГНЧ ессно). Наличие джиттера - это да - расширение спектра. А невозможность задать нужную частоту - реальный затык. ПС. Возник вопрос. Теоретически возможен генератор произвольной (целочисленной) частоты из частотомера и цифрового компаратора. Сравниваем число периодов за секунду с заданным и воздействуем на генератор. Естественно нужен осциллятор с отличной кратковременной (в пределах секунды) стабильностью. По жизни - мы вручную так и делаем, когда крутим генератор на нужную частоту. Почему-то схемы такие (мне) не встречаются. Возможно тут нужен "интеллектуальный алгоритм" ? Тогда Ардуине - самое то.
Как законченное устройство, действительно "нафиг" не надо. Мне нужны частоты от 100кГц до 2МГц, при чём точность не особо важна. С ёмкостными датчиками играюсь (изобретаю очередной лисапед), как наиграюсь так всё в утиль уйдёт. А код выкладываю, что бы поделиться техниками программирования -- может кто для себя что почерпнёт, или выскажет свои соображения. Вот очередная версия листочка, в которой можно конфигурировать какой таймер использовать (Timer1 или Timer2) и какой пин (каждый таймер может выдавать на двух пинах). Конфигурирование производится в момент компиляции прошивки. Кому интересно может доработать, чтобы конфигурирование было в рантайме. Спойлер: Листочек Код (C++): /* * This source file was created by AVR Eclipse. */ /* * Для плат Arduino на основе ATmega328 (UNO, Nano и т.п.). * * Генератор меандра с заданной частотой. * Генерация аппаратная, используется таймер Timer1 или Timer2 в режиме CTC. * Минимальная частота генерации -- 32 Гц. * Максимальная частота генерации -- 8 МГц. * * После включения Arduino, плата находится в режиме ожидания. * Для включения генератора необходимо подключиться к плате * через Serial интерфейс, используя какой-либо терминал. * Скорость подключения -- 115200 Б/с. Режим -- 8N1. * Для подключения можно использовать, например, монитор порта в Arduino IDE, * либо программу типа minicom и т.п. * После подключения необходимо ввести частоту в герцах * и передать символ перевода строки или возврата каретки (нажать Enter). * В ответ придёт сообщение: * freq: xxxxxxx -- реальная частота выдаваемая генератором; * freq: error (wrong value) -- нет возможности установить заданную частоту; * freq: off -- генератор выключен. * * Частота изменяется путём ввода числа и нажатия Enter. */ #include <Arduino.h> /* * Настройки, определяющие на каком пине будет генерироваться сигнал. * при: * #define USE_TIMER1 * #define USE_PIN_1 * будет использоваться 9 вывод (PB1) * * при: * #define USE_TIMER1 * //#define USE_PIN_1 * будет использоваться 10 вывод (PB2) * * при: * //#define USE_TIMER1 * #define USE_PIN_1 * будет использоваться 11 вывод (PB3, MOSI) * * при: * //#define USE_TIMER1 * //#define USE_PIN_1 * будет использоваться 3 вывод (PD3) */ //#define USE_TIMER1 #define USE_PIN_1 #ifdef USE_TIMER1 // Выход 1 генератора -- вывод 9 (PB1) #define OCA_PIN 9 // Выход 2 генератора -- вывод 10 (PB2) #define OCB_PIN 10 #define OCR_MAX 0x10000 #else // Выход 1 генератора -- вывод 11 (PB3, MOSI) #define OCA_PIN 11 // Выход 2 генератора -- вывод 3 (PD3) #define OCB_PIN 3 #define OCR_MAX 0x100 #endif #ifdef USE_PIN_1 #define FREQ_OUT_PIN OCA_PIN #else #define FREQ_OUT_PIN OCB_PIN #endif extern "C" void setup(); extern "C" void loop(); // Предварительные декларации функций. uint32_t readFreq(); void setupFreq(uint32_t freq); /* * Will be called once on board startup/reset */ void setup() { Serial.begin(115200); Serial.println(F("Pulse generator")); #ifdef USE_TIMER1 Serial.print(F("use Timer1 on pin ")); #else Serial.print(F("use Timer2 on pin ")); #endif Serial.println(FREQ_OUT_PIN); } /* * Will be called an infinite number of times. * This functions called in main loop. */ void loop() { // Блокирующий вызов (функция 'loop()' зависнет). // Ждёт ввода числа и перевода строки или возврата каретки. // freq -- частота в герцах uint32_t freq = readFreq(); // "Дежурный" перевод строки Serial.println(); // Установка частоты. setupFreq(freq); } // Чтение данных из последовательного порта uint32_t readFreq() { uint32_t result = 0; // буфер для сохранения частоты while(1) { // Бесконечный цикл (из-за чего функция блокирующая). // Выход в теле по условию (см. ниже). int ch = Serial.read(); // Читаем символ из Serial интерфейса. if (ch > 0) { // Если символ есть, Serial.write(ch); // отправляем обратно -- делаем "эхо". // Без "эха" не поймём, что за символ отправили // в нашу Ардуинку (полезно в терминалах). if ((ch >= '0') && (ch <= '9')) { // Если отправленный символ -- число, result *= 10; // сдвигаем на "десятку" и result += (ch - '0'); // добавляем единицы (преобразуем из символа в число). // В итоге получим число. } else if ((ch == '\n') || (ch == '\r')) { // Если перевод строки (\n) или возврат каретки (\r), return result; // возвращаем число -- частоту в герцах } else { // При любых других символах result = 0; // сбрасываем буфер (частоту) в ноль } } } } // Массив значений предделителя в соответствии с битами регистра TCCR2B. // Было бы не плохо во flash это всё "загнать", // но в контексте этой маленькой программы -- "зачем?". #ifdef USE_TIMER1 static const uint16_t prescallers[] = {0, 1, 8, 64, 256, 1024}; #else static const uint16_t prescallers[] = {0, 1, 8, 32, 64, 128, 256, 1024}; #endif // Установка частоты. void setupFreq(uint32_t freq) { uint32_t ocr = 0; // Временный буфер для хранения значения регистра OCRxA. uint8_t prescallerBits = 1; // Биты предделителя. uint16_t prescaller = prescallers[prescallerBits]; // Значение предделителя на основе 'prescallerBits'. #ifdef USE_TIMER1 TCCR1C = 0; TIMSK1 = 0; // Прерывания таймера не нужны -- отключаем. #else TIMSK2 = 0; // Прерывания таймера не нужны -- отключаем. #endif if (freq) { // Если частота не '0' do // Пытаемся подобрать значения регистра OCRxA и предделителя // для заданной частоты. { ocr = F_CPU / freq / 2 / prescaller - 1; // Вычисляем значение для регистра OCRxA. if (ocr < OCR_MAX) // Если значение верное // Для Timer2 регистр 8-мибитный и всё, что меньше 256, допустимо. // Для Timer1 регистр 16-тибитный и всё, что меньше 65536, допустимо. { break; // Завершаем цикл. } prescallerBits += 1; // Если значение рагистра OCRxA не допустимо (условие выше не выполняется), // увеличиваем значение предделителя (понижаем частоту). prescaller = prescallers[prescallerBits]; // Новое значение предделителя. } while (prescallerBits < (sizeof(prescallers) / sizeof(uint16_t))); // Подбираем значение регистра и предделителя, // пока биты предделителя не выходят за допустимые пределы. // Биты предделителя определяют индекс элемента массива 'prescallers' // и здесь проверяется, чтобы индекс не вышел за пределы массива if (ocr < OCR_MAX) // Если подобрано корректное значение регистра OCRxA. { digitalWrite(FREQ_OUT_PIN, LOW); // Вывод в '0'. pinMode(FREQ_OUT_PIN, OUTPUT); // Вывод как выход. #ifdef USE_TIMER1 // Устанавливаем для таймера 1 режим CTC. #ifdef USE_PIN_1 TCCR1A = (1 << COM1A0); // Задаём смену уровня вывода 9 при совпадении // счётчика таймера и регистра OCR1A. #else TCCR1A = (1 << COM1B0); // Задаём смену уровня вывода 10 при совпадении // счётчика таймера и регистра OCR1A. #endif OCR1A = ocr; // Задаём предел счёта (регистр OCR1A). TCCR1B = prescallerBits // Устанавливаем предделитель для таймера. | (1 << WGM12); // Режим CTC 16 бит. #else // Устанавливаем для таймера 2 режим CTC. #ifdef USE_PIN_1 TCCR2A = (1 << COM2A0) // Задаём смену уровня вывода 11 при совпадении // счётчика таймера и регистра OCR2A. #else TCCR2A = (1 << COM2B0) // Задаём смену уровня вывода 3 при совпадении // счётчика таймера и регистра OCR2A. #endif | (1 << WGM21); // Режим CTC. OCR2A = ocr; // Задаём предел счёта (регистр OCR2A). TCCR2B = prescallerBits; // Устанавливаем предделитель для таймера. #endif freq = F_CPU / 2 / prescaller / (ocr + 1); // Вычисляем полученную частоту // (она может отличаться от заданной). Serial.print(F("freq: ")); // Сообщаем о частоте. Serial.println(freq); } else // Если не смогли подобрать значение регистра OCRxA. { Serial.println(F("freq: error (wrong value)")); // Сообщаем о неправильных данных. } } else { // Если частота '0' #ifdef USE_TIMER1 TCCR1B = 0; // Выключаем PWM #else TCCR2B = 0; // Выключаем PWM #endif pinMode(FREQ_OUT_PIN, INPUT); // Ногу делаем входом. digitalWrite(FREQ_OUT_PIN, LOW); // Отключаем поддтяжку на ноге (на всякий случай). Serial.println(F("freq: off")); // Сообщаем о выключении генератора. } }
Попытка внедрения любого алгоритма приведёт к появлению джиттера при генерации. А с джиттером можно ногодрыгом на прерываниях сделать. Но кому оно будет нужно?
Нет, я имел в виду процесс вроде PLL, но не фазовой компарации, а ЧИСЛОВОЙ. Там сам генератор к алгоритмам отношения не имеет. Но это - флуд. Удачи.
Аппаратно получить любую частоту не получиться (по крайней мере простых способов на горизонте не видно). Но можно попробовать наколхозить программно, например, на прерываниях. А как только появляется программная составляющая, так сразу появляется неравномерность времени исполнения кода и как следствие джиттер. Конечно, если контроллер будет заниматься исключительно генерацией меандра, то можно добиться точных временных интервалов, но в этом случае вместо МК лучше взять какую-нибудь микруху типа 555, а не тратить в пустую ресурсы МК.
ну так можно подкручивать частоту осциллятора. невысокие частоты можно генерировать с помощью биений. На Attiny817 есть все средства для этого.