Sähköinen johtoruuvi sorviin.

  • Viestiketjun aloittaja Viestiketjun aloittaja kkuula
  • Aloituspäivämäärä Aloituspäivämäärä
Nyt on yön yli nukuttua monikin asia kirkastunut.
Tuo piirikortin powerointi 24V syötöllä ja monimutkainen encooderien sisääntulokytkentä unohdetaan ja speksataan tässä käytettäväksi jotain määrätyn tyyppistä 5V jännitteellä toimivaa encooderia. USB-C antaa riittävästi tehoa.
MUTTA tässä käy niin että tarvittaneen kolme ulkoista virtalähdettä, askelmoottorille 50-70V, näytölle ja askelmoottorinohjaimelle 24V ja tälle ohjainlaitteelle 5V usb-C. Parasta olisi yksi töpseli seinään ja sillä selvä mutta kun ei taida niin vaan onnistua ja käy äkkiä että tämä laitteisto vaatiikin Jos käyttäisi askelmoottoria jossa integroitu ohjain olisi yksi poweroitava kikkare vähemmän mutta silti tuo näyttö OP320 taritsee 24V.
 
Senku ottaa jollain valmiilla kikkareella 5V->24V ?

muok: valmiilla kikkareella tarkoitan niitä alipertiltä löytyviä muutaman euron palikoita.
Ja jos kikkareelta joku "on/off" löytyy, on se kaikissa vastaantulleissa ollut "input"-puolen
volteilla, että monesta asiasta riippuen sillä vois helposti säätää laitteen käynnistystä
"jouheammaksi" hidastamalla näytön käynnistystä pisteeseen, jossa itse ohjain on jo vaikka
100% valmis vs. ohjain ja näyttö kilpailisivat boottinsa heti ku töpseliin jännite ilmestyy.
 
Viimeksi muokattu:
Nyt on yön yli nukuttua monikin asia kirkastunut.
Tuo piirikortin powerointi 24V syötöllä ja monimutkainen encooderien sisääntulokytkentä unohdetaan ja speksataan tässä käytettäväksi jotain määrätyn tyyppistä 5V jännitteellä toimivaa encooderia. USB-C antaa riittävästi tehoa.
MUTTA tässä käy niin että tarvittaneen kolme ulkoista virtalähdettä, askelmoottorille 50-70V, näytölle ja askelmoottorinohjaimelle 24V ja tälle ohjainlaitteelle 5V usb-C. Parasta olisi yksi töpseli seinään ja sillä selvä mutta kun ei taida niin vaan onnistua ja käy äkkiä että tämä laitteisto vaatiikin Jos käyttäisi askelmoottoria jossa integroitu ohjain olisi yksi poweroitava kikkare vähemmän mutta silti tuo näyttö OP320 taritsee 24V.
Jos löytäsit jostain servomoottorin ja ohjaimen joka syö step/dir signaalia niin tulisit luultavasti välttymään monelta harmilta.
 
Servo olisi hyvä mutta nyt on askelmoottori ja asennettuna ja sille kyllä on ohjain joka syö step/dir signaaleja. Tämä 5V tulisi usb:n kautta ja voinee olla vähimmillään 0,5A eli 2,5W joten siitä ei riittäne paljoa jakamista.
 
Miten olisi tietokoneen poweri? Sieltä löytyy jännitteitä 3.3v, 5v, 12v, 24v...
Niitä joutuisi kait sietää laittaa sitten kaksi sarjaan, että oikeasti hyödyllisen 24V irti sais, mutta tässä taitaa toi
kivikautisen usb:in 500mA olla joku kriteeri jostain syystä(jota en nyt itse väkisin yrittämälläkään keksi:leveahymy:).
 
Niitä joutuisi kait sietää laittaa sitten kaksi sarjaan, että oikeasti hyödyllisen 24V irti sais, mutta tässä taitaa toi
kivikautisen usb:in 500mA olla joku kriteeri jostain syystä(jota en nyt itse väkisin yrittämälläkään keksi:leveahymy:).
Itsellä tallissa puuhastelu virtalähteenä atx poweri ja tuollainen mokkula. Siinä on 12v ja - 12v jonka välistä saa jokusen ampeerin 24v.

Screenshot_20251104_072352_Chrome.webp
 
Tuo usb on tässä siksi koska se on jo piirilevyllä ja siitä saa suoraan 5V ja helposti 3,3V regulaattorilla ja nämä jännitteet on piirilevyllä pakollisia toiminnan kannalta. USB tarvitaan ainoastaan mikro-ohjaimen ohjelmointiin mutta sen voisi korvata erillisellä ohjelmointiliitännällä ja käyttää ulkoista usb ohjelmointipalikkaa.
Vähiten ulkoisia powereita tarvittaisiin vaihtoehdossa jossa laitetaan piirilevylle poweriosio millä tehdään tarvittavat jännitteet.

Vaihtoehtoja on liikaa jotta olisi yksiselitteinen paras vaihtoehto olemassa. 😀

Edit: mulla itseasiassa on tuollaisia palikoita atx powerille ja siitä tulikin yksi vaihtoehto lisää mieleen mitäpä jos laitankin itse suoraan tuon emolevyliittimen tälle omalle piirilevylle.
 
Sanoisin että tässä härvelissä sen näytön ja askelmoottoriohjaimen syötölle jokin puoli amppeeria on ihan riittävästi.
 
No niin sain tuon OP320 näyttö-näppäinpaneelin ja hintaan nähden vaikuttaa asialliselta. Tehneet vaan tämmösen kiusan tuon D9 liittimen kanssa, joutuu sukupuolenvaihtajan etsimään jostain..

IMG_9252.webp



IMG_9261.webp


IMG_9254.webp



IMG_9262.webp
 
Edistyy se tämäkin sen verran että sain ohjaimen piirilevyn suunniteltua valmiiksi ja tilattua. Hinnaksi kahdelle kappaleelle piirilevyjä tuli n.50€. Tähän tulee teholähteeksi yksi 48V 10A poweri askelmoottorille samasta tulee erillisellä buck convertterilla tehdään ensin 24V ja sitten 5V ja tuo ESP32 palikka tekee 3,3V. Tuohon powerointiin oli liikaa hyviä vaihtoehtoja ja tämä on nyt tehty siten että on helppo poweroida miten tahansa. Piirilevyn taakse tulee myös sarjaliikenteelle rs422/485<->TTL muunnokselle palikka.
Suunnittelun tein KiCad:lla ja on parempi mitä JLCPCB:n EasyEDA. Hieman tuossa KiCad:issa oli säätämistä jotta siihen sai tuon JLC:n komponenttikirjastot ja muuta asetukset leivottua sisään.

Screenshot 2025-12-29 115014.webp


Screenshot 2025-12-29 115758.webp
 
Raportoidaan edistysaskeleita, piirilevyt tulivat, heti tuli eteen yksi melkoinen bugi, tuon Mikro-ohjainpalikan jalanjälkikuva oli pikkusen liian kapea, tämä on osin ihan oma moka kun en sen mittoja tarkistanut vaan oletin nopealla googlaamisella löytämäni kuvan olevan sopiva mutta eipä ollut. Onneksi sentään pinnijärjestys oli oikein ja sen tarkistinkin moneen kertaan. Sai tuon kötöstettyä kumminkin kasaan. Toinen vastaantullut bugi oli yhden väärän IO pinnin käyttö sisääntulona mikä esti ohjelman lataamisen Uc:lle onneksi kyseessä on io mitä ilmankin pärjää ja ei taida edes koskaan tulla käyttöön.
Seuraava vaihe on kytkeä tuohon encooderi askelmoottori ja tuo näyttöpaneeli ja saada ne keskustelemaan sujuvasti keskenään.

IMG_9490.webp


IMG_9491.webp


IMG_9493.webp
 
Jälleen pientä edistymistä havaittavissa. Pari virhettä tuosta sunnittelsuta löytyi mutta ne sai korjattua ja nyt askelmoottori seuraa enkooderia nätisti.
Kokeilin myös pienellä koodinpätkällä Modbus protokollaa ja kyllähän se kommunikoi tuon näyttöpaneelin kanssa kuten pitikin. Kytkennät on vielä luokaa protomallia ja mulla on tuolle jo sopiva kotelo varattuna. käyttöliittymä pääsosin tuon näyttöpaneelin kautta mutta varattuna on kolme yleiskäyttöistä siääntuloa esimerkiksi nappia tai kytkintä varten.

 
Kuten arvelinkin, isoin työ on ollut tämän mikro-ohjaimen koodin väännössä. Ensimmäisen kerran temusin tämän aiheen kanssa 5-6 vuotta sitten ja silloin totesin että ei onnistu oman osaamisen ja ajan puitteissa. Tuossa viime syksynä kokeilin koodaamista tekoälyn avulla ja tulin heti tulokseen että tämän ohjelmointi onnistuu helposti sen avulla. Koodin ja käyttöliittymän kehittelyyn on nyt mennyt rapiat kolmen viikon vapaa-ajat. Tuossa alla on Geminillä teettämäni koodi, käytännössä en ole itse kirjoittanut tuosta ensimmäistäkään merkkiä vaan aloin kertomaan Geminille yksityiskohtia jonka jälkeen alkoi välittömästi tulla valmista koodia. Tuo koodi kääntyy Arduino ympäristössä ja saa käyttää vapaasti jos siitä on jollekin iloa.

Koodi:
/* Electronic Lead screw By Antti Logren
   VERSION ELS_9.3 - Fixed-Point Math Optimization & 1s RPM Stability
*/

#include <ModbusRTUSlave.h>
#include <ESP32Encoder.h>
#include <Preferences.h>

// --- PINS ---
#define ENCODER_A 10
#define ENCODER_B 11
#define STEP_PIN   41
#define DIR_PIN    40
#define EN_PIN     42
#define MODBUS_TX 13
#define MODBUS_RX 14
#define BUTTON_PIN 48
#define POT_PIN    18

ESP32Encoder encoder;
ModbusRTUSlave modbus(Serial1);
Preferences prefs;

enum SystemState { STATE_IDLE, STATE_FEED, STATE_THREAD, STATE_IMPERIAL };
volatile SystemState currentState = STATE_IDLE;

// --- FIXED POINT SCALING ---
const uint32_t SHIFT_FACTOR = 16;
const uint32_t SCALE_VALUE = (1 << SHIFT_FACTOR); // 65536

// --- MODBUS MEMORY MAP ---
bool coils[9] = {false, false, false, false, false, false, false, false, false};
bool discreteInputs[9] = {false, false, false, false, false, false, false, false, false};
uint16_t holdingRegisters[11] = {1, 25, 150, 1440, 3175, 400, 400, 8, 0, 0, 0};
uint16_t inputRegisters[11] = {0, 0, 0, 1440, 3175, 400, 400, 0, 0, 0, 0};

// --- SHARED VARIABLES ---
volatile uint32_t stepsPerCountFixed = 0; // Fixed-point ratio
volatile long lastPosition = 0;
float encoderCPR = 1440.0;
float leadscrewPitch_mm = 3.175;
float leadscrewTPI = 8.0;
float stepperStepsPerRev = 400.0;
float stepperGearReduction = 4.0;
bool isImperialLeadscrew = true;

unsigned long lastRPMCheck = 0;
long lastEncoderCountRPM = 0;

const uint16_t threadTable[] = {35, 40, 45, 50, 60, 70, 75, 80, 90, 100, 125, 150, 175, 200, 250, 300};
const uint16_t tpiTable[] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 24, 28, 32};

TaskHandle_t MotionTask;

// --- UNIVERSAL RATIO CALCULATIONS ---
void updateRatioMetric(float target_mm) {
  if (encoderCPR <= 0) return;
  float ratio = (target_mm / leadscrewPitch_mm) * (stepperStepsPerRev / encoderCPR) * stepperGearReduction;
  stepsPerCountFixed = (uint32_t)(ratio * SCALE_VALUE);
}

void updateRatioImperial(float target_tpi) {
  if (encoderCPR <= 0 || target_tpi <= 0) return;
  float ratio;
  if (isImperialLeadscrew) {
    ratio = (leadscrewTPI / target_tpi) * (stepperStepsPerRev / encoderCPR) * stepperGearReduction;
  } else {
    float target_mm = 25.4 / target_tpi;
    ratio = (target_mm / leadscrewPitch_mm) * (stepperStepsPerRev / encoderCPR) * stepperGearReduction;
  }
  stepsPerCountFixed = (uint32_t)(ratio * SCALE_VALUE);
}

// --- MOTION TASK (CORE 1) ---
void motionLoop(void * pvParameters) {
  uint32_t stepAccumulator = 0;
  for(;;) {
    if (currentState != STATE_IDLE && stepsPerCountFixed > 0) {
      long currentPosition = encoder.getCount();
      if (currentPosition != lastPosition) {
        long deltaCounts = currentPosition - lastPosition;
        lastPosition = currentPosition;
        
        bool finalDir = (deltaCounts > 0 ? LOW : HIGH) ^ discreteInputs[3];
        digitalWrite(DIR_PIN, finalDir);
        
        // Fixed point accumulation: No float math in the hot loop
        stepAccumulator += abs(deltaCounts) * stepsPerCountFixed;
        
        // While accumulator is greater than or equal to our scale (1.0)
        while (stepAccumulator >= SCALE_VALUE) {
          digitalWrite(STEP_PIN, HIGH);
          ets_delay_us(2); // Reduced for higher frequency capability
          digitalWrite(STEP_PIN, LOW);
          ets_delay_us(2);
          stepAccumulator -= SCALE_VALUE;
        }
      }
    } else {
      lastPosition = encoder.getCount();
      stepAccumulator = 0;
    }
    yield();
  }
}

void setup() {
  Serial.begin(115200);
  Serial1.begin(9600, SERIAL_8N1, MODBUS_RX, MODBUS_TX);
 
  prefs.begin("els_params", false);
  encoderCPR = prefs.getFloat("cpr", 1440.0);
  leadscrewPitch_mm = prefs.getFloat("pitch", 3.175);
  stepperStepsPerRev = prefs.getFloat("steps", 400.0);
  stepperGearReduction = prefs.getFloat("gear", 4.0);
  isImperialLeadscrew = prefs.getBool("is_imp", true);
  leadscrewTPI = 25.4 / leadscrewPitch_mm;
 
  // Sync Modbus UI
  coils[8] = isImperialLeadscrew;
  discreteInputs[8] = isImperialLeadscrew;
  inputRegisters[3] = (uint16_t)encoderCPR;
  inputRegisters[4] = isImperialLeadscrew ? (uint16_t)leadscrewTPI : (uint16_t)(leadscrewPitch_mm * 1000.0);
  inputRegisters[5] = (uint16_t)stepperStepsPerRev;
  inputRegisters[6] = (uint16_t)(stepperGearReduction * 100.0);
 
  holdingRegisters[3] = inputRegisters[3];
  holdingRegisters[4] = inputRegisters[4];
  holdingRegisters[5] = inputRegisters[5];
  holdingRegisters[6] = inputRegisters[6];

  encoder.attachFullQuad(ENCODER_A, ENCODER_B);
  pinMode(STEP_PIN, OUTPUT); pinMode(DIR_PIN, OUTPUT); pinMode(EN_PIN, OUTPUT);
  digitalWrite(EN_PIN, HIGH);
  pinMode(BUTTON_PIN, INPUT_PULLUP);
 
  modbus.configureCoils(coils, 9);
  modbus.configureDiscreteInputs(discreteInputs, 9);
  modbus.configureHoldingRegisters(holdingRegisters, 11);
  modbus.configureInputRegisters(inputRegisters, 11);
  modbus.begin(1, 9600);

  xTaskCreatePinnedToCore(motionLoop, "MotionTask", 10000, NULL, 1, &MotionTask, 1);
}

void loop() {
  modbus.poll();
  uint16_t pageID = holdingRegisters[0];

  // --- 1. SETTINGS LOGIC ---
  if (pageID >= 5 && pageID <= 8) {
    if (coils[3]) {
      switch(pageID) {
        case 5: encoderCPR = (float)holdingRegisters[3]; prefs.putFloat("cpr", encoderCPR); inputRegisters[3] = holdingRegisters[3]; break;
        case 6:
          isImperialLeadscrew = coils[8];
          if (isImperialLeadscrew) { leadscrewTPI = (float)holdingRegisters[4]; leadscrewPitch_mm = 25.4 / leadscrewTPI; }
          else { leadscrewPitch_mm = (float)holdingRegisters[4] / 1000.0; leadscrewTPI = 25.4 / leadscrewPitch_mm; }
          prefs.putFloat("pitch", leadscrewPitch_mm); prefs.putBool("is_imp", isImperialLeadscrew);
          inputRegisters[4] = holdingRegisters[4]; discreteInputs[8] = isImperialLeadscrew; break;
        case 7: stepperStepsPerRev = (float)holdingRegisters[5]; prefs.putFloat("steps", stepperStepsPerRev); inputRegisters[5] = holdingRegisters[5]; break;
        case 8: stepperGearReduction = (float)holdingRegisters[6] / 100.0; prefs.putFloat("gear", stepperGearReduction); inputRegisters[6] = holdingRegisters[6]; break;
      }
      discreteInputs[6] = true; coils[3] = false;
    }
  } else { discreteInputs[6] = false; }

  // --- 2. RPM CALC (1s intervals) ---
  if (pageID == 9) {
    unsigned long now = millis();
    if (now - lastRPMCheck >= 1000) {
      long currentCount = encoder.getCount();
      long delta = abs(currentCount - lastEncoderCountRPM);
      // RPM = (Delta * 60) / CPR
      inputRegisters[10] = (uint16_t)((delta * 60.0) / encoderCPR);
      lastEncoderCountRPM = currentCount;
      lastRPMCheck = now;
    }
  }

  // --- 3. HW DIRECTION & POTENTIOMETER ---
  discreteInputs[3] = (digitalRead(BUTTON_PIN) == LOW);
  if (coils[0]) {
    int pot = analogRead(POT_PIN);
    static int lastPot = 0;
    if (abs(pot - lastPot) > 50) {
      lastPot = pot;
      if (pageID == 2) holdingRegisters[1] = (uint16_t)((map(pot, 0, 4095, 5, 100) + 2) / 5) * 5;
      else if (pageID == 3) holdingRegisters[2] = threadTable[map(pot, 0, 4095, 0, 15)];
      else if (pageID == 4) holdingRegisters[7] = tpiTable[map(pot, 0, 4095, 0, 17)];
    }
  }

  // --- 4. STATE MACHINE ---
  if (currentState == STATE_IDLE) {
    if (pageID == 2 && coils[1]) { updateRatioMetric((float)holdingRegisters[1] / 100.0); currentState = STATE_FEED; digitalWrite(EN_PIN, LOW); discreteInputs[4] = true; }
    else if (pageID == 3 && coils[2]) { updateRatioMetric((float)holdingRegisters[2] / 100.0); currentState = STATE_THREAD; digitalWrite(EN_PIN, LOW); discreteInputs[5] = true; }
    else if (pageID == 4 && coils[4]) { updateRatioImperial((float)holdingRegisters[7]); currentState = STATE_IMPERIAL; digitalWrite(EN_PIN, LOW); discreteInputs[7] = true; }
  } else {
    if ((currentState == STATE_FEED && !coils[1]) || (currentState == STATE_THREAD && !coils[2]) || (currentState == STATE_IMPERIAL && !coils[4])) {
      currentState = STATE_IDLE; digitalWrite(EN_PIN, HIGH); discreteInputs[4] = discreteInputs[5] = discreteInputs[7] = false;
    }
  }
}

IMG_9580.webp
 
Seuraavaksi pitäisi keksiä kuinka kytken encooderin sorvin perään, kuva sorvin ratasvetopäästä ja pääakselissa on 25 hammastus, tilasin samankokoisen hamamspyörän ja ajatus oli ensin että kytken hammaspyörän suoraan encooderin akselille ja sille paketille kiinnitys encooderin rungosta sorvin runkoon. Sitten tuli mieleen että noinkohan on hyvä ajatus? Piirisin toisen vaihtoehdon jossa hammaspyörä on laakeroitu sorvin sunkoon kiinnetylle akselille ja encooderi kytketyisi siihen joustoliitoksella. Tulisi itseasiassa tuon alkuperäisen suunnanvaihtokeinun toisen hammaspyöärän tilalle. Ehsotuksia?
Näyttökuva 2026-02-05 201859.webp


IMG_9605.webp
 

Luo tili tai kirjaudu sisään kommentoidaksesi

Sinun täytyy olla jäsen voidaksesi jättää kommentin.

Luo käyttäjätili

Liity Konekansalaiseksi. Se on helppoa ja ilmaista! Rekisteröityneenä et näe mainoksia, voit käyttää hakua, näet alueita, joita nyt ovat piilossa...jne.

Kirjaudu sisään

Oletko jo Konekansan jäsen? Kirjaudu sisään tästä.

Takaisin
Ylös