-
Notifications
You must be signed in to change notification settings - Fork 42
a performance improvement for scanning #262
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
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left a bunch of comments. Some ask for clarification, some for fixes.
At least 2 things we definitely need to do:
- Fix the changes to
CephlaStage
to respectAbstractStage
- Decide whether or not this is okay to roll out as is, since it will break all customers until they do a firmware update.
@hongquanli for 2. - how do we want to handle that? If we merge this all customers will break until they do a firmware update (if they update their code to at or post merge of this PR).
// byte[3]: how many micro steps - lower 8 bits | ||
|
||
static const int CMD_LENGTH = 8; | ||
static const int CMD_LENGTH = 24; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't figure out why this needs to go up to 24 instead of 10 (we need 2 extra bytes for the MOVE*_XY, but I don't see why we need 11-24).
It's probably okay, but it does mean we send 14 more bytes per command than we absolutely need to. I think that's only ~8 us more per command at 2 MHz, but if we don't need it we might as well not waste the time.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't thought much about it; I'm just leaving room for future situations. OK, I would change to 10.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The command length must be adjusted in multiples of 4 bytes. @ianohara
int target_tolerance; | ||
int pid_tolerance; | ||
|
||
int ramp_mode; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we don't initialize this in tmc4361A_init
, which means I think we default it to 0. To preserve the original behavior it'd be good to set tmc4361a->ramp_mode = TMC4361_RAMP_SSHAP
in the init.
I know we set the ramp mode in our .py
implementation, but you generally don't want to assume other pieces of code will do the right thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I agree with you.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@veerwang can you implement the suggested change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I would do it.
else | ||
{ | ||
focusPosition = focusPosition + (int32_t(uint32_t(buffer[0]) << 24 | uint32_t(buffer[1]) << 16 | uint32_t(buffer[2]) << 8 | uint32_t(buffer[3])) - focuswheel_pos); | ||
focusPosition = focusPosition - (int32_t(uint32_t(buffer[0]) << 24 | uint32_t(buffer[1]) << 16 | uint32_t(buffer[2]) << 8 | uint32_t(buffer[3])) - focuswheel_pos); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I can't figure out why we switched the sign here. Can you help me understand how this is related to scanning performance, and what the sign switch does?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The knob is designed to increase with clockwise rotation and decrease with counterclockwise rotation. This modification aims to align with conventional intuition.
uint8_t axis = buffer_rx[2]; | ||
uint8_t mode = buffer_rx[3]; | ||
// map to w axis, in the host w axis index is 5 | ||
if (axis == 5) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you elaborate on the comment above? What code is the host code in this case?
Also, we have AXIS_W
defined - can you use that?
And I don't understand why we map AXIS_W
to AXIS_THETA
here. Can you explain that?
In general - try to avoid using bare numbers like this. Instead, use constants with clear names. This helps make the code more clear, and helps in refactoring later (For instance, if we need to change the axis number for AXIS_W in the future, it's easy to change the AXIS_W define but hard to go and figure out which inline 5
are AXIS_W
or some other use of the number 5). Bare numbers are only sortof okay if they are true universal constants (as in, they never change throughout the entire universe).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like the python axes match the defines here, so I'm really not sure what the "map to w axis, in host w axis is 5" comment means.
Here are the firmware defines:
static const int AXIS_X = 0;
static const int AXIS_Y = 1;
static const int AXIS_Z = 2;
static const int AXIS_THETA = 3;
static const int AXES_XY = 4;
static const int AXIS_W = 5;
Here are the python defines:
class AXIS:
X = 0
Y = 1
Z = 2
THETA = 3
XY = 4
W = 5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I would change the code to easy to understand.
axis = 3; | ||
|
||
// guard code | ||
if (axis > STAGE_AXES) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it only okay to change the ramp mode for X, Y, and Z? It isn't okay to change them for THETA and W?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For safety reasons, I’ve only tested along the X, Y, and Z axes so far, so I’m keeping it in safe mode for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just fails silently, though. If someone tries to use this command with axis > STAGE_AXES
, how do they know that they didn't actually set ramp mode on the axis?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we let all other commands fail silently, so I guess we can keep this.
We really need to introduce a mechanism for communicating command failures, though. Failing silently shouldn't be an option!
{ | ||
X_commanded_movement_in_progress = false; | ||
mcu_cmd_execution_in_progress = false || Y_commanded_movement_in_progress || Z_commanded_movement_in_progress || W_commanded_movement_in_progress; | ||
us_since_last_check_position = interval_check_position + 1; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment here to explain this might be helpful. Something like "// It's important that we check positions next time around because <explain why ...>, so force the elapsed time to be larger than our interval."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK
|
||
self.coordinates_pd = pd.concat([self.coordinates_pd, new_row], ignore_index=True) | ||
|
||
def move_to_coordinate(self, coordinate_mm): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe there's a reason I'm missing here, but this refactor duplicates code. Instead, I probably would do:
def move_to_coordinate(self, coordinate_mm):
x_mm = coordinate_mm[0]
y_mm = coordinate_mm[1]
have_z = len(coordinate_mm) >= 3
self.stage.move_xy_to(x_mm, y_mm, blocking=not have_z)
if have_z >= 3:
self.move_to_z_level(z_mm)
self._sleep(SCAN_STABILIZATION_TIME_MS / 1000)
This:
- Does not duplicate the x_mm, y_mm code.
- Doesn't have two
move_xy_to
calls
Also, since SCAN_STABILIZATION_TIME_MS_Y
is no longer used for just Y in your code, we should re-name it. This is to follow the "principle of least surprise" - it'd be really surprising to someone in the future to change SCAN_STABILIZATION_TIME_MS_Y
and see that all the axis move stabilization times change (not just Y).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I would copy the code.
cmd[5] = payload & 0xFF | ||
self.send_command(cmd) | ||
|
||
def move_xy_to_usteps(self, xusteps, yusteps): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we also added a MOVE_XY
- we should add that here so users of the Microcontroller
know it exists. Or remove it from the firmware.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I will temporarily remove MOVE_XY from the firmware.
self._calc_move_timeout(abs_mm - self.get_pos().y_mm, self.get_config().Y_AXIS.MAX_SPEED) | ||
) | ||
|
||
def move_xy_to(self, x_abs_mm: float, y_abs_mm: float, blocking: bool = True): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have an AbstractStage
interface now. This means the codebase outside of squid/stage
can only use methods on the AbstractStage
class, and any class that implements AbstractStage
must implement all the @abstractmethod
on it. As is, this breaks the PriorStage
and the AbstractStage
interface.
To do this change properly, we should:
- Add an
@abstractmethod
calledmove_xy_to
toAbstractStage
. - Implement
move_xy_to
on all the stage implementations insquid/stage
(right now, just the cephla and prior stages).
Test, or find someone that can help you test, the new code on all the stages we support.
We should not merge this before doing all of the above since it will break some customers!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I would add an @AbstractMethod called move_xy_to to AbstractStage. Would you please ask somebody do the implementation of move_xy_to in to prior_stages? @hongquanli
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or I just add an empty function in prior_stages?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Other than adding the PriorStage
definition, this looks good (assuming it has been tested on all the Cephla stage hardware it will need to run on).
@hongquanli we still need to decide what we want to do in terms of managing firmware updates. Once this merges, users will need to do a firmware update if they update their repos.
axis = 3; | ||
|
||
// guard code | ||
if (axis > STAGE_AXES) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This just fails silently, though. If someone tries to use this command with axis > STAGE_AXES
, how do they know that they didn't actually set ramp mode on the axis?
threading.Thread(target=self.wait_for_stop, daemon=True).start() | ||
|
||
def move_xy_to(self, x_abs_mm: float, y_abs_mm: float, blocking: bool = True): | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For now, I think you can just make this:
def move_xy_to(self, x_abs_mm: float, y_abs_mm: float, blocking: bool = True):
self.move_x_to(x_abs_mm, blocking)
self.move_y_to(y_abs_mm, blocking)
axis = 3; | ||
|
||
// guard code | ||
if (axis > STAGE_AXES) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It looks like we let all other commands fail silently, so I guess we can keep this.
We really need to introduce a mechanism for communicating command failures, though. Failing silently shouldn't be an option!
By simultaneously driving the optimization adjustments of the X and Y axes, a performance improvement of approximately 5% was achieved.