Очень странное поведение DUE на прерываниях
- Войдите на сайт для отправки комментариев
Вообщем код запускает ШИМ на двух каналах:
#define BTN1 21
#define BTN2 20
#define BTN3 19
uint16_t duty = 21;
uint16_t freq = 42;
uint16_t n_impulse = 4;
void setup()
{
Serial.begin(9600);
pinMode(BTN1, INPUT);
pinMode(BTN2, INPUT);
pinMode(BTN3, INPUT);
REG_PMC_PCER1 |= PMC_PCER1_PID33;
REG_PIOC_ABSR |= PIO_ABSR_P26 | PIO_ABSR_P25;
REG_PIOC_PDR |= PIO_PDR_P26 | PIO_PDR_P25;
REG_TC2_CMR0 = TC_CMR_BCPC_CLEAR |
TC_CMR_ACPC_CLEAR |
TC_CMR_BCPB_SET |
TC_CMR_ACPA_SET |
TC_CMR_WAVE |
TC_CMR_WAVSEL_UP_RC |
TC_CMR_EEVT_XC0 |
TC_CMR_TCCLKS_TIMER_CLOCK1;
REG_TC2_RC0 = freq;
REG_TC2_IER0 = 0b00010000;
REG_TC2_IDR0 = 0b11101111;
NVIC_EnableIRQ(TC6_IRQn);
REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN;
}
void loop() {
if (digitalRead(BTN1) == 0)
{
Serial.println("BTN1");
}
if (digitalRead(BTN2) == 0)
{
Serial.println("BTN2");
}
if (digitalRead(BTN3) == 0)
{
Serial.println("BTN3");
}
}
void TC6_Handler(){
static int i = 0;
i++;
uint16_t dummy = REG_TC2_SR0;
REG_TC2_RB0 = freq;
REG_TC2_RA0 = duty;
if (i >= n_impulse)
{
REG_TC2_RA0 = freq;
REG_TC2_RB0 = duty;
i = 0;
}
}
В обработчике события TC6_Handler() я формирую нужные мне формы сигнала на двух выводах. Частота freq и скважность импульса duty задаются отдельной функцие. В данном случае код максимально упрощен, но баг отлавливается. Значит первая страннойсть. Если в обработчике TC6_Handler() параметр REG_TC2_RA0 выглядит так как в коде выше, то есть:
REG_TC2_RA0 = duty;
То функция loop не работает от слова совсем. Если я параметр укажу прямо:
REG_TC2_RA0 = 21;
То функция loop работает условно замечательно. Почему условно. А здесь второй баг. Если я оставлю один обработчик кнопок, скажем так:
void loop() {
if (digitalRead(BTN1) == 0)
{
Serial.println("BTN1");
}
}
То функция не работает ни в случае прямой передачи переменной duty в REG_TC2_RA0=21, ни в случае REG_TC2_RA0 = 21.
Кто знает в чем дело? Хелп плиз
Глобальные переменные которые используются в прерывании должны быть объявлены как volatile иначе оптимизатор может напортачить.
Ах вот оно что! Спасибо огромное за помощь. Два дня потратил отлавливая баг)
Есть ещё небольшая вероятность, что компилятор может строчку uint16_t dummy = REG_TC2_SR0 выкинуть (т.к. её результат нигде не используется) , тогда прерывание будет вечным, и до лупа очередь не дойдёт... :)
Вы тут один из самых гуристых гуру, может сможете подсказать. У меня в данном коде формируется сигнал на двух выходах с одной частотой, но с определенными пропусками. Примерно так:
Выход 1: _П_П_П___П_П_П___П_П_П_
Выход 2: _______П________П_______
То есть пропущенный импульс на выходе 1, формируется на выходе 2 с той же фазой, скважностью и частотой. Частоту, скважность и количество импульсов для пропуска я задаю сам. Но если я в функции loop занимаюсь опросом кнопок, у меня постоянно скачет количество пропущенных импульсов. Примерно так:
Выход 1: _П_П____П_П_П_П___П_П___П_П____П_П_П_П___П_П___
Выход 2: ______П__________П_____П______П__________П_____П_
Если я ставлю прерывания, то ситуация чуть лучше. Вот пробую разные варианты типов встроенной переменной-счетчика отвечающей за количество импульсов:
static uint16_t i = 0;
volatile uint16_t i = 0;
volatile static uint16_t i = 0;
Так же ее выношу и вношу за пределы Слушателя TC6_Handler()
Во многом тыкаю пальцем в небо без особого понимания того, что делаю. Спасибо
Есть ещё небольшая вероятность, что компилятор может строчку uint16_t dummy = REG_TC2_SR0 выкинуть (т.к. её результат нигде не используется) , тогда прерывание будет вечным, и до лупа очередь не дойдёт... :)
Volatile же исправляет ситуацию?
Есть ли в DUE понятие приоритет прерываний? Дело в том, что такой стиль програмировария очень плох - смешивание ардуино и CMSYSа. Ардуина вешает свои прерывания, которые Вы не контролируете, и они могут вредить Вашим. И можно долго искать глюки. Поробуйте не использовать loop setup a main while. При этом не будет инициализироваться другая переферия и прерывания, кроме Ваших.
А как это должно выглядеть? можно простенький пример? Спасибо!
Первая ссылка из гугля https://ph0en1x.net/80-simple-program-for-avr-microcontroller-c-language...
И как только нажимается кнопка, начинается вывод в сериал. Если приоритет прерываний сериал выше чем таймера, будет та фигня, что Вы описали. Измените скорость вывода на 115200 и если дергаться будет меньше, значит так и есть. Надо приоритет своего прерывания ставить выше.
Mulin.by, для переменной i внутри прерывания ничего менять не нужно. Нужно только глобальные freq duty сделать volatile. Ещё мне не нравится содержание прерывания. Получается регистры RB0/RA0 вы сначала программируете одним, но если это был четвёртый вход -то тут же переписывате другим -так не делается.
Если всё равно проблема не ушла, то нужно посмотреть что во флаге dummy, сделайте его тоже глобальным, и в loop прочитайте. Ещё не помещает в прерывании дёргать какую-нибудь ногу, и осциллографом/частометром посмотреть с какой частотой происходит вход в прерывание, соответствует ли это тому, что задумано.
Получается регистры RB0/RA0 вы сначала программируете одним, но если это был четвёртый вход -то тут же переписываете другим -так не делается.
Такой код вроде правильнее, насколько я понял
volatile uint32_t dummy; void TC6_Handler() { volatile static uint32_t i = 0; i++; dummy = REG_TC2_SR0; if (i < n_impulse) { REG_TC2_RB0 = freq; REG_TC2_RA0 = duty; } else { REG_TC2_RA0 = freq; REG_TC2_RB0 = duty; i = 0; } }Вы используете NVIC для поднятия прерывания. У NVIC есть другие функции, позволяющие установить приоритет прерывания до его поднятия. Посмотрите в описании и примените.
Mulin.by, вы проверили работу после изменения переменных на volatile , какие флаги в dummy ?
Mulin.by, вы проверили работу после изменения переменных на volatile , какие флаги в dummy ?
Dummy я вывел в Serial. Плавает в трех значениях 6556 65556 65560. Как это интерпретировать не совсем понимаю. Вообщем по осциллографу пропуски импульсов происходят на определенной частоте +/-. Причем вот этот код работает условно нормально (пропуски на определенной частоте)
void TC6_Handler() { i++; dummy = REG_TC2_SR0; if (i >= n_impulse) { i = 0; REG_TC2_RB0 = duty; REG_TC2_RA0 = freq; } else //C таким вариантом условия работает условно нормально { REG_TC2_RB0 = freq; REG_TC2_RA0 = duty; } }А вот этот уже не отрабатывает второй if
void TC6_Handler() { dummy = REG_TC2_SR0; if (i >= n_impulse) { i = 0; REG_TC2_RB0 = duty; REG_TC2_RA0 = freq; } if (i < n_impulse) //Вот так вроде не отрабатывет второй if { REG_TC2_RB0 = freq; REG_TC2_RA0 = duty; } }Но если инкремент воткнуть во второй if как здесь:
void TC6_Handler() { dummy = REG_TC2_SR0; if (i >= n_impulse) { i = 0; REG_TC2_RB0 = duty; REG_TC2_RA0 = freq; } if (i < n_impulse) { i++; //Инкремент переместил в условие REG_TC2_RB0 = freq; REG_TC2_RA0 = duty; } }То опять начинаются пропуски, но уже во втором канале
Serial вообще отключал, все равно пропуски бывают. Такое ощущение, что не всегда при условии происходит присвоение значения REG_TC2_RA0 и REG_TC2_RB0. Еще замечено, что влияет на пропуски последовательность записи REG_TC2_RB0 и REG_TC2_RA0 то есть что стоит первое.
Mulin.by, выложите полный скетч.
Убрал все лишнее оставил суть. Когда я докручиваю энкодером до частоты 525000 и до примерно 444000 начинается нестабильная работа например так:
#define ENA 17 #define ENB 18 #define ENSW 16 volatile char encoder_A; // Вывод энкодера 1 volatile char encoder_B; // Вывод энкодера 2 volatile char encoder_A_prev = 0; volatile uint32_t i = 0; // Это счетчик внутри обработчика прерываний volatile uint32_t dummy; // Это пустышка для очистки счетчика внутри обработчика uint32_t freq = 42; // Количество тактов задающих частоту uint32_t duty = 21; // Количество тактов задающих длительность const uint16_t PWM_divider = 50; // Дискретизация ШИМ const uint32_t clk_Freq = 21000000; // Частота контроллера /4 uint32_t freq2 = 635000; //Частота в герцах uint32_t duty2 = 50; //Скважность в процентах volatile uint32_t n_impulse = 4; //Количество импульсов для пропуска ниже как это выглядит // Выход 1 _П_П_П___П_П_П___П_П_П // Выход 2 _______П_______П______ // Таким образом каждый четвертый импульс на втором выходе void setup() { attachInterrupt(ENA, listner, CHANGE); attachInterrupt(ENB, listner, CHANGE); attachInterrupt(ENSW, listner, CHANGE); REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKDIS; // Активирую таймер TC6 REG_PMC_PCER1 |= PMC_PCER1_PID33; // Активирую переферию TC6 (TC2 Channel 0) REG_PIOC_ABSR |= PIO_ABSR_P26 | PIO_ABSR_P25; // Подключаю выводы D4 и D5 REG_PIOC_PDR |= PIO_PDR_P26 | PIO_PDR_P25; // Отключаю иную переферию REG_TC2_CMR0 = TC_CMR_BCPC_CLEAR | // Установливаю TIOB на совпадение с RC0 TC_CMR_ACPC_CLEAR | // Установливаю TIOА на совпадение с RC0 TC_CMR_BCPB_SET | // Чет там делаю с B TC_CMR_ACPA_SET | // Чет там делаю с А TC_CMR_WAVE | // Реджим генератора TC_CMR_WAVSEL_UP_RC | // Автотригер RC TC_CMR_EEVT_XC0 | // Set event selection to XC0 to make TIOB an output TC_CMR_TCCLKS_TIMER_CLOCK1; // Set the timer clock to TCLK1 (MCK/2 = 84MHz/2 = 48MHz) REG_TC2_RC0 = freq; REG_TC2_IER0 = 0b00010000; // Включаю прерывания для = rc REG_TC2_IDR0 = 0b11101111; // Отключаю все остальные прерывания // enable interrupt vector NVIC_EnableIRQ(TC6_IRQn); NVIC_SetPriority (TC6_IRQn, 0); // Задаю высший приоритет как выше писал nik182 REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Включаю и очищаю счетчик } void loop() {} void listner()// Это обработчик для энкодера. Кручу в одну сторону, частота увеличивается, в другую - уменьшается { REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKDIS; // Выключаю и очищаю счетчик encoder_A = digitalRead(ENA); encoder_B = digitalRead(ENB); if ((!encoder_A) && (encoder_A_prev)) { if (encoder_B) { freq2 += 1000; //увеличиваем частоту } else { freq2 -= 1000; //уменьшаем частоту } } encoder_A_prev = encoder_A; // Вычисляю новую частоту freq = (clk_Freq * 2) / freq2; // Вычисленная частота ШИМ duty = freq * (PWM_divider - duty2 / 2) / PWM_divider; // Вычисленная скважность ШИМ setParam();// Вызов функции которая присваивает новые значения частоты } void setParam() // Функция которая присваивает новые значения частоты { REG_TC2_RC0 = freq; // Устанавливаю новую частоту REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Включаю и очищаю счетчик } void TC6_Handler() { i++; dummy = REG_TC2_SR0; if (i >= n_impulse) { i = 0; REG_TC2_RB0 = duty; REG_TC2_RA0 = freq; } else { REG_TC2_RB0 = freq; REG_TC2_RA0 = duty; } }Вот фото с осциллографа

Приоритет лучше задавать до запуска, а так все в порядке. Нужно посчитать сколько тактов живёт прерывание, со всеми входами и выходами. Укладывается в 2 микросекунды?
Такты прописаны в регистре REG_TC2_SR0 ? Если так, то 6556 65556 65560. Значение плавает
Заметил, что проблемная частота лежит в пределах freq = 84/85 duty = 42
Mulin.by, вам стоило сразу написать про частоту. МК не может выполнять прерывания с такой высокой частотой, как я понял по умолчанию прерывания у вас в программе строчат с частотой 1млн раз в секунду. Вернее выполнить прерывание именно Due сможет, но ни на что другое времени просто не остаётся. Думаю не стоит настраивать прерывания чаще, чем 100 000 раз в секунду.
Mulin.by, вам стоило сразу написать про частоту. МК не может выполнять прерывания с такой высокой частотой, как я понял по умолчанию прерывания у вас в программе строчат с частотой 1млн раз в секунду. Вернее выполнить прерывание именно Due сможет, но ни на что другое времени просто не остаётся. Думаю не стоит настраивать прерывания чаще, чем 100 000 раз в секунду.
Mulin.by, если использовать такие высокие частоты нужно принципиально, то придётся отказаться от прерываний совсем. Есть другой способ управлять сигналами - модуляция/стробирование. В общем связка двух и более таймеров, когда один управляет другим. Не уверен, что это возможно на Due, может есть смысл сразу перейти на stm32, там такие вещи возможны.
Mulin.by, если использовать такие высокие частоты нужно принципиально, то придётся отказаться от прерываний совсем. Есть другой способ управлять сигналами - модуляция/стробирование. В общем связка двух и более таймеров, когда один управляет другим. Не уверен, что это возможно на Due, может есть смысл сразу перейти на stm32, там такие вещи возможны.
#define ENA 17 #define ENB 18 #define ENSW 16 volatile char encoder_A; // Вывод энкодера 1 volatile char encoder_B; // Вывод энкодера 2 volatile char encoder_A_prev = 0; volatile uint32_t freq = 84; // Количество тактов задающих частоту volatile uint32_t duty = 42; // Количество тактов задающих длительность volatile uint32_t n_impulse = 4; //Количество импульсов для пропуска ниже как это выглядит const uint16_t PWM_divider = 50; // Дискретизация ШИМ const uint32_t clk_Freq = 21000000; // Частота контроллера /4 volatile uint32_t freq2 = 635000; //Частота в герцах volatile uint32_t duty2 = 50; //Скважность в процентах volatile uint32_t imp1[1000]; volatile uint32_t imp2[1000]; void setup() { attachInterrupt(ENA, listner, CHANGE); attachInterrupt(ENB, listner, CHANGE); attachInterrupt(ENSW, listner, CHANGE); REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKDIS; // Активирую таймер TC6 REG_PMC_PCER1 |= PMC_PCER1_PID33; // Активирую переферию TC6 (TC2 Channel 0) REG_PIOC_ABSR |= PIO_ABSR_P26 | PIO_ABSR_P25; // Подключаю выводы D4 и D5 REG_PIOC_PDR |= PIO_PDR_P26 | PIO_PDR_P25; // Отключаю иную переферию REG_TC2_CMR0 = TC_CMR_BCPC_CLEAR | // Установливаю TIOB на совпадение с RC0 TC_CMR_ACPC_CLEAR | // Установливаю TIOА на совпадение с RC0 TC_CMR_BCPB_SET | // Чет там делаю с B TC_CMR_ACPA_SET | // Чет там делаю с А TC_CMR_WAVE | // Реджим генератора TC_CMR_WAVSEL_UP_RC | // Автотригер RC TC_CMR_EEVT_XC0 | // Set event selection to XC0 to make TIOB an output TC_CMR_TCCLKS_TIMER_CLOCK1; // Set the timer clock to TCLK1 (MCK/2 = 84MHz/2 = 48MHz) REG_TC2_RC0 = freq; REG_TC2_IER0 = 0b00010000; // Включаю прерывания для = rc REG_TC2_IDR0 = 0b11101111; // Отключаю все остальные прерывания // enable interrupt vector NVIC_SetPriority (TC6_IRQn, 0); // Задаю высший приоритет как выше писал nik182 NVIC_EnableIRQ(TC6_IRQn); REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Включаю и очищаю счетчик } void loop() {} volatile uint32_t i = 0; // Это счетчик внутри обработчика прерываний volatile uint32_t dummy; // Это пустышка для очистки счетчика внутри обработчика void TC6_Handler() { i++; dummy = REG_TC2_SR0; REG_TC2_RA0 = imp2[i]; REG_TC2_RB0 = imp1[i]; if (i == n_impulse + 1) { i = 0; } } void listner()// Это обработчик для энкодера. Кручу в одну сторону, частота увеличивается, в другую - уменьшается { REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKDIS; // Выключаю и очищаю счетчик encoder_A = digitalRead(ENA); encoder_B = digitalRead(ENB); if ((!encoder_A) && (encoder_A_prev)) { if (encoder_B) { freq2 += 1000; //увеличиваем частоту } else { freq2 -= 1000; //уменьшаем частоту } } encoder_A_prev = encoder_A; // Вычисляю новую частоту freq = (clk_Freq * 2) / freq2; // Вычисленная частота ШИМ duty = freq * (PWM_divider - duty2 / 2) / PWM_divider; // Вычисленная скважность ШИМ for (int i = 0; i < n_impulse + 1; i++) { imp1[i] = freq; imp2[i] = duty; if (i == n_impulse) { imp1[i] = duty; imp2[i] = freq; } } setParam();// Вызов функции которая присваивает новые значения частоты } void setParam() // Функция которая присваивает новые значения частоты { REG_TC2_RC0 = freq; // Устанавливаю новую частоту REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Включаю и очищаю счетчик }Но и там есть некоторые проблемы
У меня есть еще один вариант. В котором я запускаю два таймера. Первый дает постоянный меандр, второй с пропуском:
Выход 1: _П_П_П_П_П_П_П_П_П_П
Выход 2: _____П______П_____П_
Фронты на самом деле совпадают идеально. Может есть какой способ принудительно, не останавливая первый таймер, устанавливать его выход на 0 при единице на выходе 2? Здесь у меня совсем все просто и топорно. Зато работает стабильно, при чем верчу кручу скважностью импульсами частотами в реалайме
freq = (clk_Freq * 2) / freq2; // Вычисленная частота ШИМ duty = freq * (PWM_divider - duty2/2) / PWM_divider; // Вычисленная скважность ШИМ REG_TC2_CMR0 |= TC_CMR_ASWTRG_CLEAR; REG_PMC_PCER0 |= PMC_PCER0_PID27; // Enable peripheral TC6 (TC2 Channel 0) REG_PIOB_ABSR |= PIO_ABSR_P25; // Switch the multiplexer to peripheral B for TIOA6 and TIOB6 D5 REG_PIOB_PDR |= PIO_PDR_P25; // Disable the GPIO on the corresponding pins REG_TC0_WPMR = 0x54494D00; // enable write to registers REG_TC0_CMR0 = 0b00000000000001101100010000000000; // alternative CMR for inverted output REG_TC0_IER0 = 0b00010000; // enable interrupt on counter = rc REG_TC0_IDR0 = 0b11101111; // disable other interrupts NVIC_EnableIRQ(TC0_IRQn); NVIC_SetPriority (TC0_IRQn, 0); REG_PMC_PCER1 |= PMC_PCER1_PID33; // Enable peripheral TC6 (TC2 Channel 0) REG_PIOC_ABSR |= PIO_ABSR_P25; // Switch the multiplexer to peripheral B for TIOA6 and TIOB6 D5 REG_PIOC_PDR |= PIO_PDR_P25; // Disable the GPIO on the corresponding pins REG_TC2_WPMR = 0x54494D00; // enable write to registers REG_TC2_CMR0 = 0b000000000000001101100010000000000; // alternative CMR for inverted output REG_TC2_IER0 = 0b00010000; // enable interrupt on counter = rc REG_TC2_IDR0 = 0b11101111; // disable other interrupts //NVIC_EnableIRQ(TC6_IRQn); counter1[n_impulse + 1]; for (int i = 0; i <n_impulse + 1; i++) { counter1[i] = freq; if (i == n_impulse) { counter1[i] = freq *2; } } REG_TC0_RC0 = freq * (n_impulse+2); // рассчёт частоты REG_TC0_RA0 = duty; // PWM value REG_TC2_RC0 = freq; // Низкий уровень REG_TC2_RA0 = duty; // Высокий уровень REG_TC0_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Enable the timer TC6 REG_TC2_CCR0 = TC_CCR_SWTRG | TC_CCR_CLKEN; // Enable the timer TC6А далее какой-нибудь обработчик вроде:
void TC0_Handler() { long dummy = REG_TC0_SR0; if (на этом таймере высоко) { вывод первого таймера сделать низким } else { выводом первого таймера управляет счетчик { }Вообщем кому интересно поставил на выходе логический исключающее-или SN74HC86 с фронтами 17нс, что является эквивалентом ~5мГц. На выходе небольшие пульсации сгладил пикофарадным конденсатором.