363 lines
11 KiB
C++
Raw Normal View History

/*
servo_axis.cpp
Part of Grbl_ESP32
copyright (c) 2018 - Bart Dring. This file was intended for use on the ESP32
CPU. Do not use this with Grbl for atMega328P
Grbl is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Grbl is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Grbl. If not, see <http://www.gnu.org/licenses/>.
See servo_axis.h for more details
*/
#include "grbl.h"
#ifdef USE_SERVO_AXES
static TaskHandle_t servosSyncTaskHandle = 0;
#ifdef SERVO_X_PIN
ServoAxis X_Servo_Axis(X_AXIS, SERVO_X_PIN, SERVO_X_CHANNEL_NUM);
#endif
#ifdef SERVO_Y_PIN
ServoAxis Y_Servo_Axis(Y_AXIS, SERVO_Y_PIN, SERVO_Y_CHANNEL_NUM);
#endif
#ifdef SERVO_Z_PIN
ServoAxis Z_Servo_Axis(Z_AXIS, SERVO_Z_PIN, SERVO_Z_CHANNEL_NUM);
#endif
#ifdef SERVO_A_PIN
ServoAxis A_Servo_Axis(A_AXIS, SERVO_A_PIN, SERVO_A_CHANNEL_NUM);
#endif
#ifdef SERVO_B_PIN
ServoAxis B_Servo_Axis(B_AXIS, SERVO_B_PIN, SERVO_B_CHANNEL_NUM);
#endif
#ifdef SERVO_C_PIN
ServoAxis C_Servo_Axis(C_AXIS, SERVO_C_PIN, SERVO_C_CHANNEL_NUM);
#endif
void init_servos()
{
//grbl_send(CLIENT_SERIAL, "[MSG: Init Servos]\r\n");
#ifdef SERVO_X_PIN
grbl_sendf(CLIENT_SERIAL, "[MSG:X Servo range %4.3f to %4.3f]\r\n", SERVO_X_RANGE_MIN, SERVO_X_RANGE_MAX);
X_Servo_Axis.init();
X_Servo_Axis.set_range(SERVO_X_RANGE_MIN, SERVO_X_RANGE_MAX);
X_Servo_Axis.set_homing_type(SERVO_HOMING_OFF);
X_Servo_Axis.set_disable_on_alarm(false);
X_Servo_Axis.set_disable_with_steppers(false);
#endif
#ifdef SERVO_Y_PIN
grbl_sendf(CLIENT_SERIAL, "[MSG:Y Servo range %4.3f to %4.3f]\r\n", SERVO_Y_RANGE_MIN, SERVO_Y_RANGE_MAX);
Y_Servo_Axis.init();
Y_Servo_Axis.set_range(SERVO_Y_RANGE_MIN, SERVO_Y_RANGE_MAX);
#endif
#ifdef SERVO_Z_PIN
grbl_sendf(CLIENT_SERIAL, "[MSG:Z Servo range %4.3f to %4.3f]\r\n", SERVO_Z_RANGE_MIN, SERVO_Z_RANGE_MAX);
Z_Servo_Axis.init();
Z_Servo_Axis.set_range(SERVO_Z_RANGE_MIN, SERVO_Z_RANGE_MAX);
#ifdef SERVO_Z_HOMING_TYPE
Z_Servo_Axis.set_homing_type(SERVO_Z_HOMING_TYPE);
#endif
#ifdef SERVO_Z_HOME_POS
Z_Servo_Axis.set_homing_position(SERVO_Z_HOME_POS);
#endif
#ifdef SERVO_Z_MPOS // value should be true or false
Z_Servo_Axis.set_use_mpos(SERVO_Z_MPOS);
#endif
#endif
#ifdef SERVO_A_PIN
grbl_sendf(CLIENT_SERIAL, "[MSG:A Servo range %4.3f to %4.3f]\r\n", SERVO_A_RANGE_MIN, SERVO_A_RANGE_MAX);
A_Servo_Axis.init();
A_Servo_Axis.set_range(SERVO_A_RANGE_MIN, SERVO_A_RANGE_MAX);
A_Servo_Axis.set_homing_type(SERVO_HOMING_OFF);
A_Servo_Axis.set_disable_on_alarm(false);
A_Servo_Axis.set_disable_with_steppers(false);
#endif
#ifdef SERVO_B_PIN
grbl_sendf(CLIENT_SERIAL, "[MSG:B Servo range %4.3f to %4.3f]\r\n", SERVO_B_RANGE_MIN, SERVO_B_RANGE_MAX);
B_Servo_Axis.init();
B_Servo_Axis.set_range(SERVO_B_RANGE_MIN, SERVO_B_RANGE_MAX);
#endif
#ifdef SERVO_C_PIN
grbl_sendf(CLIENT_SERIAL, "[MSG:C Servo range %4.3f to %4.3f]\r\n", SERVO_C_RANGE_MIN, SERVO_C_RANGE_MAX);
C_Servo_Axis.init();
C_Servo_Axis.set_range(SERVO_C_RANGE_MIN, SERVO_C_RANGE_MAX);
//C_Servo_Axis.set_homing_type(SERVO_HOMING_TARGET);
//C_Servo_Axis.set_homing_position(SERVO_C_RANGE_MAX);
#ifdef SERVO_C_HOMING_TYPE
C_Servo_Axis.set_homing_type(SERVO_C_HOMING_TYPE);
#endif
#ifdef SERVO_C_HOME_POS
C_Servo_Axis.set_homing_position(SERVO_C_HOME_POS);
#endif
#ifdef SERVO_C_MPOS // value should be true or false
C_Servo_Axis.set_use_mpos(SERVO_C_MPOS);
#endif
#endif
// setup a task that will calculate the determine and set the servo positions
xTaskCreatePinnedToCore( servosSyncTask, // task
"servosSyncTask", // name for task
4096, // size of task stack
NULL, // parameters
1, // priority
&servosSyncTaskHandle,
0 // core
);
}
// this is the task
void servosSyncTask(void *pvParameters)
{
TickType_t xLastWakeTime;
const TickType_t xServoFrequency = SERVO_TIMER_INT_FREQ; // in ticks (typically ms)
xLastWakeTime = xTaskGetTickCount(); // Initialise the xLastWakeTime variable with the current time.
while(true) { // don't ever return from this or the task dies
#ifdef SERVO_X_PIN
X_Servo_Axis.set_location();
#endif
#ifdef SERVO_Y_PIN
Y_Servo_Axis.set_location();
#endif
#ifdef SERVO_Z_PIN
Z_Servo_Axis.set_location();
#endif
#ifdef SERVO_A_PIN
A_Servo_Axis.set_location();
#endif
#ifdef SERVO_B_PIN
B_Servo_Axis.set_location();
#endif
#ifdef SERVO_C_PIN
C_Servo_Axis.set_location();
#endif
vTaskDelayUntil(&xLastWakeTime, xServoFrequency);
}
}
// =============================== Class Stuff ================================= //
ServoAxis::ServoAxis(uint8_t axis, uint8_t pin_num, uint8_t channel_num) // constructor
{
_axis = axis;
_pin_num = pin_num;
_channel_num = channel_num;
_showError = true; // this will be used to show calibration error only once
_use_mpos = true; // default is to use the machine position rather than work position
}
void ServoAxis::init()
{
_cal_is_valid();
ledcSetup(_channel_num, _pwm_freq, _pwm_resolution_bits);
ledcAttachPin(_pin_num, _channel_num);
disable();
}
void ServoAxis::set_location()
{
// These are the pulse lengths for the minimum and maximum positions
// Note: Some machines will have the physical max/min inverted with pulse length max/min due to invert setting $3=...
float servo_pulse_min, servo_pulse_max;
float min_pulse_cal, max_pulse_cal; // calibration values in percent 110% = 1.1
uint32_t servo_pulse_len;
float servo_pos, mpos, offset;
// skip location if we are in alarm mode
if (_disable_on_alarm && (sys.state == STATE_ALARM)) {
disable();
return;
}
// track the disable status of the steppers if desired.
if (_disable_with_steppers && get_stepper_disable()) {
disable();
return;
}
if ( (_homing_type == SERVO_HOMING_TARGET) && (sys.state == STATE_HOMING) ) {
servo_pos = _homing_position; // go to servos home position
}
else {
mpos = system_convert_axis_steps_to_mpos(sys_position, _axis); // get the axis machine position in mm
if (_use_mpos) {
servo_pos = mpos;
}
else {
offset = gc_state.coord_system[_axis] + gc_state.coord_offset[_axis]; // get the current axis work offset
servo_pos = mpos - offset; // determine the current work position
}
}
// 1. Get the pulse ranges of the servos
// 2. Invert if selected in the settings
// 3. Get the calibration values from the settings
// 4. Adjust the calibration offset direction of the cal based on the direction
// 5. Apply the calibrarion
servo_pulse_min = SERVO_MIN_PULSE;
servo_pulse_max = SERVO_MAX_PULSE;
if (bit_istrue(settings.dir_invert_mask,bit(_axis))) { // this allows the user to change the direction via settings
swap(servo_pulse_min, servo_pulse_max);
}
// get the calibration values
if (_cal_is_valid()) { // if calibration settings are OK then apply them
// apply a calibration
// the cals apply differently if the direction is reverse (i.e. longer pulse is lower position)
if (bit_isfalse(settings.dir_invert_mask,bit(_axis))) { // normal direction
min_pulse_cal = 2.0 - (settings.steps_per_mm[_axis] / 100.0);
max_pulse_cal = (settings.max_travel[_axis] / -100.0);
}
else { // inverted direction
min_pulse_cal = (settings.steps_per_mm[_axis] / 100.0);
max_pulse_cal = 2.0 - (settings.max_travel[_axis] / -100.0);
}
}
else { // settings are not valid so don't apply any calibration
min_pulse_cal = 1.0;
max_pulse_cal = 1.0;
}
// apply the calibrations
servo_pulse_min *= min_pulse_cal;
servo_pulse_max *= max_pulse_cal;
// determine the pulse length
servo_pulse_len = (uint32_t)mapConstrain(servo_pos, _position_min, _position_max, servo_pulse_min, servo_pulse_max );
_write_pwm(servo_pulse_len);
}
void ServoAxis::_write_pwm(uint32_t duty)
{
if (ledcRead(_channel_num) != duty) { // only write if it is changing
ledcWrite(_channel_num, duty);
}
}
// sets the PWM to zero. This allows most servos to be manually moved
void ServoAxis::disable()
{
_write_pwm(0);
}
// checks to see if calibration values are in an acceptable range
// vebose = true if you want an error sent to serial port
bool ServoAxis::_cal_is_valid()
{
bool settingsOK = true;
if ( (settings.steps_per_mm[_axis] < SERVO_CAL_MIN) || (settings.steps_per_mm[_axis] > SERVO_CAL_MAX) ) {
if (_showError) {
grbl_sendf(CLIENT_SERIAL, "[MSG:Servo calibration ($10%d) value error. Reset to 100]\r\n", _axis);
settings.steps_per_mm[_axis] = 100;
write_global_settings();
}
settingsOK = false;
}
// Note: Max travel is set positive via $$, but stored as a negative number
if ( (settings.max_travel[_axis] < -SERVO_CAL_MAX) || (settings.max_travel[_axis] > -SERVO_CAL_MIN) ) {
if (_showError) {
grbl_sendf(CLIENT_SERIAL, "[MSG:Servo calibration ($13%d) value error. Reset to 100]\r\n", _axis);
settings.max_travel[_axis] = -100;
write_global_settings();
}
settingsOK = false;
}
_showError = false; // to show error once
if (! settingsOK) {
write_global_settings(); // they were changed so write them to
}
return settingsOK;
}
/*
Use this to set the max and min position in mm of the servo
This is used when mapping pulse length to the position
*/
void ServoAxis::set_range(float min, float max) {
if (min < max) {
_position_min = min;
_position_max = max;
}
else {
grbl_send(CLIENT_SERIAL, "[MSG:Error setting range. Min not smaller than max]\r\n");
}
}
/*
Sets the mode the servo will be in during homing
See servo_axis.h for SERVO_HOMING_xxxxx types
*/
void ServoAxis::set_homing_type(uint8_t homing_type)
{
if (homing_type <= SERVO_HOMING_TARGET)
_homing_type = homing_type;
}
/*
Use this to set the homing position the servo will be commanded to go if
the current homing mode is SERVO_HOMING_TARGET
*/
void ServoAxis::set_homing_position(float homing_position)
{
_homing_position = homing_position;
}
/*
Use this to set the disable on alarm feature. If true, then hobby servo PWM
will be disable in Grbl alarm mode (like before homing). Typical hobby servo
can be moved by hand in this mode
*/
void ServoAxis::set_disable_on_alarm (bool disable_on_alarm)
{
_disable_on_alarm = disable_on_alarm;
}
void ServoAxis::set_disable_with_steppers(bool disable_with_steppers) {
_disable_with_steppers = disable_with_steppers;
}
/*
If true, servo position will alway be calculated in machine position
Offsets will not be applied
*/
void ServoAxis::set_use_mpos(bool use_mpos) {
_use_mpos = use_mpos;
}
#endif