Помощь с аппаратными таймерами.

gus21rus
Offline
Зарегистрирован: 21.05.2017

Добрый день господа форумчане, я пришел за помощью в вопросе таймеров.

// Обработчик переполнения счетчика.
ISR(TIMER1_OVF_vect)
{
  if(timerConf==true && timerRun==true)
  pop_task();
  if( (MAX_ULONG-microsec)<MAXMICROTIMER){ //Если произойдет переполнение.
    microsec=MAXMICROTIMER-(MAX_ULONG-microsec);
    return;
  }
  microsec+=MAXMICROTIMER;
}
//----------------------------------------------------------------------------------------------------
// Совпадение A таймера-счётчика 1
ISR(TIMER2_COMPA_vect) //Регистр TCNT1 принял значение, равное регистру OCR1A
{
 //run_funcTL(jobCOMPA);
 busyCOMPA=false; //Выставить триггер занятости на свободный
 timerCOMPAdisable(); //Отключить прерывания по сравнению.
 pop_task(); //Найти новую задачу.
}
//----------------------------------------------------------------------------------------------------
// Совпадение B таймера-счётчика 1
ISR(TIMER2_COMPB_vect) //Регистр TCNT1 принял значение, равное регистру OCR1B
{
 //run_funcTL(jobCOMPB);
 busyCOMPB=false; //Выставить триггер занятости на свободный
 timerCOMPBdisable(); //Отключить прерывания по сравнению.
 pop_task(); //Найти новую задачу.
}
//****************************************************************************************************
// Тумблеры включения/отключения прерываний по сравнению OCR1 и OCR2
void timerCOMPAenable()
{
  TIMSK1=(busyCOMPB==false) ? 0x03 : 0x07;
}
//----------------------------------------------------------------------------------------------------
void timerCOMPAdisable()
{
  TIMSK1=(busyCOMPB==false) ? 0x01 : 0x05;
}
//----------------------------------------------------------------------------------------------------
void timerCOMPBenable()
{
  TIMSK1=(busyCOMPA==false) ? 0x07 : 0x05;
}
//----------------------------------------------------------------------------------------------------
void timerCOMPBdisable()
{
  TIMSK1=(busyCOMPA==false) ? 0x01 : 0x03;
}
//****************************************************************************************************
//                                  Настройка пуск и стоп таймера
int settingTimer()
{
    TCCR1A=0x00; //настройка таймера
    TCNT1=0x00; //Обнуляем регистр счетчика.
    OCR1A=0x00; //обнуление регистра сравнения (записывается функциями)
    OCR1B=0x00; //тоже но второй регистр сравнения 2
    TIMSK1=0x01; //Разрешаем прерывания по переполнению.
    init_tl(); // Инициализируем список.
    timerConf=true;
}
//----------------------------------------------------------------------------------------------------
int startTimer()
{
  if(!timerConf){
     return -1; //Вначале нужно вызвать settingTimer!
  }
  timerRun=true;
  TCCR1B=0x01; // Настраиваем прескалер на тактирования от системного кварца.
}
//----------------------------------------------------------------------------------------------------
int stopTimer()
{
  if(timerRun){
  TCCR1B=0x00; //Настраиваем регистр на отсутствие источника тактирования( таймер остановлен )
  timerRun=false;
  microsec=0;
  }
}

Вызов startTimer вызывает хаотичную перезагрузку микроконтроллера. Все перепробывал, тчетно. Возможно у гуру есть ответ в чем же я ошибься ? Благодарю.

wdrakula
wdrakula аватар
Offline
Зарегистрирован: 15.03.2016

gus21rus пишет:

в чем же я ошибься ?

1. в строках 14 и 23 указаны таймер2, а не 1. Следовательно может быть все что угодно;

2. Главная ошибка в создании очередной ОС для Ардуино. Для процессоров с малыми ресурсами она не применима, а для процессоров с достаточными ресурсами уже создана.

gus21rus
Offline
Зарегистрирован: 21.05.2017

Благодарю за наведение на косяк. ОС я не создаю, нужен таймер со списком задач. который я сейчас г**кожу

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

gus21rus,  https://github.com/DetSimen/Arduino-   список таймеров.  Может, поможет. 

gus21rus
Offline
Зарегистрирован: 21.05.2017

мой на микросекунды настроен.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Тогда надо быть очень осторожным, за микросекунды выполняется ограниченное число команд процессора, поэтому функция отклика должна быть как можно короче, иначе следующее прерывание можно пропустить.  А если исполнять длинную функцию вне контекста прерывания, тогда можно исчерпать стек. 

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

В коде нет реализации pop_task() .. она что делает? Выложите..

За микросекунду выполняется 16 однотактовых команд, но поскольку все команды работы с переменными (загрузка, запись в/из памяти, стека) выполняются за 2 такта, то надо ориентироваться "в смеси" будет примерно 10 команд. Байтовых. Поскольку часто пользуют int или вовсе long, то эту цифирьку надо уменьшить примерно в 2 раза. Да, ещё вызов подпрограммы без параметров занимает 10 тактов по 5 на "вход" и "выход", и каждый параметр съедает ещё 2-8 тактов в зависимости от разрядности и порядкового номера..

Функция pop_task() вызывается из под обработчика прерывания, соответственно время его исполнения увеличивается на время работы этой функции + 10тактов (вход-выход). Если за это время прилетит следующее прерывание оно запросто может быть "утерянным"..

Если эта функция "активирует" (вызывает) найденную задачу (из коммента), то ваще будет "труба" ибо прерывания тут - не открываются явно: потеря прерываний прогнозируется с большой вероятностью.

Если прерывания разрешаются внутри pop_task() И запускается задача из под неё - можно прогнозировать переполнение стека (зачистка стеком данных) и произвольное поведение программы в целом. Это если внутри pop_task() не принято специальных мер по защите.. (тут их не видно).

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Ну и ещё: "тут микросекунды" - весьма опрометчиво, поскольку вход-выход в обработчик прерывания на "С/С++" очень прожолив и надо ожидать, что сам обработчик исполняется в среднем 40-50мксек. даже если он состоит из пары действий на ЯВУ.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Вот именно из тех соображений, которые изложил я и уважаемый Arhat109-2, я и выбрал для своего TTimerList минимальный интервал времени 1 миллисекунда.  За это время вызываемая функция гарантированно успеет вызваться, положить сообщение в очередь, и выйти из контекста прерывания. Примерно так: 

кусок из работающего проекта вентилятора в сортире 

в сетапе настраиваются таймеры на разный интервал срабатывания

  hvarblink = TimerList.Add(tmrBlink, 1000); // индикаторный светодиод на панели
  hMQ2 = TimerList.AddSeconds(tmrReadMQ2, 2); // опросить датчик дыма раз в две секунды
  hTick = TimerList.AddSeconds(tmrTick, 1); // часы (раз в секунду)
  hKeyShield = TimerList.Add(tmrReadKey,200); // опросить клавиатуру (5 раз в секунду)
  hVentTimer = TimerList.AddMinutes(tmrVentOff, 1);    // вентилятор работает минуту, после включения
  TimerList.TimerStop(hVentTimer);

функция, которая вызывается таймером 

void tmrReadMQ2(void)
{
	MessageList->Add(msg_ReadMQ2);
} // вот здесь мы покидаем контекст прерывания

А в loop() просто отслеживаются сообщения и передаются исполнителям

void loop() 
{

	if (NOT MessageList->Availiable()) return;

	TMessage msg = MessageList->GetMessage();

	switch (msg.Message)
	{
		case msg_Empty:					break;   // пустое сообщение, просто убираем его из очереди
		case msg_Paint:      evtPaint();		break;   // перерисовать икранчег
		case msg_ReadMQ2:    MQ2.Read();		break;   // прочитать датчик дыма
		case msg_MQ2Changed: MQ2Changed(msg);		break;   // показания даччика дыма изменены
		case msg_SecondTick: PrintColon();		break;   // помигать двоеточием в часах (раз в секунду)
		case msg_ReadKey:    KeyShield.ReadKey();       break;   // опросить клавиатуру

		case msg_VentON:     evtVentON();	        break;   // включить вентилятор
		case msg_VentOFF:    evtVentOFF();	        break;   // выключить вентилятор 
		default: Serial << "Error in MessageList\n";
	}
}

функция реагирования тоже только помещает сообщение в очередь

void MQ2Changed(TMessage msg) // изменились показания даччика дыма
{

	if (msg.LoParam > 400) MessageList->Add(msg_VentON);

	MessageList->Paint();   // перерисовать экран
}

 

b707
Offline
Зарегистрирован: 26.05.2017

DetSimen пишет:

Вот именно из тех соображений, которые изложил я и уважаемый Arhat109-2, я и выбрал для своего TTimerList минимальный интервал времени 1 миллисекунда.  За это время вызываемая функция гарантированно успеет вызваться, положить сообщение в очередь, и выйти из контекста прерывания.

Очень интересный код. Однако, как я понимаю, в плотно загруженном loop() своевременная обработка сообщений в очереди не гарантируется. Можно ли для самых критичных ко времени действий, вместо помещения сообщений в очередь, выполнять их прямо из контекста прерывания? Что можно успеть при периоде 1 мс? Помигать парой диодов успеем, чтобы осталось время и на работу остальной части программы?

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

примерно 10 тысяч инструкций в 1 мс.

Некоторые вещи, которые не требуют много времени, я делаю прямо в таймере. 

Что характерно, часы на этом таймере отстают всего на полминуты в сутки ( при условии хорошего кварца и правильно подобранной константы в таймере). А потом синхронизируются в полночь по NRF24L01.

Единственное, операции, требующие millis() или упаси Боже, micros() в контексте прерывания неправильно работают.  Нарвался так на чтении DHT11.

b707
Offline
Зарегистрирован: 26.05.2017

DetSimen пишет:

Единственное, операции, требующие millis() или упаси Боже, micros() в контексте прерывания неправильно работают.  Нарвался так на чтении DHT11.

Я так понимаю, что в контексте прерываний не только millis() и micros() неверно работают, но и например обращение к Serial или SoftSerial. Думаю, в контексте прерываний вообще не стоит использовать никакие библиотеки или протоколы доступа к устройствам, так как все они или на прерываниях, или millis и micros

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Поэтому, чтоб не думать об..., я и реализовал всю логику, через очередь сообщений. 

Например, в функции отклика таймера (в прерывании) можно часами рулить. Что нибудь простое там запросто поместица. 

void tmrTick(void)
{
	MessageList->Add(msg_SecondTick);  // мигать двоеточием
	
	sec++;
	if (sec < 60) return; else { sec = 0; min++; } // если изменились только секунды, на выход
	if (min == 60) { min = 0; hour++; }
	if (hour == 24) { hour = 0;}
	MessageList->Paint();  // Минуты или часы изменились, перерисовать
}

 

gus21rus
Offline
Зарегистрирован: 21.05.2017

Господа, спокойнее. я не говорю о том что мне нужна размерность строго в 1мкс. мне нужно чтобы условие мое срабатывало Чаще чем 1мс.  Что там в pop_task ввиду самозащиты от шквала негативных комментариев по поводу связки г...о кода и костылей выкладывать не стал. Вопрос был с таймером, вопрос решен.

Arhat109-2
Offline
Зарегистрирован: 24.09.2015

Все и так спокойны. Зря что не стали выкладывать, ибо это и есть ключ к работоспособности всей идеи. Вам просто перечислили небольшую кучку дерьма, в которую Вы стремитесь вступить обеими лапками .. и только.

А в целом, вполне можно из под прерывания работать и с длинным/долгим кодом и с временем и с сериалом .. только это надо делать грамотно, понимая в точности что, где, когда и как происходит.

Получилось .. и ладно. При возникновении следующих затыков - перечитайте внимательно тутошние комменты. Поможет на 146%. :)