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.
Спасибо!
/*
Итак, после ваших советов и дальнейших поисков я нашел решение для получения чистой прямоугольной формы сигнала. Он работает на частоте 2,66 МГц и, вероятно, является самым быстрым из возможных:
void setup()
{
pinMode(2, OUTPUT);
noInterrupts();
}
void loop()
{
while(true)
{
bitSet(PORTD, 2);
bitClear(PORTD, 2);
}
}
@Jirka Mikes, 👍2
Обсуждение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 мкс, что означает 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 отлично с этим справился.
Ура!
Если вы просто хотите протестировать грубую прямоугольную волну и вас не волнует частота, попробуйте следующее:
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
- Как использовать SPI на Arduino?
- Как решить проблему «avrdude: stk500_recv(): programmer is not responding»?
- Как создать несколько запущенных потоков?
- Как подключиться к Arduino с помощью WiFi?
- avrdude ser_open() can't set com-state
- Как узнать частоту дискретизации?
- Что такое Serial.begin(9600)?
- Я закирпичил свой Arduino Uno? Проблемы с загрузкой скетчей на плату
В 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