Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

P2x Huanyang VFD's #453

Closed
atlaste opened this issue Jun 25, 2020 · 30 comments
Closed

P2x Huanyang VFD's #453

atlaste opened this issue Jun 25, 2020 · 30 comments
Labels
enhancement New feature or request

Comments

@atlaste
Copy link
Collaborator

atlaste commented Jun 25, 2020

Since I hate it that these things happen, I guessed it would be nice to let you know. This is relevant for P2A, P2B and P2C VFD's.

Let me get right to it:

Bad news: basically they changed the protocol in a few different ways. This applies to the P2x VFD's from Huanyang - which will probably become the new standard.
Ugly news: the manual is pretty crappy. While it explains how it should work, in reality it's just a mess with information missing, etc.
Good news: it's easy to fix, and I already some working code that I implemented for Marlin. Should be easy enough to integrate in the current Huanyang implementation :)

Here's the docs that you need: https://github.com/atlaste/Marlin/blob/2.0.x/docs/P2A_VFD_Modbus_Protocol.md
And code: https://github.com/atlaste/Marlin/blob/2.0.x/Marlin/src/feature/vfd_spindle.cpp

Cheers!

@atlaste atlaste added the enhancement New feature or request label Jun 25, 2020
@bdring
Copy link
Owner

bdring commented Jun 25, 2020

@atlaste Thanks for letting me know.

I do not have a VFD with that protocol to test with. If you want to test in Grbl_ESP32, I would suggest using the existing VFD spindle class as a template for a new one.

@atlaste
Copy link
Collaborator Author

atlaste commented Jun 25, 2020 via email

@karoria
Copy link

karoria commented Jun 26, 2020

Hi @atlaste
I am not a programmer but I am quite interested in running my spindle with RS485. I use Delta MS300 VFD which has capability for RS485 Modbus input. I use grbl_esp32 with pwm and then converting it to 0-10V analog for input to vfd presently. Please let me know if I can be useful for testing your code. I will be happy to help.

@atlaste
Copy link
Collaborator Author

atlaste commented Jun 26, 2020

I just created this code for the H2x VFD's. This code works. Tested extensively today on H2A VFD and Arduino DUE, see MD file for more information on all the implementation details.

SpindleTest.zip

@bdring You should be able to simply copy/paste this code into a new VFD spindle driver, and it will work. Just don't touch any of the send/receive code, because the timings are a pain to get right...

@atlaste
Copy link
Collaborator Author

atlaste commented Jun 26, 2020

Hi @atlaste
I am not a programmer but I am quite interested in running my spindle with RS485. I use Delta MS300 VFD which has capability for RS485 Modbus input. I use grbl_esp32 with pwm and then converting it to 0-10V analog for input to vfd presently. Please let me know if I can be useful for testing your code. I will be happy to help.

Unfortunately this is next to impossible to get this right without an actual VFD here, and they all seem to be different with regards to the protocol.

I do have some good news though. You can download the SpindleTest.zip file I just uploaded, get the manual of the VFD, and start messing around with the commands. There should be a modbus commands manual somewhere for your VFD. The Modbus part basically tells that the first byte (address) and the checksum (last 2 bytes) should be correct before it accepts the code, so there is no real harm in trying this. Also, you can define the VFD_RS485_DEBUG variable to get all the binary data. Make sure you start with some 'read' command, and once that works, start with 'stop spindle' before you move on.

Obviously, Google first if someone already did it for your VFD, and/or ask the tech rep of the VFD company for a reference implementation or example commands. If he did, copy/pasting the codes themselves is the easy part, as I already took care of all the timing issues.

I'm afraid there's no way around this rather painful process. I spent hours and hours trying to get my RS485 commands figured out, because the manuals are quite lacking to use an understatement...

@karoria
Copy link

karoria commented Jun 26, 2020

Thanks @atlaste for the detailed answer. Will give it a try for sure.

@bdring
Copy link
Owner

bdring commented Jun 26, 2020

Thanks for the code. It can used for reference. It would need to be converted into a Grbl_ESP32 spindle class. The timing may be easier in ESP32 because of the RTOS.

I would be happy to review someones Grbl_ESP32 class code, but it would be a slow process for me to write without test hardware.

@atlaste
Copy link
Collaborator Author

atlaste commented Jun 26, 2020

Thanks for the code. It can used for reference. It would need to be converted into a Grbl_ESP32 spindle class. The timing may be easier in ESP32 because of the RTOS.

I would be happy to review someones Grbl_ESP32 class code, but it would be a slow process for me to write without test hardware.

I just ordered an ESP32, which should arrive next sunday. Don't have any experience with what you describe as RTOS, but I should have time & a lot of interest in running some tests next sunday evening.

@bdring
Copy link
Owner

bdring commented Jun 27, 2020

Here is a link to the current VFD Spindle Class code.

https://github.com/bdring/Grbl_Esp32/blob/master/Grbl_Esp32/Spindles/HuanyangSpindle.cpp

You can use that as a template to make a new class.

With Grbl, you should not use any form of delay in the same thread as the motion controller. This code uses a separate, lower priority task, to do the communication and response waiting. The uart_read_bytes(...) will wait for for the response bytes or timeout after a specified amount of time. The code runs as fast as possible will this method.

@atlaste
Copy link
Collaborator Author

atlaste commented Jun 27, 2020

Here is a link to the current VFD Spindle Class code. [...]

Yes, I am quite aware of that class (and know the original grbl code base quite well).

I'm still in doubt what the best approach is. I understand why grbl doesn't like any delay, but what we obviously also don't want is some communication error to occur and a non-rotating spindle to crash in your piece :-) That could take some time if an error occurs...

One other way to handle this is to make the whole interface non-blocking. There are only so many G-codes that control the spindle after all, so it is perfectly possible to split these codes up into a 'startXXX', and 'updateStateXXX' phase. Perhaps even use a state pattern; I suspect the ESP has enough power for things like that.

@bdring
Copy link
Owner

bdring commented Jun 27, 2020

If you look at the code you can see an unresponsive spindle is detected. Currently we only pop out a message. There are also comments where a regular status request could be put. If a normal spindle command is not queued a status request for current speed, etc could be made every 200ms.

In either case an alarm or pause could be used to halt the machine.

All communication is "non blocking" because it is a separate task.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 11, 2020

Yes. So i did some research and i think i can port the code with all the safety checks to grbl esp32. Thats currently on the agenda... Only issue i have at the moment is that i have trouble getting grbl esp32 to work on my custom build, and without that its nearly impossible to get it all working.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 21, 2020

@bdring I started implementing the VFD code for the H2x today. Still untested, but I do have some open questions that need some answer, I'm mostly curious about the behavior that was intended by grbl... I took the Huanyang code as a reference point.

  1. What should happen if the command queue is full. I mean, if I ignore the message (like Huanyang), and it is a 'disable spindle' command or 'reverse spindle direction', things will go wrong. Still, I can't just retry it because grbl doesn't let me afaik. What other options are there?

I did notice the "VFD Queue Full" message. Abort in such a case is perhaps a better option?

  1. How can speed be reinforced in GRBL. F.ex. when I say 'do 20k rpm' and it takes 20 seconds for the spindle to spin up, I would like it to wait cutting material till it's spun up. Is there a g-code for this? Or can I just set sys.spindle_speed and assume the main loop will take care of the correct behavior?

  2. As you noticed in the comment, a VFD is a dangerous tool. I would like to play it on the safe side, and run xQueueReset and a 'stop' command when the spindle is stopped. I guess this includes the grbl reset command. I assume this calls 'init'?

@bdring
Copy link
Owner

bdring commented Jul 21, 2020

The current implementation was just good enough to test some hardware I was ordering. I have been working on a few additions to it. The first is a general spindle alarm. This would stop all motion if the communication failed or returned status suggests the spindle is not running properly. You can see there are some TODOs and comments regarding some of that.

I was going to test some failure scenarios. I was not expecting queue issues under normal operation, but if it did happen, possibly the stop command could deal with the issue. If the VFD is not responding there is not much you can do. Note: The loop in the task, can send commands independent of the queue. This is how status updates were likely to work.

The vTaskDelay(200); in the task was just a guess. I do not know what frequency to use for status updates.

There is no spindle spin up/down delay. One could be added that would work like a dwell (G4). It could be implemented with a $Spindle/Delay/On and $Spindle/Delay/Off setting. Spindle classes could enable this so lasers would ignore it.

I think for, in job, spindle speed overrides it would have to ignore the delay.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 22, 2020

@bdring Are these changes already on github? I would like to integrate them.

Just so you know, the version I'm currently working on can be found here: https://github.com/atlaste/Grbl_Esp32/blob/master/Grbl_Esp32/Spindles/H2XSpindle.cpp . I took a state machine approach, mainly because some actions (like 'set speed') on my VFD need multiple dependent actions.

I was going to test some failure scenarios. [...] If the VFD is not responding there is not much you can do.

Some VFD's have an emergency break feature. We could attempt to trigger that or a stop spindle command, it's more or less the best one can hope for. In the worst case, the VFD is not responding, in which case GRBL should stop whatever it is it's doing. Unfortunately I think this is quite difficult to implement, because of the non-blocking way grbl works.

I was not expecting queue issues under normal operation, but if it did happen, possibly the stop command could deal with the issue.

What I do is automatic retries when a packet does not result in a satisfying response. I've seen packet drops happen a lot of times, so I don't think this is optional. Depending on the number of packets that are dropped, this can give queueing issues, especially when using a power delay (which will result in quite some communication and long running tasks).

The vTaskDelay(200); in the task was just a guess. I do not know what frequency to use for status updates.

Well, my VFD manual states that the time between two commands should be at least 7 characters. With a baud rate of 19200bps and 9 bits per char, that's 63 bits, that equals roughly 3.281 ms. So that would yield a value of 4 as the absolute minimum.

Another way to look at this is to use the buffer size of the uart as reference point. We don't want messages to be dropped. From ESP32 technical reference manual I've learned that UART controllers share a total of 1024 bytes RAM, which equals roughly 500 ms. Let's call that the absolute maximum.

I guess the reality is that the 200 is the min time between two spindle commands. Unfortunately, spindle commands are part of the block feed rate, so the real question here is how much time is between two blocks. If your gantry moves 1000 mm/min a value of 200 is roughly 3.5 mm. For PCB milling that's quite a lot...

All things considered, I would think ~50ms would be the best balance. That's 50*portTICK_PERIOD_MS (=50) ticks.

Another approach... What you would do on a pc is use a conditional variable, which is triggered from the other thread. That way, you can avoid sleeping, which makes it easier on the task scheduler. Another way to do this is to use an atomic variable, which is reset when the task quits, and set just before the task starts. Tasks are just spun up then when they are used. However, I do not know what the overhead of this would be on an ESP32.

Note: The loop in the task, can send commands independent of the queue. This is how status updates were likely to work.

Yeah about that...

This also has me worried tbh. I see no thread synchronization in the code. While I'm not an expert on ESP32 architecture, I do know my way around multi-threading and I would have expected at least some volatile, memory fences or synchronization constructs like that. However, I might be wrong here; it all depends on the exact architecture.

There is no spindle spin up/down delay. One could be added that would work like a dwell (G4). It could be implemented with a $Spindle/Delay/On and $Spindle/Delay/Off setting. Spindle classes could enable this so lasers would ignore it.
I think for, in job, spindle speed overrides it would have to ignore the delay.

I've thought about it... What I would propose is to have M3/M4/M5 use a power delay, and have block spindle speed overrides ignore the delay. That's at least what I would expect when using g-code.

@bdring
Copy link
Owner

bdring commented Jul 22, 2020

200ms Update Rate

The constant update from Grbl is ignored if the speed does not change from the last time. The only thing that would cause a rapid change is real time spindle speed override. Moving _current_pwm_rpm = rpm; to a point after the command is sent could fix full queue issues. nobody should expect a VFD to reach a new speed immediately.

Queue issues

One possibility to to xQueueReset(..) before some commands, like stop.

Dropped comm under normal conditions

I have not seen this, but I suppose you would need to allow a few retries. A state machine might deal with this better. A state machine within the task loop could eliminate the need for the xQueue.

Delay

I am going to look at this and alarms first. They are independent of any one spindle class.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 22, 2020

The constant update from Grbl is ignored if the speed does not change from the last time. The only thing that would cause a rapid change is real time spindle speed override. Moving _current_pwm_rpm = rpm; to a point after the command is sent could fix full queue issues. nobody should expect a VFD to reach a new speed immediately.

My VFD can poll the current rpm value, as part of the diagnostics codes. I would expect this from other vfd's as well.

The idle path of the task can be used for this I suppose.

One possibility to to xQueueReset(..) before some commands, like stop.

Agreed. Also what i found.

Dropped comm under normal conditions

I have not seen this, but I suppose you would need to allow a few retries. A state machine might deal with this better. A state machine within the task loop could eliminate the need for the xQueue.

Its quite rare, i found that roughly 1 in 200 packets get corrupted on 19200, 8e1. For 38400 baud, its a lot more. All using non-shielded rs485 wires.

Delay

I am going to look at this and alarms first. They are independent of any one spindle class.

Sounds like a good idea.

I'm going to do some minor refactoring, think it should be possible and easier to add new vfd classes now that i've seen the differences. Also going to take a look at the limit switches/dual gantry squaring enhancement that i want... Do let me know if you have anything on git, it would be a waste if we fix the same thing.

@bdring
Copy link
Owner

bdring commented Jul 22, 2020

I think I have the delays working. I need to do some more testing and fine tuning

M3 and M4 delay for a new float setting $Spindle/Delay/SpinUp seconds.

M3 delays for a new float setting $Spindle/Delay/SpinDown seconds.

Currently, if you change speeds in the middle of gcode via just an S value, there is no additional delay. You of change it with M3 Sxxxx there is another full delay. I need to experiment what can be done here. I think my VFD takes the same time to change between any speeds.

Anytime you send M5 there is a SpinDown delay, even if the spindle was already off.

Trying to stop by sending S0 and no M5 will not give a delay. With VFDs, they will likely go to a minimum safe RPM anyway.

Yes, if you try to write bad gcode, you might not get the delays you want.

It uses the mc_dwell() function which has gcode synchronization (makes sure all previous gcode is done before starting delay).

Everything in main grbl is stopped during the delay, but reporting still continues. There are no new states.

I need to look at parking. I think if you have parking on, it will work the same, except you would probably lower the parking timing because most of it was for spinup/down anyway.

I used the PWM spindle class for testing. I need to add this into other spindles.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 22, 2020

Nice. I'll check the code out later today or tomorrow.

I noticed some time ago that Marlin also uses a nr of secs for the delay. I am very sure however that the delay on my vfd depends on the target speed - and that it works as an s-curve.

So... Why use a delay and not a virtual bool IsAtTargetSpeed() method on the spindle class? For the H2x VFD I'd simply query the current speed then.

@bdring
Copy link
Owner

bdring commented Jul 22, 2020

So... Why use a delay and not a virtual bool IsAtTargetSpeed()

I currently have about 8 spindle types and RS485 is only one that can do that. Go ahead and do that in your spindle class if you need it.

Depends on target speed....s-curve

Then a delay sounds useless. accelerations and jerk values would be needed to determine the time between speeds.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 22, 2020

I currently have about 8 spindle types and RS485 is only one that can do that. Go ahead and do that in your spindle class if you need it.

Hmm I would have hoped that all spindle types can support that. If not, it is of course a useless abstraction.
Will look into it.

What git branch were you working on? Would like to merge the relevant code.

@bdring
Copy link
Owner

bdring commented Jul 22, 2020

useless abstraction

On a relay spindle, you cannot get feedback on when it IsAtTargetSpeed().

which git branch

My changes are all local changes to the current Devt. I will not push until I am happy with my code.

@bdring
Copy link
Owner

bdring commented Jul 23, 2020

I pushed my code to the Devt branch.

  • I added spindle delays with settings ($Spindle/Delay/SpinUp and $Spindle/Delay/SpinDown).
  • The Huanyang can generate an alarm if is not responding in certain states.
  • I added some test gcode for spindles.

I was not able to find good documentation on how to ping the vfd between jobs. Asking for the rpm just returns the control register value whether it is on or not. I need to look into that further.

The alarm generation is just a proof of concept right now.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 25, 2020

Thanks for the heads up, I'll pull and merge my code. (Or throw it away and start over... not sure yet, I'm not really happy with it and I'm still reading up on how the code base works exactly)

I was not able to find good documentation on how to ping the vfd between jobs. Asking for the rpm just returns the control register value whether it is on or not. I need to look into that further.

I did scan quite some Huanyang documentation and other code before I got my spindle (and found out it was completely different) so I went back to my docs to see if I could find something. Perhaps this is of help: http://royaumedeole.fr/informatique/plugin-mach3-pour-vfdhuanyang/mach3-plugin-for-huanyang-vfd/ . Note that they talk about "RPM monitoring through system DRO(39) [...]". Unfortunately I do not have a mach3 system, otherwise I would reverse engineer the command codes for you.

Another source that might be interesting is https://github.com/GilchristT/SpindleTalker2/releases . They have an 'output frequency' and 'set frequency'.

"PD025 = 1 (starting mode: frequency track)" might also be the issue, according to https://make-a-project.blogspot.com/2013/05/connecting-huanyang-hy01d523b-vfd-speed.html

P.S. 2: While going back to the docs, I did note that 'PD023 = 1' enables backward rotation. Iirc vfd's are nowadays two-direction in all cases. Might be interesting to read this register during initialization.

@bdring
Copy link
Owner

bdring commented Jul 26, 2020

Thanks for the links. No real new or official information, but it did confirm some things. I ran the SpindleTalker 2 program and it worked great.

I updated the firmware (devt). It now loops through a few status values in the background. If there is a problem with a command it creates an alarm. If the status queries have a problem, it will only generate an alarm when a job is running. I am not done, but getting close.

Here are the status items I know about.

/*
    Header  Read  Len     Reg     Data    Data    CRC    CRC
    0x01    0x04    0x03    0x00                                                 //  Set Frequency
    0x01    0x04    0x03    0x01                                                 //  Ouput Frequency
    0x01    0x04    0x03    0x02                                                 //  Ouput Amps
    0x01    0x04    0x03    0x03    0x00    0x00    0xF0    0x4E    //  Read RPM
    0x01    0x04    0x03    0x04                                                 //  DC voltage
    0x01    0x04    0x03    0x05                                                //  AC voltage
    0x01    0x04    0x03    0x06                                                //  Cont
    0x01    0x04    0x03    0x07                                                //  VFD Temp
*/

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 27, 2020

@bdring Sounds like a good plan.

One other status I have on my VFD is 'current running state'. When my VFD resets, it's in a 'stop' state, and when you run something it goes in 'forward run' or backward run'. One important thing to note is that user input on the VFD terminal IS possible during a run, and the value set by set speed (set frequency in your case) will not change. During a run, I found this to be the most valuable piece of information.

One other thing: I do think that's a lot of status to poll. I've noticed in the past that you can easily flood the RS485 modbus by polling too much information.

In theory, they keep 4 chars before and after the command as a timeout, as well as a fixed timeout set in the VFD. F.ex. let's say that the timeout is 50 ms, and you run 9600 baud, then if a command & response are both 10 chars, according to the documentation I have you end up with (10+4+4)*2 * 9 = 324 bits, which is ~34 ms. In short, that would imply 50+34 = 84 ms per request at max speed. Wrong here means 'incorrect crc' (probably packet collision), 'incorrect address' (my vfd masks the address for errors), or no response at all.

In reality, I've actually seen things gone wrong with delays of ~100 ms on 19200 baud. That's ~200 ms on 9600 baud. What I basically did to circumvent this is implement a policy that uses the above calculation (with some slack), and then incremented that slack timeout (till a max value of ~1 sec) when things started to go wrong.

@bdring
Copy link
Owner

bdring commented Jul 27, 2020

I have run hour long tests, without a single timeout or "flood". I am continuously polling (4) status items. I don't think that will ever happen, but a user hammering on the spindle override could get rejected by queue full.

As I said before, I will probably have some commands, like stop, clear the queue first. I think that the queue full on override could probably be be fixed with retries to match the rpm.

At this point the capabilities the spindle exceeds the features and capabilities of grbl and senders. I don't think I will do too much more right now. I don't have the excess time to donate.

@atlaste
Copy link
Collaborator Author

atlaste commented Jul 27, 2020

Hmm interesting. Its difficult to know what the cause is then, perhaps it was just a concurrency issue at my side then, as i've only seen this happen on my marlin implementation - but thats quite different in many regards. (Including the little fact that you probably use shielded cables and I don't).

Regardless, if you don't see it, its irrelevant. If I see it happen on my spindle, I'll just fix it. I've already started implementing and fixing stuff (also to get to know the code base better), once everything is working again I'll just make a pull req. My current aim is to make the vfd code as easy as possible for other vfd's in the future, because i can already see the differences and similarities in code (i scanned a few other manuals as well).

In any case, one thing that would interesting, is a test script for vfd's (or spindles in general). I'll ask my test engineer at my company for input as well, but if you already have a solution for this, let me know.

@atlaste
Copy link
Collaborator Author

atlaste commented Aug 14, 2020

PR #544 is currently being reviewed, with H2A support.
Going to close this issue; this should be resolved momentarily.

@atlaste atlaste closed this as completed Aug 14, 2020
@Harvie
Copy link

Harvie commented Nov 26, 2020

Also see terjeio/grblHAL#68

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants