grblHAL / core

grblHAL core code and master Wiki
Other
326 stars 85 forks source link

MPG on Secondary UART #339

Open dogmaphobic opened 1 year ago

dogmaphobic commented 1 year ago

First, is there a proper discussion forum (Discord, Slack, etc.) for questions that may not be an "issue"?

To make a long story short, I’ve converted a mini mill to CNC. I still want to manually control it so I built a 3-rotary encoder MPG (physically connected to the mill). I'm using a T41U5XBB board and on its own, it all works fine. I derived all the grbl parsing code from @terjeio but gave up trying to understand the HAL portion and wrote my own, simple, single target code. I don't have any physical keys or buttons as I'm using a 10" Nextion touch display, and I have 3 rotary encoders for an XYZ machine as opposed to his lathe MPG (really, really well done by the way).

The firmware is setup as:

#define MPG_ENABLE    1
#define KEYPAD_ENABLE 2

Which, in theory, allows me to stream Jog commands through the secondary UART and use key commands to stop the Jog. Grbl parsing is fully functional and I have the full state of the controller in real time. I make sure the state is Idle, etc.

When I enter a jog command on ioSender’s MDI (same exact string as sent by my MPG), it does what it is supposed to do:

<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS|WCO:0.000,0.000,0.000>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS|Ov:100,100,100>
$J=G21 G91 Z-0.100 F600
ok
<Jog|MPos:0.000,0.000,-0.004|Bf:34,1023|FS:42,0|Pn:PXYZHS>
<Idle|MPos:0.000,0.000,-0.100|Bf:35,1023|FS:0,0|Pn:PXYZHS|WCO:0.000,0.000,0.000>
<Idle|MPos:0.000,0.000,-0.100|Bf:35,1023|FS:0,0|Pn:PXYZHS>

When I send the command through the secondary UART (from my MPG), a few things happen very differently:

Firmware was built off of master. Is there something I’m missing?

terjeio commented 1 year ago

First, is there a proper discussion forum (Discord, Slack, etc.) for questions that may not be an "issue"?

Github Discussions. I am wary of signing up to other "free" services after I was badly affected by the morons that coded LinkeIn. But if someone wants to set up a forum then go ahead.

Is there something I’m missing?

Do you send the command character (0x8B) to enter MPG mode and wait for |MPG:1 to appear in the real-time report before sending jog commands?

dogmaphobic commented 1 year ago

Github Discussions. I am wary of signing up to other "free" services after I was badly affected by the morons that coded LinkeIn. But if someone wants to set up a forum then go ahead.

Ha! Good to know. No, I don't have any lost love with those either. Github Discussion works just fine.

Do you send the command character (0x8B) to enter MPG mode and wait for |MPG:1 to appear in the real-time report before sending jog commands?

Yes, I had started with that but nothing happened with that command either. I noticed that the controller switches from Idle to Jog only when I actually send a $J= command. (The 0x8B below are part of my debug output and not something that came from the stream--The stream however, is printed verbatim as they come through the wire before getting parsed by your parseData function).

0x8B
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS|WCO:0.000,0.000,0.000>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS|Ov:100,100,100>
0x8B
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>
<Idle|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>

Looks like I have a lot more digging to do. Worst case I will build your MPG code and see what goes through the wire. The trick will be getting it to work with the a Pico alone. I don't have any of the other hardware at hand.

terjeio commented 1 year ago

Which driver? Could be that hal.driver_cap.mpg_mode is not on as it should be?

FYI if the MPG stream is not routed as input to the core parser (MPG mode 1) then all characters sent are processed by the keypad code.

dogmaphobic commented 1 year ago

Yeah, I had skimmed through that code and wondered. Is there a simple way to wire a debug output to an alternate port?

terjeio commented 1 year ago

Is there a simple way to wire a debug output to an alternate port?

Yes and no. The iMXRT1062 driver has only support for one UART port so cannot use it for debugging when it is claimed by the MPG.

I have retested the driver with my RP2040 MPG and it works. How do you transmit the 0x8B character?

dogmaphobic commented 1 year ago

How do you transmit the 0x8B character?

I'm using a Teensy 4.0. The project is Platformio based on top of the Arduino framework (same as the controller). I've been using 3 UARTs. Serial (USB) is for debugging. Serial1 for the link with the controller and Serial2 for the link to the display. I'm sending these commands (and everything else) using either write() or print():

_serial->write(CMD_MPG_MODE_TOGGLE);

Where _serial is a pointer to Serial1.

terjeio commented 1 year ago

You'll then have to somehow check that the correct character is received, e.g. by writing to an output in the keypad_process_keypress() function. One method would be to call report_message(uitoa(keycode), Message_Plain); to send it to the output stream in a standard format, another just to set a pin such as a coolant flood output with hal.coolant.set_state((coolant_state_t){1});

dogmaphobic commented 1 year ago

I'm getting different results by being careful with timing. That is, sending 0x8B, properly buffering the last jog command and waiting for MPG:1 and only then allow jog commands go out. The behavior now is that the stream from the controller stops, the status remain Idle and 0x85 won't return or do anything. Jog commands however, are generating stepper pulses (the controller is on a bench with just a scope connected to the stepper signals).

With the stream halted, I can't tell what is going on and neither can ioSender.

<Idle|MPos:-133.196,0.000,133.196|Bf:35,1023|FS:0,0|Pn:PXYZHS|Ov:100,100,100>
0x8B
<Idle|MPos:-133.196,0.000,133.196|Bf:35,1023|FS:0,0|Pn:PXYZHS|WCO:0.000,0.000,0.000|WCS:G54|A:|Sc:|MPG:1|H:0|T:0|TLR:0|FW:grblHAL>
$J=G21 G91 Z0.100 F600
ok
$J=G21 G91 Z0.100 F600
ok
0x85

Still digging. Thanks for all the help! The suggestions above using report_message is a good one and I will try it next.

dogmaphobic commented 1 year ago

No luck. I've been at this for too long. Deliberately sending just a single 0x8B and nothing else causes the stream to stop. Sending a 0x85 has no effect. It is being caught in protocol.c. Sending a reset (0x18) does have an effect and things go back to normal (ioSender however doesn't survive. Once the stream stops, it freezes and you need to kill the process).

It's late. I will let this rest for a bit.

terjeio commented 1 year ago

Deliberately sending just a single 0x8B and nothing else causes the stream to stop.

What do you mean by that? No more real-time reports arriving? If so you have to request them from the MPG as the sender input stream is ignored by the controller until MPG mode is exited. It is possible to enable auto-reporting by setting $481 to a value between 100 and 1000 (ms). This option was recently added to the controller so might not be available if you have an old version. ioSender may have issues with it when enabled - versions earlier than 2.0.43 will not work correctly since real-time reports arriving during startup is used to detect that a MPG is in control. 2.0.43 may work but may cause issues when a MPG takes control.

Sending a 0x85 has no effect.

If the controller is not in the JOG state that is the normal behaviour. But do not send 0x85 unless cancelling a jog or in IDLE state since it flushes the input buffers (both the serial input buffer and the line input buffer) to get rid of any pending jog commands.

Note that many single character commands does not return anything unless they are for explicit output requests. Some may cause state changes that will be reported after the next real time report request, but not always depending on the current state. E.g. a feed hold request received when the state is HOLD will be ignored.

dogmaphobic commented 1 year ago

Well, if for nothing else, it shows my ignorance of the Grbl protocol. Yes, up to now, whenever I would start the MPG MCU and connect to the controller using its secondary UART, streaming would start immediately. Note that in all cases, I had ioSender already running on a different computer connected to the controller and had its console up and running showing verbose output.

I checked and $481 is present and set to 0. So yes, the (MPG) MCU would start, connect to the controller and immediately start receiving the stream. Once I send a single 0x8B byte the streaming stops.

This option was recently added to the controller so might not be available if you have an old version.

I'm running the controller firmware off of master from two days ago.

Note that many single character commands does not return anything unless they are for explicit output requests.

Yes, I figured that. The UI now has a button to toggle MPG mode. It tests the state to make sure it's Idle and it sends CMD_MPG_MODE_TOGGLE. The stream sends one final line with MPG:1 and it stops.

I don’t even know how to request the stream. The documentation says that once you set $481 you must hard reboot the controller.

Just for sanity check, I just connected everything with the exception of the sender (ioSender). The MCU started receiving the stream as usual but it’s in Hold state as expected. I have to think about if the MPG should enable/home the controller and all that. Not its job, hence the sender being up and running in prior runs.

<Hold:0|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>
<Hold:0|MPos:0.000,0.000,0.000|Bf:35,1023|FS:0,0|Pn:PXYZHS>

Either way, let me dig more into the whole Grbl protocol to better understand everything before wasting your time.

For some background, I've been running (my own, DIY) CNC machines for around 15 years. So far I've been using Eding CNC controllers. They work just fine but not only they are rather expensive, I'm sick and tired of having to run Windows just for these things (ironically, the only Gcode sender that behaves well is Windows only 🙄). I do have one large, commercial CNC router (Avid) that came with Mach4/Smoothstepper and even though I tried to be patient with it, I ended up replacing everything with EdingCNC as I have an extensive collection of my own plugins and extensions for it (all Qt based). I don't know what's up with these CNC motion controller softwares. They all look and behave like it's 1990. I did give LinuxCNC a try but I could not find any motion controller. The Mesa cards, which seems to be what most people use are all out of stock perpetually. Hence, giving Grbl a try for this mini mill conversion.

terjeio commented 1 year ago

I don’t even know how to request the stream.

Like a sender you have to send the real-time report request character ?, typically every 200ms or so. Or send the grblHAL specific top bit set alternative.

The documentation says that once you set $481 you must hard reboot the controller.

Correct, but only after changing the setting as it is stored in non-volatile memory. I'll have to check how ioSender behaves with an MPG taking control when auto reporting is enabled since ioSender disables auto reporting while connected. It should reenable it when a MPG takes control, or perhaps the MPG should do that to keep backwards compatibility? The current state of auto reporting can be queried by requesting a complete real-time report, returned in the |AR element. BTW it is the complete report that is automatically sent when entering MPG mode so requesting it separately is really not needed.

I have to think about if the MPG should enable/home the controller and all that. Not its job, hence the sender being up and running in prior runs.

The MPG is in full control so can act like sender if you want to. Some want to be able to run the machine without having to connect it to a PC...

Either way, let me dig more into the whole Grbl protocol to better understand everything before wasting your time.

Feel free to ask when needed, I may uncover issues that I am not aware of that needs correction as well.

FYI there are users that have made fairly sofisticated MPGs/panels, like this one, that uses ModBus (or CanBus) to communicate with the controller bypassing the Grbl/grblHAL protocol completely. This is another one that uses the keypad plugin.

dogmaphobic commented 1 year ago

I took the day off to clear my mind (and spent it writing an integration plugin for Home Assistant 🙄)

Like a sender you have to send the real-time report request character ?, typically every 200ms or so. Or send the grblHAL specific top bit set alternative.

Ha! I assumed it was automatic. At least that’s what it seems to be doing even when $481 is 0. One way or the other, got it.

Correct, but only after changing the setting as it is stored in non-volatile memory. I'll have to check how ioSender behaves with an MPG taking control when auto reporting is enabled since ioSender disables auto reporting while connected.

Does it do the hard reboot on the fly? How else is it disabling auto reporting?

It should reenable it when a MPG takes control, or perhaps the MPG should do that to keep backwards compatibility?

Good question but I think the onus should be on the external component and not on the sender. With that said, the sender should be smart enough to self-heal when something is out of whack.

I did clone the ioSender repository and just looking at it told me there is no chance I can help you there 😜 I did some Windows work back in the late 90s/early 2000s and that was it. Looking at today’s Visual Studio I don’t even know where to begin (and I have no desired to find out).

The MPG is in full control so can act like sender if you want to. Some want to be able to run the machine without having to connect it to a PC…

This made the rather obvious, clear to me. I’m still in a mind frame that the “software” (PC) does everything and the “firmware” (hardware) only handles step generation, i/o and so on. This is how everything I’ve dealt with so far works. The whole concept of the “firmware” doing everything and the “software” being simply a glorified UI is a different paradigm. I knew it in the back of my head but it took the above paragraph to cement it for what it truly is. You’re 100% correct. I need to rethink how this all should be implemented.

FYI there are users that have made fairly sofisticated MPGs/panels, like this one, that uses ModBus (or CanBus) to communicate with the controller bypassing the Grbl/grblHAL protocol completely.

Holy crap! The fundamentals of that project is perfect. It looks like the beginning of a killer Masso Touch controller (and eventually a Masso Touch controller killer 😜) I skimmed through the code. I see that the firmware side sends all the values at once but it looks like the panel driver within the motion controller (Grbl) picks up the mpg wheel and splits into three (in the case of 3 axis) Jog moves. Is there a specific reason for it? That sounds inefficient and jerky. Why can’t it combine into one $J=XxYxZxFx command? If you are moving 2-dimensionally (most less likely 3-dimensionally unless you have 3 hands), it makes sense to move it diagonally instead of stair-stepping it. If there is no reason, it’s an easy fix. This is my current “draft” implementation of the code that sends the jog command (yes, the code is heavily lifted off yours):

    mpg_t m;
    uint8_t mpgChanges = _mpgEncoders.getEncoders(m);
    if(mpgChanges) {
        float d = 0.0f;
        float s = 0.0f;
        switch(_jogMode) {
            case JogMode_Slow:
                d = _settings.jog_config.slow_distance;
                s = _settings.jog_config.slow_speed;
                break;
            case JogMode_Step:
                d = _settings.jog_config.step_distance;
                s = _settings.jog_config.step_speed;
                break;
            default:
                d = _settings.jog_config.fast_distance;
                s = _settings.jog_config.fast_speed;
                break;
        }
        char command[128];
        char dist[16];
        strcpy(command, "$J=G21 G91");
        if(mpgChanges & MPG::AxisX) {
            sprintf(dist, " X%.3f", d * m.x);
            strcat(command, dist);
        }
        if(mpgChanges & MPG::AxisY) {
            sprintf(dist, " Y%.3f", d * m.y);
            strcat(command, dist);
        }
        if(mpgChanges & MPG::AxisZ) {
            sprintf(dist, " Z%.3f", d * m.z);
            strcat(command, dist);
        }
        sprintf(dist, " F%.0f", s);
        strcat(command, dist);
        DebugSerial.println(command);
        sendCommandSynchronous(command);
    }

As I said above, this all changes my thinking and I will go back to the drawing board. I had already started separating data gathering for output and data display from input in a module while the code handling the protocol and I/o are in classes that can be replaced based on protocol / hardware. Now I have the incentive to go all the way in that direction.

This is another one that uses the keypad plugin.

Ha! They are here in Canada (yes, I’m in Canada but operate in European time zone 🙄). Coincidentally, I just ordered their FlexiHAL board last week to try it out. The compelling reason was their claim of supporting LinuxCNC (which was the reason I had started separating hardware handling from protocol—One MPG, multiple protocols).

\<rant> As a side note, I get the benefits of Zephyr and in the long run, if this is to become a commercial product, that makes sense. From an open source perspective though, you are eliminating a great deal of contributors due to the steep hassle of setting up the development environment. This, of course, is a personal opinion and reflects how I felt when going through the process of installing it and its dependencies and giving up half way through as it was way too much hassle. \</rant>

dogmaphobic commented 1 year ago

Ha! Cool. I've got the MPG fully controlling the system. I can unlock it, home, toggle MPG in and out, set M54, jog all axis (simultaneously), basically most of the things I set out to do. Not sure I can trust to run a program yet and ioSender really doesn't like it when I am connected. I have yet to sort that out.

Thanks!

dresco commented 1 year ago

Hi @dogmaphobic, am the author of the panel implementation that Terje linked above..

I see that the firmware side sends all the values at once but it looks like the panel driver within the motion controller (Grbl) picks up the mpg wheel and splits into three (in the case of 3 axis) Jog moves. Is there a specific reason for it?

Only that it's not been implemented yet ;) Part of the rationale for sending all the key states at once was to make multi-axis keypad jogging (and key rollover) simpler to implement on the plugin side..

As a side note, I get the benefits of Zephyr and in the long run, if this is to become a commercial product, that makes sense. From an open source perspective though, you are eliminating a great deal of contributors due to the steep hassle of setting up the development environment.

I wanted to use Zephyr as a learning exercise, but I don't disagree. The controller was originally a PlatformIO based Zephyr project, which did a good job of hiding all of that installation & setup. Unfortunately, they were not keeping the Zephyr support up to date, so I had to abandon that and go to a native build.

Overall I like it, was already somewhat used to devicetree from embedded Linux, which probably helped. Has definitely been a benefit when changing dev hardware due to chip shortages etc.

dogmaphobic commented 1 year ago

Only that it's not been implemented yet ;) Part of the rationale for sending all the key states at once was to make multi-axis keypad jogging (and key rollover) simpler to implement on the plugin side..

I figured it. Everything else is setup that way, quite elegantly at that.

Overall I like it, was already somewhat used to devicetree from embedded Linux, which probably helped. Has definitely been a benefit when changing dev hardware due to chip shortages etc.

I get it. In different times, I would not have minded for the exact same reason. Learning it.

Your project is way cool!

terjeio commented 1 year ago

I've got the MPG fully controlling the system.

👍

Does it do the hard reboot on the fly? How else is it disabling auto reporting?

By sending auto reporting toggle command character, 0x8C, when it detects auto reporting is active.

Not sure I can trust to run a program yet and ioSender really doesn't like it when I am connected. I have yet to sort that out.

What is the ioSender issue?

dogmaphobic commented 1 year ago

By sending auto reporting toggle command character, 0x8C, when it detects auto reporting is active.

I run a session with Wireshark capturing everything sent between ioSender and the controller. I didn't see any 0x8C. I don't remember what state the controller was in when it booted. Either way, it was a great way to see the boot process and all the traffic that initializes ioSender.

What is the ioSender issue?

It's too random. Telling you what I'm seeing right now would just raise a bunch of red herrings. Too many variables at play and I'm sure I am causing most of the issues. Once I have things somewhat stabilized, I will take a more methodical look into the issues I'm seeing with ioSender.

Yesterday I spent the day with the UI. All I needed was some draft buttons to facilitate sending commands but obviously I wasted the day playing with it a bit too much. It's nowhere near what I want it to be yet but I was just having too much fun :P

Main Screen

Needless to say, I have ways to go :P

IMG_1428

dogmaphobic commented 1 year ago

IMG_1509 IMG_1511

An update on a first, rough prototype. For now, the firmware sends somewhat raw data to be interpreted accordingly depending on the target (LinuxCNC, EdingCNC, Mach4, etc.) The odd man is Grbl as it works completely differently. Ideally, I would interface with ioSender and not Grbl directly. Still working on that. My intent is to have one single "pendant" that I can take from machine to machine, regardless what is driving it. I shrunk its width, which allows for a better ergonomics. I can manipulate both X and Y MPG simultaneously using my thumbs. That meant going to a smaller screen, with much less real estate for UI but plenty to be useful.