Advertisement
abdullahkahraman

PID Controller

Oct 4th, 2013
177
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C 19.90 KB | None | 0 0
  1. /*
  2.  * File:   main.c
  3.  * Author: abdullah kahraman
  4.  *
  5.  * Created on 30 Haziran 2012 Cumartesi, 16:45
  6.  */
  7. #include <xc.h> // Include the header file needed by the compiler
  8. #include "RTOS_Debug.h" // Include the header for co-operative RTOS.
  9. #define _XTAL_FREQ 8000000 // XTAL frequency is 8MHz. This is needed for the __delay_us() function of the XC8.
  10. __CONFIG(FOSC_INTOSCIO & WDTE_ON & PWRTE_ON & MCLRE_OFF & CP_ON & IOSCFS_8MHZ & BOREN_ON);
  11. /*
  12.  * CONFIGURATION WORD:
  13.  * -------------------
  14.  * Internal Oscillator is set to 8 MHz.
  15.  * I/O function on both of the oscillator pins, i.e on RA4 and on RA5.
  16.  * Master clear is disabled. RA3 pin is digital input.
  17.  * Power-up timer is enabled.
  18.  * Watchdog timer is enabled.
  19.  * Code protection is enabled.
  20.  * Brown-out reset is enabled.
  21.  */
  22. /***** ADC Channel DEFINITIONS *****/
  23. #define KpValueAnalogChannel  5 // These are the channels that are connected to the
  24. #define KiValueAnalogChannel  4 // PID gain setting potentiometers. Value represents
  25. #define KdValueAnalogChannel  1 // the channel number. For example "1" means "AN1".
  26. #define setValueAnalogChannel 0 // The potentiometer for setting the desired motor speed.
  27. /* END OF ADC Channel DEFINITIONS */
  28.  
  29. #define DebugPin               RA4 // Pin to output debug information
  30. #define PWM_dutyCycle          CCPR1L // Give the PWM duty cycle setter variable a more readable name.
  31. #define PID_integralUpperLimit 2000 // Maximum limit for PID Integral
  32.  
  33. /**** RTOS DEFINITIONS  ****/
  34. unsigned char OS_currentTask; // This register holds the current task's place in the array OS_tasks
  35. unsigned char OS_tasks[6]; /* This array holds PCL and PCLATH for tasks. This array will have
  36.                               (number of tasks)*2 elements, since every task occupies 2 places.*/
  37. /* END OF RTOS DEFINITIONS */
  38.  
  39. /**** TIMING VARIABLES ****/
  40. unsigned char _50_us = 0; // This is the variable that the program will store the time in fifty microseconds.
  41. unsigned char _10_ms = 0; // This is the variable that the program will store the time in milliseconds.
  42. unsigned char _10_ms_ctr = 0; // This variable will count the _50_us so that we can get 10 ms in every 200 x _50_us.
  43. unsigned char _50_ms_ctr = 0; // This variable will count _10_ms so that we can get 50ms in every 5 x _1_ms
  44. unsigned char _100_ms_ctr = 0; // This variable will count _10_ms so that we can get 100ms in every 10 x _1_ms
  45. /**** END OF TIMING VARIABLES ****/
  46.  
  47. /*** SYSTEM VARIABLES ****/
  48. bit startPID; // This bit controls when the applyPID task will do its job.
  49. bit sampleADCs; // Control bit for the ADC sampling task. The task will wait for this bit to be 1.
  50. bit calculateSpeed; // Notifies readEncoder task to calculate the speed, every 50ms.
  51. unsigned char risingEdges; // Whenever an interrupt occurs on INT pin, this variable will be incremented.
  52. unsigned char currentMotorSpeed; // Holds the speed of the motor which is gathered from the encoder on RA2 pin.
  53. unsigned char setMotorSpeed; // The desired motor speed set by the user via a potentiometer.
  54. unsigned char PID_Kp; // Proportional error constant variable for PID control, set by a pot.
  55. unsigned char PID_Ki; // Integral error constant variable for PID control, set by a pot.
  56. unsigned char PID_Kd; // Derivative error constant variable for PID control, set by a pot.
  57. /*** END OF SYSTEM VARIABLES ****/
  58.  
  59. /**** Interrupt Function ****/
  60. void interrupt myInterrupt(void)
  61. {
  62.     if (TMR0IF) // If we have a TMR2 interrupt
  63.     {
  64.         // Interrupt will come up every 50uS
  65.         asm("INCF __50_us, F"); // Increment the 50us counter.
  66.         asm("INCF __10_ms_ctr, F"); // Increment the 1ms counter.
  67.         if (_10_ms_ctr == 200) // If _10_ms_ctr is 200, (50us x 200)=10ms
  68.         {
  69.             asm("INCF __10_ms, F"); // Increment the 10ms counter.
  70.             asm("INCF __100_ms_ctr, F"); // Increment the 100ms counter.
  71.             asm("INCF __50_ms_ctr, F"); // Increment the 50ms counter.
  72.             if (_50_ms_ctr == 5) // If it is 5, (10msx5) = 50ms
  73.             {
  74.                 calculateSpeed = 1; // Set this bit so that readEncoder task can function.
  75.                 _50_ms_ctr = 0; // Reset the 50ms counter.
  76.             }
  77.             if (_100_ms_ctr == 10) // If it is 10, (10ms x 10) = 100ms
  78.             {
  79.                 sampleADCs = 1; // Set sampleADCs bit so that ADC sampling task can fire and sample.
  80.                 _100_ms_ctr = 0; // Reset the 100ms counter.
  81.             }
  82.             _10_ms_ctr = 0; // Reset the 10ms counter.
  83.         }
  84.         TMR0 = 187; // Reload the value for 50us period.
  85.         TMR0IF = 0; // Clear the TMR0 interrupt flag.
  86.     }
  87.     if (INTF) // If we have a rising edge on RA2 (INT)
  88.     {
  89.         if (risingEdges < 255) // Ensure that risingEdges will saturate at 255 and will not overflow.
  90.             asm("INCF _risingEdges, F"); // Increment risingEdges variable. Used in readEncoder task.
  91.         INTF = 0; // Clear the INTF interrupt flag.
  92.     }
  93. }
  94.  
  95. /*
  96.  * ADC_Sampler Task:
  97.  * -----------------
  98.  * Checks the ADC channels that are defined above in order,
  99.  * scales them and puts the result in the corresponding variables.
  100.  *
  101.  * This function waits for sampleADCs bit to be set. This bit is
  102.  * set every 100ms. Check the interrupt routine above for more detail.
  103.  *
  104.  * When the sampleADCs bit is not set, the task yields the control of
  105.  * the CPU for other tasks' use.
  106.  */
  107. void ADC_Sampler(void)
  108. {
  109.     static unsigned int ADC_ResultBuffer; // This is the buffer variable to combine ADRESL and ADRESH.
  110.     OS_initializeTask(); // Initialize the task. Needed by RTOS. See RTOS header file for the details.
  111.     while (1)
  112.     {
  113.         while (!sampleADCs) // Wait for sampleADCs to be 1.
  114.         {
  115.             OS_yield(); // If sampleADCs is not 1, yield the CPU to other tasks.
  116.         }
  117.  
  118.         ADON = 1; // Turn the ADC ON
  119.  
  120.         ADCON0bits.CHS = KpValueAnalogChannel; // Select the appropriate analog channel.
  121.         __delay_us(25); // Wait for the 25us ADC aquisition time.
  122.         GO_nDONE = 1; // Start the conversion. This bit will be cleared when the conversation is done.
  123.         while (GO_nDONE) // Wait for the GO_nDONE bit to be zero.
  124.         {
  125.             OS_yield(); // If GO_nDONE is not 0, yield the CPU to other tasks.
  126.         }
  127.         ADC_ResultBuffer = (unsigned int) (ADRESH << 8) + ADRESL; // Combine 2 bit "ADRESH" and  8 bit "ADRESL" to one 16 bit register.
  128.         ADC_ResultBuffer >>= 3; // The result was 0-1023. Divide by 8 so that we get a range of 0-127.
  129.         PID_Kp = (unsigned char) (ADC_ResultBuffer); // Put the result into variable for proportional constant.
  130.  
  131.         OS_yield(); // We have used the CPU enough, yield the control to other tasks.
  132.  
  133.         ADCON0bits.CHS = KiValueAnalogChannel; // Select the appropriate analog channel.
  134.         __delay_us(25); // Wait for the 25us ADC aquisition time.
  135.         GO_nDONE = 1; // Start the conversion. This bit will be cleared when the conversation is done.
  136.         while (GO_nDONE) // Wait for the GO_nDONE bit to be zero.
  137.         {
  138.             OS_yield(); // If GO_nDONE is not 0, yield the CPU to other tasks.
  139.         }
  140.         ADC_ResultBuffer = (unsigned int) (ADRESH << 8) + ADRESL; // Combine 2 bit "ADRESH" and  8 bit "ADRESL" to one 16 bit register.
  141.         ADC_ResultBuffer >>= 5; // The result was 0-1023. Divide by 32 so that we get a range of 0-32.
  142.         PID_Ki = (unsigned char) (ADC_ResultBuffer); // Put the result into variable for integral constant.
  143.  
  144.         OS_yield(); // We have used the CPU enough, yield the control to other tasks.
  145.  
  146.         ADCON0bits.CHS = KdValueAnalogChannel; // Select the appropriate analog channel.
  147.         __delay_us(25); // Wait for the 25us ADC aquisition time.
  148.         GO_nDONE = 1; // Start the conversion. This bit will be cleared when the conversation is done.
  149.         while (GO_nDONE) // Wait for the GO_nDONE bit to be zero.
  150.         {
  151.             OS_yield(); // If GO_nDONE is not 0, yield the CPU to other tasks.
  152.         }
  153.         ADC_ResultBuffer = (unsigned int) (ADRESH << 8) + ADRESL; // Combine 2 bit "ADRESH" and  8 bit "ADRESL" to one 16 bit register.
  154.         ADC_ResultBuffer >>= 5; // The result was 0-1023. Divide by 32 so that we get a range of 0-32.
  155.         PID_Kd = (unsigned char) (ADC_ResultBuffer); // Put the result into variable for derivative constant.
  156.  
  157.         OS_yield(); // We have used the CPU enough, yield the control to other tasks.
  158.  
  159.         ADCON0bits.CHS = setValueAnalogChannel; // Select the appropriate analog channel.
  160.         __delay_us(25); // Wait for the 25us ADC aquisition time.
  161.         GO_nDONE = 1; // Start the conversion. This bit will be cleared when the conversation is done.
  162.         while (GO_nDONE) // Wait for the GO_nDONE bit to be zero.
  163.         {
  164.             OS_yield(); // If GO_nDONE is not 0, yield the CPU to other tasks.
  165.         }
  166.         ADC_ResultBuffer = (unsigned int) (ADRESH << 8) + ADRESL; // Combine 2 bit "ADRESH" and  8 bit "ADRESL" to one 16 bit register.
  167.         ADC_ResultBuffer >>= 2; // The result was 0-1023. Divide by 4 so that we get a range of 0-255.
  168.         setMotorSpeed = (unsigned char) (ADC_ResultBuffer); // Put the result into variable for derivative constant.
  169.  
  170.         ADON = 0; // Turn the ADC OFF.
  171.         sampleADCs = 0; // Do not come to this task until an another call is made.
  172.     }
  173. }
  174.  
  175. /*
  176.  * readEncoder Task:
  177.  * -----------------
  178.  * Measures the speed of the motor, that is fed into RA2(INT) from an
  179.  * encoder.
  180.  *
  181.  * It can measure from 25 Hz, up to 5 kHz.
  182.  *
  183.  * The task first waits for the calculateSpeed flag to be set. That will
  184.  * occur every 50 ms. Then, when the flag is raised, the task updates
  185.  * the motor speed with risingEdges count, and clears risingEdges register.
  186.  * Afterwards, the task raises startPID flag, letting applyPID task function.
  187.  * In the end, it clears its own control flag and waits for an another 50ms.
  188.  *
  189.  * risingEdges variable is updated by the hardware interrupt on pin
  190.  * RA2(INT). Every time a rising edge occurs, it triggers the interrupt and
  191.  * the interrupt increments risingEdges. In the end, speed will be determined
  192.  * by the amount of rising edges that happened in 50ms.
  193.  *
  194.  * This task will fire when the calculateSpeed bit is set. This happens
  195.  * every 50ms. When the calculateSpeed bit is not set, the task
  196.  * yields the control of the CPU for other tasks' use.
  197.  */
  198. void readEncoder(void)
  199. {
  200.     OS_initializeTask(); // Initialize the task. Needed by RTOS. See RTOS header file for the details.
  201.     while (1)
  202.     {
  203.         while (!calculateSpeed) // Wait for calculateSpeed to be 1.
  204.         {
  205.             OS_yield(); // If calculateSpeed is not 1, yield the CPU to other tasks.
  206.         }
  207.         currentMotorSpeed = risingEdges; // Get the value of the risingEdges.
  208.         risingEdges = 0; // Clear it, so that a fresh sample can be taken.
  209.         startPID = 1; // Set this bit so that applyPID task can now function, since we have a sample of the speed now.
  210.         calculateSpeed = 0; // Clear the bit, so that task can wait for the ISR to set this bit, after 50ms.
  211.     }
  212. }
  213.  
  214. /*
  215.  * applyEncoder Task:
  216.  * -----------------
  217.  * Calculates the PID (proportional-integral-derivative) to set the motor
  218.  * speed.
  219.  *
  220.  * PID_error = setMotorSpeed - currentMotorSpeed
  221.  * PID_sum = PID_Kp * (PID_error) + PID_Ki * ∫(PID_error) + PID_Kd * (ΔPID_error)
  222.  *
  223.  * or if the motor is speedier than it is set;
  224.  *
  225.  * PID_error = currentMotorSpeed - setMotorSpeed
  226.  * PID_sum = - PID_Kp * (PID_error) - PID_Ki * ∫(PID_error) - PID_Kd * (ΔPID_error)
  227.  *
  228.  * Maximum value of PID_sum will be about:
  229.  * 127*255 + 63*Iul + 63*255 = 65500
  230.  *
  231.  * Where Iul is Integral upper limit and is about 250.
  232.  *
  233.  * If we divide by 256, we scale that down to about 0 to 255, that is the scale
  234.  * of the PWM value.
  235.  *
  236.  * This task takes about 750us. Real figure is at the debug pin.
  237.  *
  238.  * This task will fire when the startPID bit is set. This happens when a
  239.  * sample is taken, about every 50 ms. When the startPID bit is not set,
  240.  * the task yields the control of the CPU for other tasks' use.
  241.  */
  242. void applyPID(void)
  243. {
  244.     static unsigned int PID_sum = 0; // Sum of all PID terms.
  245.     static unsigned int PID_integral = 0; // Integral for the integral term.
  246.     static unsigned char PID_derivative = 0; // PID derivative term.
  247.     static unsigned char PID_error; // Error term.
  248.     static unsigned char PID_lastError = 0; // Record of the previous error term.
  249.     static unsigned int tmp1; // Temporary register for holding miscellaneous stuff.
  250.     static unsigned int tmp2; // Temporary register for holding miscellaneous stuff.
  251.     OS_initializeTask(); // Initialize the task. Needed by RTOS. See RTOS header file for the details.
  252.     while (1)
  253.     {
  254.         while (!startPID) // Wait for startPID bit to be 1.
  255.         {
  256.             OS_yield(); // If startPID is not 1, yield the CPU to other tasks in the mean-time.
  257.         }
  258.         DebugPin = 1; // We will measure how much time it takes to implement a PID controller.
  259.  
  260.         if (currentMotorSpeed > setMotorSpeed) // If the motor is speedier than it is set,
  261.         {
  262.             // PID error is the difference between set value and current value.
  263.             PID_error = (unsigned char) (currentMotorSpeed - setMotorSpeed);
  264.  
  265.             // Integrate errors by subtracting them from the PID_integral variable.
  266.             if (PID_error < PID_integral) // If the subtraction will not underflow,
  267.                 PID_integral -= PID_error; // Subtract the error from the current error integration.
  268.             else
  269.                 PID_integral = 0; // If the subtraction will underflow, then set it to zero.
  270.             // Integral term is: Ki * ∫error
  271.             tmp1 = PID_Ki * PID_integral;
  272.             // Check if PID_sum will overflow in the addition of integral term.
  273.             tmp2 = 0xFFFF - tmp1;
  274.             if (PID_sum < tmp2)
  275.                 PID_sum += tmp1; // If it will not overflow, then add it.
  276.             else
  277.                 PID_sum = 0xFFFF; // If it will, then saturate it.
  278.  
  279.             if (PID_error >= PID_lastError) // If current error is bigger than last error,
  280.                 PID_derivative = (unsigned char) (PID_error - PID_lastError);
  281.                 // then calculate the derivative by subtracting them.
  282.             else
  283.                 PID_derivative = (unsigned char) (PID_lastError - PID_error);
  284.             // Derivative term is : Kd * d(Δerror)
  285.             tmp1 = PID_Kd * PID_derivative;
  286.             // Check if PID_sum will overflow in the addition of derivative term.
  287.             if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
  288.                 PID_sum -= tmp1;
  289.             else PID_sum = 0; // If the subtraction will underflow, then set it to zero.
  290.  
  291.             // Proportional term is: Kp * error
  292.             tmp1 = PID_Kp * PID_error; // Calculate the proportional term.
  293.             if (tmp1 < PID_sum) // Check if subtraction will underflow PID_sum
  294.                 PID_sum -= tmp1;
  295.             else PID_sum = 0; // If the subtraction will underflow, then set it to zero.
  296.         }
  297.         else // If the motor is slower than it is set,
  298.         {
  299.             PID_error = (unsigned char) (setMotorSpeed - currentMotorSpeed);
  300.             // Proportional term is: Kp * error
  301.             PID_sum = PID_Kp * PID_error;
  302.  
  303.             PID_integral += PID_error; // Add the error to the integral term.
  304.             if (PID_integral > PID_integralUpperLimit) // If we have reached the upper limit of the integral,
  305.                 PID_integral = PID_integralUpperLimit; // then limit it there.
  306.             // Integral term is: Ki * ∫error
  307.             tmp1 = PID_Ki * PID_integral;
  308.             // Check if PID_sum will overflow in the addition of integral term.
  309.             tmp2 = 0xFFFF - tmp1;
  310.             if (PID_sum < tmp2)
  311.                 PID_sum += tmp1; // If it will not overflow, then add it.
  312.             else
  313.                 PID_sum = 0xFFFF; // If it will, then saturate it.
  314.  
  315.             if (PID_error >= PID_lastError) // If current error is bigger than last error,
  316.                 PID_derivative = (unsigned char) (PID_error - PID_lastError);
  317.                 // then calculate the derivative by subtracting them.
  318.             else
  319.                 PID_derivative = (unsigned char) (PID_lastError - PID_error);
  320.             // Derivative term is : Kd * d(Δerror)
  321.             tmp1 = PID_Kd * PID_derivative;
  322.             // Check if PID_sum will overflow in the addition of derivative term.
  323.             tmp2 = 0xFFFF - tmp1;
  324.             if (PID_sum < tmp2)
  325.                 PID_sum += tmp1; // If it will not overflow, then add it.
  326.             else
  327.                 PID_sum = 0xFFFF; // If it will, then saturate it.
  328.         }
  329.  
  330.         // Scale the sum to 0 - 255 from 0 - 65535 , dividing by 256, or right shifting 8.
  331.         PID_sum >>= 8;
  332.  
  333.         // Set the duty cycle to the calculated and scaled PID_sum.
  334.         PWM_dutyCycle = (unsigned char) PID_sum;
  335.         PID_lastError = PID_error; // Make the current error the last error, since it is old now.
  336.  
  337.         startPID = 0; // Clear the flag. That will let this task wait for the flag.
  338.         DebugPin = 0; // We are finished with the PID control block.
  339.     }
  340. }
  341.  
  342. void main(void)
  343. {
  344.     TRISA = 0x2F; // RA0: Input / RA1: Input / RA2: Input / RA3: Input / RA4: Output / RA5: Input.
  345.     TRISC = 0x1F; // RC0: Input / RC1: Input / RC2: Input / RC3: Input / RC4: Input  / RC5: Output.
  346.  
  347.     ANSEL = 0x33; // AN0(RA0), AN1(RA1), AN4(RC0) and AN5(RC1) pins are analog inputs.
  348.     ADFM = 1; // ADFM: A/D Conversion Result Format Select bit: 1 = Right justified, 0 = Left justified
  349.     ADCON1bits.ADCS = 0x05; // ADCS<2:0>: A/D Conversion Clock Select bits : 101 = FOSC/16, 2us conversion time
  350.  
  351.     PSA = 1; // Prescaler Assignment bit : 1 = Prescaler is assigned to the WDT, 0 = Prescaler is assigned to the Timer0 module
  352.     OPTION_REGbits.PS = 0x07; // WDT Prescaler= 1 : 128 . Timeout period: ~2.3 seconds.
  353.     T0CS = 0; // T0CS: TMR0 Clock Source Select bit : 0 = Internal instruction cycle clock (FOSC/4), 1 = Transition on T0CKI pin
  354.  
  355.     T2CON = 0x04; /* bit 7                 Unimplemented: Read as ‘0’
  356.                   *  bit 6-3  TOUTPS<3:0>: Timer2 Output Postscaler Select bits
  357.                   *           x0000xxx     0000 = 1:1 Postscaler
  358.                   *  bit 2    TMR2ON:      Timer2 On bit
  359.                   *           xxxxx1xx     1 = Timer2 is on
  360.                   *  bit 1-0  T2CKPS<1:0>: Timer2 Clock Prescale Select bits
  361.                   *           xxxxxx00     00 = Prescaler is 1           */
  362.     CCP1CON = 0x0C; /* bit 7-6  P1M<1:0>:   PWM Output Configuration bits
  363.                                 00xxxxxx    00 = Single output; P1A modulated; P1B, P1C, P1D assigned as Port pins
  364.                      * bit 5-4  DC1B<1:0>:  PWM Duty Cycle Least Significant bits
  365.                      *          xx00xxxx    These bits are the two LSbs of the PWM duty cycle. The eight MSbs are found in CCPR1L.
  366.                      * bit 3-0  CCP1M<3:0>: ECCP Mode Select bits
  367.                      *          xxxx1100    PWM mode; P1A, P1C active-high; P1B, P1D active-high          */
  368.     PR2 = 150; // Timer2 Module Period Register is 100, yielding to a PWM period of : (Tosc*4) * 100 = 75usec
  369.     CCPR1L = 100; // This is the PWM duty-cycle setter. 0 to 150.
  370.  
  371.     OPTION_REGbits.INTEDG = 1; // INTEDG: Interrupt Edge Select bit. 1 = Interrupt on rising edge of RA2/INT pin.
  372.     T0IE = 1; //T0IE: Timer0 Overflow Interrupt Enable bit
  373.     GIE = 1; // GIE: Global Interrupt Enable bit
  374.  
  375.     sampleADCs = 0; // Clear the bits in the initialization.
  376.     calculateSpeed = 0;
  377.  
  378.     OS_currentTask = 0; // Current task pointer points to the first task.
  379.     ADC_Sampler(); // Run all the tasks to initialize them one by one.
  380.     OS_currentTask += 2; // Increment task pointer by two since every task occupies 2 places in the array.
  381.     readEncoder();
  382.     OS_currentTask += 2;
  383.     applyPID();
  384.     OS_runTasks(6); // Run the tasks in order. The argument of this macro takes is: (Number of tasks) * 2
  385. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement