Interrupt для чайника

gps38region

✩✩✩✩✩✩✩
24 Дек 2022
10
0
Помогите чайнику...

Вот код лампы на ARGB управляю через IR пульт.
Как только выполняю функцию SvetFire(); по прерыванию не могу сменить переменную comm
В чем моя ошибка ?
C++:
#include <NecDecoder.h>
NecDecoder ir;
volatile int comm;

#include "FastLED.h"
#include <FastLEDsupport.h>  // нужна для шума

#define PIN 13
#define WIDTH 11      // Светодиодов в кольце
#define HEIGHT 27     // Колец в лампе
#define NUM_LEDS 300  //300

CRGB leds[NUM_LEDS];
byte counter;
boolean Svet = false;
int br = 128;
int HE = 40;
byte baza = 0;  // изменение оттенка LED


void setup() {

  Serial.begin(115200);

  attachInterrupt(0, irIsr, FALLING);
  pinMode(PIN, OUTPUT);
  FastLED.addLeds<WS2811, PIN, RGB>(leds, NUM_LEDS).setCorrection(TypicalLEDStrip);

  FastLED.setBrightness(255);
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].setHue(HE);
  }
  FastLED.setBrightness(0);
  FastLED.show();
}

// Функция вызываемая прерыванием
// в прерывании вызываем tick()
void irIsr() {
  ir.tick();
  if (ir.available()) {
    comm = ir.readCommand();
    Serial.println(comm);
  }
}

void loop() {  // ЦИКЛ по comm
               //  Serial.println(comm);
  switch (comm) {

    case 162:  // (1)
      SvetRED();
      break;

    case 98:  // (2)
      if (Svet) SvetGREEN();
      break;

    case 226:  // (3)
      if (Svet) SvetBLUE();
      break;

    case 34:  // (4)
      if (Svet) SvetYELLOW();
      break;

    case 2:  // (5)
      if (Svet) SvetWHITE();
      break;

    case 194:  // (6)
      if (Svet) SvetPURPUR();
      break;

    case 224:  // (7)
      SvetFire();
      break;

    case 168:  // (8)
      SvetFire2();
      break;

    case 144:  // (9)
      if (Svet) SvetKONFETI();
      break;

    case 104:  // (*)
      SvetYaMinus();
      comm = 0;
      break;

    case 152:  // (0)
      if (Svet) SvetYaAvr();
      break;

    case 176:  // (#)
      SvetYaPlus();
      SvetBLINK();
      comm = 0;
      break;

    case 24:  // (up) увеличить яркость
      br = FastLED.getBrightness();
      br += 4;
      FastLED.setBrightness(br);
      FastLED.show();
      comm = 0;
      break;

    case 16:  // (left)
      if (Svet) SvetColMINUS();
      comm = 0;
      break;

    case 56:  // (Ok) Включение и выключение света
      if (Svet) {
        SvetOFF();
      } else {
        HE = 64;
        SvetON();
      }
      Svet = !Svet;
      comm = 0;
      break;

    case 90:  // (rigth)
      if (Svet) SvetColPLUS();
      comm = 0;
      break;

    case 74:  // (doun) уменьшить яркость
      br = FastLED.getBrightness();
      br -= 4;
      FastLED.setBrightness(br);
      FastLED.show();
      comm = 0;
      break;
  }
}

/////////////////////////////////////////////////////////////////////////////////////////////
void SvetFire2() {

  FastLED.setBrightness(255);
  for (int ii = 0; ii < NUM_LEDS; ii++) {
    leds[ii] = CRGB(0, 0, 0);
    //Serial.println(ii);
  }
  FastLED.show();

  // Заполняем нижнее кольцо
  for (int y = 0; y < 256; y++) {
    for (int x = 0; x < 300; x++) {  //WIDTH

      //FastLED.setBrightness(random(40,65)); //Общая яркость
      //leds[i] = CHSV(random(10, 37), random(200, 255), random(40, 250));
      leds[x] = CHSV(inoise8(x * 50, y * 50, y * 50), 255, 255);
      // Serial.print(x);
      // Serial.print(" ");
      // Serial.println(inoise8(x * 20));
      if (comm != 168) break;
    }
    FastLED.show();
  }


  //Поднимаем кольца вверх
  for (int i = 1; i < HEIGHT; i++) {
    for (int j = 0; j < WIDTH; j++) {
      leds[i * WIDTH + j] = leds[(i - 1) * WIDTH + j];
      leds[i * WIDTH + j] -= CRGB(8, 8, 8);
    }
    FastLED.show();
  }

  delay(50);

  FastLED.show();
}

void SvetFire() {
  FastLED.setBrightness(128);

  // Заполняем нижнее кольцо
  for (int i = 0; i < WIDTH; i++) {
    //FastLED.setBrightness(random(40,65)); //Общая яркость
    leds[i] = CHSV(random(10, 37), random(200, 255), random(40, 250));
  }
  FastLED.show();
  // Поднимаем кольца вверх
  for (int i = 1; i < HEIGHT; i++) {
    for (int j = 0; j < WIDTH; j++) {
      leds[i * WIDTH + j] = leds[(i - 1) * WIDTH + j];
      leds[i * WIDTH + j] -= CRGB(8, 8, 8);
    }
    FastLED.show();
  }
  delay(50);
  comm = 0;
  // FastLED.show();
}
// убавить яркость до 1
void SvetYaMinus() {
  for (int i = br; i >= 4; i--) {
    FastLED.setBrightness(i);
    FastLED.show();
  }
  br = 4;
}
// увеличить яркость до 255
void SvetYaPlus() {
  for (int i = br; i < 256; i++) {
    FastLED.setBrightness(i);
    FastLED.show();
    //delay(40);
  }
  br = 255;
}
// Яркость по средине
void SvetYaAvr() {
  if (br < 128) {
    for (int i = br; i < 128; i++) {
      FastLED.setBrightness(i);
      FastLED.show();
    }
  } else {
    for (int i = br; i >= 128; i--) {
      FastLED.setBrightness(i);
      FastLED.show();
    }
  }
  br = 128;
}
//*** ON Включение света
void SvetON() {
  SvetYELLOW();
  for (int i = 0; i < br; i++) {
    FastLED.setBrightness(i);
    FastLED.show();
    //delay(50);
  }
  //SvetBLINK();
}
// OFF Выключение света
void SvetOFF() {
  for (int i = br; i >= 0; i--) {
    FastLED.setBrightness(i);
    FastLED.show();
  }
  HE = 40;
  br = 128;
  baza = 0;
}
// Моргнуть яркостью
void SvetBLINK() {
  FastLED.setBrightness(br - br / 4);
  FastLED.show();
  delay(100);
  FastLED.setBrightness(br);
  FastLED.show();
}
// ФОКУС
void SvetFOCUS() {
  for (int ii = 0; ii < NUM_LEDS; ii++) {
    fadeToBlackBy(leds, NUM_LEDS, 2);
    for (int i = 0; i < 8; i++) {
      leds[beatsin16(i + 7, 0, NUM_LEDS - 1)] |= CHSV(baza += 16, 200, 255);
    }
  }
  FastLED.setBrightness(br);
  FastLED.show();
  //delay(20);
}
// ЦИКЛОН
void SvetCICLON() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].nscale8(250);
  }
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CHSV(baza++, 255, 255);
    FastLED.setBrightness(br);
    FastLED.show();
    //delay(20);
  }
}
// Радуга
void SvetRADUGA() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CHSV(baza + i * 5, 255, 255);
  }
  baza++;
  FastLED.setBrightness(br);
  FastLED.show();
}
// Конфети
void SvetKONFETI() {
  for (int i = 0; i < NUM_LEDS; i++) {
    for (int j = 0; j < 4; j++) {
      fadeToBlackBy(leds, NUM_LEDS, 2);
      int pos = random16(NUM_LEDS);
      leds[pos] += CHSV(baza++ + random8(64), 200, 255);
      FastLED.setBrightness(br);
      FastLED.show();
    }
  }
}
// Раскраска RGB очередность
void SvetRGB() {
  for (int i = 0; i < NUM_LEDS / 3; i++) {
    leds[i * 3].setHue(1);
    leds[i * 3 + 1].setHue(95);
    leds[i * 3 + 2].setHue(160);
  }
  FastLED.show();
  comm = 0;
}
// Красный цвет
void SvetRED() {
  for (int i = 0; i < NUM_LEDS; i++) {
    HE = 1;
    leds[i].setHue(HE);
  }
  FastLED.show();
  comm = 0;
}
// БЕЛЫЙ цвет
void SvetWHITE() {
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i] = CRGB::White;
    FastLED.setBrightness(br);
  }
  FastLED.show();
  comm = 0;
}
// Зеленый свет
void SvetGREEN() {
  for (int i = 0; i < NUM_LEDS; i++) {
    HE = 95;
    leds[i].setHue(HE);
  }
  FastLED.show();
  comm = 0;
}
// Синий цвет
void SvetBLUE() {
  for (int i = 0; i < NUM_LEDS; i++) {
    HE = 160;
    leds[i].setHue(HE);
  }
  FastLED.show();
  comm = 0;
}
// Желтый цвет
void SvetYELLOW() {
  HE = 40;
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].setHue(HE);
  }
  FastLED.show();
  comm = 0;
}  // Пурпурный цвет
void SvetPURPUR() {
  HE = 232;
  for (int i = 0; i < NUM_LEDS; i++) {
    leds[i].setHue(HE);
  }
  FastLED.show();
  comm = 0;
}
// Цвет НUЕ увеличить
void SvetColPLUS() {
  br = FastLED.getBrightness();
  HE += 4;
  for (int i = 0; i < NUM_LEDS; i++) {  // Прибавить цвет
    leds[i].setHue(HE);
  }
  FastLED.setBrightness(br);
  FastLED.show();
}
// Цвет НUЕ уменьшить
void SvetColMINUS() {
  br = FastLED.getBrightness();
  HE -= 4;
  for (int i = 0; i < NUM_LEDS; i++) {  // Убавить цвет
    leds[i].setHue(HE);
  }
  FastLED.setBrightness(br);
  FastLED.show();
}
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty, Уверены? У меня среда ардуины не работает, что то в ней сломалось. Попробуйде скомпилить такое
C++:
void isr1(void)
{
    PORTB |= 1;
}

void isr2(void)
{
    PORTB |= 2;
}

attachInterrupt(0, isr1, FALLING);
attachInterrupt(0, isr2, FALLING);
Если компиляция пройдет - там указатель.
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
@Forgetweb, честно говоря - не очень понимаю к чему этот спор. Возможно, это стоит перенести в другую ветку.
там - это где? В векторе прерываний? Ну да, я так и написал ранее:
attachInterrupt записывает в нужный вектор прерывания адрес обработчика
Но вызывает-то этот обработчик не код программы, а МП, это аппаратное прерывание! Соответственно, МП за 4 выделенных ему для этого такта сохраняет всё, что нужно в стек и переходит по этому адресу.
 

Kir

★✩✩✩✩✩✩
28 Мар 2020
69
16
То, что у AVR нет DMA - это конечно не очень здорово, но и без него можно обойтись.

Буфера у него нет.
Стандартный SPI не имеет буфера - это может потенциально создать проблему, так как могут появиться разрывы между передачами, тут зависит как быстро будет работать обработчик SPI. Однако 328-я ATMEGA может и USART запустить в режиме SPI и там уже есть буферизация размером в 1 байт, что уже позволит выводить поток без разрывов. Но, про вывод отладки можно забыть, увы.

SPI глобально проблему не исправит.
Не соглашусь. Использование SPI позволит высвободить несколько свободных тактов, около 80, по моим прикидкам, это конечно очень не много, но лучше чем ничего, и за счет этих тактов, и возможности хоть как-то диспетчеризовать код этими прерываниями, задачу автора данной темы решить возможно - отправлять данные в ленту и принимать сигналы с пульта, для этого придется сделать свою реализацию контроллера для работы с лентой.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Вектор это флеш. Если адрес функции записывается туда, то вопросов нет. Но вектор это 2 или 4 байта. Там никаких сохранений нет. А функция определена как обычно - она тоже не сохраняет регистры. То есть вариант без нормального обработчика будет в принципе неработоспособен.
За 4 такта сохраняется ТОЛЬКО счетчик команд. Больше ничего. Даже регистр статуса сохраняет уже обработчик. И этого очевидно в обычной функции не происходит. Значит что? А то, что реально обработчик есть всегда, даже когда не нужен. И в нем сохраняется КУЧА регистров и... вызывается функция по указателю.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Не соглашусь. Использование SPI позволит высвободить несколько свободных тактов, около 80, по моим прикидкам
Выводить можно по 2 бита. 1 бит это 1 микросекунда. 2 соответственно 2 мкс. 2 микросекунды при 16 МГц это 32 такта. Не 80.
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
@Forgetweb, не очень понял причём здесь флеш и размер вектора. И я показывал, да и Вы приводили пример того, что функция сама всё сохраняет, никакого внешнего обработчика не предусмотрено.
То, что сохраняет аппаратный инициатор прерывания - расписано в datasheet, тут выдумывать ничего не нужно.
Но дальше рассуждать по этой теме не буду. Останемся каждый при своём мнении.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Я прекрасно знаю что сохраняет аппаратный инициатор прерывания - PC и... все. Все остальные регистры сохраняет уже программно обработчик.
А он есть, хотя его Ардуина и прячет. И в нем вызов функции по указателю сидит. А до вызова - сохраняются в стек куча регистров. даже если это и не требуется. Даташит почитайте, там все есть.
The interrupt execution response for all the enabled AVR interrupts is four clock cycles mini-
mum. After four clock cycles the program vector address for the actual interrupt handling routine
is executed. During this four clock cycle period, the Program Counter is pushed onto the Stack.
The vector is normally a jump to the interrupt routine, and this jump takes three clock cycles.
Только PC.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Kir,
Я не знал, 2811 не использовал. Обычно все используют не 2811, а ws2812b, там по даташиту 1,25 мкс, но SPI дает только 1 при такте в 16МГц. И это прекрасно работает. И получается с SPI как раз 2 мкс при выводе 2 бит подряд за 1 загрузку SPI.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty,
В общем я вопрос для себя закрыл. Ардуина использует именно указатель в обработчике.
Вот такое скомпилилось:
C++:
void isisr(void)
{
  PORTB |= 1;
 }

void isisr2(void)
{
  PORTB |= 1;
}

void setup() {
  // put your setup code here, to run once:
  attachInterrupt(0, isisr, FALLING);
}

void loop() {
  // put your main code here, to run repeatedly:
  attachInterrupt(0, isisr2, FALLING);
}
Иначе как с указателем это бы просто не прошло. Программа в Авр сидит во флеши и из ОЗУ выполниться не может. А два "обработчика" на один вектор Ардуина повесить дает. Вывод - в реальном обработчике, который мы вообще не видим, сохраняются регистры в стек и вызывается внешняя функция по указателю. К чему это приводит я привел выше. К потерям времени и распухшему коду.
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
@Forgetweb, а с чего Вы взяли, что будут работать оба обработчика? Следуя Вашей логике, с каждым циклом loop в "очередь" обработчиков будет снова и снова добавляться isisr2. Вторая команда просто перезаписывает адрес в векторе на второй обработчик, только и всего. Первый в этом случае вызываться не будет вообще (если, конечно, за время между первым и вторым вызовом attachInterrupt не произойдёт события).
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty,
А вот обработчик от Ардуины. Найдите три отличия от вызова функции по указателю. Я не нашел...
Код:
+000000A2:   921F        PUSH      R1             Push register on stack
+000000A3:   920F        PUSH      R0             Push register on stack
+000000A4:   B60F        IN        R0,0x3F        In from I/O location
+000000A5:   920F        PUSH      R0             Push register on stack
+000000A6:   2411        CLR       R1             Clear Register
+000000A7:   932F        PUSH      R18            Push register on stack
+000000A8:   933F        PUSH      R19            Push register on stack
+000000A9:   934F        PUSH      R20            Push register on stack
+000000AA:   935F        PUSH      R21            Push register on stack
+000000AB:   936F        PUSH      R22            Push register on stack
+000000AC:   937F        PUSH      R23            Push register on stack
+000000AD:   938F        PUSH      R24            Push register on stack
+000000AE:   939F        PUSH      R25            Push register on stack
+000000AF:   93AF        PUSH      R26            Push register on stack
+000000B0:   93BF        PUSH      R27            Push register on stack
+000000B1:   93EF        PUSH      R30            Push register on stack
+000000B2:   93FF        PUSH      R31            Push register on stack
+000000B3:   91E00102    LDS       R30,0x0102     Load direct from data space
+000000B5:   91F00103    LDS       R31,0x0103     Load direct from data space
+000000B7:   9509        ICALL                    Indirect call to (Z)
+000000B8:   91FF        POP       R31            Pop register from stack
+000000B9:   91EF        POP       R30            Pop register from stack
+000000BA:   91BF        POP       R27            Pop register from stack
+000000BB:   91AF        POP       R26            Pop register from stack
+000000BC:   919F        POP       R25            Pop register from stack
+000000BD:   918F        POP       R24            Pop register from stack
+000000BE:   917F        POP       R23            Pop register from stack
+000000BF:   916F        POP       R22            Pop register from stack
+000000C0:   915F        POP       R21            Pop register from stack
+000000C1:   914F        POP       R20            Pop register from stack
+000000C2:   913F        POP       R19            Pop register from stack
+000000C3:   912F        POP       R18            Pop register from stack
+000000C4:   900F        POP       R0             Pop register from stack
+000000C5:   BE0F        OUT       0x3F,R0        Out to I/O location
+000000C6:   900F        POP       R0             Pop register from stack
+000000C7:   901F        POP       R1             Pop register from stack
+000000C8:   9518        RETI                     Interrupt return
Все та же простыня. Если нужна быстрая реакция на прерывание то все эт аттачинтеррупт идут лесом.

Вторая команда просто перезаписывает адрес в векторе на второй обработчик, только и всего.
КАК перезаписывает??? Адрес так то во флеши сидит! Его не перезаписать в принципе. Не в Ардуине я два обработчика на один вектор повесить не смогу никак. Компилятор меня пошлет.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty,
Вы же функцию по указателю не из обработчика прерывания вызываете. Сам по себе указатель на функцию вполне нормальный вариант. Но не во всех случаях.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty, где я такое говорил? Сохраняют регистры только в обработчике прерывания. Ардуиновцы выбрали самый неудачный вариант с указателем. Но есть и хорошая новость - этим можно не пользоваться. Стандартное объявление через ISR(vector_name) вполне работает. Я так то в Ардуине чайник чайником ))) Поставил ее и думал она не работает. А оказалось что ей иногда надо подумать пару минут. Да и вообще она жесть какая тормозная. Даже не быстрая Эклипса на фоне Ардуины просто метеор.
 

Sergo_ST

★★★★★★✩
15 Мар 2020
1,000
837
@Forgetweb, @poty прав. Указатель хранится в озу, он же и перезаписывается вызовом attachInterrupt(). А переход происходит по адресу в Z регистре, путём загрузки в него адреса хранящегося в озу(командой ICALL или IJMP)
Выдержка из вашего листинга:
C++:
+000000B3: 91E00102 LDS R30,0x0102 Load direct from data space
+000000B5: 91F00103 LDS R31,0x0103 Load direct from data space
+000000B7: 9509 ICALL Indirect call to (Z)
 
Изменено:

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Sergo_ST,
Вы указатель с вектором путаете. Это в общем то одно и то же, но вектор хранится во флеши. В самом ее начале.
Хотя все же не одно и то же. Вектор - команда перехода. JMP. Адрес перехода является ее частью. Перезаписать его можно только перепрошивкой.
 

Sergo_ST

★★★★★★✩
15 Мар 2020
1,000
837
@Forgetweb, Всё верно, сначала мы прыгаем на вектор прерывания(при этом сохраняя в стеке текущее состояние счётчика), затем с вектора прерывания прыгаем к началу программы прерывания(в которой сохраняются регистры, и её адрес как раз и хранится во флеш), а уже в теле самой программы прерывания и происходит непрямой вызов подпрограммы(которая была передана через attachInterrupt()).
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@Sergo_ST,
и именно в этом и проблема. Даже если весь обработчик это что то типа PORTB |= 1; придется сохранить тучу регистров в стек. А если обойтись без attachInterrupt то обработчик будет раза в 3-4 короче и быстрее.