Difference between revisions of "ME 333 Lab 4"
Andrew Long (talk | contribs) (→RS232) |
NickMarchuk (talk | contribs) |
||
Line 340: | Line 340: | ||
DIRECTION_PIN = REVERSE; |
DIRECTION_PIN = REVERSE; |
||
mLED_2_Off(); |
mLED_2_Off(); |
||
SetDCOC1PWM( |
SetDCOC1PWM(MAX_RESOLUTION - pwmMagn); |
||
} |
} |
||
Revision as of 13:45, 22 January 2010
In this lab, we are going to use the PIC32 microcontroller to control the motor's position with a proportional feedback controller. A block diagram of the overview of this lab is shown below.
The PIC32 is going to generate a square wave reference signal (ref) that will cause the motor to alternate between two positions. This reference signal could be changed to any arbitrary signal such as a sinusoid or a triangle wave, but for this lab we are going to stick with a square wave. The period and amplitude of the square wave will be analog inputs to the PIC32.
The quadrature encoder attached to your motor creates two signals similar to those shown below. These signals will be sent to a encoder/decoder chip (LS7083) that converts the signals into up and down counts to be sent to the PIC32. The up and down counts will be read by the PIC32 and converted into a position for the motor (output). This algorithm will be explained in further detail in the encoder section.
The position error is the difference between the reference signal and the output. The error will be sent to a controller, in this case a proportional controller and converted into an input signal (u). This is shown in the equation below:
The input signal is then converted into a PWM to cause the motor to move and produce a new output.
So how do we do this?
First, we are going to write the code in which we will learn about the following programming topics:
- Timers
- Interrupts
- PWM
After programming the PIC32, you will construct the circuit for this motor controller. This circuit will include the following hardware pieces:
- H-bridge
- Motor with Encoder
- Encoder/Decoder Chip
After the circuit has been created, we will test the feedback control with different proportional gains (Kp).
Programming
Getting Started
This section details the code required for Feedback Control of Motor Position with the PIC32.
Overview: write something here
Create a new project folder and call it MotorPositionController. - Put HardwareProfile.h, HardwareProfile_NU32.h, and procdefs.ld. These are the same as in HelloWorld. The can be downloaded here - Create a new MPLAB project using the normal procedure - Make a new file in MPLAB and save as "MotorPositionController.c" without the quotes. This will be our main c file.
- Copy and paste the following lines of code that will serve as our template for the code.
/* Motor Position Control Lab 4 */ /** INCLUDES ***************************************************/ #include "HardwareProfile.h" /** Constants **************************************************/ #define TRUE 1 #define FALSE 0 /** Function Declarations **************************************/ /** Global Variables *******************************************/ /** Main Function **********************************************/ int main(void) { int pbClk; // Configure the proper PB frequency and the number of wait states pbClk = SYSTEMConfigPerformance(SYS_FREQ); // Allow vector interrupts INTEnableSystemMultiVectoredInt(); mInitAllLEDs(); while(1) { } } //end main /** Interrupt Handlers *****************************************/ /** Other Functions ********************************************/
All of the lines that have /*....*/ are just commented out lines referring to different sections of code. We will fill in these sections. The main.c function currently has SYSTEMConfigPerformance which optimizes the PB frequency and number of wait states. This function also returns the peripheral bus clock frequency, which we will need later. INTEnableSystemMultiVectoredInt() is a function that enables system wide interrupts. Interrupts will be discussed below. We are also initializing the LEDS on the NU32 board and creating an infinite while loop. In fact, this infinite while loop will remain empty for this entire lab. The rest of the code will be taken care of in interrupts (discussed below).
Motor PWM
The PIC32 Output Compare Module has 5 pins (OC1:OC5) that can be used for pulse-width modulation (PWM) output. PWM essentially creates variable voltage across the motor. The PWM period is based on a 16 bit period register of Timer 2 or Timer 3. These two timers can be combined to get a 32 bit period register. In this section, we are going to initialize PWM, Timer2 and create an interrupt based on Timer 3 to update the PWM duty cycle. Describe Interrupts...
All of our initializations could be placed in the main function, but to make our code more modular we are going to create functions to initialze different segments of our code.
In the "Function Declarations" section, put the following line of code:
void initMotorPWM();
This function will initialize everything we need for PWM.
For this lab, we are going to use 3 pins to control the motor.
- A2 - digital output for enable pin
- A3 - digital output for direction
- D0 - PWM pin
Since, we are using two digital outputs, define the following lines of code in the "Constants" section.Note that the PWM pin does not need a constant because it will be initialized as a PWM pin.
#define ENABLE_PIN LATAbits.LATA2 #define DIRECTION_PIN LATAbits.LATA3
We also want to define constants for the direction. Put these lines of code in the "Constants" section.
#define FORWARD 0 #define REVERSE 1
Copy and paste the following function into the "Other Functions" section. Note that this function is not complete because there are several 4 things that have X's in them.
void initMotorPWM(void) { //Set Enable and Direction Pins (A2, A3) as digital outputs // Initialize as low LATA |= 0xXXXX; TRISA &= 0xXXXX; // init OC1 module, on pin D0 OpenOC1( OC_ON | OC_TIMER2_SRC | OC_PWM_FAULT_PIN_DISABLE, 0, 0); // init Timer2 mode and period (PR2) // produces 1ms period OpenTimer2( T2_ON | T2_PS_1_X | T2_SOURCE_INT, 0xXXXX); }
The first line of code needs to initialize pins A2 and A3 as digital outputs (see lab 2 for digital i/o information).
- Determine the Hex Number to set A2 and A3 as Low with LATA
- Determine the Hex Number to initialize A2 and A3 as digital outputs and leave everything else alone.
The second line of code (OpenOC1(), turns on the PWM for OC1 based on Timer2 with no fault pin.
Timer 2 is going to be the source of our PWM period. Timers basically increment a 16-bit variable TMRx where x is the Timer number. OpenTimerX() takes two inputs. The first input is the configuration constants and the second variable is known as the Period Register(PR). The configuration constants above turn on Timer 2, set a prescaler value and determine where the source of the clock is for the Timer. T2_SOURCE_INT indicates that the source of the clock will be internal, so the PB frequency is how fast the TMRx will be incremented. The combination of the prescaler value and PR determine the period of resetting TMRx back to zero. This resetting can be configured to trigger an interrupt flag. Interrupts are discussed in the next section. For PWM, this resetting sends the next pulse width, essentially creating a PWM period. The period of resetting is calculated using the following formula:
Period = [(PR + 1) Tpb (TMR_Prescaler_Value)] (convert to math) Frequency = 1 / Period where Tpb is the period of the peripheral bus (1/80Mhz for our PIC32)
To complete our initMotorPWM() function, we need to determine the prescalar value and the Period register. For PWM, the common frequencies are 5kHz - 40kHz. For this lab, we are going to use 20kHz as the frequency. Basically, a higher PR number results in higher resolution for the duty cycle. Therefore, we want to have the highest PR number we can afford, meaning that we want the lowest Prescalar Value.
- Calculate PR for a 20kHz frequency and a prescalar value of 1.
The PR number is a 16bit integer, so PR needs to be less than 2^16 - 1 (65536). If you calculate PR to be greater than this value, you will need to increase the prescalar value of 1. (Note that you don't need to for this section, but we will in the next section)
- With the prescalar value, fix the X in that constant.
- With the PR, put that number for the second input. (Its nice to convert to a 16bit hex number, so you remember that it can't be greater than 65536)
PR refers to the maximum number you can use for your duty cycle of PWM, therefore, we want to record what this number is.
- Put the following line of code in the "Constants" section
#define MAX_RESOLUTION 0xXXXX // Proportional to period of PWM
where 0xXXXX is the hex value for the calculated PR.
We now need to use the initMotorPWM function in the main function.
- Put the following line of code after mInitAllLEDs(); in your main function
initMotorPWM();
We also need to turn on (enable) the H-bridge in the main function.
- Put the following line of code before the infinite while loop
ENABLE_PIN = 1; // Enable the H-bridge
- For good practice, put the following line of code after the infinite while loop. We can use this function to turn off the PWM, but we won't ever get to this function.
CloseOC1();
The function to set the duty cycle is SetDCOCxPWM(short) where x is the module number and short is the duty cycle. The percentage on will be short / PR * 100 percent. This function will be used in the Feedback Controller section.
At this point, you have now initialized the PWM and a couple digital outputs for controlling the motor. We need to update the duty cycle, which we are going to do in an interrupt service routine in the next section.
Interrupt Controller
In this section, we are going to initialize and create an interrupt to control the main chunk of our code. Interrupt flags can be generated by many different things such as key strokes on the keyboard for RS232, Timer overflows, external pins, etc. When a interrupt flag is generated, the program jumps to an interrupt service routine (ISR) and carries out the lines of code in the ISR before returning to the original code. Essentially, it interrupts (stops) the main code and jumps somewhere else performs an action and then returns to the interrupted line of code. Our code will be sitting in the infinite while loop until an interrupt is generated. When the interrupt is generated, it will carry out several actions such as checking the encoder and updating the duty cycle before returning back to the infinite while loop.
Timer based interrupts are set up similar to the PWM discussed above. We are going to create a new function to initialize this interrupt.
- Put the following line of code in the "Function Declarations" section:
void initInterruptController();
- Put the following lines of code in the "Other Functions" section:
void initInterruptController(void) { // init Timer3 mode and period (PR3) // produces 1ms period OpenTimer3( T3_ON | T3_PS_1_X | T3_SOURCE_INT, 0xXXXX); mT3SetIntPriority( 7); // set Timer3 Interrupt Priority mT3ClearIntFlag(); // clear interrupt flag mT3IntEnable( 1); // enable timer3 interrupts }
The first line of code is the same as that for PWM except for with timer 3. The period resulting form the prescalar and PR will be the interrupt period.
- Choose a prescalar value and calculate the PR value to produce a 1ms interrupt period. (Remember that PR < 65536). Available prescalar constants for the X are 1, 2, 4, 8, 16, 32, 64, and 256 as shown in timer.h.
The next line of code sets a priority for the interrupts. There will be some cases in which several interrupts are generated at the same time or an interrupt may be generated while another ISR is being carried out. To resolve this mess, priorities are set between 0 and 7. Higher priorities can interrupt lower priorities. If two interrupts are generated at the same time, higher priorities get selected. We want this interrupt to be very important so we are going to set it to 7.
The third line of code clears the interrupt flag, so an interrupt isn't generated immediately upon enabling the interrupt.
The last line enables the interrupt based with Timer 3.
- Again, we need to include this in our main function. Put the following line below initMotorPWM();
initInterruptController();
Now we need to create our ISR for Timer 3 which is the code that the interrupt goes to every 1 ms.
- Put the following lines of code in the "Interrupt Handler" section
// interrput code for the timer 3 void __ISR( _TIMER_3_VECTOR, ipl7) T3Interrupt( void) { // clear interrupt flag and exit mT3ClearIntFlag(); } // T3 Interrupt
This is the skeleton for our Timer 3 interrupt. All interrupt handlers start with 'void__ISR('. The first constant is a vector constant defined in pic32mx460f512l.h. These vector constants indicate what causes the interrupt. For this case, timer 3 causes the interrupt. You will see another example of an interrupt vector for RS232 in the RS232 section. The second input (ipl7) indicates the priority level. This must be the same (7) as the priority level that you set in the initialization of the interrupt. If the priority level is set differently here, the interrupt probably will not work correctly. T3Interrupt is just a label for this interrupt (this is not actually used anywhere, just names the ISR).
In our interrupt, mT3ClearIntFlag() is called to clear the interrupt flag and returns back to the original line of code. Any code added to the interrupt must be placed above mT3ClearIntFlag(); We will fill in the interrupt as we go.
Encoder
This section details initializing encoder counting and a function to return the position of the motor.
- Again, put the following line of code in the "Function Declarations" section.
void initEncoder(void);
As discussed above, Timers can be incremented internally by the peripheral bus or externally. We are going to use Timers 4 and 5 to count the up and downcounts from our encoder/decoder chip. There are 5 external counter pins labeled TxCK on the PIC32. Since they are associated with specific Timers, we cannot use T2CK and T3CK because these two timers are used for PWM and the Interrupt.
- Put the following lines of code in the "Other Functions" section
void initEncoder(void) { // init Timer4 and Timer5 mode and periods (PR4, PR5) OpenTimer4( T4_ON | T4_PS_1_1 | T4_SOURCE_EXT, 0xFFFF); OpenTimer5( T5_ON | T5_PS_1_1 | T5_SOURCE_EXT, 0xFFFF); }
These two lines of code simply open the timers. If we wanted to have the timers generate an interrupt we could modify the prescaler and the PR value. We are not going to use any interrupts with these timers, so we will set the prescalar to be 1:1 with a PR of 0xFFFF.
We are also going to define a function that calculates the current position of the motor based on these two counters.
- Put the following line of code in the "Function Declarations" section:
int getEncoderPosition(void);
This function will return a 32 bit integer of the encoder position.
- Put the following lines of code in the "Other Functions" section:
int getEncoderPosition() { short count0 = ReadTimer4(); // in your routine this must be done at least every 32000 encoder counts to avoid rollover ambiguity short count1 = ReadTimer5(); bigcount += count0 - last0; // add on the recent up-counts, since the last time if (count0 < last0) { bigcount += 65536; // count0 only increments, so if it got lower it must have rolled over } last0 = count0; bigcount -= count1 - last1; // we're not worrying about rollover of the 32 bit bigcount total if (count1 < last1) { bigcount -= 65536; } last1 = count1; return bigcount; }
This function reads the values of the up counts (Timer 4) and the down counts (Timer 5) to calculate the encoder position stored in bigcount. This function accounts for roll-over of the 2 counters, but does not consider roll-over of the 32 bit number bigcount. This function returns the encoder position (bigcount).
Since this function depends on the counts of the previous step and a global encoder position, we need to include three global variables.
- Put the following lines of code in the 'Global Variables' section:
signed int bigcount = 0; // set encoder value initially to zero, it can go + or - // 32 bit number short last0 = 0, last1 = 0; // 16 bit number, prev tmr4 and tmr5
Finally, we need to include our initialization in the main function.
- Put the following line of code after initInterruptController(); in the main function.
initEncoder();
We have now initialized our external counters for the up and down counts and created a function to get our encoder position. This function will be used in the controller section.
Feedback Controller
Before we write our feedback controller, we need to create a reference signal as shown in the block diagram above.
For this lab, we are going to create a function that returns a scalar reference signal value based on a global timing index. The global timing index will be incremented each interrupt cycle of Timer 3. For our case, the global timing index will be a 1ms counter essentially. We are going to use a periodic reference signal with period 'refPeriod' and amplitude 'refAmplitude', so the global timing index will be reset at the end of each reference period.
Several global variables will be needed for the feedback controller and the reference signal.
- Put the following lines of code in the 'Global Variable' section:
int globalIndex = 0; int refPeriod = 2000; // period in ms int refAmplitude = 0; // in encoder counts
The reference period is in ms and is initialized to be a 2 sec period. The reference amplitude is 0, so the motor will not move initially.
We are going to create a function that returns the scalar reference value based on the global index.
- Put the following line of code in the 'Function Declarations' section:
int getReference(int index);
This function could be based on any type of reference signal. For this lab, we are going to use a simple square wave.
- Put the following lines of code in the 'Other Functions' section:
int getReference(int index) { if(index > refPeriod/2) { return refAmplitude; } else { return -refAmplitude; } }
We are now going to begin to populate the Timer 3 interrupt handler.
First, we are going to calculate the error based on the reference signal and the encoder information.
- Put the following lines of code at the beginning of the Timer 3 interrupt handler:
int currentPosition = getEncoderPosition(); int ref = getReference(globalIndex); signed int error = ref - currentPosition;
With this calculated error, we are going to set the PWM and direction. We will do this in a new function.
- Put the following line of code in the "Functions Declaration" section:
int setPWMandDirection(signed int error);
For debugging purposes, this function returns the magnitude of the PWM.
- Put the following lines of code in the "Other Functions" section:
int setPWMandDirection(signed int error) { int pwmMagn; pwmMagn = getPWMmagn(error); if (error > 0) // Go Forward r > y { DIRECTION_PIN = FORWARD; mLED_2_On(); SetDCOC1PWM(pwmMagn); } else // Go Reverse r < y { DIRECTION_PIN = REVERSE; mLED_2_Off(); SetDCOC1PWM(MAX_RESOLUTION - pwmMagn); } return pwmMagn; }
This function begins by calculating the PWM magnitude with another new function (defined shortly).
For our motor control, we are sending a PWM signal to one input of the H-bridge and a direction pin to the other input. For this program, FORWARD is defined as low and REVERSE is defined as high. There are four cases for the motor control and are shown in the following table.
Direction | PWM | Motion |
---|---|---|
0 | 0 | Brake |
0 | 1 | Forward |
1 | 0 | Reverse |
1 | 1 | Brake |
For a typical PWM signal, the duty cycle (percentage high) is proportional to how fast you want your motor to operate. This works for forward direction because a high in the PWM causes the motor to go forward. However, for the reverse direction, the percentage low is proportional to how fast we want the motor to run. This is essentially a bit-wise inverse of the PWM signal. This is done if the error is less than zero with the ~ operator as shown above.
In the function above, if the error is greater than 0, the PWM is set normally, the Direction Pin is set to FORWARD and LED2 is turned on to indicate go FORWARD. If the error is less than 0, the PWM is inverted, the Direction Pin is set to REVERSE and LED2 is turned off to indicate go REVERSE.
We are now going to create our proportional controller for the getPWMmagn() function.
- Put the following line of code in the "Function Declarations" section:
int getPWMmagn(signed int error);
This function returns the magnitude of the pwm signal.
For this lab, we are only using proportional control. Other forms of control include integral and derivative terms. This function as well as the set function could be modified to include the derivatives and integrals of the error. We are only going to use proportional control, so the only input is the error.
- Put the following lines of code in the "Other Functions" section:
int getPWMmagn(signed int error) { int pwmMagn = abs(error) * Kp + Offset; // Proportional Controller // condition ? value if true : value if false return pwmMagn > MAX_RESOLUTION ? MAX_RESOLUTION : pwmMagn; }
This function first calculates magnitude of the PWM based on a proportional constant Kp and Offset. There will be a deadband of voltages that the motor will not rotate. Therefore, we are including an offset that will cause an error of zero to be on the verge of rotating. Since we have a MAX_RESOLUTION for the pwm magnitude, the return value is either pwmMagn calculated on the first line or the maximum resolution.
We need to define Kp and Offset.
- Put the following lines of code in the "Global Variables" section:
int kp = 25; // proportional gain int offset = 5000; // feedback offset
These variables are set as global variables instead of constants because you may choose to use analog inputs to adjust the gain and offset values.
You can now put the setPWMandDirection() function in the Timer 3 interrupt
- Put the following line of code in the Timer 3 interrupt after you calculate the error.
setPWMandDirection(error);
As stated above, our reference signal is generated based on a global 1ms timer index. We are going to rezero this global index at the end of each period. These lines of code will toggle LED1 to indicate a new period.
- Put the following lines of code below the setPWMandDirection() function but above the clear flag function
globalIndex++; if (globalIndex > refPeriod) { globalIndex = 0; mLED_1_Toggle(); }
We have now set up our feedback controller based on Timer 3 interrupt. However, it would be nice to change the period and amplitude of the square wave as well as see how our feedback is performing compared to the reference signal. Since we have already learned how to operate RS232, we are going to communicate to your computer using RS232 as discussed in the next section.
RS232
This section details how we are going to use RS232 communication with our motor controller.
- Define the Baudrate in the "Constants" section
#define DESIRED_BAUDRATE (9600) // The desired BaudRate
- Put the following line of code in the "Function Declarations" section, so we can initialize our RS232:
void initUART2(int pbClk);
Note that we are using the pbClk as an input.
- Put the following lines of code in the "Other Functions" section:
void initUART2(int pbClk) { // define setup Configuration 1 for OpenUARTx // Module Enable // Work in IDLE mode // Communication through usual pins // Disable wake-up // Loop back disabled // Input to Capture module from ICx pin // no parity 8 bit // 1 stop bit // IRDA encoder and decoder disabled // CTS and RTS pins are disabled // UxRX idle state is '1' // 16x baud clock - normal speed #define config1 UART_EN | UART_IDLE_CON | UART_RX_TX | UART_DIS_WAKE | UART_DIS_LOOPBACK | UART_DIS_ABAUD | UART_NO_PAR_8BIT | UART_1STOPBIT | UART_IRDA_DIS | UART_DIS_BCLK_CTS_RTS| UART_NORMAL_RX | UART_BRGH_SIXTEEN // define setup Configuration 2 for OpenUARTx // IrDA encoded UxTX idle state is '0' // Enable UxRX pin // Enable UxTX pin // Interrupt on transfer of every character to TSR // Interrupt on every char received // Disable 9-bit address detect // Rx Buffer Over run status bit clear #define config2 UART_TX_PIN_LOW | UART_RX_ENABLE | UART_TX_ENABLE | UART_INT_TX | UART_INT_RX_CHAR | UART_ADR_DETECT_DIS | UART_RX_OVERRUN_CLEAR // Open UART2 with config1 and config2 OpenUART2( config1, config2, pbClk/16/DESIRED_BAUDRATE-1); // calculate actual BAUD generate value. // Configure UART2 RX Interrupt with priority 2 ConfigIntUART2(UART_INT_PR2 | UART_RX_INT_EN); }
This sets up RS232 communication with 9600 baudrate, no parity bit, 8 data bits, 1 stop bit and no flow control. It also configures an interrupt based on every character received. The priority is set at 2 for the RS232 interrupt, so its not as important as the timer 3 interrupt.
- Put the following line with the other initialization functions in the main function:
initUART2(pbClk);
- Put the following line before the infinite while loop to display a start program message on your terminal
putsUART2("*** Start Program ***\r\n");
- Put the following lines of code in "Interrupt Handlers" section
// UART 2 interrupt handler // it is set at priority level 2 void __ISR(_UART2_VECTOR, ipl2) IntUart2Handler(void) { char data; // Is this an RX interrupt? if(mU2RXGetIntFlag()) { data = ReadUART2(); // Echo what we just received. putcUART2(data); switch(data) { case 'r': // reset refPeriod = 2000; // Change to analog input refAmplitude = 1000; // Change to analog input break; case 'f': // increase kp kp+=1; break; case 'b': // decrease kp kp-=1; break; case 'p': // record and print data recordData = TRUE; recordIndex = 0; break; case 's': // stop refAmplitude = 0; break; } // Toggle LED to indicate UART activity mLED_0_Toggle(); // Clear the RX interrupt Flag mU2RXClearIntFlag(); } // We don't care about TX interrupt if ( mU2TXGetIntFlag() ) { mU2TXClearIntFlag(); } }
This interrupt handler is defined with _UART2_VECTOR and ipl2 to indicate that the ISR for RS232 with the second set of pins with a priority of 2. At the moment, this ISR is triggered by both receiving and transmitting characters. The interrupt first checks which type of interrupt flag was generated (receive or transmit). If the interrupt was receive interrupt, it first reads the key pressed and echos it back to the terminal. It then goes into a switch statement depending on the letter. The letters are described below:
- 'r' - this resets the period and amplitude of the square wave. The period and amplitude maybe analog inputs, but are just constants right now.
- 'f' - increases the proportional gain
- 'b' - decreases the proportional gain
- 'p' - causes the interrupt Timer 3 to save data and print it back to the terminal (discussed in more detail below)
- 's' - stops the motor by setting the amplitude to zero
LED0 is then toggled to indicate RS232 activity.
If the interrupt was generated by the transmit, this interrupt function just clears the flag and returns to the other code.
Our final task is to store and send encoder data to the PC. This is initiated by pressing a 'p' on the keyboard. RS232 communication is slow, so we can't send our data back every 1ms. Therefore, we are going to store X number of data points and then send all the data when we are done collecting.
- Put the following line of code in the 'Constants' section to indicate the number of sample points.
#define NUM_DATA_POINTS 2000
In the interrupt handler for UART2, when a 'p' is pressed, the variable recordData is set to TRUE and recordIndex is set to 0. We need to set up these global variables
- Put the following lines of code in the 'Global Variables' section:
int recordIndex = 0; int recordData = FALSE;
We are going to store both the reference signal and the encoder signal in two global arrays of size NUM_DATA_POINTS.
- Put the following lines of code in the 'Global Variables' section:
int encoderCounts[NUM_DATA_POINTS]; int referenceData[NUM_DATA_POINTS];
Similar to Lab 2, we are going to use sprintf to make strings with numbers; therefore, we are going to create an RS232 buffer.
- Put this line of code in the 'Global Variables' section:
char RS232_Out_Buffer[32];
- In the timer 3 interrupt, put the following lines of code before the lines about globalIndex.
if(recordData) { encoderCounts[recordIndex] = currentPosition; referenceData[recordIndex] = ref; if(++recordIndex == NUM_DATA_POINTS) { sendDataRS232(); } }
These lines of code will only be run when recordData is true. They store the currentPosition and ref values for NUM_DATA_POINTS. When the recordIndex is equal to NUM_DATA_POINTS, the data is sent with a new function called sendDataRS232(); This is defined below.
- Put the following line of code in the 'Function Declarations' section:
void sendDataRS232(void);
- Put the following lines of code in the 'Other Functions' section:
void sendDataRS232() { int i; recordData = FALSE; sprintf(RS232_Out_Buffer, "Data for period = %d, amp = %d, kp = %d\r\n", refPeriod, refAmplitude, kp); putsUART2(RS232_Out_Buffer); for(i = 0; i < NUM_DATA_POINTS; i++) { sprintf(RS232_Out_Buffer,"%d,%d,%d\r\n",i+1,encoderCounts[i], referenceData[i]); putsUART2(RS232_Out_Buffer); } }
This function changes recordData to false. Prints to the terminal the period and amplitude of the square wave as well as the proportional controller constant. In a for loop, the index, encoder count and reference data is sent to the terminal. This function will not be interrupted because it is currently being called in a high priority interrupt.
You can manipulate this data using excel or processing as discussed in the next section.
Programming Summary
Wow! That was a lot of programming. This section summarizes what you did above. You created code to do the following:
- Initialize:
- PWM based on Internal Timer 2
- 1ms Interrupts based on Internal Timer 3
- Encoder Counters based on External Timers 4 and 5
- RS232 Communication using UART2
- Interrupt Every 1 ms to:
- Get the Reference Signal
- Calculate Current Position
- Calculate Error and PWM based on Proportional Controller
- RS232 Interrupt to:
- Reset the Period and Amplitude of square wave
- Increase or decrease the proportional gain, kp
- Initiate storage and printing of data
- Store on the PIC32 and Print X number of data points to the terminal
While creating this code, you have seen how to create interrupts and create modular code.