Skocz do zawartości
espiridion

Programowanie przełączników do kontroli serw

Pomocna odpowiedź

Napisano (edytowany)

Cześć FORBOT,

 

Proszę na wstępie o wyrozumiałość. Nie jestem osobą techniczną. Zrobiłem na ile jestem w stanie, póki co instalacja działa (jako tako do testów) i mogę nią testować swoje obiekty.

Jest to studencka instalacja artystyczna. Oczekuję tylko pomocy w napisaniu tego kodu właśnie pod millis() by móc się pozbyć delays.

 

Dla wyjaśnienia czym jest instalacja artystyczna, można znaleźć prace na festiwalu BIENNALE WRO 2019: http://wro2019.wrocenter.pl

 

Chciałbym napisać coś takiego, ale nie wiem jak ominąć "czas". Funkcja milis() czy moze cos innego?

Czyli np tak: (to mój skrót myślowy w postaci kodu myślowego....ale chciałbym bardzo takie działanie)

if (Switcher[1] == LOW) {

led[1] == HIGH,

counterLED[1]  ==HIGH,

servo[1]pwm1 ( servomin_1 = 150, servomax_1 = 450, speed = 100),  //np. jakas predkosc np. jak w varspeedservo

servo[1]pwm0 (countermin_1 = 200, servomax_1 = 450, speed = 20) //np. szybsza predkosc

} else if (Switcher[1] == HIGH) {

led[1] == LOW,

counterLED[1] == LOW,

servo[1]pwm1 == LOW, //serwa wylaczone

servo[1]pwm0 == LOW //serwa wylaczone

}

 

Obecny przykładowy kod działania jednego przełącznika, którego teraz używam:

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

int i = 0;

const int LED[] {26, 24, 22, 10, 8, 6, 4, 2};
const int CounterLED[] {36, 38, 40, 42, 28, 30, 32, 34};
const int Switch[] {27, 25, 23, 11, 9, 7, 5, 3};
const uint8_t servo[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15};
 
Adafruit_PWMServoDriver pwm0 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x41);

 
#define SERVOMIN_1  150 
#define SERVOMAX_1  600 

#define SERVOMIN_2  350 
#define SERVOMAX_2  500 
 
#define COUNTER1_MIN 200
#define COUNTER1_MAX 450

#define COUNTER2_MIN 300
#define COUNTER2_MAX 440

//... i tak dalej, wiem, że to kilometry definów (mozna w ramkach)
 
 
void setup() {
  Serial.begin(9600);
  pinMode(Switch[i], INPUT_PULLUP);
  pinMode(LED[i], OUTPUT);
  pinMode(CounterLED[i], OUTPUT);
 
  pwm0.begin();
  pwm0.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
 
  pwm1.begin();
  pwm1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
  delay(10);
}
void setServoPulse(uint8_t n, double pulse) {
  double pulselength;
 
  pulselength = 1000000;   // 1,000,000 us per second
  pulselength /= 60;   // 60 Hz
  Serial.print(pulselength); Serial.println(" us per period");
  pulselength /= 4096;  // 12 bits of resolution
  Serial.print(pulselength); Serial.println(" us per bit");
  pulse *= 1000000;  // convert to us
  pulse /= pulselength;
  Serial.println(pulse);
  pwm0.setPWM(n, 0, pulse);
  pwm1.setPWM(n, 0, pulse);
  delay(10);
}
 
void loop() {
  {
  if(digitalRead(Switch[1]) == LOW) // PRZEŁĄCZNIK NR. 1
  {
    digitalWrite(LED[1], HIGH);
    digitalWrite(CounterLED[1], HIGH);
    //ruch wskazowek i robota w jedna strone
    for (uint16_t pulselen = SERVOMIN_1; pulselen < SERVOMAX_1; pulselen++) {
      pwm1.setPWM(servo[0], 0, pulselen);
    }
    for (uint16_t pulselen = COUNTER1_MIN; pulselen < COUNTER1_MAX; pulselen++) {
      pwm0.setPWM(servo[0], 0, pulselen);
    }
    delay(500);
    //ruch wskazowki od poczatku i ruch orczyka robota od poczatku
    for (uint16_t pulselen = SERVOMAX_1; pulselen > SERVOMIN_1; pulselen--) {
      pwm1.setPWM(servo[0], 0, pulselen);
    }
    for (uint16_t pulselen = COUNTER1_MAX; pulselen > COUNTER1_MIN; pulselen--) {
      pwm0.setPWM(servo[0], 0, pulselen);
    }
    delay(500);
    //ruch wskazowki i robota zakoncozny
    
    //Tutaj nie chodzi o to zeby wskazowki mialyby sie zatrzymywac na pol sekundy
    //bardziej chodzi o to ze wskazowki i orczyk robota po prostu caly czas sie rusza od kata
    //powiedzmy np. od 45 stopni do 130 stopni a ten delay to czas pokonania wlasnie tej odleglosci miedzy katami
    //np. wskazowki maja byc ruchome, cos w stylu awarii elektrownii w Czarnobylu :)
    //a ruch serwa robota to poprostu zwykle obroty serwa jak w najprostszysch tutoriala dla zielonych
    //jesli istnialaby mozliwosc to fajnie by bylo zrobic dla duzego serwa z pwm1 np. oś czasu, czyli
    //pierwsza sekwencja od 300 do 600 z delayem (200) potem druga sekwencja z delayem(100) potem trzecia sekwencja z delayem (1000) itd.
  } else if (digitalRead(Switch[1]) == HIGH) {
    digitalWrite(LED[1], LOW);
    digitalWrite(CounterLED[1], LOW);
  }
}
}

 

Obecne działanie:

Kiedy przełączam przełącznik dźwigniowy nr 1 na ON - uruchamia on 2 ledy (led1, counterled1) oraz servo1 z pierwszego sterownika i2c (pwm0) i drugiego sterownika i2c (pwm1).

Kiedy przełączam przełącznik dźwigniowy nr 1 na OFF - wyłącza on 2 ledy (led1, counterled1) oraz servo1 z pierwszego sterownika i2c (pwm0) i drugiego sterownika i2c (pwm1).

Analogicznie tak działa aż do 8 przełączników. Każdy przełącznik => to komplet 2 ledów i 2 serwomechanizmów. Czasami w niektórych wypadkach może być więcej serw dołączonych do kompletu.

 

Problem:

Mam 8 przełączników. Delay powoduje opóźnienia. Kiedy włączam przełącznik 1 uruchamia się wszystko zgodnie, potem np. decyduję włączyć przełącznik 4 to zgodnie z zasadą delay trzeba odczekać sekundę po przełączeniu na ON aż włączą się 2 ledy i 2 serwomechanizmy. Gorzej się robi, jeśli chcemy manipulować kilkoma przełącznikami naraz.

 

Czego chcę?:

  • Pozbycia się delay() i wprowadzenie funkcji milis(). 
  • Każdy przełącznik ma działać niezależnie od innych. Kiedy go włączę na ON ma działać natychmiast, kiedy włączę go na OFF ma wyłączać natychmiast.
  • Parametry do wszystkich serwomechanizmów : wychylenia od min: 150 do 600 max. Na przykład dla servo1 z pwm0 to będzie 200 do 300. A dla servo1 z pwm1 to będzie 150 do 450. Plus jeśli to możliwe "prędkość serw jaką ma pokonać dystans między tym a tym kątem". Aż do servo16 (pwm0 jak i pwm1) możliwość późniejszej edycji parametrów wychylenia kątów i prędkości.
  • Zakomentowanie niektórych linijek kodu tak abym mógł edytować do woli różne parametry.

 

Biblioteka to:

https://github.com/adafruit/Adafruit-PWM-Servo-Driver-Library

 

Lista urządzeń to:

  • Mikrokontroler Arduino Mega 
  • 8 szt. micro servo TowerPro SG-90
  • 12-16 szt. standard servo Tower 5010 
  • 2 szt. 16-kanałowy sterownik serwomechanizmów PCA9685
  • 8 szt. Przełączniki dźwigniowe ON/OFF
  • 8 szt. Diody LED zielone 5mm + rezystory
  • 8 szt. Diody LED biały 3mm + rezystory
  • Zasilacz 5V do Arduino Mega
  • Zasilacz 5V do sterowników serwomechanizmów 5V z 30A lub 40A
  • Płyta stykowa (potem będzie zamieniona na uniwersalną płytkę do lutowania przewodów).

 

! Schemat elektroniczny zrobiony na szybko, ale tak wygląda moje podłączenie z moim panelem sterowania i serwomechanizmami (one będą poza panelem sterowania, jeśli to ważne).

W schemacie nie uwzględniłem wszystkich LEDów i przełączników bo schemat przysłaniałoby za dużo połączeń więc nie było nic praktycznie widać na zdjęciu.

Dlatego dałem 4 (ale ma być 16 podlaczonych LEDów i 8 przełączników).

Analogicznie wszystkie ledy są podłączone w ten sam sposób czyli: katoda do masy wspólnej GND, anoda do pinów mikrokontrolera. (lista pinów w kodzie).

 

Pozdrawiam

schemat-instalacji-artystycznej.png

Edytowano przez espiridion

Udostępnij ten post


Link to post
Share on other sites

Po pierwsze nie ma sensu aktualizować stanu serwa częściej niż to 60Hz. więc te pętle for inkrementujące/dekrementujące są tu zbędne. Co z tego, że inkrementujesz zawartość rejestru drivera kiedy driver i tak nie zaktualizuje szerokości impulsu częściej niż 60Hz.

Po drugie zrób jak Ci napisałem. Napisz sensowny obiekt do każdego odgałęzienia czyli przełącznik+serva+diody+delay z millisa i ikrementuj obiekty w pętli głównej - tak będzie najprościej. Można też ewentualnie użyć przerwań do obsługi stanów przełączników.

Zamiast inkrementować serwo po prostu wpisz w tą funkcję adafruita zadaną wartość i niech kręci z pełną prędkością.

 

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites

Nie wiem po co w ogóle ten delay tam jeśli ma to działać na zasadzie włącz/wyłącz. Zobacz ten przykład czy będzie Ci działał i gdyby to delay było Ci konieczne to też tam masz nieblokujące ale trzeba sprawdzać czy czas upłynął.

#include <Wire.h>
#include <Adafruit_PWMServoDriver.h>

#define ENABLE_DEBUG_OUTPUT 1
#define DEV_CNT             8   // count of devices
#define SERVOMIN            150 // this is the 'minimum' pulse length count (out of 4096)
#define SERVOMAX            600 // this is the 'maximum' pulse length count (out of 4096)
#define COUNTER_MIN         200
//#define MIN_ONSTATE_DELAY   500

Adafruit_PWMServoDriver pwm0 = Adafruit_PWMServoDriver(0x40);
Adafruit_PWMServoDriver pwm1 = Adafruit_PWMServoDriver(0x41);


class 
msDelay{
public: 
  msDelay(){delay_cnt = 0;} 
  msDelay(uint16_t delay_val):delay_cnt(delay_val){}
  inline void
  delay(unsigned long del){
    delay_cnt = del + millis();
  }
  inline bool 
  expire(void){
    if(delay_cnt > millis()) return false;
    else return true;   
  }
private:
  uint16_t delay_cnt;
};

typedef struct{
    uint8_t    sw,sv,l,cl;
    msDelay    dl; // if necesary 
    bool       state_flag;
}my_dev;
  
my_dev switch_nr[]=
//switch pin|pwm0 servo max|led pin|counter led pin 
    {{27,        450,          26,       36},
     {25,        320,          24,       38},
     {23,        500,          22,       40},
     {11,        280,          10,       42},
     {9,         480,          8,        28},
     {7,         550,          6,        30},
     {5,         250,          4,        32},
     {3,         370,          2,        34}};
     
void setup() {  
  Serial.begin(9600);
  for(uint8_t cnt=0; cnt<DEV_CNT; cnt++){
    pinMode(switch_nr[cnt].sw, INPUT_PULLUP);
    pinMode(switch_nr[cnt].l, OUTPUT);
    pinMode(switch_nr[cnt].cl, OUTPUT);
  }
  Wire.setClock(400000); // 400kHz I2C clock
   
  pwm0.begin();
  pwm0.setPWMFreq(60);  // Analog servos run at ~60 Hz updates 
                        // czyli aktualizacja co  16.666 ms
                        // nie ma więc sensu zmieniać 
                        // pozycji częściej niż ZADANY INTERWAŁ
                        // dekrementacja/inkrementacja NIE MA SENSU
 
  pwm1.begin();
  pwm1.setPWMFreq(60);  // Analog servos run at ~60 Hz updates
  delay(10);
}

void loop() {
  for(uint8_t cnt=0; cnt<DEV_CNT; cnt++){
          
      if(digitalRead(switch_nr[cnt].sw == LOW)){
      if(switch_nr[cnt].state_flag == true) continue;  
        digitalWrite(switch_nr[cnt].l, HIGH);
        digitalWrite(switch_nr[cnt].cl, HIGH);
        
        pwm1.setPWM(cnt, 0, SERVOMAX);
        pwm0.setPWM(cnt, 0, switch_nr[cnt].sv);

        switch_nr[cnt].state_flag = true;
      }else{
        
        pwm1.setPWM(cnt, 0, SERVOMIN);
        pwm0.setPWM(cnt, 0, COUNTER_MIN);

        digitalWrite(switch_nr[cnt].l, LOW); 
        digitalWrite(switch_nr[cnt].cl, LOW);

        switch_nr[cnt].state_flag = false;
      }
  }
    
}

 

Tak miało być

class 
msDelay{
public: 
  msDelay(){delay_cnt = 0;} 
  msDelay(uint16_t del):delay_cnt(del+millis){}
  inline void
  delay(unsigned long delay_val){
    delay_cnt = del + millis();
  }
  inline bool 
  expire(void){
    if(delay_cnt > millis()) return false;
    else return true;   
  }
private:
  unsigned long delay_cnt;
};

albo wywal drugi konstruktor całkiem.

 

 

delay_val może być też typu uint16_t. Jakby coś nie grało to pokombinuj i wybacz ale mam tego Twojego potykacza programistycznego serdecznie dość 😉

  • Pomogłeś! 1

Udostępnij ten post


Link to post
Share on other sites

Szanowny @atMegaTona , wielkie dzięki za podesłanie kodu.

Wygląda bardzo interesująco i jestem pod ogromnym wrażeniem twojej pomocy.😮 Narazie kombinuję z nim, ale zapowiada się bardzo dobrze.

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

Dlaczego diody nie są połączone z Arduino przez rezystory

Edytowano przez aerograf7

Udostępnij ten post


Link to post
Share on other sites

@aerograf7 Przecież na schemacie są diody podłączone przez rezystory.

  • Pomogłeś! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

@atMegaTona 

Tutaj wkradł się błąd:

if(digitalRead(switch_nr[cnt].sw == LOW)){

A powinno być tak:

if(digitalRead(switch_nr[cnt].sw) == LOW){

i wnet wszystko ożyło 🙂

Edytowano przez espiridion

Udostępnij ten post


Link to post
Share on other sites

W drugim przykładzie klasy msDelay kompilator mi pokazuje, że "del" nie jest zadeklarowane.

Udostępnij ten post


Link to post
Share on other sites

Więc zadeklaruj. Dodaj jeszcze


      }else{
        if(!switch_nr[cnt].state_flag) continue;  // <<-- lub if(switch_nr[cnt].state_flag == false) continue;
        pwm1.setPWM(cnt, 0, SERVOMIN);
        pwm0.setPWM(cnt, 0, COUNTER_MIN);

        digitalWrite(switch_nr[cnt].l, LOW); 
        digitalWrite(switch_nr[cnt].cl, LOW);

        switch_nr[cnt].state_flag = false;
      }

Nie musi tego kręcić bez potrzeby.

Żeby sterować prędkością poruszania serwa najlepiej użyć przerwania do wysyłania wartości do drivera co jakieś minimum kilkadziesiąt ms ale policz sobie najpierw sensowny skok (4096/(kąt wychyłu serwa))*(ileś tam stopni żeby mniej więcej równa wartość wyszła) trzeba dopasować żeby płynnie chodziło bo to zależy od prędkości maksymalnej serwa.

 

msDelay(uint16_t del):delay_cnt(del+millis()){}
// albo tak 
msDelay(uint16_t del){ delay_cnt = del + millis()}
//albo jeszcze tak
msDelay(void){}

 

  • Lubię! 1

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

@atMegaTona 

Zamiast

if (switch_nr[cnt].state_flag == true) continue;

dałem

if (switch_nr[cnt].state_flag == true && switch_nr[cnt].ld + RENEW_TIME > milis()) continue;

i mam ciągły ruch serwa w tą i tamtą stronę tak jak chciałem.

 

Ale mam jeszcze do Ciebie pytanie, mam nadzieje ostatnie bo nie chcę Cie męczyć 😳 bo zastanawiam się co robi klasa msDelay tutaj, chyba, że "del" źle zadeklarowałem?

public: 
  msDelay(){delay_cnt = 0;} 
  msDelay(uint16_t del):delay_cnt(del+millis){}
  uint16_t del;

 

Edytowano przez espiridion

Udostępnij ten post


Link to post
Share on other sites
(edytowany)

W klasie msDelay jest błąd - zła nazwa zmiennej. Żeby to uprościć i poprawić wywal konstruktory całkiem i zmień nazwę na poprawną i powinno być ok:

public: 
  msDelay(){delay_cnt = 0;} 
//msDelay(uint16_t del):delay_cnt(del+millis){}
  inline void
  delay(uint16_t del){
    delay_cnt = del + millis();
  }

teraz ustalasz sobie delay funkcją delay i sprawdzasz co obieg pętli for:

}else{
        if( !switch_nr[cnt].state_flag || !switch_nr[cnt].dl.expire() ) continue;

Teraz nawet jak przełącznik jest na off to i tak przejdzie do kolejnej iteracji pętli for jeśli przypisany do przełącznika czas nie upłynął. Nie trzeba już używać w kodzie funkcji millis() bo funkcja dl.delay(wartość_opóźnienia)  jej używa w swoim ciele.

Jeśli chcesz np. wykorzystać ten mechanizm w innej części programu lub w zupełnie innym to tworzysz sobie obiekt msDelay:

msDelay my_delay;
...
my_delay.delay(500);
...
if(my_delay.expire())led_off();

i ustalasz delay w jednej części programu a w innej np. w if(...) sprawdzasz czy czas upłynął. W powyższym przykładzie funkcja led_off() zostanie wykonana jeśli wcześniej zadany czas 500ms upłynął.

Np.

while(!switch_nr[cnt].dl.expire());

działał by jak zwykła blokująca funkcja delay(); i taka jest tu różnica. Obiekt msDelay nie blokuje wykonywania programu ale trzeba sprawdzać czy czas upłynął.

Mam nadzieję, że teraz jest już wszystko jasne.

------------------------------------------

Poprawiona klasa msDelay:

class 
msDelay{
public: 
  msDelay():delay_cnt(0){}; // można wywalić też
  inline void
  delay(uint16_t del){
    this->delay_cnt = del + millis();
  }
  inline bool 
  expire(void){
    if(this->delay_cnt > millis()) return false;
    else return true;   
  }
private:
  unsigned long delay_cnt;
};

 

Edytowano przez atMegaTona
  • Pomogłeś! 1

Udostępnij ten post


Link to post
Share on other sites

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Gość
Napisz odpowiedź...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.


×
×
  • Utwórz nowe...