Перейти к содержанию
    

GD32F103RBT6. Пропуск прерываний в режиме захвата

В GD32F103RBT6 имеется Timer1. В Timer1 имеется 4 канала. Каждый канал можно настроить в режим захвата по фронту/срезу на пине контроллера и настроить соответствующие прерывания. Собственно я так и настроил. Функция обработки прерывания от Timer1 выглядит так:

Spoiler
void TIMER1_IRQHandler (void)
{
uint32_t temp = 0;
uint8_t  ch   = 0; // Переменная определяет номер канала.

static uint16_t prev_tim_capture_reg_val [NUMBER_OF_DALI_CHANNELS] = {0}; // Предыдущее значение регистра захвата для каждого канала.

//---If channel 0 capture/compare interrupt flag is set---//
if (TIMER_INTF(TIMER1) & TIMER_INTF_CH0IF) // If channel 0 capture/compare interrupt flag is set.
  {
  //---Input capture event--------------------------------------------------------------//

  //-Clear capture/compare interrupt flag-//
  TIMER_INTF(TIMER1) &= (~TIMER_INTF_CH0IF);
//      NVIC_ClearPendingIRQ(TIMER1_IRQn);
  //--------------------------------------//

  ch   = 0; // Выбор номера канала.
  temp = TIMER_CH0CV(TIMER1) & 0xFFFF;

  if (wait_start_bit_flag[ch] == RESET)
    {
    if (temp > prev_tim_capture_reg_val[ch])
      PulseDuration[ch] = temp - prev_tim_capture_reg_val[ch];
    else
      PulseDuration[ch] = 0xFFFF - prev_tim_capture_reg_val[ch] + temp;
    }
  else
    PulseDuration[ch] = 0; // Сброс счётчика длительности импульса.

  prev_tim_capture_reg_val[ch] = temp;

  if (TIMER_CHCTL2(TIMER1) & TIMER_CHCTL2_CH0P) // Если было настроено прерывание на срез...
    {
    CallbackInputPulse_ch0(DALI_BUS_LOG0, PulseDuration[ch]);

    // Раз словили срез, то надо начинать отслеживать stop, отслеживаем его после каждого среза.
    StopDuration[ch] = 0; // Для старта отслеживания stop-а, сбрасываем длительность стоп-интервала. Другой счётчик воспримет этот сброс как старт к подсчёту длительности.
    }
  else // Если было настроено прерывание на фронт... Для микроконтроллера посылка начинается с фронта на его пине, поэтому при приёме посылки, сначала придём сюда!!!
    {
    if (wait_start_bit_flag[ch]) // Если словили стартовый фронт...
      wait_start_bit_flag[ch] = RESET;
    else
      CallbackInputPulse_ch0(DALI_BUS_LOG1, PulseDuration[ch]);
    }
  TIMER_CHCTL2(TIMER1) ^= TIMER_CHCTL2_CH0P; // Change channel x capture/compare function polarity.
  DaliStates[ch]        = (!DaliStates[ch]); // Присваиваем новое лог. значение установившееся на шине DALI.
  //------------------------------------------------------------------------------------//
  }
//--------------------------------------------------------//


//---If channel 1 capture/compare interrupt flag is set---//
if (TIMER_INTF(TIMER1) & TIMER_INTF_CH1IF) // If channel 1 capture/compare interrupt flag is set.
  {
  //---Input capture event--------------------------------------------------------------//
  TIMER_INTF(TIMER1) &= (~TIMER_INTF_CH1IF); // Clear capture/compare interrupt flag.

  ch   = 1; // Выбор номера канала.
  temp = TIMER_CH1CV(TIMER1) & 0xFFFF;

  if (wait_start_bit_flag[ch] == RESET)
    {
    if (temp > prev_tim_capture_reg_val[ch])
      PulseDuration[ch] = temp - prev_tim_capture_reg_val[ch];
    else
      PulseDuration[ch] = 0xFFFF - prev_tim_capture_reg_val[ch] + temp;
    }
  else
    PulseDuration[ch] = 0; // Сброс счётчика длительности импульса.
    
  prev_tim_capture_reg_val[ch] = temp;
  
//  i_ch1++;

  if (TIMER_CHCTL2(TIMER1) & TIMER_CHCTL2_CH1P) // Если было настроено прерывание на срез...
    {
    CallbackInputPulse_ch1(DALI_BUS_LOG0, PulseDuration[ch]);
    StopDuration[ch] = 0; // Для старта отслеживания stop-а, сбрасываем длительность стоп-интервала. Другой счётчик воспримет этот сброс как старт к подсчёту длительности.
    }
  else // Если было настроено прерывание на фронт...
    {
    if (wait_start_bit_flag[ch])
      wait_start_bit_flag[ch] = RESET;
    else
      CallbackInputPulse_ch1(DALI_BUS_LOG1, PulseDuration[ch]);
    }
  TIMER_CHCTL2(TIMER1) ^= TIMER_CHCTL2_CH1P; // Change channel x capture/compare function polarity.
  DaliStates[ch]        = (!DaliStates[ch]); // Присваиваем новое лог. значение установившееся на шине DALI.
  //------------------------------------------------------------------------------------//
  }
//--------------------------------------------------------//


//---If channel 2 capture/compare interrupt flag is set---//
if (TIMER_INTF(TIMER1) & TIMER_INTF_CH2IF) // If channel 2 capture/compare interrupt flag is set.
  {
  //---Input capture event--------------------------------------------------------------//
  TIMER_INTF(TIMER1) &= (~TIMER_INTF_CH2IF); // Clear update interrupt flag.

  ch   = 2; // Выбор номера канала.
  temp = TIMER_CH2CV(TIMER1) & 0xFFFF;

  if (wait_start_bit_flag[ch] == RESET)
    {
    if (temp > prev_tim_capture_reg_val[ch])
      PulseDuration[ch] = temp - prev_tim_capture_reg_val[ch];
    else
      PulseDuration[ch] = 0xFFFF - prev_tim_capture_reg_val[ch] + temp;
    }
  else
    PulseDuration[ch] = 0; // Сброс счётчика длительности импульса.
    
  prev_tim_capture_reg_val[ch] = temp;

  if (TIMER_CHCTL2(TIMER1) & TIMER_CHCTL2_CH2P) // Если было настроено прерывание на срез...
    {
    CallbackInputPulse_ch2(DALI_BUS_LOG0, PulseDuration[ch]);
    StopDuration[ch] = 0; // Для старта отслеживания stop-а, сбрасываем длительность стоп-интервала. Другой счётчик воспримет этот сброс как старт к подсчёту длительности.
    }
  else // Если было настроено прерывание на фронт...
    {
    if (wait_start_bit_flag[ch])
      wait_start_bit_flag[ch] = RESET;
    else
      CallbackInputPulse_ch2(DALI_BUS_LOG1, PulseDuration[ch]);
    }
  TIMER_CHCTL2(TIMER1) ^= TIMER_CHCTL2_CH2P; // Change channel x capture/compare function polarity.
  DaliStates[ch]        = (!DaliStates[ch]); // Присваиваем новое лог. значение установившееся на шине DALI.
  //------------------------------------------------------------------------------------//
  }
//--------------------------------------------------------//


//---If channel 3 capture/compare interrupt flag is set---//
if (TIMER_INTF(TIMER1) & TIMER_INTF_CH3IF) // If channel 3 capture/compare interrupt flag is set.
  {
  //---Input capture event--------------------------------------------------------------//
  TIMER_INTF(TIMER1) &= (~TIMER_INTF_CH3IF); // Clear capture/compare interrupt flag.

  ch   = 3; // Выбор номера канала.
  temp = TIMER_CH3CV(TIMER1) & 0xFFFF;

  if (wait_start_bit_flag[ch] == RESET)
    {
    if (temp > prev_tim_capture_reg_val[ch])
      PulseDuration[ch] = temp - prev_tim_capture_reg_val[ch];
    else
      PulseDuration[ch] = 0xFFFF - prev_tim_capture_reg_val[ch] + temp;
    }
  else
    PulseDuration[ch] = 0; // Сброс счётчика длительности импульса.
    
  prev_tim_capture_reg_val[ch] = temp;

  if (TIMER_CHCTL2(TIMER1) & TIMER_CHCTL2_CH3P) // Если было настроено прерывание на срез...
    {
    CallbackInputPulse_ch3(DALI_BUS_LOG0, PulseDuration[ch]);
    StopDuration[ch] = 0; // Для старта отслеживания stop-а, сбрасываем длительность стоп-интервала. Другой счётчик воспримет этот сброс как старт к подсчёту длительности.
    }
  else // Если было настроено прерывание на фронт...
    {
    if (wait_start_bit_flag[ch])
      wait_start_bit_flag[ch] = RESET;
    else
      CallbackInputPulse_ch3(DALI_BUS_LOG1, PulseDuration[ch]);
    }
  TIMER_CHCTL2(TIMER1) ^= TIMER_CHCTL2_CH3P; // Change channel x capture/compare function polarity.
  DaliStates[ch]        = (!DaliStates[ch]); // Присваиваем новое лог. значение установившееся на шине DALI.
  //------------------------------------------------------------------------------------//
  }
//--------------------------------------------------------//
}
//------------------------------------------------------------------------------//

Когда фронты/срезы поступают на один из 4 каналов, всё работает как надо и вопросов не возникает. Но если фронты/срезы будут поступать одновременно на два и более каналов, то вопросы возникают.

Если слать фронты/срезы на канал 0 и канал 1, то на канале 1 будут пропуски в обработке прерываний. Выяснил это просто инкрементируя счётчики фронтов/срезов при обработке прерываний от каналов. Причём, если код как в посте, то пропуски будут на канале 1. 

Если в коде поменяю местами очерёдность обработки каналов (сначала канал 1, а потом канал 0), то пропуски всё равно будут на канале 1. Если уберу обработку канала 0, и буду слать фронты/срезы на каналы 1 и 2. То пропуски будут на канале 2.

Вангую, что мне ответят:

1) читай Джозефа Ю.;

2) не нужно совать всю обработку события в функцию обработки прерывания. Нужно установить флаг события, а уж в основном цикле разбираться;

3) использовать другие таймеры - не могу, остальные все заняты;

4) в UserManualе ответа не нашёл, Erratы не нашёл на сайте производителя.

Если есть у кого-нибудь другие комментарии, очень прошу подсказать. Как устранить или минимизировать пропуски?

Сам пробовал увеличить стек - не помогло. Приоритет прерываний от Timer1 равен 7. Приоритеты остальных прерываний ниже (значение больше 7).

 

Изменено пользователем haker_fox
Длинный код спрятал под спойлер.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 2/12/2024 at 3:33 PM, LAS9891 said:

Если есть у кого-нибудь другие комментарии, очень прошу подсказать. Как устранить или минимизировать пропуски?

Сам пробовал увеличить стек - не помогло. Повысил приоритет прерываний от Timer1 с 0 до 7. Вроде как стало лучше.

 

Какая частота на вход захвата подается и какая частота тактирования таймера ?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

6 minutes ago, dimka76 said:

Какая частота на вход захвата подается и какая частота тактирования таймера ?

На вход приходит такое:

image.thumb.jpeg.4f859b2e0406cfe443e3958b6c2551f3.jpeg

Если я не обсчитался, то это что-то около 1,2 кГц.

На счёт частоты тактирования таймера.  На вход таймера приходят 108 MHz (частота тактирования шины APB1 54 MHz умноженная на 2), затем импульсы проходят через prescaler, который понижает частоту до 1 MHz. На всякий случай код:

//---Настройка TIMER1---//
// SYSTEM_CLOCK = 108 MHz
RCU_APB1RST &= (~RCU_APB1RST_TIMER1RST);     // TIMER1 Unreset.
RCU_APB1EN  |= RCU_APB1EN_TIMER1EN;          // TIMER1 clock enable.
temp         = RCU_ClockFreq_Get(CK_APB1)*2; // Умножаем на 2 потому что в нашем случае APB1 prescale != 1, поэтому CK_TIMERx = CK_APB1 x2 = 54MHz x2 = 108MHz.
//temp = 108_000_000

//---Вариант для SYSCLK = 108 MHz---//
TIMER_PSC(TIMER1)    = (uint16_t)((temp/1000000)-1); // CK_TIMERx = CK_APB1 x2 = 54MHz x2 = 108MHz -> if TIMER_PSC = 107 -> 108MHz/(107+1) = 1000000 -> PSC_CK = 1 MHz.
//---------------------------------//

 

25 minutes ago, dimka76 said:

Повысил приоритет прерываний от Timer1 с 0 до 7. Вроде как стало лучше.

Это я херню написал.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 2/12/2024 at 4:13 PM, LAS9891 said:

На вход приходит такое:

А вы осциллографом не смотрели, что на входе микроконтроллера твориться ?

On 2/12/2024 at 4:13 PM, LAS9891 said:
//---Вариант для SYSCLK = 108 MHz---//
TIMER_PSC(TIMER1)    = (uint16_t)((temp/1000000)-1); // CK_TIMERx = CK_APB1 x2 = 54MHz x2 = 108MHz -> if TIMER_PSC = 107 -> 108MHz/(107+1) = 1000000 -> PSC_CK = 1 MHz.
//---------------------------------//

У вас случайно здесь переполнения не происходит ?

Может отладчиком посмотреть.
Или выход таймера на ножку вывести и осциллографом посмотреть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

1 minute ago, dimka76 said:

А вы осциллографом не смотрели

Смотрели, ничего криминального не увидели.

 

2 minutes ago, dimka76 said:

У вас случайно здесь переполнения не происходит ?

Я в комментариях к коду написал, что temp = 108000000, значение из отладчика. Соответственно ((108_000_000 / 1_000_000) - 1) = 107, и это всё в uint16_t, где переполнение?

 

4 minutes ago, dimka76 said:

ножку вывести и осциллографом посмотреть

При переполнении и прерывании на выходе было 1 МHz, как и должно быть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 2/12/2024 at 4:27 PM, LAS9891 said:

Смотрели, ничего криминального не увидели

А время работы обработчика прерывания не измеряли ?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

On 2/12/2024 at 5:05 PM, LAS9891 said:

Как и зачем?

Как:

Хотя бы так - в начале прерывания какую-нибудь свободную ножку в единичку поставить, а перед выходом из обработчика в нолик ее сбросить.

И осциллографом наблюдать длительность  получившегося импульса.


Зачем:
Чтобы развеять все сомнения.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

2 часа назад, LAS9891 сказал:
if (TIMER_INTF(TIMER1) & TIMER_INTF_CH0IF) // If channel 0 capture/compare interrupt flag is set.
  {
  //---Input capture event--------------------------------------------------------------//

  //-Clear capture/compare interrupt flag-//
  TIMER_INTF(TIMER1) &= (~TIMER_INTF_CH0IF);

Ну ведь уже 100500 раз здесь на форуме писали, почему так не надо делать. И почему нужно читать документацию на контроллер...

Так нет же - всё равно продолжаете наступать на те же самые грабли.  :unknw:

Вот этот быдлокод и приводит к потере прерываний. Открывайте мануал на МК, читайте описание регистра INTF таймера и начинайте думать - как правильно с ним работать.

2 часа назад, LAS9891 сказал:

4) в UserManualе ответа не нашёл

Да вы его даже не читали. Иначе бы не написали такое как выше.

Корректный код должен быть примерно таким:

u32 i = TIMER_INTF(TIMER1);
if (i & TIMER_INTF_CH0IF) {
  TIMER_INTF(TIMER1) = ~TIMER_INTF_CH0IF; // Clear capture/compare interrupt flag.
  ...
}
if (i & TIMER_INTF_CH1IF) {
  TIMER_INTF(TIMER1) = ~TIMER_INTF_CH1IF; // Clear capture/compare interrupt flag.
  ...
}
if (i & TIMER_INTF_CH2IF) {
  TIMER_INTF(TIMER1) = ~TIMER_INTF_CH2IF; // Clear capture/compare interrupt flag.
  ...
}
...

Это первое, что бросилось в глаза. Дальше не смотрел. Возможно там ещё баги есть.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

А я бы вообще вот так сделал 

u32 i = TIMER_INTF(TIMER1);
TIMER_INTF(TIMER1) = 0;

if (i & TIMER_INTF_CH0IF) {
  ...
}
if (i & TIMER_INTF_CH1IF) {
  ...
}

 

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 часа назад, LAS9891 сказал:
if (TIMER_CHCTL2(TIMER1) & TIMER_CHCTL2_CH0P) { // Если было настроено прерывание на срез...
...
} else { // Если было настроено прерывание на фронт... Для микроконтроллера посылка начинается с фронта на его пине, поэтому при приёме посылки, сначала придём сюда!!!
...
}
TIMER_CHCTL2(TIMER1) ^= TIMER_CHCTL2_CH0P; // Change channel x capture/compare function polarity.

Здесь тоже потенциальный баг. Плохая идея так делать - менять полярность на ходу. И даже не предпринять никаких мер коррекции случаев дребезга.

Подумайте - что будет если будет дребезг сигнала возле уровня переключения "0"/"1" и до строчки:

TIMER_CHCTL2(TIMER1) ^= TIMER_CHCTL2_CH0P;

сигнал успеет несколько раз пересечь уровень переключения "0"/"1" туда-сюда?

 

Если уж решили менять полярность фронт/спад на ходу, то следует принять меры против случаев многократных пересечений порога из-за дребезга.

42 минуты назад, VladislavS сказал:

А я бы вообще вот так сделал 

u32 i = TIMER_INTF(TIMER1);
TIMER_INTF(TIMER1) = 0;

Так делать нельзя. Иначе будут теряться прерывания. Подумайте почему.

(сначала написал неправильно)

Если уж хочется сразу сбросить все флажки, то нужно так:

u32 i = TIMER_INTF(TIMER1);
TIMER_INTF(TIMER1) = ~(i & (TIMER_INTF_CH0IF | TIMER_INTF_CH1IF | ...));
if (i & TIMER_INTF_CH0IF) {
  ...
}
if (i & TIMER_INTF_CH1IF) {
  ...
}

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Теоретически да. Несколько тактов "под угрозой". Безопаснее будет

TIMER_INTF(TIMER1) = ~i;

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Переписал согласно:

18 hours ago, jcxz said:

Корректный код должен быть примерно таким:

Вроде бы стало лучше, но полностью проблема не ушла.

 

18 hours ago, jcxz said:

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

Полностью согласен с Вами, мастер. По своему убогому скудоумию не знаю как поступить с этим. Подскажите, мастер.

Изменено пользователем LAS9891

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

41 минуту назад, LAS9891 сказал:

В этой строке всё верно? Прям "=" должно быть? Может "&=" ?

Такие вопросы как раз и возникают из-за того, что

41 минуту назад, LAS9891 сказал:

Поскольку про регистр INTF в документации не читал,

А ведь прочитать про него в документации займет даже меньше времени, чем писать на форум.

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

3 minutes ago, Сергей Борщ said:

займет даже меньше времени

Согласен. Вот всё что есть в UserManual о регистре INTF в Timer1:

(13_02.202409-47-22).thumb.jpg.2a105047453a479291bb2d942546e06d.jpg(13_02.202409-47-53).thumb.jpg.8d6731466a91d019a91d498b1272b602.jpg

Что я тут должен понять? Между каких строк я не вижу текста?

Поделиться сообщением


Ссылка на сообщение
Поделиться на другие сайты

Присоединяйтесь к обсуждению

Вы можете написать сейчас и зарегистрироваться позже. Если у вас есть аккаунт, авторизуйтесь, чтобы опубликовать от имени своего аккаунта.

Гость
Ответить в этой теме...

×   Вставлено с форматированием.   Вставить как обычный текст

  Разрешено использовать не более 75 эмодзи.

×   Ваша ссылка была автоматически встроена.   Отображать как обычную ссылку

×   Ваш предыдущий контент был восстановлен.   Очистить редактор

×   Вы не можете вставлять изображения напрямую. Загружайте или вставляйте изображения по ссылке.

×
×
  • Создать...