From 0594ff5f4d24a050df3945b3bbe3ddba5df4f4ef Mon Sep 17 00:00:00 2001 From: Stefan de Bruijn Date: Tue, 3 Nov 2020 13:35:56 +0100 Subject: [PATCH] Finished implementing PWM and UART pin users --- Grbl_Esp32/src/PinUsers/PwmPin.cpp | 84 ++++++++++++++++++++++++++++++ Grbl_Esp32/src/PinUsers/PwmPin.h | 36 +++++++++++++ Grbl_Esp32/src/PinUsers/Uart.cpp | 1 + Grbl_Esp32/src/PinUsers/Uart.h | 3 +- 4 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 Grbl_Esp32/src/PinUsers/PwmPin.cpp create mode 100644 Grbl_Esp32/src/PinUsers/PwmPin.h diff --git a/Grbl_Esp32/src/PinUsers/PwmPin.cpp b/Grbl_Esp32/src/PinUsers/PwmPin.cpp new file mode 100644 index 00000000..1e90666b --- /dev/null +++ b/Grbl_Esp32/src/PinUsers/PwmPin.cpp @@ -0,0 +1,84 @@ +#include "PwmPin.h" + +#include "../Pin.h" +#include "../Assert.h" +#include "LimitedResource.h" + +#include + +namespace PinUsers { + class NativePwm : public PwmDetail { + static LimitedResource<16>& PwmChannelResources() { + // The ESP32 chip has 16 PWM channels. + + static LimitedResource<16> instances_; + return instances_; + } + + Pin pin_; + int pwmChannel_; + uint32_t frequency_; + uint32_t maxDuty_; + uint8_t resolutionBits_; + + /* + Calculate the highest precision of a PWM based on the frequency in bits + + 80,000,000 / freq = period + determine the highest precision where (1 << precision) < period + */ + uint8_t calculatePwmPrecision(uint32_t freq) { + uint8_t precision = 0; + + // increase the precision (bits) until it exceeds allow by frequency the max or is 16 + // TODO is there a named value for the 80MHz? + while ((1 << precision) < (uint32_t)(80000000 / freq) && precision <= 16) { + precision++; + } + + return precision - 1; + } + + public: + NativePwm(Pin pin, uint32_t frequency, uint32_t maxDuty) : frequency_(frequency), maxDuty_(maxDuty) { + auto native = pin.getNative(Pin::Capabilities::PWM | Pin::Capabilities::Native); + + pwmChannel_ = PwmChannelResources().tryClaim(); + Assert(pwmChannel_ != -1, "PWM Channel could not be claimed. Are all PWM channels in use?"); + + resolutionBits_ = calculatePwmPrecision(frequency); + + ledcSetup(pwmChannel_, frequency, resolutionBits_); + ledcAttachPin(native, pwmChannel_); + ledcWrite(pwmChannel_, 0); + + // grbl_msg_sendf(CLIENT_SERIAL, MsgLevel::Info, "PWM Output:%d on Pin:%s Freq:%0.0fHz", _number, _pin.name().c_str(), _pwm_frequency); + } + + uint32_t getFrequency() const override { return frequency_; } + uint32_t getMaxDuty() const override { return maxDuty_; } + + void setValue(float value) { + if (value < 0) { + value = 0; + } else if (value > 1) { + value = 1; + } + + auto duty = value * (uint32_t(1) << int(resolutionBits_)); + ledcWrite(pwmChannel_, uint32_t(duty)); + } + + ~NativePwm() override { + ledcWrite(pwmChannel_, 0); + PwmChannelResources().release(pwmChannel_); + } + }; + + PwmPin::PwmPin(Pin pin, uint32_t frequency, uint32_t maxDuty) { + Assert(pin.capabilities().has(Pin::Capabilities::PWM | Pin::Capabilities::Native), "Pin does not support PWM"); + + // For now, we only support Native pins. In the future, we might support other pins. + _detail = new NativePwm(pin, frequency, maxDuty); + } +} diff --git a/Grbl_Esp32/src/PinUsers/PwmPin.h b/Grbl_Esp32/src/PinUsers/PwmPin.h new file mode 100644 index 00000000..9b2b2427 --- /dev/null +++ b/Grbl_Esp32/src/PinUsers/PwmPin.h @@ -0,0 +1,36 @@ +#pragma once + +#include "../Pin.h" +#include "../Assert.h" + +namespace PinUsers { + class PwmDetail { + public: + virtual uint32_t getFrequency() const = 0; + virtual uint32_t getMaxDuty() const = 0; + + // Sets the PWM value from 0..1. + virtual void setValue(float value) = 0; + + virtual ~PwmDetail() {} + }; + + class PwmPin { + Pin _pin; + PwmDetail* _detail; + + public: + PwmPin() : _pin(Pin::UNDEFINED), _detail(nullptr) {} + PwmPin(Pin pin, uint32_t frequency, uint32_t maxDuty); + + // Returns actual frequency which might not be exactly the same as requested(nearest supported value) + inline uint32_t getFrequency() const { return _detail->getFrequency(); } + + // Returns actual maxDuty which might not be exactly the same as requested(nearest supported value) + inline uint32_t getMaxDuty() const { return _detail->getMaxDuty(); } + + inline void setValue(float value) const { return _detail->setValue(value); } + + inline ~PwmPin() { delete _detail; } + }; +} diff --git a/Grbl_Esp32/src/PinUsers/Uart.cpp b/Grbl_Esp32/src/PinUsers/Uart.cpp index 07e3b3c8..6e821c06 100644 --- a/Grbl_Esp32/src/PinUsers/Uart.cpp +++ b/Grbl_Esp32/src/PinUsers/Uart.cpp @@ -4,6 +4,7 @@ #include "../Pins/PinOptionsParser.h" #include "../Assert.h" #include "LimitedResource.h" + #include namespace PinUsers { diff --git a/Grbl_Esp32/src/PinUsers/Uart.h b/Grbl_Esp32/src/PinUsers/Uart.h index 98170d28..c2e4df1c 100644 --- a/Grbl_Esp32/src/PinUsers/Uart.h +++ b/Grbl_Esp32/src/PinUsers/Uart.h @@ -1,9 +1,10 @@ #pragma once #include "../Pin.h" -#include #include "../Assert.h" +#include + // TODO FIXME: Uart also suffers from the way settings works: What you would want is 2 phases, // so (1) validation and preparation, and (2) building the actual uart. Now, it's all combined // in the single constructor, which works, but could throw an exception.