We’ve all been there: asked to work on a project where some or all of the programming has already been done. You pick up the teach pendant to find a program called MAIN which contains 1000 LOC with a mess of labels, old sections of code that are jumped out and may even be controlling other parts of the machine at the same time. If you’re lucky, there might be some comments or thoughtfully-labeled sections, but it’s still going to be a nightmare to work on. If only the original programmer had put some more thought into the architecture of his programming you wouldn’t have to sift through 1000 lines to fix a bug.
Naming
There are only two hard things in Computer Science: cache invalidation and naming things.
Phil Karlton
Luckily we don’t really have to deal with cache invalidation when programming robots, but we do have to name things: programs, macros, I/O, registers, positions, position registers, etc. Name things such that their intent is clear, so that anyone who sees the program, subroutine, etc. can intuitively understand its purpose.
How often have you seen a set of macros called GRIP_OPEN and GRIP_CLOSE? What does that mean? You might assume that GRIP_CLOSE would grip the part and GRIP_OPEN would release it, but what if the gripper expands within the part to grip it? GRIP_ON and GRIP_OFF are a little more clear, but I typically use GRIP and UNGRIP.
FANUC doesn’t gives us very many options to organize our code. There’s no concept of directories for your programs; the best you get are sub-types for macros, condition handlers, KAREL programs, etc. Since everything is ordered alphabetically by default, I generally preface my programs in a way to keep related programs together. At the very least, I usually prefix all of my TP programs with some letter and an underscore (e.g. J_) and all KAREL programs with a K_ to easily distinguish between the two. Beyond that, I’ll try to simply extend a given operation into more detail:
- J_UNLOAD
- J_UNLOAD_CONVEYOR
- J_UNLOAD_BUFFER
- J_LOAD
- J_LOAD_CONVEYOR
- J_LOAD_BUFFER
This helps me quickly scroll through the SELECT menu to find the program I’m looking for.
Single Responsibility Principle
If you’ve ever done any object-oriented programming, you’ve probably heard of this before. If you haven’t, it means that any class (think of a program or subroutine) should have a single responsibility; it shouldn’t be doing many different things.
Let’s look at our earlier example. We had one program, MAIN, that did the entire operation. Assuming the operation is more than just a move from A to B, we can probably do a better job of communicating intent.
If it’s a pick and place operation, your main program might look like this:
! J_MAIN.LS ;
! ========= ;
CALL J_INIT ;
;
LBL[1] ;
IF DI[1:Cycle stop]=ON,JMP LBL[999] ;
;
CALL J_PICK ;
CALL J_PLACE ;
JMP LBL[1] ;
;
LBL[999] ;
Suppose your robot can picks from two locations. You might be tempted to write something like this:
! J_PICK.LS ;
! ========= ;
SELECT R[1:Pick location]=1,JMP LBL[100] ;
=2,JMP LBL[200] ;
ELSE,JMP LBL[404] ;
;
LBL[100] ;
L P[1:pick 1 approach] max_speed CNT100 ;
L P[2:pick 1] max_speed CNT0 ;
CALL J_GRIP ;
L P[3:pick 1 retreat] max_speed CNT100 ;
JMP LBL[999] ;
;
LBL[200] ;
L P[4:pick 2 approach] max_speed CNT100 ;
L P[5:pick 2] max_speed CNT0 ;
CALL J_GRIP ;
L P[6:pick 2 retreat] max_speed CNT100 ;
JMP LBL[999] ;
;
LBL[999] ;
I’d argue that you’re better off writing:
! J_PICK.LS ;
! ================= ;
! AR[1:Pick target] ;
! ----------------- ;
IF AR[1]=1,JMP LBL[100] ;
IF AR[1]=2,JMP LBL[200] ;
JMP LBL[404] ;
;
LBL[100] ;
PR[1:Pick]=P[1:Pick 1] ;
JMP LBL[300] ;
;
LBL[200] ;
PR[1:Pick]=P[2:Pick 2] ;
JMP LBL[300] ;
;
LBL[300] ;
L PR[1:Pick] max_speed CNT100 Offset,PR[1:Approach] ;
L PR[1:Pick] max_speed CNT0 ;
CALL J_GRIP ;
L PR[1:Pick] max_speed CNT100 Offset,PR[2:Retreat] ;
The programs are about the same length, but we’ve reduced duplication significantly. If you wanted to change a segment speed or an approach offset, you only have to change it in one place. Yes, we’ve added one level of indirection here, but it’s not hard to follow. The tradeoff in clarity and reduced duplication makes it easy to justify.
Don’t Repeat Yourself (DRY)
I believe this may be the most important programming principle I follow. Any time I see code that looks similar in two places, I try and think of a way to refactor it so that the code is not repeated. It’s much easier to fix a bug in one place than many places. A smaller code-base is much easier to maintain than a large one.
Small Bites
What is it people say about eating elephants? Big problems are much easier when you break them down into smallerchunks.
Try and reduce your problem into its main operations, then break those operations into even smaller subroutines. I had a project recently where the robot had to be ready to perform any given operation depending on the current state of the cell. I broke the requirement down into its two main parts and then broke those parts down into smaller chunks:
- J_HANDLE_UNFINISHED
- J_PICK_STA9
- J_PLACE_STA10
- J_HANDLE_FINISHED
- J_PICK_FINISHED
- J_PICK_STA19
- J_PICK_BUFFER
- J_PICK_INSPECT
- J_PLACE_FINISHED
- J_PLACE_DUNNAGE
- J_PLACE_BUFFER
- J_PLACE_INSPECT
- J_PLACE_REJECT
- J_PICK_FINISHED
This could have gotten messy real fast if it had all been one giant program jumping around to many different labels.
I’d argue that your main routine should probably have only a couple of labels at most, with the only jumps being in and out of the main loop. Anyone should be able to look at your main routine and clearly understand what the robot’s operation is.
There you have it:
- Name things accurately to communicate intent
- Organize your programs by name to save time
- Your programs should only do one thing (Single Responsibility Principle (SRP)
- Don’t Repeat Yourself (DRY)
- Break things down into small chunks
- Your MAIN program should be simple
If you follow these guidelines, you and the other programmers who end up having to support your code will have an easier time and thank you for it.