Small Programs

Filed under: FANUC TP Programming Workflow

FANUC’s TP programming environment is great for creating simple programs quickly. From the comfort of your teach pendant, you can record a few points, turn a few bits on and off and have a fully functional demo within a few minutes. However, as soon as you have to add some real intelligence to your robot, you’ll quickly find the editor to be cumbersome and slow.

I’ve already written about how I write my TP programs by hand, but here’s a quick trick I’ve been using to make my development faster and my programs a little easier to understand: tiny programs. I’m talking really small, like one or two lines.

Here’s a quick example of a rudimentary “move home” program:

UFRAME_NUM=0 ;
UTOOL_NUM=1 ;
J PR[1:HOME] 100% CNT0 ;
RO[1:open fingers]=ON ;
PAYLOAD[1] ;

Five lines of code… probably can’t get much shorter, right? But maybe we can make it a bit more readable. Try this:

CALL UFRAME_WORLD ;
CALL UTOOL_END_CAP ;
J PR[1:HOME] 100% CNT0 ;
CALL UNGRIP_END_CAP ;
CALL PAYLOAD_EMPTY ;

I know, that’s a lot of CALL statements for a 5-line program, but it couldn’t be more clear. You’ll thank yourself when you come back to this program in a year and don’t remember that UTOOL[1] is for end caps, turning RO[1]=ON opens the end cap gripper and PAYLOAD[1] is for an empty EOAT.

Considering the fact that program names are pretty much the only unique identifiers we get in this environment, shouldn’t we use them to our advantage?

I’ve been using tiny 1-line programs for things like UFRAMEs, UTOOLs, PAYLOADs, WAIT statements and part-presence checking. It’s a little bit more work on the front-end, but it makes your programs much more descriptive and readable, and I find that I write subroutines much quicker when I don’t have to remember which array index constant goes with which gripper, payload, etc.

Here’s how I would have programmed a pick routine before using this method:

LBL[1] ;
UFRAME_NUM=0 ;
UTOOL_NUM=1 ;
J PR[1:approach] 100% CNT100 ;
WAIT DI[1:part present]=ON TIMEOUT,LBL[500] ;
WAIT RI[1:grip open]=ON TIMEOUT,LBL[501] ;
 ;
UFRAME_NUM=1 ;
J PR[2:pick] 100% CNT100 Tool_Offset,PR[10:approach TO] ;
L PR[2:pick] max_speed CNT0 ;
CALL GRIP ;
WAIT R[1] ;
PAYLOAD[2] ;
L PR[2:pick] max_speed CNT100 Tool_Offset,PR[11:retreat TO] ;
 ;
WAIT RI[2:part gripped]=ON TIMEOUT,LBL[502] ;
WAIT DI[1:part present]=OFF TIMEOUT,LBL[503] ;
 ;
UFRAME_NUM=0 ;
J PR[3:retreat] 100% CNT100 ;
END ;
 ;
LBL[500] ;
! station not ready ;
JMP LBL[1] ;
 ;
LBL[501] ;
! gripper not gripped ;
JMP LBL[1] ;
 ;
LBL[502] ;
! part not gripped ;
JMP LBL[1] ;
 ;
LBL[503] ;
! part present stuck on ;
JMP LBL[1] ;

Let’s replace a few items:

LBL[1] ;
CALL UFRAME_WORLD ;
CALL UTOOL_GRIPPER ;
J PR[1:approach] 100% CNT100 ;
WAIT DI[1:part present]=ON TIMEOUT,LBL[500] ;
WAIT RI[1:grip open]=ON TIMEOUT,LBL[501] ;
 ;
CALL UFRAME_FIXTURE ;
J PR[2:pick] 100% CNT100 Tool_Offset,PR[10:approach TO] ;
L PR[2:pick] max_speed CNT0 ;
CALL GRIP ;
CALL GRIP_DELAY ;
CALL PAYLOAD_FULL ;
L PR[2:pick] max_speed CNT100 Tool_Offset,PR[11:retreat TO] ;
 ;
WAIT RI[2:part gripped]=ON TIMEOUT,LBL[502] ;
WAIT DI[1:part present]=OFF TIMEOUT,LBL[503] ;
 ;
CALL UFRAME_WORLD ;
J PR[3:retreat] 100% CNT100 ;
END ;
 ;
LBL[500] ;
! station not ready ;
JMP LBL[1] ;
 ;
LBL[501] ;
! gripper not gripped ;
JMP LBL[1] ;
 ;
LBL[502] ;
! part not gripped ;
JMP LBL[1] ;
 ;
LBL[503] ;
! part present stuck on ;
JMP LBL[1] ;

Let’s pull out the gripper and part-presence checking too:

LBL[1] ;
CALL UFRAME_WORLD ;
CALL UTOOL_GRIPPER ;
J PR[1:approach] 100% CNT100 ;
CALL ENSURE_PART_PRESENT ;
CALL ENSURE_GRIPPER_OPEN ;
 ;
CALL UFRAME_FIXTURE ;
J PR[2:pick] 100% CNT100 Tool_Offset,PR[10:approach TO] ;
L PR[2:pick] max_speed CNT0 ;
CALL GRIP ;
CALL GRIP_DELAY ;
CALL PAYLOAD_FULL ;
L PR[2:pick] max_speed CNT100 Tool_Offset,PR[11:retreat TO] ;
 ;
CALL ENSURE_PART_GRIPPED ;
CALL ENSURE_STATION_EMPTY ;
 ;
CALL UFRAME_WORLD ;
J PR[3:retreat] 100% CNT100 ;
END ;

Let’s go a bit further, probably overkill:

CALL APPROACH_STATION ;
 ;
CALL ENSURE_PART_PRESENT ;
CALL ENSURE_GRIPPER_OPEN ;
 ;
CALL APPROACH_PART ;
CALL MOVE_TO_PICK_LOCATION ;
CALL GRIP ;
CALL GRIP_DELAY ;
CALL PAYLOAD_FULL ;
CALL RETREAT_FROM_PART ;
 ;
CALL ENSURE_PART_GRIPPED ;
CALL ENSURE_STATION_EMPTY ;
 ;
CALL RETREAT_FROM_STATION ;

To be clear, I don’t advocate extracting every single motion statement into its own subroutine, but where do you draw the line?

Our goal is to program robots that work 100% of the time. They should be able to handle common error cases (and uncommon, if you have time!). They should just work. I’ve found that it’s much easier to get things to work when I’ve done a good job encapsulating tasks and hiding information that’s not necessary. This keeps our code clean, readable and less prone to error.

TP’s syntax and environment may be robot-friendly, but it’s not very programmer-friendly. We can do the other programmers that work with our code (and our future selves) a favor by using well-named small programs that make our programs easier to read and understand.

For more on this topic, there’s a great article by Edward V. Berard on the similarities and differences between Abstraction, Encapsulation, and Information Hiding.


Want posts like this delivered right to your inbox?

If you liked this post, please sign up for my mailing list!