Обработчик прерываний кладет контроллер

Доброго дня всем
Возникает проблема случайных зависаний контроллера при срабатывании прерывания. Было предположение, что так получается из-за прерывания определенных участков кода, что влечет краш, или из-за самих инструкций в обработчике прерывания. Прописал для отладки зажигание/погасание 13светодиода (для чего убрал SPI) в обработчике, понял, что он работает по изменению пина, а не по фронту, что могло вызвать подобное зависание, программно сделал его по фронту (да, не само прерывание, но хотя бы выполнение инструкций), теперь светодиод зажигался единожды, поспамил прерывание, крашей не произошло. Убираю 13 светодиод и возвращаю SPI, вернулась и проблема с крашами. Понял, что в этом как-то замешан SPI. На него подключены 4 ЦАП, ниже представлены отрывки кода с обработчиком прерываний и с классом ЦАП, методы класса вызываются в бесконечном цикле одной единственной функцией.
Попробовал запретить прерывания во время записи значений в ЦАП(в классе видно), но не помогло, может кто-то объяснить, в чем скрывается проблема?

uint8_t oldpinc = 0xFF;
volatile bool interrupt_state = 0;
ISR(PCINT1_vect) {  // Обработчик прерывания на пине А4
uint8_t changedbits = PINC ^ oldpinc;
  oldpinc = PINC;
  if (interrupt_state == 0) {
 
  if (changedbits & (1 << PC4)) { // Изменился A4, тарирование
    for (int i = 0; i < 4; i++) {
    
    scale[i].tare();  // тарирование
    scale[i].filt = 0;
  }
  // digitalWrite(13, HIGH);
  // delay(1500);
  // digitalWrite(13, LOW);
  }

  if (changedbits & (1 << PC5)) { // Изменился A5, калибровка
    for (int i = 0; i < 4; i++) {
    calibration(i);  // калибровка
  }
  }
  
  interrupt_state = 1;// Установка флага тарирования
  }
  else if (interrupt_state == 1){
    tare_sig = true;
    interrupt_state = 0;
  }
}
class DAC {
public:
  DAC() {
  }
  void begin(uint8_t CS, byte Regs[2]) {  // Инициализация ЦАП, Pins - пины SPI
    _DACMode = Regs[0];
    _DACWrite = Regs[1];
    CS_PIN = CS;
    bitSet(_SFR_IO8(_DACMode), CS_PIN);
    bitSet(_SFR_IO8(_DACWrite), CS_PIN);

    // Начало транзакции, 20МГц, Старший байт первый, Мод0
  }
  void write(float value) { 
    noInterrupts(); // Запись значения в ЦАП
    if (value < 0) {         // Если значение опускается меньше нуля, записывать ноль
      _VAL = 0;
    } else if (value > REF_GR_DAC) {  // Если значение выше референсного, записывать максимальное
      _VAL = 65535;
    } else {
      _VAL = uint16_t(65535 * value / REF_GR_DAC);  // Расчет значения по пропорции
    }
    
    bitClear(_SFR_IO8(_DACWrite), CS_PIN);  // Запись в чипселект 0 для выбора ЦАП для передачи

    SPI.transfer16(_VAL);  // передача ЦАП
    //delay(15);
    bitSet(_SFR_IO8(_DACWrite), CS_PIN);  // Запись в чипселект 1 для окончания передачи
    interrupts();
  }
  uint16_t get_value() {
    return _VAL;
  }
  //MSBFIRST в настройке spi = избегаем разделения значений по двум байтам, выдача начинается со старшего байта
private:
  uint16_t _VAL;
  uint8_t CS_PIN;
  byte _DACMode, _DACWrite;
};

void four_ch_test() {
  tare_sig = false;
  for (byte i = 0; (i < 4) && !tare_sig; i++) { // Выполнение при активном флаге
    values[i] = scale[i].filter();  //запись фильтрованного значения
  }

  for (byte i = 0; (i < 4) && !tare_sig; i++) {
    dac[i].write(values[i]);  // запись в ЦАП
  }
}

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

2 лайка

секретный код для этого вызова можно посмотреть?

По огрызкам такие нетривиальные ляпы не ищутся. Тут каждая мелочь важна.

Например, никто не знает какую именно библиотеку SPI Вы используете. В некоторых нельзя вызывать transfer с запрещёнными прерываниями (как Вы это делаете), т.к. внутри transfer используется прерывание “SPI Serial Transfer Complete”. Ну, и вообще, тут много нюансов.

Начинайте сокращать код, наблюдая за проблемой. Постарайтесь сократить код как можно радикальнее, но чтобы проблема сохранялась. Тогда станет понятно в чём беда.

1 лайк

Есть такое, но я его специально не прикреплял, потому что не вызываю его

void calibration(int ch) {
  digitalWrite(13, LOW);
  delay(2000);
  digitalWrite(13, HIGH);
  delay(5000);
  scale[ch].calibrate_scale(weight_of_standard);
  EEPROM.put(ch*4, scale[ch].get_scale());
  digitalWrite(13, LOW);
  delay(3000);
}

В ISR не работает.

В прерывании я поднимаю флаг того, что оно произошло, чтобы не продолжать выполнять код, на котором оно остановилось
в four_ch_test() у меня в условии стоит проверка флага прерывания, чтобы циклы for перестали выполняться, если прерывание сработало. Это нужно, чтобы в ЦАП не произошла запись мусора, который может образоваться, если прерывание срабатывает во время побитового чтения АЦП
Я не совсем понял, вы имеете в виду, проставить подобные условия (на ложность флага прерывания, который будет сбрасываться в начале loop) в других участках кода, или наоборот имеете в виду выполнение по истинности флага?

Благодарю за замечание, точно, нужно будет переписать на millis(), delay тут рудимент, пока я пользовался функцией до добавления ее функционала в прерывание

millis() тоже не работает ))

следующий…))

Да, благодарю за совет
Что касается полного кода, прикреплю здесь

Спойлер
#include <SPI.h>
#include <EEPROM.h>

#define weight_of_standard 100  // Калибровочный вес
#define power_down() \
  \          
bitSet(_SFR_IO8(_Write), _clkPin); \
  delayMicroseconds(64)
#define power_up() bitClear(_SFR_IO8(_Write), _clkPin)  // Макрос для включения питания АЦП
#define GAIN64A 2                                       // Установка Коэффициента усиления
#define REF_GR_DAC 140.00
#define SCALE_CH 0
#define Scale_coeff 380
volatile bool tare_sig = false;

class DAC {
public:
  DAC() {
  }
  void begin(uint8_t CS, byte Regs[2]) {  // Инициализация ЦАП, Pins - пины SPI
    _DACMode = Regs[0];
    _DACWrite = Regs[1];
    CS_PIN = CS;
    bitSet(_SFR_IO8(_DACMode), CS_PIN);
    bitSet(_SFR_IO8(_DACWrite), CS_PIN);

    // Начало транзакции, 20МГц, Старший байт первый, Мод0
  }
  void write(float value) {  // Запись значения в ЦАП
    if (value < 0) {         // Если значение опускается меньше нуля, записывать ноль
      _VAL = 0;
    } else if (value > REF_GR_DAC) {  // Если значение выше референсного, записывать максимальное
      _VAL = 65535;
    } else {
      _VAL = uint16_t(65535 * value / REF_GR_DAC);  // Расчет значения по пропорции
    }
    
    bitClear(_SFR_IO8(_DACWrite), CS_PIN);  // Запись в чипселект 0 для выбора ЦАП для передачи

    SPI.transfer16(_VAL);  // передача ЦАП
    //delay(15);
    bitSet(_SFR_IO8(_DACWrite), CS_PIN);  // Запись в чипселект 1 для окончания передачи
  }
  uint16_t get_value() {
    return _VAL;
  }
  //MSBFIRST в настройке spi = избегаем разделения значений по двум байтам, выдача начинается со старшего байта
private:
  uint16_t _VAL;
  uint8_t CS_PIN;
  byte _DACMode, _DACWrite;
};

class SCALE {
public:
  /*Порядок записи в массив: DR, DW, DM, CW, CM, DP, CP*/
  SCALE(int i = 1) {
  }

  /*ИНИЦИАЛИЗАЦИЯ ДАТЧИКА*/
  void begin(byte Regs[3], byte Pins[2]) {  // Инициализация АЦП
                                            /*Адресса регистров HX PIND, PORTD, DDRD  PORTB DDRB*/
    _Read = Regs[0];                        // Запись значений регистров и пинов
    _Write = Regs[1];
    _Mode = Regs[2];
    _dataPin = Pins[0];
    _clkPin = Pins[1];
    bitSet(_SFR_IO8(_Mode), _clkPin);  // Настройка регистров
    bitClear(_SFR_IO8(_Mode), _dataPin);
    bitClear(_SFR_IO8(_Write), _clkPin);
    bitClear(_SFR_IO8(_Write), _dataPin);
    power_down();  //  Выключение и включение АЦП
    power_up();
    flag_filt = 0;
    _chan = GAIN64A;
    _weight = 0;
    _value = 0;
    _offset = 0;
    _scale = 1;
  }

  /*УСТАНОВКА МАСШТАБА*/
  void set_scale(float scale) {
    _scale = 1.0 / scale;
  }


  /*КАЛИБРОВКА*/
  void calibrate_scale(uint8_t weight) {
    long read_av = 0;
    for (uint8_t i = 0; i < 15; i++) {  // Калибровка на 15 значениях среднее арифметическое
      read_av += read();
    }
    read_av = read_av / 15;
    _scale = (1.0 * weight) / (read_av - _offset);  // получаем масштаб
  }

  /*ВЕРНУТЬ МАСШТАБ*/
  float get_scale() {
    return 1 / _scale;
  }

  /*ТАРИРОВАНИЕ*/
  void tare() {
    _offset = 0;
    for (uint8_t i = 0; i < 3; i++) {  // среднее арифметическое по трем замерам
      _offset += read();
    }
    _offset = _offset / 3;
  }

  /*ЧТЕНИЕ СЫРОГО ЗНАЧЕНИЯ*/
  long read() {
    while (bitRead(_SFR_IO8(_Read), _dataPin) == HIGH) yield();
    _weight = 0;
    for (uint8_t i = 0; i < 24; i++) {    // посылка сигналов тактирования
      bitSet(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, HIGH);
      delayMicroseconds(1);
      _weight <<= 1;
      if (bitRead(_SFR_IO8(_Read), _dataPin)) _weight |= 1;
      bitClear(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, LOW);
      delayMicroseconds(1);
    }
    for (uint8_t i = 0; (i < _chan + 1); i++) {
      bitSet(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, 1);
      delayMicroseconds(1);
      bitClear(_SFR_IO8(_Write), _clkPin);  //digitalWrite(_clock, 0);
      delayMicroseconds(1);
    }
    if (_weight & 0x800000) _weight |= 0xFF000000;  // отрицательные
    return _weight;
  }

  /*ПЕРЕВОД СЫРОГО ЗНАЧЕНИЯ В ГРАММЫ*/
  float value() {
    read();
    _value = (_weight - _offset) * _scale;
    return _value;
  }

  /*ВЕРНУТЬ ЗНАЧЕНИЕ В ГРАММАХ*/
  float get_value() {
    return _value;
  }

  /*ФИЛЬТРАЦИЯ ЗНАЧЕНИЯ В ГРАММАХ*/
  float filter() {
    noInterrupts();
    float newVal = value();
    byte k = 5;
    if (!flag_filt) filt = newVal;
    flag_filt = true;
    if (abs(newVal - filt) <= 0.5) k = 150;  // Выбор коэффициента для бегущего среднего в зависимости от величины изменения
    else if (abs(newVal - filt) >= 0.51 && abs(newVal - filt) < 2.7) k = 4;
    else if (abs(newVal - filt) >= 2.7 && abs(newVal - filt) < 3.5) k = 2;
    else if (abs(newVal - filt) >= 3.5 && abs(newVal - filt) < 5) k = 6;
    else if (abs(newVal - filt) >= 5 && abs(newVal - filt) < 7) k = 10;
    else if (abs(newVal - filt) >= 7) k = 20;
    filt += (newVal - filt) / k;  // фильтрация по бегущему среднему
    return filt;
    interrupts();
  }


  float filt;

private:
  byte _Read, _Write, _Mode;
  byte _dataPin, _clkPin, _chan;
  volatile long _weight, _offset;
  float _scale, _value;
  
  bool flag_filt;
};

SCALE scale[4];  // создание объектов АЦП
DAC dac[4];      // Создание объектов ЦАП

// Значения веса с датчиков
float values[4] = { 0, 0, 0, 0 };


/*Адресса регистров HX PIND, PORTD, DDRD */
byte ADC_D_Regs[3] = { 0x09, 0x0B, 0x0A };
// DDRC PORTC
byte DAC_C_Regs[2] = { 0x07, 0x08 };
// Пины CS для DAC      CS0 CS1 CS2 CS3
byte SPI_CS[4] = { 0, 1, 2, 3 };
// Коэффициенты для АЦП 1-4 каналы
float scale_coeffs[4];
//float scale_coeffs_hard[4] = {-368.68, 917, -430.39, 528};
// Пины данных и тактирования для АЦП
byte ADC_D_Pins[4][2] = {
  { 1, 0 },
  { 2, 3 },
  { 4, 5 },
  { 6, 7 },
};

bool flag = false; // флаг для калибровки 
// Функции работы с каналами: чтение и запись канала SCALE_CH, всех четырех, возврат масштаба первого канала, возврат канала 2-4
void four_ch_test(), calibration(int channel);


uint8_t oldpinc = 0xFF;
volatile bool interrupt_state = 0;
ISR(PCINT1_vect) {  // Обработчик прерывания на пине А4
uint8_t changedbits = PINC ^ oldpinc;
  oldpinc = PINC;
  if (interrupt_state == 0) {
 
  if (changedbits & (1 << PC4)) { // Изменился A4, тарирование
    for (int i = 0; i < 4; i++) {
    
    scale[i].tare();  // тарирование
    scale[i].filt = 0;
  }
  // digitalWrite(13, HIGH);
  // delay(1500);
  // digitalWrite(13, LOW);
  }

  if (changedbits & (1 << PC5)) { // Изменился A5, калибровка
    for (int i = 0; i < 4; i++) {
    calibration(i);  // калибровка
  }
  }
  
  interrupt_state = 1;// Установка флага тарирования
  }
  else if (interrupt_state == 1){
    tare_sig = true;
    interrupt_state = 0;
  }
}

void setup() {
  // Serial.begin(9600);
  SPI.begin();  // Запуск интерфейса SPI
  SPI.beginTransaction(SPISettings(2000000, MSBFIRST, SPI_MODE0));
  delay(2000);
  
  DDRC &= ~(1 << 4) & ~(1 << 5);
  PORTC |= (1 << 4) | (1 << 5);
  SREG |= 1 << SREG_I;
  PCICR |= 1 << 1;
  PCMSK1 |= (1 << 4) | (1 << 5);  // НАСТРОЙКА ПРЕРЫВАНИЯ НА ПИНАХ А4 и А5
  

  // Инициализация 4 ДАТЧИКОВ
  for (int i = 0; i < 4; i++) {
    scale[i].begin(ADC_D_Regs, ADC_D_Pins[i]);  // инициализация АЦП
    scale[i].tare();  // тарирование
    EEPROM.get(i*4, scale_coeffs[i]);
    scale[i].set_scale(scale_coeffs[i]);
    dac[i].begin(SPI_CS[i], DAC_C_Regs);  // инициализация ЦАП
  }
// calibration(3);
//   for (int i = 0; i<4; i++) {
//   calibration(i);
//   }
//   Serial.begin(9600);
// for (int i = 0; i<4; i++) {
//     EEPROM.get(i*4, scale_coeffs[i]);
//     Serial.print("/n");
//     Serial.print(scale_coeffs[i]);
//   }
}


/* Список действий на производстве
  0. Установка кейсов
  1. Припаивание датчиков
  2. Подключение датчиков к ардуино
  3. Произведение калибровки: Запуск функции calibration() для каждого датчика поочередно. Калибровка записывает данные в EEPROM
     поэтому можно спокойно перезапускать контроллер
  4. Установить программу с основной функцией four_ch_test()
  5. Подключить ардуино к ПЛК
*/


void loop() {
  tare_sig = false;

  while (!tare_sig) {  // Выполнение при активном флаге
  tare_sig = false;
    four_ch_test();
  }
}


void four_ch_test() {
  tare_sig = false;
  for (byte i = 0; (i < 4) && !tare_sig; i++) { // Выполнение при активном флаге
    values[i] = scale[i].filter();  //запись фильтрованного значения
  }

  for (byte i = 0; (i < 4) && !tare_sig; i++) {
    dac[i].write(values[i]);  // запись в ЦАП
  }
}

void calibration(int ch) {
  digitalWrite(13, LOW);
  delay(2000);
  digitalWrite(13, HIGH);
  delay(5000);
  scale[ch].calibrate_scale(weight_of_standard);
  EEPROM.put(ch*4, scale[ch].get_scale());
  digitalWrite(13, LOW);
  delay(3000);
}

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

Способы есть - _delay_ms() и _delay_us(), но это, само по себе, очень плохая идея.

Какие нах… задержки внутри прерывания ??? Вы в своём уме ???

Давай попробуем изучить вопрос о том, как избегать любых задержек в прерываниях? И заодно ЛЮБЫХ вызовов (tare и что-то еще).

Тогда я не совсем понимаю смысл прерываний, если в них нежелательно использовать вызовы внешних функций, это ведь сильно урезает функциональность прерываний, нет?

Это как заявку из другого отдела тебе приносят.
Т.е. ты поставлен в известность, что нужно что-то сделать, но прям сразу бежать не надо. В 99,9% случаев.

2 лайка

Боже, ну конечно. Теперь понятно и то, о чем говорил товарищ xDriver.
Я, по неопытности, думал, что такое использование прерываний позволительно, но сейчас, конечно, начинаю понимать, что это излишне. Благодарю всех за помощь не в решении моей проблемы на неправильном пути, но в наставлении на путь правильный!

Позволительно, но тогда, когда программист сам очень хорошо знает используемый язык, компилятор и “все эти вещи”.
Это будто выпускник автошколы с первыми правами пытается научиться делать контролируемый занос. :wink:

1 лайк

Справедливое замечание, спасибо!)