Интернет вещей (IoT) произвел революцию в способах управления устройствами, обеспечив бесперебойный удаленный мониторинг и управление по сети. В этом руководстве представлено инновационное решение для удаленного управления шаговым двигателем, позволяющее точно регулировать его положение, отслеживать его работу и получать обратную связь в реальном времени непосредственно из простого веб-браузера.
Для тех, кто только начинает знакомиться с робототехникой и автоматизацией, понимание основ работы шагового двигателя — отличный первый шаг перед тем, как приступить к этому сложному проекту. В этом руководстве представлено инновационное решение для управления шаговым двигателем ESP32 с помощью Wi-Fi, позволяющее точно регулировать положение двигателя, отслеживать его работу и получать обратную связь в реальном времени из простого веб-браузера.
В этом руководстве мы рассмотрим беспроводной контроллер шагового двигателя на базе ESP32 — простой, но мощный способ добавить управление шаговым двигателем по Wi-Fi в любой проект. Этот беспроводной драйвер шагового двигателя сочетает в себе надежное управление и современные возможности подключения, что делает его идеальным для применения в автоматизации, робототехнике и промышленном прототипировании. Благодаря этой беспроводной конструкции драйвера шагового двигателя вы можете управлять двигателем напрямую через локальный веб-интерфейс, без необходимости в дополнительном программном обеспечении или сложной настройке.
Вы можете ознакомиться с файлами проекта, включая файлы Gerber для печатной платы и спецификацию компонентов, чтобы самостоятельно собрать этот беспроводной контроллер шагового двигателя.
Принципы работы беспроводного контроллера шагового двигателя
Беспроводной контроллер шагового двигателя с ESP32 — это интеллектуальная система управления двигателем, сочетающая в себе точное управление шаговым двигателем с беспроводным подключением и интеллектуальным управлением питанием.
Беспроводное управление шаговым двигателем: управляйте двигателем дистанционно через простой веб-интерфейс в любом браузере или на смартфоне.
Точность и аккуратность: обеспечьте высокоточное позиционирование с шагом до 1/256 микрошага и обратной связью от энкодера в реальном времени с помощью датчика AS5600.
Интеллектуальное управление питанием: автоматическое согласование оптимальных уровней мощности от источников питания USB-C Power Delivery (PD) в диапазоне от 5 В до 20 В.
Мониторинг в реальном времени: отслеживайте важные данные, включая температуру двигателя, потребляемый ток и точное положение, — вся информация обновляется в режиме реального времени на веб-панели управления.
Интуитивно понятная визуальная обратная связь: используйте цветные RGB-светодиодные индикаторы, чтобы мгновенно увидеть состояние питания системы и активность двигателей.
Основные характеристики нашего беспроводного драйвера шагового двигателя
- Веб-управление: простые HTTP-команды управляют всеми функциями двигателя — специальное программное обеспечение не требуется.
- Точное перемещение: до 1/256 микрошага с замкнутым контуром позиционирования с использованием магнитного энкодера AS5600.
- Интеллектуальное управление питанием: автоматически выбирает напряжение 5 В, 9 В, 12 В или 15 В от источников USB-PD.
- Визуальная обратная связь: RGB-светодиоды отображают уровень мощности (цветовая кодировка) и активность двигателя (мигание).
- Мониторинг в реальном времени: обновление информации о состоянии в режиме реального времени, включая температуру, потребляемый ток и местоположение.
- Неблокирующая работа: веб-сервер остается отзывчивым, а двигатель работает бесперебойно.
Необходимые компоненты
| Компонент | Количество | Описание |
| ESP32-S3-WROOM-1 | 1 | Модуль микроконтроллера с поддержкой Wi-Fi и Bluetooth 5.0, двухъядерным процессором Xtensa LX7, 512 КБ SRAM и 8 МБ флэш-памяти. |
| TMC2240ATJ+T | 1 | Высокоточный драйвер шагового двигателя с управлением по протоколу SPI, поддерживающий ток до 2 А и обеспечивающий сверхтихую работу. |
| AS5600-ASOM | 1 | Бесконтактный магнитный датчик положения вращения с 12-битным разрешением для точного измерения угла. |
| ADPL44002AUJZ | 1 | Линейный стабилизатор напряжения с низким падением напряжения (LDO), поддерживающий входное напряжение 2,7–40 В и выходной ток до 200 мА. |
| FUSB302BMPX | 1 | Контроллер USB Type-C с поддержкой Power Delivery (PD) для согласования напряжения и переключения ролей передачи данных. |
| ADM803SAKSZ | 1 | Микросхема контроля напряжения с двухтактным выходом для надежного управления сбросом системы. |
| AO3400A | 1 | P-канальный MOSFET (30 В, 4 А) для низковольтных коммутационных приложений. |
| EVQ-P7A01P | 4 | Тактильные кнопочные переключатели (6×6 мм, усилие срабатывания 160 гс) для ввода данных пользователем. |
| SMF40A | 1 | Диод подавления переходных напряжений (TVS) на 40 В для защиты цепи от скачков напряжения. |
| SD05 | 1 | Массив TVS-диодов, предназначенный для защиты от USB-зарядов и электростатического разряда. |
| LTST-C191KRKT | 1 | Красный светодиод для поверхностного монтажа (корпус 0603) для индикации состояния. |
| XL-1615RGBC-YG | 1 | RGB-светодиод (с общим катодом, корпус 1615) для многоцветной визуальной обратной связи. |
| Другие пассивные компоненты | — | Включает конденсаторы, резисторы и необходимые дискретные компоненты. |
| Разъемы | — | Дополнительные разъемы по мере необходимости. |
| Заказная печатная плата | — | Основная печатная плата системы. |
| Детали, вырезанные лазером | — | Корпуса и механические опоры. |
| Прочие инструменты и расходные материалы | — | Материалы для сборки и тестирования. |
Компонентов достаточно много, но для проекта с таким функционалом это нормально.
Схема проекта
Схема беспроводного контроллера шагового двигателя на ESP32 и TMC2240 представлена на следующем рисунке. Она демонстрирует как эффективно управлять шаговым двигателем с помощью ESP32. Ее также можно скачать в формате PDF из репозитория GitHub, ссылка на который приведена в конце статьи.
Схема полностью настраиваема. Вы можете изменить любую часть проекта в соответствии со своими конкретными потребностями.
Во-первых, силовой блок, который объединяет контроллер USB Power Delivery (PD) и стабилизатор напряжения с низким падением напряжения (LDO) для управления входным напряжением и обеспечения стабильного выходного напряжения 3,3 В. В качестве контроллера USB PD используется FUSB302BMPX, который согласовывает профили питания через разъем USB-C. Он подключается к линиям CC1 и CC2 через подтягивающие резисторы 5,1 кОм для определения ориентации кабеля и профилей напряжения. После согласования параметров питания линия VBUS подает необходимое напряжение (например, 9 В или 12 В), которое затем передается на стабилизатор LDO.
В качестве стабилизатора напряжения используется LDO ADP4140002, который понижает согласованное напряжение до стабильного значения 3,3 В. Это напряжение 3,3 В используется для питания микроконтроллера ESP32-S3, логической части драйвера шагового двигателя, датчика магнитного положения и светодиодных индикаторов.
LDO-регулятор включает в себя входные и выходные конденсаторы для стабилизации напряжения и фильтрации шума, а контроллер PD взаимодействует с ESP32-S3 через I²C и уведомляет его о любых событиях, связанных с питанием, с помощью вывода прерывания.
В основе системы лежит модуль ESP32-S3-WROOM-1, обеспечивающий подключение по Wi-Fi и Bluetooth, а также множество выводов GPIO для управления и связи. Эта конфигурация демонстрирует, как эффективно управлять шаговым двигателем с помощью ESP32. Его выводы GPIO0 и EN подключены к кнопкам для загрузки и сброса. Связь осуществляется через USB с использованием собственных выводов D+ и D–, которые подключены через резисторы 33 Ом.
Шина I²C на выводах GPIO9 (SCL) и GPIO8 (SDA) подключается к контроллеру USB PD и магнитному датчику положения, обеспечивая двустороннюю связь для контроля питания и положения. ESP32 также взаимодействует с драйвером шагового двигателя TMC2240 как через SPI, так и через выделенные управляющие выводы. Для связи по SPI используются выводы GPIO10–13, а для сигналов STEP и DIR — выводы GPIO5 и GPIO6.
Кроме того, три светодиода подключены к выводам GPIO40, GPIO41 и GPIO42 через резисторы сопротивлением 330 Ом и служат индикаторами состояния.
Драйвер шагового двигателя TMC2240 обеспечивает точное управление шаговым двигателем. Он получает входное питание двигателя (VM) от внешнего источника питания и логическое питание от стабилизатора напряжения 3,3 В. Управление двигателем осуществляется с помощью сигналов STEP и DIR от ESP32, а шина SPI используется для настройки внутренних параметров и считывания диагностических данных. Драйвер также включает выводы разрешения и диагностики для точного управления и обратной связи. Защита реализована с помощью TVS-диода, подключенного параллельно линии VM, для защиты от скачков напряжения, и резисторов для контроля тока двигателя. Выходные выводы драйвера подключены непосредственно к обмоткам двигателя, что обеспечивает плавную работу в режиме микрошага. Для тех, кто использует драйверы A4988, наше пошаговое руководство по подключению A4988 к Arduino поможет вам разобраться в этом вопросе.
В состав системы входит магнитный датчик углового положения на базе AS5600, предназначенный для определения углового положения вращающегося вала. Этот датчик взаимодействует по интерфейсу I²C, используя ту же шину, что и ПД-контроллер. Он питается от шины 3,3 В и включает в себя развязывающий конденсатор для фильтрации шума. Датчик предоставляет точные данные об угле поворота для ESP32, что позволяет использовать его в системах управления двигателем с обратной связью или в приложениях с отслеживанием положения.
Таким образом, мы рассмотрели принципиальную схему. Далее переходим к печатной плате.
Благодаря интеграции универсального микроконтроллера ESP32 с высокопроизводительным драйвером TMC2240 и энкодером AS5600, эта система управления шаговым двигателем на базе ESP32 с поддержкой Wi-Fi обеспечивает непревзойденную точность и обратную связь в реальном времени.
Разработка печатной платы для беспроводного драйвера шагового двигателя
Для этого проекта мы решили создать собственную печатную плату. Это гарантирует, что конечный продукт будет максимально компактным, простым в сборке и использовании. Плата была разработана с помощью KiCad, и все файлы проекта доступны для скачивания из репозитория GitHub, ссылка на который приведена ниже в этой статье.
Размеры печатной платы составляют приблизительно 42,3 мм x 42,3 мм, что точно соответствует стандартным размерам шаговых двигателей, таких как NEMA 17. Таким образом, она легко устанавливается за двигателем.
После завершения и полной проверки проекта печатной платы мы отправили соответствующие файлы Gerber в компанию по изготовлению печатных плат. Ниже вы можете увидеть готовую печатную плату.
Сборка проекта
Первым шагом при сборке печатной платы была сортировка всех необходимых компонентов в соответствии со спецификацией материалов. После сортировки мы разместили компоненты на печатной плате и припаяли их по одному.
Для упрощения этого процесса можно использовать трафарет для SMD-компонентов, нанося паяльную пасту, а затем разместить компоненты перед пайкой оплавлением на печатной плате с помощью паяльной станции для SMD-компонентов или печи для оплавления. Однако вы не ограничены этими методами; ручная пайка также хорошо подходит для небольших партий.
Выше представлено изображение полностью собранной печатной платы беспроводного контроллера шагового двигателя.
Корпус для драйвера шагового двигателя, вырезанный лазером
Чтобы сделать корпус простым и быстрым, мы выбрали лазерную резку. Нам понадобятся две основные детали, а также несколько шайб. Внизу нам понадобится проставка для компенсации высоты микросхемы магнитного энкодера. Сверху необходимо закрыть печатную плату, чтобы избежать внешних повреждений.
Выше вы можете увидеть изображение вырезанных фигур.
Ниже вы можете увидеть компоненты, необходимые для сборки беспроводного драйвера шагового двигателя.
Сборка очень проста. Единственное, что требует особого внимания, — это размещение магнита на валу. Это важно, поскольку он отвечает за определение положения вала.
Я разломил обычный магнит диаметром 3 мм пополам и прикрепил одну половинку, как показано на изображении ниже. Для склеивания магнита я использовал клей Feviquick.
Далее следует корпус. Он собирается, как показано на следующем изображении.
После полной сборки готовый проект выглядит так, как на изображении ниже. Вы можете оценить компактность этого проекта.
Отсутствие боковых кожухов обусловлено необходимостью обеспечения вентиляции для микросхемы драйвера, что делает её идеальным решением для быстрого прототипирования.
Этот проект беспроводного контроллера шагового двигателя, созданный своими руками, предлагает мощное и компактное решение для робототехнических проектов и проектов домашней автоматизации. Благодаря интеграции универсального микроконтроллера ESP32 с высокопроизводительным драйвером TMC2240 и энкодером AS5600, эта система обеспечивает непревзойденную точность и обратную связь в реальном времени. Веб-управление и интеллектуальное управление питанием делают ее идеальным инструментом для быстрого прототипирования, робототехники и промышленных приложений. Вы можете загрузить весь код и файлы проекта из репозитория GitHub, ссылка на который приведена ниже, чтобы собрать свою собственную систему.
Техническое описание и репозиторий GitHub
Краткое техническое описание, в котором изложены основная идея, принцип работы и основные свойства проекта. Кроме того, репозиторий GitHub содержит исходный код, файлы проекта и документацию для удобства использования. Это описание дает читателю краткий обзор проекта и достаточный метод для его воспроизведения с использованием материалов с открытым исходным кодом.
Объяснение кода для ESP32
В основе этого проекта беспроводного контроллера шагового двигателя лежит универсальный код для Arduino, который органично интегрирует несколько передовых технологий. Прошивка наглядно демонстрирует, как управлять шаговым двигателем с помощью ESP32, и предназначена для объединения следующих функций:
Драйвер шагового двигателя TMC2240: для сверхтихого и точного управления шаговым двигателем.
Технология USB-C Power Delivery: обеспечивает интеллектуальное и регулируемое питание.
Магнитный энкодер AS5600: для высокоточной обратной связи по положению в замкнутом контуре.
RGB-светодиоды состояния: обеспечивают наглядную визуальную индикацию состояния системы.
Веб-интерфейс сервера: для удобного удаленного управления и мониторинга в режиме реального времени.
Система позволяет управлять шаговым двигателем через веб-браузер, одновременно отслеживая мощность, температуру и положение в режиме реального времени.
Библиотеки
В этом коде используется несколько библиотек для обработки различных компонентов.
|
1 2 3 4 5 6 7 8 |
#include <SPI.h> #include <WiFi.h> #include <WebServer.h> #include "AS5600.h" #include <Wire.h> #include <PD_UFP.h> #include <ArduinoJson.h> #include "index_page.h" |
- » SPI.h » – обеспечивает связь с драйвером TMC2240 с использованием последовательного периферийного интерфейса (встроенная библиотека).
- » Wire.h » – Обеспечивает связь по протоколу I2C с магнитным энкодером (встроенная библиотека).
- » WiFi.h » и «WebServer.h» — обеспечивают подключение к Wi-Fi и управление через веб-интерфейс (встроенные библиотеки).
- «AS5600.h» — управляет магнитным энкодером для определения положения.
- «PD_UFP.h» – Управляет согласованием параметров питания USB-C.
- «ArduinoJson.h» – форматирует данные для ответов веб-API.
- «index_page.h» – содержит HTML/CSS/JavaScript для веб-интерфейса (пользовательская библиотека).
Распределение контактов и аппаратные соединения
В системе используются специальные контакты микроконтроллера для различных компонентов, что обеспечивает надлежащую связь и управление.
|
1 2 3 4 |
const int MOSI_PIN = 11, MISO_PIN = 13, SCK_PIN = 12, CS_PIN = 10; const int EN_PIN = 14; const int STEP_PIN = 5; const int DIR_PIN = 6; |
Драйвер шагового двигателя TMC2240 использует интерфейс SPI с выводами MOSI на контакте 11, MISO на контакте 13, SCK на контакте 12 и CS на контакте 10, в то время как выводы управления двигателем включают Enable на контакте 14, Step на контакте 5 и Direction на контакте 6.
Магнитный энкодер AS5600 взаимодействует по протоколу I2C, используя контакты 8 для SDA и 9 для SCL.
|
1 2 3 |
const int RGB_RED_PIN = 40; const int RGB_GREEN_PIN = 42; const int RGB_BLUE_PIN = 41; |
RGB-светодиод использует три отдельных контакта для управления цветом: красный — на контакте 40, зеленый — на контакте 42, а синий — на контакте 41.
|
1 |
#define FUSB302_INT_PIN 7 |
В системе USB-C Power Delivery контакт 7 используется в качестве контакта прерывания для контроллера FUSB302.
Глобальные переменные и конфигурация
Система поддерживает различные глобальные переменные для отслеживания разных аспектов работы.
|
1 2 3 4 5 6 7 8 |
bool isMotorEnabled = false; bool currentDirection = true; bool isMovingToAngle = false; bool pd_negotiation_complete = false; int currentSpeed = DEFAULT_SPEED; uint16_t motorCurrent = DEFAULT_CURRENT; uint8_t microSteps = DEFAULT_MICROSTEPS; int STEPS_PER_REVOLUTION = STEPS_PER_REV * DEFAULT_MICROSTEPS; |
К переменным управления двигателем относятся isMotorEnabled, отслеживающая, включен ли двигатель, currentDirection, хранящая направление вращения, и isMovingToAngle, указывающая на активность автоматического позиционирования по углу. Параметры двигателя, такие как currentSpeed, motorCurrent и microSteps, можно регулировать во время работы для точной настройки производительности.
|
1 2 3 4 5 |
float targetAngle = 0.0; float currentAngleTolerance = 0.0; float cachedCurrentAngle = 0.0; float cachedTemperature = 0.0; int cachedCurrentB = 0; |
Переменные управления положением, такие как targetAngle и currentAngleTolerance, обеспечивают точные перемещения на основе углов, а система кэширует показания датчиков в переменных, таких как cachedCurrentAngle и cachedTemperature, чтобы уменьшить накладные расходы на передачу данных и повысить производительность.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
struct PDProfile { float voltage; float current; const char* name; }; const PDProfile pd_profiles[] = { {5.0, 3.0, "5V/3A"}, {9.0, 2.0, "9V/2A"}, {9.0, 3.0, "9V/3A"}, {12.0, 1.5, "12V/1.5A"}, {15.0, 2.0, "15V/2A"}, {20.0, 1.5, "20V/1.5A"} }; |
В конфигурации подачи питания используется массив pd_profiles, определяющий различные комбинации напряжения и тока, включая варианты 5 В, 9 В, 12 В, 15 В и 20 В, которые могут быть согласованы с источниками питания USB-C.
|
1 2 3 4 5 6 7 8 9 10 |
const RGBColor COLOR_OFF = { 0, 0, 0 }; const RGBColor COLOR_RED = { 255, 0, 0 }; const RGBColor COLOR_GREEN = { 0, 255, 0 }; const RGBColor COLOR_BLUE = { 0, 0, 255 }; const RGBColor COLOR_YELLOW = { 255, 255, 0 }; const RGBColor COLOR_PURPLE = { 255, 0, 255 }; const RGBColor COLOR_CYAN = { 0, 255, 255 }; const RGBColor COLOR_WHITE = { 255, 255, 255 }; const RGBColor COLOR_ORANGE = { 255, 165, 0 }; const RGBColor COLOR_PINK = { 255, 20, 147 }; |
Система управления состоянием светодиодов основана на использовании цветовых констант RGB и временных переменных для управления системой визуальной обратной связи, при этом разные цвета указывают на различные состояния, такие как подключение к Wi-Fi, уровни мощности и состояние работы двигателя.
Функция setup() — инициализация системы
Функция setup() инициализирует все компоненты системы в тщательно спланированной последовательности, чтобы обеспечить корректную работу функции управления шаговым двигателем ESP32 по Wi-Fi .
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
Serial.begin(115200); Serial.println("TMC2240 Stepper Motor Control"); initRGBLED(); Wire.setPins(8, 9); Wire.begin(); PD_UFP.init(FUSB302_INT_PIN, PD_POWER_OPTION_MAX_5V); PD_UFP.set_power_option(PD_POWER_OPTION_MAX_5V); as5600.begin(4); as5600.setDirection(AS5600_CLOCK_WISE); if (!as5600.isConnected()) { Serial.println("Warning: AS5600 encoder not connected!"); } pinMode(EN_PIN, OUTPUT); pinMode(STEP_PIN, OUTPUT); pinMode(DIR_PIN, OUTPUT); pinMode(CS_PIN, OUTPUT); pinMode(MOSI_PIN, OUTPUT); pinMode(MISO_PIN, INPUT); pinMode(SCK_PIN, OUTPUT); pinMode(UART_EN_PIN, OUTPUT); digitalWrite(CS_PIN, HIGH); digitalWrite(EN_PIN, HIGH); digitalWrite(UART_EN_PIN, LOW); digitalWrite(DIR_PIN, currentDirection); SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN); |
Процесс начинается с инициализации последовательной связи в целях отладки, за которой следует тестирование RGB-светодиодов с использованием последовательности цветов: красный, зеленый и синий, для проверки работоспособности. Настройка аппаратных компонентов включает в себя конфигурирование связи I2C для магнитного энкодера, инициализацию контроллера Power Delivery с начальным профилем 5 В и настройку всех контактов GPIO для управления двигателем и связи SPI.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 |
// Connect to WiFi network WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); setRGBColor(COLOR_YELLOW); // Yellow while connecting // Wait for WiFi connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nConnected to WiFi"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); setRGBColor(COLOR_GREEN); // Green when connected delay(1000); // Main page route - serves the web interface server.on("/", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; server.send(200, "text/html", html_page); }); // Motor enable route - enables the stepper motor server.on("/enable", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; digitalWrite(EN_PIN, LOW); isMotorEnabled = true; updateMotorStatusColor(); server.send(200, "text/plain", "Motor Enabled"); }); // Motor disable route - disables the stepper motor server.on("/disable", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; digitalWrite(EN_PIN, HIGH); isMotorEnabled = false; updateMotorStatusColor(); server.send(200, "text/plain", "Motor Disabled"); }); // Emergency stop route - immediately stops all motor activity server.on("/stop", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; digitalWrite(EN_PIN, HIGH); digitalWrite(STEP_PIN, LOW); isMotorEnabled = false; isMovingToAngle = false; currentSpeed = DEFAULT_SPEED; updateMotorStatusColor(); String response = "{\"status\":\"Emergency Stop Activated\",\"motorEnabled\":false}"; server.send(200, "application/json", response); Serial.println("EMERGENCY STOP ACTIVATED"); }); // Speed control route - sets motor speed server.on("/speed", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("value")) { currentSpeed = server.arg("value").toInt(); server.send(200, "text/plain", "Speed set to " + String(currentSpeed)); } }); // Manual movement route - moves motor by specified steps server.on("/move", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("steps")) { int targetSteps = server.arg("steps").toInt(); moveMotor(currentDirection, targetSteps); server.send(200, "text/plain", "Moving " + String(targetSteps) + " steps"); } }); // System status route - returns current system status server.on("/status", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; bool pd_ready = PD_UFP.is_power_ready(); float pd_voltage = (pd_ready && pd_negotiation_complete) ? PD_UFP.get_voltage() : 0.0; float pd_current = (pd_ready && pd_negotiation_complete) ? PD_UFP.get_current() : 0.0; String status = "{\"temperature\":" + String(cachedTemperature, 1) + ",\"current\":" + String(cachedCurrentB) + ",\"pd_ready\":" + String(pd_ready ? "true" : "false") + ",\"pd_negotiated\":" + String(pd_negotiation_complete ? "true" : "false") + ",\"pd_voltage\":" + String(pd_voltage, 2) + ",\"pd_current\":" + String(pd_current, 2) + "}"; server.send(200, "application/json", status); }); // Motor current control route - sets motor current server.on("/current", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("value")) { motorCurrent = server.arg("value").toInt(); setCurrent(motorCurrent); server.send(200, "text/plain", "Motor current set to " + String(motorCurrent) + " mA"); } }); // Microstepping control route - sets microstepping resolution server.on("/microsteps", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("value")) { microSteps = server.arg("value").toInt(); setMicrostepping(microSteps); server.send(200, "text/plain", "Microstepping set to 1/" + String(microSteps)); } }); // Encoder status route - returns current encoder readings server.on("/encoder", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; String json = String("{\"speed\":0") + ",\"rawAngle\":" + String(cachedCurrentAngle, 1) + ",\"current\":" + String(cachedCurrentB) + "}"; server.send(200, "application/json", json); }); // Power status route - returns PD power status server.on("/power_status", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; StaticJsonDocument<200> doc; bool power_ready = PD_UFP.is_power_ready(); float voltage = 0.0; float current = 0.0; if (power_ready && pd_negotiation_complete) { voltage = PD_UFP.get_voltage(); current = PD_UFP.get_current(); } bool pd_connected = power_ready && pd_negotiation_complete && voltage > 0.0 && current > 0.0; doc["pd_connected"] = pd_connected; doc["pd_negotiated"] = pd_negotiation_complete; if (pd_connected) { doc["voltage"] = voltage; doc["current"] = current; doc["power"] = voltage * current; } String response; serializeJson(doc, response); server.send(200, "application/json", response); }); server.on("/direction", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("dir")) { currentDirection = (server.arg("dir") == "true"); digitalWrite(DIR_PIN, currentDirection); server.send(200, "text/plain", "Direction set to " + String(currentDirection ? "Forward" : "Backward")); } else { server.send(400, "text/plain", "Missing direction parameter"); } }); server.on("/set_angle", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("angle")) { targetAngle = server.arg("angle").toFloat(); currentAngleTolerance = calculateAngleTolerance(microSteps); isMovingToAngle = true; server.send(200, "text/plain", "Moving to angle " + String(targetAngle) + "° with accuracy ±" + String(currentAngleTolerance) + "°"); } else { server.send(400, "text/plain", "Missing angle parameter"); } }); // Web server endpoint to set Power Delivery (PD) profile server.on("/setPD", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("profile")) { int profile = server.arg("profile").toInt(); PD_power_option_t power_option; switch (profile) { case 5: power_option = PD_POWER_OPTION_MAX_5V; currentPDProfile = 0; break; case 9: power_option = PD_POWER_OPTION_MAX_9V; currentPDProfile = 1; break; case 12: power_option = PD_POWER_OPTION_MAX_12V; currentPDProfile = 2; break; case 15: power_option = PD_POWER_OPTION_MAX_15V; currentPDProfile = 3; break; case 20: power_option = PD_POWER_OPTION_MAX_20V; currentPDProfile = 4; break; default: server.send(400, "text/plain", "Invalid profile"); return; } PD_UFP.set_power_option(power_option); for (int i = 0; i < 5; i++) { PD_UFP.run(); delay(100); } forceUpdatePDColors(); server.send(200, "text/plain", "PD profile set to " + String(profile) + "V"); } else { server.send(400, "text/plain", "Missing profile parameter"); } }); // Start the web server server.begin(); Serial.println("HTTP server started"); // Configure the TMC2240 stepper motor driver configureDriver(); |
Установление Wi-Fi-соединения — важный этап, на котором система подключается к указанной сети, отображая желтый светодиод во время процесса подключения и меняя цвет на зеленый при успешном подключении. Затем система отображает назначенный IP-адрес для доступа к веб-сайту. Конфигурация маршрутов веб-сервера создает несколько HTTP-конечных точек, включая корневой путь для обслуживания основного интерфейса управления, маршруты включения и выключения управления питанием двигателя, маршруты настройки параметров скорости, тока и микрошага, маршруты команд перемещения для базового шагового и углового позиционирования, маршруты передачи данных в реальном времени для информации о состоянии и энкодере, а также маршруты управления подачей питания. Наконец, драйвер TMC2240 настраивается с параметрами по умолчанию, включая ограничения тока, разрешение микрошага и параметры синхронизации прерывателя.
Основной цикл — непрерывная работа
Основной цикл непрерывно обрабатывает множество задач для поддержания работоспособности и быстродействия системы.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
server.handleClient(); PD_UFP.run(); updateLEDBlink(); static unsigned long lastStatusLEDUpdate = 0; if (millis() - lastStatusLEDUpdate >= 100) { updateStatusLED(); lastStatusLEDUpdate = millis(); } if (millis() - last_pd_check >= PD_CHECK_INTERVAL) { bool was_negotiated = pd_negotiation_complete; if (PD_UFP.is_power_ready()) { if (!pd_negotiation_complete) { Serial.println("PD Power Negotiation Complete!"); pd_negotiation_complete = true; lastPDVoltageCheck = 0; } } else { if (pd_negotiation_complete) { Serial.println("PD Power Negotiation Lost!"); pd_negotiation_complete = false; pdColorModeActive = false; } } last_pd_check = millis(); } |
Обработка запросов включает в себя вызов метода server.handleClient() для обработки входящих веб-запросов и вызов метода PD_UFP.run() для поддержания согласования подачи питания с источниками USB-C. Система визуальной обратной связи обновляет светодиодную индикацию в зависимости от уровня приоритета: проблемы с подключением к Wi-Fi отображаются миганием красного цвета, успешное согласование PD — цветами, кодированными напряжением, а нормальная работа указывает на состояние подключения.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
if (millis() - lastEncoderUpdate >= ENCODER_UPDATE_INTERVAL) { updateCachedValues(); lastEncoderUpdate = millis(); } if (isMovingToAngle) { moveToAngle(); } if (millis() - lastStatusUpdate >= STATUS_UPDATE_INTERVAL) { Serial.println("System Status: Motor " + String(isMotorEnabled ? "Enabled" : "Disabled") + ", Temp: " + String(cachedTemperature, 1) + "°C" + ", WiFi: " + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + ", Web Client: " + String(webClientConnected ? "Active" : "Inactive") + ", PD Mode: " + String(pdColorModeActive ? "Active" : "Inactive")); lastStatusUpdate = millis(); } |
Периодически происходит обновление данных с датчиков, считывающих показания магнитного энкодера и датчика температуры TMC2240, при этом значения кэшируются во избежание избыточной передачи данных, которая может замедлить работу системы. Функция автоматического перемещения активируется, когда isMovingToAngle имеет значение true, в результате чего функция moveToAngle() непрерывно корректирует положение двигателя в направлении целевого угла. Отчеты о состоянии периодически выводят в последовательный порт информацию о системе, включая состояние двигателя, показания температуры и состояние подключения, для целей отладки и мониторинга.
Объяснение основных функций
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
void setRGBColor(RGBColor color) { analogWrite(RGB_RED_PIN, color.red); // Set red component analogWrite(RGB_GREEN_PIN, color.green); // Set green component analogWrite(RGB_BLUE_PIN, color.blue); // Set blue component currentLEDColor = color; // Update current color } void updateStatusLED() { // Highest Priority: WiFi disconnected - Red fast blink if (WiFi.status() != WL_CONNECTED) { pdColorModeActive = false; enableLEDBlink(COLOR_RED, 200); // Red fast blink for WiFi issues return; } // Second Priority: PD is ready and negotiated - show voltage colors if (PD_UFP.is_power_ready() && pd_negotiation_complete) { // Only update PD colors periodically to avoid constant changes if (millis() - lastPDVoltageCheck >= PD_CHECK_INTERVAL) { updatePDVoltageColor(); lastPDVoltageCheck = millis(); } return; // Exit here - PD colors are active } // Third Priority: WiFi connected but no PD - show connection status pdColorModeActive = false; // Update client connection status based on timeout if (millis() - lastClientRequest > CLIENT_TIMEOUT) { webClientConnected = false; } if (webClientConnected) { // WiFi connected and web client active - Solid Green setRGBColor(COLOR_GREEN); ledBlinkEnabled = false; } else { // WiFi connected but no web client - Blue slow blink enableLEDBlink(COLOR_BLUE, 1000); } } |
Функции управления RGB-светодиодами, такие как setRGBColor() и updateStatusLED(), управляют системой визуальной обратной связи, которая предоставляет пользователям мгновенную информацию о состоянии. Система использует цветовую кодировку, где зеленый цвет обозначает напряжение 5 В, синий — 9 В, фиолетовый — 12 В, оранжевый — 15 В, а розовый — 20 В.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
// Write data to TMC2240 register via SPI void writeRegister(uint8_t address, uint32_t value) { digitalWrite(CS_PIN, LOW); // Select TMC2240 SPI.transfer(address | 0x80); // Send address with write bit SPI.transfer((value >> 24) & 0xFF); // Send MSB SPI.transfer((value >> 16) & 0xFF); // Send byte 2 SPI.transfer((value >> 8) & 0xFF); // Send byte 1 SPI.transfer(value & 0xFF); // Send LSB digitalWrite(CS_PIN, HIGH); // Deselect TMC2240 } // Read data from TMC2240 register via SPI uint32_t readRegister(uint8_t address) { digitalWrite(CS_PIN, LOW); // Select TMC2240 SPI.transfer(address & 0x7F); // Send address with read bit uint32_t value = 0; value |= (uint32_t)SPI.transfer(0) << 24; // Read MSB value |= (uint32_t)SPI.transfer(0) << 16; // Read byte 2 value |= (uint32_t)SPI.transfer(0) << 8; // Read byte 1 value |= (uint32_t)SPI.transfer(0); // Read LSB digitalWrite(CS_PIN, HIGH); // Deselect TMC2240 return value; } // Set motor current in milliamps void setCurrent(uint16_t current) { uint32_t ihold_irun = 0; // Set hold current (current when motor is stationary) ihold_irun |= ((current / 100) & 0x1F) << 0; // Set run current (current when motor is moving) ihold_irun |= ((current / 100) & 0x1F) << 8; // Set hold delay (time before reducing to hold current) ihold_irun |= ((current / 200) & 0x0F) << 16; writeRegister(IHOLD_IRUN, ihold_irun); } // Set microstepping resolution (1, 2, 4, 8, 16, 32, 64, 128, 256) void setMicrostepping(uint8_t microsteps) { uint32_t chopconf = readRegister(CHOPCONF); chopconf &= ~(0x0F << 24); // Clear MRES bits // Convert microsteps to MRES register value uint8_t mres; switch (microsteps) { case 256: mres = 0x0; break; // 1/256 microstepping case 128: mres = 0x1; break; // 1/128 microstepping case 64: mres = 0x2; break; // 1/64 microstepping case 32: mres = 0x3; break; // 1/32 microstepping case 16: mres = 0x4; break; // 1/16 microstepping case 8: mres = 0x5; break; // 1/8 microstepping case 4: mres = 0x6; break; // 1/4 microstepping case 2: mres = 0x7; break; // 1/2 microstepping (half step) case 1: mres = 0x8; break; // Full step default: mres = 0x4; break; // Default to 1/16 microstepping } chopconf |= (mres << 24); // Set MRES bits chopconf |= (1 << 28); // Enable interpolation writeRegister(CHOPCONF, chopconf); // Update global calculation variables STEPS_PER_REVOLUTION = STEPS_PER_REV * microsteps; currentAngleTolerance = calculateAngleTolerance(microsteps); Serial.print("Microstepping set to 1/"); Serial.println(microsteps); } |
Связь с TMC2240 осуществляется с помощью функций writeRegister() и readRegister(), которые обрабатывают SPI-соединение с драйвером двигателя, в то время как такие функции, как setCurrent() и setMicrostepping(), настраивают параметры двигателя путем записи в определенные аппаратные регистры.
// Переместить двигатель на заданное количество шагов в указанном направлении
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
void moveMotor(bool direction, uint32_t steps) { digitalWrite(DIR_PIN, direction); // Set direction pin static unsigned long lastStepTime = 0; static uint32_t stepsRemaining = 0; // Initialize step counter on first call if (stepsRemaining == 0) { stepsRemaining = steps; lastStepTime = millis(); } // Generate step pulses at controlled speed if (stepsRemaining > 0 && millis() - lastStepTime >= (1000 / currentSpeed)) { digitalWrite(STEP_PIN, HIGH); // Start step pulse delayMicroseconds(10); // Short pulse duration digitalWrite(STEP_PIN, LOW); // End step pulse stepsRemaining--; // Decrement remaining steps lastStepTime = millis(); // Update timing } } // Automatically move motor to target angle with precision control void moveToAngle() { if (!isMovingToAngle) return; // Exit if not in angle movement mode // Calculate angular difference to target float angleDifference = targetAngle - cachedCurrentAngle; // Normalize angle difference to [-180, 180] range for shortest path while (angleDifference > 180) angleDifference -= 360; while (angleDifference < -180) angleDifference += 360; // Check if target angle is reached within tolerance if (abs(angleDifference) <= currentAngleTolerance) { isMovingToAngle = false; // Stop angle movement mode digitalWrite(STEP_PIN, LOW); // Ensure step pin is low Serial.println("Target angle reached"); return; } // Set motor direction based on angle difference currentDirection = (angleDifference > 0); // Positive = forward digitalWrite(DIR_PIN, currentDirection); // Enable motor if not already enabled if (!isMotorEnabled) { digitalWrite(EN_PIN, LOW); // Enable motor (active low) isMotorEnabled = true; } // Dynamic speed control based on remaining angle int stepDelay; float absDiff = abs(angleDifference); // Slower speeds as we approach target for better precision if (absDiff <= 5.0) { stepDelay = MIN_STEP_DELAY * 4; // Very slow for final approach } else if (absDiff <= 20.0) { stepDelay = MIN_STEP_DELAY * 2; // Slow for precision zone } else if (absDiff <= 45.0) { stepDelay = MIN_STEP_DELAY; // Normal speed } else { stepDelay = MIN_STEP_DELAY / 2; // Fast for large movements } // Take multiple steps toward target (up to 10 per loop iteration) for (int i = 0; i < 10 && isMovingToAngle; i++) { digitalWrite(STEP_PIN, HIGH); // Start step pulse delayMicroseconds(stepDelay); // Pulse high time digitalWrite(STEP_PIN, LOW); // End step pulse delayMicroseconds(stepDelay); // Pulse low time // Update current angle reading after each step cachedCurrentAngle = (as5600.readAngle() * 360.0) / 4096.0; angleDifference = targetAngle - cachedCurrentAngle; // Re-normalize angle difference while (angleDifference > 180) angleDifference -= 360; while (angleDifference < -180) angleDifference += 360; // Check if target reached during movement if (abs(angleDifference) <= currentAngleTolerance) { isMovingToAngle = false; Serial.println("Target angle reached during movement"); break; } } } // Calculate angle tolerance based on microstepping resolution float calculateAngleTolerance(uint8_t microsteps) { // Calculate minimum angle step and multiply by 2 for tolerance return (360.0 / (200.0 * microsteps)) * 2.0; } |
Функционал управления движением включает функцию moveMotor() для генерации ступенчатых импульсов для базовых перемещений и функцию moveToAngle() для точного позиционирования с использованием обратной связи от энкодера. Система автоматически регулирует скорость движения в зависимости от расстояния, оставшегося до целевого угла, используя более низкие скорости для точного позиционирования и более высокие скорости для больших перемещений.
Система управления питанием PD согласовывает соответствующие уровни мощности с источниками питания USB-C, позволяя двигателю получать оптимальное напряжение в диапазоне от 5 В до 20 В в зависимости от требований системы и возможностей источника питания.
Загрузка прошивки и тестирование веб-интерфейса
Для начала подключите изготовленную на заказ печатную плату к компьютеру и загрузите код с помощью среды разработки Arduino IDE.
Если вы хотите отлаживать программу через последовательный монитор, не забудьте включить параметр CDC (Communication Device Class) в среде разработки Arduino IDE в разделе Инструменты > Режим USB перед загрузкой кода.
Теперь давайте посмотрим на пользовательский интерфейс. Он простой и понятный.
Всего здесь 5 вкладок.
-
Настройки двигателя (задает ток двигателя (1000 мА) и режим микрошага (1/16 шага))
-
Управление питанием PD (выбор профиля напряжения)
-
Состояние системы (отображает текущее состояние системы, выполненные команды и т. д.)
-
Управление двигателем (обеспечивает оперативное управление с помощью кнопок включения/выключения, аварийной остановки, вариантов направленного движения, регулировки скорости и позиционирования, позволяющих перемещаться с заданным количеством шагов или под целевыми углами с использованием полей ввода или ползункового интерфейса).
-
Состояние двигателя (на правой панели отображается информация о состоянии в режиме реального времени, включая текущий угол поворота двигателя, скорость вращения (об/мин), фактический потребляемый ток двигателя (мА) и рабочую температуру.)
Выше вы можете увидеть пользовательский интерфейс этого проекта беспроводного контроллера шагового двигателя, который прекрасно демонстрирует возможности управления шаговым двигателем ESP32 по Wi-Fi через современный веб-интерфейс.
Часто задаваемые вопросы о проекте беспроводного контроллера шагового двигателя
⇥ Можно ли использовать другой шаговый двигатель с этим беспроводным контроллером?
Да. Поскольку драйвер TMC2240 поддерживает большое количество шаговых двигателей, типичными примерами являются NEMA 17 и NEMA 23. Вам просто нужно будет настроить параметры ограничения тока в веб-интерфейсе контроллера в диапазоне от 100 мА до 2000 мА в соответствии со спецификациями вашего двигателя. Кроме того, через веб-интерфейс вы также можете выбрать микрошаговый режим для вашего двигателя.
⇥ С какими типами источников питания может работать данный контроллер шагового двигателя ESP32?
Этот контроллер поддерживает технологию USB-C Power Delivery в диапазоне от 5 до 20 В. Он будет выбирать напряжение, наиболее подходящее для работы беспроводного драйвера шагового двигателя, которое может быть одним из следующих: 5 В/3 А, 9 В/3 А, 12 В/1,5 А, 15 В/2 А или 20 В/1,5 А.
⇥ Можно ли управлять несколькими шаговыми двигателями с помощью одного ESP32?
Данная схема предназначена для управления одним двигателем с помощью одного ESP32. Для управления несколькими двигателями необходимо использовать несколько беспроводных контроллеров шаговых двигателей или перепроектировать схему, чтобы использовать несколько драйверов TMC2240, каждый со своим собственным выводом выбора микросхемы SPI.
⇥ Каков диапазон управления беспроводным шаговым двигателем?
Диапазон беспроводного управления ограничен местом передачи сигнала Wi-Fi. В помещении диапазон обычно составляет 30-100 метров, но для приложений с большими расстояниями и установок промышленной автоматизации, требующих расширенного беспроводного покрытия, целесообразно использовать Wi-Fi-ретранслятор или ячеистую сеть.
⇥ Насколько точен проект беспроводного контроллера шагового двигателя?
Система отличается высокой точностью благодаря разрешению микрошага 1/256 и обратной связи с замкнутым контуром от энкодера AS5600, обеспечивая точность позиционирования ±0,35 градуса для большинства приложений автоматизации и робототехники, требующих точного контроля положения двигателя.м
Полный код проекта
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 |
/* * =============================================================================== * TMC2240 STEPPER MOTOR CONTROL WITH PD POWER DELIVERY & RGB STATUS INDICATORS * =============================================================================== * * DESCRIPTION: * This project implements a comprehensive stepper motor control system using the * TMC2240 driver with advanced features including PD (Power Delivery) negotiation, * magnetic encoder feedback, and RGB LED status indication. * * MAIN FEATURES: * 1. TMC2240 Stepper Motor Driver Control * - Configurable microstepping (1 to 256 steps) * - Current control (up to 2A+) * - SPI communication interface * - Real-time temperature monitoring * * 2. USB-C Power Delivery (PD) Integration * - FUSB302 PD controller support * - Multiple voltage profiles (5V, 9V, 12V, 15V, 20V) * - Automatic power negotiation * - Real-time voltage/current monitoring * * 3. AS5600 Magnetic Encoder Integration * - 12-bit resolution (4096 steps per revolution) * - Absolute angle positioning * - I2C communication * - Precise angle-based motor control * * 4. RGB LED Status System * - WiFi connection status indication * - PD voltage level color coding * - Motor state indication (enabled/disabled) * - Web client connection status * * 5. Web-based Control Interface * - Real-time motor control * - Parameter adjustment (speed, current, microstepping) * - System status monitoring * - Emergency stop functionality * * 6. Advanced Motion Control * - Absolute angle positioning * - Dynamic speed control * - Precision movement with tolerance * - Direction control * * WORKING PRINCIPLE: * The system operates by combining multiple control interfaces: * - Web interface provides user control and monitoring * - PD controller manages power delivery from USB-C source * - TMC2240 drives the stepper motor with precise control * - AS5600 provides position feedback for closed-loop control * - RGB LED provides visual status feedback * * HARDWARE CONNECTIONS: * - TMC2240: SPI interface (MOSI=11, MISO=13, SCK=12, CS=10) * - AS5600: I2C interface (SDA=8, SCL=9) * - FUSB302: Digital pin 7 for interrupt * - RGB LED: Pins 40(R), 42(G), 41(B) * - Motor Control: EN=14, STEP=5, DIR=6, UART_EN=15 * * AUTHOR: [Rithik Krisna M] * DATE: [28/05/2025] * VERSION: 1.0 * =============================================================================== */ // =============================================================================== // LIBRARY INCLUDES // =============================================================================== #include <SPI.h> // SPI communication for TMC2240 #include <WiFi.h> // WiFi connectivity #include <WebServer.h> // HTTP server for web interface #include "AS5600.h" // Magnetic encoder library - https://github.com/RobTillaart/AS5600 #include <Wire.h> // I2C communication #include <PD_UFP.h> // USB-C Power Delivery library - https://github.com/kcl93/fusb302_arduino #include <ArduinoJson.h> // JSON handling for web API - https://arduinojson.org/?utm_source=meta&utm_medium=library.properties // Version 7.4.1 #include "index_page.h" // HTML page for web interface // =============================================================================== // PIN DEFINITIONS // =============================================================================== #define FUSB302_INT_PIN 7 // Interrupt pin for PD controller // WiFi Configuration const char* ssid = "xxxx"; // WiFi network name const char* password = "xxxx"; // WiFi password // =============================================================================== // OBJECT INSTANTIATION // =============================================================================== WebServer server(80); // Web server on port 80 AS5600 as5600; // Magnetic encoder object PD_UFP_c PD_UFP; // Power Delivery controller object // =============================================================================== // POWER DELIVERY PROFILES CONFIGURATION // =============================================================================== // Structure to define PD power profiles with voltage, current, and name struct PDProfile { float voltage; // Voltage in volts float current; // Current in amperes const char* name; // Human-readable name }; // Predefined PD power profiles for different voltage levels const PDProfile pd_profiles[] = { {5.0, 3.0, "5V/3A"}, // USB standard power {9.0, 2.0, "9V/2A"}, // Quick charge level 1 {9.0, 3.0, "9V/3A"}, // Quick charge level 2 {12.0, 1.5, "12V/1.5A"}, // Low power 12V {15.0, 2.0, "15V/2A"}, // Medium power 15V {20.0, 1.5, "20V/1.5A"} // High voltage 20V }; // =============================================================================== // RGB LED CONFIGURATION // =============================================================================== // Pin definitions for RGB LED (common anode or cathode) const int RGB_RED_PIN = 40; // Red LED pin const int RGB_GREEN_PIN = 42; // Green LED pin const int RGB_BLUE_PIN = 41; // Blue LED pin // RGB color structure for easy color management struct RGBColor { int red; // Red component (0-255) int green; // Green component (0-255) int blue; // Blue component (0-255) }; // Predefined color constants for different states const RGBColor COLOR_OFF = { 0, 0, 0 }; // LED off const RGBColor COLOR_RED = { 255, 0, 0 }; // Error/disconnected const RGBColor COLOR_GREEN = { 0, 255, 0 }; // Connected/5V const RGBColor COLOR_BLUE = { 0, 0, 255 }; // Waiting/9V const RGBColor COLOR_YELLOW = { 255, 255, 0 }; // Connecting const RGBColor COLOR_PURPLE = { 255, 0, 255 }; // 12V const RGBColor COLOR_CYAN = { 0, 255, 255 }; // Special state const RGBColor COLOR_WHITE = { 255, 255, 255 }; // Unknown/default const RGBColor COLOR_ORANGE = { 255, 165, 0 }; // 15V const RGBColor COLOR_PINK = { 255, 20, 147 }; // 20V // =============================================================================== // LED STATE MANAGEMENT VARIABLES // =============================================================================== unsigned long lastLEDUpdate = 0; // Last LED update timestamp unsigned long ledBlinkInterval = 500; // Blink interval in milliseconds bool ledBlinkState = false; // Current blink state (on/off) RGBColor currentLEDColor = COLOR_OFF; // Current LED color bool ledBlinkEnabled = false; // Blink enable flag unsigned long lastClientRequest = 0; // Last web client request time bool webClientConnected = false; // Web client connection status bool pdColorModeActive = false; // PD color mode active flag // =============================================================================== // TMC2240 STEPPER DRIVER CONFIGURATION // =============================================================================== // SPI pin definitions for TMC2240 communication const int MOSI_PIN = 11, MISO_PIN = 13, SCK_PIN = 12, CS_PIN = 10; // Motor control pin definitions const int EN_PIN = 14; // Enable pin (LOW = enabled, HIGH = disabled) const int STEP_PIN = 5; // Step pulse pin const int DIR_PIN = 6; // Direction pin (HIGH/LOW for CW/CCW) const int UART_EN_PIN = 15; // UART enable pin (not used in SPI mode) // =============================================================================== // TMC2240 REGISTER ADDRESSES // =============================================================================== const uint8_t GCONF = 0x00; // General configuration register const uint8_t CHOPCONF = 0x6C; // Chopper configuration register const uint8_t IHOLD_IRUN = 0x10; // Current control register const uint8_t TPOWERDOWN = 0x11; // Power down delay register const uint8_t MSCNT = 0x6A; // Microstep counter register const uint8_t MSCURACT = 0x6B; // Actual microstep current register const uint8_t SG_RESULT = 0x40; // StallGuard result register const uint8_t DRV_STATUS = 0x6F; // Driver status register const uint8_t ADC_TEMP = 0x51; // Temperature ADC register // =============================================================================== // MOTOR CONFIGURATION CONSTANTS // =============================================================================== const uint16_t DEFAULT_CURRENT = 1000; // Default motor current in mA const uint8_t DEFAULT_MICROSTEPS = 16; // Default microstepping setting const uint16_t DEFAULT_SPEED = 5; // Default speed in steps/sec const int STEPS_PER_REV = 200; // Steps per revolution (1.8° motors) const int MIN_STEP_DELAY = 100; // Minimum delay between steps (μs) // =============================================================================== // GLOBAL STATE VARIABLES // =============================================================================== // Motor state variables bool isMotorEnabled = false; // Motor enable state bool currentDirection = true; // Current direction (true = forward) bool isMovingToAngle = false; // Angle movement in progress flag bool pd_negotiation_complete = false; // PD negotiation status // Motor parameter variables int currentSpeed = DEFAULT_SPEED; // Current speed setting uint16_t motorCurrent = DEFAULT_CURRENT; // Current motor current setting uint8_t microSteps = DEFAULT_MICROSTEPS; // Current microstepping setting int STEPS_PER_REVOLUTION = STEPS_PER_REV * DEFAULT_MICROSTEPS; // Total steps per rev // Position control variables float targetAngle = 0.0; // Target angle for movement float currentAngleTolerance = 0.0; // Current angle tolerance float cachedCurrentAngle = 0.0; // Cached current angle reading float cachedTemperature = 0.0; // Cached temperature reading int cachedCurrentB = 0; // Cached current reading // =============================================================================== // TIMING CONTROL VARIABLES // =============================================================================== unsigned long lastStatusUpdate = 0; // Last status update timestamp unsigned long lastEncoderUpdate = 0; // Last encoder update timestamp unsigned long last_pd_check = 0; // Last PD check timestamp unsigned long lastPDVoltageCheck = 0; // Last PD voltage check timestamp // Timing intervals (in milliseconds) const unsigned long STATUS_UPDATE_INTERVAL = 1000; // Status update every 1 second const unsigned long ENCODER_UPDATE_INTERVAL = 100; // Encoder update every 100ms const unsigned long PD_CHECK_INTERVAL = 100; // PD check every 100ms const unsigned long CLIENT_TIMEOUT = 5000; // Web client timeout (5 seconds) // Current PD profile index int currentPDProfile = 0; // Default to the first profile (5V/3A) // PD voltage check interval const unsigned long PD_VOLTAGE_CHECK_INTERVAL = 100; // =============================================================================== // RGB LED CONTROL FUNCTIONS // =============================================================================== /** * Set RGB LED to specified color * @param color RGBColor structure with red, green, blue values */ void setRGBColor(RGBColor color) { analogWrite(RGB_RED_PIN, color.red); // Set red component analogWrite(RGB_GREEN_PIN, color.green); // Set green component analogWrite(RGB_BLUE_PIN, color.blue); // Set blue component currentLEDColor = color; // Update current color } /** * Set RGB LED with individual color values * @param red Red component (0-255) * @param green Green component (0-255) * @param blue Blue component (0-255) */ void setRGBColor(int red, int green, int blue) { RGBColor color = { red, green, blue }; setRGBColor(color); } /** * Turn off RGB LED and disable blinking */ void turnOffLED() { setRGBColor(COLOR_OFF); ledBlinkEnabled = false; } /** * Enable LED blinking with specified color and interval * @param color Color to blink * @param interval Blink interval in milliseconds (default: 500ms) */ void enableLEDBlink(RGBColor color, unsigned long interval = 500) { currentLEDColor = color; ledBlinkInterval = interval; ledBlinkEnabled = true; ledBlinkState = false; lastLEDUpdate = millis(); } /** * Update blinking LED state (call in main loop) * Handles the timing and state changes for LED blinking */ void updateLEDBlink() { if (!ledBlinkEnabled) return; // Check if it's time to toggle the LED if (millis() - lastLEDUpdate >= ledBlinkInterval) { if (ledBlinkState) { setRGBColor(COLOR_OFF); // Turn off LED } else { setRGBColor(currentLEDColor); // Turn on LED with current color } ledBlinkState = !ledBlinkState; // Toggle blink state lastLEDUpdate = millis(); // Update last update time } } /** * Update status LED based on system state priority * Priority: WiFi > PD Status > Client Connection */ void updateStatusLED() { // Highest Priority: WiFi disconnected - Red fast blink if (WiFi.status() != WL_CONNECTED) { pdColorModeActive = false; enableLEDBlink(COLOR_RED, 200); // Red fast blink for WiFi issues return; } // Second Priority: PD is ready and negotiated - show voltage colors if (PD_UFP.is_power_ready() && pd_negotiation_complete) { // Only update PD colors periodically to avoid constant changes if (millis() - lastPDVoltageCheck >= PD_CHECK_INTERVAL) { updatePDVoltageColor(); lastPDVoltageCheck = millis(); } return; // Exit here - PD colors are active } // Third Priority: WiFi connected but no PD - show connection status pdColorModeActive = false; // Update client connection status based on timeout if (millis() - lastClientRequest > CLIENT_TIMEOUT) { webClientConnected = false; } if (webClientConnected) { // WiFi connected and web client active - Solid Green setRGBColor(COLOR_GREEN); ledBlinkEnabled = false; } else { // WiFi connected but no web client - Blue slow blink enableLEDBlink(COLOR_BLUE, 1000); } } /** * Update LED color based on PD voltage level * Different colors represent different voltage levels: * Green=5V, Blue=9V, Purple=12V, Orange=15V, Pink=20V, Red=Unknown */ void updatePDVoltageColor() { float voltage = pd_profiles[currentPDProfile].voltage; // Use selected profile voltage RGBColor voltageColor = COLOR_WHITE; // Default for unknown voltage // Determine color based on voltage range if (voltage >= 4.5 && voltage < 6.0) { voltageColor = COLOR_GREEN; // 5V - Green } else if (voltage >= 8.5 && voltage < 10.0) { voltageColor = COLOR_BLUE; // 9V - Blue } else if (voltage >= 11.5 && voltage < 13.0) { voltageColor = COLOR_PURPLE; // 12V - Purple } else if (voltage >= 14.5 && voltage < 16.0) { voltageColor = COLOR_ORANGE; // 15V - Orange } else if (voltage >= 19.0 && voltage < 21.0) { voltageColor = COLOR_PINK; // 20V - Pink } else if (voltage > 0.0) { voltageColor = COLOR_RED; // Unknown voltage - Red } else { voltageColor = COLOR_WHITE; // No voltage/not ready - White } // Set PD color mode active pdColorModeActive = true; // Set blink speed based on motor state if (isMotorEnabled) { enableLEDBlink(voltageColor, 200); // Fast blink when motor enabled } else { enableLEDBlink(voltageColor, 1000); // Slow blink when motor disabled } // Debug output for voltage color (can be removed in production) if (voltageColor.red == COLOR_GREEN.red && voltageColor.green == COLOR_GREEN.green) Serial.print("GREEN"); else if (voltageColor.red == COLOR_BLUE.red && voltageColor.green == COLOR_BLUE.green) Serial.print("BLUE"); else if (voltageColor.red == COLOR_PURPLE.red && voltageColor.green == COLOR_PURPLE.green) Serial.print("PURPLE"); else if (voltageColor.red == COLOR_ORANGE.red && voltageColor.green == COLOR_ORANGE.green) Serial.print("ORANGE"); else if (voltageColor.red == COLOR_PINK.red && voltageColor.green == COLOR_PINK.green) Serial.print("PINK"); else if (voltageColor.red == COLOR_RED.red && voltageColor.green == COLOR_RED.green) Serial.print("RED"); else Serial.print("WHITE"); } /** * Update motor status color when motor state changes */ void updateMotorStatusColor() { // Only update if PD is ready, otherwise let updateStatusLED handle it if (PD_UFP.is_power_ready() && pd_negotiation_complete) { updatePDVoltageColor(); } } /** * Force immediate PD color update (call when motor state changes) */ void forceUpdatePDColors() { if (PD_UFP.is_power_ready() && pd_negotiation_complete) { lastPDVoltageCheck = 0; // Force immediate update updatePDVoltageColor(); } } /** * Initialize RGB LED pins and test sequence */ void initRGBLED() { // Set pins as outputs pinMode(RGB_RED_PIN, OUTPUT); pinMode(RGB_GREEN_PIN, OUTPUT); pinMode(RGB_BLUE_PIN, OUTPUT); // Test sequence to verify LED functionality setRGBColor(COLOR_RED); delay(200); setRGBColor(COLOR_GREEN); delay(200); setRGBColor(COLOR_BLUE); delay(200); turnOffLED(); Serial.println("RGB LED initialized"); } // =============================================================================== // TMC2240 DRIVER CONTROL FUNCTIONS // =============================================================================== // Function declarations for TMC2240 control void configureDriver(); // Configure TMC2240 with default settings void setCurrent(uint16_t current); // Set motor current void setMicrostepping(uint8_t microsteps); // Set microstepping resolution void writeRegister(uint8_t address, uint32_t value); // Write to TMC2240 register uint32_t readRegister(uint8_t address); // Read from TMC2240 register void updateCachedValues(); // Update cached sensor values void moveMotor(bool direction, uint32_t steps); // Move motor specified steps void moveToAngle(); // Move to target angle float calculateAngleTolerance(uint8_t microsteps); // Calculate angle tolerance // =============================================================================== // MAIN SETUP FUNCTION // =============================================================================== void setup() { // Initialize serial communication for debugging Serial.begin(115200); Serial.println("TMC2240 Stepper Motor Control"); // Initialize RGB LED system initRGBLED(); // Initialize I2C for AS5600 encoder Wire.setPins(8, 9); // Set custom I2C pins Wire.begin(); // Initialize PD controller with 5V maximum initially PD_UFP.init(FUSB302_INT_PIN, PD_POWER_OPTION_MAX_5V); PD_UFP.set_power_option(PD_POWER_OPTION_MAX_5V); // Initialize AS5600 magnetic encoder as5600.begin(4); // Begin with direction pin 4 as5600.setDirection(AS5600_CLOCK_WISE); // Check encoder connection if (!as5600.isConnected()) { Serial.println("Warning: AS5600 encoder not connected!"); } // Initialize TMC2240 control pins pinMode(EN_PIN, OUTPUT); // Motor enable pin pinMode(STEP_PIN, OUTPUT); // Step pulse pin pinMode(DIR_PIN, OUTPUT); // Direction control pin pinMode(CS_PIN, OUTPUT); // SPI chip select pin pinMode(MOSI_PIN, OUTPUT); // SPI master out, slave in pinMode(MISO_PIN, INPUT); // SPI master in, slave out pinMode(SCK_PIN, OUTPUT); // SPI clock pin pinMode(UART_EN_PIN, OUTPUT); // UART enable (not used) // Set initial pin states digitalWrite(CS_PIN, HIGH); // Deselect TMC2240 digitalWrite(EN_PIN, HIGH); // Disable motor initially digitalWrite(UART_EN_PIN, LOW); // Disable UART mode digitalWrite(DIR_PIN, currentDirection); // Set initial direction // Initialize SPI communication SPI.begin(SCK_PIN, MISO_PIN, MOSI_PIN, CS_PIN); // Connect to WiFi network WiFi.begin(ssid, password); Serial.print("Connecting to WiFi"); setRGBColor(COLOR_YELLOW); // Yellow while connecting // Wait for WiFi connection while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); } Serial.println("\nConnected to WiFi"); Serial.print("IP Address: "); Serial.println(WiFi.localIP()); setRGBColor(COLOR_GREEN); // Green when connected delay(1000); // =============================================================================== // WEB SERVER ROUTE CONFIGURATION // =============================================================================== // Main page route - serves the web interface server.on("/", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; server.send(200, "text/html", html_page); }); // Motor enable route - enables the stepper motor server.on("/enable", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; digitalWrite(EN_PIN, LOW); // Enable motor (active low) isMotorEnabled = true; updateMotorStatusColor(); // Update LED status server.send(200, "text/plain", "Motor Enabled"); }); // Motor disable route - disables the stepper motor server.on("/disable", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; digitalWrite(EN_PIN, HIGH); // Disable motor (active low) isMotorEnabled = false; updateMotorStatusColor(); // Update LED status server.send(200, "text/plain", "Motor Disabled"); }); // Emergency stop route - immediately stops all motor activity server.on("/stop", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; digitalWrite(EN_PIN, HIGH); // Disable motor digitalWrite(STEP_PIN, LOW); // Ensure step pin is low isMotorEnabled = false; isMovingToAngle = false; // Stop angle movement currentSpeed = DEFAULT_SPEED; // Reset speed updateMotorStatusColor(); // Update LED status String response = "{\"status\":\"Emergency Stop Activated\",\"motorEnabled\":false}"; server.send(200, "application/json", response); Serial.println("EMERGENCY STOP ACTIVATED"); }); // Speed control route - sets motor speed server.on("/speed", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("value")) { currentSpeed = server.arg("value").toInt(); server.send(200, "text/plain", "Speed set to " + String(currentSpeed)); } }); // Manual movement route - moves motor by specified steps server.on("/move", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("steps")) { int targetSteps = server.arg("steps").toInt(); moveMotor(currentDirection, targetSteps); server.send(200, "text/plain", "Moving " + String(targetSteps) + " steps"); } }); // System status route - returns current system status server.on("/status", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; bool pd_ready = PD_UFP.is_power_ready(); float pd_voltage = (pd_ready && pd_negotiation_complete) ? PD_UFP.get_voltage() : 0.0; float pd_current = (pd_ready && pd_negotiation_complete) ? PD_UFP.get_current() : 0.0; String status = "{\"temperature\":" + String(cachedTemperature, 1) + ",\"current\":" + String(cachedCurrentB) + ",\"pd_ready\":" + String(pd_ready ? "true" : "false") + ",\"pd_negotiated\":" + String(pd_negotiation_complete ? "true" : "false") + ",\"pd_voltage\":" + String(pd_voltage, 2) + ",\"pd_current\":" + String(pd_current, 2) + "}"; server.send(200, "application/json", status); }); // Motor current control route - sets motor current server.on("/current", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("value")) { motorCurrent = server.arg("value").toInt(); setCurrent(motorCurrent); server.send(200, "text/plain", "Motor current set to " + String(motorCurrent) + " mA"); } }); // Microstepping control route - sets microstepping resolution server.on("/microsteps", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("value")) { microSteps = server.arg("value").toInt(); setMicrostepping(microSteps); server.send(200, "text/plain", "Microstepping set to 1/" + String(microSteps)); } }); // Encoder status route - returns current encoder readings server.on("/encoder", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; String json = String("{\"speed\":0") + ",\"rawAngle\":" + String(cachedCurrentAngle, 1) + ",\"current\":" + String(cachedCurrentB) + "}"; server.send(200, "application/json", json); }); // Power status route - returns PD power status server.on("/power_status", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; StaticJsonDocument<200> doc; bool power_ready = PD_UFP.is_power_ready(); float voltage = 0.0; float current = 0.0; if (power_ready && pd_negotiation_complete) { voltage = PD_UFP.get_voltage(); current = PD_UFP.get_current(); } bool pd_connected = power_ready && pd_negotiation_complete && voltage > 0.0 && current > 0.0; doc["pd_connected"] = pd_connected; doc["pd_negotiated"] = pd_negotiation_complete; if (pd_connected) { doc["voltage"] = voltage; doc["current"] = current; doc["power"] = voltage * current; } String response; serializeJson(doc, response); server.send(200, "application/json", response); }); // Direction control route - sets motor direction server.on("/direction", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("dir")) { currentDirection = (server.arg("dir") == "true"); digitalWrite(DIR_PIN, currentDirection); server.send(200, "text/plain", "Direction set to " + String(currentDirection ? "Forward" : "Backward")); } else { server.send(400, "text/plain", "Missing direction parameter"); } }); // Angle positioning route - moves motor to specific angle server.on("/set_angle", HTTP_GET, []() { lastClientRequest = millis(); webClientConnected = true; if (server.hasArg("angle")) { targetAngle = server.arg("angle").toFloat(); currentAngleTolerance = calculateAngleTolerance(microSteps); isMovingToAngle = true; server.send(200, "text/plain", "Moving to angle " + String(targetAngle) + "° with accuracy ±" + String(currentAngleTolerance) + "°"); } else { server.send(400, "text/plain", "Missing angle parameter"); } }); // Web server endpoint to set Power Delivery (PD) profile server.on("/setPD", HTTP_GET, []() { // Update client activity tracking lastClientRequest = millis(); webClientConnected = true; // Check if profile parameter is provided in the request if (server.hasArg("profile")) { int profile = server.arg("profile").toInt(); // Get requested voltage profile PD_power_option_t power_option; // Variable to store PD power option // Map voltage profile to corresponding PD power option and profile index switch (profile) { case 5: power_option = PD_POWER_OPTION_MAX_5V; currentPDProfile = 0; // Index of 5V profile in pd_profiles[] array break; case 9: power_option = PD_POWER_OPTION_MAX_9V; currentPDProfile = 1; // Index of 9V profile in pd_profiles[] array break; case 12: power_option = PD_POWER_OPTION_MAX_12V; currentPDProfile = 2; // Index of 12V profile in pd_profiles[] array break; case 15: power_option = PD_POWER_OPTION_MAX_15V; currentPDProfile = 3; // Index of 15V profile in pd_profiles[] array break; case 20: power_option = PD_POWER_OPTION_MAX_20V; currentPDProfile = 4; // Index of 20V profile in pd_profiles[] array break; default: // Invalid profile requested - send error response server.send(400, "text/plain", "Invalid profile"); return; } // Apply the new power option to PD controller PD_UFP.set_power_option(power_option); // Run PD controller multiple times to ensure negotiation for (int i = 0; i < 5; i++) { PD_UFP.run(); // Process PD negotiation delay(100); // Small delay between runs } // Force immediate LED color update to reflect new voltage forceUpdatePDColors(); // Send success response to client server.send(200, "text/plain", "PD profile set to " + String(profile) + "V"); } else { // No profile parameter provided - send error response server.send(400, "text/plain", "Missing profile parameter"); } }); // Start the web server server.begin(); Serial.println("HTTP server started"); // Configure the TMC2240 stepper motor driver configureDriver(); } // Main program loop - runs continuously void loop() { // Handle incoming web server requests server.handleClient(); // Run PD controller to maintain power negotiation PD_UFP.run(); // Update LED blinking animation if enabled updateLEDBlink(); // Update status LED periodically (every 100ms) to avoid constant updates static unsigned long lastStatusLEDUpdate = 0; if (millis() - lastStatusLEDUpdate >= 100) { updateStatusLED(); // Update LED based on system status lastStatusLEDUpdate = millis(); } // Handle Power Delivery controller status checks if (millis() - last_pd_check >= PD_CHECK_INTERVAL) { bool was_negotiated = pd_negotiation_complete; // Store previous state // Check if PD power is ready/negotiated if (PD_UFP.is_power_ready()) { if (!pd_negotiation_complete) { // Power negotiation just completed Serial.println("PD Power Negotiation Complete!"); pd_negotiation_complete = true; // Force immediate LED color update to show new voltage lastPDVoltageCheck = 0; } } else { if (pd_negotiation_complete) { // Power negotiation was lost Serial.println("PD Power Negotiation Lost!"); pd_negotiation_complete = false; pdColorModeActive = false; // Disable PD color mode } } last_pd_check = millis(); // Reset check timer } // Update cached sensor values periodically if (millis() - lastEncoderUpdate >= ENCODER_UPDATE_INTERVAL) { updateCachedValues(); // Update angle, temperature, current readings lastEncoderUpdate = millis(); } // Handle automatic angle-based motor movement if (isMovingToAngle) { moveToAngle(); // Continue moving motor to target angle } // Print system status to serial monitor periodically if (millis() - lastStatusUpdate >= STATUS_UPDATE_INTERVAL) { Serial.println("System Status: Motor " + String(isMotorEnabled ? "Enabled" : "Disabled") + ", Temp: " + String(cachedTemperature, 1) + "°C" + ", WiFi: " + String(WiFi.status() == WL_CONNECTED ? "Connected" : "Disconnected") + ", Web Client: " + String(webClientConnected ? "Active" : "Inactive") + ", PD Mode: " + String(pdColorModeActive ? "Active" : "Inactive")); lastStatusUpdate = millis(); } } // Configure TMC2240 stepper driver with default settings void configureDriver() { // Set motor current and microstepping to default values setCurrent(DEFAULT_CURRENT); setMicrostepping(DEFAULT_MICROSTEPS); // Set power down delay to 0 (immediate power down when idle) writeRegister(TPOWERDOWN, 0x0000); // Enable step interpolation for smoother movement uint32_t gconf = readRegister(GCONF); gconf |= (1 << 1); // Set interpolation bit writeRegister(GCONF, gconf); // Set chopper off time (TOFF) to 5 for proper operation uint32_t chopconf = readRegister(CHOPCONF); chopconf &= ~(0x0F << 0); // Clear TOFF bits chopconf |= (5 << 0); // Set TOFF to 5 writeRegister(CHOPCONF, chopconf); Serial.println("TMC2240 configured with default settings"); } // Set motor current in milliamps void setCurrent(uint16_t current) { uint32_t ihold_irun = 0; // Set hold current (current when motor is stationary) ihold_irun |= ((current / 100) & 0x1F) << 0; // Set run current (current when motor is moving) ihold_irun |= ((current / 100) & 0x1F) << 8; // Set hold delay (time before reducing to hold current) ihold_irun |= ((current / 200) & 0x0F) << 16; writeRegister(IHOLD_IRUN, ihold_irun); } // Set microstepping resolution (1, 2, 4, 8, 16, 32, 64, 128, 256) void setMicrostepping(uint8_t microsteps) { uint32_t chopconf = readRegister(CHOPCONF); chopconf &= ~(0x0F << 24); // Clear MRES bits // Convert microsteps to MRES register value uint8_t mres; switch (microsteps) { case 256: mres = 0x0; break; // 1/256 microstepping case 128: mres = 0x1; break; // 1/128 microstepping case 64: mres = 0x2; break; // 1/64 microstepping case 32: mres = 0x3; break; // 1/32 microstepping case 16: mres = 0x4; break; // 1/16 microstepping case 8: mres = 0x5; break; // 1/8 microstepping case 4: mres = 0x6; break; // 1/4 microstepping case 2: mres = 0x7; break; // 1/2 microstepping (half step) case 1: mres = 0x8; break; // Full step default: mres = 0x4; break; // Default to 1/16 microstepping } chopconf |= (mres << 24); // Set MRES bits chopconf |= (1 << 28); // Enable interpolation writeRegister(CHOPCONF, chopconf); // Update global calculation variables STEPS_PER_REVOLUTION = STEPS_PER_REV * microsteps; currentAngleTolerance = calculateAngleTolerance(microsteps); Serial.print("Microstepping set to 1/"); Serial.println(microsteps); } // Move motor a specified number of steps in given direction void moveMotor(bool direction, uint32_t steps) { digitalWrite(DIR_PIN, direction); // Set direction pin static unsigned long lastStepTime = 0; static uint32_t stepsRemaining = 0; // Initialize step counter on first call if (stepsRemaining == 0) { stepsRemaining = steps; lastStepTime = millis(); } // Generate step pulses at controlled speed if (stepsRemaining > 0 && millis() - lastStepTime >= (1000 / currentSpeed)) { digitalWrite(STEP_PIN, HIGH); // Start step pulse delayMicroseconds(10); // Short pulse duration digitalWrite(STEP_PIN, LOW); // End step pulse stepsRemaining--; // Decrement remaining steps lastStepTime = millis(); // Update timing } } // Write data to TMC2240 register via SPI void writeRegister(uint8_t address, uint32_t value) { digitalWrite(CS_PIN, LOW); // Select TMC2240 SPI.transfer(address | 0x80); // Send address with write bit SPI.transfer((value >> 24) & 0xFF); // Send MSB SPI.transfer((value >> 16) & 0xFF); // Send byte 2 SPI.transfer((value >> 8) & 0xFF); // Send byte 1 SPI.transfer(value & 0xFF); // Send LSB digitalWrite(CS_PIN, HIGH); // Deselect TMC2240 } // Read data from TMC2240 register via SPI uint32_t readRegister(uint8_t address) { digitalWrite(CS_PIN, LOW); // Select TMC2240 SPI.transfer(address & 0x7F); // Send address with read bit uint32_t value = 0; value |= (uint32_t)SPI.transfer(0) << 24; // Read MSB value |= (uint32_t)SPI.transfer(0) << 16; // Read byte 2 value |= (uint32_t)SPI.transfer(0) << 8; // Read byte 1 value |= (uint32_t)SPI.transfer(0); // Read LSB digitalWrite(CS_PIN, HIGH); // Deselect TMC2240 return value; } // Update cached sensor values (called periodically to reduce I2C/SPI traffic) void updateCachedValues() { // Read and convert encoder angle from raw value to degrees cachedCurrentAngle = (as5600.readAngle() * 360.0) / 4096.0; // Read TMC2240 temperature sensor with filtering uint32_t adc_temp = readRegister(ADC_TEMP); int temp_raw = adc_temp & 0x1FFF; // Extract temperature bits float temp_celsius = (temp_raw - 2000) * 0.13; // Convert to Celsius // Only update temperature if reading is within reasonable range if (temp_celsius >= -50 && temp_celsius <= 150) { cachedTemperature = temp_celsius; } // Read motor current from TMC2240 uint32_t mscuract = readRegister(MSCURACT); cachedCurrentB = mscuract & 0x01FF; // Extract current bits } // Automatically move motor to target angle with precision control void moveToAngle() { if (!isMovingToAngle) return; // Exit if not in angle movement mode // Calculate angular difference to target float angleDifference = targetAngle - cachedCurrentAngle; // Normalize angle difference to [-180, 180] range for shortest path while (angleDifference > 180) angleDifference -= 360; while (angleDifference < -180) angleDifference += 360; // Check if target angle is reached within tolerance if (abs(angleDifference) <= currentAngleTolerance) { isMovingToAngle = false; // Stop angle movement mode digitalWrite(STEP_PIN, LOW); // Ensure step pin is low Serial.println("Target angle reached"); return; } // Set motor direction based on angle difference currentDirection = (angleDifference > 0); // Positive = forward digitalWrite(DIR_PIN, currentDirection); // Enable motor if not already enabled if (!isMotorEnabled) { digitalWrite(EN_PIN, LOW); // Enable motor (active low) isMotorEnabled = true; } // Dynamic speed control based on remaining angle int stepDelay; float absDiff = abs(angleDifference); // Slower speeds as we approach target for better precision if (absDiff <= 5.0) { stepDelay = MIN_STEP_DELAY * 4; // Very slow for final approach } else if (absDiff <= 20.0) { stepDelay = MIN_STEP_DELAY * 2; // Slow for precision zone } else if (absDiff <= 45.0) { stepDelay = MIN_STEP_DELAY; // Normal speed } else { stepDelay = MIN_STEP_DELAY / 2; // Fast for large movements } // Take multiple steps toward target (up to 10 per loop iteration) for (int i = 0; i < 10 && isMovingToAngle; i++) { digitalWrite(STEP_PIN, HIGH); // Start step pulse delayMicroseconds(stepDelay); // Pulse high time digitalWrite(STEP_PIN, LOW); // End step pulse delayMicroseconds(stepDelay); // Pulse low time // Update current angle reading after each step cachedCurrentAngle = (as5600.readAngle() * 360.0) / 4096.0; angleDifference = targetAngle - cachedCurrentAngle; // Re-normalize angle difference while (angleDifference > 180) angleDifference -= 360; while (angleDifference < -180) angleDifference += 360; // Check if target reached during movement if (abs(angleDifference) <= currentAngleTolerance) { isMovingToAngle = false; Serial.println("Target angle reached during movement"); break; } } } // Calculate angle tolerance based on microstepping resolution float calculateAngleTolerance(uint8_t microsteps) { // Calculate minimum angle step and multiply by 2 for tolerance return (360.0 / (200.0 * microsteps)) * 2.0; } |
