Controllo motore Nema17
Nano-TMC2209-ST7735
Contenuti: |
1 |
 |
Come controllare il motore passo-passo con il driver DRV8825 e Arduino |
2 |

|
|
3 |
 |
|
4 |
 |
|
Vorrei pilotare con arduino un motore Nema17 utilizzando il driver TMC2209, un monitor ST7735 per visualizzare i dati che avrà bisogno di un convertitore logico TXS0108E e 2 Moduli step down XL4015 per le alimentazioni, due condensatorei uno elettrolitico ed uno ceramico per stabilizzare l'alimentazione.
Sul monitor ho le seguenti informazioni: in alto viene indicato il passo impostato da 1 a 1/256, sulla successiva riga viene indicato il moltiplicatore un numero a 4 cifre che moltiplica il valore dell'encoder, poi abbiamo due frecce una con punta a sinistra ed una con punta a destra che indicano la direzione di rotazione, visibili alternativamente. In fine, nell'ultima riga viene indicato l'RPM (i giri per minuto).
Quando si accende il sistema la situazione di partenza è la seguente:
- Passo impostato ad 1 (passo intero)
- Moltiplicatore ad 1
- Direzione oraria, freccia verso destra
- RPM 0000
Procediamo con le impostazioni:
- Con l'encoder a rotella rosso sulla destra andiamo ad impostare il passo, potremo lasciarlo ad 1 per ora
- Con l'encoder di sinistra andiamo ad impostare il moltiplicatore, per default è impostato a 1, premendo il pulsante con un click si attiva la modalità modifica, il primo digit a sinistra inizia a lampeggiare, ruotando l'encoder possiamo impostare il valore da 0 a 9, con un'altro click si attiva il digit successivo sulla destra, il precedete si fissa, conservando il valore impostato, l'attuale inizia a lampeggiare, ruotando posso settare il valore numerico. Di click in click raggiunto il quarto digit, al click successivo si tornera al primo digit e così via. Per uscire dalla modalità modifica eseguire un long press, a questo punto avremo impostato il moltiplicatore ad es. a 10 il che significa che se facciamo uno scatto con l'encoder di destra in realtà è come se ne facessimo 10, questo rende l'encoder della velocità più reattivo
- Con l'encoder di destra posso:
- Controllare la velocità: ruotandolo, i valori impostabili rimangono all'interno di un ramge impostato nel codice ad es tra 0 e 3000, quando raggiunto decrementando lo zero ruotando ulteriormente l'encoder il valore rimane fisso a 0, quando raggiunto il valore 3000 un'ulteriore rotazione verso destra non avrà effetto il valore rimarrà fisso su 3000
- Impostare la direzione: con un long press del pulsante, ad ogni pressione la direzione cambierà verso
- Fermare il motore: eseguendo un semplice click la velocità viene impostata a zero il motore si ferma
I valori sullo schermo si aggiorneranno in tempo reale.
Materiale necessario:
- Arduino Nano
- 2 Moduli step down XL4015
- Motore passo-passo Nema 17 (1,7 A, 0,59 Nm)
- Drive TMC2209 vedi
- Condensatori: un 100µF elettrolitico 50V, un ceramico da 100nF (0.1µF) 50V
- Resistenza da 220 Ohm
- Tre encoder rotativi
- Monotor LCD ST7735
- TXS0108E Logic level converter
- Un alimentatore a 24V per alimentare il motore
- 3 interruttori

Schema di collegamento:
TMC2209
Lato sinistro:
- ENABLE non utilizzato, rimane libero
- MS1 (Microstep mode 1): libero
- MS2 (Microstep mode 2): libero
- ---R8 duplicato del pin UART lasciare libero
- UART collegare al pin TX di Arduino mediante resistenza da 220 Ohm
- NC non utilizzato
- STP al Pin 3 per gli step
- DIR al Pin 2 per la direzione
Lato destro:
- VM al positivo dell'alimentatore a 24 V tramite i due condensatori
- GND al negativo dell'alimentatore a 24 V tramite i due condensatori
- 2B filo blu motore
- 2A filo rosso motore
- 1A filo nero motore
- 1B filo verde motore
- VDD AI 5v Arduino
- GND comune a tutti i GND
Collegamenti cavi motore al driver:
- Avvolgimento 1: Nero – Verde
- Avvolgimento 2: Rosso – Blu
Schema su breadboard:

Schema su millefori - solo alimientazione:

Schema su millefori - completo:

Condensatori
Stabilizzano la tensione: i motori stepper assorbono corrente in modo impulsivo, e un condensatore aiuta a ridurre i cali di tensione.
Riducono i disturbi elettrici: evita picchi di tensione che potrebbero danneggiare il DRV8825 o altri componenti.
Proteggono da accensioni/spegnimenti improvvisi: aiuta a gestire la corrente di spunto quando il motore parte.
- 100 µF - 470 µF, 50V (elettrolitico)
- 100nF (0,1 µF) ceramico → filtra meglio i disturbi ad alta frequenza
Alimentazione Arduino:
- VIN: 7.5V (positivo dell'alimentatore)
- GND:7.5V (negativo dell'alimentatore)
Nema17full.ino
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <EncButton.h>
#include "Blink4.h"
#include "VelDir.h"
#include "Passo.h"
#include "Etich.h"
#include "StepperMotorController.h"
StepperMotorController stepper(4, 3, 7, 6, 5);
Adafruit_ST7735 tft(10, 9, 8);
EncButton velo(A0,A1,A2);
EncButton molt(A5, A4, A3);
EncButton pass(12,2,-1);
Blink4* dspDg;
VelDir* velDr;
Passo* setPs;
Etich* etic;
int stepMode = 0;
bool direz = true;
void setup() {
stepper.begin();
tft.initR(INITR_BLACKTAB);
tft.setRotation(1);
tft.fillScreen(ST77XX_BLACK);
dspDg = new Blink4(90, 40, &tft, &molt);
dspDg->drawAll(true);
velDr = new VelDir(8, 126, &tft, &velo);
setPs = new Passo(&tft,&pass);
etic = new Etich(&tft);
etic->drawAll();
}
void loop() {
dspDg->update();
setPs->update();
velDr->update();
float passo = setPs->getValue(); // es. 0.25
int idPas = setPs->getValForCase(); // es. 0 - 5
int moltiplicatore = dspDg->getValue(); // es. 100
int velocita = velDr->getValue(); // es. 2
bool dir = velDr->getDir(); // true = destra
velDr->setMaxPassiSec(5000,dspDg->getValue());
stepper.setMicrostepping(idPas);
stepper.setParameters(passo, moltiplicatore, velocita, dir);
stepper.update();
velDr->drawRpm(stepper.getRpm());
}
|
Blink4.h
#pragma once
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <EncButton.h>
#include "DS_Digit18pt7b.h"
class Blink4 {
private:
Adafruit_ST7735* tft;
EncButton* enc;
const GFXfont* font;
int x, y, spz;
int cifre[4];
int xPos[4];
const int blinkInterval = 400;
unsigned long lastBlink = 0;
bool visible = true;
bool inEdit = false;
int pos = 0;
uint16_t color;
uint16_t holdSec;
public:
Blink4(int x, int y, Adafruit_ST7735* display, EncButton* encod )
: x(x), y(y), tft(display), enc(encod), spz(16), font(&DS_Digit18pt7b),
color(0xffff00), holdSec(400)
{
enc->setEncReverse(true);
setLpTm(holdSec);
calcCfr();
calcPos();
}
void setSpaz(int spazio) {
spz = spazio;
calcPos();
}
void setFont(const GFXfont* fnt) {
font = fnt;
}
void setColor(uint32_t hexColor) {
color = hexToColor565(hexColor);
}
unsigned int getValue() {
return cifre[0] * 1000 + cifre[1] * 100 + cifre[2] * 10 + cifre[3];
}
void setLpTm(uint16_t msRit){
enc->setHoldTimeout(msRit);
}
void drawAll(bool allVisible) {
tft->setFont(font);
for (int i = 0; i < 4; i++) {
bool vis = allVisible || (inEdit && i == pos && visible);
drawDigit(i, vis);
}
}
void update() {
enc->tick();
if (enc->click()) {
if (!inEdit) {
inEdit = true;
pos = 0;
visible = true;
lastBlink = millis();
} else {
drawDigit(pos, true);
pos = (pos + 1) % 4;
visible = true;
lastBlink = millis();
}
}
if (enc->hold()) {
inEdit = false;
visible = true;
drawAll(true);
}
if (inEdit) {
bool changed = false;
if (enc->right()) {
drawDigit(pos, false);
cifre[pos] = (cifre[pos] + 1) % 10;
changed = true;
} else if (enc->left()) {
drawDigit(pos, false);
cifre[pos] = (cifre[pos] + 9) % 10;
changed = true;
}
if (changed || millis() - lastBlink >= blinkInterval) {
visible = !visible;
drawDigit(pos, visible);
lastBlink = millis();
}
}
}
private:
void calcPos() {
xPos[0] = x;
for (int i = 1; i < 4; i++) {
xPos[i] = xPos[i - 1] + spz;
}
}
void calcCfr(){
for (int i = 0; i < 4; i++) cifre[i] = 0;
}
uint16_t hexToColor565(uint32_t hexColor) {
uint8_t r = (hexColor >> 16) & 0xFF;
uint8_t g = (hexColor >> 8) & 0xFF;
uint8_t b = hexColor & 0xFF;
return tft->color565(r, g, b); // o tft.color565(...) se non è un puntatore
}
void drawDigit(int p, bool vis) {
tft->setFont(font);
tft->setCursor(xPos[p], y);
tft->setTextColor(vis ? color : ST77XX_BLACK, ST77XX_BLACK);
tft->print(cifre[p]);
}
}; |
VelDir.h
#pragma once
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <EncButton.h>
#include "DS_Digit36pt7b.h"
class VelDir {
private:
Adafruit_ST7735* tft;
EncButton* enc;
const GFXfont* font;
int x, y;
uint16_t color;
int value = 0;
int maxVal = 1000;
bool dir = true; // true = destra, false = sinistra
uint16_t arrowRightColor = ST77XX_GREEN;
uint16_t arrowLeftColor = ST77XX_RED;
uint16_t bgColor = ST77XX_BLACK;
uint16_t txtColor = ST77XX_YELLOW;
uint32_t holdSec;
bool changed = true;
int valuePrecedente = -9999;
int prevValue = -1;
bool prevDir = !dir;
public:
VelDir(int x, int y, Adafruit_ST7735* display, EncButton* encod)
: x(x), y(y), tft(display), enc(encod), font(&DS_Digit36pt7b),
color(0xffff00), holdSec(400) {
enc->setHoldTimeout(holdSec);
enc->setEncReverse(true);
}
void setFont(const GFXfont* f) { font = f; }
void setColor(uint16_t c) { txtColor = c; }
void setHoldTm(uint32_t ms) { holdSec = ms; enc->setHoldTimeout(holdSec); }
void setMaxPassiSec(int maxPassiSec, int moltiplicatore) {
if (moltiplicatore <= 0) return;
maxVal = maxPassiSec / moltiplicatore;
}
int getValue() const { return value; }
bool getDir() const { return dir; }
void drawRpm(uint16_t rpm) {
static int lastDigits[4] = {-1, -1, -1, -1};
int digits[4] = {0, 0, 0, 0};
int temp = rpm;
for (int i = 3; i >= 0; i--) {
digits[i] = temp % 10;
temp /= 10;
}
// Calcola l'indice del primo digit significativo
int firstDigit = 0;
while (firstDigit < 3 && digits[firstDigit] == 0) {
firstDigit++;
}
tft->setFont(font);
for (int i = 0; i < 4; i++) {
int xPos = 10 + i * 36;
// Se la cifra è non significativa e non è l'ultima (cioè, tutto zero)
if (i < firstDigit) {
if (lastDigits[i] != -1) {
tft->setTextColor(bgColor, bgColor);
tft->setCursor(xPos, y);
tft->print(lastDigits[i]); // cancella
lastDigits[i] = -1;
}
continue; // salta stampa
}
if (digits[i] != lastDigits[i]) {
// Cancella cifra precedente
tft->setTextColor(bgColor, bgColor);
tft->setCursor(xPos, y);
if (lastDigits[i] >= 0) tft->print(lastDigits[i]);
// Scrive nuova cifra
tft->setTextColor(txtColor, bgColor);
tft->setCursor(xPos, y);
tft->print(digits[i]);
lastDigits[i] = digits[i];
}
}
}
void update() {
enc->tick();
if (enc->right() && value < maxVal) value++;
if (enc->left() && value > 0) value--;
if (enc->click()) value = 0;
if (enc->hold()) dir = !dir;
if (dir != prevDir) {
drawArrow();
prevDir = dir;
}
}
private:
uint16_t hexToColor565(uint32_t hexColor) {
uint8_t r = (hexColor >> 16) & 0xFF;
uint8_t g = (hexColor >> 8) & 0xFF;
uint8_t b = hexColor & 0xFF;
return tft->color565(r, g, b);
}
void drawArrow() {
int cy = 60;
int size = 14;
// Cancella entrambe le aree prima di ridisegnare
tft->fillRect(8, cy - size - 2, 30, size * 2 + 4, bgColor); // area sinistra
tft->fillRect(122, cy - size - 2, 30, size * 2 + 4, bgColor); // area destra
if (dir) {
// Freccia a DESTRA (verde)
int cx = 136;
tft->fillTriangle(
cx - size, cy - size,
cx - size, cy + size,
cx + size, cy,
arrowRightColor
);
} else {
// Freccia a SINISTRA (rossa)
int cx = 22;
tft->fillTriangle(
cx + size, cy - size,
cx + size, cy + size,
cx - size, cy,
arrowLeftColor
);
}
}
}; |
Passo.h
#pragma once
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include <EncButton.h>
#include "ARLRDBD10pt7b.h"
class Passo {
private:
Adafruit_ST7735* tft;
EncButton* enc;
int x, y;
const GFXfont* font;
uint16_t textColor;
int value = 0;
public:
Passo(Adafruit_ST7735* display, EncButton* encod): tft(display), enc(encod), font(&ARLRDBD10pt7b){
tft->setFont(font);
enc->setEncReverse(true);
}
void setFont(const GFXfont* newFont) {
if (newFont) {
font = newFont;
tft->setFont(font);
}
}
void setColor(uint32_t hexColor) {
textColor = hexToColor565(hexColor);
tft->setTextColor(textColor);
}
void setCursor(int newX, int newY) {
x = newX;
y = newY;
tft->setCursor(x, y);
}
float getValue(){
static const float stepTable[] = {1, 0.5, 0.25, 0.125, 0.0625, 0.03125};
return stepTable[value];
}
int getValForCase() {
return value;
}
void draw(const String& text) {
tft->setCursor(x, y);
tft->print(text);
}
void update() {
//static int value = 0; // mantiene il valore tra 0 e 5
static int lastValue = -1;
tft->setFont(font);
enc->tick(); // aggiorna lo stato dell'encoder
if (enc->right() && value < 5) {
value++;
} else if (enc->left() && value > 0) {
value--;
}
if (value != lastValue) {
static const char* values[] = { "1", "1/2", "1/4", "1/8", "1/16", "1/32" };
// Cancella il valore precedente scrivendolo in nero
tft->setTextColor(0x0000, 0x0000); // nero su nero
tft->setCursor(98, 14);
tft->print(values[lastValue >= 0 ? lastValue : 0]);
// Scrive il nuovo valore in bianco su sfondo nero
tft->setTextColor(0xFFFF, 0x0000); // giallo su nero
tft->setCursor(98, 14);
tft->print(values[value]);
lastValue = value;
}
}
private:
uint16_t hexToColor565(uint32_t hexColor) {
uint8_t r = (hexColor >> 16) & 0xFF;
uint8_t g = (hexColor >> 8) & 0xFF;
uint8_t b = hexColor & 0xFF;
return tft->color565(r, g, b); // o tft.color565(...) se non è un puntatore
}
}; |
Etich.h
#pragma once
#include <Adafruit_GFX.h>
#include <Adafruit_ST7735.h>
#include "ARLRDBD8pt7b.h"
#include "ARLRDBD14pt7b.h" // Font di default
class Etich {
private:
Adafruit_ST7735* tft;
int x, y;
const GFXfont* font;
uint16_t textColor;
public:
Etich(Adafruit_ST7735* display): tft(display){
tft->setFont(&ARLRDBD8pt7b);
setColor(0x00ff00);
}
void setFont(const GFXfont* newFont) {
if (newFont) {
font = newFont;
tft->setFont(font);
}
}
void setColor(uint32_t hexColor) {
textColor = hexToColor565(hexColor);
tft->setTextColor(textColor);
}
void setCursor(int newX, int newY) {
x = newX;
y = newY;
tft->setCursor(x, y);
}
void setCenter(int newY, String str) {
y = newY;
// Usa getTextBounds per ottenere le dimensioni del testo
int16_t x1, y1;
uint16_t textWidth = 0, textHeight = 0;
tft->getTextBounds(str.c_str(), 0, 0, &x1, &y1, &textWidth, &textHeight);
// Calcola la posizione orizzontale centrata
int centerX = (tft->width() - textWidth) / 2;
setCursor(centerX, y);
tft->print(str);
}
void draw(const String& text) {
tft->setCursor(x, y);
tft->print(text);
}
void drawAll() {
setCursor(7, 33);
draw("Multiplier:");
setCursor(40, 12);
draw("Passo: ");
setFont(&ARLRDBD14pt7b);
setCursor(50, 68);
draw("RPM");
}
private:
uint16_t hexToColor565(uint32_t hexColor) {
uint8_t r = (hexColor >> 16) & 0xFF;
uint8_t g = (hexColor >> 8) & 0xFF;
uint8_t b = hexColor & 0xFF;
return tft->color565(r, g, b); // o tft.color565(...) se non è un puntatore
}
}; |
StepperMotorController.h
#pragma once
#include <Arduino.h>
#include <TMCStepper.h>
#define R_SENSE 0.11f // Valore standard se non conosci quello esatto, altrimenti sostituiscilo
class StepperMotorController {
private:
uint8_t stepPin, dirPin;
TMC2209Stepper driver;
bool direction = true;
float passo = 1.0;
unsigned long lastStepTime = 0;
unsigned long delayPerStep = 1000; // in microsecondi
int rpm = 0;
int passiAlSecondo = 0;
public:
StepperMotorController(uint8_t step, uint8_t dir)
: stepPin(step), dirPin(dir), driver(&Serial, R_SENSE) {}
void begin() {
pinMode(stepPin, OUTPUT);
pinMode(dirPin, OUTPUT);
Serial.begin(115200); // UART per TMC2209 via pin TX (D1)
delay(500); // Tempo per inizializzazione driver
driver.begin(); // Inizializza il driver
driver.rms_current(1200); // Corrente RMS prudenziale (mA)
driver.microsteps(16); // Imposta il microstepping
driver.en_spreadCycle(false); // Usa stealthChop per funzionamento silenzioso
driver.pdn_disable(true); // Disabilita pin PDN (default HIGH)
driver.I_scale_analog(false); // Disabilita controllo analogico corrente
}
void setMicrostepping(uint16_t microstepValue) {
driver.microsteps(microstepValue); // Es: 1, 2, 4, 8, 16, 32, 64, 128, 256
}
void setCurrent(uint16_t mA) {
driver.rms_current(mA); // Imposta corrente RMS in mA
}
void setParameters(float passoVal, int moltiplicatore, int velocita, bool dir) {
passo = passoVal;
direction = dir;
float passiPerGiro = 200.0 / passo;
passiAlSecondo = moltiplicatore * velocita;
delayPerStep = (passiAlSecondo > 0) ? (1000000UL / passiAlSecondo) : 1000000UL;
if (delayPerStep < 200) delayPerStep = 200;
rpm = (uint16_t)((float)passiAlSecondo * 60.0 / passiPerGiro);
digitalWrite(dirPin, direction);
}
void update() {
unsigned long now = micros();
if (passiAlSecondo <= 0) {
digitalWrite(stepPin, LOW);
return;
}
if (now - lastStepTime >= delayPerStep) {
digitalWrite(stepPin, HIGH);
delayMicroseconds(delayPerStep / 2);
digitalWrite(stepPin, LOW);
delayMicroseconds(delayPerStep / 2);
lastStepTime = micros();
}
}
int getRpm() const {
return rpm;
}
}; |
|