Skip to content

Commit

Permalink
Merge pull request #554 from bdring/Devt
Browse files Browse the repository at this point in the history
Devt
  • Loading branch information
bdring authored Aug 4, 2022
2 parents 5db1039 + 49149f2 commit 6863256
Show file tree
Hide file tree
Showing 36 changed files with 332 additions and 612 deletions.
130 changes: 130 additions & 0 deletions FluidNC/esp32/PwmPin.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// Copyright 2022 - Mitch Bradley
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

/*
PWM capabilities provided by the ESP32 LEDC controller via the ESP-IDF driver
*/

#include "Driver/PwmPin.h"
#include "src/Logging.h"

#include <soc/ledc_struct.h> // LEDC

#include "soc/soc_caps.h"
#include "driver/ledc.h"

//Use XTAL clock if possible to avoid timer frequency error when setting APB clock < 80 Mhz
//Need to be fixed in ESP-IDF
#ifdef SOC_LEDC_SUPPORT_XTAL_CLOCK
# define LEDC_DEFAULT_CLK LEDC_USE_XTAL_CLK
#else
# define LEDC_DEFAULT_CLK LEDC_AUTO_CLK
#endif

#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
# include "esp32/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32S2
# include "esp32s2/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32S3
# include "esp32s3/rom/gpio.h"
#elif CONFIG_IDF_TARGET_ESP32C3
# include "esp32c3/rom/gpio.h"
#else
# error Target CONFIG_IDF_TARGET is not supported
#endif

static int allocateChannel() {
static int nextLedcChannel = 0;

// Increment by 2 because there are only 4 timers so only
// four completely independent channels. We could be
// smarter about this and look for an unallocated channel
// that is already on the same frequency. There is some
// code for that in PinUsers/PwmPin.cpp TryGrabChannel()

Assert(nextLedcChannel < 8, "Out of LEDC PwmPin channels");
nextLedcChannel += 2;
return nextLedcChannel - 2;
}

// Calculate the highest PwmPin precision in bits for the desired frequency
// 80,000,000 (APB Clock) = freq * maxCount
// maxCount is a power of two between 2^1 and 2^20
// frequency is at most 80,000,000 / 2^1 = 40,000,000, limited elsewhere
// to 20,000,000 to give a period of at least 2^2 = 4 levels of control.
static uint8_t calc_pwm_precision(uint32_t frequency) {
if (frequency == 0) {
frequency = 1; // Limited elsewhere but just to be safe...
}

// Increase the precision (bits) until it exceeds the frequency
// The hardware maximum precision is 20 bits
const uint8_t ledcMaxBits = 20;
const uint32_t apbFreq = 80000000;
const uint32_t maxCount = apbFreq / frequency;
for (uint8_t bits = 2; bits <= ledcMaxBits; ++bits) {
if ((1u << bits) > maxCount) {
return bits - 1;
}
}

return ledcMaxBits;
}

PwmPin::PwmPin(Pin& pin, uint32_t frequency) : _frequency(frequency) {
uint8_t bits = calc_pwm_precision(frequency);
_period = (1 << bits) - 1;
_channel = allocateChannel();
uint8_t group = (_channel / 8);
ledc_timer_t timer = ledc_timer_t((_channel / 2) % 4);

ledc_timer_config_t ledc_timer = { .speed_mode = ledc_mode_t(group),
.duty_resolution = ledc_timer_bit_t(bits),
.timer_num = timer,
.freq_hz = frequency,
.clk_cfg = LEDC_DEFAULT_CLK };

if (ledc_timer_config(&ledc_timer) != ESP_OK) {
log_error("ledc timer setup failed");
throw -1;
}

_gpio = pin.getNative(Pin::Capabilities::PWM);

bool isActiveLow = pin.getAttr().has(Pin::Attr::ActiveLow);

ledc_channel_config_t ledc_channel = { .gpio_num = _gpio,
.speed_mode = ledc_mode_t(group),
.channel = ledc_channel_t(_channel),
.intr_type = LEDC_INTR_DISABLE,
.timer_sel = timer,
.duty = 0,
.hpoint = 0,
.flags = { .output_invert = isActiveLow } };
if (ledc_channel_config(&ledc_channel) != ESP_OK) {
log_error("ledc channel setup failed");
throw -1;
}
}

void IRAM_ATTR PwmPin::setDuty(uint32_t duty) {
uint8_t g = _channel >> 3, c = _channel & 7;
bool on = duty != 0;

// This is like ledcWrite, but it is called from an ISR
// and ledcWrite uses RTOS features not compatible with ISRs
// Also, ledcWrite infers enable from duty, which is incorrect
// for use with RcServo which wants the

// We might be able to use ledc_duty_config() which is IRAM_ATTR

LEDC.channel_group[g].channel[c].duty.duty = duty << 4;
LEDC.channel_group[g].channel[c].conf0.sig_out_en = on;
LEDC.channel_group[g].channel[c].conf1.duty_start = on;
}

PwmPin::~PwmPin() {
// XXX we need to release the channel so it can be reused by later calls to pwmInit
#define MATRIX_DETACH_OUT_SIG 0x100
gpio_matrix_out(_gpio, MATRIX_DETACH_OUT_SIG, false, false);
}
24 changes: 24 additions & 0 deletions FluidNC/include/Driver/PwmPin.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright 2022 - Mitch Bradley
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

#pragma once

// PWM driver interface

#include "src/Pin.h"

class PwmPin {
public:
PwmPin(Pin& pin, uint32_t frequency);
~PwmPin();
uint32_t frequency() { return _frequency; }
uint32_t period() { return _period; }

void setDuty(uint32_t duty);

private:
uint32_t _frequency;
int _channel;
int _period;
int _gpio;
};
2 changes: 1 addition & 1 deletion FluidNC/ld/esp32/vtable_in_dram.ld
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,5 @@

/* Pin Details */
**Detail.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*)
*LedcPin.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*)
*PwmPin.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*)
*Pin.cpp.o(.rodata .rodata.* .xt.prop .xt.prop.*)
11 changes: 10 additions & 1 deletion FluidNC/src/GCode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ Error gc_execute_line(char* line, Channel& channel) {
bool jogMotion = false;
bool checkMantissa = false;
bool clockwiseArc = false;
bool probeExplicit = false;
bool probeAway = false;
bool probeNoError = false;
bool syncLaser = false;
Expand Down Expand Up @@ -311,6 +312,14 @@ Error gc_execute_line(char* line, Channel& channel) {
if (axis_command != AxisCommand::None) {
FAIL(Error::GcodeAxisCommandConflict); // [Axis word/command conflict]
}

// Indicate that the block contains an explicit G38.n word. This lets us
// allow "G53 G38.n ..." without also allowing a sequence like
// "G38.n F10 Z-5 \n G53 X5". In other words, we can use G53 to make the
// probe limit be in machine coordinates, without causing a later G53 that
// has no motion word to infer G38.n mode.
probeExplicit = true;

axis_command = AxisCommand::MotionMode;
switch (mantissa) {
case 20:
Expand Down Expand Up @@ -1063,7 +1072,7 @@ Error gc_execute_line(char* line, Channel& channel) {
case NonModal::AbsoluteOverride:
// [G53 Errors]: G0 and G1 are not active. Cutter compensation is enabled.
// NOTE: All explicit axis word commands are in this modal group. So no implicit check necessary.
if (!(gc_block.modal.motion == Motion::Seek || gc_block.modal.motion == Motion::Linear)) {
if (!(probeExplicit || gc_block.modal.motion == Motion::Seek || gc_block.modal.motion == Motion::Linear)) {
FAIL(Error::GcodeG53InvalidMotionMode); // [G53 G0/1 not active]
}
break;
Expand Down
5 changes: 5 additions & 0 deletions FluidNC/src/Machine/Axis.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ namespace Machine {
set_bitnum(Axes::homingMask, _axis);
}

if (!_motors[0] && _motors[1]) {
sys.state = State::ConfigAlarm;
log_error("motor1 defined without motor0");
}

// If dual motors and only one motor has switches, this is the configuration
// for a POG style squaring. The switch should report as being on both axes
if (hasDualMotor() && (motorsWithSwitches() == 1)) {
Expand Down
33 changes: 11 additions & 22 deletions FluidNC/src/Machine/UserOutputs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@
// Use of this source code is governed by a GPLv3 license that can be found in the LICENSE file.

#include "UserOutputs.h"
#include "../Logging.h" // log_*
#include "../Pins/LedcPin.h" // ledcInit()
#include <esp32-hal-ledc.h> // ledc*
#include <esp32-hal-cpu.h> // getApbFrequency()
#include "../Logging.h" // log_*
#include <esp32-hal-cpu.h> // getApbFrequency()

namespace Machine {
UserOutputs::UserOutputs() {
Expand All @@ -32,18 +30,9 @@ namespace Machine {
uint8_t resolution_bits;
Pin& pin = _analogOutput[i];
if (pin.defined()) {
// increase the precision (bits) until it exceeds allow by frequency the max or is 16
resolution_bits = 0;
auto pwm_frequency = _analogFrequency[i];
while ((1u << resolution_bits) < (apb_frequency / pwm_frequency) && resolution_bits <= 16) {
++resolution_bits;
}
// resolution_bits is now just barely too high, so drop it down one
--resolution_bits;
_denominator[i] = 1UL << resolution_bits;
_pwm_channel[i] = ledcInit(pin, -1, pwm_frequency, resolution_bits);
ledcWrite(_pwm_channel[i], 0);
log_info("User Analog Output " << i << " on Pin:" << pin.name() << " Freq:" << pwm_frequency << "Hz");
_pwm[i] = new PwmPin(pin, _analogFrequency[i]);
_pwm[i]->setDuty(0);
log_info("User Analog Output " << i << " on Pin:" << pin.name() << " Freq:" << _pwm[i]->frequency() << "Hz");
}
}
}
Expand Down Expand Up @@ -72,21 +61,21 @@ namespace Machine {
return percent == 0.0;
}

uint32_t numerator = uint32_t(percent / 100.0f * _denominator[io_num]);
uint32_t duty = uint32_t(percent / 100.0f * _denominator[io_num]);

auto pwm_channel = _pwm_channel[io_num];
if (pwm_channel == -1) {
auto pwm = _pwm[io_num];
if (!pwm) {
log_error("M67 PWM channel error");
return false;
}

if (_current_value[io_num] == numerator) {
if (_current_value[io_num] == duty) {
return true;
}

_current_value[io_num] = numerator;
_current_value[io_num] = duty;

ledcWrite(pwm_channel, numerator);
pwm->setDuty(duty);

return true;
}
Expand Down
5 changes: 3 additions & 2 deletions FluidNC/src/Machine/UserOutputs.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
#pragma once

#include "../Configuration/Configurable.h"
#include "../GCode.h" // MaxUserDigitalPin MaxUserAnalogPin
#include "../GCode.h" // MaxUserDigitalPin MaxUserAnalogPin
#include "Driver/PwmPin.h" // pwm_chan_t

namespace Machine {
class UserOutputs : public Configuration::Configurable {
uint8_t _pwm_channel[MaxUserAnalogPin];
PwmPin* _pwm[MaxUserAnalogPin];
uint32_t _current_value[MaxUserAnalogPin];
uint32_t _denominator[MaxUserAnalogPin];

Expand Down
3 changes: 1 addition & 2 deletions FluidNC/src/MotionControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -432,8 +432,7 @@ void mc_reset() {
// NOTE: If steppers are kept enabled via the step idle delay setting, this also keeps
// the steppers enabled by avoiding the go_idle call altogether, unless the motion state is
// violated, by which, all bets are off.
if ((sys.state == State::Cycle || sys.state == State::Homing || sys.state == State::Jog) ||
(sys.step_control.executeHold || sys.step_control.executeSysMotion)) {
if (inMotionState() || sys.step_control.executeHold || sys.step_control.executeSysMotion) {
if (sys.state == State::Homing) {
if (rtAlarm == ExecAlarm::None) {
rtAlarm = ExecAlarm::HomingFailReset;
Expand Down
2 changes: 1 addition & 1 deletion FluidNC/src/Motors/Dynamixel2.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ This allows Dynamixel Servo motors to be used as an axis motor. This document wa

They can be used in place of any lettered axis, but only one per axis. These servos are limited to a single 360° of rotation. This is mapped to the MaxTravel of the axis. If you want map 300mm of travel to an X axis servo, you set $X/MaxTravel=300.

This sets a range in machine space. You are allowed to zero the axis anywhere in that travel. If you do a move that exceeds the travel, the servo will move to the end of of travel and stay there. The axis position will still be running, but the servo will stop. You can use $Limits/Soft=On to alarm if the range is exceeded.
This sets a range in machine space. You are allowed to zero the axis anywhere in that travel. If you do a move that exceeds the travel, the servo will move to the end of of travel and stay there. The axis position will still be running, but the servo will stop.

You should set the the axis speed and acceleration to what the servo can handle. If those settings are too high, the motor will still move, but will lag behind where the firmware thinks it is.

Expand Down
26 changes: 13 additions & 13 deletions FluidNC/src/Motors/RcServo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,12 @@

#include "../Machine/MachineConfig.h"
#include "../System.h" // mpos_to_steps() etc
#include "../Pins/LedcPin.h"
#include "../Pin.h"
#include "../Limits.h" // limitsMaxPosition
#include "RcServoSettings.h"
#include "../NutsBolts.h"

#include <esp32-hal-ledc.h> // ledcWrite
#include <freertos/task.h> // vTaskDelay
#include <freertos/task.h> // vTaskDelay

namespace MotorDrivers {
// RcServo::RcServo(Pin pwm_pin) : Servo(), _pwm_pin(pwm_pin) {}
Expand All @@ -40,29 +38,32 @@ namespace MotorDrivers {

_axis_index = axis_index();

read_settings();
config_message();
_pwm = new PwmPin(_output_pin, _pwm_freq); // Allocate a channel

_pwm_chan_num = ledcInit(_output_pin, -1, double(_pwm_freq), SERVO_PWM_RESOLUTION_BITS); // Allocate a channel
_current_pwm_duty = 0;

read_settings();

config_message();

_disabled = true;

startUpdateTask(_timer_ms);
}

void RcServo::config_message() {
log_info(" " << name() << " Pin:" << _output_pin.name() << " Pulse Len(" << _min_pulse_us << "," << _max_pulse_us << ")");
log_info(" " << name() << " Pin:" << _output_pin.name() << " Pulse Len(" << _min_pulse_us << "," << _max_pulse_us
<< " period:" << _pwm->period() << ")");
}

void RcServo::_write_pwm(uint32_t duty) {
// to prevent excessive calls to ledcWrite, make sure duty has changed
// to prevent excessive calls to pwmSetDuty, make sure duty has changed
if (duty == _current_pwm_duty) {
return;
}

_current_pwm_duty = duty;
ledcSetDuty(_pwm_chan_num, duty);
_pwm->setDuty(duty);
}

// sets the PWM to zero. This allows most servos to be manually moved
Expand Down Expand Up @@ -115,15 +116,14 @@ namespace MotorDrivers {
servo_pulse_len = static_cast<uint32_t>(mapConstrain(
servo_pos, limitsMinPosition(_axis_index), limitsMaxPosition(_axis_index), (float)_min_pulse_cnt, (float)_max_pulse_cnt));

//log_info("su " << servo_pulse_len);
// log_info("su " << servo_pulse_len);

_write_pwm(servo_pulse_len);
}

void RcServo::read_settings() {
_min_pulse_cnt =
(_min_pulse_us * ((_pwm_freq * SERVO_PWM_MAX_DUTY) / 1000)) / 1000; // play some math games to prevent overflowing 32 bit
_max_pulse_cnt = (_max_pulse_us * ((_pwm_freq * SERVO_PWM_MAX_DUTY) / 1000)) / 1000;
_min_pulse_cnt = (_min_pulse_us * ((_pwm_freq * _pwm->period()) / 1000)) / 1000; // play some math games to prevent overflowing 32 bit
_max_pulse_cnt = (_max_pulse_us * ((_pwm_freq * _pwm->period()) / 1000)) / 1000;
}

// Configuration registration
Expand Down
Loading

0 comments on commit 6863256

Please sign in to comment.