Before beginning I should mention some assumptions that I made beforehand. I will assume that you have a decent understanding of programming a PIC in assembly language and therefore I won't spend time going over the basics of configuring every single peripheral and some of the more basic subroutines found in the programs (delays etc...). More info on AD conversions can be found in my H-Bridge tutorial as well as the very informative Googlium Tutorials
Upon processor startup, you will see a call to "peripheralInit":
The subroutine "peripheralInit" is found in the init.asm file: and contains all of the necessary startup code to configure microcontroller pins and peripherals as well as the 20x4 LCD panel of the control box. I won't cover all of the file here as much of it is pretty basic if you have ever configured a PIC MCU before, but I will revisit the topic if needed when we discuss more of the project in detail.
Although we have not yet discussed the code running on the ROV itself, one of the things it does is initialize the 6 ESCs and then send a signal to the topside control box once they are initialized. The reception of this signal (Code #2 in the Indicators and Warnings Codes) triggers an interrupt in our control box MCU which lights the "ESC ready" LED that is connected to PORTC, 1. By checking the status of this pin and waiting for it to go high, we ensure that the ROV ESCs are properly initialized before jumping into the main loop of our control box code. We also wait to initialize any PORTB interrupts till this has occurred since the joystick push-button switch will trigger an interrupt on PORTB.
At the top of the main loop we send any calculated values from our AD conversion to the ROV. There are no calculated ADC values just after processor startup, but our initialization routine preloaded values that correspond to "stopped" signals to the ESCs and they are sent on the first iteration of the main loop.
The main loop of our program then checks the analog input from the joystick potentiometer that is mapped to a forward/reverse direction of the ROV (ANO). We first configure the ADC module to use the appropriate input for the ADC conversion, add a short delay to let the ADC module stabilize, wait for the conversion to complete and then place a copy of the ADC result into a "shadow register" that holds the value for the ADC result that will determine our forward/reverse movement (ADRESH0)
Next, we do the exact same thing but this time we use AN1 as the analog input for our AD conversion and save the ADC value in a separate register to be used for determining left/right movement of the ROV (ADRESH1).
Additionally we need to take into account the play or "slop" of the joystick. Simply moving the control box around may induce small and unintended movements of the joystick. Also, depending on the quality of your joystick, it may not exactly hold to true "dead center" when released to a neutral position. I make a call to the subroutine "checkSlop" to handle this:
The contents of the subroutine are as follows and can be found in the motorCalculations.asm file:
Essentially you need to do some testing to see what range of ADC values you need to "disregard" in order to prevent jittery thruster firing due to play in the joystick when it is in a neutral position. The values I use in my code err on the side of caution and will probably be trimmed down somewhat in the future (I switched to a better quality joystick and can now probably get away with a smaller bracket of values.) You can see that the code checks to make sure the ADC value falls outside our determined range of values for "slop" and if so returns from the subroutine with no changes made to the registers. If the values do fall inside the predetermined range for "slop" then we essentially send stop signals to all 6 thrusters as well as the "state" code for stop via UART (more on that later).
Determining Prevailing Direction of Travel
So now that we have ADC values from the two potentiometers that are connected to the fwd/rev and left/right movements of our joystick, we need to determine upon which axis (x or y) is found the greatest "displacement" from a neutral joystick position. A neutral joystick position is represented by an ADC value of 127 for both axes. While this method does not allow for very granular control over the movement of the ROV, it is a starting point that can be improved upon down the road.
We first check the displacement from 127 for the analog input AN0, which is mapped to the forward/reverse direction of the joystick:
The "getAn0Disp" subroutine is as follows and can be found in the motorCalculations.asm file:
As you can see, we are simply determining whether the joystick is above or below the neutral position (regarding a fwd/rev or y-axis orientation) and then finding out how far away it is from this neutral position (denoted by a decimal value 127). This "displacement" value is then placed into the memory location named "AN0disp"
Similarly, we do the same thing for the analog input AN1, which is mapped to the left/right direction of the joystick:
The "getAn1Disp" subroutine is as follows and can be found in the motorCalculations.asm file:
The method here is nearly identical to the "getAn0Disp" subroutine but we are now considering the left/right (or x-axis) direction. The "displacement" value from a neutral position of 127 is placed into the memory location named "AN1disp."
Now that we have two displacement values for two different axes of motion, we determine which one is greater. This will determine whether our joystick movement represents a desire to move left/right or fwd/rev.
All that is happening here is a simple subtraction and check of the carry/borrow bit of the STATUS register. Read up on the STATUS register if you are rusty with subtractions and borrows, but the code comments help to explain somewhat. The status of this bit determines whether we proceed under the assumption of fwd/rev direction of travel or left/right.
Lets assume that the result of the above code brings to a direction of travel that is either forward or reverse (y-axis direction). We now need to determine exactly whether the joystick movement represents movement forward (positive y direction) or reverse (negative y direction). We do this by determining whether the value in memory location "ADRESH0" is above or below a neutral value of 127. Using our knowledge of the binary number system, we can simply test the most significant bit of this 8 bit value. A "1" means the number is > 127. A "0" means it is <= 127.
If the value is greater than 127, the "goto reverse" instruction is skipped and the "forward" section of code is next in line to be executed. We can see the " forward" section of code below:
The "state" variable can be one of 8 numbers that represents the overall intended directional movement of the ROV. This comes into play when we transmit or UART data packets and parse out those packets with the MCU on the ROV itself. For now just know that the state codes can be found in the UART State Codes document and are described below:
- 0 = Forward
- 1 = Reverse
- 2 = Traverse Right
- 3 = Traverse Left
- 4 = Rotate Clockwise
- 5 = Rotate Counter Clockwise
- 6 = Up/Down
- 7 = Stop
In the situation where we have determined a desired forward direction, we place the value "0" into the "state" variable.
Next we need to realize that regardless of the direction in which the ROV is moving, two thrusters will be spinning in one rotational direction and the other two in the opposite direction. The rotational speed of all four thrusters will be the same. The only difference is that the two pairs will be rotating in opposite directions. (This will always be the case regardless if we are moving forward, backward, left, right, rotating clockwise or counter-clockwise). If we desire to move forward, the normal (uncompensated) ADC value will be used to determine the thrusters rotating in a manner that moves them "forward". However, we must do a little math to determine the value to be sent to the thrusters that will rotate in a manner that moves them in "reverse" This is done by using the displacement value that we calculated earlier. Keep in mind that much of this depends on the orientation in which you are attaching the thrusters to the ROV frame. There is a front and back side to the units and in my case I have the front side of all 4 thrusters facing outboard from the ROV
The above section of code does exactly this and makes use of the subroutine "getMotorSpeed" which can also be found in the motorCalculations.asm file. When the desired direction is "reverse" we execute the code seen below. It is very similar to going forward but we load the appropriate value into the "state" variable and place the value returned from "getMotorSpeed" into a variable named "reverseSpeed"
The variables "forwardSpeed" and "reverseSpeed" are the second and third of the four total data packets that will be sent to the sub-surface MCU found on-board the ROV. They represent values that will be placed into the CCPRXL registers of the PWM module. The ROV's MCU will determine where to place these values depending on the directional "state" that is receives.
In the event that we need to move laterally left or right, we also follow a very similar procedure but this time it is performed using the ADC result from analog input AN1. We simply use different variables to hold the values and place the corresponding value into "state code". The following code shows how we determine whether we are moving left or right, followed by the process used in determing values for each direction:
So with that completed we have finished the main loop of the programs and achieved movement along the x and y axes. We have yet to discuss movement along the z-axis (diving and surfacing) or rotation about the z-axis (clockwise and counter-clockwise rotation). To accomplish those things we will turn to interrupts in the next section.