Блок управление велофарой, с питанием и от динамо-втулки, и от аккумуляторов.

gosvamih
Offline
Зарегистрирован: 28.04.2017

Собственно, есть велофара AXA Luxx 70 и фонарь заднего света этой же фирмы, которые питаются от динамо-втулки shimano. Меня напрягало, что на остановках,  встроенных ёмкостей фары хватало ненадолго,  и надо было доставать и цеплять батарейные фары. Поэтому я решил собрать блок управления на ардуино, все запчасти уже были в наличии. Отладкой занимался на ардуино uno, а рабочий вариант на ардуино про мини.

Устройство состоит из двух реле, диодного моста, DC-DC импульсного понижающего преобразователя, мосфета, ардуино и двух  лититевых аккумуляторов , включенных последовательно.  Я снял около 4 вольт между аккумуляторами, чтобы питать реле и ардуино, а на фару идут через мосфет все 8 вольт, максимум 8.4. Реле нужны, чтобы когда устройство выключено, на фару идет питание от динамо втулки через НЗ контакты реле.То есть фара работает как обычно от динамки.  При включении устройства реле включаются, и  цепь с динамки переводится на диодный мост, дальше идет на преобразователь, который понижает напряжение с динамки до максимум 8.4 вольт для заряда аккумуляторов, потом на аккумы и с них на мосфет.

Теперь почему понадобился ардуино и мосфет.  Потому что фара питается переменным током с динамки, переменной частотой в зависимости от скорости вращения колеса. За один оборот колеса 18 импульсов смены полярности. Электроника фары определяет, что частота импульсов соответствует скорости меньше 11 км.ч (с моим диаметром колеса около 36 импульсов в секунду)  и включает режим ближнего света. Два голубых светодиода подсвечивают ближнее поле, фара рефлекторная. При большей скорости, то есть частоте вся мощность идет на один сверх-яркий светодиод дальнего света. Так что я преобразую постоянный ток с аккумов в импульсный, с помощью ардуино и мосфетов, а еще с кнопки управляю ручным включением режима, что позволяет включать ближний свет на любой скорости.

Сначала, я написал программу  с использованием millis. Она работала хорошо, но при включении ближнего света светодиод фары заметно "подмигивал", шаг частоты устанавливался слишком грубо. 

#include <SPI.h>
#include <Bounce.h>

#define LOWBEAM 13 ;
#define HIGHBEAM 3;



int button = 6;
int Relay = 7;
int Status = LOW;
int Mosfet = 4;
int speaker = 5;
int  INTERVAL = LOWBEAM;
Bounce bouncer = Bounce(button,5);

uint32_t ms, ms1 = 0;
bool stat    = true;

void setup() {
Serial.begin(57600);                    // connect to the serial port (если нужны данные через порт раскомментировать)
  pinMode(button,INPUT);
  pinMode(Relay, OUTPUT);
  pinMode(Mosfet, OUTPUT);
  pinMode(speaker, OUTPUT);   // устанавливаем  ногу как выход на speaker 
  
digitalWrite(Relay,LOW); 



  beep(); delay(70);
}

void loop()
{
     //если сменилось состояние кнопки
  if ( bouncer.update() ) {  if ( bouncer.read() == LOW) {
    if ( Status == LOW ) { Status = HIGH; INTERVAL = HIGHBEAM; for (int i=0; i <= 1; i++) {beep(); delay(9);}  } else { Status = LOW; INTERVAL = LOWBEAM;  beep(); delay(9);  } } } 
     
  ms = millis();        
  if((ms - ms1) > INTERVAL || ms < ms1 ){
    ms1 = ms;  
    digitalWrite(Mosfet, stat);
    stat = !stat; 
    }
  
   
}
void beep(){
  for (int i=0; i <= 18; i++) {digitalWrite(speaker, HIGH); delay(1); digitalWrite(speaker, LOW); delay(1);} // Все прерывания заняты, функция tone заменяется этой
}

 

Эксперименты показали, что я терял много энергии на равной длине отношения импульса к паузе между импульсами, а мне надо было сделать время включения мосфета (длину импульса) в несколько раз больше паузы между импульсами, чтобы уменьшить потерю энергии, при этом автоматике фары короткие паузы были без разницы. Тогда на этом форуме  я нашел программы генераторов с использованием программируемых таймеров. И я одну такую переделал для своих нужд. Конечный вариант, который работает сейчас, с уже подобранными значениями длительности и паузы, я вляется вот этот.

// Программа прошивки блока управления велосипедным светом от батарей, который управляет фарами предназначенными для питания только от динамо.
// Фара имеет два режима - ближний свет, и дальний. Режимы переключаются фарой автоматически, в зависимости от частоты пульсации тока.
// Имитируем пульсацию тока динамо-втулки для выбора режима, а мощность подбираем отношением длительности импульса к 
// длине паузы до следующего импульса. 
// Используется программируемый таймер 1. Номер выхода таймера жестко задан на 9-й пин ардуино.
// Программа проверена на ардуино про мини и ардуино уно. Набор фар  Аxa Luxx 70 plus Steady Auto + AXA Rear Light Slim Steady Parking Light for Dynamo


#include <SPI.h>
#include <Bounce.h>                  // библиотека подавления дребезга контактов



uint8_t S = 86;                    // переменная для делителя времени периода ключа на мосфете 
uint8_t P = 15;                    // переменная для делителя времени выключения ключа на мосфете 
                                    // делитель периода S по формуле  
                                    // ICR1= (S) (X+Y) *1000/128; где X - время включения, Y - выключено, в миллисекундах
                                    // делитель периода "выключено" OCR1A= (P) Y*1000 /128;                                    

                
uint8_t Status = LOW;              // установка переменной режима на значение "ближний свет"
                        

Bounce bouncer = Bounce(6,3);           // параметр подавления дребезга контактов кнопки на входе 6


void setup() {
    TCCR1A=(1<<COM1A1)|(1<<COM1A0);         // программирование таймера 1
    TCCR1B=(1<<WGM13)|(1<<CS12)|(1<<CS10);
OCR1A=15;                                   // начальные значения делителя для таймера на "ближний свет" - длительность выключения ключа 3ms
ICR1=86;                                    // делитель длительности включения ключа 9ms
  
  pinMode(6,INPUT);                    // вход с кнопки переключения режима ближний - дальний свет LOW = нажата
  pinMode(7, OUTPUT);                   // выход на реле LOW == вкл
  pinMode(9, OUTPUT);                  // выход на электронный ключ с мосфетом HIGH == вкл
  pinMode(13, OUTPUT);                      // светодиод на платке ардуино 
  
digitalWrite(7,LOW);                    // включаем реле,  отключаем фару от динамки и запитываем от динамо-втулки аккумуляторы



}

void loop()
{                                                             // начало главного цикла
                                               
  if ( bouncer.update() ) {  if ( bouncer.read() == LOW) {   //     если сменилось состояние кнопки
    
    digitalWrite(13, HIGH);                                  // подмигиваем светодиодом по нажатию кнопки
    delay (5);
    digitalWrite(13, LOW);
    
    if ( Status == LOW ) { Status = HIGH; S = 55; P = 5;  // меняем режим на "дальний свет" и устанавливаем параметры делителя 
                                                          // включение S на 7ms, выключение P на 1ms

  } else { Status = LOW; ;  S = 86; P = 15;               // меняем режим на ближний свет и устанавливаем параметры делителя 
                                                          // включение S на 9ms и выключение P на 3ms
} } } 
     
   ICR1= S;                                               // задаём временные параметры таймера для выбранного режима
   OCR1A= P;
    
}                                                         // конец главного цикла

Всё работает, но есть одна проблема, которой нет в первой версии программы на millis. Когда я нажимаю на кнопку смены режима, чаще всего всёнормально, и режим меняется. Но иногда, примерно раз из десяти, фара внезапно пригасает, хотя напряжение на ней даже немного возрастает. Такое чувство что частота меняется, но не знаю, померять частоту нечем. Затем она не реагирует на кнопки, не меняет режим, хотя по 13 светодиоду видно, что считывание кнопки работает. Затем она снова загорается ярко и работает, и режимы снова меняются. Этот "провал" может случиться в любой момент, при первом нажатии или десятом. Закономерности от нажатия не заметил. Я убирал функцию подавление дребезга bounce и заменял её операторами выбора if, но это ничего не изменило.

void loop() {
  
  if(digitalRead(button)==LOW&&flag==0)//если кнопка нажата    
     // и перемення flag равна 0 , то ... 
     { 
           
       digitalWrite(13,!digitalRead(13)); 
       flag=1; 
        //это нужно для того что бы с каждым нажатием кнопки 
        //происходило только одно действие 
        // плюс защита от "дребезга"  
        
        if ( Status == LOW ) {Status = HIGH; S = 55; P = 5;}  
      
           else { Status = LOW; S = 86; P = 15; }
  
          ICR1= S;
          OCR1A= P;
        
          } 
        
      if(digitalRead(button)==HIGH&&flag==1)//если кнопка НЕ нажата 
     //и переменная flag равна - 1 ,то ... 
     { 
           
        flag=0;//обнуляем переменную flag 
     } 
     
    
    
}

Пригасания случались и без bounce. То есть "косяк" где-то в блоке программирования таймера, или в его работе. Поскольку в версии millis этого косяка нет, то думаю что я то-то не понимаю. Я оказался не способен увидеть ошибку, и решил обратиться к более опытным людям. Может быть кто-то знает, что с этим таймером происходит, почему случаются такие провалы? Где тут затык?

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

Код не смотрел, но по словесному описанию похоже на следующее:

допустим у нас коэффициент деления 100, а нам надо повысить  частоту и установить коэффициент деления, скажем, в 95. Если мы это сделаем в момент, когда счетчик досчитал до 80, все пройдет гладко, а если в момент, когда счетчик досчитал до 97, то следующий сброс счетчика в 0 произойдет только после его переполнения на значении 65536. Т.е.  длительности ериодов будут 100, 100, 65535, 95, 95...

gosvamih
Offline
Зарегистрирован: 28.04.2017

Пригасание фары длится больше 5 секунд. Это очень много.

У меня появилась идея. А если я перед каждой сменой частоты буду останавливать таймер. Менять значения и пускать его снова.

Другой вариант, или имеет значение, какой параметр я меняю первым при работающем таймере? У меня сейчас в программе сначала меняется ICR1 а следом  OCR1A. Может их порядок изменить? 

Соберу стенд. Буду крутить. 

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

gosvamih, вы счётный регистр не сбрасываете, поэтому всё как писал andriano. Таймер начинает отсчитывать 0,0625µS*1024*65535*2= ~8секунд

gosvamih
Offline
Зарегистрирован: 28.04.2017

Я, кстати, ваш код переделывал, вроде. Для стробоскопа. Вот этот.

Только заменил выражения уже вычисленными значениями, и убрал обмен с serial.

void setup() {
Serial.begin(9600);
pinMode(9,OUTPUT);
TCCR1A=(1<<COM1A1)|(1<<COM1A0);
TCCR1B=(1<<WGM13)|(1<<CS12)|(1<<CS10);
OCR1A=1; ICR1=0;
}

void loop() {
if (Serial.available()) { 
 uint16_t x = Serial.parseInt();
 uint16_t y = Serial.parseInt();
   ICR1= (uint32_t) (x+y) *1000/128;
   OCR1A= (uint32_t) y*1000 /128;
    }
 }
 
 // Для запуска в терминалке (предварительно выставить скорость 9600) нужно ввести 2 числа, раздёлённых пробелом. 
 // Первое -сколько времени должен гореть светодид, второе число сколько времени должен быть потушен. 
 // В миллисекундах. Например:  30 100

У вас ведь он работает тоже без сброса, да еще и в режиме "горячей смены частоты". Или этот код  просто "идея" был?

dimax
dimax аватар
Offline
Зарегистрирован: 25.12.2013

gosvamih, это код был написан для стробоскопа, и в нём всё по-максимому упрощено. Мне вообще не понятно, зачем вам такие извращения, почему не использовать стандартный шим.

gosvamih
Offline
Зарегистрирован: 28.04.2017

Мосфет. Я теряю 2 вольта на мосфете, если у меня импульсы и паузы равной длины. Динамка даёт переменный ток, а я обманываю фару импульсным. Так что мне лучше приблизить данные к постоянному току, и чем длинее отношения импульса к паузе, тем меньше у меня потери энергии. Практически, с моими данными я теряю всего пару десятков вольт, у меня паузы в 7 - 9 раз короче импульса. Ваш стробоскоп очень подходит. если решу проблему, запилю ещё режим мигания, чтобы в тумане было лучше меня видно.

gosvamih
Offline
Зарегистрирован: 28.04.2017

Всё верно вы сказали. Собрал стенд, заметил что пригасания действительно возникают только на повышение частоты. Раньше не заметил этого.

Вот в таком виде всё работает нормально

void loop()
{                                                             // начало главного цикла
                                               
  if ( bouncer.update() ) {  if ( bouncer.read() == LOW) {   //     если сменилось состояние кнопки
    
    digitalWrite(13, HIGH);                                  // подмигиваем светодиодом по нажатию кнопки
    delay (5);
    digitalWrite(13, LOW);
    
    if ( Status == LOW ) { Status = HIGH; S = 55; P = 5;  // меняем режим на "дальний свет" и устанавливаем параметры делителя 
                                                          // включение S на 7ms, выключение P на 1ms

  } else { Status = LOW; ;  S = 86; P = 15;               // меняем режим на ближний свет и устанавливаем параметры делителя 
                                                          // включение S на 9ms и выключение P на 3ms
} }  

   TCNT1= 0;  
   ICR1= S;                                               // задаём временные параметры таймера для выбранного режима
   OCR1A= P;
  } 
}                                                         // конец главного цикла

 

gosvamih
Offline
Зарегистрирован: 28.04.2017

Благодарю за помощь. Без вас не нашёл бы, стал бы всю программу менять. А так всего одной командой больше.