Arduino UNO не выдает ожидаемую прямоугольную форму сигнала

Я хочу создать быстрый прямоугольный сигнал для целей тестирования. Если я буду использовать этот код:

void setup()
{
 pinMode(2, OUTPUT);
}

void loop() 
{
 bitSet(PORTD, 2);
 bitClear(PORTD, 2);
} 

Я получу ожидаемые импульсы, но я также получу эти 6,25 мкс провалов в осциллограмме примерно каждую 1 мс.

Я использую последнюю версию среды Arduino 1.6.9.

Весь цикл цикла занимает 1 мкс, что означает 16 инструкций (62,5 нс на инструкцию). Таким образом, я получу высокий сигнал 125 нс, а в остальное время низкий сигнал.

Есть идеи, что я делаю неправильно?

То же результаты, если я использую asm sbi и sci вместо макросов bitSet/bitClear.

Спасибо!

Осциллограмма сигнала - детализация 62,5 мкс

/*

Итак, после ваших советов и дальнейших поисков я нашел решение для получения чистой прямоугольной формы сигнала. Он работает на частоте 2,66 МГц и, вероятно, является самым быстрым из возможных:

void setup()
{
 pinMode(2, OUTPUT);
 noInterrupts();   
}

void loop() 
{
 while(true)
 {
 bitSet(PORTD, 2);
 bitClear(PORTD, 2);
 }
} 

, 👍2

Обсуждение

В Uno прерывание переполнения таймера 0 срабатывает каждые 2014 мкс. Вероятно, это глюк, который вы видите. Если вы хотите избежать этого, не используйте ядро Arduino, просто avr-libc., @Edgar Bonet

Или отключить прерывания., @Majenko

«_Цикл всего цикла занимает 1 мс, что означает 16 инструкций_». На самом деле инструкций всего 6. Два из них занимают по 4 такта (call и ret), остальные по 2 такта (sbi, cbi, sbiw и breq)., @Edgar Bonet

Да. Я должен был написать «циклы» вместо инструкций., @Jirka Mikes

Самое быстрое, что вы можете получить, это 8 МГц: возьмите код «Используйте аппаратный таймер», который я разместил, и измените регистры OCR2x на OCR2A = 1; OCR2B = 0;., @Edgar Bonet

@ЭдгарБонет Вау! Потрясающий! Твои знания об Ардуино меня поражают! Большое спасибо!, @Jirka Mikes


3 ответа


Лучший ответ:

3

Как я объяснил в своем комментарии, причиной этого сбоя является Таймер 0 прерывание от переполнения срабатывает каждые 1024 мкс. Это прерывание используется базовой библиотекой Arduino для хронометража. Это основа millis(), micros() и delay(). Прерывание разрешено Функция <code>init()</code> в проводка.c. Есть несколько способов избежать этого:

Избегайте инициализации ядра Arduino

Это может быть излишним, но, поскольку кажется, что вы уже знакомы с напрямую обращаясь к IO портам чипа ATmega, вполне может подойти ты. Вы можете использовать Arduino IDE без инициализации ядра Arduino. просто определив свой собственный main():

#include <util/delay.h>

int main(void)
{
    DDRD |= _BV(PD2);        // PD2 как выход
    for (;;) {
        PORTD |= _BV(PD2);   // PD2 высокий
        PORTD &= ~_BV(PD2);  // PD2 низкий
        _delay_us(0.625);    // 10 тактов ЦП, время цикла = 16 тактов
    }
}

Обратите внимание на дополнительную задержку: без нее цикл завершился бы за 6 ЦП. циклов (2 на доступ к порту, еще 2 на возврат). Не занимаюсь ардуиной инициализация ядра означает, что у вас нет хронометража и нет analogRead() или analogWrite(). Другие функции Arduino должны работать нормально, но некоторые, например Serial.begin(), будут создавать собственные прерывания.

Отключить прерывания

Как предположил Маженко, это может быть самый простой способ форма волны:

void setup()
{
    pinMode(2, OUTPUT);
    noInterrupts();
}

void loop()
{
    PORTD |= _BV(PD2);   // PD2 высокий
    PORTD &= ~_BV(PD2);  // PD2 низкий
}

Без прерываний у вас нет хронометража и последовательной связи. и, очевидно, никакого attachInterrupt(). Большинство других функций Arduino должно работать.

Используйте аппаратный таймер

Это позволяет генерировать сигнал, не полагаясь на центральный процессор. Ты может держать прерывания включенными и выполнять другую работу в программе, пока форма волны не нарушается. Самый простой способ сгенерировать такую форму волны используется analogWrite(). Результирующая частота довольно низкая, хотя, например, от 0,5 до 1 кГц. Если вы хотите 1 МГц, вам придется настройте таймер сложным способом. Например:

void setup()
{
    DDRD |= _BV(PD3);     // OC2B = PD3 как выход
    OCR2A = 15;           // период = 16 тактов
    OCR2B = 1;            // высокий уровень в течение 2 тактов
    TCCR2A = _BV(COM2B1)  // неинвертирующий ШИМ
           | _BV(WGM20)   // быстрый ШИМ, TOP = OCR2A
           | _BV(WGM21);  // то же самое
    TCCR2B = _BV(WGM22)   // то же самое
           | _BV(CS20);   // часы на f_CPU
}

void loop() { }

Выход находится на контакте PD3 = цифровой 3. Эта конфигурация отключит analogWrite() на контактах 3 (PD3) и 11 (PB3).

,

Какая часть Arduino запускает этот таймер / реализует его прерывание (вы должны убить его в корне? Чтобы вы все еще могли использовать другие прерывания)? И есть ли простой способ увидеть, какие фрагменты кода зависят от прерывания Arduino по времени?, @Paul

@Paul: Все это есть в файле wired.c, на который я уже ссылался. Процедура обслуживания прерывания обновляет только три переменные: timer0_millis, timer0_fract и timer0_overflow_count. Они используются только внутри wring.c. Вы можете увидеть, что зависит от прерывания, выполнив поиск этих переменных в файле: только millis() и micros(). Но тогда delay() зависит от micros(), Stream от millis() и некоторые вещи USB (не на Uno) от delay()., @Edgar Bonet

Круто, так что я мог бы отключить это прерывание и по-прежнему использовать библиотеки, которые используют прерывания (такие как servo/softserial). Или мне нужно проверить, полагаются ли они на millis() или micros()., @Paul

@Paul: Это правильно, но любое разрешенное прерывание вызовет такой сбой, как здесь., @Edgar Bonet

Поэтому я добавил noInterrupts(); в моем коде, и теперь форма сигнала идеальна! Большое спасибо, @Edgar Bonet! А я попробую и другие способы. Еще так многому предстоит научиться. Я знаком с таймером Arduino, но думал, что он не влияет на работу программы. Кстати, вы все, наверное, это знаете, но эти микроконтроллеры такие крутые :D ха-ха, @Jirka Mikes


1

Я хочу создать быстрый прямоугольный сигнал для тестирования.

Весь цикл цикла занимает 1 мкс, что означает 16 инструкций (62,5 нс на инструкцию). Таким образом, я получу высокий сигнал 125 нс, а в остальное время низкий сигнал. Любая идея, что я делаю неправильно?

Вы предполагаете, что то, что происходит вне цикла(), и вызов цикла() вообще не занимает времени.

Это то, что ядро Arduino main() похоже.

int main(void)
{
    init();

    initVariant();

#if defined(USBCON)
    USBDevice.attach();
#endif

    setup();

    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }

    return 0;
}

Есть вызовы loop(), if-branch и for-branch. Все это складывается. Вдобавок к этому существует ряд процедур обслуживания прерываний (ISR), например, для таймера, который используется для подсчета micros() и millis(). Они будут периодически чередоваться (как вы заметили).

Теперь вопрос в том, как это исправить, и я думаю, что @Edgar Bonet отлично с этим справился.

Ура!

,

1

Если вы просто хотите протестировать грубую прямоугольную волну и вас не волнует частота, попробуйте следующее:

void setup()
{
   pinMode(2, OUTPUT);
}

void loop() 
{
   digitalWrite(2, true);
   delay(2);
   digitalWrite(2, false);
   delay(2);
} 
,

Если вас не волнует частота, analogWrite(3, 128); проще., @Edgar Bonet

@sa_leinad: меня волнует частота, поэтому я использовал bitSet/bitClear, чтобы сделать это быстрее. Теперь у меня есть чистая форма волны 1 МГц (спасибо Эдгару), которую я никогда не добьюсь с помощью стандартного digitalWrite., @Jirka Mikes

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