Проблема с выводом сигнала 2,5 кГц ATTiny85 с Digispark

Я относительно новичок в работе с Digispark IDE, и у меня есть некоторые проблемы с чем-то, что должно быть очень простым.

Я пытаюсь вывести прямоугольный сигнал 2,5 кГц, который будет колебаться +/- 500 Гц. Таким образом, он изменяется от 2000 Гц до 3000 Гц от микроконтроллера, который использует ATTiny85 с IDE Digispark.

Я нашел этот учебник в Интернете: https://digistump.com/wiki/digispark/tricks , который объясняет, что я хочу сделать. Как они сказали, чтобы изменить частоту HW PWM, я изменил значение с 64 на 8 в MS_TIMER_TICK_EVERY_X_CYCLES внутри wired.c

Благодаря этому мне удалось получить более высокое значение частоты. Но мне не удалось его настроить, поэтому я продолжил программную ШИМ-манипуляцию.

Я использовал этот код:

#include <TinySoftPwm.h>
#include <DigiUSB.h>

#define HW_PWM_PIN               0 /* Used to check HW PWM with analogWrite() */
#define SW_PWM_BUILT_IN_LED_PIN  1 /* Digispark Model A (Rev2) built-in LED pin number (Change it to 2 for Model B) */
#define TIME_TEST_PIN            5 /* Used to check with oscilloscope micros(), millis() are still OK */

void setup()
{
   TinySoftPwm_begin(128, 0); /* 128 x TinySoftPwm_process() calls before overlap (Frequency tuning), 0 = PWM init for all declared pins */
   pinMode(TIME_TEST_PIN, OUTPUT);
   DigiUSB.begin(); 
}

void loop()
{
static uint32_t PwmStartUs=micros();
static uint32_t PwmStartMs=millis();
static uint8_t  Pwm=0;
static int8_t   Dir=1;
static boolean  State=LOW;
static uint32_t BlinkStartMs=millis();

  /***********************************************************/
  /* Call TinySoftPwm_process() with a period of 60 us       */
  /* The PWM frequency = 128 x 60 # 7.7 ms -> F # 130Hz      */
  /* 128 is the first argument passed to TinySoftPwm_begin() */
  /***********************************************************/
  if((micros() - PwmStartUs) >= 60)
  {
    /* We arrive here every 60 microseconds */
    PwmStartUs=micros();
    TinySoftPwm_process(); /* This function shall be called periodically (like here, based on micros(), or in a timer ISR) */
  }

  /*************************************************************/
  /* Increment/decrement PWM on LED Pin with a period of 10 ms */
  /*************************************************************/
  if((millis()-PwmStartMs) >= 10)
  {
    /* We arrived here every 10 milliseconds */
    PwmStartMs=millis();
    Pwm+=Dir; /* increment or decrement PWM depending of sign of Dir */
    TinySoftPwm_analogWrite(SW_PWM_BUILT_IN_LED_PIN, Pwm); /* Software PWM: Update built-in LED for Digispark */
    analogWrite(HW_PWM_PIN, Pwm); /* Copy Pwm duty cycle to Hardware PWM */
    if(Pwm==255) Dir=-1; /* if PWM reaches the maximum: change direction */
    if(Pwm==0)   Dir=+1; /* if PWM reaches the minimum: change direction */
  }

  /* Blink half period = 5 ms */
  if(millis()-BlinkStartMs>=5)
  {
    BlinkStartMs=millis();
    digitalWrite(TIME_TEST_PIN, State);
    State=!State;
  }

  /* Check USB is still working */
  if(DigiUSB.available()) /* Just hit "Enter" in the digiterm to check USB */
  {
    DigiUSB.read(); /* just to clear the Rx buffer */
    DigiUSB.println(F("DigiUSB is still alive!"));
  }
  DigiUSB.refresh();

}

Неважно, что я делаю, я читаю колеблющееся значение 4,065 кГц - 4,049 кГц.

Что я делаю не так? Мой осциллограф сломался? Должен ли я делать это каким-либо другим способом?

К вашему сведению другим членам EE.SE: этот вопрос является копией и вставкой с форумов Digistump (ссылка) , и, хотя на него еще не было ответа, вопрос ОП может быть решен ответом там в любое время, конечно .

Ответы (1)

Вы можете сделать это на таймере 0 в режиме CTC. Если ваш микроконтроллер работает на частоте 8 МГц, вам нужна настройка предварительного делителя 8, чтобы достичь желаемого частотного диапазона. Инвертирование формулы частоты из таблицы данных дает правильные значения переполнения. Делаем это отдельно, используя наш любимый язык (у меня Haskell):

module Main (main) where

f_cpu = 8e6
freq n c = f_cpu / (2 * n * (1 + c))
count n f = f_cpu / (f * 2 * n) - 1

main :: IO ()
main = mapM_ (print . round . count 8) [ 2e3, 2.5e3, 3e3 ]

что дает нам 249, 199, 166 соответственно. Теперь мы готовы написать программу:

#include <avr/io.h>
#include <util/delay.h>

void setup()
{
    TCCR0A = _BV(WGM01) | _BV(COM0A0);  // CTC mode w toggle channel A on compare match
    TCCR0B = _BV(CS01);                 // clock select prescale = 8
    DDRB |= _BV(0);                     // enable channel A = OC0A (PB0) output
}

void loop()
{
    static uint8_t i = 0;

    switch (++i & 0x3)
    {
        case 0: OCR0A = 249; break;     // 2kHz
        case 1: OCR0A = 199; break;     // 2.5kHz
        case 2: OCR0A = 166; break;     // 3kHz
        case 3: OCR0A = 199; break;     // 2.5kHz
    }

    _delay_ms(2000);
}

int main()
{
    setup();
    for (;;)
        loop();
}

Это будет циклически переключать частоты 2, 2,5 и 3 кГц с шагом 500 Гц по 2 секунды каждый. Я протестировал на чистом ATTiny85, работающем на частоте 8 МГц, и измерил его на своем осциллографе: 2,01 кГц, 2,52 кГц и 3,01 кГц - вот что я получил.

Если вы не можете использовать таймер 0, должно быть просто добиться того же на таймере 1 (у него больше параметров предварительного масштабирования и более широкий диапазон).