2 This file is part of Smoothie (http://smoothieware.org/). The motion control part is heavily based on Grbl (https://github.com/simen/grbl).
3 Smoothie 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.
4 Smoothie 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.
5 You should have received a copy of the GNU General Public License along with Smoothie. If not, see <http://www.gnu.org/licenses/>.
11 #include "nuts_bolts.h"
13 #include "StreamOutputPool.h"
14 #include "SerialMessage.h"
15 #include "checksumm.h"
16 #include "ConfigValue.h"
17 #include "StepTicker.h"
19 #include "SlowTicker.h"
24 #include "PwmOut.h" // mbed.h lib
25 #include "PublicDataRequest.h"
29 #define laser_checksum CHECKSUM("laser")
30 #define laser_module_enable_checksum CHECKSUM("laser_module_enable")
31 #define laser_module_pin_checksum CHECKSUM("laser_module_pin")
32 #define laser_module_pwm_pin_checksum CHECKSUM("laser_module_pwm_pin")
33 #define laser_module_ttl_pin_checksum CHECKSUM("laser_module_ttl_pin")
34 #define laser_module_pwm_period_checksum CHECKSUM("laser_module_pwm_period")
35 #define laser_module_maximum_power_checksum CHECKSUM("laser_module_maximum_power")
36 #define laser_module_minimum_power_checksum CHECKSUM("laser_module_minimum_power")
37 #define laser_module_tickle_power_checksum CHECKSUM("laser_module_tickle_power")
38 #define laser_module_max_power_checksum CHECKSUM("laser_module_max_power")
39 #define laser_module_maximum_s_value_checksum CHECKSUM("laser_module_maximum_s_value")
50 void Laser::on_module_loaded()
52 if( !THEKERNEL
->config
->value( laser_module_enable_checksum
)->by_default(false)->as_bool() ) {
53 // as not needed free up resource
58 // Get smoothie-style pin from config
59 Pin
* dummy_pin
= new Pin();
60 dummy_pin
->from_string(THEKERNEL
->config
->value(laser_module_pin_checksum
)->by_default("nc")->as_string())->as_output();
62 // Alternative less ambiguous name for pwm_pin
63 if (!dummy_pin
->connected())
64 dummy_pin
->from_string(THEKERNEL
->config
->value(laser_module_pwm_pin_checksum
)->by_default("nc")->as_string())->as_output();
66 pwm_pin
= dummy_pin
->hardware_pwm();
68 if (pwm_pin
== NULL
) {
69 THEKERNEL
->streams
->printf("Error: Laser cannot use P%d.%d (P2.0 - P2.5, P1.18, P1.20, P1.21, P1.23, P1.24, P1.26, P3.25, P3.26 only). Laser module disabled.\n", dummy_pin
->port_number
, dummy_pin
->pin
);
76 this->pwm_inverting
= dummy_pin
->is_inverting();
82 this->ttl_pin
= new Pin();
83 ttl_pin
->from_string( THEKERNEL
->config
->value(laser_module_ttl_pin_checksum
)->by_default("nc" )->as_string())->as_output();
84 this->ttl_used
= ttl_pin
->connected();
85 this->ttl_inverting
= ttl_pin
->is_inverting();
94 uint32_t period
= THEKERNEL
->config
->value(laser_module_pwm_period_checksum
)->by_default(20)->as_number();
95 this->pwm_pin
->period_us(period
);
96 this->pwm_pin
->write(this->pwm_inverting
? 1 : 0);
97 this->laser_maximum_power
= THEKERNEL
->config
->value(laser_module_maximum_power_checksum
)->by_default(1.0f
)->as_number() ;
99 // These config variables are deprecated, they have been replaced with laser_module_maximum_power and laser_module_minimum_power
100 this->laser_minimum_power
= THEKERNEL
->config
->value(laser_module_tickle_power_checksum
)->by_default(0)->as_number() ;
102 // Load in our preferred config variables
103 this->laser_minimum_power
= THEKERNEL
->config
->value(laser_module_minimum_power_checksum
)->by_default(this->laser_minimum_power
)->as_number() ;
105 // S value that represents maximum (default 1)
106 this->laser_maximum_s_value
= THEKERNEL
->config
->value(laser_module_maximum_s_value_checksum
)->by_default(1.0f
)->as_number() ;
110 //register for events
111 this->register_for_event(ON_HALT
);
112 this->register_for_event(ON_GCODE_RECEIVED
);
113 this->register_for_event(ON_CONSOLE_LINE_RECEIVED
);
114 this->register_for_event(ON_GET_PUBLIC_DATA
);
116 // no point in updating the power more than the PWM frequency, but not faster than 1KHz
117 ms_per_tick
= 1000 / std::min(1000UL, 1000000 / period
);
118 THEKERNEL
->slow_ticker
->attach(std::min(1000UL, 1000000 / period
), this, &Laser::set_proportional_power
);
121 void Laser::on_console_line_received( void *argument
)
123 if(THEKERNEL
->is_halted()) return; // if in halted state ignore any commands
125 SerialMessage
*msgp
= static_cast<SerialMessage
*>(argument
);
126 string possible_command
= msgp
->message
;
128 // ignore anything that is not lowercase or a letter
129 if(possible_command
.empty() || !islower(possible_command
[0]) || !isalpha(possible_command
[0])) {
133 string cmd
= shift_parameter(possible_command
);
135 // Act depending on command
137 string power
= shift_parameter(possible_command
);
139 msgp
->stream
->printf("Usage: fire power%% [durationms]|off|status\n");
144 fire_duration
= 0; // By default unlimited
145 if(power
== "status") {
146 msgp
->stream
->printf("laser manual state: %s\n", manual_fire
? "on" : "off");
149 if(power
== "off" || power
== "0") {
151 msgp
->stream
->printf("turning laser off and returning to auto mode\n");
153 p
= strtof(power
.c_str(), NULL
);
154 p
= confine(p
, 0.0F
, 100.0F
);
155 string duration
= shift_parameter(possible_command
);
156 if(!duration
.empty()) {
157 fire_duration
= atoi(duration
.c_str());
158 // Avoid negative values, its just incorrect
159 if (fire_duration
< ms_per_tick
) {
160 msgp
->stream
->printf("WARNING: Minimal duration is %ld ms, not firing\n", ms_per_tick
);
163 // rounding to minimal value
164 if (fire_duration
% ms_per_tick
!= 0) {
165 fire_duration
= (fire_duration
/ ms_per_tick
) * ms_per_tick
;
167 msgp
->stream
->printf("WARNING: Firing laser at %1.2f%% power, for %ld ms, use fire off to stop test fire earlier\n", p
, fire_duration
);
169 msgp
->stream
->printf("WARNING: Firing laser at %1.2f%% power, entering manual mode use fire off to return to auto mode\n", p
);
174 manual_fire
= set_laser_power(p
);
179 void Laser::on_get_public_data(void* argument
)
181 PublicDataRequest
* pdr
= static_cast<PublicDataRequest
*>(argument
);
183 if(!pdr
->starts_with(laser_checksum
)) return;
184 pdr
->set_data_ptr(this);
189 void Laser::on_gcode_received(void *argument
)
191 Gcode
*gcode
= static_cast<Gcode
*>(argument
);
193 // M codes execute immediately
195 if (gcode
->m
== 221) { // M221 S100 change laser power by percentage S
196 if(gcode
->has_letter('S')) {
197 this->scale
= gcode
->get_value('S') / 100.0F
;
200 gcode
->stream
->printf("Laser power scale at %6.2f %%\n", this->scale
* 100.0F
);
206 // calculates the current speed ratio from the currently executing block
207 float Laser::current_speed_ratio(const Block
*block
) const
209 // find the primary moving actuator (the one with the most steps)
211 uint32_t max_steps
= 0;
212 for (size_t i
= 0; i
< THEROBOT
->get_number_registered_motors(); i
++) {
213 // find the motor with the most steps
214 if(block
->steps
[i
] > max_steps
) {
215 max_steps
= block
->steps
[i
];
220 // figure out the ratio of its speed, from 0 to 1 based on where it is on the trapezoid,
221 // this is based on the fraction it is of the requested rate (nominal rate)
222 float ratio
= block
->get_trapezoid_rate(pm
) / block
->nominal_rate
;
227 // get laser power for the currently executing block, returns false if nothing running or a G0
228 bool Laser::get_laser_power(float& power
) const
230 const Block
*block
= StepTicker::getInstance()->get_current_block();
232 // Note to avoid a race condition where the block is being cleared we check the is_ready flag which gets cleared first,
233 // as this is an interrupt if that flag is not clear then it cannot be cleared while this is running and the block will still be valid (albeit it may have finished)
234 if(block
!= nullptr && block
->is_ready
&& block
->is_g123
) {
235 float requested_power
= ((float)block
->s_value
/ (1 << 11)) / this->laser_maximum_s_value
; // s_value is 1.11 Fixed point
236 float ratio
= current_speed_ratio(block
);
237 power
= requested_power
* ratio
* scale
;
245 // called every millisecond from timer ISR
246 uint32_t Laser::set_proportional_power(uint32_t dummy
)
249 // If we have fire duration set
251 // Decrease it each ms
252 fire_duration
-= ms_per_tick
;
253 // And if it turned 0, disable laser and manual fire mode
254 if (fire_duration
<= 0) {
263 if(get_laser_power(power
)) {
264 // adjust power to maximum power and actual velocity
265 float proportional_power
= ( (this->laser_maximum_power
- this->laser_minimum_power
) * power
) + this->laser_minimum_power
;
266 set_laser_power(proportional_power
);
268 } else if(laser_on
) {
275 bool Laser::set_laser_power(float power
)
277 // Ensure power is >=0 and <= 1
278 power
= confine(power
, 0.0F
, 1.0F
);
280 if(power
> 0.00001F
) {
281 this->pwm_pin
->write(this->pwm_inverting
? 1 - power
: power
);
282 if(!laser_on
&& this->ttl_used
) this->ttl_pin
->set(true);
286 this->pwm_pin
->write(this->pwm_inverting
? 1 : 0);
287 if (this->ttl_used
) this->ttl_pin
->set(false);
294 void Laser::on_halt(void *argument
)
296 if(argument
== nullptr) {
302 float Laser::get_current_power() const
304 float p
= pwm_pin
->read();
305 return (this->pwm_inverting
? 1 - p
: p
) * 100;