Мистика с таймером

Soy

✩✩✩✩✩✩✩
12 Мар 2021
7
0
Всем салют!
Пишу библиотеку на подобие OneWireSlave, в отличии от тех что есть на GitHub'е работает на внешнем прерывании и с несколькими датчиками на одной шине (по задумке). Столкнулся с такой ерундой: необходимо отлавливать Reset от мастера, причем мастер ведь может в любой момент прекратить сессию с ведомым послав тот же Reset, поэтому использую Timer2 на Atnega328p (Tmer0 используется delay(), milis() и micros(), поэтому исключаю, Tmer1 - Servo.h.
Настройка таймера в конструкторе класса:


C++:
//OWSLAVE.cpp

#include "OWSLAVE.h"
#include "Arduino.h"
#include <avr/io.h>
#include <avr/interrupt.h>


volatile uint8_t  *Intrrpt_Msk_Rgstr_Addr;
volatile uint8_t  *Intrrpt_Flg_Rgstr_Addr;
volatile uint8_t  *Ext_Intrrpt_Cntrl_Rgstr_Addr;
volatile uint8_t *reg, *out;
uint8_t INTx, INTFx, ISC0_bit, ISC1_bit,_Pin, _bit, _port;

#define EN_INT {*Intrrpt_Msk_Rgstr_Addr|=(1<<INTx);*Intrrpt_Flg_Rgstr_Addr|=(1<<INTFx);}  //enable interrupt
#define DIS_INT *Intrrpt_Msk_Rgstr_Addr&=~(1<<INTx);  //disable interrupt
#define SET_RISING (*Ext_Intrrpt_Cntrl_Rgstr_Addr=(1<<ISC1_bit)|(1<<ISC0_bit))  //set interrupt at rising edge
#define SET_FALLING {*Ext_Intrrpt_Cntrl_Rgstr_Addr &= ~(1 << ISC0_bit);*Ext_Intrrpt_Cntrl_Rgstr_Addr|=(1<<ISC1_bit);} //set interrupt at falling edge
#define SET_CHANGE {*Ext_Intrrpt_Cntrl_Rgstr_Addr &= ~(1 << ISC1_bit) ;*Ext_Intrrpt_Cntrl_Rgstr_Addr|=(1<<ISC0_bit);}//set interrupt at change edge
#define CHK_INT_EN (*Intrrpt_Msk_Rgstr_Addr|=(1<<INTx))==(1<<INTx) //test if interrupt enabled

#define SET_LOW {*reg |= _bit;}  //линия 1-Wire на выход и на низкий уровень  *out &= ~_bit;
#define ENTR_LINE {*reg &= ~_bit;}  //линия 1-Wire на вход и Z уровень
#define READ_HIGH (*portInputRegister(_port) & _bit)//считать высокий сигнал с линии
#define READ_LOW (!(*portInputRegister(_port) & _bit))//считать низкий сигнал с линии
#if defined ([B]AVR_ATmega328P[/B])
    #define TIMER_INT ISR(TIMER2_COMPA_vect) //the timer interrupt service routine
    #define TCNT_REG TCNT2  //register of timer-counter
    #define EN_TIMER {TCNT_REG=0;TIMSK2|= (1 << OCIE2A); TIFR2|=(1<<TOV2);} //enable timer interrupt
    #define DIS_TIMER {TIMSK2 &=~(1<<TOIE2);}// disable timer interrupt
#endif


#define OWT_RESET 480
#define clock_MC (F_CPU / 1000000L )
#define clock_RESET (OWT_RESET/(64/clock_MC))

ISR (INT1_vect, ISR_ALIASOF (INT0_vect));
ISR (INT0_vect){
// код внешнего прерывания
}

TIMER_INT {
//код прерывания таймера
}
OWSLAVE::OWSLAVE(uint8_t pin){
         _Pin = pin;
        _bit = digitalPinToBitMask(_Pin);
        _port = digitalPinToPort(_Pin);
        if (_port == NOT_A_PIN) return;
        out = portOutputRegister(_port);
        reg = portModeRegister(_port);
        #if defined ([B]AVR_ATmega328P[/B])
            Intrrpt_Msk_Rgstr_Addr=&EIMSK;
            Intrrpt_Flg_Rgstr_Addr=&EIFR;
            Ext_Intrrpt_Cntrl_Rgstr_Addr=&EICRA;
            if (_Pin==2){
                INTx=INT0;
                INTFx=INTF0;
                ISC0_bit=ISC00;
                ISC1_bit=ISC01;
            }else if(_Pin==3){
                INTx=INT1;
                INTFx=INTF1;
                ISC0_bit=ISC10;
                ISC1_bit=ISC11;
            }
           
            TCCR2A = 0;
            TCCR2B = 0;
            TCNT2 = 0;
            TIMSK2 = 0;
            OCR2A = clock_RESET;
            TCCR2A |=(1<<WGM21);
            TCCR2B |=(1<<CS22) | (0<<CS21) | (0<<CS20);//clkT2S/64 (From prescaler)
            DIS_TIMER;
        #endif
}
Теперь собственно в чем проблема:
как видно из кода ниже я запускаю таймер в обработчике внешнего прерывания:

C++:
static OWSLAVE *instance = NULL;
//====== Спасибо Гайверу!!! =====================
void attachFunction(void (*function)()) { // передача указателя на функцию
  p_function = *function;
}
void (*p_function)();   // указатель на p_function
//=============================================
ISR (INT1_vect, ISR_ALIASOF (INT0_vect));
ISR (INT0_vect){
  if(!status){ // эта переменная активна только при SEARCH_ROM приеме
    EN_TIMER;// запускаем таймер детектора Reset
    return;
  }
}

TIMER_INT {
  DIS_TIMER;//тормозим внешнее прерывание
  DIS_INT;//тормозим таймер
  mode=OW_RESET;
  (*p_function)();// void Process()
}

void Process(){
    uint8_t err;
    byte addr[8];
    instance-> errno = ONEWIRE_NO_ERROR;
    uint8_t r=READ_HIGH;
    switch (mode) {
        case OW_RESET:
            presence();
            //mode=OW_COMMAND;
            EN_INT;
            return;
    }
}

void presence(){
  delayMicroseconds(30);
  SET_LOW;//установить на входе низкий уровень
   delayMicroseconds(120);
  ENTR_LINE;//ВХОД НА ПРИЕМ(шина отпущена)
  while (READ_LOW); //ждем высокий уровень на входе
  delayMicroseconds(30); 
}

void OWSLAVE::begin(uint8_t *rom) {//по прерыванию
    cli();//откл. глоб.прер.
    ENTR_LINE;//пин на вход
    SET_FALLING;//отслеживаем FALLING
    setRom(rom); //стандартно
    attachFunction(Process);// спасибо Гайверу
    instance = this;
    EN_INT;
    sei(); // вкл. глоб.прер.
}

void OWSLAVE::setRom(byte  rom[8]) {
#if ONEWIRESLAVE_CRC
    for (int i=0; i<7; i++)
        this->rom[i] = rom[i];
    this->rom[7] = crc8(this->rom, 7);
#else
    for (int i=0; i<8; i++)
        this->rom[i] = rom[i];
#endif
}
tstOWSLAVE.ino:
//тестинг ведомого

#include "OWSLAVE.h"

OWSLAVE ds(2);

//                     {Fami, <---, ----, ----, ID--, ----, --->,  CRC}
byte  rom[8] = {0x44, 0x1A, 0x00, 0x00, 0x0F, 0x00, 0x00, 0xFF}; //адрес устройства, 8 байт по протоколу

void setup() {
  Serial.begin(9600);
  ds.begin(rom);
}

void loop() {


}
... и вот тут начинается...
если в тестовом проекте мастера сделать так:

tst_OneWire.ino:
#include <OneWire.h>

#define TX_PIN 5      // пин

OneWire ds(TX_PIN);

byte addr[8];


void setup() {
  Serial.begin(9600);
}


void loop() {
  ds.reset();
    //delay(50;)
}
... то все норм - presets отправляется, мастером детектируется.
Но, как только раскомментишь delay(), то начинаются "чертовы пляски" пропуски иvпульсов Reset от мастера в неопределенной последовательности.

Если же отказаться от таймера в функции Process() и сделать так:
OWSLAVE.cpp:
volatile uint32_t old_previous = 0;

ISR (INT1_vect, ISR_ALIASOF (INT0_vect));
ISR (INT0_vect){
  if(!status){
    (*p_function)();
    return;
  }
}

void Process(){
    uint8_t err;
    byte addr[8];
    instance-> errno = ONEWIRE_NO_ERROR;
    uint8_t r=READ_HIGH;
    switch (mode) {
        case OW_START:
           old_previous = micros();
           mode=OW_RESET;
           SET_RISING;
           return;
        case OW_RESET:
             DIS_INT;
             if(micros()-old_previous <480){
               mode=OW_START;SET_FALLING;EN_INT;
               return;
            }
            old_previous=0;
             
            presence();
            //mode=OW_COMMAND;
            EN_INT;
            return;  
    }
}
... то все работает безукоризненно.

Подскажите в чем проблема, плиз, а то два дня парюсь, думаю уже отказаться от таймера вообще, но тогда жестко надо быть привязанным к правилу: ни каких лишних Reset!
 

Forgetweb

✩✩✩✩✩✩✩
8 Май 2022
11
5
@Soy,
p_function = *function;
Это именно так и задумано? Может должно быть так - p_function = function; ?
Ну и просто мысли -
ISR (INT0_vect){
if(!status){
(*p_function)();
return;
}
Вызов функции по указателю в обработчике прерывания ведет к сохранению в стеке вообще почти всех регистров.
 

Soy

✩✩✩✩✩✩✩
12 Мар 2021
7
0
@Forgetweb,
Process() определена как friend void Process(), поэтому вызывается через указатель, для того чтобы иметь доступ ко всем функциям внутри класса через instance->.....
В любом случае причина не в этом.

P.S. Нашел причину. ошибка была здесь:
C++:
#define DIS_TIMER {TIMSK2 &=~(1<<TOIE2);}// disable timer interrupt
 
Изменено: