Бегущие огни на Ардуино для сборной модели автомобиля

Schwarz
Offline
Зарегистрирован: 11.04.2021

Доброго дня, уважаемые форумчане!

Мое увлечение программированием Ардуино пришло от хобби сборки масштабных моделей автомобилей. Организация поворотников, ближнего/дальнего света, тормозных сигналов и т.п. с помощью микроконтроллера, это оказалось удивительно просто при наличии минимальных знаний по программированию на С++. Однако, попробовав усложнить себе задачу, я столкнулся с непреодолимой трудностью. В очередную сборную модель автомобиля я решил установить бегущие поворотники. Написал скетч, но вот уже больше месяца бьюсь над проблемой. Задача программы, это последовательное чередование режимов бегущих поворотников через нажатие тактовой кнопки:

  1. левый поворотник
  2. правый поворотник
  3. аварийка
  4. выкл

Режим каждого бегущего поворотника организован на массиве из четырех пинов, сам скетч полностью рабочий, но если нажимать тактовую кнопку, не дожидаясь 100% пробега до 4-го пина, то следующий режим поворотника или аварийки  включится не с array[0], а с того пина, на котором был прерван предыдущий цикл бегущей строки.

Ниже я привожу самый простой вариант скетча, хотя пробовал организовать его через выделение динамической памяти для массива, через организацию класса бегущей строки. Единственный способ, который мне удалось реализовать для нормальной работы бегущих строк с первого пина, это полный программный сброс микроконтроллера  void(* resetFunc) (void) = 0, но этот способ лишает меня возможности использовать остальные пины Ардуино для другой светотехники модели.

Подскажите, пожалуйста, что мне нужно изменить в скетче или в моей голове? Может, я неправильно обращаюсь с массивами или с областью видимости?

// Организация бегущих поворотников через функцию changeLed
/* нажатием тактовой кнопки включаем режимы:
  1. бегущий левый поворотник
  2. бегущий правый поворотник
  3. бегущая аварийка
  4. выключение
*/
#define BUTTON_1_PIN 2  // номер вывода кнопки 1 
boolean lastButton = LOW; // переменные для функции антидребезга
boolean currentButton = LOW;
int array1[10]  {3, 4, 5, 6}; // инициализация массива левого поворотник
int array2[10]  {A0, A1, A2, A3}; // инициализация массива правого поворотника
int ledDelay(100); // инициализация задержки в функции бегущих огней
int currentLed(0); // инициализация текущего пина
unsigned long pastTime; // прошедшее время для millis
unsigned long mill; //  переменная для millis
int counter = 0; // счетчик режимов кнопки
// функция для подавления дребезга
boolean debounse(boolean last, int swichPin ) {
  boolean current = digitalRead(swichPin);
  if (last != current) {
    delay(5);
    current = digitalRead(swichPin);
  }
  return current;
}
// функция бегущих огней
void changeLed(int *massive1, int *massive2) {
  static unsigned long pastTime; // прошедшее время для millis
  if ((mill - pastTime) > ledDelay) { // если пришло время перейти к следующему светодиоду
    pastTime = mill;
    // включаем текущий LED
    digitalWrite(massive1[currentLed], HIGH);
    digitalWrite(massive2[currentLed], HIGH);
    // переходим к следующему пину
    currentLed ++;
    // увеличиваем иттерацию по массиву на 6 несуществующих элементов для получения требуемой задержки
    if (currentLed == 10) {
      // выключаем все светодиоды
      for (int i = 0; i < 10; i++) {
        digitalWrite(massive1[i], LOW);
        digitalWrite(massive2[i], LOW);
      }
      currentLed = 0;
    }
  }
}
// функция сброса левого поворотника
void offLeft() {
  for (int i = 0; i < 10; i++)
    digitalWrite(array1[i], LOW);
}
// функция сброса правого поворотника
void offRight() {
  for (int i = 0; i < 10; i++)
    digitalWrite(array2[i], LOW);
}
void setup()
{
  for (int pin = array1[0]; pin <= array1[3]; ++pin)
    pinMode(pin, OUTPUT);
  for (int pin = array2[0]; pin <= array2[3]; ++pin)
    pinMode(pin, OUTPUT);
  pinMode(BUTTON_1_PIN, INPUT);
}
void loop() {
  mill = millis(); // один запрос millis() на один цикл
  // блок управления режимами переключениями поворотников
  currentButton = debounse(lastButton, BUTTON_1_PIN);
  if (lastButton == LOW && currentButton == HIGH)   // было нажатие кнопки
    counter++;
  lastButton = currentButton;        // сброс признака клика
  if (counter == 1) {
    offRight(); // выключаем правый поворотник, включаем левый
    changeLed(array1, array2);
  }
  if (counter == 2) {
    offLeft();  // выключаем левый поворотник, включаем правый
    changeLed(array1, array2);
  }
  if (counter == 3) {
    changeLed(array1, array2); // включаем аварийку
  }
  if (counter == 4) {
    offLeft();  // выключаем все и обнуляем счетчик
    offRight();
    counter = 0;
  }
}

 

Komandir
Komandir аватар
Offline
Зарегистрирован: 18.08.2018

currentLed = 0;

надо поместить в 71 строке

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

А что это у вас в строках 13 и 14 такое интересное написано? ))

По вопросу - видимо, нужно обнулять currentLed при смене режима

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

А кто ж за Вас будет с начала-то начинать?

Во все функции offXXX добавьте 

currentLed = 0;

и всего делов.

b707
Offline
Зарегистрирован: 26.05.2017

Странные у вас массивы. Если в каждом поворотнике по 4 пина - зачем в массиве обьявлены,  и, главное, итерируются по десять элементов??

Далее... вот это называется "левой пяткой за ухом"

for (int pin = array1[0]; pin <= array1[3]; ++pin)
    pinMode(pin, OUTPUT);

итерировать цикл надо по индексу массива, а не по значению... а так у вас полная ерунда выходит

 

И в догонку - функция changeLed тоже вызывает вопросы. зачем вы ей всегда передаете оба массива поворотников. даже если мигаете только одним?

 

В общем, код - очередная иллюстрация к мифу "ардуино - это так просто"

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

v258 пишет:

А что это у вас в строках 13 и 14 такое интересное написано? ))

А что там такого интересного? Объявления переменных с инициализацией.

b707
Offline
Зарегистрирован: 26.05.2017

Schwarz пишет:

... пробовал организовать его через выделение динамической памяти для массива, через организацию класса бегущей строки. Единственный способ, который мне удалось реализовать для нормальной работы бегущих строк с первого пина, это полный программный сброс микроконтроллера  void(* resetFunc) (void) = 0....

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

sadman41
Offline
Зарегистрирован: 19.10.2016

Горе от ума.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

ЕвгенийП пишет:

v258 пишет:

А что это у вас в строках 13 и 14 такое интересное написано? ))

А что там такого интересного? Объявления переменных с инициализацией.

Вот это и интересно. Не знал, что можно так инициализировать, в скобочках. В учебниках пишут, что переменные инициализируются присвоением 

int currentLed = 0;

 

rkit
Offline
Зарегистрирован: 23.11.2016

Скобки это сокращённый конструктора.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

rkit пишет:
Скобки это сокращённый конструктора.

Конструктор переменной о_О?

rkit
Offline
Зарегистрирован: 23.11.2016

Конструктор значения переменной.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

v258 пишет:

rkit пишет:
Скобки это сокращённый конструктора.

Конструктор переменной о_О?

Да. А чо смешного?  

когда ты пишешь  int i;

неявно вызывается int i(0);

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

DetSimen пишет:

Да. А чо смешного?  

когда ты пишешь  int i;

неявно вызывается int i(0);

Я говорил, что смешного? Дед, не наговаривай на меня. Я просто подофигел ))

rkit
Offline
Зарегистрирован: 23.11.2016

DetSimen пишет:

v258 пишет:

rkit пишет:
Скобки это сокращённый конструктора.

Конструктор переменной о_О?

Да. А чо смешного?  

когда ты пишешь  int i;

неявно вызывается int i(0);


Это смотря где напишешь.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

rkit пишет:
Это смотря где напишешь.

Да. В глобалах - точно, в локальных - нинаю. 

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

Строго говоря, в глобалах тоже не так. Там просто чистится вся память, не выделяя из нее отдельные переменные.

А вот инициализация через скобки - да, стандартный прием в Си++.

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

сегмент инициализированных данных тоже тупо чистится?  Там не только нули могут быть. 

rkit
Offline
Зарегистрирован: 23.11.2016

andriano пишет:

Строго говоря, в глобалах тоже не так. Там просто чистится вся память, не выделяя из нее отдельные переменные.

А вот инициализация через скобки - да, стандартный прием в Си++.

Нет. Там вызывается конструктор по умолчанию. То есть не int i(0);, а int i();, на самом деле.

ЕвгенийП
ЕвгенийП аватар
Offline
Зарегистрирован: 25.05.2015

Куда ж не нафиг :-)))

andriano
andriano аватар
Offline
Зарегистрирован: 20.06.2015

DetSimen пишет:

сегмент инициализированных данных тоже тупо чистится?  Там не только нули могут быть. 

На самом деле там переменные разделяются на инициализированные и неинициализированные. В младших адресах размещаются инициализированные, значения которых циклом копируются из PROGMEM, а за ними - неинициализированные, которые так же в цикле обнуляются:

000000ba <__do_copy_data>:
  ba:	11 e0       	ldi	r17, 0x01	; 1
  bc:	a0 e0       	ldi	r26, 0x00	; 0
  be:	b1 e0       	ldi	r27, 0x01	; 1
  c0:	ec ef       	ldi	r30, 0xFC	; 252
  c2:	f3 e0       	ldi	r31, 0x03	; 3
  c4:	02 c0       	rjmp	.+4      	; 0xca <__do_copy_data+0x10>
  c6:	05 90       	lpm	r0, Z+
  c8:	0d 92       	st	X+, r0
  ca:	ae 30       	cpi	r26, 0x0E	; 14
  cc:	b1 07       	cpc	r27, r17
  ce:	d9 f7       	brne	.-10     	; 0xc6 <__do_copy_data+0xc>

000000d0 <__do_clear_bss>:
  d0:	21 e0       	ldi	r18, 0x01	; 1
  d2:	ae e0       	ldi	r26, 0x0E	; 14
  d4:	b1 e0       	ldi	r27, 0x01	; 1
  d6:	01 c0       	rjmp	.+2      	; 0xda <.do_clear_bss_start>

000000d8 <.do_clear_bss_loop>:
  d8:	1d 92       	st	X+, r1

000000da <.do_clear_bss_start>:
  da:	ab 31       	cpi	r26, 0x1B	; 27
  dc:	b2 07       	cpc	r27, r18
  de:	e1 f7       	brne	.-8      	; 0xd8 <.do_clear_bss_loop>
  e0:	0e 94 67 01 	call	0x2ce	; 0x2ce <main>
  e4:	0c 94 fc 01 	jmp	0x3f8	; 0x3f8 <_exit>

 

Schwarz
Offline
Зарегистрирован: 11.04.2021

Друзья! Спасибо Вам за оказанную помощь и справедливую критику.  Отдельная благодарность уважаемому форумчанину под ником Komandir. Один стейтмент в 71-й строке «currentLed = 0;» решил все проблемы. Обязуюсь учесть все замечания. У меня теперь программа работает, как нужно. Единственное, переход от второго поворотника к аварийке стал немного некорректным, но это я додумаю.  Еще раз благодарю всех за помощь, критику и участие!

Schwarz
Offline
Зарегистрирован: 11.04.2021

Все готово! Внес еще маленькое изменение в скетч, и теперь это 100% готовый и корректный вариант. Параллельно постарался учесть все справедливые замечания. Выкладываю результат, может кому-нибудь пригодится.

// Организация бегущих поворотников через функцию changeLed
/* нажатием тактовой кнопки включаем режимы:
  1. бегущий левый поворотник
  2. бегущий правый поворотник
  3. бегущая аварийка
  4. выключение
*/
#define BUTTON_1_PIN 2  // номер вывода кнопки 1 
boolean lastButton = LOW; // переменные для функции антидребезга
boolean currentButton = LOW;
int array1[10]  {3, 4, 5, 6}; // инициализация массива левого поворотник
int array2[10]  {A0, A1, A2, A3}; // инициализация массива правого поворотника
int ledDelay(100); // инициализация задержки в функции бегущих огней
int currentLed(0); // инициализация текущего пина
unsigned long pastTime; // прошедшее время для millis
unsigned long mill; //  переменная для millis
int counter = 0; // счетчик режимов кнопки
// функция для подавления дребезга
boolean debounse(boolean last, int swichPin ) {
  boolean current = digitalRead(swichPin);
  if (last != current) {
    delay(5);
    current = digitalRead(swichPin);
  }
  return current;
}
// функция бегущих огней
void changeLed(int *massive1, int *massive2) {
  static unsigned long pastTime; // прошедшее время для millis

  if ((mill - pastTime) > ledDelay) { // если пришло время перейти к следующему светодиоду

    pastTime = mill;

    // включаем текущий LED
    digitalWrite(massive1[currentLed], HIGH);
    digitalWrite(massive2[currentLed], HIGH);
    // переходим к следующему пину
    currentLed ++;
    // увеличиваем иттерацию по массиву на 6 несуществующих элементов для получения требуемой задержки
    if (currentLed == 10) {
      // выключаем все светодиоды
      for (int i = 0; i < 10; i++) {
        digitalWrite(massive1[i], LOW);
        digitalWrite(massive2[i], LOW);
      }
      currentLed = 0;
    }
  }
}
// функция сброса левого поворотника
void offLeft() {
  for (int i = 0; i < 10; i++)
    digitalWrite(array1[i], LOW);
}
// функция сброса правого поворотника
void offRight() {
  for (int i = 0; i < 10; i++)
    digitalWrite(array2[i], LOW);
}
void setup()
{

  for (int i = 0; i <= 3; ++i)
    pinMode(array1[i], OUTPUT);
  for (int i = 0; i <= 3; ++i)
    pinMode(array2[i], OUTPUT);
  pinMode(BUTTON_1_PIN, INPUT);
}
void loop() {
  mill = millis(); // один запрос millis() на один цикл
  // блок управления режимами переключениями поворотников
  currentButton = debounse(lastButton, BUTTON_1_PIN);
  if (lastButton == LOW && currentButton == HIGH) {  // было нажатие кнопки
    counter++;
    currentLed = 0;
    offRight();
    offLeft();
  }
  lastButton = currentButton;        // сброс признака клика
  if (counter == 1) {
    offRight(); // выключаем правый поворотник, включаем левый
    changeLed(array1, 0 );
  }
  if (counter == 2) {
    offLeft();  // выключаем левый поворотник, включаем правый
    changeLed( 0, array2);
  }

  if (counter == 3) {
    changeLed(array1, array2); // включаем аварийку
  }
  if (counter == 4) {
    offLeft();  // выключаем все и обнуляем счетчик
    offRight();
    counter = 0;
  }
}

 

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

А фото/видео результата?

Schwarz
Offline
Зарегистрирован: 11.04.2021

Фото будет не информативным, а видео выложить не получается. Боюсь, на этой площадке видеофайлы не поддерживаются.

v258
v258 аватар
Offline
Зарегистрирован: 25.05.2020

Гифки вполне себе размещаются

Schwarz
Offline
Зарегистрирован: 11.04.2021

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

Блин, да нафига макетка то? Модельку с поворотниками хотелось. Или вы думаете, что тут никто не знает, как последовательно 4 светодиода загораются?

Schwarz
Offline
Зарегистрирован: 11.04.2021

Я собираю модели из парворков, они собираются по два, три года. Поэтому, динамические поворотники на моем собранном авто можно будет посмотреть только через пару лет. А пока у меня в коллекции только те модели, тираж которых завершен издательством. На них я организовал самую простую схему светотехники на основе Ардуино.

b707
Offline
Зарегистрирован: 26.05.2017

Schwarz пишет:

Я собираю модели из парворков

откуда-откуда, простите?

Schwarz
Offline
Зарегистрирован: 11.04.2021

Прошу прощения, опечатка, конечно речь идет о периодическом издании партворков.

b707
Offline
Зарегистрирован: 26.05.2017

Schwarz пишет:

Прошу прощения, опечатка, конечно речь идет о периодическом издании партворков.

если вы думаете, что это я вас троллил за пропущенную букву - но таки нет.

"Партворк" - впервые слышу это слово. хотя калька с английского понятна.

Удивительно, что у кго-то хватает терпение собирать модель, когда каждый месяц выходить в продажу только пара деталей...

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

Не пойму, какой масштаб там. Питание и электроника "у ей в нутре"? 

Schwarz
Offline
Зарегистрирован: 11.04.2021

Масштаб 1:8, и да, вся электронная начинка и питание спрятаны внутри

Rumata
Rumata аватар
Offline
Зарегистрирован: 29.03.2019

класс))

DetSimen
DetSimen аватар
Offline
Зарегистрирован: 25.01.2017

Schwarz пишет:

На копейке никогда аварийки не было.