Генератор меандра на основе ATmega328

Тема в разделе "Микроконтроллеры AVR", создана пользователем AlexU, 12 май 2019.

Метки:
  1. AlexU

    AlexU Гуру

    Доброго всем времени суток!
    Может кому пригодиться...
    Сегодня понадобился управляемый генератор меандра 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"));      // Сообщаем о выключении генератора.
      }
    }
     
     
    vvr, DetSimen и ИгорьК нравится это.
  2. Ariadna-on-Line

    Ariadna-on-Line Гуру

    "Стандартные" частоты тракта УПЧ радиоприемников (465КГц и 455КГц ) - не дает. Дает 480ххх. Тракт ужЕ не настроишь. Наверняка оно никому нафиг не надо, но - "имеет место быть".
    ПС. "Часовую" частоту (32768) не даёт - даёт 32786. И тд.
     
    Последнее редактирование: 13 май 2019
  3. parovoZZ

    parovoZZ Гуру

    ЗАЧЕМ ОНО? К тому же там меандр, а значит приличный хвост гармоник.
     
  4. Ariadna-on-Line

    Ariadna-on-Line Гуру

    Для меандра - гармоники - 3-я ( 5-я, 7-я и тд.). Далеко. Вряд ли помешают основной частоте (если это не ГНЧ ессно). Наличие джиттера - это да - расширение спектра. А невозможность задать нужную частоту - реальный затык.
    ПС. Возник вопрос. Теоретически возможен генератор произвольной (целочисленной) частоты из частотомера и цифрового компаратора. Сравниваем число периодов за секунду с заданным и воздействуем на генератор. Естественно нужен осциллятор с отличной кратковременной (в пределах секунды) стабильностью. По жизни - мы вручную так и делаем, когда крутим генератор на нужную частоту. Почему-то схемы такие (мне) не встречаются. Возможно тут нужен "интеллектуальный алгоритм" ? Тогда Ардуине - самое то.
     
    Последнее редактирование: 14 май 2019
  5. AlexU

    AlexU Гуру

    Как законченное устройство, действительно "нафиг" не надо. Мне нужны частоты от 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"));   // Сообщаем о выключении генератора.
      }
    }
     
     
    ИгорьК нравится это.
  6. AlexU

    AlexU Гуру

    Попытка внедрения любого алгоритма приведёт к появлению джиттера при генерации. А с джиттером можно ногодрыгом на прерываниях сделать. Но кому оно будет нужно?
     
  7. Ariadna-on-Line

    Ariadna-on-Line Гуру

    Нет, я имел в виду процесс вроде PLL, но не фазовой компарации, а ЧИСЛОВОЙ. Там сам генератор к алгоритмам отношения не имеет. Но это - флуд. Удачи.
     
    Последнее редактирование: 12 июн 2019
  8. parovoZZ

    parovoZZ Гуру

    Не пойму - джиттер откуда лезет?
     
  9. AlexU

    AlexU Гуру

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

    parovoZZ Гуру

    ну так можно подкручивать частоту осциллятора.

    невысокие частоты можно генерировать с помощью биений. На Attiny817 есть все средства для этого.