From 90d951f191c315d502a7586efe1975ffae31e19c Mon Sep 17 00:00:00 2001
From: Jochen Vothknecht <jochen3120@gmail.com>
Date: Thu, 26 Oct 2023 06:10:31 +0200
Subject: [PATCH] Adding BallMill and SimplePWM

---
 .../SiliconTorch/Hardware/SimplePWM.cpp       | 127 +++++++++++++++++
 .../SiliconTorch/Hardware/SimplePWM.hpp       |  63 +++++++++
 CLC-qthing/SiliconTorch/Service/BallMill.cpp  | 130 ++++++++++++++++++
 CLC-qthing/SiliconTorch/Service/BallMill.hpp  |  74 ++++++++++
 .../SiliconTorch/Service/SpiderFurnace.hpp    |   2 +-
 .../SiliconTorch/Service/__services__.cpp     |   2 +
 6 files changed, 397 insertions(+), 1 deletion(-)
 create mode 100644 CLC-qthing/SiliconTorch/Hardware/SimplePWM.cpp
 create mode 100644 CLC-qthing/SiliconTorch/Hardware/SimplePWM.hpp
 create mode 100644 CLC-qthing/SiliconTorch/Service/BallMill.cpp
 create mode 100644 CLC-qthing/SiliconTorch/Service/BallMill.hpp

diff --git a/CLC-qthing/SiliconTorch/Hardware/SimplePWM.cpp b/CLC-qthing/SiliconTorch/Hardware/SimplePWM.cpp
new file mode 100644
index 0000000..657ca38
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Hardware/SimplePWM.cpp
@@ -0,0 +1,127 @@
+#include "SimplePWM.hpp"
+
+// C++ system level
+// #include <cstdio>     // sprintf
+// #include <functional>
+
+// ESP32 specific
+#include "esp_log.h"
+#include "driver/gpio.h"
+#include "driver/ledc.h"
+
+// project specific
+#include <Types.hpp>
+#include "SiliconTorch/NVSExplorer.hpp"
+
+// qthing stuff
+#include <qthing>
+//#include <qthing/mqtt_common.hpp>
+#include "SiliconTorch/CyanBus.hpp"
+
+
+
+static const char* TAG = "SimplePWM";
+
+
+namespace SimplePWM {
+
+  SimplePWM::SimplePWM(u8 gpio, u8 ledcChannel, u8 timerChannel) {
+
+    // Setup IO
+    gpio_config_t conf;
+
+    conf.intr_type    = GPIO_INTR_DISABLE;
+    conf.mode         = GPIO_MODE_OUTPUT;
+    conf.pin_bit_mask = 1ULL << gpio;
+    conf.pull_up_en   = GPIO_PULLUP_DISABLE;
+    conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+
+    gpio_config(&conf);
+    gpio_set_level((gpio_num_t)gpio, 0);
+
+
+    // Setup PWM
+    timer_cfg.duty_resolution = (ledc_timer_bit_t)resolution;
+    timer_cfg.freq_hz         = frequency;
+    timer_cfg.speed_mode      = LEDC_HIGH_SPEED_MODE;
+    timer_cfg.timer_num       = (ledc_timer_t)timerChannel;
+    timer_cfg.clk_cfg         = LEDC_AUTO_CLK;
+
+    channel_cfg.channel      = (ledc_channel_t)ledcChannel;
+    channel_cfg.duty         = 0;
+    channel_cfg.gpio_num     = (gpio_num_t)gpio;
+    channel_cfg.speed_mode   = LEDC_HIGH_SPEED_MODE;
+    channel_cfg.hpoint       = 0;
+    channel_cfg.timer_sel    = (ledc_timer_t)timerChannel;
+    channel_cfg.intr_type    = LEDC_INTR_DISABLE; 
+
+    ledc_fade_func_install(0);
+    ledc_timer_config(&timer_cfg);
+    ledc_channel_config(&channel_cfg);
+
+  }
+
+
+  void SimplePWM::setDuty(u32 _duty) {
+
+    u32 limit = (1 << resolution) - 1;
+
+    if (_duty > limit) _duty = limit;
+
+    ledc_set_duty(LEDC_HIGH_SPEED_MODE, channel_cfg.channel, _duty);
+    ledc_update_duty(LEDC_HIGH_SPEED_MODE, channel_cfg.channel);
+
+    duty = _duty;
+  }
+
+  void SimplePWM::setDutyFloat(f32 _duty) {
+
+    if (_duty < 0.0f) _duty = 0.0f;
+    if (_duty > 1.0f) _duty = 1.0f;
+
+    u32 limit = (1 << resolution) - 1;
+
+    setDuty(_duty * limit);
+  }
+
+  bool SimplePWM::setFrequency(u32 _frq) {
+    return setFrqRes(_frq, resolution);
+  }
+
+  bool SimplePWM::setResolution(u8 _res) {
+    return setFrqRes(frequency, _res);
+  }
+
+  bool SimplePWM::setFrqRes(u32 _frq, u8 _res) {
+
+    timer_cfg.duty_resolution = (ledc_timer_bit_t)_res;
+    timer_cfg.freq_hz = _frq;
+
+    if (ledc_timer_config(&timer_cfg) == ESP_OK) {
+      frequency  = _frq;
+      resolution = _res;
+
+      ESP_LOGI(TAG, "frequency[ %i Hz ]  resolution[ %i bit ]", _frq, _res);
+      return true;
+
+    } else {
+
+      ESP_LOGW(TAG, "Invalid frequency[ %i Hz ] and resolution[ %i bit ] setting!", _frq, _res);
+      return false;
+
+    }
+  }
+
+  u32 SimplePWM::getDuty() const {
+    return duty;
+  }
+
+  u32 SimplePWM::getFrequency() const {
+    return frequency;
+  }
+
+  u8 SimplePWM::getResolution() const {
+    return resolution;
+  }
+
+}
diff --git a/CLC-qthing/SiliconTorch/Hardware/SimplePWM.hpp b/CLC-qthing/SiliconTorch/Hardware/SimplePWM.hpp
new file mode 100644
index 0000000..781b227
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Hardware/SimplePWM.hpp
@@ -0,0 +1,63 @@
+#pragma once
+
+// C++ system level
+// #include <cstdio>     // sprintf
+// #include <functional>
+
+// ESP32 specific
+#include "esp_log.h"
+#include "driver/ledc.h"
+
+// project specific
+#include <Types.hpp>
+#include "SiliconTorch/NVSExplorer.hpp"
+
+// qthing stuff
+#include <qthing>
+//#include <qthing/mqtt_common.hpp>
+#include "SiliconTorch/CyanBus.hpp"
+
+
+
+namespace SimplePWM {
+
+  constexpr u16 DEFAULT_FREQUENCY  = 1000;
+  constexpr u16 DEFAULT_RESOLUTION =   10;
+
+
+  class SimplePWM {
+
+    public:
+
+      SimplePWM(u8 gpio, u8 ledcChannel = 0, u8 timerChannel = 0);
+
+
+      u32 getDuty() const;
+      u32 getFrequency() const;
+      u8  getResolution() const;
+
+      void setDuty(u32 _duty);
+      void setDutyFloat(f32 _duty);  // accepts range [0, 1]
+      bool setFrequency(u32 _frq);
+      bool setResolution(u8 _res);
+      bool setFrqRes(u32 _frq, u8 _res);
+
+
+      // TODO: should we block copy/assignment by default…?
+      //SimplePWM() {};
+      SimplePWM(const SimplePWM&) = delete;
+      SimplePWM& operator=(SimplePWM const&) = delete;
+
+
+    private:
+
+      u32 frequency  = DEFAULT_FREQUENCY;
+      u8  resolution = DEFAULT_RESOLUTION;
+      u32 duty       = 0;
+
+      ledc_timer_config_t   timer_cfg;
+      ledc_channel_config_t channel_cfg;
+
+  };
+
+}
diff --git a/CLC-qthing/SiliconTorch/Service/BallMill.cpp b/CLC-qthing/SiliconTorch/Service/BallMill.cpp
new file mode 100644
index 0000000..7c7adae
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Service/BallMill.cpp
@@ -0,0 +1,130 @@
+#include "BallMill.hpp"
+
+// C++ system level
+#include <cstdio>     // sprintf
+// #include <functional>
+
+// ESP32 specific
+#include "esp_log.h"
+#include "driver/gpio.h"
+
+// project specific
+#include <Time.hpp>
+#include <Types.hpp>
+#include "SiliconTorch/NVSExplorer.hpp"
+
+// qthing stuff
+#include <qthing>
+//#include <qthing/mqtt_common.hpp>
+#include "SiliconTorch/CyanBus.hpp"
+
+// misc
+#include <nlohmann/json.hpp>
+
+
+using nlohmann::json;
+using SpiderLib::Time;
+
+
+static const char* TAG = "BallMill";
+
+static u32 _frq = 0;
+
+namespace SiliconTorch {
+
+  namespace Service {
+
+    namespace BallMill {
+
+      void BallMill::init() {
+        setIcon("🪩");
+        setName("BallMill");
+        setNameSpace("BallMill");
+      }
+
+
+      void BallMill::start() {
+
+        /*
+        // Load config values from NVS
+        f32 _kP = SiliconTorch::NVSExplorer::NVSExplorer::instance().getFloat(getNameSpace(), "kP");
+        f32 _kI = SiliconTorch::NVSExplorer::NVSExplorer::instance().getFloat(getNameSpace(), "kI");
+        f32 _kD = SiliconTorch::NVSExplorer::NVSExplorer::instance().getFloat(getNameSpace(), "kD");
+
+        if (!std::isnan(_kP)) kP = _kP;
+        if (!std::isnan(_kI)) kI = _kI;
+        if (!std::isnan(_kD)) kD = _kD;
+
+        ESP_LOGI(TAG, "PIDvals: kP[ %.3f ]  kI[ %.3f ]  kD[ %.3f ]", kP, kI, kD);
+        */
+
+        ch0 = new SimplePWM::SimplePWM(CH0_STEP);
+        ch0->setFrqRes(1, 10);
+        ch0->setDuty(0);
+
+        // Setup stepper task
+        stepTask = new SpiderLib::Util::LambdaTask([&](){
+
+          TickType_t lastWakeTime = xTaskGetTickCount();
+
+          while (true) {
+
+            i32 frq = currentRPM / 60.0f * CFG_STEPS_PER_ROUND;
+
+            // TODO: Handle negative FRQ!!
+            //if (frq > 0) {
+            //  ch0->setDuty(1);
+            //  ch0->setFrqRes(frq, 1);
+            //} else {
+            //  ch0->setDuty(0);
+            //}
+
+            ch0->setFrqRes(_frq, 10);
+            ch0->setDuty(1 << (10 - 1));  // TODO: is setDutyFloat(0.5f) precise enough…?
+
+
+            if (abs(targetRPM - currentRPM) < CFG_RPM_CHANGE_PER_SECOND) {
+              currentRPM = targetRPM;
+            } else {
+              f32 sign = (targetRPM - currentRPM) < 0.0f ? -1.0f : 1.0f;
+              currentRPM = targetRPM + sign * CFG_RPM_CHANGE_PER_SECOND;
+            }
+
+            ESP_LOGI(TAG, "frq[ %i ]  currentRPM[ %f ]  targetRPM[ %f ]", frq, currentRPM, targetRPM);
+
+            vTaskDelayUntil(&lastWakeTime, 1000 / CFG_TICK_FRQ / portTICK_PERIOD_MS);
+          }
+        });
+
+
+        qthing::add_message_callback(deviceTopic(TAG, "setRPM"), [&](const str& message) {
+          f32 rpm = std::strtof(message.c_str(), NULL);
+
+          if (!std::isnan(rpm))
+            targetRPM = rpm;
+        });
+
+        qthing::add_message_callback(deviceTopic(TAG, "setFRQ"), [&](const str& message) {
+          _frq = std::strtol(message.c_str(), NULL, 0);
+        });
+
+      }
+
+
+
+      json BallMill::getConfigJSON() const {
+
+        json out;
+
+        out["bla"] = "fasel";
+
+        return out;
+      }
+
+
+
+    }
+  }
+}
+
+
diff --git a/CLC-qthing/SiliconTorch/Service/BallMill.hpp b/CLC-qthing/SiliconTorch/Service/BallMill.hpp
new file mode 100644
index 0000000..082c729
--- /dev/null
+++ b/CLC-qthing/SiliconTorch/Service/BallMill.hpp
@@ -0,0 +1,74 @@
+#pragma once
+
+// C++ system level
+#include <vector>
+// #include <cstring>     // memset, strncmp
+// #include <cstdlib>     // TODO: is this for memcpy?
+// #include <functional>
+
+// ESP32 specific
+#include "esp_log.h"
+#include "driver/ledc.h"
+
+// project specific
+#include <SpiderLib.hpp>
+#include "Service.hpp"
+#include "../Hardware/SimplePWM.hpp"
+
+// qthing stuff
+#include "SiliconTorch/FxCyanRGB8.hpp"
+// #include <qthing>
+
+// misc
+#include <nlohmann/json.hpp>
+
+
+namespace SiliconTorch {
+
+  namespace Service {
+
+    namespace BallMill {
+
+
+      constexpr u8  CH0_STEP      = 23;
+      constexpr u8  CH0_DIRECTION = 22;
+
+      constexpr u8  CFG_TICK_FRQ              =  10;
+      constexpr u16 CFG_STEPS_PER_ROUND       = 200;
+      constexpr f32 CFG_RPM_CHANGE_PER_SECOND = 13.37f;
+
+
+
+
+      class BallMill : public ServiceManager::Service, public SpiderLib::HasMQTT {
+
+        public:
+
+          void init();
+
+          void start();
+
+          virtual nlohmann::json getConfigJSON() const;
+
+
+          // TODO: should we block copy/assignment by default…?
+          BallMill() {};
+          BallMill(const BallMill&) = delete;
+          BallMill& operator=(BallMill const&) = delete;
+
+
+        private:
+
+          f32 targetRPM  = 0.0f;
+          f32 currentRPM = 0.0f;
+
+          SimplePWM::SimplePWM* ch0 = NULL;
+
+          SpiderLib::Util::LambdaTask* stepTask = NULL;
+
+      };
+
+
+    }
+  }
+}
diff --git a/CLC-qthing/SiliconTorch/Service/SpiderFurnace.hpp b/CLC-qthing/SiliconTorch/Service/SpiderFurnace.hpp
index f3c9b00..ee549a4 100644
--- a/CLC-qthing/SiliconTorch/Service/SpiderFurnace.hpp
+++ b/CLC-qthing/SiliconTorch/Service/SpiderFurnace.hpp
@@ -46,7 +46,7 @@ namespace SiliconTorch {
       constexpr u8  IO_MOSI                =    16;
       constexpr u8  IO_SCLK                =    27;
       constexpr u8  IO_HEATER              =    22;
-
+      constexpr u8  IO_FAN                 =    23;
 
       // enum FurnaceState {
       //   RUNNING,
diff --git a/CLC-qthing/SiliconTorch/Service/__services__.cpp b/CLC-qthing/SiliconTorch/Service/__services__.cpp
index b830724..ec7ae05 100644
--- a/CLC-qthing/SiliconTorch/Service/__services__.cpp
+++ b/CLC-qthing/SiliconTorch/Service/__services__.cpp
@@ -3,6 +3,7 @@
 // our services
 #include "FxCyanF.hpp"
 #include "CyanBus.hpp"
+#include "BallMill.hpp"
 #include "FxPublish.hpp"
 #include "CyanStripe.hpp"
 #include "SpiderFurnace.hpp"
@@ -19,6 +20,7 @@ namespace SiliconTorch {
       mgr->registerService(new SiliconTorch::Service::FxCyanF());
       mgr->registerService(new SiliconTorch::Service::CyanBus());
       mgr->registerService(new SiliconTorch::Service::FxPublish());
+      mgr->registerService(new SiliconTorch::Service::BallMill::BallMill());
       mgr->registerService(new SiliconTorch::Service::CyanStripe::CyanStripe());
       mgr->registerService(new SiliconTorch::Service::SpiderFurnace::SpiderFurnace());
 
-- 
GitLab