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();
}
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
Убрать из прерывания Serial.println. Обрабатывать comm только при изменении значения переменной. При использовании comm выключать прерывания на момент чтения/записи.
 

gps38region

✩✩✩✩✩✩✩
24 Дек 2022
10
0
При использовании comm выключать прерывания на момент чтения/записи.
Может не правильно меня поняли.

1. Есть быстрое заполнение ленты.
2. Есть заполнение ленты в цикле но с проверкой изменения команды с пульта.
То есть, Выводится эффект в цикле пока не выбран новый эффект с пульта.
Отключать прерывание мне не нужно так как в прерывании я опрашиваю пульт и меняю COMM
3. Вызываю SvetFire(); и comm уже сменить не могу....

C++:
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();
}
Так тоже не помогает...
C++:
// Функция вызываемая прерыванием
// в прерывании вызываем tick()
void irIsr() {
  noInterrupts();
  ir.tick();
  if (ir.available()) {
    comm = ir.readCommand();
    //Serial.println(comm);
  }
  interrupts();
}
Не выходит из цикла :mad: :cry:
 
Изменено:

poty

★★★★★★✩
19 Фев 2020
3,265
950
Вызываю SvetFire(); и comm уже сменить не могу....
Что вот это может значить? comm меняете не Вы, а программа в прерывании + кое-где в коде оно обнуляется. Оно не меняется или что?
Отключать прерывание мне не нужно
выключать прерывания на момент чтения/записи
Насколько я понял из кода используется 8-битный МП. То есть, чтение или запись в comm - это две операции. Между этими операциями может случиться прерывание и Вы прочитаете младший байт старого значения и старший байт нового. Аналогично - при записи.
 

gps38region

✩✩✩✩✩✩✩
24 Дек 2022
10
0
Между этими операциями может случиться прерывание ...
Если есть нажатие пульта в comm пишем код с пульта.
Все работает кроме зацикливания... :cry:
То есть зацикливание формируется НЕ сбросом comm=0;

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

Bruzzer

★★★✩✩✩✩
23 Май 2020
500
149
Может не правильно меня поняли.
Опишите вашу последовательность действий, и их результат - как реально работает.
Например
Нормальная работа -
Оправляю по ИК команду NNN, получаю в Serial ответ NNN один раз, лампа делает ....
Оправляю по ИК команду MMM, получаю в Serial ответ MMM один раз, лампа делает ....
Оправляю по ИК команду SvetFire , получаю в Serial ответ SvetFire один раз, лампа делает ....
после этого появляются проблемы
Оправляю по ИК команду NNN, получаю в Serial ответ ......, лампа делает ....
 

gps38region

✩✩✩✩✩✩✩
24 Дек 2022
10
0
Ищем в чем проблема :
Читаем тут https://github.com/FastLED/FastLED/wiki/Interrupt-problems
Interrupt Problems
(Or, why do I lose (serial|IR|servo) data?)

If you are using a 3-wire led chipset, (aka Neopixels, WS2812, TM1809), you may have run into some problems when trying to pair it with reading serial data, wifi (for example, on an esp8266), or using i2c, or other libraries.

The problem is, in a nutshell, interrupts.

Writing out WS2812 data requires some pretty tight timing. Tight enough that FastLED disables interrupts while it is writing out led data. This means that while the led data is being written out, any interrupts that happen will be delayed until all the led data is written out. How long will that be for? Depends on the number of leds. WS2812 led data takes 30µs per pixel. If you have 100 pixels, then that means interrupts will be disabled for 3000µs, or 3ms.

What does this practically mean, however?

Let's say you are using an AVR based arduino and you are reading serial data at 57.6kbps. The AVR has a single byte serial receive buffer. Which means it can hold one byte of received data while it is receiving the next byte of data. If your code hasn't read that byte of data before the new byte finishes being read, it will get lost. The way this is most often handled is with an interrupt. This interrupt gets triggered every time a byte finishes being read. The interrupt handler then copies the byte out of the receive buffer into a (hopefully larger) buffer elsewhere in memory.

At 57.6kbps you will receive 7200 bytes per second. Or, put differently, one byte every 138µs. This means that when a byte of serial data comes in you have 138µs in which to move that byte out of the serial receive buffer before the next incoming byte over writes it. Remember above, though, that interrupts are disabled while writing out WS2812 led data, and that each pixel takes 30µs to write. This means that if you have more than 4 leds, it's going to be more than 138µs between when that interrupt is allowed to fire. Then you lose data, and everyone is sad. (;_;)

So what do I do?

In an ideal world, you would move to a 4-wire led chipset, like the APA102 or LPD8806. Because these chipsets don't have the WS2812 timing requirements, they don't need to have interrupts disabled while writing data out, and this problem never happens.

In a less than ideal world, you could move to the teensy 3.x or the arduino due. These are ARM based systems that have far more clock cycles per second available to them. On these platforms, FastLED will briefly re-enable interrupts between each pixel, to allow handlers to run. As long as those interrupt handlers don't take more than 5µs to run, everything will be happy. As long as your interrupt handlers don't need to run more frequently than once every 30µs, that is.

What if my frames are getting cut short, because interrupts are still running too long - or i'm getting other weird behavior?

Well, then you have a choice - you can either fully disable interrupts while writing out led data by putting the line:

#define FASTLED_ALLOW_INTERRUPTS 0

before you #include <FastLED.h>. Sometimes, especially on the esp8266, you might have better luck by just tweaking the re-try attempt code with:

#define FASTLED_INTERRUPT_RETRY_COUNT 1

before you #include <FastLED.h>

Another possibility is to split your leds into multiple shorter strips of leds, which would allow you to have even shorter periods of time with interrupts disabled (and/or not having to worry as much about frame writing being interrupted by long interrupts if you have them enabled)

And if I'm stuck with WS2812's and AVR?

If you are stuck with the hardware, all is not lost, at least not for reading in serial data. (Note: If the lack of serviceable interrupts is causing problems for servos, you are out of luck - make one of the hardware switches mentioned above, or use a dual-controller setup where one avr talks to servos, and the other avr talks to the leds - also some folks have had luck using this to drive their servos - https://learn.adafruit.com/neopixels-and-servos/the-ticoservo-library).

The trick is to be a bit more careful/thoughtful in how you're receiving your serial data.

The best way to do things is to switch to a protocol where your arduino asks for data when it's ready. There's two ways to do this. The first way is to have the arduino send something over serial when it's in a place to receive data. Then it waits for a little bit to see if the other side has anything to send, and if it does, it reads the all the data that it can before doing more LED work. The second way is somewhat similar to this, except instead of sending something over serial when you're ready to receive data instead you raise a pin high indicating that it's ok for the other side to be sending data.

For example:

void loop() {
uint8_t cmd_buffer[64];
// Tell the other side to send data
Serial.println("OK");
// Read a pre-determined amount of data
Serial.readBytes(cmd_buffer, 64);
// do stuff with the read data

// now do led stuff

FastLED.show();
}

One downside to this is that you could end up spending a lot of time sending data when there isn't really data to send. A way you could change this is make it so that when the Arduino sends its "OK" to get more data, the first byte the other side sends back is a count of how many bytes of data there is. If there's nothing new to send, then it'll just send back 0:

void loop() {
uint8_t cmd_buffer[64];
uint8_t size;

Serial.println("OK");
size = Serial.read();
if(size) {
Serial.readBytes(cmd_buffer, size);
// handle the new data that came in
}

// the rest of your normal loop
}

Let's say you don't want a back and forth like this however. What if instead you wanted to have something just continually sending data? (E.g. boblight/ambilight/adalight). Then what you can do is make sure that every "batch" of data begins with a known string of bytes called a header. Let's say that every batch of led data will begin with four bytes - 0xDEADBEEF.

Now, what we do is we read serial data until we see those four bytes in a row, then we read our full frame of data. The extra trick is that after we're done calling show, we flush the serial read buffer entirely to make sure we don't have a partial frame of data. For example:

const uint8_t header[4] = { 0xDE, 0xAD, 0xBE, 0xEF };

void loop() {
// we're going to read led data directly from serial, after we get our header
while(true) {
while(Serial.available() == 0){} // wait for new serial data
uint8_t b = Serial.read();
bool looksLikeHeader = false;
if(b == header[0]) {
looksLikeHeader = true;
for(int i = 1; looksLikeHeader && (i < sizeof(header)); i++) {
while(Serial.available() == 0){} // wait for new serial data
b = Serial.read();
if(b != header) {
// whoops, not a match, this no longer looks like a header.
looksLikeHeader = false;
}
}
}

if(looksLikeHeader) {
// hey, we read all the header bytes! Yay! Now read the frame data
int bytesRead = 0;
while(bytesRead < (NUM_LEDS *3)) {
bytesRead += Serial.readBytes(((uint8_t*)leds) + bytesRead, (NUM_LEDS*3)-bytesRead);
}
// all bytes are read into led buffer. Now we can break out of while loop
break;
}
}

// now show the led data
FastLED.show();

// finally, flush out any data in the serial buffer, as it may have been interrupted oddly by writing out led data:
while(Serial.available() > 0) { Serial.read(); }

}

What's known to work and where?
Проблемы с прерываниями
(Или почему я теряю (последовательный|ИК|серво) данные?)

Если вы используете 3-проводной светодиодный чипсет (он же Neopixels, WS2812, TM1809), вы можете столкнуться с некоторыми проблемами при попытке связать его с чтением последовательных данных, Wi-Fi (например, на esp8266) или с использованием i2c. или другие библиотеки.

Проблема, в двух словах, в прерываниях.

Запись данных WS2812 требует довольно короткого времени. Достаточно плотно, чтобы FastLED отключал прерывания во время записи данных светодиода. Это означает, что во время записи данных светодиода любые происходящие прерывания будут отложены до тех пор, пока не будут записаны все данные светодиода. Как долго это будет? Зависит от количества светодиодов. Данные светодиода WS2812 занимают 30 мкс на пиксель. Если у вас 100 пикселей, это означает, что прерывания будут отключены на 3000 мкс или 3 мс.

Однако что это означает на практике?

Допустим, вы используете Arduino на базе AVR и считываете последовательные данные со скоростью 57,6 кбит/с. AVR имеет однобайтовый последовательный приемный буфер. Это означает, что он может хранить один байт полученных данных, в то время как он получает следующий байт данных. Если ваш код не прочитал этот байт данных до завершения чтения нового байта, он будет потерян. Наиболее часто это обрабатывается с помощью прерывания. Это прерывание срабатывает каждый раз, когда байт завершает чтение. Затем обработчик прерывания копирует байт из приемного буфера в (надеюсь, больший) буфер в другом месте памяти.

При скорости 57,6 кбит/с вы получите 7200 байт в секунду. Или, другими словами, один байт каждые 138 мкс. Это означает, что когда приходит байт последовательных данных, у вас есть 138 мкс, чтобы переместить этот байт из буфера последовательного приема до того, как следующий входящий байт перепишет его. Однако помните выше, что прерывания отключены при записи данных светодиода WS2812, и что запись каждого пикселя занимает 30 мкс. Это означает, что если у вас есть более 4 светодиодов, между тем, когда это прерывание будет разрешено, пройдет более 138 мкс. Потом вы теряете данные, и всем становится грустно. (;_;)

Итак, что мне делать?

В идеальном мире вы бы перешли на 4-проводной светодиодный чипсет, такой как APA102 или LPD8806. Поскольку эти наборы микросхем не имеют требований к синхронизации WS2812, им не нужно отключать прерывания при записи данных, и эта проблема никогда не возникает.

В далеком от идеального мире вы могли бы перейти на тинси 3.х или на ардуино. Это системы на базе ARM, которые имеют гораздо больше тактов в секунду. На этих платформах FastLED на короткое время повторно разрешает прерывания между каждым пикселем, чтобы разрешить работу обработчиков. Пока эти обработчики прерываний не занимают более 5 мкс, все будет хорошо. Пока ваши обработчики прерываний не должны запускаться чаще, чем раз в 30 мкс, то есть.

Что, если мои кадры обрываются, потому что прерывания продолжаются слишком долго, или я наблюдаю другое странное поведение?

Что ж, тогда у вас есть выбор — вы можете либо полностью отключить прерывания при записи данных светодиода, поставив строку:

#define FASTLED_ALLOW_INTERRUPTS 0

перед тобой #include <FastLED.h>. Иногда, особенно на esp8266, вам может повезти, просто изменив код повторной попытки с помощью:

#define FASTLED_INTERRUPT_RETRY_COUNT 1

до тебя#include <FastLED.h>

Другая возможность состоит в том, чтобы разделить ваши светодиоды на несколько более коротких полос светодиодов, что позволит вам иметь еще более короткие периоды времени с отключенными прерываниями (и/или не беспокоиться о том, что запись кадра прерывается длинными прерываниями, если они у вас есть). включено)

А если я застрял с WS2812 и AVR?

Если вы застряли с аппаратным обеспечением, не все потеряно, по крайней мере, не для чтения последовательных данных. (Примечание: если отсутствие обслуживаемых прерываний вызывает проблемы для сервоприводов, вам не повезло — сделайте один из аппаратных переключателей, упомянутых выше, или используйте настройку с двумя контроллерами, где один avr общается с сервоприводами, а другой avr общается с светодиоды - также некоторым людям повезло использовать их для управления своими сервоприводами - https://learn.adafruit.com/neopixels-and-servos/the-ticoservo-library ).

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

Лучший способ сделать что-то — переключиться на протокол, в котором ваша Arduino запрашивает данные, когда они готовы. Есть два способа сделать это. Первый способ — заставить Arduino отправлять что-то по последовательному порту, когда он находится в месте для приема данных. Затем он немного ждет, чтобы узнать, есть ли у другой стороны что-нибудь для отправки, и если да, он считывает все данные, которые может, прежде чем выполнять дальнейшую работу со светодиодами. Второй способ чем-то похож на этот, за исключением того, что вместо того, чтобы отправлять что-то по последовательному порту, когда вы готовы получать данные, вместо этого вы поднимаете контакт на высокий уровень, указывая, что другая сторона может отправлять данные.

Например:

void loop() {
uint8_t cmd_buffer[64];
// Tell the other side to send data
Serial.println("OK");
// Read a pre-determined amount of data
Serial.readBytes(cmd_buffer, 64);
// do stuff with the read data

// now do led stuff

FastLED.show();
}

Одним из недостатков этого является то, что вы можете потратить много времени на отправку данных, когда на самом деле нет данных для отправки. Вы можете изменить это так: когда Arduino отправляет «ОК» для получения дополнительных данных, первый байт, который другая сторона отправляет обратно, является подсчетом количества байтов данных. Если нет ничего нового для отправки, он просто отправит обратно 0:

void loop() {
uint8_t cmd_buffer[64];
uint8_t size;

Serial.println("OK");
size = Serial.read();
if(size) {
Serial.readBytes(cmd_buffer, size);
// handle the new data that came in
}

// the rest of your normal loop
}

Допустим, вы не хотите, чтобы туда и обратно, как это, однако. Что, если вместо этого вы хотите иметь что-то, что просто постоянно отправляет данные? (Например, boblight/ambilight/adalight). Затем вы можете убедиться, что каждый «пакет» данных начинается с известной строки байтов, называемой заголовком. Предположим, что каждый пакет данных светодиода будет начинаться с четырех байтов — 0xDEADBEEF.

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

const uint8_t header[4] = { 0xDE, 0xAD, 0xBE, 0xEF };

void loop() {
// we're going to read led data directly from serial, after we get our header
while(true) {
while(Serial.available() == 0){} // wait for new serial data
uint8_t b = Serial.read();
bool looksLikeHeader = false;
if(b == header[0]) {
looksLikeHeader = true;
for(int i = 1; looksLikeHeader && (i < sizeof(header)); i++) {
while(Serial.available() == 0){} // wait for new serial data
b = Serial.read();
if(b != header) {
// whoops, not a match, this no longer looks like a header.
looksLikeHeader = false;
}
}
}

if(looksLikeHeader) {
// hey, we read all the header bytes! Yay! Now read the frame data
int bytesRead = 0;
while(bytesRead < (NUM_LEDS *3)) {
bytesRead += Serial.readBytes(((uint8_t*)leds) + bytesRead, (NUM_LEDS*3)-bytesRead);
}
// all bytes are read into led buffer. Now we can break out of while loop
break;
}
}

// now show the led data
FastLED.show();

// finally, flush out any data in the serial buffer, as it may have been interrupted oddly by writing out led data:
while(Serial.available() > 0) { Serial.read(); }

}

Что, как известно, работает и где?
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
Если есть нажатие пульта в comm пишем код с пульта
Ладно, не хотите разбираться - пусть будет "закладка" на будущие проблемы.
зацикливание формируется НЕ сбросом comm=0;
Можете поточнее сказать, какие действия приводят к "зацикливанию" и как Вы это обнаруживаете? Разбирать весь код никому не интересно. Вот я, например, вижу, что в кодах 98, 226, 2... обнуление comm зависит от переменной Svet. То есть, если Svet = false, то и comm не обнулится.
 

gps38region

✩✩✩✩✩✩✩
24 Дек 2022
10
0
Можете поточнее сказать, какие действия приводят к "зацикливанию" и как Вы это обнаруживаете?
- Есть функции типа SvetRED(); которые заполняют светодиоды один раз каким-то цветом.
Что бы не закрашивать постоянно одним и тем же цветом я обнуляю команду пульта comm=0; и жду новой команды
- Есть функции типа SvetFire(); которые заполняют светодиоды рандомным цветом и при повторном вызове цвета не повторяются.
В этих функциях сброс команды с пульта comm=0; я не делаю и команда пульта висит не изменяясь , что позволяет зациклить эффект.

Но как написал выше FastLED отключает прерывания....
Я так понял затык в этом заключается ?

Наиболее прошареные скажут, а как же прерывания ? А так:
Александр Симонов написал(а):
FastLED запрещает прерывания на время вывода данных в ленту
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
void irIsr() {
noInterrupts();
...
interrupts(); }
надо убрать, это не здесь нужно делать.
Прерывания запрещаются на момент вызова FastLED.show(), точнее - той её части, что выводит данные на ленту. Не помню точно, блокирующая ли это функция, но при таком количестве светодиодов вывод будет осуществляться примерно 9мс на изменение. Если команда с пульта приходит в эти 9мс, то она не запишется, возможно даже, что приведёт к таймауту в библиотеке (не знаю, как там реализована библиотека IR). А FastLED.show() Вы вызываете часто для этого проблемного режима.
Попробуйте сделать большой delay (секунду, например) после каждого FastLED.show() в проблемной функции.
 
Изменено:

gps38region

✩✩✩✩✩✩✩
24 Дек 2022
10
0
Попробуйте сделать большой delay (секунду, например) после каждого FastLED.show() в проблемной функции.
А теперь представте эффект огня или движения света по ленте с вашим большим delay();
Рекомендация неприемлемая. Спасибо за такие советы.
Я с лентой работаю а не математику вычисляю и жду окончания расчета...
 

poty

★★★★★★✩
19 Фев 2020
3,265
950

@gps38region, речь шла о том, чтобы подтвердить или опровергнуть предположение. Понятно, что это - не решение проблемы.
 

Kir

★✩✩✩✩✩✩
28 Мар 2020
69
16
В чем моя ошибка ?
Ошибка в том, что код, который обрабатывает сигналы от ИК-пульта работает через прерывания, а код, который работает с адресной лентой запрещает прерывания.

Реализация драйвера, которая работает с лентой для МК AVR использует "ногодрыг", и для гарантии стабильной работы реализация драйвера запрещает вклиниваться между переключениями ножки DATA, так как это может привести к нарушению временных диаграмм, и как следствие привести к искажению данных.

Реализация обработчика сигналов с ИК-пульта оценивает время, которое происходит между вызовами, таким образом определяет какой бит сейчас пришел. Для точного определения времени используеются прерывания, если прерывание запретить, то вызов откладывается, до момента следующего разрешения прерывания. и в этом проблема, так как декодер оперируется величинами 1.15мс и 2.25мс, а у вас прерывания запрещаются на 9мс. и получаются код корректно не может определить какой бит был принят, поэтому этот кусочек:
C++:
void irIsr() {
  ir.tick();
  if (ir.available()) {
    comm = ir.readCommand();
    Serial.println(comm);
  }
}
не будет выполняться (который следовало бы вытащить из прерывания в основной цикл программы, что позволить избавиться от квалификатора volatile для переменной comm), так как ir.available() будет возвращать false.

Для получения свободных тактов, на обработку прерываний нужно использовать аппаратные средства для генерации сигнала для ленты. например SPI (в случае с AVR не факт, что будет корректно работать - не проверялось), но и в этом случае не факт, что этого будет достаточно.
 
  • Аррр! -2
Реакции: gps38region

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Библиотека же в исходнике? Надо найти в ней запрет прерываний и убрать его нафиг. При этом могут появиться сбои вывода на ленту в момент приема посылки с пульта, но обычно пульт и нажимают в ожидании какого то изменения. Ну и соответственно вариант с "Что бы не закрашивать постоянно одним и тем же цветом я обнуляю команду пульта comm=0; и жду новой команды " не пройдет - надо закрашивать постоянно. Тогда короткий возможный сбой будет не заметен.
 

poty

★★★★★★✩
19 Фев 2020
3,265
950

@Kir, SPI для вывода на ленту проверялся, был даже кусок кода для этого здесь, на форуме. Но искать лень.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
SPI глобально проблему не исправит. Буфера у него нет. DMA тоже нет. Так что использование SPI может освободить ядро на пару микросекунд, не более. А обработчики прерываний в Ардуине сделаны самым кривым образом.
attachInterrupt это ни что иное, как вызов из обработчика функции по указателю. То, чего делать вообще не стоит! Компилятор в этом случае на реальном векторе сохраняет в стек практически все регистры. А их в ядре АВР 32 штуки. Плюс статус и регистр команд. В итоге вход в прерывание занимает больше 2 микросекунд. И выход тоже. В других ядрах можно вызывать - в том же кортексе все регистры сохраняются за 12 тактов. С учетом того, что там и тактовая куда выше вход/выход куда быстрее пары микросекунд.
И еще - вызов из обработчика функции из другого файла это то же самое, что и по указателю. Опять же все регистры пойдут в стек. GCC компилирует файлы по отдельности, поэтому понятия не имеет о том, что делается в файлах вне единицы трансляции. Поэтому это все красиво и не разрулить. Библиотеки это те самые "другие файлы"! Вызов ir.tick() будет раздувать и тормозить обработчик, даже если его и определить нормально, а не ардуиновскими средствами.
 
Изменено:

poty

★★★★★★✩
19 Фев 2020
3,265
950
@Forgetweb, в целом про Ардуино говорить не буду, но для 8-битных процессоров ATMEL логика вызова прерывания/функции немного другая: первый параметр (если возможно), передаётся в регистре, все остальные - сохраняются в стек, плюс к этому - сохраняется адрес возврата, а уже в функции/обработчике "досохраняются" используемые далее регистры. Т.о., компилятор, при преобразовании функции в бинарный код, может оптимизировать количество сохраняемых регистров. По факту, я измерял время входа в прерывание на Atmega328p @16MHz и оно крайне редко превышало 1мкс (даже с учётом погрешности измерений), а это всего 16 тактов. Но это не отменяет того факта, что количество кода в прерывании нужно минимизировать!
Вектор прерываний везде используется, это типичный способ обработки прерываний. На "больших" процессорах можно выделить разные области памяти для стека и данных каждого модуля и переключаться между ними изменением лишь одного регистра, то есть экономить на восстановлении стека например, но это уже другая весовая категория.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty, в целом - прерывания не имеют параметров в принципе. Там ничего никуда не передается, ни в регистре, ни на стеке. И это не только в АВР - в любом ядре. Компилятор может оптимизировать обработчик - не сохранять неиспользуемые регистры. Но только тогда когда он знает какие будут использованы. Очевидно, что если функция вызывается по указателю, то что там будет в реальности компилятору неизвестно. Поэтому он перестраховывается - считает что используются вообще все регистры. То же при вызове функции определенной в другом файле - компилятор транслирует по отдельности и не знает какие регистры используются вне единицы трансляции.
Это не в АВР, это в Ардуине! Так то можно написать обработчик так, что вообще ничего сохраняться не будет. И вход в обработчик будет всего 4 такта. Но для этого нужно не использовать вызов функций из других файлов в обработчике, в том числе - библиотечных. И не использовать вызов функций по указателю, а это тот самый attachInterrupt.
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
@Forgetweb,что в прерывании параметров нет - это не обсуждается.
Компилятор, при передаче управления функции, ничего кроме параметров, при их наличии, одного регистра и адреса возврата в стеке не сохраняет. Бремя сохранения состояния используемых ресурсов, включая регистры, лежит на теле функции самой по себе, а там компилятор точно знает, что будет использовано. Так работают все компиляторы, никто ничего не предугадывает и не перестраховывается. Поэтому, с точки зрения компилятора - вызов обычной функции и прерывания ничем не отличаются: и там, и там сохранение нужных для выполнения куска кода регистров обеспечивается компилятором на стадии компиляции самого кода прерывания или функции. А при вызове функции компилятор уверен, что эти вещи в самой функции учтены.
Вот, специально взял программу "от Ардуино", вот как выглядит вызов функций setup(), loop() и одного из методов Serial:
1686901367568.png
 
Изменено:

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Хорошо. Надо пример? Сейчас нарисуем.
Простейший обработчик:
ISR(INT0_vect)
{
    PORTB |= 1;
}
Что получим
Код:
ISR(INT0_vect)
{
  46:    1f 92           push    r1
  48:    0f 92           push    r0
  4a:    0f b6           in    r0, 0x3f    ; 63
  4c:    0f 92           push    r0
  4e:    11 24           eor    r1, r1
    PORTB |= 1;
  50:    28 9a           sbi    0x05, 0    ; 5
  52:    0f 90           pop    r0
  54:    0f be           out    0x3f, r0    ; 63
  56:    0f 90           pop    r0
  58:    1f 90           pop    r1
  5a:    18 95           reti
  }
Теперь изменим код.
с указателем:
typedef (*pFunc_t)(void);

pFunc_t myFunc;

ISR(INT0_vect)
{
    (*myFunc)();
}
И что имеем
Накомпилили...:
ISR(INT0_vect)
{
  56:    1f 92           push    r1
  58:    0f 92           push    r0
  5a:    0f b6           in    r0, 0x3f    ; 63
  5c:    0f 92           push    r0
  5e:    11 24           eor    r1, r1
  60:    2f 93           push    r18
  62:    3f 93           push    r19
  64:    4f 93           push    r20
  66:    5f 93           push    r21
  68:    6f 93           push    r22
  6a:    7f 93           push    r23
  6c:    8f 93           push    r24
  6e:    9f 93           push    r25
  70:    af 93           push    r26
  72:    bf 93           push    r27
  74:    ef 93           push    r30
  76:    ff 93           push    r31
    (*myFunc)();
  78:    e0 91 a6 00     lds    r30, 0x00A6
  7c:    f0 91 a7 00     lds    r31, 0x00A7
  80:    09 95           icall
  82:    ff 91           pop    r31
  84:    ef 91           pop    r30
  86:    bf 91           pop    r27
  88:    af 91           pop    r26
  8a:    9f 91           pop    r25
  8c:    8f 91           pop    r24
  8e:    7f 91           pop    r23
  90:    6f 91           pop    r22
  92:    5f 91           pop    r21
  94:    4f 91           pop    r20
  96:    3f 91           pop    r19
  98:    2f 91           pop    r18
  9a:    0f 90           pop    r0
  9c:    0f be           out    0x3f, r0    ; 63
  9e:    0f 90           pop    r0
  a0:    1f 90           pop    r1
  a2:    18 95           reti
  }
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Даже если вызывается такая функция
C++:
void myfunc(void)
{
PORTB |= 1;
}
Регистры все равно сохраняются! Хотя и не используются. Функция вообще ни один регистр не использует.
 

poty

★★★★★★✩
19 Фев 2020
3,265
950
@Forgetweb, это несколько другой случай - вызов функции по указателю. А если просто функцию вызвать, без сложного указателя?
Я же показал, как это происходит в моём случае.
 

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
@poty,
А attachInterrupt это типа что то иное? Тот самый вызов по указателю. Его и обсуждаем.
 

poty

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

Forgetweb

★✩✩✩✩✩✩
8 Май 2022
61
20
Кстати первый обработчик
C++:
ISR(INT0_vect)
{
    PORTB |= 1;
}
Если его немного изменить
C++:
ISR(INT0_vect) ISR_NAKED;
ISR(INT0_vect)
{
    PORTB|= 1;
    asm volatile("reti");
}
Получим
C++:
ISR(INT0_vect)
{
    PORTB |= 1;
  46:    28 9a           sbi    0x05, 0    ; 5
    asm volatile("reti");
  48:    18 95           reti
}
Вот этот обработчик будет быстрым. 4 такта вход, 2 такта cbi, 4 такта выход. Итого 10 таков, меньше микросекунды при частоте 16МГц.
 
Изменено: