управление по умолчанию регулировает speed, нажал на кнопку настройки 1 строки, нажал еще раз настройки 2 строки, нажал еще 4 строка, нажал четвертый раз по умолчанию
не нравится меню дисплея, как то надо по другому все организовывать, выходов мало зрительно надо переходить на PCA9685 до 16 ШИМ
есть идеи по меню пиши
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#define LedA 3 //220 вольт на к1182пм1р https://www.chipdip.ru/product/k1182pm1r
#define LedB 5
#define LedC 6
#define LedD 9
#define LedE 10
#define LedF 11
#define BUTTON A2 //кнопка энкодора
LiquidCrystal_I2C lcd(0x3F,20,4);
//byte mainMenuPosition = 0; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ПУНКТА ГЛАВНОГО МЕНЮ
//byte mainMenuState = 9; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СОСТОЯНИЯ ГЛАВНОГО МЕНЮ (ДЕАКТИВАЦИЯ ВЫБРАННОГО ПУНКТА МЕНЮ)
int powerState = 155; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ЯРКОСТИ
byte sceneNumber = 101; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СЦЕНЫ
byte effectNumber = 101; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ ПРОГРАММЫ БЕГУШКИ
byte speedNumber = 250; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ
byte minSpeedDelay = 250; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ
byte maxSpeedDelay = 35; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ
void setup() {
lcd.init(); // initialize the lcd
PCICR=1<<PCIE1; //разрешить пренрывание
PCMSK1=(1<<PCINT9)|(1<<PCINT8); //выбрать входы Кнопка на А2, энкодер на А0, А1. Конденсаторы на землю нужны.
lcd.clear();
pinMode(BUTTON,INPUT_PULLUP);
pinMode(LedA, OUTPUT);
pinMode(LedB, OUTPUT);
pinMode(LedC, OUTPUT);
pinMode(LedD, OUTPUT);
pinMode(LedE, OUTPUT);
pinMode(LedF, OUTPUT);
digitalWrite(LedA, LOW);
digitalWrite(LedB, LOW);
digitalWrite(LedC, LOW);
digitalWrite(LedD, LOW);
digitalWrite(LedE, LOW);
digitalWrite(LedF, LOW);
lcd.backlight();
}
volatile int enc;
ISR (PCINT1_vect){
static byte old_n=PINC&3; // маска B00000011 что б читать только нужные 2 бита
byte new_n=PINC&3;
if (old_n == 1 && new_n == 2 || old_n == 2 && new_n == 0) {enc++;}
if (old_n == 2 && new_n == 2 || old_n == 1 && new_n == 0) {enc--;}
old_n = new_n;
}
boolean a=0, b=0, c=0, d=0, e=0, f=0;
byte w=4;
void LightMode() {
if (sceneNumber == 101) { //1 светит
if (w == 0) { a=1; f=0; b=0; }
if (w == 1) { b=1; a=0; c=0; }
if (w == 2) { c=1; b=0; d=0; }
if (w == 3) { d=1; c=0; e=0; }
if (w == 4) { e=1; d=0; f=0; }
if (w == 5) { f=1; e=0; a=0; }
}
if (sceneNumber == 102) { //2 светят
if (w == 0) { a=1; b=1; f=0; c=0; }
if (w == 1) { b=1; c=1; a=0; d=0; }
if (w == 2) { c=1; d=1; b=0; e=0; }
if (w == 3) { d=1; e=1; c=0; f=0; }
if (w == 4) { e=1; f=1; d=0; a=0; }
if (w == 5) { f=1; a=1; e=0; b=0; }
}
if (sceneNumber == 103) { //3 светят
if (w == 0) { a=1; c=1; f=0; d=0; }
if (w == 1) { b=1; d=1; a=0; e=0; }
if (w == 2) { c=1; e=1; b=0; f=0; }
if (w == 3) { d=1; f=1; c=0; a=0; }
if (w == 4) { e=1; a=1; d=0; b=0; }
if (w == 5) { f=1; b=1; e=0; c=0; }
}
if (sceneNumber == 104) { //4 светят
if (w == 0) { a=1; d=1; f=0; e=0; }
if (w == 1) { b=1; e=1; a=0; f=0; }
if (w == 2) { c=1; f=1; b=0; a=0; }
if (w == 3) { d=1; a=1; c=0; b=0; }
if (w == 4) { e=1; b=1; d=0; c=0; }
if (w == 5) { f=1; c=1; e=0; e=0; }
}
if (sceneNumber == 105) { //5 светят
if (w == 0) { a=0; f=1; b=1; }
if (w == 1) { b=0; a=1; c=1; }
if (w == 2) { c=0; b=1; d=1; }
if (w == 3) { d=0; c=1; e=1; }
if (w == 4) { e=0; d=1; f=1; }
if (w == 5) { f=0; e=1; a=1; }
}
}
boolean pressed=0;
byte pres=0;
boolean flagPressed=0;
boolean flagDisPlay=1;
byte regimDisPlay=0;
int ledState = LOW; // этой переменной устанавливаем состояние светодиода
long previousMillis = 0; // храним время последнего переключения светодиода
void loop() {
boolean button_pressed = digitalRead(BUTTON);
if (button_pressed == 0 && flagPressed == 0) { pressed=1; pres++; flagPressed=1; }
if (button_pressed == 1) flagPressed=0;
if (pres > 4) pres=0;
if (pressed == 1) {
if (pres == 1) {
sceneNumber += enc;
enc=0;
flagDisPlay=1;
regimDisPlay=1;
if (sceneNumber >= 106) sceneNumber=105;
if (sceneNumber <= 100) sceneNumber=101; }
if (pres == 2) {
effectNumber += enc;
enc=0;
flagDisPlay=1;
regimDisPlay=2;
if (effectNumber >= 103) effectNumber=102;
if (effectNumber <= 100) effectNumber=101; }
if (pres == 3) {
powerState += enc;
enc=0;
flagDisPlay=1;
regimDisPlay=4;
if (powerState >= 255) powerState=255;
if (powerState <= 0) powerState=0; }
if (pres == 4) { pressed=0; pres=0; }
}
if (enc !=0 && pressed == 0) {
speedNumber += enc;
enc=0;
flagDisPlay=1;
regimDisPlay=3;
if (speedNumber <= maxSpeedDelay) speedNumber = maxSpeedDelay;
if (speedNumber >= minSpeedDelay) speedNumber = minSpeedDelay;
}
if (flagDisPlay!=0) DisPlay(); // дисплей запускаем
// СОБСТВЕННО ТЕЛО ГЕНЕРАТОРА
unsigned long currentMillis = millis();
// ПРОВЕРЯЕМ НЕ ПРОШЕЛ ЛИ НУЖНЫЙ ИНТЕРВАЛ, ЕСЛИ ПРОШЕЛ ТО
if (currentMillis - previousMillis > speedNumber) {
// СОХРАНЯЕМ ВРЕМЯ ПОСЛЕДНЕГО ПЕРЕКЛЮЧЕНИЯ
previousMillis = currentMillis;
// ЕСЛИ СВЕТОДИОД НЕ ГОРИТ, ТО ЗАЖИГАЕМ, И НАОБОРОТ
ledState = !ledState;
// УСТАНАВЛИВАЕМ СОСТОЯНИЯ ВЫХОДА, ЧТОБЫ ВКЛЮЧИТЬ ИЛИ ВЫКЛЮЧИТЬ СВЕТОДИОД
if (ledState == 1) { // ход с лева на право
if (effectNumber == 101) {
w++;
if (w > 5) w=0; }
if (effectNumber == 102) { // ход справа налево
w--;
if (w > 250) w=5; }}
LightMode();
WITHDRAWAI();
}
}
void DisPlay(){
// ********** ГЛАВНОЕ МЕНЮ **********
if (regimDisPlay == 0 || regimDisPlay == 1) {
lcd.setCursor(1,0);
lcd.print("SCENE:");
lcd.setCursor(9,0);
if (sceneNumber == 101) lcd.print("lone runner");
if (sceneNumber == 102) lcd.print("two runner");
if (sceneNumber == 103) lcd.print("three runn");
if (sceneNumber == 104) lcd.print("four runner");
if (sceneNumber == 105) lcd.print("five runner");
//lcd.setCursor(15,0);
//lcd.print("SMART");
if (regimDisPlay != 0) regimDisPlay=5; }
if (regimDisPlay == 0 || regimDisPlay == 2) {
lcd.setCursor(1,1);
lcd.print("EFFECT:");
lcd.setCursor(9,1);
if (effectNumber == 101) lcd.print("run LEFT");
if (effectNumber == 102) lcd.print("run RIGHT");
//lcd.setCursor(15,1);
//lcd.print("DMX");
if (regimDisPlay != 0) regimDisPlay=5; }
if (regimDisPlay == 0 || regimDisPlay == 3) {
lcd.setCursor(1,2);
lcd.print("SPEED:");
lcd.setCursor(9,2);
lcd.print(speedNumber);
if (speedNumber < 100) {
lcd.setCursor(11,2);
lcd.print(" "); }
//lcd.setCursor(15,2);
//lcd.print("LASER");
if (regimDisPlay != 0) regimDisPlay=5;
}
if (regimDisPlay == 0 || regimDisPlay == 4) {
lcd.setCursor(1,3);
lcd.print("POWER:");
lcd.setCursor(9,3);
lcd.print(powerState);
if (powerState < 100) {
lcd.setCursor(11,3);
lcd.print(" "); }
if (powerState < 10) {
lcd.setCursor(10,3);
lcd.print(" "); }
//lcd.setCursor(15,3);
//lcd.print("SETUP");
regimDisPlay=5;
}
flagDisPlay=0;
}
boolean flagA=0, flagB=0, flagC=0, flagD=0, flagE=0, flagF=0;
void WITHDRAWAI() {
if (a!=1 && flagA==0) { analogWrite(LedA, 0); flagA=1; }
if (a==1 && flagA==1) { analogWrite(LedA, powerState); flagA=0; }
if (b!=1 && flagB==0) { analogWrite(LedB, 0); flagB=1; }
if (b==1 && flagB==1) { analogWrite(LedB, powerState); flagB=0; }
if (c!=1 && flagC==0) { analogWrite(LedC, 0); flagC=1; }
if (c==1 && flagC==1) { analogWrite(LedC, powerState); flagC=0; }
if (d!=1 && flagD==0) { analogWrite(LedD, 0); flagD=1; }
if (d==1 && flagD==1) { analogWrite(LedD, powerState); flagD=0; }
if (e!=1 && flagE==0) { analogWrite(LedE, 0); flagE=1; }
if (e==1 && flagE==1) { analogWrite(LedE, powerState); flagE=0; }
if (f!=1 && flagF==0) { analogWrite(LedF, 0); flagF=1; }
if (f==1 && flagF==1) { analogWrite(LedF, powerState); flagF=0; }
}
ну, во-первых, у меня на каждом канале больше полутора киловатт (для надёжности поставил симистор тыц, схемка и плата ниже), а во-вторых - это вообще не из той оперы, намного ближе это и это - но пока оно у меня в голове ещё не уложилось, чтобы понять и допилить. Короче, эта микруха - совсем не то...
не нравится меню дисплея, как то надо по другому все организовывать, выходов мало зрительно надо переходить на PCA9685 до 16 ШИМ
валяется платка уже 2-й месяц, перепробовал кучу либ - ничего толком не работает на 32-битной архитектуре (либо руки кривые и не доразобрался). Но мне как раз 12 ШИМ каналов хватает у Due. Попытаюсь позже вернуться к этой теме...
mrtester пишет:
есть идеи по меню пиши
Есть. Уже реализовал, правда, юзаю либу стороннюю для LCD, в ней можно вместо каждый раз СетКусор устанавливать позиции как lcd.printAt (posX, posY, #val). Удобнейшая штука. Сама либа разрабом сделана как либа для lcd, энкодера и меню, но функционалом меню в ней нереально пользоваться, т.к. она сама является бесконечным циклом и вешает всё остальное. Ну, как вариант, выделить какую-нибудь нанку специально для менюхи - меню там шикарное и задокументированное. Если бы ещё всё было без тормозов, на прерываниях - цены бы ей не было. Сцыль в спойлере.
// библиотека с менюхой тут: https://tsibrov.blogspot.com/2018/10/LiquidCrystalI2CExt.html
// РИСУЕМ ГЛАВНЫЙ ЭКРАН =======================================
void mainScreen() {
// ********** ГЛАВНОЕ МЕНЮ **********
lcd1.printAt(1, 0, "SCENE:");
lcd1.printAt(9, 0, sceneNumber);
lcd1.printAt(1, 1, "EFFECT:");
lcd1.printAt(9, 1, effectNumber);
lcd1.printAt(1, 2, "SPEED:");
lcd1.printAt(9, 2, speedNumber);
lcd1.printAt(1, 3, "POWER:");
lcd1.printAt(9, 3, powerState);
lcd1.printAt(15, 0, "SMART");
if (smartState == 0) lcd1.printAt(14, 0, "\xDB");
if (smartState == 1) lcd1.printAt(14, 0, "\xFF");
lcd1.printAt(15, 1, "DMX");
if (dmxState == 0) lcd1.printAt(14, 1, "\xDB");
if (dmxState == 1) lcd1.printAt(14, 1, "\xFF");
lcd1.printAt(15, 2, "LASER");
if (laserState == 0) lcd1.printAt(14, 2, "\xDB");
if (laserState == 1) lcd1.printAt(14, 2, "\xFF");
lcd1.printAt(15, 3, "SETUP");
}
// ЕСЛИ ЧТО В МЕНЮХЕ ВЫБРАНО - НЫРЯЕМ В СООТВЕТСТВУЮЩУЮ ФУНКЦИЮ (СЕТАП, ЛАЗЕР, СМАРТ И DMX ПОКА НЕ ГОТОВЫ).
// ЕСЛИ НИЧЕГО НЕ ВЫБРАНО (mainMenuState == 9) - НЫРЯЕМ В ФУНКЦИЮ ВЫБОРА mainMenuSelector()
if (mainMenuState == 9) mainMenuSelector();
else if (mainMenuState == 0) sceneSelector();
else if (mainMenuState == 1) effectSelector();
else if (mainMenuState == 2) speedSelector();
else if (mainMenuState == 3) powerSelector();
else if (mainMenuState == 4) smartSubmenu();
else if (mainMenuState == 5) dmxSubmenu();
else if (mainMenuState == 6) laserSubmenu();
else if (mainMenuState == 7) setupSubmenu();
// ОБРАБОТЧИК ВЫБОРА ГЛАВНОГО МЕНЮ ==============================
void mainMenuSelector() {
signed char mm = encoder.getPosition();
if (mm != 0) {
mainMenuPosition = mainMenuPosition + mm;
mainMenuPosition = constrain(mainMenuPosition, 0, 7);
// ЗАЧИЩАЕМ СТАРЫЕ СТРЕЛКИ СЛЕВА
for (byte i = 0; i <= 3; i++) lcd1.printAt(0, i, " ");
for (byte i = 0; i <= 3; i++) lcd1.printAt(13, i, " ");
encoder.reset();
}
// СТРЕЛКА НА ЛЕВЫЙ УКАЗАТЕЛЬ МЕНЮХИ
if (mainMenuPosition == 0) lcd1.printAt(0, 0, "\x7E");
else if (mainMenuPosition == 1) lcd1.printAt(0, 1, "\x7E");
else if (mainMenuPosition == 2) lcd1.printAt(0, 2, "\x7E");
else if (mainMenuPosition == 3) lcd1.printAt(0, 3, "\x7E");
else if (mainMenuPosition == 4) lcd1.printAt(13, 0, "\x7E");
else if (mainMenuPosition == 5) lcd1.printAt(13, 1, "\x7E");
else if (mainMenuPosition == 6) lcd1.printAt(13, 2, "\x7E");
else if (mainMenuPosition == 7) lcd1.printAt(13, 3, "\x7E");
// ЗАКРЕПЛЯЕМ ВЫБРАННЫЙ ПУНКТ МЕНЮ
if (digitalRead(ENCODER_SW) == LOW) {
mainMenuState = mainMenuPosition;
// СТРЕЛКА НА ПРАВЫЙ УКАЗАТЕЛЬ МЕНЮХИ (ЧТО ВЫБРАНО)
if (mainMenuState == 0) lcd1.printAt(8, 0, "\x7E");
else if (mainMenuState == 1) lcd1.printAt(8, 1, "\x7E");
else if (mainMenuState == 2) lcd1.printAt(8, 2, "\x7E");
else if (mainMenuState == 3) lcd1.printAt(8, 3, "\x7E");
delay(debounce);
}
}
// ВЫБОР ЯРКОСТИ =====================================
void powerSelector() {
lcd1.printAt(8, 0, " ");
lcd1.printAt(8, 1, " ");
lcd1.printAt(8, 2, " ");
lcd1.printAt(8, mainMenuState, "\x7E");
signed char pwr = encoder.getPosition();
if (pwr != 0) {
if (digitalRead(ENCODER_SHIFT) == HIGH) powerState = powerState + pwr;
if (digitalRead(ENCODER_SHIFT) == LOW) powerState = powerState + 10*pwr;
powerState = constrain(powerState, minPowerState, maxPowerState);
powerPWM = map(powerState, 1, 99, powerPWMmin, powerPWMmax);
lcd1.printAt(9, 3, powerState);
if (powerState < 10) {
lcd1.printAt(10, mainMenuState, " ");
}lcd2.printAt(2,0," ");
lcd2.printAt(0,0,powerPWM);
encoder.reset();
}
if (digitalRead(ENCODER_SW) == LOW) {
delay(debounce);
lcd1.printAt(8, mainMenuState, " ");
mainMenuState = 9; // ВЫВАЛИВАЕМСЯ ИЗ ФУНКЦИИ
}
}
// ОБРАБОТЧИК ВЫБОРА ЭФФЕКТА
void effectSelector() {
lcd1.printAt(8, 0, " ");
lcd1.printAt(8, 2, " ");
lcd1.printAt(8, 3, " ");
lcd1.printAt(8, mainMenuState, "\x7E");
signed char eff = encoder.getPosition();
if (eff != 0) {
effectNumber = effectNumber + eff;
effectNumber = constrain(effectNumber, minEffectNumber, maxEffectNumber);
if (effectNumber < 10) {
lcd1.printAt(9, mainMenuState, " ");
}
lcd1.printAt(9, 1, effectNumber);
encoder.reset();
}
if (digitalRead(ENCODER_SW) == LOW) {
delay(debounce);
lcd1.printAt(8, mainMenuState, " ");
mainMenuState = 9;
}
}
// ОБРАБОТЧИК РЕГУЛИРОВКИ СКОРОСТИ
void speedSelector() {
lcd1.printAt(8, 0, " ");
lcd1.printAt(8, 1, " ");
lcd1.printAt(8, 3, " ");
lcd1.printAt(8, mainMenuState, "\x7E");
signed char spd = encoder.getPosition();
if (spd != 0) {
if (digitalRead(ENCODER_SHIFT) == HIGH) speedNumber = speedNumber + spd; // ПРИ НЕНАЖАТОМ ШИФТЕ СКАЧЕМ ПО 1
else if (digitalRead(ENCODER_SHIFT) == LOW) speedNumber = speedNumber + 10*spd; // ПРИ НАЖАТИИ ШИФТА СКАЧЕМ ПО 10
speedNumber = constrain(speedNumber, minSpeedNumber, maxSpeedNumber);
lcd1.printAt(9, 2, speedNumber);
if (speedNumber < 100) lcd1.printAt(11, mainMenuState, " "); // ЗАТИРКА ДЛЯ ЗНАЧЕНИЙ СКОРОСТИ БОЛЕЕ 100
if (speedNumber < 10) lcd1.printAt(10, mainMenuState, " ");// ДЛЯ ЗНАЧЕНИЙ СКОРОСТИ БОЛЕЕ 10
encoder.reset();
}
if (digitalRead(ENCODER_SW) == LOW) {
delay(debounce);
lcd1.printAt(8, mainMenuState, " ");
mainMenuState = 9;
}
}
Код в скетче разнёс по табам, так что, возможно, собрал в кучу криво, но идея должна быть понятна. Фотку, как оно (моё меню) выглядит, выкладывал выше. Вот она:
Как оно работает? Энкодером гоняем стрелку (слева от надписей) по 8 пунктам меню (в функции mainMenuSelector() меняем значение mainMenuPosition). В 1-4 пунктах меню есть ещё и значения, остальные - вкл/выкл. Поэтому когда кнопкой энкодера выбираем, например, EFFECT, то слева от нолика появляется ещё одна стрелка, которая показывает активный (выбранный) пункт меню(то есть значению mainMenuState мы присваиваем значение из mainMenuPosition). Как только произошла эта защёлка - ныряем в функцию выбора эффекта effectSelector() (if (mainMenuState == 1) effectSelector();) Крутим энкодер - меняется значение выбранного пункта. Клик по кнопке энкодера - выходим из редактирования, опять в mainMenuSelector(), "запомнив" при этом введённое значение.
Ну, ещё раз повторюсь: я не программер, только пытаюсь вникнуть. Возможно, что-то можно реализовать намного проще, но я делаю на уровне интуиции, не влезая в дебри: туда пока рановато...
Спустя 8 месяцев вернулся к данной бегушке, вот на свежую голову переписал (без ШИМа, для упрощения). Всего 10 каналов, энкодер, LCD2004, 5 кнопок быстрого вызова программ. Arduino Pro Micro.
На дисплее три строки меню: Скорость, Эффект и Настройки (последнюю здесь убрал для упрощения кода). При неактивных (невыбранных) пунктах меню в 1-й колонке энкодером гоняется "левая" стрелка (voidleftArrow) вверх-вниз. Нужный пункт меню активируется при нажатии кнопки энкодера, при этом на соответствующей строке в колонке 8 появляется ещё одна стрелка (voidrightArrow), указывающая, что данный пункт меню активен. После "накрутки" энкодером нужного значения выходим в корень нажатием кнопки энкодера, правая стрелка затирается.
В дальнейшем планирую программы перенести в EEPROM и сделать возможность юзеру пошагово создавать свои пользовательские программы. Поэтому прошу конструктивной критики: что нужно доработать и что можно упростить? Постарался закомментить максимально.
#include <Wire.h>
#include <LiquidCrystal_I2C_Menu.h> // ЛИБА ДИСПЛЕЯ, ПОЗВОЛЯЕТ ОБХОДИТЬСЯ БЕЗ lcd.setCursor
LiquidCrystal_I2C_Menu lcd(0x27, 20, 4);
#include <EncoderStepCounter.h> // ЛИБА ЭНКОДЕРА
// ===== ARDUINO PRO MICRO =====
// ====== ПИНЫ НА МОСФЕТЫ ======
#define CH01 19
#define CH02 18
#define CH03 15
#define CH04 14
#define CH05 8
#define CH06 7
#define CH07 6
#define CH08 5
#define CH09 10
#define CH10 9
// ====== ЭНКОДЕР ======
#define pinCLK 1
#define pinDT 0
#define pinSW 4
#define ENCODER_INT1 digitalPinToInterrupt(pinCLK)
#define ENCODER_INT2 digitalPinToInterrupt(pinDT)
EncoderStepCounter encoder(pinCLK, pinDT);
// АНАЛОГОВЫЙ ВХОД НА 5 КНОПОК БЫСТРОГО ВЫЗОВА ПРОГРАММ
#define butPIN 21
// ПЕРЕМЕННЫЕ ДЛЯ ЗАДАЮЩЕГО ГЕНЕРАТОРА
int ledState = LOW;
long prevMillis = 0;
// ПЕРЕМЕННЫЕ ДЛЯ ГЛАВНОГО МЕНЮ
// СТАРТОВЫЕ ЗНАЧЕНИЯ ЭЛЕМЕНТОВ МЕНЮ
byte speedNumber = 70; // НАЧАЛЬНОЕ ЗНАЧЕНИЕ СКОРОСТИ (ПРОСТО ЦИФРЫ НА ЭКРАНЕ ОТ 0 ДО 99)
long speedDelay; // ЗНАЧЕНИЕ ТЕКУЩЕЙ ЗАДЕРЖКИ (В МИЛЛИСЕКУНДАХ)
long minSpeedDelay = 350; // МИНИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ (СООТВЕТСТВУЕТ 1-Й "СКОРОСТИ", В МИЛЛИСЕКУНДАХ)
long maxSpeedDelay = 15; // МАКСИМАЛЬНОЕ ЗНАЧЕНИЕ ЗАДЕРЖКИ (СООТВЕТСТВУЕТ 99-Й "СКОРОСТИ", В МИЛЛИСЕКУНДАХ)
byte mainMenuState = 0; // НАЧАЛЬНОЕ СОСТОЯНИЕ МЕНЮ (0 - КОГДА НЕ АКТИВЕН (НЕ ВЫБРАН) НИ ОДИН ПУНКТ МЕНЮ)
byte mainMenuPos = 1; // НАЧАЛЬНОЕ ПОЛОЖЕНИЕ МЕНЮ (ЛЕВОЙ СТРЕЛКИ-УКАЗАТЕЛЯ)
byte effectNumber = 1; // НАЧАЛЬНЫЙ НОМЕР ЭФФЕКТА
void setup() {
// ====== ЭНКОДЕР ======
attachInterrupt(ENCODER_INT1, interrupt, CHANGE);
attachInterrupt(ENCODER_INT2, interrupt, CHANGE);
Wire.begin();
lcd.begin();
// ====== ВЫХОДЫ НА МОСФЕТЫ ======
byte CHANNEL[10] = {CH01,CH02,CH03,CH04,CH05,CH06,CH07,CH08,CH09,CH10};
for(byte i = 0; i < 10; i++) {
pinMode(CHANNEL[i], OUTPUT);
digitalWrite(CHANNEL[i], HIGH); // СВЕТОДИОДЫ ПОДКЛЮЧЕНЫ С ОБЩИМ "ПЛЮСОМ", ТУШИМ ПРИ ЗАГРУЗКЕ
}
// АНАЛОГОВЫЙ ВХОД ДЛЯ 5 КНОПОК БЫСТРОГО ВЫЗОВА ЭФФЕКТОВ
pinMode(butPIN, INPUT_PULLUP);
// ====== ЭНКОДЕР ======
pinMode(pinCLK, INPUT_PULLUP);
pinMode(pinDT, INPUT_PULLUP);
pinMode(pinSW, INPUT_PULLUP);
// ОТРИСОВКА ГЛАВНОГО МЕНЮ
GUImainMenu();
}
void interrupt() {
encoder.tick();
}
void loop() {
encoder.tick();
// ЗАПУСКАЕМ ЗАДАЮЩИЙ ГЕНЕРАТОР
generator();
// ОБРАБАТЫВАЕМ КНОПКИ БЫСТРОГО ПЕРЕКЛЮЧЕНИЯ ЭФФЕКТОВ
buttonHandler();
// ======== ПЕРЕКЛЮЧАТЕЛЬ ПУНКТОВ МЕНЮ =========
switch(mainMenuState) {
case 0:
mainMenuHandler(); // КОРЕНЬ МЕНЮ
case 1:
speedHandler(); // ВЫБОР СКОРОСТИ
case 2:
effectHandler(); // ВЫБОР ЭФФЕКТА
case 3:
GUIsetupMenu(); // НЫРЯЕМ ВО 2-Й ЭКРАН (НАСТРОЙКИ, ПОКА НЕ ПОДКЛЮЧЕНЫ)
setupMenu();
}
// ======== ПЕРЕКЛЮЧАТЕЛЬ ЭФФЕКТОВ =========
switch (effectNumber) {
case 1:
lcd.printAt(9,2,effectNumber);
effect1Loop();
break;
case 2:
lcd.printAt(9,2,effectNumber);
effect2Loop();
break;
case 3:
lcd.printAt(9,2,effectNumber);
effect3Loop();
break;
case 4:
lcd.printAt(9,2,effectNumber);
effect4Loop();
break;
case 5:
lcd.printAt(9,2,effectNumber);
effect5Loop();
break;
}
}
// КОНЕЦ ЛУПА
// ============ ОТРИСОВКА ГЛАВНОГО МЕНЮ
void GUImainMenu() {
lcd.printAt(3,0, "УПРАВЛЕНИЕ СВЕТОМ"); // ЗАГОЛОВОК ЭКРАНА
leftArrow();
lcd.printAt(1,1, "Скор.:");
lcd.printAt(9,1, speedNumber);
lcd.printAt(1,2, "Эффект:");
lcd.printAt(9,2, effectNumber);
lcd.printAt(1,3, "SETUP");
}
void generator() {
// МАПИМ ЗНАЧЕНИЯ СКОРОСТИ В ЗНАЧЕНИЕ ЗАДЕРЖКИ И ОГРАНИЧИВАЕМ
speedNumber = constrain(speedNumber, 0, 99);
speedDelay = map(speedNumber, 0, 99, minSpeedDelay, maxSpeedDelay);
// СОБСТВЕННО ТЕЛО ГЕНЕРАТОРА
unsigned long currMillis = millis();
// ПРОВЕРЯЕМ НЕ ПРОШЕЛ ЛИ НУЖНЫЙ ИНТЕРВАЛ, ЕСЛИ ПРОШЕЛ ТО
if (currMillis - prevMillis > speedDelay) {
// СОХРАНЯЕМ ВРЕМЯ ПОСЛЕДНЕГО ПЕРЕКЛЮЧЕНИЯ
prevMillis = currMillis;
// ЕСЛИ СВЕТОДИОД НЕ ГОРИТ, ТО ЗАЖИГАЕМ, И НАОБОРОТ
if (ledState == LOW) {
ledState = !ledState;
}
else {
ledState = !ledState;
}
}
}
// ============ ОБРАБОТЧИК ГЛАВНОГО МЕНЮ
void mainMenuHandler() {
signed char m = encoder.getPosition();
if (m != 0) {
mainMenuPos = mainMenuPos + m; // КРУТИМ ЗНАЧЕНИЕ ЭНКОДЕРА
mainMenuPos = constrain(mainMenuPos, 1, 3); // ОГРАНИЧИВАЕМ ТРЕМЯ ПУНКТАМИ МЕНЮ
leftArrow(); // РИСУЕМ И ЗАТИРАЕМ ЛЕВЫЕ СТРЕЛКИ-УКАЗАТЕЛИ
encoder.reset();
}
if(digitalRead(pinSW) == 0) {
mainMenuState = mainMenuPos; // ПРИ НАЖАТИИ КНОПКИ ЭНКОДЕРА АКТИВИРУЕМ ВЫБРАННЫЙ ПУНКТ МЕНЮ
rightArrow(); // РИСУЕМ / ЗАТИРАЕМ ПРАВЫЕ СТРЕЛКИ-УКАЗАТЕЛИ
delay(300); // НЕКРИТИЧНО В ДАННОМ СЛУЧАЕ
/* ЕСЛИ ЖМЁМ КНОПКУ ЭНКОДЕРА ДЛЯ ВЫХОДА, ТО ПРИ ЭТОМ ПОПАДАЕМ В ГЛАВНОЕ МЕНЮ, А В НЁМ ТОЖЕ
* ОПРАШИВАЕТСЯ КНОПКА ЭНКОДЕРА. И СОСТОЯНИЕ ПЕРЕМЕННОЙ mainMenuPos БУДЕТ НЕПРЕДСКАЗУЕМЫМ.
* ПОЭТОМУ ОСТАНОВИЛСЯ НА delay(300). КАК СДЕЛАТЬ ЛУЧШЕ? ПРЕДЛОЖИТЕ
*/
}
}
// ============ ОБРАБОТЧИК СКОРОСТИ
void speedHandler() {
signed char m = encoder.getPosition();
if (m != 0) {
speedNumber = speedNumber + m;
speedNumber = constrain(speedNumber, 1, 99);
lcd.printAt(9,1, speedNumber);
encoder.reset();
}
if((digitalRead(pinSW) == 0) && mainMenuState == 1) { // ЕСЛИ АКТИВЕН ВЫБОР СКОРОСТИ И НАЖАТА КНОПКА ЭНКОДЕРА
lcd.printAt(8,mainMenuState," "); // ВЫТИРАЕМ ПРАВУЮ СТРЕЛКУ-УКАЗАТЕЛЬ ВЫБРАННОСТИ ПУНКТА МЕНЮ
mainMenuState = 0; // И ВЫХОДИМ В КОРЕНЬ
delay(300);
}
}
// ============ ОБРАБОТЧИК ЭФФЕКТОВ
void effectHandler() {
signed char m = encoder.getPosition();
if (m != 0) {
effectNumber = effectNumber + m;
effectNumber = constrain(effectNumber, 1, effectCount);
leftArrow();
encoder.reset();
}
if((digitalRead(pinSW) == 0) && mainMenuState == 2) {
lcd.printAt(8,mainMenuState," ");
mainMenuState = 0;
delay(300);
}
}
// ============ ОТРИСОВКА ЛЕВОЙ СТРЕЛКИ
void leftArrow() {
for(byte i=1;i<=3;i++) lcd.printAt(0,i," "); // ПАЧКОЙ ЗАТИРАЕМ
lcd.printAt(0,mainMenuPos, ">"); РИСУЕМ
}
// ============ ОТРИСОВКА ПРАВОЙ СТРЕЛКИ
void rightArrow() {
for(byte i=1;i<=3;i++) lcd.printAt(8,i," ");
lcd.printAt(8,mainMenuState, ">");
}
// ============ ОБРАБОТКА КНОПОК НА АНАЛОГОВОМ ВХОДЕ
void buttonHandler() {
int e = (analogRead(butPIN));
if(e > 300 && e < 370) effectNumber = 1;
if(e > 420 && e < 490) effectNumber = 2;
if(e > 540 && e < 600) effectNumber = 3;
if(e > 900 && e < 960) effectNumber = 4;
if(e > 1000) effectNumber = 5;
}
// ============ ОТРИСОВКА МЕНЮ НАСТРОЕК
void GUIsetupMenu() {
}
// ============ ОБРАБОТКА НАСТРОЕК
void setupMenu() {
}
// =============================================
// НЕПОСРЕДСТВЕННО САМИ ЭФФЕКТЫ ДЛЯ ДЕМОНСТРАЦИИ
bool flag = 0;
int x = 0; // КООРДИНАТА Х В МАССИВЕ effect_Х_Loop
int y = 0; // КООРДИНАТА Y В МАССИВЕ effect_Y_Loop
byte CHANNEL[10] = {CH01,CH02,CH03,CH04,CH05,CH06,CH07,CH08,CH09,CH10};
void effect1Loop() {
byte steps = 25;
boolean effect1[steps][10]{
{1,0,0,0,0,0,0,0,0,0}, // ШАГ 1
{1,1,0,0,0,0,0,0,0,0}, // ШАГ 2
{1,1,1,0,0,0,0,0,0,0}, // ШАГ 3
{1,1,1,1,0,0,0,0,0,0}, // ШАГ 4
{1,1,1,1,1,0,0,0,0,0}, // ШАГ 5
{1,1,1,1,1,1,0,0,0,0}, // ШАГ 6
{1,1,1,1,1,1,1,0,0,0}, // ШАГ 7
{1,1,1,1,1,1,1,1,0,0}, // ШАГ 8
{1,1,1,1,1,1,1,1,1,0}, // ШАГ 9
{1,1,1,1,1,1,1,1,1,1}, // ШАГ 10
{1,1,1,1,1,1,1,1,1,1}, // ШАГ 11
{1,1,1,1,1,1,1,1,1,1}, // ШАГ 12
{1,1,1,1,1,1,1,1,1,1}, // ШАГ 13
{1,1,1,1,1,1,1,1,1,1}, // ШАГ 14
{1,1,1,1,1,1,1,1,1,1}, // ШАГ 15
{0,1,1,1,1,1,1,1,1,1}, // ШАГ 16
{0,0,1,1,1,1,1,1,1,1}, // ШАГ 17
{0,0,0,1,1,1,1,1,1,1}, // ШАГ 18
{0,0,0,0,1,1,1,1,1,1}, // ШАГ 19
{0,0,0,0,0,1,1,1,1,1}, // ШАГ 20
{0,0,0,0,0,0,1,1,1,1}, // ШАГ 21
{0,0,0,0,0,0,0,1,1,1}, // ШАГ 22
{0,0,0,0,0,0,0,0,1,1}, // ШАГ 23
{0,0,0,0,0,0,0,0,0,1}, // ШАГ 24
{0,0,0,0,0,0,0,0,0,0} // ШАГ 25
};
if (ledState == 1 && flag == false) {
for (x = 0; x <= 9; x++) {
byte k = effect1[y][x];
digitalWrite(CHANNEL[x],!k);
}
y++;
if (y > (steps - 1)) y = 0;
flag = true;
}
if (ledState == 0 && flag == true) flag = false;
}
void effect2Loop() {
byte steps = 10;
boolean effect2[steps][10]{
{1,0,0,0,0,0,0,0,0,0}, // ШАГ 1
{0,1,0,0,0,0,0,0,0,0}, // ШАГ 2
{0,0,1,0,0,0,0,0,0,0}, // ШАГ 3
{0,0,0,1,0,0,0,0,0,0}, // ШАГ 4
{0,0,0,0,1,0,0,0,0,0}, // ШАГ 5
{0,0,0,0,0,1,0,0,0,0}, // ШАГ 6
{0,0,0,0,0,0,1,0,0,0}, // ШАГ 7
{0,0,0,0,0,0,0,1,0,0}, // ШАГ 8
{0,0,0,0,0,0,0,0,1,0}, // ШАГ 9
{0,0,0,0,0,0,0,0,0,1} // ШАГ 10
};
if (ledState == 1 && flag == false) {
for (x = 0; x <= 9; x++) {
byte k = effect2[y][x];
digitalWrite(CHANNEL[x],!k);
}
y++;
if (y > (steps - 1)) y = 0;
flag = true;
}
if (ledState == 0 && flag == true) flag = false;
}
void effect3Loop() {
byte steps = 10;
boolean effect3[steps][10]{
{1,0,0,0,0,0,0,0,0,0}, // ШАГ 1
{0,1,0,0,0,0,0,0,0,0}, // ШАГ 2
{0,0,1,0,0,0,0,0,0,0}, // ШАГ 3
{0,0,0,1,0,0,0,0,0,0}, // ШАГ 4
{0,0,0,0,1,0,0,0,0,0}, // ШАГ 5
{0,0,0,0,0,1,0,0,0,0}, // ШАГ 6
{0,0,0,0,0,0,1,0,0,0}, // ШАГ 7
{0,0,0,0,0,0,0,1,0,0}, // ШАГ 8
{0,0,0,0,0,0,0,0,1,0}, // ШАГ 9
{0,0,0,0,0,0,0,0,0,1} // ШАГ 10
};
if (ledState == 1 && flag == false) {
for (x = 0; x <= 9; x++) {
byte k = effect3[y][x];
digitalWrite(CHANNEL[x],k);
}
y++;
if (y > (steps - 1)) y = 0;
flag = true;
}
if (ledState == 0 && flag == true) flag = false;
}
void effect4Loop() {
byte steps = 18;
boolean effect4[steps][10]{
{1,0,0,0,0,0,0,0,0,0}, // ШАГ 1
{0,1,0,0,0,0,0,0,0,0}, // ШАГ 2
{0,0,1,0,0,0,0,0,0,0}, // ШАГ 3
{0,0,0,1,0,0,0,0,0,0}, // ШАГ 4
{0,0,0,0,1,0,0,0,0,0}, // ШАГ 5
{0,0,0,0,0,1,0,0,0,0}, // ШАГ 6
{0,0,0,0,0,0,1,0,0,0}, // ШАГ 7
{0,0,0,0,0,0,0,1,0,0}, // ШАГ 8
{0,0,0,0,0,0,0,0,1,0}, // ШАГ 9
{0,0,0,0,0,0,0,0,0,1}, // ШАГ 10
{0,0,0,0,0,0,0,0,1,0}, // ШАГ 11
{0,0,0,0,0,0,0,1,0,0}, // ШАГ 12
{0,0,0,0,0,0,1,0,0,0}, // ШАГ 13
{0,0,0,0,0,1,0,0,0,0}, // ШАГ 14
{0,0,0,0,1,0,0,0,0,0}, // ШАГ 15
{0,0,0,1,0,0,0,0,0,0}, // ШАГ 16
{0,0,1,0,0,0,0,0,0,0}, // ШАГ 17
{0,1,0,0,0,0,0,0,0,0} // ШАГ 18
};
if (ledState == 1 && flag == false) {
for (x = 0; x <= 9; x++) {
byte k = effect4[y][x];
digitalWrite(CHANNEL[x],!k);
}
y++;
if (y > (steps - 1)) y = 0;
flag = true;
}
if (ledState == 0 && flag == true) flag = false;
}
void effect5Loop() {
byte steps = 18;
boolean effect5[steps][10]{
{1,0,0,0,0,0,0,0,0,0}, // ШАГ 1
{0,1,0,0,0,0,0,0,0,0}, // ШАГ 2
{0,0,1,0,0,0,0,0,0,0}, // ШАГ 3
{0,0,0,1,0,0,0,0,0,0}, // ШАГ 4
{0,0,0,0,1,0,0,0,0,0}, // ШАГ 5
{0,0,0,0,0,1,0,0,0,0}, // ШАГ 6
{0,0,0,0,0,0,1,0,0,0}, // ШАГ 7
{0,0,0,0,0,0,0,1,0,0}, // ШАГ 8
{0,0,0,0,0,0,0,0,1,0}, // ШАГ 9
{0,0,0,0,0,0,0,0,0,1}, // ШАГ 10
{0,0,0,0,0,0,0,0,1,0}, // ШАГ 11
{0,0,0,0,0,0,0,1,0,0}, // ШАГ 12
{0,0,0,0,0,0,1,0,0,0}, // ШАГ 13
{0,0,0,0,0,1,0,0,0,0}, // ШАГ 14
{0,0,0,0,1,0,0,0,0,0}, // ШАГ 15
{0,0,0,1,0,0,0,0,0,0}, // ШАГ 16
{0,0,1,0,0,0,0,0,0,0}, // ШАГ 17
{0,1,0,0,0,0,0,0,0,0} // ШАГ 18
};
if (ledState == 1 && flag == false) {
for (x = 0; x <= 9; x++) {
byte k = effect5[y][x];
digitalWrite(CHANNEL[x],k);
}
y++;
if (y > (steps - 1)) y = 0;
flag = true;
}
if (ledState == 0 && flag == true) flag = false;
}
byte n_step=0; // номер шага
dword led_state=B100000000000; // состояние светодиодов, макс. размер 32 шт
// если не понимает dword - пишем unsigned long
void effect5Loop_process() { // процесс потому что неблокирующий, можно вызывать из loop()
byte i; // для порядку
if(millis()-last_effect5<EFFECT_5_INTERVAL) return; // последнее время эффекта (unsig long), мс
// интервал в 1с/проход, мс - 10 раз в сек = 1000/10 = 100 мс
// если рано - выкинуть (return)
last_effect5=millis(); // запомнить новое время
if(n_step<=11) led_state>>=1; // до 12го шага (который от нуля, = 12-1 = 11) то двигать вправо
// >> ака SHR = сделать из 0100 0010, т е сдвиг вправо. из 4 делает 2
else led_state<<=1; // после 12го назад эйкейэй << a.k.a SHL
n_step++; // новый шаг
if(n_step>21) n_step=1; // если слишком новый то нормальный (restrict bounds)
for(i=0;i<12;i++) digitalWrite(i,(led_state>>i)&1); // вывод (например)
// почему >> на i раз - потому что мегаох_лиард число 32бита, из него
// надо сделать bool. поэтому выкинем все правые нули
// зачем ANDить (логически И-ть знаком &) - затем чтобы выяснить,
// является ли число 1. если да то жжём, иначе гасим
}
итд. Это во первых
Во вторых всё это, без экрана но с DMX, уместится в m328, DUE/Mega тут ни к чему.
В третьих, достаточно взять ShiftPWM и выдрать из неё все упоминания Serial (закомментировать или удалить) и она становится совместимой с DMX. С вашей большой меги с экраном можете ей слать DMX трафик. Бонусом и плавно, но частота модуляции низкая tbh.
Возможно, all просто предательски молчит, закидывается попкорном и ржёт, а я, крыса-кун, обламываю общественный кайф. Но держать в памяти светодиоды, да ещё и ради простого счётчика Джонсона - лютый моветон. У меги в 16 раз больше памяти, но не в сто.
Во вторых всё это, без экрана но с DMX, уместится в m328, DUE/Mega тут ни к чему.
8 месяцев назад, когда я только начинал сие, упор был на Дуйке:
1) потому что была под руками
2) потому что у неё было как раз нужных мне 12 каналов pwm
3) потому что на всю остальную периферию (а это большой светопульт) мне нужно ещё как минимум 20 каналов плюс куча кнопок (типа быстрого вызова, причём в большинстве случаев матричное подключение кнопок не прокатит, т.к. в результате будет паутина проводов по всему девайсу) и светодиодов к ним. Ну, предположим, 16 светодиодов по четырём линиям можно динамически светить с монстроподобным дешифратором К155ИД3 (благо в кладовке их ведро). Ну и pwm типа PCA9685 пришлось бы добавлять.
Последний вариант скетча был для Arduino Pro Micro, без DMX, на 10 каналов, из них (при необходимости, это уже будет другая версия прошивки) 3 штуки пойдёт на ШИМ (для RGB лент или диодов), 7 на бегушку,1 аналоговый - для уровня уличного освещения, и последний свободный пин - для smart-лент (такая вот штучка для рекламных нужд).
Voodoo Doll пишет:
У меги в 16 раз больше памяти, но не в сто.
Ну, поэтому я и хочу взять EEPROM внешнюю, даже 24с512. Копейки. Туда заколеблешься программы забивать на этот объём.
За рекомендации по коду спасибо, буду вникать разбираться. Пока что почти ничё не понятно...
Есличо, &1 в строке вывода по-идее не нужно (всё равно ничего не делает) но это только в случаях когда левее по числу нет ещё единиц.
Алсо, вместо digitalWrite() реще будет прямой записью в порт, у AVR порты это 8 выходов, как правило они находятся рядом, в т. ч. у плат Arduino. Крайне полезно бы почитать библиотеку CyberLib, она простая, состоит из одного файла всего. Там как раз замена всех этих небыстрых вводов-выводов под конкретный процессор.
Деталь, необходимая для вывода по SPI (shiftPWM) - сдвиговые регистры вывода, оно называется 74595. Эта микросхема дешёвая, особенно с буквой d (sot корпус 1.27 между ног).
Насчёт "много PWM" - это удочка на которую у меня ловился батя. Практичнее разбить PWM-нужды на группы по три, и на каждую поставить одну жалкую дешёвую atmega8 или 328, а ей кидать информацию по сериалу/модбусу/дмх. Передать сериал просто (трансиверы, дифференциальные линии). Передать PWM непросто (наводки, искажения, отражения). Под словом "передать" я имею ввиду на большое расстояние. "Большое расстояние" - это более двух метров.
Алсо, вместо digitalWrite() реще будет прямой записью в порт, у AVR порты это 8 выходов, как правило они находятся рядом, в т. ч. у плат Arduino
Я пытаюсь понять, чем digitalWrite вреден? Весь код придуман из головы на основании школьного курса информатики, пройденного мною в 1989 году (старенький я, мы ещё васик учили). Ардуинкой увлёкся на карантине в апреле, потом "завязал" на полгода (работа привалила). Посему "немного" отстал, навёрстываю. Поэтому и задаю такие глупые вопросы.
Voodoo Doll пишет:
dword led_state=B100000000000;
Кроме того, мне непонятно, почему использовать бульки моветон? Они же меньше памяти занимают.
Voodoo Doll пишет:
Насчёт "много PWM" - это удочка на которую у меня ловился батя. Практичнее разбить PWM-нужды на группы по три, и на каждую поставить одну жалкую дешёвую atmega8 или 328, а ей кидать информацию по сериалу/модбусу/дмх.
А зачем это в рамках корпуса 400х600мм (светопульт), где длина провода от контроллера до диммера не превышает 250мм? Тем более что перед передачей pwm можно усилить сигнал. Разумеется, на расстояния (большие) я бы тоже кинул dmx, но пока такой потребности нет.
можно сделать управление через переменный резистор, а можно через МК ШИМ
накидал код на uno
управление по умолчанию регулировает speed, нажал на кнопку настройки 1 строки, нажал еще раз настройки 2 строки, нажал еще 4 строка, нажал четвертый раз по умолчанию
не нравится меню дисплея, как то надо по другому все организовывать, выходов мало зрительно надо переходить на PCA9685 до 16 ШИМ
есть идеи по меню пиши
https://www.chipdip.ru/product/k1182pm1r
ну, во-первых, у меня на каждом канале больше полутора киловатт (для надёжности поставил симистор тыц, схемка и плата ниже), а во-вторых - это вообще не из той оперы, намного ближе это и это - но пока оно у меня в голове ещё не уложилось, чтобы понять и допилить. Короче, эта микруха - совсем не то...
не правильная схема найди на moc3010 описание и посмотри как надо
тогда тебе ШИМ не нужен в логике переключения , а сделать в синхронизации фазы
на микруху можно поставить симистор и выход будет = Р семистра
проще будет сделать весь остальной софт
не правильная схема найди на moc3010 описание и посмотри как надо
Схема у меня на МОС3041, просто не было её в библиотеке Игла, взял чтобы плату развести
У меня есть ещё куча регулируемых независимых выходов, в которых таки нужен ШИМ, поэтому и оконечник слепил универсальный: и шим, и ключ
не нравится меню дисплея, как то надо по другому все организовывать, выходов мало зрительно надо переходить на PCA9685 до 16 ШИМ
валяется платка уже 2-й месяц, перепробовал кучу либ - ничего толком не работает на 32-битной архитектуре (либо руки кривые и не доразобрался). Но мне как раз 12 ШИМ каналов хватает у Due. Попытаюсь позже вернуться к этой теме...
есть идеи по меню пиши
Есть. Уже реализовал, правда, юзаю либу стороннюю для LCD, в ней можно вместо каждый раз СетКусор устанавливать позиции как lcd.printAt (posX, posY, #val). Удобнейшая штука. Сама либа разрабом сделана как либа для lcd, энкодера и меню, но функционалом меню в ней нереально пользоваться, т.к. она сама является бесконечным циклом и вешает всё остальное. Ну, как вариант, выделить какую-нибудь нанку специально для менюхи - меню там шикарное и задокументированное. Если бы ещё всё было без тормозов, на прерываниях - цены бы ей не было. Сцыль в спойлере.
Код в скетче разнёс по табам, так что, возможно, собрал в кучу криво, но идея должна быть понятна. Фотку, как оно (моё меню) выглядит, выкладывал выше. Вот она:
Как оно работает? Энкодером гоняем стрелку (слева от надписей) по 8 пунктам меню (в функции mainMenuSelector() меняем значение mainMenuPosition). В 1-4 пунктах меню есть ещё и значения, остальные - вкл/выкл. Поэтому когда кнопкой энкодера выбираем, например, EFFECT, то слева от нолика появляется ещё одна стрелка, которая показывает активный (выбранный) пункт меню(то есть значению mainMenuState мы присваиваем значение из mainMenuPosition). Как только произошла эта защёлка - ныряем в функцию выбора эффекта effectSelector() (if (mainMenuState == 1) effectSelector();) Крутим энкодер - меняется значение выбранного пункта. Клик по кнопке энкодера - выходим из редактирования, опять в mainMenuSelector(), "запомнив" при этом введённое значение.
Ну, ещё раз повторюсь: я не программер, только пытаюсь вникнуть. Возможно, что-то можно реализовать намного проще, но я делаю на уровне интуиции, не влезая в дебри: туда пока рановато...
Спустя 8 месяцев вернулся к данной бегушке, вот на свежую голову переписал (без ШИМа, для упрощения). Всего 10 каналов, энкодер, LCD2004, 5 кнопок быстрого вызова программ. Arduino Pro Micro.
На дисплее три строки меню: Скорость, Эффект и Настройки (последнюю здесь убрал для упрощения кода). При неактивных (невыбранных) пунктах меню в 1-й колонке энкодером гоняется "левая" стрелка (
void
leftArrow
) вверх-вниз. Нужный пункт меню активируется при нажатии кнопки энкодера, при этом на соответствующей строке в колонке 8 появляется ещё одна стрелка (void
rightArrow
), указывающая, что данный пункт меню активен. После "накрутки" энкодером нужного значения выходим в корень нажатием кнопки энкодера, правая стрелка затирается.В дальнейшем планирую программы перенести в EEPROM и сделать возможность юзеру пошагово создавать свои пользовательские программы. Поэтому прошу конструктивной критики: что нужно доработать и что можно упростить? Постарался закомментить максимально.
вместо
-
итд. Это во первых
Во вторых всё это, без экрана но с DMX, уместится в m328, DUE/Mega тут ни к чему.
В третьих, достаточно взять ShiftPWM и выдрать из неё все упоминания Serial (закомментировать или удалить) и она становится совместимой с DMX. С вашей большой меги с экраном можете ей слать DMX трафик. Бонусом и плавно, но частота модуляции низкая tbh.
Возможно, all просто предательски молчит, закидывается попкорном и ржёт, а я, крыса-кун, обламываю общественный кайф. Но держать в памяти светодиоды, да ещё и ради простого счётчика Джонсона - лютый моветон. У меги в 16 раз больше памяти, но не в сто.
https://github.com/elcojacobs/ShiftPWM
8 месяцев назад, когда я только начинал сие, упор был на Дуйке:
1) потому что была под руками
2) потому что у неё было как раз нужных мне 12 каналов pwm
3) потому что на всю остальную периферию (а это большой светопульт) мне нужно ещё как минимум 20 каналов плюс куча кнопок (типа быстрого вызова, причём в большинстве случаев матричное подключение кнопок не прокатит, т.к. в результате будет паутина проводов по всему девайсу) и светодиодов к ним. Ну, предположим, 16 светодиодов по четырём линиям можно динамически светить с монстроподобным дешифратором К155ИД3 (благо в кладовке их ведро). Ну и pwm типа PCA9685 пришлось бы добавлять.
Последний вариант скетча был для Arduino Pro Micro, без DMX, на 10 каналов, из них (при необходимости, это уже будет другая версия прошивки) 3 штуки пойдёт на ШИМ (для RGB лент или диодов), 7 на бегушку,1 аналоговый - для уровня уличного освещения, и последний свободный пин - для smart-лент (такая вот штучка для рекламных нужд).
Ну, поэтому я и хочу взять EEPROM внешнюю, даже 24с512. Копейки. Туда заколеблешься программы забивать на этот объём.
За рекомендации по коду спасибо, буду вникать разбираться. Пока что почти ничё не понятно...
Есличо, &1 в строке вывода по-идее не нужно (всё равно ничего не делает) но это только в случаях когда левее по числу нет ещё единиц.
Алсо, вместо digitalWrite() реще будет прямой записью в порт, у AVR порты это 8 выходов, как правило они находятся рядом, в т. ч. у плат Arduino. Крайне полезно бы почитать библиотеку CyberLib, она простая, состоит из одного файла всего. Там как раз замена всех этих небыстрых вводов-выводов под конкретный процессор.
Деталь, необходимая для вывода по SPI (shiftPWM) - сдвиговые регистры вывода, оно называется 74595. Эта микросхема дешёвая, особенно с буквой d (sot корпус 1.27 между ног).
Насчёт "много PWM" - это удочка на которую у меня ловился батя. Практичнее разбить PWM-нужды на группы по три, и на каждую поставить одну жалкую дешёвую atmega8 или 328, а ей кидать информацию по сериалу/модбусу/дмх. Передать сериал просто (трансиверы, дифференциальные линии). Передать PWM непросто (наводки, искажения, отражения). Под словом "передать" я имею ввиду на большое расстояние. "Большое расстояние" - это более двух метров.
хм... А вот так, что, можно?
что-то у меня отложилось в памяти, что бинарная запись поддерживается только для байта...
Возможно. тогда лед_стате=Бвосемьцыфр; лед_стате*=256 и т д.
Я пытаюсь понять, чем digitalWrite вреден? Весь код придуман из головы на основании школьного курса информатики, пройденного мною в 1989 году (старенький я, мы ещё васик учили). Ардуинкой увлёкся на карантине в апреле, потом "завязал" на полгода (работа привалила). Посему "немного" отстал, навёрстываю. Поэтому и задаю такие глупые вопросы.
Кроме того, мне непонятно, почему использовать бульки моветон? Они же меньше памяти занимают.
А зачем это в рамках корпуса 400х600мм (светопульт), где длина провода от контроллера до диммера не превышает 250мм? Тем более что перед передачей pwm можно усилить сигнал. Разумеется, на расстояния (большие) я бы тоже кинул dmx, но пока такой потребности нет.
Кроме того, мне непонятно, почему использовать бульки моветон? Они же меньше памяти занимают.
В восьмиразрядной ахритектуре булева переменная занимает 8 бит. Как байт.
В восьмиразрядной ахритектуре булева переменная занимает 8 бит. Как байт.
А по скорости обработки данных как?
В восьмиразрядной ахритектуре булева переменная занимает 8 бит. Как байт.
А по скорости обработки данных как?
Что именно "как"?
И какая "обработка" имеется в виду?
Ну и заодно уж:
Кроме того, мне непонятно, почему использовать бульки моветон? Они же меньше памяти занимают.
Вообще-то здесь ни о каких булевых переменных (если именно они названы "бульками") речи не идет.
dword - это двойное слово,
"B" - в константе от слова "binary", а не "boolean".
Что именно "как"?
И какая "обработка" имеется в виду?
Какой тип данных обработается быстрее: булька или dword?
Да, я в курсе, выше пояснил
Что именно "как"?
И какая "обработка" имеется в виду?
Какой тип данных обработается быстрее: булька или dword?
В общем виде ответить на этот вопрос невозможно, т.к. ответ зависит от конкретных условий.
Что именно подразумевается под "обработкой"?
И на какой архитектуре?
Чтение-запись.
Выше писал: последний скетч для Дуни про микро, а значит, ATmega32U4
А что, кто-то чтение-запись уже считает обработкой?
Ну. раз разница в скорости записи напрямую в порт в сотни раз быстрее, чем digitalWrite - то таки да, обработка
Ну. раз разница в скорости записи напрямую в порт в сотни раз быстрее, чем digitalWrite - то таки да, обработка
Т.е. достаточно вставить delay(), и уже можно считать, что мы сделали обработку?