максимальная частота ШИМ на основе прерываний при 500 Гц
Мне нужно управлять ультразвуковым преобразователем 24 кГц, и я решил использовать Arduino Uno, который у меня есть.
Использование tone()
или аппаратной ШИМ Atmega не очень подходит, потому что мне нужны две прямоугольные волны 50/50 с разностью фаз pi (что равно двум выводам с XOR) для переключать полярность выхода h-моста синхронно. Поскольку UC больше нечего делать, я написал следующий скетч, в котором я просто жду прерывания и переключаю контакты 2 и 3:
bool do_task=false;
void timer_init () {
TCCR0A |= (1 << WGM01);
OCR0A = 83;
TIMSK0 |= (1 << OCIE0A);
TCCR0B |= (1 << CS01);
}
ISR (TIMER0_COMPA_vect) {
do_task=true;
}
bool state=false;
void toggle() {
state=!state;
digitalWrite(2,state);
digitalWrite(3,!state);
}
void setup() {
pinMode(2,OUTPUT);
pinMode(3,OUTPUT);
timer_init();
}
void loop() {
if(do_task) {
do_task=false;
toggle();
}
}
Я ожидал, что на выходе будет частота ~24 кГц, но вместо этого она составляет 500 Гц.
Насколько я понимаю:
- Частота ЦП 16 МГц
- пределитель timer0 равен 8 (
TCCR0B |= (1 << CS01)
) - выходное значение сравнения (
OCR0A
) равно 83
приведет к частоте 16 МГц / 8 / 83 = 24096,4 кГц
- но, как я уже упоминал, это 500 Гц и, похоже, не зависит от прескалера.
@Sim Son, 👍1
Обсуждение2 ответа
Лучший ответ:
Что именно делает такую простую функцию, как digitalWrite(), такой чертовски медленной??! Почему это не просто функция-оболочка для прямого управления портом?
Ну, он может принимать переменную в качестве аргумента. И содержимое переменной нужно искать в таблице, чтобы увидеть, какой порт и какой бит.
Если вы используете библиотеку DigitalWriteFast, это превращает вашу запись в прямую манипуляцию с портом, при условии, что вы используете константы.
Таким образом, вы можете использовать:
digitalWriteFast(2,HIGH); // или НИЗКИЙ
Но нет:
digitalWriteFast(myPort,someState); // если они не константы
На самом деле вы можете отправлять переменные, но это превращается в те же медленные вызовы, которые использует digitalWrite.
Теперь я решил не использовать ядро arduino
С ядром все в порядке. Однако нужно понимать, что он делает. Чтобы быть вежливым с новичками, такие вещи, как digitalWrite, могут принимать переменные в качестве аргументов, поэтому возможны такие вещи:
for (int i = 0; i < 13; i++)
digitalWrite (i, HIGH);
Как я намекнул в комментарии, все это можно сделать в ISR:
ISR (TIMER0_COMPA_vect) {
static bool state;
if (state)
{
digitalWriteFast(2,HIGH);
digitalWriteFast(3,LOW);
state = false;
}
else
{
digitalWriteFast(2,LOW);
digitalWriteFast(3,HIGH);
state = true;
}
} // конец TIMER0_COMPA_vect
Изначально я планировал считывать частоту с потенциометра после каждого прерывания, разве это не было бы легко возможно за 8×83=664 цикла?
Аналоговое чтение (чтение из АЦП) занимает около 104 мкс при использовании предварительного делителя АЦП по умолчанию. То есть 1664 такта. Так что нет, вы не можете читать в это время. Это аппаратное ограничение. Вы можете уменьшить предварительный масштаб без потери точности.
См. мою страницу о ADC. Установка предделителя 16 вместо 128 значительно увеличит скорость с минимальной потерей точности.
Чтобы сделать это на Uno:
ADCSRA &= ~(bit (ADPS0) | bit (ADPS1) | bit (ADPS2)); // очистить биты предделителя
ADCSRA |= bit (ADPS2); // 16
Я не новичок в программировании UC, я просто предпочитаю использовать программные регистры напрямую, а не в среде arduino. Я подумал, что эту маленькую программу можно было бы быстрее реализовать с использованием ядра arduino без необходимости искать что-то в таблице данных, но теперь я все равно это сделал. Что касается adc: если он находится в автономном режиме, аппаратное обеспечение выполняет преобразование параллельно. Поэтому, если я проверяю бит состояния только для завершенного преобразования, это не должно быть проблемой, и фактическое время, необходимое для обработки аналогового значения, значительно уменьшится. Разве это не правда?, @Sim Son
Мне нужно только аналоговое значение каждые 100 мс, и я не очень много обрабатываю его..., @Sim Son
Вы писали:
Использование [...] аппаратного pwm atmega не очень подходит, потому что я нужны две прямоугольные волны 50/50 с разностью фаз пи
Я считаю, что аппаратная ШИМ — лучшее решение вашей проблемы. ШИМ выходы могут быть установлены либо в «инвертирующий», либо в «неинвертирующий» режим. Установите два канала одного таймера на один и тот же рабочий цикл, с одним в неинвертирующем режиме, а другой в инвертирующем режиме, и у вас есть ваши дополнительные результаты.
Пример использования таймера 1:
const float SIGNAL_FREQUENCY = 24e3;
const uint16_t HALF_PERIOD = round(F_CPU / SIGNAL_FREQUENCY / 2);
const uint16_t PERIOD = 2 * HALF_PERIOD;
int main()
{
/* Set the PWM pins as outputs. */
DDRB |= _BV(PB1); // цифровой 9 = PB1 = OC1A
DDRB |= _BV(PB2); // цифровой 10 = PB2 = OC1B
/* Configure Timer 1. */
ICR1 = PERIOD - 1;
OCR1A = PERIOD/2 - 1;
OCR1B = PERIOD/2 - 1;
TCCR1A = _BV(COM1A1) // неинвертирующий ШИМ на OC1A
| _BV(COM1B0) // инвертирование ШИМ на OC1B
| _BV(COM1B1) // то же самое
| _BV(WGM11); // режим 14: быстрая ШИМ, TOP = ICR1
TCCR1B = _BV(WGM12) // то же самое
| _BV(WGM13) // то же самое
| _BV(CS10); // часы на F_CPU, запуск таймера
}
Спасибо за Ваш ответ! Да, я знаю режим инвертирования, но скоро мне нужно будет также изменить частоту в небольшом диапазоне. Сейчас я делаю прямые манипуляции с портами, и пока все работает нормально. Но вопрос: что это за _BV()
вы используете?, @Sim Son
@SimSon: _BV(n)
[это макрос для (1<<(n))
](https://www.nongnu.org/avr-libc/user-manual/group__avr__sfr.html#ga11643f271076024c395a93800b3d9546) , из avr-libc. Я предполагаю, что это означает «значение байта». Мне нравится использовать его при написании AVR-идиоматического кода., @Edgar Bonet
- ATmega328P - проблема с использованием таймера 2 для генерации тона
- Может ли Arduino uno регулировать входную квадратную волну, фазу и частоту только с помощью таймера-счетчика?
- Генерация стабильной частоты
- Можно ли сгенерировать точный тактовый импульс 15 кГц с помощью ардуино?
- Светодиод Arduino PWM с замиранием в сборке
- Изменчивая переменная не обновляется с таймера ISR
- генерировать два сдвинутых по фазе ШИМ-импульса, запускаемых внешним сигналом с частотным разделением, с помощью Arduino uno?
- Точность синхронизации Arduino nano
Вызовы
digitalWrite()
занимают слишком много времени. Это довольно большая функция. Используйте прямое портманипулирование, @chrislвы должны удалить предыдущие настройки таймера. Ядро Arduino инициализирует таймеры для PWM, и вы делаете
|=
, @JurajПочему бы вам не сделать переключение в ISR? Время, которое требуется функции цикла, чтобы найти, что флаг изменился, не будет тривиальным., @Nick Gammon
@chrisl, в конце концов, я решил не использовать ядро ардуино, все ведет себя так странно ... что именно делает простую функцию, такую как
digitalWrite()
, такой чертовски медленной??! Почему это не просто функция-оболочка для прямого манипулирования портом?, @Sim Son@NickGammon Я начал сокращать код, и он просто остался там. Сколько циклов на самом деле занимает такое логическое сравнение? Изначально я планировал считывать частоту с потенциометра после каждого прерывания, разве это не было бы легко возможно за 8×83=664 цикла?, @Sim Son
@Юрай правильно! Теперь я решил не использовать ядро ардуино ..., @Sim Son