ATTiny84 I2C не работает с NeoPixels

Я получаю значения RGB через I2C, что работает (с функцией разделения).

Теперь у меня проблема:

  • Когда я записываю значения непосредственно в strip.color(), например strip.Color(255,0,0,0), NeoPixels становятся красными при получении значений.
  • Когда я использую значения из соединения I2C, оно больше не работает. Но значения верны, если я посмотрю в Serial Monitor.

Может быть, тип данных неправильный или что-то в этом роде?

#include <Wire.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN   4
#define LED_COUNT 4

static const int SLAVE_ADDRESS = 0x08;

uint32_t currentColor;
int currentBrightness = 50;
int flashState = 0;
String ledRed, ledGreen, ledBlue, ledWhite;

String temp;
String payload;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800);

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

  strip.begin();           
  strip.show(); // Отключаем все пиксели как можно скорее
  strip.setBrightness(50); // Установить яркость
  
  pinMode(3, OUTPUT);
  digitalWrite(3, HIGH);

  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(change);
}


// Задаем цвет всем пикселям
void setPixelColors(uint32_t color) {  
  for(int p = 0; p < LED_COUNT; p++) {
    strip.setPixelColor(p, color);
  }
  strip.show();

  delay(1000);
  digitalWrite(3, HIGH);
}


// Разделить сообщение I2C
String split(String s, char parser, int index) {
  String rs="";
  int parserIndex = index;
  int parserCnt=0;
  int rFromIndex=0, rToIndex=-1;
  while (index >= parserCnt) {
    rFromIndex = rToIndex+1;
    rToIndex = s.indexOf(parser,rFromIndex);
    if (index == parserCnt) {
      if (rToIndex == 0 || rToIndex == -1) return "";
      return s.substring(rFromIndex,rToIndex);
    } else parserCnt++;
  }
  return rs;
}


void loop() {
  // ...
}



void change(int howMany) {
  digitalWrite(3, LOW);
  temp = "";
  if (Wire.available()) {
    // Получить строку
    while(Wire.available()) {
      char c = Wire.read();
      temp.concat(c);
    }

    payload = String(temp);
    flashState = split(payload, ',', 0).toInt();
    ledRed = split(payload, ',', 1);
    ledGreen = split(payload, ',', 2);
    ledBlue = split(payload, ',', 3);
    ledWhite = split(payload, ',', 4);

    byte bufRed[3];
    ledRed.toCharArray(bufRed, 3);
    
    byte bufGreen[3];
    ledGreen.toCharArray(bufGreen, 3);

    byte bufBlue[3];
    ledBlue.toCharArray(bufBlue, 3);
    
    byte bufWhite[3];
    ledWhite.toCharArray(bufWhite, 3);

    
    //setPixelColors(strip.Color(255, 0, 255, 0)); // РАБОТАЮЩИЙ
        
    currentColor = strip.Color(bufRed, bufGreen, bufBlue, bufWhite);
    setPixelColors(currentColor); // НЕ РАБОТАЕТ
  }
  
  // Доказательство завершения чтения I2C
  delay(10);
}

Я пробовал разные типы данных, прикрепил индикатор состояния, чтобы знать, что соединение I2C работает. Когда я тестирую это с моим UNO, я получаю R = 255 G = 0 B = 255 W = 0; как и предполагалось.

"Забавный факт": на моем UNO это работает как шарм. Только ATTiny85 не принимает эти значения(?)


Новый код:

#include <Wire.h>
#include <Adafruit_NeoPixel.h>

#define LED_PIN   4
#define LED_COUNT 20

static const int SLAVE_ADDRESS = 0x08;

int currentBrightness = 50;
int flashState = 0;
int ledRed, ledGreen, ledBlue, ledWhite;


volatile bool recvFlag = false;
volatile uint32_t currentColor = 0;
String temp;

String payload;

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRBW + NEO_KHZ800);

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

  strip.begin();
  strip.show(); // Отключаем все пиксели как можно скорее
  strip.setBrightness(50); // Установите ЯРКОСТЬ примерно на 1/5 (макс. = 255)

  Wire.begin(SLAVE_ADDRESS);
  Wire.onReceive(change);
}


void setPixelColors(uint32_t color) {
  for (int p = 0; p < LED_COUNT; p++) {
    strip.setPixelColor(p, color);
    strip.show();
  }
}


String split(String s, char parser, int index) {
  String rs = "";
  int parserIndex = index;
  int parserCnt = 0;
  int rFromIndex = 0, rToIndex = -1;
  while (index >= parserCnt) {
    rFromIndex = rToIndex + 1;
    rToIndex = s.indexOf(parser, rFromIndex);
    if (index == parserCnt) {
      if (rToIndex == 0 || rToIndex == -1) return "";
      return s.substring(rFromIndex, rToIndex);
    } else parserCnt++;
  }
  return rs;
}

void loop() {
  if (recvFlag) {   
    payload = String(temp);
    flashState = split(payload, ',', 0).toInt();
    ledRed = split(payload, ',', 1).toInt();
    ledGreen = split(payload, ',', 2).toInt();
    ledBlue = split(payload, ',', 3).toInt();
    ledWhite = split(payload, ',', 4).toInt();

    currentColor = strip.Color(ledRed, ledGreen, ledBlue, ledWhite);
    setPixelColors(currentColor);
    
    recvFlag = false;
  }
}



void change(int howMany) {
  temp = "";
  if (Wire.available()) {
    // Получить строку
    while (Wire.available()) {
      char c = Wire.read();
      temp.concat(c);
    }

    // Доказательство завершения чтения I2C
    recvFlag = true;
  }
}

, 👍1


1 ответ


Лучший ответ:

0

Честно говоря, я не знаю, почему именно это работает на вашем Uno. Вы должны знать, что функция обратного вызова onReceive вызывается из ISR (процедуры обслуживания прерываний). И внутри ISR вы не можете делать вещи, которые полагаются на работу прерываний (например, delay()), так как только одно прерывание может выполняться одновременно. Я не уверен, как библиотека Neopixel работает в своем ядре, но, вероятно, не очень хорошая идея запускать метод show() внутри ISR. Кроме того, как правило, вы не хотите делать длинные вещи внутри ISR, так как это заблокирует все другие функции прерывания вашего микроконтроллера (например, I2C, Serial, отсчет времени через delay() или < код>millis()...).

Вместо этого рекомендуется только установить флаг, а затем выполнить эти действия в основном коде (он же loop()). Итак, что-то вроде этого:

volatile bool recv_flag = false;
volatile uint32_t current_color = 0;

void setup(){
    // Инициируем все, как вы уже делали в своем коде
    ...
}

void loop(){
    if(recv_flag){
        setPixelColors(currentColor);
        recv_flag = false;
    }
}
void change(int howMany) {
  digitalWrite(3, LOW);
  temp = "";
  if (Wire.available()) {
    // Выполняем разбор всех данных, как вы уже это делали
    ...
    // сохраняем текущий цвет в глобальной переменной
    currentColor = strip.Color(bufRed, bufGreen, bufBlue, bufWhite);
    // устанавливаем флаг
    recv_flag = true;
  }
}

Обратите внимание, что каждая переменная, которая будет записана внутри ISR, должна быть объявлена как volatile. Это не позволяет компилятору оптимизировать его, поскольку он не может знать, когда произойдут прерывания. Поэтому он может подумать, что эта переменная никогда не изменится, и просто заменить ее литералом. Ключевое слово volatile предотвращает это.


Как упоминалось выше, мне даже интересно, почему ваш код работает на Uno. Хотя основное различие между Uno и Attiny84 заключается в том, что Uno представляет собой полноценный аппаратный интерфейс I2C, а Attiny84 имеет только USI (универсальный последовательный интерфейс). Таким образом, в Uno большая часть связи I2C реализована аппаратно, в то время как Attiny84 должен запускать (вероятно, основанный на прерывании) код для осуществления связи.

,

Я заставил его работать очень быстро с 4 светодиодами. Думал, что все. Но потом попробовал с 20 светодиодами - опять ничего не получилось. Я попробовал пример с «colorWipe» из библиотеки. Это работает - так что мощности достаточно, и каждый светодиод работает .... И мой API + Splitting должен работать, потому что он работал в течение 2-3 тестов, прежде чем добавлять больше светодиодов. Любые другие идеи, почему ЭТО происходит сейчас? :/, @dessi

@dessi Вы можете добавить новый код к своему вопросу. Тогда я посмотрю, могу ли я помочь вам с этим. Но, пожалуйста, оставьте текущий текст вопроса нетронутым, чтобы другие могли извлечь из него уроки., @chrisl

только что обновил вопрос с новым кодом, @dessi

@dessi В целом ваш код выглядит хорошо. Вы должны объявить temp как volatile, так как вы записываете в него внутри ISR. Но я не думаю, что это проблема. Можете ли вы быть уверены, что связь I2C работает должным образом? Например по подсветке и светодиоду были получены правильные данные? И можете ли вы проверить, блокируется ли код в какой-то момент? Если да, то где именно блокирует? Или код запускается, но не действует? Это была бы важная информация для дальнейшей отладки., @chrisl

Возможно, у вас возникнут проблемы со всем использованием String. Он использовал динамическое выделение памяти, что может сделать из вашей памяти швейцарский сыр (для справки [запись в блоге Majenkos об этом] (https://majenko.co.uk/blog/evils-arduino-strings). Хотя это должно быть только проблема после еще нескольких транзакций I2C., @chrisl

Эй, приятель, я только что сдался... Теперь я сделаю это по-другому. Я могу передать один единственный символ без проблем, поэтому я беру с собой символ значения пользовательской матрицы и фиксирую значения светодиодов на ATTiny85: / Проект немного критичен по времени, поэтому я должен исправить эти значения: / Но спасибо вам за вашу помощь в любом случае! Может быть, когда-нибудь кому-нибудь понадобится volatile штука :), @dessi