Machsupport Forum

Mach Discussion => Mach4 General Discussion => Topic started by: momofr83 on February 03, 2026, 10:38:42 AM

Title: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 03, 2026, 10:38:42 AM
Hi,

I have a pokeys57cnc and mach4 and would like to control a laser with PWM to engrave images.

A few years earlier, I used Auggie. It was fine but not very user-friendly.
I saw a bit of developments coming from pokeys's developers and I thought they might have implemented new ways to control a pwm and bypass S commands breaking constant velocity.

I saw somewhere a way to use an arduino nano to take step/dir signals from a motor and convert it as a pwm signal for my diode. But with a 150€ cards, isn't that possible ? I tried M62/M67 with no sucess.
I would like to avoid the nano solution or change my card and therefore settings.

Thanks,
Alexandre
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 03, 2026, 04:32:06 PM
In the changelog of the pokey plugin for mach4, it says :
9.06.0.5325 (28.3.2024)
- Support for M67 (analog motion-sync commands - e.g. laser PWM control) - M67 E0 Q100 (only Analog output 0 is supported)

Does anybody know how to make it work ?
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 04, 2026, 10:46:26 AM
Ok, I will answer to myself in order to maybe help others on the subject.

After a few echange with poscope's support, I installed last plugin (9.17.0.5596) and it appeared that my mach4 version was too old (5036). It was the version available on their website. But there are new versions available on https://www.machsupport.com/ftp/Mach4/DevlopmentVersions/
I installed the last build (6693) and finally could assign the desired pwm pin on the analog output 0 in order to use M67 E0 QXX to control my laser with synced movements.

I'll now need to change my scripts since the new version seems to have broke those and make some tunings with my laser settings.

Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 05, 2026, 01:47:25 AM
I first tried to use LaserGRBL or lightburn but those two doesn't suit my CNC. It is a queenbee screw driven. Therefore I need spaces to be able to reach the maximum velocity. Auggie did that and I used a python script to generate a greyscale matrix with variable feedrates and power. Since the raster is bidirectionnal, I saw some blur caused by the small delay of M67 commands. With a bit of tweaking, I managed to wipe them. If someone is interested, here's the script :
Code: [Select]
# =========================================
# Laser Calibration - Feedrates
# =========================================

def linspace(start, stop, n):
    if n <= 1: return [start]
    return [start + i * (stop - start) / (n - 1) for i in range(n)]

# --- PARAMETERS ---
min_power = 8
max_power = 35
num_steps = 10
power_steps = linspace(min_power, max_power, num_steps)

feedrates = [1400, 1600, 2000, 2400, 2600, 2800, 3000]

# Base 16ms (Parfaite à F1500)
laser_latency_ms = 16.0

# blur tweak
boost_at_3000 = -0.25
reference_feed = 1500.0

# runway premove
pre_move = 15.0 

band_width = 2.0
gap_width = 1.0
line_step = 0.14

output_file = "laser_calib_negative_boost.nc"

# --- CALCULATIONS ---
lines_per_band = int(band_width / line_step)
total_length = (num_steps * 2.0) + ((num_steps - 1) * 0.5)

gcode = []
gcode.append("G21 G90 G17 G94")
gcode.append("M3 M67 E0 Q0")

current_y = 0.0

for feed in feedrates:
    # 1. Base 16ms
    base_offset = (feed * laser_latency_ms) / 60000
   
    # 2. Boost négatif progressif
    extra_boost = 0.0
    if feed > reference_feed:
        # La valeur sera négative ici
        extra_boost = boost_at_3000 * ((feed - reference_feed) / (3000.0 - reference_feed))
   
    current_offset = base_offset + extra_boost
   
    gcode.append(f"\n( BAND F{feed} | Offset: {current_offset:.3f}mm )")
   
    for line in range(lines_per_band):
        if line % 2 == 0:
            x_dir, x_start = 1, 0.0
            corr = current_offset
        else:
            x_dir, x_start = -1, total_length
            corr = -current_offset

        start_runway = x_start - (pre_move * x_dir)
        gcode.append(f"G0 X{start_runway:.3f} Y{current_y:.3f}")

        x_cursor = x_start
        gcode.append(f"G1 X{x_cursor + corr:.3f} F{feed}")

        p_seq = list(enumerate(power_steps)) if x_dir == 1 else list(enumerate(power_steps))[::-1]
       
        for i, p in p_seq:
            gcode.append(f"M67 E0 Q{p:.2f}")
            x_cursor += x_dir * 2.0
            gcode.append(f"G1 X{x_cursor + corr:.3f}")

            if (x_dir == 1 and i < num_steps-1) or (x_dir == -1 and i > 0):
                gcode.append("M67 E0 Q0")
                x_cursor += x_dir * 0.5
                gcode.append(f"G1 X{x_cursor + corr:.3f}")

        gcode.append("M67 E0 Q0")
        end_runway = x_cursor + (pre_move * x_dir)
        gcode.append(f"G1 X{end_runway:.3f}")

        current_y += line_step
    current_y += gap_width

gcode.append("\nM5\nM30")

with open(output_file, "w") as f:
    f.write("\n".join(gcode))
   
print(f"G-code généré : {output_file}")
print(f"F1500 : Offset = {(1500*laser_latency_ms)/60000:.3f} mm (Boost: 0.000)")
print(f"F3000 : Offset = {((3000*laser_latency_ms)/60000 + boost_at_3000):.3f} mm (Boost: {boost_at_3000})")

Next step : gamma settings and a converter Image ->Gcode.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: Tweakie.CNC on February 05, 2026, 02:22:08 AM
Following your progress with interest.

Tweakie.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 05, 2026, 03:09:13 AM
Hi Tweakie,
It's a honor to have you since I red your laser project topic several years ago.

I made a python script to convert an image in Gcode, not that bad but more tuning is needed to get the fullscale gray level potential of my laser. Here's the code :
Code: [Select]
from PIL import Image, ImageOps

# --- PARAMÈTRES UTILISATEUR ---
input_image_path = "IMG_3901.jpg"
output_file = "IMG_3901.nc"

target_width_mm = 50.0   
feedrate = 3000         
line_step = 0.14         

min_power = 10            # Blanc / Gris très clair
max_power = 25           # Noir profond (Saturation à partir de 20)

# --- REGLAGE DU CONTRASTE ---
# gamma > 1.0 : éclaircit les gris moyens (donne plus de nuances dans les zones sombres)
gamma_correction = 1.6 

# --- CALIBRATION DYNAMIQUE ---
laser_latency_ms = 16.0
boost_at_3000 = -0.25
reference_feed = 1500.0
pre_move = 15.0         

# --- CALCUL DE L'OFFSET ---
base_offset = (feedrate * laser_latency_ms) / 60000
extra_boost = 0.0
if feedrate > reference_feed:
    extra_boost = boost_at_3000 * ((feedrate - reference_feed) / (3000.0 - reference_feed))
current_offset = base_offset + extra_boost

# --- TRAITEMENT IMAGE ---
img = Image.open(input_image_path).convert('L')

# 1. Égalisation et Contraste pour utiliser toute l'échelle de puissance
img = ImageOps.equalize(img)
img = ImageOps.autocontrast(img, cutoff=1)

# 2. Redimensionnement proportionnel
original_w, original_h = img.size
width_pixels = int(target_width_mm / line_step)
height_pixels = int(width_pixels * (original_h / original_w))
img = img.resize((width_pixels, height_pixels), Image.Resampling.LANCZOS)

real_width_mm = width_pixels * line_step
real_height_mm = height_pixels * line_step

# --- GÉNÉRATION G-CODE ---
gcode = []
gcode.append(f"( Image: {input_image_path} | Gamma: {gamma_correction} )")
gcode.append("G21 G90 G17 G94")
gcode.append("M3 M67 E0 Q0")

# Boucle de gravure (de bas en haut pour origine en bas à gauche)
for py in range(height_pixels - 1, -1, -1):
    y_pos = (height_pixels - 1 - py) * line_step
    is_forward = (py % 2 == 0)
   
    if is_forward:
        x_start, x_end, x_dir = 0.0, (width_pixels - 1) * line_step, 1
        x_range = range(width_pixels)
        corr = current_offset
    else:
        x_start, x_end, x_dir = (width_pixels - 1) * line_step, 0.0, -1
        x_range = range(width_pixels - 1, -1, -1)
        corr = -current_offset

    # Approche (Runway)
    gcode.append(f"G0 X{x_start - (pre_move * x_dir):.3f} Y{y_pos:.3f}")
    gcode.append(f"G1 X{x_start + corr:.3f} F{feedrate}")

    # Gravure de la ligne pixel par pixel
    for px in x_range:
        pixel_val = img.getpixel((px, py)) # 0 à 255
       
        # Inversion et Normalisation (0.0 à 1.0)
        norm_val = 1.0 - (pixel_val / 255.0)
       
        # Courbe Gamma
        corrected_val = norm_val ** gamma_correction
       
        # Conversion en puissance réelle
        power = min_power + (corrected_val * (max_power - min_power))
       
        x_pixel = px * line_step
        gcode.append(f"M67 E0 Q{power:.2f}")
        gcode.append(f"G1 X{x_pixel + corr:.3f}")

    # Fin de ligne et Overscan
    gcode.append("M67 E0 Q0")
    gcode.append(f"G1 X{x_end + (pre_move * x_dir):.3f}")

gcode.append("M5\nM30")

# Écriture du fichier
with open(output_file, "w") as f:
    f.write("\n".join(gcode))

# Affichage des informations
print(f"Fichier généré avec correction gamma {gamma_correction}")
print(f"Nombre de pixels : {width_pixels}x{height_pixels}")
print(f"Taille de sortie : {real_width_mm:.2f}x{real_height_mm:.2f} mm")
I tried to upload an image but an error occured, I uploaded it : https://i.postimg.cc/Zq9d0N0h/IMG-4230c.jpg (https://i.postimg.cc/Zq9d0N0h/IMG-4230c.jpg)

Ps : Don't mind the spoiler board, the real one is underneath !
Ps2 : the verification method is broken, forced to listen to it ? (The letters you typed don't match the letters that were shown in the picture)
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 06, 2026, 03:37:23 AM
I had some troubles with my macros not working anymore. I don't know why but .mcc wouldn't create. It was admin restrictions, just had to allow total control on the folder.
I took the occasion to test a notification system when the job is done on my smartphone, with the application "ntfy". I just need to add M334 at the end of every Gcode.
Code: [Select]
function m334()
    local inst = mc.mcGetInstance()
   
    local topic = "mach4_momo"
    local message = "Job's done ! Wake up !"
   
    -- curl command
    local cmd = string.format('curl -d "%s" http://ntfy.sh/%s', message, topic)
   
    mc.mcCntlSetLastError(inst, "Execution : " .. cmd)
   
    local success, reason, code = os.execute(cmd)
   
    if success then
        mc.mcCntlSetLastError(inst, "NTFY : Success (Code " .. tostring(code) .. ")")
    else
        mc.mcCntlSetLastError(inst, "NTFY : Error (" .. tostring(reason) .. ")")
    end
end

if (mc.mcInEditor() == 1) then
    m334()
end
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 06, 2026, 04:22:58 AM
Note : In a Gcode, to ensure the successfull sending of the notification :
M5    -- Stop the laser in this case
M334 -- our macro to send notification
G4 P2 -- Pause 2s 
M30 -- Program end
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 06, 2026, 04:39:13 AM
Back to my Gcode converter, configuring the ideal settings is tricky. It seems that my Laser is very sensible. It's a laser tree 80W.
With a range limit of 9.5 to 20%, I got the nuances I want but the burning wood is not a linear process. So I'm trying to change the power map and what's best than a histogram (in attachment) to see my image translation in power ?
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 06, 2026, 09:18:57 AM
I spent some time figuring why there was no difference in small power percentage, the PWM was too high (20000Hz).
With 5000Hz, gray are less quantized. I'm not sure whose the most guilty pokey's card or the laser. (I'm assuming the latter)

I'll pursue my tests and post a picture when i'm satisfied.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 07, 2026, 04:53:19 AM
My remapping function of power isn't satisfying yet. I must say that i didn't take the easiest photo. I made a quick user interface (Python).

Lessons Learned :
To avoid white lines caused by a mismatch between the requested travel distances and the minimum step resolution allowed on an axis, I implemented a step-based logic where the commanded distance is computed by multiplying the number of Y-axis steps by the axis counts-per-unit value.
The same resolution was initially used for the X-axis motion; however, the system was unable to keep up with the resulting instruction rate, causing the feed rate to drop to 2400 instead of the requested 3000. As a result, the resolution had to be reduced.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in grayscales
Post by: momofr83 on February 08, 2026, 08:29:37 AM
Here's the first release of my Gcode converter, AliG : https://github.com/MoMo830/AliG/releases/tag/AliG

Feek free to comment.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 08, 2026, 08:38:47 AM
A small example, once settings are correct.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: Tweakie.CNC on February 09, 2026, 01:33:45 AM
Excellent progress.

nb. GitHub returns 404 error for your Alig.

Tweakie.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: momofr83 on February 09, 2026, 01:39:47 AM
Sorry, the good one : https://github.com/MoMo830/AliG (https://github.com/MoMo830/AliG)
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in grayscales
Post by: momofr83 on February 10, 2026, 03:07:43 AM
I made some updates to ALIG :
- Added Analog output choice (not suitable yet with pokeys, as it requires analog output 0)
- Added S support
- Choice of the origin point
- Improved graphics

Link of the project : https://github.com/MoMo830/AliG (https://github.com/MoMo830/AliG)

Do I need to make a new topic as S commands are now supported ?
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: Tweakie.CNC on February 10, 2026, 05:03:03 AM
Do we know if Mach4 now supports the S### command being used within a running GCode without breaking the CV chain ?
Last I tried it failed miserably.

Tweakie.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in grayscales
Post by: momofr83 on February 10, 2026, 05:07:24 AM
No it doesn't, thats why I built this software. It allows to use M67. I just added S for non mach users.
I can now engrave within Mach4.

I still use S for cutting only.
Title: Re: MACH4\pokeys57 : Controlling PWM to laser engrave in greyscales
Post by: Tweakie.CNC on February 10, 2026, 10:02:52 AM
OK thanks, I now understand your reasoning for adding the S### support.

Tweakie.