WS2812 + Apds-9960
- Войдите на сайт для отправки комментариев
Всем привет.
Хочу сделать себе светодиодное освещение из 300 светодиодов (WS2812) с переключением эффектов жестами (с помощью датчика Apds-9960).
Сами эффекты беру тут: https://www.tweaking4all.com/hardware/arduino/adruino-led-strip-effects/#LEDEffects
Сам тоже писал немного, поосваивал, так сказать, программирование WS2812.
Пока испытываю на 16 светодиодах это дело (ну, жесты, то есть), дабы потом без проблем залить скетч под 300 штук. Начал с простого: с пары эффектов (влево - один эффект, вправо - другой, вниз и вверх - выключить ленту).
Собственно, столкнулся с проблемой прерываний. Если зациклить какой-нибудь эффект, то из него уже не выйти, а если сделать конечным, то он просто пройдет один раз и остановится. Сами эффекты находятся в циклах for(). Как реализовать выход из циклов по событию (при смене жеста)?
Сам код:
#include <Adafruit_NeoPixel.h>
#include <Wire.h> // Для работы с шиной I2C
#include <SparkFun_APDS9960.h> // Для работы с датчиком APDS-9960
#define PIN 6
#define NUM_LEDS 16
#define APDS9960_INT 2 // Needs to be an interrupt pin. We should be able to use the Arduino's pin 3 as well.
Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800);
SparkFun_APDS9960 apds = SparkFun_APDS9960(); // Initialize a SparkFun_APDS9960 object. This does all the magic incl. i2c communication with the sensor.
volatile int isr_flag = 0; // interrupt flag, triggered when a gesture has been dectected. Used to detect gesture detection in the interrupt handler
void setup()
{
// Инициализация RGB
pinMode(PIN, OUTPUT);
strip.begin();
strip.show(); // Initialize all pixels to 'off'
// APDS Initialization code
pinMode(APDS9960_INT, INPUT); // Set interrupt pin as input. @@Note: this should be handled my the library. But it isn't
attachInterrupt(0, interruptRoutine, FALLING); // Initialize interrupt service routine. Basicly it'll call our interrupt routine
if (apds.init()) // Initialize APDS-9960 (configure I2C and initial values)
{
// @@NOTE: original value is two. But it looks like the modern gesture sensor or more sensitive. 1 does it for me
apds.setGestureGain( 1 );
}
if (apds.enableGestureSensor(true)) // Start running the APDS-9960 gesture sensor engine. Sensor support more functions than gesture detection only.
{
//strip.begin();
strip.show();
}
}
void loop()
{
if(isr_flag == 1)
{
detachInterrupt(0);
handleGesture();
isr_flag = 0;
attachInterrupt(0, interruptRoutine, FALLING);
}
}
void interruptRoutine()
{
isr_flag = 1;
}
void handleGesture()
{
if (apds.isGestureAvailable())
{
switch (apds.readGesture())
{
case DIR_LEFT: RGBLoop(); break;
case DIR_RIGHT: CylonBounce(0, 0xff, 0, 4, 10, 50); break;
case DIR_UP: strip.clear(); strip.show(); break;
case DIR_DOWN: strip.show(); strip.clear(); break;
}
}
}
// ---> here we define the effect function <---
// *** REPLACE TO HERE ***
void RGBLoop()
{
for(;;)
{
for(int j = 0; j < 3; j++ )
{
// Fade IN
for(int k = 0; k < 256; k++)
{
switch(j)
{
case 0: setAll(k,0,0); break;
case 1: setAll(0,k,0); break;
case 2: setAll(0,0,k); break;
}
showStrip();
//delay(3);
}
// Fade OUT
for(int k = 255; k >= 0; k--)
{
switch(j)
{
case 0: setAll(k,0,0); break;
case 1: setAll(0,k,0); break;
case 2: setAll(0,0,k); break;
}
showStrip();
//delay(3);
}
}
//break;
}
}
void CylonBounce(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay)
{
for(;;)
{
for(int i = 0; i < NUM_LEDS-EyeSize-2; i++)
{
setAll(0,0,0);
setPixel(i, red/10, green/10, blue/10);
for(int j = 1; j <= EyeSize; j++)
{
setPixel(i+j, red, green, blue);
}
setPixel(i+EyeSize+1, red/10, green/10, blue/10);
showStrip();
delay(SpeedDelay);
}
delay(ReturnDelay);
for(int i = NUM_LEDS-EyeSize-2; i > 0; i--)
{
setAll(0,0,0);
setPixel(i, red/10, green/10, blue/10);
for(int j = 1; j <= EyeSize; j++)
{
setPixel(i+j, red, green, blue);
}
setPixel(i+EyeSize+1, red/10, green/10, blue/10);
showStrip();
delay(SpeedDelay);
}
delay(ReturnDelay);
//break;
}
}
void showStrip()
{
#ifdef ADAFRUIT_NEOPIXEL_H
// NeoPixel
strip.show();
#endif
#ifndef ADAFRUIT_NEOPIXEL_H
// FastLED
FastLED.show();
#endif
}
void setPixel(int Pixel, byte red, byte green, byte blue)
{
#ifdef ADAFRUIT_NEOPIXEL_H
// NeoPixel
strip.setPixelColor(Pixel, strip.Color(red, green, blue));
#endif
#ifndef ADAFRUIT_NEOPIXEL_H
// FastLED
leds[Pixel].r = red;
leds[Pixel].g = green;
leds[Pixel].b = blue;
#endif
}
void setAll(byte red, byte green, byte blue)
{
for(int i = 0; i < NUM_LEDS; i++ )
{
setPixel(i, red, green, blue);
}
showStrip();
}
Где нужно ставить isr_flag = 0? Что только не перепробовал... Примеров внятных нет по внешним прерываниям циклов for или while. Всё что находил - мигание светодиодом (в библиотеке apds тоже есть примеры с прерываниями, но так же не цикличны), поэтому мне сложно понять как работает прерывание.
Если рассуждать логически, то нужно сначала зациклить в бесконечность какой-нибудь эффект, а когда сработал жест, выйти из цикла и начать следующее действие.
И, да, я сознательно закомментировал delay в эффекте RGBLoop, потому что эта задержка, возможно, мешает чтению жестов. Как делать с millis() тоже пока не разобрался, как им заменить delay, но это ладно, потом уже. Сейчас главное читать датчик во время выполнения эффекта.
Заменял ещё for(;;)
наwhile(!apds.readGesture()) в 71 и 106 строках. Но работает по прошествии всего цикла, а не в любой момент. Есть долгоработающие эффекты и ждать прошествия цикла ну как-то не вариант.Если отвечать на поставленный вами вопрос - как выйти из цикла по прерыванию, то можно, например, сказать, что нужно проверять isr_flag внутри самого "глубокого" цикла (например в районе строк 78, 90 и тп.) и делать return, если он равен ненулевой.
Если же делать правильно, то вам нужно код каждого спецэффекта заменить на неблокирующий и тогда проблема почти что разрешиться сама собой.
Что для вас проще - я не знаю.
Если же делать правильно, то вам нужно код каждого спецэффекта заменить на неблокирующий и тогда проблема почти что разрешиться сама собой.
В смысле без циклов, одними условиями if-else?
А не подскажете ещё по millis()? Где можно подробнее почитать, кроме примеров со светодиодом "мигаем blink без delay"? Этот пример мало что даёт, например, не объясняет как использовать его в глубинах циклов, при чтении каких-либо датчиков...
И ещё интересно... Подключал до apds модуль радиоканала. Управлял с пульта. Самое интересное, что там никакая пропитка выходами из цикла не нужна, переключение работает и так. Но тут другая проблема, после первого чтения команды с пульта, последующие переключения работают плохо, пульт нужно подносить очень близко, срабатывает не сразу. Может кто что посоветовать/объяснить как это исправить? Из-за задержек, насколько я понимаю? Привожу код.
#include <Adafruit_NeoPixel.h> #include <RCSwitch.h> #define PIN 6 #define NUM_LEDS 16 // Parameter 1 = number of pixels in strip // Parameter 2 = pin number (most are valid) // Parameter 3 = pixel type flags, add together as needed: // NEO_KHZ800 800 KHz bitstream (most NeoPixel products w/WS2812 LEDs) // NEO_KHZ400 400 KHz (classic 'v1' (not v2) FLORA pixels, WS2811 drivers) // NEO_GRB Pixels are wired for GRB bitstream (most NeoPixel products) // NEO_RGB Pixels are wired for RGB bitstream (v1 FLORA pixels, not v2) Adafruit_NeoPixel strip = Adafruit_NeoPixel(NUM_LEDS, PIN, NEO_GRB + NEO_KHZ800); RCSwitch mySwitch = RCSwitch(); void setup() { strip.begin(); strip.show(); // Initialize all pixels to 'off' mySwitch.enableReceive(0); // Receiver on interrupt 0 => that is pin #2 } // *** REPLACE FROM HERE *** void loop() { // ---> here we call the effect function <--- readpult(); } // Функция чтения пульта void readpult() { if(mySwitch.getReceivedValue() == 12932400 || mySwitch.getReceivedValue() == 11591234) { RGBLoop(); } if(mySwitch.getReceivedValue() == 12932364) { CylonBounce(0xff, 0, 0, 4, 10, 50); } } // ---> here we define the effect function <--- // *** REPLACE TO HERE *** void RGBLoop(){ for(;;) { for(int j = 0; j < 3; j++ ) { // Fade IN for(int k = 0; k < 256; k++) { switch(j) { case 0: setAll(k,0,0); break; case 1: setAll(0,k,0); break; case 2: setAll(0,0,k); break; } showStrip(); delay(3); } // Fade OUT for(int k = 255; k >= 0; k--) { switch(j) { case 0: setAll(k,0,0); break; case 1: setAll(0,k,0); break; case 2: setAll(0,0,k); break; } showStrip(); delay(3); } } break; } } void CylonBounce(byte red, byte green, byte blue, int EyeSize, int SpeedDelay, int ReturnDelay){ for(;;) { for(int i = 0; i < NUM_LEDS-EyeSize-2; i++) { setAll(0,0,0); setPixel(i, red/10, green/10, blue/10); for(int j = 1; j <= EyeSize; j++) { setPixel(i+j, red, green, blue); } setPixel(i+EyeSize+1, red/10, green/10, blue/10); showStrip(); delay(SpeedDelay); } delay(ReturnDelay); for(int i = NUM_LEDS-EyeSize-2; i > 0; i--) { setAll(0,0,0); setPixel(i, red/10, green/10, blue/10); for(int j = 1; j <= EyeSize; j++) { setPixel(i+j, red, green, blue); } setPixel(i+EyeSize+1, red/10, green/10, blue/10); showStrip(); delay(SpeedDelay); } delay(ReturnDelay); break; } } void showStrip() { #ifdef ADAFRUIT_NEOPIXEL_H // NeoPixel strip.show(); #endif #ifndef ADAFRUIT_NEOPIXEL_H // FastLED FastLED.show(); #endif } void setPixel(int Pixel, byte red, byte green, byte blue) { #ifdef ADAFRUIT_NEOPIXEL_H // NeoPixel strip.setPixelColor(Pixel, strip.Color(red, green, blue)); #endif #ifndef ADAFRUIT_NEOPIXEL_H // FastLED leds[Pixel].r = red; leds[Pixel].g = green; leds[Pixel].b = blue; #endif } void setAll(byte red, byte green, byte blue) { for(int i = 0; i < NUM_LEDS; i++ ) { setPixel(i, red, green, blue); } showStrip(); }В смысле без циклов, одними условиями if-else?
А не подскажете ещё по millis()? Где можно подробнее почитать, кроме примеров со светодиодом "мигаем blink без delay"? Этот пример мало что даёт, например, не объясняет как использовать его в глубинах циклов, при чтении каких-либо датчиков...
Слишком абстрактный вопрос задаете. Наврядли ответ на него вам даст понимание. Приведите простой случай который вам близок. Могу на вареном яйце объяснить, к примеру.
Слишком абстрактный вопрос задаете. Наврядли ответ на него вам даст понимание. Приведите простой случай который вам близок. Могу на вареном яйце объяснить, к примеру.
Ну вот более простой пример, написал такую вот мигалку:
int8_t i; int8_t j; for (i = 0, j = count_led / 2; i < count_led / 2, j < count_led; i++, j++) { pixels.setPixelColor(i, 0,0,10); pixels.setPixelColor(j, 0,10,0); delay(35); // здесь pixels.show(); } for (j = 0, i = count_led / 2; j < count_led / 2, i < count_led; j++, i++) { pixels.setPixelColor(i, 0,0,10); pixels.setPixelColor(j, 0,10,0); delay(35); // и здесь pixels.show(); }delay здесь явно лишние для чтения каких-либо датчиков... Заменить на millis вообще не понял как.
Поправьте, что не так делаю?
#include <Adafruit_NeoPixel.h> #define PIN 6 // номер порта к которому подключен модуль #define count_led 16 // количество светодиодов unsigned long timing; // переменная для хранения времени int8_t i; int8_t j; for (i = 0, j = count_led / 2; i < count_led / 2, j < count_led; i++, j++) { if(millis() - timing > 3500) { pixels.setPixelColor(i, 0,0,10); pixels.setPixelColor(j, 0,10,0); //delay(35); pixels.show(); } } for (j = 0, i = count_led / 2; j < count_led / 2, i < count_led; j++, i++) { if(millis() - timing > 3500) { pixels.setPixelColor(i, 0,0,10); pixels.setPixelColor(j, 0,10,0); //delay(35); pixels.show(); } }void loop() { static uint8_t pos = 0, dir; static uint32_t timing = 0, color; // тут можем читать датчик, проверять кнопки и пр. // каждые 35 ms делаем очередной "шаг" if(millis() - timing > 35) { timing = millis(); // Если стоим в начале страйпа, то начинаем двигаться вперед и заливать все цветом 0xFF00FF if (0 >= pos) { color = 0xFF00FF; dir = 1; } // Если дошли/стоим в конце страйпа, то начинаем двигаться назад и заливать все цветом 0x00FF00 if (count_led <= pos) { color = 0x00FF00; dir = -1; } // На этом шаге красим только один пиксель (остальные уже покрашены) и выводим массив pixels на страйп pos += dir; pixels.setPixelColor(pos, color); pixels.show(); } }То есть всё-таки рекомендуете заменить циклы и заменять готовые эффекты на подобное чтобы получилось как надо с millis? Эххх... Очень всё непросто выходит. Спасибо, буду пробовать что-нибудь.
А по прерываниям на радиоканале что посоветуете? Почему пульт плохо срабатывает когда нужно выйти из подпрограммы?
Мм... Проблема всё же в прерываниях. Убрал все attachInterrupt из Setup и Loop - работает всё так же до конца цикла. Не работают прерывания, в общем. Может кто показать как их применить в моём примере?
Теперь более понятно почему с пульта работает почти как надо. В библиотеке RCSwitch есть такая замечательная функция
mySwitch.enableReceive(0);// Receiver on interrupt 0 => that is pin #2С ней прерывания работают почти как надо (выходит из цикла for(;;) в любой момент по нажатию кнопки), за исключением того что во время цикла эффекта почему-то плохо работает пульт (но это, вероятно, из-за задержек (?)).
А на APDS-9960 без понятия как использовать прерывания... Нужны ли флаги (isr_flag)? Пытался сделать как здесь: http://mysensors.ru/build/gesture-controller/ взяв пример DimmableLight_v2.ino. Не работают прерывания если делать как здесь, заменяя функции на свои.
Проблема в том, что вы начинаете метаться, перебирая компоненты и программные приемы, не понимая, как они работают и какие у них типовые ограничения.
Например, известно, что для вывода на WS2811/2812/etc библиотеки, как правило, запрещают обработку прерываний. Так что всё, что прилетает на ардуину извне имеет ненулевой шанс быть упущенным.
...или пытаетесь свести два устройство в одно. По ссылке - там ардуина, связанная с сенсором, не занимается сложной обработкой и выводами на страйп, поэтому у нее достаточно ресурсов, чтобы ловить движения на сенсоре. Она опросила сенсор, решение приняла, десять байт выплюнула по nrf24l01 и дальше готова к работе.
Например, известно, что для вывода на WS2811/2812/etc библиотеки, как правило, запрещают обработку прерываний. Так что всё, что прилетает на ардуину извне имеет ненулевой шанс быть упущенным
Ну как-то же делают наверняка такое. Те же китайцы прилагают готовые запрограммированные пульты к таким лентам. Ардуина не справляется разве с таким?
Проблема в том, что вы начинаете метаться, перебирая компоненты и программные приемы, не понимая, как они работают и какие у них типовые ограничения.
Вы правы, но как-то ж надо попробовать... Выходит, да, что надо знать и тонкости ардуины, и тонкости библиотек компонентов.
Ну как-то же делают наверняка такое. Те же китайцы прилагают готовые запрограммированные пульты к таким лентам. Ардуина не справляется разве с таким?
В общем, что хотел сделать - пожалуй, удалось.) Поделюсь, может, кому пригодится. Взял за основу готовые эффекты AlexGyver'а.
https://www.youtube.com/watch?v=_h-Vj8Z6hqs
Прикрутил модули радиоканала и bluetooth'а. Дописал вот такой код:
#include <RCSwitch.h> #include <SoftwareSerial.h> #define PIN_BTRX 15 #define PIN_BTTX 14 RCSwitch mySwitch = RCSwitch(); SoftwareSerial bluetooth(PIN_BTTX, PIN_BTRX); //функция для запуска синезубого void setupBluetooth() { bluetooth.begin(115200); // The Bluetooth Mate defaults to 115200bps bluetooth.print("$$$"); // Enter command mode delay(100); // Short delay, wait for the Mate to send back CMD bluetooth.println("U,9600,N"); // Temporarily Change the baudrate to 9600, no parity // 115200 can be too fast at times for NewSoftSerial to relay the data reliably bluetooth.begin(9600); // Start bluetooth serial at 9600 } ... // Далее код Alex'а void setup() { mySwitch.enableReceive(0); // Receiver on interrupt 0 => that is pin #2 setupBluetooth(); ... // далее код Alex'а } void loop() { // Здесь просто две кнопки переключения между эффектами if (mySwitch.available()) { if(mySwitch.getReceivedValue() == 12932400) // команды пульта пишем свои { ledMode++; } if(mySwitch.getReceivedValue() == 12932364) // здесь тоже команда { ledMode--; } // сброс счётчика эффектов, если число больше или меньше диапазона if(ledMode < 2) { ledMode = 41; } if(ledMode > 41) { ledMode = 2; } mySwitch.resetAvailable(); } // чтение синезуба if (bluetooth.available()) { ledMode = bluetooth.parseInt(); } ... // далее код Alex'а } ...Ну, собственно, и всё. Пульт, конечно, работает немного не так, как хотелось бы - всему виной задержки в кодах эффектов. Но переписывать это дело под millis задача просто невозможная, имхо. Да и "не так, как хотелось бы" заключается лишь в небольшом удерживании кнопки, около полусекунды, а хотелось бы чтобы при одном коротком нажатии.
Синезубый приятель же работает без отказов - идеально. Программу для управления рекомендую Bluetooth Electronics. В ней настраиваются не только сами кнопки, но и их количество, а ещё можно приделать всякие слайдеры и переключатели.
Из личного минуса - не работает айфон с этими модулями (HR-05) bluetooth, но это уже "спасибо" эпплу с его политикой:
ios does not support most bluetooth profiles, because apple wants a slice of peripheral profits through its MFi program. If you can't dump ios, you will have to use BLE (which is unrestricted and not part of MFi).
Товарищ андройд - наше всё.)
Все что Вы искали, уже выложено в открытом доступе https://youtu.be/C4QELMzOpiA
Все что Вы искали, уже выложено в открытом доступе https://youtu.be/C4QELMzOpiA
реклама? "Все что вы искали" - кому это? - тем более что ТС ничего и не искал
Ссылку не открывал, что там - не знаю, но уж очень агрессивно подается. На уровне "самые красивые сиськи - в нашем видео"
А Вы полностью прочитали сообщение топикстартера?
Цитирую:
"Собственно, столкнулся с проблемой прерываний. Если зациклить какой-нибудь эффект, то из него уже не выйти, а если сделать конечным, то он просто пройдет один раз и остановится. Сами эффекты находятся в циклах for(). Как реализовать выход из циклов по событию (при смене жеста)?"
Ссылку не открывал, что там - не знаю, но уж очень агрессивно подается. На уровне "самые красивые сиськи - в нашем видео"
А Вам , то что от этого плохо стало или другие мотивы?
А Вы полностью прочитали сообщение топикстартера?
А вы всю ветку читали? - Автору было предложено порядка пяти разных способов решения его проблемы...
Не говоря уж о том, что проблема эта обсуждается каждым вторым новичком и решения для нее приведены в сотнях источников в гугле.
Вы уверены. что ваша ссылка спустя год хоть что-то добавляет к обсуждению? :)))
Я уверен на 100%, что мое решение избавит от всех проблем, присущих этому датчику. Вы его скорее всего не использовали никогда и не в курсе о его нестабильной работе