Gå til innhold
  • Bli medlem
Støtt hjemmeautomasjon.no!

ESP32 MQTT basert elektrisk rullegardin på 12V


Jeg har en del rullegardiner fra IKEA som er automatisert med Rollertol motorer.

Her om dagen gikk en istykker og jeg hadde lite lyst til å bestille en ny fra USA. - Dyre er de også.

 

Men jeg hadde nettop oppdaget hvor lett det er å designe og 3D printe tannhjul (Jeg bruker OnShape men regner med at Fusion 360 er like enkelt) så jeg tenkte at her var et fint byggeprosjekt.

 

Jeg hadde kjøpt noen artige motorer fra Aliexpress for en stund siden som kunne passe til slikt bruk.

https://www.aliexpress.com/item/32855724293.html

Den jeg bruker her er 50RPM.

 

Disse er giret kraftig ned med planet-gir og har rotasjons sensorer som gir 11 pulser / rotasjon.

 

image.png image.png

 

Dette gjør at de er mye sterkere enn en stepper motor men kan posisjoneres like nøyaktig ved å telle pulser.

 

Fasongen inviterer til å plassere den i et klikk-on stativ som dette:

(Slutt-versjonen måtte ha enda en klemme for å holde den helt fast)

image.png

 

Tannhjulene ble laget for å kunne settes inn i enden av rullegardinen og et passende veggfeste ble printet:

 

image.png  image.png

 

Siden det kun var et museklikk å lage tannhjulene heliske (skrå tenner) måtte jeg prøve dette. (Jeg hadde hørt at en gjorde slikt i girkasser for at de skulle lage mindre lyd)

image.png

 

Dette ga meg to raske lærdommer:
- Tannhjulene som skal gripe inn i hverandre må ha motsatt vinkling

- Når de belastes vil de få en skyvekraft sidelengs

Det siste bruker jeg nå aktivt for å forhindre at tannhjulet på motoren trekkes av akslingen når gardinen kjøres opp.

 

Denne L298N motor-driveren tar 12V og er litt overdimensjonert men har også en 5V utgang som er fin å mate ESP32'en med.

 

image.png

 

Jeg hadde bestemt meg for at jeg skulle ha 3 knapper: OPP, NED og SET

 

I drift skulle knappene kjøre gardinen (helt) opp og (helt) ned og SET knappen skulle sette den i SETUP mode.

 

I setup mode skulle knappene kjøre gardinen opp og ned så lenge de ble holdt inne.

Først for å finne TOP posistion og så kvittere denne ved å trykke SET

og deretter finne BUNN posisjonen og kvittere denne ved å trykke SET på nytt.

 

En RGB LED skulle kommunisere hvilken modus du var i.

 

Så var det bare å printe en passende boks, med skrue hull, for å putte hele greia inn i:

 

image.png  image.png

 

Koblingen var sånn:

 

image.png

 

For å unngå at jeg måtte sette oppigjen stopp posisjonene i topp og bunn dersom strømmen forsvant skrev jeg disse (=denne, top er uansett alltid 0) ned i Flash memory.

Jeg bestemte derfor også at gardinen ikke skulle kunne stoppes halveis og kun var enten åpen eller lukket. Denne statusen (Oppe eller Nede) ble også skrevet til Flash.

Flash tillater kun 100 000 skrivinger så om jeg kjører gardinen 3 ganger om dagen vil den kun holde i ca 100 år...

 

Motor pulsene telles med et Interupt på pinne 25.

 

Knappene bruker også en Interupt-timer for å slippe å håndtere rippel.

For hver 100ms sjekker jeg om knappene er av eller på. Er de aktive teller jeg en teller OPP er de passive teller jeg en teller NED.

Blir telleren 4 setter jeg knappen som AKTIV, blir den -4 setter jeg den som inaktiv.

 

LED'en fikk kun en motstand felles (latskap) så den tåler ikke at mer enn en farge skrus på om gangen.
 

Selve logikken er en tilstands maskin med disse tilstandene:

SETUP_DEFINING_TOP,
SETUP_DEFINING_BOTTOM,
SETUP_STOP_SET_PRESSED_TOP,
SETUP_STOP_SET_PRESSED_BOTTOM,
MOVING,
STOPPED_TOP,
STOPPED_BOTTOM,
STOP_SET_PRESSED

 

I tillegg holdes det styr på rettning:

MOVING_UP,
MOVING_DOWN,
STOPPED

 

Da er det også enkelt å vite om pulsene som kommer fra motoren skal telles opp eller ned.

Motoren har riktignok 2 sensorer som står i forskjellig vinkel på akslingen så det ville være mulig å måle avstanden mellom pulsene for å avgjøre rettning men dette er MYE lettere.

 

MQTT biblioteket jeg fant var så enkelt å bruke at det nesten var latterlig.

 

Jeg burde selvfølgelig rydde masse i koden og skrive pedagogiske kommentarer for å vise alle hva for en dreven og fantastisk programmerer jeg (egentlig) er men det gidder jeg ikke nå, sorry. Dere får heller spørre om dere lurer på noe.

 

Jeg gadd heller ikke å lage noe fancy setup automatikk med oppstarts AP og Webside for å sette IP og velge SSID.
(Post gjerne slik kode som jeg kan stjele til neste gang)

 

Her er koden:

 

#include <Preferences.h>
#include <EspMQTTClient.h>

Preferences preferanser;

EspMQTTClient client(
  "SSID",
  "PWD",
  "192.168.232.8",  // MQTT Broker server ip
  "",   // MQTTUsername Can be omitted if not needed
  "",   // MQTTPassword Can be omitted if not needed
  "rullegardinClient",     // Client name that uniquely identify your device
  1883              // The MQTT port, default to 1883. this line can be omitted
);

// MQTT
int receivedCommand=0; // 1=UP 2=Down 0=Executed

// Motor
int motorPin1 = 14; 
int motorPin2 = 12; 
int enablePin = 13; 


// Interupt 
portMUX_TYPE sync1 = portMUX_INITIALIZER_UNLOCKED;
// External
int IntPin1 = 25; 
// Timer
hw_timer_t * timer = NULL;

// Switches
const int swPinUp = 23; 
const int swPinDown = 22; 
const int swPinStopSet = 21; 
volatile bool swUp = false;
volatile bool swDown = false;
volatile bool swStopSet = false;

// LED
const int RED = 5; 
const int BLUE = 18; 
const int GREEN = 19; 

// Modes
enum stateType {
SETUP_DEFINING_TOP,
SETUP_DEFINING_BOTTOM,
SETUP_STOP_SET_PRESSED_TOP,
SETUP_STOP_SET_PRESSED_BOTTOM,
MOVING,
STOPPED_TOP,
STOPPED_BOTTOM,
STOP_SET_PRESSED,
};

// Direction
enum directionType {
MOVING_UP,
MOVING_DOWN,
STOPPED
};

//VARS
volatile stateType state;
volatile directionType movingDirection;

volatile long stepCount=0; //0= På toppen
volatile long stepMax=0;
volatile int countUpSW =0; 
volatile int countDownSW = 0; 
volatile int countStopSetSW = 0; 

unsigned long lastTime=0;

char lastPos;

//------------- Interupt Routine Steps -----------------
void IRAM_ATTR isr_steps() {
 portENTER_CRITICAL(&sync1);
 if (movingDirection==MOVING_DOWN) { 
  stepCount++;  
 } else if (movingDirection==MOVING_UP) { 
  stepCount--;  
 }
 if (stepCount<0)  stepCount=0 ; // cap on 0
 portEXIT_CRITICAL(&sync1);
}

//------------- Interupt Routine Timer -----------------
void IRAM_ATTR isr_timer() {


 portENTER_CRITICAL(&sync1);
 if (digitalRead(swPinUp)==LOW) {
  countUpSW++;
  if (countUpSW > 4) {
    swUp = true;
    countUpSW=0;
  }
 } else {
  countUpSW--;
  if (countUpSW < -4) {
    swUp = false;
    countUpSW=0;
  }
 }

 if (digitalRead(swPinDown)==LOW) {
  countDownSW++;
  if (countDownSW > 4) {
    swDown = true;
    countDownSW=0;
  }
 } else {
  countDownSW--;
  if (countDownSW < -4) {
    swDown = false;
    countDownSW=0;
  }
 }

 if (digitalRead(swPinStopSet)==LOW) {
  countStopSetSW++;
  if (countStopSetSW > 4) {
    swStopSet = true;
    countStopSetSW=0;
  }
 } else {
  countStopSetSW--;
  if (countStopSetSW < -4) {
    swStopSet = false;
    countStopSetSW=0;
  }
 }

 portEXIT_CRITICAL(&sync1);
}

//************************* INIT *********************
void setup() {

   // LED pins
  pinMode(RED, OUTPUT);
  pinMode(GREEN, OUTPUT);
  pinMode(BLUE, OUTPUT);

  digitalWrite(RED, HIGH);

  preferanser.begin("Prefs",false);
  stepMax=preferanser.getLong("stepMax",-1); // -1= not init before
  if (stepMax==-1) {
      state=SETUP_DEFINING_TOP;
  } else {
    lastPos=preferanser.getChar("position",' ');
    if (lastPos=='T') {
      state=STOPPED_TOP;
      client.publish("rullegardin/position", "TOP"); 
    } else if (lastPos=='B') {
      state=STOPPED_BOTTOM;
      client.publish("rullegardin/position", "BOTTOM"); 
    } else { // error or default
      state=SETUP_DEFINING_TOP;
    }
  }

  // Motor pins
  pinMode(motorPin1, OUTPUT);
  pinMode(motorPin2, OUTPUT);
  pinMode(enablePin, OUTPUT);

  // init interupt
  pinMode(IntPin1, INPUT);
  attachInterrupt(IntPin1,&isr_steps,FALLING);

    // Interupt timer
  timer = timerBegin(0,240,true); // 240Mhz
  timerAttachInterrupt(timer,&isr_timer,true);
  timerAlarmWrite(timer, 100, true); // 100ms ?
  timerAlarmEnable(timer);

  // SW
  pinMode(swPinUp, INPUT_PULLUP);
  pinMode(swPinDown, INPUT_PULLUP);
  pinMode(swPinStopSet, INPUT_PULLUP);

  // Motor Enable
  digitalWrite(enablePin,HIGH );

  // testing
  Serial.begin(9600);
  Serial.println("Testing DC Motor...");

  client.enableDebuggingMessages();
  client.enableLastWillMessage("rullegardin/lastwill", "I am going offline"); 

   digitalWrite(RED, LOW);
   digitalWrite(GREEN, HIGH);
   digitalWrite(BLUE, LOW);

}

//*********************************** ROUTINES 

void move_down() {
  movingDirection=MOVING_DOWN;
  digitalWrite(motorPin1,HIGH); 
  digitalWrite(motorPin2,LOW);
}

void move_up() {
  movingDirection=MOVING_UP;
  digitalWrite(motorPin1,LOW); 
  digitalWrite(motorPin2,HIGH);
}

void stop_moving() {
  movingDirection=STOPPED;
  digitalWrite(motorPin1,LOW); 
  digitalWrite(motorPin2,LOW);
}

void update_leds() {
  // LED
  if (state==SETUP_DEFINING_TOP){
     digitalWrite(RED, LOW);
     digitalWrite(GREEN, HIGH);
     digitalWrite(BLUE, LOW);
  }
  if (state==SETUP_DEFINING_BOTTOM){
     digitalWrite(RED, LOW);
     digitalWrite(GREEN, LOW);
     digitalWrite(BLUE, HIGH);
  } else {
     digitalWrite(RED, LOW);
     digitalWrite(GREEN, LOW);
     digitalWrite(BLUE, LOW);
  }
}

// ************************ LOOP ***********************
void loop() {
  // MQTT
  client.loop();

   
      
 // Debug
    if (millis()-lastTime>200) {
      Serial.print("State:");
      Serial.print(state);
      Serial.print(" moving:");
      Serial.print(movingDirection);
      Serial.print(" Cnt:");
      Serial.print(stepCount);
      Serial.print(" Max:");
      Serial.print(stepMax);

      Serial.print(" UP:");
      Serial.print(swUp);
      Serial.print(" Dwn:");
      Serial.print(swDown);
      Serial.print(" StopSet:");
      Serial.print(swStopSet);

      Serial.println("");
      lastTime=millis();
    }

  update_leds();


  // Final state machine
  // SETUP
  if (state==SETUP_DEFINING_TOP) { // --------------
    if ((movingDirection==STOPPED) && swStopSet) {
      // Set top pos
      stepCount=0;      
      state=SETUP_STOP_SET_PRESSED_TOP;
    } else if (swUp) {
      move_up();
    } else if (swDown) {
      move_down();
    } else { // no buttons pressed
      stop_moving();      
    }
  } else if (state==SETUP_DEFINING_BOTTOM) { // --------------
    if ((movingDirection==STOPPED) && swStopSet) {
      // Set bottom pos
      stepMax=stepCount;
      preferanser.putLong("stepMax",stepMax); //save for next boot
      Serial.println("Saving stepMax");
      state=SETUP_STOP_SET_PRESSED_BOTTOM;
    } else if (swUp) {
      move_up();
    } else if (swDown) {
      move_down();
    } else { // no buttons pressed
      stop_moving();      
    }
  } else if (state==SETUP_STOP_SET_PRESSED_TOP) { // --------------
    if (!swStopSet) { // released
      state=SETUP_DEFINING_BOTTOM;
    }  
  } else if (state==SETUP_STOP_SET_PRESSED_BOTTOM) { // --------------
    if (!swStopSet) { // released
      move_up();
      state=MOVING;
    }  
  // NOT SETUP
  } else if (state==MOVING) { // --------------
    if (swStopSet) {
      stop_moving();
      state=SETUP_DEFINING_TOP;
    } else if (swUp) {
      move_up();
      state=MOVING;
    } else if (swDown) {
      move_down();
      state=MOVING;
    } 
    // Ende stop?
    if (stepCount<=0 && movingDirection==MOVING_UP) {  
      stop_moving();
      preferanser.putChar("position",'T');
      Serial.println("Saving Position = T");
      state=STOPPED_TOP;
      client.publish("rullegardin/position", "TOP"); 
    } else if (stepCount>stepMax && movingDirection==MOVING_DOWN) {  
      stop_moving();
      preferanser.putChar("position",'B');
      Serial.println("Saving Position = B");
      state=STOPPED_BOTTOM;
      client.publish("rullegardin/position", "BOTTOM"); 
    }
  } else if (state==STOPPED_TOP) { // --------------
    if (swDown) {
      move_down();
      state=MOVING;
    } else if (swStopSet) {
      state=STOP_SET_PRESSED;
    }  else if (receivedCommand==2) {
      move_down();
      state=MOVING;
      receivedCommand=0;
    }
  } else if (state==STOPPED_BOTTOM) { // --------------
    if (swStopSet) {
      state=STOP_SET_PRESSED;
    } else if (swUp) {
      move_up();
      state=MOVING;
    } else if (receivedCommand==1) {
      move_up();
      state=MOVING;
      receivedCommand=0;
    }
  } else if (state==STOP_SET_PRESSED) { // --------------
    if (!swStopSet) { // release button
      state=SETUP_DEFINING_TOP;
    }  
  } else {
    Serial.println("ERROR Unknown state");
  }
}


void onConnectionEstablished()
{
  // Subscribe to "mytopic/test" and display received message to Serial
  client.subscribe("rullegardin/command", [](const String & payload) {
    Serial.println(payload);
    receivedCommand=payload.toInt();
  });

/*  // Subscribe to "mytopic/wildcardtest/#" and display received message to Serial
  client.subscribe("mytopic/wildcardtest/#", [](const String & topic, const String & payload) {
    Serial.println("(From wildcard) topic: " + topic + ", payload: " + payload);
  });
*/
  // Publish a message to "mytopic/test"
  client.publish("rullegardin/position", "startUp"); // You can activate the retain flag by setting the third parameter to true

/*  // Execute delayed instructions
  client.executeDelayed(5 * 1000, []() {
    client.publish("mytopic/wildcardtest/test123", "This is a message sent 5 seconds later");
  });
*/
}

 

 

 

image.png

 

 

 

  • Like 9
  • Thanks 5

4 kommentarer


Anbefalte kommentarer

julenissemannen

Skrevet

flott prosjekt.. har tenkt på det samme en stund da jeg har mange lignende gardiner :)

Fermate

Skrevet

Jon Olav Neset på FB IOT forumet postet dette designet.
Jeg må si at det virker som en bedre teknisk løsning enn min:

image.png

aarpi3

Skrevet

On 20/03/2022 at 10:25, Fermate said:

Jon Olav Neset på FB IOT forumet postet dette designet.
Jeg må si at det virker som en bedre teknisk løsning enn min:
 

 

Det ser ut som et design som ligger på https://www.thingiverse.com/thing:2392856

Og jeg har laget den med en med en standard 28BYJ-48 stepper motor. Den er IKKE sterk nok :( 

  • Like 1
  • Thanks 1
Fermate

Skrevet

aarpi3 skrev (På 27.3.2022 den 17.40):

Og jeg har laget den med en med en standard 28BYJ-48 stepper motor. Den er IKKE sterk nok :( 


Han sa at han brukte 12V versjonen. Er det 5V du snakker om der?

 

 

Kunne nok muligens gire den anderledes da?
(Lage andre 3D tannhjul )

Gjest
Skriv en kommentar...

×   Du har limt inn tekst med formatering.   Lim inn uten formatering i stedet

  Du kan kun bruke opp til 75 smilefjes.

×   Lenken din har blitt bygget inn på siden automatisk.   Vis som en ordinær lenke i stedet

×   Tidligere tekst har blitt gjenopprettet.   Tøm tekstverktøy

×   Du kan ikke lime inn bilder direkte. Last opp eller legg inn bilder fra URL.

×
×
  • Opprett ny...

Viktig informasjon

Vi har plassert informasjonskapsler/cookies på din enhet for å gjøre denne siden bedre. Du kan justere dine innstillinger for informasjonskapsler, ellers vil vi anta at dette er ok for deg.